From d71c4babb39e20c5477b762bc33b83698b6dc1f6 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 20:46:33 +0530 Subject: [PATCH 01/19] create app --- .cursor/rules/APPWRITE.mdc | 1173 ++++++++++++ .gitignore | 23 + .npmrc | 1 + .prettierignore | 9 + .prettierrc | 17 + LICENSE | 21 - README.md | 38 + eslint.config.js | 40 + package.json | 37 + pnpm-lock.yaml | 2054 +++++++++++++++++++++ pnpm-workspace.yaml | 2 + src/app.d.ts | 13 + src/app.html | 12 + src/lib/appwrite.js | 14 + src/lib/assets/favicon.svg | 1 + src/lib/languages/common/install.js | 148 ++ src/lib/languages/common/mcp.js | 55 + src/lib/languages/common/products.js | 119 ++ src/lib/languages/common/security.js | 62 + src/lib/languages/common/utils.js | 92 + src/lib/languages/dart/index.js | 26 + src/lib/languages/dart/server.js | 15 + src/lib/languages/dotnet/index.js | 3 + src/lib/languages/dotnet/server.js | 9 + src/lib/languages/dotnet/vanilla.js | 8 + src/lib/languages/go/index.js | 2 + src/lib/languages/go/server.js | 9 + src/lib/languages/index.js | 11 + src/lib/languages/js/angular.js | 9 + src/lib/languages/js/astro.js | 12 + src/lib/languages/js/index.js | 13 + src/lib/languages/js/nextjs.js | 10 + src/lib/languages/js/nodejs.js | 10 + src/lib/languages/js/nuxt.js | 13 + src/lib/languages/js/qwik.js | 10 + src/lib/languages/js/react.js | 9 + src/lib/languages/js/solid.js | 10 + src/lib/languages/js/svelte.js | 10 + src/lib/languages/js/tanstack.js | 17 + src/lib/languages/js/vanilla.js | 9 + src/lib/languages/js/vue.js | 9 + src/lib/languages/kotlin/index.js | 46 + src/lib/languages/php/index.js | 2 + src/lib/languages/php/server.js | 9 + src/lib/languages/python/flask.js | 8 + src/lib/languages/python/index.js | 3 + src/lib/languages/python/server.js | 9 + src/lib/languages/react-native/index.js | 2 + src/lib/languages/react-native/vanilla.js | 11 + src/lib/languages/ruby/index.js | 2 + src/lib/languages/ruby/server.js | 9 + src/lib/languages/swift/index.js | 36 + src/lib/rules-generator.js | 326 ++++ src/lib/utils/versions.js | 61 + src/routes/+layout.svelte | 13 + src/routes/+page.svelte | 453 +++++ static/robots.txt | 3 + svelte.config.js | 18 + tsconfig.json | 20 + vite.config.ts | 6 + 60 files changed, 5171 insertions(+), 21 deletions(-) create mode 100644 .cursor/rules/APPWRITE.mdc create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc delete mode 100644 LICENSE create mode 100644 README.md create mode 100644 eslint.config.js create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 src/app.d.ts create mode 100644 src/app.html create mode 100644 src/lib/appwrite.js create mode 100644 src/lib/assets/favicon.svg create mode 100644 src/lib/languages/common/install.js create mode 100644 src/lib/languages/common/mcp.js create mode 100644 src/lib/languages/common/products.js create mode 100644 src/lib/languages/common/security.js create mode 100644 src/lib/languages/common/utils.js create mode 100644 src/lib/languages/dart/index.js create mode 100644 src/lib/languages/dart/server.js create mode 100644 src/lib/languages/dotnet/index.js create mode 100644 src/lib/languages/dotnet/server.js create mode 100644 src/lib/languages/dotnet/vanilla.js create mode 100644 src/lib/languages/go/index.js create mode 100644 src/lib/languages/go/server.js create mode 100644 src/lib/languages/index.js create mode 100644 src/lib/languages/js/angular.js create mode 100644 src/lib/languages/js/astro.js create mode 100644 src/lib/languages/js/index.js create mode 100644 src/lib/languages/js/nextjs.js create mode 100644 src/lib/languages/js/nodejs.js create mode 100644 src/lib/languages/js/nuxt.js create mode 100644 src/lib/languages/js/qwik.js create mode 100644 src/lib/languages/js/react.js create mode 100644 src/lib/languages/js/solid.js create mode 100644 src/lib/languages/js/svelte.js create mode 100644 src/lib/languages/js/tanstack.js create mode 100644 src/lib/languages/js/vanilla.js create mode 100644 src/lib/languages/js/vue.js create mode 100644 src/lib/languages/kotlin/index.js create mode 100644 src/lib/languages/php/index.js create mode 100644 src/lib/languages/php/server.js create mode 100644 src/lib/languages/python/flask.js create mode 100644 src/lib/languages/python/index.js create mode 100644 src/lib/languages/python/server.js create mode 100644 src/lib/languages/react-native/index.js create mode 100644 src/lib/languages/react-native/vanilla.js create mode 100644 src/lib/languages/ruby/index.js create mode 100644 src/lib/languages/ruby/server.js create mode 100644 src/lib/languages/swift/index.js create mode 100644 src/lib/rules-generator.js create mode 100644 src/lib/utils/versions.js create mode 100644 src/routes/+layout.svelte create mode 100644 src/routes/+page.svelte create mode 100644 static/robots.txt create mode 100644 svelte.config.js create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.cursor/rules/APPWRITE.mdc b/.cursor/rules/APPWRITE.mdc new file mode 100644 index 0000000..acdc8d2 --- /dev/null +++ b/.cursor/rules/APPWRITE.mdc @@ -0,0 +1,1173 @@ +--- +description: You are an expert developer focused on building apps with Appwrite's APIs and SDKs. +alwaysApply: false +--- + +# Appwrite Development Rules + +## SDK Initialization + +### Client-Side (Web/React/Next.js) + +Always initialize the Appwrite client with proper configuration: + +```javascript +import { Client, Account, TablesDB, Storage } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') // Your Appwrite endpoint + .setProject('your-project-id'); // Your project ID + +// Initialize services +export const account = new Account(client); +export const tablesDB = new TablesDB(client); +export const storage = new Storage(client); +``` + +**Best Practices:** +- Store endpoint and project ID in environment variables (`.env.local`) +- Never commit API keys or secrets to version control +- Use separate clients for different environments (dev/staging/prod) +- Initialize services once and export them as singletons + +### Server-Side (Node.js/Next.js API Routes) + +Use server SDK with API key for admin operations: + +```javascript +import { Client, TablesDB, Storage, ID } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject(process.env.APPWRITE_PROJECT_ID) + .setKey(process.env.APPWRITE_API_KEY); // Server-side only + +export const tablesDB = new TablesDB(client); +export const storage = new Storage(client); +``` + +**Security:** +- API keys should NEVER be exposed to client-side code +- Use environment variables for all sensitive configuration +- API keys grant admin access - use with extreme caution + +## Authentication Patterns + +### Session Management + +Always check authentication state before making authenticated requests: + +```javascript +import { account } from './appwrite-config'; + +// Check current session +async function getCurrentUser() { + try { + return await account.get(); + } catch (error) { + // User not authenticated + return null; + } +} + +// Create session +async function login(email, password) { + try { + await account.createEmailPasswordSession(email, password); + return await account.get(); + } catch (error) { + throw new Error(`Login failed: ${error.message}`); + } +} + +// Delete session +async function logout() { + try { + await account.deleteSession('current'); + } catch (error) { + console.error('Logout error:', error); + } +} +``` + +### OAuth Providers + +When implementing OAuth, handle redirects properly: + +```javascript +// Initiate OAuth +async function loginWithOAuth(provider) { + account.createOAuth2Session( + provider, // 'google', 'github', etc. + 'https://yourapp.com/success', // Success redirect + 'https://yourapp.com/failure' // Failure redirect + ); +} + +// Handle OAuth callback (check URL params) +if (window.location.search.includes('success')) { + const user = await account.get(); + // Redirect to dashboard +} +``` + +### SSR Authentication (Server-Side Rendering) + +For SSR frameworks like Next.js, Remix, or SvelteKit, handle authentication on the server using cookies. + +#### Server-Side Client Setup + +Create a server-side client that reads cookies from requests: + +```javascript +// lib/appwrite-server.js +import { Client, Account, TablesDB } from 'appwrite'; +import { cookies } from 'next/headers'; // Next.js App Router +// or: import { parseCookies } from 'nookies'; // Next.js Pages Router + +export function createServerClient() { + const client = new Client() + .setEndpoint(process.env.APPWRITE_ENDPOINT) + .setProject(process.env.APPWRITE_PROJECT_ID); + + return client; +} + +export async function getServerAccount() { + const client = createServerClient(); + const account = new Account(client); + + // Get session cookies from request + const cookieStore = await cookies(); // Next.js App Router + const sessionCookie = cookieStore.get('a_session_'); + + if (sessionCookie) { + // Set session cookie on client + client.setSession(sessionCookie.value); + } + + return account; +} + +// Alternative for Pages Router +export function getServerAccountPages(req) { + const client = createServerClient(); + const account = new Account(client); + + const cookieStore = parseCookies({ req }); + const sessionCookie = cookieStore['a_session_']; + + if (sessionCookie) { + client.setSession(sessionCookie); + } + + return account; +} +``` + +#### Next.js App Router - Server Components + +Authenticate in Server Components and pass user data to client: + +```javascript +// app/dashboard/page.js (Server Component) +import { getServerAccount } from '@/lib/appwrite-server'; +import { redirect } from 'next/navigation'; +import DashboardClient from './dashboard-client'; + +export default async function DashboardPage() { + const account = await getServerAccount(); + + try { + const user = await account.get(); + + // User is authenticated, render dashboard + return ; + } catch (error) { + // User not authenticated, redirect to login + redirect('/login'); + } +} +``` + +#### Next.js App Router - Server Actions + +Handle authentication in Server Actions: + +```javascript +// app/actions/auth.js +'use server'; + +import { getServerAccount } from '@/lib/appwrite-server'; +import { cookies } from 'next/headers'; +import { redirect } from 'next/navigation'; + +export async function loginAction(email, password) { + const account = await getServerAccount(); + + try { + const session = await account.createEmailPasswordSession(email, password); + + // Set session cookie + const cookieStore = await cookies(); + cookieStore.set('a_session_', session.secret, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + path: '/', + }); + + redirect('/dashboard'); + } catch (error) { + return { error: error.message }; + } +} + +export async function logoutAction() { + const account = await getServerAccount(); + + try { + await account.deleteSession('current'); + + // Clear session cookie + const cookieStore = await cookies(); + cookieStore.delete('a_session_'); + + redirect('/login'); + } catch (error) { + return { error: error.message }; + } +} +``` + +#### Next.js Pages Router - getServerSideProps + +Authenticate in `getServerSideProps`: + +```javascript +// pages/dashboard.js +import { getServerAccountPages } from '@/lib/appwrite-server'; + +export async function getServerSideProps({ req, res }) { + const account = getServerAccountPages(req); + + try { + const user = await account.get(); + + return { + props: { + user: { + $id: user.$id, + email: user.email, + name: user.name, + }, + }, + }; + } catch (error) { + // Redirect to login if not authenticated + return { + redirect: { + destination: '/login', + permanent: false, + }, + }; + } +} + +export default function Dashboard({ user }) { + return ( +
+

Welcome, {user.name}

+

Email: {user.email}

+
+ ); +} +``` + +#### Next.js Middleware - Route Protection + +Protect routes using Next.js middleware: + +```javascript +// middleware.js (Next.js 12+) +import { NextResponse } from 'next/server'; +import { getServerAccount } from '@/lib/appwrite-server'; +import { cookies } from 'next/headers'; + +export async function middleware(request) { + const protectedPaths = ['/dashboard', '/profile', '/settings']; + const isProtectedPath = protectedPaths.some(path => + request.nextUrl.pathname.startsWith(path) + ); + + if (!isProtectedPath) { + return NextResponse.next(); + } + + try { + const account = await getServerAccount(); + await account.get(); + + // User is authenticated, allow request + return NextResponse.next(); + } catch (error) { + // User not authenticated, redirect to login + const loginUrl = new URL('/login', request.url); + loginUrl.searchParams.set('redirect', request.nextUrl.pathname); + return NextResponse.redirect(loginUrl); + } +} + +export const config = { + matcher: ['/dashboard/:path*', '/profile/:path*', '/settings/:path*'], +}; +``` + +#### Server-Side API Routes with Authentication + +Authenticate in API routes: + +```javascript +// app/api/protected/route.js (App Router) +import { getServerAccount } from '@/lib/appwrite-server'; +import { NextResponse } from 'next/server'; + +export async function GET(request) { + try { + const account = await getServerAccount(); + const user = await account.get(); + + // User is authenticated, proceed with request + const data = await fetchProtectedData(user.$id); + + return NextResponse.json({ data }); + } catch (error) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } +} + +// pages/api/protected.js (Pages Router) +import { getServerAccountPages } from '@/lib/appwrite-server'; + +export default async function handler(req, res) { + try { + const account = getServerAccountPages(req); + const user = await account.get(); + + // User is authenticated, proceed with request + const data = await fetchProtectedData(user.$id); + + res.json({ data }); + } catch (error) { + res.status(401).json({ error: 'Unauthorized' }); + } +} +``` + +#### Cookie Management Best Practices + +1. **HttpOnly cookies** - Always set `httpOnly: true` to prevent XSS attacks +2. **Secure flag** - Use `secure: true` in production (HTTPS only) +3. **SameSite** - Use `sameSite: 'lax'` or `'strict'` for CSRF protection +4. **Cookie name** - Appwrite uses `a_session_` format +5. **Session validation** - Always validate session on server before trusting it + +#### SSR Authentication Helper + +Create a reusable authentication helper: + +```javascript +// lib/auth-server.js +import { getServerAccount } from './appwrite-server'; +import { redirect } from 'next/navigation'; + +export async function requireAuth() { + const account = await getServerAccount(); + + try { + const user = await account.get(); + return { user, account }; + } catch (error) { + redirect('/login'); + } +} + +export async function optionalAuth() { + const account = await getServerAccount(); + + try { + const user = await account.get(); + return { user, account, isAuthenticated: true }; + } catch (error) { + return { user: null, account: null, isAuthenticated: false }; + } +} + +// Usage in Server Components +export default async function ProtectedPage() { + const { user, account } = await requireAuth(); + + // User is guaranteed to be authenticated here + return
Welcome, {user.name}
; +} +``` + +## Teams Management + +Appwrite Teams enable collaborative features and team-based permissions. Always initialize the Teams service alongside other services. + +### SDK Initialization + +Include Teams in your client setup: + +```javascript +import { Client, Account, Teams, TablesDB, Storage } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('your-project-id'); + +export const account = new Account(client); +export const teams = new Teams(client); +export const tablesDB = new TablesDB(client); +export const storage = new Storage(client); +``` + +### Creating Teams + +Create teams with proper error handling: + +```javascript +import { ID } from 'appwrite'; + +// Create a new team +async function createTeam(name) { + try { + return await teams.create(ID.unique(), name); + } catch (error) { + throw new Error(`Failed to create team: ${error.message}`); + } +} + +// Create team with custom ID +async function createTeamWithId(teamId, name) { + try { + return await teams.create(teamId, name); + } catch (error) { + if (error.code === 409) { + throw new Error('Team ID already exists'); + } + throw new Error(`Failed to create team: ${error.message}`); + } +} +``` + +### Team Membership Operations + +Manage team members with proper role assignment: + +```javascript +// Add member to team +async function addTeamMember(teamId, email, roles = ['member']) { + try { + // First, get user ID by email (requires server-side with API key) + // Or pass userId directly if you have it + return await teams.createMembership(teamId, email, roles); + } catch (error) { + if (error.code === 409) { + throw new Error('User is already a member of this team'); + } + throw new Error(`Failed to add member: ${error.message}`); + } +} + +// Add member by user ID (server-side only) +async function addTeamMemberById(teamId, userId, roles = ['member']) { + try { + return await teams.createMembership(teamId, undefined, roles, undefined, userId); + } catch (error) { + throw new Error(`Failed to add member: ${error.message}`); + } +} + +// Update member roles +async function updateMemberRoles(teamId, membershipId, roles) { + try { + return await teams.updateMembershipRoles(teamId, membershipId, roles); + } catch (error) { + throw new Error(`Failed to update roles: ${error.message}`); + } +} + +// Remove member from team +async function removeTeamMember(teamId, membershipId) { + try { + await teams.deleteMembership(teamId, membershipId); + } catch (error) { + throw new Error(`Failed to remove member: ${error.message}`); + } +} +``` + +### Listing Teams and Members + +Query teams and members efficiently: + +```javascript +// List user's teams +async function getUserTeams() { + try { + const response = await teams.list(); + return response.teams; + } catch (error) { + throw new Error(`Failed to list teams: ${error.message}`); + } +} + +// Get team details +async function getTeam(teamId) { + try { + return await teams.get(teamId); + } catch (error) { + if (error.code === 404) { + return null; + } + throw new Error(`Failed to get team: ${error.message}`); + } +} + +// List team members +async function getTeamMembers(teamId) { + try { + const response = await teams.listMemberships(teamId); + return response.memberships; + } catch (error) { + throw new Error(`Failed to list members: ${error.message}`); + } +} + +// Get specific membership +async function getMembership(teamId, membershipId) { + try { + return await teams.getMembership(teamId, membershipId); + } catch (error) { + if (error.code === 404) { + return null; + } + throw new Error(`Failed to get membership: ${error.message}`); + } +} +``` + +### Team Roles and Permissions + +Use team roles in database and storage permissions: + +```javascript +import { Permission, Role } from 'appwrite'; + +// Team-based permissions for rows +async function createTeamRow(teamId, data) { + try { + return await tablesDB.createRow( + 'database-id', + 'table-id', + ID.unique(), + data, + [ + // All team members can read + Permission.read(Role.team(teamId)), + // Only team admins can write + Permission.write(Role.team(teamId, 'admin')), + // Only team owners can delete + Permission.delete(Role.team(teamId, 'owner')) + ] + ); + } catch (error) { + throw new Error(`Failed to create row: ${error.message}`); + } +} + +// Team-based permissions for files +async function uploadTeamFile(teamId, file, bucketId) { + try { + return await storage.createFile( + bucketId, + ID.unique(), + file, + [ + Permission.read(Role.team(teamId)), + Permission.write(Role.team(teamId, 'admin')), + Permission.delete(Role.team(teamId, 'owner')) + ] + ); + } catch (error) { + throw new Error(`Failed to upload file: ${error.message}`); + } +} + +// Check if user has specific role in team +async function hasTeamRole(teamId, role) { + try { + const membership = await teams.getMembership(teamId, 'me'); // Get current user's membership + return membership.roles.includes(role); + } catch (error) { + return false; + } +} +``` + +### Team Management Operations + +Update and manage team settings: + +```javascript +// Update team name +async function updateTeamName(teamId, name) { + try { + return await teams.updateName(teamId, name); + } catch (error) { + throw new Error(`Failed to update team name: ${error.message}`); + } +} + +// Delete team +async function deleteTeam(teamId) { + try { + await teams.delete(teamId); + } catch (error) { + throw new Error(`Failed to delete team: ${error.message}`); + } +} + +// Accept team invitation +async function acceptTeamInvitation(teamId, membershipId, userId, secret) { + try { + return await teams.updateMembershipStatus( + teamId, + membershipId, + userId, + secret + ); + } catch (error) { + throw new Error(`Failed to accept invitation: ${error.message}`); + } +} +``` + +### React Hook for Teams + +Create a reusable hook for team management: + +```javascript +import { useState, useEffect } from 'react'; +import { ID } from 'appwrite'; +import { teams } from './appwrite-config'; + +function useTeams() { + const [teamsList, setTeamsList] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + async function fetchTeams() { + try { + const response = await teams.list(); + setTeamsList(response.teams); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + } + + fetchTeams(); + }, []); + + const createTeam = async (name) => { + try { + const newTeam = await teams.create(ID.unique(), name); + setTeamsList([...teamsList, newTeam]); + return newTeam; + } catch (err) { + setError(err.message); + throw err; + } + }; + + return { teams: teamsList, loading, error, createTeam }; +} +``` + +### Server-Side Team Operations + +Handle team operations on the server with API key: + +```javascript +// lib/appwrite-server.js +import { Client, Teams } from 'appwrite'; + +const serverClient = new Client() + .setEndpoint(process.env.APPWRITE_ENDPOINT) + .setProject(process.env.APPWRITE_PROJECT_ID) + .setKey(process.env.APPWRITE_API_KEY); + +export const serverTeams = new Teams(serverClient); + +// Add user to team (server-side) +export async function addUserToTeam(teamId, userId, roles = ['member']) { + try { + return await serverTeams.createMembership( + teamId, + undefined, // email not needed when userId provided + roles, + undefined, // URL not needed for direct add + userId + ); + } catch (error) { + throw new Error(`Failed to add user to team: ${error.message}`); + } +} + +// List all teams (admin operation) +export async function listAllTeams() { + try { + const response = await serverTeams.list(); + return response.teams; + } catch (error) { + throw new Error(`Failed to list teams: ${error.message}`); + } +} +``` + +### Team-Based Access Control + +Implement team-based access control patterns: + +```javascript +import { Query } from 'appwrite'; + +// Check if user can access team resource +async function canAccessTeamResource(teamId, requiredRole = 'member') { + try { + const team = await teams.get(teamId); + const memberships = await teams.listMemberships(teamId); + + // Get current user + const user = await account.get(); + + // Find user's membership + const membership = memberships.find(m => m.userId === user.$id); + + if (!membership) { + return false; + } + + // Check role hierarchy: owner > admin > member + const roleHierarchy = { owner: 3, admin: 2, member: 1 }; + const requiredLevel = roleHierarchy[requiredRole] || 1; + const userLevel = Math.max(...membership.roles.map(r => roleHierarchy[r] || 0)); + + return userLevel >= requiredLevel; + } catch (error) { + return false; + } +} + +// Filter rows by team membership +async function getTeamRows(teamId) { + try { + // First verify user is team member + const canAccess = await canAccessTeamResource(teamId); + if (!canAccess) { + throw new Error('Access denied'); + } + + // Query rows with team permission + return await tablesDB.listRows( + 'database-id', + 'table-id', + [Query.equal('teamId', teamId)] + ); + } catch (error) { + throw new Error(`Failed to get team rows: ${error.message}`); + } +} +``` + +### Team Best Practices + +1. **Role Management** - Use consistent role names: 'owner', 'admin', 'member' +2. **Invitations** - Always handle invitation acceptance flow properly +3. **Permissions** - Combine team roles with specific permissions (e.g., `Role.team(teamId, 'admin')`) +4. **Validation** - Always verify team membership before granting access +5. **Error Handling** - Handle 404 (team not found) and 403 (access denied) appropriately +6. **Team Limits** - Be aware of team size limits and implement pagination for large teams +7. **Cleanup** - Remove team memberships when users are deleted + +## Database Operations + +### Query Patterns + +Use Appwrite's query builder for efficient queries: + +```javascript +import { Query } from 'appwrite'; + +// Single query +const rows = await tablesDB.listRows( + 'database-id', + 'table-id', + [Query.equal('status', 'active')] +); + +// Multiple conditions +const filtered = await tablesDB.listRows( + 'database-id', + 'table-id', + [ + Query.equal('status', 'active'), + Query.greaterThan('createdAt', '2024-01-01'), + Query.limit(10), + Query.orderDesc('createdAt') + ] +); + +// Full-text search +const searchResults = await tablesDB.listRows( + 'database-id', + 'table-id', + [Query.search('title', 'search term')] +); +``` + +### CRUD Operations + +Follow consistent patterns for create, read, update, delete: + +```javascript +import { ID } from 'appwrite'; + +// Create row +async function createRow(data) { + try { + return await tablesDB.createRow( + 'database-id', + 'table-id', + ID.unique(), + data, + [ + Permission.read(Role.user(userId)), // User can read + Permission.write(Role.user(userId)) // User can write + ] + ); + } catch (error) { + throw new Error(`Failed to create: ${error.message}`); + } +} + +// Read row +async function getRow(rowId) { + try { + return await tablesDB.getRow( + 'database-id', + 'table-id', + rowId + ); + } catch (error) { + if (error.code === 404) { + return null; + } + throw error; + } +} + +// Update row +async function updateRow(rowId, data) { + try { + return await tablesDB.updateRow( + 'database-id', + 'table-id', + rowId, + data + ); + } catch (error) { + throw new Error(`Failed to update: ${error.message}`); + } +} + +// Delete row +async function deleteRow(rowId) { + try { + await tablesDB.deleteRow( + 'database-id', + 'table-id', + rowId + ); + } catch (error) { + throw new Error(`Failed to delete: ${error.message}`); + } +} +``` + +### Permissions + +Always set appropriate permissions when creating/updating rows: + +```javascript +import { Permission, Role } from 'appwrite'; + +// User-specific row +const permissions = [ + Permission.read(Role.user(userId)), + Permission.write(Role.user(userId)), + Permission.delete(Role.user(userId)) +]; + +// Public read, authenticated write +const publicPermissions = [ + Permission.read(Role.any()), + Permission.write(Role.users()), +]; + +// Team-based permissions +const teamPermissions = [ + Permission.read(Role.team(teamId)), + Permission.write(Role.team(teamId, 'admin')) +]; +``` + +## Storage Operations + +### File Upload + +Handle file uploads with progress tracking: + +```javascript +import { ID } from 'appwrite'; + +async function uploadFile(file, bucketId) { + try { + return await storage.createFile( + bucketId, + ID.unique(), + file, + [ + Permission.read(Role.any()), // Adjust based on needs + Permission.write(Role.users()) + ], + (progress) => { + console.log(`Upload progress: ${progress.progress}%`); + } + ); + } catch (error) { + throw new Error(`Upload failed: ${error.message}`); + } +} + +// Get file preview/URL +function getFileUrl(bucketId, fileId) { + return storage.getFilePreview(bucketId, fileId); +} + +// Get file download +function getFileDownload(bucketId, fileId) { + return storage.getFileDownload(bucketId, fileId); +} +``` + +### File Management + +```javascript +// List files +async function listFiles(bucketId, queries = []) { + return await storage.listFiles(bucketId, queries); +} + +// Delete file +async function deleteFile(bucketId, fileId) { + await storage.deleteFile(bucketId, fileId); +} + +// Update file +async function updateFile(bucketId, fileId, name) { + return await storage.updateFile(bucketId, fileId, name); +} +``` + +## Error Handling + +Always implement proper error handling: + +```javascript +async function safeAppwriteCall(operation) { + try { + return await operation(); + } catch (error) { + // Handle specific error codes + switch (error.code) { + case 401: + // Unauthorized - redirect to login + window.location.href = '/login'; + break; + case 404: + // Not found + return null; + case 409: + // Conflict (e.g., duplicate email) + throw new Error('Resource already exists'); + case 429: + // Rate limited + throw new Error('Too many requests. Please try again later.'); + default: + console.error('Appwrite error:', error); + throw new Error(`Operation failed: ${error.message}`); + } + } +} + +// Usage +const user = await safeAppwriteCall(() => account.get()); +``` + +## Real-time Subscriptions + +Use real-time listeners for live updates: + +```javascript +import { RealtimeResponseEvent } from 'appwrite'; + +function subscribeToTable(databaseId, tableId, callback) { + return client.subscribe( + `databases.${databaseId}.tables.${tableId}.rows`, + (response) => { + if (response.events.includes('databases.*.tables.*.rows.*.create')) { + callback('create', response.payload); + } else if (response.events.includes('databases.*.tables.*.rows.*.update')) { + callback('update', response.payload); + } else if (response.events.includes('databases.*.tables.*.rows.*.delete')) { + callback('delete', response.payload); + } + } + ); +} + +// Usage +const unsubscribe = subscribeToTable( + 'database-id', + 'table-id', + (event, payload) => { + console.log(`${event} event:`, payload); + } +); + +// Cleanup +// unsubscribe(); +``` + +## Server-Side Functions + +When using Appwrite Functions, follow these patterns: + +```javascript +// Function handler example (Node.js) +export default async ({ req, res, log, error }) => { + try { + const { databaseId, tableId } = JSON.parse(req.body); + + // Use server SDK + const tablesDB = new TablesDB(client); + const rows = await tablesDB.listRows(databaseId, tableId); + + return res.json({ success: true, data: rows }); + } catch (err) { + error(err.message); + return res.json({ success: false, error: err.message }, 500); + } +}; +``` + +## Security Best Practices + +1. **Never expose API keys** - Use environment variables +2. **Validate permissions** - Always set appropriate row/file permissions +3. **Sanitize inputs** - Validate and sanitize user inputs before database operations +4. **Use HTTPS** - Always use HTTPS endpoints in production +5. **Rate limiting** - Implement client-side rate limiting for user-facing operations +6. **Session management** - Implement proper session expiration and refresh logic + +## Performance Optimization + +1. **Pagination** - Always use `Query.limit()` and `Query.offset()` for large datasets +2. **Indexing** - Create database indexes for frequently queried fields +3. **Caching** - Cache frequently accessed data on the client side +4. **Batch operations** - Use batch requests when possible +5. **Selective fields** - Use `Query.select()` to fetch only needed fields + +## Common Patterns + +### React Hook Example + +```javascript +import { useState, useEffect } from 'react'; +import { account } from './appwrite-config'; + +function useAuth() { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + account.get() + .then(setUser) + .catch(() => setUser(null)) + .finally(() => setLoading(false)); + }, []); + + return { user, loading }; +} +``` + +### Next.js API Route Example + +```javascript +// pages/api/data.js or app/api/data/route.js +import { tablesDB } from '@/lib/appwrite-server'; + +export default async function handler(req, res) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const rows = await tablesDB.listRows( + process.env.APPWRITE_DATABASE_ID, + process.env.APPWRITE_TABLE_ID + ); + res.json(rows); + } catch (error) { + res.status(500).json({ error: error.message }); + } +} +``` + +## Testing Patterns + +When writing tests, mock Appwrite SDK: + +```javascript +// Mock Appwrite in tests +jest.mock('appwrite', () => ({ + Client: jest.fn(), + Account: jest.fn(() => ({ + get: jest.fn(), + createEmailPasswordSession: jest.fn(), + })), + TablesDB: jest.fn(() => ({ + listRows: jest.fn(), + createRow: jest.fn(), + })), +})); +``` diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..7d74fe2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Miscellaneous +/static/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b513262 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,17 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": [ + "prettier-plugin-svelte" + ], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 466695b..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Appwrite - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..75842c4 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```sh +# create a new project in the current directory +npx sv create + +# create a new project in my-app +npx sv create my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```sh +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```sh +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..134b1b2 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,40 @@ +import { fileURLToPath } from 'node:url'; +import { includeIgnoreFile } from '@eslint/compat'; +import js from '@eslint/js'; +import svelte from 'eslint-plugin-svelte'; +import { defineConfig } from 'eslint/config'; +import globals from 'globals'; +import ts from 'typescript-eslint'; +import svelteConfig from './svelte.config.js'; + +const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); + +export default defineConfig( + includeIgnoreFile(gitignorePath), + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs.recommended, + { + languageOptions: { + globals: { ...globals.browser, ...globals.node } + }, + rules: { // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. + // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors + "no-undef": 'off' } + }, + { + files: [ + '**/*.svelte', + '**/*.svelte.ts', + '**/*.svelte.js' + ], + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: ['.svelte'], + parser: ts.parser, + svelteConfig + } + } + } +); diff --git a/package.json b/package.json new file mode 100644 index 0000000..6cbc568 --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "appwrite-cursor-rules", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --write .", + "lint": "prettier --check . && eslint ." + }, + "devDependencies": { + "@eslint/compat": "^1.4.0", + "@eslint/js": "^9.39.1", + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/kit": "^2.48.5", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@types/node": "^22", + "eslint": "^9.39.1", + "eslint-plugin-svelte": "^3.13.0", + "globals": "^16.5.0", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", + "svelte": "^5.43.8", + "svelte-check": "^4.3.4", + "typescript": "^5.9.3", + "typescript-eslint": "^8.47.0", + "vite": "^7.2.2" + }, + "dependencies": { + "@appwrite.io/pink": "^1.0.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..3db13e6 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2054 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@appwrite.io/pink': + specifier: ^1.0.0 + version: 1.0.0 + devDependencies: + '@eslint/compat': + specifier: ^1.4.0 + version: 1.4.1(eslint@9.39.1) + '@eslint/js': + specifier: ^9.39.1 + version: 9.39.1 + '@sveltejs/adapter-auto': + specifier: ^7.0.0 + version: 7.0.0(@sveltejs/kit@2.49.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1))) + '@sveltejs/kit': + specifier: ^2.48.5 + version: 2.49.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)) + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.1 + version: 6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)) + '@types/node': + specifier: ^22 + version: 22.19.1 + eslint: + specifier: ^9.39.1 + version: 9.39.1 + eslint-plugin-svelte: + specifier: ^3.13.0 + version: 3.13.0(eslint@9.39.1)(svelte@5.44.0) + globals: + specifier: ^16.5.0 + version: 16.5.0 + prettier: + specifier: ^3.6.2 + version: 3.6.2 + prettier-plugin-svelte: + specifier: ^3.4.0 + version: 3.4.0(prettier@3.6.2)(svelte@5.44.0) + svelte: + specifier: ^5.43.8 + version: 5.44.0 + svelte-check: + specifier: ^4.3.4 + version: 4.3.4(picomatch@4.0.3)(svelte@5.44.0)(typescript@5.9.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.47.0 + version: 8.48.0(eslint@9.39.1)(typescript@5.9.3) + vite: + specifier: ^7.2.2 + version: 7.2.4(@types/node@22.19.1) + +packages: + + '@appwrite.io/pink-icons@1.0.0': + resolution: {integrity: sha512-+zpksP07MvOYwhx9AZDFW0pxXQNC2juKwyOQVRAwAOkN1ACSQKPlyytkI1u2ci6CQPWjJe20CzbvBBuRNXOKjA==} + + '@appwrite.io/pink@1.0.0': + resolution: {integrity: sha512-q/RFlKjtGL7yBfjYkPkHYnhPt9jSMT3zoLRlEIu+G2lt59QWjybELQW17cS8gm1ZBTcncuzoPtVd6x1vrEo5uw==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/compat@1.4.1': + resolution: {integrity: sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.40 || 9 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@rollup/rollup-android-arm-eabi@4.53.3': + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.53.3': + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.53.3': + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.53.3': + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.53.3': + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.53.3': + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.53.3': + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.53.3': + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.53.3': + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.53.3': + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.53.3': + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.3': + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + cpu: [x64] + os: [win32] + + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@sveltejs/acorn-typescript@1.0.7': + resolution: {integrity: sha512-znp1A/Y1Jj4l/Zy7PX5DZKBE0ZNY+5QBngiE21NJkfSTyzzC5iKNWOtwFXKtIrn7MXEFBck4jD95iBNkGjK92Q==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/adapter-auto@7.0.0': + resolution: {integrity: sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + + '@sveltejs/kit@2.49.0': + resolution: {integrity: sha512-oH8tXw7EZnie8FdOWYrF7Yn4IKrqTFHhXvl8YxXxbKwTMcD/5NNCryUSEXRk2ZR4ojnub0P8rNrsVGHXWqIDtA==} + engines: {node: '>=18.13'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.0.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + + '@sveltejs/vite-plugin-svelte-inspector@5.0.1': + resolution: {integrity: sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^6.0.0-next.0 + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + + '@sveltejs/vite-plugin-svelte@6.2.1': + resolution: {integrity: sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@22.19.1': + resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==} + + '@typescript-eslint/eslint-plugin@8.48.0': + resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.48.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.48.0': + resolution: {integrity: sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.48.0': + resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.48.0': + resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.48.0': + resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.48.0': + resolution: {integrity: sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.48.0': + resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.48.0': + resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.48.0': + resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.48.0': + resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + devalue@5.5.0: + resolution: {integrity: sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-svelte@3.13.0: + resolution: {integrity: sha512-2ohCCQJJTNbIpQCSDSTWj+FN0OVfPmSO03lmSNT7ytqMaWF6kpT86LdzDqtm4sh7TVPl/OEWJ/d7R87bXP2Vjg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.1 || ^9.0.0 + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrap@2.2.0: + resolution: {integrity: sha512-WBmtxe7R9C5mvL4n2le8nMUe4mD5V9oiK2vJpQ9I3y20ENPUomPcphBXE8D1x/Bm84oN1V+lOfgXxtqmxTp3Xg==} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + normalize.css@8.0.1: + resolution: {integrity: sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss-load-config@3.1.4: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.4.31 + + postcss-scss@4.0.9: + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + engines: {node: '>=4'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-svelte@3.4.0: + resolution: {integrity: sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==} + peerDependencies: + prettier: ^3.0.0 + svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + rollup@4.53.3: + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + svelte-check@4.3.4: + resolution: {integrity: sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' + + svelte-eslint-parser@1.4.0: + resolution: {integrity: sha512-fjPzOfipR5S7gQ/JvI9r2H8y9gMGXO3JtmrylHLLyahEMquXI0lrebcjT+9/hNgDej0H7abTyox5HpHmW1PSWA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.18.3} + peerDependencies: + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true + + svelte@5.44.0: + resolution: {integrity: sha512-R7387No2zEGw4CtYtI2rgsui6BqjFARzoZFGLiLN5OPla0Pq4Ra2WwcP/zBomP3MYalhSNvF1fzDMuU0P0zPJw==} + engines: {node: '>=18'} + + the-new-css-reset@1.11.3: + resolution: {integrity: sha512-61SB81vu9foUyEIqoU1CeqxrdlsVjJojj/CBXoG8BdvlKFsllB0Rza63DblnRqH+3uttPj3FGWo7+c9nu7MT+A==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.48.0: + resolution: {integrity: sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@7.2.4: + resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.1: + resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} + +snapshots: + + '@appwrite.io/pink-icons@1.0.0': {} + + '@appwrite.io/pink@1.0.0': + dependencies: + '@appwrite.io/pink-icons': 1.0.0 + normalize.css: 8.0.1 + the-new-css-reset: 1.11.3 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': + dependencies: + eslint: 9.39.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/compat@1.4.1(eslint@9.39.1)': + dependencies: + '@eslint/core': 0.17.0 + optionalDependencies: + eslint: 9.39.1 + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.1': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@polka/url@1.0.0-next.29': {} + + '@rollup/rollup-android-arm-eabi@4.53.3': + optional: true + + '@rollup/rollup-android-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-x64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.3': + optional: true + + '@standard-schema/spec@1.0.0': {} + + '@sveltejs/acorn-typescript@1.0.7(acorn@8.15.0)': + dependencies: + acorn: 8.15.0 + + '@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.49.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))': + dependencies: + '@sveltejs/kit': 2.49.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)) + + '@sveltejs/kit@2.49.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1))': + dependencies: + '@standard-schema/spec': 1.0.0 + '@sveltejs/acorn-typescript': 1.0.7(acorn@8.15.0) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)) + '@types/cookie': 0.6.0 + acorn: 8.15.0 + cookie: 0.6.0 + devalue: 5.5.0 + esm-env: 1.2.2 + kleur: 4.1.5 + magic-string: 0.30.21 + mrmime: 2.0.1 + sade: 1.8.1 + set-cookie-parser: 2.7.2 + sirv: 3.0.2 + svelte: 5.44.0 + vite: 7.2.4(@types/node@22.19.1) + + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1))': + dependencies: + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)) + debug: 4.4.3 + svelte: 5.44.0 + vite: 7.2.4(@types/node@22.19.1) + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)) + debug: 4.4.3 + deepmerge: 4.3.1 + magic-string: 0.30.21 + svelte: 5.44.0 + vite: 7.2.4(@types/node@22.19.1) + vitefu: 1.1.1(vite@7.2.4(@types/node@22.19.1)) + transitivePeerDependencies: + - supports-color + + '@types/cookie@0.6.0': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@22.19.1': + dependencies: + undici-types: 6.21.0 + + '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.0 + eslint: 9.39.1 + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.0 + debug: 4.4.3 + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.48.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) + '@typescript-eslint/types': 8.48.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.48.0': + dependencies: + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 + + '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.1 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.48.0': {} + + '@typescript-eslint/typescript-estree@8.48.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.48.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.48.0': + dependencies: + '@typescript-eslint/types': 8.48.0 + eslint-visitor-keys: 4.2.1 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + axobject-query@4.1.0: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + callsites@3.1.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + cookie@0.6.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + devalue@5.5.0: {} + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escape-string-regexp@4.0.0: {} + + eslint-plugin-svelte@3.13.0(eslint@9.39.1)(svelte@5.44.0): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@jridgewell/sourcemap-codec': 1.5.5 + eslint: 9.39.1 + esutils: 2.0.3 + globals: 16.5.0 + known-css-properties: 0.37.0 + postcss: 8.5.6 + postcss-load-config: 3.1.4(postcss@8.5.6) + postcss-safe-parser: 7.0.1(postcss@8.5.6) + semver: 7.7.3 + svelte-eslint-parser: 1.4.0(svelte@5.44.0) + optionalDependencies: + svelte: 5.44.0 + transitivePeerDependencies: + - ts-node + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.39.1 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + esm-env@1.2.2: {} + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrap@2.2.0: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + fsevents@2.3.3: + optional: true + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@16.5.0: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + isexe@2.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@4.1.5: {} + + known-css-properties@0.37.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@2.1.0: {} + + locate-character@3.0.0: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + mri@1.2.0: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + normalize.css@8.0.1: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss-load-config@3.1.4(postcss@8.5.6): + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.2 + optionalDependencies: + postcss: 8.5.6 + + postcss-safe-parser@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-scss@4.0.9(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-selector-parser@7.1.0: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.44.0): + dependencies: + prettier: 3.6.2 + svelte: 5.44.0 + + prettier@3.6.2: {} + + punycode@2.3.1: {} + + readdirp@4.1.2: {} + + resolve-from@4.0.0: {} + + rollup@4.53.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.53.3 + '@rollup/rollup-android-arm64': 4.53.3 + '@rollup/rollup-darwin-arm64': 4.53.3 + '@rollup/rollup-darwin-x64': 4.53.3 + '@rollup/rollup-freebsd-arm64': 4.53.3 + '@rollup/rollup-freebsd-x64': 4.53.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 + '@rollup/rollup-linux-arm64-musl': 4.53.3 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 + '@rollup/rollup-linux-x64-gnu': 4.53.3 + '@rollup/rollup-linux-x64-musl': 4.53.3 + '@rollup/rollup-openharmony-arm64': 4.53.3 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 + '@rollup/rollup-win32-x64-gnu': 4.53.3 + '@rollup/rollup-win32-x64-msvc': 4.53.3 + fsevents: 2.3.3 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + semver@7.7.3: {} + + set-cookie-parser@2.7.2: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + source-map-js@1.2.1: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.44.0)(typescript@5.9.3): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + chokidar: 4.0.3 + fdir: 6.5.0(picomatch@4.0.3) + picocolors: 1.1.1 + sade: 1.8.1 + svelte: 5.44.0 + typescript: 5.9.3 + transitivePeerDependencies: + - picomatch + + svelte-eslint-parser@1.4.0(svelte@5.44.0): + dependencies: + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + postcss: 8.5.6 + postcss-scss: 4.0.9(postcss@8.5.6) + postcss-selector-parser: 7.1.0 + optionalDependencies: + svelte: 5.44.0 + + svelte@5.44.0: + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + '@sveltejs/acorn-typescript': 1.0.7(acorn@8.15.0) + '@types/estree': 1.0.8 + acorn: 8.15.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + clsx: 2.1.1 + devalue: 5.5.0 + esm-env: 1.2.2 + esrap: 2.2.0 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.21 + zimmerframe: 1.1.4 + + the-new-css-reset@1.11.3: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + totalist@3.0.1: {} + + ts-api-utils@2.1.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.48.0(eslint@9.39.1)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vite@7.2.4(@types/node@22.19.1): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.19.1 + fsevents: 2.3.3 + + vitefu@1.1.1(vite@7.2.4(@types/node@22.19.1)): + optionalDependencies: + vite: 7.2.4(@types/node@22.19.1) + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yaml@1.10.2: {} + + yocto-queue@0.1.0: {} + + zimmerframe@1.1.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..efc037a --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +onlyBuiltDependencies: + - esbuild diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..0bcd3ce --- /dev/null +++ b/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/src/lib/appwrite.js b/src/lib/appwrite.js new file mode 100644 index 0000000..7b905d2 --- /dev/null +++ b/src/lib/appwrite.js @@ -0,0 +1,14 @@ +import { Client, Account } from 'appwrite'; +// @ts-ignore - SvelteKit env variables +import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_ID } from '$env/static/public'; + +const endpoint = PUBLIC_APPWRITE_ENDPOINT || 'https://cloud.appwrite.io/v1'; +const projectId = PUBLIC_APPWRITE_PROJECT_ID || ''; + +const client = new Client() + .setEndpoint(endpoint) + .setProject(projectId); + +export const account = new Account(client); +export { client }; + diff --git a/src/lib/assets/favicon.svg b/src/lib/assets/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/src/lib/languages/common/install.js b/src/lib/languages/common/install.js new file mode 100644 index 0000000..e84f9d5 --- /dev/null +++ b/src/lib/languages/common/install.js @@ -0,0 +1,148 @@ +/** + * Common installation instructions by package manager/language + */ + +export const npmInstall = `npm install appwrite`; + +export const yarnInstall = `yarn add appwrite`; + +export const pnpmInstall = `pnpm add appwrite`; + +export const bunInstall = `bun add appwrite`; + +/** + * JavaScript/TypeScript installation section + * @param {string} [packageName] - Package name (default: 'appwrite') + * @param {string} [title] - Installation title (default: 'Install the Appwrite JavaScript SDK') + */ +export function jsInstall(packageName = 'appwrite', title = 'Install the Appwrite JavaScript SDK') { + return `## SDK Installation + +${title} using npm: + +\`\`\`bash +npm install ${packageName} +\`\`\` + +Or using yarn: + +\`\`\`bash +yarn add ${packageName} +\`\`\` + +Or using pnpm: + +\`\`\`bash +pnpm add ${packageName} +\`\`\` + +Or using bun: + +\`\`\`bash +bun add ${packageName} +\`\`\``; +} + +/** + * Default JavaScript installation (export as constant for backwards compatibility) + */ +export const jsInstallDefault = jsInstall(); + +/** + * Python installation section + */ +export const pythonInstall = `## SDK Installation + +Install the Appwrite Python SDK using pip: + +\`\`\`bash +pip install appwrite +\`\`\` + +Or using poetry: + +\`\`\`bash +poetry add appwrite +\`\`\``; + +/** + * PHP installation section + */ +export const phpInstall = `## SDK Installation + +Install the Appwrite PHP SDK using Composer: + +\`\`\`bash +composer require appwrite/appwrite +\`\`\``; + +/** + * Go installation section + */ +export const goInstall = `## SDK Installation + +Install the Appwrite Go SDK: + +\`\`\`bash +go get github.com/appwrite/sdk-for-go +\`\`\``; + +/** + * Ruby installation section + */ +export const rubyInstall = `## SDK Installation + +Install the Appwrite Ruby SDK using Bundler: + +\`\`\`bash +gem install appwrite +\`\`\` + +Or add to your \`Gemfile\`: + +\`\`\`ruby +gem 'appwrite' +\`\`\``; + +/** + * .NET installation section + */ +export const dotnetInstall = `## SDK Installation + +Install the Appwrite .NET SDK using NuGet: + +\`\`\`bash +dotnet add package Appwrite +\`\`\` + +Or using Package Manager: + +\`\`\`powershell +Install-Package Appwrite +\`\`\``; + +/** + * Dart/Flutter installation section (async - requires version lookup) + * @param {string} version - SDK version + * @param {boolean} isServer - Whether this is for server SDK + */ +export const dartInstall = (version, isServer = false) => { + const sdkName = isServer ? 'Dart Server SDK' : 'Flutter SDK'; + const pubCommand = isServer ? 'dart pub get' : 'flutter pub get'; + + return `## SDK Installation + +Add the Appwrite ${sdkName} to your \`pubspec.yaml\`: + +\`\`\`yaml +dependencies: + appwrite: ^${version} +\`\`\` + +Then install it: + +\`\`\`bash +${pubCommand} +\`\`\``; +}; + diff --git a/src/lib/languages/common/mcp.js b/src/lib/languages/common/mcp.js new file mode 100644 index 0000000..51b9c1a --- /dev/null +++ b/src/lib/languages/common/mcp.js @@ -0,0 +1,55 @@ +/** + * MCP Server recommendation content + * This content is included in all generated rules to recommend installing the Appwrite Docs MCP server + */ + +/** + * MCP Server recommendation section + * @returns {string} + */ +export function generateMCPRecommendation() { + return `## Recommended: Install Appwrite Docs MCP Server + +**We highly recommend installing the Appwrite Docs MCP server** to get instant access to Appwrite documentation, code examples, and best practices directly in Cursor. This enables the AI assistant to provide accurate, up-to-date Appwrite guidance and code examples. + +### Installation Steps + +1. **Open Cursor Settings** + - Go to **Cursor Settings** → **MCP** tab + - Click **Add new global MCP server** + +2. **Add the Docs Server** + - Update the \`mcp.json\` file with the following configuration: + +\`\`\`json +{ + "mcpServers": { + "appwrite-docs": { + "command": "npx", + "args": [ + "mcp-remote", + "https://mcp-for-docs.appwrite.io" + ] + } + } +} +\`\`\` + +3. **Save and Restart** + - Save the \`mcp.json\` file + - Restart Cursor if the MCP server doesn't start automatically + +### Benefits + +Once installed, you can ask questions like: +- "How do I set up real-time subscriptions in Appwrite?" +- "Show me how to authenticate users with OAuth" +- "What are the best practices for database queries?" +- "How do I implement file uploads with Appwrite Storage?" +- "Show me an example of using Appwrite Functions" + +**Note:** You can also install the Appwrite API MCP server for direct API interactions. For full setup instructions, visit: https://appwrite.io/docs/tooling/mcp/cursor + +---`; +} + diff --git a/src/lib/languages/common/products.js b/src/lib/languages/common/products.js new file mode 100644 index 0000000..cc620dd --- /dev/null +++ b/src/lib/languages/common/products.js @@ -0,0 +1,119 @@ +/** + * Common product documentation links and descriptions + * These are reused across all SDKs and frameworks + */ + +/** + * Product documentation sections that appear in every SDK initialization + */ +export const commonProductLinks = `For TablesDB operations (create, read, update, delete rows), see the [Rows Documentation](https://appwrite.io/docs/products/databases/rows). + +For Storage operations (upload, download files), see the [Storage Documentation](https://appwrite.io/docs/products/storage/upload-download).`; + +/** + * Authentication product documentation + */ +export const authProductLinks = `For detailed authentication and session management instructions, see the [Authentication Quick Start Guide](https://appwrite.io/docs/products/auth/quick-start). + +For session management, login, logout, and authentication state checking, see the [Authentication Documentation](https://appwrite.io/docs/products/auth/quick-start). + +For OAuth providers and social authentication, see the [OAuth2 Documentation](https://appwrite.io/docs/products/auth/oauth2). + +For server-side rendering (SSR) authentication, see the [SSR Login Guide](https://appwrite.io/docs/products/auth/server-side-rendering). + +For Teams management, team invitations, and team-based permissions, see the [Teams Documentation](https://appwrite.io/docs/products/auth/teams). + +For team invites and membership management, see the [Team Invites Guide](https://appwrite.io/docs/products/auth/team-invites). + +For multi-tenancy using Teams, see the [Multi-tenancy Guide](https://appwrite.io/docs/products/auth/multi-tenancy).`; + +/** + * Database product documentation + */ +export const databaseProductLinks = `For detailed database operations, see the [Rows Documentation](https://appwrite.io/docs/products/databases/rows). + +For querying and filtering data, see the [Queries Guide](https://appwrite.io/docs/products/databases/queries). + +For pagination, see the [Pagination Documentation](https://appwrite.io/docs/products/databases/pagination). + +For permissions and access control, see the [Permissions Guide](https://appwrite.io/docs/products/databases/permissions). + +For transactions, see the [Transactions Documentation](https://appwrite.io/docs/products/databases/transactions).`; + +/** + * Storage product documentation + */ +export const storageProductLinks = `For detailed storage operations, see the [Storage Documentation](https://appwrite.io/docs/products/storage/upload-download). + +For file uploads and downloads, see the [Upload & Download Guide](https://appwrite.io/docs/products/storage/upload-download). + +For permissions and access control, see the [Storage Permissions Documentation](https://appwrite.io/docs/products/storage/permissions).`; + +/** + * Functions product documentation + */ +export const functionsProductLinks = `For detailed serverless function execution, see the [Functions Execution Documentation](https://appwrite.io/docs/products/functions/execute). + +For function domains and custom endpoints, see the [Functions Domains Guide](https://appwrite.io/docs/products/functions/domains). + +For event-driven function execution, see the [Events Documentation](https://appwrite.io/docs/advanced/platform/events). + +For scheduled function execution, see the [Scheduled Executions Guide](https://appwrite.io/docs/products/functions/execute#schedule).`; + +/** + * Messaging product documentation + */ +export const messagingProductLinks = `For detailed messaging operations, see the [Messaging Documentation](https://appwrite.io/docs/products/messaging). + +For sending push notifications, see the [Push Notifications Guide](https://appwrite.io/docs/products/messaging/send-push-notifications). + +For sending emails, see the [Email Messages Guide](https://appwrite.io/docs/products/messaging/send-email-messages). + +For sending SMS messages, see the [SMS Messages Guide](https://appwrite.io/docs/products/messaging/send-sms-messages). + +For messaging providers, see the [Providers Documentation](https://appwrite.io/docs/products/messaging/providers).`; + +/** + * Sites product documentation + */ +export const sitesProductLinks = `For detailed Sites hosting and deployment, see the [Sites Documentation](https://appwrite.io/docs/products/sites). + +For getting started with Sites, see the [Sites Quick Start Guide](https://appwrite.io/docs/products/sites/quick-start). + +For deploying from Git, see the [Deploy from Git Guide](https://appwrite.io/docs/products/sites/deploy-from-git). + +For deploying from CLI, see the [Deploy from CLI Guide](https://appwrite.io/docs/products/sites/deploy-from-cli). + +For manual deployments, see the [Manual Deployment Guide](https://appwrite.io/docs/products/sites/deploy-manually). + +For deployment management, see the [Deployments Documentation](https://appwrite.io/docs/products/sites/deployments). + +For custom domains, see the [Domains Documentation](https://appwrite.io/docs/products/sites/domains). + +For rendering strategies (static vs SSR), see the [Rendering Documentation](https://appwrite.io/docs/products/sites/rendering). + +For static site hosting, see the [Static Rendering Guide](https://appwrite.io/docs/products/sites/rendering/static). + +For server-side rendering, see the [SSR Rendering Guide](https://appwrite.io/docs/products/sites/rendering/ssr). + +For supported frameworks, see the [Frameworks Documentation](https://appwrite.io/docs/products/sites/frameworks). + +For instant rollbacks, see the [Instant Rollbacks Guide](https://appwrite.io/docs/products/sites/instant-rollbacks). + +For deployment previews, see the [Previews Documentation](https://appwrite.io/docs/products/sites/previews).`; + +/** + * Real-time product documentation + */ +export const realtimeProductLinks = `For detailed real-time subscriptions, see the [Real-time Documentation](https://appwrite.io/docs/products/realtime). + +For subscribing to database changes, see the [Database Subscriptions Guide](https://appwrite.io/docs/products/realtime/subscribe-to-databases). + +For subscribing to storage changes, see the [Storage Subscriptions Guide](https://appwrite.io/docs/products/realtime/subscribe-to-storage). + +For subscribing to account changes, see the [Account Subscriptions Guide](https://appwrite.io/docs/products/realtime/subscribe-to-account). + +For real-time channels and events, see the [Channels Documentation](https://appwrite.io/docs/products/realtime/channels). + +For real-time event types and payloads, see the [Events Documentation](https://appwrite.io/docs/products/realtime/events).`; + diff --git a/src/lib/languages/common/security.js b/src/lib/languages/common/security.js new file mode 100644 index 0000000..3a7f23f --- /dev/null +++ b/src/lib/languages/common/security.js @@ -0,0 +1,62 @@ +/** + * Common security best practices and guidelines + */ + +/** + * Client-side security best practices (for web frameworks) + */ +export const clientSecurity = `**Best Practices:** +- Store endpoint and project ID in environment variables +- Never commit API keys to version control +- Initialize services once and export as singletons`; + +/** + * Client-side security with environment variable note + * @param {string} envFile - Environment file name (e.g., '.env.local', '.env') + */ +export const clientSecurityWithEnv = (envFile = '.env.local') => `**Best Practices:** +- Store endpoint and project ID in environment variables (${envFile}) +- Never commit API keys to version control +- Initialize services once and export as singletons`; + +/** + * Server-side security best practices + */ +export const serverSecurity = `**Security:** +- API keys should NEVER be exposed to client-side code +- Use environment variables for all sensitive configuration +- API keys grant admin access - use with extreme caution`; + +/** + * Server-side security with framework-specific config note + * @param {string} configMethod - Configuration method (e.g., "configuration files or environment variables", "Rails credentials") + */ +export const serverSecurityWithConfig = (configMethod = 'environment variables') => `**Security:** +- API keys should NEVER be exposed to client-side code +- Use ${configMethod} for configuration +- API keys grant admin access - use with extreme caution`; + +/** + * Authentication-specific notes + */ +export const authNote = `**Authentication:** +- Prefer SSR auth for better security and performance`; + +/** + * Framework-specific notes + */ +export const frameworkNotes = { + angular: `**Best Practices:** +- Store endpoint and project ID in environment variables +- Never commit API keys to version control +- Use Angular services for dependency injection + +${authNote}`, + + nodejs: `**Best Practices:** +- Store endpoint and project ID in environment variables +- Never commit API keys to version control +- Use environment variables for configuration +- API keys grant admin access - use with extreme caution` +}; + diff --git a/src/lib/languages/common/utils.js b/src/lib/languages/common/utils.js new file mode 100644 index 0000000..058d9af --- /dev/null +++ b/src/lib/languages/common/utils.js @@ -0,0 +1,92 @@ +/** + * Utility functions for composing SDK initialization templates + */ + +/** + * Creates a security/best practices section (SDK Initialization section removed) + * @param {Object} options + * @param {string} options.securityNotes - Security/best practices section + * @param {string} [options.additionalNotes] - Additional framework-specific notes + * @returns {string} + */ +export function createSecuritySection({ + securityNotes, + additionalNotes = '' +}) { + return securityNotes + (additionalNotes ? `\n\n${additionalNotes}` : ''); +} + +/** + * Creates a complete framework template by combining installation and security notes + * @param {Object} options + * @param {string} options.installation - Installation section + * @param {string} options.securityNotes - Security/best practices section + * @param {string} [options.additionalNotes] - Additional framework-specific notes + * @returns {string} + */ +export function createFrameworkTemplate({ installation, securityNotes, additionalNotes = '' }) { + const securitySection = createSecuritySection({ securityNotes, additionalNotes }); + return `${installation} + +${securitySection}`; +} + +/** + * Quick Start Guide URLs by framework + */ +export const quickStartUrls = { + // JavaScript frameworks + react: 'https://appwrite.io/docs/quick-starts/react', + nextjs: 'https://appwrite.io/docs/quick-starts/nextjs', + vue: 'https://appwrite.io/docs/quick-starts/vue', + svelte: 'https://appwrite.io/docs/quick-starts/sveltekit', + angular: 'https://appwrite.io/docs/quick-starts/angular', + astro: 'https://appwrite.io/docs/quick-starts/astro', + nuxt: 'https://appwrite.io/docs/quick-starts/nuxt', + qwik: 'https://appwrite.io/docs/quick-starts/qwik', + solid: 'https://appwrite.io/docs/quick-starts/solid', + tanstack: 'https://appwrite.io/docs/quick-starts/tanstack', + nodejs: 'https://appwrite.io/docs/quick-starts/nodejs', + vanilla: 'https://appwrite.io/docs/quick-starts/web', + + // React Native + 'react-native': 'https://appwrite.io/docs/quick-starts/react-native', + + // Server SDKs + python: 'https://appwrite.io/docs/quick-starts/python', + php: 'https://appwrite.io/docs/quick-starts/php', + go: 'https://appwrite.io/docs/quick-starts/go', + ruby: 'https://appwrite.io/docs/quick-starts/ruby', + dotnet: 'https://appwrite.io/docs/quick-starts/dotnet', + dart: 'https://appwrite.io/docs/quick-starts/dart', + flutter: 'https://appwrite.io/docs/quick-starts/flutter', + kotlin: 'https://appwrite.io/docs/quick-starts/kotlin' +}; + +/** + * Framework display names + */ +export const frameworkNames = { + react: 'React', + nextjs: 'Next.js', + vue: 'Vue', + svelte: 'SvelteKit', + angular: 'Angular', + astro: 'Astro', + nuxt: 'Nuxt', + qwik: 'Qwik', + solid: 'Solid', + tanstack: 'TanStack', + nodejs: 'Node.js', + vanilla: 'Web', + 'react-native': 'React Native', + python: 'Python', + php: 'PHP', + go: 'Go', + ruby: 'Ruby', + dotnet: '.NET', + dart: 'Dart', + flutter: 'Flutter', + kotlin: 'Kotlin' +}; + diff --git a/src/lib/languages/dart/index.js b/src/lib/languages/dart/index.js new file mode 100644 index 0000000..8b82657 --- /dev/null +++ b/src/lib/languages/dart/index.js @@ -0,0 +1,26 @@ +import { getSDKVersion } from '$lib/utils/versions.js'; +import { dartInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; + +/** + * Gets the Flutter SDK installation template with the latest version from Appwrite's API + * This is the main export used by the rules generator for Flutter + * @returns {Promise} + */ +export const flutter = async () => { + const version = await getSDKVersion('client-flutter'); + const installation = dartInstall(version, false); + return createFrameworkTemplate({ installation, securityNotes: '' }); +}; + +/** + * Export vanilla as an alias to flutter for backwards compatibility + * @returns {Promise} + */ +export const vanilla = flutter; + +/** + * Export server from server.js + */ +export { server } from './server.js'; + diff --git a/src/lib/languages/dart/server.js b/src/lib/languages/dart/server.js new file mode 100644 index 0000000..a00c1f3 --- /dev/null +++ b/src/lib/languages/dart/server.js @@ -0,0 +1,15 @@ +import { getSDKVersion } from '$lib/utils/versions.js'; +import { dartInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; + +/** + * Gets the Dart Server SDK installation template with the latest version from Appwrite's API + * @returns {Promise} + */ +export const server = async () => { + const version = await getSDKVersion('server-dart'); + const installation = dartInstall(version, true); + return createFrameworkTemplate({ installation, securityNotes: serverSecurity }); +}; + diff --git a/src/lib/languages/dotnet/index.js b/src/lib/languages/dotnet/index.js new file mode 100644 index 0000000..fac460d --- /dev/null +++ b/src/lib/languages/dotnet/index.js @@ -0,0 +1,3 @@ +export { server } from './server.js'; +export { vanilla } from './vanilla.js'; + diff --git a/src/lib/languages/dotnet/server.js b/src/lib/languages/dotnet/server.js new file mode 100644 index 0000000..d009aca --- /dev/null +++ b/src/lib/languages/dotnet/server.js @@ -0,0 +1,9 @@ +import { dotnetInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurityWithConfig } from '../common/security.js'; + +export const server = createFrameworkTemplate({ + installation: dotnetInstall, + securityNotes: serverSecurityWithConfig('configuration files or environment variables') +}); + diff --git a/src/lib/languages/dotnet/vanilla.js b/src/lib/languages/dotnet/vanilla.js new file mode 100644 index 0000000..4b481e2 --- /dev/null +++ b/src/lib/languages/dotnet/vanilla.js @@ -0,0 +1,8 @@ +import { dotnetInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; + +export const vanilla = createFrameworkTemplate({ + installation: dotnetInstall, + securityNotes: '' +}); + diff --git a/src/lib/languages/go/index.js b/src/lib/languages/go/index.js new file mode 100644 index 0000000..5a95a78 --- /dev/null +++ b/src/lib/languages/go/index.js @@ -0,0 +1,2 @@ +export { server } from './server.js'; + diff --git a/src/lib/languages/go/server.js b/src/lib/languages/go/server.js new file mode 100644 index 0000000..1c6dfb7 --- /dev/null +++ b/src/lib/languages/go/server.js @@ -0,0 +1,9 @@ +import { goInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; + +export const server = createFrameworkTemplate({ + installation: goInstall, + securityNotes: serverSecurity +}); + diff --git a/src/lib/languages/index.js b/src/lib/languages/index.js new file mode 100644 index 0000000..c2eee88 --- /dev/null +++ b/src/lib/languages/index.js @@ -0,0 +1,11 @@ +export * as js from './js/index.js'; +export * as python from './python/index.js'; +export * as php from './php/index.js'; +export * as go from './go/index.js'; +export * as dart from './dart/index.js'; +export * as swift from './swift/index.js'; +export * as kotlin from './kotlin/index.js'; +export * as reactNative from './react-native/index.js'; +export * as ruby from './ruby/index.js'; +export * as dotnet from './dotnet/index.js'; + diff --git a/src/lib/languages/js/angular.js b/src/lib/languages/js/angular.js new file mode 100644 index 0000000..ba24fad --- /dev/null +++ b/src/lib/languages/js/angular.js @@ -0,0 +1,9 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { frameworkNotes } from '../common/security.js'; + +export const angular = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: frameworkNotes.angular +}); + diff --git a/src/lib/languages/js/astro.js b/src/lib/languages/js/astro.js new file mode 100644 index 0000000..3726df4 --- /dev/null +++ b/src/lib/languages/js/astro.js @@ -0,0 +1,12 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurityWithEnv, authNote } from '../common/security.js'; + +export const astro = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: `${clientSecurityWithEnv('.env')} + +- Use separate clients for client-side and server-side operations`, + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/index.js b/src/lib/languages/js/index.js new file mode 100644 index 0000000..ce81dcd --- /dev/null +++ b/src/lib/languages/js/index.js @@ -0,0 +1,13 @@ +export { react } from './react.js'; +export { nextjs } from './nextjs.js'; +export { svelte } from './svelte.js'; +export { nodejs } from './nodejs.js'; +export { vanilla } from './vanilla.js'; +export { vue } from './vue.js'; +export { angular } from './angular.js'; +export { astro } from './astro.js'; +export { nuxt } from './nuxt.js'; +export { qwik } from './qwik.js'; +export { solid } from './solid.js'; +export { tanstack } from './tanstack.js'; + diff --git a/src/lib/languages/js/nextjs.js b/src/lib/languages/js/nextjs.js new file mode 100644 index 0000000..a418c40 --- /dev/null +++ b/src/lib/languages/js/nextjs.js @@ -0,0 +1,10 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity, authNote } from '../common/security.js'; + +export const nextjs = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: serverSecurity, + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/nodejs.js b/src/lib/languages/js/nodejs.js new file mode 100644 index 0000000..5176ec4 --- /dev/null +++ b/src/lib/languages/js/nodejs.js @@ -0,0 +1,10 @@ +import { jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; + +export const nodejs = createFrameworkTemplate({ + installation: jsInstall('node-appwrite', 'Install the Appwrite Node.js SDK'), + securityNotes: `${serverSecurity} +- Never log or expose API keys in error messages` +}); + diff --git a/src/lib/languages/js/nuxt.js b/src/lib/languages/js/nuxt.js new file mode 100644 index 0000000..3ad76a1 --- /dev/null +++ b/src/lib/languages/js/nuxt.js @@ -0,0 +1,13 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { authNote } from '../common/security.js'; + +export const nuxt = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: `**Best Practices:** +- Store endpoint and project ID in \`nuxt.config.ts\` +- Never commit API keys to version control +- Use Nuxt plugins for client-side initialization`, + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/qwik.js b/src/lib/languages/js/qwik.js new file mode 100644 index 0000000..005ec60 --- /dev/null +++ b/src/lib/languages/js/qwik.js @@ -0,0 +1,10 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurity, authNote } from '../common/security.js'; + +export const qwik = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: clientSecurity, + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/react.js b/src/lib/languages/js/react.js new file mode 100644 index 0000000..ea92424 --- /dev/null +++ b/src/lib/languages/js/react.js @@ -0,0 +1,9 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurity } from '../common/security.js'; + +export const react = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: clientSecurity +}); + diff --git a/src/lib/languages/js/solid.js b/src/lib/languages/js/solid.js new file mode 100644 index 0000000..006df4e --- /dev/null +++ b/src/lib/languages/js/solid.js @@ -0,0 +1,10 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurityWithEnv, authNote } from '../common/security.js'; + +export const solid = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: clientSecurityWithEnv('.env'), + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/svelte.js b/src/lib/languages/js/svelte.js new file mode 100644 index 0000000..673df65 --- /dev/null +++ b/src/lib/languages/js/svelte.js @@ -0,0 +1,10 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurityWithEnv, authNote } from '../common/security.js'; + +export const svelte = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: clientSecurityWithEnv('.env'), + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/tanstack.js b/src/lib/languages/js/tanstack.js new file mode 100644 index 0000000..68569d8 --- /dev/null +++ b/src/lib/languages/js/tanstack.js @@ -0,0 +1,17 @@ +import { jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { authNote } from '../common/security.js'; + +export const tanstack = createFrameworkTemplate({ + installation: jsInstall('appwrite @tanstack/react-query', 'Install the Appwrite JavaScript SDK and TanStack Query'), + securityNotes: `For TanStack Query integration examples and best practices, refer to the [TanStack Query Documentation](https://tanstack.com/query/latest). + +**Best Practices:** +- Use query keys consistently for cache management +- Invalidate queries after mutations to keep data fresh +- Use \`enabled\` option to conditionally fetch data +- Store endpoint and project ID in environment variables +- Never commit API keys to version control`, + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/vanilla.js b/src/lib/languages/js/vanilla.js new file mode 100644 index 0000000..9cf3bd1 --- /dev/null +++ b/src/lib/languages/js/vanilla.js @@ -0,0 +1,9 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurity } from '../common/security.js'; + +export const vanilla = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: clientSecurity.replace('- Initialize services once and export as singletons', '').trim() +}); + diff --git a/src/lib/languages/js/vue.js b/src/lib/languages/js/vue.js new file mode 100644 index 0000000..c8d5d8d --- /dev/null +++ b/src/lib/languages/js/vue.js @@ -0,0 +1,9 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurityWithEnv } from '../common/security.js'; + +export const vue = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: clientSecurityWithEnv('.env') +}); + diff --git a/src/lib/languages/kotlin/index.js b/src/lib/languages/kotlin/index.js new file mode 100644 index 0000000..9b0a111 --- /dev/null +++ b/src/lib/languages/kotlin/index.js @@ -0,0 +1,46 @@ +import { getSDKVersion } from '$lib/utils/versions.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; + +/** + * Generates the Kotlin SDK installation template with the latest version + * @param {string} version - The SDK version to use + * @returns {string} + */ +function generateInstallationTemplate(version) { + return `## SDK Installation + +Add the Appwrite Kotlin SDK to your \`build.gradle.kts\`: + +**Recommended: Specify exact version for stability** + +\`\`\`kotlin +dependencies { + implementation("io.appwrite:sdk-for-kotlin:${version}") +} +\`\`\` + +Or for Maven, add to \`pom.xml\`: + +**Recommended: Specify exact version** + +\`\`\`xml + + io.appwrite + sdk-for-kotlin + ${version} + +\`\`\` +`; +} + +/** + * Gets the Kotlin SDK installation template with the latest version from Appwrite's API + * This is the main export used by the rules generator + * @returns {Promise} + */ +export const vanilla = async () => { + const version = await getSDKVersion('server-kotlin'); + const installation = generateInstallationTemplate(version); + return createFrameworkTemplate({ installation, securityNotes: serverSecurity }); +}; diff --git a/src/lib/languages/php/index.js b/src/lib/languages/php/index.js new file mode 100644 index 0000000..5a95a78 --- /dev/null +++ b/src/lib/languages/php/index.js @@ -0,0 +1,2 @@ +export { server } from './server.js'; + diff --git a/src/lib/languages/php/server.js b/src/lib/languages/php/server.js new file mode 100644 index 0000000..182f357 --- /dev/null +++ b/src/lib/languages/php/server.js @@ -0,0 +1,9 @@ +import { phpInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; + +export const server = createFrameworkTemplate({ + installation: phpInstall, + securityNotes: serverSecurity +}); + diff --git a/src/lib/languages/python/flask.js b/src/lib/languages/python/flask.js new file mode 100644 index 0000000..9425800 --- /dev/null +++ b/src/lib/languages/python/flask.js @@ -0,0 +1,8 @@ +import { createSecuritySection } from '../common/utils.js'; + +export const flask = createSecuritySection({ + securityNotes: `**Best Practices:** +- Use environment variables for configuration +- Never commit API keys to version control` +}); + diff --git a/src/lib/languages/python/index.js b/src/lib/languages/python/index.js new file mode 100644 index 0000000..3ab6ffe --- /dev/null +++ b/src/lib/languages/python/index.js @@ -0,0 +1,3 @@ +export { flask } from './flask.js'; +export { server } from './server.js'; + diff --git a/src/lib/languages/python/server.js b/src/lib/languages/python/server.js new file mode 100644 index 0000000..0ceba5b --- /dev/null +++ b/src/lib/languages/python/server.js @@ -0,0 +1,9 @@ +import { pythonInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; + +export const server = createFrameworkTemplate({ + installation: pythonInstall, + securityNotes: serverSecurity +}); + diff --git a/src/lib/languages/react-native/index.js b/src/lib/languages/react-native/index.js new file mode 100644 index 0000000..57da198 --- /dev/null +++ b/src/lib/languages/react-native/index.js @@ -0,0 +1,2 @@ +export { vanilla } from './vanilla.js'; + diff --git a/src/lib/languages/react-native/vanilla.js b/src/lib/languages/react-native/vanilla.js new file mode 100644 index 0000000..f125743 --- /dev/null +++ b/src/lib/languages/react-native/vanilla.js @@ -0,0 +1,11 @@ +import { jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; + +export const vanilla = createFrameworkTemplate({ + installation: jsInstall('react-native-appwrite', 'Install the Appwrite React Native SDK'), + securityNotes: `**Best Practices:** +- Store endpoint and project ID in environment variables or config files +- Never commit API keys to version control +- Initialize services once and export as singletons` +}); + diff --git a/src/lib/languages/ruby/index.js b/src/lib/languages/ruby/index.js new file mode 100644 index 0000000..5a95a78 --- /dev/null +++ b/src/lib/languages/ruby/index.js @@ -0,0 +1,2 @@ +export { server } from './server.js'; + diff --git a/src/lib/languages/ruby/server.js b/src/lib/languages/ruby/server.js new file mode 100644 index 0000000..f45ed75 --- /dev/null +++ b/src/lib/languages/ruby/server.js @@ -0,0 +1,9 @@ +import { rubyInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurityWithConfig } from '../common/security.js'; + +export const server = createFrameworkTemplate({ + installation: rubyInstall, + securityNotes: serverSecurityWithConfig('environment variables or Rails credentials') +}); + diff --git a/src/lib/languages/swift/index.js b/src/lib/languages/swift/index.js new file mode 100644 index 0000000..8bcce6a --- /dev/null +++ b/src/lib/languages/swift/index.js @@ -0,0 +1,36 @@ +import { getSDKVersion } from '$lib/utils/versions.js'; +import { createFrameworkTemplate } from '../common/utils.js'; + +/** + * Generates the Swift SDK installation template with the latest version + * @param {string} version - The SDK version to use + * @returns {string} + */ +function generateInstallationTemplate(version) { + return `## SDK Installation + +Add the Appwrite Swift SDK to your \`Package.swift\`: + +\`\`\`swift +dependencies: [ + .package(url: "https://github.com/appwrite/sdk-for-apple", from: "${version}") +] +\`\`\` + +Or add it via Xcode: +1. File → Add Packages... +2. Enter: \`https://github.com/appwrite/sdk-for-apple\` +3. Select version: \`${version}\` or later`; +} + +/** + * Gets the Swift SDK installation template with the latest version from Appwrite's API + * This is the main export used by the rules generator + * @returns {Promise} + */ +export const vanilla = async () => { + const version = await getSDKVersion('client-apple'); + const installation = generateInstallationTemplate(version); + return createFrameworkTemplate({ installation, securityNotes: '' }); +}; + diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js new file mode 100644 index 0000000..2d91620 --- /dev/null +++ b/src/lib/rules-generator.js @@ -0,0 +1,326 @@ +// Rules generator for different Appwrite SDKs and frameworks +import * as codeExamples from './languages/index.js'; +import { generateMCPRecommendation } from './languages/common/mcp.js'; + +/** @typedef {Object} SDKConfig + * @property {string} name + * @property {string[]} frameworks + * @property {string} importSyntax + * @property {string} exportSyntax + * @property {string} asyncSyntax + */ + +/** @typedef {Object} GeneratorConfig + * @property {string} sdk + * @property {string} framework + * @property {string[]} features + */ + +/** @type {Record} */ +export const SDK_OPTIONS = { + javascript: { + name: 'JavaScript/TypeScript', + frameworks: ['nextjs', 'react', 'vue', 'svelte', 'angular', 'astro', 'nuxt', 'qwik', 'solid', 'tanstack', 'nodejs', 'vanilla'], + importSyntax: 'import', + exportSyntax: 'export', + asyncSyntax: 'async/await' + }, + 'react-native': { + name: 'React Native', + frameworks: ['react-native', 'vanilla'], + importSyntax: 'import', + exportSyntax: 'export', + asyncSyntax: 'async/await' + }, + python: { + name: 'Python', + frameworks: ['flask', 'django', 'fastapi', 'server'], + importSyntax: 'from', + exportSyntax: 'def', + asyncSyntax: 'async def' + }, + flutter: { + name: 'Flutter/Dart', + frameworks: ['flutter', 'server'], + importSyntax: 'import', + exportSyntax: 'class', + asyncSyntax: 'Future' + }, + swift: { + name: 'Swift', + frameworks: ['ios', 'vanilla'], + importSyntax: 'import', + exportSyntax: 'func', + asyncSyntax: 'async' + }, + kotlin: { + name: 'Kotlin', + frameworks: ['android', 'vanilla'], + importSyntax: 'import', + exportSyntax: 'fun', + asyncSyntax: 'suspend' + }, + php: { + name: 'PHP', + frameworks: ['laravel', 'symfony', 'server'], + importSyntax: 'use', + exportSyntax: 'function', + asyncSyntax: 'async' + }, + go: { + name: 'Go', + frameworks: ['gin', 'fiber', 'server'], + importSyntax: 'import', + exportSyntax: 'func', + asyncSyntax: 'goroutine' + }, + ruby: { + name: 'Ruby', + frameworks: ['rails', 'server'], + importSyntax: 'require', + exportSyntax: 'def', + asyncSyntax: 'async' + }, + dotnet: { + name: '.NET', + frameworks: ['aspnet', 'server', 'vanilla'], + importSyntax: 'using', + exportSyntax: 'public', + asyncSyntax: 'async Task' + } +}; + +/** + * @param {GeneratorConfig} config + * @returns {Promise} + */ +export async function generateRules(config) { + const { sdk, framework, features } = config; + const sdkInfo = SDK_OPTIONS[sdk]; + + const sdkInit = await generateSDKInitialization(sdk, framework); + + // Generate all sections in parallel + const sections = await Promise.all([ + features.includes('auth') ? generateAuthSection(sdk, framework) : Promise.resolve(''), + features.includes('database') ? generateDatabaseSection(sdk, framework) : Promise.resolve(''), + features.includes('storage') ? generateStorageSection(sdk, framework) : Promise.resolve(''), + features.includes('functions') ? generateFunctionsSection(sdk, framework) : Promise.resolve(''), + features.includes('messaging') ? generateMessagingSection(sdk, framework) : Promise.resolve(''), + features.includes('sites') ? generateSitesSection(sdk, framework) : Promise.resolve(''), + features.includes('realtime') ? generateRealtimeSection(sdk, framework) : Promise.resolve('') + ]); + + let rules = `--- +description: You are an expert developer focused on building apps with Appwrite's ${sdkInfo?.name || sdk} SDK. +alwaysApply: false +--- + +# Appwrite Development Rules + +${generateMCPRecommendation()} + +${sdkInit} +${sections.join('\n\n')} +`; + + return rules; +} + + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateSDKInitialization(sdk, framework) { + /** @type {Record Promise)>>} */ + const templates = { + javascript: codeExamples.js, + 'react-native': codeExamples.reactNative, + python: codeExamples.python, + php: codeExamples.php, + go: codeExamples.go, + flutter: codeExamples.dart, + swift: codeExamples.swift, + kotlin: codeExamples.kotlin, + ruby: codeExamples.ruby, + dotnet: codeExamples.dotnet + }; + + const sdkTemplates = templates[sdk]; + if (sdkTemplates && sdkTemplates[framework]) { + const template = sdkTemplates[framework]; + // Check if it's an async function + if (typeof template === 'function') { + return await template(); + } + return template; + } + + // Fallback to vanilla if available + if (sdkTemplates && sdkTemplates.vanilla) { + const template = sdkTemplates.vanilla; + // Check if it's an async function + if (typeof template === 'function') { + return await template(); + } + return template; + } + + // Final fallback + return `## SDK Initialization + +Configure your Appwrite client for ${SDK_OPTIONS[sdk]?.name || sdk}.`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateAuthSection(sdk, framework) { + const { authProductLinks } = await import('./languages/common/products.js'); + return `## Authentication & Teams + +${authProductLinks} + +### Best Practices for Authentication & Teams + +- **Session Security**: Always use HttpOnly cookies for session storage in SSR applications +- **API Keys**: Never expose API keys to client-side code - use environment variables +- **Session Validation**: Always validate sessions on the server before trusting them +- **Team Permissions**: Use team roles for granular access control in multi-tenant applications +- **OAuth Redirects**: Handle OAuth redirects properly with success and failure URLs +- **Password Security**: Use strong password requirements and consider implementing MFA +- **Session Expiry**: Configure appropriate session expiry times based on your security requirements`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateDatabaseSection(sdk, framework) { + const { databaseProductLinks } = await import('./languages/common/products.js'); + return `## Database Operations + +${databaseProductLinks} + +### Best Practices for Databases + +- **Permissions**: Always set appropriate permissions at table and row levels +- **Query Optimization**: Use indexes for frequently queried fields to improve performance +- **Data Validation**: Validate data before creating or updating rows +- **Transactions**: Use transactions for operations that must succeed or fail together +- **Pagination**: Always implement pagination for large datasets to improve performance +- **Type Safety**: Use type-safe models when available in your SDK for better code quality`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateStorageSection(sdk, framework) { + const { storageProductLinks } = await import('./languages/common/products.js'); + return `## Storage Operations + +${storageProductLinks} + +### Best Practices for Storage + +- **File Size Limits**: Set appropriate file size limits to prevent abuse +- **File Types**: Validate file types before upload to ensure security +- **Permissions**: Set proper permissions on buckets and files to control access +- **Cleanup**: Implement cleanup strategies for unused or temporary files +- **Virus Scanning**: Consider implementing virus scanning for uploaded files`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateFunctionsSection(sdk, framework) { + const { functionsProductLinks } = await import('./languages/common/products.js'); + return `## Functions + +${functionsProductLinks} + +### Best Practices for Functions + +- **Error Handling**: Implement comprehensive error handling in your functions +- **Timeouts**: Be aware of function execution timeouts and optimize accordingly +- **Environment Variables**: Use environment variables for configuration, not hardcoded values +- **Logging**: Implement proper logging for debugging and monitoring +- **Security**: Validate all inputs and never trust user-provided data +- **Resource Limits**: Be mindful of memory and CPU limits for function executions`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateMessagingSection(sdk, framework) { + const { messagingProductLinks } = await import('./languages/common/products.js'); + return `## Messaging + +${messagingProductLinks} + +### Best Practices for Messaging + +- **Provider Selection**: Choose the right messaging provider based on your needs (FCM, APNS, Mailgun, Twilio, etc.) +- **Message Content**: Keep push notifications concise and actionable +- **Scheduling**: Use scheduled messages for better user engagement timing +- **Personalization**: Personalize messages to increase engagement +- **Rate Limiting**: Be mindful of rate limits when sending bulk messages +- **Error Handling**: Implement retry logic for failed message deliveries`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateSitesSection(sdk, framework) { + const { sitesProductLinks } = await import('./languages/common/products.js'); + return `## Sites + +${sitesProductLinks} + +### Best Practices for Sites + +- **Environment Variables**: Use environment variables for configuration, not hardcoded values +- **Custom Domains**: Configure custom domains for production sites for better branding +- **Rendering Strategy**: Choose between static and SSR based on your content needs and SEO requirements +- **Deployment Strategy**: Use Git deployments for automatic builds on commits +- **Rollback Plan**: Keep previous deployments ready for instant rollbacks if needed`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateRealtimeSection(sdk, framework) { + const { realtimeProductLinks } = await import('./languages/common/products.js'); + return `## Real-time Subscriptions + +${realtimeProductLinks} + +### Best Practices for Real-time Subscriptions + +- **Connection Management**: Always unsubscribe from channels when components unmount or pages are closed to prevent memory leaks +- **Error Handling**: Implement reconnection logic for dropped connections and handle network errors gracefully +- **Event Filtering**: Filter events on the client side to only process relevant updates for better performance +- **Channel Selection**: Subscribe only to the specific channels you need to minimize bandwidth and improve performance +- **Payload Validation**: Always validate payload data before processing to ensure data integrity +- **Rate Limiting**: Be mindful of the number of subscriptions and events to avoid overwhelming the client +- **State Synchronization**: Use real-time updates to keep local state in sync with server state, but handle conflicts appropriately +- **Authentication**: Ensure proper authentication is in place before subscribing to protected channels +- **Testing**: Test real-time functionality with network interruptions and reconnection scenarios +- **Cleanup**: Store unsubscribe functions and call them in cleanup hooks (useEffect cleanup, componentWillUnmount, etc.)`; +} + diff --git a/src/lib/utils/versions.js b/src/lib/utils/versions.js new file mode 100644 index 0000000..9771863 --- /dev/null +++ b/src/lib/utils/versions.js @@ -0,0 +1,61 @@ +/** + * Fetches the latest SDK versions from Appwrite's versions API + * @returns {Promise>} + */ +export async function fetchSDKVersions() { + try { + const response = await fetch('https://cloud.appwrite.io/versions'); + if (!response.ok) { + throw new Error(`Failed to fetch versions: ${response.statusText}`); + } + return await response.json(); + } catch (error) { + console.error('Error fetching SDK versions:', error); + // Return fallback versions if API is unavailable + return { + 'server-kotlin': '13.0.0', + 'client-flutter': '20.3.2', + 'client-apple': '13.4.0', + 'client-android': '11.3.0', + 'client-react-native': '0.18.0', + 'server-nodejs': '26.0.0', + 'server-php': '20.0.0', + 'server-python': '15.0.0', + 'server-ruby': '19.4.0', + 'server-go': 'v0.15.0', + 'server-dotnet': '0.23.0', + 'server-dart': '20.0.1', + 'server-swift': '14.0.0', + 'client-web': '21.4.0' + }; + } +} + +/** + * Gets the version for a specific SDK + * @param {string} sdkKey - The SDK key (e.g., 'server-kotlin', 'client-flutter') + * @returns {Promise} + */ +export async function getSDKVersion(sdkKey) { + const versions = await fetchSDKVersions(); + return versions[sdkKey] || 'latest'; +} + +/** + * Version cache to avoid multiple API calls + * @type {Record | null} + */ +let versionCache = null; + +/** + * Gets cached or fetches SDK versions + * @returns {Promise>} + */ +export async function getCachedVersions() { + if (versionCache) { + return versionCache; + } + versionCache = await fetchSDKVersions(); + return versionCache; +} + diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..5cdf9e2 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,13 @@ + + + + + + + +{@render children()} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..5ade608 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,453 @@ + + + + Appwrite Rules Generator + + +
+ + +
+ {#if previewVisible && generatedRules} +
+
+

Generated Rules

+
+ + +
+
+
+
{generatedRules}
+
+
+ {:else} +
+
+

Select your options and click "Generate Rules" to see the output here.

+
+
+ {/if} +
+
+ + + + diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..1295460 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,18 @@ +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), + + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter() + } +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2c2ed3c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..bbf8c7d --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()] +}); From 7df4cecdc96c3ea9448956f0556927203ef3eb00 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 21:01:12 +0530 Subject: [PATCH 02/19] remove initial draft rules file --- .cursor/rules/APPWRITE.mdc | 1173 ------------------------------------ 1 file changed, 1173 deletions(-) delete mode 100644 .cursor/rules/APPWRITE.mdc diff --git a/.cursor/rules/APPWRITE.mdc b/.cursor/rules/APPWRITE.mdc deleted file mode 100644 index acdc8d2..0000000 --- a/.cursor/rules/APPWRITE.mdc +++ /dev/null @@ -1,1173 +0,0 @@ ---- -description: You are an expert developer focused on building apps with Appwrite's APIs and SDKs. -alwaysApply: false ---- - -# Appwrite Development Rules - -## SDK Initialization - -### Client-Side (Web/React/Next.js) - -Always initialize the Appwrite client with proper configuration: - -```javascript -import { Client, Account, TablesDB, Storage } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://cloud.appwrite.io/v1') // Your Appwrite endpoint - .setProject('your-project-id'); // Your project ID - -// Initialize services -export const account = new Account(client); -export const tablesDB = new TablesDB(client); -export const storage = new Storage(client); -``` - -**Best Practices:** -- Store endpoint and project ID in environment variables (`.env.local`) -- Never commit API keys or secrets to version control -- Use separate clients for different environments (dev/staging/prod) -- Initialize services once and export them as singletons - -### Server-Side (Node.js/Next.js API Routes) - -Use server SDK with API key for admin operations: - -```javascript -import { Client, TablesDB, Storage, ID } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://cloud.appwrite.io/v1') - .setProject(process.env.APPWRITE_PROJECT_ID) - .setKey(process.env.APPWRITE_API_KEY); // Server-side only - -export const tablesDB = new TablesDB(client); -export const storage = new Storage(client); -``` - -**Security:** -- API keys should NEVER be exposed to client-side code -- Use environment variables for all sensitive configuration -- API keys grant admin access - use with extreme caution - -## Authentication Patterns - -### Session Management - -Always check authentication state before making authenticated requests: - -```javascript -import { account } from './appwrite-config'; - -// Check current session -async function getCurrentUser() { - try { - return await account.get(); - } catch (error) { - // User not authenticated - return null; - } -} - -// Create session -async function login(email, password) { - try { - await account.createEmailPasswordSession(email, password); - return await account.get(); - } catch (error) { - throw new Error(`Login failed: ${error.message}`); - } -} - -// Delete session -async function logout() { - try { - await account.deleteSession('current'); - } catch (error) { - console.error('Logout error:', error); - } -} -``` - -### OAuth Providers - -When implementing OAuth, handle redirects properly: - -```javascript -// Initiate OAuth -async function loginWithOAuth(provider) { - account.createOAuth2Session( - provider, // 'google', 'github', etc. - 'https://yourapp.com/success', // Success redirect - 'https://yourapp.com/failure' // Failure redirect - ); -} - -// Handle OAuth callback (check URL params) -if (window.location.search.includes('success')) { - const user = await account.get(); - // Redirect to dashboard -} -``` - -### SSR Authentication (Server-Side Rendering) - -For SSR frameworks like Next.js, Remix, or SvelteKit, handle authentication on the server using cookies. - -#### Server-Side Client Setup - -Create a server-side client that reads cookies from requests: - -```javascript -// lib/appwrite-server.js -import { Client, Account, TablesDB } from 'appwrite'; -import { cookies } from 'next/headers'; // Next.js App Router -// or: import { parseCookies } from 'nookies'; // Next.js Pages Router - -export function createServerClient() { - const client = new Client() - .setEndpoint(process.env.APPWRITE_ENDPOINT) - .setProject(process.env.APPWRITE_PROJECT_ID); - - return client; -} - -export async function getServerAccount() { - const client = createServerClient(); - const account = new Account(client); - - // Get session cookies from request - const cookieStore = await cookies(); // Next.js App Router - const sessionCookie = cookieStore.get('a_session_'); - - if (sessionCookie) { - // Set session cookie on client - client.setSession(sessionCookie.value); - } - - return account; -} - -// Alternative for Pages Router -export function getServerAccountPages(req) { - const client = createServerClient(); - const account = new Account(client); - - const cookieStore = parseCookies({ req }); - const sessionCookie = cookieStore['a_session_']; - - if (sessionCookie) { - client.setSession(sessionCookie); - } - - return account; -} -``` - -#### Next.js App Router - Server Components - -Authenticate in Server Components and pass user data to client: - -```javascript -// app/dashboard/page.js (Server Component) -import { getServerAccount } from '@/lib/appwrite-server'; -import { redirect } from 'next/navigation'; -import DashboardClient from './dashboard-client'; - -export default async function DashboardPage() { - const account = await getServerAccount(); - - try { - const user = await account.get(); - - // User is authenticated, render dashboard - return ; - } catch (error) { - // User not authenticated, redirect to login - redirect('/login'); - } -} -``` - -#### Next.js App Router - Server Actions - -Handle authentication in Server Actions: - -```javascript -// app/actions/auth.js -'use server'; - -import { getServerAccount } from '@/lib/appwrite-server'; -import { cookies } from 'next/headers'; -import { redirect } from 'next/navigation'; - -export async function loginAction(email, password) { - const account = await getServerAccount(); - - try { - const session = await account.createEmailPasswordSession(email, password); - - // Set session cookie - const cookieStore = await cookies(); - cookieStore.set('a_session_', session.secret, { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'lax', - path: '/', - }); - - redirect('/dashboard'); - } catch (error) { - return { error: error.message }; - } -} - -export async function logoutAction() { - const account = await getServerAccount(); - - try { - await account.deleteSession('current'); - - // Clear session cookie - const cookieStore = await cookies(); - cookieStore.delete('a_session_'); - - redirect('/login'); - } catch (error) { - return { error: error.message }; - } -} -``` - -#### Next.js Pages Router - getServerSideProps - -Authenticate in `getServerSideProps`: - -```javascript -// pages/dashboard.js -import { getServerAccountPages } from '@/lib/appwrite-server'; - -export async function getServerSideProps({ req, res }) { - const account = getServerAccountPages(req); - - try { - const user = await account.get(); - - return { - props: { - user: { - $id: user.$id, - email: user.email, - name: user.name, - }, - }, - }; - } catch (error) { - // Redirect to login if not authenticated - return { - redirect: { - destination: '/login', - permanent: false, - }, - }; - } -} - -export default function Dashboard({ user }) { - return ( -
-

Welcome, {user.name}

-

Email: {user.email}

-
- ); -} -``` - -#### Next.js Middleware - Route Protection - -Protect routes using Next.js middleware: - -```javascript -// middleware.js (Next.js 12+) -import { NextResponse } from 'next/server'; -import { getServerAccount } from '@/lib/appwrite-server'; -import { cookies } from 'next/headers'; - -export async function middleware(request) { - const protectedPaths = ['/dashboard', '/profile', '/settings']; - const isProtectedPath = protectedPaths.some(path => - request.nextUrl.pathname.startsWith(path) - ); - - if (!isProtectedPath) { - return NextResponse.next(); - } - - try { - const account = await getServerAccount(); - await account.get(); - - // User is authenticated, allow request - return NextResponse.next(); - } catch (error) { - // User not authenticated, redirect to login - const loginUrl = new URL('/login', request.url); - loginUrl.searchParams.set('redirect', request.nextUrl.pathname); - return NextResponse.redirect(loginUrl); - } -} - -export const config = { - matcher: ['/dashboard/:path*', '/profile/:path*', '/settings/:path*'], -}; -``` - -#### Server-Side API Routes with Authentication - -Authenticate in API routes: - -```javascript -// app/api/protected/route.js (App Router) -import { getServerAccount } from '@/lib/appwrite-server'; -import { NextResponse } from 'next/server'; - -export async function GET(request) { - try { - const account = await getServerAccount(); - const user = await account.get(); - - // User is authenticated, proceed with request - const data = await fetchProtectedData(user.$id); - - return NextResponse.json({ data }); - } catch (error) { - return NextResponse.json( - { error: 'Unauthorized' }, - { status: 401 } - ); - } -} - -// pages/api/protected.js (Pages Router) -import { getServerAccountPages } from '@/lib/appwrite-server'; - -export default async function handler(req, res) { - try { - const account = getServerAccountPages(req); - const user = await account.get(); - - // User is authenticated, proceed with request - const data = await fetchProtectedData(user.$id); - - res.json({ data }); - } catch (error) { - res.status(401).json({ error: 'Unauthorized' }); - } -} -``` - -#### Cookie Management Best Practices - -1. **HttpOnly cookies** - Always set `httpOnly: true` to prevent XSS attacks -2. **Secure flag** - Use `secure: true` in production (HTTPS only) -3. **SameSite** - Use `sameSite: 'lax'` or `'strict'` for CSRF protection -4. **Cookie name** - Appwrite uses `a_session_` format -5. **Session validation** - Always validate session on server before trusting it - -#### SSR Authentication Helper - -Create a reusable authentication helper: - -```javascript -// lib/auth-server.js -import { getServerAccount } from './appwrite-server'; -import { redirect } from 'next/navigation'; - -export async function requireAuth() { - const account = await getServerAccount(); - - try { - const user = await account.get(); - return { user, account }; - } catch (error) { - redirect('/login'); - } -} - -export async function optionalAuth() { - const account = await getServerAccount(); - - try { - const user = await account.get(); - return { user, account, isAuthenticated: true }; - } catch (error) { - return { user: null, account: null, isAuthenticated: false }; - } -} - -// Usage in Server Components -export default async function ProtectedPage() { - const { user, account } = await requireAuth(); - - // User is guaranteed to be authenticated here - return
Welcome, {user.name}
; -} -``` - -## Teams Management - -Appwrite Teams enable collaborative features and team-based permissions. Always initialize the Teams service alongside other services. - -### SDK Initialization - -Include Teams in your client setup: - -```javascript -import { Client, Account, Teams, TablesDB, Storage } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://cloud.appwrite.io/v1') - .setProject('your-project-id'); - -export const account = new Account(client); -export const teams = new Teams(client); -export const tablesDB = new TablesDB(client); -export const storage = new Storage(client); -``` - -### Creating Teams - -Create teams with proper error handling: - -```javascript -import { ID } from 'appwrite'; - -// Create a new team -async function createTeam(name) { - try { - return await teams.create(ID.unique(), name); - } catch (error) { - throw new Error(`Failed to create team: ${error.message}`); - } -} - -// Create team with custom ID -async function createTeamWithId(teamId, name) { - try { - return await teams.create(teamId, name); - } catch (error) { - if (error.code === 409) { - throw new Error('Team ID already exists'); - } - throw new Error(`Failed to create team: ${error.message}`); - } -} -``` - -### Team Membership Operations - -Manage team members with proper role assignment: - -```javascript -// Add member to team -async function addTeamMember(teamId, email, roles = ['member']) { - try { - // First, get user ID by email (requires server-side with API key) - // Or pass userId directly if you have it - return await teams.createMembership(teamId, email, roles); - } catch (error) { - if (error.code === 409) { - throw new Error('User is already a member of this team'); - } - throw new Error(`Failed to add member: ${error.message}`); - } -} - -// Add member by user ID (server-side only) -async function addTeamMemberById(teamId, userId, roles = ['member']) { - try { - return await teams.createMembership(teamId, undefined, roles, undefined, userId); - } catch (error) { - throw new Error(`Failed to add member: ${error.message}`); - } -} - -// Update member roles -async function updateMemberRoles(teamId, membershipId, roles) { - try { - return await teams.updateMembershipRoles(teamId, membershipId, roles); - } catch (error) { - throw new Error(`Failed to update roles: ${error.message}`); - } -} - -// Remove member from team -async function removeTeamMember(teamId, membershipId) { - try { - await teams.deleteMembership(teamId, membershipId); - } catch (error) { - throw new Error(`Failed to remove member: ${error.message}`); - } -} -``` - -### Listing Teams and Members - -Query teams and members efficiently: - -```javascript -// List user's teams -async function getUserTeams() { - try { - const response = await teams.list(); - return response.teams; - } catch (error) { - throw new Error(`Failed to list teams: ${error.message}`); - } -} - -// Get team details -async function getTeam(teamId) { - try { - return await teams.get(teamId); - } catch (error) { - if (error.code === 404) { - return null; - } - throw new Error(`Failed to get team: ${error.message}`); - } -} - -// List team members -async function getTeamMembers(teamId) { - try { - const response = await teams.listMemberships(teamId); - return response.memberships; - } catch (error) { - throw new Error(`Failed to list members: ${error.message}`); - } -} - -// Get specific membership -async function getMembership(teamId, membershipId) { - try { - return await teams.getMembership(teamId, membershipId); - } catch (error) { - if (error.code === 404) { - return null; - } - throw new Error(`Failed to get membership: ${error.message}`); - } -} -``` - -### Team Roles and Permissions - -Use team roles in database and storage permissions: - -```javascript -import { Permission, Role } from 'appwrite'; - -// Team-based permissions for rows -async function createTeamRow(teamId, data) { - try { - return await tablesDB.createRow( - 'database-id', - 'table-id', - ID.unique(), - data, - [ - // All team members can read - Permission.read(Role.team(teamId)), - // Only team admins can write - Permission.write(Role.team(teamId, 'admin')), - // Only team owners can delete - Permission.delete(Role.team(teamId, 'owner')) - ] - ); - } catch (error) { - throw new Error(`Failed to create row: ${error.message}`); - } -} - -// Team-based permissions for files -async function uploadTeamFile(teamId, file, bucketId) { - try { - return await storage.createFile( - bucketId, - ID.unique(), - file, - [ - Permission.read(Role.team(teamId)), - Permission.write(Role.team(teamId, 'admin')), - Permission.delete(Role.team(teamId, 'owner')) - ] - ); - } catch (error) { - throw new Error(`Failed to upload file: ${error.message}`); - } -} - -// Check if user has specific role in team -async function hasTeamRole(teamId, role) { - try { - const membership = await teams.getMembership(teamId, 'me'); // Get current user's membership - return membership.roles.includes(role); - } catch (error) { - return false; - } -} -``` - -### Team Management Operations - -Update and manage team settings: - -```javascript -// Update team name -async function updateTeamName(teamId, name) { - try { - return await teams.updateName(teamId, name); - } catch (error) { - throw new Error(`Failed to update team name: ${error.message}`); - } -} - -// Delete team -async function deleteTeam(teamId) { - try { - await teams.delete(teamId); - } catch (error) { - throw new Error(`Failed to delete team: ${error.message}`); - } -} - -// Accept team invitation -async function acceptTeamInvitation(teamId, membershipId, userId, secret) { - try { - return await teams.updateMembershipStatus( - teamId, - membershipId, - userId, - secret - ); - } catch (error) { - throw new Error(`Failed to accept invitation: ${error.message}`); - } -} -``` - -### React Hook for Teams - -Create a reusable hook for team management: - -```javascript -import { useState, useEffect } from 'react'; -import { ID } from 'appwrite'; -import { teams } from './appwrite-config'; - -function useTeams() { - const [teamsList, setTeamsList] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - async function fetchTeams() { - try { - const response = await teams.list(); - setTeamsList(response.teams); - } catch (err) { - setError(err.message); - } finally { - setLoading(false); - } - } - - fetchTeams(); - }, []); - - const createTeam = async (name) => { - try { - const newTeam = await teams.create(ID.unique(), name); - setTeamsList([...teamsList, newTeam]); - return newTeam; - } catch (err) { - setError(err.message); - throw err; - } - }; - - return { teams: teamsList, loading, error, createTeam }; -} -``` - -### Server-Side Team Operations - -Handle team operations on the server with API key: - -```javascript -// lib/appwrite-server.js -import { Client, Teams } from 'appwrite'; - -const serverClient = new Client() - .setEndpoint(process.env.APPWRITE_ENDPOINT) - .setProject(process.env.APPWRITE_PROJECT_ID) - .setKey(process.env.APPWRITE_API_KEY); - -export const serverTeams = new Teams(serverClient); - -// Add user to team (server-side) -export async function addUserToTeam(teamId, userId, roles = ['member']) { - try { - return await serverTeams.createMembership( - teamId, - undefined, // email not needed when userId provided - roles, - undefined, // URL not needed for direct add - userId - ); - } catch (error) { - throw new Error(`Failed to add user to team: ${error.message}`); - } -} - -// List all teams (admin operation) -export async function listAllTeams() { - try { - const response = await serverTeams.list(); - return response.teams; - } catch (error) { - throw new Error(`Failed to list teams: ${error.message}`); - } -} -``` - -### Team-Based Access Control - -Implement team-based access control patterns: - -```javascript -import { Query } from 'appwrite'; - -// Check if user can access team resource -async function canAccessTeamResource(teamId, requiredRole = 'member') { - try { - const team = await teams.get(teamId); - const memberships = await teams.listMemberships(teamId); - - // Get current user - const user = await account.get(); - - // Find user's membership - const membership = memberships.find(m => m.userId === user.$id); - - if (!membership) { - return false; - } - - // Check role hierarchy: owner > admin > member - const roleHierarchy = { owner: 3, admin: 2, member: 1 }; - const requiredLevel = roleHierarchy[requiredRole] || 1; - const userLevel = Math.max(...membership.roles.map(r => roleHierarchy[r] || 0)); - - return userLevel >= requiredLevel; - } catch (error) { - return false; - } -} - -// Filter rows by team membership -async function getTeamRows(teamId) { - try { - // First verify user is team member - const canAccess = await canAccessTeamResource(teamId); - if (!canAccess) { - throw new Error('Access denied'); - } - - // Query rows with team permission - return await tablesDB.listRows( - 'database-id', - 'table-id', - [Query.equal('teamId', teamId)] - ); - } catch (error) { - throw new Error(`Failed to get team rows: ${error.message}`); - } -} -``` - -### Team Best Practices - -1. **Role Management** - Use consistent role names: 'owner', 'admin', 'member' -2. **Invitations** - Always handle invitation acceptance flow properly -3. **Permissions** - Combine team roles with specific permissions (e.g., `Role.team(teamId, 'admin')`) -4. **Validation** - Always verify team membership before granting access -5. **Error Handling** - Handle 404 (team not found) and 403 (access denied) appropriately -6. **Team Limits** - Be aware of team size limits and implement pagination for large teams -7. **Cleanup** - Remove team memberships when users are deleted - -## Database Operations - -### Query Patterns - -Use Appwrite's query builder for efficient queries: - -```javascript -import { Query } from 'appwrite'; - -// Single query -const rows = await tablesDB.listRows( - 'database-id', - 'table-id', - [Query.equal('status', 'active')] -); - -// Multiple conditions -const filtered = await tablesDB.listRows( - 'database-id', - 'table-id', - [ - Query.equal('status', 'active'), - Query.greaterThan('createdAt', '2024-01-01'), - Query.limit(10), - Query.orderDesc('createdAt') - ] -); - -// Full-text search -const searchResults = await tablesDB.listRows( - 'database-id', - 'table-id', - [Query.search('title', 'search term')] -); -``` - -### CRUD Operations - -Follow consistent patterns for create, read, update, delete: - -```javascript -import { ID } from 'appwrite'; - -// Create row -async function createRow(data) { - try { - return await tablesDB.createRow( - 'database-id', - 'table-id', - ID.unique(), - data, - [ - Permission.read(Role.user(userId)), // User can read - Permission.write(Role.user(userId)) // User can write - ] - ); - } catch (error) { - throw new Error(`Failed to create: ${error.message}`); - } -} - -// Read row -async function getRow(rowId) { - try { - return await tablesDB.getRow( - 'database-id', - 'table-id', - rowId - ); - } catch (error) { - if (error.code === 404) { - return null; - } - throw error; - } -} - -// Update row -async function updateRow(rowId, data) { - try { - return await tablesDB.updateRow( - 'database-id', - 'table-id', - rowId, - data - ); - } catch (error) { - throw new Error(`Failed to update: ${error.message}`); - } -} - -// Delete row -async function deleteRow(rowId) { - try { - await tablesDB.deleteRow( - 'database-id', - 'table-id', - rowId - ); - } catch (error) { - throw new Error(`Failed to delete: ${error.message}`); - } -} -``` - -### Permissions - -Always set appropriate permissions when creating/updating rows: - -```javascript -import { Permission, Role } from 'appwrite'; - -// User-specific row -const permissions = [ - Permission.read(Role.user(userId)), - Permission.write(Role.user(userId)), - Permission.delete(Role.user(userId)) -]; - -// Public read, authenticated write -const publicPermissions = [ - Permission.read(Role.any()), - Permission.write(Role.users()), -]; - -// Team-based permissions -const teamPermissions = [ - Permission.read(Role.team(teamId)), - Permission.write(Role.team(teamId, 'admin')) -]; -``` - -## Storage Operations - -### File Upload - -Handle file uploads with progress tracking: - -```javascript -import { ID } from 'appwrite'; - -async function uploadFile(file, bucketId) { - try { - return await storage.createFile( - bucketId, - ID.unique(), - file, - [ - Permission.read(Role.any()), // Adjust based on needs - Permission.write(Role.users()) - ], - (progress) => { - console.log(`Upload progress: ${progress.progress}%`); - } - ); - } catch (error) { - throw new Error(`Upload failed: ${error.message}`); - } -} - -// Get file preview/URL -function getFileUrl(bucketId, fileId) { - return storage.getFilePreview(bucketId, fileId); -} - -// Get file download -function getFileDownload(bucketId, fileId) { - return storage.getFileDownload(bucketId, fileId); -} -``` - -### File Management - -```javascript -// List files -async function listFiles(bucketId, queries = []) { - return await storage.listFiles(bucketId, queries); -} - -// Delete file -async function deleteFile(bucketId, fileId) { - await storage.deleteFile(bucketId, fileId); -} - -// Update file -async function updateFile(bucketId, fileId, name) { - return await storage.updateFile(bucketId, fileId, name); -} -``` - -## Error Handling - -Always implement proper error handling: - -```javascript -async function safeAppwriteCall(operation) { - try { - return await operation(); - } catch (error) { - // Handle specific error codes - switch (error.code) { - case 401: - // Unauthorized - redirect to login - window.location.href = '/login'; - break; - case 404: - // Not found - return null; - case 409: - // Conflict (e.g., duplicate email) - throw new Error('Resource already exists'); - case 429: - // Rate limited - throw new Error('Too many requests. Please try again later.'); - default: - console.error('Appwrite error:', error); - throw new Error(`Operation failed: ${error.message}`); - } - } -} - -// Usage -const user = await safeAppwriteCall(() => account.get()); -``` - -## Real-time Subscriptions - -Use real-time listeners for live updates: - -```javascript -import { RealtimeResponseEvent } from 'appwrite'; - -function subscribeToTable(databaseId, tableId, callback) { - return client.subscribe( - `databases.${databaseId}.tables.${tableId}.rows`, - (response) => { - if (response.events.includes('databases.*.tables.*.rows.*.create')) { - callback('create', response.payload); - } else if (response.events.includes('databases.*.tables.*.rows.*.update')) { - callback('update', response.payload); - } else if (response.events.includes('databases.*.tables.*.rows.*.delete')) { - callback('delete', response.payload); - } - } - ); -} - -// Usage -const unsubscribe = subscribeToTable( - 'database-id', - 'table-id', - (event, payload) => { - console.log(`${event} event:`, payload); - } -); - -// Cleanup -// unsubscribe(); -``` - -## Server-Side Functions - -When using Appwrite Functions, follow these patterns: - -```javascript -// Function handler example (Node.js) -export default async ({ req, res, log, error }) => { - try { - const { databaseId, tableId } = JSON.parse(req.body); - - // Use server SDK - const tablesDB = new TablesDB(client); - const rows = await tablesDB.listRows(databaseId, tableId); - - return res.json({ success: true, data: rows }); - } catch (err) { - error(err.message); - return res.json({ success: false, error: err.message }, 500); - } -}; -``` - -## Security Best Practices - -1. **Never expose API keys** - Use environment variables -2. **Validate permissions** - Always set appropriate row/file permissions -3. **Sanitize inputs** - Validate and sanitize user inputs before database operations -4. **Use HTTPS** - Always use HTTPS endpoints in production -5. **Rate limiting** - Implement client-side rate limiting for user-facing operations -6. **Session management** - Implement proper session expiration and refresh logic - -## Performance Optimization - -1. **Pagination** - Always use `Query.limit()` and `Query.offset()` for large datasets -2. **Indexing** - Create database indexes for frequently queried fields -3. **Caching** - Cache frequently accessed data on the client side -4. **Batch operations** - Use batch requests when possible -5. **Selective fields** - Use `Query.select()` to fetch only needed fields - -## Common Patterns - -### React Hook Example - -```javascript -import { useState, useEffect } from 'react'; -import { account } from './appwrite-config'; - -function useAuth() { - const [user, setUser] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - account.get() - .then(setUser) - .catch(() => setUser(null)) - .finally(() => setLoading(false)); - }, []); - - return { user, loading }; -} -``` - -### Next.js API Route Example - -```javascript -// pages/api/data.js or app/api/data/route.js -import { tablesDB } from '@/lib/appwrite-server'; - -export default async function handler(req, res) { - if (req.method !== 'GET') { - return res.status(405).json({ error: 'Method not allowed' }); - } - - try { - const rows = await tablesDB.listRows( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_TABLE_ID - ); - res.json(rows); - } catch (error) { - res.status(500).json({ error: error.message }); - } -} -``` - -## Testing Patterns - -When writing tests, mock Appwrite SDK: - -```javascript -// Mock Appwrite in tests -jest.mock('appwrite', () => ({ - Client: jest.fn(), - Account: jest.fn(() => ({ - get: jest.fn(), - createEmailPasswordSession: jest.fn(), - })), - TablesDB: jest.fn(() => ({ - listRows: jest.fn(), - createRow: jest.fn(), - })), -})); -``` From 4181a43631fd380f912b76033a21d1ac3f07a148 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 21:01:38 +0530 Subject: [PATCH 03/19] Add multitenancy guideline to rules generator --- src/lib/rules-generator.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index 2d91620..fe3f8d9 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -191,6 +191,7 @@ ${authProductLinks} - **API Keys**: Never expose API keys to client-side code - use environment variables - **Session Validation**: Always validate sessions on the server before trusting them - **Team Permissions**: Use team roles for granular access control in multi-tenant applications +- **Multitenancy**: Use team-based permissions when a user requires multitenancy to properly isolate data and resources between tenants - **OAuth Redirects**: Handle OAuth redirects properly with success and failure URLs - **Password Security**: Use strong password requirements and consider implementing MFA - **Session Expiry**: Configure appropriate session expiry times based on your security requirements`; From ad127804f438ad45056abf41e1cb1b81bb1f3d42 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 21:18:51 +0530 Subject: [PATCH 04/19] Add Apple and Android SDK support to rules generator; update installation templates and quick start URLs --- src/lib/languages/android/index.js | 47 ++++++++++++++++++++++++++++++ src/lib/languages/apple/index.js | 37 +++++++++++++++++++++++ src/lib/languages/common/utils.js | 12 ++++++-- src/lib/languages/index.js | 2 ++ src/lib/languages/swift/index.js | 11 +++---- src/lib/rules-generator.js | 20 +++++++++++-- 6 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 src/lib/languages/android/index.js create mode 100644 src/lib/languages/apple/index.js diff --git a/src/lib/languages/android/index.js b/src/lib/languages/android/index.js new file mode 100644 index 0000000..bee6839 --- /dev/null +++ b/src/lib/languages/android/index.js @@ -0,0 +1,47 @@ +import { getSDKVersion } from '$lib/utils/versions.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurity } from '../common/security.js'; + +/** + * Generates the Android SDK installation template with the latest version + * @param {string} version - The SDK version to use + * @returns {string} + */ +function generateInstallationTemplate(version) { + return `## SDK Installation + +Add the Appwrite Android SDK to your \`build.gradle.kts\`: + +**Recommended: Specify exact version for stability** + +\`\`\`kotlin +dependencies { + implementation("io.appwrite:sdk-for-android:${version}") +} +\`\`\` + +Or for Maven, add to \`pom.xml\`: + +**Recommended: Specify exact version** + +\`\`\`xml + + io.appwrite + sdk-for-android + ${version} + +\`\`\` +`; +} + +/** + * Gets the Android SDK installation template with the latest version from Appwrite's API + * This is the main export used by the rules generator + * @returns {Promise} + */ +export const vanilla = async () => { + const version = await getSDKVersion('client-android'); + const installation = generateInstallationTemplate(version); + return createFrameworkTemplate({ installation, securityNotes: clientSecurity }); +}; + diff --git a/src/lib/languages/apple/index.js b/src/lib/languages/apple/index.js new file mode 100644 index 0000000..d477e88 --- /dev/null +++ b/src/lib/languages/apple/index.js @@ -0,0 +1,37 @@ +import { getSDKVersion } from '$lib/utils/versions.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurity } from '../common/security.js'; + +/** + * Generates the Apple SDK installation template with the latest version + * @param {string} version - The SDK version to use + * @returns {string} + */ +function generateInstallationTemplate(version) { + return `## SDK Installation + +Add the Appwrite Apple SDK to your \`Package.swift\`: + +\`\`\`swift +dependencies: [ + .package(url: "https://github.com/appwrite/sdk-for-apple", from: "${version}") +] +\`\`\` + +Or add it via Xcode: +1. File → Add Packages... +2. Enter: \`https://github.com/appwrite/sdk-for-apple\` +3. Select version: \`${version}\` or later`; +} + +/** + * Gets the Apple SDK installation template with the latest version from Appwrite's API + * This is the main export used by the rules generator + * @returns {Promise} + */ +export const vanilla = async () => { + const version = await getSDKVersion('client-apple'); + const installation = generateInstallationTemplate(version); + return createFrameworkTemplate({ installation, securityNotes: clientSecurity }); +}; + diff --git a/src/lib/languages/common/utils.js b/src/lib/languages/common/utils.js index 058d9af..fcf1f16 100644 --- a/src/lib/languages/common/utils.js +++ b/src/lib/languages/common/utils.js @@ -52,6 +52,10 @@ export const quickStartUrls = { // React Native 'react-native': 'https://appwrite.io/docs/quick-starts/react-native', + // Mobile Client SDKs + apple: 'https://appwrite.io/docs/quick-starts/apple', + android: 'https://appwrite.io/docs/quick-starts/android', + // Server SDKs python: 'https://appwrite.io/docs/quick-starts/python', php: 'https://appwrite.io/docs/quick-starts/php', @@ -60,7 +64,8 @@ export const quickStartUrls = { dotnet: 'https://appwrite.io/docs/quick-starts/dotnet', dart: 'https://appwrite.io/docs/quick-starts/dart', flutter: 'https://appwrite.io/docs/quick-starts/flutter', - kotlin: 'https://appwrite.io/docs/quick-starts/kotlin' + kotlin: 'https://appwrite.io/docs/quick-starts/kotlin', + swift: 'https://appwrite.io/docs/quick-starts/swift' }; /** @@ -80,6 +85,8 @@ export const frameworkNames = { nodejs: 'Node.js', vanilla: 'Web', 'react-native': 'React Native', + apple: 'Apple', + android: 'Android', python: 'Python', php: 'PHP', go: 'Go', @@ -87,6 +94,7 @@ export const frameworkNames = { dotnet: '.NET', dart: 'Dart', flutter: 'Flutter', - kotlin: 'Kotlin' + kotlin: 'Kotlin', + swift: 'Swift' }; diff --git a/src/lib/languages/index.js b/src/lib/languages/index.js index c2eee88..060ee08 100644 --- a/src/lib/languages/index.js +++ b/src/lib/languages/index.js @@ -8,4 +8,6 @@ export * as kotlin from './kotlin/index.js'; export * as reactNative from './react-native/index.js'; export * as ruby from './ruby/index.js'; export * as dotnet from './dotnet/index.js'; +export * as apple from './apple/index.js'; +export * as android from './android/index.js'; diff --git a/src/lib/languages/swift/index.js b/src/lib/languages/swift/index.js index 8bcce6a..4e33efc 100644 --- a/src/lib/languages/swift/index.js +++ b/src/lib/languages/swift/index.js @@ -1,5 +1,6 @@ import { getSDKVersion } from '$lib/utils/versions.js'; import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; /** * Generates the Swift SDK installation template with the latest version @@ -9,17 +10,17 @@ import { createFrameworkTemplate } from '../common/utils.js'; function generateInstallationTemplate(version) { return `## SDK Installation -Add the Appwrite Swift SDK to your \`Package.swift\`: +Add the Appwrite Swift Server SDK to your \`Package.swift\`: \`\`\`swift dependencies: [ - .package(url: "https://github.com/appwrite/sdk-for-apple", from: "${version}") + .package(url: "https://github.com/appwrite/sdk-for-swift", from: "${version}") ] \`\`\` Or add it via Xcode: 1. File → Add Packages... -2. Enter: \`https://github.com/appwrite/sdk-for-apple\` +2. Enter: \`https://github.com/appwrite/sdk-for-swift\` 3. Select version: \`${version}\` or later`; } @@ -29,8 +30,8 @@ Or add it via Xcode: * @returns {Promise} */ export const vanilla = async () => { - const version = await getSDKVersion('client-apple'); + const version = await getSDKVersion('server-swift'); const installation = generateInstallationTemplate(version); - return createFrameworkTemplate({ installation, securityNotes: '' }); + return createFrameworkTemplate({ installation, securityNotes: serverSecurity }); }; diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index fe3f8d9..122aea0 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -46,16 +46,30 @@ export const SDK_OPTIONS = { exportSyntax: 'class', asyncSyntax: 'Future' }, + apple: { + name: 'Apple', + frameworks: ['vanilla'], + importSyntax: 'import', + exportSyntax: 'func', + asyncSyntax: 'async' + }, + android: { + name: 'Android', + frameworks: ['vanilla'], + importSyntax: 'import', + exportSyntax: 'fun', + asyncSyntax: 'suspend' + }, swift: { name: 'Swift', - frameworks: ['ios', 'vanilla'], + frameworks: ['server', 'vanilla'], importSyntax: 'import', exportSyntax: 'func', asyncSyntax: 'async' }, kotlin: { name: 'Kotlin', - frameworks: ['android', 'vanilla'], + frameworks: ['server', 'vanilla'], importSyntax: 'import', exportSyntax: 'fun', asyncSyntax: 'suspend' @@ -142,6 +156,8 @@ async function generateSDKInitialization(sdk, framework) { php: codeExamples.php, go: codeExamples.go, flutter: codeExamples.dart, + apple: codeExamples.apple, + android: codeExamples.android, swift: codeExamples.swift, kotlin: codeExamples.kotlin, ruby: codeExamples.ruby, From 50a18294191e3b2be0f71617835423a938772b81 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 21:18:58 +0530 Subject: [PATCH 05/19] Update Dart SDK installation instructions and enhance Qwik security notes with server-side guidelines --- src/lib/languages/common/install.js | 3 ++- src/lib/languages/js/qwik.js | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/lib/languages/common/install.js b/src/lib/languages/common/install.js index e84f9d5..ec1d33d 100644 --- a/src/lib/languages/common/install.js +++ b/src/lib/languages/common/install.js @@ -129,6 +129,7 @@ Install-Package Appwrite export const dartInstall = (version, isServer = false) => { const sdkName = isServer ? 'Dart Server SDK' : 'Flutter SDK'; const pubCommand = isServer ? 'dart pub get' : 'flutter pub get'; + const packageName = isServer ? 'dart_appwrite' : 'appwrite'; return `## SDK Installation @@ -136,7 +137,7 @@ Add the Appwrite ${sdkName} to your \`pubspec.yaml\`: \`\`\`yaml dependencies: - appwrite: ^${version} + ${packageName}: ^${version} \`\`\` Then install it: diff --git a/src/lib/languages/js/qwik.js b/src/lib/languages/js/qwik.js index 005ec60..4da9355 100644 --- a/src/lib/languages/js/qwik.js +++ b/src/lib/languages/js/qwik.js @@ -1,10 +1,17 @@ import { jsInstallDefault as jsInstall } from '../common/install.js'; import { createFrameworkTemplate } from '../common/utils.js'; -import { clientSecurity, authNote } from '../common/security.js'; +import { clientSecurityWithEnv, authNote } from '../common/security.js'; export const qwik = createFrameworkTemplate({ installation: jsInstall, - securityNotes: clientSecurity, + securityNotes: `${clientSecurityWithEnv('.env')} + +**Server-Side Security (Critical for Qwik):** +- API keys should NEVER be exposed to client-side code +- Separate client and server code: protect API keys on server, never expose them to client +- Use route loaders (\`routeLoader$\`) or server endpoints (\`server$\`) for secret operations +- For operations requiring API keys, use server actions or route loaders that run only on the server +- Client-side code should only use public endpoint and project ID`, additionalNotes: authNote }); From 8684766c1c313e6acfc96ea71fb0e2f1422e7325 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 22:39:49 +0530 Subject: [PATCH 06/19] Change "real-time" to "realtime" --- src/lib/languages/common/mcp.js | 2 +- src/lib/languages/common/products.js | 8 ++++---- src/lib/rules-generator.js | 8 ++++---- src/routes/+page.svelte | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/lib/languages/common/mcp.js b/src/lib/languages/common/mcp.js index 51b9c1a..556aa87 100644 --- a/src/lib/languages/common/mcp.js +++ b/src/lib/languages/common/mcp.js @@ -42,7 +42,7 @@ export function generateMCPRecommendation() { ### Benefits Once installed, you can ask questions like: -- "How do I set up real-time subscriptions in Appwrite?" +- "How do I set up realtime subscriptions in Appwrite?" - "Show me how to authenticate users with OAuth" - "What are the best practices for database queries?" - "How do I implement file uploads with Appwrite Storage?" diff --git a/src/lib/languages/common/products.js b/src/lib/languages/common/products.js index cc620dd..4077324 100644 --- a/src/lib/languages/common/products.js +++ b/src/lib/languages/common/products.js @@ -103,9 +103,9 @@ For instant rollbacks, see the [Instant Rollbacks Guide](https://appwrite.io/doc For deployment previews, see the [Previews Documentation](https://appwrite.io/docs/products/sites/previews).`; /** - * Real-time product documentation + * Realtime product documentation */ -export const realtimeProductLinks = `For detailed real-time subscriptions, see the [Real-time Documentation](https://appwrite.io/docs/products/realtime). +export const realtimeProductLinks = `For detailed subscriptions, see the [Realtime Documentation](https://appwrite.io/docs/products/realtime). For subscribing to database changes, see the [Database Subscriptions Guide](https://appwrite.io/docs/products/realtime/subscribe-to-databases). @@ -113,7 +113,7 @@ For subscribing to storage changes, see the [Storage Subscriptions Guide](https: For subscribing to account changes, see the [Account Subscriptions Guide](https://appwrite.io/docs/products/realtime/subscribe-to-account). -For real-time channels and events, see the [Channels Documentation](https://appwrite.io/docs/products/realtime/channels). +For realtime channels and events, see the [Channels Documentation](https://appwrite.io/docs/products/realtime/channels). -For real-time event types and payloads, see the [Events Documentation](https://appwrite.io/docs/products/realtime/events).`; +For realtime event types and payloads, see the [Events Documentation](https://appwrite.io/docs/products/realtime/events).`; diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index 122aea0..af353f9 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -323,11 +323,11 @@ ${sitesProductLinks} */ async function generateRealtimeSection(sdk, framework) { const { realtimeProductLinks } = await import('./languages/common/products.js'); - return `## Real-time Subscriptions + return `## Realtime Subscriptions ${realtimeProductLinks} -### Best Practices for Real-time Subscriptions +### Best Practices for Realtime Subscriptions - **Connection Management**: Always unsubscribe from channels when components unmount or pages are closed to prevent memory leaks - **Error Handling**: Implement reconnection logic for dropped connections and handle network errors gracefully @@ -335,9 +335,9 @@ ${realtimeProductLinks} - **Channel Selection**: Subscribe only to the specific channels you need to minimize bandwidth and improve performance - **Payload Validation**: Always validate payload data before processing to ensure data integrity - **Rate Limiting**: Be mindful of the number of subscriptions and events to avoid overwhelming the client -- **State Synchronization**: Use real-time updates to keep local state in sync with server state, but handle conflicts appropriately +- **State Synchronization**: Use realtime updates to keep local state in sync with server state, but handle conflicts appropriately - **Authentication**: Ensure proper authentication is in place before subscribing to protected channels -- **Testing**: Test real-time functionality with network interruptions and reconnection scenarios +- **Testing**: Test realtime functionality with network interruptions and reconnection scenarios - **Cleanup**: Store unsubscribe functions and call them in cleanup hooks (useEffect cleanup, componentWillUnmount, etc.)`; } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5ade608..2175202 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -15,7 +15,7 @@ { id: 'functions', label: 'Functions' }, { id: 'messaging', label: 'Messaging' }, { id: 'sites', label: 'Sites' }, - { id: 'realtime', label: 'Real-time' } + { id: 'realtime', label: 'Realtime' } ]; function updateFrameworks() { From 90b50052675ddc72af1885f40f15e59d3c030328 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 22:56:05 +0530 Subject: [PATCH 07/19] update favicon to appwrite logo --- src/lib/assets/favicon.svg | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib/assets/favicon.svg b/src/lib/assets/favicon.svg index cc5dc66..bd847aa 100644 --- a/src/lib/assets/favicon.svg +++ b/src/lib/assets/favicon.svg @@ -1 +1,8 @@ -svelte-logo \ No newline at end of file + + + + \ No newline at end of file From 0d0510285bdf21736e265f54c25f74f69db08717 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Fri, 28 Nov 2025 01:46:35 +0530 Subject: [PATCH 08/19] Add MCP section to rules generator and update package.json with Next.js generation script; include MCP in framework options --- package.json | 3 +- scripts/generate-nextjs-rules.js | 32 ++ scripts/lib-loader.js | 22 ++ src/lib/rules-generator.js | 510 +++++++++++++++++++++++++++++-- src/routes/+page.svelte | 3 +- 5 files changed, 549 insertions(+), 21 deletions(-) create mode 100644 scripts/generate-nextjs-rules.js create mode 100644 scripts/lib-loader.js diff --git a/package.json b/package.json index 6cbc568..665662b 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "format": "prettier --write .", - "lint": "prettier --check . && eslint ." + "lint": "prettier --check . && eslint .", + "generate:nextjs": "node --loader ./scripts/lib-loader.js scripts/generate-nextjs-rules.js" }, "devDependencies": { "@eslint/compat": "^1.4.0", diff --git a/scripts/generate-nextjs-rules.js b/scripts/generate-nextjs-rules.js new file mode 100644 index 0000000..1a4b7cb --- /dev/null +++ b/scripts/generate-nextjs-rules.js @@ -0,0 +1,32 @@ +import { generateRules } from '../src/lib/rules-generator.js'; +import { writeFile } from 'fs/promises'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +async function generateNextJSRules() { + try { + console.log('Generating Next.js rules with all products enabled...'); + + const rules = await generateRules({ + sdk: 'javascript', + framework: 'nextjs', + features: ['auth', 'database', 'storage', 'functions', 'messaging', 'sites', 'realtime'], + includeMCP: true + }); + + const outputPath = join(__dirname, '..', 'APPWRITE-javascript-nextjs.mdc'); + await writeFile(outputPath, rules, 'utf-8'); + + console.log(`Rules generated successfully!`); + console.log(`Output file: ${outputPath}`); + } catch (error) { + console.error('Error generating rules:', error); + process.exit(1); + } +} + +generateNextJSRules(); + diff --git a/scripts/lib-loader.js b/scripts/lib-loader.js new file mode 100644 index 0000000..22b6fe9 --- /dev/null +++ b/scripts/lib-loader.js @@ -0,0 +1,22 @@ +// Custom Node.js loader to resolve $lib aliases +import { pathToFileURL } from 'node:url'; +import { resolve as resolvePath } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const projectRoot = resolvePath(__dirname, '..'); + +export async function resolve(specifier, context, nextResolve) { + if (specifier.startsWith('$lib/')) { + const relativePath = specifier.replace('$lib/', ''); + const absolutePath = resolvePath(projectRoot, 'src', 'lib', relativePath); + const fileUrl = pathToFileURL(absolutePath).href; + return { + url: fileUrl, + format: 'module', + shortCircuit: true + }; + } + return nextResolve(specifier, context); +} + diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index af353f9..c81b805 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -14,6 +14,7 @@ import { generateMCPRecommendation } from './languages/common/mcp.js'; * @property {string} sdk * @property {string} framework * @property {string[]} features + * @property {boolean} [includeMCP] - Whether to include MCP recommendation section */ /** @type {Record} */ @@ -109,14 +110,16 @@ export const SDK_OPTIONS = { * @returns {Promise} */ export async function generateRules(config) { - const { sdk, framework, features } = config; + const { sdk, framework, features, includeMCP = false } = config; const sdkInfo = SDK_OPTIONS[sdk]; const sdkInit = await generateSDKInitialization(sdk, framework); // Generate all sections in parallel + // Permissions section is mandatory for all products const sections = await Promise.all([ features.includes('auth') ? generateAuthSection(sdk, framework) : Promise.resolve(''), + generatePermissionsSection(sdk, framework), features.includes('database') ? generateDatabaseSection(sdk, framework) : Promise.resolve(''), features.includes('storage') ? generateStorageSection(sdk, framework) : Promise.resolve(''), features.includes('functions') ? generateFunctionsSection(sdk, framework) : Promise.resolve(''), @@ -125,6 +128,8 @@ export async function generateRules(config) { features.includes('realtime') ? generateRealtimeSection(sdk, framework) : Promise.resolve('') ]); + const mcpSection = includeMCP ? `${generateMCPRecommendation()}\n\n` : ''; + let rules = `--- description: You are an expert developer focused on building apps with Appwrite's ${sdkInfo?.name || sdk} SDK. alwaysApply: false @@ -132,9 +137,7 @@ alwaysApply: false # Appwrite Development Rules -${generateMCPRecommendation()} - -${sdkInit} +${mcpSection}${sdkInit} ${sections.join('\n\n')} `; @@ -206,11 +209,409 @@ ${authProductLinks} - **Session Security**: Always use HttpOnly cookies for session storage in SSR applications - **API Keys**: Never expose API keys to client-side code - use environment variables - **Session Validation**: Always validate sessions on the server before trusting them -- **Team Permissions**: Use team roles for granular access control in multi-tenant applications -- **Multitenancy**: Use team-based permissions when a user requires multitenancy to properly isolate data and resources between tenants +- **Team-Based Architecture**: ALWAYS prefer team/member-based roles over user-specific roles for any application requiring shared access or multi-tenancy +- **Multi-Tenant Applications**: Use teams as the primary mechanism for tenant isolation and resource sharing - **OAuth Redirects**: Handle OAuth redirects properly with success and failure URLs - **Password Security**: Use strong password requirements and consider implementing MFA -- **Session Expiry**: Configure appropriate session expiry times based on your security requirements`; +- **Session Expiry**: Configure appropriate session expiry times based on your security requirements + +### Team & Member Management Fundamentals + +When building applications that involve multiple users or tenants: + +1. **Always Start with Teams**: For any feature requiring shared access, create a team first, then add members with roles +2. **Role-Based Access**: Assign roles (e.g., "owner", "admin", "member", "viewer") to team members rather than setting individual user permissions +3. **Team Isolation**: Use teams as the boundary for data isolation in multi-tenant applications +4. **Member Invitations**: Implement team invitation workflows for onboarding new members +5. **Role Management**: Build role management UIs that allow team owners/admins to manage member roles dynamically`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generatePermissionsSection(sdk, framework) { + const { authProductLinks } = await import('./languages/common/products.js'); + return `## Permissions & Multi-Tenancy + +This section is CRITICAL for building secure, scalable applications with Appwrite. Multi-tenancy is one of the most important architectural patterns in modern applications, and Appwrite's team-based permission system is designed specifically for this. + +### Why Multi-Tenancy Matters + +Multi-tenancy allows a single application instance to serve multiple isolated groups of users (tenants) while maintaining complete data isolation and security. Almost every modern SaaS application requires multi-tenancy to scale efficiently. + +### Team/Member Roles vs User-Specific Roles: The Critical Distinction + +**ALWAYS PREFER TEAM/MEMBER-BASED ROLES over user-specific roles.** This is a fundamental architectural decision: + +#### Avoid: User-Specific Permissions +\`\`\` +// DON'T do this for multi-tenant apps +create(collectionId, data, [ + Permission.read(Role.user(userId1)), + Permission.write(Role.user(userId1)) +]) +\`\`\` + +**Problems with user-specific permissions:** +- Hard to scale when users need to share resources +- Difficult to add/remove access without updating every document +- No way to represent organizational hierarchies +- Poor support for collaborative features +- Maintenance nightmare as teams grow + +#### Prefer: Team/Member-Based Roles +\`\`\` +// DO this for multi-tenant apps +create(collectionId, data, [ + Permission.read(Role.team(teamId, "owner")), + Permission.read(Role.team(teamId, "admin")), + Permission.read(Role.team(teamId, "member")), + Permission.update(Role.team(teamId, "owner")), + Permission.update(Role.team(teamId, "admin")), + Permission.delete(Role.team(teamId, "owner")) +]) +\`\`\` + +**Benefits of team/member-based roles:** +- Automatic access for all team members based on their role +- Easy to add/remove members without touching documents +- Scales naturally as teams grow +- Supports organizational hierarchies and complex permissions +- Industry-standard pattern for SaaS applications + +### Building Multi-Tenant Applications from Scratch + +#### Step 1: Create Teams Structure + +Teams in Appwrite represent tenants. Each team should map to a business entity (company, organization, workspace, etc.). + +**Creating a team:** +\`\`\` +import { Teams } from 'appwrite'; + +// Create a team when a new tenant/organization signs up +const team = await teams.create( + teamId, // Unique team ID (can be auto-generated) + teamName, // Display name + roles // Array of role strings: ['owner', 'admin', 'member'] +); +\`\`\` + +#### Step 2: Define Custom Roles + +Create roles that match your application's permission model. Common roles: +- **owner**: Full control, can manage team settings and members +- **admin**: Can manage resources and most settings, but not team membership +- **member**: Can create/edit resources, but with limited permissions +- **viewer**: Read-only access + +**Creating custom roles (Server-side only):** +\`\`\` +import { Teams } from 'appwrite'; + +// Define roles when creating the team (optional, defaults exist) +// Or create via Appwrite Console or Server SDK +// Roles are created per team, allowing different permission models per tenant +\`\`\` + +#### Step 3: Member Management from Scratch + +Member management is the foundation of multi-tenant applications. Here's how to build it: + +**A. Invite Members to Teams** + +\`\`\` +import { Teams } from 'appwrite'; + +// Send team invitation (email-based) +const invite = await teams.createMembership( + teamId, + email, // Email of user to invite + roles, // Array of role strings: ['admin', 'member'] + url // Invitation redirect URL +); + +// Or invite by user ID (if user already exists) +const membership = await teams.createMembership( + teamId, + userId, + roles +); +\`\`\` + +**B. List Team Members** + +\`\`\` +import { Teams } from 'appwrite'; + +// Get all members of a team +const memberships = await teams.listMemberships(teamId); + +// Access member data +memberships.memberships.forEach(membership => { + console.log(membership.userId); + console.log(membership.roles); // Array of role strings + console.log(membership.userName); + console.log(membership.userEmail); +}); +\`\`\` + +**C. Update Member Roles** + +\`\`\` +import { Teams } from 'appwrite'; + +// Update a member's roles (only team owners/admins can do this) +await teams.updateMembershipRoles( + teamId, + membershipId, + ['admin', 'member'] // New roles array +); +\`\`\` + +**D. Remove Members** + +\`\`\` +import { Teams } from 'appwrite'; + +// Remove a member from a team +await teams.deleteMembership(teamId, membershipId); +\`\`\` + +**E. Get Current User's Teams** + +\`\`\` +import { Teams } from 'appwrite'; + +// List all teams the current user belongs to +const teams = await teams.list(); + +teams.teams.forEach(team => { + console.log(team.$id); + console.log(team.name); +}); +\`\`\` + +**F. Get Current User's Role in a Team** + +\`\`\` +import { Teams } from 'appwrite'; + +// Get membership details for current user in a specific team +const memberships = await teams.listMemberships(teamId); + +const userMembership = memberships.memberships.find( + m => m.userId === currentUserId +); + +if (userMembership) { + console.log(userMembership.roles); // ['owner', 'admin', etc.] + const hasAdminRole = userMembership.roles.includes('admin'); +} +\`\`\` + +#### Step 4: Apply Permissions in Collections + +When creating documents in multi-tenant applications, always use team roles: + +**Database Collections:** + +\`\`\` +import { TablesDB, Permission, Role } from 'appwrite'; + +// Create document with team-based permissions +await tablesdb.createRow( + databaseId, + tableId, + documentId, + { + title: 'My Document', + teamId: teamId, // Always store teamId for querying + // ... other fields + }, + [ + // Owners and admins can do everything + Permission.read(Role.team(teamId, "owner")), + Permission.read(Role.team(teamId, "admin")), + Permission.read(Role.team(teamId, "member")), + Permission.update(Role.team(teamId, "owner")), + Permission.update(Role.team(teamId, "admin")), + Permission.delete(Role.team(teamId, "owner")), + Permission.delete(Role.team(teamId, "admin")) + ] +); +\`\`\` + +**Collection-Level Permissions:** + +When creating collections, set default permissions: + +\`\`\` +import { TablesDB, Permission, Role } from 'appwrite'; + +// Create collection with team-based permissions +await tablesdb.createTable( + databaseId, + tableId, + tableName, + [ + // Collection permissions + Permission.create(Role.team(teamId, "member")), + Permission.read(Role.team(teamId, "member")), + Permission.update(Role.team(teamId, "admin")), + Permission.delete(Role.team(teamId, "owner")) + ] +); +\`\`\` + +#### Step 5: Query with Team Isolation + +Always filter queries by teamId to ensure data isolation: + +\`\`\` +import { TablesDB, Query } from 'appwrite'; + +// ALWAYS filter by teamId to ensure tenant isolation +const documents = await tablesdb.listDocuments( + databaseId, + tableId, + [ + Query.equal('teamId', teamId), // Critical: filter by team + Query.orderDesc('$createdAt'), + Query.limit(25) + ] +); +\`\`\` + +#### Step 6: Storage Permissions + +Apply the same team-based permission pattern to storage: + +\`\`\` +import { Storage, Permission, Role } from 'appwrite'; + +// Create file with team-based permissions +await storage.createFile( + bucketId, + fileId, + fileInput, + [ + Permission.read(Role.team(teamId, "member")), + Permission.update(Role.team(teamId, "admin")), + Permission.delete(Role.team(teamId, "owner")) + ] +); +\`\`\` + +### Complete Member Management Implementation Pattern + +Here's a complete pattern for building member management UI and logic: + +**1. Team Creation Flow:** +\`\`\` +// When user creates account/organization +const team = await teams.create(uniqueId(), 'Company Name'); +// Make creator an owner +await teams.createMembership(team.$id, userId, ['owner']); +\`\`\` + +**2. Invite Flow:** +\`\`\` +// Owner/admin invites new member +const invite = await teams.createMembership( + teamId, + email, + ['member'], // Default role + 'https://yourapp.com/accept-invite' // Redirect after accepting +); +// User receives email, clicks link, accepts invitation +\`\`\` + +**3. Member List UI:** +\`\`\` +// Display all team members with their roles +const memberships = await teams.listMemberships(teamId); +// Show list with role badges and action buttons +\`\`\` + +**4. Role Change:** +\`\`\` +// Admin/owner changes member role +await teams.updateMembershipRoles(teamId, membershipId, ['admin']); +\`\`\` + +**5. Member Removal:** +\`\`\` +// Remove member (with confirmation) +await teams.deleteMembership(teamId, membershipId); +\`\`\` + +### Permission Best Practices + +1. **Always Store teamId**: Every document/resource in a multi-tenant app should have a \`teamId\` field for filtering and isolation + +2. **Default Deny**: Don't grant permissions unless explicitly needed. Use minimal permission sets. + +3. **Role Hierarchy**: Design your roles to reflect natural hierarchies (owner > admin > member > viewer) + +4. **Permission Consistency**: Use the same permission pattern across database, storage, and other resources + +5. **Server-Side Validation**: Always validate team membership on the server side, even if client has permissions + +6. **Query Isolation**: Always include \`teamId\` in queries to prevent cross-tenant data leaks + +7. **Role Checks**: Before allowing sensitive operations, check the user's role in the team: + \`\`\` + const membership = await getCurrentUserMembership(teamId); + if (!membership.roles.includes('admin')) { + throw new Error('Insufficient permissions'); + } + \`\`\` + +8. **Permission Inheritance**: Consider if child resources should inherit parent team permissions + +9. **Document-Level Permissions**: For fine-grained control, set permissions on individual documents while still using team roles + +10. **Audit Trail**: Log permission changes and team membership changes for security auditing + +### Common Multi-Tenancy Patterns + +**Pattern 1: Workspace-Based (e.g., Notion, Slack)** +- Each workspace is a team +- Users can belong to multiple teams +- Resources belong to one team +- Perfect for: Collaboration tools, project management + +**Pattern 2: Organization-Based (e.g., GitHub, GitLab)** +- Each organization is a team +- Resources belong to organization +- Members have roles within organization +- Perfect for: Enterprise SaaS, developer tools + +**Pattern 3: Project-Based (e.g., Linear, Asana)** +- Each project is a team +- Resources scoped to project +- Members invited per project +- Perfect for: Project management, task tracking + +### Debugging Permission Issues + +When permissions aren't working: + +1. **Check Team Membership**: Verify user is actually a member of the team +2. **Verify Roles**: Ensure user has the required role (check \`membership.roles\`) +3. **Check Permission Strings**: Verify permission strings match exactly (case-sensitive) +4. **Query Filters**: Ensure \`teamId\` filters are applied correctly +5. **Server vs Client**: Some operations require server SDK (like creating custom roles) +6. **Session Context**: Permissions are evaluated in the context of the current session + +### Additional Resources + +${authProductLinks} + +For comprehensive permission patterns and examples, always refer to the official Appwrite documentation on Teams, Multi-tenancy, and Permissions.`; } /** @@ -226,12 +627,17 @@ ${databaseProductLinks} ### Best Practices for Databases -- **Permissions**: Always set appropriate permissions at table and row levels -- **Query Optimization**: Use indexes for frequently queried fields to improve performance -- **Data Validation**: Validate data before creating or updating rows -- **Transactions**: Use transactions for operations that must succeed or fail together -- **Pagination**: Always implement pagination for large datasets to improve performance -- **Type Safety**: Use type-safe models when available in your SDK for better code quality`; +- **SDK Usage**: Always use \`TablesDB\` instead of \`Databases\` in the SDKs +- **Permissions & Multi-Tenancy**: ALWAYS use team/member-based roles for permissions (see Permissions & Multi-Tenancy section above). Never use user-specific permissions in multi-tenant applications +- **Tenant Isolation**: Always include \`teamId\` fields in your documents and filter queries by \`teamId\` to ensure complete data isolation between tenants +- **Permission Patterns**: Apply team roles (owner, admin, member, viewer) consistently across all collections. Use Role.team() for all permission checks +- **Query Security**: Every multi-tenant query MUST include a \`teamId\` filter to prevent cross-tenant data access +- **Collection Permissions**: Set collection-level permissions using team roles, then override at document level when needed +- **Query Optimization**: Use indexes for frequently queried fields, especially on \`teamId\` and commonly filtered fields +- **Data Validation**: Validate data before creating or updating rows, including team membership validation +- **Transactions**: Use transactions for operations that must succeed or fail together, ensuring atomicity across tenant boundaries +- **Pagination**: Always implement pagination for large datasets to improve performance and reduce response sizes +- **Type Safety**: Use type-safe models when available in your SDK for better code quality and fewer runtime errors`; } /** @@ -247,11 +653,60 @@ ${storageProductLinks} ### Best Practices for Storage -- **File Size Limits**: Set appropriate file size limits to prevent abuse -- **File Types**: Validate file types before upload to ensure security -- **Permissions**: Set proper permissions on buckets and files to control access -- **Cleanup**: Implement cleanup strategies for unused or temporary files -- **Virus Scanning**: Consider implementing virus scanning for uploaded files`; +- **Permissions & Multi-Tenancy**: ALWAYS use team/member-based roles for storage permissions (see Permissions & Multi-Tenancy section above). Apply Role.team() permissions to buckets and files for proper tenant isolation +- **Bucket Organization**: Consider organizing files by team/tenant using folder structures or bucket naming conventions for easier management +- **Tenant Isolation**: When querying files, always filter by metadata (e.g., \`teamId\`) to ensure users only access files from their teams +- **File Size Limits**: Set appropriate file size limits to prevent abuse and manage costs +- **File Types**: Validate file types before upload to ensure security and prevent malicious uploads +- **Permission Patterns**: Use team roles (owner, admin, member, viewer) consistently for bucket and file permissions, matching your database permission model +- **Cleanup**: Implement cleanup strategies for unused or temporary files, especially when teams are deleted +- **Virus Scanning**: Consider implementing virus scanning for uploaded files to protect all tenants +- **Access Control**: Validate team membership before allowing file uploads/downloads, even if permissions are set correctly`; +} + +/** + * Maps SDK names to their corresponding template paths in the Appwrite templates repository + * @param {string} sdk + * @returns {string|null} Template path or null if no template available + */ +function getFunctionTemplatePath(sdk) { + /** @type {Record} */ + const templateMap = { + javascript: 'node/starter', + 'react-native': 'node/starter', + python: 'python/starter', + php: 'php/starter', + go: 'go/starter', + flutter: 'dart/starter', + swift: 'swift/starter', + kotlin: 'kotlin/starter', + ruby: 'ruby/starter', + dotnet: 'dotnet/starter' + }; + return templateMap[sdk] || null; +} + +/** + * Generates template links section for functions + * @param {string} sdk + * @returns {string} + */ +function generateFunctionTemplateLinks(sdk) { + const templatePath = getFunctionTemplatePath(sdk); + if (!templatePath) { + return ''; + } + + const templateUrl = `https://github.com/appwrite/templates/tree/main/${templatePath}`; + const templatesBaseUrl = 'https://github.com/appwrite/templates'; + + return `### Starter Templates + +For getting started with Appwrite Functions, use the official starter template for your runtime: + +- **${SDK_OPTIONS[sdk]?.name || sdk} Starter**: [View Template](${templateUrl}) + +For more templates and examples, see the [Appwrite Templates Repository](${templatesBaseUrl}).`; } /** @@ -261,10 +716,26 @@ ${storageProductLinks} */ async function generateFunctionsSection(sdk, framework) { const { functionsProductLinks } = await import('./languages/common/products.js'); + const templateLinks = generateFunctionTemplateLinks(sdk); + return `## Functions ${functionsProductLinks} +${templateLinks} + +### When to Use Starter Templates + +**ALWAYS use starter templates from the [Appwrite Templates Repository](https://github.com/appwrite/templates) when building functions for:** + +- **Scheduled Tasks**: Functions that run on a schedule (cron jobs, periodic cleanup, etc.) +- **Event-Driven Tasks**: Functions triggered by Appwrite events (database changes, storage uploads, user events, etc.) +- **Background Processing**: Long-running or resource-intensive operations +- **Integration Functions**: Functions that integrate with third-party services (APIs, webhooks, etc.) +- **Complex Functions**: Any function that requires specific runtime configuration or dependencies + +**Why use templates?** Starter templates provide the correct project structure, dependencies, and configuration needed for functions to build and execute successfully. They ensure proper handling of environment variables, logging, error handling, and Appwrite SDK initialization. + ### Best Practices for Functions - **Error Handling**: Implement comprehensive error handling in your functions @@ -272,7 +743,8 @@ ${functionsProductLinks} - **Environment Variables**: Use environment variables for configuration, not hardcoded values - **Logging**: Implement proper logging for debugging and monitoring - **Security**: Validate all inputs and never trust user-provided data -- **Resource Limits**: Be mindful of memory and CPU limits for function executions`; +- **Resource Limits**: Be mindful of memory and CPU limits for function executions +- **Template Usage**: Start with official templates for scheduled and event-driven functions to ensure proper setup`; } /** diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 2175202..fc01412 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -15,7 +15,8 @@ { id: 'functions', label: 'Functions' }, { id: 'messaging', label: 'Messaging' }, { id: 'sites', label: 'Sites' }, - { id: 'realtime', label: 'Realtime' } + { id: 'realtime', label: 'Realtime' }, + { id: 'mcp', label: 'MCP' } ]; function updateFrameworks() { From d2ef717e1f75e4f2d49ae47e9e5e07c87792d69a Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Fri, 28 Nov 2025 01:51:41 +0530 Subject: [PATCH 09/19] Enhance README with detailed features and usage instructions for Appwrite Cursor Rules Generator; add .mdc file extension to .gitignore --- .gitignore | 4 ++ README.md | 138 ++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 120 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 3b462cb..bc116d1 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,7 @@ Thumbs.db # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* + +# Rules + +*.mdc \ No newline at end of file diff --git a/README.md b/README.md index 75842c4..72eb616 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,132 @@ -# sv +# Appwrite Cursor Rules Generator -Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). +A web application for generating comprehensive Cursor rules (`.mdc` files) for Appwrite development across multiple SDKs and frameworks. This tool helps developers create customized development rules that include best practices, code examples, and guidance for building applications with Appwrite. -## Creating a project +## Features -If you're seeing this, you've probably already done this step. Congrats! +- **Multi-SDK Support**: Generate rules for JavaScript/TypeScript, Python, PHP, Go, Flutter/Dart, Apple, Android, Swift, Kotlin, Ruby, .NET, and React Native +- **Framework-Specific Rules**: Get tailored rules for popular frameworks like Next.js, React, Vue, Svelte, Angular, Astro, Nuxt, Qwik, Solid, and more +- **Feature Selection**: Choose which Appwrite features to include: + - Authentication & Teams + - Database Operations + - Storage Operations + - Functions + - Messaging + - Sites + - Realtime Subscriptions + - MCP (Model Context Protocol) recommendations +- **Export Options**: Copy to clipboard or download as `.mdc` file +- **Best Practices**: Generated rules include comprehensive best practices, multi-tenancy patterns, and security guidelines -```sh -# create a new project in the current directory -npx sv create +## Supported SDKs and Frameworks -# create a new project in my-app -npx sv create my-app -``` +| SDK | Frameworks | +|-----|------------| +| JavaScript/TypeScript | Next.js, React, Vue, Svelte, Angular, Astro, Nuxt, Qwik, Solid, TanStack, Node.js, Vanilla | +| React Native | React Native, Vanilla | +| Python | Flask, Django, FastAPI, Server | +| Flutter/Dart | Flutter, Server | +| Apple | Vanilla | +| Android | Vanilla | +| Swift | Server, Vanilla | +| Kotlin | Server, Vanilla | +| PHP | Laravel, Symfony, Server | +| Go | Gin, Fiber, Server | +| Ruby | Rails, Server | +| .NET | ASP.NET, Server, Vanilla | + +## Getting Started + +### Prerequisites -## Developing +- Node.js 18+ +- pnpm -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: +### Installation + +1. Clone the repository: +```bash +git clone +cd appwrite-cursor-rules +``` -```sh -npm run dev +2. Install dependencies: +```bash +pnpm install +``` -# or start the server and open the app in a new browser tab -npm run dev -- --open +3. Start the development server: +```bash +pnpm dev ``` -## Building +4. Open your browser and navigate to `http://localhost:5173` (or the port shown in the terminal) + +## Usage + +1. **Select SDK**: Choose your preferred Appwrite SDK from the dropdown +2. **Select Framework**: Pick the framework you're using (options depend on the selected SDK) +3. **Choose Features**: Check the boxes for the Appwrite features you want to include in your rules +4. **Generate Rules**: Click the "Generate Rules" button +5. **Export**: Copy the rules to your clipboard or download as a `.mdc` file + +The generated rules file can be used in Cursor IDE to provide AI-assisted development guidance specific to your Appwrite setup. + +## Development -To create a production version of your app: +### Available Scripts -```sh -npm run build +- `pnpm dev` - Start development server +- `pnpm build` - Build for production +- `pnpm preview` - Preview production build +- `pnpm check` - Run Svelte type checking +- `pnpm lint` - Run ESLint and Prettier +- `pnpm format` - Format code with Prettier +- `pnpm generate:nextjs` - Generate Next.js rules file (example script) + +### Project Structure + +``` +appwrite-cursor-rules/ +├── src/ +│ ├── lib/ +│ │ ├── languages/ # SDK and framework-specific code examples +│ │ │ ├── js/ # JavaScript/TypeScript frameworks +│ │ │ ├── python/ # Python frameworks +│ │ │ ├── common/ # Shared rules (products, MCP, etc.) +│ │ │ └── ... # Other SDKs +│ │ ├── rules-generator.js # Main rules generation logic +│ │ └── utils/ # Utility functions +│ └── routes/ +│ ├── +page.svelte # Main application page +│ └── +layout.svelte # Layout component +├── scripts/ +│ ├── generate-nextjs-rules.js # Example script for generating rules +│ └── lib-loader.js # Module loader for scripts +└── static/ # Static assets ``` -You can preview the production build with `npm run preview`. +### Adding New SDKs or Frameworks + +1. Create a new file in `src/lib/languages/[sdk-name]/index.js` (or add to existing SDK directory) +2. Export framework-specific initialization code +3. Add the SDK configuration to `SDK_OPTIONS` in `src/lib/rules-generator.js` +4. Export the SDK module in `src/lib/languages/index.js` + +### Adding New Features + +1. Create a new section generator function in `src/lib/rules-generator.js` (e.g., `generateNewFeatureSection`) +2. Add the feature to the features array in `src/routes/+page.svelte` +3. Include the feature in the `generateRules` function's Promise.all array + +## Generated Rules Format + +The generated rules follow the Cursor `.mdc` format and include: + +- **Frontmatter**: Metadata about the rules (description, alwaysApply flag) +- **SDK Initialization**: Framework-specific code examples for setting up Appwrite +- **Feature Sections**: Best practices and guidance for selected features +- **Multi-Tenancy Guide**: Comprehensive guide on using teams and permissions +- **Product Links**: Links to official Appwrite documentation + -> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. From db0f222216b873c8a4e1bba04a8f3d20ef33d620 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Fri, 28 Nov 2025 02:47:50 +0530 Subject: [PATCH 10/19] Integrate permission examples into rules generator documentation; enhance code snippets for multi-tenancy best practices and member management flows. --- .../languages/common/permissions-examples.js | 4889 +++++++++++++++++ src/lib/rules-generator.js | 260 +- 2 files changed, 4958 insertions(+), 191 deletions(-) create mode 100644 src/lib/languages/common/permissions-examples.js diff --git a/src/lib/languages/common/permissions-examples.js b/src/lib/languages/common/permissions-examples.js new file mode 100644 index 0000000..0661d5e --- /dev/null +++ b/src/lib/languages/common/permissions-examples.js @@ -0,0 +1,4889 @@ +/** + * Language-specific permission examples for Appwrite + * All examples are validated against official Appwrite documentation + */ + +export const permissionExamples = { + javascript: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import { TablesDB, Permission, Role } from 'appwrite'; + +await tablesDB.createRow({ + databaseId: '', + tableId: '', + rowId: '', + data: { title: 'My Document' }, + permissions: [ + Permission.read(Role.user('')), + Permission.write(Role.user('')) + ] +});`, + preferTeamPermissions: `// DO this for multi-tenant apps +import { TablesDB, Permission, Role } from 'appwrite'; + +await tablesDB.createRow({ + databaseId: '', + tableId: '', + rowId: '', + data: { title: 'My Document' }, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +});`, + createTeam: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Create a team when a new tenant/organization signs up +const team = await teams.create({ + teamId: '', // Unique team ID (can be auto-generated) + name: '', // Display name + roles: ['owner', 'admin', 'member'] // Optional: Array of role strings +});`, + createMembershipEmail: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Send team invitation (email-based) +const invite = await teams.createMembership({ + teamId: '', + roles: ['admin', 'member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' // Invitation redirect URL +});`, + createMembershipUserId: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Or invite by user ID (if user already exists) +const membership = await teams.createMembership({ + teamId: '', + roles: ['admin', 'member'], + userId: '' +});`, + listMemberships: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Get all members of a team +const response = await teams.listMemberships({ + teamId: '' +}); + +// Access member data +response.memberships.forEach(membership => { + console.log(membership.userId); + console.log(membership.roles); // Array of role strings + console.log(membership.userName); + console.log(membership.userEmail); +});`, + updateMembership: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Update a member's roles (only team owners/admins can do this) +await teams.updateMembership({ + teamId: '', + membershipId: '', + roles: ['admin', 'member'] // New roles array +});`, + deleteMembership: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Remove a member from a team +await teams.deleteMembership({ + teamId: '', + membershipId: '' +});`, + listTeams: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// List all teams the current user belongs to +const response = await teams.list(); + +response.teams.forEach(team => { + console.log(team.$id); + console.log(team.name); +});`, + getUserRole: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Get membership details for current user in a specific team +const response = await teams.listMemberships({ + teamId: '' +}); + +const userMembership = response.memberships.find( + m => m.userId === '' +); + +if (userMembership) { + console.log(userMembership.roles); // ['owner', 'admin', etc.] + const hasAdminRole = userMembership.roles.includes('admin'); +}`, + createRow: `import { Client, TablesDB, Permission, Role } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const tablesDB = new TablesDB(client); + +// Create document with team-based permissions +await tablesDB.createRow({ + databaseId: '', + tableId: '', + rowId: '', + data: { + title: 'My Document', + teamId: '', // Always store teamId for querying + // ... other fields + }, + permissions: [ + // Owners and admins can do everything + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')), + Permission.delete(Role.team('', 'admin')) + ] +});`, + createTable: `import { Client, TablesDB, Permission, Role } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); // Server SDK requires API key + +const tablesDB = new TablesDB(client); + +// Create table with team-based permissions +await tablesDB.createTable({ + databaseId: '', + tableId: '', + name: '', + permissions: [ + // Collection permissions + Permission.create(Role.team('', 'member')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +});`, + listRows: `import { Client, TablesDB, Query } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const tablesDB = new TablesDB(client); + +// ALWAYS filter by teamId to ensure tenant isolation +const response = await tablesDB.listRows({ + databaseId: '', + tableId: '', + queries: [ + Query.equal('teamId', ''), // Critical: filter by team + Query.orderDesc('$createdAt'), + Query.limit(25) + ] +});`, + createFile: `import { Client, Storage, Permission, Role } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const storage = new Storage(client); + +// Create file with team-based permissions +await storage.createFile({ + bucketId: '', + fileId: '', + file: fileInput, // File object from input + permissions: [ + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +});`, + teamCreationFlow: `// When user creates account/organization +import { Client, Teams, ID } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const team = await teams.create({ + teamId: ID.unique(), + name: 'Company Name' +}); + +// Make creator an owner +await teams.createMembership({ + teamId: team.$id, + roles: ['owner'], + userId: '' +});`, + inviteFlow: `// Owner/admin invites new member +import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const invite = await teams.createMembership({ + teamId: '', + roles: ['member'], // Default role + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' // Redirect after accepting +}); +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const response = await teams.listMemberships({ + teamId: '' +}); +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +await teams.updateMembership({ + teamId: '', + membershipId: '', + roles: ['admin'] +});`, + memberRemoval: `// Remove member (with confirmation) +import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +await teams.deleteMembership({ + teamId: '', + membershipId: '' +});`, + roleCheck: `const response = await teams.listMemberships({ + teamId: '' +}); + +const membership = response.memberships.find( + m => m.userId === '' +); + +if (!membership || !membership.roles.includes('admin')) { + throw new Error('Insufficient permissions'); +}` + }, + 'react-native': { + // React Native uses the same syntax as JavaScript + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import { TablesDB, Permission, Role } from 'react-native-appwrite'; + +await tablesDB.createRow({ + databaseId: '', + tableId: '', + rowId: '', + data: { title: 'My Document' }, + permissions: [ + Permission.read(Role.user('')), + Permission.write(Role.user('')) + ] +});`, + preferTeamPermissions: `// DO this for multi-tenant apps +import { TablesDB, Permission, Role } from 'react-native-appwrite'; + +await tablesDB.createRow({ + databaseId: '', + tableId: '', + rowId: '', + data: { title: 'My Document' }, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +});`, + createTeam: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const team = await teams.create({ + teamId: '', + name: '', + roles: ['owner', 'admin', 'member'] +});`, + createMembershipEmail: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const invite = await teams.createMembership({ + teamId: '', + roles: ['admin', 'member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +});`, + createMembershipUserId: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const membership = await teams.createMembership({ + teamId: '', + roles: ['admin', 'member'], + userId: '' +});`, + listMemberships: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const response = await teams.listMemberships({ + teamId: '' +}); + +response.memberships.forEach(membership => { + console.log(membership.userId); + console.log(membership.roles); + console.log(membership.userName); + console.log(membership.userEmail); +});`, + updateMembership: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +await teams.updateMembership({ + teamId: '', + membershipId: '', + roles: ['admin', 'member'] +});`, + deleteMembership: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +await teams.deleteMembership({ + teamId: '', + membershipId: '' +});`, + listTeams: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const response = await teams.list(); + +response.teams.forEach(team => { + console.log(team.$id); + console.log(team.name); +});`, + getUserRole: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const response = await teams.listMemberships({ + teamId: '' +}); + +const userMembership = response.memberships.find( + m => m.userId === '' +); + +if (userMembership) { + console.log(userMembership.roles); + const hasAdminRole = userMembership.roles.includes('admin'); +}`, + createRow: `import { Client, TablesDB, Permission, Role } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const tablesDB = new TablesDB(client); + +await tablesDB.createRow({ + databaseId: '', + tableId: '', + rowId: '', + data: { + title: 'My Document', + teamId: '', + }, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')), + Permission.delete(Role.team('', 'admin')) + ] +});`, + createTable: `import { Client, TablesDB, Permission, Role } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const tablesDB = new TablesDB(client); + +await tablesDB.createTable({ + databaseId: '', + tableId: '', + name: '', + permissions: [ + Permission.create(Role.team('', 'member')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +});`, + listRows: `import { Client, TablesDB, Query } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const tablesDB = new TablesDB(client); + +const response = await tablesDB.listRows({ + databaseId: '', + tableId: '', + queries: [ + Query.equal('teamId', ''), + Query.orderDesc('$createdAt'), + Query.limit(25) + ] +});`, + createFile: `import { Client, Storage, Permission, Role } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const storage = new Storage(client); + +await storage.createFile({ + bucketId: '', + fileId: '', + file: fileInput, + permissions: [ + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +});`, + teamCreationFlow: `import { Client, Teams, ID } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const team = await teams.create({ + teamId: ID.unique(), + name: 'Company Name' +}); + +await teams.createMembership({ + teamId: team.$id, + roles: ['owner'], + userId: '' +});`, + inviteFlow: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const invite = await teams.createMembership({ + teamId: '', + roles: ['member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +});`, + memberListUI: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const response = await teams.listMemberships({ + teamId: '' +});`, + roleChange: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +await teams.updateMembership({ + teamId: '', + membershipId: '', + roles: ['admin'] +});`, + memberRemoval: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +await teams.deleteMembership({ + teamId: '', + membershipId: '' +});`, + roleCheck: `const response = await teams.listMemberships({ + teamId: '' +}); + +const membership = response.memberships.find( + m => m.userId === '' +); + +if (!membership || !membership.roles.includes('admin')) { + throw new Error('Insufficient permissions'); +}` + }, + python: { + avoidUserPermissions: `# DON'T do this for multi-tenant apps +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import Permission, Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +tables_db = TablesDB(client) + +tables_db.create_row( + database_id='', + table_id='', + row_id='', + data={'title': 'My Document'}, + permissions=[ + Permission.read(Role.user('')), + Permission.write(Role.user('')) + ] +)`, + preferTeamPermissions: `# DO this for multi-tenant apps +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import Permission, Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +tables_db = TablesDB(client) + +tables_db.create_row( + database_id='', + table_id='', + row_id='', + data={'title': 'My Document'}, + permissions=[ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +)`, + createTeam: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# Create a team when a new tenant/organization signs up +team = teams.create( + team_id='', + name='', + roles=['owner', 'admin', 'member'] # optional +)`, + createMembershipEmail: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# Send team invitation (email-based) +invite = teams.create_membership( + team_id='', + roles=['admin', 'member'], + email='user@example.com', + url='https://yourapp.com/accept-invite' +)`, + createMembershipUserId: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') # Server SDK + +teams = Teams(client) + +# Or invite by user ID (if user already exists) +membership = teams.create_membership( + team_id='', + roles=['admin', 'member'], + user_id='' +)`, + listMemberships: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# Get all members of a team +response = teams.list_memberships(team_id='') + +# Access member data +for membership in response['memberships']: + print(membership['userId']) + print(membership['roles']) # Array of role strings + print(membership['userName']) + print(membership['userEmail'])`, + updateMembership: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# Update a member's roles (only team owners/admins can do this) +teams.update_membership( + team_id='', + membership_id='', + roles=['admin', 'member'] +)`, + deleteMembership: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# Remove a member from a team +teams.delete_membership( + team_id='', + membership_id='' +)`, + listTeams: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# List all teams the current user belongs to +response = teams.list() + +for team in response['teams']: + print(team['$id']) + print(team['name'])`, + getUserRole: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# Get membership details for current user in a specific team +response = teams.list_memberships(team_id='') + +user_membership = next( + (m for m in response['memberships'] if m['userId'] == ''), + None +) + +if user_membership: + print(user_membership['roles']) # ['owner', 'admin', etc.] + has_admin_role = 'admin' in user_membership['roles']`, + createRow: `from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import Permission, Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +tables_db = TablesDB(client) + +# Create document with team-based permissions +tables_db.create_row( + database_id='', + table_id='', + row_id='', + data={ + 'title': 'My Document', + 'teamId': '', # Always store teamId for querying + }, + permissions=[ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')), + Permission.delete(Role.team('', 'admin')) + ] +)`, + createTable: `from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import Permission, Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') # Server SDK requires API key + +tables_db = TablesDB(client) + +# Create table with team-based permissions +tables_db.create_table( + database_id='', + table_id='', + name='', + permissions=[ + Permission.create(Role.team('', 'member')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +)`, + listRows: `from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +tables_db = TablesDB(client) + +# ALWAYS filter by teamId to ensure tenant isolation +response = tables_db.list_rows( + database_id='', + table_id='', + queries=[ + Query.equal('teamId', ''), # Critical: filter by team + Query.order_desc('$createdAt'), + Query.limit(25) + ] +)`, + createFile: `from appwrite.client import Client +from appwrite.services.storage import Storage +from appwrite.models import Permission, Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +storage = Storage(client) + +# Create file with team-based permissions +storage.create_file( + bucket_id='', + file_id='', + file=file_input, + permissions=[ + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +)`, + teamCreationFlow: `# When user creates account/organization +from appwrite.client import Client +from appwrite.services.teams import Teams +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +team = teams.create( + team_id=ID.unique(), + name='Company Name' +) + +# Make creator an owner +teams.create_membership( + team_id=team['$id'], + roles=['owner'], + user_id='' +)`, + inviteFlow: `# Owner/admin invites new member +from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +invite = teams.create_membership( + team_id='', + roles=['member'], # Default role + email='user@example.com', + url='https://yourapp.com/accept-invite' +) +# User receives email, clicks link, accepts invitation`, + memberListUI: `# Display all team members with their roles +from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +response = teams.list_memberships(team_id='') +# Show list with role badges and action buttons`, + roleChange: `# Admin/owner changes member role +from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +teams.update_membership( + team_id='', + membership_id='', + roles=['admin'] +)`, + memberRemoval: `# Remove member (with confirmation) +from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +teams.delete_membership( + team_id='', + membership_id='' +)`, + roleCheck: `response = teams.list_memberships(team_id='') + +user_membership = next( + (m for m in response['memberships'] if m['userId'] == ''), + None +) + +if not user_membership or 'admin' not in user_membership['roles']: + raise Exception('Insufficient permissions')` + }, + php: { + avoidUserPermissions: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$tablesDB = new TablesDB($client); + +$tablesDB->createRow( + databaseId: '', + tableId: '', + rowId: '', + data: ['title' => 'My Document'], + permissions: [ + Permission::read(Role::user('')), + Permission::write(Role::user('')) + ] +);`, + preferTeamPermissions: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$tablesDB = new TablesDB($client); + +$tablesDB->createRow( + databaseId: '', + tableId: '', + rowId: '', + data: ['title' => 'My Document'], + permissions: [ + Permission::read(Role::team('', 'owner')), + Permission::read(Role::team('', 'admin')), + Permission::read(Role::team('', 'member')), + Permission::update(Role::team('', 'owner')), + Permission::update(Role::team('', 'admin')), + Permission::delete(Role::team('', 'owner')) + ] +);`, + createTeam: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// Create a team when a new tenant/organization signs up +$team = $teams->create( + teamId: '', + name: '', + roles: ['owner', 'admin', 'member'] // optional +);`, + createMembershipEmail: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// Send team invitation (email-based) +$invite = $teams->createMembership( + teamId: '', + roles: ['admin', 'member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +);`, + createMembershipUserId: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); // Server SDK + +$teams = new Teams($client); + +// Or invite by user ID (if user already exists) +$membership = $teams->createMembership( + teamId: '', + roles: ['admin', 'member'], + userId: '' +);`, + listMemberships: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// Get all members of a team +$response = $teams->listMemberships(teamId: ''); + +// Access member data +foreach ($response['memberships'] as $membership) { + echo $membership['userId']; + echo implode(', ', $membership['roles']); // Array of role strings + echo $membership['userName']; + echo $membership['userEmail']; +}`, + updateMembership: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// Update a member's roles (only team owners/admins can do this) +$teams->updateMembership( + teamId: '', + membershipId: '', + roles: ['admin', 'member'] +);`, + deleteMembership: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// Remove a member from a team +$teams->deleteMembership( + teamId: '', + membershipId: '' +);`, + listTeams: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// List all teams the current user belongs to +$response = $teams->list(); + +foreach ($response['teams'] as $team) { + echo $team['$id']; + echo $team['name']; +}`, + getUserRole: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// Get membership details for current user in a specific team +$response = $teams->listMemberships(teamId: ''); + +$userMembership = array_filter( + $response['memberships'], + fn($m) => $m['userId'] === '' +); + +if (!empty($userMembership)) { + $membership = reset($userMembership); + echo implode(', ', $membership['roles']); // ['owner', 'admin', etc.] + $hasAdminRole = in_array('admin', $membership['roles']); +}`, + createRow: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$tablesDB = new TablesDB($client); + +// Create document with team-based permissions +$tablesDB->createRow( + databaseId: '', + tableId: '', + rowId: '', + data: [ + 'title' => 'My Document', + 'teamId' => '', // Always store teamId for querying + ], + permissions: [ + Permission::read(Role::team('', 'owner')), + Permission::read(Role::team('', 'admin')), + Permission::read(Role::team('', 'member')), + Permission::update(Role::team('', 'owner')), + Permission::update(Role::team('', 'admin')), + Permission::delete(Role::team('', 'owner')), + Permission::delete(Role::team('', 'admin')) + ] +);`, + createTable: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); // Server SDK requires API key + +$tablesDB = new TablesDB($client); + +// Create table with team-based permissions +$tablesDB->createTable( + databaseId: '', + tableId: '', + name: '', + permissions: [ + Permission::create(Role::team('', 'member')), + Permission::read(Role::team('', 'member')), + Permission::update(Role::team('', 'admin')), + Permission::delete(Role::team('', 'owner')) + ] +);`, + listRows: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$tablesDB = new TablesDB($client); + +// ALWAYS filter by teamId to ensure tenant isolation +$response = $tablesDB->listRows( + databaseId: '', + tableId: '', + queries: [ + Query::equal('teamId', ''), // Critical: filter by team + Query::orderDesc('$createdAt'), + Query::limit(25) + ] +);`, + createFile: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$storage = new Storage($client); + +// Create file with team-based permissions +$storage->createFile( + bucketId: '', + fileId: '', + file: $fileInput, + permissions: [ + Permission::read(Role::team('', 'member')), + Permission::update(Role::team('', 'admin')), + Permission::delete(Role::team('', 'owner')) + ] +);`, + teamCreationFlow: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +$team = $teams->create( + teamId: ID::unique(), + name: 'Company Name' +); + +// Make creator an owner +$teams->createMembership( + teamId: $team['$id'], + roles: ['owner'], + userId: '' +);`, + inviteFlow: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +$invite = $teams->createMembership( + teamId: '', + roles: ['member'], // Default role + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +); +// User receives email, clicks link, accepts invitation`, + memberListUI: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +$response = $teams->listMemberships(teamId: ''); +// Show list with role badges and action buttons`, + roleChange: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +$teams->updateMembership( + teamId: '', + membershipId: '', + roles: ['admin'] +);`, + memberRemoval: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +$teams->deleteMembership( + teamId: '', + membershipId: '' +);`, + roleCheck: `$response = $teams->listMemberships(teamId: ''); + +$userMembership = array_filter( + $response['memberships'], + fn($m) => $m['userId'] === '' +); + +if (empty($userMembership) || !in_array('admin', reset($userMembership)['roles'])) { + throw new Exception('Insufficient permissions'); +}` + }, + go: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" + "github.com/appwrite/sdk-for-go/models" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +tablesDB := tablesdb.New(client) + +tablesDB.CreateRow( + "", + "", + "", + map[string]interface{}{ + "title": "My Document", + }, + []interface{}{ + models.PermissionRead(models.RoleUser("")), + models.PermissionWrite(models.RoleUser("")), + }, +)`, + preferTeamPermissions: `// DO this for multi-tenant apps +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" + "github.com/appwrite/sdk-for-go/models" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +tablesDB := tablesdb.New(client) + +tablesDB.CreateRow( + "", + "", + "", + map[string]interface{}{ + "title": "My Document", + }, + []interface{}{ + models.PermissionRead(models.RoleTeam("", "owner")), + models.PermissionRead(models.RoleTeam("", "admin")), + models.PermissionRead(models.RoleTeam("", "member")), + models.PermissionUpdate(models.RoleTeam("", "owner")), + models.PermissionUpdate(models.RoleTeam("", "admin")), + models.PermissionDelete(models.RoleTeam("", "owner")), + }, +)`, + createTeam: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// Create a team when a new tenant/organization signs up +service.Create( + "", + "", + teams.WithCreateRoles([]interface{}{"owner", "admin", "member"}), +)`, + createMembershipEmail: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// Send team invitation (email-based) +service.CreateMembership( + "", + []interface{}{"admin", "member"}, + teams.WithCreateMembershipEmail("user@example.com"), + teams.WithCreateMembershipUrl("https://yourapp.com/accept-invite"), +)`, + createMembershipUserId: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithKey(""), // Server SDK +) + +service := teams.New(client) + +// Or invite by user ID (if user already exists) +service.CreateMembership( + "", + []interface{}{"admin", "member"}, + teams.WithCreateMembershipUserId(""), +)`, + listMemberships: `package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// Get all members of a team +response, _ := service.ListMemberships("") + +// Access member data +for _, membership := range response.Memberships { + fmt.Println(membership.UserId) + fmt.Println(membership.Roles) // Array of role strings + fmt.Println(membership.UserName) + fmt.Println(membership.UserEmail) +}`, + updateMembership: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// Update a member's roles (only team owners/admins can do this) +service.UpdateMembership( + "", + "", + []interface{}{"admin", "member"}, +)`, + deleteMembership: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// Remove a member from a team +service.DeleteMembership( + "", + "", +)`, + listTeams: `package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// List all teams the current user belongs to +response, _ := service.List() + +for _, team := range response.Teams { + fmt.Println(team.Id) + fmt.Println(team.Name) +}`, + getUserRole: `package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// Get membership details for current user in a specific team +response, _ := service.ListMemberships("") + +var userMembership *teams.Membership +for _, membership := range response.Memberships { + if membership.UserId == "" { + userMembership = &membership + break + } +} + +if userMembership != nil { + fmt.Println(userMembership.Roles) // ['owner', 'admin', etc.] + hasAdminRole := false + for _, role := range userMembership.Roles { + if role == "admin" { + hasAdminRole = true + break + } + } +}`, + createRow: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" + "github.com/appwrite/sdk-for-go/models" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +tablesDB := tablesdb.New(client) + +// Create document with team-based permissions +tablesDB.CreateRow( + "", + "", + "", + map[string]interface{}{ + "title": "My Document", + "teamId": "", // Always store teamId for querying + }, + []interface{}{ + models.PermissionRead(models.RoleTeam("", "owner")), + models.PermissionRead(models.RoleTeam("", "admin")), + models.PermissionRead(models.RoleTeam("", "member")), + models.PermissionUpdate(models.RoleTeam("", "owner")), + models.PermissionUpdate(models.RoleTeam("", "admin")), + models.PermissionDelete(models.RoleTeam("", "owner")), + models.PermissionDelete(models.RoleTeam("", "admin")), + }, +)`, + createTable: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" + "github.com/appwrite/sdk-for-go/models" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithKey(""), // Server SDK requires API key +) + +tablesDB := tablesdb.New(client) + +// Create table with team-based permissions +tablesDB.CreateTable( + "", + "", + "", + tablesdb.WithCreateTablePermissions([]interface{}{ + models.PermissionCreate(models.RoleTeam("", "member")), + models.PermissionRead(models.RoleTeam("", "member")), + models.PermissionUpdate(models.RoleTeam("", "admin")), + models.PermissionDelete(models.RoleTeam("", "owner")), + }), +)`, + listRows: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" + "github.com/appwrite/sdk-for-go/query" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +tablesDB := tablesdb.New(client) + +// ALWAYS filter by teamId to ensure tenant isolation +tablesDB.ListRows( + "", + "", + tablesdb.WithListRowsQueries([]interface{}{ + query.Equal("teamId", ""), // Critical: filter by team + query.OrderDesc("$createdAt"), + query.Limit(25), + }), +)`, + createFile: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/storage" + "github.com/appwrite/sdk-for-go/models" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +storageService := storage.New(client) + +// Create file with team-based permissions +storageService.CreateFile( + "", + "", + fileInput, + storage.WithCreateFilePermissions([]interface{}{ + models.PermissionRead(models.RoleTeam("", "member")), + models.PermissionUpdate(models.RoleTeam("", "admin")), + models.PermissionDelete(models.RoleTeam("", "owner")), + }), +)`, + teamCreationFlow: `// When user creates account/organization +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" + "github.com/appwrite/sdk-for-go/id" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +team, _ := service.Create( + id.Unique(), + "Company Name", +) + +// Make creator an owner +service.CreateMembership( + team.Id, + []interface{}{"owner"}, + teams.WithCreateMembershipUserId(""), +)`, + inviteFlow: `// Owner/admin invites new member +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +service.CreateMembership( + "", + []interface{}{"member"}, // Default role + teams.WithCreateMembershipEmail("user@example.com"), + teams.WithCreateMembershipUrl("https://yourapp.com/accept-invite"), +) +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +response, _ := service.ListMemberships("") +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +service.UpdateMembership( + "", + "", + []interface{}{"admin"}, +)`, + memberRemoval: `// Remove member (with confirmation) +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +service.DeleteMembership( + "", + "", +)`, + roleCheck: `response, _ := service.ListMemberships("") + +var userMembership *teams.Membership +for _, membership := range response.Memberships { + if membership.UserId == "" { + userMembership = &membership + break + } +} + +hasAdminRole := false +if userMembership != nil { + for _, role := range userMembership.Roles { + if role == "admin" { + hasAdminRole = true + break + } + } +} + +if !hasAdminRole { + panic("Insufficient permissions") +}` + }, + ruby: { + avoidUserPermissions: `# DON'T do this for multi-tenant apps +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +tables_db = TablesDB.new(client) + +tables_db.create_row( + database_id: '', + table_id: '', + row_id: '', + data: {'title' => 'My Document'}, + permissions: [ + Permission.read(Role.user('')), + Permission.write(Role.user('')) + ] +)`, + preferTeamPermissions: `# DO this for multi-tenant apps +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +tables_db = TablesDB.new(client) + +tables_db.create_row( + database_id: '', + table_id: '', + row_id: '', + data: {'title' => 'My Document'}, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +)`, + createTeam: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# Create a team when a new tenant/organization signs up +team = teams.create( + team_id: '', + name: '', + roles: ['owner', 'admin', 'member'] # optional +)`, + createMembershipEmail: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# Send team invitation (email-based) +invite = teams.create_membership( + team_id: '', + roles: ['admin', 'member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +)`, + createMembershipUserId: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') # Server SDK + +teams = Teams.new(client) + +# Or invite by user ID (if user already exists) +membership = teams.create_membership( + team_id: '', + roles: ['admin', 'member'], + user_id: '' +)`, + listMemberships: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# Get all members of a team +response = teams.list_memberships(team_id: '') + +# Access member data +response['memberships'].each do |membership| + puts membership['userId'] + puts membership['roles'] # Array of role strings + puts membership['userName'] + puts membership['userEmail'] +end`, + updateMembership: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# Update a member's roles (only team owners/admins can do this) +teams.update_membership( + team_id: '', + membership_id: '', + roles: ['admin', 'member'] +)`, + deleteMembership: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# Remove a member from a team +teams.delete_membership( + team_id: '', + membership_id: '' +)`, + listTeams: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# List all teams the current user belongs to +response = teams.list + +response['teams'].each do |team| + puts team['$id'] + puts team['name'] +end`, + getUserRole: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# Get membership details for current user in a specific team +response = teams.list_memberships(team_id: '') + +user_membership = response['memberships'].find do |m| + m['userId'] == '' +end + +if user_membership + puts user_membership['roles'] # ['owner', 'admin', etc.] + has_admin_role = user_membership['roles'].include?('admin') +end`, + createRow: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +tables_db = TablesDB.new(client) + +# Create document with team-based permissions +tables_db.create_row( + database_id: '', + table_id: '', + row_id: '', + data: { + 'title' => 'My Document', + 'teamId' => '', # Always store teamId for querying + }, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')), + Permission.delete(Role.team('', 'admin')) + ] +)`, + createTable: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') # Server SDK requires API key + +tables_db = TablesDB.new(client) + +# Create table with team-based permissions +tables_db.create_table( + database_id: '', + table_id: '', + name: '', + permissions: [ + Permission.create(Role.team('', 'member')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +)`, + listRows: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +tables_db = TablesDB.new(client) + +# ALWAYS filter by teamId to ensure tenant isolation +response = tables_db.list_rows( + database_id: '', + table_id: '', + queries: [ + Query.equal('teamId', ''), # Critical: filter by team + Query.order_desc('$createdAt'), + Query.limit(25) + ] +)`, + createFile: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +storage = Storage.new(client) + +# Create file with team-based permissions +storage.create_file( + bucket_id: '', + file_id: '', + file: file_input, + permissions: [ + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +)`, + teamCreationFlow: `# When user creates account/organization +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +team = teams.create( + team_id: ID.unique, + name: 'Company Name' +) + +# Make creator an owner +teams.create_membership( + team_id: team['$id'], + roles: ['owner'], + user_id: '' +)`, + inviteFlow: `# Owner/admin invites new member +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +invite = teams.create_membership( + team_id: '', + roles: ['member'], # Default role + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +) +# User receives email, clicks link, accepts invitation`, + memberListUI: `# Display all team members with their roles +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +response = teams.list_memberships(team_id: '') +# Show list with role badges and action buttons`, + roleChange: `# Admin/owner changes member role +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +teams.update_membership( + team_id: '', + membership_id: '', + roles: ['admin'] +)`, + memberRemoval: `# Remove member (with confirmation) +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +teams.delete_membership( + team_id: '', + membership_id: '' +)`, + roleCheck: `response = teams.list_memberships(team_id: '') + +user_membership = response['memberships'].find do |m| + m['userId'] == '' +end + +if !user_membership || !user_membership['roles'].include?('admin') + raise 'Insufficient permissions' +end` + }, + dotnet: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +TablesDB tablesDB = new TablesDB(client); + +await tablesDB.CreateRow( + databaseId: "", + tableId: "", + rowId: "", + data: new { title = "My Document" }, + permissions: new List { + Permission.Read(Role.User("")), + Permission.Write(Role.User("")) + } +);`, + preferTeamPermissions: `// DO this for multi-tenant apps +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +TablesDB tablesDB = new TablesDB(client); + +await tablesDB.CreateRow( + databaseId: "", + tableId: "", + rowId: "", + data: new { title = "My Document" }, + permissions: new List { + Permission.Read(Role.Team("", "owner")), + Permission.Read(Role.Team("", "admin")), + Permission.Read(Role.Team("", "member")), + Permission.Update(Role.Team("", "owner")), + Permission.Update(Role.Team("", "admin")), + Permission.Delete(Role.Team("", "owner")) + } +);`, + createTeam: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// Create a team when a new tenant/organization signs up +Team team = await teams.Create( + teamId: "", + name: "", + roles: new List { "owner", "admin", "member" } // optional +);`, + createMembershipEmail: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// Send team invitation (email-based) +Membership invite = await teams.CreateMembership( + teamId: "", + roles: new List { "admin", "member" }, + email: "user@example.com", + url: "https://yourapp.com/accept-invite" +);`, + createMembershipUserId: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); // Server SDK + +Teams teams = new Teams(client); + +// Or invite by user ID (if user already exists) +Membership membership = await teams.CreateMembership( + teamId: "", + roles: new List { "admin", "member" }, + userId: "" +);`, + listMemberships: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// Get all members of a team +MembershipList response = await teams.ListMemberships(teamId: ""); + +// Access member data +foreach (var membership in response.Memberships) { + Console.WriteLine(membership.UserId); + Console.WriteLine(string.Join(", ", membership.Roles)); // Array of role strings + Console.WriteLine(membership.UserName); + Console.WriteLine(membership.UserEmail); +}`, + updateMembership: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// Update a member's roles (only team owners/admins can do this) +await teams.UpdateMembership( + teamId: "", + membershipId: "", + roles: new List { "admin", "member" } +);`, + deleteMembership: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// Remove a member from a team +await teams.DeleteMembership( + teamId: "", + membershipId: "" +);`, + listTeams: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// List all teams the current user belongs to +TeamList response = await teams.List(); + +foreach (var team in response.Teams) { + Console.WriteLine(team.Id); + Console.WriteLine(team.Name); +}`, + getUserRole: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; +using System.Linq; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// Get membership details for current user in a specific team +MembershipList response = await teams.ListMemberships(teamId: ""); + +var userMembership = response.Memberships.FirstOrDefault( + m => m.UserId == "" +); + +if (userMembership != null) { + Console.WriteLine(string.Join(", ", userMembership.Roles)); // ['owner', 'admin', etc.] + bool hasAdminRole = userMembership.Roles.Contains("admin"); +}`, + createRow: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +TablesDB tablesDB = new TablesDB(client); + +// Create document with team-based permissions +await tablesDB.CreateRow( + databaseId: "", + tableId: "", + rowId: "", + data: new { + title = "My Document", + teamId = "", // Always store teamId for querying + }, + permissions: new List { + Permission.Read(Role.Team("", "owner")), + Permission.Read(Role.Team("", "admin")), + Permission.Read(Role.Team("", "member")), + Permission.Update(Role.Team("", "owner")), + Permission.Update(Role.Team("", "admin")), + Permission.Delete(Role.Team("", "owner")), + Permission.Delete(Role.Team("", "admin")) + } +);`, + createTable: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); // Server SDK requires API key + +TablesDB tablesDB = new TablesDB(client); + +// Create table with team-based permissions +await tablesDB.CreateTable( + databaseId: "", + tableId: "", + name: "", + permissions: new List { + Permission.Create(Role.Team("", "member")), + Permission.Read(Role.Team("", "member")), + Permission.Update(Role.Team("", "admin")), + Permission.Delete(Role.Team("", "owner")) + } +);`, + listRows: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; +using Appwrite.Query; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +TablesDB tablesDB = new TablesDB(client); + +// ALWAYS filter by teamId to ensure tenant isolation +RowList response = await tablesDB.ListRows( + databaseId: "", + tableId: "", + queries: new List { + Query.Equal("teamId", ""), // Critical: filter by team + Query.OrderDesc("$createdAt"), + Query.Limit(25) + } +);`, + createFile: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Storage storage = new Storage(client); + +// Create file with team-based permissions +await storage.CreateFile( + bucketId: "", + fileId: "", + file: fileInput, + permissions: new List { + Permission.Read(Role.Team("", "member")), + Permission.Update(Role.Team("", "admin")), + Permission.Delete(Role.Team("", "owner")) + } +);`, + teamCreationFlow: `// When user creates account/organization +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; +using Appwrite.ID; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +Team team = await teams.Create( + teamId: ID.Unique(), + name: "Company Name" +); + +// Make creator an owner +await teams.CreateMembership( + teamId: team.Id, + roles: new List { "owner" }, + userId: "" +);`, + inviteFlow: `// Owner/admin invites new member +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +Membership invite = await teams.CreateMembership( + teamId: "", + roles: new List { "member" }, // Default role + email: "user@example.com", + url: "https://yourapp.com/accept-invite" +); +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +MembershipList response = await teams.ListMemberships(teamId: ""); +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +await teams.UpdateMembership( + teamId: "", + membershipId: "", + roles: new List { "admin" } +);`, + memberRemoval: `// Remove member (with confirmation) +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +await teams.DeleteMembership( + teamId: "", + membershipId: "" +);`, + roleCheck: `MembershipList response = await teams.ListMemberships(teamId: ""); + +var userMembership = response.Memberships.FirstOrDefault( + m => m.UserId == "" +); + +if (userMembership == null || !userMembership.Roles.Contains("admin")) { + throw new Exception("Insufficient permissions"); +}` + }, + swift: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let tablesDB = TablesDB(client) + +try await tablesDB.createRow( + databaseId: "", + tableId: "", + rowId: "", + data: ["title": "My Document"], + permissions: [ + Permission.read(Role.user("")), + Permission.write(Role.user("")) + ] +)`, + preferTeamPermissions: `// DO this for multi-tenant apps +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let tablesDB = TablesDB(client) + +try await tablesDB.createRow( + databaseId: "", + tableId: "", + rowId: "", + data: ["title": "My Document"], + permissions: [ + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ] +)`, + createTeam: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// Create a team when a new tenant/organization signs up +let team = try await teams.create( + teamId: "", + name: "", + roles: ["owner", "admin", "member"] // optional +)`, + createMembershipEmail: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// Send team invitation (email-based) +let invite = try await teams.createMembership( + teamId: "", + roles: ["admin", "member"], + email: "user@example.com", + url: "https://yourapp.com/accept-invite" +)`, + createMembershipUserId: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") // Server SDK + +let teams = Teams(client) + +// Or invite by user ID (if user already exists) +let membership = try await teams.createMembership( + teamId: "", + roles: ["admin", "member"], + userId: "" +)`, + listMemberships: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// Get all members of a team +let response = try await teams.listMemberships(teamId: "") + +// Access member data +for membership in response.memberships { + print(membership.userId) + print(membership.roles) // Array of role strings + print(membership.userName ?? "") + print(membership.userEmail ?? "") +}`, + updateMembership: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// Update a member's roles (only team owners/admins can do this) +try await teams.updateMembership( + teamId: "", + membershipId: "", + roles: ["admin", "member"] +)`, + deleteMembership: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// Remove a member from a team +try await teams.deleteMembership( + teamId: "", + membershipId: "" +)`, + listTeams: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// List all teams the current user belongs to +let response = try await teams.list() + +for team in response.teams { + print(team.id) + print(team.name) +}`, + getUserRole: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// Get membership details for current user in a specific team +let response = try await teams.listMemberships(teamId: "") + +let userMembership = response.memberships.first { $0.userId == "" } + +if let membership = userMembership { + print(membership.roles) // ['owner', 'admin', etc.] + let hasAdminRole = membership.roles.contains("admin") +}`, + createRow: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let tablesDB = TablesDB(client) + +// Create document with team-based permissions +try await tablesDB.createRow( + databaseId: "", + tableId: "", + rowId: "", + data: [ + "title": "My Document", + "teamId": "", // Always store teamId for querying + ], + permissions: [ + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")), + Permission.delete(Role.team("", "admin")) + ] +)`, + createTable: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") // Server SDK requires API key + +let tablesDB = TablesDB(client) + +// Create table with team-based permissions +try await tablesDB.createTable( + databaseId: "", + tableId: "", + name: "", + permissions: [ + Permission.create(Role.team("", "member")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ] +)`, + listRows: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let tablesDB = TablesDB(client) + +// ALWAYS filter by teamId to ensure tenant isolation +let response = try await tablesDB.listRows( + databaseId: "", + tableId: "", + queries: [ + Query.equal("teamId", ""), // Critical: filter by team + Query.orderDesc("$createdAt"), + Query.limit(25) + ] +)`, + createFile: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let storage = Storage(client) + +// Create file with team-based permissions +try await storage.createFile( + bucketId: "", + fileId: "", + file: fileInput, + permissions: [ + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ] +)`, + teamCreationFlow: `// When user creates account/organization +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +let team = try await teams.create( + teamId: ID.unique(), + name: "Company Name" +) + +// Make creator an owner +try await teams.createMembership( + teamId: team.id, + roles: ["owner"], + userId: "" +)`, + inviteFlow: `// Owner/admin invites new member +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +let invite = try await teams.createMembership( + teamId: "", + roles: ["member"], // Default role + email: "user@example.com", + url: "https://yourapp.com/accept-invite" +) +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +let response = try await teams.listMemberships(teamId: "") +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +try await teams.updateMembership( + teamId: "", + membershipId: "", + roles: ["admin"] +)`, + memberRemoval: `// Remove member (with confirmation) +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +try await teams.deleteMembership( + teamId: "", + membershipId: "" +)`, + roleCheck: `let response = try await teams.listMemberships(teamId: "") + +let userMembership = response.memberships.first { $0.userId == "" } + +if userMembership == nil || !userMembership!.roles.contains("admin") { + throw NSError(domain: "Appwrite", code: 403, userInfo: [NSLocalizedDescriptionKey: "Insufficient permissions"]) +}` + }, + kotlin: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val tablesDB = TablesDB(client) + +tablesDB.createRow( + databaseId = "", + tableId = "", + rowId = "", + data = mapOf("title" to "My Document"), + permissions = listOf( + Permission.read(Role.user("")), + Permission.write(Role.user("")) + ) +)`, + preferTeamPermissions: `// DO this for multi-tenant apps +import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val tablesDB = TablesDB(client) + +tablesDB.createRow( + databaseId = "", + tableId = "", + rowId = "", + data = mapOf("title" to "My Document"), + permissions = listOf( + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ) +)`, + createTeam: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// Create a team when a new tenant/organization signs up +teams.create( + "", // teamId + "", // name + listOf("owner", "admin", "member") // roles (optional) +)`, + createMembershipEmail: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// Send team invitation (email-based) +teams.createMembership( + "", // teamId + listOf("admin", "member"), // roles + "user@example.com", // email + null, // userId (optional) + null, // phone (optional) + "https://yourapp.com/accept-invite", // url + null // name (optional) +)`, + createMembershipUserId: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") // Server SDK + +val teams = Teams(client) + +// Or invite by user ID (if user already exists) +teams.createMembership( + "", // teamId + listOf("admin", "member"), // roles + null, // email + "", // userId + null, // phone + null, // url + null // name +)`, + listMemberships: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// Get all members of a team +val response = teams.listMemberships( + "", // teamId + listOf(), // queries (optional) + null // search (optional) +) + +// Access member data +response.memberships.forEach { membership -> + println(membership.userId) + println(membership.roles) // Array of role strings + println(membership.userName) + println(membership.userEmail) +}`, + updateMembership: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// Update a member's roles (only team owners/admins can do this) +teams.updateMembership( + "", // teamId + "", // membershipId + listOf("admin", "member") // roles +)`, + deleteMembership: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// Remove a member from a team +teams.deleteMembership( + "", // teamId + "" // membershipId +)`, + listTeams: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// List all teams the current user belongs to +val response = teams.list( + listOf(), // queries (optional) + null // search (optional) +) + +response.teams.forEach { team -> + println(team.id) + println(team.name) +}`, + getUserRole: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// Get membership details for current user in a specific team +val response = teams.listMemberships("") + +val userMembership = response.memberships.find { + it.userId == "" +} + +if (userMembership != null) { + println(userMembership.roles) // ['owner', 'admin', etc.] + val hasAdminRole = userMembership.roles.contains("admin") +}`, + createRow: `import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val tablesDB = TablesDB(client) + +// Create document with team-based permissions +tablesDB.createRow( + databaseId = "", + tableId = "", + rowId = "", + data = mapOf( + "title" to "My Document", + "teamId" to "", // Always store teamId for querying + ), + permissions = listOf( + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")), + Permission.delete(Role.team("", "admin")) + ) +)`, + createTable: `import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") // Server SDK requires API key + +val tablesDB = TablesDB(client) + +// Create table with team-based permissions +tablesDB.createTable( + databaseId = "", + tableId = "", + name = "", + permissions = listOf( + Permission.create(Role.team("", "member")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ) +)`, + listRows: `import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.Query + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val tablesDB = TablesDB(client) + +// ALWAYS filter by teamId to ensure tenant isolation +val response = tablesDB.listRows( + databaseId = "", + tableId = "", + queries = listOf( + Query.equal("teamId", ""), // Critical: filter by team + Query.orderDesc("$createdAt"), + Query.limit(25) + ) +)`, + createFile: `import io.appwrite.Client +import io.appwrite.services.Storage +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val storage = Storage(client) + +// Create file with team-based permissions +storage.createFile( + bucketId = "", + fileId = "", + file = fileInput, + permissions = listOf( + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ) +)`, + teamCreationFlow: `// When user creates account/organization +import io.appwrite.Client +import io.appwrite.services.Teams +import io.appwrite.ID + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +val team = teams.create( + ID.unique(), + "Company Name" +) + +// Make creator an owner +teams.createMembership( + team.id, // teamId + listOf("owner"), // roles + null, // email + "", // userId + null, // phone + null, // url + null // name +)`, + inviteFlow: `// Owner/admin invites new member +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +teams.createMembership( + "", // teamId + listOf("member"), // Default role + "user@example.com", // email + null, // userId + null, // phone + "https://yourapp.com/accept-invite", // url + null // name +) +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +val response = teams.listMemberships("") +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +teams.updateMembership( + "", // teamId + "", // membershipId + listOf("admin") // roles +)`, + memberRemoval: `// Remove member (with confirmation) +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +teams.deleteMembership( + "", // teamId + "" // membershipId +)`, + roleCheck: `val response = teams.listMemberships("") + +val userMembership = response.memberships.find { + it.userId == "" +} + +if (userMembership == null || !userMembership.roles.contains("admin")) { + throw Exception("Insufficient permissions") +}` + }, + apple: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let tablesDB = TablesDB(client) + +let row = try await tablesDB.createRow( + databaseId: "", + tableId: "", + rowId: "", + data: ["title": "My Document"], + permissions: [ + Permission.read(Role.user("")), + Permission.write(Role.user("")) + ] +)`, + preferTeamPermissions: `// DO this for multi-tenant apps +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let tablesDB = TablesDB(client) + +let row = try await tablesDB.createRow( + databaseId: "", + tableId: "", + rowId: "", + data: ["title": "My Document"], + permissions: [ + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ] +)`, + createTeam: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Create a team when a new tenant/organization signs up +let team = try await teams.create( + teamId: "", + name: "", + roles: ["owner", "admin", "member"] // optional +)`, + createMembershipEmail: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Send team invitation (email-based) +let membership = try await teams.createMembership( + teamId: "", + roles: ["admin", "member"], + email: "user@example.com", + url: "https://yourapp.com/accept-invite" +)`, + createMembershipUserId: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Or invite by user ID (if user already exists) +let membership = try await teams.createMembership( + teamId: "", + roles: ["admin", "member"], + userId: "" +)`, + listMemberships: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Get all members of a team +let response = try await teams.listMemberships( + teamId: "" +) + +// Access member data +for membership in response.memberships { + print(membership.userId) + print(membership.roles) // Array of role strings + print(membership.userName ?? "") + print(membership.userEmail ?? "") +}`, + updateMembership: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Update a member's roles (only team owners/admins can do this) +let membership = try await teams.updateMembership( + teamId: "", + membershipId: "", + roles: ["admin", "member"] +)`, + deleteMembership: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Remove a member from a team +try await teams.deleteMembership( + teamId: "", + membershipId: "" +)`, + listTeams: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// List all teams the current user belongs to +let response = try await teams.list() + +for team in response.teams { + print(team.id) + print(team.name) +}`, + getUserRole: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Get membership details for current user in a specific team +let response = try await teams.listMemberships(teamId: "") + +let userMembership = response.memberships.first { $0.userId == "" } + +if let membership = userMembership { + print(membership.roles) // ['owner', 'admin', etc.] + let hasAdminRole = membership.roles.contains("admin") +}`, + createRow: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let tablesDB = TablesDB(client) + +// Create document with team-based permissions +let row = try await tablesDB.createRow( + databaseId: "", + tableId: "", + rowId: "", + data: [ + "title": "My Document", + "teamId": "", // Always store teamId for querying + ], + permissions: [ + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")), + Permission.delete(Role.team("", "admin")) + ] +)`, + createTable: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") // Server SDK requires API key + +let tablesDB = TablesDB(client) + +// Create table with team-based permissions +let table = try await tablesDB.createTable( + databaseId: "", + tableId: "", + name: "", + permissions: [ + Permission.create(Role.team("", "member")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ] +)`, + listRows: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let tablesDB = TablesDB(client) + +// ALWAYS filter by teamId to ensure tenant isolation +let response = try await tablesDB.listRows( + databaseId: "", + tableId: "", + queries: [ + Query.equal("teamId", ""), // Critical: filter by team + Query.orderDesc("$createdAt"), + Query.limit(25) + ] +)`, + createFile: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let storage = Storage(client) + +// Create file with team-based permissions +let file = try await storage.createFile( + bucketId: "", + fileId: "", + file: fileInput, + permissions: [ + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ] +)`, + teamCreationFlow: `// When user creates account/organization +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +let team = try await teams.create( + teamId: ID.unique(), + name: "Company Name" +) + +// Make creator an owner +try await teams.createMembership( + teamId: team.id, + roles: ["owner"], + userId: "" +)`, + inviteFlow: `// Owner/admin invites new member +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +let invite = try await teams.createMembership( + teamId: "", + roles: ["member"], // Default role + email: "user@example.com", + url: "https://yourapp.com/accept-invite" +) +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +let response = try await teams.listMemberships(teamId: "") +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +let membership = try await teams.updateMembership( + teamId: "", + membershipId: "", + roles: ["admin"] +)`, + memberRemoval: `// Remove member (with confirmation) +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +try await teams.deleteMembership( + teamId: "", + membershipId: "" +)`, + roleCheck: `let response = try await teams.listMemberships(teamId: "") + +let userMembership = response.memberships.first { $0.userId == "" } + +if userMembership == nil || !userMembership!.roles.contains("admin") { + throw NSError(domain: "Appwrite", code: 403, userInfo: [NSLocalizedDescriptionKey: "Insufficient permissions"]) +}` + }, + android: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val tablesDB = TablesDB(client) + +tablesDB.createRow( + databaseId = "", + tableId = "", + rowId = "", + data = mapOf("title" to "My Document"), + permissions = listOf( + Permission.read(Role.user("")), + Permission.write(Role.user("")) + ) +)`, + preferTeamPermissions: `// DO this for multi-tenant apps +import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val tablesDB = TablesDB(client) + +tablesDB.createRow( + databaseId = "", + tableId = "", + rowId = "", + data = mapOf("title" to "My Document"), + permissions = listOf( + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ) +)`, + createTeam: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Create a team when a new tenant/organization signs up +teams.create( + "", // teamId + "", // name + listOf("owner", "admin", "member") // roles (optional) +)`, + createMembershipEmail: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Send team invitation (email-based) +teams.createMembership( + "", // teamId + listOf("admin", "member"), // roles + "user@example.com", // email + null, // userId (optional) + null, // phone (optional) + "https://yourapp.com/accept-invite", // url + null // name (optional) +)`, + createMembershipUserId: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Or invite by user ID (if user already exists) +teams.createMembership( + "", // teamId + listOf("admin", "member"), // roles + null, // email + "", // userId + null, // phone + null, // url + null // name +)`, + listMemberships: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Get all members of a team +val response = teams.listMemberships( + "", // teamId + listOf(), // queries (optional) + null // search (optional) +) + +// Access member data +response.memberships.forEach { membership -> + Log.d("Appwrite", membership.userId) + Log.d("Appwrite", membership.roles.toString()) // Array of role strings + Log.d("Appwrite", membership.userName ?: "") + Log.d("Appwrite", membership.userEmail ?: "") +}`, + updateMembership: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Update a member's roles (only team owners/admins can do this) +teams.updateMembership( + "", // teamId + "", // membershipId + listOf("admin", "member") // roles +)`, + deleteMembership: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Remove a member from a team +teams.deleteMembership( + "", // teamId + "" // membershipId +)`, + listTeams: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// List all teams the current user belongs to +val response = teams.list( + listOf(), // queries (optional) + null // search (optional) +) + +response.teams.forEach { team -> + Log.d("Appwrite", team.id) + Log.d("Appwrite", team.name) +}`, + getUserRole: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Get membership details for current user in a specific team +val response = teams.listMemberships("") + +val userMembership = response.memberships.find { + it.userId == "" +} + +if (userMembership != null) { + Log.d("Appwrite", userMembership.roles.toString()) // ['owner', 'admin', etc.] + val hasAdminRole = userMembership.roles.contains("admin") +}`, + createRow: `import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val tablesDB = TablesDB(client) + +// Create document with team-based permissions +tablesDB.createRow( + databaseId = "", + tableId = "", + rowId = "", + data = mapOf( + "title" to "My Document", + "teamId" to "", // Always store teamId for querying + ), + permissions = listOf( + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")), + Permission.delete(Role.team("", "admin")) + ) +)`, + createTable: `import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") // Server SDK requires API key + +val tablesDB = TablesDB(client) + +// Create table with team-based permissions +tablesDB.createTable( + databaseId = "", + tableId = "", + name = "", + permissions = listOf( + Permission.create(Role.team("", "member")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ) +)`, + listRows: `import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.Query + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val tablesDB = TablesDB(client) + +// ALWAYS filter by teamId to ensure tenant isolation +val response = tablesDB.listRows( + databaseId = "", + tableId = "", + queries = listOf( + Query.equal("teamId", ""), // Critical: filter by team + Query.orderDesc("$createdAt"), + Query.limit(25) + ) +)`, + createFile: `import io.appwrite.Client +import io.appwrite.services.Storage +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val storage = Storage(client) + +// Create file with team-based permissions +storage.createFile( + bucketId = "", + fileId = "", + file = fileInput, + permissions = listOf( + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ) +)`, + teamCreationFlow: `// When user creates account/organization +import io.appwrite.Client +import io.appwrite.services.Teams +import io.appwrite.ID + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +val team = teams.create( + ID.unique(), + "Company Name" +) + +// Make creator an owner +teams.createMembership( + team.id, // teamId + listOf("owner"), // roles + null, // email + "", // userId + null, // phone + null, // url + null // name +)`, + inviteFlow: `// Owner/admin invites new member +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +teams.createMembership( + "", // teamId + listOf("member"), // Default role + "user@example.com", // email + null, // userId + null, // phone + "https://yourapp.com/accept-invite", // url + null // name +) +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +val response = teams.listMemberships("") +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +teams.updateMembership( + "", // teamId + "", // membershipId + listOf("admin") // roles +)`, + memberRemoval: `// Remove member (with confirmation) +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +teams.deleteMembership( + "", // teamId + "" // membershipId +)`, + roleCheck: `val response = teams.listMemberships("") + +val userMembership = response.memberships.find { + it.userId == "" +} + +if (userMembership == null || !userMembership.roles.contains("admin")) { + throw Exception("Insufficient permissions") +}` + }, + flutter: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +final row = await tablesDB.createRow( + databaseId: '', + tableId: '', + rowId: '', + data: {'title': 'My Document'}, + permissions: [ + Permission.read(Role.user('')), + Permission.write(Role.user('')) + ] +);`, + preferTeamPermissions: `// DO this for multi-tenant apps +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +final row = await tablesDB.createRow( + databaseId: '', + tableId: '', + rowId: '', + data: {'title': 'My Document'}, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +);`, + createTeam: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Create a team when a new tenant/organization signs up +final team = await teams.create( + teamId: '', + name: '', + roles: ['owner', 'admin', 'member'] // optional +);`, + createMembershipEmail: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Send team invitation (email-based) +final invite = await teams.createMembership( + teamId: '', + roles: ['admin', 'member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +);`, + createMembershipUserId: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Or invite by user ID (if user already exists) +final membership = await teams.createMembership( + teamId: '', + roles: ['admin', 'member'], + userId: '' +);`, + listMemberships: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Get all members of a team +final response = await teams.listMemberships(teamId: ''); + +// Access member data +for (final membership in response.memberships) { + print(membership.userId); + print(membership.roles); // Array of role strings + print(membership.userName); + print(membership.userEmail); +}`, + updateMembership: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Update a member's roles (only team owners/admins can do this) +await teams.updateMembership( + teamId: '', + membershipId: '', + roles: ['admin', 'member'] +);`, + deleteMembership: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Remove a member from a team +await teams.deleteMembership( + teamId: '', + membershipId: '' +);`, + listTeams: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// List all teams the current user belongs to +final response = await teams.list(); + +for (final team in response.teams) { + print(team.id); + print(team.name); +}`, + getUserRole: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Get membership details for current user in a specific team +final response = await teams.listMemberships(teamId: ''); + +final userMembership = response.memberships.firstWhere( + (m) => m.userId == '', + orElse: () => null, +); + +if (userMembership != null) { + print(userMembership.roles); // ['owner', 'admin', etc.] + final hasAdminRole = userMembership.roles.contains('admin'); +}`, + createRow: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +// Create document with team-based permissions +final row = await tablesDB.createRow( + databaseId: '', + tableId: '', + rowId: '', + data: { + 'title': 'My Document', + 'teamId': '', // Always store teamId for querying + }, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')), + Permission.delete(Role.team('', 'admin')) + ] +);`, + createTable: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); // Server SDK requires API key + +final tablesDB = TablesDB(client); + +// Create table with team-based permissions +final table = await tablesDB.createTable( + databaseId: '', + tableId: '', + name: '', + permissions: [ + Permission.create(Role.team('', 'member')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +);`, + listRows: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +// ALWAYS filter by teamId to ensure tenant isolation +final response = await tablesDB.listRows( + databaseId: '', + tableId: '', + queries: [ + Query.equal('teamId', ''), // Critical: filter by team + Query.orderDesc('\$createdAt'), + Query.limit(25) + ] +);`, + createFile: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final storage = Storage(client); + +// Create file with team-based permissions +final file = await storage.createFile( + bucketId: '', + fileId: '', + file: fileInput, + permissions: [ + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +);`, + teamCreationFlow: `// When user creates account/organization +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +final team = await teams.create( + teamId: ID.unique(), + name: 'Company Name' +); + +// Make creator an owner +await teams.createMembership( + teamId: team.id, + roles: ['owner'], + userId: '' +);`, + inviteFlow: `// Owner/admin invites new member +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +final invite = await teams.createMembership( + teamId: '', + roles: ['member'], // Default role + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +); +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +final response = await teams.listMemberships(teamId: ''); +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +await teams.updateMembership( + teamId: '', + membershipId: '', + roles: ['admin'] +);`, + memberRemoval: `// Remove member (with confirmation) +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +await teams.deleteMembership( + teamId: '', + membershipId: '' +);`, + roleCheck: `final response = await teams.listMemberships(teamId: ''); + +final userMembership = response.memberships.firstWhere( + (m) => m.userId == '', + orElse: () => null, +); + +if (userMembership == null || !userMembership.roles.contains('admin')) { + throw Exception('Insufficient permissions'); +}` + }, + dart: { + // Dart uses the same syntax as Flutter + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +final row = await tablesDB.createRow( + databaseId: '', + tableId: '', + rowId: '', + data: {'title': 'My Document'}, + permissions: [ + Permission.read(Role.user('')), + Permission.write(Role.user('')) + ] +);`, + preferTeamPermissions: `// DO this for multi-tenant apps +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +final row = await tablesDB.createRow( + databaseId: '', + tableId: '', + rowId: '', + data: {'title': 'My Document'}, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +);`, + createTeam: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Create a team when a new tenant/organization signs up +final team = await teams.create( + teamId: '', + name: '', + roles: ['owner', 'admin', 'member'] // optional +);`, + createMembershipEmail: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Send team invitation (email-based) +final invite = await teams.createMembership( + teamId: '', + roles: ['admin', 'member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +);`, + createMembershipUserId: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Or invite by user ID (if user already exists) +final membership = await teams.createMembership( + teamId: '', + roles: ['admin', 'member'], + userId: '' +);`, + listMemberships: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Get all members of a team +final response = await teams.listMemberships(teamId: ''); + +// Access member data +for (final membership in response.memberships) { + print(membership.userId); + print(membership.roles); // Array of role strings + print(membership.userName); + print(membership.userEmail); +}`, + updateMembership: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Update a member's roles (only team owners/admins can do this) +await teams.updateMembership( + teamId: '', + membershipId: '', + roles: ['admin', 'member'] +);`, + deleteMembership: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Remove a member from a team +await teams.deleteMembership( + teamId: '', + membershipId: '' +);`, + listTeams: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// List all teams the current user belongs to +final response = await teams.list(); + +for (final team in response.teams) { + print(team.id); + print(team.name); +}`, + getUserRole: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Get membership details for current user in a specific team +final response = await teams.listMemberships(teamId: ''); + +final userMembership = response.memberships.firstWhere( + (m) => m.userId == '', + orElse: () => null, +); + +if (userMembership != null) { + print(userMembership.roles); // ['owner', 'admin', etc.] + final hasAdminRole = userMembership.roles.contains('admin'); +}`, + createRow: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +// Create document with team-based permissions +final row = await tablesDB.createRow( + databaseId: '', + tableId: '', + rowId: '', + data: { + 'title': 'My Document', + 'teamId': '', // Always store teamId for querying + }, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')), + Permission.delete(Role.team('', 'admin')) + ] +);`, + createTable: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); // Server SDK requires API key + +final tablesDB = TablesDB(client); + +// Create table with team-based permissions +final table = await tablesDB.createTable( + databaseId: '', + tableId: '', + name: '', + permissions: [ + Permission.create(Role.team('', 'member')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +);`, + listRows: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +// ALWAYS filter by teamId to ensure tenant isolation +final response = await tablesDB.listRows( + databaseId: '', + tableId: '', + queries: [ + Query.equal('teamId', ''), // Critical: filter by team + Query.orderDesc('\$createdAt'), + Query.limit(25) + ] +);`, + createFile: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final storage = Storage(client); + +// Create file with team-based permissions +final file = await storage.createFile( + bucketId: '', + fileId: '', + file: fileInput, + permissions: [ + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +);`, + teamCreationFlow: `// When user creates account/organization +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +final team = await teams.create( + teamId: ID.unique(), + name: 'Company Name' +); + +// Make creator an owner +await teams.createMembership( + teamId: team.id, + roles: ['owner'], + userId: '' +);`, + inviteFlow: `// Owner/admin invites new member +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +final invite = await teams.createMembership( + teamId: '', + roles: ['member'], // Default role + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +); +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +final response = await teams.listMemberships(teamId: ''); +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +await teams.updateMembership( + teamId: '', + membershipId: '', + roles: ['admin'] +);`, + memberRemoval: `// Remove member (with confirmation) +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +await teams.deleteMembership( + teamId: '', + membershipId: '' +);`, + roleCheck: `final response = await teams.listMemberships(teamId: ''); + +final userMembership = response.memberships.firstWhere( + (m) => m.userId == '', + orElse: () => null, +); + +if (userMembership == null || !userMembership.roles.contains('admin')) { + throw Exception('Insufficient permissions'); +}` + } +}; + +/** + * Get permission examples for a specific SDK + * @param {string} sdk - The SDK name (javascript, python, php, go, etc.) + * @returns {Object} Permission examples for the SDK + */ +export function getPermissionExamples(sdk) { + // Map SDK names to their examples + /** @type {Record} */ + const sdkMap = { + javascript: permissionExamples.javascript, + 'react-native': permissionExamples['react-native'], + python: permissionExamples.python, + php: permissionExamples.php, + go: permissionExamples.go, + ruby: permissionExamples.ruby, + dotnet: permissionExamples.dotnet, + swift: permissionExamples.swift, + kotlin: permissionExamples.kotlin, + apple: permissionExamples.apple, + android: permissionExamples.android, + flutter: permissionExamples.flutter, + dart: permissionExamples.dart + }; + + return sdkMap[sdk] || permissionExamples.javascript; // Default to JavaScript +} + diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index c81b805..30d8d17 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -233,6 +233,9 @@ When building applications that involve multiple users or tenants: */ async function generatePermissionsSection(sdk, framework) { const { authProductLinks } = await import('./languages/common/products.js'); + const { getPermissionExamples } = await import('./languages/common/permissions-examples.js'); + const examples = getPermissionExamples(sdk); + return `## Permissions & Multi-Tenancy This section is CRITICAL for building secure, scalable applications with Appwrite. Multi-tenancy is one of the most important architectural patterns in modern applications, and Appwrite's team-based permission system is designed specifically for this. @@ -246,12 +249,8 @@ Multi-tenancy allows a single application instance to serve multiple isolated gr **ALWAYS PREFER TEAM/MEMBER-BASED ROLES over user-specific roles.** This is a fundamental architectural decision: #### Avoid: User-Specific Permissions -\`\`\` -// DON'T do this for multi-tenant apps -create(collectionId, data, [ - Permission.read(Role.user(userId1)), - Permission.write(Role.user(userId1)) -]) +\`\`\`${getLanguageFromSdk(sdk)} +${examples.avoidUserPermissions} \`\`\` **Problems with user-specific permissions:** @@ -262,16 +261,8 @@ create(collectionId, data, [ - Maintenance nightmare as teams grow #### Prefer: Team/Member-Based Roles -\`\`\` -// DO this for multi-tenant apps -create(collectionId, data, [ - Permission.read(Role.team(teamId, "owner")), - Permission.read(Role.team(teamId, "admin")), - Permission.read(Role.team(teamId, "member")), - Permission.update(Role.team(teamId, "owner")), - Permission.update(Role.team(teamId, "admin")), - Permission.delete(Role.team(teamId, "owner")) -]) +\`\`\`${getLanguageFromSdk(sdk)} +${examples.preferTeamPermissions} \`\`\` **Benefits of team/member-based roles:** @@ -288,15 +279,8 @@ create(collectionId, data, [ Teams in Appwrite represent tenants. Each team should map to a business entity (company, organization, workspace, etc.). **Creating a team:** -\`\`\` -import { Teams } from 'appwrite'; - -// Create a team when a new tenant/organization signs up -const team = await teams.create( - teamId, // Unique team ID (can be auto-generated) - teamName, // Display name - roles // Array of role strings: ['owner', 'admin', 'member'] -); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.createTeam} \`\`\` #### Step 2: Define Custom Roles @@ -308,9 +292,7 @@ Create roles that match your application's permission model. Common roles: - **viewer**: Read-only access **Creating custom roles (Server-side only):** -\`\`\` -import { Teams } from 'appwrite'; - +\`\`\`${getLanguageFromSdk(sdk)} // Define roles when creating the team (optional, defaults exist) // Or create via Appwrite Console or Server SDK // Roles are created per team, allowing different permission models per tenant @@ -322,94 +304,42 @@ Member management is the foundation of multi-tenant applications. Here's how to **A. Invite Members to Teams** +\`\`\`${getLanguageFromSdk(sdk)} +${examples.createMembershipEmail} \`\`\` -import { Teams } from 'appwrite'; - -// Send team invitation (email-based) -const invite = await teams.createMembership( - teamId, - email, // Email of user to invite - roles, // Array of role strings: ['admin', 'member'] - url // Invitation redirect URL -); - -// Or invite by user ID (if user already exists) -const membership = await teams.createMembership( - teamId, - userId, - roles -); + +\`\`\`${getLanguageFromSdk(sdk)} +${examples.createMembershipUserId} \`\`\` **B. List Team Members** -\`\`\` -import { Teams } from 'appwrite'; - -// Get all members of a team -const memberships = await teams.listMemberships(teamId); - -// Access member data -memberships.memberships.forEach(membership => { - console.log(membership.userId); - console.log(membership.roles); // Array of role strings - console.log(membership.userName); - console.log(membership.userEmail); -}); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.listMemberships} \`\`\` **C. Update Member Roles** -\`\`\` -import { Teams } from 'appwrite'; - -// Update a member's roles (only team owners/admins can do this) -await teams.updateMembershipRoles( - teamId, - membershipId, - ['admin', 'member'] // New roles array -); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.updateMembership} \`\`\` **D. Remove Members** -\`\`\` -import { Teams } from 'appwrite'; - -// Remove a member from a team -await teams.deleteMembership(teamId, membershipId); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.deleteMembership} \`\`\` **E. Get Current User's Teams** -\`\`\` -import { Teams } from 'appwrite'; - -// List all teams the current user belongs to -const teams = await teams.list(); - -teams.teams.forEach(team => { - console.log(team.$id); - console.log(team.name); -}); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.listTeams} \`\`\` **F. Get Current User's Role in a Team** -\`\`\` -import { Teams } from 'appwrite'; - -// Get membership details for current user in a specific team -const memberships = await teams.listMemberships(teamId); - -const userMembership = memberships.memberships.find( - m => m.userId === currentUserId -); - -if (userMembership) { - console.log(userMembership.roles); // ['owner', 'admin', etc.] - const hasAdminRole = userMembership.roles.includes('admin'); -} +\`\`\`${getLanguageFromSdk(sdk)} +${examples.getUserRole} \`\`\` #### Step 4: Apply Permissions in Collections @@ -418,91 +348,32 @@ When creating documents in multi-tenant applications, always use team roles: **Database Collections:** -\`\`\` -import { TablesDB, Permission, Role } from 'appwrite'; - -// Create document with team-based permissions -await tablesdb.createRow( - databaseId, - tableId, - documentId, - { - title: 'My Document', - teamId: teamId, // Always store teamId for querying - // ... other fields - }, - [ - // Owners and admins can do everything - Permission.read(Role.team(teamId, "owner")), - Permission.read(Role.team(teamId, "admin")), - Permission.read(Role.team(teamId, "member")), - Permission.update(Role.team(teamId, "owner")), - Permission.update(Role.team(teamId, "admin")), - Permission.delete(Role.team(teamId, "owner")), - Permission.delete(Role.team(teamId, "admin")) - ] -); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.createRow} \`\`\` **Collection-Level Permissions:** When creating collections, set default permissions: -\`\`\` -import { TablesDB, Permission, Role } from 'appwrite'; - -// Create collection with team-based permissions -await tablesdb.createTable( - databaseId, - tableId, - tableName, - [ - // Collection permissions - Permission.create(Role.team(teamId, "member")), - Permission.read(Role.team(teamId, "member")), - Permission.update(Role.team(teamId, "admin")), - Permission.delete(Role.team(teamId, "owner")) - ] -); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.createTable} \`\`\` #### Step 5: Query with Team Isolation Always filter queries by teamId to ensure data isolation: -\`\`\` -import { TablesDB, Query } from 'appwrite'; - -// ALWAYS filter by teamId to ensure tenant isolation -const documents = await tablesdb.listDocuments( - databaseId, - tableId, - [ - Query.equal('teamId', teamId), // Critical: filter by team - Query.orderDesc('$createdAt'), - Query.limit(25) - ] -); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.listRows} \`\`\` #### Step 6: Storage Permissions Apply the same team-based permission pattern to storage: -\`\`\` -import { Storage, Permission, Role } from 'appwrite'; - -// Create file with team-based permissions -await storage.createFile( - bucketId, - fileId, - fileInput, - [ - Permission.read(Role.team(teamId, "member")), - Permission.update(Role.team(teamId, "admin")), - Permission.delete(Role.team(teamId, "owner")) - ] -); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.createFile} \`\`\` ### Complete Member Management Implementation Pattern @@ -510,42 +381,28 @@ await storage.createFile( Here's a complete pattern for building member management UI and logic: **1. Team Creation Flow:** -\`\`\` -// When user creates account/organization -const team = await teams.create(uniqueId(), 'Company Name'); -// Make creator an owner -await teams.createMembership(team.$id, userId, ['owner']); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.teamCreationFlow} \`\`\` **2. Invite Flow:** -\`\`\` -// Owner/admin invites new member -const invite = await teams.createMembership( - teamId, - email, - ['member'], // Default role - 'https://yourapp.com/accept-invite' // Redirect after accepting -); -// User receives email, clicks link, accepts invitation +\`\`\`${getLanguageFromSdk(sdk)} +${examples.inviteFlow} \`\`\` **3. Member List UI:** -\`\`\` -// Display all team members with their roles -const memberships = await teams.listMemberships(teamId); -// Show list with role badges and action buttons +\`\`\`${getLanguageFromSdk(sdk)} +${examples.memberListUI} \`\`\` **4. Role Change:** -\`\`\` -// Admin/owner changes member role -await teams.updateMembershipRoles(teamId, membershipId, ['admin']); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.roleChange} \`\`\` **5. Member Removal:** -\`\`\` -// Remove member (with confirmation) -await teams.deleteMembership(teamId, membershipId); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.memberRemoval} \`\`\` ### Permission Best Practices @@ -563,11 +420,8 @@ await teams.deleteMembership(teamId, membershipId); 6. **Query Isolation**: Always include \`teamId\` in queries to prevent cross-tenant data leaks 7. **Role Checks**: Before allowing sensitive operations, check the user's role in the team: - \`\`\` - const membership = await getCurrentUserMembership(teamId); - if (!membership.roles.includes('admin')) { - throw new Error('Insufficient permissions'); - } + \`\`\`${getLanguageFromSdk(sdk)} +${examples.roleCheck} \`\`\` 8. **Permission Inheritance**: Consider if child resources should inherit parent team permissions @@ -614,6 +468,30 @@ ${authProductLinks} For comprehensive permission patterns and examples, always refer to the official Appwrite documentation on Teams, Multi-tenancy, and Permissions.`; } +/** + * Get language identifier for code blocks based on SDK + * @param {string} sdk + * @returns {string} + */ +function getLanguageFromSdk(sdk) { + const languageMap = { + javascript: 'javascript', + 'react-native': 'javascript', + python: 'python', + php: 'php', + go: 'go', + flutter: 'dart', + dart: 'dart', + apple: 'swift', + android: 'kotlin', + swift: 'swift', + kotlin: 'kotlin', + ruby: 'ruby', + dotnet: 'csharp' + }; + return languageMap[sdk] || 'javascript'; +} + /** * @param {string} sdk * @param {string} framework From 5d7e36750dbd33c84df514b6349e9982634ef87f Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Sat, 29 Nov 2025 00:42:38 +0530 Subject: [PATCH 11/19] Add API endpoints for SDK listing and rule generation --- README.md | 93 +++++++++++++++++++++++++++++++++ src/routes/api/rules/+server.js | 85 ++++++++++++++++++++++++++++++ src/routes/api/sdks/+server.js | 36 +++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 src/routes/api/rules/+server.js create mode 100644 src/routes/api/sdks/+server.js diff --git a/README.md b/README.md index 72eb616..3e07947 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,94 @@ pnpm dev The generated rules file can be used in Cursor IDE to provide AI-assisted development guidance specific to your Appwrite setup. +## API Endpoints + +The application exposes REST API endpoints for programmatic access to rule generation. + +### Get Available SDKs and Frameworks + +**GET** `/api/sdks` + +Returns a list of all available SDKs, their frameworks, and available features. + +**Response:** +```json +{ + "sdks": [ + { + "id": "javascript", + "name": "JavaScript/TypeScript", + "frameworks": ["nextjs", "react", "vue", ...], + "importSyntax": "import", + "exportSyntax": "export", + "asyncSyntax": "async/await" + }, + ... + ], + "availableFeatures": ["auth", "database", "storage", "functions", "messaging", "sites", "realtime"] +} +``` + +### Generate Rules + +**GET** `/api/rules` + +Generate rules using query parameters. + +**Query Parameters:** +- `sdk` (required): SDK identifier (e.g., `javascript`, `python`, `go`) +- `framework` (required): Framework identifier (e.g., `nextjs`, `react`, `flask`) +- `features` (optional): Comma-separated list of features (default: `auth`) + - Available: `auth`, `database`, `storage`, `functions`, `messaging`, `sites`, `realtime` + - Special: `all` - includes all available features +- `mcp` (optional): Include MCP recommendations (`true` or `false`, default: `false`) +- `format` (optional): Response format (`text` or `json`, default: `text`) + +**Example:** +```bash +# Get rules as markdown text +curl "http://localhost:5173/api/rules?sdk=javascript&framework=nextjs&features=auth,database&format=text" + +# Get all features +curl "http://localhost:5173/api/rules?sdk=javascript&framework=nextjs&features=all" + +# Get rules as JSON +curl "http://localhost:5173/api/rules?sdk=python&framework=flask&features=auth,storage&format=json" +``` + +**Response (format=text):** +- Content-Type: `text/markdown; charset=utf-8` +- Returns the generated rules as markdown text +- Includes `Content-Disposition` header for file download + +**Response (format=json):** +```json +{ + "sdk": "javascript", + "framework": "nextjs", + "features": ["auth", "database"], + "includeMCP": false, + "rules": "---\ndescription: You are an expert developer...\n---\n\n# Appwrite Development Rules\n..." +} +``` + +**Error Responses:** +- `400 Bad Request`: Invalid SDK or framework +- `500 Internal Server Error`: Server error during rule generation + +**Example Usage:** + +```javascript +// Fetch rules using fetch API +const response = await fetch('/api/rules?sdk=javascript&framework=nextjs&features=auth,database'); +const rules = await response.text(); + +// Get all features as JSON +const response = await fetch('/api/rules?sdk=javascript&framework=nextjs&features=all&format=json'); +const data = await response.json(); +console.log(data.rules); +``` + ## Development ### Available Scripts @@ -98,6 +186,11 @@ appwrite-cursor-rules/ │ │ ├── rules-generator.js # Main rules generation logic │ │ └── utils/ # Utility functions │ └── routes/ +│ ├── api/ +│ │ ├── rules/ +│ │ │ └── +server.js # API endpoint for generating rules +│ │ └── sdks/ +│ │ └── +server.js # API endpoint for listing SDKs │ ├── +page.svelte # Main application page │ └── +layout.svelte # Layout component ├── scripts/ diff --git a/src/routes/api/rules/+server.js b/src/routes/api/rules/+server.js new file mode 100644 index 0000000..65f8a03 --- /dev/null +++ b/src/routes/api/rules/+server.js @@ -0,0 +1,85 @@ +import { generateRules, SDK_OPTIONS } from '$lib/rules-generator.js'; +import { json, text } from '@sveltejs/kit'; + +// All available features +const ALL_FEATURES = ['auth', 'database', 'storage', 'functions', 'messaging', 'sites', 'realtime']; + +/** + * Normalize features array - replace 'all' with all available features + * @param {string[]} features + * @returns {string[]} + */ +function normalizeFeatures(features) { + if (features.includes('all')) { + return ALL_FEATURES; + } + return features; +} + +/** + * @param {{ url: URL }} event + */ +export async function GET({ url }) { + try { + const sdk = url.searchParams.get('sdk') || 'javascript'; + const framework = url.searchParams.get('framework') || 'nextjs'; + const featuresParam = url.searchParams.get('features'); + const includeMCP = url.searchParams.get('mcp') === 'true'; + const format = url.searchParams.get('format') || 'text'; // 'text' or 'json' + + // Validate SDK + if (!SDK_OPTIONS[sdk]) { + return json( + { error: `Invalid SDK: ${sdk}. Available SDKs: ${Object.keys(SDK_OPTIONS).join(', ')}` }, + { status: 400 } + ); + } + + // Validate framework + const sdkInfo = SDK_OPTIONS[sdk]; + if (!sdkInfo.frameworks.includes(framework)) { + return json( + { + error: `Invalid framework: ${framework} for SDK: ${sdk}. Available frameworks: ${sdkInfo.frameworks.join(', ')}` + }, + { status: 400 } + ); + } + + // Parse and normalize features + const features = normalizeFeatures( + featuresParam ? featuresParam.split(',').filter(Boolean) : ['auth'] + ); + + // Generate rules + const rules = await generateRules({ + sdk, + framework, + features, + includeMCP + }); + + // Return based on format + if (format === 'json') { + return json({ + sdk, + framework, + features, + includeMCP, + rules + }); + } + + return text(rules, { + headers: { + 'Content-Type': 'text/markdown; charset=utf-8', + 'Content-Disposition': `attachment; filename="APPWRITE-${sdk}-${framework}.mdc"` + } + }); + } catch (error) { + console.error('Error generating rules:', error); + const errorMessage = error instanceof Error ? error.message : 'Failed to generate rules'; + return json({ error: errorMessage }, { status: 500 }); + } +} + diff --git a/src/routes/api/sdks/+server.js b/src/routes/api/sdks/+server.js new file mode 100644 index 0000000..744ddcc --- /dev/null +++ b/src/routes/api/sdks/+server.js @@ -0,0 +1,36 @@ +import { SDK_OPTIONS } from '$lib/rules-generator.js'; +import { json } from '@sveltejs/kit'; + +/** + * @param {Request} request + */ +export async function GET() { + try { + const sdks = Object.entries(SDK_OPTIONS).map(([key, value]) => ({ + id: key, + name: value.name, + frameworks: value.frameworks, + importSyntax: value.importSyntax, + exportSyntax: value.exportSyntax, + asyncSyntax: value.asyncSyntax + })); + + return json({ + sdks, + availableFeatures: [ + 'auth', + 'database', + 'storage', + 'functions', + 'messaging', + 'sites', + 'realtime', + 'all' + ] + }); + } catch (error) { + console.error('Error fetching SDKs:', error); + return json({ error: error.message || 'Failed to fetch SDKs' }, { status: 500 }); + } +} + From 9c347b542ee6bdf3d91d5e4a43d02ededc755636 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Sat, 29 Nov 2025 00:54:09 +0530 Subject: [PATCH 12/19] Replace "document" with "row" and "collection" with "table" --- .../languages/common/permissions-examples.js | 104 +++++++++--------- src/lib/rules-generator.js | 24 ++-- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/lib/languages/common/permissions-examples.js b/src/lib/languages/common/permissions-examples.js index 0661d5e..6ff4a62 100644 --- a/src/lib/languages/common/permissions-examples.js +++ b/src/lib/languages/common/permissions-examples.js @@ -12,7 +12,7 @@ await tablesDB.createRow({ databaseId: '', tableId: '', rowId: '', - data: { title: 'My Document' }, + data: { title: 'My Row' }, permissions: [ Permission.read(Role.user('')), Permission.write(Role.user('')) @@ -25,7 +25,7 @@ await tablesDB.createRow({ databaseId: '', tableId: '', rowId: '', - data: { title: 'My Document' }, + data: { title: 'My Row' }, permissions: [ Permission.read(Role.team('', 'owner')), Permission.read(Role.team('', 'admin')), @@ -169,13 +169,13 @@ const client = new Client() const tablesDB = new TablesDB(client); -// Create document with team-based permissions +// Create row with team-based permissions await tablesDB.createRow({ databaseId: '', tableId: '', rowId: '', data: { - title: 'My Document', + title: 'My Row', teamId: '', // Always store teamId for querying // ... other fields }, @@ -205,7 +205,7 @@ await tablesDB.createTable({ tableId: '', name: '', permissions: [ - // Collection permissions + // Table permissions Permission.create(Role.team('', 'member')), Permission.read(Role.team('', 'member')), Permission.update(Role.team('', 'admin')), @@ -346,7 +346,7 @@ await tablesDB.createRow({ databaseId: '', tableId: '', rowId: '', - data: { title: 'My Document' }, + data: { title: 'My Row' }, permissions: [ Permission.read(Role.user('')), Permission.write(Role.user('')) @@ -359,7 +359,7 @@ await tablesDB.createRow({ databaseId: '', tableId: '', rowId: '', - data: { title: 'My Document' }, + data: { title: 'My Row' }, permissions: [ Permission.read(Role.team('', 'owner')), Permission.read(Role.team('', 'admin')), @@ -499,7 +499,7 @@ await tablesDB.createRow({ tableId: '', rowId: '', data: { - title: 'My Document', + title: 'My Row', teamId: '', }, permissions: [ @@ -664,7 +664,7 @@ tables_db.create_row( database_id='', table_id='', row_id='', - data={'title': 'My Document'}, + data={'title': 'My Row'}, permissions=[ Permission.read(Role.user('')), Permission.write(Role.user('')) @@ -686,7 +686,7 @@ tables_db.create_row( database_id='', table_id='', row_id='', - data={'title': 'My Document'}, + data={'title': 'My Row'}, permissions=[ Permission.read(Role.team('', 'owner')), Permission.read(Role.team('', 'admin')), @@ -843,13 +843,13 @@ client.set_session('') tables_db = TablesDB(client) -# Create document with team-based permissions +# Create row with team-based permissions tables_db.create_row( database_id='', table_id='', row_id='', data={ - 'title': 'My Document', + 'title': 'My Row', 'teamId': '', # Always store teamId for querying }, permissions=[ @@ -1042,7 +1042,7 @@ $tablesDB->createRow( databaseId: '', tableId: '', rowId: '', - data: ['title' => 'My Document'], + data: ['title' => 'My Row'], permissions: [ Permission::read(Role::user('')), Permission::write(Role::user('')) @@ -1066,7 +1066,7 @@ $tablesDB->createRow( databaseId: '', tableId: '', rowId: '', - data: ['title' => 'My Document'], + data: ['title' => 'My Row'], permissions: [ Permission::read(Role::team('', 'owner')), Permission::read(Role::team('', 'admin')), @@ -1237,13 +1237,13 @@ $client = (new Client()) $tablesDB = new TablesDB($client); -// Create document with team-based permissions +// Create row with team-based permissions $tablesDB->createRow( databaseId: '', tableId: '', rowId: '', data: [ - 'title' => 'My Document', + 'title' => 'My Row', 'teamId' => '', // Always store teamId for querying ], permissions: [ @@ -1451,7 +1451,7 @@ tablesDB.CreateRow( "", "", map[string]interface{}{ - "title": "My Document", + "title": "My Row", }, []interface{}{ models.PermissionRead(models.RoleUser("")), @@ -1480,7 +1480,7 @@ tablesDB.CreateRow( "", "", map[string]interface{}{ - "title": "My Document", + "title": "My Row", }, []interface{}{ models.PermissionRead(models.RoleTeam("", "owner")), @@ -1698,13 +1698,13 @@ client := client.New( tablesDB := tablesdb.New(client) -// Create document with team-based permissions +// Create row with team-based permissions tablesDB.CreateRow( "", "", "", map[string]interface{}{ - "title": "My Document", + "title": "My Row", "teamId": "", // Always store teamId for querying }, []interface{}{ @@ -1949,7 +1949,7 @@ tables_db.create_row( database_id: '', table_id: '', row_id: '', - data: {'title' => 'My Document'}, + data: {'title' => 'My Row'}, permissions: [ Permission.read(Role.user('')), Permission.write(Role.user('')) @@ -1971,7 +1971,7 @@ tables_db.create_row( database_id: '', table_id: '', row_id: '', - data: {'title' => 'My Document'}, + data: {'title' => 'My Row'}, permissions: [ Permission.read(Role.team('', 'owner')), Permission.read(Role.team('', 'admin')), @@ -2138,13 +2138,13 @@ client = Client.new tables_db = TablesDB.new(client) -# Create document with team-based permissions +# Create row with team-based permissions tables_db.create_row( database_id: '', table_id: '', row_id: '', data: { - 'title' => 'My Document', + 'title' => 'My Row', 'teamId' => '', # Always store teamId for querying }, permissions: [ @@ -2339,7 +2339,7 @@ await tablesDB.CreateRow( databaseId: "", tableId: "", rowId: "", - data: new { title = "My Document" }, + data: new { title = "My Row" }, permissions: new List { Permission.Read(Role.User("")), Permission.Write(Role.User("")) @@ -2361,7 +2361,7 @@ await tablesDB.CreateRow( databaseId: "", tableId: "", rowId: "", - data: new { title = "My Document" }, + data: new { title = "My Row" }, permissions: new List { Permission.Read(Role.Team("", "owner")), Permission.Read(Role.Team("", "admin")), @@ -2529,13 +2529,13 @@ Client client = new Client() TablesDB tablesDB = new TablesDB(client); -// Create document with team-based permissions +// Create row with team-based permissions await tablesDB.CreateRow( databaseId: "", tableId: "", rowId: "", data: new { - title = "My Document", + title = "My Row", teamId = "", // Always store teamId for querying }, permissions: new List { @@ -2730,7 +2730,7 @@ try await tablesDB.createRow( databaseId: "", tableId: "", rowId: "", - data: ["title": "My Document"], + data: ["title": "My Row"], permissions: [ Permission.read(Role.user("")), Permission.write(Role.user("")) @@ -2750,7 +2750,7 @@ try await tablesDB.createRow( databaseId: "", tableId: "", rowId: "", - data: ["title": "My Document"], + data: ["title": "My Row"], permissions: [ Permission.read(Role.team("", "owner")), Permission.read(Role.team("", "admin")), @@ -2897,13 +2897,13 @@ let client = Client() let tablesDB = TablesDB(client) -// Create document with team-based permissions +// Create row with team-based permissions try await tablesDB.createRow( databaseId: "", tableId: "", rowId: "", data: [ - "title": "My Document", + "title": "My Row", "teamId": "", // Always store teamId for querying ], permissions: [ @@ -3081,7 +3081,7 @@ tablesDB.createRow( databaseId = "", tableId = "", rowId = "", - data = mapOf("title" to "My Document"), + data = mapOf("title" to "My Row"), permissions = listOf( Permission.read(Role.user("")), Permission.write(Role.user("")) @@ -3104,7 +3104,7 @@ tablesDB.createRow( databaseId = "", tableId = "", rowId = "", - data = mapOf("title" to "My Document"), + data = mapOf("title" to "My Row"), permissions = listOf( Permission.read(Role.team("", "owner")), Permission.read(Role.team("", "admin")), @@ -3278,13 +3278,13 @@ val client = Client() val tablesDB = TablesDB(client) -// Create document with team-based permissions +// Create row with team-based permissions tablesDB.createRow( databaseId = "", tableId = "", rowId = "", data = mapOf( - "title" to "My Document", + "title" to "My Row", "teamId" to "", // Always store teamId for querying ), permissions = listOf( @@ -3481,7 +3481,7 @@ let row = try await tablesDB.createRow( databaseId: "", tableId: "", rowId: "", - data: ["title": "My Document"], + data: ["title": "My Row"], permissions: [ Permission.read(Role.user("")), Permission.write(Role.user("")) @@ -3500,7 +3500,7 @@ let row = try await tablesDB.createRow( databaseId: "", tableId: "", rowId: "", - data: ["title": "My Document"], + data: ["title": "My Row"], permissions: [ Permission.read(Role.team("", "owner")), Permission.read(Role.team("", "admin")), @@ -3640,13 +3640,13 @@ let client = Client() let tablesDB = TablesDB(client) -// Create document with team-based permissions +// Create row with team-based permissions let row = try await tablesDB.createRow( databaseId: "", tableId: "", rowId: "", data: [ - "title": "My Document", + "title": "My Row", "teamId": "", // Always store teamId for querying ], permissions: [ @@ -3816,7 +3816,7 @@ tablesDB.createRow( databaseId = "", tableId = "", rowId = "", - data = mapOf("title" to "My Document"), + data = mapOf("title" to "My Row"), permissions = listOf( Permission.read(Role.user("")), Permission.write(Role.user("")) @@ -3838,7 +3838,7 @@ tablesDB.createRow( databaseId = "", tableId = "", rowId = "", - data = mapOf("title" to "My Document"), + data = mapOf("title" to "My Row"), permissions = listOf( Permission.read(Role.team("", "owner")), Permission.read(Role.team("", "admin")), @@ -4003,13 +4003,13 @@ val client = Client(context) val tablesDB = TablesDB(client) -// Create document with team-based permissions +// Create row with team-based permissions tablesDB.createRow( databaseId = "", tableId = "", rowId = "", data = mapOf( - "title" to "My Document", + "title" to "My Row", "teamId" to "", // Always store teamId for querying ), permissions = listOf( @@ -4199,7 +4199,7 @@ final row = await tablesDB.createRow( databaseId: '', tableId: '', rowId: '', - data: {'title': 'My Document'}, + data: {'title': 'My Row'}, permissions: [ Permission.read(Role.user('')), Permission.write(Role.user('')) @@ -4218,7 +4218,7 @@ final row = await tablesDB.createRow( databaseId: '', tableId: '', rowId: '', - data: {'title': 'My Document'}, + data: {'title': 'My Row'}, permissions: [ Permission.read(Role.team('', 'owner')), Permission.read(Role.team('', 'admin')), @@ -4359,13 +4359,13 @@ final client = Client() final tablesDB = TablesDB(client); -// Create document with team-based permissions +// Create row with team-based permissions final row = await tablesDB.createRow( databaseId: '', tableId: '', rowId: '', data: { - 'title': 'My Document', + 'title': 'My Row', 'teamId': '', // Always store teamId for querying }, permissions: [ @@ -4536,7 +4536,7 @@ final row = await tablesDB.createRow( databaseId: '', tableId: '', rowId: '', - data: {'title': 'My Document'}, + data: {'title': 'My Row'}, permissions: [ Permission.read(Role.user('')), Permission.write(Role.user('')) @@ -4555,7 +4555,7 @@ final row = await tablesDB.createRow( databaseId: '', tableId: '', rowId: '', - data: {'title': 'My Document'}, + data: {'title': 'My Row'}, permissions: [ Permission.read(Role.team('', 'owner')), Permission.read(Role.team('', 'admin')), @@ -4696,13 +4696,13 @@ final client = Client() final tablesDB = TablesDB(client); -// Create document with team-based permissions +// Create row with team-based permissions final row = await tablesDB.createRow( databaseId: '', tableId: '', rowId: '', data: { - 'title': 'My Document', + 'title': 'My Row', 'teamId': '', // Always store teamId for querying }, permissions: [ diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index 30d8d17..6d14ce9 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -255,7 +255,7 @@ ${examples.avoidUserPermissions} **Problems with user-specific permissions:** - Hard to scale when users need to share resources -- Difficult to add/remove access without updating every document +- Difficult to add/remove access without updating every row - No way to represent organizational hierarchies - Poor support for collaborative features - Maintenance nightmare as teams grow @@ -267,7 +267,7 @@ ${examples.preferTeamPermissions} **Benefits of team/member-based roles:** - Automatic access for all team members based on their role -- Easy to add/remove members without touching documents +- Easy to add/remove members without touching rows - Scales naturally as teams grow - Supports organizational hierarchies and complex permissions - Industry-standard pattern for SaaS applications @@ -342,19 +342,19 @@ ${examples.listTeams} ${examples.getUserRole} \`\`\` -#### Step 4: Apply Permissions in Collections +#### Step 4: Apply Permissions in Tables -When creating documents in multi-tenant applications, always use team roles: +When creating rows in multi-tenant applications, always use team roles: -**Database Collections:** +**Database Tables:** \`\`\`${getLanguageFromSdk(sdk)} ${examples.createRow} \`\`\` -**Collection-Level Permissions:** +**Table-Level Permissions:** -When creating collections, set default permissions: +When creating tables, set default permissions: \`\`\`${getLanguageFromSdk(sdk)} ${examples.createTable} @@ -407,7 +407,7 @@ ${examples.memberRemoval} ### Permission Best Practices -1. **Always Store teamId**: Every document/resource in a multi-tenant app should have a \`teamId\` field for filtering and isolation +1. **Always Store teamId**: Every row/resource in a multi-tenant app should have a \`teamId\` field for filtering and isolation 2. **Default Deny**: Don't grant permissions unless explicitly needed. Use minimal permission sets. @@ -426,7 +426,7 @@ ${examples.roleCheck} 8. **Permission Inheritance**: Consider if child resources should inherit parent team permissions -9. **Document-Level Permissions**: For fine-grained control, set permissions on individual documents while still using team roles +9. **Row-Level Permissions**: For fine-grained control, set permissions on individual rows while still using team roles 10. **Audit Trail**: Log permission changes and team membership changes for security auditing @@ -507,10 +507,10 @@ ${databaseProductLinks} - **SDK Usage**: Always use \`TablesDB\` instead of \`Databases\` in the SDKs - **Permissions & Multi-Tenancy**: ALWAYS use team/member-based roles for permissions (see Permissions & Multi-Tenancy section above). Never use user-specific permissions in multi-tenant applications -- **Tenant Isolation**: Always include \`teamId\` fields in your documents and filter queries by \`teamId\` to ensure complete data isolation between tenants -- **Permission Patterns**: Apply team roles (owner, admin, member, viewer) consistently across all collections. Use Role.team() for all permission checks +- **Tenant Isolation**: Always include \`teamId\` fields in your rows and filter queries by \`teamId\` to ensure complete data isolation between tenants +- **Permission Patterns**: Apply team roles (owner, admin, member, viewer) consistently across all tables. Use Role.team() for all permission checks - **Query Security**: Every multi-tenant query MUST include a \`teamId\` filter to prevent cross-tenant data access -- **Collection Permissions**: Set collection-level permissions using team roles, then override at document level when needed +- **Table Permissions**: Set table-level permissions using team roles, then override at row level when needed - **Query Optimization**: Use indexes for frequently queried fields, especially on \`teamId\` and commonly filtered fields - **Data Validation**: Validate data before creating or updating rows, including team membership validation - **Transactions**: Use transactions for operations that must succeed or fail together, ensuring atomicity across tenant boundaries From 404442e793c33d5c76511251f4bba060f48d4573 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Sat, 29 Nov 2025 00:59:49 +0530 Subject: [PATCH 13/19] Remove outdated common product links --- src/lib/languages/common/products.js | 7 ------- src/lib/languages/common/utils.js | 10 ++++------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/lib/languages/common/products.js b/src/lib/languages/common/products.js index 4077324..0329140 100644 --- a/src/lib/languages/common/products.js +++ b/src/lib/languages/common/products.js @@ -3,13 +3,6 @@ * These are reused across all SDKs and frameworks */ -/** - * Product documentation sections that appear in every SDK initialization - */ -export const commonProductLinks = `For TablesDB operations (create, read, update, delete rows), see the [Rows Documentation](https://appwrite.io/docs/products/databases/rows). - -For Storage operations (upload, download files), see the [Storage Documentation](https://appwrite.io/docs/products/storage/upload-download).`; - /** * Authentication product documentation */ diff --git a/src/lib/languages/common/utils.js b/src/lib/languages/common/utils.js index fcf1f16..6be628b 100644 --- a/src/lib/languages/common/utils.js +++ b/src/lib/languages/common/utils.js @@ -49,12 +49,11 @@ export const quickStartUrls = { nodejs: 'https://appwrite.io/docs/quick-starts/nodejs', vanilla: 'https://appwrite.io/docs/quick-starts/web', - // React Native - 'react-native': 'https://appwrite.io/docs/quick-starts/react-native', - // Mobile Client SDKs apple: 'https://appwrite.io/docs/quick-starts/apple', android: 'https://appwrite.io/docs/quick-starts/android', + flutter: 'https://appwrite.io/docs/quick-starts/flutter', + 'react-native': 'https://appwrite.io/docs/quick-starts/react-native', // Server SDKs python: 'https://appwrite.io/docs/quick-starts/python', @@ -63,7 +62,6 @@ export const quickStartUrls = { ruby: 'https://appwrite.io/docs/quick-starts/ruby', dotnet: 'https://appwrite.io/docs/quick-starts/dotnet', dart: 'https://appwrite.io/docs/quick-starts/dart', - flutter: 'https://appwrite.io/docs/quick-starts/flutter', kotlin: 'https://appwrite.io/docs/quick-starts/kotlin', swift: 'https://appwrite.io/docs/quick-starts/swift' }; @@ -84,16 +82,16 @@ export const frameworkNames = { tanstack: 'TanStack', nodejs: 'Node.js', vanilla: 'Web', - 'react-native': 'React Native', apple: 'Apple', android: 'Android', + flutter: 'Flutter', + 'react-native': 'React Native', python: 'Python', php: 'PHP', go: 'Go', ruby: 'Ruby', dotnet: '.NET', dart: 'Dart', - flutter: 'Flutter', kotlin: 'Kotlin', swift: 'Swift' }; From 1b2f159f3381fef5802cfcaf0fe92f3f4c822e40 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Sat, 29 Nov 2025 01:01:49 +0530 Subject: [PATCH 14/19] Remove redundant authentication documentation link from common product links --- src/lib/languages/common/products.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/languages/common/products.js b/src/lib/languages/common/products.js index 0329140..e01a13e 100644 --- a/src/lib/languages/common/products.js +++ b/src/lib/languages/common/products.js @@ -8,8 +8,6 @@ */ export const authProductLinks = `For detailed authentication and session management instructions, see the [Authentication Quick Start Guide](https://appwrite.io/docs/products/auth/quick-start). -For session management, login, logout, and authentication state checking, see the [Authentication Documentation](https://appwrite.io/docs/products/auth/quick-start). - For OAuth providers and social authentication, see the [OAuth2 Documentation](https://appwrite.io/docs/products/auth/oauth2). For server-side rendering (SSR) authentication, see the [SSR Login Guide](https://appwrite.io/docs/products/auth/server-side-rendering). From 3a5ceb02eb44a59fc127b6908128dfc7bc455393 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Sat, 29 Nov 2025 01:09:41 +0530 Subject: [PATCH 15/19] Remove unnecessary JSDoc comment from SDK server route --- src/routes/api/sdks/+server.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/routes/api/sdks/+server.js b/src/routes/api/sdks/+server.js index 744ddcc..ce9dd19 100644 --- a/src/routes/api/sdks/+server.js +++ b/src/routes/api/sdks/+server.js @@ -1,9 +1,6 @@ import { SDK_OPTIONS } from '$lib/rules-generator.js'; import { json } from '@sveltejs/kit'; -/** - * @param {Request} request - */ export async function GET() { try { const sdks = Object.entries(SDK_OPTIONS).map(([key, value]) => ({ From 2cfc254f6af383d796b8122a2a5567e1b2aa3abd Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Sat, 29 Nov 2025 01:09:54 +0530 Subject: [PATCH 16/19] Refactor rules generator functions to remove unnecessary parameters --- src/lib/rules-generator.js | 46 +++++++++++++------------------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index 6d14ce9..49b50c1 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -118,14 +118,14 @@ export async function generateRules(config) { // Generate all sections in parallel // Permissions section is mandatory for all products const sections = await Promise.all([ - features.includes('auth') ? generateAuthSection(sdk, framework) : Promise.resolve(''), - generatePermissionsSection(sdk, framework), - features.includes('database') ? generateDatabaseSection(sdk, framework) : Promise.resolve(''), - features.includes('storage') ? generateStorageSection(sdk, framework) : Promise.resolve(''), - features.includes('functions') ? generateFunctionsSection(sdk, framework) : Promise.resolve(''), - features.includes('messaging') ? generateMessagingSection(sdk, framework) : Promise.resolve(''), - features.includes('sites') ? generateSitesSection(sdk, framework) : Promise.resolve(''), - features.includes('realtime') ? generateRealtimeSection(sdk, framework) : Promise.resolve('') + features.includes('auth') ? generateAuthSection() : Promise.resolve(''), + generatePermissionsSection(sdk), + features.includes('database') ? generateDatabaseSection() : Promise.resolve(''), + features.includes('storage') ? generateStorageSection() : Promise.resolve(''), + features.includes('functions') ? generateFunctionsSection(sdk) : Promise.resolve(''), + features.includes('messaging') ? generateMessagingSection() : Promise.resolve(''), + features.includes('sites') ? generateSitesSection() : Promise.resolve(''), + features.includes('realtime') ? generateRealtimeSection() : Promise.resolve('') ]); const mcpSection = includeMCP ? `${generateMCPRecommendation()}\n\n` : ''; @@ -194,11 +194,9 @@ Configure your Appwrite client for ${SDK_OPTIONS[sdk]?.name || sdk}.`; } /** - * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateAuthSection(sdk, framework) { +async function generateAuthSection() { const { authProductLinks } = await import('./languages/common/products.js'); return `## Authentication & Teams @@ -228,10 +226,9 @@ When building applications that involve multiple users or tenants: /** * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generatePermissionsSection(sdk, framework) { +async function generatePermissionsSection(sdk) { const { authProductLinks } = await import('./languages/common/products.js'); const { getPermissionExamples } = await import('./languages/common/permissions-examples.js'); const examples = getPermissionExamples(sdk); @@ -493,11 +490,9 @@ function getLanguageFromSdk(sdk) { } /** - * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateDatabaseSection(sdk, framework) { +async function generateDatabaseSection() { const { databaseProductLinks } = await import('./languages/common/products.js'); return `## Database Operations @@ -519,11 +514,9 @@ ${databaseProductLinks} } /** - * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateStorageSection(sdk, framework) { +async function generateStorageSection() { const { storageProductLinks } = await import('./languages/common/products.js'); return `## Storage Operations @@ -589,10 +582,9 @@ For more templates and examples, see the [Appwrite Templates Repository](${templ /** * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateFunctionsSection(sdk, framework) { +async function generateFunctionsSection(sdk) { const { functionsProductLinks } = await import('./languages/common/products.js'); const templateLinks = generateFunctionTemplateLinks(sdk); @@ -626,11 +618,9 @@ ${templateLinks} } /** - * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateMessagingSection(sdk, framework) { +async function generateMessagingSection() { const { messagingProductLinks } = await import('./languages/common/products.js'); return `## Messaging @@ -647,11 +637,9 @@ ${messagingProductLinks} } /** - * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateSitesSection(sdk, framework) { +async function generateSitesSection() { const { sitesProductLinks } = await import('./languages/common/products.js'); return `## Sites @@ -667,11 +655,9 @@ ${sitesProductLinks} } /** - * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateRealtimeSection(sdk, framework) { +async function generateRealtimeSection() { const { realtimeProductLinks } = await import('./languages/common/products.js'); return `## Realtime Subscriptions From cf48a441f22ba50112d82c50e6eb58357f1d5907 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Mon, 15 Dec 2025 20:54:05 +0530 Subject: [PATCH 17/19] Refactor installation instructions and enhance SSR authentication documentation across multiple frameworks --- src/lib/languages/common/install.js | 29 +++--------- src/lib/languages/common/products.js | 2 - src/lib/languages/common/security.js | 69 ++++++++++++++++++++++++++++ src/lib/languages/js/astro.js | 20 +++++--- src/lib/languages/js/nextjs.js | 15 ++++-- src/lib/languages/js/nuxt.js | 16 +++++-- src/lib/languages/js/svelte.js | 20 ++++++-- 7 files changed, 125 insertions(+), 46 deletions(-) diff --git a/src/lib/languages/common/install.js b/src/lib/languages/common/install.js index ec1d33d..f00d2d7 100644 --- a/src/lib/languages/common/install.js +++ b/src/lib/languages/common/install.js @@ -4,12 +4,6 @@ export const npmInstall = `npm install appwrite`; -export const yarnInstall = `yarn add appwrite`; - -export const pnpmInstall = `pnpm add appwrite`; - -export const bunInstall = `bun add appwrite`; - /** * JavaScript/TypeScript installation section * @param {string} [packageName] - Package name (default: 'appwrite') @@ -24,23 +18,7 @@ ${title} using npm: npm install ${packageName} \`\`\` -Or using yarn: - -\`\`\`bash -yarn add ${packageName} -\`\`\` - -Or using pnpm: - -\`\`\`bash -pnpm add ${packageName} -\`\`\` - -Or using bun: - -\`\`\`bash -bun add ${packageName} -\`\`\``; +You can also use yarn, pnpm, or bun instead.`; } /** @@ -48,6 +26,11 @@ bun add ${packageName} */ export const jsInstallDefault = jsInstall(); +/** + * Node.js Server SDK installation section for SSR frameworks + */ +export const nodeAppwriteInstall = jsInstall('node-appwrite', 'Install the Appwrite Node.js Server SDK'); + /** * Python installation section */ diff --git a/src/lib/languages/common/products.js b/src/lib/languages/common/products.js index e01a13e..3adf44b 100644 --- a/src/lib/languages/common/products.js +++ b/src/lib/languages/common/products.js @@ -10,8 +10,6 @@ export const authProductLinks = `For detailed authentication and session managem For OAuth providers and social authentication, see the [OAuth2 Documentation](https://appwrite.io/docs/products/auth/oauth2). -For server-side rendering (SSR) authentication, see the [SSR Login Guide](https://appwrite.io/docs/products/auth/server-side-rendering). - For Teams management, team invitations, and team-based permissions, see the [Teams Documentation](https://appwrite.io/docs/products/auth/teams). For team invites and membership management, see the [Team Invites Guide](https://appwrite.io/docs/products/auth/team-invites). diff --git a/src/lib/languages/common/security.js b/src/lib/languages/common/security.js index 3a7f23f..cf84cf4 100644 --- a/src/lib/languages/common/security.js +++ b/src/lib/languages/common/security.js @@ -42,6 +42,75 @@ export const serverSecurityWithConfig = (configMethod = 'environment variables') export const authNote = `**Authentication:** - Prefer SSR auth for better security and performance`; +/** + * SSR Authentication pattern + */ +export const ssrAuthPattern = `**SSR Authentication Pattern:** + +Server-side rendering requires using the Server SDK (node-appwrite) instead of the client SDK. + +**Authentication Flow:** +1. User credentials are sent from browser to your server +2. Your server authenticates with Appwrite using the Server SDK +3. Appwrite returns a session object +4. Store the session secret in an httpOnly cookie +5. Subsequent requests include the session cookie +6. Your server makes authenticated requests on behalf of the user + +**Key Implementation Details:** + +**Initialize Two Clients:** +- **Admin Client**: Uses API key for unauthenticated requests and session creation +- **Session Client**: Uses session cookie for user-specific requests + +**Creating Sessions:** +\`\`\`javascript +import { Client, Account } from "node-appwrite"; + +// In your login endpoint: +const account = new Account(adminClient); +const session = await account.createEmailPasswordSession(email, password); + +// Set httpOnly cookie with session secret +res.cookie('a_session_', session.secret, { + httpOnly: true, + secure: true, + sameSite: 'strict', + expires: new Date(session.expire), + path: '/' +}); +\`\`\` + +**Making Authenticated Requests:** +\`\`\`javascript +// Read session from cookie +const session = req.cookies['a_session_']; + +// Create session client +const sessionClient = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('') + .setSession(session); + +const account = new Account(sessionClient); +const user = await account.get(); +\`\`\` + +**OAuth2 Flow:** +1. Redirect to OAuth provider using createOAuth2Token +2. Handle callback with userId and secret parameters +3. Call createSession to exchange for session object +4. Store session secret in cookie + +**Best Practices:** +- Use httpOnly, secure, and sameSite cookie flags +- Create new session client per request +- Never share clients between requests +- Use API key for admin client to bypass rate limits +- Set forwarded user agent for better session tracking + +**See full SSR auth guide:** https://appwrite.io/docs/products/auth/server-side-rendering`; + /** * Framework-specific notes */ diff --git a/src/lib/languages/js/astro.js b/src/lib/languages/js/astro.js index 3726df4..5385cf7 100644 --- a/src/lib/languages/js/astro.js +++ b/src/lib/languages/js/astro.js @@ -1,12 +1,20 @@ -import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { nodeAppwriteInstall } from '../common/install.js'; import { createFrameworkTemplate } from '../common/utils.js'; -import { clientSecurityWithEnv, authNote } from '../common/security.js'; +import { ssrAuthPattern } from '../common/security.js'; export const astro = createFrameworkTemplate({ - installation: jsInstall, - securityNotes: `${clientSecurityWithEnv('.env')} + installation: nodeAppwriteInstall, + securityNotes: `**Best Practices:** +- Store endpoint and project ID in \`.env\` file +- Never commit API keys to version control +- Use Astro API routes for SSR authentication +- API keys should NEVER be exposed to client-side code +- Use node-appwrite for server-side operations -- Use separate clients for client-side and server-side operations`, - additionalNotes: authNote +**Rendering Strategy:** +- Default to static/server-side rendering for all pages +- Only add client-side interactivity with client:* directives when needed +- Leverage Astro API routes for server-side operations`, + additionalNotes: ssrAuthPattern }); diff --git a/src/lib/languages/js/nextjs.js b/src/lib/languages/js/nextjs.js index a418c40..1c7fcab 100644 --- a/src/lib/languages/js/nextjs.js +++ b/src/lib/languages/js/nextjs.js @@ -1,10 +1,15 @@ -import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { nodeAppwriteInstall } from '../common/install.js'; import { createFrameworkTemplate } from '../common/utils.js'; -import { serverSecurity, authNote } from '../common/security.js'; +import { serverSecurity, ssrAuthPattern } from '../common/security.js'; export const nextjs = createFrameworkTemplate({ - installation: jsInstall, - securityNotes: serverSecurity, - additionalNotes: authNote + installation: nodeAppwriteInstall, + securityNotes: `${serverSecurity} + +**Rendering Strategy:** +- Default to Server Components and server-side pages +- Only use Client Components when explicitly needed for interactivity +- Leverage Server Actions for mutations and data fetching`, + additionalNotes: ssrAuthPattern }); diff --git a/src/lib/languages/js/nuxt.js b/src/lib/languages/js/nuxt.js index 3ad76a1..54f9637 100644 --- a/src/lib/languages/js/nuxt.js +++ b/src/lib/languages/js/nuxt.js @@ -1,13 +1,19 @@ -import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { nodeAppwriteInstall } from '../common/install.js'; import { createFrameworkTemplate } from '../common/utils.js'; -import { authNote } from '../common/security.js'; +import { ssrAuthPattern } from '../common/security.js'; export const nuxt = createFrameworkTemplate({ - installation: jsInstall, + installation: nodeAppwriteInstall, securityNotes: `**Best Practices:** - Store endpoint and project ID in \`nuxt.config.ts\` - Never commit API keys to version control -- Use Nuxt plugins for client-side initialization`, - additionalNotes: authNote +- Use Nuxt server routes for SSR authentication +- API keys should NEVER be exposed to client-side code + +**Rendering Strategy:** +- Default to server-side rendering (SSR) for all pages +- Only use client-side rendering when explicitly needed +- Leverage server API routes and middleware for data operations`, + additionalNotes: ssrAuthPattern }); diff --git a/src/lib/languages/js/svelte.js b/src/lib/languages/js/svelte.js index 673df65..fcd4d3b 100644 --- a/src/lib/languages/js/svelte.js +++ b/src/lib/languages/js/svelte.js @@ -1,10 +1,20 @@ -import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { nodeAppwriteInstall } from '../common/install.js'; import { createFrameworkTemplate } from '../common/utils.js'; -import { clientSecurityWithEnv, authNote } from '../common/security.js'; +import { ssrAuthPattern } from '../common/security.js'; export const svelte = createFrameworkTemplate({ - installation: jsInstall, - securityNotes: clientSecurityWithEnv('.env'), - additionalNotes: authNote + installation: nodeAppwriteInstall, + securityNotes: `**Best Practices:** +- Store endpoint and project ID in \`.env\` file +- Never commit API keys to version control +- Use SvelteKit server routes (+server.js) for SSR authentication +- API keys should NEVER be exposed to client-side code +- Use node-appwrite for server-side operations + +**Rendering Strategy:** +- Default to server-side rendering (SSR) for all pages +- Only use client-side rendering when explicitly needed +- Leverage server load functions and form actions for data operations`, + additionalNotes: ssrAuthPattern }); From 2e5298631d35646c891051adf1ebbed43f71340a Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Fri, 19 Dec 2025 17:55:18 +0530 Subject: [PATCH 18/19] Update AGENTS.md generation and documentation; refactor rules generator to remove MCP references and enhance framework-specific best practices --- .gitignore | 9 +- README.md | 25 +- package.json | 2 +- scripts/generate-nextjs-rules.js | 5 +- scripts/lib-loader.js | 22 - src/lib/languages/android/index.js | 35 +- src/lib/languages/apple/index.js | 35 +- .../common/implementation-patterns.js | 5893 +++++++++++++++++ src/lib/languages/common/mcp.js | 55 - .../languages/common/permissions-examples.js | 5133 +------------- src/lib/languages/common/products.js | 143 +- src/lib/languages/common/security.js | 339 +- src/lib/languages/dart/index.js | 34 +- src/lib/languages/dart/server.js | 34 +- src/lib/languages/dotnet/server.js | 33 +- src/lib/languages/dotnet/vanilla.js | 24 +- src/lib/languages/go/server.js | 33 +- src/lib/languages/js/astro.js | 95 +- src/lib/languages/js/nextjs.js | 69 +- src/lib/languages/js/nuxt.js | 96 +- src/lib/languages/js/svelte.js | 82 +- src/lib/languages/js/tanstack.js | 60 +- src/lib/languages/kotlin/index.js | 34 +- src/lib/languages/php/server.js | 35 +- src/lib/languages/python/flask.js | 75 +- src/lib/languages/python/server.js | 32 +- src/lib/languages/react-native/vanilla.js | 37 +- src/lib/languages/ruby/server.js | 33 +- src/lib/languages/swift/index.js | 35 +- src/lib/rules-generator.js | 372 +- src/routes/+page.svelte | 7 +- src/routes/api/rules/+server.js | 7 +- 32 files changed, 7546 insertions(+), 5377 deletions(-) delete mode 100644 scripts/lib-loader.js create mode 100644 src/lib/languages/common/implementation-patterns.js delete mode 100644 src/lib/languages/common/mcp.js diff --git a/.gitignore b/.gitignore index bc116d1..e981ac1 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,11 @@ vite.config.ts.timestamp-* # Rules -*.mdc \ No newline at end of file +AGENTS.md + +# Generated Files + +API_FIX_SUMMARY_V2.md +API_FIX_SUMMARY.md +API_REFERENCES_FINAL.md +API_REFERENCES_VERIFICATION.md \ No newline at end of file diff --git a/README.md b/README.md index 3e07947..91c2578 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Appwrite Cursor Rules Generator +# Appwrite AGENTS.md Generator -A web application for generating comprehensive Cursor rules (`.mdc` files) for Appwrite development across multiple SDKs and frameworks. This tool helps developers create customized development rules that include best practices, code examples, and guidance for building applications with Appwrite. +A web application for generating comprehensive AGENTS.md files for Appwrite development across multiple SDKs and frameworks. This tool helps developers create customized AI coding assistant instructions that include best practices, code examples, and guidance for building applications with Appwrite. ## Features @@ -14,8 +14,7 @@ A web application for generating comprehensive Cursor rules (`.mdc` files) for A - Messaging - Sites - Realtime Subscriptions - - MCP (Model Context Protocol) recommendations -- **Export Options**: Copy to clipboard or download as `.mdc` file +- **Export Options**: Copy to clipboard or download as `AGENTS.md` file - **Best Practices**: Generated rules include comprehensive best practices, multi-tenancy patterns, and security guidelines ## Supported SDKs and Frameworks @@ -68,9 +67,9 @@ pnpm dev 2. **Select Framework**: Pick the framework you're using (options depend on the selected SDK) 3. **Choose Features**: Check the boxes for the Appwrite features you want to include in your rules 4. **Generate Rules**: Click the "Generate Rules" button -5. **Export**: Copy the rules to your clipboard or download as a `.mdc` file +5. **Export**: Copy the rules to your clipboard or download as a `AGENTS.md` file -The generated rules file can be used in Cursor IDE to provide AI-assisted development guidance specific to your Appwrite setup. +The generated AGENTS.md file can be used with AI coding assistants (Cursor, GitHub Copilot, OpenAI Codex, etc.) to provide AI-assisted development guidance specific to your Appwrite setup. ## API Endpoints @@ -112,7 +111,6 @@ Generate rules using query parameters. - `features` (optional): Comma-separated list of features (default: `auth`) - Available: `auth`, `database`, `storage`, `functions`, `messaging`, `sites`, `realtime` - Special: `all` - includes all available features -- `mcp` (optional): Include MCP recommendations (`true` or `false`, default: `false`) - `format` (optional): Response format (`text` or `json`, default: `text`) **Example:** @@ -138,8 +136,7 @@ curl "http://localhost:5173/api/rules?sdk=python&framework=flask&features=auth,s "sdk": "javascript", "framework": "nextjs", "features": ["auth", "database"], - "includeMCP": false, - "rules": "---\ndescription: You are an expert developer...\n---\n\n# Appwrite Development Rules\n..." + "rules": "# Appwrite Development Rules\n\n> You are an expert developer...\n\n## Overview\n..." } ``` @@ -181,7 +178,7 @@ appwrite-cursor-rules/ │ │ ├── languages/ # SDK and framework-specific code examples │ │ │ ├── js/ # JavaScript/TypeScript frameworks │ │ │ ├── python/ # Python frameworks -│ │ │ ├── common/ # Shared rules (products, MCP, etc.) +│ │ │ ├── common/ # Shared rules (products, permissions, etc.) │ │ │ └── ... # Other SDKs │ │ ├── rules-generator.js # Main rules generation logic │ │ └── utils/ # Utility functions @@ -212,14 +209,16 @@ appwrite-cursor-rules/ 2. Add the feature to the features array in `src/routes/+page.svelte` 3. Include the feature in the `generateRules` function's Promise.all array -## Generated Rules Format +## Generated AGENTS.md Format -The generated rules follow the Cursor `.mdc` format and include: +The generated AGENTS.md file follows the [AGENTS.md standard](https://agents.md/) and includes: -- **Frontmatter**: Metadata about the rules (description, alwaysApply flag) +- **Title and Description**: Project context and overview for AI coding assistants - **SDK Initialization**: Framework-specific code examples for setting up Appwrite - **Feature Sections**: Best practices and guidance for selected features - **Multi-Tenancy Guide**: Comprehensive guide on using teams and permissions - **Product Links**: Links to official Appwrite documentation +The AGENTS.md format is compatible with multiple AI coding assistants including Cursor, GitHub Copilot, OpenAI Codex, and others. + diff --git a/package.json b/package.json index 665662b..de0addf 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "format": "prettier --write .", "lint": "prettier --check . && eslint .", - "generate:nextjs": "node --loader ./scripts/lib-loader.js scripts/generate-nextjs-rules.js" + "generate:nextjs": "node ./scripts/generate-nextjs-rules.js" }, "devDependencies": { "@eslint/compat": "^1.4.0", diff --git a/scripts/generate-nextjs-rules.js b/scripts/generate-nextjs-rules.js index 1a4b7cb..d191173 100644 --- a/scripts/generate-nextjs-rules.js +++ b/scripts/generate-nextjs-rules.js @@ -13,11 +13,10 @@ async function generateNextJSRules() { const rules = await generateRules({ sdk: 'javascript', framework: 'nextjs', - features: ['auth', 'database', 'storage', 'functions', 'messaging', 'sites', 'realtime'], - includeMCP: true + features: ['auth', 'database', 'storage', 'functions', 'messaging', 'sites', 'realtime'] }); - const outputPath = join(__dirname, '..', 'APPWRITE-javascript-nextjs.mdc'); + const outputPath = join(__dirname, '..', 'AGENTS.md'); await writeFile(outputPath, rules, 'utf-8'); console.log(`Rules generated successfully!`); diff --git a/scripts/lib-loader.js b/scripts/lib-loader.js deleted file mode 100644 index 22b6fe9..0000000 --- a/scripts/lib-loader.js +++ /dev/null @@ -1,22 +0,0 @@ -// Custom Node.js loader to resolve $lib aliases -import { pathToFileURL } from 'node:url'; -import { resolve as resolvePath } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const __dirname = fileURLToPath(new URL('.', import.meta.url)); -const projectRoot = resolvePath(__dirname, '..'); - -export async function resolve(specifier, context, nextResolve) { - if (specifier.startsWith('$lib/')) { - const relativePath = specifier.replace('$lib/', ''); - const absolutePath = resolvePath(projectRoot, 'src', 'lib', relativePath); - const fileUrl = pathToFileURL(absolutePath).href; - return { - url: fileUrl, - format: 'module', - shortCircuit: true - }; - } - return nextResolve(specifier, context); -} - diff --git a/src/lib/languages/android/index.js b/src/lib/languages/android/index.js index bee6839..e446c29 100644 --- a/src/lib/languages/android/index.js +++ b/src/lib/languages/android/index.js @@ -1,6 +1,6 @@ -import { getSDKVersion } from '$lib/utils/versions.js'; -import { createFrameworkTemplate } from '../common/utils.js'; +import { getSDKVersion } from '../../utils/versions.js'; import { clientSecurity } from '../common/security.js'; +import { getMobileImplementationGuide } from '../common/implementation-patterns.js'; /** * Generates the Android SDK installation template with the latest version @@ -37,11 +37,36 @@ Or for Maven, add to \`pom.xml\`: /** * Gets the Android SDK installation template with the latest version from Appwrite's API * This is the main export used by the rules generator + * @param {string[]} [features=[]] - Selected features to include patterns for * @returns {Promise} */ -export const vanilla = async () => { +export const vanilla = async (features = []) => { const version = await getSDKVersion('client-android'); const installation = generateInstallationTemplate(version); - return createFrameworkTemplate({ installation, securityNotes: clientSecurity }); -}; + const androidImplementation = getMobileImplementationGuide('android', features); + + return `${installation} + +**Framework Documentation:** +- [Account API](https://appwrite.io/docs/references/cloud/client-web/account) - Authentication and user management +- [Databases API](https://appwrite.io/docs/references/cloud/client-web/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/client-web/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/client-web/functions) - Serverless functions execution +- [Messaging API](https://appwrite.io/docs/references/cloud/client-web/messaging) - Push notifications and messaging +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/android) + +${clientSecurity} + +${androidImplementation} +## Android-Specific Best Practices + +- Initialize AppwriteService in Application class +- Use Hilt or Koin for dependency injection +- Use ViewModel + StateFlow for UI state +- Store session in EncryptedSharedPreferences +- Handle configuration changes properly +- Use WorkManager for background operations +- Follow Material Design guidelines +`; +}; diff --git a/src/lib/languages/apple/index.js b/src/lib/languages/apple/index.js index d477e88..504bc46 100644 --- a/src/lib/languages/apple/index.js +++ b/src/lib/languages/apple/index.js @@ -1,6 +1,6 @@ -import { getSDKVersion } from '$lib/utils/versions.js'; -import { createFrameworkTemplate } from '../common/utils.js'; +import { getSDKVersion } from '../../utils/versions.js'; import { clientSecurity } from '../common/security.js'; +import { getMobileImplementationGuide } from '../common/implementation-patterns.js'; /** * Generates the Apple SDK installation template with the latest version @@ -27,11 +27,36 @@ Or add it via Xcode: /** * Gets the Apple SDK installation template with the latest version from Appwrite's API * This is the main export used by the rules generator + * @param {string[]} [features=[]] - Selected features to include patterns for * @returns {Promise} */ -export const vanilla = async () => { +export const vanilla = async (features = []) => { const version = await getSDKVersion('client-apple'); const installation = generateInstallationTemplate(version); - return createFrameworkTemplate({ installation, securityNotes: clientSecurity }); -}; + const appleImplementation = getMobileImplementationGuide('apple', features); + + return `${installation} + +**Framework Documentation:** +- [Account API](https://appwrite.io/docs/references/cloud/client-web/account) - Authentication and user management +- [Databases API](https://appwrite.io/docs/references/cloud/client-web/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/client-web/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/client-web/functions) - Serverless functions execution +- [Messaging API](https://appwrite.io/docs/references/cloud/client-web/messaging) - Push notifications and messaging +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/apple) + +${clientSecurity} +${appleImplementation} + +## Apple Platform Best Practices + +- Use @MainActor for UI-related async operations +- Store configuration in Info.plist or xcconfig files +- Use Keychain for storing session tokens +- Use Combine or async/await for reactive programming +- Follow Human Interface Guidelines +- Support Dark Mode and Dynamic Type +- Test on multiple device sizes +`; +}; diff --git a/src/lib/languages/common/implementation-patterns.js b/src/lib/languages/common/implementation-patterns.js new file mode 100644 index 0000000..af2f4af --- /dev/null +++ b/src/lib/languages/common/implementation-patterns.js @@ -0,0 +1,5893 @@ +/** + * Comprehensive implementation patterns for Appwrite integration + * These patterns ensure secure, typed, ownership-enforced data access + */ + +/** + * General implementation rules that apply to all Appwrite apps + */ +export const generalImplementationRules = ` +## 🚨 Absolute Rules (Non-Negotiable) + +### Authoritative Access Pattern + +- **NEVER** import or invoke the Appwrite SDK directly from feature/component code +- **ALWAYS** use centralized wrapper functions for all data access +- **ALWAYS** authenticate before any data operation +- **NEVER** expose API keys to client-side code + +--- + +## Error Handling + +- Let errors bubble by default for consistent error handling +- Catch only when adding context or performing cleanup +- Always rethrow with clear error messages +- Never swallow errors silently + +--- + +## Type Safety + +- Avoid untyped/dynamic types where possible +- Define models/interfaces/structs for all data structures +- Use your language's type system to enforce constraints +- Validate inputs with appropriate validation libraries for your language +`; + +/** + * Database-specific implementation rules + */ +export const databaseImplementationRules = ` +## Database Implementation Rules + +### Database Wrapper Requirements + +Create centralized database helpers that: +- Configure the admin client with proper credentials +- Handle project, database, and table IDs +- Manage permissions automatically +- Return typed/structured objects (never raw Appwrite rows) + +- **NEVER** use TablesDB SDK directly for app data +- **ALWAYS** use centralized wrapper functions for database access + +--- + +## Ownership Enforcement (Critical) + +### User-Owned Entities (\`createdBy\`) + +Every user-owned table must include a \`createdBy\` column. + +| Operation | Rule | +|-----------|------| +| **Create** | Set \`createdBy\` to authenticated user's \`$id\` | +| **List** | Filter with \`Query.equal('createdBy', [userId])\` | +| **Read** | Verify ownership before returning data | +| **Update** | Confirm ownership; NEVER allow \`createdBy\` to change | +| **Delete** | Confirm ownership before deletion | + +### Team-Owned Entities (\`teamId\`) + +For shared workspaces and organization data: + +| Operation | Rule | +|-----------|------| +| **Create** | Set \`teamId\`; verify user is team member | +| **List** | Filter with \`Query.equal('teamId', [teamId])\` | +| **Read** | Verify team match AND user membership | +| **Update** | Confirm membership; NEVER allow \`teamId\` to change | +| **Delete** | Confirm membership before deletion | + +--- + +## Database Usage Rules + +- Import database helpers from centralized location only +- Operate on **tables**, expect **rows** +- Create payloads: exclude system columns (\`$id\`, \`$createdAt\`, \`$updatedAt\`) +- Update payloads: partial data, exclude system and ownership columns +- NEVER modify: \`$id\`, \`$createdAt\`, \`$updatedAt\`, \`createdBy\`, \`teamId\` +- Use \`Query.equal()\` for ownership filtering +- Return serialized/structured objects only (no raw SDK responses) + +--- + +## Sanitization & Payload Rules + +- Trim all user-provided strings +- Convert empty optional text to \`null\` (or language equivalent) +- Creates: require full payload (minus system fields) +- Updates: accept partial payload +- Ownership fields (\`createdBy\`, \`teamId\`) are IMMUTABLE after creation +`; + +/** + * Storage-specific implementation rules + */ +export const storageImplementationRules = ` +## Storage Implementation Rules + +### Storage Wrapper Requirements + +Create centralized storage helpers that: +- Initialize storage with proper bucket configuration +- Handle file upload/download conversions +- Manage file permissions +- Return only file IDs or data URLs (never raw binary data to client) + +- **NEVER** use Storage SDK directly for app data +- **ALWAYS** use centralized wrapper functions for storage access + +--- + +## Storage Usage Rules + +### Upload Flow (Mandatory Conversion) + +\`\`\` +Client → Server: base64 string (or file bytes) +Server: Decode base64 → bytes/buffer → InputFile +Server → Storage: InputFile +Storage → Server: File metadata +Server → Client: file ID only +\`\`\` + +Rules: +- Strip \`data:...;base64,\` prefix before processing +- Decode base64 to bytes using your language's standard library +- Use the SDK's InputFile helper for uploads +- NEVER store base64 strings in database +- Store **file IDs only** in database fields + +### Read Flow + +\`\`\` +Server: Fetch file from storage (binary/bytes) +Server: Convert bytes → base64 → data URL +Server → Client: data URL string (or secure download URL) +\`\`\` + +- NEVER expose raw binary data to client +- Always return URL strings or base64 data URLs + +--- + +## File References in Rows + +- Store file references as string arrays (\`fileIds\` field) +- On row load, fetch file URLs and inject into response +- Handle missing files gracefully +- Implement orphaned file cleanup when rows are deleted +`; + +/** + * TanStack Start specific implementation pattern + */ +export const tanstackStartPattern = ` +## TanStack Start Server Function Pattern + +### Mandatory Structure + +Every endpoint must: +1. Use \`createServerFn\` wrapper +2. Attach \`.validator(...)\` for input validation +3. Authenticate immediately in handler +4. Call ONLY centralized db/storage helpers +5. Return plain serializable objects + +### Canonical Example + +\`\`\`typescript +import { createServerFn } from '@tanstack/start' + +export const createItemFn = createServerFn({ method: 'POST' }) + .handler(async ({ data }) => { + // 1. Authenticate first + const { currentUser } = await authMiddleware() + if (!currentUser) throw new Error('Unauthorized') + + // 2. Use centralized db helper + const item = await db.items.create({ + title: data.title.trim(), + description: null, + createdBy: currentUser.$id, + teamId: data.teamId ?? null, + }) + + // 3. Return serialized object + return { item } + }) +\`\`\` + +### Route Integration + +\`\`\`typescript +// In route loader +export const Route = createFileRoute('/items')({ + loader: async () => { + const { items } = await listItemsFn() + return { items } + }, +}) + +// In component +function ItemsPage() { + const { items } = Route.useLoaderData() + const router = useRouter() + + const handleCreate = async (data) => { + await createItemFn({ data }) + router.invalidate() // Refresh data + } +} +\`\`\` + +### Environment Variables + +Required in \`@/server/lib/appwrite.ts\`: +- \`APPWRITE_ENDPOINT\` +- \`APPWRITE_PROJECT_ID\` +- \`APPWRITE_API_KEY\` +- \`APPWRITE_DATABASE_ID\` +- \`APPWRITE_BUCKET_ID\` (if using storage) +`; + +/** + * Next.js specific implementation pattern + */ +export const nextjsPattern = ` +## Next.js Server Action Pattern + +### Mandatory Structure (App Router) + +Every server action must: +1. Be marked with \`'use server'\` +2. Authenticate immediately +3. Validate input +4. Use centralized db/storage helpers only +5. Return plain serializable objects + +### Canonical Example + +\`\`\`typescript +// app/actions/items.ts +'use server' + +import { auth } from '@/lib/auth' +import { db } from '@/lib/db' + +export async function createItem(formData: FormData) { + // 1. Authenticate first + const session = await auth() + if (!session?.user) throw new Error('Unauthorized') + + // 2. Validate input + const title = formData.get('title')?.toString() + const teamId = formData.get('teamId')?.toString() + + if (!title || title.length === 0 || title.length > 120) { + throw new Error('Invalid title') + } + + // 3. Use centralized db helper + const item = await db.items.create({ + title: title.trim(), + description: null, + createdBy: session.user.id, + teamId: teamId ?? null, + }) + + // 4. Revalidate and return + revalidatePath('/items') + return { item } +} +\`\`\` + +### Route Handler Pattern + +\`\`\`typescript +// app/api/items/route.ts +import { NextResponse } from 'next/server' +import { auth } from '@/lib/auth' +import { db } from '@/lib/db' + +export async function GET() { + const session = await auth() + if (!session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const items = await db.items.listByOwner(session.user.id) + return NextResponse.json({ items }) +} + +export async function POST(request: Request) { + const session = await auth() + if (!session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const body = await request.json() + const item = await db.items.create({ + ...body, + createdBy: session.user.id, + }) + + return NextResponse.json({ item }, { status: 201 }) +} +\`\`\` + +### Server Component Data Fetching + +\`\`\`typescript +// app/items/page.tsx +import { auth } from '@/lib/auth' +import { db } from '@/lib/db' + +export default async function ItemsPage() { + const session = await auth() + if (!session?.user) redirect('/login') + + const items = await db.items.listByOwner(session.user.id) + + return +} +\`\`\` + +### Environment Variables + +Required in \`.env.local\`: +- \`APPWRITE_ENDPOINT\` +- \`APPWRITE_PROJECT_ID\` +- \`APPWRITE_API_KEY\` +- \`APPWRITE_DATABASE_ID\` +- \`APPWRITE_BUCKET_ID\` (if using storage) +`; + +/** + * SvelteKit specific implementation pattern + */ +export const sveltekitPattern = ` +## SvelteKit Server Pattern + +### Mandatory Structure + +Every server operation must: +1. Use \`+server.ts\` for API routes or \`+page.server.ts\` for page data +2. Authenticate via \`locals\` or session check +3. Validate input +4. Use centralized db/storage helpers only +5. Return plain serializable objects + +### Form Actions Pattern + +\`\`\`typescript +// routes/items/+page.server.ts +import { fail, redirect } from '@sveltejs/kit' +import { db } from '$lib/server/db' + +export const actions = { + create: async ({ request, locals }) => { + // 1. Authenticate first + if (!locals.user) throw redirect(303, '/login') + + // 2. Validate input + const formData = await request.formData() + const title = formData.get('title')?.toString() + const teamId = formData.get('teamId')?.toString() + + if (!title || title.length === 0 || title.length > 120) { + return fail(400, { error: 'Invalid title' }) + } + + // 3. Use centralized db helper + const item = await db.items.create({ + title: title.trim(), + description: null, + createdBy: locals.user.id, + teamId: teamId ?? null, + }) + + return { success: true, item } + }, + + delete: async ({ request, locals }) => { + if (!locals.user) throw redirect(303, '/login') + + const formData = await request.formData() + const id = formData.get('id') as string + + // Verify ownership before delete + const item = await db.items.get(id) + if (item?.createdBy !== locals.user.id) { + return fail(403, { error: 'Forbidden' }) + } + + await db.items.delete(id) + return { success: true } + } +} +\`\`\` + +### Load Function Pattern + +\`\`\`typescript +// routes/items/+page.server.ts +import type { PageServerLoad } from './$types' +import { redirect } from '@sveltejs/kit' +import { db } from '$lib/server/db' + +export const load: PageServerLoad = async ({ locals }) => { + if (!locals.user) throw redirect(303, '/login') + + const items = await db.items.listByOwner(locals.user.id) + + return { items } +} +\`\`\` + +### API Route Pattern + +\`\`\`typescript +// routes/api/items/+server.ts +import { json, error } from '@sveltejs/kit' +import { db } from '$lib/server/db' + +export async function GET({ locals }) { + if (!locals.user) throw error(401, 'Unauthorized') + + const items = await db.items.listByOwner(locals.user.id) + return json({ items }) +} + +export async function POST({ request, locals }) { + if (!locals.user) throw error(401, 'Unauthorized') + + const body = await request.json() + const item = await db.items.create({ + ...body, + createdBy: locals.user.id, + }) + + return json({ item }, { status: 201 }) +} +\`\`\` + +### Environment Variables + +Required in \`.env\`: +- \`APPWRITE_ENDPOINT\` +- \`APPWRITE_PROJECT_ID\` +- \`APPWRITE_API_KEY\` +- \`APPWRITE_DATABASE_ID\` +- \`APPWRITE_BUCKET_ID\` (if using storage) +`; + +/** + * Nuxt specific implementation pattern + */ +export const nuxtPattern = ` +## Nuxt Server Pattern + +### Mandatory Structure + +Every server operation must: +1. Use \`server/api/\` routes or \`server/routes/\` +2. Authenticate via \`event.context\` or session +3. Validate input +4. Use centralized db/storage helpers only +5. Return plain serializable objects + +### API Route Pattern + +\`\`\`typescript +// server/api/items/index.get.ts +import { db } from '~/server/lib/db' + +export default defineEventHandler(async (event) => { + // 1. Authenticate first + const user = event.context.user + if (!user) { + throw createError({ statusCode: 401, message: 'Unauthorized' }) + } + + // 2. Use centralized db helper + const items = await db.items.listByOwner(user.id) + + return { items } +}) +\`\`\` + +\`\`\`typescript +// server/api/items/index.post.ts +import { db } from '~/server/lib/db' + +export default defineEventHandler(async (event) => { + // 1. Authenticate first + const user = event.context.user + if (!user) { + throw createError({ statusCode: 401, message: 'Unauthorized' }) + } + + // 2. Validate input + const body = await readBody(event) + const { title, teamId } = body + + if (!title || title.length === 0 || title.length > 120) { + throw createError({ statusCode: 400, message: 'Invalid title' }) + } + + // 3. Use centralized db helper + const item = await db.items.create({ + title: title.trim(), + description: null, + createdBy: user.id, + teamId: teamId ?? null, + }) + + return { item } +}) +\`\`\` + +### Ownership Verification + +\`\`\`typescript +// server/api/items/[id].delete.ts +import { db } from '~/server/lib/db' + +export default defineEventHandler(async (event) => { + const user = event.context.user + if (!user) { + throw createError({ statusCode: 401, message: 'Unauthorized' }) + } + + const id = getRouterParam(event, 'id') + + // Verify ownership before delete + const item = await db.items.get(id) + if (!item || item.createdBy !== user.id) { + throw createError({ statusCode: 403, message: 'Forbidden' }) + } + + await db.items.delete(id) + return { success: true } +}) +\`\`\` + +### Composables Pattern + +\`\`\`typescript +// composables/useItems.ts +export function useItems() { + const items = ref([]) + + async function fetchItems() { + const { data } = await useFetch('/api/items') + items.value = data.value?.items ?? [] + } + + async function createItem(title: string) { + await $fetch('/api/items', { + method: 'POST', + body: { title } + }) + await fetchItems() // Refresh + } + + return { items, fetchItems, createItem } +} +\`\`\` + +### Environment Variables + +Required in \`.env\` or \`nuxt.config.ts\`: +- \`APPWRITE_ENDPOINT\` +- \`APPWRITE_PROJECT_ID\` +- \`APPWRITE_API_KEY\` +- \`APPWRITE_DATABASE_ID\` +- \`APPWRITE_BUCKET_ID\` (if using storage) +`; + +/** + * Astro specific implementation pattern + */ +export const astroPattern = ` +## Astro Server Pattern + +### Mandatory Structure + +Every server operation must: +1. Use API routes in \`src/pages/api/\` +2. Authenticate via request context or session +3. Validate input +4. Use centralized db/storage helpers only +5. Return plain serializable objects + +### API Route Pattern + +\`\`\`typescript +// src/pages/api/items/index.ts +import type { APIRoute } from 'astro' +import { db } from '@/lib/db' +import { getSession } from '@/lib/auth' + +export const GET: APIRoute = async ({ request }) => { + // 1. Authenticate first + const session = await getSession(request) + if (!session?.user) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }) + } + + // 2. Use centralized db helper + const items = await db.items.listByOwner(session.user.id) + + return new Response(JSON.stringify({ items }), { + headers: { 'Content-Type': 'application/json' } + }) +} + +export const POST: APIRoute = async ({ request }) => { + const session = await getSession(request) + if (!session?.user) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }) + } + + const body = await request.json() + const { title } = body + + if (!title || title.length === 0 || title.length > 120) { + return new Response(JSON.stringify({ error: 'Invalid title' }), { + status: 400, + headers: { 'Content-Type': 'application/json' } + }) + } + + const item = await db.items.create({ + title: title.trim(), + createdBy: session.user.id, + }) + + return new Response(JSON.stringify({ item }), { + status: 201, + headers: { 'Content-Type': 'application/json' } + }) +} +\`\`\` + +### Server-Side Data Fetching + +\`\`\`astro +--- +// src/pages/items.astro +import { db } from '@/lib/db' +import { getSession } from '@/lib/auth' +import ItemsList from '@/components/ItemsList' + +const session = await getSession(Astro.request) +if (!session?.user) { + return Astro.redirect('/login') +} + +const items = await db.items.listByOwner(session.user.id) +--- + + + + +\`\`\` + +### Environment Variables + +Required in \`.env\`: +- \`APPWRITE_ENDPOINT\` +- \`APPWRITE_PROJECT_ID\` +- \`APPWRITE_API_KEY\` +- \`APPWRITE_DATABASE_ID\` +- \`APPWRITE_BUCKET_ID\` (if using storage) +`; + +/** + * Database wrapper templates for each language + */ +const databaseWrapperTemplates = { + javascript: ` +## Database Wrapper Template + +Create a centralized database helper at your designated location (e.g., \`lib/db.ts\` or \`server/lib/db.ts\`): + +\`\`\`typescript +import { Client, TablesDB, Query, ID } from 'node-appwrite' + +// Initialize admin client (server-side only) +const client = new Client() + .setEndpoint(process.env.APPWRITE_ENDPOINT!) + .setProject(process.env.APPWRITE_PROJECT_ID!) + .setKey(process.env.APPWRITE_API_KEY!) + +const tablesDB = new TablesDB(client) +const DATABASE_ID = process.env.APPWRITE_DATABASE_ID! + +// Generic CRUD helper factory +function createTable(tableId: string) { + return { + async create(data: Omit) { + const doc = await tablesDB.createRow( + DATABASE_ID, + tableId, + ID.unique(), + data + ) + return doc as T + }, + + async get(id: string) { + try { + const doc = await tablesDB.getRow(DATABASE_ID, tableId, id) + return doc as T + } catch { + return null + } + }, + + async listByOwner(userId: string) { + const response = await tablesDB.listRows(DATABASE_ID, tableId, [ + Query.equal('createdBy', [userId]), + Query.orderDesc('$createdAt'), + ]) + return response.rows as T[] + }, + + async listByTeam(teamId: string) { + const response = await tablesDB.listRows(DATABASE_ID, tableId, [ + Query.equal('teamId', [teamId]), + Query.orderDesc('$createdAt'), + ]) + return response.rows as T[] + }, + + async update(id: string, data: Partial) { + // Remove immutable columns + const { $id, $createdAt, $updatedAt, createdBy, teamId, ...updateData } = data as any + const doc = await tablesDB.updateRow(DATABASE_ID, tableId, id, updateData) + return doc as T + }, + + async delete(id: string) { + await tablesDB.deleteRow(DATABASE_ID, tableId, id) + }, + } +} + +// Export typed tables +export const db = { + items: createTable('items'), + projects: createTable('projects'), + // Add more tables as needed +} +\`\`\` +`, + + python: ` +## Database Wrapper Template + +Create a centralized database helper at \`services/db.py\`: + +\`\`\`python +import os +from typing import TypeVar, Generic, Optional, List, Dict, Any +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.query import Query +from appwrite.id import ID + +# Initialize admin client (server-side only) +client = Client() +client.set_endpoint(os.environ['APPWRITE_ENDPOINT']) +client.set_project(os.environ['APPWRITE_PROJECT_ID']) +client.set_key(os.environ['APPWRITE_API_KEY']) + +tables_db = TablesDB(client) +DATABASE_ID = os.environ['APPWRITE_DATABASE_ID'] + +T = TypeVar('T') + +class Table(Generic[T]): + def __init__(self, table_id: str): + self.table_id = table_id + + def create(self, data: Dict[str, Any]) -> T: + """Create row with auto-generated ID""" + doc = tables_db.create_row( + DATABASE_ID, + self.table_id, + ID.unique(), + data + ) + return doc + + def get(self, row_id: str) -> Optional[T]: + """Get row by ID""" + try: + return tables_db.get_row( + DATABASE_ID, + self.table_id, + row_id + ) + except Exception: + return None + + def list_by_owner(self, user_id: str) -> List[T]: + """List rows by owner""" + result = tables_db.list_rows( + DATABASE_ID, + self.table_id, + [ + Query.equal('createdBy', user_id), + Query.order_desc('$createdAt') + ] + ) + return result['rows'] + + def list_by_team(self, team_id: str) -> List[T]: + """List rows by team""" + result = tables_db.list_rows( + DATABASE_ID, + self.table_id, + [ + Query.equal('teamId', team_id), + Query.order_desc('$createdAt') + ] + ) + return result['rows'] + + def update(self, row_id: str, data: Dict[str, Any]) -> T: + """Update row (removes immutable columns)""" + safe_data = {k: v for k, v in data.items() + if k not in ('$id', '$createdAt', '$updatedAt', 'createdBy', 'teamId')} + return tables_db.update_row( + DATABASE_ID, + self.table_id, + row_id, + safe_data + ) + + def delete(self, row_id: str) -> None: + """Delete row""" + tables_db.delete_row(DATABASE_ID, self.table_id, row_id) + +# Export typed tables +items = Table('items') +projects = Table('projects') +\`\`\` +`, + + php: ` +## Database Wrapper Template + +Create a centralized database helper at \`src/Services/DatabaseService.php\`: + +\`\`\`php +tableId = $tableId; + $this->databaseId = $_ENV['APPWRITE_DATABASE_ID']; + } + + private static function getClient(): Client + { + if (self::$client === null) { + self::$client = new Client(); + self::$client + ->setEndpoint($_ENV['APPWRITE_ENDPOINT']) + ->setProject($_ENV['APPWRITE_PROJECT_ID']) + ->setKey($_ENV['APPWRITE_API_KEY']); + } + return self::$client; + } + + private static function getTablesDB(): TablesDB + { + if (self::$tablesDB === null) { + self::$tablesDB = new TablesDB(self::getClient()); + } + return self::$tablesDB; + } + + public function create(array $data): array + { + return self::getTablesDB()->createRow( + $this->databaseId, + $this->tableId, + ID::unique(), + $data + ); + } + + public function get(string $rowId): ?array + { + try { + return self::getTablesDB()->getRow( + $this->databaseId, + $this->tableId, + $rowId + ); + } catch (\\Exception $e) { + return null; + } + } + + public function listByOwner(string $userId): array + { + $result = self::getTablesDB()->listRows( + $this->databaseId, + $this->tableId, + [ + Query::equal('createdBy', [$userId]), + Query::orderDesc('\\$createdAt') + ] + ); + return $result['rows']; + } + + public function listByTeam(string $teamId): array + { + $result = self::getTablesDB()->listRows( + $this->databaseId, + $this->tableId, + [ + Query::equal('teamId', [$teamId]), + Query::orderDesc('\\$createdAt') + ] + ); + return $result['rows']; + } + + public function update(string $rowId, array $data): array + { + // Remove immutable columns + unset($data['\\$id'], $data['\\$createdAt'], $data['\\$updatedAt'], + $data['createdBy'], $data['teamId']); + + return self::getTablesDB()->updateRow( + $this->databaseId, + $this->tableId, + $rowId, + $data + ); + } + + public function delete(string $rowId): void + { + self::getTablesDB()->deleteRow( + $this->databaseId, + $this->tableId, + $rowId + ); + } +} + +// Usage: $items = new DatabaseService('items'); +\`\`\` +`, + + go: ` +## Database Wrapper Template + +Create a centralized database helper at \`internal/db/db.go\`: + +\`\`\`go +package db + +import ( + "os" + "sync" + + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/id" + "github.com/appwrite/sdk-for-go/query" +) + +var ( + client *appwrite.Client + tablesDB *appwrite.TablesDB + once sync.Once + DatabaseID string +) + +func init() { + once.Do(func() { + client = appwrite.NewClient() + client.SetEndpoint(os.Getenv("APPWRITE_ENDPOINT")) + client.SetProject(os.Getenv("APPWRITE_PROJECT_ID")) + client.SetKey(os.Getenv("APPWRITE_API_KEY")) + + tablesDB = appwrite.NewTablesDB(client) + DatabaseID = os.Getenv("APPWRITE_DATABASE_ID") + }) +} + +type Table struct { + ID string +} + +func NewTable(tableID string) *Table { + return &Table{ID: tableID} +} + +func (c *Table) Create(data map[string]interface{}) (map[string]interface{}, error) { + doc, err := tablesDB.CreateRow(DatabaseID, c.ID, id.Unique(), data) + if err != nil { + return nil, err + } + return doc.ToMap(), nil +} + +func (c *Table) Get(rowID string) (map[string]interface{}, error) { + doc, err := tablesDB.GetRow(DatabaseID, c.ID, rowID) + if err != nil { + return nil, err + } + return doc.ToMap(), nil +} + +func (c *Table) ListByOwner(userID string) ([]map[string]interface{}, error) { + result, err := tablesDB.ListRows( + DatabaseID, + c.ID, + tablesDB.WithListRowsQueries([]string{ + query.Equal("createdBy", userID), + query.OrderDesc("$createdAt"), + }), + ) + if err != nil { + return nil, err + } + + docs := make([]map[string]interface{}, len(result.Rows)) + for i, doc := range result.Rows { + docs[i] = doc.ToMap() + } + return docs, nil +} + +func (c *Table) ListByTeam(teamID string) ([]map[string]interface{}, error) { + result, err := tablesDB.ListRows( + DatabaseID, + c.ID, + tablesDB.WithListRowsQueries([]string{ + query.Equal("teamId", teamID), + query.OrderDesc("$createdAt"), + }), + ) + if err != nil { + return nil, err + } + + docs := make([]map[string]interface{}, len(result.Rows)) + for i, doc := range result.Rows { + docs[i] = doc.ToMap() + } + return docs, nil +} + +func (c *Table) Update(rowID string, data map[string]interface{}) (map[string]interface{}, error) { + // Remove immutable columns + delete(data, "$id") + delete(data, "$createdAt") + delete(data, "$updatedAt") + delete(data, "createdBy") + delete(data, "teamId") + + doc, err := tablesDB.UpdateRow(DatabaseID, c.ID, rowID, data) + if err != nil { + return nil, err + } + return doc.ToMap(), nil +} + +func (c *Table) Delete(rowID string) error { + _, err := tablesDB.DeleteRow(DatabaseID, c.ID, rowID) + return err +} + +// Exported tables +var ( + Items = NewTable("items") + Projects = NewTable("projects") +) +\`\`\` +`, + + ruby: ` +## Database Wrapper Template + +Create a centralized database helper at \`lib/database_service.rb\`: + +\`\`\`ruby +require 'appwrite' + +module DatabaseService + class << self + def client + @client ||= begin + client = Appwrite::Client.new + client + .set_endpoint(ENV['APPWRITE_ENDPOINT']) + .set_project(ENV['APPWRITE_PROJECT_ID']) + .set_key(ENV['APPWRITE_API_KEY']) + client + end + end + + def tables_db + @tables_db ||= Appwrite::TablesDB.new(client) + end + + def database_id + ENV['APPWRITE_DATABASE_ID'] + end + end + + class Table + def initialize(table_id) + @table_id = table_id + end + + def create(data) + DatabaseService.tables_db.create_row( + database_id: DatabaseService.database_id, + table_id: @table_id, + row_id: Appwrite::ID.unique, + data: data + ) + end + + def get(row_id) + DatabaseService.tables_db.get_row( + database_id: DatabaseService.database_id, + table_id: @table_id, + row_id: row_id + ) + rescue Appwrite::Exception + nil + end + + def list_by_owner(user_id) + result = DatabaseService.tables_db.list_rows( + database_id: DatabaseService.database_id, + table_id: @table_id, + queries: [ + Appwrite::Query.equal('createdBy', [user_id]), + Appwrite::Query.order_desc('$createdAt') + ] + ) + result.rows + end + + def list_by_team(team_id) + result = DatabaseService.tables_db.list_rows( + database_id: DatabaseService.database_id, + table_id: @table_id, + queries: [ + Appwrite::Query.equal('teamId', [team_id]), + Appwrite::Query.order_desc('$createdAt') + ] + ) + result.rows + end + + def update(row_id, data) + # Remove immutable fields + safe_data = data.except('$id', '$createdAt', '$updatedAt', 'createdBy', 'teamId') + + DatabaseService.tables_db.update_row( + database_id: DatabaseService.database_id, + table_id: @table_id, + row_id: row_id, + data: safe_data + ) + end + + def delete(row_id) + DatabaseService.tables_db.delete_row( + database_id: DatabaseService.database_id, + table_id: @table_id, + row_id: row_id + ) + end + end +end + +# Convenience accessors +module DB + def self.items + @items ||= DatabaseService::Table.new('items') + end + + def self.projects + @projects ||= DatabaseService::Table.new('projects') + end +end +\`\`\` +`, + + dotnet: ` +## Database Wrapper Template + +Create a centralized database helper at \`Services/DatabaseService.cs\`: + +\`\`\`csharp +using Appwrite; +using Appwrite.Services; +using Appwrite.Models; + +public class DatabaseService where T : class +{ + private static Client? _client; + private static TablesDB? _tablesDB; + private readonly string _tableId; + + public static string DatabaseId => Environment.GetEnvironmentVariable("APPWRITE_DATABASE_ID")!; + + private static Client GetClient() + { + if (_client == null) + { + _client = new Client() + .SetEndpoint(Environment.GetEnvironmentVariable("APPWRITE_ENDPOINT")!) + .SetProject(Environment.GetEnvironmentVariable("APPWRITE_PROJECT_ID")!) + .SetKey(Environment.GetEnvironmentVariable("APPWRITE_API_KEY")!); + } + return _client; + } + + private static TablesDB GetTablesDB() + { + return _tablesDB ??= new TablesDB(GetClient()); + } + + public DatabaseService(string tableId) + { + _tableId = tableId; + } + + public async Task CreateAsync(Dictionary data) + { + return await GetTablesDB().CreateRow( + databaseId: DatabaseId, + tableId: _tableId, + rowId: ID.Unique(), + data: data + ); + } + + public async Task GetAsync(string rowId) + { + try + { + return await GetTablesDB().GetRow( + databaseId: DatabaseId, + tableId: _tableId, + rowId: rowId + ); + } + catch (AppwriteException) + { + return null; + } + } + + public async Task> ListByOwnerAsync(string userId) + { + var result = await GetTablesDB().ListRows( + databaseId: DatabaseId, + tableId: _tableId, + queries: new List + { + Query.Equal("createdBy", new List { userId }), + Query.OrderDesc("$createdAt") + } + ); + return result.Rows; + } + + public async Task> ListByTeamAsync(string teamId) + { + var result = await GetTablesDB().ListRows( + databaseId: DatabaseId, + tableId: _tableId, + queries: new List + { + Query.Equal("teamId", new List { teamId }), + Query.OrderDesc("$createdAt") + } + ); + return result.Rows; + } + + public async Task UpdateAsync(string rowId, Dictionary data) + { + // Remove immutable columns + data.Remove("$id"); + data.Remove("$createdAt"); + data.Remove("$updatedAt"); + data.Remove("createdBy"); + data.Remove("teamId"); + + return await GetTablesDB().UpdateRow( + databaseId: DatabaseId, + tableId: _tableId, + rowId: rowId, + data: data + ); + } + + public async Task DeleteAsync(string rowId) + { + await GetTablesDB().DeleteRow( + databaseId: DatabaseId, + tableId: _tableId, + rowId: rowId + ); + } +} + +// Static accessors +public static class DB +{ + public static DatabaseService Items { get; } = new("items"); + public static DatabaseService Projects { get; } = new("projects"); +} +\`\`\` +`, + + dart: ` +## Database Wrapper Template + +Create a centralized database helper at \`lib/services/database.dart\`: + +\`\`\`dart +import 'dart:io'; +import 'package:dart_appwrite/dart_appwrite.dart'; + +class AppwriteService { + static Client? _client; + static TablesDB? _tablesDB; + + static String get databaseId => Platform.environment['APPWRITE_DATABASE_ID']!; + + static Client get client { + _client ??= Client() + .setEndpoint(Platform.environment['APPWRITE_ENDPOINT']!) + .setProject(Platform.environment['APPWRITE_PROJECT_ID']!) + .setKey(Platform.environment['APPWRITE_API_KEY']!); + return _client!; + } + + static TablesDB get tablesDB { + _tablesDB ??= TablesDB(client); + return _tablesDB!; + } +} + +class Table { + final String tableId; + + Table(this.tableId); + + Future create(Map data) async { + return await AppwriteService.tablesDB.createRow( + databaseId: AppwriteService.databaseId, + tableId: tableId, + rowId: ID.unique(), + data: data, + ); + } + + Future get(String rowId) async { + try { + return await AppwriteService.tablesDB.getRow( + databaseId: AppwriteService.databaseId, + tableId: tableId, + rowId: rowId, + ); + } on AppwriteException { + return null; + } + } + + Future> listByOwner(String userId) async { + final result = await AppwriteService.tablesDB.listRows( + databaseId: AppwriteService.databaseId, + tableId: tableId, + queries: [ + Query.equal('createdBy', userId), + Query.orderDesc(r'$createdAt'), + ], + ); + return result.rows; + } + + Future> listByTeam(String teamId) async { + final result = await AppwriteService.tablesDB.listRows( + databaseId: AppwriteService.databaseId, + tableId: tableId, + queries: [ + Query.equal('teamId', teamId), + Query.orderDesc(r'$createdAt'), + ], + ); + return result.rows; + } + + Future update(String rowId, Map data) async { + // Remove immutable columns + data.remove(r'$id'); + data.remove(r'$createdAt'); + data.remove(r'$updatedAt'); + data.remove('createdBy'); + data.remove('teamId'); + + return await AppwriteService.tablesDB.updateRow( + databaseId: AppwriteService.databaseId, + tableId: tableId, + rowId: rowId, + data: data, + ); + } + + Future delete(String rowId) async { + await AppwriteService.tablesDB.deleteRow( + databaseId: AppwriteService.databaseId, + tableId: tableId, + rowId: rowId, + ); + } +} + +// Exported tables +final items = Table('items'); +final projects = Table('projects'); +\`\`\` +`, + + kotlin: ` +## Database Wrapper Template + +Create a centralized database helper at \`services/DatabaseService.kt\`: + +\`\`\`kotlin +package com.example.services + +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.Query +import io.appwrite.models.Row +import io.appwrite.services.TablesDB + +object AppwriteService { + val databaseId: String = System.getenv("APPWRITE_DATABASE_ID") + + val client: Client by lazy { + Client() + .setEndpoint(System.getenv("APPWRITE_ENDPOINT")) + .setProject(System.getenv("APPWRITE_PROJECT_ID")) + .setKey(System.getenv("APPWRITE_API_KEY")) + } + + val tablesDB: TablesDB by lazy { TablesDB(client) } +} + +class Table(private val tableId: String) { + private val tablesDB = AppwriteService.tablesDB + private val databaseId = AppwriteService.databaseId + + suspend fun create(data: Map): Row> { + return tablesDB.createRow( + databaseId = databaseId, + tableId = tableId, + rowId = ID.unique(), + data = data + ) + } + + suspend fun get(rowId: String): Row>? { + return try { + tablesDB.getRow( + databaseId = databaseId, + tableId = tableId, + rowId = rowId + ) + } catch (e: Exception) { + null + } + } + + suspend fun listByOwner(userId: String): List>> { + val result = tablesDB.listRows( + databaseId = databaseId, + tableId = tableId, + queries = listOf( + Query.equal("createdBy", listOf(userId)), + Query.orderDesc("\$createdAt") + ) + ) + return result.rows + } + + suspend fun listByTeam(teamId: String): List>> { + val result = tablesDB.listRows( + databaseId = databaseId, + tableId = tableId, + queries = listOf( + Query.equal("teamId", listOf(teamId)), + Query.orderDesc("\$createdAt") + ) + ) + return result.rows + } + + suspend fun update(rowId: String, data: Map): Row> { + // Remove immutable columns + val safeData = data.filterKeys { + it !in listOf("\$id", "\$createdAt", "\$updatedAt", "createdBy", "teamId") + } + + return tablesDB.updateRow( + databaseId = databaseId, + tableId = tableId, + rowId = rowId, + data = safeData + ) + } + + suspend fun delete(rowId: String) { + tablesDB.deleteRow( + databaseId = databaseId, + tableId = tableId, + rowId = rowId + ) + } +} + +// Exported tables +object DB { + val items = Table("items") + val projects = Table("projects") +} +\`\`\` +`, + + swift: ` +## Database Wrapper Template + +Create a centralized database helper at \`Services/DatabaseService.swift\`: + +\`\`\`swift +import Appwrite +import Foundation + +class AppwriteService { + static let shared = AppwriteService() + + let client: Client + let tablesDB: TablesDB + let databaseId: String + + private init() { + client = Client() + .setEndpoint(ProcessInfo.processInfo.environment["APPWRITE_ENDPOINT"]!) + .setProject(ProcessInfo.processInfo.environment["APPWRITE_PROJECT_ID"]!) + .setKey(ProcessInfo.processInfo.environment["APPWRITE_API_KEY"]!) + + tablesDB = TablesDB(client) + databaseId = ProcessInfo.processInfo.environment["APPWRITE_DATABASE_ID"]! + } +} + +class Table { + let tableId: String + private let tablesDB: TablesDB + private let databaseId: String + + init(_ tableId: String) { + self.tableId = tableId + self.tablesDB = AppwriteService.shared.tablesDB + self.databaseId = AppwriteService.shared.databaseId + } + + func create(data: [String: Any]) async throws -> Row<[String: AnyCodable]> { + return try await tablesDB.createRow( + databaseId: databaseId, + tableId: tableId, + rowId: ID.unique(), + data: data + ) + } + + func get(rowId: String) async throws -> Row<[String: AnyCodable]>? { + do { + return try await tablesDB.getRow( + databaseId: databaseId, + tableId: tableId, + rowId: rowId + ) + } catch { + return nil + } + } + + func listByOwner(userId: String) async throws -> [Row<[String: AnyCodable]>] { + let result = try await tablesDB.listRows( + databaseId: databaseId, + tableId: tableId, + queries: [ + Query.equal("createdBy", value: userId), + Query.orderDesc("$createdAt") + ] + ) + return result.rows + } + + func listByTeam(teamId: String) async throws -> [Row<[String: AnyCodable]>] { + let result = try await tablesDB.listRows( + databaseId: databaseId, + tableId: tableId, + queries: [ + Query.equal("teamId", value: teamId), + Query.orderDesc("$createdAt") + ] + ) + return result.rows + } + + func update(rowId: String, data: [String: Any]) async throws -> Row<[String: AnyCodable]> { + // Remove immutable columns + var safeData = data + safeData.removeValue(forKey: "$id") + safeData.removeValue(forKey: "$createdAt") + safeData.removeValue(forKey: "$updatedAt") + safeData.removeValue(forKey: "createdBy") + safeData.removeValue(forKey: "teamId") + + return try await tablesDB.updateRow( + databaseId: databaseId, + tableId: tableId, + rowId: rowId, + data: safeData + ) + } + + func delete(rowId: String) async throws { + _ = try await tablesDB.deleteRow( + databaseId: databaseId, + tableId: tableId, + rowId: rowId + ) + } +} + +// Exported tables +struct DB { + static let items = Table("items") + static let projects = Table("projects") +} +\`\`\` +` +}; + +/** + * Get database wrapper template for a specific SDK + * @param {string} sdk - SDK name (javascript, python, php, etc.) + * @returns {string} Database wrapper template + */ +export function getDatabaseWrapperTemplate(sdk) { + // Map SDK names to template keys + /** @type {Record} */ + const sdkMap = { + javascript: 'javascript', + 'react-native': 'javascript', + python: 'python', + php: 'php', + go: 'go', + flutter: 'dart', + dart: 'dart', + apple: 'swift', + android: 'kotlin', + swift: 'swift', + kotlin: 'kotlin', + ruby: 'ruby', + dotnet: 'dotnet' + }; + + const templateKey = sdkMap[sdk] || 'javascript'; + /** @type {Record} */ + const templates = databaseWrapperTemplates; + return templates[templateKey] || templates.javascript; +} + +// Legacy export for backwards compatibility +export const databaseWrapperTemplate = databaseWrapperTemplates.javascript; + +/** + * Storage wrapper templates for each language + */ +const storageWrapperTemplates = { + javascript: ` +## Storage Wrapper Template + +Create a centralized storage helper: + +\`\`\`typescript +import { Client, Storage, ID } from 'node-appwrite' +import { InputFile } from 'node-appwrite/file' + +const client = new Client() + .setEndpoint(process.env.APPWRITE_ENDPOINT!) + .setProject(process.env.APPWRITE_PROJECT_ID!) + .setKey(process.env.APPWRITE_API_KEY!) + +const storage = new Storage(client) +const BUCKET_ID = process.env.APPWRITE_BUCKET_ID! + +export const fileStorage = { + /** + * Upload file from base64 string + * @returns File ID only (never store base64 in database) + */ + async upload(base64Data: string, fileName: string, mimeType: string) { + // Strip data URL prefix if present + const base64Clean = base64Data.replace(/^data:[^;]+;base64,/, '') + + // Convert to buffer + const buffer = Buffer.from(base64Clean, 'base64') + + // Create InputFile + const inputFile = InputFile.fromBuffer(buffer, fileName) + + // Upload to storage + const file = await storage.createFile(BUCKET_ID, ID.unique(), inputFile) + + return file.$id // Return ID only + }, + + /** + * Get file as data URL for client consumption + */ + async getAsDataUrl(fileId: string, mimeType: string) { + const arrayBuffer = await storage.getFileDownload(BUCKET_ID, fileId) + const base64 = Buffer.from(arrayBuffer).toString('base64') + return \`data:\${mimeType};base64,\${base64}\` + }, + + /** + * Get file preview URL + */ + getPreviewUrl(fileId: string, width?: number, height?: number) { + return storage.getFilePreview(BUCKET_ID, fileId, width, height) + }, + + /** + * Delete file + */ + async delete(fileId: string) { + await storage.deleteFile(BUCKET_ID, fileId) + }, + + /** + * Delete multiple files (for cleanup) + */ + async deleteMany(fileIds: string[]) { + await Promise.allSettled( + fileIds.map(id => storage.deleteFile(BUCKET_ID, id)) + ) + }, +} +\`\`\` +`, + + python: ` +## Storage Wrapper Template + +Create a centralized storage helper at \`services/storage.py\`: + +\`\`\`python +import os +import base64 +from typing import Optional +from appwrite.client import Client +from appwrite.services.storage import Storage +from appwrite.id import ID +from appwrite.input_file import InputFile + +# Initialize admin client (server-side only) +client = Client() +client.set_endpoint(os.environ['APPWRITE_ENDPOINT']) +client.set_project(os.environ['APPWRITE_PROJECT_ID']) +client.set_key(os.environ['APPWRITE_API_KEY']) + +storage = Storage(client) +BUCKET_ID = os.environ.get('APPWRITE_BUCKET_ID', '') + +class FileStorage: + @staticmethod + def upload(base64_data: str, file_name: str, mime_type: str) -> str: + """ + Upload file from base64 string + Returns: File ID only (never store base64 in database) + """ + # Strip data URL prefix if present + if ';base64,' in base64_data: + base64_data = base64_data.split(';base64,')[1] + + # Decode base64 to bytes + file_bytes = base64.b64decode(base64_data) + + # Create InputFile from bytes + input_file = InputFile.from_bytes(file_bytes, file_name, mime_type) + + # Upload to storage + result = storage.create_file(BUCKET_ID, ID.unique(), input_file) + + return result['$id'] + + @staticmethod + def get_as_data_url(file_id: str, mime_type: str) -> str: + """Get file as data URL for client consumption""" + file_bytes = storage.get_file_download(BUCKET_ID, file_id) + base64_str = base64.b64encode(file_bytes).decode('utf-8') + return f"data:{mime_type};base64,{base64_str}" + + @staticmethod + def get_preview_url(file_id: str, width: Optional[int] = None, height: Optional[int] = None) -> str: + """Get file preview URL""" + return storage.get_file_preview(BUCKET_ID, file_id, width=width, height=height) + + @staticmethod + def delete(file_id: str) -> None: + """Delete file""" + storage.delete_file(BUCKET_ID, file_id) + + @staticmethod + def delete_many(file_ids: list) -> None: + """Delete multiple files (for cleanup)""" + for file_id in file_ids: + try: + storage.delete_file(BUCKET_ID, file_id) + except Exception: + pass # Continue even if some files fail + +file_storage = FileStorage() +\`\`\` +`, + + php: ` +## Storage Wrapper Template + +Create a centralized storage helper at \`src/Services/StorageService.php\`: + +\`\`\`php +bucketId = $_ENV['APPWRITE_BUCKET_ID']; + } + + private static function getClient(): Client + { + if (self::$client === null) { + self::$client = new Client(); + self::$client + ->setEndpoint($_ENV['APPWRITE_ENDPOINT']) + ->setProject($_ENV['APPWRITE_PROJECT_ID']) + ->setKey($_ENV['APPWRITE_API_KEY']); + } + return self::$client; + } + + private static function getStorage(): Storage + { + if (self::$storage === null) { + self::$storage = new Storage(self::getClient()); + } + return self::$storage; + } + + /** + * Upload file from base64 string + * @return string File ID only (never store base64 in database) + */ + public function upload(string $base64Data, string $fileName, string $mimeType): string + { + // Strip data URL prefix if present + if (strpos($base64Data, ';base64,') !== false) { + $base64Data = explode(';base64,', $base64Data)[1]; + } + + // Decode base64 to binary + $fileData = base64_decode($base64Data); + + // Create InputFile + $inputFile = InputFile::withData($fileData, $fileName, $mimeType); + + // Upload to storage + $file = self::getStorage()->createFile($this->bucketId, ID::unique(), $inputFile); + + return $file['\\$id']; + } + + /** + * Get file as data URL for client consumption + */ + public function getAsDataUrl(string $fileId, string $mimeType): string + { + $fileData = self::getStorage()->getFileDownload($this->bucketId, $fileId); + $base64 = base64_encode($fileData); + return "data:{$mimeType};base64,{$base64}"; + } + + /** + * Get file preview URL + */ + public function getPreviewUrl(string $fileId, ?int $width = null, ?int $height = null): string + { + return self::getStorage()->getFilePreview($this->bucketId, $fileId, $width, $height); + } + + /** + * Delete file + */ + public function delete(string $fileId): void + { + self::getStorage()->deleteFile($this->bucketId, $fileId); + } + + /** + * Delete multiple files (for cleanup) + */ + public function deleteMany(array $fileIds): void + { + foreach ($fileIds as $fileId) { + try { + self::getStorage()->deleteFile($this->bucketId, $fileId); + } catch (\\Exception $e) { + // Continue even if some files fail + } + } + } +} +\`\`\` +`, + + go: ` +## Storage Wrapper Template + +Create a centralized storage helper at \`internal/storage/storage.go\`: + +\`\`\`go +package storage + +import ( + "encoding/base64" + "fmt" + "os" + "strings" + + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/file" + "github.com/appwrite/sdk-for-go/id" +) + +var ( + client *appwrite.Client + storage *appwrite.Storage + BucketID string +) + +func init() { + client = appwrite.NewClient() + client.SetEndpoint(os.Getenv("APPWRITE_ENDPOINT")) + client.SetProject(os.Getenv("APPWRITE_PROJECT_ID")) + client.SetKey(os.Getenv("APPWRITE_API_KEY")) + + storage = appwrite.NewStorage(client) + BucketID = os.Getenv("APPWRITE_BUCKET_ID") +} + +// Upload file from base64 string, returns File ID only +func Upload(base64Data, fileName, mimeType string) (string, error) { + // Strip data URL prefix if present + if idx := strings.Index(base64Data, ";base64,"); idx != -1 { + base64Data = base64Data[idx+8:] + } + + // Decode base64 to bytes + fileBytes, err := base64.StdEncoding.DecodeString(base64Data) + if err != nil { + return "", err + } + + // Create InputFile + inputFile := file.NewInputFile(fileBytes, fileName) + + // Upload to storage + result, err := storage.CreateFile(BucketID, id.Unique(), inputFile) + if err != nil { + return "", err + } + + return result.Id, nil +} + +// GetAsDataUrl returns file as data URL for client consumption +func GetAsDataUrl(fileId, mimeType string) (string, error) { + fileBytes, err := storage.GetFileDownload(BucketID, fileId) + if err != nil { + return "", err + } + + base64Str := base64.StdEncoding.EncodeToString(fileBytes) + return fmt.Sprintf("data:%s;base64,%s", mimeType, base64Str), nil +} + +// GetPreviewUrl returns file preview URL +func GetPreviewUrl(fileId string, width, height int) string { + return storage.GetFilePreview(BucketID, fileId, + storage.WithGetFilePreviewWidth(width), + storage.WithGetFilePreviewHeight(height)) +} + +// Delete removes a file +func Delete(fileId string) error { + _, err := storage.DeleteFile(BucketID, fileId) + return err +} + +// DeleteMany removes multiple files (for cleanup) +func DeleteMany(fileIds []string) { + for _, fileId := range fileIds { + _ = Delete(fileId) // Continue even if some fail + } +} +\`\`\` +`, + + ruby: ` +## Storage Wrapper Template + +Create a centralized storage helper at \`lib/storage_service.rb\`: + +\`\`\`ruby +require 'appwrite' +require 'base64' + +module StorageService + class << self + def client + @client ||= begin + client = Appwrite::Client.new + client + .set_endpoint(ENV['APPWRITE_ENDPOINT']) + .set_project(ENV['APPWRITE_PROJECT_ID']) + .set_key(ENV['APPWRITE_API_KEY']) + client + end + end + + def storage + @storage ||= Appwrite::Storage.new(client) + end + + def bucket_id + ENV['APPWRITE_BUCKET_ID'] + end + end + + class FileStorage + # Upload file from base64 string, returns File ID only + def upload(base64_data, file_name, mime_type) + # Strip data URL prefix if present + if base64_data.include?(';base64,') + base64_data = base64_data.split(';base64,').last + end + + # Decode base64 to bytes + file_bytes = Base64.decode64(base64_data) + + # Create InputFile + input_file = Appwrite::InputFile.from_bytes(file_bytes, file_name, mime_type) + + # Upload to storage + result = StorageService.storage.create_file( + bucket_id: StorageService.bucket_id, + file_id: Appwrite::ID.unique, + file: input_file + ) + + result['$id'] + end + + # Get file as data URL for client consumption + def get_as_data_url(file_id, mime_type) + file_bytes = StorageService.storage.get_file_download( + bucket_id: StorageService.bucket_id, + file_id: file_id + ) + base64_str = Base64.strict_encode64(file_bytes) + "data:#{mime_type};base64,#{base64_str}" + end + + # Get file preview URL + def get_preview_url(file_id, width: nil, height: nil) + StorageService.storage.get_file_preview( + bucket_id: StorageService.bucket_id, + file_id: file_id, + width: width, + height: height + ) + end + + # Delete file + def delete(file_id) + StorageService.storage.delete_file( + bucket_id: StorageService.bucket_id, + file_id: file_id + ) + end + + # Delete multiple files (for cleanup) + def delete_many(file_ids) + file_ids.each do |file_id| + delete(file_id) + rescue StandardError + # Continue even if some files fail + end + end + end +end + +# Convenience accessor +def file_storage + @file_storage ||= StorageService::FileStorage.new +end +\`\`\` +`, + + dotnet: ` +## Storage Wrapper Template + +Create a centralized storage helper at \`Services/StorageService.cs\`: + +\`\`\`csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Appwrite; +using Appwrite.Services; + +public class StorageService +{ + private static Client? _client; + private static Storage? _storage; + private static string BucketId => Environment.GetEnvironmentVariable("APPWRITE_BUCKET_ID")!; + + private static Client GetClient() + { + if (_client == null) + { + _client = new Client() + .SetEndpoint(Environment.GetEnvironmentVariable("APPWRITE_ENDPOINT")!) + .SetProject(Environment.GetEnvironmentVariable("APPWRITE_PROJECT_ID")!) + .SetKey(Environment.GetEnvironmentVariable("APPWRITE_API_KEY")!); + } + return _client; + } + + private static Storage GetStorage() + { + return _storage ??= new Storage(GetClient()); + } + + /// + /// Upload file from base64 string + /// + /// File ID only (never store base64 in database) + public static async Task UploadAsync(string base64Data, string fileName, string mimeType) + { + // Strip data URL prefix if present + if (base64Data.Contains(";base64,")) + { + base64Data = base64Data.Split(";base64,")[1]; + } + + // Decode base64 to bytes + var fileBytes = Convert.FromBase64String(base64Data); + + // Create InputFile + var inputFile = InputFile.FromBytes(fileBytes, fileName, mimeType); + + // Upload to storage + var file = await GetStorage().CreateFile( + bucketId: BucketId, + fileId: ID.Unique(), + file: inputFile + ); + + return file.Id; + } + + /// + /// Get file as data URL for client consumption + /// + public static async Task GetAsDataUrlAsync(string fileId, string mimeType) + { + var fileBytes = await GetStorage().GetFileDownload(BucketId, fileId); + var base64Str = Convert.ToBase64String(fileBytes); + return $"data:{mimeType};base64,{base64Str}"; + } + + /// + /// Get file preview URL + /// + public static string GetPreviewUrl(string fileId, int? width = null, int? height = null) + { + return GetStorage().GetFilePreview(BucketId, fileId, width: width, height: height); + } + + /// + /// Delete file + /// + public static async Task DeleteAsync(string fileId) + { + await GetStorage().DeleteFile(BucketId, fileId); + } + + /// + /// Delete multiple files (for cleanup) + /// + public static async Task DeleteManyAsync(IEnumerable fileIds) + { + foreach (var fileId in fileIds) + { + try + { + await GetStorage().DeleteFile(BucketId, fileId); + } + catch + { + // Continue even if some files fail + } + } + } +} +\`\`\` +`, + + dart: ` +## Storage Wrapper Template + +Create a centralized storage helper at \`lib/services/storage.dart\`: + +\`\`\`dart +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:dart_appwrite/dart_appwrite.dart'; + +class AppwriteStorage { + static Client? _client; + static Storage? _storage; + + static String get bucketId => Platform.environment['APPWRITE_BUCKET_ID']!; + + static Client get client { + _client ??= Client() + .setEndpoint(Platform.environment['APPWRITE_ENDPOINT']!) + .setProject(Platform.environment['APPWRITE_PROJECT_ID']!) + .setKey(Platform.environment['APPWRITE_API_KEY']!); + return _client!; + } + + static Storage get storage { + _storage ??= Storage(client); + return _storage!; + } +} + +class FileStorageService { + /// Upload file from base64 string + /// Returns: File ID only (never store base64 in database) + Future upload(String base64Data, String fileName, String mimeType) async { + // Strip data URL prefix if present + if (base64Data.contains(';base64,')) { + base64Data = base64Data.split(';base64,').last; + } + + // Decode base64 to bytes + final Uint8List fileBytes = base64Decode(base64Data); + + // Create InputFile + final inputFile = InputFile.fromBytes( + bytes: fileBytes, + filename: fileName, + contentType: mimeType, + ); + + // Upload to storage + final file = await AppwriteStorage.storage.createFile( + bucketId: AppwriteStorage.bucketId, + fileId: ID.unique(), + file: inputFile, + ); + + return file.\$id; + } + + /// Get file as data URL for client consumption + Future getAsDataUrl(String fileId, String mimeType) async { + final fileBytes = await AppwriteStorage.storage.getFileDownload( + bucketId: AppwriteStorage.bucketId, + fileId: fileId, + ); + final base64Str = base64Encode(fileBytes); + return 'data:$mimeType;base64,$base64Str'; + } + + /// Get file preview URL + String getPreviewUrl(String fileId, {int? width, int? height}) { + return AppwriteStorage.storage.getFilePreview( + bucketId: AppwriteStorage.bucketId, + fileId: fileId, + width: width, + height: height, + ).toString(); + } + + /// Delete file + Future delete(String fileId) async { + await AppwriteStorage.storage.deleteFile( + bucketId: AppwriteStorage.bucketId, + fileId: fileId, + ); + } + + /// Delete multiple files (for cleanup) + Future deleteMany(List fileIds) async { + for (final fileId in fileIds) { + try { + await delete(fileId); + } catch (_) { + // Continue even if some files fail + } + } + } +} + +final fileStorage = FileStorageService(); +\`\`\` +`, + + kotlin: ` +## Storage Wrapper Template + +Create a centralized storage helper at \`services/StorageService.kt\`: + +\`\`\`kotlin +package com.example.services + +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.models.File +import io.appwrite.services.Storage +import java.util.Base64 + +object AppwriteStorageService { + val bucketId: String = System.getenv("APPWRITE_BUCKET_ID") + + val client: Client by lazy { + Client() + .setEndpoint(System.getenv("APPWRITE_ENDPOINT")) + .setProject(System.getenv("APPWRITE_PROJECT_ID")) + .setKey(System.getenv("APPWRITE_API_KEY")) + } + + val storage: Storage by lazy { Storage(client) } +} + +class FileStorageService { + private val storage = AppwriteStorageService.storage + private val bucketId = AppwriteStorageService.bucketId + + /** + * Upload file from base64 string + * @return File ID only (never store base64 in database) + */ + suspend fun upload(base64Data: String, fileName: String, mimeType: String): String { + // Strip data URL prefix if present + val cleanBase64 = if (base64Data.contains(";base64,")) { + base64Data.substringAfter(";base64,") + } else { + base64Data + } + + // Decode base64 to bytes + val fileBytes = Base64.getDecoder().decode(cleanBase64) + + // Upload to storage + val file = storage.createFile( + bucketId = bucketId, + fileId = ID.unique(), + file = io.appwrite.models.InputFile.fromBytes(fileBytes, fileName, mimeType) + ) + + return file.id + } + + /** + * Get file as data URL for client consumption + */ + suspend fun getAsDataUrl(fileId: String, mimeType: String): String { + val fileBytes = storage.getFileDownload(bucketId, fileId) + val base64Str = Base64.getEncoder().encodeToString(fileBytes) + return "data:\$mimeType;base64,\$base64Str" + } + + /** + * Get file preview URL + */ + fun getPreviewUrl(fileId: String, width: Int? = null, height: Int? = null): String { + return storage.getFilePreview(bucketId, fileId, width = width, height = height) + } + + /** + * Delete file + */ + suspend fun delete(fileId: String) { + storage.deleteFile(bucketId, fileId) + } + + /** + * Delete multiple files (for cleanup) + */ + suspend fun deleteMany(fileIds: List) { + fileIds.forEach { fileId -> + try { + delete(fileId) + } catch (_: Exception) { + // Continue even if some files fail + } + } + } +} + +val fileStorage = FileStorageService() +\`\`\` +`, + + swift: ` +## Storage Wrapper Template + +Create a centralized storage helper at \`Services/StorageService.swift\`: + +\`\`\`swift +import Appwrite +import Foundation + +class AppwriteStorageService { + static let shared = AppwriteStorageService() + + let client: Client + let storage: Storage + let bucketId: String + + private init() { + client = Client() + .setEndpoint(ProcessInfo.processInfo.environment["APPWRITE_ENDPOINT"]!) + .setProject(ProcessInfo.processInfo.environment["APPWRITE_PROJECT_ID"]!) + .setKey(ProcessInfo.processInfo.environment["APPWRITE_API_KEY"]!) + + storage = Storage(client) + bucketId = ProcessInfo.processInfo.environment["APPWRITE_BUCKET_ID"] ?? "" + } +} + +class FileStorageService { + private let storage = AppwriteStorageService.shared.storage + private let bucketId = AppwriteStorageService.shared.bucketId + + /// Upload file from base64 string + /// Returns: File ID only (never store base64 in database) + func upload(base64Data: String, fileName: String, mimeType: String) async throws -> String { + // Strip data URL prefix if present + var cleanBase64 = base64Data + if let range = base64Data.range(of: ";base64,") { + cleanBase64 = String(base64Data[range.upperBound...]) + } + + // Decode base64 to bytes + guard let fileData = Data(base64Encoded: cleanBase64) else { + throw NSError(domain: "StorageService", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid base64 data"]) + } + + // Create InputFile + let inputFile = InputFile.fromData(fileData, filename: fileName, mimeType: mimeType) + + // Upload to storage + let file = try await storage.createFile( + bucketId: bucketId, + fileId: ID.unique(), + file: inputFile + ) + + return file.id + } + + /// Get file as data URL for client consumption + func getAsDataUrl(fileId: String, mimeType: String) async throws -> String { + let fileData = try await storage.getFileDownload(bucketId: bucketId, fileId: fileId) + let base64Str = fileData.base64EncodedString() + return "data:\\(mimeType);base64,\\(base64Str)" + } + + /// Get file preview URL + func getPreviewUrl(fileId: String, width: Int? = nil, height: Int? = nil) -> String { + return storage.getFilePreview( + bucketId: bucketId, + fileId: fileId, + width: width, + height: height + ) + } + + /// Delete file + func delete(fileId: String) async throws { + _ = try await storage.deleteFile(bucketId: bucketId, fileId: fileId) + } + + /// Delete multiple files (for cleanup) + func deleteMany(fileIds: [String]) async { + for fileId in fileIds { + do { + try await delete(fileId: fileId) + } catch { + // Continue even if some files fail + } + } + } +} + +let fileStorage = FileStorageService() +\`\`\` +` +}; + +/** + * Get storage wrapper template for a specific SDK + * @param {string} sdk - SDK name (javascript, python, php, etc.) + * @returns {string} Storage wrapper template + */ +export function getStorageWrapperTemplate(sdk) { + // Map SDK names to template keys + /** @type {Record} */ + const sdkMap = { + javascript: 'javascript', + 'react-native': 'javascript', + python: 'python', + php: 'php', + go: 'go', + flutter: 'dart', + dart: 'dart', + apple: 'swift', + android: 'kotlin', + swift: 'swift', + kotlin: 'kotlin', + ruby: 'ruby', + dotnet: 'dotnet' + }; + + const templateKey = sdkMap[sdk] || 'javascript'; + /** @type {Record} */ + const templates = storageWrapperTemplates; + return templates[templateKey] || templates.javascript; +} + +// Legacy export for backwards compatibility +export const storageWrapperTemplate = storageWrapperTemplates.javascript; + +/** + * Functions implementation pattern (language-agnostic) + */ +export const functionsPattern = ` +## Functions Integration Pattern + +**Documentation:** [Functions Quick Start](https://appwrite.io/docs/products/functions/quick-start) + +### When to Use Appwrite Functions + +- **Scheduled tasks**: Cron jobs, periodic cleanup, report generation +- **Event-driven processing**: Triggered by database changes, file uploads, user events +- **Background jobs**: Long-running operations, email sending, data processing +- **Webhooks**: Third-party integrations, payment processing +- **Server-side logic**: Operations requiring elevated permissions + +### Execution Patterns + +| Pattern | Use Case | Method | +|---------|----------|--------| +| **Synchronous** | Immediate response needed | \`createExecution(functionId, body, async=false)\` | +| **Asynchronous** | Fire-and-forget, long tasks | \`createExecution(functionId, body, async=true)\` | +| **Scheduled** | Cron-based triggers | Configure in Console/appwrite.json | +| **Event-driven** | Database/storage triggers | Configure event subscriptions | + +### Implementation Steps + +1. **Initialize Functions service** with your SDK's client +2. **Call \`createExecution()\`** with: + - \`functionId\`: The function's unique ID + - \`body\`: JSON string of data to pass + - \`async\`: Boolean for sync/async execution + - \`path\`: Optional route path (default: "/") + - \`method\`: HTTP method (GET, POST, etc.) +3. **Handle response**: Check \`status\` field for success/failure +4. **Parse \`responseBody\`** as JSON for the result + +### Function Development Best Practices + +- Use [Appwrite Function Templates](https://github.com/appwrite/templates) as starting points +- Store secrets in function environment variables, not in code +- Return JSON responses for easy parsing +- Implement proper error handling and logging +- Use typed request/response structures for your language + +### Resources + +- [Develop Functions](https://appwrite.io/docs/products/functions/develop) +- [Execute Functions](https://appwrite.io/docs/products/functions/execute) +- [Function Runtimes](https://appwrite.io/docs/products/functions/runtimes) +- [Event Triggers](https://appwrite.io/docs/advanced/platform/events) +`; + +/** + * Messaging implementation pattern (language-agnostic) + */ +export const messagingPattern = ` +## Messaging Integration Pattern + +**Documentation:** [Messaging Overview](https://appwrite.io/docs/products/messaging) + +### Message Types + +| Type | Method | Use Case | +|------|--------|----------| +| **Email** | \`createEmail()\` | Transactional emails, notifications | +| **Push** | \`createPush()\` | Mobile/web push notifications | +| **SMS** | \`createSms()\` | Text messages, verification codes | + +### Targeting Options + +Messages can be sent to: +- **Users**: Array of user IDs (\`users\` parameter) +- **Targets**: Specific device/endpoint IDs (\`targets\` parameter) +- **Topics**: Broadcast to subscribers (\`topics\` parameter) + +### Implementation Steps + +1. **Initialize Messaging service** with your SDK's client (requires API key) +2. **Create message** using the appropriate method: + - \`createEmail(messageId, subject, content, topics, users, targets, ...)\` + - \`createPush(messageId, title, body, topics, users, targets, data)\` + - \`createSms(messageId, content, topics, users, targets)\` +3. **Handle delivery status** by checking the returned message object + +### Topic Subscriptions + +- **Subscribe**: \`createSubscriber(topicId, subscriberId, targetId)\` +- **Unsubscribe**: \`deleteSubscriber(topicId, subscriberId)\` + +### Provider Configuration Required + +| Channel | Providers | +|---------|-----------| +| **Email** | SMTP, Mailgun, SendGrid, Mailchimp | +| **Push** | FCM (Android), APNS (iOS) | +| **SMS** | Twilio, Vonage, Textmagic, Telesign | + +Configure providers in Appwrite Console → Messaging → Providers + +### Best Practices + +- Generate unique message IDs for each send +- Handle message failures gracefully +- Use topics for broadcast messages, targets for specific devices +- Store provider credentials securely in Console +- Implement retry logic for failed deliveries + +### Resources + +- [Send Email](https://appwrite.io/docs/products/messaging/send-email-messages) +- [Send Push](https://appwrite.io/docs/products/messaging/send-push-notifications) +- [Send SMS](https://appwrite.io/docs/products/messaging/send-sms-messages) +- [Topics](https://appwrite.io/docs/products/messaging/topics) +`; + +/** + * Realtime implementation pattern (language-agnostic) + */ +export const realtimePattern = ` +## Realtime Integration Pattern + +**Documentation:** [Realtime Overview](https://appwrite.io/docs/products/realtime) + +### Overview + +Realtime subscriptions allow your app to receive live updates when data changes. Subscriptions run on the **client side** using the client SDK. + +### Subscription Flow + +1. **Initialize client** with endpoint and project ID +2. **Subscribe to channel(s)** using \`client.subscribe(channels, callback)\` +3. **Handle events** in the callback (create, update, delete) +4. **Unsubscribe** when component unmounts or no longer needed + +### Realtime Channels Reference + +| Channel Pattern | Description | +|-----------------|-------------| +| \`databases.[DB_ID].tables.[TABLE_ID].rows\` | All rows in table | +| \`databases.[DB_ID].tables.[TABLE_ID].rows.[ROW_ID]\` | Specific row | +| \`buckets.[BUCKET_ID].files\` | All files in bucket | +| \`buckets.[BUCKET_ID].files.[FILE_ID]\` | Specific file | +| \`account\` | Current user's account changes | +| \`teams\` | Team membership changes | +| \`teams.[TEAM_ID]\` | Specific team changes | + +### Event Types + +The callback receives an event object with: +- \`events\`: Array of event strings (e.g., \`databases.*.tables.*.rows.*.create\`) +- \`payload\`: The row/file/account data that changed + +| Event | Triggered When | +|-------|----------------| +| \`*.create\` | New row/file created | +| \`*.update\` | Existing row/file updated | +| \`*.delete\` | Row/file deleted | + +### Implementation Pattern + +\`\`\` +// Pseudocode for any SDK +subscription = client.subscribe( + ["databases.DB_ID.tables.TABLE_ID.rows"], + function(event) { + if event.type contains "create": + add event.payload to local state + else if event.type contains "update": + update matching item in local state + else if event.type contains "delete": + remove matching item from local state + } +) + +// On cleanup/unmount: +subscription.close() // or unsubscribe() +\`\`\` + +### Best Practices + +- **Always unsubscribe** in cleanup/dispose functions +- Use **client SDK** for realtime (not server SDK) +- Filter events client-side if you only need specific event types +- Implement **reconnection logic** for dropped connections +- Consider **optimistic updates** combined with realtime for better UX +- Use **initial data from server** then enhance with realtime updates +- Handle the case where connection drops and reconnects + +### Resources + +- [Subscribe to Databases](https://appwrite.io/docs/products/realtime/subscribe-to-databases) +- [Subscribe to Storage](https://appwrite.io/docs/products/realtime/subscribe-to-storage) +- [Subscribe to Account](https://appwrite.io/docs/products/realtime/subscribe-to-account) +- [Channels Reference](https://appwrite.io/docs/products/realtime/channels) +`; + +/** + * Sites deployment notes (primarily documentation reference) + */ +export const sitesPattern = ` +## Sites Deployment Pattern + +Appwrite Sites is a hosting platform for static and SSR applications. Configuration is primarily done through the Appwrite Console or CLI. + +### Deployment Methods + +1. **Git Integration** (Recommended) + - Connect your repository in Appwrite Console + - Automatic deployments on push to configured branch + - See: [Deploy from Git](https://appwrite.io/docs/products/sites/deploy-from-git) + +2. **CLI Deployment** + - Use \`appwrite deploy\` command + - See: [Deploy from CLI](https://appwrite.io/docs/products/sites/deploy-from-cli) + +3. **Manual Upload** + - Upload built assets directly + - See: [Deploy Manually](https://appwrite.io/docs/products/sites/deploy-manually) + +### Environment Variables + +Configure in Appwrite Console under Site settings: +- \`APPWRITE_ENDPOINT\` - Your Appwrite endpoint +- \`APPWRITE_PROJECT_ID\` - Your project ID +- Build-time vs runtime variables as needed + +### Framework-Specific Notes + +- **Static Sites**: Build output uploaded as-is +- **SSR Apps**: Require Appwrite Functions runtime +- Check [Frameworks Documentation](https://appwrite.io/docs/products/sites/frameworks) for your specific framework + +### Rollbacks + +Appwrite Sites supports instant rollbacks: +- View deployment history in Console +- Click "Activate" on any previous deployment +- See: [Instant Rollbacks](https://appwrite.io/docs/products/sites/instant-rollbacks) +`; + +// ============================================================ +// SERVER SDK PATTERNS +// ============================================================ + +/** + * Python Server SDK implementation pattern + */ +export const pythonServerPattern = ` +## Python Implementation Pattern + +### Project Structure + +\`\`\` +project/ +├── app/ +│ ├── __init__.py +│ ├── main.py # Application entry point +│ ├── config.py # Configuration +│ └── services/ +│ ├── __init__.py +│ ├── appwrite.py # Appwrite client singleton +│ ├── db.py # Database wrapper +│ └── storage.py # Storage wrapper +├── requirements.txt +└── .env +\`\`\` + +### Appwrite Client Singleton + +\`\`\`python +# app/services/appwrite.py +import os +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.services.storage import Storage +from appwrite.services.users import Users + +client = Client() +client.set_endpoint(os.environ['APPWRITE_ENDPOINT']) +client.set_project(os.environ['APPWRITE_PROJECT_ID']) +client.set_key(os.environ['APPWRITE_API_KEY']) + +tables_db = TablesDB(client) +storage = Storage(client) +users = Users(client) + +DATABASE_ID = os.environ['APPWRITE_DATABASE_ID'] +BUCKET_ID = os.environ.get('APPWRITE_BUCKET_ID', '') +\`\`\` + +### Database Wrapper + +\`\`\`python +# app/services/db.py +from typing import TypeVar, Generic, Optional, List +from appwrite.query import Query +from appwrite.id import ID +from .appwrite import databases, DATABASE_ID + +T = TypeVar('T') + +class Table(Generic[T]): + def __init__(self, table_id: str): + self.table_id = table_id + + def create(self, data: dict) -> T: + """Create row with auto-generated ID""" + doc = tablesDB.create_row( + DATABASE_ID, + self.table_id, + ID.unique(), + data + ) + return doc + + def get(self, row_id: str) -> Optional[T]: + """Get row by ID""" + try: + return tablesDB.get_row( + DATABASE_ID, + self.table_id, + row_id + ) + except Exception: + return None + + def list_by_owner(self, user_id: str) -> List[T]: + """List rows by owner""" + result = tablesDB.list_rows( + DATABASE_ID, + self.table_id, + [ + Query.equal('createdBy', user_id), + Query.order_desc('$createdAt') + ] + ) + return result['rows'] + + def list_by_team(self, team_id: str) -> List[T]: + """List rows by team""" + result = tablesDB.list_rows( + DATABASE_ID, + self.table_id, + [ + Query.equal('teamId', team_id), + Query.order_desc('$createdAt') + ] + ) + return result['rows'] + + def update(self, row_id: str, data: dict) -> T: + """Update row (removes immutable columns)""" + safe_data = {k: v for k, v in data.items() + if k not in ('$id', '$createdAt', '$updatedAt', 'createdBy', 'teamId')} + return tablesDB.update_row( + DATABASE_ID, + self.table_id, + row_id, + safe_data + ) + + def delete(self, row_id: str) -> None: + """Delete row""" + tablesDB.delete_row(DATABASE_ID, self.table_id, row_id) + + +# Export typed tables +items = Table('items') +projects = Table('projects') +\`\`\` + +### Flask Integration Example + +\`\`\`python +# app/main.py +from flask import Flask, request, jsonify, g +from functools import wraps +from app.services import db +from app.services.appwrite import users + +app = Flask(__name__) + +def require_auth(f): + @wraps(f) + def decorated(*args, **kwargs): + session = request.cookies.get('session') + if not session: + return jsonify({'error': 'Unauthorized'}), 401 + + try: + # Verify session and get user + user = users.get(session) # Or use session client + g.user = user + except Exception: + return jsonify({'error': 'Invalid session'}), 401 + + return f(*args, **kwargs) + return decorated + +@app.route('/api/items', methods=['GET']) +@require_auth +def list_items(): + items_list = db.items.list_by_owner(g.user['$id']) + return jsonify({'items': items_list}) + +@app.route('/api/items', methods=['POST']) +@require_auth +def create_item(): + data = request.get_json() + + item = db.items.create({ + 'title': data['title'].strip(), + 'description': data.get('description'), + 'createdBy': g.user['$id'], + 'teamId': data.get('teamId') + }) + + return jsonify({'item': item}), 201 +\`\`\` + +### Environment Variables + +Required in \`.env\`: +\`\`\` +APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 +APPWRITE_PROJECT_ID=your-project-id +APPWRITE_API_KEY=your-api-key +APPWRITE_DATABASE_ID=your-database-id +APPWRITE_BUCKET_ID=your-bucket-id +\`\`\` +`; + +/** + * PHP Server SDK implementation pattern + */ +export const phpServerPattern = ` +## PHP Implementation Pattern + +### Project Structure + +\`\`\` +project/ +├── src/ +│ ├── Services/ +│ │ ├── AppwriteService.php # Client singleton +│ │ ├── DatabaseService.php # Database wrapper +│ │ └── StorageService.php # Storage wrapper +│ └── Middleware/ +│ └── AuthMiddleware.php +├── public/ +│ └── index.php +├── composer.json +└── .env +\`\`\` + +### Appwrite Client Service + +\`\`\`php +setEndpoint($_ENV['APPWRITE_ENDPOINT']) + ->setProject($_ENV['APPWRITE_PROJECT_ID']) + ->setKey($_ENV['APPWRITE_API_KEY']); + } + return self::$client; + } + + public static function getTablesDB(): TablesDB + { + if (self::$tablesDB === null) { + self::$tablesDB = new TablesDB(self::getClient()); + } + return self::$tablesDB; + } + + public static function getStorage(): Storage + { + if (self::$storage === null) { + self::$storage = new Storage(self::getClient()); + } + return self::$storage; + } +} +\`\`\` + +### Database Wrapper + +\`\`\`php +databaseId = $_ENV['APPWRITE_DATABASE_ID']; + $this->tablesDB = AppwriteService::getTablesDB(); + } + + public function create(string $tableId, array $data): array + { + return $this->tablesDB->createRow( + $this->databaseId, + $tableId, + ID::unique(), + $data + ); + } + + public function get(string $tableId, string $rowId): ?array + { + try { + return $this->tablesDB->getRow( + $this->databaseId, + $tableId, + $rowId + ); + } catch (\\Exception $e) { + return null; + } + } + + public function listByOwner(string $tableId, string $userId): array + { + $result = $this->tablesDB->listRows( + $this->databaseId, + $tableId, + [ + Query::equal('createdBy', [$userId]), + Query::orderDesc('\\$createdAt') + ] + ); + return $result['rows']; + } + + public function listByTeam(string $tableId, string $teamId): array + { + $result = $this->tablesDB->listRows( + $this->databaseId, + $tableId, + [ + Query::equal('teamId', [$teamId]), + Query::orderDesc('\\$createdAt') + ] + ); + return $result['rows']; + } + + public function update(string $tableId, string $rowId, array $data): array + { + // Remove immutable columns + unset($data['\\$id'], $data['\\$createdAt'], $data['\\$updatedAt'], + $data['createdBy'], $data['teamId']); + + return $this->tablesDB->updateRow( + $this->databaseId, + $tableId, + $rowId, + $data + ); + } + + public function delete(string $tableId, string $rowId): void + { + $this->tablesDB->deleteRow( + $this->databaseId, + $tableId, + $rowId + ); + } +} +\`\`\` + +### Laravel Integration Example + +\`\`\`php +db = $db; + } + + public function index(Request $request) + { + $userId = $request->user()->id; + $items = $this->db->listByOwner('items', $userId); + + return response()->json(['items' => $items]); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'title' => 'required|string|max:120', + 'teamId' => 'nullable|string' + ]); + + $item = $this->db->create('items', [ + 'title' => trim($validated['title']), + 'description' => null, + 'createdBy' => $request->user()->id, + 'teamId' => $validated['teamId'] ?? null + ]); + + return response()->json(['item' => $item], 201); + } + + public function destroy(Request $request, string $id) + { + $item = $this->db->get('items', $id); + + if (!$item || $item['createdBy'] !== $request->user()->id) { + return response()->json(['error' => 'Forbidden'], 403); + } + + $this->db->delete('items', $id); + + return response()->json(['success' => true]); + } +} +\`\`\` +`; + +/** + * Go Server SDK implementation pattern + */ +export const goServerPattern = ` +## Go Implementation Pattern + +### Project Structure + +\`\`\` +project/ +├── cmd/ +│ └── server/ +│ └── main.go +├── internal/ +│ ├── appwrite/ +│ │ ├── client.go # Client singleton +│ │ ├── db.go # Database wrapper +│ │ └── storage.go # Storage wrapper +│ ├── handlers/ +│ │ └── items.go +│ └── middleware/ +│ └── auth.go +├── go.mod +└── .env +\`\`\` + +### Appwrite Client + +\`\`\`go +// internal/appwrite/client.go +package appwrite + +import ( + "os" + "sync" + + "github.com/appwrite/sdk-for-go/appwrite" +) + +var ( + client *appwrite.Client + once sync.Once + + DatabaseID string + BucketID string +) + +func GetClient() *appwrite.Client { + once.Do(func() { + client = appwrite.NewClient() + client.SetEndpoint(os.Getenv("APPWRITE_ENDPOINT")) + client.SetProject(os.Getenv("APPWRITE_PROJECT_ID")) + client.SetKey(os.Getenv("APPWRITE_API_KEY")) + + DatabaseID = os.Getenv("APPWRITE_DATABASE_ID") + BucketID = os.Getenv("APPWRITE_BUCKET_ID") + }) + return client +} + +func GetTablesDB() *appwrite.TablesDB { + return appwrite.NewTablesDB(GetClient()) +} + +func GetStorage() *appwrite.Storage { + return appwrite.NewStorage(GetClient()) +} +\`\`\` + +### Database Wrapper + +\`\`\`go +// internal/appwrite/db.go +package appwrite + +import ( + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/id" + "github.com/appwrite/sdk-for-go/query" +) + +type Table struct { + ID string + db *appwrite.TablesDB +} + +func NewTable(tableID string) *Table { + return &Table{ + ID: tableID, + db: GetTablesDB(), + } +} + +func (c *Table) Create(data map[string]interface{}) (map[string]interface{}, error) { + doc, err := c.db.CreateRow( + DatabaseID, + c.ID, + id.Unique(), + data, + ) + if err != nil { + return nil, err + } + return doc.ToMap(), nil +} + +func (c *Table) Get(rowID string) (map[string]interface{}, error) { + doc, err := c.db.GetRow(DatabaseID, c.ID, rowID) + if err != nil { + return nil, err + } + return doc.ToMap(), nil +} + +func (c *Table) ListByOwner(userID string) ([]map[string]interface{}, error) { + result, err := c.db.ListRows( + DatabaseID, + c.ID, + c.db.WithListRowsQueries([]string{ + query.Equal("createdBy", userID), + query.OrderDesc("$createdAt"), + }), + ) + if err != nil { + return nil, err + } + + docs := make([]map[string]interface{}, len(result.Rows)) + for i, doc := range result.Rows { + docs[i] = doc.ToMap() + } + return docs, nil +} + +func (c *Table) ListByTeam(teamID string) ([]map[string]interface{}, error) { + result, err := c.db.ListRows( + DatabaseID, + c.ID, + c.db.WithListRowsQueries([]string{ + query.Equal("teamId", teamID), + query.OrderDesc("$createdAt"), + }), + ) + if err != nil { + return nil, err + } + + docs := make([]map[string]interface{}, len(result.Rows)) + for i, doc := range result.Rows { + docs[i] = doc.ToMap() + } + return docs, nil +} + +func (c *Table) Update(rowID string, data map[string]interface{}) (map[string]interface{}, error) { + // Remove immutable columns + delete(data, "$id") + delete(data, "$createdAt") + delete(data, "$updatedAt") + delete(data, "createdBy") + delete(data, "teamId") + + doc, err := c.db.UpdateRow(DatabaseID, c.ID, rowID, data) + if err != nil { + return nil, err + } + return doc.ToMap(), nil +} + +func (c *Table) Delete(rowID string) error { + _, err := c.db.DeleteRow(DatabaseID, c.ID, rowID) + return err +} + +// Exported tables +var ( + Items = NewTable("items") + Projects = NewTable("projects") +) +\`\`\` + +### HTTP Handler Example + +\`\`\`go +// internal/handlers/items.go +package handlers + +import ( + "encoding/json" + "net/http" + "strings" + + "yourproject/internal/appwrite" + "yourproject/internal/middleware" +) + +func ListItems(w http.ResponseWriter, r *http.Request) { + user := middleware.GetUser(r.Context()) + + items, err := appwrite.Items.ListByOwner(user.ID) + if err != nil { + http.Error(w, "Failed to fetch items", http.StatusInternalServerError) + return + } + + json.NewEncoder(w).Encode(map[string]interface{}{"items": items}) +} + +func CreateItem(w http.ResponseWriter, r *http.Request) { + user := middleware.GetUser(r.Context()) + + var input struct { + Title string \`json:"title"\` + TeamID *string \`json:"teamId"\` + } + + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + item, err := appwrite.Items.Create(map[string]interface{}{ + "title": strings.TrimSpace(input.Title), + "description": nil, + "createdBy": user.ID, + "teamId": input.TeamID, + }) + if err != nil { + http.Error(w, "Failed to create item", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]interface{}{"item": item}) +} +\`\`\` +`; + +/** + * Ruby Server SDK implementation pattern + */ +export const rubyServerPattern = ` +## Ruby Implementation Pattern + +### Project Structure + +\`\`\` +project/ +├── lib/ +│ ├── appwrite_client.rb # Client singleton +│ ├── database_service.rb # Database wrapper +│ └── storage_service.rb # Storage wrapper +├── app/ +│ └── controllers/ +│ └── items_controller.rb +├── Gemfile +└── .env +\`\`\` + +### Appwrite Client + +\`\`\`ruby +# lib/appwrite_client.rb +require 'appwrite' + +module AppwriteClient + class << self + def client + @client ||= begin + client = Appwrite::Client.new + client + .set_endpoint(ENV['APPWRITE_ENDPOINT']) + .set_project(ENV['APPWRITE_PROJECT_ID']) + .set_key(ENV['APPWRITE_API_KEY']) + client + end + end + + def tables_db + @tables_db ||= Appwrite::TablesDB.new(client) + end + + def storage + @storage ||= Appwrite::Storage.new(client) + end + + def database_id + ENV['APPWRITE_DATABASE_ID'] + end + + def bucket_id + ENV['APPWRITE_BUCKET_ID'] + end + end +end +\`\`\` + +### Database Service + +\`\`\`ruby +# lib/database_service.rb +require_relative 'appwrite_client' + +class DatabaseService + def initialize(table_id) + @table_id = table_id + @tables_db = AppwriteClient.tables_db + @database_id = AppwriteClient.database_id + end + + def create(data) + @tables_db.create_row( + database_id: @database_id, + table_id: @table_id, + row_id: Appwrite::ID.unique, + data: data + ) + end + + def get(row_id) + @tables_db.et_row( + database_id: @database_id, + table_id: @table_id, + row_id: row_id + ) + rescue Appwrite::Exception + nil + end + + def list_by_owner(user_id) + result = @tables_db.list_rows( + database_id: @database_id, + table_id: @table_id, + queries: [ + Appwrite::Query.equal('createdBy', [user_id]), + Appwrite::Query.order_desc('$createdAt') + ] + ) + result.rows + end + + def list_by_team(team_id) + result = @tables_db.list_rows( + database_id: @database_id, + table_id: @table_id, + queries: [ + Appwrite::Query.equal('teamId', [team_id]), + Appwrite::Query.order_desc('$createdAt') + ] + ) + result.rows + end + + def update(row_id, data) + # Remove immutable fields + safe_data = data.except('$id', '$createdAt', '$updatedAt', 'createdBy', 'teamId') + + @tables_db.update_row( + database_id: @database_id, + table_id: @table_id, + row_id: row_id, + data: safe_data + ) + end + + def delete(row_id) + @tables_db.delete_row( + database_id: @database_id, + table_id: @table_id, + row_id: row_id + ) + end +end + +# Convenience accessors +module DB + def self.items + @items ||= DatabaseService.new('items') + end + + def self.projects + @projects ||= DatabaseService.new('projects') + end +end +\`\`\` + +### Rails Controller Example + +\`\`\`ruby +# app/controllers/items_controller.rb +class ItemsController < ApplicationController + before_action :authenticate_user! + + def index + items = DB.items.list_by_owner(current_user.id) + render json: { items: items } + end + + def create + item = DB.items.create( + title: params[:title].strip, + description: nil, + createdBy: current_user.id, + teamId: params[:team_id] + ) + + render json: { item: item }, status: :created + end + + def destroy + item = DB.items.get(params[:id]) + + if item.nil? || item['createdBy'] != current_user.id + return render json: { error: 'Forbidden' }, status: :forbidden + end + + DB.items.delete(params[:id]) + render json: { success: true } + end +end +\`\`\` +`; + +/** + * .NET Server SDK implementation pattern + */ +export const dotnetServerPattern = ` +## .NET Implementation Pattern + +### Project Structure + +\`\`\` +Project/ +├── Services/ +│ ├── AppwriteService.cs # Client singleton +│ ├── DatabaseService.cs # Database wrapper +│ └── StorageService.cs # Storage wrapper +├── Controllers/ +│ └── ItemsController.cs +├── Models/ +│ └── Item.cs +├── Program.cs +├── appsettings.json +└── .env +\`\`\` + +### Appwrite Service + +\`\`\`csharp +// Services/AppwriteService.cs +using Appwrite; +using Appwrite.Services; + +public class AppwriteService +{ + private static Client? _client; + private static TablesDB? _tablesDB; + private static Storage? _storage; + + public static string DatabaseId => Environment.GetEnvironmentVariable("APPWRITE_DATABASE_ID")!; + public static string BucketId => Environment.GetEnvironmentVariable("APPWRITE_BUCKET_ID")!; + + public static Client GetClient() + { + if (_client == null) + { + _client = new Client() + .SetEndpoint(Environment.GetEnvironmentVariable("APPWRITE_ENDPOINT")!) + .SetProject(Environment.GetEnvironmentVariable("APPWRITE_PROJECT_ID")!) + .SetKey(Environment.GetEnvironmentVariable("APPWRITE_API_KEY")!); + } + return _client; + } + + public static TablesDB GetTablesDB() + { + return _tablesDB ??= new TablesDB(GetClient()); + } + + public static Storage GetStorage() + { + return _storage ??= new Storage(GetClient()); + } +} +\`\`\` + +### Database Service + +\`\`\`csharp +// Services/DatabaseService.cs +using Appwrite; +using Appwrite.Services; +using Appwrite.Models; +using System.Text.Json; + +public class DatabaseService where T : class +{ +private readonly string _tableId; + private readonly TablesDB _tablesDB; + + public DatabaseService(string tableId) + { + _tableId = tableId; + _tablesDB = AppwriteService.GetTablesDB(); + } + + public async Task CreateAsync(Dictionary data) + { + return await _tablesDB.CreateRow( + databaseId: AppwriteService.DatabaseId, + tableId: _tableId, + rowId: ID.Unique(), + data: data + ); + } + + public async Task GetAsync(string rowId) + { + try + { + return await _tablesDB.GetRow( + databaseId: AppwriteService.DatabaseId, + tableId: _tableId, + rowId: rowId + ); + } + catch (AppwriteException) + { + return null; + } + } + + public async Task> ListByOwnerAsync(string userId) + { + var result = await _tablesDB.ListRows( + databaseId: AppwriteService.DatabaseId, + tableId: _tableId, + queries: new List + { + Query.Equal("createdBy", new List { userId }), + Query.OrderDesc("$createdAt") + } + ); + return result.Rows; + } + + public async Task> ListByTeamAsync(string teamId) + { + var result = await _tablesDB.ListRows( + databaseId: AppwriteService.DatabaseId, + tableId: _tableId, + queries: new List + { + Query.Equal("teamId", new List { teamId }), + Query.OrderDesc("$createdAt") + } + ); + return result.Rows; + } + + public async Task UpdateAsync(string rowId, Dictionary data) + { + // Remove immutable columns + data.Remove("$id"); + data.Remove("$createdAt"); + data.Remove("$updatedAt"); + data.Remove("createdBy"); + data.Remove("teamId"); + + return await _tablesDB.UpdateRow( + databaseId: AppwriteService.DatabaseId, + tableId: _tableId, + rowId: rowId, + data: data + ); + } + + public async Task DeleteAsync(string rowId) + { + await _tablesDB.DeleteRow( + databaseId: AppwriteService.DatabaseId, + tableId: _tableId, + rowId: rowId + ); + } +} + +// Static accessors +public static class DB +{ + public static DatabaseService Items { get; } = new("items"); + public static DatabaseService Projects { get; } = new("projects"); +} +\`\`\` + +### Controller Example + +\`\`\`csharp +// Controllers/ItemsController.cs +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; + +[ApiController] +[Route("api/[controller]")] +[Authorize] +public class ItemsController : ControllerBase +{ + [HttpGet] + public async Task GetItems() + { + var userId = User.FindFirst("sub")?.Value!; + var items = await DB.Items.ListByOwnerAsync(userId); + return Ok(new { items }); + } + + [HttpPost] + public async Task CreateItem([FromBody] CreateItemRequest request) + { + var userId = User.FindFirst("sub")?.Value!; + + var item = await DB.Items.CreateAsync(new Dictionary + { + { "title", request.Title.Trim() }, + { "description", null! }, + { "createdBy", userId }, + { "teamId", request.TeamId ?? null! } + }); + + return CreatedAtAction(nameof(GetItems), new { item }); + } + + [HttpDelete("{id}")] + public async Task DeleteItem(string id) + { + var userId = User.FindFirst("sub")?.Value!; + var item = await DB.Items.GetAsync(id); + + if (item == null || item.Data["createdBy"].ToString() != userId) + { + return Forbid(); + } + + await DB.Items.DeleteAsync(id); + return Ok(new { success = true }); + } +} + +public record CreateItemRequest(string Title, string? TeamId); +\`\`\` +`; + +/** + * Dart Server SDK implementation pattern + */ +export const dartServerPattern = ` +## Dart Server Implementation Pattern + +### Project Structure + +\`\`\` +project/ +├── bin/ +│ └── server.dart # Entry point +├── lib/ +│ ├── services/ +│ │ ├── appwrite.dart # Client singleton +│ │ ├── database.dart # Database wrapper +│ │ └── storage.dart # Storage wrapper +│ └── handlers/ +│ └── items.dart +├── pubspec.yaml +└── .env +\`\`\` + +### Appwrite Client + +\`\`\`dart +// lib/services/appwrite.dart +import 'dart:io'; +import 'package:dart_appwrite/dart_appwrite.dart'; + +class AppwriteService { + static Client? _client; + static TablesDB? _tablesDB; + static Storage? _storage; + + static String get databaseId => Platform.environment['APPWRITE_DATABASE_ID']!; + static String get bucketId => Platform.environment['APPWRITE_BUCKET_ID']!; + + static Client get client { + _client ??= Client() + .setEndpoint(Platform.environment['APPWRITE_ENDPOINT']!) + .setProject(Platform.environment['APPWRITE_PROJECT_ID']!) + .setKey(Platform.environment['APPWRITE_API_KEY']!); + return _client!; + } + + static TablesDB get tablesDB { + _tablesDB ??= TablesDB(client); + return _tablesDB!; + } + + static Storage get storage { + _storage ??= Storage(client); + return _storage!; + } +} +\`\`\` + +### Database Wrapper + +\`\`\`dart +// lib/services/database.dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'appwrite.dart'; + +class Table { + final String tableId; + + Table(this.tableId); + + Future create(Map data) async { + return await AppwriteService.tablesDB.createRow( + databaseId: AppwriteService.databaseId, + tableId: tableId, + rowId: ID.unique(), + data: data, + ); + } + + Future get(String rowId) async { + try { + return await AppwriteService.tablesDB.getRow( + databaseId: AppwriteService.databaseId, + tableId: tableId, + rowId: rowId, + ); + } on AppwriteException { + return null; + } + } + + Future> listByOwner(String userId) async { + final result = await AppwriteService.tablesDB.listRows( + databaseId: AppwriteService.databaseId, + tableId: tableId, + queries: [ + Query.equal('createdBy', userId), + Query.orderDesc(r'$createdAt'), + ], + ); + return result.rows; + } + + Future> listByTeam(String teamId) async { + final result = await AppwriteService.tablesDB.ws( + databaseId: AppwriteService.databaseId, + tableId: tableId, + queries: [ + Query.equal('teamId', teamId), + Query.orderDesc(r'$createdAt'), + ], + ); + return result.rows; + } + + Future update(String rowId, Map data) async { + // Remove immutable columns + data.remove(r'$id'); + data.remove(r'$createdAt'); + data.remove(r'$updatedAt'); + data.remove('createdBy'); + data.remove('teamId'); + + return await AppwriteService.tablesDB.w( + databaseId: AppwriteService.databaseId, + tableId: tableId, + rowId: rowId, + data: data, + ); + } + + Future delete(String rowId) async { + await AppwriteService.tablesDB.eteRow( + databaseId: AppwriteService.databaseId, + tableId: tableId, + rowId: rowId, + ); + } +} + +// Exported tables +final items = Table('items'); +final projects = Table('projects'); +\`\`\` + +### Shelf Handler Example + +\`\`\`dart +// lib/handlers/items.dart +import 'dart:convert'; +import 'package:shelf/shelf.dart'; +import '../services/database.dart'; + +Response jsonResponse(Object data, {int statusCode = 200}) { + return Response( + statusCode, + body: jsonEncode(data), + headers: {'content-type': 'application/json'}, + ); +} + +Future listItems(Request request) async { + final userId = request.context['userId'] as String; + + final itemsList = await items.listByOwner(userId); + final itemsData = itemsList.map((doc) => doc.data).toList(); + + return jsonResponse({'items': itemsData}); +} + +Future createItem(Request request) async { + final userId = request.context['userId'] as String; + final body = jsonDecode(await request.readAsString()) as Map; + + final item = await items.create({ + 'title': (body['title'] as String).trim(), + 'description': null, + 'createdBy': userId, + 'teamId': body['teamId'], + }); + + return jsonResponse({'item': item.data}, statusCode: 201); +} + +Future deleteItem(Request request, String id) async { + final userId = request.context['userId'] as String; + + final item = await items.get(id); + if (item == null || item.data['createdBy'] != userId) { + return jsonResponse({'error': 'Forbidden'}, statusCode: 403); + } + + await items.delete(id); + return jsonResponse({'success': true}); +} +\`\`\` +`; + +/** + * Kotlin Server SDK implementation pattern + */ +export const kotlinServerPattern = ` +## Kotlin Server Implementation Pattern + +### Project Structure + +\`\`\` +project/ +├── src/main/kotlin/ +│ ├── Application.kt +│ ├── services/ +│ │ ├── AppwriteService.kt +│ │ ├── DatabaseService.kt +│ │ └── StorageService.kt +│ └── routes/ +│ └── ItemRoutes.kt +├── build.gradle.kts +└── .env +\`\`\` + +### Appwrite Service + +\`\`\`kotlin +// services/AppwriteService.kt +package com.example.services + +import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.services.Storage + +object AppwriteService { + val databaseId: String = System.getenv("APPWRITE_DATABASE_ID") + val bucketId: String = System.getenv("APPWRITE_BUCKET_ID") + + val client: Client by lazy { + Client() + .setEndpoint(System.getenv("APPWRITE_ENDPOINT")) + .setProject(System.getenv("APPWRITE_PROJECT_ID")) + .setKey(System.getenv("APPWRITE_API_KEY")) + } + + val tablesDB: TablesDB by lazy { TablesDB(client) } + val storage: Storage by lazy { Storage(client) } +} +\`\`\` + +### Database Service + +\`\`\`kotlin +// services/DatabaseService.kt +package com.example.services + +import io.appwrite.ID +import io.appwrite.Query +import io.appwrite.models.Row + +class Table(private val tableId: String) { + private val tablesDB = AppwriteService.tablesDB + private val databaseId = AppwriteService.databaseId + + suspend fun create(data: Map): Row> { + return tablesDB.createRow( + databaseId = databaseId, + tableId = tableId, + rowId = ID.unique(), + data = data + ) + } + + suspend fun get(rowId: String): Row>? { + return try { + tablesDB.etRow( + databaseId = databaseId, + tableId = tableId, + rowId = rowId + ) + } catch (e: Exception) { + null + } + } + + suspend fun listByOwner(userId: String): List>> { + val result = tablesDB.tRows( + databaseId = databaseId, + tableId = tableId, + queries = listOf( + Query.equal("createdBy", listOf(userId)), + Query.orderDesc("\$createdAt") + ) + ) + return result.rows + } + + suspend fun listByTeam(teamId: String): List>> { + val result = tablesDB.listRows( + databaseId = databaseId, + tableId = tableId, + queries = listOf( + Query.equal("teamId", listOf(teamId)), + Query.orderDesc("\$createdAt") + ) + ) + return result.rows + } + + suspend fun update(rowId: String, data: Map): Row> { + // Remove immutable columns + val safeData = data.filterKeys { + it !in listOf("\$id", "\$createdAt", "\$updatedAt", "createdBy", "teamId") + } + + return tablesDB.updateRow( + databaseId = databaseId, + tableId = tableId, + rowId = rowId, + data = safeData + ) + } + + suspend fun delete(rowId: String) { + tablesDB.deleteRow( + databaseId = databaseId, + tableId = tableId, + rowId = rowId + ) + } +} + +// Exported tables +object DB { + val items = Table("items") + val projects = Table("projects") +} +\`\`\` + +### Ktor Routes Example + +\`\`\`kotlin +// routes/ItemRoutes.kt +package com.example.routes + +import com.example.services.DB +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +fun Route.itemRoutes() { + authenticate { + route("/api/items") { + get { + val userId = call.principal()!!.id + val items = DB.items.listByOwner(userId) + call.respond(mapOf("items" to items.map { it.data })) + } + + post { + val userId = call.principal()!!.id + val request = call.receive() + + val item = DB.items.create(mapOf( + "title" to request.title.trim(), + "description" to null, + "createdBy" to userId, + "teamId" to request.teamId + )) + + call.respond(HttpStatusCode.Created, mapOf("item" to item.data)) + } + + delete("/{id}") { + val userId = call.principal()!!.id + val id = call.parameters["id"]!! + + val item = DB.items.get(id) + if (item == null || item.data["createdBy"] != userId) { + call.respond(HttpStatusCode.Forbidden, mapOf("error" to "Forbidden")) + return@delete + } + + DB.items.delete(id) + call.respond(mapOf("success" to true)) + } + } + } +} + +data class CreateItemRequest(val title: String, val teamId: String?) +\`\`\` +`; + +/** + * Swift Server SDK implementation pattern + */ +export const swiftServerPattern = ` +## Swift Server Implementation Pattern + +### Project Structure + +\`\`\` +Project/ +├── Sources/ +│ └── App/ +│ ├── main.swift +│ ├── Services/ +│ │ ├── AppwriteService.swift +│ │ ├── DatabaseService.swift +│ │ └── StorageService.swift +│ └── Controllers/ +│ └── ItemController.swift +├── Package.swift +└── .env +\`\`\` + +### Appwrite Service + +\`\`\`swift +// Services/AppwriteService.swift +import Appwrite +import Foundation + +class AppwriteService { + static let shared = AppwriteService() + + let client: Client + let tablesDB: TablesDB + let storage: Storage + + let databaseId: String + let bucketId: String + + private init() { + client = Client() + .setEndpoint(ProcessInfo.processInfo.environment["APPWRITE_ENDPOINT"]!) + .setProject(ProcessInfo.processInfo.environment["APPWRITE_PROJECT_ID"]!) +.setKey(ProcessInfo.processInfo.environment["APPWRITE_API_KEY"]!) + + tablesDB = TablesDB(client) + storage = Storage(client) + + databaseId = ProcessInfo.processInfo.environment["APPWRITE_DATABASE_ID"]! + bucketId = ProcessInfo.processInfo.environment["APPWRITE_BUCKET_ID"] ?? "" + } +} +\`\`\` + +### Database Service + +\`\`\`swift +// Services/DatabaseService.swift +import Appwrite +import Foundation + +class Table { + let tableId: String + private let tablesDB: TablesDB + private let databaseId: String + + init(_ tableId: String) { + self.tableId = tableId + self.tablesDB = AppwriteService.shared.tablesDB + self.databaseId = AppwriteService.shared.databaseId + } + + func create(data: [String: Any]) async throws -> Row<[String: AnyCodable]> { + return try await tablesDB.createRow( + databaseId: databaseId, + tableId: tableId, + rowId: ID.unique(), + data: data + ) + } + + func get(rowId: String) async throws -> Row<[String: AnyCodable]>? { + do { + return try await tablesDB.getRow( + databaseId: databaseId, + tableId: tableId, + rowId: rowId + ) + } catch { + return nil + } + } + + func listByOwner(userId: String) async throws -> [Row<[String: AnyCodable]>] { + let result = try await tablesDB.listRows( + databaseId: databaseId, + tableId: tableId, + queries: [ + Query.equal("createdBy", value: userId), + Query.orderDesc("$createdAt") + ] + ) + return result.rows + } + + func listByTeam(teamId: String) async throws -> [Row<[String: AnyCodable]>] { + let result = try await tablesDB.listRows( + databaseId: databaseId, + tableId: tableId, + queries: [ + Query.equal("teamId", value: teamId), + Query.orderDesc("$createdAt") + ] + ) + return result.rows + } + + func update(rowId: String, data: [String: Any]) async throws -> Row<[String: AnyCodable]> { + // Remove immutable columns + var safeData = data + safeData.removeValue(forKey: "$id") + safeData.removeValue(forKey: "$createdAt") + safeData.removeValue(forKey: "$updatedAt") + safeData.removeValue(forKey: "createdBy") + safeData.removeValue(forKey: "teamId") + + return try await tablesDB.updateRow( + databaseId: databaseId, + tableId: tableId, + rowId: rowId, + data: safeData + ) + } + + func delete(rowId: String) async throws { + _ = try await tablesDB.deleteRow( + databaseId: databaseId, + tableId: tableId, + rowId: rowId + ) + } +} + +// Exported tables +struct DB { + static let items = Table("items") + static let projects = Table("projects") +} +\`\`\` + +### Vapor Controller Example + +\`\`\`swift +// Controllers/ItemController.swift +import Vapor + +struct ItemController: RouteCollection { + func boot(routes: RoutesBuilder) throws { + let items = routes.grouped("api", "items") + + let protected = items.grouped(AuthMiddleware()) + protected.get(use: index) + protected.post(use: create) + protected.delete(":id", use: delete) + } + + func index(req: Request) async throws -> Response { + let user = try req.auth.require(User.self) + let items = try await DB.items.listByOwner(userId: user.id) + + return try Response( + status: .ok, + body: .init(data: JSONEncoder().encode(["items": items.map { $0.data }])) + ) + } + + func create(req: Request) async throws -> Response { + let user = try req.auth.require(User.self) + let input = try req.content.decode(CreateItemRequest.self) + + let item = try await DB.items.create(data: [ + "title": input.title.trimmingCharacters(in: .whitespaces), + "description": nil as Any, + "createdBy": user.id, + "teamId": input.teamId as Any + ]) + + return try Response( + status: .created, + body: .init(data: JSONEncoder().encode(["item": item.data])) + ) + } + + func delete(req: Request) async throws -> Response { + let user = try req.auth.require(User.self) + let id = req.parameters.get("id")! + + guard let item = try await DB.items.get(rowId: id), + item.data["createdBy"]?.value as? String == user.id else { + throw Abort(.forbidden) + } + + try await DB.items.delete(rowId: id) + return Response(status: .ok) + } +} + +struct CreateItemRequest: Content { + let title: String + let teamId: String? +} +\`\`\` +`; + +// ============================================================ +// MOBILE/CLIENT SDK PATTERNS +// ============================================================ + +/** + * Android SDK implementation pattern + */ +export const androidPattern = ` +## Android Implementation Pattern + +### Project Structure + +\`\`\` +app/ +├── src/main/java/com/example/app/ +│ ├── AppwriteService.kt # Client singleton +│ ├── data/ +│ │ ├── repository/ +│ │ │ ├── ItemRepository.kt # Data repository +│ │ │ └── AuthRepository.kt +│ │ └── model/ +│ │ └── Item.kt +│ ├── ui/ +│ │ └── items/ +│ │ ├── ItemsViewModel.kt +│ │ └── ItemsScreen.kt +│ └── di/ +│ └── AppModule.kt # Dependency injection +├── build.gradle.kts +└── local.properties +\`\`\` + +### Appwrite Client + +\`\`\`kotlin +// AppwriteService.kt +package com.example.app + +import android.content.Context +import io.appwrite.Client +import io.appwrite.services.Account +import io.appwrite.services.TablesDB +import io.appwrite.services.Storage +import io.appwrite.services.Realtime + +object AppwriteService { + private lateinit var client: Client + + lateinit var account: Account + lateinit var tablesDB: TablesDB + lateinit var storage: Storage + lateinit var realtime: Realtime + + const val DATABASE_ID = "your-database-id" + const val BUCKET_ID = "your-bucket-id" + + fun init(context: Context) { + client = Client(context) + .setEndpoint(BuildConfig.APPWRITE_ENDPOINT) + .setProject(BuildConfig.APPWRITE_PROJECT_ID) + + account = Account(client) + tablesDB = TablesDB(client) + storage = Storage(client) + realtime = Realtime(client) + } +} +\`\`\` + +### Repository Pattern + +\`\`\`kotlin +// data/repository/ItemRepository.kt +package com.example.app.data.repository + +import io.appwrite.ID +import io.appwrite.Query +import io.appwrite.models.Row +import com.example.app.AppwriteService +import com.example.app.data.model.Item +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +class ItemRepository { + private val tablesDB = AppwriteService.tablesDB + private val tableId = "items" + + suspend fun create(title: String, userId: String, teamId: String? = null): Item { + val doc = tablesDB.createRow( + databaseId = AppwriteService.DATABASE_ID, + tableId = tableId, + rowId = ID.unique(), + data = mapOf( + "title" to title.trim(), + "description" to null, + "createdBy" to userId, + "teamId" to teamId + ) + ) + return doc.toItem() + } + + suspend fun listByOwner(userId: String): List { + val result = tablesDB.listRows( + databaseId = AppwriteService.DATABASE_ID, + tableId = tableId, + queries = listOf( + Query.equal("createdBy", listOf(userId)), + Query.orderDesc("\$createdAt"), + Query.limit(100) + ) + ) + return result.rows.map { it.toItem() } + } + + suspend fun get(id: String): Item? { + return try { + tablesDB.getRow( + databaseId = AppwriteService.DATABASE_ID, + tableId = tableId, + rowId = id + ).toItem() + } catch (e: Exception) { + null + } + } + + suspend fun update(id: String, title: String): Item { + val doc = tablesDB.updateRow( + databaseId = AppwriteService.DATABASE_ID, + tableId = tableId, + rowId = id, + data = mapOf("title" to title.trim()) + ) + return doc.toItem() + } + + suspend fun delete(id: String) { + tablesDB.deleteRow( + databaseId = AppwriteService.DATABASE_ID, + tableId = tableId, + rowId = id + ) + } + + private fun Row>.toItem(): Item { + return Item( + id = this.id, + title = this.data["title"] as String, + description = this.data["description"] as? String, + createdBy = this.data["createdBy"] as String, + teamId = this.data["teamId"] as? String, + createdAt = this.createdAt, + updatedAt = this.updatedAt + ) + } +} +\`\`\` + +### ViewModel Pattern + +\`\`\`kotlin +// ui/items/ItemsViewModel.kt +package com.example.app.ui.items + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.app.data.repository.ItemRepository +import com.example.app.data.repository.AuthRepository +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class ItemsViewModel( + private val itemRepository: ItemRepository, + private val authRepository: AuthRepository +) : ViewModel() { + + private val _items = MutableStateFlow>(emptyList()) + val items: StateFlow> = _items + + private val _isLoading = MutableStateFlow(false) + val isLoading: StateFlow = _isLoading + + fun loadItems() { + viewModelScope.launch { + _isLoading.value = true + try { + val user = authRepository.getCurrentUser() + _items.value = itemRepository.listByOwner(user.id) + } catch (e: Exception) { + // Handle error + } finally { + _isLoading.value = false + } + } + } + + fun createItem(title: String, teamId: String? = null) { + viewModelScope.launch { + try { + val user = authRepository.getCurrentUser() + itemRepository.create(title, user.id, teamId) + loadItems() // Refresh list + } catch (e: Exception) { + // Handle error + } + } + } + + fun deleteItem(id: String) { + viewModelScope.launch { + try { + itemRepository.delete(id) + loadItems() + } catch (e: Exception) { + // Handle error + } + } + } +} +\`\`\` + +### Realtime Subscription + +\`\`\`kotlin +// Subscribe to table changes +fun subscribeToItems(onUpdate: (List) -> Unit): RealtimeSubscription { + return AppwriteService.realtime.subscribe( + "databases.\${AppwriteService.DATABASE_ID}.tables.items.rows" + ) { response -> + // Refresh items on any change + viewModelScope.launch { + loadItems() + } + } +} + +// Remember to unsubscribe in onCleared() +override fun onCleared() { + super.onCleared() + subscription?.close() +} +\`\`\` + +### Best Practices + +- Initialize AppwriteService in Application class +- Use Repository pattern for data access +- Use ViewModel for UI state management +- Handle offline/network errors gracefully +- Store session in encrypted SharedPreferences +- Never hardcode API keys in source code +`; + +/** + * Apple (iOS/macOS) SDK implementation pattern + */ +export const applePattern = ` +## Apple (iOS/macOS) Implementation Pattern + +### Project Structure + +\`\`\` +App/ +├── AppwriteService.swift # Client singleton +├── Repositories/ +│ ├── ItemRepository.swift +│ └── AuthRepository.swift +├── Models/ +│ └── Item.swift +├── ViewModels/ +│ └── ItemsViewModel.swift +├── Views/ +│ └── ItemsView.swift +└── App.swift +\`\`\` + +### Appwrite Client + +\`\`\`swift +// AppwriteService.swift +import Appwrite +import Foundation + +class AppwriteService { + static let shared = AppwriteService() + + let client: Client + let account: Account + let tablesDB: TablesDB + let storage: Storage + let realtime: Realtime + + static let databaseId = "your-database-id" + static let bucketId = "your-bucket-id" + + private init() { + client = Client() + .setEndpoint(Bundle.main.infoDictionary?["APPWRITE_ENDPOINT"] as? String ?? "") + .setProject(Bundle.main.infoDictionary?["APPWRITE_PROJECT_ID"] as? String ?? "") + + account = Account(client) + tablesDB = TablesDB(client) + storage = Storage(client) + realtime = Realtime(client) + } +} +\`\`\` + +### Model + +\`\`\`swift +// Models/Item.swift +import Foundation + +struct Item: Identifiable, Codable { + let id: String + var title: String + var description: String? + let createdBy: String + let teamId: String? + let createdAt: String + let updatedAt: String + + enum CodingKeys: String, CodingKey { + case id = "$id" + case title, description, createdBy, teamId + case createdAt = "$createdAt" + case updatedAt = "$updatedAt" + } +} +\`\`\` + +### Repository Pattern + +\`\`\`swift +// Repositories/ItemRepository.swift +import Appwrite +import Foundation + +class ItemRepository { + private let tablesDB = AppwriteService.shared.tablesDB + private let tableId = "items" + + func create(title: String, userId: String, teamId: String? = nil) async throws -> Item { + let doc = try await tablesDB.createRow( + databaseId: AppwriteService.databaseId, + tableId: tableId, + rowId: ID.unique(), + data: [ + "title": title.trimmingCharacters(in: .whitespaces), + "description": nil as Any, + "createdBy": userId, + "teamId": teamId as Any + ] + ) + return try JSONDecoder().decode(Item.self, from: JSONSerialization.data(withJSONObject: doc.data)) + } + + func listByOwner(userId: String) async throws -> [Item] { + let result = try await tablesDB.listRows( + databaseId: AppwriteService.databaseId, + tableId: tableId, + queries: [ + Query.equal("createdBy", value: userId), + Query.orderDesc("$createdAt"), + Query.limit(100) + ] + ) + + return try result.rows.map { doc in + try JSONDecoder().decode(Item.self, from: JSONSerialization.data(withJSONObject: doc.data)) + } + } + + func get(id: String) async throws -> Item? { + do { + let doc = try await tablesDB.getRow( + databaseId: AppwriteService.databaseId, + tableId: tableId, + rowId: id + ) + return try JSONDecoder().decode(Item.self, from: JSONSerialization.data(withJSONObject: doc.data)) + } catch { + return nil + } + } + + func update(id: String, title: String) async throws -> Item { + let doc = try await tablesDB.updateRow( + databaseId: AppwriteService.databaseId, + tableId: tableId, + rowId: id, + data: ["title": title.trimmingCharacters(in: .whitespaces)] + ) + return try JSONDecoder().decode(Item.self, from: JSONSerialization.data(withJSONObject: doc.data)) + } + + func delete(id: String) async throws { + _ = try await tablesDB.deleteRow( + databaseId: AppwriteService.databaseId, + tableId: tableId, + rowId: id + ) + } +} +\`\`\` + +### ViewModel (ObservableObject) + +\`\`\`swift +// ViewModels/ItemsViewModel.swift +import SwiftUI + +@MainActor +class ItemsViewModel: ObservableObject { + @Published var items: [Item] = [] + @Published var isLoading = false + @Published var error: Error? + + private let itemRepository = ItemRepository() + private let authRepository = AuthRepository() + private var realtimeSubscription: RealtimeSubscription? + + func loadItems() async { + isLoading = true + error = nil + + do { + let user = try await authRepository.getCurrentUser() + items = try await itemRepository.listByOwner(userId: user.id) + } catch { + self.error = error + } + + isLoading = false + } + + func createItem(title: String, teamId: String? = nil) async { + do { + let user = try await authRepository.getCurrentUser() + _ = try await itemRepository.create(title: title, userId: user.id, teamId: teamId) + await loadItems() + } catch { + self.error = error + } + } + + func deleteItem(id: String) async { + do { + try await itemRepository.delete(id: id) + await loadItems() + } catch { + self.error = error + } + } + + func subscribeToChanges() { + realtimeSubscription = AppwriteService.shared.realtime.subscribe( + channels: ["databases.\\(AppwriteService.databaseId).tables.items.rows"] + ) { [weak self] event in + Task { @MainActor in + await self?.loadItems() + } + } + } + + func unsubscribe() { + realtimeSubscription?.close() + } +} +\`\`\` + +### SwiftUI View + +\`\`\`swift +// Views/ItemsView.swift +import SwiftUI + +struct ItemsView: View { + @StateObject private var viewModel = ItemsViewModel() + @State private var newItemTitle = "" + + var body: some View { + NavigationView { + List { + ForEach(viewModel.items) { item in + Text(item.title) + } + .onDelete { indexSet in + for index in indexSet { + Task { + await viewModel.deleteItem(id: viewModel.items[index].id) + } + } + } + } + .navigationTitle("Items") + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button("Add") { + Task { + await viewModel.createItem(title: newItemTitle) + } + } + } + } + .refreshable { + await viewModel.loadItems() + } + } + .task { + await viewModel.loadItems() + viewModel.subscribeToChanges() + } + .onDisappear { + viewModel.unsubscribe() + } + } +} +\`\`\` + +### Best Practices + +- Use @MainActor for UI-related async operations +- Store configuration in Info.plist or xcconfig files +- Use Keychain for storing session tokens +- Handle network errors with retry logic +- Unsubscribe from realtime when view disappears +`; + +/** + * Flutter SDK implementation pattern + */ +export const flutterPattern = ` +## Flutter Implementation Pattern + +### Project Structure + +\`\`\` +lib/ +├── main.dart +├── services/ +│ └── appwrite_service.dart +├── repositories/ +│ ├── item_repository.dart +│ └── auth_repository.dart +├── models/ +│ └── item.dart +├── providers/ +│ └── items_provider.dart +└── screens/ + └── items_screen.dart +\`\`\` + +### Appwrite Service + +\`\`\`dart +// services/appwrite_service.dart +import 'package:appwrite/appwrite.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class AppwriteService { + static final AppwriteService _instance = AppwriteService._internal(); + factory AppwriteService() => _instance; + + late final Client client; + late final Account account; + late final TablesDB tablesDB; + late final Storage storage; + late final Realtime realtime; + + static String get databaseId => dotenv.env['APPWRITE_DATABASE_ID']!; + static String get bucketId => dotenv.env['APPWRITE_BUCKET_ID'] ?? ''; + + AppwriteService._internal() { + client = Client() + .setEndpoint(dotenv.env['APPWRITE_ENDPOINT']!) + .setProject(dotenv.env['APPWRITE_PROJECT_ID']!); + + account = Account(client); + tablesDB = TablesDB(client); + storage = Storage(client); + realtime = Realtime(client); + } +} +\`\`\` + +### Model + +\`\`\`dart +// models/item.dart +class Item { + final String id; + String title; + String? description; + final String createdBy; + final String? teamId; + final DateTime createdAt; + final DateTime updatedAt; + + Item({ + required this.id, + required this.title, + this.description, + required this.createdBy, + this.teamId, + required this.createdAt, + required this.updatedAt, + }); + + factory Item.fromRow(Row doc) { + return Item( + id: doc.$id, + title: doc.data['title'], + description: doc.data['description'], + createdBy: doc.data['createdBy'], + teamId: doc.data['teamId'], + createdAt: DateTime.parse(doc.$createdAt), + updatedAt: DateTime.parse(doc.$updatedAt), + ); + } +} +\`\`\` + +### Repository + +\`\`\`dart +// repositories/item_repository.dart +import 'package:appwrite/appwrite.dart'; +import '../services/appwrite_service.dart'; +import '../models/item.dart'; + +class ItemRepository { + final _tablesDB = AppwriteService().tablesDB; + final _tableId = 'items'; + + Future create({ + required String title, + required String userId, + String? teamId, + }) async { + final doc = await _tablesDB.createRow( + databaseId: AppwriteService.databaseId, + tableId: _tableId, + rowId: ID.unique(), + data: { + 'title': title.trim(), + 'description': null, + 'createdBy': userId, + 'teamId': teamId, + }, + ); + return Item.fromRow(doc); + } + + Future> listByOwner(String userId) async { + final result = await _tablesDB.listRows( + databaseId: AppwriteService.databaseId, + tableId: _tableId, + queries: [ + Query.equal('createdBy', userId), + Query.orderDesc(r'$createdAt'), + Query.limit(100), + ], + ); + return result.rows.map(Item.fromRow).toList(); + } + + Future get(String id) async { + try { + final doc = await _tablesDB.getRow( + databaseId: AppwriteService.databaseId, + tableId: _tableId, + rowId: id, + ); + return Item.fromRow(doc); + } on AppwriteException { + return null; + } + } + + Future update(String id, {required String title}) async { + final doc = await _tablesDB.updateRow( + databaseId: AppwriteService.databaseId, + tableId: _tableId, + rowId: id, + data: {'title': title.trim()}, + ); + return Item.fromRow(doc); + } + + Future delete(String id) async { + await _tablesDB.deleteRow( + databaseId: AppwriteService.databaseId, + tableId: _tableId, + rowId: id, + ); + } +} +\`\`\` + +### Provider (Riverpod Example) + +\`\`\`dart +// providers/items_provider.dart +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../repositories/item_repository.dart'; +import '../repositories/auth_repository.dart'; +import '../models/item.dart'; + +final itemRepositoryProvider = Provider((ref) => ItemRepository()); +final authRepositoryProvider = Provider((ref) => AuthRepository()); + +final itemsProvider = StateNotifierProvider>>((ref) { + return ItemsNotifier( + ref.watch(itemRepositoryProvider), + ref.watch(authRepositoryProvider), + ); +}); + +class ItemsNotifier extends StateNotifier>> { + final ItemRepository _itemRepo; + final AuthRepository _authRepo; + RealtimeSubscription? _subscription; + + ItemsNotifier(this._itemRepo, this._authRepo) : super(const AsyncValue.loading()); + + Future loadItems() async { + state = const AsyncValue.loading(); + try { + final user = await _authRepo.getCurrentUser(); + final items = await _itemRepo.listByOwner(user.$id); + state = AsyncValue.data(items); + } catch (e, st) { + state = AsyncValue.error(e, st); + } + } + + Future createItem(String title, {String? teamId}) async { + try { + final user = await _authRepo.getCurrentUser(); + await _itemRepo.create(title: title, userId: user.$id, teamId: teamId); + await loadItems(); + } catch (e) { + // Handle error + } + } + + Future deleteItem(String id) async { + try { + await _itemRepo.delete(id); + await loadItems(); + } catch (e) { + // Handle error + } + } + + void subscribeToChanges() { + final channel = 'databases.\${AppwriteService.databaseId}.tables.items.rows'; + _subscription = AppwriteService().realtime.subscribe([channel]); + _subscription!.stream.listen((event) => loadItems()); + } + + void unsubscribe() { + _subscription?.close(); + } + + @override + void dispose() { + unsubscribe(); + super.dispose(); + } +} +\`\`\` + +### Screen Widget + +\`\`\`dart +// screens/items_screen.dart +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../providers/items_provider.dart'; + +class ItemsScreen extends ConsumerStatefulWidget { + const ItemsScreen({super.key}); + + @override + ConsumerState createState() => _ItemsScreenState(); +} + +class _ItemsScreenState extends ConsumerState { + @override + void initState() { + super.initState(); + ref.read(itemsProvider.notifier).loadItems(); + ref.read(itemsProvider.notifier).subscribeToChanges(); + } + + @override + void dispose() { + ref.read(itemsProvider.notifier).unsubscribe(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final itemsAsync = ref.watch(itemsProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Items')), + body: itemsAsync.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (err, _) => Center(child: Text('Error: \$err')), + data: (items) => ListView.builder( + itemCount: items.length, + itemBuilder: (context, index) { + final item = items[index]; + return ListTile( + title: Text(item.title), + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () => ref.read(itemsProvider.notifier).deleteItem(item.id), + ), + ); + }, + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => _showCreateDialog(context), + child: const Icon(Icons.add), + ), + ); + } + + void _showCreateDialog(BuildContext context) { + // Show dialog to create new item + } +} +\`\`\` + +### Best Practices + +- Initialize AppwriteService before runApp() +- Use flutter_dotenv for environment variables +- Use state management (Riverpod, Provider, Bloc) +- Handle loading/error states in UI +- Dispose realtime subscriptions properly +- Use offline-first approach with local caching +`; + +/** + * React Native SDK implementation pattern + */ +export const reactNativePattern = ` +## React Native Implementation Pattern + +### Project Structure + +\`\`\` +src/ +├── services/ +│ └── appwrite.ts +├── repositories/ +│ ├── itemRepository.ts +│ └── authRepository.ts +├── hooks/ +│ ├── useItems.ts +│ └── useAuth.ts +├── types/ +│ └── index.ts +└── screens/ + └── ItemsScreen.tsx +\`\`\` + +### Appwrite Service + +\`\`\`typescript +// services/appwrite.ts +import { Client, Account, TablesDB, Storage } from 'react-native-appwrite' +import Config from 'react-native-config' + +const client = new Client() + .setEndpoint(Config.APPWRITE_ENDPOINT!) + .setProject(Config.APPWRITE_PROJECT_ID!) + +export const account = new Account(client) +export const tablesDB = new TablesDB(client) +export const storage = new Storage(client) +export { client } + +export const DATABASE_ID = Config.APPWRITE_DATABASE_ID! +export const BUCKET_ID = Config.APPWRITE_BUCKET_ID ?? '' +\`\`\` + +### Types + +\`\`\`typescript +// types/index.ts +export interface Item { + $id: string + title: string + description: string | null + createdBy: string + teamId: string | null + $createdAt: string + $updatedAt: string +} + +export interface User { + $id: string + email: string + name: string +} +\`\`\` + +### Repository + +\`\`\`typescript +// repositories/itemRepository.ts +import { ID, Query } from 'react-native-appwrite' +import { databases, DATABASE_ID } from '../services/appwrite' +import type { Item } from '../types' + +const TABLE_ID = 'items' + +export const itemRepository = { + async create(data: { title: string; userId: string; teamId?: string }): Promise { + return tablesDB.createRow( + DATABASE_ID, + TABLE_ID, + ID.unique(), + { + title: data.title.trim(), + description: null, + createdBy: data.userId, + teamId: data.teamId ?? null, + } + ) + }, + + async listByOwner(userId: string): Promise { + const result = await tablesDB.listRows( + DATABASE_ID, + TABLE_ID, + [ + Query.equal('createdBy', userId), + Query.orderDesc('$createdAt'), + Query.limit(100), + ] + ) + return result.rows + }, + + async get(id: string): Promise { + try { + return await tablesDB.getRow(DATABASE_ID, TABLE_ID, id) + } catch { + return null + } + }, + + async update(id: string, data: { title?: string }): Promise { + const updateData: Record = {} + if (data.title) updateData.title = data.title.trim() + + return tablesDB.updateRow(DATABASE_ID, TABLE_ID, id, updateData) + }, + + async delete(id: string): Promise { + await tablesDB.deleteRow(DATABASE_ID, TABLE_ID, id) + }, +} +\`\`\` + +### Custom Hook + +\`\`\`typescript +// hooks/useItems.ts +import { useState, useEffect, useCallback } from 'react' +import { RealtimeResponseEvent } from 'react-native-appwrite' +import { client, DATABASE_ID } from '../services/appwrite' +import { itemRepository } from '../repositories/itemRepository' +import { useAuth } from './useAuth' +import type { Item } from '../types' + +export function useItems() { + const [items, setItems] = useState([]) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + const { user } = useAuth() + + const loadItems = useCallback(async () => { + if (!user) return + + setIsLoading(true) + setError(null) + + try { + const data = await itemRepository.listByOwner(user.$id) + setItems(data) + } catch (e) { + setError(e as Error) + } finally { + setIsLoading(false) + } + }, [user]) + + const createItem = useCallback(async (title: string, teamId?: string) => { + if (!user) throw new Error('Not authenticated') + + await itemRepository.create({ title, userId: user.$id, teamId }) + await loadItems() + }, [user, loadItems]) + + const deleteItem = useCallback(async (id: string) => { + await itemRepository.delete(id) + await loadItems() + }, [loadItems]) + + // Initial load + useEffect(() => { + loadItems() + }, [loadItems]) + + // Realtime subscription + useEffect(() => { + if (!user) return + + const channel = \`databases.\${DATABASE_ID}.tables.items.rows\` + const unsubscribe = client.subscribe(channel, (response: RealtimeResponseEvent) => { + const event = response.events[0] + + if (event.includes('.create')) { + setItems(prev => [response.payload, ...prev]) + } else if (event.includes('.update')) { + setItems(prev => prev.map(item => + item.$id === response.payload.$id ? response.payload : item + )) + } else if (event.includes('.delete')) { + setItems(prev => prev.filter(item => item.$id !== response.payload.$id)) + } + }) + + return () => unsubscribe() + }, [user]) + + return { + items, + isLoading, + error, + refresh: loadItems, + createItem, + deleteItem, + } +} +\`\`\` + +### Screen Component + +\`\`\`typescript +// screens/ItemsScreen.tsx +import React, { useState } from 'react' +import { + View, + Text, + FlatList, + TextInput, + TouchableOpacity, + ActivityIndicator, + RefreshControl, + StyleSheet, +} from 'react-native' +import { useItems } from '../hooks/useItems' + +export function ItemsScreen() { + const { items, isLoading, error, refresh, createItem, deleteItem } = useItems() + const [newTitle, setNewTitle] = useState('') + + const handleCreate = async () => { + if (!newTitle.trim()) return + await createItem(newTitle) + setNewTitle('') + } + + if (error) { + return ( + + Error: {error.message} + + Retry + + + ) + } + + return ( + + + + + Add + + + + item.$id} + renderItem={({ item }) => ( + + {item.title} + deleteItem(item.$id)}> + Delete + + + )} + refreshControl={ + + } + ListEmptyComponent={ + !isLoading ? No items yet : null + } + /> + + ) +} + +const styles = StyleSheet.create({ + container: { flex: 1, padding: 16 }, + center: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + inputRow: { flexDirection: 'row', marginBottom: 16 }, + input: { flex: 1, borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 12 }, + addButton: { marginLeft: 8, backgroundColor: '#007AFF', borderRadius: 8, padding: 12 }, + addButtonText: { color: 'white', fontWeight: 'bold' }, + itemRow: { flexDirection: 'row', justifyContent: 'space-between', padding: 16, borderBottomWidth: 1, borderBottomColor: '#eee' }, + itemTitle: { fontSize: 16 }, + deleteText: { color: 'red' }, + empty: { textAlign: 'center', marginTop: 32, color: '#999' }, +}) +\`\`\` + +### Best Practices + +- Use react-native-config for environment variables +- Store session in SecureStore or encrypted storage +- Handle app state changes (background/foreground) +- Implement proper error boundaries +- Use custom hooks for data fetching logic +- Unsubscribe from realtime in cleanup functions +- Handle offline state gracefully +`; + +/** + * Get implementation pattern for a specific framework + * @param {string} framework - Framework name + * @returns {string} Implementation pattern + */ +export function getImplementationPattern(framework) { + /** @type {Record} */ + const patterns = { + // JS SSR frameworks + tanstack: tanstackStartPattern, + nextjs: nextjsPattern, + svelte: sveltekitPattern, + nuxt: nuxtPattern, + astro: astroPattern, + // Server SDKs + python: pythonServerPattern, + php: phpServerPattern, + go: goServerPattern, + ruby: rubyServerPattern, + dotnet: dotnetServerPattern, + dart: dartServerPattern, + kotlin: kotlinServerPattern, + swift: swiftServerPattern, + // Mobile/Client SDKs + android: androidPattern, + apple: applePattern, + flutter: flutterPattern, + 'react-native': reactNativePattern, + }; + + return patterns[framework] || ''; +} + +/** + * Check if framework is a server SDK + * @param {string} framework - Framework name + * @returns {boolean} + */ +export function isServerSDK(framework) { + const serverSDKs = ['python', 'php', 'go', 'ruby', 'dotnet', 'dart', 'kotlin', 'swift']; + return serverSDKs.includes(framework); +} + +/** + * Check if framework is a mobile/client SDK + * @param {string} framework - Framework name + * @returns {boolean} + */ +export function isMobileSDK(framework) { + const mobileSDKs = ['android', 'apple', 'flutter', 'react-native']; + return mobileSDKs.includes(framework); +} + +/** + * Get full implementation guide for a JS SSR framework + * @param {string} framework - Framework name + * @param {string} [sdk='javascript'] - SDK name for template selection + * @param {string[]} [features=[]] - Selected features to include patterns for + * @returns {string} Complete implementation guide + */ +export function getFullImplementationGuide(framework, sdk = 'javascript', features = []) { + const frameworkPattern = getImplementationPattern(framework); + + if (!frameworkPattern) { + return generalImplementationRules; + } + + // Build sections based on selected features + const sections = [generalImplementationRules, frameworkPattern]; + + // Include database-specific rules if database is selected + if (features.includes('database')) { + sections.push(databaseImplementationRules); + sections.push(getDatabaseWrapperTemplate(sdk)); + } + + // Include storage-specific rules if storage is selected + if (features.includes('storage')) { + sections.push(storageImplementationRules); + sections.push(getStorageWrapperTemplate(sdk)); + } + + // Include other service patterns + if (features.includes('functions')) { + sections.push(functionsPattern); + } + + if (features.includes('messaging')) { + sections.push(messagingPattern); + } + + if (features.includes('realtime')) { + sections.push(realtimePattern); + } + + if (features.includes('sites')) { + sections.push(sitesPattern); + } + + return sections.join('\n\n'); +} + +/** + * Get implementation guide for server SDKs + * @param {string} sdk - SDK/Framework name (python, php, go, ruby, dotnet, dart, kotlin, swift) + * @param {string[]} [features=[]] - Selected features to include patterns for + * @returns {string} Server SDK implementation guide + */ +export function getServerImplementationGuide(sdk, features = []) { + const frameworkPattern = getImplementationPattern(sdk); + + if (!frameworkPattern) { + return ''; + } + + // Build sections based on selected features + const sections = [generalImplementationRules, frameworkPattern]; + + // Include database-specific rules if database is selected + if (features.includes('database')) { + sections.push(databaseImplementationRules); + sections.push(getDatabaseWrapperTemplate(sdk)); + } + + // Include storage-specific rules if storage is selected + if (features.includes('storage')) { + sections.push(storageImplementationRules); + sections.push(getStorageWrapperTemplate(sdk)); + } + + // Include other service patterns + if (features.includes('functions')) { + sections.push(functionsPattern); + } + + if (features.includes('messaging')) { + sections.push(messagingPattern); + } + + return sections.join('\n\n'); +} + +/** + * Get implementation guide for mobile/client SDKs + * @param {string} sdk - SDK/Framework name (android, apple, flutter, react-native) + * @param {string[]} [features=[]] - Selected features to include patterns for + * @returns {string} Mobile SDK implementation guide + */ +export function getMobileImplementationGuide(sdk, features = []) { + const frameworkPattern = getImplementationPattern(sdk); + + if (!frameworkPattern) { + return ''; + } + + // Build sections based on selected features + const sections = [generalImplementationRules, frameworkPattern]; + + // Include database-specific rules if database is selected + if (features.includes('database')) { + sections.push(databaseImplementationRules); + sections.push(getDatabaseWrapperTemplate(sdk)); + } + + // Include storage-specific rules if storage is selected + if (features.includes('storage')) { + sections.push(storageImplementationRules); + sections.push(getStorageWrapperTemplate(sdk)); + } + + // Include other service patterns + if (features.includes('functions')) { + sections.push(functionsPattern); + } + + if (features.includes('messaging')) { + sections.push(messagingPattern); + } + + if (features.includes('realtime')) { + sections.push(realtimePattern); + } + + return sections.join('\n\n'); +} diff --git a/src/lib/languages/common/mcp.js b/src/lib/languages/common/mcp.js deleted file mode 100644 index 556aa87..0000000 --- a/src/lib/languages/common/mcp.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * MCP Server recommendation content - * This content is included in all generated rules to recommend installing the Appwrite Docs MCP server - */ - -/** - * MCP Server recommendation section - * @returns {string} - */ -export function generateMCPRecommendation() { - return `## Recommended: Install Appwrite Docs MCP Server - -**We highly recommend installing the Appwrite Docs MCP server** to get instant access to Appwrite documentation, code examples, and best practices directly in Cursor. This enables the AI assistant to provide accurate, up-to-date Appwrite guidance and code examples. - -### Installation Steps - -1. **Open Cursor Settings** - - Go to **Cursor Settings** → **MCP** tab - - Click **Add new global MCP server** - -2. **Add the Docs Server** - - Update the \`mcp.json\` file with the following configuration: - -\`\`\`json -{ - "mcpServers": { - "appwrite-docs": { - "command": "npx", - "args": [ - "mcp-remote", - "https://mcp-for-docs.appwrite.io" - ] - } - } -} -\`\`\` - -3. **Save and Restart** - - Save the \`mcp.json\` file - - Restart Cursor if the MCP server doesn't start automatically - -### Benefits - -Once installed, you can ask questions like: -- "How do I set up realtime subscriptions in Appwrite?" -- "Show me how to authenticate users with OAuth" -- "What are the best practices for database queries?" -- "How do I implement file uploads with Appwrite Storage?" -- "Show me an example of using Appwrite Functions" - -**Note:** You can also install the Appwrite API MCP server for direct API interactions. For full setup instructions, visit: https://appwrite.io/docs/tooling/mcp/cursor - ----`; -} - diff --git a/src/lib/languages/common/permissions-examples.js b/src/lib/languages/common/permissions-examples.js index 6ff4a62..392081e 100644 --- a/src/lib/languages/common/permissions-examples.js +++ b/src/lib/languages/common/permissions-examples.js @@ -1,4889 +1,318 @@ /** - * Language-specific permission examples for Appwrite - * All examples are validated against official Appwrite documentation + * Essential permission patterns for Appwrite multi-tenancy + * + * These examples demonstrate the critical patterns that differentiate + * good multi-tenant architecture from poor practices. For full API + * documentation, see the official Appwrite docs. */ -export const permissionExamples = { - javascript: { - avoidUserPermissions: `// DON'T do this for multi-tenant apps -import { TablesDB, Permission, Role } from 'appwrite'; - -await tablesDB.createRow({ - databaseId: '', - tableId: '', - rowId: '', - data: { title: 'My Row' }, - permissions: [ - Permission.read(Role.user('')), - Permission.write(Role.user('')) - ] -});`, - preferTeamPermissions: `// DO this for multi-tenant apps -import { TablesDB, Permission, Role } from 'appwrite'; - -await tablesDB.createRow({ - databaseId: '', - tableId: '', - rowId: '', - data: { title: 'My Row' }, - permissions: [ - Permission.read(Role.team('', 'owner')), - Permission.read(Role.team('', 'admin')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'owner')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -});`, - createTeam: `import { Client, Teams } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -// Create a team when a new tenant/organization signs up -const team = await teams.create({ - teamId: '', // Unique team ID (can be auto-generated) - name: '', // Display name - roles: ['owner', 'admin', 'member'] // Optional: Array of role strings -});`, - createMembershipEmail: `import { Client, Teams } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -// Send team invitation (email-based) -const invite = await teams.createMembership({ - teamId: '', - roles: ['admin', 'member'], - email: 'user@example.com', - url: 'https://yourapp.com/accept-invite' // Invitation redirect URL -});`, - createMembershipUserId: `import { Client, Teams } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -// Or invite by user ID (if user already exists) -const membership = await teams.createMembership({ - teamId: '', - roles: ['admin', 'member'], - userId: '' -});`, - listMemberships: `import { Client, Teams } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -// Get all members of a team -const response = await teams.listMemberships({ - teamId: '' -}); - -// Access member data -response.memberships.forEach(membership => { - console.log(membership.userId); - console.log(membership.roles); // Array of role strings - console.log(membership.userName); - console.log(membership.userEmail); -});`, - updateMembership: `import { Client, Teams } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -// Update a member's roles (only team owners/admins can do this) -await teams.updateMembership({ - teamId: '', - membershipId: '', - roles: ['admin', 'member'] // New roles array -});`, - deleteMembership: `import { Client, Teams } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -// Remove a member from a team -await teams.deleteMembership({ - teamId: '', - membershipId: '' -});`, - listTeams: `import { Client, Teams } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -// List all teams the current user belongs to -const response = await teams.list(); - -response.teams.forEach(team => { - console.log(team.$id); - console.log(team.name); -});`, - getUserRole: `import { Client, Teams } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -// Get membership details for current user in a specific team -const response = await teams.listMemberships({ - teamId: '' -}); - -const userMembership = response.memberships.find( - m => m.userId === '' -); - -if (userMembership) { - console.log(userMembership.roles); // ['owner', 'admin', etc.] - const hasAdminRole = userMembership.roles.includes('admin'); -}`, - createRow: `import { Client, TablesDB, Permission, Role } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const tablesDB = new TablesDB(client); - -// Create row with team-based permissions -await tablesDB.createRow({ - databaseId: '', - tableId: '', - rowId: '', - data: { - title: 'My Row', - teamId: '', // Always store teamId for querying - // ... other fields - }, - permissions: [ - // Owners and admins can do everything - Permission.read(Role.team('', 'owner')), - Permission.read(Role.team('', 'admin')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'owner')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')), - Permission.delete(Role.team('', 'admin')) - ] -});`, - createTable: `import { Client, TablesDB, Permission, Role } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject('') - .setKey(''); // Server SDK requires API key - -const tablesDB = new TablesDB(client); - -// Create table with team-based permissions -await tablesDB.createTable({ - databaseId: '', - tableId: '', - name: '', - permissions: [ - // Table permissions - Permission.create(Role.team('', 'member')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -});`, - listRows: `import { Client, TablesDB, Query } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const tablesDB = new TablesDB(client); - -// ALWAYS filter by teamId to ensure tenant isolation -const response = await tablesDB.listRows({ - databaseId: '', - tableId: '', - queries: [ - Query.equal('teamId', ''), // Critical: filter by team - Query.orderDesc('$createdAt'), - Query.limit(25) - ] -});`, - createFile: `import { Client, Storage, Permission, Role } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const storage = new Storage(client); - -// Create file with team-based permissions -await storage.createFile({ - bucketId: '', - fileId: '', - file: fileInput, // File object from input - permissions: [ - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -});`, - teamCreationFlow: `// When user creates account/organization -import { Client, Teams, ID } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -const team = await teams.create({ - teamId: ID.unique(), - name: 'Company Name' -}); - -// Make creator an owner -await teams.createMembership({ - teamId: team.$id, - roles: ['owner'], - userId: '' -});`, - inviteFlow: `// Owner/admin invites new member -import { Client, Teams } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -const invite = await teams.createMembership({ - teamId: '', - roles: ['member'], // Default role - email: 'user@example.com', - url: 'https://yourapp.com/accept-invite' // Redirect after accepting -}); -// User receives email, clicks link, accepts invitation`, - memberListUI: `// Display all team members with their roles -import { Client, Teams } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -const response = await teams.listMemberships({ - teamId: '' -}); -// Show list with role badges and action buttons`, - roleChange: `// Admin/owner changes member role -import { Client, Teams } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -await teams.updateMembership({ - teamId: '', - membershipId: '', - roles: ['admin'] -});`, - memberRemoval: `// Remove member (with confirmation) -import { Client, Teams } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -await teams.deleteMembership({ - teamId: '', - membershipId: '' -});`, - roleCheck: `const response = await teams.listMemberships({ - teamId: '' -}); - -const membership = response.memberships.find( - m => m.userId === '' -); - -if (!membership || !membership.roles.includes('admin')) { - throw new Error('Insufficient permissions'); -}` - }, - 'react-native': { - // React Native uses the same syntax as JavaScript - avoidUserPermissions: `// DON'T do this for multi-tenant apps -import { TablesDB, Permission, Role } from 'react-native-appwrite'; - -await tablesDB.createRow({ - databaseId: '', - tableId: '', - rowId: '', - data: { title: 'My Row' }, - permissions: [ - Permission.read(Role.user('')), - Permission.write(Role.user('')) - ] -});`, - preferTeamPermissions: `// DO this for multi-tenant apps -import { TablesDB, Permission, Role } from 'react-native-appwrite'; - -await tablesDB.createRow({ - databaseId: '', - tableId: '', - rowId: '', - data: { title: 'My Row' }, - permissions: [ - Permission.read(Role.team('', 'owner')), - Permission.read(Role.team('', 'admin')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'owner')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -});`, - createTeam: `import { Client, Teams } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -const team = await teams.create({ - teamId: '', - name: '', - roles: ['owner', 'admin', 'member'] -});`, - createMembershipEmail: `import { Client, Teams } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -const invite = await teams.createMembership({ - teamId: '', - roles: ['admin', 'member'], - email: 'user@example.com', - url: 'https://yourapp.com/accept-invite' -});`, - createMembershipUserId: `import { Client, Teams } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -const membership = await teams.createMembership({ - teamId: '', - roles: ['admin', 'member'], - userId: '' -});`, - listMemberships: `import { Client, Teams } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -const response = await teams.listMemberships({ - teamId: '' -}); - -response.memberships.forEach(membership => { - console.log(membership.userId); - console.log(membership.roles); - console.log(membership.userName); - console.log(membership.userEmail); -});`, - updateMembership: `import { Client, Teams } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -await teams.updateMembership({ - teamId: '', - membershipId: '', - roles: ['admin', 'member'] -});`, - deleteMembership: `import { Client, Teams } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -await teams.deleteMembership({ - teamId: '', - membershipId: '' -});`, - listTeams: `import { Client, Teams } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -const response = await teams.list(); - -response.teams.forEach(team => { - console.log(team.$id); - console.log(team.name); -});`, - getUserRole: `import { Client, Teams } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -const response = await teams.listMemberships({ - teamId: '' -}); - -const userMembership = response.memberships.find( - m => m.userId === '' -); - -if (userMembership) { - console.log(userMembership.roles); - const hasAdminRole = userMembership.roles.includes('admin'); -}`, - createRow: `import { Client, TablesDB, Permission, Role } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const tablesDB = new TablesDB(client); - -await tablesDB.createRow({ - databaseId: '', - tableId: '', - rowId: '', - data: { - title: 'My Row', - teamId: '', - }, - permissions: [ - Permission.read(Role.team('', 'owner')), - Permission.read(Role.team('', 'admin')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'owner')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')), - Permission.delete(Role.team('', 'admin')) - ] -});`, - createTable: `import { Client, TablesDB, Permission, Role } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject('') - .setKey(''); - -const tablesDB = new TablesDB(client); - -await tablesDB.createTable({ - databaseId: '', - tableId: '', - name: '', - permissions: [ - Permission.create(Role.team('', 'member')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -});`, - listRows: `import { Client, TablesDB, Query } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const tablesDB = new TablesDB(client); - -const response = await tablesDB.listRows({ - databaseId: '', - tableId: '', - queries: [ - Query.equal('teamId', ''), - Query.orderDesc('$createdAt'), - Query.limit(25) - ] -});`, - createFile: `import { Client, Storage, Permission, Role } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const storage = new Storage(client); - -await storage.createFile({ - bucketId: '', - fileId: '', - file: fileInput, - permissions: [ - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -});`, - teamCreationFlow: `import { Client, Teams, ID } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -const team = await teams.create({ - teamId: ID.unique(), - name: 'Company Name' -}); - -await teams.createMembership({ - teamId: team.$id, - roles: ['owner'], - userId: '' -});`, - inviteFlow: `import { Client, Teams } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -const invite = await teams.createMembership({ - teamId: '', - roles: ['member'], - email: 'user@example.com', - url: 'https://yourapp.com/accept-invite' -});`, - memberListUI: `import { Client, Teams } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -const response = await teams.listMemberships({ - teamId: '' -});`, - roleChange: `import { Client, Teams } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -await teams.updateMembership({ - teamId: '', - membershipId: '', - roles: ['admin'] -});`, - memberRemoval: `import { Client, Teams } from 'react-native-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const teams = new Teams(client); - -await teams.deleteMembership({ - teamId: '', - membershipId: '' -});`, - roleCheck: `const response = await teams.listMemberships({ - teamId: '' -}); - -const membership = response.memberships.find( - m => m.userId === '' -); - -if (!membership || !membership.roles.includes('admin')) { - throw new Error('Insufficient permissions'); -}` - }, - python: { - avoidUserPermissions: `# DON'T do this for multi-tenant apps -from appwrite.client import Client -from appwrite.services.tables_db import TablesDB -from appwrite.models import Permission, Role - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -tables_db = TablesDB(client) - -tables_db.create_row( - database_id='', - table_id='', - row_id='', - data={'title': 'My Row'}, - permissions=[ - Permission.read(Role.user('')), - Permission.write(Role.user('')) - ] -)`, - preferTeamPermissions: `# DO this for multi-tenant apps -from appwrite.client import Client -from appwrite.services.tables_db import TablesDB -from appwrite.models import Permission, Role - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -tables_db = TablesDB(client) - -tables_db.create_row( - database_id='', - table_id='', - row_id='', - data={'title': 'My Row'}, - permissions=[ - Permission.read(Role.team('', 'owner')), - Permission.read(Role.team('', 'admin')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'owner')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -)`, - createTeam: `from appwrite.client import Client -from appwrite.services.teams import Teams - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -teams = Teams(client) - -# Create a team when a new tenant/organization signs up -team = teams.create( - team_id='', - name='', - roles=['owner', 'admin', 'member'] # optional -)`, - createMembershipEmail: `from appwrite.client import Client -from appwrite.services.teams import Teams - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -teams = Teams(client) - -# Send team invitation (email-based) -invite = teams.create_membership( - team_id='', - roles=['admin', 'member'], - email='user@example.com', - url='https://yourapp.com/accept-invite' -)`, - createMembershipUserId: `from appwrite.client import Client -from appwrite.services.teams import Teams - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_key('') # Server SDK - -teams = Teams(client) - -# Or invite by user ID (if user already exists) -membership = teams.create_membership( - team_id='', - roles=['admin', 'member'], - user_id='' -)`, - listMemberships: `from appwrite.client import Client -from appwrite.services.teams import Teams - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -teams = Teams(client) - -# Get all members of a team -response = teams.list_memberships(team_id='') - -# Access member data -for membership in response['memberships']: - print(membership['userId']) - print(membership['roles']) # Array of role strings - print(membership['userName']) - print(membership['userEmail'])`, - updateMembership: `from appwrite.client import Client -from appwrite.services.teams import Teams - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -teams = Teams(client) - -# Update a member's roles (only team owners/admins can do this) -teams.update_membership( - team_id='', - membership_id='', - roles=['admin', 'member'] -)`, - deleteMembership: `from appwrite.client import Client -from appwrite.services.teams import Teams - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -teams = Teams(client) - -# Remove a member from a team -teams.delete_membership( - team_id='', - membership_id='' -)`, - listTeams: `from appwrite.client import Client -from appwrite.services.teams import Teams - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -teams = Teams(client) - -# List all teams the current user belongs to -response = teams.list() - -for team in response['teams']: - print(team['$id']) - print(team['name'])`, - getUserRole: `from appwrite.client import Client -from appwrite.services.teams import Teams - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -teams = Teams(client) - -# Get membership details for current user in a specific team -response = teams.list_memberships(team_id='') - -user_membership = next( - (m for m in response['memberships'] if m['userId'] == ''), - None -) - -if user_membership: - print(user_membership['roles']) # ['owner', 'admin', etc.] - has_admin_role = 'admin' in user_membership['roles']`, - createRow: `from appwrite.client import Client -from appwrite.services.tables_db import TablesDB -from appwrite.models import Permission, Role - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -tables_db = TablesDB(client) - -# Create row with team-based permissions -tables_db.create_row( - database_id='', - table_id='', - row_id='', - data={ - 'title': 'My Row', - 'teamId': '', # Always store teamId for querying - }, - permissions=[ - Permission.read(Role.team('', 'owner')), - Permission.read(Role.team('', 'admin')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'owner')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')), - Permission.delete(Role.team('', 'admin')) - ] -)`, - createTable: `from appwrite.client import Client -from appwrite.services.tables_db import TablesDB -from appwrite.models import Permission, Role - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_key('') # Server SDK requires API key - -tables_db = TablesDB(client) - -# Create table with team-based permissions -tables_db.create_table( - database_id='', - table_id='', - name='', - permissions=[ - Permission.create(Role.team('', 'member')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -)`, - listRows: `from appwrite.client import Client -from appwrite.services.tables_db import TablesDB -from appwrite.query import Query - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -tables_db = TablesDB(client) - -# ALWAYS filter by teamId to ensure tenant isolation -response = tables_db.list_rows( - database_id='', - table_id='', - queries=[ - Query.equal('teamId', ''), # Critical: filter by team - Query.order_desc('$createdAt'), - Query.limit(25) - ] -)`, - createFile: `from appwrite.client import Client -from appwrite.services.storage import Storage -from appwrite.models import Permission, Role - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -storage = Storage(client) - -# Create file with team-based permissions -storage.create_file( - bucket_id='', - file_id='', - file=file_input, - permissions=[ - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -)`, - teamCreationFlow: `# When user creates account/organization -from appwrite.client import Client -from appwrite.services.teams import Teams -from appwrite.id import ID - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -teams = Teams(client) - -team = teams.create( - team_id=ID.unique(), - name='Company Name' -) - -# Make creator an owner -teams.create_membership( - team_id=team['$id'], - roles=['owner'], - user_id='' -)`, - inviteFlow: `# Owner/admin invites new member -from appwrite.client import Client -from appwrite.services.teams import Teams - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -teams = Teams(client) - -invite = teams.create_membership( - team_id='', - roles=['member'], # Default role - email='user@example.com', - url='https://yourapp.com/accept-invite' -) -# User receives email, clicks link, accepts invitation`, - memberListUI: `# Display all team members with their roles -from appwrite.client import Client -from appwrite.services.teams import Teams - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -teams = Teams(client) - -response = teams.list_memberships(team_id='') -# Show list with role badges and action buttons`, - roleChange: `# Admin/owner changes member role -from appwrite.client import Client -from appwrite.services.teams import Teams - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -teams = Teams(client) - -teams.update_membership( - team_id='', - membership_id='', - roles=['admin'] -)`, - memberRemoval: `# Remove member (with confirmation) -from appwrite.client import Client -from appwrite.services.teams import Teams - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_session('') - -teams = Teams(client) - -teams.delete_membership( - team_id='', - membership_id='' -)`, - roleCheck: `response = teams.list_memberships(team_id='') - -user_membership = next( - (m for m in response['memberships'] if m['userId'] == ''), - None -) - -if not user_membership or 'admin' not in user_membership['roles']: - raise Exception('Insufficient permissions')` - }, - php: { - avoidUserPermissions: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$tablesDB = new TablesDB($client); - -$tablesDB->createRow( - databaseId: '', - tableId: '', - rowId: '', - data: ['title' => 'My Row'], - permissions: [ - Permission::read(Role::user('')), - Permission::write(Role::user('')) - ] -);`, - preferTeamPermissions: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$tablesDB = new TablesDB($client); - -$tablesDB->createRow( - databaseId: '', - tableId: '', - rowId: '', - data: ['title' => 'My Row'], - permissions: [ - Permission::read(Role::team('', 'owner')), - Permission::read(Role::team('', 'admin')), - Permission::read(Role::team('', 'member')), - Permission::update(Role::team('', 'owner')), - Permission::update(Role::team('', 'admin')), - Permission::delete(Role::team('', 'owner')) - ] -);`, - createTeam: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$teams = new Teams($client); - -// Create a team when a new tenant/organization signs up -$team = $teams->create( - teamId: '', - name: '', - roles: ['owner', 'admin', 'member'] // optional -);`, - createMembershipEmail: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$teams = new Teams($client); - -// Send team invitation (email-based) -$invite = $teams->createMembership( - teamId: '', - roles: ['admin', 'member'], - email: 'user@example.com', - url: 'https://yourapp.com/accept-invite' -);`, - createMembershipUserId: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setKey(''); // Server SDK - -$teams = new Teams($client); - -// Or invite by user ID (if user already exists) -$membership = $teams->createMembership( - teamId: '', - roles: ['admin', 'member'], - userId: '' -);`, - listMemberships: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$teams = new Teams($client); - -// Get all members of a team -$response = $teams->listMemberships(teamId: ''); - -// Access member data -foreach ($response['memberships'] as $membership) { - echo $membership['userId']; - echo implode(', ', $membership['roles']); // Array of role strings - echo $membership['userName']; - echo $membership['userEmail']; -}`, - updateMembership: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$teams = new Teams($client); - -// Update a member's roles (only team owners/admins can do this) -$teams->updateMembership( - teamId: '', - membershipId: '', - roles: ['admin', 'member'] -);`, - deleteMembership: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$teams = new Teams($client); - -// Remove a member from a team -$teams->deleteMembership( - teamId: '', - membershipId: '' -);`, - listTeams: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$teams = new Teams($client); - -// List all teams the current user belongs to -$response = $teams->list(); - -foreach ($response['teams'] as $team) { - echo $team['$id']; - echo $team['name']; -}`, - getUserRole: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$teams = new Teams($client); - -// Get membership details for current user in a specific team -$response = $teams->listMemberships(teamId: ''); - -$userMembership = array_filter( - $response['memberships'], - fn($m) => $m['userId'] === '' -); - -if (!empty($userMembership)) { - $membership = reset($userMembership); - echo implode(', ', $membership['roles']); // ['owner', 'admin', etc.] - $hasAdminRole = in_array('admin', $membership['roles']); -}`, - createRow: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$tablesDB = new TablesDB($client); - -// Create row with team-based permissions -$tablesDB->createRow( - databaseId: '', - tableId: '', - rowId: '', - data: [ - 'title' => 'My Row', - 'teamId' => '', // Always store teamId for querying - ], - permissions: [ - Permission::read(Role::team('', 'owner')), - Permission::read(Role::team('', 'admin')), - Permission::read(Role::team('', 'member')), - Permission::update(Role::team('', 'owner')), - Permission::update(Role::team('', 'admin')), - Permission::delete(Role::team('', 'owner')), - Permission::delete(Role::team('', 'admin')) - ] -);`, - createTable: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setKey(''); // Server SDK requires API key - -$tablesDB = new TablesDB($client); - -// Create table with team-based permissions -$tablesDB->createTable( - databaseId: '', - tableId: '', - name: '', - permissions: [ - Permission::create(Role::team('', 'member')), - Permission::read(Role::team('', 'member')), - Permission::update(Role::team('', 'admin')), - Permission::delete(Role::team('', 'owner')) - ] -);`, - listRows: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$tablesDB = new TablesDB($client); - -// ALWAYS filter by teamId to ensure tenant isolation -$response = $tablesDB->listRows( - databaseId: '', - tableId: '', - queries: [ - Query::equal('teamId', ''), // Critical: filter by team - Query::orderDesc('$createdAt'), - Query::limit(25) - ] -);`, - createFile: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$storage = new Storage($client); - -// Create file with team-based permissions -$storage->createFile( - bucketId: '', - fileId: '', - file: $fileInput, - permissions: [ - Permission::read(Role::team('', 'member')), - Permission::update(Role::team('', 'admin')), - Permission::delete(Role::team('', 'owner')) - ] -);`, - teamCreationFlow: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$teams = new Teams($client); - -$team = $teams->create( - teamId: ID::unique(), - name: 'Company Name' -); - -// Make creator an owner -$teams->createMembership( - teamId: $team['$id'], - roles: ['owner'], - userId: '' -);`, - inviteFlow: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$teams = new Teams($client); - -$invite = $teams->createMembership( - teamId: '', - roles: ['member'], // Default role - email: 'user@example.com', - url: 'https://yourapp.com/accept-invite' -); -// User receives email, clicks link, accepts invitation`, - memberListUI: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$teams = new Teams($client); - -$response = $teams->listMemberships(teamId: ''); -// Show list with role badges and action buttons`, - roleChange: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$teams = new Teams($client); - -$teams->updateMembership( - teamId: '', - membershipId: '', - roles: ['admin'] -);`, - memberRemoval: `setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setSession(''); - -$teams = new Teams($client); - -$teams->deleteMembership( - teamId: '', - membershipId: '' -);`, - roleCheck: `$response = $teams->listMemberships(teamId: ''); - -$userMembership = array_filter( - $response['memberships'], - fn($m) => $m['userId'] === '' -); - -if (empty($userMembership) || !in_array('admin', reset($userMembership)['roles'])) { - throw new Exception('Insufficient permissions'); -}` - }, - go: { - avoidUserPermissions: `// DON'T do this for multi-tenant apps -package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/tablesdb" - "github.com/appwrite/sdk-for-go/models" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -tablesDB := tablesdb.New(client) - -tablesDB.CreateRow( - "", - "", - "", - map[string]interface{}{ - "title": "My Row", - }, - []interface{}{ - models.PermissionRead(models.RoleUser("")), - models.PermissionWrite(models.RoleUser("")), - }, -)`, - preferTeamPermissions: `// DO this for multi-tenant apps -package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/tablesdb" - "github.com/appwrite/sdk-for-go/models" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -tablesDB := tablesdb.New(client) - -tablesDB.CreateRow( - "", - "", - "", - map[string]interface{}{ - "title": "My Row", - }, - []interface{}{ - models.PermissionRead(models.RoleTeam("", "owner")), - models.PermissionRead(models.RoleTeam("", "admin")), - models.PermissionRead(models.RoleTeam("", "member")), - models.PermissionUpdate(models.RoleTeam("", "owner")), - models.PermissionUpdate(models.RoleTeam("", "admin")), - models.PermissionDelete(models.RoleTeam("", "owner")), - }, -)`, - createTeam: `package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/teams" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -service := teams.New(client) - -// Create a team when a new tenant/organization signs up -service.Create( - "", - "", - teams.WithCreateRoles([]interface{}{"owner", "admin", "member"}), -)`, - createMembershipEmail: `package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/teams" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -service := teams.New(client) - -// Send team invitation (email-based) -service.CreateMembership( - "", - []interface{}{"admin", "member"}, - teams.WithCreateMembershipEmail("user@example.com"), - teams.WithCreateMembershipUrl("https://yourapp.com/accept-invite"), -)`, - createMembershipUserId: `package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/teams" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithKey(""), // Server SDK -) - -service := teams.New(client) - -// Or invite by user ID (if user already exists) -service.CreateMembership( - "", - []interface{}{"admin", "member"}, - teams.WithCreateMembershipUserId(""), -)`, - listMemberships: `package main - -import ( - "fmt" - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/teams" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -service := teams.New(client) - -// Get all members of a team -response, _ := service.ListMemberships("") - -// Access member data -for _, membership := range response.Memberships { - fmt.Println(membership.UserId) - fmt.Println(membership.Roles) // Array of role strings - fmt.Println(membership.UserName) - fmt.Println(membership.UserEmail) -}`, - updateMembership: `package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/teams" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -service := teams.New(client) - -// Update a member's roles (only team owners/admins can do this) -service.UpdateMembership( - "", - "", - []interface{}{"admin", "member"}, -)`, - deleteMembership: `package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/teams" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -service := teams.New(client) - -// Remove a member from a team -service.DeleteMembership( - "", - "", -)`, - listTeams: `package main - -import ( - "fmt" - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/teams" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -service := teams.New(client) - -// List all teams the current user belongs to -response, _ := service.List() - -for _, team := range response.Teams { - fmt.Println(team.Id) - fmt.Println(team.Name) -}`, - getUserRole: `package main - -import ( - "fmt" - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/teams" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -service := teams.New(client) - -// Get membership details for current user in a specific team -response, _ := service.ListMemberships("") - -var userMembership *teams.Membership -for _, membership := range response.Memberships { - if membership.UserId == "" { - userMembership = &membership - break - } -} - -if userMembership != nil { - fmt.Println(userMembership.Roles) // ['owner', 'admin', etc.] - hasAdminRole := false - for _, role := range userMembership.Roles { - if role == "admin" { - hasAdminRole = true - break - } - } -}`, - createRow: `package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/tablesdb" - "github.com/appwrite/sdk-for-go/models" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -tablesDB := tablesdb.New(client) - -// Create row with team-based permissions -tablesDB.CreateRow( - "", - "", - "", - map[string]interface{}{ - "title": "My Row", - "teamId": "", // Always store teamId for querying - }, - []interface{}{ - models.PermissionRead(models.RoleTeam("", "owner")), - models.PermissionRead(models.RoleTeam("", "admin")), - models.PermissionRead(models.RoleTeam("", "member")), - models.PermissionUpdate(models.RoleTeam("", "owner")), - models.PermissionUpdate(models.RoleTeam("", "admin")), - models.PermissionDelete(models.RoleTeam("", "owner")), - models.PermissionDelete(models.RoleTeam("", "admin")), - }, -)`, - createTable: `package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/tablesdb" - "github.com/appwrite/sdk-for-go/models" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithKey(""), // Server SDK requires API key -) - -tablesDB := tablesdb.New(client) - -// Create table with team-based permissions -tablesDB.CreateTable( - "", - "", - "", - tablesdb.WithCreateTablePermissions([]interface{}{ - models.PermissionCreate(models.RoleTeam("", "member")), - models.PermissionRead(models.RoleTeam("", "member")), - models.PermissionUpdate(models.RoleTeam("", "admin")), - models.PermissionDelete(models.RoleTeam("", "owner")), - }), -)`, - listRows: `package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/tablesdb" - "github.com/appwrite/sdk-for-go/query" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -tablesDB := tablesdb.New(client) - -// ALWAYS filter by teamId to ensure tenant isolation -tablesDB.ListRows( - "", - "", - tablesdb.WithListRowsQueries([]interface{}{ - query.Equal("teamId", ""), // Critical: filter by team - query.OrderDesc("$createdAt"), - query.Limit(25), - }), -)`, - createFile: `package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/storage" - "github.com/appwrite/sdk-for-go/models" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -storageService := storage.New(client) - -// Create file with team-based permissions -storageService.CreateFile( - "", - "", - fileInput, - storage.WithCreateFilePermissions([]interface{}{ - models.PermissionRead(models.RoleTeam("", "member")), - models.PermissionUpdate(models.RoleTeam("", "admin")), - models.PermissionDelete(models.RoleTeam("", "owner")), - }), -)`, - teamCreationFlow: `// When user creates account/organization -package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/teams" - "github.com/appwrite/sdk-for-go/id" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -service := teams.New(client) - -team, _ := service.Create( - id.Unique(), - "Company Name", -) - -// Make creator an owner -service.CreateMembership( - team.Id, - []interface{}{"owner"}, - teams.WithCreateMembershipUserId(""), -)`, - inviteFlow: `// Owner/admin invites new member -package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/teams" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -service := teams.New(client) - -service.CreateMembership( - "", - []interface{}{"member"}, // Default role - teams.WithCreateMembershipEmail("user@example.com"), - teams.WithCreateMembershipUrl("https://yourapp.com/accept-invite"), -) -// User receives email, clicks link, accepts invitation`, - memberListUI: `// Display all team members with their roles -package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/teams" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -service := teams.New(client) - -response, _ := service.ListMemberships("") -// Show list with role badges and action buttons`, - roleChange: `// Admin/owner changes member role -package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/teams" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -service := teams.New(client) - -service.UpdateMembership( - "", - "", - []interface{}{"admin"}, -)`, - memberRemoval: `// Remove member (with confirmation) -package main - -import ( - "github.com/appwrite/sdk-for-go/client" - "github.com/appwrite/sdk-for-go/teams" -) - -client := client.New( - client.WithEndpoint("https://.cloud.appwrite.io/v1"), - client.WithProject(""), - client.WithSession(""), -) - -service := teams.New(client) - -service.DeleteMembership( - "", - "", -)`, - roleCheck: `response, _ := service.ListMemberships("") - -var userMembership *teams.Membership -for _, membership := range response.Memberships { - if membership.UserId == "" { - userMembership = &membership - break - } -} - -hasAdminRole := false -if userMembership != nil { - for _, role := range userMembership.Roles { - if role == "admin" { - hasAdminRole = true - break - } - } -} - -if !hasAdminRole { - panic("Insufficient permissions") -}` - }, - ruby: { - avoidUserPermissions: `# DON'T do this for multi-tenant apps -require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -tables_db = TablesDB.new(client) - -tables_db.create_row( - database_id: '', - table_id: '', - row_id: '', - data: {'title' => 'My Row'}, - permissions: [ - Permission.read(Role.user('')), - Permission.write(Role.user('')) - ] -)`, - preferTeamPermissions: `# DO this for multi-tenant apps -require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -tables_db = TablesDB.new(client) - -tables_db.create_row( - database_id: '', - table_id: '', - row_id: '', - data: {'title' => 'My Row'}, - permissions: [ - Permission.read(Role.team('', 'owner')), - Permission.read(Role.team('', 'admin')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'owner')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -)`, - createTeam: `require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -teams = Teams.new(client) - -# Create a team when a new tenant/organization signs up -team = teams.create( - team_id: '', - name: '', - roles: ['owner', 'admin', 'member'] # optional -)`, - createMembershipEmail: `require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -teams = Teams.new(client) - -# Send team invitation (email-based) -invite = teams.create_membership( - team_id: '', - roles: ['admin', 'member'], - email: 'user@example.com', - url: 'https://yourapp.com/accept-invite' -)`, - createMembershipUserId: `require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_key('') # Server SDK - -teams = Teams.new(client) - -# Or invite by user ID (if user already exists) -membership = teams.create_membership( - team_id: '', - roles: ['admin', 'member'], - user_id: '' -)`, - listMemberships: `require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -teams = Teams.new(client) - -# Get all members of a team -response = teams.list_memberships(team_id: '') - -# Access member data -response['memberships'].each do |membership| - puts membership['userId'] - puts membership['roles'] # Array of role strings - puts membership['userName'] - puts membership['userEmail'] -end`, - updateMembership: `require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -teams = Teams.new(client) - -# Update a member's roles (only team owners/admins can do this) -teams.update_membership( - team_id: '', - membership_id: '', - roles: ['admin', 'member'] -)`, - deleteMembership: `require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -teams = Teams.new(client) - -# Remove a member from a team -teams.delete_membership( - team_id: '', - membership_id: '' -)`, - listTeams: `require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -teams = Teams.new(client) - -# List all teams the current user belongs to -response = teams.list - -response['teams'].each do |team| - puts team['$id'] - puts team['name'] -end`, - getUserRole: `require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -teams = Teams.new(client) - -# Get membership details for current user in a specific team -response = teams.list_memberships(team_id: '') - -user_membership = response['memberships'].find do |m| - m['userId'] == '' -end - -if user_membership - puts user_membership['roles'] # ['owner', 'admin', etc.] - has_admin_role = user_membership['roles'].include?('admin') -end`, - createRow: `require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -tables_db = TablesDB.new(client) - -# Create row with team-based permissions -tables_db.create_row( - database_id: '', - table_id: '', - row_id: '', - data: { - 'title' => 'My Row', - 'teamId' => '', # Always store teamId for querying - }, - permissions: [ - Permission.read(Role.team('', 'owner')), - Permission.read(Role.team('', 'admin')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'owner')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')), - Permission.delete(Role.team('', 'admin')) - ] -)`, - createTable: `require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_key('') # Server SDK requires API key - -tables_db = TablesDB.new(client) - -# Create table with team-based permissions -tables_db.create_table( - database_id: '', - table_id: '', - name: '', - permissions: [ - Permission.create(Role.team('', 'member')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -)`, - listRows: `require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -tables_db = TablesDB.new(client) - -# ALWAYS filter by teamId to ensure tenant isolation -response = tables_db.list_rows( - database_id: '', - table_id: '', - queries: [ - Query.equal('teamId', ''), # Critical: filter by team - Query.order_desc('$createdAt'), - Query.limit(25) - ] -)`, - createFile: `require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -storage = Storage.new(client) - -# Create file with team-based permissions -storage.create_file( - bucket_id: '', - file_id: '', - file: file_input, - permissions: [ - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -)`, - teamCreationFlow: `# When user creates account/organization -require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -teams = Teams.new(client) - -team = teams.create( - team_id: ID.unique, - name: 'Company Name' -) - -# Make creator an owner -teams.create_membership( - team_id: team['$id'], - roles: ['owner'], - user_id: '' -)`, - inviteFlow: `# Owner/admin invites new member -require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -teams = Teams.new(client) - -invite = teams.create_membership( - team_id: '', - roles: ['member'], # Default role - email: 'user@example.com', - url: 'https://yourapp.com/accept-invite' -) -# User receives email, clicks link, accepts invitation`, - memberListUI: `# Display all team members with their roles -require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -teams = Teams.new(client) - -response = teams.list_memberships(team_id: '') -# Show list with role badges and action buttons`, - roleChange: `# Admin/owner changes member role -require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -teams = Teams.new(client) - -teams.update_membership( - team_id: '', - membership_id: '', - roles: ['admin'] -)`, - memberRemoval: `# Remove member (with confirmation) -require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_session('') - -teams = Teams.new(client) - -teams.delete_membership( - team_id: '', - membership_id: '' -)`, - roleCheck: `response = teams.list_memberships(team_id: '') - -user_membership = response['memberships'].find do |m| - m['userId'] == '' -end - -if !user_membership || !user_membership['roles'].include?('admin') - raise 'Insufficient permissions' -end` - }, - dotnet: { - avoidUserPermissions: `// DON'T do this for multi-tenant apps -using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -TablesDB tablesDB = new TablesDB(client); - -await tablesDB.CreateRow( - databaseId: "", - tableId: "", - rowId: "", - data: new { title = "My Row" }, - permissions: new List { - Permission.Read(Role.User("")), - Permission.Write(Role.User("")) - } -);`, - preferTeamPermissions: `// DO this for multi-tenant apps -using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -TablesDB tablesDB = new TablesDB(client); - -await tablesDB.CreateRow( - databaseId: "", - tableId: "", - rowId: "", - data: new { title = "My Row" }, - permissions: new List { - Permission.Read(Role.Team("", "owner")), - Permission.Read(Role.Team("", "admin")), - Permission.Read(Role.Team("", "member")), - Permission.Update(Role.Team("", "owner")), - Permission.Update(Role.Team("", "admin")), - Permission.Delete(Role.Team("", "owner")) - } -);`, - createTeam: `using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -Teams teams = new Teams(client); - -// Create a team when a new tenant/organization signs up -Team team = await teams.Create( - teamId: "", - name: "", - roles: new List { "owner", "admin", "member" } // optional -);`, - createMembershipEmail: `using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -Teams teams = new Teams(client); - -// Send team invitation (email-based) -Membership invite = await teams.CreateMembership( - teamId: "", - roles: new List { "admin", "member" }, - email: "user@example.com", - url: "https://yourapp.com/accept-invite" -);`, - createMembershipUserId: `using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetKey(""); // Server SDK - -Teams teams = new Teams(client); - -// Or invite by user ID (if user already exists) -Membership membership = await teams.CreateMembership( - teamId: "", - roles: new List { "admin", "member" }, - userId: "" -);`, - listMemberships: `using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -Teams teams = new Teams(client); - -// Get all members of a team -MembershipList response = await teams.ListMemberships(teamId: ""); - -// Access member data -foreach (var membership in response.Memberships) { - Console.WriteLine(membership.UserId); - Console.WriteLine(string.Join(", ", membership.Roles)); // Array of role strings - Console.WriteLine(membership.UserName); - Console.WriteLine(membership.UserEmail); -}`, - updateMembership: `using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -Teams teams = new Teams(client); - -// Update a member's roles (only team owners/admins can do this) -await teams.UpdateMembership( - teamId: "", - membershipId: "", - roles: new List { "admin", "member" } -);`, - deleteMembership: `using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -Teams teams = new Teams(client); - -// Remove a member from a team -await teams.DeleteMembership( - teamId: "", - membershipId: "" -);`, - listTeams: `using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -Teams teams = new Teams(client); - -// List all teams the current user belongs to -TeamList response = await teams.List(); - -foreach (var team in response.Teams) { - Console.WriteLine(team.Id); - Console.WriteLine(team.Name); -}`, - getUserRole: `using Appwrite; -using Appwrite.Models; -using Appwrite.Services; -using System.Linq; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -Teams teams = new Teams(client); - -// Get membership details for current user in a specific team -MembershipList response = await teams.ListMemberships(teamId: ""); - -var userMembership = response.Memberships.FirstOrDefault( - m => m.UserId == "" -); - -if (userMembership != null) { - Console.WriteLine(string.Join(", ", userMembership.Roles)); // ['owner', 'admin', etc.] - bool hasAdminRole = userMembership.Roles.Contains("admin"); -}`, - createRow: `using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -TablesDB tablesDB = new TablesDB(client); - -// Create row with team-based permissions -await tablesDB.CreateRow( - databaseId: "", - tableId: "", - rowId: "", - data: new { - title = "My Row", - teamId = "", // Always store teamId for querying - }, - permissions: new List { - Permission.Read(Role.Team("", "owner")), - Permission.Read(Role.Team("", "admin")), - Permission.Read(Role.Team("", "member")), - Permission.Update(Role.Team("", "owner")), - Permission.Update(Role.Team("", "admin")), - Permission.Delete(Role.Team("", "owner")), - Permission.Delete(Role.Team("", "admin")) - } -);`, - createTable: `using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetKey(""); // Server SDK requires API key - -TablesDB tablesDB = new TablesDB(client); - -// Create table with team-based permissions -await tablesDB.CreateTable( - databaseId: "", - tableId: "", - name: "", - permissions: new List { - Permission.Create(Role.Team("", "member")), - Permission.Read(Role.Team("", "member")), - Permission.Update(Role.Team("", "admin")), - Permission.Delete(Role.Team("", "owner")) - } -);`, - listRows: `using Appwrite; -using Appwrite.Models; -using Appwrite.Services; -using Appwrite.Query; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -TablesDB tablesDB = new TablesDB(client); - -// ALWAYS filter by teamId to ensure tenant isolation -RowList response = await tablesDB.ListRows( - databaseId: "", - tableId: "", - queries: new List { - Query.Equal("teamId", ""), // Critical: filter by team - Query.OrderDesc("$createdAt"), - Query.Limit(25) - } -);`, - createFile: `using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -Storage storage = new Storage(client); - -// Create file with team-based permissions -await storage.CreateFile( - bucketId: "", - fileId: "", - file: fileInput, - permissions: new List { - Permission.Read(Role.Team("", "member")), - Permission.Update(Role.Team("", "admin")), - Permission.Delete(Role.Team("", "owner")) - } -);`, - teamCreationFlow: `// When user creates account/organization -using Appwrite; -using Appwrite.Models; -using Appwrite.Services; -using Appwrite.ID; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -Teams teams = new Teams(client); - -Team team = await teams.Create( - teamId: ID.Unique(), - name: "Company Name" -); - -// Make creator an owner -await teams.CreateMembership( - teamId: team.Id, - roles: new List { "owner" }, - userId: "" -);`, - inviteFlow: `// Owner/admin invites new member -using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -Teams teams = new Teams(client); - -Membership invite = await teams.CreateMembership( - teamId: "", - roles: new List { "member" }, // Default role - email: "user@example.com", - url: "https://yourapp.com/accept-invite" -); -// User receives email, clicks link, accepts invitation`, - memberListUI: `// Display all team members with their roles -using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -Teams teams = new Teams(client); - -MembershipList response = await teams.ListMemberships(teamId: ""); -// Show list with role badges and action buttons`, - roleChange: `// Admin/owner changes member role -using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -Teams teams = new Teams(client); - -await teams.UpdateMembership( - teamId: "", - membershipId: "", - roles: new List { "admin" } -);`, - memberRemoval: `// Remove member (with confirmation) -using Appwrite; -using Appwrite.Models; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetSession(""); - -Teams teams = new Teams(client); - -await teams.DeleteMembership( - teamId: "", - membershipId: "" -);`, - roleCheck: `MembershipList response = await teams.ListMemberships(teamId: ""); - -var userMembership = response.Memberships.FirstOrDefault( - m => m.UserId == "" -); - -if (userMembership == null || !userMembership.Roles.Contains("admin")) { - throw new Exception("Insufficient permissions"); -}` - }, - swift: { - avoidUserPermissions: `// DON'T do this for multi-tenant apps -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let tablesDB = TablesDB(client) - -try await tablesDB.createRow( - databaseId: "", - tableId: "", - rowId: "", - data: ["title": "My Row"], - permissions: [ - Permission.read(Role.user("")), - Permission.write(Role.user("")) - ] -)`, - preferTeamPermissions: `// DO this for multi-tenant apps -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let tablesDB = TablesDB(client) - -try await tablesDB.createRow( - databaseId: "", - tableId: "", - rowId: "", - data: ["title": "My Row"], - permissions: [ - Permission.read(Role.team("", "owner")), - Permission.read(Role.team("", "admin")), - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "owner")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")) - ] -)`, - createTeam: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let teams = Teams(client) - -// Create a team when a new tenant/organization signs up -let team = try await teams.create( - teamId: "", - name: "", - roles: ["owner", "admin", "member"] // optional -)`, - createMembershipEmail: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let teams = Teams(client) - -// Send team invitation (email-based) -let invite = try await teams.createMembership( - teamId: "", - roles: ["admin", "member"], - email: "user@example.com", - url: "https://yourapp.com/accept-invite" -)`, - createMembershipUserId: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setKey("") // Server SDK - -let teams = Teams(client) - -// Or invite by user ID (if user already exists) -let membership = try await teams.createMembership( - teamId: "", - roles: ["admin", "member"], - userId: "" -)`, - listMemberships: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let teams = Teams(client) - -// Get all members of a team -let response = try await teams.listMemberships(teamId: "") - -// Access member data -for membership in response.memberships { - print(membership.userId) - print(membership.roles) // Array of role strings - print(membership.userName ?? "") - print(membership.userEmail ?? "") -}`, - updateMembership: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let teams = Teams(client) - -// Update a member's roles (only team owners/admins can do this) -try await teams.updateMembership( - teamId: "", - membershipId: "", - roles: ["admin", "member"] -)`, - deleteMembership: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let teams = Teams(client) - -// Remove a member from a team -try await teams.deleteMembership( - teamId: "", - membershipId: "" -)`, - listTeams: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let teams = Teams(client) - -// List all teams the current user belongs to -let response = try await teams.list() - -for team in response.teams { - print(team.id) - print(team.name) -}`, - getUserRole: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let teams = Teams(client) - -// Get membership details for current user in a specific team -let response = try await teams.listMemberships(teamId: "") - -let userMembership = response.memberships.first { $0.userId == "" } - -if let membership = userMembership { - print(membership.roles) // ['owner', 'admin', etc.] - let hasAdminRole = membership.roles.contains("admin") -}`, - createRow: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let tablesDB = TablesDB(client) - -// Create row with team-based permissions -try await tablesDB.createRow( - databaseId: "", - tableId: "", - rowId: "", - data: [ - "title": "My Row", - "teamId": "", // Always store teamId for querying - ], - permissions: [ - Permission.read(Role.team("", "owner")), - Permission.read(Role.team("", "admin")), - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "owner")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")), - Permission.delete(Role.team("", "admin")) - ] -)`, - createTable: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setKey("") // Server SDK requires API key - -let tablesDB = TablesDB(client) - -// Create table with team-based permissions -try await tablesDB.createTable( - databaseId: "", - tableId: "", - name: "", - permissions: [ - Permission.create(Role.team("", "member")), - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")) - ] -)`, - listRows: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let tablesDB = TablesDB(client) - -// ALWAYS filter by teamId to ensure tenant isolation -let response = try await tablesDB.listRows( - databaseId: "", - tableId: "", - queries: [ - Query.equal("teamId", ""), // Critical: filter by team - Query.orderDesc("$createdAt"), - Query.limit(25) - ] -)`, - createFile: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let storage = Storage(client) - -// Create file with team-based permissions -try await storage.createFile( - bucketId: "", - fileId: "", - file: fileInput, - permissions: [ - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")) - ] -)`, - teamCreationFlow: `// When user creates account/organization -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let teams = Teams(client) - -let team = try await teams.create( - teamId: ID.unique(), - name: "Company Name" -) - -// Make creator an owner -try await teams.createMembership( - teamId: team.id, - roles: ["owner"], - userId: "" -)`, - inviteFlow: `// Owner/admin invites new member -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let teams = Teams(client) - -let invite = try await teams.createMembership( - teamId: "", - roles: ["member"], // Default role - email: "user@example.com", - url: "https://yourapp.com/accept-invite" -) -// User receives email, clicks link, accepts invitation`, - memberListUI: `// Display all team members with their roles -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let teams = Teams(client) - -let response = try await teams.listMemberships(teamId: "") -// Show list with role badges and action buttons`, - roleChange: `// Admin/owner changes member role -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let teams = Teams(client) - -try await teams.updateMembership( - teamId: "", - membershipId: "", - roles: ["admin"] -)`, - memberRemoval: `// Remove member (with confirmation) -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -let teams = Teams(client) - -try await teams.deleteMembership( - teamId: "", - membershipId: "" -)`, - roleCheck: `let response = try await teams.listMemberships(teamId: "") - -let userMembership = response.memberships.first { $0.userId == "" } - -if userMembership == nil || !userMembership!.roles.contains("admin") { - throw NSError(domain: "Appwrite", code: 403, userInfo: [NSLocalizedDescriptionKey: "Insufficient permissions"]) -}` - }, - kotlin: { - avoidUserPermissions: `// DON'T do this for multi-tenant apps -import io.appwrite.Client -import io.appwrite.services.TablesDB -import io.appwrite.models.Permission -import io.appwrite.models.Role - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val tablesDB = TablesDB(client) - -tablesDB.createRow( - databaseId = "", - tableId = "", - rowId = "", - data = mapOf("title" to "My Row"), - permissions = listOf( - Permission.read(Role.user("")), - Permission.write(Role.user("")) - ) -)`, - preferTeamPermissions: `// DO this for multi-tenant apps -import io.appwrite.Client -import io.appwrite.services.TablesDB -import io.appwrite.models.Permission -import io.appwrite.models.Role - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val tablesDB = TablesDB(client) - -tablesDB.createRow( - databaseId = "", - tableId = "", - rowId = "", - data = mapOf("title" to "My Row"), - permissions = listOf( - Permission.read(Role.team("", "owner")), - Permission.read(Role.team("", "admin")), - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "owner")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")) - ) -)`, - createTeam: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val teams = Teams(client) - -// Create a team when a new tenant/organization signs up -teams.create( - "", // teamId - "", // name - listOf("owner", "admin", "member") // roles (optional) -)`, - createMembershipEmail: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val teams = Teams(client) - -// Send team invitation (email-based) -teams.createMembership( - "", // teamId - listOf("admin", "member"), // roles - "user@example.com", // email - null, // userId (optional) - null, // phone (optional) - "https://yourapp.com/accept-invite", // url - null // name (optional) -)`, - createMembershipUserId: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setKey("") // Server SDK - -val teams = Teams(client) - -// Or invite by user ID (if user already exists) -teams.createMembership( - "", // teamId - listOf("admin", "member"), // roles - null, // email - "", // userId - null, // phone - null, // url - null // name -)`, - listMemberships: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val teams = Teams(client) - -// Get all members of a team -val response = teams.listMemberships( - "", // teamId - listOf(), // queries (optional) - null // search (optional) -) - -// Access member data -response.memberships.forEach { membership -> - println(membership.userId) - println(membership.roles) // Array of role strings - println(membership.userName) - println(membership.userEmail) -}`, - updateMembership: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val teams = Teams(client) - -// Update a member's roles (only team owners/admins can do this) -teams.updateMembership( - "", // teamId - "", // membershipId - listOf("admin", "member") // roles -)`, - deleteMembership: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val teams = Teams(client) - -// Remove a member from a team -teams.deleteMembership( - "", // teamId - "" // membershipId -)`, - listTeams: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val teams = Teams(client) - -// List all teams the current user belongs to -val response = teams.list( - listOf(), // queries (optional) - null // search (optional) -) - -response.teams.forEach { team -> - println(team.id) - println(team.name) -}`, - getUserRole: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val teams = Teams(client) - -// Get membership details for current user in a specific team -val response = teams.listMemberships("") - -val userMembership = response.memberships.find { - it.userId == "" -} - -if (userMembership != null) { - println(userMembership.roles) // ['owner', 'admin', etc.] - val hasAdminRole = userMembership.roles.contains("admin") -}`, - createRow: `import io.appwrite.Client -import io.appwrite.services.TablesDB -import io.appwrite.models.Permission -import io.appwrite.models.Role - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val tablesDB = TablesDB(client) - -// Create row with team-based permissions -tablesDB.createRow( - databaseId = "", - tableId = "", - rowId = "", - data = mapOf( - "title" to "My Row", - "teamId" to "", // Always store teamId for querying - ), - permissions = listOf( - Permission.read(Role.team("", "owner")), - Permission.read(Role.team("", "admin")), - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "owner")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")), - Permission.delete(Role.team("", "admin")) - ) -)`, - createTable: `import io.appwrite.Client -import io.appwrite.services.TablesDB -import io.appwrite.models.Permission -import io.appwrite.models.Role - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setKey("") // Server SDK requires API key - -val tablesDB = TablesDB(client) - -// Create table with team-based permissions -tablesDB.createTable( - databaseId = "", - tableId = "", - name = "", - permissions = listOf( - Permission.create(Role.team("", "member")), - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")) - ) -)`, - listRows: `import io.appwrite.Client -import io.appwrite.services.TablesDB -import io.appwrite.Query - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val tablesDB = TablesDB(client) - -// ALWAYS filter by teamId to ensure tenant isolation -val response = tablesDB.listRows( - databaseId = "", - tableId = "", - queries = listOf( - Query.equal("teamId", ""), // Critical: filter by team - Query.orderDesc("$createdAt"), - Query.limit(25) - ) -)`, - createFile: `import io.appwrite.Client -import io.appwrite.services.Storage -import io.appwrite.models.Permission -import io.appwrite.models.Role - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val storage = Storage(client) - -// Create file with team-based permissions -storage.createFile( - bucketId = "", - fileId = "", - file = fileInput, - permissions = listOf( - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")) - ) -)`, - teamCreationFlow: `// When user creates account/organization -import io.appwrite.Client -import io.appwrite.services.Teams -import io.appwrite.ID - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val teams = Teams(client) - -val team = teams.create( - ID.unique(), - "Company Name" -) - -// Make creator an owner -teams.createMembership( - team.id, // teamId - listOf("owner"), // roles - null, // email - "", // userId - null, // phone - null, // url - null // name -)`, - inviteFlow: `// Owner/admin invites new member -import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val teams = Teams(client) - -teams.createMembership( - "", // teamId - listOf("member"), // Default role - "user@example.com", // email - null, // userId - null, // phone - "https://yourapp.com/accept-invite", // url - null // name -) -// User receives email, clicks link, accepts invitation`, - memberListUI: `// Display all team members with their roles -import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val teams = Teams(client) - -val response = teams.listMemberships("") -// Show list with role badges and action buttons`, - roleChange: `// Admin/owner changes member role -import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val teams = Teams(client) - -teams.updateMembership( - "", // teamId - "", // membershipId - listOf("admin") // roles -)`, - memberRemoval: `// Remove member (with confirmation) -import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setSession("") - -val teams = Teams(client) - -teams.deleteMembership( - "", // teamId - "" // membershipId -)`, - roleCheck: `val response = teams.listMemberships("") - -val userMembership = response.memberships.find { - it.userId == "" -} - -if (userMembership == null || !userMembership.roles.contains("admin")) { - throw Exception("Insufficient permissions") -}` - }, - apple: { - avoidUserPermissions: `// DON'T do this for multi-tenant apps -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let tablesDB = TablesDB(client) - -let row = try await tablesDB.createRow( - databaseId: "", - tableId: "", - rowId: "", - data: ["title": "My Row"], - permissions: [ - Permission.read(Role.user("")), - Permission.write(Role.user("")) - ] -)`, - preferTeamPermissions: `// DO this for multi-tenant apps -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let tablesDB = TablesDB(client) - -let row = try await tablesDB.createRow( - databaseId: "", - tableId: "", - rowId: "", - data: ["title": "My Row"], - permissions: [ - Permission.read(Role.team("", "owner")), - Permission.read(Role.team("", "admin")), - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "owner")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")) - ] -)`, - createTeam: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let teams = Teams(client) - -// Create a team when a new tenant/organization signs up -let team = try await teams.create( - teamId: "", - name: "", - roles: ["owner", "admin", "member"] // optional -)`, - createMembershipEmail: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let teams = Teams(client) - -// Send team invitation (email-based) -let membership = try await teams.createMembership( - teamId: "", - roles: ["admin", "member"], - email: "user@example.com", - url: "https://yourapp.com/accept-invite" -)`, - createMembershipUserId: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let teams = Teams(client) - -// Or invite by user ID (if user already exists) -let membership = try await teams.createMembership( - teamId: "", - roles: ["admin", "member"], - userId: "" -)`, - listMemberships: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let teams = Teams(client) - -// Get all members of a team -let response = try await teams.listMemberships( - teamId: "" -) - -// Access member data -for membership in response.memberships { - print(membership.userId) - print(membership.roles) // Array of role strings - print(membership.userName ?? "") - print(membership.userEmail ?? "") -}`, - updateMembership: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let teams = Teams(client) - -// Update a member's roles (only team owners/admins can do this) -let membership = try await teams.updateMembership( - teamId: "", - membershipId: "", - roles: ["admin", "member"] -)`, - deleteMembership: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let teams = Teams(client) - -// Remove a member from a team -try await teams.deleteMembership( - teamId: "", - membershipId: "" -)`, - listTeams: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let teams = Teams(client) - -// List all teams the current user belongs to -let response = try await teams.list() - -for team in response.teams { - print(team.id) - print(team.name) -}`, - getUserRole: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let teams = Teams(client) - -// Get membership details for current user in a specific team -let response = try await teams.listMemberships(teamId: "") - -let userMembership = response.memberships.first { $0.userId == "" } - -if let membership = userMembership { - print(membership.roles) // ['owner', 'admin', etc.] - let hasAdminRole = membership.roles.contains("admin") -}`, - createRow: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let tablesDB = TablesDB(client) - -// Create row with team-based permissions -let row = try await tablesDB.createRow( - databaseId: "", - tableId: "", - rowId: "", - data: [ - "title": "My Row", - "teamId": "", // Always store teamId for querying - ], - permissions: [ - Permission.read(Role.team("", "owner")), - Permission.read(Role.team("", "admin")), - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "owner")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")), - Permission.delete(Role.team("", "admin")) - ] -)`, - createTable: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setKey("") // Server SDK requires API key - -let tablesDB = TablesDB(client) - -// Create table with team-based permissions -let table = try await tablesDB.createTable( - databaseId: "", - tableId: "", - name: "", - permissions: [ - Permission.create(Role.team("", "member")), - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")) - ] -)`, - listRows: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let tablesDB = TablesDB(client) - -// ALWAYS filter by teamId to ensure tenant isolation -let response = try await tablesDB.listRows( - databaseId: "", - tableId: "", - queries: [ - Query.equal("teamId", ""), // Critical: filter by team - Query.orderDesc("$createdAt"), - Query.limit(25) - ] -)`, - createFile: `import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let storage = Storage(client) - -// Create file with team-based permissions -let file = try await storage.createFile( - bucketId: "", - fileId: "", - file: fileInput, - permissions: [ - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")) - ] -)`, - teamCreationFlow: `// When user creates account/organization -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let teams = Teams(client) - -let team = try await teams.create( - teamId: ID.unique(), - name: "Company Name" -) - -// Make creator an owner -try await teams.createMembership( - teamId: team.id, - roles: ["owner"], - userId: "" -)`, - inviteFlow: `// Owner/admin invites new member -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let teams = Teams(client) - -let invite = try await teams.createMembership( - teamId: "", - roles: ["member"], // Default role - email: "user@example.com", - url: "https://yourapp.com/accept-invite" -) -// User receives email, clicks link, accepts invitation`, - memberListUI: `// Display all team members with their roles -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let teams = Teams(client) - -let response = try await teams.listMemberships(teamId: "") -// Show list with role badges and action buttons`, - roleChange: `// Admin/owner changes member role -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let teams = Teams(client) - -let membership = try await teams.updateMembership( - teamId: "", - membershipId: "", - roles: ["admin"] -)`, - memberRemoval: `// Remove member (with confirmation) -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let teams = Teams(client) - -try await teams.deleteMembership( - teamId: "", - membershipId: "" -)`, - roleCheck: `let response = try await teams.listMemberships(teamId: "") - -let userMembership = response.memberships.first { $0.userId == "" } - -if userMembership == nil || !userMembership!.roles.contains("admin") { - throw NSError(domain: "Appwrite", code: 403, userInfo: [NSLocalizedDescriptionKey: "Insufficient permissions"]) -}` - }, - android: { - avoidUserPermissions: `// DON'T do this for multi-tenant apps -import io.appwrite.Client -import io.appwrite.services.TablesDB -import io.appwrite.models.Permission -import io.appwrite.models.Role - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val tablesDB = TablesDB(client) - -tablesDB.createRow( - databaseId = "", - tableId = "", - rowId = "", - data = mapOf("title" to "My Row"), - permissions = listOf( - Permission.read(Role.user("")), - Permission.write(Role.user("")) - ) -)`, - preferTeamPermissions: `// DO this for multi-tenant apps -import io.appwrite.Client -import io.appwrite.services.TablesDB -import io.appwrite.models.Permission -import io.appwrite.models.Role - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val tablesDB = TablesDB(client) - -tablesDB.createRow( - databaseId = "", - tableId = "", - rowId = "", - data = mapOf("title" to "My Row"), - permissions = listOf( - Permission.read(Role.team("", "owner")), - Permission.read(Role.team("", "admin")), - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "owner")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")) - ) -)`, - createTeam: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val teams = Teams(client) - -// Create a team when a new tenant/organization signs up -teams.create( - "", // teamId - "", // name - listOf("owner", "admin", "member") // roles (optional) -)`, - createMembershipEmail: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val teams = Teams(client) - -// Send team invitation (email-based) -teams.createMembership( - "", // teamId - listOf("admin", "member"), // roles - "user@example.com", // email - null, // userId (optional) - null, // phone (optional) - "https://yourapp.com/accept-invite", // url - null // name (optional) -)`, - createMembershipUserId: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val teams = Teams(client) - -// Or invite by user ID (if user already exists) -teams.createMembership( - "", // teamId - listOf("admin", "member"), // roles - null, // email - "", // userId - null, // phone - null, // url - null // name -)`, - listMemberships: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val teams = Teams(client) - -// Get all members of a team -val response = teams.listMemberships( - "", // teamId - listOf(), // queries (optional) - null // search (optional) -) - -// Access member data -response.memberships.forEach { membership -> - Log.d("Appwrite", membership.userId) - Log.d("Appwrite", membership.roles.toString()) // Array of role strings - Log.d("Appwrite", membership.userName ?: "") - Log.d("Appwrite", membership.userEmail ?: "") -}`, - updateMembership: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val teams = Teams(client) - -// Update a member's roles (only team owners/admins can do this) -teams.updateMembership( - "", // teamId - "", // membershipId - listOf("admin", "member") // roles -)`, - deleteMembership: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val teams = Teams(client) - -// Remove a member from a team -teams.deleteMembership( - "", // teamId - "" // membershipId -)`, - listTeams: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val teams = Teams(client) - -// List all teams the current user belongs to -val response = teams.list( - listOf(), // queries (optional) - null // search (optional) -) - -response.teams.forEach { team -> - Log.d("Appwrite", team.id) - Log.d("Appwrite", team.name) -}`, - getUserRole: `import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val teams = Teams(client) - -// Get membership details for current user in a specific team -val response = teams.listMemberships("") - -val userMembership = response.memberships.find { - it.userId == "" -} - -if (userMembership != null) { - Log.d("Appwrite", userMembership.roles.toString()) // ['owner', 'admin', etc.] - val hasAdminRole = userMembership.roles.contains("admin") -}`, - createRow: `import io.appwrite.Client -import io.appwrite.services.TablesDB -import io.appwrite.models.Permission -import io.appwrite.models.Role - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val tablesDB = TablesDB(client) - -// Create row with team-based permissions -tablesDB.createRow( - databaseId = "", - tableId = "", - rowId = "", - data = mapOf( - "title" to "My Row", - "teamId" to "", // Always store teamId for querying - ), - permissions = listOf( - Permission.read(Role.team("", "owner")), - Permission.read(Role.team("", "admin")), - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "owner")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")), - Permission.delete(Role.team("", "admin")) - ) -)`, - createTable: `import io.appwrite.Client -import io.appwrite.services.TablesDB -import io.appwrite.models.Permission -import io.appwrite.models.Role - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setKey("") // Server SDK requires API key - -val tablesDB = TablesDB(client) - -// Create table with team-based permissions -tablesDB.createTable( - databaseId = "", - tableId = "", - name = "", - permissions = listOf( - Permission.create(Role.team("", "member")), - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")) - ) -)`, - listRows: `import io.appwrite.Client -import io.appwrite.services.TablesDB -import io.appwrite.Query - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val tablesDB = TablesDB(client) - -// ALWAYS filter by teamId to ensure tenant isolation -val response = tablesDB.listRows( - databaseId = "", - tableId = "", - queries = listOf( - Query.equal("teamId", ""), // Critical: filter by team - Query.orderDesc("$createdAt"), - Query.limit(25) - ) -)`, - createFile: `import io.appwrite.Client -import io.appwrite.services.Storage -import io.appwrite.models.Permission -import io.appwrite.models.Role - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val storage = Storage(client) - -// Create file with team-based permissions -storage.createFile( - bucketId = "", - fileId = "", - file = fileInput, - permissions = listOf( - Permission.read(Role.team("", "member")), - Permission.update(Role.team("", "admin")), - Permission.delete(Role.team("", "owner")) - ) -)`, - teamCreationFlow: `// When user creates account/organization -import io.appwrite.Client -import io.appwrite.services.Teams -import io.appwrite.ID - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val teams = Teams(client) - -val team = teams.create( - ID.unique(), - "Company Name" -) - -// Make creator an owner -teams.createMembership( - team.id, // teamId - listOf("owner"), // roles - null, // email - "", // userId - null, // phone - null, // url - null // name -)`, - inviteFlow: `// Owner/admin invites new member -import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val teams = Teams(client) - -teams.createMembership( - "", // teamId - listOf("member"), // Default role - "user@example.com", // email - null, // userId - null, // phone - "https://yourapp.com/accept-invite", // url - null // name -) -// User receives email, clicks link, accepts invitation`, - memberListUI: `// Display all team members with their roles -import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val teams = Teams(client) - -val response = teams.listMemberships("") -// Show list with role badges and action buttons`, - roleChange: `// Admin/owner changes member role -import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val teams = Teams(client) - -teams.updateMembership( - "", // teamId - "", // membershipId - listOf("admin") // roles -)`, - memberRemoval: `// Remove member (with confirmation) -import io.appwrite.Client -import io.appwrite.services.Teams - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val teams = Teams(client) - -teams.deleteMembership( - "", // teamId - "" // membershipId -)`, - roleCheck: `val response = teams.listMemberships("") - -val userMembership = response.memberships.find { - it.userId == "" -} - -if (userMembership == null || !userMembership.roles.contains("admin")) { - throw Exception("Insufficient permissions") -}` - }, - flutter: { - avoidUserPermissions: `// DON'T do this for multi-tenant apps -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final tablesDB = TablesDB(client); - -final row = await tablesDB.createRow( - databaseId: '', - tableId: '', - rowId: '', - data: {'title': 'My Row'}, - permissions: [ - Permission.read(Role.user('')), - Permission.write(Role.user('')) - ] -);`, - preferTeamPermissions: `// DO this for multi-tenant apps -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final tablesDB = TablesDB(client); - -final row = await tablesDB.createRow( - databaseId: '', - tableId: '', - rowId: '', - data: {'title': 'My Row'}, - permissions: [ - Permission.read(Role.team('', 'owner')), - Permission.read(Role.team('', 'admin')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'owner')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -);`, - createTeam: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// Create a team when a new tenant/organization signs up -final team = await teams.create( - teamId: '', - name: '', - roles: ['owner', 'admin', 'member'] // optional -);`, - createMembershipEmail: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// Send team invitation (email-based) -final invite = await teams.createMembership( - teamId: '', - roles: ['admin', 'member'], - email: 'user@example.com', - url: 'https://yourapp.com/accept-invite' -);`, - createMembershipUserId: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// Or invite by user ID (if user already exists) -final membership = await teams.createMembership( - teamId: '', - roles: ['admin', 'member'], - userId: '' -);`, - listMemberships: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// Get all members of a team -final response = await teams.listMemberships(teamId: ''); - -// Access member data -for (final membership in response.memberships) { - print(membership.userId); - print(membership.roles); // Array of role strings - print(membership.userName); - print(membership.userEmail); -}`, - updateMembership: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// Update a member's roles (only team owners/admins can do this) -await teams.updateMembership( - teamId: '', - membershipId: '', - roles: ['admin', 'member'] -);`, - deleteMembership: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// Remove a member from a team -await teams.deleteMembership( - teamId: '', - membershipId: '' +/** + * Permission patterns for each SDK + * Only includes the essential anti-pattern vs correct pattern examples + */ +const permissionPatterns = { + javascript: { + avoidUserPermissions: `// DON'T: User-specific permissions don't scale +Permission.read(Role.user('')) +Permission.write(Role.user(''))`, + + preferTeamPermissions: `// DO: Team-based permissions scale with your organization +Permission.read(Role.team('', 'member')) +Permission.update(Role.team('', 'admin')) +Permission.delete(Role.team('', 'owner'))`, + + queryWithTeamId: `// ALWAYS filter by teamId for tenant isolation +const response = await tablesDB.listRows( + '', + '', + [ + Query.equal('teamId', ''), // Critical for isolation + Query.orderDesc('$createdAt'), + Query.limit(25) + ] );`, - listTeams: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// List all teams the current user belongs to -final response = await teams.list(); - -for (final team in response.teams) { - print(team.id); - print(team.name); -}`, - getUserRole: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); + + roleCheck: `// Verify permissions before sensitive operations +const memberships = await teams.listMemberships(''); +const membership = memberships.memberships.find(m => m.userId === userId); -final teams = Teams(client); - -// Get membership details for current user in a specific team -final response = await teams.listMemberships(teamId: ''); - -final userMembership = response.memberships.firstWhere( - (m) => m.userId == '', - orElse: () => null, -); - -if (userMembership != null) { - print(userMembership.roles); // ['owner', 'admin', etc.] - final hasAdminRole = userMembership.roles.contains('admin'); -}`, - createRow: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final tablesDB = TablesDB(client); - -// Create row with team-based permissions -final row = await tablesDB.createRow( - databaseId: '', - tableId: '', - rowId: '', - data: { - 'title': 'My Row', - 'teamId': '', // Always store teamId for querying - }, - permissions: [ - Permission.read(Role.team('', 'owner')), - Permission.read(Role.team('', 'admin')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'owner')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')), - Permission.delete(Role.team('', 'admin')) +if (!membership?.roles.includes('admin')) { + throw new Error('Insufficient permissions'); +}` + }, + + python: { + avoidUserPermissions: `# DON'T: User-specific permissions don't scale +Permission.read(Role.user('')) +Permission.write(Role.user(''))`, + + preferTeamPermissions: `# DO: Team-based permissions scale with your organization +Permission.read(Role.team('', 'member')) +Permission.update(Role.team('', 'admin')) +Permission.delete(Role.team('', 'owner'))`, + + queryWithTeamId: `# ALWAYS filter by teamId for tenant isolation +response = tables_db.list_rows( + database_id='', + table_id='', + queries=[ + Query.equal('teamId', ''), # Critical for isolation + Query.order_desc('$createdAt'), + Query.limit(25) ] -);`, - createTable: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject('') - .setKey(''); // Server SDK requires API key - -final tablesDB = TablesDB(client); +)`, + + roleCheck: `# Verify permissions before sensitive operations +memberships = teams.list_memberships('') +membership = next((m for m in memberships.memberships if m.user_id == user_id), None) -// Create table with team-based permissions -final table = await tablesDB.createTable( +if not membership or 'admin' not in membership.roles: + raise Exception('Insufficient permissions')` + }, + + php: { + avoidUserPermissions: `// DON'T: User-specific permissions don't scale +Permission::read(Role::user('')) +Permission::write(Role::user(''))`, + + preferTeamPermissions: `// DO: Team-based permissions scale with your organization +Permission::read(Role::team('', 'member')) +Permission::update(Role::team('', 'admin')) +Permission::delete(Role::team('', 'owner'))`, + + queryWithTeamId: `// ALWAYS filter by teamId for tenant isolation +$response = $tablesDB->listRows( databaseId: '', tableId: '', - name: '', - permissions: [ - Permission.create(Role.team('', 'member')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) + queries: [ + Query::equal('teamId', ''), // Critical for isolation + Query::orderDesc('$createdAt'), + Query::limit(25) ] );`, - listRows: `import 'package:appwrite/appwrite.dart'; + + roleCheck: `// Verify permissions before sensitive operations +$memberships = $teams->listMemberships(''); +$membership = array_filter($memberships->memberships, fn($m) => $m->userId === $userId); -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final tablesDB = TablesDB(client); +if (empty($membership) || !in_array('admin', current($membership)->roles)) { + throw new Exception('Insufficient permissions'); +}` + }, + + kotlin: { + avoidUserPermissions: `// DON'T: User-specific permissions don't scale +Permission.read(Role.user("")) +Permission.write(Role.user(""))`, + + preferTeamPermissions: `// DO: Team-based permissions scale with your organization +Permission.read(Role.team("", "member")) +Permission.update(Role.team("", "admin")) +Permission.delete(Role.team("", "owner"))`, + + queryWithTeamId: `// ALWAYS filter by teamId for tenant isolation +val response = tablesDB.listRows( + databaseId = "", + tableId = "", + queries = listOf( + Query.equal("teamId", ""), // Critical for isolation + Query.orderDesc("\$createdAt"), + Query.limit(25) + ) +)`, + + roleCheck: `// Verify permissions before sensitive operations +val memberships = teams.listMemberships("") +val membership = memberships.memberships.find { it.userId == userId } -// ALWAYS filter by teamId to ensure tenant isolation -final response = await tablesDB.listRows( - databaseId: '', - tableId: '', +if (membership == null || "admin" !in membership.roles) { + throw Exception("Insufficient permissions") +}` + }, + + swift: { + avoidUserPermissions: `// DON'T: User-specific permissions don't scale +Permission.read(Role.user("")) +Permission.write(Role.user(""))`, + + preferTeamPermissions: `// DO: Team-based permissions scale with your organization +Permission.read(Role.team("", "member")) +Permission.update(Role.team("", "admin")) +Permission.delete(Role.team("", "owner"))`, + + queryWithTeamId: `// ALWAYS filter by teamId for tenant isolation +let response = try await tablesDB.listRows( + databaseId: "", + tableId: "", queries: [ - Query.equal('teamId', ''), // Critical: filter by team - Query.orderDesc('\$createdAt'), + Query.equal("teamId", ""), // Critical for isolation + Query.orderDesc("$createdAt"), Query.limit(25) ] -);`, - createFile: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final storage = Storage(client); - -// Create file with team-based permissions -final file = await storage.createFile( - bucketId: '', - fileId: '', - file: fileInput, - permissions: [ - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -);`, - teamCreationFlow: `// When user creates account/organization -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -final team = await teams.create( - teamId: ID.unique(), - name: 'Company Name' -); - -// Make creator an owner -await teams.createMembership( - teamId: team.id, - roles: ['owner'], - userId: '' -);`, - inviteFlow: `// Owner/admin invites new member -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -final invite = await teams.createMembership( - teamId: '', - roles: ['member'], // Default role - email: 'user@example.com', - url: 'https://yourapp.com/accept-invite' -); -// User receives email, clicks link, accepts invitation`, - memberListUI: `// Display all team members with their roles -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -final response = await teams.listMemberships(teamId: ''); -// Show list with role badges and action buttons`, - roleChange: `// Admin/owner changes member role -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -await teams.updateMembership( - teamId: '', - membershipId: '', - roles: ['admin'] -);`, - memberRemoval: `// Remove member (with confirmation) -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -await teams.deleteMembership( - teamId: '', - membershipId: '' -);`, - roleCheck: `final response = await teams.listMemberships(teamId: ''); - -final userMembership = response.memberships.firstWhere( - (m) => m.userId == '', - orElse: () => null, -); - -if (userMembership == null || !userMembership.roles.contains('admin')) { - throw Exception('Insufficient permissions'); +)`, + + roleCheck: `// Verify permissions before sensitive operations +let memberships = try await teams.listMemberships("") +guard let membership = memberships.memberships.first(where: { $0.userId == userId }), + membership.roles.contains("admin") else { + throw AppError.insufficientPermissions }` }, + dart: { - // Dart uses the same syntax as Flutter - avoidUserPermissions: `// DON'T do this for multi-tenant apps -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final tablesDB = TablesDB(client); - -final row = await tablesDB.createRow( - databaseId: '', - tableId: '', - rowId: '', - data: {'title': 'My Row'}, - permissions: [ - Permission.read(Role.user('')), - Permission.write(Role.user('')) - ] -);`, - preferTeamPermissions: `// DO this for multi-tenant apps -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final tablesDB = TablesDB(client); - -final row = await tablesDB.createRow( - databaseId: '', - tableId: '', - rowId: '', - data: {'title': 'My Row'}, - permissions: [ - Permission.read(Role.team('', 'owner')), - Permission.read(Role.team('', 'admin')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'owner')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -);`, - createTeam: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// Create a team when a new tenant/organization signs up -final team = await teams.create( - teamId: '', - name: '', - roles: ['owner', 'admin', 'member'] // optional -);`, - createMembershipEmail: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// Send team invitation (email-based) -final invite = await teams.createMembership( - teamId: '', - roles: ['admin', 'member'], - email: 'user@example.com', - url: 'https://yourapp.com/accept-invite' -);`, - createMembershipUserId: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// Or invite by user ID (if user already exists) -final membership = await teams.createMembership( - teamId: '', - roles: ['admin', 'member'], - userId: '' -);`, - listMemberships: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// Get all members of a team -final response = await teams.listMemberships(teamId: ''); - -// Access member data -for (final membership in response.memberships) { - print(membership.userId); - print(membership.roles); // Array of role strings - print(membership.userName); - print(membership.userEmail); -}`, - updateMembership: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// Update a member's roles (only team owners/admins can do this) -await teams.updateMembership( - teamId: '', - membershipId: '', - roles: ['admin', 'member'] -);`, - deleteMembership: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// Remove a member from a team -await teams.deleteMembership( - teamId: '', - membershipId: '' -);`, - listTeams: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// List all teams the current user belongs to -final response = await teams.list(); - -for (final team in response.teams) { - print(team.id); - print(team.name); -}`, - getUserRole: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -// Get membership details for current user in a specific team -final response = await teams.listMemberships(teamId: ''); - -final userMembership = response.memberships.firstWhere( - (m) => m.userId == '', - orElse: () => null, -); - -if (userMembership != null) { - print(userMembership.roles); // ['owner', 'admin', etc.] - final hasAdminRole = userMembership.roles.contains('admin'); -}`, - createRow: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final tablesDB = TablesDB(client); - -// Create row with team-based permissions -final row = await tablesDB.createRow( - databaseId: '', - tableId: '', - rowId: '', - data: { - 'title': 'My Row', - 'teamId': '', // Always store teamId for querying - }, - permissions: [ - Permission.read(Role.team('', 'owner')), - Permission.read(Role.team('', 'admin')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'owner')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')), - Permission.delete(Role.team('', 'admin')) - ] -);`, - createTable: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject('') - .setKey(''); // Server SDK requires API key - -final tablesDB = TablesDB(client); - -// Create table with team-based permissions -final table = await tablesDB.createTable( - databaseId: '', - tableId: '', - name: '', - permissions: [ - Permission.create(Role.team('', 'member')), - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -);`, - listRows: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final tablesDB = TablesDB(client); - -// ALWAYS filter by teamId to ensure tenant isolation + avoidUserPermissions: `// DON'T: User-specific permissions don't scale +Permission.read(Role.user('')) +Permission.write(Role.user(''))`, + + preferTeamPermissions: `// DO: Team-based permissions scale with your organization +Permission.read(Role.team('', 'member')) +Permission.update(Role.team('', 'admin')) +Permission.delete(Role.team('', 'owner'))`, + + queryWithTeamId: `// ALWAYS filter by teamId for tenant isolation final response = await tablesDB.listRows( - databaseId: '', - tableId: '', - queries: [ - Query.equal('teamId', ''), // Critical: filter by team - Query.orderDesc('\$createdAt'), - Query.limit(25) - ] -);`, - createFile: `import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final storage = Storage(client); - -// Create file with team-based permissions -final file = await storage.createFile( - bucketId: '', - fileId: '', - file: fileInput, - permissions: [ - Permission.read(Role.team('', 'member')), - Permission.update(Role.team('', 'admin')), - Permission.delete(Role.team('', 'owner')) - ] -);`, - teamCreationFlow: `// When user creates account/organization -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -final team = await teams.create( - teamId: ID.unique(), - name: 'Company Name' -); - -// Make creator an owner -await teams.createMembership( - teamId: team.id, - roles: ['owner'], - userId: '' -);`, - inviteFlow: `// Owner/admin invites new member -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -final invite = await teams.createMembership( - teamId: '', - roles: ['member'], // Default role - email: 'user@example.com', - url: 'https://yourapp.com/accept-invite' + databaseId: '', + tableId: '', + queries: [ + Query.equal('teamId', ''), // Critical for isolation + Query.orderDesc('\$createdAt'), + Query.limit(25), + ], +);`, + + roleCheck: `// Verify permissions before sensitive operations +final memberships = await teams.listMemberships(''); +final membership = memberships.memberships.firstWhere( + (m) => m.userId == userId, + orElse: () => null, ); -// User receives email, clicks link, accepts invitation`, - memberListUI: `// Display all team members with their roles -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final teams = Teams(client); - -final response = await teams.listMemberships(teamId: ''); -// Show list with role badges and action buttons`, - roleChange: `// Admin/owner changes member role -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); -final teams = Teams(client); - -await teams.updateMembership( - teamId: '', - membershipId: '', - roles: ['admin'] -);`, - memberRemoval: `// Remove member (with confirmation) -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); +if (membership == null || !membership.roles.contains('admin')) { + throw Exception('Insufficient permissions'); +}` + }, + + go: { + avoidUserPermissions: `// DON'T: User-specific permissions don't scale +permission.Read(role.User("")) +permission.Write(role.User(""))`, + + preferTeamPermissions: `// DO: Team-based permissions scale with your organization +permission.Read(role.Team("", "member")) +permission.Update(role.Team("", "admin")) +permission.Delete(role.Team("", "owner"))`, + + queryWithTeamId: `// ALWAYS filter by teamId for tenant isolation +response, err := tablesDB.ListRows( + "", + "", + tablesDB.WithListRowsQueries([]string{ + query.Equal("teamId", ""), // Critical for isolation + query.OrderDesc("$createdAt"), + query.Limit(25), + }), +)`, + + roleCheck: `// Verify permissions before sensitive operations +memberships, _ := teams.ListMemberships("") +var membership *models.Membership +for _, m := range memberships.Memberships { + if m.UserId == userId { + membership = &m + break + } +} -final teams = Teams(client); +if membership == nil || !contains(membership.Roles, "admin") { + return errors.New("insufficient permissions") +}` + }, + + ruby: { + avoidUserPermissions: `# DON'T: User-specific permissions don't scale +Permission.read(Role.user('')) +Permission.write(Role.user(''))`, + + preferTeamPermissions: `# DO: Team-based permissions scale with your organization +Permission.read(Role.team('', 'member')) +Permission.update(Role.team('', 'admin')) +Permission.delete(Role.team('', 'owner'))`, + + queryWithTeamId: `# ALWAYS filter by teamId for tenant isolation +response = tables_db.list_rows( + database_id: '', + table_id: '', + queries: [ + Query.equal('teamId', ''), # Critical for isolation + Query.order_desc('$createdAt'), + Query.limit(25) + ] +)`, + + roleCheck: `# Verify permissions before sensitive operations +memberships = teams.list_memberships('') +membership = memberships.memberships.find { |m| m.user_id == user_id } -await teams.deleteMembership( - teamId: '', - membershipId: '' +unless membership&.roles&.include?('admin') + raise 'Insufficient permissions' +end` + }, + + dotnet: { + avoidUserPermissions: `// DON'T: User-specific permissions don't scale +Permission.Read(Role.User("")) +Permission.Write(Role.User(""))`, + + preferTeamPermissions: `// DO: Team-based permissions scale with your organization +Permission.Read(Role.Team("", "member")) +Permission.Update(Role.Team("", "admin")) +Permission.Delete(Role.Team("", "owner"))`, + + queryWithTeamId: `// ALWAYS filter by teamId for tenant isolation +var response = await tablesDB.ListRows( + databaseId: "", + tableId: "", + queries: new List { + Query.Equal("teamId", ""), // Critical for isolation + Query.OrderDesc("$createdAt"), + Query.Limit(25) + } );`, - roleCheck: `final response = await teams.listMemberships(teamId: ''); + + roleCheck: `// Verify permissions before sensitive operations +var memberships = await teams.ListMemberships(""); +var membership = memberships.Memberships.FirstOrDefault(m => m.UserId == userId); -final userMembership = response.memberships.firstWhere( - (m) => m.userId == '', - orElse: () => null, -); - -if (userMembership == null || !userMembership.roles.contains('admin')) { - throw Exception('Insufficient permissions'); +if (membership == null || !membership.Roles.Contains("admin")) +{ + throw new UnauthorizedAccessException("Insufficient permissions"); }` } }; /** * Get permission examples for a specific SDK - * @param {string} sdk - The SDK name (javascript, python, php, go, etc.) - * @returns {Object} Permission examples for the SDK + * @param {string} sdk - The SDK name + * @returns {Object} Permission pattern examples */ export function getPermissionExamples(sdk) { - // Map SDK names to their examples - /** @type {Record} */ + // Map SDK names to their pattern keys const sdkMap = { - javascript: permissionExamples.javascript, - 'react-native': permissionExamples['react-native'], - python: permissionExamples.python, - php: permissionExamples.php, - go: permissionExamples.go, - ruby: permissionExamples.ruby, - dotnet: permissionExamples.dotnet, - swift: permissionExamples.swift, - kotlin: permissionExamples.kotlin, - apple: permissionExamples.apple, - android: permissionExamples.android, - flutter: permissionExamples.flutter, - dart: permissionExamples.dart + javascript: 'javascript', + 'react-native': 'javascript', + python: 'python', + php: 'php', + go: 'go', + ruby: 'ruby', + dotnet: 'dotnet', + swift: 'swift', + kotlin: 'kotlin', + apple: 'swift', + android: 'kotlin', + flutter: 'dart', + dart: 'dart' }; - - return sdkMap[sdk] || permissionExamples.javascript; // Default to JavaScript + + const patternKey = sdkMap[sdk] || 'javascript'; + return permissionPatterns[patternKey] || permissionPatterns.javascript; } - diff --git a/src/lib/languages/common/products.js b/src/lib/languages/common/products.js index 3adf44b..cf610bd 100644 --- a/src/lib/languages/common/products.js +++ b/src/lib/languages/common/products.js @@ -6,103 +6,108 @@ /** * Authentication product documentation */ -export const authProductLinks = `For detailed authentication and session management instructions, see the [Authentication Quick Start Guide](https://appwrite.io/docs/products/auth/quick-start). +export const authProductLinks = `**Authentication Documentation:** + +- [Authentication Quick Start](https://appwrite.io/docs/products/auth/quick-start) - Getting started with authentication +- [Email & Password](https://appwrite.io/docs/products/auth/email-password) - Email/password authentication +- [OAuth2 Providers](https://appwrite.io/docs/products/auth/oauth2) - Social authentication (Google, GitHub, etc.) +- [Magic URL](https://appwrite.io/docs/products/auth/magic-url) - Passwordless authentication via email +- [Phone (SMS)](https://appwrite.io/docs/products/auth/phone-sms) - Phone number authentication +- [Anonymous Sessions](https://appwrite.io/docs/products/auth/anonymous) - Guest/anonymous users +- [JWT Tokens](https://appwrite.io/docs/products/auth/jwt) - JSON Web Token authentication +- [MFA/2FA](https://appwrite.io/docs/products/auth/mfa) - Multi-factor authentication +- [SSR Authentication](https://appwrite.io/docs/products/auth/server-side-rendering) - Server-side rendering auth patterns +- [Teams](https://appwrite.io/docs/products/auth/teams) - Team management and team-based permissions +- [Team Invites](https://appwrite.io/docs/products/auth/team-invites) - Inviting members to teams +- [Multi-tenancy](https://appwrite.io/docs/products/auth/multi-tenancy) - Building multi-tenant applications with teams`; -For OAuth providers and social authentication, see the [OAuth2 Documentation](https://appwrite.io/docs/products/auth/oauth2). - -For Teams management, team invitations, and team-based permissions, see the [Teams Documentation](https://appwrite.io/docs/products/auth/teams). - -For team invites and membership management, see the [Team Invites Guide](https://appwrite.io/docs/products/auth/team-invites). +/** + * Permissions product documentation + */ +export const permissionsProductLinks = `**Permissions Documentation:** -For multi-tenancy using Teams, see the [Multi-tenancy Guide](https://appwrite.io/docs/products/auth/multi-tenancy).`; +- [Permissions Overview](https://appwrite.io/docs/advanced/platform/permissions) - Understanding Appwrite's permission system +- [Role Types](https://appwrite.io/docs/advanced/platform/permissions#role-types) - Available permission roles (any, users, guests, team, member, label) +- [Permission Types](https://appwrite.io/docs/advanced/platform/permissions#permission-types) - Read, create, update, delete permissions +- [Teams](https://appwrite.io/docs/products/auth/teams) - Team-based access control +- [Multi-tenancy](https://appwrite.io/docs/products/auth/multi-tenancy) - Tenant isolation patterns`; /** * Database product documentation */ -export const databaseProductLinks = `For detailed database operations, see the [Rows Documentation](https://appwrite.io/docs/products/databases/rows). +export const databaseProductLinks = `**Database Documentation:** -For querying and filtering data, see the [Queries Guide](https://appwrite.io/docs/products/databases/queries). - -For pagination, see the [Pagination Documentation](https://appwrite.io/docs/products/databases/pagination). - -For permissions and access control, see the [Permissions Guide](https://appwrite.io/docs/products/databases/permissions). - -For transactions, see the [Transactions Documentation](https://appwrite.io/docs/products/databases/transactions).`; +- [Database Quick Start](https://appwrite.io/docs/products/databases/quick-start) - Getting started with TablesDB +- [Tables](https://appwrite.io/docs/products/databases/tables) - Creating and managing tables +- [Rows](https://appwrite.io/docs/products/databases/rows) - CRUD operations on rows +- [Queries](https://appwrite.io/docs/products/databases/queries) - Filtering, sorting, and querying data +- [Pagination](https://appwrite.io/docs/products/databases/pagination) - Paginating large datasets +- [Relationships](https://appwrite.io/docs/products/databases/relationships) - One-to-one, one-to-many, many-to-many relationships +- [Permissions](https://appwrite.io/docs/products/databases/permissions) - Row and table-level permissions +- [Transactions](https://appwrite.io/docs/products/databases/transactions) - Atomic operations`; /** * Storage product documentation */ -export const storageProductLinks = `For detailed storage operations, see the [Storage Documentation](https://appwrite.io/docs/products/storage/upload-download). - -For file uploads and downloads, see the [Upload & Download Guide](https://appwrite.io/docs/products/storage/upload-download). +export const storageProductLinks = `**Storage Documentation:** -For permissions and access control, see the [Storage Permissions Documentation](https://appwrite.io/docs/products/storage/permissions).`; +- [Storage Quick Start](https://appwrite.io/docs/products/storage/quick-start) - Getting started with storage +- [Buckets](https://appwrite.io/docs/products/storage/buckets) - Creating and managing storage buckets +- [Upload & Download](https://appwrite.io/docs/products/storage/upload-download) - File upload and download operations +- [Permissions](https://appwrite.io/docs/products/storage/permissions) - Bucket and file-level permissions +- [Images](https://appwrite.io/docs/products/storage/images) - Image manipulation and transformations`; /** * Functions product documentation */ -export const functionsProductLinks = `For detailed serverless function execution, see the [Functions Execution Documentation](https://appwrite.io/docs/products/functions/execute). +export const functionsProductLinks = `**Functions Documentation:** -For function domains and custom endpoints, see the [Functions Domains Guide](https://appwrite.io/docs/products/functions/domains). - -For event-driven function execution, see the [Events Documentation](https://appwrite.io/docs/advanced/platform/events). - -For scheduled function execution, see the [Scheduled Executions Guide](https://appwrite.io/docs/products/functions/execute#schedule).`; +- [Functions Quick Start](https://appwrite.io/docs/products/functions/quick-start) - Getting started with serverless functions +- [Develop Functions](https://appwrite.io/docs/products/functions/develop) - Writing and developing functions +- [Execute Functions](https://appwrite.io/docs/products/functions/execute) - Triggering function executions +- [Deployments](https://appwrite.io/docs/products/functions/deployments) - Deploying function code +- [Domains](https://appwrite.io/docs/products/functions/domains) - Custom domains for functions +- [Events](https://appwrite.io/docs/advanced/platform/events) - Event-driven function execution +- [Scheduled Executions](https://appwrite.io/docs/products/functions/execute#schedule) - Cron-based scheduling`; /** * Messaging product documentation */ -export const messagingProductLinks = `For detailed messaging operations, see the [Messaging Documentation](https://appwrite.io/docs/products/messaging). - -For sending push notifications, see the [Push Notifications Guide](https://appwrite.io/docs/products/messaging/send-push-notifications). - -For sending emails, see the [Email Messages Guide](https://appwrite.io/docs/products/messaging/send-email-messages). +export const messagingProductLinks = `**Messaging Documentation:** -For sending SMS messages, see the [SMS Messages Guide](https://appwrite.io/docs/products/messaging/send-sms-messages). - -For messaging providers, see the [Providers Documentation](https://appwrite.io/docs/products/messaging/providers).`; +- [Messaging Overview](https://appwrite.io/docs/products/messaging) - Getting started with messaging +- [Push Notifications](https://appwrite.io/docs/products/messaging/send-push-notifications) - Sending push notifications +- [Email Messages](https://appwrite.io/docs/products/messaging/send-email-messages) - Sending emails +- [SMS Messages](https://appwrite.io/docs/products/messaging/send-sms-messages) - Sending SMS +- [Topics](https://appwrite.io/docs/products/messaging/topics) - Managing message topics +- [Targets](https://appwrite.io/docs/products/messaging/targets) - Managing message targets +- [Providers](https://appwrite.io/docs/products/messaging/providers) - Configuring messaging providers`; /** * Sites product documentation */ -export const sitesProductLinks = `For detailed Sites hosting and deployment, see the [Sites Documentation](https://appwrite.io/docs/products/sites). - -For getting started with Sites, see the [Sites Quick Start Guide](https://appwrite.io/docs/products/sites/quick-start). - -For deploying from Git, see the [Deploy from Git Guide](https://appwrite.io/docs/products/sites/deploy-from-git). - -For deploying from CLI, see the [Deploy from CLI Guide](https://appwrite.io/docs/products/sites/deploy-from-cli). - -For manual deployments, see the [Manual Deployment Guide](https://appwrite.io/docs/products/sites/deploy-manually). - -For deployment management, see the [Deployments Documentation](https://appwrite.io/docs/products/sites/deployments). - -For custom domains, see the [Domains Documentation](https://appwrite.io/docs/products/sites/domains). - -For rendering strategies (static vs SSR), see the [Rendering Documentation](https://appwrite.io/docs/products/sites/rendering). - -For static site hosting, see the [Static Rendering Guide](https://appwrite.io/docs/products/sites/rendering/static). - -For server-side rendering, see the [SSR Rendering Guide](https://appwrite.io/docs/products/sites/rendering/ssr). - -For supported frameworks, see the [Frameworks Documentation](https://appwrite.io/docs/products/sites/frameworks). - -For instant rollbacks, see the [Instant Rollbacks Guide](https://appwrite.io/docs/products/sites/instant-rollbacks). - -For deployment previews, see the [Previews Documentation](https://appwrite.io/docs/products/sites/previews).`; +export const sitesProductLinks = `**Sites Documentation:** + +- [Sites Quick Start](https://appwrite.io/docs/products/sites/quick-start) - Getting started with Sites +- [Deploy from Git](https://appwrite.io/docs/products/sites/deploy-from-git) - Git-based deployments +- [Deploy from CLI](https://appwrite.io/docs/products/sites/deploy-from-cli) - CLI deployments +- [Deploy Manually](https://appwrite.io/docs/products/sites/deploy-manually) - Manual file uploads +- [Deployments](https://appwrite.io/docs/products/sites/deployments) - Managing deployments +- [Domains](https://appwrite.io/docs/products/sites/domains) - Custom domain configuration +- [Rendering](https://appwrite.io/docs/products/sites/rendering) - Static vs SSR rendering +- [Frameworks](https://appwrite.io/docs/products/sites/frameworks) - Supported frameworks +- [Rollbacks](https://appwrite.io/docs/products/sites/instant-rollbacks) - Instant rollbacks +- [Previews](https://appwrite.io/docs/products/sites/previews) - Deployment previews`; /** * Realtime product documentation */ -export const realtimeProductLinks = `For detailed subscriptions, see the [Realtime Documentation](https://appwrite.io/docs/products/realtime). - -For subscribing to database changes, see the [Database Subscriptions Guide](https://appwrite.io/docs/products/realtime/subscribe-to-databases). - -For subscribing to storage changes, see the [Storage Subscriptions Guide](https://appwrite.io/docs/products/realtime/subscribe-to-storage). - -For subscribing to account changes, see the [Account Subscriptions Guide](https://appwrite.io/docs/products/realtime/subscribe-to-account). - -For realtime channels and events, see the [Channels Documentation](https://appwrite.io/docs/products/realtime/channels). - -For realtime event types and payloads, see the [Events Documentation](https://appwrite.io/docs/products/realtime/events).`; +export const realtimeProductLinks = `**Realtime Documentation:** + +- [Realtime Overview](https://appwrite.io/docs/products/realtime) - Getting started with realtime +- [Database Subscriptions](https://appwrite.io/docs/products/realtime/subscribe-to-databases) - Subscribe to database changes +- [Storage Subscriptions](https://appwrite.io/docs/products/realtime/subscribe-to-storage) - Subscribe to storage changes +- [Account Subscriptions](https://appwrite.io/docs/products/realtime/subscribe-to-account) - Subscribe to account changes +- [Channels](https://appwrite.io/docs/products/realtime/channels) - Available subscription channels +- [Events](https://appwrite.io/docs/products/realtime/events) - Event types and payloads`; diff --git a/src/lib/languages/common/security.js b/src/lib/languages/common/security.js index cf84cf4..440223b 100644 --- a/src/lib/languages/common/security.js +++ b/src/lib/languages/common/security.js @@ -43,11 +43,12 @@ export const authNote = `**Authentication:** - Prefer SSR auth for better security and performance`; /** - * SSR Authentication pattern + * SSR Authentication pattern (language-agnostic explanation) + * Use this for the conceptual overview, then pair with language-specific examples */ -export const ssrAuthPattern = `**SSR Authentication Pattern:** +export const ssrAuthPatternExplanation = `**SSR Authentication Pattern:** -Server-side rendering requires using the Server SDK (node-appwrite) instead of the client SDK. +Server-side rendering requires using the Server SDK instead of the client SDK. **Authentication Flow:** 1. User credentials are sent from browser to your server @@ -63,9 +64,21 @@ Server-side rendering requires using the Server SDK (node-appwrite) instead of t - **Admin Client**: Uses API key for unauthenticated requests and session creation - **Session Client**: Uses session cookie for user-specific requests -**Creating Sessions:** -\`\`\`javascript -import { Client, Account } from "node-appwrite"; +**Best Practices:** +- Use httpOnly, secure, and sameSite cookie flags +- Create new session client per request +- Never share clients between requests +- Use API key for admin client to bypass rate limits +- Set forwarded user agent for better session tracking + +**See full SSR auth guide:** https://appwrite.io/docs/products/auth/server-side-rendering`; + +/** + * SSR Auth code examples by language + */ +const ssrAuthExamples = { + javascript: { + createSession: `import { Client, Account } from "node-appwrite"; // In your login endpoint: const account = new Account(adminClient); @@ -78,12 +91,8 @@ res.cookie('a_session_', session.secret, { sameSite: 'strict', expires: new Date(session.expire), path: '/' -}); -\`\`\` - -**Making Authenticated Requests:** -\`\`\`javascript -// Read session from cookie +});`, + useSession: `// Read session from cookie const session = req.cookies['a_session_']; // Create session client @@ -93,23 +102,308 @@ const sessionClient = new Client() .setSession(session); const account = new Account(sessionClient); -const user = await account.get(); +const user = await account.get();` + }, + python: { + createSession: `from appwrite.client import Client +from appwrite.services.account import Account + +# In your login endpoint: +account = Account(admin_client) +session = account.create_email_password_session(email, password) + +# Set httpOnly cookie with session secret +response.set_cookie( + 'a_session_', + session['secret'], + httponly=True, + secure=True, + samesite='strict', + expires=session['expire'], + path='/' +)`, + useSession: `# Read session from cookie +session = request.cookies.get('a_session_') + +# Create session client +session_client = Client() +session_client.set_endpoint('https://cloud.appwrite.io/v1') +session_client.set_project('') +session_client.set_session(session) + +account = Account(session_client) +user = account.get()` + }, + php: { + createSession: `use Appwrite\\Client; +use Appwrite\\Services\\Account; + +// In your login endpoint: +$account = new Account($adminClient); +$session = $account->createEmailPasswordSession($email, $password); + +// Set httpOnly cookie with session secret +setcookie( + 'a_session_', + $session['secret'], + [ + 'httponly' => true, + 'secure' => true, + 'samesite' => 'Strict', + 'expires' => strtotime($session['expire']), + 'path' => '/' + ] +);`, + useSession: `// Read session from cookie +$session = $_COOKIE['a_session_'] ?? null; + +// Create session client +$sessionClient = new Client(); +$sessionClient + ->setEndpoint('https://cloud.appwrite.io/v1') + ->setProject('') + ->setSession($session); + +$account = new Account($sessionClient); +$user = $account->get();` + }, + ruby: { + createSession: `require 'appwrite' + +# In your login endpoint: +account = Appwrite::Account.new(admin_client) +session = account.create_email_password_session(email: email, password: password) + +# Set httpOnly cookie with session secret +cookies['a_session_'] = { + value: session['secret'], + httponly: true, + secure: true, + same_site: :strict, + expires: Time.parse(session['expire']), + path: '/' +}`, + useSession: `# Read session from cookie +session = cookies['a_session_'] + +# Create session client +session_client = Appwrite::Client.new +session_client + .set_endpoint('https://cloud.appwrite.io/v1') + .set_project('') + .set_session(session) + +account = Appwrite::Account.new(session_client) +user = account.get` + }, + go: { + createSession: `import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +// In your login endpoint: +account := appwrite.NewAccount(adminClient) +session, _ := account.CreateEmailPasswordSession(email, password) + +// Set httpOnly cookie with session secret +http.SetCookie(w, &http.Cookie{ + Name: "a_session_", + Value: session.Secret, + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteStrictMode, + Expires: session.Expire, + Path: "/", +})`, + useSession: `// Read session from cookie +cookie, _ := r.Cookie("a_session_") + +// Create session client +sessionClient := appwrite.NewClient() +sessionClient.SetEndpoint("https://cloud.appwrite.io/v1") +sessionClient.SetProject("") +sessionClient.SetSession(cookie.Value) + +account := appwrite.NewAccount(sessionClient) +user, _ := account.Get()` + }, + dotnet: { + createSession: `using Appwrite; +using Appwrite.Services; + +// In your login endpoint: +var account = new Account(adminClient); +var session = await account.CreateEmailPasswordSession(email, password); + +// Set httpOnly cookie with session secret +Response.Cookies.Append("a_session_", session.Secret, new CookieOptions +{ + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Strict, + Expires = DateTimeOffset.Parse(session.Expire), + Path = "/" +});`, + useSession: `// Read session from cookie +var session = Request.Cookies["a_session_"]; + +// Create session client +var sessionClient = new Client() + .SetEndpoint("https://cloud.appwrite.io/v1") + .SetProject("") + .SetSession(session); + +var account = new Account(sessionClient); +var user = await account.Get();` + }, + kotlin: { + createSession: `import io.appwrite.Client +import io.appwrite.services.Account + +// In your login endpoint: +val account = Account(adminClient) +val session = account.createEmailPasswordSession(email, password) + +// Set httpOnly cookie with session secret +call.response.cookies.append( + name = "a_session_", + value = session.secret, + httpOnly = true, + secure = true, + extensions = mapOf("SameSite" to "Strict"), + expires = GMTDate(session.expire), + path = "/" +)`, + useSession: `// Read session from cookie +val session = call.request.cookies["a_session_"] + +// Create session client +val sessionClient = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("") + .setSession(session) + +val account = Account(sessionClient) +val user = account.get()` + }, + swift: { + createSession: `import Appwrite + +// In your login endpoint: +let account = Account(adminClient) +let session = try await account.createEmailPasswordSession(email: email, password: password) + +// Set httpOnly cookie with session secret +response.cookies["a_session_"] = HTTPCookie( + name: "a_session_", + value: session.secret, + httpOnly: true, + secure: true, + sameSite: .strict, + expires: session.expire, + path: "/" +)`, + useSession: `// Read session from cookie +let session = request.cookies["a_session_"] + +// Create session client +let sessionClient = Client() + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("") + .setSession(session) + +let account = Account(sessionClient) +let user = try await account.get()` + }, + dart: { + createSession: `import 'package:dart_appwrite/dart_appwrite.dart'; + +// In your login endpoint: +final account = Account(adminClient); +final session = await account.createEmailPasswordSession( + email: email, + password: password, +); + +// Set httpOnly cookie with session secret +response.headers.add( + 'Set-Cookie', + 'a_session_=\${session.secret}; HttpOnly; Secure; SameSite=Strict; Path=/', +);`, + useSession: `// Read session from cookie +final session = request.headers['Cookie'] + ?.split('; ') + .firstWhere((c) => c.startsWith('a_session_=')) + ?.split('=')[1]; + +// Create session client +final sessionClient = Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('') + .setSession(session); + +final account = Account(sessionClient); +final user = await account.get();` + } +}; + +/** + * Get SSR auth examples for a specific language + * @param {string} language - The language/SDK name + * @returns {Object} SSR auth code examples + */ +export function getSSRAuthExamples(language) { + const languageMap = { + javascript: 'javascript', + nodejs: 'javascript', + 'react-native': 'javascript', + python: 'python', + php: 'php', + go: 'go', + ruby: 'ruby', + dotnet: 'dotnet', + swift: 'swift', + kotlin: 'kotlin', + dart: 'dart', + flutter: 'dart' + }; + + const key = languageMap[language] || 'javascript'; + return ssrAuthExamples[key] || ssrAuthExamples.javascript; +} + +/** + * Get full SSR auth pattern with language-specific examples + * @param {string} language - The language/SDK name + * @returns {string} Complete SSR auth pattern with examples + */ +export function getSSRAuthPattern(language = 'javascript') { + const examples = getSSRAuthExamples(language); + + return `${ssrAuthPatternExplanation} + +**Creating Sessions:** +\`\`\`${language === 'javascript' || language === 'nodejs' ? 'javascript' : language} +${examples.createSession} +\`\`\` + +**Making Authenticated Requests:** +\`\`\`${language === 'javascript' || language === 'nodejs' ? 'javascript' : language} +${examples.useSession} \`\`\` **OAuth2 Flow:** 1. Redirect to OAuth provider using createOAuth2Token 2. Handle callback with userId and secret parameters 3. Call createSession to exchange for session object -4. Store session secret in cookie +4. Store session secret in cookie`; +} -**Best Practices:** -- Use httpOnly, secure, and sameSite cookie flags -- Create new session client per request -- Never share clients between requests -- Use API key for admin client to bypass rate limits -- Set forwarded user agent for better session tracking - -**See full SSR auth guide:** https://appwrite.io/docs/products/auth/server-side-rendering`; +/** + * SSR Authentication pattern for Node.js/JavaScript SSR frameworks + * @deprecated Use getSSRAuthPattern('javascript') instead + */ +export const ssrAuthPattern = getSSRAuthPattern('javascript'); /** * Framework-specific notes @@ -128,4 +422,3 @@ ${authNote}`, - Use environment variables for configuration - API keys grant admin access - use with extreme caution` }; - diff --git a/src/lib/languages/dart/index.js b/src/lib/languages/dart/index.js index 8b82657..8a9856b 100644 --- a/src/lib/languages/dart/index.js +++ b/src/lib/languages/dart/index.js @@ -1,20 +1,45 @@ -import { getSDKVersion } from '$lib/utils/versions.js'; +import { getSDKVersion } from '../../utils/versions.js'; import { dartInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; +import { getMobileImplementationGuide } from '../common/implementation-patterns.js'; /** * Gets the Flutter SDK installation template with the latest version from Appwrite's API * This is the main export used by the rules generator for Flutter + * @param {string[]} [features=[]] - Selected features to include patterns for * @returns {Promise} */ -export const flutter = async () => { +export const flutter = async (features = []) => { const version = await getSDKVersion('client-flutter'); const installation = dartInstall(version, false); - return createFrameworkTemplate({ installation, securityNotes: '' }); + const flutterImplementation = getMobileImplementationGuide('flutter', features); + + return `${installation} + +**Framework Documentation:** +- [Account API](https://appwrite.io/docs/references/cloud/client-web/account) - Authentication and user management +- [Databases API](https://appwrite.io/docs/references/cloud/client-web/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/client-web/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/client-web/functions) - Serverless functions execution +- [Messaging API](https://appwrite.io/docs/references/cloud/client-web/messaging) - Push notifications and messaging +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/flutter) + +${flutterImplementation} + +## Flutter-Specific Best Practices + +- Initialize AppwriteService in main() before runApp() +- Use flutter_dotenv for environment variables +- Use Riverpod, Provider, or Bloc for state management +- Handle loading/error states in UI with AsyncValue +- Dispose realtime subscriptions in dispose() +- Use flutter_secure_storage for session tokens +- Implement offline-first with local caching (Hive, Isar) +`; }; /** * Export vanilla as an alias to flutter for backwards compatibility + * @param {string[]} [features=[]] - Selected features to include patterns for * @returns {Promise} */ export const vanilla = flutter; @@ -23,4 +48,3 @@ export const vanilla = flutter; * Export server from server.js */ export { server } from './server.js'; - diff --git a/src/lib/languages/dart/server.js b/src/lib/languages/dart/server.js index a00c1f3..650d1c8 100644 --- a/src/lib/languages/dart/server.js +++ b/src/lib/languages/dart/server.js @@ -1,15 +1,39 @@ -import { getSDKVersion } from '$lib/utils/versions.js'; +import { getSDKVersion } from '../../utils/versions.js'; import { dartInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; import { serverSecurity } from '../common/security.js'; +import { getServerImplementationGuide } from '../common/implementation-patterns.js'; /** * Gets the Dart Server SDK installation template with the latest version from Appwrite's API + * @param {string[]} [features=[]] - Selected features to include patterns for * @returns {Promise} */ -export const server = async () => { +export const server = async (features = []) => { const version = await getSDKVersion('server-dart'); const installation = dartInstall(version, true); - return createFrameworkTemplate({ installation, securityNotes: serverSecurity }); -}; + const dartImplementation = getServerImplementationGuide('dart', features); + + return `${installation} + +**Framework Documentation:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/dart) + +${serverSecurity} +${dartImplementation} + +## Dart Server Best Practices + +- Use shelf or dart_frog for HTTP servers +- Use environment variables for configuration +- Implement proper error handling with try/catch +- Use null safety features (Dart 2.12+) +- Create factory constructors for models +- Use streams for realtime data +`; +}; diff --git a/src/lib/languages/dotnet/server.js b/src/lib/languages/dotnet/server.js index d009aca..0060448 100644 --- a/src/lib/languages/dotnet/server.js +++ b/src/lib/languages/dotnet/server.js @@ -1,9 +1,32 @@ import { dotnetInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; import { serverSecurityWithConfig } from '../common/security.js'; +import { getServerImplementationGuide } from '../common/implementation-patterns.js'; -export const server = createFrameworkTemplate({ - installation: dotnetInstall, - securityNotes: serverSecurityWithConfig('configuration files or environment variables') -}); +export async function server(features = []) { + const dotnetImplementation = getServerImplementationGuide('dotnet', features); + return `${dotnetInstall} + +**Framework Documentation:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/dotnet) + +${serverSecurityWithConfig('configuration files (appsettings.json) or environment variables')} + +${dotnetImplementation} + +## .NET-Specific Best Practices + +- Use dependency injection (built-in or Autofac) +- Store secrets in User Secrets (dev) or Azure Key Vault (prod) +- Use async/await for all I/O operations +- Use IOptions pattern for configuration +- Implement IDisposable for cleanup +- Use records for immutable data models +- Follow C# naming conventions (PascalCase for public members) +`; +} diff --git a/src/lib/languages/dotnet/vanilla.js b/src/lib/languages/dotnet/vanilla.js index 4b481e2..ae558d0 100644 --- a/src/lib/languages/dotnet/vanilla.js +++ b/src/lib/languages/dotnet/vanilla.js @@ -1,8 +1,22 @@ import { dotnetInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurity } from '../common/security.js'; -export const vanilla = createFrameworkTemplate({ - installation: dotnetInstall, - securityNotes: '' -}); +export const vanilla = `${dotnetInstall} +**Framework Documentation:** +- [Account API](https://appwrite.io/docs/references/cloud/client-web/account) - Authentication and user management +- [Databases API](https://appwrite.io/docs/references/cloud/client-web/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/client-web/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/client-web/functions) - Serverless functions execution +- [Messaging API](https://appwrite.io/docs/references/cloud/client-web/messaging) - Push notifications and messaging +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/dotnet) + +${clientSecurity} + +## .NET Client Best Practices + +- Use the SDK in console apps, desktop apps (WPF, WinForms, MAUI) +- Store configuration in appsettings.json or user secrets +- Use async/await for all SDK operations +- Implement proper error handling with try/catch +`; diff --git a/src/lib/languages/go/server.js b/src/lib/languages/go/server.js index 1c6dfb7..aeab15e 100644 --- a/src/lib/languages/go/server.js +++ b/src/lib/languages/go/server.js @@ -1,9 +1,32 @@ import { goInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; import { serverSecurity } from '../common/security.js'; +import { getServerImplementationGuide } from '../common/implementation-patterns.js'; -export const server = createFrameworkTemplate({ - installation: goInstall, - securityNotes: serverSecurity -}); +export async function server(features = []) { + const goImplementation = getServerImplementationGuide('go', features); + return `${goInstall} + +**Framework Documentation:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/go) + +${serverSecurity} + +${goImplementation} + +## Go-Specific Best Practices + +- Use Go modules for dependency management +- Use godotenv or similar for environment variables +- Create interfaces for testability +- Use context.Context for request cancellation +- Handle errors explicitly (don't ignore returned errors) +- Use goroutines carefully with proper synchronization +- Implement graceful shutdown for servers +`; +} diff --git a/src/lib/languages/js/astro.js b/src/lib/languages/js/astro.js index 5385cf7..5ba9ec5 100644 --- a/src/lib/languages/js/astro.js +++ b/src/lib/languages/js/astro.js @@ -1,20 +1,83 @@ import { nodeAppwriteInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; import { ssrAuthPattern } from '../common/security.js'; +import { getFullImplementationGuide } from '../common/implementation-patterns.js'; -export const astro = createFrameworkTemplate({ - installation: nodeAppwriteInstall, - securityNotes: `**Best Practices:** -- Store endpoint and project ID in \`.env\` file -- Never commit API keys to version control -- Use Astro API routes for SSR authentication -- API keys should NEVER be exposed to client-side code -- Use node-appwrite for server-side operations - -**Rendering Strategy:** -- Default to static/server-side rendering for all pages -- Only add client-side interactivity with client:* directives when needed -- Leverage Astro API routes for server-side operations`, - additionalNotes: ssrAuthPattern -}); +export async function astro(features = []) { + const astroImplementation = getFullImplementationGuide('astro', 'javascript', features); + + return `${nodeAppwriteInstall} + +**Framework Documentation:** +- [Astro Server Endpoints](https://docs.astro.build/en/guides/endpoints/) +- [Astro Middleware](https://docs.astro.build/en/guides/middleware/) +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/astro) + +${ssrAuthPattern} + +${astroImplementation} + +## Astro-Specific Best Practices + +### File Organization +\`\`\` +src/ +├── lib/ +│ ├── db.ts # Database wrapper (server-only) +│ ├── storage.ts # Storage wrapper (server-only) +│ └── auth.ts # Auth helpers +├── pages/ +│ ├── api/ +│ │ ├── items/ +│ │ │ ├── index.ts # GET/POST /api/items +│ │ │ └── [id].ts # GET/PUT/DELETE /api/items/:id +│ │ └── auth/ +│ │ └── session.ts +│ └── items.astro # Server-rendered page +├── components/ +│ └── ItemsList.tsx # Interactive component +└── middleware.ts # Auth middleware +\`\`\` + +### Middleware Pattern +\`\`\`typescript +// src/middleware.ts +import { defineMiddleware } from 'astro:middleware' +import { getSession } from '@/lib/auth' + +export const onRequest = defineMiddleware(async (context, next) => { + const session = await getSession(context.request) + context.locals.user = session?.user ?? null + return next() +}) +\`\`\` + +### Hybrid Rendering +\`\`\`astro +--- +// Force server rendering for this page +export const prerender = false + +import { db } from '@/lib/db' +const user = Astro.locals.user +if (!user) return Astro.redirect('/login') + +const items = await db.items.listByOwner(user.id) +--- + + + +

Your Items

+ + + +
+\`\`\` + +### Client Directive Guidelines +- \`client:load\` - Hydrate immediately (for critical interactivity) +- \`client:idle\` - Hydrate when browser is idle +- \`client:visible\` - Hydrate when component enters viewport +- Never use Appwrite SDK in client components +`; +} diff --git a/src/lib/languages/js/nextjs.js b/src/lib/languages/js/nextjs.js index 1c7fcab..87afd8c 100644 --- a/src/lib/languages/js/nextjs.js +++ b/src/lib/languages/js/nextjs.js @@ -1,15 +1,64 @@ import { nodeAppwriteInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; -import { serverSecurity, ssrAuthPattern } from '../common/security.js'; +import { ssrAuthPattern } from '../common/security.js'; +import { getFullImplementationGuide } from '../common/implementation-patterns.js'; -export const nextjs = createFrameworkTemplate({ - installation: nodeAppwriteInstall, - securityNotes: `${serverSecurity} +export async function nextjs(features = []) { + const nextjsImplementation = getFullImplementationGuide('nextjs', 'javascript', features); -**Rendering Strategy:** -- Default to Server Components and server-side pages + return `${nodeAppwriteInstall} + +**Framework Documentation:** +- [Next.js App Router Docs](https://nextjs.org/docs/app) +- [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/nextjs) + +${ssrAuthPattern} + +${nextjsImplementation} + +## Next.js-Specific Best Practices + +### Rendering Strategy +- Default to Server Components for all data fetching +- Use \`'use server'\` for all mutation functions - Only use Client Components when explicitly needed for interactivity -- Leverage Server Actions for mutations and data fetching`, - additionalNotes: ssrAuthPattern -}); +- Never import Appwrite SDK in Client Components + +### Data Fetching Pattern +\`\`\`typescript +// In Server Component - direct async/await +async function ItemsPage() { + const items = await db.items.listByOwner(userId) + return +} +\`\`\` + +### Revalidation +\`\`\`typescript +// After mutations, revalidate the path +import { revalidatePath } from 'next/cache' + +export async function createItem(data) { + 'use server' + const item = await db.items.create(data) + revalidatePath('/items') + return { item } +} +\`\`\` + +### File Organization +\`\`\` +app/ +├── actions/ # Server Actions +│ └── items.ts +├── lib/ +│ ├── db.ts # Database wrapper (server-only) +│ ├── storage.ts # Storage wrapper (server-only) +│ └── auth.ts # Auth helpers +└── (routes)/ + └── items/ + └── page.tsx # Server Component +\`\`\` +`; +} diff --git a/src/lib/languages/js/nuxt.js b/src/lib/languages/js/nuxt.js index 54f9637..6934e58 100644 --- a/src/lib/languages/js/nuxt.js +++ b/src/lib/languages/js/nuxt.js @@ -1,19 +1,85 @@ import { nodeAppwriteInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; import { ssrAuthPattern } from '../common/security.js'; +import { getFullImplementationGuide } from '../common/implementation-patterns.js'; -export const nuxt = createFrameworkTemplate({ - installation: nodeAppwriteInstall, - securityNotes: `**Best Practices:** -- Store endpoint and project ID in \`nuxt.config.ts\` -- Never commit API keys to version control -- Use Nuxt server routes for SSR authentication -- API keys should NEVER be exposed to client-side code - -**Rendering Strategy:** -- Default to server-side rendering (SSR) for all pages -- Only use client-side rendering when explicitly needed -- Leverage server API routes and middleware for data operations`, - additionalNotes: ssrAuthPattern -}); +export async function nuxt(features = []) { + const nuxtImplementation = getFullImplementationGuide('nuxt', 'javascript', features); + + return `${nodeAppwriteInstall} + +**Framework Documentation:** +- [Nuxt Server Routes](https://nuxt.com/docs/guide/directory-structure/server) +- [Nuxt Middleware](https://nuxt.com/docs/guide/directory-structure/middleware) +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/nuxt) + +${ssrAuthPattern} + +${nuxtImplementation} + +## Nuxt-Specific Best Practices + +### Server Route Organization +\`\`\` +server/ +├── api/ +│ ├── items/ +│ │ ├── index.get.ts # GET /api/items +│ │ ├── index.post.ts # POST /api/items +│ │ └── [id].delete.ts # DELETE /api/items/:id +│ └── auth/ +│ └── session.get.ts +├── lib/ +│ ├── db.ts # Database wrapper +│ └── storage.ts # Storage wrapper +└── middleware/ + └── auth.ts # Auth middleware +\`\`\` + +### Auth Middleware Pattern +\`\`\`typescript +// server/middleware/auth.ts +export default defineEventHandler(async (event) => { + // Skip auth for public routes + if (event.path.startsWith('/api/public')) return + + const session = await getSession(event) + event.context.user = session?.user ?? null +}) +\`\`\` + +### Composables for Client +\`\`\`typescript +// composables/useItems.ts +export function useItems() { + const { data: items, refresh } = useFetch('/api/items') + + async function createItem(title: string) { + await $fetch('/api/items', { + method: 'POST', + body: { title } + }) + await refresh() + } + + return { items, createItem, refresh } +} +\`\`\` + +### Environment Configuration +\`\`\`typescript +// nuxt.config.ts +export default defineNuxtConfig({ + runtimeConfig: { + // Server-only (never exposed to client) + appwriteApiKey: process.env.APPWRITE_API_KEY, + // Can be overridden by NUXT_PUBLIC_* env vars + public: { + appwriteEndpoint: process.env.APPWRITE_ENDPOINT, + appwriteProjectId: process.env.APPWRITE_PROJECT_ID, + } + } +}) +\`\`\` +`; +} diff --git a/src/lib/languages/js/svelte.js b/src/lib/languages/js/svelte.js index fcd4d3b..3ba6126 100644 --- a/src/lib/languages/js/svelte.js +++ b/src/lib/languages/js/svelte.js @@ -1,20 +1,70 @@ import { nodeAppwriteInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; import { ssrAuthPattern } from '../common/security.js'; +import { getFullImplementationGuide } from '../common/implementation-patterns.js'; -export const svelte = createFrameworkTemplate({ - installation: nodeAppwriteInstall, - securityNotes: `**Best Practices:** -- Store endpoint and project ID in \`.env\` file -- Never commit API keys to version control -- Use SvelteKit server routes (+server.js) for SSR authentication -- API keys should NEVER be exposed to client-side code -- Use node-appwrite for server-side operations - -**Rendering Strategy:** -- Default to server-side rendering (SSR) for all pages -- Only use client-side rendering when explicitly needed -- Leverage server load functions and form actions for data operations`, - additionalNotes: ssrAuthPattern -}); +export async function svelte(features = []) { + const sveltekitImplementation = getFullImplementationGuide('svelte', 'javascript', features); + + return `${nodeAppwriteInstall} + +**Framework Documentation:** +- [SvelteKit Load Functions](https://kit.svelte.dev/docs/load) +- [SvelteKit Form Actions](https://kit.svelte.dev/docs/form-actions) +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/sveltekit) + +${ssrAuthPattern} + +${sveltekitImplementation} + +## SvelteKit-Specific Best Practices + +### File Organization +\`\`\` +src/ +├── lib/ +│ └── server/ +│ ├── db.ts # Database wrapper (server-only) +│ ├── storage.ts # Storage wrapper (server-only) +│ └── auth.ts # Auth helpers +├── routes/ +│ ├── items/ +│ │ ├── +page.svelte # Client component +│ │ ├── +page.server.ts # Load + Actions +│ │ └── +server.ts # API endpoints +│ └── +layout.server.ts # Root auth check +└── hooks.server.ts # Auth middleware +\`\`\` + +### Auth Hook Pattern +\`\`\`typescript +// src/hooks.server.ts +import type { Handle } from '@sveltejs/kit' +import { getSession } from '$lib/server/auth' + +export const handle: Handle = async ({ event, resolve }) => { + const session = await getSession(event.cookies) + event.locals.user = session?.user ?? null + return resolve(event) +} +\`\`\` + +### Form Actions Pattern +\`\`\`svelte + + + +
+ + +
+\`\`\` + +### Progressive Enhancement +- Use \`use:enhance\` for form submissions to enable JS-enhanced UX +- Forms work without JavaScript enabled +- Server handles all validation and ownership checks +`; +} diff --git a/src/lib/languages/js/tanstack.js b/src/lib/languages/js/tanstack.js index 68569d8..d2bfa65 100644 --- a/src/lib/languages/js/tanstack.js +++ b/src/lib/languages/js/tanstack.js @@ -1,17 +1,45 @@ -import { jsInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; -import { authNote } from '../common/security.js'; - -export const tanstack = createFrameworkTemplate({ - installation: jsInstall('appwrite @tanstack/react-query', 'Install the Appwrite JavaScript SDK and TanStack Query'), - securityNotes: `For TanStack Query integration examples and best practices, refer to the [TanStack Query Documentation](https://tanstack.com/query/latest). - -**Best Practices:** -- Use query keys consistently for cache management -- Invalidate queries after mutations to keep data fresh -- Use \`enabled\` option to conditionally fetch data -- Store endpoint and project ID in environment variables -- Never commit API keys to version control`, - additionalNotes: authNote -}); +import { nodeAppwriteInstall } from '../common/install.js'; +import { ssrAuthPattern } from '../common/security.js'; +import { getFullImplementationGuide } from '../common/implementation-patterns.js'; + +export async function tanstack(features = []) { + const tanstackImplementation = getFullImplementationGuide('tanstack', 'javascript', features); + + return `${nodeAppwriteInstall} + +**Framework Documentation:** +- [TanStack Start Docs](https://tanstack.com/start/latest) +- [TanStack Query Docs](https://tanstack.com/query/latest) +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/tanstack) + +${ssrAuthPattern} + +${tanstackImplementation} + +## TanStack-Specific Best Practices + +- Use \`createServerFn\` for ALL data operations - never call SDK from components +- Validate inputs in server functions before processing +- Use \`router.invalidate()\` after mutations to refresh cached data +- Leverage TanStack Query for client-side caching when needed +- Keep server functions in dedicated files (e.g., \`server/functions/\`) +- Export typed return types from server functions + +## Query Invalidation Pattern + +\`\`\`typescript +// After mutation, invalidate relevant queries +const router = useRouter() + +async function handleCreate(data) { + await createItemFn({ data }) + router.invalidate() // Invalidates all route data +} + +// Or use TanStack Query for fine-grained control +const queryClient = useQueryClient() +queryClient.invalidateQueries({ queryKey: ['items'] }) +\`\`\` +`; +} diff --git a/src/lib/languages/kotlin/index.js b/src/lib/languages/kotlin/index.js index 9b0a111..ba835ce 100644 --- a/src/lib/languages/kotlin/index.js +++ b/src/lib/languages/kotlin/index.js @@ -1,6 +1,6 @@ -import { getSDKVersion } from '$lib/utils/versions.js'; -import { createFrameworkTemplate } from '../common/utils.js'; +import { getSDKVersion } from '../../utils/versions.js'; import { serverSecurity } from '../common/security.js'; +import { getServerImplementationGuide } from '../common/implementation-patterns.js'; /** * Generates the Kotlin SDK installation template with the latest version @@ -37,10 +37,36 @@ Or for Maven, add to \`pom.xml\`: /** * Gets the Kotlin SDK installation template with the latest version from Appwrite's API * This is the main export used by the rules generator + * @param {string[]} [features=[]] - Selected features to include patterns for * @returns {Promise} */ -export const vanilla = async () => { +export const vanilla = async (features = []) => { const version = await getSDKVersion('server-kotlin'); const installation = generateInstallationTemplate(version); - return createFrameworkTemplate({ installation, securityNotes: serverSecurity }); + const kotlinImplementation = getServerImplementationGuide('kotlin', features); + + return `${installation} + +**Framework Documentation:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/kotlin) + +${serverSecurity} + +${kotlinImplementation} + +## Kotlin-Specific Best Practices + +- Use Ktor or Spring Boot for HTTP servers +- Use coroutines for async operations +- Use Kotlin Flow for reactive streams +- Use data classes for models +- Use object declarations for singletons +- Handle errors with Result or sealed classes +- Use Koin or Hilt for dependency injection +`; }; diff --git a/src/lib/languages/php/server.js b/src/lib/languages/php/server.js index 182f357..ee1f120 100644 --- a/src/lib/languages/php/server.js +++ b/src/lib/languages/php/server.js @@ -1,9 +1,32 @@ import { phpInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; -import { serverSecurity } from '../common/security.js'; +import { serverSecurityWithConfig } from '../common/security.js'; +import { getServerImplementationGuide } from '../common/implementation-patterns.js'; -export const server = createFrameworkTemplate({ - installation: phpInstall, - securityNotes: serverSecurity -}); +export async function server(features = []) { + const phpImplementation = getServerImplementationGuide('php', features); + return `${phpInstall} + +**Framework Documentation:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/php) + +${serverSecurityWithConfig('environment variables or .env files')} + +${phpImplementation} + +## PHP-Specific Best Practices + +- Use Composer for dependency management +- Use PHP 8.1+ for better type support and enums +- Use vlucas/phpdotenv for environment variables +- Implement PSR-4 autoloading +- Use dependency injection containers (Laravel, Symfony) +- Handle exceptions with try/catch blocks +- Use strict typing with \`declare(strict_types=1)\` +`; +} diff --git a/src/lib/languages/python/flask.js b/src/lib/languages/python/flask.js index 9425800..9c93b59 100644 --- a/src/lib/languages/python/flask.js +++ b/src/lib/languages/python/flask.js @@ -1,8 +1,71 @@ -import { createSecuritySection } from '../common/utils.js'; +import { pythonInstall } from '../common/install.js'; +import { serverSecurity } from '../common/security.js'; +import { getServerImplementationGuide } from '../common/implementation-patterns.js'; -export const flask = createSecuritySection({ - securityNotes: `**Best Practices:** -- Use environment variables for configuration -- Never commit API keys to version control` -}); +export async function flask(features = []) { + const pythonImplementation = getServerImplementationGuide('python', features); + return `${pythonInstall} + +**Framework Documentation:** +- [Flask Documentation](https://flask.palletsprojects.com/) +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/python) + +${serverSecurity} + +${pythonImplementation} + +## Flask-Specific Best Practices + +### Application Factory Pattern + +\`\`\`python +# app/__init__.py +from flask import Flask +from app.services.appwrite import init_appwrite + +def create_app(): + app = Flask(__name__) + app.config.from_object('config.Config') + + # Initialize Appwrite client + init_appwrite(app) + + # Register blueprints + from app.routes import items + app.register_blueprint(items.bp) + + return app +\`\`\` + +### Request Context + +\`\`\`python +# Use Flask's g object for request-scoped data +from flask import g + +@app.before_request +def load_user(): + session_token = request.cookies.get('session') + if session_token: + g.user = verify_session(session_token) + else: + g.user = None +\`\`\` + +### Error Handling + +\`\`\`python +from appwrite.exception import AppwriteException + +@app.errorhandler(AppwriteException) +def handle_appwrite_error(error): + return jsonify({'error': error.message}), error.code +\`\`\` +`; +} diff --git a/src/lib/languages/python/server.js b/src/lib/languages/python/server.js index 0ceba5b..678f3f0 100644 --- a/src/lib/languages/python/server.js +++ b/src/lib/languages/python/server.js @@ -1,9 +1,31 @@ import { pythonInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; import { serverSecurity } from '../common/security.js'; +import { getServerImplementationGuide } from '../common/implementation-patterns.js'; -export const server = createFrameworkTemplate({ - installation: pythonInstall, - securityNotes: serverSecurity -}); +export async function server(features = []) { + const pythonImplementation = getServerImplementationGuide('python', features); + return `${pythonInstall} + +**Framework Documentation:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/python) + +${serverSecurity} + +${pythonImplementation} + +## Python-Specific Best Practices + +- Use virtual environments (venv, poetry, pipenv) +- Type hints for better IDE support and documentation +- Use async/await with asyncio for concurrent operations +- Store configuration in environment variables (.env with python-dotenv) +- Use dataclasses or Pydantic for models +- Implement proper exception handling with try/except +`; +} diff --git a/src/lib/languages/react-native/vanilla.js b/src/lib/languages/react-native/vanilla.js index f125743..cf6fc74 100644 --- a/src/lib/languages/react-native/vanilla.js +++ b/src/lib/languages/react-native/vanilla.js @@ -1,11 +1,34 @@ import { jsInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; +import { getMobileImplementationGuide } from '../common/implementation-patterns.js'; -export const vanilla = createFrameworkTemplate({ - installation: jsInstall('react-native-appwrite', 'Install the Appwrite React Native SDK'), - securityNotes: `**Best Practices:** -- Store endpoint and project ID in environment variables or config files +export async function vanilla(features = []) { + const reactNativeImplementation = getMobileImplementationGuide('react-native', features); + + return `${jsInstall('react-native-appwrite', 'Install the Appwrite React Native SDK')} + +**Framework Documentation:** +- [Account API](https://appwrite.io/docs/references/cloud/client-web/account) - Authentication and user management +- [Databases API](https://appwrite.io/docs/references/cloud/client-web/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/client-web/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/client-web/functions) - Serverless functions execution +- [Messaging API](https://appwrite.io/docs/references/cloud/client-web/messaging) - Push notifications and messaging +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/react-native) + +**Best Practices:** +- Store endpoint and project ID in react-native-config - Never commit API keys to version control -- Initialize services once and export as singletons` -}); +- Initialize services once and export as singletons + +${reactNativeImplementation} + +## React Native-Specific Best Practices +- Use react-native-config for environment variables +- Store session in expo-secure-store or react-native-keychain +- Use React Navigation for routing +- Handle app state changes (AppState API) +- Implement proper error boundaries +- Use custom hooks for data fetching logic +- Test on both iOS and Android +`; +} diff --git a/src/lib/languages/ruby/server.js b/src/lib/languages/ruby/server.js index f45ed75..2dc6293 100644 --- a/src/lib/languages/ruby/server.js +++ b/src/lib/languages/ruby/server.js @@ -1,9 +1,32 @@ import { rubyInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; import { serverSecurityWithConfig } from '../common/security.js'; +import { getServerImplementationGuide } from '../common/implementation-patterns.js'; -export const server = createFrameworkTemplate({ - installation: rubyInstall, - securityNotes: serverSecurityWithConfig('environment variables or Rails credentials') -}); +export async function server(features = []) { + const rubyImplementation = getServerImplementationGuide('ruby', features); + return `${rubyInstall} + +**Framework Documentation:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/ruby) + +${serverSecurityWithConfig('environment variables or Rails credentials')} + +${rubyImplementation} + +## Ruby-Specific Best Practices + +- Use Bundler for dependency management +- Use dotenv-rails or Rails credentials for secrets +- Follow Ruby naming conventions (snake_case) +- Use modules for namespacing services +- Implement proper exception handling with begin/rescue +- Use symbols for hash keys +- Consider using Sorbet or RBS for type checking +`; +} diff --git a/src/lib/languages/swift/index.js b/src/lib/languages/swift/index.js index 4e33efc..7093c15 100644 --- a/src/lib/languages/swift/index.js +++ b/src/lib/languages/swift/index.js @@ -1,6 +1,6 @@ -import { getSDKVersion } from '$lib/utils/versions.js'; -import { createFrameworkTemplate } from '../common/utils.js'; +import { getSDKVersion } from '../../utils/versions.js'; import { serverSecurity } from '../common/security.js'; +import { getServerImplementationGuide } from '../common/implementation-patterns.js'; /** * Generates the Swift SDK installation template with the latest version @@ -27,11 +27,36 @@ Or add it via Xcode: /** * Gets the Swift SDK installation template with the latest version from Appwrite's API * This is the main export used by the rules generator + * @param {string[]} [features=[]] - Selected features to include patterns for * @returns {Promise} */ -export const vanilla = async () => { +export const vanilla = async (features = []) => { const version = await getSDKVersion('server-swift'); const installation = generateInstallationTemplate(version); - return createFrameworkTemplate({ installation, securityNotes: serverSecurity }); -}; + const swiftImplementation = getServerImplementationGuide('swift', features); + + return `${installation} + +**Framework Documentation:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File storage and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/swift) + +${serverSecurity} +${swiftImplementation} + +## Swift Server Best Practices + +- Use Vapor or Hummingbird for HTTP servers +- Use async/await for all SDK operations +- Use Codable for JSON serialization +- Use actors for thread-safe singletons +- Handle errors with do/catch +- Use Swift Package Manager for dependencies +- Store configuration in environment variables +`; +}; diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index 49b50c1..07747d0 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -1,6 +1,5 @@ // Rules generator for different Appwrite SDKs and frameworks import * as codeExamples from './languages/index.js'; -import { generateMCPRecommendation } from './languages/common/mcp.js'; /** @typedef {Object} SDKConfig * @property {string} name @@ -14,7 +13,6 @@ import { generateMCPRecommendation } from './languages/common/mcp.js'; * @property {string} sdk * @property {string} framework * @property {string[]} features - * @property {boolean} [includeMCP] - Whether to include MCP recommendation section */ /** @type {Record} */ @@ -110,10 +108,10 @@ export const SDK_OPTIONS = { * @returns {Promise} */ export async function generateRules(config) { - const { sdk, framework, features, includeMCP = false } = config; + const { sdk, framework, features } = config; const sdkInfo = SDK_OPTIONS[sdk]; - const sdkInit = await generateSDKInitialization(sdk, framework); + const sdkInit = await generateSDKInitialization(sdk, framework, features); // Generate all sections in parallel // Permissions section is mandatory for all products @@ -128,16 +126,15 @@ export async function generateRules(config) { features.includes('realtime') ? generateRealtimeSection() : Promise.resolve('') ]); - const mcpSection = includeMCP ? `${generateMCPRecommendation()}\n\n` : ''; - - let rules = `--- -description: You are an expert developer focused on building apps with Appwrite's ${sdkInfo?.name || sdk} SDK. -alwaysApply: false ---- + let rules = `# Appwrite Development Rules + +> You are an expert developer focused on building apps with Appwrite's ${sdkInfo?.name || sdk} SDK. -# Appwrite Development Rules +## Overview -${mcpSection}${sdkInit} +This file provides AI coding assistants with Appwrite-specific development instructions, best practices, and code patterns for the ${sdkInfo?.name || sdk} SDK${framework !== 'vanilla' ? ` with ${framework}` : ''}. + +${sdkInit} ${sections.join('\n\n')} `; @@ -148,10 +145,11 @@ ${sections.join('\n\n')} /** * @param {string} sdk * @param {string} framework + * @param {string[]} features * @returns {Promise} */ -async function generateSDKInitialization(sdk, framework) { - /** @type {Record Promise)>>} */ +async function generateSDKInitialization(sdk, framework, features) { + /** @type {Record Promise)>>} */ const templates = { javascript: codeExamples.js, 'react-native': codeExamples.reactNative, @@ -172,7 +170,7 @@ async function generateSDKInitialization(sdk, framework) { const template = sdkTemplates[framework]; // Check if it's an async function if (typeof template === 'function') { - return await template(); + return await template(features); } return template; } @@ -182,7 +180,7 @@ async function generateSDKInitialization(sdk, framework) { const template = sdkTemplates.vanilla; // Check if it's an async function if (typeof template === 'function') { - return await template(); + return await template(features); } return template; } @@ -229,7 +227,7 @@ When building applications that involve multiple users or tenants: * @returns {Promise} */ async function generatePermissionsSection(sdk) { - const { authProductLinks } = await import('./languages/common/products.js'); + const { authProductLinks, permissionsProductLinks } = await import('./languages/common/products.js'); const { getPermissionExamples } = await import('./languages/common/permissions-examples.js'); const examples = getPermissionExamples(sdk); @@ -237,11 +235,13 @@ async function generatePermissionsSection(sdk) { This section is CRITICAL for building secure, scalable applications with Appwrite. Multi-tenancy is one of the most important architectural patterns in modern applications, and Appwrite's team-based permission system is designed specifically for this. +${permissionsProductLinks} + ### Why Multi-Tenancy Matters Multi-tenancy allows a single application instance to serve multiple isolated groups of users (tenants) while maintaining complete data isolation and security. Almost every modern SaaS application requires multi-tenancy to scale efficiently. -### Team/Member Roles vs User-Specific Roles: The Critical Distinction +### The Critical Pattern: Team-Based Permissions **ALWAYS PREFER TEAM/MEMBER-BASED ROLES over user-specific roles.** This is a fundamental architectural decision: @@ -255,7 +255,6 @@ ${examples.avoidUserPermissions} - Difficult to add/remove access without updating every row - No way to represent organizational hierarchies - Poor support for collaborative features -- Maintenance nightmare as teams grow #### Prefer: Team/Member-Based Roles \`\`\`${getLanguageFromSdk(sdk)} @@ -267,202 +266,79 @@ ${examples.preferTeamPermissions} - Easy to add/remove members without touching rows - Scales naturally as teams grow - Supports organizational hierarchies and complex permissions -- Industry-standard pattern for SaaS applications - -### Building Multi-Tenant Applications from Scratch -#### Step 1: Create Teams Structure +### Query Isolation Pattern -Teams in Appwrite represent tenants. Each team should map to a business entity (company, organization, workspace, etc.). +**CRITICAL**: Always filter queries by \`teamId\` to ensure tenant isolation: -**Creating a team:** \`\`\`${getLanguageFromSdk(sdk)} -${examples.createTeam} +${examples.queryWithTeamId} \`\`\` -#### Step 2: Define Custom Roles +### Role Verification Pattern -Create roles that match your application's permission model. Common roles: -- **owner**: Full control, can manage team settings and members -- **admin**: Can manage resources and most settings, but not team membership -- **member**: Can create/edit resources, but with limited permissions -- **viewer**: Read-only access +Before allowing sensitive operations, always verify the user's role: -**Creating custom roles (Server-side only):** \`\`\`${getLanguageFromSdk(sdk)} -// Define roles when creating the team (optional, defaults exist) -// Or create via Appwrite Console or Server SDK -// Roles are created per team, allowing different permission models per tenant -\`\`\` - -#### Step 3: Member Management from Scratch - -Member management is the foundation of multi-tenant applications. Here's how to build it: - -**A. Invite Members to Teams** - -\`\`\`${getLanguageFromSdk(sdk)} -${examples.createMembershipEmail} -\`\`\` - -\`\`\`${getLanguageFromSdk(sdk)} -${examples.createMembershipUserId} -\`\`\` - -**B. List Team Members** - -\`\`\`${getLanguageFromSdk(sdk)} -${examples.listMemberships} -\`\`\` - -**C. Update Member Roles** - -\`\`\`${getLanguageFromSdk(sdk)} -${examples.updateMembership} -\`\`\` - -**D. Remove Members** - -\`\`\`${getLanguageFromSdk(sdk)} -${examples.deleteMembership} -\`\`\` - -**E. Get Current User's Teams** - -\`\`\`${getLanguageFromSdk(sdk)} -${examples.listTeams} -\`\`\` - -**F. Get Current User's Role in a Team** - -\`\`\`${getLanguageFromSdk(sdk)} -${examples.getUserRole} -\`\`\` - -#### Step 4: Apply Permissions in Tables - -When creating rows in multi-tenant applications, always use team roles: - -**Database Tables:** - -\`\`\`${getLanguageFromSdk(sdk)} -${examples.createRow} -\`\`\` - -**Table-Level Permissions:** - -When creating tables, set default permissions: - -\`\`\`${getLanguageFromSdk(sdk)} -${examples.createTable} -\`\`\` - -#### Step 5: Query with Team Isolation - -Always filter queries by teamId to ensure data isolation: - -\`\`\`${getLanguageFromSdk(sdk)} -${examples.listRows} +${examples.roleCheck} \`\`\` -#### Step 6: Storage Permissions +### Multi-Tenancy Implementation Guide -Apply the same team-based permission pattern to storage: +#### Step 1: Create Teams Structure -\`\`\`${getLanguageFromSdk(sdk)} -${examples.createFile} -\`\`\` +Teams in Appwrite represent tenants. Each team should map to a business entity (company, organization, workspace). -### Complete Member Management Implementation Pattern +See: [Teams Documentation](https://appwrite.io/docs/products/auth/teams) -Here's a complete pattern for building member management UI and logic: +#### Step 2: Define Custom Roles -**1. Team Creation Flow:** -\`\`\`${getLanguageFromSdk(sdk)} -${examples.teamCreationFlow} -\`\`\` +Common role hierarchy: +- **owner**: Full control, can manage team settings and members +- **admin**: Can manage resources and most settings +- **member**: Can create/edit resources with limited permissions +- **viewer**: Read-only access -**2. Invite Flow:** -\`\`\`${getLanguageFromSdk(sdk)} -${examples.inviteFlow} -\`\`\` +#### Step 3: Member Management -**3. Member List UI:** -\`\`\`${getLanguageFromSdk(sdk)} -${examples.memberListUI} -\`\`\` +For team invitations and membership management, see: +- [Team Invites Guide](https://appwrite.io/docs/products/auth/team-invites) +- [Teams API Reference](https://appwrite.io/docs/references) -**4. Role Change:** -\`\`\`${getLanguageFromSdk(sdk)} -${examples.roleChange} -\`\`\` +#### Step 4: Apply Permissions Consistently -**5. Member Removal:** -\`\`\`${getLanguageFromSdk(sdk)} -${examples.memberRemoval} -\`\`\` +Use team roles for all resources: +- **Database rows**: Apply \`Role.team('', 'role')\` permissions +- **Storage files**: Same team-based permission pattern +- **Always include \`teamId\`** as a field in your rows for query filtering ### Permission Best Practices -1. **Always Store teamId**: Every row/resource in a multi-tenant app should have a \`teamId\` field for filtering and isolation - -2. **Default Deny**: Don't grant permissions unless explicitly needed. Use minimal permission sets. - -3. **Role Hierarchy**: Design your roles to reflect natural hierarchies (owner > admin > member > viewer) - -4. **Permission Consistency**: Use the same permission pattern across database, storage, and other resources - -5. **Server-Side Validation**: Always validate team membership on the server side, even if client has permissions - -6. **Query Isolation**: Always include \`teamId\` in queries to prevent cross-tenant data leaks - -7. **Role Checks**: Before allowing sensitive operations, check the user's role in the team: - \`\`\`${getLanguageFromSdk(sdk)} -${examples.roleCheck} - \`\`\` - -8. **Permission Inheritance**: Consider if child resources should inherit parent team permissions - -9. **Row-Level Permissions**: For fine-grained control, set permissions on individual rows while still using team roles - -10. **Audit Trail**: Log permission changes and team membership changes for security auditing +1. **Always Store teamId**: Every row in a multi-tenant app should have a \`teamId\` field +2. **Default Deny**: Don't grant permissions unless explicitly needed +3. **Role Hierarchy**: Design roles to reflect natural hierarchies (owner > admin > member > viewer) +4. **Server-Side Validation**: Always validate team membership server-side +5. **Query Isolation**: Every multi-tenant query MUST include a \`teamId\` filter ### Common Multi-Tenancy Patterns -**Pattern 1: Workspace-Based (e.g., Notion, Slack)** -- Each workspace is a team -- Users can belong to multiple teams -- Resources belong to one team -- Perfect for: Collaboration tools, project management - -**Pattern 2: Organization-Based (e.g., GitHub, GitLab)** -- Each organization is a team -- Resources belong to organization -- Members have roles within organization -- Perfect for: Enterprise SaaS, developer tools - -**Pattern 3: Project-Based (e.g., Linear, Asana)** -- Each project is a team -- Resources scoped to project -- Members invited per project -- Perfect for: Project management, task tracking +| Pattern | Example Apps | Structure | +|---------|--------------|-----------| +| Workspace-Based | Notion, Slack | Each workspace = 1 team, users can belong to multiple teams | +| Organization-Based | GitHub, GitLab | Each org = 1 team, resources scoped to org | +| Project-Based | Linear, Asana | Each project = 1 team, members invited per project | ### Debugging Permission Issues -When permissions aren't working: - 1. **Check Team Membership**: Verify user is actually a member of the team -2. **Verify Roles**: Ensure user has the required role (check \`membership.roles\`) -3. **Check Permission Strings**: Verify permission strings match exactly (case-sensitive) +2. **Verify Roles**: Check \`membership.roles\` contains the required role +3. **Check Permission Strings**: Permission strings are case-sensitive 4. **Query Filters**: Ensure \`teamId\` filters are applied correctly -5. **Server vs Client**: Some operations require server SDK (like creating custom roles) -6. **Session Context**: Permissions are evaluated in the context of the current session +5. **Server vs Client**: Some operations require the Server SDK ### Additional Resources -${authProductLinks} - -For comprehensive permission patterns and examples, always refer to the official Appwrite documentation on Teams, Multi-tenancy, and Permissions.`; +${authProductLinks}`; } /** @@ -498,9 +374,139 @@ async function generateDatabaseSection() { ${databaseProductLinks} -### Best Practices for Databases +### Database Setup Scripts + +**ALWAYS create a database setup script using the Server SDK and API key** to initialize your database schema. This script should be version-controlled and run during deployment or initial setup. + +**Why Use Setup Scripts:** +- **Infrastructure as Code**: Database schema becomes part of your codebase, not manual console clicks +- **Reproducibility**: Easy to recreate database structure across different environments (dev, staging, production) +- **Version Control**: Track schema changes over time with Git +- **Team Collaboration**: All developers can sync database structure automatically +- **CI/CD Integration**: Automate database setup in deployment pipelines +- **Documentation**: The script serves as living documentation of your database structure + +**What Your Setup Script Should Include:** + +1. **Table Creation**: All tables with proper naming and IDs +2. **Column Definitions**: All columns with correct data types (string, integer, boolean, datetime, email, url, etc.) +3. **Indexes**: Performance-critical indexes on frequently queried fields (especially \`teamId\`, foreign keys, search fields) +4. **Relationships**: All table relationships and foreign key constraints +5. **Default Permissions**: Table-level permissions using team roles +6. **Column Constraints**: Required fields, string lengths, number ranges, enum values, default values + +**Setup Script Requirements:** + +- **Use Server SDK**: Must use the Server SDK (node-appwrite, appwrite/appwrite for PHP, etc.), NOT the client SDK +- **Require API Key**: The script must use an API key with appropriate scopes (\`databases.write\`, \`tables.write\`, \`columns.write\`) +- **Idempotent**: Script should safely handle re-runs (check if tables exist before creating) +- **Environment Variables**: Store API key, endpoint, project ID, and database ID in environment variables +- **Error Handling**: Proper error handling with clear error messages +- **Logging**: Log progress and errors for debugging + +**Example Setup Script Structure:** + +\`\`\`${getLanguageFromSdk('javascript')} +// scripts/setup-database.js (Node.js example) +import { Client, TablesDB, Permission, Role } from 'node-appwrite'; + +const client = new Client() + .setEndpoint(process.env.APPWRITE_ENDPOINT) + .setProject(process.env.APPWRITE_PROJECT_ID) + .setKey(process.env.APPWRITE_API_KEY); + +const tablesDB = new TablesDB(client); +const databaseId = process.env.APPWRITE_DATABASE_ID; + +async function setupDatabase() { + try { + // Create Users table + await tablesDB.createTable(databaseId, 'users', 'Users', [ + Permission.read(Role.users()), + Permission.write(Role.users()) + ]); + + // Add columns to Users table + await tablesDB.createStringColumn(databaseId, 'users', 'name', 255, true); + await tablesDB.createEmailColumn(databaseId, 'users', 'email', true); + await tablesDB.createStringColumn(databaseId, 'users', 'teamId', 255, true); + + // Create index on teamId for query performance + await tablesDB.createIndex(databaseId, 'users', 'idx_team', 'key', ['teamId']); + + // Create Projects table with team-based permissions + await tablesDB.createTable(databaseId, 'projects', 'Projects', [ + Permission.read(Role.team('[TEAM_ID]')), + Permission.create(Role.team('[TEAM_ID]', 'member')), + Permission.update(Role.team('[TEAM_ID]', 'admin')), + Permission.delete(Role.team('[TEAM_ID]', 'owner')), + ]); + + // Add columns to Projects table + await tablesDB.createStringColumn(databaseId, 'projects', 'name', 255, true); + await tablesDB.createStringColumn(databaseId, 'projects', 'description', 5000, false); + await tablesDB.createStringColumn(databaseId, 'projects', 'teamId', 255, true); + await tablesDB.createStringColumn(databaseId, 'projects', 'ownerId', 255, true); + await tablesDB.createDatetimeColumn(databaseId, 'projects', 'createdAt', true); + + // Create indexes + await tablesDB.createIndex(databaseId, 'projects', 'idx_team', 'key', ['teamId']); + await tablesDB.createIndex(databaseId, 'projects', 'idx_owner', 'key', ['ownerId']); + + // Create relationship between projects and users + await tablesDB.createRelationshipColumn( + databaseId, + 'projects', + 'users', + 'oneToMany', + false, // twoWay + 'owner', // key in projects + 'projects', // key in users + 'cascade' // onDelete + ); + + console.log('Database setup completed successfully!'); + } catch (error) { + // Handle "already exists" errors gracefully for idempotency + if (error.code !== 409) { + console.error('Database setup failed:', error); + throw error; + } else { + console.log('Tables already exist, skipping creation'); + } + } +} + +setupDatabase(); +\`\`\` + +**Running the Setup Script:** + +\`\`\`bash +# Set environment variables +export APPWRITE_ENDPOINT="https://cloud.appwrite.io/v1" +export APPWRITE_PROJECT_ID="your-project-id" +export APPWRITE_API_KEY="your-api-key" +export APPWRITE_DATABASE_ID="your-database-id" + +# Run the setup script +node scripts/setup-database.js +\`\`\` + +**Best Practices for Setup Scripts:** + +1. **Separate File**: Keep setup scripts in a \`scripts/\` directory +2. **Documentation**: Add comments explaining each table's purpose and relationships +3. **Testing**: Test the script on a separate development database before production +4. **Migration Strategy**: For schema changes, create new migration scripts instead of modifying the original setup +5. **Backup First**: Always backup production data before running schema changes +6. **Team ID Fields**: Always include \`teamId\` fields in multi-tenant tables +7. **Timestamp Fields**: Include \`createdAt\` and \`updatedAt\` fields for auditing +8. **Foreign Keys**: Use relationship columns to enforce referential integrity + +### Best Practices for TablesDB -- **SDK Usage**: Always use \`TablesDB\` instead of \`Databases\` in the SDKs +- **SDK Usage**: Use the \`TablesDB\` service (formerly \`Databases\`) for all database operations - **Permissions & Multi-Tenancy**: ALWAYS use team/member-based roles for permissions (see Permissions & Multi-Tenancy section above). Never use user-specific permissions in multi-tenant applications - **Tenant Isolation**: Always include \`teamId\` fields in your rows and filter queries by \`teamId\` to ensure complete data isolation between tenants - **Permission Patterns**: Apply team roles (owner, admin, member, viewer) consistently across all tables. Use Role.team() for all permission checks diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index fc01412..64958e6 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -15,8 +15,7 @@ { id: 'functions', label: 'Functions' }, { id: 'messaging', label: 'Messaging' }, { id: 'sites', label: 'Sites' }, - { id: 'realtime', label: 'Realtime' }, - { id: 'mcp', label: 'MCP' } + { id: 'realtime', label: 'Realtime' } ]; function updateFrameworks() { @@ -40,7 +39,7 @@ const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = `APPWRITE-${selectedSDK}-${selectedFramework}.mdc`; + a.download = 'AGENTS.md'; document.body.appendChild(a); a.click(); document.body.removeChild(a); @@ -130,7 +129,7 @@ {copyButtonText} diff --git a/src/routes/api/rules/+server.js b/src/routes/api/rules/+server.js index 65f8a03..6652dee 100644 --- a/src/routes/api/rules/+server.js +++ b/src/routes/api/rules/+server.js @@ -24,7 +24,6 @@ export async function GET({ url }) { const sdk = url.searchParams.get('sdk') || 'javascript'; const framework = url.searchParams.get('framework') || 'nextjs'; const featuresParam = url.searchParams.get('features'); - const includeMCP = url.searchParams.get('mcp') === 'true'; const format = url.searchParams.get('format') || 'text'; // 'text' or 'json' // Validate SDK @@ -55,8 +54,7 @@ export async function GET({ url }) { const rules = await generateRules({ sdk, framework, - features, - includeMCP + features }); // Return based on format @@ -65,7 +63,6 @@ export async function GET({ url }) { sdk, framework, features, - includeMCP, rules }); } @@ -73,7 +70,7 @@ export async function GET({ url }) { return text(rules, { headers: { 'Content-Type': 'text/markdown; charset=utf-8', - 'Content-Disposition': `attachment; filename="APPWRITE-${sdk}-${framework}.mdc"` + 'Content-Disposition': 'attachment; filename="AGENTS.md"' } }); } catch (error) { From dd27f5b7fe40f188fa11177c6c91e2539cfb4ab6 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Fri, 19 Dec 2025 21:52:17 +0530 Subject: [PATCH 19/19] Add EXAMPLE-AGENTS.md for comprehensive Appwrite development guidelines; enhance rules generator and framework templates with API references for improved documentation. --- .gitignore | 9 +- EXAMPLE-AGENTS.md | 1251 +++++++++++++++++++++++++++++ src/lib/languages/common/utils.js | 18 +- src/lib/languages/js/astro.js | 11 +- src/lib/languages/js/nextjs.js | 11 +- src/lib/languages/js/nodejs.js | 18 +- src/lib/languages/js/nuxt.js | 11 +- src/lib/languages/js/svelte.js | 11 +- src/lib/languages/js/tanstack.js | 11 +- src/lib/rules-generator.js | 4 +- 10 files changed, 1326 insertions(+), 29 deletions(-) create mode 100644 EXAMPLE-AGENTS.md diff --git a/.gitignore b/.gitignore index e981ac1..7273ae5 100644 --- a/.gitignore +++ b/.gitignore @@ -24,11 +24,4 @@ vite.config.ts.timestamp-* # Rules -AGENTS.md - -# Generated Files - -API_FIX_SUMMARY_V2.md -API_FIX_SUMMARY.md -API_REFERENCES_FINAL.md -API_REFERENCES_VERIFICATION.md \ No newline at end of file +AGENTS.md \ No newline at end of file diff --git a/EXAMPLE-AGENTS.md b/EXAMPLE-AGENTS.md new file mode 100644 index 0000000..36b36a8 --- /dev/null +++ b/EXAMPLE-AGENTS.md @@ -0,0 +1,1251 @@ +# Appwrite Development Rules + +> You are an expert developer focused on building apps with Appwrite's JavaScript/TypeScript SDK. + +## Overview + +This file provides AI coding assistants with Appwrite-specific development instructions, best practices, and code patterns for the JavaScript/TypeScript SDK with nextjs. + +## SDK Installation + +Install the Appwrite Node.js Server SDK using npm: + +```bash +npm install node-appwrite +``` + +You can also use yarn, pnpm, or bun instead. + +**Framework Documentation:** +- [Next.js App Router Docs](https://nextjs.org/docs/app) +- [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) +- [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/nextjs) + +**API References:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Account API](https://appwrite.io/docs/references/cloud/server-nodejs/account) - Session management and account operations +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations and queries +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File upload, download, and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications + +**SSR Authentication Pattern:** + +Server-side rendering requires using the Server SDK instead of the client SDK. + +**Authentication Flow:** +1. User credentials are sent from browser to your server +2. Your server authenticates with Appwrite using the Server SDK +3. Appwrite returns a session object +4. Store the session secret in an httpOnly cookie +5. Subsequent requests include the session cookie +6. Your server makes authenticated requests on behalf of the user + +**Key Implementation Details:** + +**Initialize Two Clients:** +- **Admin Client**: Uses API key for unauthenticated requests and session creation +- **Session Client**: Uses session cookie for user-specific requests + +**Best Practices:** +- Use httpOnly, secure, and sameSite cookie flags +- Create new session client per request +- Never share clients between requests +- Use API key for admin client to bypass rate limits +- Set forwarded user agent for better session tracking + +**See full SSR auth guide:** https://appwrite.io/docs/products/auth/server-side-rendering + +**Creating Sessions:** +```javascript +import { Client, Account } from "node-appwrite"; + +// In your login endpoint: +const account = new Account(adminClient); +const session = await account.createEmailPasswordSession(email, password); + +// Set httpOnly cookie with session secret +res.cookie('a_session_', session.secret, { + httpOnly: true, + secure: true, + sameSite: 'strict', + expires: new Date(session.expire), + path: '/' +}); +``` + +**Making Authenticated Requests:** +```javascript +// Read session from cookie +const session = req.cookies['a_session_']; + +// Create session client +const sessionClient = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('') + .setSession(session); + +const account = new Account(sessionClient); +const user = await account.get(); +``` + +**OAuth2 Flow:** +1. Redirect to OAuth provider using createOAuth2Token +2. Handle callback with userId and secret parameters +3. Call createSession to exchange for session object +4. Store session secret in cookie + + +## 🚨 Absolute Rules (Non-Negotiable) + +### Authoritative Access Pattern + +- **NEVER** import or invoke the Appwrite SDK directly from feature/component code +- **ALWAYS** use centralized wrapper functions for all data access +- **ALWAYS** authenticate before any data operation +- **NEVER** expose API keys to client-side code + +--- + +## Error Handling + +- Let errors bubble by default for consistent error handling +- Catch only when adding context or performing cleanup +- Always rethrow with clear error messages +- Never swallow errors silently + +--- + +## Type Safety + +- Avoid untyped/dynamic types where possible +- Define models/interfaces/structs for all data structures +- Use your language's type system to enforce constraints +- Validate inputs with appropriate validation libraries for your language + + + +## Next.js Server Action Pattern + +### Mandatory Structure (App Router) + +Every server action must: +1. Be marked with `'use server'` +2. Authenticate immediately +3. Validate input +4. Use centralized db/storage helpers only +5. Return plain serializable objects + +### Canonical Example + +```typescript +// app/actions/items.ts +'use server' + +import { auth } from '@/lib/auth' +import { db } from '@/lib/db' + +export async function createItem(formData: FormData) { + // 1. Authenticate first + const session = await auth() + if (!session?.user) throw new Error('Unauthorized') + + // 2. Validate input + const title = formData.get('title')?.toString() + const teamId = formData.get('teamId')?.toString() + + if (!title || title.length === 0 || title.length > 120) { + throw new Error('Invalid title') + } + + // 3. Use centralized db helper + const item = await db.items.create({ + title: title.trim(), + description: null, + createdBy: session.user.id, + teamId: teamId ?? null, + }) + + // 4. Revalidate and return + revalidatePath('/items') + return { item } +} +``` + +### Route Handler Pattern + +```typescript +// app/api/items/route.ts +import { NextResponse } from 'next/server' +import { auth } from '@/lib/auth' +import { db } from '@/lib/db' + +export async function GET() { + const session = await auth() + if (!session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const items = await db.items.listByOwner(session.user.id) + return NextResponse.json({ items }) +} + +export async function POST(request: Request) { + const session = await auth() + if (!session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const body = await request.json() + const item = await db.items.create({ + ...body, + createdBy: session.user.id, + }) + + return NextResponse.json({ item }, { status: 201 }) +} +``` + +### Server Component Data Fetching + +```typescript +// app/items/page.tsx +import { auth } from '@/lib/auth' +import { db } from '@/lib/db' + +export default async function ItemsPage() { + const session = await auth() + if (!session?.user) redirect('/login') + + const items = await db.items.listByOwner(session.user.id) + + return +} +``` + +### Environment Variables + +Required in `.env.local`: +- `APPWRITE_ENDPOINT` +- `APPWRITE_PROJECT_ID` +- `APPWRITE_API_KEY` +- `APPWRITE_DATABASE_ID` +- `APPWRITE_BUCKET_ID` (if using storage) + + + +## Database Implementation Rules + +### Database Wrapper Requirements + +Create centralized database helpers that: +- Configure the admin client with proper credentials +- Handle project, database, and table IDs +- Manage permissions automatically +- Return typed/structured objects (never raw Appwrite rows) + +- **NEVER** use TablesDB SDK directly for app data +- **ALWAYS** use centralized wrapper functions for database access + +--- + +## Ownership Enforcement (Critical) + +### User-Owned Entities (`createdBy`) + +Every user-owned table must include a `createdBy` column. + +| Operation | Rule | +|-----------|------| +| **Create** | Set `createdBy` to authenticated user's `$id` | +| **List** | Filter with `Query.equal('createdBy', [userId])` | +| **Read** | Verify ownership before returning data | +| **Update** | Confirm ownership; NEVER allow `createdBy` to change | +| **Delete** | Confirm ownership before deletion | + +### Team-Owned Entities (`teamId`) + +For shared workspaces and organization data: + +| Operation | Rule | +|-----------|------| +| **Create** | Set `teamId`; verify user is team member | +| **List** | Filter with `Query.equal('teamId', [teamId])` | +| **Read** | Verify team match AND user membership | +| **Update** | Confirm membership; NEVER allow `teamId` to change | +| **Delete** | Confirm membership before deletion | + +--- + +## Database Usage Rules + +- Import database helpers from centralized location only +- Operate on **tables**, expect **rows** +- Create payloads: exclude system columns (`$id`, `$createdAt`, `$updatedAt`) +- Update payloads: partial data, exclude system and ownership columns +- NEVER modify: `$id`, `$createdAt`, `$updatedAt`, `createdBy`, `teamId` +- Use `Query.equal()` for ownership filtering +- Return serialized/structured objects only (no raw SDK responses) + +--- + +## Sanitization & Payload Rules + +- Trim all user-provided strings +- Convert empty optional text to `null` (or language equivalent) +- Creates: require full payload (minus system fields) +- Updates: accept partial payload +- Ownership fields (`createdBy`, `teamId`) are IMMUTABLE after creation + + + +## Database Wrapper Template + +Create a centralized database helper at your designated location (e.g., `lib/db.ts` or `server/lib/db.ts`): + +```typescript +import { Client, TablesDB, Query, ID } from 'node-appwrite' + +// Initialize admin client (server-side only) +const client = new Client() + .setEndpoint(process.env.APPWRITE_ENDPOINT!) + .setProject(process.env.APPWRITE_PROJECT_ID!) + .setKey(process.env.APPWRITE_API_KEY!) + +const tablesDB = new TablesDB(client) +const DATABASE_ID = process.env.APPWRITE_DATABASE_ID! + +// Generic CRUD helper factory +function createTable(tableId: string) { + return { + async create(data: Omit) { + const doc = await tablesDB.createRow( + DATABASE_ID, + tableId, + ID.unique(), + data + ) + return doc as T + }, + + async get(id: string) { + try { + const doc = await tablesDB.getRow(DATABASE_ID, tableId, id) + return doc as T + } catch { + return null + } + }, + + async listByOwner(userId: string) { + const response = await tablesDB.listRows(DATABASE_ID, tableId, [ + Query.equal('createdBy', [userId]), + Query.orderDesc('$createdAt'), + ]) + return response.rows as T[] + }, + + async listByTeam(teamId: string) { + const response = await tablesDB.listRows(DATABASE_ID, tableId, [ + Query.equal('teamId', [teamId]), + Query.orderDesc('$createdAt'), + ]) + return response.rows as T[] + }, + + async update(id: string, data: Partial) { + // Remove immutable columns + const { $id, $createdAt, $updatedAt, createdBy, teamId, ...updateData } = data as any + const doc = await tablesDB.updateRow(DATABASE_ID, tableId, id, updateData) + return doc as T + }, + + async delete(id: string) { + await tablesDB.deleteRow(DATABASE_ID, tableId, id) + }, + } +} + +// Export typed tables +export const db = { + items: createTable('items'), + projects: createTable('projects'), + // Add more tables as needed +} +``` + + + +## Storage Implementation Rules + +### Storage Wrapper Requirements + +Create centralized storage helpers that: +- Initialize storage with proper bucket configuration +- Handle file upload/download conversions +- Manage file permissions +- Return only file IDs or data URLs (never raw binary data to client) + +- **NEVER** use Storage SDK directly for app data +- **ALWAYS** use centralized wrapper functions for storage access + +--- + +## Storage Usage Rules + +### Upload Flow (Mandatory Conversion) + +``` +Client → Server: base64 string (or file bytes) +Server: Decode base64 → bytes/buffer → InputFile +Server → Storage: InputFile +Storage → Server: File metadata +Server → Client: file ID only +``` + +Rules: +- Strip `data:...;base64,` prefix before processing +- Decode base64 to bytes using your language's standard library +- Use the SDK's InputFile helper for uploads +- NEVER store base64 strings in database +- Store **file IDs only** in database fields + +### Read Flow + +``` +Server: Fetch file from storage (binary/bytes) +Server: Convert bytes → base64 → data URL +Server → Client: data URL string (or secure download URL) +``` + +- NEVER expose raw binary data to client +- Always return URL strings or base64 data URLs + +--- + +## File References in Rows + +- Store file references as string arrays (`fileIds` field) +- On row load, fetch file URLs and inject into response +- Handle missing files gracefully +- Implement orphaned file cleanup when rows are deleted + + + +## Storage Wrapper Template + +Create a centralized storage helper: + +```typescript +import { Client, Storage, ID } from 'node-appwrite' +import { InputFile } from 'node-appwrite/file' + +const client = new Client() + .setEndpoint(process.env.APPWRITE_ENDPOINT!) + .setProject(process.env.APPWRITE_PROJECT_ID!) + .setKey(process.env.APPWRITE_API_KEY!) + +const storage = new Storage(client) +const BUCKET_ID = process.env.APPWRITE_BUCKET_ID! + +export const fileStorage = { + /** + * Upload file from base64 string + * @returns File ID only (never store base64 in database) + */ + async upload(base64Data: string, fileName: string, mimeType: string) { + // Strip data URL prefix if present + const base64Clean = base64Data.replace(/^data:[^;]+;base64,/, '') + + // Convert to buffer + const buffer = Buffer.from(base64Clean, 'base64') + + // Create InputFile + const inputFile = InputFile.fromBuffer(buffer, fileName) + + // Upload to storage + const file = await storage.createFile(BUCKET_ID, ID.unique(), inputFile) + + return file.$id // Return ID only + }, + + /** + * Get file as data URL for client consumption + */ + async getAsDataUrl(fileId: string, mimeType: string) { + const arrayBuffer = await storage.getFileDownload(BUCKET_ID, fileId) + const base64 = Buffer.from(arrayBuffer).toString('base64') + return `data:${mimeType};base64,${base64}` + }, + + /** + * Get file preview URL + */ + getPreviewUrl(fileId: string, width?: number, height?: number) { + return storage.getFilePreview(BUCKET_ID, fileId, width, height) + }, + + /** + * Delete file + */ + async delete(fileId: string) { + await storage.deleteFile(BUCKET_ID, fileId) + }, + + /** + * Delete multiple files (for cleanup) + */ + async deleteMany(fileIds: string[]) { + await Promise.allSettled( + fileIds.map(id => storage.deleteFile(BUCKET_ID, id)) + ) + }, +} +``` + + + +## Functions Integration Pattern + +**Documentation:** [Functions Quick Start](https://appwrite.io/docs/products/functions/quick-start) + +### When to Use Appwrite Functions + +- **Scheduled tasks**: Cron jobs, periodic cleanup, report generation +- **Event-driven processing**: Triggered by database changes, file uploads, user events +- **Background jobs**: Long-running operations, email sending, data processing +- **Webhooks**: Third-party integrations, payment processing +- **Server-side logic**: Operations requiring elevated permissions + +### Execution Patterns + +| Pattern | Use Case | Method | +|---------|----------|--------| +| **Synchronous** | Immediate response needed | `createExecution(functionId, body, async=false)` | +| **Asynchronous** | Fire-and-forget, long tasks | `createExecution(functionId, body, async=true)` | +| **Scheduled** | Cron-based triggers | Configure in Console/appwrite.json | +| **Event-driven** | Database/storage triggers | Configure event subscriptions | + +### Implementation Steps + +1. **Initialize Functions service** with your SDK's client +2. **Call `createExecution()`** with: + - `functionId`: The function's unique ID + - `body`: JSON string of data to pass + - `async`: Boolean for sync/async execution + - `path`: Optional route path (default: "/") + - `method`: HTTP method (GET, POST, etc.) +3. **Handle response**: Check `status` field for success/failure +4. **Parse `responseBody`** as JSON for the result + +### Function Development Best Practices + +- Use [Appwrite Function Templates](https://github.com/appwrite/templates) as starting points +- Store secrets in function environment variables, not in code +- Return JSON responses for easy parsing +- Implement proper error handling and logging +- Use typed request/response structures for your language + +### Resources + +- [Develop Functions](https://appwrite.io/docs/products/functions/develop) +- [Execute Functions](https://appwrite.io/docs/products/functions/execute) +- [Function Runtimes](https://appwrite.io/docs/products/functions/runtimes) +- [Event Triggers](https://appwrite.io/docs/advanced/platform/events) + + + +## Messaging Integration Pattern + +**Documentation:** [Messaging Overview](https://appwrite.io/docs/products/messaging) + +### Message Types + +| Type | Method | Use Case | +|------|--------|----------| +| **Email** | `createEmail()` | Transactional emails, notifications | +| **Push** | `createPush()` | Mobile/web push notifications | +| **SMS** | `createSms()` | Text messages, verification codes | + +### Targeting Options + +Messages can be sent to: +- **Users**: Array of user IDs (`users` parameter) +- **Targets**: Specific device/endpoint IDs (`targets` parameter) +- **Topics**: Broadcast to subscribers (`topics` parameter) + +### Implementation Steps + +1. **Initialize Messaging service** with your SDK's client (requires API key) +2. **Create message** using the appropriate method: + - `createEmail(messageId, subject, content, topics, users, targets, ...)` + - `createPush(messageId, title, body, topics, users, targets, data)` + - `createSms(messageId, content, topics, users, targets)` +3. **Handle delivery status** by checking the returned message object + +### Topic Subscriptions + +- **Subscribe**: `createSubscriber(topicId, subscriberId, targetId)` +- **Unsubscribe**: `deleteSubscriber(topicId, subscriberId)` + +### Provider Configuration Required + +| Channel | Providers | +|---------|-----------| +| **Email** | SMTP, Mailgun, SendGrid, Mailchimp | +| **Push** | FCM (Android), APNS (iOS) | +| **SMS** | Twilio, Vonage, Textmagic, Telesign | + +Configure providers in Appwrite Console → Messaging → Providers + +### Best Practices + +- Generate unique message IDs for each send +- Handle message failures gracefully +- Use topics for broadcast messages, targets for specific devices +- Store provider credentials securely in Console +- Implement retry logic for failed deliveries + +### Resources + +- [Send Email](https://appwrite.io/docs/products/messaging/send-email-messages) +- [Send Push](https://appwrite.io/docs/products/messaging/send-push-notifications) +- [Send SMS](https://appwrite.io/docs/products/messaging/send-sms-messages) +- [Topics](https://appwrite.io/docs/products/messaging/topics) + + + +## Realtime Integration Pattern + +**Documentation:** [Realtime Overview](https://appwrite.io/docs/products/realtime) + +### Overview + +Realtime subscriptions allow your app to receive live updates when data changes. Subscriptions run on the **client side** using the client SDK. + +### Subscription Flow + +1. **Initialize client** with endpoint and project ID +2. **Subscribe to channel(s)** using `client.subscribe(channels, callback)` +3. **Handle events** in the callback (create, update, delete) +4. **Unsubscribe** when component unmounts or no longer needed + +### Realtime Channels Reference + +| Channel Pattern | Description | +|-----------------|-------------| +| `databases.[DB_ID].tables.[TABLE_ID].rows` | All rows in table | +| `databases.[DB_ID].tables.[TABLE_ID].rows.[ROW_ID]` | Specific row | +| `buckets.[BUCKET_ID].files` | All files in bucket | +| `buckets.[BUCKET_ID].files.[FILE_ID]` | Specific file | +| `account` | Current user's account changes | +| `teams` | Team membership changes | +| `teams.[TEAM_ID]` | Specific team changes | + +### Event Types + +The callback receives an event object with: +- `events`: Array of event strings (e.g., `databases.*.tables.*.rows.*.create`) +- `payload`: The row/file/account data that changed + +| Event | Triggered When | +|-------|----------------| +| `*.create` | New row/file created | +| `*.update` | Existing row/file updated | +| `*.delete` | Row/file deleted | + +### Implementation Pattern + +``` +// Pseudocode for any SDK +subscription = client.subscribe( + ["databases.DB_ID.tables.TABLE_ID.rows"], + function(event) { + if event.type contains "create": + add event.payload to local state + else if event.type contains "update": + update matching item in local state + else if event.type contains "delete": + remove matching item from local state + } +) + +// On cleanup/unmount: +subscription.close() // or unsubscribe() +``` + +### Best Practices + +- **Always unsubscribe** in cleanup/dispose functions +- Use **client SDK** for realtime (not server SDK) +- Filter events client-side if you only need specific event types +- Implement **reconnection logic** for dropped connections +- Consider **optimistic updates** combined with realtime for better UX +- Use **initial data from server** then enhance with realtime updates +- Handle the case where connection drops and reconnects + +### Resources + +- [Subscribe to Databases](https://appwrite.io/docs/products/realtime/subscribe-to-databases) +- [Subscribe to Storage](https://appwrite.io/docs/products/realtime/subscribe-to-storage) +- [Subscribe to Account](https://appwrite.io/docs/products/realtime/subscribe-to-account) +- [Channels Reference](https://appwrite.io/docs/products/realtime/channels) + + + +## Sites Deployment Pattern + +Appwrite Sites is a hosting platform for static and SSR applications. Configuration is primarily done through the Appwrite Console or CLI. + +### Deployment Methods + +1. **Git Integration** (Recommended) + - Connect your repository in Appwrite Console + - Automatic deployments on push to configured branch + - See: [Deploy from Git](https://appwrite.io/docs/products/sites/deploy-from-git) + +2. **CLI Deployment** + - Use `appwrite deploy` command + - See: [Deploy from CLI](https://appwrite.io/docs/products/sites/deploy-from-cli) + +3. **Manual Upload** + - Upload built assets directly + - See: [Deploy Manually](https://appwrite.io/docs/products/sites/deploy-manually) + +### Environment Variables + +Configure in Appwrite Console under Site settings: +- `APPWRITE_ENDPOINT` - Your Appwrite endpoint +- `APPWRITE_PROJECT_ID` - Your project ID +- Build-time vs runtime variables as needed + +### Framework-Specific Notes + +- **Static Sites**: Build output uploaded as-is +- **SSR Apps**: Require Appwrite Functions runtime +- Check [Frameworks Documentation](https://appwrite.io/docs/products/sites/frameworks) for your specific framework + +### Rollbacks + +Appwrite Sites supports instant rollbacks: +- View deployment history in Console +- Click "Activate" on any previous deployment +- See: [Instant Rollbacks](https://appwrite.io/docs/products/sites/instant-rollbacks) + + +## Next.js-Specific Best Practices + +### Rendering Strategy +- Default to Server Components for all data fetching +- Use `'use server'` for all mutation functions +- Only use Client Components when explicitly needed for interactivity +- Never import Appwrite SDK in Client Components + +### Data Fetching Pattern +```typescript +// In Server Component - direct async/await +async function ItemsPage() { + const items = await db.items.listByOwner(userId) + return +} +``` + +### Revalidation +```typescript +// After mutations, revalidate the path +import { revalidatePath } from 'next/cache' + +export async function createItem(data) { + 'use server' + const item = await db.items.create(data) + revalidatePath('/items') + return { item } +} +``` + +### File Organization +``` +app/ +├── actions/ # Server Actions +│ └── items.ts +├── lib/ +│ ├── db.ts # Database wrapper (server-only) +│ ├── storage.ts # Storage wrapper (server-only) +│ └── auth.ts # Auth helpers +└── (routes)/ + └── items/ + └── page.tsx # Server Component +``` + +## Authentication & Teams + +**Authentication Documentation:** + +- [Authentication Quick Start](https://appwrite.io/docs/products/auth/quick-start) - Getting started with authentication +- [Email & Password](https://appwrite.io/docs/products/auth/email-password) - Email/password authentication +- [OAuth2 Providers](https://appwrite.io/docs/products/auth/oauth2) - Social authentication (Google, GitHub, etc.) +- [Magic URL](https://appwrite.io/docs/products/auth/magic-url) - Passwordless authentication via email +- [Phone (SMS)](https://appwrite.io/docs/products/auth/phone-sms) - Phone number authentication +- [Anonymous Sessions](https://appwrite.io/docs/products/auth/anonymous) - Guest/anonymous users +- [JWT Tokens](https://appwrite.io/docs/products/auth/jwt) - JSON Web Token authentication +- [MFA/2FA](https://appwrite.io/docs/products/auth/mfa) - Multi-factor authentication +- [SSR Authentication](https://appwrite.io/docs/products/auth/server-side-rendering) - Server-side rendering auth patterns +- [Teams](https://appwrite.io/docs/products/auth/teams) - Team management and team-based permissions +- [Team Invites](https://appwrite.io/docs/products/auth/team-invites) - Inviting members to teams +- [Multi-tenancy](https://appwrite.io/docs/products/auth/multi-tenancy) - Building multi-tenant applications with teams + +### Best Practices for Authentication & Teams + +- **Session Security**: Always use HttpOnly cookies for session storage in SSR applications +- **API Keys**: Never expose API keys to client-side code - use environment variables +- **Session Validation**: Always validate sessions on the server before trusting them +- **Team-Based Architecture**: ALWAYS prefer team/member-based roles over user-specific roles for any application requiring shared access or multi-tenancy +- **Multi-Tenant Applications**: Use teams as the primary mechanism for tenant isolation and resource sharing +- **OAuth Redirects**: Handle OAuth redirects properly with success and failure URLs +- **Password Security**: Use strong password requirements and consider implementing MFA +- **Session Expiry**: Configure appropriate session expiry times based on your security requirements + +### Team & Member Management Fundamentals + +When building applications that involve multiple users or tenants: + +1. **Always Start with Teams**: For any feature requiring shared access, create a team first, then add members with roles +2. **Role-Based Access**: Assign roles (e.g., "owner", "admin", "member", "viewer") to team members rather than setting individual user permissions +3. **Team Isolation**: Use teams as the boundary for data isolation in multi-tenant applications +4. **Member Invitations**: Implement team invitation workflows for onboarding new members +5. **Role Management**: Build role management UIs that allow team owners/admins to manage member roles dynamically + +## Permissions & Multi-Tenancy + +This section is CRITICAL for building secure, scalable applications with Appwrite. Multi-tenancy is one of the most important architectural patterns in modern applications, and Appwrite's team-based permission system is designed specifically for this. + +**Permissions Documentation:** + +- [Permissions Overview](https://appwrite.io/docs/advanced/platform/permissions) - Understanding Appwrite's permission system +- [Role Types](https://appwrite.io/docs/advanced/platform/permissions#role-types) - Available permission roles (any, users, guests, team, member, label) +- [Permission Types](https://appwrite.io/docs/advanced/platform/permissions#permission-types) - Read, create, update, delete permissions +- [Teams](https://appwrite.io/docs/products/auth/teams) - Team-based access control +- [Multi-tenancy](https://appwrite.io/docs/products/auth/multi-tenancy) - Tenant isolation patterns + +### Why Multi-Tenancy Matters + +Multi-tenancy allows a single application instance to serve multiple isolated groups of users (tenants) while maintaining complete data isolation and security. Almost every modern SaaS application requires multi-tenancy to scale efficiently. + +### The Critical Pattern: Team-Based Permissions + +**ALWAYS PREFER TEAM/MEMBER-BASED ROLES over user-specific roles.** This is a fundamental architectural decision: + +#### Avoid: User-Specific Permissions +```javascript +// DON'T: User-specific permissions don't scale +Permission.read(Role.user('')) +Permission.write(Role.user('')) +``` + +**Problems with user-specific permissions:** +- Hard to scale when users need to share resources +- Difficult to add/remove access without updating every row +- No way to represent organizational hierarchies +- Poor support for collaborative features + +#### Prefer: Team/Member-Based Roles +```javascript +// DO: Team-based permissions scale with your organization +Permission.read(Role.team('', 'member')) +Permission.update(Role.team('', 'admin')) +Permission.delete(Role.team('', 'owner')) +``` + +**Benefits of team/member-based roles:** +- Automatic access for all team members based on their role +- Easy to add/remove members without touching rows +- Scales naturally as teams grow +- Supports organizational hierarchies and complex permissions + +### Query Isolation Pattern + +**CRITICAL**: Always filter queries by `teamId` to ensure tenant isolation: + +```javascript +// ALWAYS filter by teamId for tenant isolation +const response = await tablesDB.listRows( + '', + '', + [ + Query.equal('teamId', ''), // Critical for isolation + Query.orderDesc('$createdAt'), + Query.limit(25) + ] +); +``` + +### Role Verification Pattern + +Before allowing sensitive operations, always verify the user's role: + +```javascript +// Verify permissions before sensitive operations +const memberships = await teams.listMemberships(''); +const membership = memberships.memberships.find(m => m.userId === userId); + +if (!membership?.roles.includes('admin')) { + throw new Error('Insufficient permissions'); +} +``` + +### Multi-Tenancy Implementation Guide + +#### Step 1: Create Teams Structure + +Teams in Appwrite represent tenants. Each team should map to a business entity (company, organization, workspace). + +See: [Teams Documentation](https://appwrite.io/docs/products/auth/teams) + +#### Step 2: Define Custom Roles + +Common role hierarchy: +- **owner**: Full control, can manage team settings and members +- **admin**: Can manage resources and most settings +- **member**: Can create/edit resources with limited permissions +- **viewer**: Read-only access + +#### Step 3: Member Management + +For team invitations and membership management, see [Team Invites Guide](https://appwrite.io/docs/products/auth/team-invites) + +#### Step 4: Apply Permissions Consistently + +Use team roles for all resources: +- **Database rows**: Apply `Role.team('', 'role')` permissions +- **Storage files**: Same team-based permission pattern +- **Always include `teamId`** as a field in your rows for query filtering + +### Permission Best Practices + +1. **Always Store teamId**: Every row in a multi-tenant app should have a `teamId` field +2. **Default Deny**: Don't grant permissions unless explicitly needed +3. **Role Hierarchy**: Design roles to reflect natural hierarchies (owner > admin > member > viewer) +4. **Server-Side Validation**: Always validate team membership server-side +5. **Query Isolation**: Every multi-tenant query MUST include a `teamId` filter + +### Common Multi-Tenancy Patterns + +| Pattern | Example Apps | Structure | +|---------|--------------|-----------| +| Workspace-Based | Notion, Slack | Each workspace = 1 team, users can belong to multiple teams | +| Organization-Based | GitHub, GitLab | Each org = 1 team, resources scoped to org | +| Project-Based | Linear, Asana | Each project = 1 team, members invited per project | + +### Debugging Permission Issues + +1. **Check Team Membership**: Verify user is actually a member of the team +2. **Verify Roles**: Check `membership.roles` contains the required role +3. **Check Permission Strings**: Permission strings are case-sensitive +4. **Query Filters**: Ensure `teamId` filters are applied correctly +5. **Server vs Client**: Some operations require the Server SDK + +### Additional Resources + +**Authentication Documentation:** + +- [Authentication Quick Start](https://appwrite.io/docs/products/auth/quick-start) - Getting started with authentication +- [Email & Password](https://appwrite.io/docs/products/auth/email-password) - Email/password authentication +- [OAuth2 Providers](https://appwrite.io/docs/products/auth/oauth2) - Social authentication (Google, GitHub, etc.) +- [Magic URL](https://appwrite.io/docs/products/auth/magic-url) - Passwordless authentication via email +- [Phone (SMS)](https://appwrite.io/docs/products/auth/phone-sms) - Phone number authentication +- [Anonymous Sessions](https://appwrite.io/docs/products/auth/anonymous) - Guest/anonymous users +- [JWT Tokens](https://appwrite.io/docs/products/auth/jwt) - JSON Web Token authentication +- [MFA/2FA](https://appwrite.io/docs/products/auth/mfa) - Multi-factor authentication +- [SSR Authentication](https://appwrite.io/docs/products/auth/server-side-rendering) - Server-side rendering auth patterns +- [Teams](https://appwrite.io/docs/products/auth/teams) - Team management and team-based permissions +- [Team Invites](https://appwrite.io/docs/products/auth/team-invites) - Inviting members to teams +- [Multi-tenancy](https://appwrite.io/docs/products/auth/multi-tenancy) - Building multi-tenant applications with teams + +## Database Operations + +**Database Documentation:** + +- [Database Quick Start](https://appwrite.io/docs/products/databases/quick-start) - Getting started with TablesDB +- [Tables](https://appwrite.io/docs/products/databases/tables) - Creating and managing tables +- [Rows](https://appwrite.io/docs/products/databases/rows) - CRUD operations on rows +- [Queries](https://appwrite.io/docs/products/databases/queries) - Filtering, sorting, and querying data +- [Pagination](https://appwrite.io/docs/products/databases/pagination) - Paginating large datasets +- [Relationships](https://appwrite.io/docs/products/databases/relationships) - One-to-one, one-to-many, many-to-many relationships +- [Permissions](https://appwrite.io/docs/products/databases/permissions) - Row and table-level permissions +- [Transactions](https://appwrite.io/docs/products/databases/transactions) - Atomic operations + +### Database Setup Scripts + +**ALWAYS create a database setup script using the Server SDK and API key** to initialize your database schema. This script should be version-controlled and run during deployment or initial setup. + +**Why Use Setup Scripts:** +- **Infrastructure as Code**: Database schema becomes part of your codebase, not manual console clicks +- **Reproducibility**: Easy to recreate database structure across different environments (dev, staging, production) +- **Version Control**: Track schema changes over time with Git +- **Team Collaboration**: All developers can sync database structure automatically +- **CI/CD Integration**: Automate database setup in deployment pipelines +- **Documentation**: The script serves as living documentation of your database structure + +**What Your Setup Script Should Include:** + +1. **Table Creation**: All tables with proper naming and IDs +2. **Column Definitions**: All columns with correct data types (string, integer, boolean, datetime, email, url, etc.) +3. **Indexes**: Performance-critical indexes on frequently queried fields (especially `teamId`, foreign keys, search fields) +4. **Relationships**: All table relationships and foreign key constraints +5. **Default Permissions**: Table-level permissions using team roles +6. **Column Constraints**: Required fields, string lengths, number ranges, enum values, default values + +**Setup Script Requirements:** + +- **Use Server SDK**: Must use the Server SDK (node-appwrite, appwrite/appwrite for PHP, etc.), NOT the client SDK +- **Require API Key**: The script must use an API key with appropriate scopes (`databases.write`, `tables.write`, `columns.write`) +- **Idempotent**: Script should safely handle re-runs (check if tables exist before creating) +- **Environment Variables**: Store API key, endpoint, project ID, and database ID in environment variables +- **Error Handling**: Proper error handling with clear error messages +- **Logging**: Log progress and errors for debugging + +**Example Setup Script Structure:** + +```javascript +// scripts/setup-database.js (Node.js example) +import { Client, TablesDB, Permission, Role } from 'node-appwrite'; + +const client = new Client() + .setEndpoint(process.env.APPWRITE_ENDPOINT) + .setProject(process.env.APPWRITE_PROJECT_ID) + .setKey(process.env.APPWRITE_API_KEY); + +const tablesDB = new TablesDB(client); +const databaseId = process.env.APPWRITE_DATABASE_ID; + +async function setupDatabase() { + try { + // Create Users table + await tablesDB.createTable(databaseId, 'users', 'Users', [ + Permission.read(Role.users()), + Permission.write(Role.users()) + ]); + + // Add columns to Users table + await tablesDB.createStringColumn(databaseId, 'users', 'name', 255, true); + await tablesDB.createEmailColumn(databaseId, 'users', 'email', true); + await tablesDB.createStringColumn(databaseId, 'users', 'teamId', 255, true); + + // Create index on teamId for query performance + await tablesDB.createIndex(databaseId, 'users', 'idx_team', 'key', ['teamId']); + + // Create Projects table with team-based permissions + await tablesDB.createTable(databaseId, 'projects', 'Projects', [ + Permission.read(Role.team('[TEAM_ID]')), + Permission.create(Role.team('[TEAM_ID]', 'member')), + Permission.update(Role.team('[TEAM_ID]', 'admin')), + Permission.delete(Role.team('[TEAM_ID]', 'owner')), + ]); + + // Add columns to Projects table + await tablesDB.createStringColumn(databaseId, 'projects', 'name', 255, true); + await tablesDB.createStringColumn(databaseId, 'projects', 'description', 5000, false); + await tablesDB.createStringColumn(databaseId, 'projects', 'teamId', 255, true); + await tablesDB.createStringColumn(databaseId, 'projects', 'ownerId', 255, true); + await tablesDB.createDatetimeColumn(databaseId, 'projects', 'createdAt', true); + + // Create indexes + await tablesDB.createIndex(databaseId, 'projects', 'idx_team', 'key', ['teamId']); + await tablesDB.createIndex(databaseId, 'projects', 'idx_owner', 'key', ['ownerId']); + + // Create relationship between projects and users + await tablesDB.createRelationshipColumn( + databaseId, + 'projects', + 'users', + 'oneToMany', + false, // twoWay + 'owner', // key in projects + 'projects', // key in users + 'cascade' // onDelete + ); + + console.log('Database setup completed successfully!'); + } catch (error) { + // Handle "already exists" errors gracefully for idempotency + if (error.code !== 409) { + console.error('Database setup failed:', error); + throw error; + } else { + console.log('Tables already exist, skipping creation'); + } + } +} + +setupDatabase(); +``` + +**Running the Setup Script:** + +```bash +# Set environment variables +export APPWRITE_ENDPOINT="https://cloud.appwrite.io/v1" +export APPWRITE_PROJECT_ID="your-project-id" +export APPWRITE_API_KEY="your-api-key" +export APPWRITE_DATABASE_ID="your-database-id" + +# Run the setup script +node scripts/setup-database.js +``` + +**Best Practices for Setup Scripts:** + +1. **Separate File**: Keep setup scripts in a `scripts/` directory +2. **Documentation**: Add comments explaining each table's purpose and relationships +3. **Testing**: Test the script on a separate development database before production +4. **Migration Strategy**: For schema changes, create new migration scripts instead of modifying the original setup +5. **Backup First**: Always backup production data before running schema changes +6. **Team ID Fields**: Always include `teamId` fields in multi-tenant tables +7. **Timestamp Fields**: Include `createdAt` and `updatedAt` fields for auditing +8. **Foreign Keys**: Use relationship columns to enforce referential integrity + +### Best Practices for TablesDB + +- **SDK Usage**: Use the `TablesDB` service (formerly `Databases`) for all database operations +- **Permissions & Multi-Tenancy**: ALWAYS use team/member-based roles for permissions (see Permissions & Multi-Tenancy section above). Never use user-specific permissions in multi-tenant applications +- **Tenant Isolation**: Always include `teamId` fields in your rows and filter queries by `teamId` to ensure complete data isolation between tenants +- **Permission Patterns**: Apply team roles (owner, admin, member, viewer) consistently across all tables. Use Role.team() for all permission checks +- **Query Security**: Every multi-tenant query MUST include a `teamId` filter to prevent cross-tenant data access +- **Table Permissions**: Set table-level permissions using team roles, then override at row level when needed +- **Query Optimization**: Use indexes for frequently queried fields, especially on `teamId` and commonly filtered fields +- **Data Validation**: Validate data before creating or updating rows, including team membership validation +- **Transactions**: Use transactions for operations that must succeed or fail together, ensuring atomicity across tenant boundaries +- **Pagination**: Always implement pagination for large datasets to improve performance and reduce response sizes +- **Type Safety**: Use type-safe models when available in your SDK for better code quality and fewer runtime errors + +## Storage Operations + +**Storage Documentation:** + +- [Storage Quick Start](https://appwrite.io/docs/products/storage/quick-start) - Getting started with storage +- [Buckets](https://appwrite.io/docs/products/storage/buckets) - Creating and managing storage buckets +- [Upload & Download](https://appwrite.io/docs/products/storage/upload-download) - File upload and download operations +- [Permissions](https://appwrite.io/docs/products/storage/permissions) - Bucket and file-level permissions +- [Images](https://appwrite.io/docs/products/storage/images) - Image manipulation and transformations + +### Best Practices for Storage + +- **Permissions & Multi-Tenancy**: ALWAYS use team/member-based roles for storage permissions (see Permissions & Multi-Tenancy section above). Apply Role.team() permissions to buckets and files for proper tenant isolation +- **Bucket Organization**: Consider organizing files by team/tenant using folder structures or bucket naming conventions for easier management +- **Tenant Isolation**: When querying files, always filter by metadata (e.g., `teamId`) to ensure users only access files from their teams +- **File Size Limits**: Set appropriate file size limits to prevent abuse and manage costs +- **File Types**: Validate file types before upload to ensure security and prevent malicious uploads +- **Permission Patterns**: Use team roles (owner, admin, member, viewer) consistently for bucket and file permissions, matching your database permission model +- **Cleanup**: Implement cleanup strategies for unused or temporary files, especially when teams are deleted +- **Virus Scanning**: Consider implementing virus scanning for uploaded files to protect all tenants +- **Access Control**: Validate team membership before allowing file uploads/downloads, even if permissions are set correctly + +## Functions + +**Functions Documentation:** + +- [Functions Quick Start](https://appwrite.io/docs/products/functions/quick-start) - Getting started with serverless functions +- [Develop Functions](https://appwrite.io/docs/products/functions/develop) - Writing and developing functions +- [Execute Functions](https://appwrite.io/docs/products/functions/execute) - Triggering function executions +- [Deployments](https://appwrite.io/docs/products/functions/deployments) - Deploying function code +- [Domains](https://appwrite.io/docs/products/functions/domains) - Custom domains for functions +- [Events](https://appwrite.io/docs/advanced/platform/events) - Event-driven function execution +- [Scheduled Executions](https://appwrite.io/docs/products/functions/execute#schedule) - Cron-based scheduling + +### Starter Templates + +For getting started with Appwrite Functions, use the official starter template for your runtime: + +- **JavaScript/TypeScript Starter**: [View Template](https://github.com/appwrite/templates/tree/main/node/starter) + +For more templates and examples, see the [Appwrite Templates Repository](https://github.com/appwrite/templates). + +### When to Use Starter Templates + +**ALWAYS use starter templates from the [Appwrite Templates Repository](https://github.com/appwrite/templates) when building functions for:** + +- **Scheduled Tasks**: Functions that run on a schedule (cron jobs, periodic cleanup, etc.) +- **Event-Driven Tasks**: Functions triggered by Appwrite events (database changes, storage uploads, user events, etc.) +- **Background Processing**: Long-running or resource-intensive operations +- **Integration Functions**: Functions that integrate with third-party services (APIs, webhooks, etc.) +- **Complex Functions**: Any function that requires specific runtime configuration or dependencies + +**Why use templates?** Starter templates provide the correct project structure, dependencies, and configuration needed for functions to build and execute successfully. They ensure proper handling of environment variables, logging, error handling, and Appwrite SDK initialization. + +### Best Practices for Functions + +- **Error Handling**: Implement comprehensive error handling in your functions +- **Timeouts**: Be aware of function execution timeouts and optimize accordingly +- **Environment Variables**: Use environment variables for configuration, not hardcoded values +- **Logging**: Implement proper logging for debugging and monitoring +- **Security**: Validate all inputs and never trust user-provided data +- **Resource Limits**: Be mindful of memory and CPU limits for function executions +- **Template Usage**: Start with official templates for scheduled and event-driven functions to ensure proper setup + +## Messaging + +**Messaging Documentation:** + +- [Messaging Overview](https://appwrite.io/docs/products/messaging) - Getting started with messaging +- [Push Notifications](https://appwrite.io/docs/products/messaging/send-push-notifications) - Sending push notifications +- [Email Messages](https://appwrite.io/docs/products/messaging/send-email-messages) - Sending emails +- [SMS Messages](https://appwrite.io/docs/products/messaging/send-sms-messages) - Sending SMS +- [Topics](https://appwrite.io/docs/products/messaging/topics) - Managing message topics +- [Targets](https://appwrite.io/docs/products/messaging/targets) - Managing message targets +- [Providers](https://appwrite.io/docs/products/messaging/providers) - Configuring messaging providers + +### Best Practices for Messaging + +- **Provider Selection**: Choose the right messaging provider based on your needs (FCM, APNS, Mailgun, Twilio, etc.) +- **Message Content**: Keep push notifications concise and actionable +- **Scheduling**: Use scheduled messages for better user engagement timing +- **Personalization**: Personalize messages to increase engagement +- **Rate Limiting**: Be mindful of rate limits when sending bulk messages +- **Error Handling**: Implement retry logic for failed message deliveries + +## Sites + +**Sites Documentation:** + +- [Sites Quick Start](https://appwrite.io/docs/products/sites/quick-start) - Getting started with Sites +- [Deploy from Git](https://appwrite.io/docs/products/sites/deploy-from-git) - Git-based deployments +- [Deploy from CLI](https://appwrite.io/docs/products/sites/deploy-from-cli) - CLI deployments +- [Deploy Manually](https://appwrite.io/docs/products/sites/deploy-manually) - Manual file uploads +- [Deployments](https://appwrite.io/docs/products/sites/deployments) - Managing deployments +- [Domains](https://appwrite.io/docs/products/sites/domains) - Custom domain configuration +- [Rendering](https://appwrite.io/docs/products/sites/rendering) - Static vs SSR rendering +- [Frameworks](https://appwrite.io/docs/products/sites/frameworks) - Supported frameworks +- [Rollbacks](https://appwrite.io/docs/products/sites/instant-rollbacks) - Instant rollbacks +- [Previews](https://appwrite.io/docs/products/sites/previews) - Deployment previews + +### Best Practices for Sites + +- **Environment Variables**: Use environment variables for configuration, not hardcoded values +- **Custom Domains**: Configure custom domains for production sites for better branding +- **Rendering Strategy**: Choose between static and SSR based on your content needs and SEO requirements +- **Deployment Strategy**: Use Git deployments for automatic builds on commits +- **Rollback Plan**: Keep previous deployments ready for instant rollbacks if needed + +## Realtime Subscriptions + +**Realtime Documentation:** + +- [Realtime Overview](https://appwrite.io/docs/products/realtime) - Getting started with realtime +- [Database Subscriptions](https://appwrite.io/docs/products/realtime/subscribe-to-databases) - Subscribe to database changes +- [Storage Subscriptions](https://appwrite.io/docs/products/realtime/subscribe-to-storage) - Subscribe to storage changes +- [Account Subscriptions](https://appwrite.io/docs/products/realtime/subscribe-to-account) - Subscribe to account changes +- [Channels](https://appwrite.io/docs/products/realtime/channels) - Available subscription channels +- [Events](https://appwrite.io/docs/products/realtime/events) - Event types and payloads + +### Best Practices for Realtime Subscriptions + +- **Connection Management**: Always unsubscribe from channels when components unmount or pages are closed to prevent memory leaks +- **Error Handling**: Implement reconnection logic for dropped connections and handle network errors gracefully +- **Event Filtering**: Filter events on the client side to only process relevant updates for better performance +- **Channel Selection**: Subscribe only to the specific channels you need to minimize bandwidth and improve performance +- **Payload Validation**: Always validate payload data before processing to ensure data integrity +- **Rate Limiting**: Be mindful of the number of subscriptions and events to avoid overwhelming the client +- **State Synchronization**: Use realtime updates to keep local state in sync with server state, but handle conflicts appropriately +- **Authentication**: Ensure proper authentication is in place before subscribing to protected channels +- **Testing**: Test realtime functionality with network interruptions and reconnection scenarios +- **Cleanup**: Store unsubscribe functions and call them in cleanup hooks (useEffect cleanup, componentWillUnmount, etc.) diff --git a/src/lib/languages/common/utils.js b/src/lib/languages/common/utils.js index 6be628b..effa417 100644 --- a/src/lib/languages/common/utils.js +++ b/src/lib/languages/common/utils.js @@ -16,18 +16,32 @@ export function createSecuritySection({ return securityNotes + (additionalNotes ? `\n\n${additionalNotes}` : ''); } +/** + * Client-side API references for JavaScript frameworks + */ +const clientAPIReferences = ` +**API References:** +- [Account API](https://appwrite.io/docs/references/cloud/client-web/account) - Authentication and user management +- [Databases API](https://appwrite.io/docs/references/cloud/client-web/databases) - Database operations and queries +- [Storage API](https://appwrite.io/docs/references/cloud/client-web/storage) - File upload, download, and management +- [Functions API](https://appwrite.io/docs/references/cloud/client-web/functions) - Serverless functions execution +- [Messaging API](https://appwrite.io/docs/references/cloud/client-web/messaging) - Push notifications and messaging +`; + /** * Creates a complete framework template by combining installation and security notes * @param {Object} options * @param {string} options.installation - Installation section * @param {string} options.securityNotes - Security/best practices section * @param {string} [options.additionalNotes] - Additional framework-specific notes + * @param {boolean} [options.includeAPIReferences=true] - Whether to include API references * @returns {string} */ -export function createFrameworkTemplate({ installation, securityNotes, additionalNotes = '' }) { +export function createFrameworkTemplate({ installation, securityNotes, additionalNotes = '', includeAPIReferences = true }) { const securitySection = createSecuritySection({ securityNotes, additionalNotes }); + const apiSection = includeAPIReferences ? clientAPIReferences : ''; return `${installation} - +${apiSection} ${securitySection}`; } diff --git a/src/lib/languages/js/astro.js b/src/lib/languages/js/astro.js index 5ba9ec5..888985d 100644 --- a/src/lib/languages/js/astro.js +++ b/src/lib/languages/js/astro.js @@ -4,6 +4,7 @@ import { getFullImplementationGuide } from '../common/implementation-patterns.js export async function astro(features = []) { const astroImplementation = getFullImplementationGuide('astro', 'javascript', features); + const authSection = features.includes('auth') ? `\n${ssrAuthPattern}\n` : ''; return `${nodeAppwriteInstall} @@ -12,8 +13,14 @@ export async function astro(features = []) { - [Astro Middleware](https://docs.astro.build/en/guides/middleware/) - [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/astro) -${ssrAuthPattern} - +**API References:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Account API](https://appwrite.io/docs/references/cloud/server-nodejs/account) - Session management and account operations +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations and queries +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File upload, download, and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +${authSection} ${astroImplementation} ## Astro-Specific Best Practices diff --git a/src/lib/languages/js/nextjs.js b/src/lib/languages/js/nextjs.js index 87afd8c..d0960f1 100644 --- a/src/lib/languages/js/nextjs.js +++ b/src/lib/languages/js/nextjs.js @@ -4,6 +4,7 @@ import { getFullImplementationGuide } from '../common/implementation-patterns.js export async function nextjs(features = []) { const nextjsImplementation = getFullImplementationGuide('nextjs', 'javascript', features); + const authSection = features.includes('auth') ? `\n${ssrAuthPattern}\n` : ''; return `${nodeAppwriteInstall} @@ -12,8 +13,14 @@ export async function nextjs(features = []) { - [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) - [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/nextjs) -${ssrAuthPattern} - +**API References:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Account API](https://appwrite.io/docs/references/cloud/server-nodejs/account) - Session management and account operations +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations and queries +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File upload, download, and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +${authSection} ${nextjsImplementation} ## Next.js-Specific Best Practices diff --git a/src/lib/languages/js/nodejs.js b/src/lib/languages/js/nodejs.js index 5176ec4..ef3e6bc 100644 --- a/src/lib/languages/js/nodejs.js +++ b/src/lib/languages/js/nodejs.js @@ -1,10 +1,16 @@ import { jsInstall } from '../common/install.js'; -import { createFrameworkTemplate } from '../common/utils.js'; import { serverSecurity } from '../common/security.js'; -export const nodejs = createFrameworkTemplate({ - installation: jsInstall('node-appwrite', 'Install the Appwrite Node.js SDK'), - securityNotes: `${serverSecurity} -- Never log or expose API keys in error messages` -}); +export const nodejs = `${jsInstall('node-appwrite', 'Install the Appwrite Node.js SDK')} + +**API References:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Account API](https://appwrite.io/docs/references/cloud/server-nodejs/account) - Session management and account operations +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations and queries +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File upload, download, and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications + +${serverSecurity} +- Never log or expose API keys in error messages`; diff --git a/src/lib/languages/js/nuxt.js b/src/lib/languages/js/nuxt.js index 6934e58..c323a2b 100644 --- a/src/lib/languages/js/nuxt.js +++ b/src/lib/languages/js/nuxt.js @@ -4,6 +4,7 @@ import { getFullImplementationGuide } from '../common/implementation-patterns.js export async function nuxt(features = []) { const nuxtImplementation = getFullImplementationGuide('nuxt', 'javascript', features); + const authSection = features.includes('auth') ? `\n${ssrAuthPattern}\n` : ''; return `${nodeAppwriteInstall} @@ -12,8 +13,14 @@ export async function nuxt(features = []) { - [Nuxt Middleware](https://nuxt.com/docs/guide/directory-structure/middleware) - [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/nuxt) -${ssrAuthPattern} - +**API References:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Account API](https://appwrite.io/docs/references/cloud/server-nodejs/account) - Session management and account operations +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations and queries +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File upload, download, and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +${authSection} ${nuxtImplementation} ## Nuxt-Specific Best Practices diff --git a/src/lib/languages/js/svelte.js b/src/lib/languages/js/svelte.js index 3ba6126..f730a14 100644 --- a/src/lib/languages/js/svelte.js +++ b/src/lib/languages/js/svelte.js @@ -4,6 +4,7 @@ import { getFullImplementationGuide } from '../common/implementation-patterns.js export async function svelte(features = []) { const sveltekitImplementation = getFullImplementationGuide('svelte', 'javascript', features); + const authSection = features.includes('auth') ? `\n${ssrAuthPattern}\n` : ''; return `${nodeAppwriteInstall} @@ -12,8 +13,14 @@ export async function svelte(features = []) { - [SvelteKit Form Actions](https://kit.svelte.dev/docs/form-actions) - [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/sveltekit) -${ssrAuthPattern} - +**API References:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Account API](https://appwrite.io/docs/references/cloud/server-nodejs/account) - Session management and account operations +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations and queries +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File upload, download, and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +${authSection} ${sveltekitImplementation} ## SvelteKit-Specific Best Practices diff --git a/src/lib/languages/js/tanstack.js b/src/lib/languages/js/tanstack.js index d2bfa65..a014edd 100644 --- a/src/lib/languages/js/tanstack.js +++ b/src/lib/languages/js/tanstack.js @@ -4,6 +4,7 @@ import { getFullImplementationGuide } from '../common/implementation-patterns.js export async function tanstack(features = []) { const tanstackImplementation = getFullImplementationGuide('tanstack', 'javascript', features); + const authSection = features.includes('auth') ? `\n${ssrAuthPattern}\n` : ''; return `${nodeAppwriteInstall} @@ -12,8 +13,14 @@ export async function tanstack(features = []) { - [TanStack Query Docs](https://tanstack.com/query/latest) - [Appwrite Quick Start](https://appwrite.io/docs/quick-starts/tanstack) -${ssrAuthPattern} - +**API References:** +- [Users API](https://appwrite.io/docs/references/cloud/server-nodejs/users) - User management and administration +- [Account API](https://appwrite.io/docs/references/cloud/server-nodejs/account) - Session management and account operations +- [Databases API](https://appwrite.io/docs/references/cloud/server-nodejs/databases) - Database operations and queries +- [Storage API](https://appwrite.io/docs/references/cloud/server-nodejs/storage) - File upload, download, and management +- [Functions API](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Serverless functions management +- [Messaging API](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) - Email, SMS, and push notifications +${authSection} ${tanstackImplementation} ## TanStack-Specific Best Practices diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index 07747d0..0e085e3 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -301,9 +301,7 @@ Common role hierarchy: #### Step 3: Member Management -For team invitations and membership management, see: -- [Team Invites Guide](https://appwrite.io/docs/products/auth/team-invites) -- [Teams API Reference](https://appwrite.io/docs/references) +For team invitations and membership management, see [Team Invites Guide](https://appwrite.io/docs/products/auth/team-invites) #### Step 4: Apply Permissions Consistently