From 1030c84eeee2df47bcd569292f7d01ed93f368e8 Mon Sep 17 00:00:00 2001 From: Giancarlo Buomprisco Date: Wed, 16 Apr 2025 09:11:20 +0700 Subject: [PATCH] Update documentation rules for various contexts and functionalities (#235) Update cusor rules for various contexts and functionalities --- .cursor/rules/accounts-context.mdc | 3 +- .cursor/rules/data-fetching.mdc | 1 - .cursor/rules/database.mdc | 148 ++++++++-------- .cursor/rules/forms.mdc | 100 +++++------ .cursor/rules/jsx.mdc | 206 +++++++++++++++++++++++ .cursor/rules/logging.mdc | 57 +++++++ .cursor/rules/page-creation.mdc | 3 +- .cursor/rules/permissions.mdc | 3 +- .cursor/rules/project-structure.mdc | 4 +- .cursor/rules/react.mdc | 224 +++++++++++++++++++++++++ .cursor/rules/route-handlers.mdc | 1 - .cursor/rules/server-actions.mdc | 36 +--- .cursor/rules/super-admin.mdc | 2 + .cursor/rules/team-account-context.mdc | 1 - .cursor/rules/translations.mdc | 146 ++++++++++++++++ .cursor/rules/typescript.mdc | 36 ++++ .cursor/rules/ui.mdc | 159 ++++++------------ 17 files changed, 854 insertions(+), 276 deletions(-) create mode 100644 .cursor/rules/jsx.mdc create mode 100644 .cursor/rules/logging.mdc create mode 100644 .cursor/rules/react.mdc create mode 100644 .cursor/rules/translations.mdc create mode 100644 .cursor/rules/typescript.mdc diff --git a/.cursor/rules/accounts-context.mdc b/.cursor/rules/accounts-context.mdc index ce632be4a..56e94b876 100644 --- a/.cursor/rules/accounts-context.mdc +++ b/.cursor/rules/accounts-context.mdc @@ -1,9 +1,8 @@ --- description: Personal Accounts context and functionality -globs: apps/*/app/home/(user),packages/features/accounts/** +globs: alwaysApply: false --- - # Personal Account Context This rule provides guidance for working with personal account related components in the application. diff --git a/.cursor/rules/data-fetching.mdc b/.cursor/rules/data-fetching.mdc index 39ef72bf3..9c94d2816 100644 --- a/.cursor/rules/data-fetching.mdc +++ b/.cursor/rules/data-fetching.mdc @@ -3,7 +3,6 @@ description: Fetch data from the Database using the Supabase Clients globs: apps/**,packages/** alwaysApply: false --- - # Data Fetching ## General Data Flow diff --git a/.cursor/rules/database.mdc b/.cursor/rules/database.mdc index f225685ee..514760e6f 100644 --- a/.cursor/rules/database.mdc +++ b/.cursor/rules/database.mdc @@ -3,7 +3,6 @@ description: Detailed Database Schema and Architecture globs: alwaysApply: true --- - # Database Rules ## Database Architecture @@ -12,10 +11,15 @@ alwaysApply: true - Accounts are the general concept of a user account, defined by the having the same ID as Supabase Auth's users (personal). They can be a team account or a personal account. - Generally speaking, other tables will be used to store data related to the account. For example, a table `notes` would have a foreign key `account_id` to link it to an account. +## Schemas +- The DB schemas are available at `apps/web/supabase/schemas` +- To edit the DB schema, we can either change the schema files, or created new ones +- To create a new schema, create a file at `apps/web/supabase/schemas/-.sql` + ## Migrations -- Migration files are placed at `apps//supabase/migrations` -- The main migration schema can be found at [20221215192558_schema.sql](mdc:apps/web/supabase/migrations/20221215192558_schema.sql) -- Use the command `pnpm --filter web supabase migrations new ` for creating well timestamped migrations +- After creating a schema, we can create a migration +- Use the command `pnpm --filter web supabase:db:diff` for creating migrations from schemas +- After generating a migration, reset the database for applying the changes using the command `pnpm --filter web supabase:db:reset` ## Security & RLS - Using RLS, we must ensure that only the account owner can access the data. Always write safe RLS policies and ensure that the policies are enforced. @@ -25,94 +29,67 @@ alwaysApply: true - Always consider the security of the data and explain the security implications of the data. - Always use Postgres schemas explicitly (e.g., `public.accounts`) - Consider the required compromises between simplicity, functionality and developer experience. However, never compromise on security, which is paramount and fundamental. +- Use existing helper functions for access control instead of making your own queries, unless unavailable ## Schema Overview Makerkit uses a Supabase Postgres database with a well-defined schema focused on multi-tenancy through the concepts of accounts (both personal and team) and robust permission systems. -### Core Entity Relationships +### Database Schema -1. **User ↔ Account**: - - Each user has a personal account (1:1) - - Users can belong to multiple team accounts (M:N through `accounts_memberships`) - - Accounts can have multiple users (M:N through `accounts_memberships`) - -2. **Account ↔ Role**: - - Each user has a role in each account they belong to - - Roles define permissions through `role_permissions` - -3. **Subscription System**: - - Accounts can have subscriptions - - Subscriptions have multiple subscription items - - Billing providers include Stripe, Lemon Squeezy, and Paddle - -4. **Invitation System**: - - Team accounts can invite users via email - - Invitations specify roles for the invited user - -5. **One-Time Tokens**: - - Used for secure verification processes - - Generic system that can be used for various purposes - -## Table Relationships - -``` -auth.users -├── public.accounts (personal_account=true, id=user_id) -└── public.accounts_memberships - └── public.accounts (personal_account=false) - └── public.roles (hierarchy_level) - └── public.role_permissions - └── app_permissions (enum) -``` - -``` -public.accounts -├── public.billing_customers -│ └── public.subscriptions -│ └── public.subscription_items -└── public.invitations -``` - -``` -public.nonces -└── auth.users (optional relationship) -``` - -## Schema Overview - -Makerkit implements a multi-tenant SaaS architecture through a robust account and permission system: - -1. **Core Entities**: - - `auth.users`: Supabase Auth users - - `public.accounts`: Both personal and team accounts - - `public.accounts_memberships`: Links users to accounts with roles - - `public.roles` and `public.role_permissions`: Define permission hierarchy - - `public.invitations`: For inviting users to team accounts - -2. **Billing System**: - - `public.billing_customers`: Account's connection to billing providers - - `public.subscriptions` and `public.subscription_items`: For subscription tracking - - `public.orders` and `public.order_items`: For one-time purchases - -3. **Security**: - - `public.nonces`: One-time tokens for secure operations +1. Enums [01-enums.sql](mdc:apps/web/supabase/schemas/01-enums.sql) +2. Config [02-config.sql](mdc:apps/web/supabase/schemas/02-config.sql) +3. Accounts [03-accounts.sql](mdc:apps/web/supabase/schemas/03-accounts.sql) +4. Roles [04-roles.sql](mdc:apps/web/supabase/schemas/04-roles.sql) +5. Memberships [05-memberships.sql](mdc:apps/web/supabase/schemas/05-memberships.sql) +6. Roles Permissions [06-roles-permissions.sql](mdc:apps/web/supabase/schemas/06-roles-permissions.sql) +7. Invitations [07-invitations.sql](mdc:apps/web/supabase/schemas/07-invitations.sql) +8. Billing Customers [08-billing-customers.sql](mdc:apps/web/supabase/schemas/08-billing-customers.sql) +9. Subscriptions [09-subscriptions.sql](mdc:apps/web/supabase/schemas/09-subscriptions.sql) +10. Orders [10-orders.sql](mdc:apps/web/supabase/schemas/10-orders.sql) +11. Notifications [11-notifications.sql](mdc:apps/web/supabase/schemas/11-notifications.sql) +12. One Time Tokens [12-one-time-tokens.sql](mdc:apps/web/supabase/schemas/12-one-time-tokens.sql) +13. Multi Factor Auth [13-mfa.sql](mdc:apps/web/supabase/schemas/13-mfa.sql) +14. Super Admin [14-super-admin.sql](mdc:apps/web/supabase/schemas/14-super-admin.sql) +15. Account Views [15-account-views.sql](mdc:apps/web/supabase/schemas/15-account-views.sql) +16. Storage [16-storage.sql](mdc:apps/web/supabase/schemas/16-storage.sql) ## Database Best Practices +### Inferring Database types + +Fetch auto-generated data types using the `@kit/supabase/database` import. Do not write types manually if the shape is the same as the one from the database row. + +```tsx +import { Tables } from '@kit/supabase/database'; + +// public.accounts +type Account = Tables<'accounts'>; + +// public.subscriptions +type Subscription = Tables<'subscriptions'>; + +// public.notifications +type Notification = Tables<'notifications'>; + +// ... +``` + ### Security - **Always enable RLS** on new tables unless explicitly instructed otherwise - **Create proper RLS policies** for all CRUD operations following existing patterns - **Always associate data with accounts** using a foreign key to ensure proper access control - **Use explicit schema references** (`public.table_name` not just `table_name`) -- **Place internal functions in the `kit` schema** +- **Private schema**: Place internal functions in the `kit` schema +- **Search Path**: Always set search path to '' when defining functions +- **Security Definer**: Do not use `security definer` functions unless stricly required ### Data Access Patterns -- Use `has_role_on_account(account_id, role?)` to check membership -- Use `has_permission(user_id, account_id, permission)` for permission checks -- Use `is_account_owner(account_id)` to identify account ownership +- Use `public.has_role_on_account(account_id, role?)` to check membership +- Use `public.has_permission(user_id, account_id, permission)` for permission checks +- Use `public.is_account_owner(account_id)` to identify account ownership ### SQL Coding Style @@ -136,12 +113,15 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an ### 1. RLS Policy Management -- **Always Enable RLS**: Always enable RLS for your tables unless you have a specific reason not to. +#### Always Enable RLS + +Always enable RLS for your tables unless you have a specific reason not to. ```sql ALTER TABLE public.my_table ENABLE ROW LEVEL SECURITY; ``` -- **Follow the Standard Policies Pattern**: Use the existing structure for policies: +#### Use Helper Functions to validate permissions and access control +Use the existing structure for policies: ```sql -- SELECT policy CREATE POLICY "my_table_read" ON public.my_table FOR SELECT @@ -153,11 +133,19 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an -- INSERT/UPDATE/DELETE policies follow similar patterns ``` +When using RLS at team-account level, use `public.has_role_on_account(account_id)` for a generic check to understand if a user is part of a team. + +When using RLS at user-account level, use `account_id = (select auth.uid())`. + +When an entity can belong to both, use both. + +When requiring a specific role, use the role parameter `public.has_role_on_account(account_id, 'owner')` + ### 2. Account Association - **Associate Data with Accounts**: Always link data to accounts using a foreign key: ```sql - CREATE TABLE public.my_data ( + CREATE TABLE if not exists public.my_data ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), account_id UUID REFERENCES public.accounts(id) ON DELETE CASCADE NOT NULL, /* other fields */ @@ -201,14 +189,14 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an ```sql CREATE TYPE public.my_status AS ENUM('active', 'inactive', 'pending'); - CREATE TABLE public.my_table ( + CREATE TABLE if not exists public.my_table ( status public.my_status NOT NULL DEFAULT 'pending' ); ``` - **Apply Appropriate Constraints**: Use constraints to ensure data integrity: ```sql - CREATE TABLE public.my_table ( + CREATE TABLE if not exists public.my_table ( email VARCHAR(255) NOT NULL CHECK (email ~* '^.+@.+\..+$'), count INTEGER NOT NULL CHECK (count >= 0), /* other fields */ @@ -265,6 +253,8 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an SELECT ... ``` +You always must use `(security_invoker = true)` for views. + ## Key Functions to Know 1. **Account Access** diff --git a/.cursor/rules/forms.mdc b/.cursor/rules/forms.mdc index 0af6a8d16..f102d37fd 100644 --- a/.cursor/rules/forms.mdc +++ b/.cursor/rules/forms.mdc @@ -3,16 +3,18 @@ description: Writing Forms with Shadcn UI, Server Actions, Zod globs: apps/**/*.tsx,packages/**/*.tsx alwaysApply: false --- - # Forms - Use React Hook Form for form validation and submission. - Use Zod for form validation. - Use the `zodResolver` function to resolve the Zod schema to the form. +- Use Server Actions [server-actions.mdc](mdc:.cursor/rules/server-actions.mdc) for server-side code handling +- Use Sonner for writing toasters for UI feedback Follow the example below to create all forms: ## Define the schema + Zod schemas should be defined in the `schema` folder and exported, so we can reuse them across a Server Action and the client-side form: ```tsx @@ -27,19 +29,15 @@ export const CreateNoteSchema = z.object({ ## Create the Server Action +Server Actions [server-actions.mdc](mdc:.cursor/rules/server-actions.mdc) can help us create endpoints for our forms. + ```tsx -// _lib/server/server-actions.ts 'use server'; import { z } from 'zod'; import { enhanceAction } from '@kit/next/actions'; import { CreateNoteSchema } from '../schema/create-note.schema'; -const CreateNoteSchema = z.object({ - title: z.string().min(1), - content: z.string().min(1), -}); - export const createNoteAction = enhanceAction( async function (data, user) { // 1. "data" has been validated against the Zod schema, and it's safe to use @@ -62,18 +60,22 @@ export const createNoteAction = enhanceAction( Then create a client component to handle the form submission: ```tsx -// _components/create-note-form.tsx 'use client'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; +import { Textarea } from '@kit/ui/textarea'; +import { Input } from '@kit/ui/input'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@kit/ui/form'; +import { toast } from '@kit/ui/sonner'; +import { useTranslation } from 'react-i18next'; import { CreateNoteSchema } from '../_lib/schema/create-note.schema'; export function CreateNoteForm() { const [pending, startTransition] = useTransition(); + const { t } = useTranslation(); const form = useForm({ resolver: zodResolver(CreateNoteSchema), @@ -85,58 +87,58 @@ export function CreateNoteForm() { const onSubmit = (data) => { startTransition(async () => { - try { - await createNoteAction(data); - } catch { - // handle error - } + await toast.promise(createNoteAction(data), { + loading: t('notes:creatingNote`), + success: t('notes:createNoteSuccess`), + error: t('notes:createNoteError`) + }) }); }; return (
- - ( - - - Title - + + ( + + + Title + - - - + + + - - - )} /> + + + )} /> - ( - - - Content - + ( + + + Content + - -