| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 15 | 0 | 10 | 60% |
| Commands | 0 | 0 | 0 | — |
| Section tags | 3 | 0 | 2 | 60% |
What each file covers
Sections
15 shared · 0 only in A · 10 only in B- + Project Instructions
- + Overview
- + Tech Stack
- + Project Structure
- + Rules
- + Frontend Rules
- + Backend Rules
- + Auth Rules
- + Payments Rules
- + Analytics Rules
- Storage Rules
- General Rules
- Organization
- Buckets
- File Structure
- Actions
- File Handling
- Upload Rules
- Download Rules
- Delete Rules
- Security
- Bucket Policies
- Access Control
- Error Handling
- Optimization
Commands
neither file has anySection tags
3 shared · 0 only in A · 2 only in B- + code-style
- + agent-behaviour
- architecture
- security
- do-not
Line diff
sportiz91/vibe-template · .cursor/rules/storage.mdc
@@ −1 @@
1---
2description: Follow these rules when working on file storage.
3globs:
4---
5# Storage Rules
6
7Follow these rules when working with Supabase Storage.
@@ −59 @@
59): Promise<ActionState<{ path: string }>> {
60 try {
61 const supabase = createClientComponentClient()
62
63 const { data, error } = await supabase
64 .storage
65 .from(bucket)
66 .upload(path, file, {
67 upsert: false,
@@ −102 @@
102 if (file.size > MAX_FILE_SIZE) {
103 throw new Error("File size exceeds limit")
104 }
105
106 if (!ALLOWED_TYPES.includes(file.type)) {
107 throw new Error("File type not allowed")
108 }
109
110 return true
111}
112```
@@ −161 @@
161- Implement progressive upload for large files
162- Clean up temporary files and failed uploads
163- Use batch operations when handling multiple files
sportiz91/vibe-template · .cursorrules
@@ +1 @@
1# Project Instructions
2
3Use specification and guidelines as you build the app.
4
5Write the complete code for every step. Do not get lazy.
6
7Your goal is to completely finish whatever I ask for.
8
9You will see <ai_context> tags in the code. These are context tags that you should use to help you understand the codebase.
10
11## Overview
12
13This is a web app template.
14
15## Tech Stack
16
17- Frontend: Next.js, Tailwind, Shadcn, Framer Motion
18- Backend: Postgres, Supabase, Drizzle ORM, Server Actions
19- Auth: Clerk
20- Payments: Stripe
21- Analytics: PostHog
22- Deployment: Vercel
23
24## Project Structure
25
26- `actions` - Server actions
27 - `db` - Database related actions
28 - Other actions
29- `app` - Next.js app router
30 - `api` - API routes
31 - `route` - An example route
32 - `_components` - One-off components for the route
33 - `layout.tsx` - Layout for the route
34 - `page.tsx` - Page for the route
35- `components` - Shared components
36 - `ui` - UI components
37 - `utilities` - Utility components
38- `db` - Database
39 - `schema` - Database schemas
40- `lib` - Library code
41 - `hooks` - Custom hooks
42 - `services` - Business logic services
43- `prompts` - Prompt files
44- `public` - Static assets
45- `types` - Type definitions
46
47## Rules
48
49Follow these rules when building the app.
50
51### General Rules
52
53- Use `@` to import anything from the app unless otherwise specified
54- Use kebab case for all files and folders unless otherwise specified
55- Don't update shadcn components unless otherwise specified
56
57#### Env Rules
58
59- If you update environment variables, update the `.env.example` file
60- All environment variables should go in `.env.local`
61- Do not expose environment variables to the frontend
62- Use `NEXT_PUBLIC_` prefix for environment variables that need to be accessed from the frontend
63- Always access environment variables via the centralized config module (`serverConfig` and `publicEnv` from `@/lib/config`). Do not use `process.env` directly in application code.
64
65#### Type Rules
66
67Follow these rules when working with types.
68
69- When importing types, use `@/types`
70- Name files like `example-types.ts`
71- All types should go in `types`
72- Make sure to export the types in `types/index.ts`
73- Prefer interfaces over type aliases
74- If referring to db types, use `@/db/schema` such as `SelectTodo` from `todos-schema.ts`
75
76An example of a type:
77
78`types/actions-types.ts`
79
80```ts
81export type ActionState<T> =
82 | { isSuccess: true; message: string; data: T }
83 | { isSuccess: false; message: string; data?: never }
84```
85
86And exporting it:
87
88`types/index.ts`
89
90```ts
91export * from "./actions-types"
92```
93
94### Frontend Rules
95
96Follow these rules when working on the frontend.
97
98It uses Next.js, Tailwind, Shadcn, and Framer Motion.
99
100#### General Rules
101
102- Use `lucide-react` for icons
103- useSidebar must be used within a SidebarProvider
104
105#### Components
106
107- Use divs instead of other html tags unless otherwise specified
108- Separate the main parts of a component's html with an extra blank line for visual spacing
109- Always tag a component with either `use server` or `use client` at the top, including layouts and pages
110
111##### Organization
112
113- All components be named using kebab case like `example-component.tsx` unless otherwise specified
114- Put components in `/_components` in the route if one-off components
115- Put components in `/components` from the root if shared components
116
117##### Data Fetching
118
119- Fetch data in server components and pass the data down as props to client components.
120- Use server actions from `/actions` to mutate data.
121
122##### Server Components
123
124- Use `"use server"` at the top of the file.
125- Implement Suspense for asynchronous data fetching to show loading states while data is being fetched.
126- If no asynchronous logic is required for a given server component, you do not need to wrap the component in `<Suspense>`. You can simply return the final UI directly since there is no async boundary needed.
127- If asynchronous fetching is required, you can use a `<Suspense>` boundary and a fallback to indicate a loading state while data is loading.
128- Server components cannot be imported into client components. If you want to use a server component in a client component, you must pass the as props using the "children" prop
129- params in server pages should be awaited such as `const { courseId } = await params` where the type is `params: Promise<{ courseId: string }>`
130
131Example of a server layout:
132
133```tsx
134"use server"
135
136export default async function ExampleServerLayout({
137 children
138}: {
139 children: React.ReactNode
140}) {
141 return children
142}
143```
144
145Example of a server page (with async logic):
146
147```tsx
148"use server"
149
150import { Suspense } from "react"
151import { SomeAction } from "@/actions/some-actions"
152import SomeComponent from "./_components/some-component"
153import SomeSkeleton from "./_components/some-skeleton"
154
155export default async function ExampleServerPage() {
156 return (
157 <Suspense fallback={<SomeSkeleton className="some-class" />}>
158 <SomeComponentFetcher />
159 </Suspense>
160 )
161}
162
163async function SomeComponentFetcher() {
164 const { data } = await SomeAction()
165 return <SomeComponent className="some-class" initialData={data || []} />
166}
167```
168
169Example of a server page (no async logic required):
170
171```tsx
172"use server"
173
174import SomeClientComponent from "./_components/some-client-component"
175
176// In this case, no asynchronous work is being done, so no Suspense or fallback is required.
177export default async function ExampleServerPage() {
178 return <SomeClientComponent initialData={[]} />
179}
180```
181
182Example of a server component:
183
184```tsx
185"use server"
186
187interface ExampleServerComponentProps {
188 // Your props here
189}
190
191export async function ExampleServerComponent({
192 props
193}: ExampleServerComponentProps) {
194 // Your code here
195}
196```
197
198##### Client Components
199
200- Use `"use client"` at the top of the file
201- Client components can safely rely on props passed down from server components, or handle UI interactions without needing <Suspense> if there's no async logic.
202- Never use server actions in client components. If you need to create a new server action, create it in `/actions`
203
204Example of a client page:
205
206```tsx
207"use client"
208
209export default function ExampleClientPage() {
210 // Your code here
211}
212```
213
214Example of a client component:
215
216```tsx
217"use client"
218
219interface ExampleClientComponentProps {
220 initialData: any[]
221}
222
223export default function ExampleClientComponent({
224 initialData
225}: ExampleClientComponentProps) {
226 // Client-side logic here
227 return <div>{initialData.length} items</div>
228}
229```
230
231### Backend Rules
232
233Follow these rules when working on the backend.
234
235It uses Postgres, Supabase, Drizzle ORM, and Server Actions.
236
237#### General Rules
238
239- Never generate migrations. You do not have to do anything in the `db/migrations` folder inluding migrations and metadata. Ignore it.
240
241#### Organization
242
243#### Schemas
244
245- When importing schemas, use `@/db/schema`
246- Name files like `example-schema.ts`
247- All schemas should go in `db/schema`
248- Make sure to export the schema in `db/schema/index.ts`
249- Make sure to add the schema to the `schema` object in `db/db.ts`
250- If using a userId, always use `userId: text("user_id").notNull()`
251- Always include createdAt and updatedAt columns in all tables
252- Make sure to cascade delete when necessary
253- Use enums for columns that have a limited set of possible values such as:
254
255```ts
256import { pgEnum } from "drizzle-orm/pg-core"
257
258export const MEMBERSHIP: PgEnum<Membership> = pgEnum(
259 "membership",
260 MEMBERSHIP_VALUES
261)
262
263membership: MEMBERSHIP("membership").notNull().default("free")
264```
265
266Example of a schema:
267
268`db/schema/todos-schema.ts`
269
270```ts
271import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
272
273export const todosTable = pgTable("todos", {
274 id: uuid("id").defaultRandom().primaryKey(),
275 userId: text("user_id").notNull(),
276 content: text("content").notNull(),
277 completed: boolean("completed").default(false).notNull(),
278 createdAt: timestamp("created_at").defaultNow().notNull(),
279 updatedAt: timestamp("updated_at")
280 .defaultNow()
281 .notNull()
282 .$onUpdate(() => new Date())
283})
284
285export type InsertTodo = typeof todosTable.$inferInsert
286export type SelectTodo = typeof todosTable.$inferSelect
287```
288
289And exporting it:
290
291`db/schema/index.ts`
292
293```ts
294export * from "./todos-schema"
295```
296
297And adding it to the schema in `db/db.ts`:
298
299`db/db.ts`
300
301```ts
302import { todosTable } from "@/db/schema"
303
304const schema = {
305 todos: todosTable
306}
307```
308
309And a more complex schema:
310
311```ts
312import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
313
314export const chatsTable = pgTable("chats", {
315 id: uuid("id").defaultRandom().primaryKey(),
316 userId: text("user_id").notNull(),
317 name: text("name").notNull(),
318 createdAt: timestamp("created_at").defaultNow().notNull(),
319 updatedAt: timestamp("updated_at")
320 .defaultNow()
321 .notNull()
322 .$onUpdate(() => new Date())
323})
324
325export type InsertChat = typeof chatsTable.$inferInsert
326export type SelectChat = typeof chatsTable.$inferSelect
327```
328
329```ts
330import { pgEnum, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
331import { chatsTable } from "./chats-schema"
332
333export const roleEnum = pgEnum("role", ["assistant", "user"])
334
335export const messagesTable = pgTable("messages", {
336 id: uuid("id").defaultRandom().primaryKey(),
337 chatId: uuid("chat_id")
338 .references(() => chatsTable.id, { onDelete: "cascade" })
339 .notNull(),
340 content: text("content").notNull(),
341 role: roleEnum("role").notNull(),
342 createdAt: timestamp("created_at").defaultNow().notNull(),
343 updatedAt: timestamp("updated_at")
344 .defaultNow()
345 .notNull()
346 .$onUpdate(() => new Date())
347})
348
349export type InsertMessage = typeof messagesTable.$inferInsert
350export type SelectMessage = typeof messagesTable.$inferSelect
351```
352
353And exporting it:
354
355`db/schema/index.ts`
356
357```ts
358export * from "./chats-schema"
359export * from "./messages-schema"
360```
361
362And adding it to the schema in `db/db.ts`:
363
364`db/db.ts`
365
366```ts
367import { chatsTable, messagesTable } from "@/db/schema"
368
369const schema = {
370 chats: chatsTable,
371 messages: messagesTable
372}
373```
374
375#### Server Actions
376
377- When importing actions, use `@/actions` or `@/actions/db` if db related
378- DB related actions should go in the `actions/db` folder
379- Other actions should go in the `actions` folder
380- Name files like `example-actions.ts`
381- All actions should go in the `actions` folder
382- Only write the needed actions
383- Return an ActionState with the needed data type from actions
384- Include Action at the end of function names `Ex: exampleFunction -> exampleFunctionAction`
385- Actions should return a Promise<ActionState<T>>
386- Sort in CRUD order: Create, Read, Update, Delete
387- Make sure to return undefined as the data type if the action is not supposed to return any data
388- **Date Handling:** For columns defined as `PgDateString` (or any date string type), always convert JavaScript `Date` objects to ISO strings using `.toISOString()` before performing operations (e.g., comparisons or insertions). This ensures value type consistency and prevents type errors.
389
390```ts
391export type ActionState<T> =
392 | { isSuccess: true; message: string; data: T }
393 | { isSuccess: false; message: string; data?: never }
394```
395
396Example of an action:
397
398`actions/db/todos-actions.ts`
399
400```ts
401"use server"
402
403import { db } from "@/db/db"
404import { InsertTodo, SelectTodo, todosTable } from "@/db/schema/todos-schema"
405import { ActionState } from "@/types"
406import { eq } from "drizzle-orm"
407
408export async function createTodoAction(
409 todo: InsertTodo
410): Promise<ActionState<SelectTodo>> {
411 try {
412 const [newTodo] = await db.insert(todosTable).values(todo).returning()
413 return {
414 isSuccess: true,
415 message: "Todo created successfully",
416 data: newTodo
417 }
418 } catch (error) {
419 console.error("Error creating todo:", error)
420 return { isSuccess: false, message: "Failed to create todo" }
421 }
422}
423
424export async function getTodosAction(
425 userId: string
426): Promise<ActionState<SelectTodo[]>> {
427 try {
428 const todos = await db.query.todos.findMany({
429 where: eq(todosTable.userId, userId)
430 })
431 return {
432 isSuccess: true,
433 message: "Todos retrieved successfully",
434 data: todos
435 }
436 } catch (error) {
437 console.error("Error getting todos:", error)
438 return { isSuccess: false, message: "Failed to get todos" }
439 }
440}
441
442export async function updateTodoAction(
443 id: string,
444 data: Partial<InsertTodo>
445): Promise<ActionState<SelectTodo>> {
446 try {
447 const [updatedTodo] = await db
448 .update(todosTable)
449 .set(data)
450 .where(eq(todosTable.id, id))
451 .returning()
452
453 return {
454 isSuccess: true,
455 message: "Todo updated successfully",
456 data: updatedTodo
457 }
458 } catch (error) {
459 console.error("Error updating todo:", error)
460 return { isSuccess: false, message: "Failed to update todo" }
461 }
462}
463
464export async function deleteTodoAction(id: string): Promise<ActionState<void>> {
465 try {
466 await db.delete(todosTable).where(eq(todosTable.id, id))
467 return {
468 isSuccess: true,
469 message: "Todo deleted successfully",
470 data: undefined
471 }
472 } catch (error) {
473 console.error("Error deleting todo:", error)
474 return { isSuccess: false, message: "Failed to delete todo" }
475 }
476}
477```
478
479### Auth Rules
480
481Follow these rules when working on auth.
482
483It uses Clerk for authentication.
484
485#### General Rules
486
487- Import the auth helper with `import { auth } from "@clerk/nextjs/server"` in server components
488- await the auth helper in server actions
489
490### Payments Rules
491
492Follow these rules when working on payments.
493
494It uses Stripe for payments.
495
496### Analytics Rules
497
498Follow these rules when working on analytics.
499
500It uses PostHog for analytics.
501
502# Storage Rules
503
504Follow these rules when working with Supabase Storage.
@@ +556 @@
556): Promise<ActionState<{ path: string }>> {
557 try {
558 const supabase = createClientComponentClient()
559
560 const { data, error } = await supabase.storage
561 .from(bucket)
562 .upload(path, file, {
563 upsert: false,
@@ +598 @@
598 if (file.size > MAX_FILE_SIZE) {
599 throw new Error("File size exceeds limit")
600 }
601
602 if (!ALLOWED_TYPES.includes(file.type)) {
603 throw new Error("File type not allowed")
604 }
605
606 return true
607}
608```
@@ +657 @@
657- Implement progressive upload for large files
658- Clean up temporary files and failed uploads
659- Use batch operations when handling multiple files
660
@@ −1 +1 @@
1−---
2−description: Follow these rules when working on file storage.
3−globs:
4−---
1+# Project Instructions
2+
3+Use specification and guidelines as you build the app.
4+
5+Write the complete code for every step. Do not get lazy.
6+
7+Your goal is to completely finish whatever I ask for.
8+
9+You will see <ai_context> tags in the code. These are context tags that you should use to help you understand the codebase.
10+
11+## Overview
12+
13+This is a web app template.
14+
15+## Tech Stack
16+
17+- Frontend: Next.js, Tailwind, Shadcn, Framer Motion
18+- Backend: Postgres, Supabase, Drizzle ORM, Server Actions
19+- Auth: Clerk
20+- Payments: Stripe
21+- Analytics: PostHog
22+- Deployment: Vercel
23+
24+## Project Structure
25+
26+- `actions` - Server actions
27+ - `db` - Database related actions
28+ - Other actions
29+- `app` - Next.js app router
30+ - `api` - API routes
31+ - `route` - An example route
32+ - `_components` - One-off components for the route
33+ - `layout.tsx` - Layout for the route
34+ - `page.tsx` - Page for the route
35+- `components` - Shared components
36+ - `ui` - UI components
37+ - `utilities` - Utility components
38+- `db` - Database
39+ - `schema` - Database schemas
40+- `lib` - Library code
41+ - `hooks` - Custom hooks
42+ - `services` - Business logic services
43+- `prompts` - Prompt files
44+- `public` - Static assets
45+- `types` - Type definitions
46+
47+## Rules
48+
49+Follow these rules when building the app.
50+
51+### General Rules
52+
53+- Use `@` to import anything from the app unless otherwise specified
54+- Use kebab case for all files and folders unless otherwise specified
55+- Don't update shadcn components unless otherwise specified
56+
57+#### Env Rules
58+
59+- If you update environment variables, update the `.env.example` file
60+- All environment variables should go in `.env.local`
61+- Do not expose environment variables to the frontend
62+- Use `NEXT_PUBLIC_` prefix for environment variables that need to be accessed from the frontend
63+- Always access environment variables via the centralized config module (`serverConfig` and `publicEnv` from `@/lib/config`). Do not use `process.env` directly in application code.
64+
65+#### Type Rules
66+
67+Follow these rules when working with types.
68+
69+- When importing types, use `@/types`
70+- Name files like `example-types.ts`
71+- All types should go in `types`
72+- Make sure to export the types in `types/index.ts`
73+- Prefer interfaces over type aliases
74+- If referring to db types, use `@/db/schema` such as `SelectTodo` from `todos-schema.ts`
75+
76+An example of a type:
77+
78+`types/actions-types.ts`
79+
80+```ts
81+export type ActionState<T> =
82+ | { isSuccess: true; message: string; data: T }
83+ | { isSuccess: false; message: string; data?: never }
84+```
85+
86+And exporting it:
87+
88+`types/index.ts`
89+
90+```ts
91+export * from "./actions-types"
92+```
93+
94+### Frontend Rules
95+
96+Follow these rules when working on the frontend.
97+
98+It uses Next.js, Tailwind, Shadcn, and Framer Motion.
99+
100+#### General Rules
101+
102+- Use `lucide-react` for icons
103+- useSidebar must be used within a SidebarProvider
104+
105+#### Components
106+
107+- Use divs instead of other html tags unless otherwise specified
108+- Separate the main parts of a component's html with an extra blank line for visual spacing
109+- Always tag a component with either `use server` or `use client` at the top, including layouts and pages
110+
111+##### Organization
112+
113+- All components be named using kebab case like `example-component.tsx` unless otherwise specified
114+- Put components in `/_components` in the route if one-off components
115+- Put components in `/components` from the root if shared components
116+
117+##### Data Fetching
118+
119+- Fetch data in server components and pass the data down as props to client components.
120+- Use server actions from `/actions` to mutate data.
121+
122+##### Server Components
123+
124+- Use `"use server"` at the top of the file.
125+- Implement Suspense for asynchronous data fetching to show loading states while data is being fetched.
126+- If no asynchronous logic is required for a given server component, you do not need to wrap the component in `<Suspense>`. You can simply return the final UI directly since there is no async boundary needed.
127+- If asynchronous fetching is required, you can use a `<Suspense>` boundary and a fallback to indicate a loading state while data is loading.
128+- Server components cannot be imported into client components. If you want to use a server component in a client component, you must pass the as props using the "children" prop
129+- params in server pages should be awaited such as `const { courseId } = await params` where the type is `params: Promise<{ courseId: string }>`
130+
131+Example of a server layout:
132+
133+```tsx
134+"use server"
135+
136+export default async function ExampleServerLayout({
137+ children
138+}: {
139+ children: React.ReactNode
140+}) {
141+ return children
142+}
143+```
144+
145+Example of a server page (with async logic):
146+
147+```tsx
148+"use server"
149+
150+import { Suspense } from "react"
151+import { SomeAction } from "@/actions/some-actions"
152+import SomeComponent from "./_components/some-component"
153+import SomeSkeleton from "./_components/some-skeleton"
154+
155+export default async function ExampleServerPage() {
156+ return (
157+ <Suspense fallback={<SomeSkeleton className="some-class" />}>
158+ <SomeComponentFetcher />
159+ </Suspense>
160+ )
161+}
162+
163+async function SomeComponentFetcher() {
164+ const { data } = await SomeAction()
165+ return <SomeComponent className="some-class" initialData={data || []} />
166+}
167+```
168+
169+Example of a server page (no async logic required):
170+
171+```tsx
172+"use server"
173+
174+import SomeClientComponent from "./_components/some-client-component"
175+
176+// In this case, no asynchronous work is being done, so no Suspense or fallback is required.
177+export default async function ExampleServerPage() {
178+ return <SomeClientComponent initialData={[]} />
179+}
180+```
181+
182+Example of a server component:
183+
184+```tsx
185+"use server"
186+
187+interface ExampleServerComponentProps {
188+ // Your props here
189+}
190+
191+export async function ExampleServerComponent({
192+ props
193+}: ExampleServerComponentProps) {
194+ // Your code here
195+}
196+```
197+
198+##### Client Components
199+
200+- Use `"use client"` at the top of the file
201+- Client components can safely rely on props passed down from server components, or handle UI interactions without needing <Suspense> if there's no async logic.
202+- Never use server actions in client components. If you need to create a new server action, create it in `/actions`
203+
204+Example of a client page:
205+
206+```tsx
207+"use client"
208+
209+export default function ExampleClientPage() {
210+ // Your code here
211+}
212+```
213+
214+Example of a client component:
215+
216+```tsx
217+"use client"
218+
219+interface ExampleClientComponentProps {
220+ initialData: any[]
221+}
222+
223+export default function ExampleClientComponent({
224+ initialData
225+}: ExampleClientComponentProps) {
226+ // Client-side logic here
227+ return <div>{initialData.length} items</div>
228+}
229+```
230+
231+### Backend Rules
232+
233+Follow these rules when working on the backend.
234+
235+It uses Postgres, Supabase, Drizzle ORM, and Server Actions.
236+
237+#### General Rules
238+
239+- Never generate migrations. You do not have to do anything in the `db/migrations` folder inluding migrations and metadata. Ignore it.
240+
241+#### Organization
242+
243+#### Schemas
244+
245+- When importing schemas, use `@/db/schema`
246+- Name files like `example-schema.ts`
247+- All schemas should go in `db/schema`
248+- Make sure to export the schema in `db/schema/index.ts`
249+- Make sure to add the schema to the `schema` object in `db/db.ts`
250+- If using a userId, always use `userId: text("user_id").notNull()`
251+- Always include createdAt and updatedAt columns in all tables
252+- Make sure to cascade delete when necessary
253+- Use enums for columns that have a limited set of possible values such as:
254+
255+```ts
256+import { pgEnum } from "drizzle-orm/pg-core"
257+
258+export const MEMBERSHIP: PgEnum<Membership> = pgEnum(
259+ "membership",
260+ MEMBERSHIP_VALUES
261+)
262+
263+membership: MEMBERSHIP("membership").notNull().default("free")
264+```
265+
266+Example of a schema:
267+
268+`db/schema/todos-schema.ts`
269+
270+```ts
271+import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
272+
273+export const todosTable = pgTable("todos", {
274+ id: uuid("id").defaultRandom().primaryKey(),
275+ userId: text("user_id").notNull(),
276+ content: text("content").notNull(),
277+ completed: boolean("completed").default(false).notNull(),
278+ createdAt: timestamp("created_at").defaultNow().notNull(),
279+ updatedAt: timestamp("updated_at")
280+ .defaultNow()
281+ .notNull()
282+ .$onUpdate(() => new Date())
283+})
284+
285+export type InsertTodo = typeof todosTable.$inferInsert
286+export type SelectTodo = typeof todosTable.$inferSelect
287+```
288+
289+And exporting it:
290+
291+`db/schema/index.ts`
292+
293+```ts
294+export * from "./todos-schema"
295+```
296+
297+And adding it to the schema in `db/db.ts`:
298+
299+`db/db.ts`
300+
301+```ts
302+import { todosTable } from "@/db/schema"
303+
304+const schema = {
305+ todos: todosTable
306+}
307+```
308+
309+And a more complex schema:
310+
311+```ts
312+import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
313+
314+export const chatsTable = pgTable("chats", {
315+ id: uuid("id").defaultRandom().primaryKey(),
316+ userId: text("user_id").notNull(),
317+ name: text("name").notNull(),
318+ createdAt: timestamp("created_at").defaultNow().notNull(),
319+ updatedAt: timestamp("updated_at")
320+ .defaultNow()
321+ .notNull()
322+ .$onUpdate(() => new Date())
323+})
324+
325+export type InsertChat = typeof chatsTable.$inferInsert
326+export type SelectChat = typeof chatsTable.$inferSelect
327+```
328+
329+```ts
330+import { pgEnum, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
331+import { chatsTable } from "./chats-schema"
332+
333+export const roleEnum = pgEnum("role", ["assistant", "user"])
334+
335+export const messagesTable = pgTable("messages", {
336+ id: uuid("id").defaultRandom().primaryKey(),
337+ chatId: uuid("chat_id")
338+ .references(() => chatsTable.id, { onDelete: "cascade" })
339+ .notNull(),
340+ content: text("content").notNull(),
341+ role: roleEnum("role").notNull(),
342+ createdAt: timestamp("created_at").defaultNow().notNull(),
343+ updatedAt: timestamp("updated_at")
344+ .defaultNow()
345+ .notNull()
346+ .$onUpdate(() => new Date())
347+})
348+
349+export type InsertMessage = typeof messagesTable.$inferInsert
350+export type SelectMessage = typeof messagesTable.$inferSelect
351+```
352+
353+And exporting it:
354+
355+`db/schema/index.ts`
356+
357+```ts
358+export * from "./chats-schema"
359+export * from "./messages-schema"
360+```
361+
362+And adding it to the schema in `db/db.ts`:
363+
364+`db/db.ts`
365+
366+```ts
367+import { chatsTable, messagesTable } from "@/db/schema"
368+
369+const schema = {
370+ chats: chatsTable,
371+ messages: messagesTable
372+}
373+```
374+
375+#### Server Actions
376+
377+- When importing actions, use `@/actions` or `@/actions/db` if db related
378+- DB related actions should go in the `actions/db` folder
379+- Other actions should go in the `actions` folder
380+- Name files like `example-actions.ts`
381+- All actions should go in the `actions` folder
382+- Only write the needed actions
383+- Return an ActionState with the needed data type from actions
384+- Include Action at the end of function names `Ex: exampleFunction -> exampleFunctionAction`
385+- Actions should return a Promise<ActionState<T>>
386+- Sort in CRUD order: Create, Read, Update, Delete
387+- Make sure to return undefined as the data type if the action is not supposed to return any data
388+- **Date Handling:** For columns defined as `PgDateString` (or any date string type), always convert JavaScript `Date` objects to ISO strings using `.toISOString()` before performing operations (e.g., comparisons or insertions). This ensures value type consistency and prevents type errors.
389+
390+```ts
391+export type ActionState<T> =
392+ | { isSuccess: true; message: string; data: T }
393+ | { isSuccess: false; message: string; data?: never }
394+```
395+
396+Example of an action:
397+
398+`actions/db/todos-actions.ts`
399+
400+```ts
401+"use server"
402+
403+import { db } from "@/db/db"
404+import { InsertTodo, SelectTodo, todosTable } from "@/db/schema/todos-schema"
405+import { ActionState } from "@/types"
406+import { eq } from "drizzle-orm"
407+
408+export async function createTodoAction(
409+ todo: InsertTodo
410+): Promise<ActionState<SelectTodo>> {
411+ try {
412+ const [newTodo] = await db.insert(todosTable).values(todo).returning()
413+ return {
414+ isSuccess: true,
415+ message: "Todo created successfully",
416+ data: newTodo
417+ }
418+ } catch (error) {
419+ console.error("Error creating todo:", error)
420+ return { isSuccess: false, message: "Failed to create todo" }
421+ }
422+}
423+
424+export async function getTodosAction(
425+ userId: string
426+): Promise<ActionState<SelectTodo[]>> {
427+ try {
428+ const todos = await db.query.todos.findMany({
429+ where: eq(todosTable.userId, userId)
430+ })
431+ return {
432+ isSuccess: true,
433+ message: "Todos retrieved successfully",
434+ data: todos
435+ }
436+ } catch (error) {
437+ console.error("Error getting todos:", error)
438+ return { isSuccess: false, message: "Failed to get todos" }
439+ }
440+}
441+
442+export async function updateTodoAction(
443+ id: string,
444+ data: Partial<InsertTodo>
445+): Promise<ActionState<SelectTodo>> {
446+ try {
447+ const [updatedTodo] = await db
448+ .update(todosTable)
449+ .set(data)
450+ .where(eq(todosTable.id, id))
451+ .returning()
452+
453+ return {
454+ isSuccess: true,
455+ message: "Todo updated successfully",
456+ data: updatedTodo
457+ }
458+ } catch (error) {
459+ console.error("Error updating todo:", error)
460+ return { isSuccess: false, message: "Failed to update todo" }
461+ }
462+}
463+
464+export async function deleteTodoAction(id: string): Promise<ActionState<void>> {
465+ try {
466+ await db.delete(todosTable).where(eq(todosTable.id, id))
467+ return {
468+ isSuccess: true,
469+ message: "Todo deleted successfully",
470+ data: undefined
471+ }
472+ } catch (error) {
473+ console.error("Error deleting todo:", error)
474+ return { isSuccess: false, message: "Failed to delete todo" }
475+ }
476+}
477+```
478+
479+### Auth Rules
480+
481+Follow these rules when working on auth.
482+
483+It uses Clerk for authentication.
484+
485+#### General Rules
486+
487+- Import the auth helper with `import { auth } from "@clerk/nextjs/server"` in server components
488+- await the auth helper in server actions
489+
490+### Payments Rules
491+
492+Follow these rules when working on payments.
493+
494+It uses Stripe for payments.
495+
496+### Analytics Rules
497+
498+Follow these rules when working on analytics.
499+
500+It uses PostHog for analytics.
501+
5502 # Storage Rules
6503
7504 Follow these rules when working with Supabase Storage.
@@ −59 +556 @@
59556 ): Promise<ActionState<{ path: string }>> {
60557 try {
61558 const supabase = createClientComponentClient()
62−
63− const { data, error } = await supabase
64− .storage
559+
560+ const { data, error } = await supabase.storage
65561 .from(bucket)
66562 .upload(path, file, {
67563 upsert: false,
@@ −102 +598 @@
102598 if (file.size > MAX_FILE_SIZE) {
103599 throw new Error("File size exceeds limit")
104600 }
105−
601+
106602 if (!ALLOWED_TYPES.includes(file.type)) {
107603 throw new Error("File type not allowed")
108604 }
109−
605+
110606 return true
111607 }
112608 ```
@@ −161 +657 @@
161657 - Implement progressive upload for large files
162658 - Clean up temporary files and failed uploads
163659 - Use batch operations when handling multiple files
660+
