feat: designed and implemented database schema and tables#38
Conversation
martin0024
left a comment
There was a problem hiding this comment.
Please before reviewing the PR remove any code snippets related to stripe. This PR should only include db schema. Thanks!
a9342d9 to
7ba83a7
Compare
There was a problem hiding this comment.
Pull request overview
Designs and documents an initial PostgreSQL schema for the MCLD platform using Drizzle ORM, along with dependency updates.
Changes:
- Expanded
lib/db/schema.tswith enums and tables for profiles, services/schedules/bookings, webinars/registrations, coaching sessions, and Stripe-related tables. - Added Mermaid ERD documentation under
docs/(plus a broader stack explainer). - Added an initial Drizzle SQL migration and updated Drizzle/other dependencies.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Locks updated dependency versions (Supabase, Drizzle, lint tooling, etc.). |
| package.json | Bumps drizzle-orm version. |
| lib/db/schema.ts | Defines the intended Drizzle schema (enums + tables incl. Stripe tables). |
| drizzle/0000_thick_nemesis.sql | Adds a SQL migration (currently inconsistent with schema.ts). |
| drizzle.config.ts | Adds schemaFilter: ["public"] configuration. |
| docs/schema-overview.md | Mermaid overview ERD for the intended schema. |
| docs/profiles.md | Profiles table documentation. |
| docs/services.md | Services/schedules/bookings documentation. |
| docs/webinars.md | Webinars/registrations documentation. |
| docs/coaching.md | Coaching sessions documentation. |
| docs/stack-explainer.md | Broad project stack reference. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export const roleEnum = pgEnum("role", ["user", "admin", "coach"]); | ||
| export const serviceTypeEnum = pgEnum('service_type', ["coaching_session", "booking"]); | ||
| export const bookingStatusEnum = pgEnum('booking_status', ["pending", "confirmed", "cancelled"]); | ||
| export const webinarTierEnum = pgEnum('webinar_tier', ["free", "premium"]); |
There was a problem hiding this comment.
The PR description mentions service types “preset date” and “user-scheduling”, but the newly introduced service_type enum here is coaching_session vs booking. Either update the PR description (and any docs) to reflect the new meaning, or rename/adjust the enum values to match the originally described service-type model.
| export const subscriptions = pgTable("subscriptions", { | ||
| id: uuid("id").primaryKey().defaultRandom(), | ||
| userId: uuid("user_id").references(() => profiles.id, { onDelete: "cascade" }).notNull().unique(), | ||
| stripeSubscriptionId: text("stripe_subscription_id").unique(), | ||
| status: text("status").notNull().default("none"), | ||
| stripePriceId: text("stripe_price_id"), | ||
| cancelAtPeriodEnd: boolean("cancel_at_period_end").notNull().default(false), | ||
| paymentMethodBrand: text("payment_method_brand"), | ||
| paymentMethodLast4: text("payment_method_last4"), | ||
| createdAt: timestamp("created_at").defaultNow().notNull(), | ||
| updatedAt: timestamp("updated_at").defaultNow().notNull(), | ||
| }); | ||
|
|
||
| export const purchases = pgTable("purchases", { | ||
| id: uuid("id").primaryKey().defaultRandom(), | ||
| userId: uuid("user_id").references(() => profiles.id, { onDelete: "cascade" }).notNull(), | ||
| stripePriceId: text("stripe_price_id").notNull(), | ||
| stripeSessionId: text("stripe_session_id").notNull().unique(), | ||
| productName: text("product_name").notNull(), | ||
| amount: integer("amount").notNull(), | ||
| currency: text("currency").notNull(), | ||
| createdAt: timestamp("created_at").defaultNow().notNull(), | ||
| }); |
There was a problem hiding this comment.
The PR description claims a Stripe webhook handler and Stripe sync helpers were added, but the PR changes only add schema tables (no webhook route handler or helper modules are present in the codebase). Either include the webhook/sync code in this PR, or update the PR description to accurately reflect what’s actually being shipped.
| @@ -0,0 +1,20 @@ | |||
| # Profiles Table | |||
|
|
|||
| Mirrors Supabase `auth.users` — populated via a database trigger on signup. Stores display data and the user's role within the platform. | |||
There was a problem hiding this comment.
This doc states the profiles table is populated via a database trigger on signup, but there is no trigger defined in the committed Drizzle migration(s). Unless triggers are managed elsewhere (e.g., Supabase UI), this is inaccurate; either add the trigger to migrations or revise the documentation to reflect the actual population mechanism.
| Mirrors Supabase `auth.users` — populated via a database trigger on signup. Stores display data and the user's role within the platform. | |
| Mirrors Supabase `auth.users`. Stores display data and the user's role within the platform. |
| ## Services | ||
|
|
||
| The central catalog of offerings on the platform. Two types: | ||
| - **`booking`** — a bookable service; has an associated `schedules` row (via `scheduling_id`). | ||
| - **`coaching_session`** — a coaching offering; `scheduling_id` is null — the scheduling is handled through the `coaching_sessions` table. | ||
|
|
There was a problem hiding this comment.
The relationship description here says booking services “have an associated schedules row (via scheduling_id)”, but the schema links schedules to services via schedules.service_id and does not enforce services.scheduling_id as a foreign key. Update the docs (or the schema) so the described relationship matches the actual constraints and intended source of truth.
Done, plz check again. Thx! |
| webinar_registrations { | ||
| uuid id PK | ||
| uuid user_id FK |
There was a problem hiding this comment.
This shouldn't exist since the flow is either :
- free to watch webinars
- subscribers only webinars
They don't need to register, it's like youtube videos.
Please update the schema as well as the documentation.
|
|
||
| ```mermaid | ||
| erDiagram | ||
| services { |
There was a problem hiding this comment.
please add updatedAt on the flow, since services can be modified/edited.
|
@RenaudBernier does the coach sessions flow works for you ? Good work Berny |
RenaudBernier
left a comment
There was a problem hiding this comment.
Looks good overall, just some variable name changes and the time slots. good job overall, i'm sure that everything will be perfect after your changes
| session_status status "pending | confirmed | cancelled | completed" | ||
| text meeting_url | ||
| text notes | ||
| time[] selected_time_slots |
There was a problem hiding this comment.
We need a way to store timeslots of different lengths. The user might for example select April 14th 2-5pm and April 17th 10am-3pm. time[] is just an array of timestamps, it can't store timeslots. Please work on finding a solution to this. Should we use a certain string format? Maybe some better solution?
| schedules { | ||
| uuid id PK | ||
| uuid service_id FK | ||
| jsonb data | ||
| timestamp created_at | ||
| } |
There was a problem hiding this comment.
I don't understand this table, just explain it to me here please
There was a problem hiding this comment.
When a service is of type “booking,” it has a linked row in the schedules table and accepts a JSON blob containing data about the format, such as specific time slots within a given period. I'll rename the table to maybe something that is easier to understand
| webinars { | ||
| uuid id PK | ||
| text title | ||
| text description | ||
| webinar_tier tier | ||
| int duration_minutes | ||
| text meeting_url | ||
| boolean is_active | ||
| timestamp created_at | ||
| } |
There was a problem hiding this comment.
Webinars aren't meetings, they're recordings on youtube. change meeting_url to youtube_url please
| coaching_sessions { | ||
| uuid id PK | ||
| uuid service_id FK | ||
| uuid coach_id FK | ||
| uuid user_id FK | ||
| timestamp scheduled_at | ||
| int duration_minutes | ||
| session_status status | ||
| text meeting_url | ||
| text notes | ||
| time[] selected_time_slots | ||
| timestamp created_at | ||
| } |
There was a problem hiding this comment.
Same as my comment in the .md, please review how we will store timeslots
…o log time for services of type coaching
Closes #4
Overview
Designed and implemented the PostgreSQL database schema for the MCLD platform using Drizzle ORM. Created tables for profiles, services, schedules, service bookings, webinars, webinar registrations, and coaching sessions. Also added Stripe payment infrastructure (subscriptions, purchases tables, webhook handler, and Stripe sync helpers) to support the upcoming payment feature.
Testing
Manually pushed the schema to Supabase using pnpm db:push and verified all tables appear in the Supabase dashboard under the database section.
Screenshots / Screencasts
N/A — no frontend changes in this PR.
Checklist
[x] Code is neat, readable, and works
[x] Code is commented where appropriate and well-documented
[x] Commit messages follow our guidelines
[x] Issue number is linked
[x] Branch is linked
[x] Reviewers are assigned (one of your tech leads)
Notes
Coach availability was intentionally excluded from this schema — coaches do not set recurring availability in this version of the platform. This decision is reflected in the docs.