From a73691e4cbb3a5a18aee8b34b95a3f230835cf22 Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 12:43:06 -0400 Subject: [PATCH 01/52] feat: pathwayShop, shopItem, and transactionLedger schemas --- .../src/lib/server/db/schema.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/resolution-frontend/src/lib/server/db/schema.ts b/resolution-frontend/src/lib/server/db/schema.ts index d31c200..243fa55 100644 --- a/resolution-frontend/src/lib/server/db/schema.ts +++ b/resolution-frontend/src/lib/server/db/schema.ts @@ -9,6 +9,9 @@ export const pathwayEnum = pgEnum('pathway', ['PYTHON', 'RUST', 'GAME_DEV', 'HAR export const difficultyEnum = pgEnum('difficulty', ['BEGINNER', 'INTERMEDIATE', 'ADVANCED']); export const shipStatusEnum = pgEnum('ship_status', ['PLANNED', 'IN_PROGRESS', 'SHIPPED', 'MISSED']); export const payoutStatusEnum = pgEnum('payout_status', ['DRAFT', 'PENDING', 'PAID', 'CANCELED']); +export const shopOrderStatusEnum = pgEnum('shop_order_status', ['PENDING', 'PROCESSING', 'FULFILLED', 'CANCELED']); // order tracking for frontend (users) +export const shopItemTypeEnum = pgEnum('shop_item_type', ['PHYSICAL', 'DIGITAL']); // for filtering +export const currencyTxnReasonEnum = pgEnum('currency_txn_reason', ['GRANT', 'PURCHASE', 'REFUND', 'ADJUSTMENT', 'OTHER']); // logging why transaction occured // Tables export const user = pgTable('user', { @@ -139,6 +142,45 @@ export const userPathway = pgTable('user_pathway', { uniqueIndex('user_pathway_unique_idx').on(table.userId, table.pathway) ]); +export const pathwayShop = pgTable('pathway_shop', { + id: text('id').primaryKey().$defaultFn(() => createId()), + pathway: pathwayEnum('pathway').notNull().unique(), + isEnabled: boolean('is_enabled').notNull().default(false), // default to not displaying unless ambassador flips switch + currencyName: text('currency_name').notNull().default('wish'), // bcs. idk. resolution = a wish or something idk + currencyNamePlural: text('currency_name_plural').notNull().default('wishes'), + lastEditedBy: text('last_edited_by').references(() => user.id), + createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow() +}); + +export const shopItem = pgTable('shop_item', { + id: text('id').primaryKey().$defaultFn(() => createId()), + pathway: pathwayEnum('pathway').notNull().references(() => pathwayShop.pathway, { onDelete: 'cascade' }), + name: text('name').notNull(), + description: text('description').notNull(), + itemImageUrl: text('item_url'), + price: integer('item_price').notNull(), + stock: integer('item_stock'), + itemType: shopItemTypeEnum('item_type').notNull(), + isActive: boolean('is_active').notNull().default(false), + lastEditedBy: text('last_edited_by').references(() => user.id), + createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow() +}); + +export const transactionLedger = pgTable('currency_transactions', { + id: text('id').primaryKey().$defaultFn(() => createId()), + userId: text('tx_user_id').references(() => user.id, { onDelete: 'set null' }), + pathway: pathwayEnum('tx_pathway').notNull().references(() => pathwayShop.pathway, { onDelete: 'cascade' }), + amount: integer('tx_amount').notNull(), + reason: currencyTxnReasonEnum('tx_reason').notNull(), + note: text('tx_note'), + grantedBy: text('tx_granted_by').references(() => user.id { onDelete: 'set null' }), + refType: text('tx_ref_type'), // SHOP, SHIP, etc. + refId: text('tx_ref_id'), // ID, such as for shop order + createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow() +}); + // Relations export const userRelations = relations(user, ({ many }) => ({ pathways: many(userPathway), From 613992d615157551129c394c0aa2813ba683fb9c Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 12:59:07 -0400 Subject: [PATCH 02/52] feat: add shopOrder table and address validation schema --- .../src/lib/server/db/schema.ts | 26 ++++++++++++++++++- .../src/lib/server/validation/schemas.ts | 18 ++++++++----- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/resolution-frontend/src/lib/server/db/schema.ts b/resolution-frontend/src/lib/server/db/schema.ts index 243fa55..a0882e4 100644 --- a/resolution-frontend/src/lib/server/db/schema.ts +++ b/resolution-frontend/src/lib/server/db/schema.ts @@ -1,6 +1,7 @@ -import { pgTable, text, timestamp, boolean, integer, real, pgEnum, uniqueIndex, index } from 'drizzle-orm/pg-core'; +import { pgTable, text, timestamp, boolean, integer, real, pgEnum, uniqueIndex, index, jsonb } from 'drizzle-orm/pg-core'; import { relations } from 'drizzle-orm'; import { createId } from '@paralleldrive/cuid2'; +import type { AddressInput } from '../validation'; // Enums export const enrollmentRoleEnum = pgEnum('enrollment_role', ['PARTICIPANT', 'AMBASSADOR']); @@ -181,6 +182,29 @@ export const transactionLedger = pgTable('currency_transactions', { createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow() }); +export const shopOrder = pgTable('shop_orders', { + id: text('id').primaryKey().$defaultFn(() => createId()), + userId: text('user_id').references(() => user.id, { onDelete: 'set null' }), + pathway: pathwayEnum('pathway').notNull().references(() => pathwayShop.pathway, { onDelete: 'cascade' }), + status: shopOrderStatusEnum('order_stauts').notNull().default("PENDING"), + totalAmount: integer('amount').notNull(), + item: text('shop_item_id').references(() => shopItem.id, { onDelete: 'set null' }), + shippingAddress: jsonb('shipping_address').$type(), + userNotes: text('user_notes'), + fufillerNotes: text('fufiller_notes'), + // claimedBy: + fufilledBy: text('fufilled_by'), + fufilledAt: timestamp('fufilled_at', { mode: 'date' }).defaultNow(), + cancelledReason: text('cancelled_reason'), + createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow(), +}) + +//currencyTransaction — id, userId, pathway, amount (signed integer), reason (enum), note (nullable), grantedBy (nullable userId), refType (nullable), refId (nullable), createdAt. Index on (userId, pathway) for fast balance lookup. +//- shopOrder — id, userId, pathway, status (enum, default PENDING), totalAmount, containsPhysical, shippingInfo (text nullable), userNotes (nullable), fulfillerNotes (nullable), claimedBy (nullable), fulfilledBy (nullable), fulfilledAt (nullable), canceledReason (nullable), createdAt, updatedAt. Index on (pathway, status) for queue queries. +//- shopOrderItem — id, orderId, itemId, quantity, unitPriceSnapshot, nameSnapshot, itemTypeSnapshot +//- fulfillerPathway — mirror [reviewerPathway](file:///Users/niko/coding-projects/resolution/resolution-frontend/src/lib/server/db/schema.ts#L224-L232) shape exactly: id, userId, pathway, assignedAt, assignedBy, with unique (userId, pathway) index + // Relations export const userRelations = relations(user, ({ many }) => ({ pathways: many(userPathway), diff --git a/resolution-frontend/src/lib/server/validation/schemas.ts b/resolution-frontend/src/lib/server/validation/schemas.ts index f87bf23..1c6d13f 100644 --- a/resolution-frontend/src/lib/server/validation/schemas.ts +++ b/resolution-frontend/src/lib/server/validation/schemas.ts @@ -41,6 +41,17 @@ const safeUrl = z.string().url('Please enter a valid URL').max(2000).refine( { message: 'URL must use http or https' } ); +export const addressSchema = z.object({ + addressLine1: z.string().min(1, 'Address is required').max(200), + addressLine2: z.string().max(200).optional().default(''), + city: z.string().min(1, 'City is required').max(100), + stateProvince: z.string().min(1, 'State / Province is required').max(100), + country: z.string().min(1, 'Country is required').max(100), + zipPostalCode: z.string().min(1, 'ZIP / Postal code is required').max(20) +}); + +export type AddressInput = z.infer; + export const projectSubmissionSchema = z.object({ codeUrl: safeUrl.refine( (val) => /^https:\/\/github\.com\/.+\/.+/.test(val), @@ -55,12 +66,7 @@ export const projectSubmissionSchema = z.object({ email: z.string().email('Please enter a valid email').max(254).transform((v) => v.trim().toLowerCase()), description: z.string().min(1, 'Description is required').max(2000), githubUsername: z.string().min(1, 'GitHub username is required').max(100), - addressLine1: z.string().min(1, 'Address is required').max(200), - addressLine2: z.string().max(200).optional().default(''), - city: z.string().min(1, 'City is required').max(100), - stateProvince: z.string().min(1, 'State / Province is required').max(100), - country: z.string().min(1, 'Country is required').max(100), - zipPostalCode: z.string().min(1, 'ZIP / Postal code is required').max(20), + ...addressSchema.shape, // needed for shop order validation birthday: z.string().min(1, 'Birthday is required').regex(/^\d{4}-\d{2}-\d{2}$/, 'Please enter a valid date'), hackatimeProject: z.string().min(1, 'Hackatime project is required').max(200), hoursSpent: z From a9093667e00fa1d5ce7ca5dc2fe5a40ca6d84d3a Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 14:56:16 -0400 Subject: [PATCH 03/52] feat: relations + one more table --- .../src/lib/server/db/schema.ts | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/resolution-frontend/src/lib/server/db/schema.ts b/resolution-frontend/src/lib/server/db/schema.ts index a0882e4..d1c2855 100644 --- a/resolution-frontend/src/lib/server/db/schema.ts +++ b/resolution-frontend/src/lib/server/db/schema.ts @@ -176,7 +176,7 @@ export const transactionLedger = pgTable('currency_transactions', { amount: integer('tx_amount').notNull(), reason: currencyTxnReasonEnum('tx_reason').notNull(), note: text('tx_note'), - grantedBy: text('tx_granted_by').references(() => user.id { onDelete: 'set null' }), + grantedBy: text('tx_granted_by').references(() => user.id, { onDelete: 'set null' }), refType: text('tx_ref_type'), // SHOP, SHIP, etc. refId: text('tx_ref_id'), // ID, such as for shop order createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow() @@ -189,21 +189,29 @@ export const shopOrder = pgTable('shop_orders', { status: shopOrderStatusEnum('order_stauts').notNull().default("PENDING"), totalAmount: integer('amount').notNull(), item: text('shop_item_id').references(() => shopItem.id, { onDelete: 'set null' }), + itemPriceSnapshot: integer('item_price_snapshot').notNull(), + itemTypeSnapshot: shopItemTypeEnum('item_type_enum'), + itemNameSnapshot: text('item_name_snapshot').notNull(), shippingAddress: jsonb('shipping_address').$type(), userNotes: text('user_notes'), fufillerNotes: text('fufiller_notes'), // claimedBy: - fufilledBy: text('fufilled_by'), - fufilledAt: timestamp('fufilled_at', { mode: 'date' }).defaultNow(), + fufilledBy: text('fufilled_by').references(() => user.id, { onDelete: 'set null' }), + fufilledAt: timestamp('fufilled_at', { mode: 'date' }), cancelledReason: text('cancelled_reason'), createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow(), }) -//currencyTransaction — id, userId, pathway, amount (signed integer), reason (enum), note (nullable), grantedBy (nullable userId), refType (nullable), refId (nullable), createdAt. Index on (userId, pathway) for fast balance lookup. -//- shopOrder — id, userId, pathway, status (enum, default PENDING), totalAmount, containsPhysical, shippingInfo (text nullable), userNotes (nullable), fulfillerNotes (nullable), claimedBy (nullable), fulfilledBy (nullable), fulfilledAt (nullable), canceledReason (nullable), createdAt, updatedAt. Index on (pathway, status) for queue queries. -//- shopOrderItem — id, orderId, itemId, quantity, unitPriceSnapshot, nameSnapshot, itemTypeSnapshot -//- fulfillerPathway — mirror [reviewerPathway](file:///Users/niko/coding-projects/resolution/resolution-frontend/src/lib/server/db/schema.ts#L224-L232) shape exactly: id, userId, pathway, assignedAt, assignedBy, with unique (userId, pathway) index +export const fufillerPathway = pgTable('fufiller_pathway', { + id: text('id').primaryKey().$defaultFn(() => createId()), + userId: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }), + pathway: pathwayEnum('pathway').notNull(), // theres a chance we won't need to assign this, for now this is assigned + assignedAt: timestamp('assigned_at', { mode: 'date' }).notNull().defaultNow(), + assignedBy: text('assigned_by').notNull().references(() => user.id) +}, (table) => [ + uniqueIndex('fufiller_pathway_unique_idx').on(table.userId, table.pathway) +]); // Relations export const userRelations = relations(user, ({ many }) => ({ @@ -215,7 +223,10 @@ export const userRelations = relations(user, ({ many }) => ({ weeklyShips: many(weeklyShip), payouts: many(ambassadorPayout), referralLinks: many(referralLink), - reviewerAssignments: many(reviewerPathway) + reviewerAssignments: many(reviewerPathway), + currencyTransactions: many(transactionLedger), + shopOrders: many(shopOrder), + fufillerAssignments: many(fufillerPathway) })); export const sessionRelations = relations(session, ({ one }) => ({ @@ -356,3 +367,35 @@ export const referralSignupRelations = relations(referralSignup, ({ one }) => ({ referralLink: one(referralLink, { fields: [referralSignup.referralLinkId], references: [referralLink.id] }), user: one(user, { fields: [referralSignup.userId], references: [user.id] }) })); + +export const pathwayShopRelations = relations(pathwayShop, ({ one, many }) => ({ + editor: one(user, { fields: [pathwayShop.lastEditedBy], references: [user.id] }), + items: many(shopItem), + transactions: many(transactionLedger), + orders: many(shopOrder) +})); + +export const shopItemRelations = relations(shopItem, ({ one, many }) => ({ + shop: one(pathwayShop, { fields: [shopItem.pathway], references: [pathwayShop.pathway] }), + editor: one(user, { fields: [shopItem.lastEditedBy], references: [user.id] }), + orders: many(shopOrder) +})); + +export const transactionLedgerRelations = relations(transactionLedger, ({ one }) => ({ + user: one(user, { fields: [transactionLedger.userId], references: [user.id] }), + grantedByUser: one(user, { fields: [transactionLedger.grantedBy], references: [user.id] }), + shop: one(pathwayShop, { fields: [transactionLedger.pathway], references: [pathwayShop.pathway] }) +})); + +export const shopOrderRelations = relations(shopOrder, ({ one }) => ({ + user: one(user, { fields: [shopOrder.userId], references: [user.id] }), + shop: one(pathwayShop, { fields: [shopOrder.pathway], references: [pathwayShop.pathway] }), + item: one(shopItem, { fields: [shopOrder.item], references: [shopItem.id] }), + fufiller: one(user, { fields: [shopOrder.fufilledBy], references: [user.id] }) +})); + +export const fufillerPathwayRelations = relations(fufillerPathway, ({ one }) => ({ + user: one(user, { fields: [fufillerPathway.userId], references: [user.id] }), + assignedByUser: one(user, { fields: [fufillerPathway.assignedBy], references: [user.id] }) +})); + From b04f9136f074650793bf463864d11314c2372658 Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 14:58:00 -0400 Subject: [PATCH 04/52] misc: migrations for a73691e, 613992d, a909366 --- .../drizzle/0005_nosy_bloodstrike.sql | 84 + .../drizzle/meta/0005_snapshot.json | 2237 +++++++++++++++++ .../drizzle/meta/_journal.json | 7 + 3 files changed, 2328 insertions(+) create mode 100644 resolution-frontend/drizzle/0005_nosy_bloodstrike.sql create mode 100644 resolution-frontend/drizzle/meta/0005_snapshot.json diff --git a/resolution-frontend/drizzle/0005_nosy_bloodstrike.sql b/resolution-frontend/drizzle/0005_nosy_bloodstrike.sql new file mode 100644 index 0000000..df50141 --- /dev/null +++ b/resolution-frontend/drizzle/0005_nosy_bloodstrike.sql @@ -0,0 +1,84 @@ +CREATE TYPE "public"."currency_txn_reason" AS ENUM('GRANT', 'PURCHASE', 'REFUND', 'ADJUSTMENT', 'OTHER');--> statement-breakpoint +CREATE TYPE "public"."shop_item_type" AS ENUM('PHYSICAL', 'DIGITAL');--> statement-breakpoint +CREATE TYPE "public"."shop_order_status" AS ENUM('PENDING', 'PROCESSING', 'FULFILLED', 'CANCELED');--> statement-breakpoint +CREATE TABLE "fufiller_pathway" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "pathway" "pathway" NOT NULL, + "assigned_at" timestamp DEFAULT now() NOT NULL, + "assigned_by" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "pathway_shop" ( + "id" text PRIMARY KEY NOT NULL, + "pathway" "pathway" NOT NULL, + "is_enabled" boolean DEFAULT false NOT NULL, + "currency_name" text DEFAULT 'wish' NOT NULL, + "currency_name_plural" text DEFAULT 'wishes' NOT NULL, + "last_edited_by" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "pathway_shop_pathway_unique" UNIQUE("pathway") +); +--> statement-breakpoint +CREATE TABLE "shop_item" ( + "id" text PRIMARY KEY NOT NULL, + "pathway" "pathway" NOT NULL, + "name" text NOT NULL, + "description" text NOT NULL, + "item_url" text, + "item_price" integer NOT NULL, + "item_stock" integer, + "item_type" "shop_item_type" NOT NULL, + "is_active" boolean DEFAULT false NOT NULL, + "last_edited_by" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "shop_orders" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text, + "pathway" "pathway" NOT NULL, + "order_stauts" "shop_order_status" DEFAULT 'PENDING' NOT NULL, + "amount" integer NOT NULL, + "shop_item_id" text, + "item_price_snapshot" integer NOT NULL, + "item_type_enum" "shop_item_type", + "item_name_snapshot" text NOT NULL, + "shipping_address" jsonb, + "user_notes" text, + "fufiller_notes" text, + "fufilled_by" text, + "fufilled_at" timestamp, + "cancelled_reason" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "currency_transactions" ( + "id" text PRIMARY KEY NOT NULL, + "tx_user_id" text, + "tx_pathway" "pathway" NOT NULL, + "tx_amount" integer NOT NULL, + "tx_reason" "currency_txn_reason" NOT NULL, + "tx_note" text, + "tx_granted_by" text, + "tx_ref_type" text, + "tx_ref_id" text, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "fufiller_pathway" ADD CONSTRAINT "fufiller_pathway_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "fufiller_pathway" ADD CONSTRAINT "fufiller_pathway_assigned_by_user_id_fk" FOREIGN KEY ("assigned_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "pathway_shop" ADD CONSTRAINT "pathway_shop_last_edited_by_user_id_fk" FOREIGN KEY ("last_edited_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_item" ADD CONSTRAINT "shop_item_pathway_pathway_shop_pathway_fk" FOREIGN KEY ("pathway") REFERENCES "public"."pathway_shop"("pathway") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_item" ADD CONSTRAINT "shop_item_last_edited_by_user_id_fk" FOREIGN KEY ("last_edited_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_pathway_pathway_shop_pathway_fk" FOREIGN KEY ("pathway") REFERENCES "public"."pathway_shop"("pathway") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_shop_item_id_shop_item_id_fk" FOREIGN KEY ("shop_item_id") REFERENCES "public"."shop_item"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_fufilled_by_user_id_fk" FOREIGN KEY ("fufilled_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "currency_transactions" ADD CONSTRAINT "currency_transactions_tx_user_id_user_id_fk" FOREIGN KEY ("tx_user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "currency_transactions" ADD CONSTRAINT "currency_transactions_tx_pathway_pathway_shop_pathway_fk" FOREIGN KEY ("tx_pathway") REFERENCES "public"."pathway_shop"("pathway") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "currency_transactions" ADD CONSTRAINT "currency_transactions_tx_granted_by_user_id_fk" FOREIGN KEY ("tx_granted_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "fufiller_pathway_unique_idx" ON "fufiller_pathway" USING btree ("user_id","pathway"); \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/0005_snapshot.json b/resolution-frontend/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000..53da688 --- /dev/null +++ b/resolution-frontend/drizzle/meta/0005_snapshot.json @@ -0,0 +1,2237 @@ +{ + "id": "2fbbf398-fe52-49d5-bcef-27f714185921", + "prevId": "a3a209aa-49a0-47e6-9126-892277549771", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ambassador_pathway": { + "name": "ambassador_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "ambassador_pathway_unique_idx": { + "name": "ambassador_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ambassador_pathway_user_id_user_id_fk": { + "name": "ambassador_pathway_user_id_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_pathway_assigned_by_user_id_fk": { + "name": "ambassador_pathway_assigned_by_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout": { + "name": "ambassador_payout", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "payout_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_ambassador_id_user_id_fk": { + "name": "ambassador_payout_ambassador_id_user_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_season_id_program_season_id_fk": { + "name": "ambassador_payout_season_id_program_season_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout_item": { + "name": "ambassador_payout_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "payout_id": { + "name": "payout_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_count": { + "name": "completion_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "rate_cents_per_completion": { + "name": "rate_cents_per_completion", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_item_payout_id_ambassador_payout_id_fk": { + "name": "ambassador_payout_item_payout_id_ambassador_payout_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "ambassador_payout", + "columnsFrom": [ + "payout_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_item_workshop_id_workshop_id_fk": { + "name": "ambassador_payout_item_workshop_id_workshop_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.fufiller_pathway": { + "name": "fufiller_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "fufiller_pathway_unique_idx": { + "name": "fufiller_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fufiller_pathway_user_id_user_id_fk": { + "name": "fufiller_pathway_user_id_user_id_fk", + "tableFrom": "fufiller_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fufiller_pathway_assigned_by_user_id_fk": { + "name": "fufiller_pathway_assigned_by_user_id_fk", + "tableFrom": "fufiller_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_shop": { + "name": "pathway_shop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_name": { + "name": "currency_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wish'" + }, + "currency_name_plural": { + "name": "currency_name_plural", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wishes'" + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "pathway_shop_last_edited_by_user_id_fk": { + "name": "pathway_shop_last_edited_by_user_id_fk", + "tableFrom": "pathway_shop", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "pathway_shop_pathway_unique": { + "name": "pathway_shop_pathway_unique", + "nullsNotDistinct": false, + "columns": [ + "pathway" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_week_content": { + "name": "pathway_week_content", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "prize_image_url": { + "name": "prize_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_submissions_open": { + "name": "is_submissions_open", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pathway_week_content_unique_idx": { + "name": "pathway_week_content_unique_idx", + "columns": [ + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pathway_week_content_last_edited_by_user_id_fk": { + "name": "pathway_week_content_last_edited_by_user_id_fk", + "tableFrom": "pathway_week_content", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_enrollment": { + "name": "program_enrollment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "enrollment_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "enrollment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "starting_week": { + "name": "starting_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "enrollment_user_season_role_idx": { + "name": "enrollment_user_season_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_enrollment_user_id_user_id_fk": { + "name": "program_enrollment_user_id_user_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "program_enrollment_season_id_program_season_id_fk": { + "name": "program_enrollment_season_id_program_season_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_season": { + "name": "program_season", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signup_opens_at": { + "name": "signup_opens_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "signup_closes_at": { + "name": "signup_closes_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "total_weeks": { + "name": "total_weeks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 8 + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "program_season_slug_unique": { + "name": "program_season_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_link": { + "name": "referral_link", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "referral_link_ambassador_id_user_id_fk": { + "name": "referral_link_ambassador_id_user_id_fk", + "tableFrom": "referral_link", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "referral_link_code_unique": { + "name": "referral_link_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_signup": { + "name": "referral_signup", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "referral_link_id": { + "name": "referral_link_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "referral_signup_unique_idx": { + "name": "referral_signup_unique_idx", + "columns": [ + { + "expression": "referral_link_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "referral_signup_referral_link_id_referral_link_id_fk": { + "name": "referral_signup_referral_link_id_referral_link_id_fk", + "tableFrom": "referral_signup", + "tableTo": "referral_link", + "columnsFrom": [ + "referral_link_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_signup_user_id_user_id_fk": { + "name": "referral_signup_user_id_user_id_fk", + "tableFrom": "referral_signup", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reviewer_pathway": { + "name": "reviewer_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "reviewer_pathway_unique_idx": { + "name": "reviewer_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reviewer_pathway_user_id_user_id_fk": { + "name": "reviewer_pathway_user_id_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reviewer_pathway_assigned_by_user_id_fk": { + "name": "reviewer_pathway_assigned_by_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_item": { + "name": "shop_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_url": { + "name": "item_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price": { + "name": "item_price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_stock": { + "name": "item_stock", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "item_type": { + "name": "item_type", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_item_pathway_pathway_shop_pathway_fk": { + "name": "shop_item_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_item", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_item_last_edited_by_user_id_fk": { + "name": "shop_item_last_edited_by_user_id_fk", + "tableFrom": "shop_item", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_orders": { + "name": "shop_orders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "order_stauts": { + "name": "order_stauts", + "type": "shop_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "shop_item_id": { + "name": "shop_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price_snapshot": { + "name": "item_price_snapshot", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_type_enum": { + "name": "item_type_enum", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "item_name_snapshot": { + "name": "item_name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "shipping_address": { + "name": "shipping_address", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_notes": { + "name": "user_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufiller_notes": { + "name": "fufiller_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_by": { + "name": "fufilled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_at": { + "name": "fufilled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_reason": { + "name": "cancelled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_orders_user_id_user_id_fk": { + "name": "shop_orders_user_id_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_pathway_pathway_shop_pathway_fk": { + "name": "shop_orders_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_orders", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_orders_shop_item_id_shop_item_id_fk": { + "name": "shop_orders_shop_item_id_shop_item_id_fk", + "tableFrom": "shop_orders", + "tableTo": "shop_item", + "columnsFrom": [ + "shop_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_fufilled_by_user_id_fk": { + "name": "shop_orders_fufilled_by_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "fufilled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.currency_transactions": { + "name": "currency_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tx_user_id": { + "name": "tx_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_pathway": { + "name": "tx_pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_amount": { + "name": "tx_amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tx_reason": { + "name": "tx_reason", + "type": "currency_txn_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_note": { + "name": "tx_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_granted_by": { + "name": "tx_granted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_type": { + "name": "tx_ref_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_id": { + "name": "tx_ref_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "currency_transactions_tx_user_id_user_id_fk": { + "name": "currency_transactions_tx_user_id_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "currency_transactions_tx_pathway_pathway_shop_pathway_fk": { + "name": "currency_transactions_tx_pathway_pathway_shop_pathway_fk", + "tableFrom": "currency_transactions", + "tableTo": "pathway_shop", + "columnsFrom": [ + "tx_pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "currency_transactions_tx_granted_by_user_id_fk": { + "name": "currency_transactions_tx_granted_by_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hack_club_id": { + "name": "hack_club_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_id": { + "name": "slack_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verification_status": { + "name": "verification_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ysws_eligible": { + "name": "ysws_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_hack_club_id_unique": { + "name": "user_hack_club_id_unique", + "nullsNotDistinct": false, + "columns": [ + "hack_club_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_pathway": { + "name": "user_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_pathway_unique_idx": { + "name": "user_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_pathway_user_id_user_id_fk": { + "name": "user_pathway_user_id_user_id_fk", + "tableFrom": "user_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.weekly_ship": { + "name": "weekly_ship", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "goal_text": { + "name": "goal_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "ship_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PLANNED'" + }, + "proof_url": { + "name": "proof_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shipped_at": { + "name": "shipped_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ship_user_season_week_idx": { + "name": "ship_user_season_week_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "weekly_ship_user_id_user_id_fk": { + "name": "weekly_ship_user_id_user_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_season_id_program_season_id_fk": { + "name": "weekly_ship_season_id_program_season_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_workshop_id_workshop_id_fk": { + "name": "weekly_ship_workshop_id_workshop_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop": { + "name": "workshop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "difficulty": { + "name": "difficulty", + "type": "difficulty", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "estimated_hours": { + "name": "estimated_hours", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_author_id_user_id_fk": { + "name": "workshop_author_id_user_id_fk", + "tableFrom": "workshop", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_season_id_program_season_id_fk": { + "name": "workshop_season_id_program_season_id_fk", + "tableFrom": "workshop", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_analytics": { + "name": "workshop_analytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "starts": { + "name": "starts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completions": { + "name": "completions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "avg_completion_mins": { + "name": "avg_completion_mins", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_analytics_workshop_id_workshop_id_fk": { + "name": "workshop_analytics_workshop_id_workshop_id_fk", + "tableFrom": "workshop_analytics", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workshop_analytics_workshop_id_unique": { + "name": "workshop_analytics_workshop_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workshop_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_completion": { + "name": "workshop_completion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_url": { + "name": "project_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_workshop_participant_season_idx": { + "name": "completion_workshop_participant_season_idx", + "columns": [ + { + "expression": "workshop_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workshop_completion_workshop_id_workshop_id_fk": { + "name": "workshop_completion_workshop_id_workshop_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_participant_id_user_id_fk": { + "name": "workshop_completion_participant_id_user_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "user", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_season_id_program_season_id_fk": { + "name": "workshop_completion_season_id_program_season_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.currency_txn_reason": { + "name": "currency_txn_reason", + "schema": "public", + "values": [ + "GRANT", + "PURCHASE", + "REFUND", + "ADJUSTMENT", + "OTHER" + ] + }, + "public.difficulty": { + "name": "difficulty", + "schema": "public", + "values": [ + "BEGINNER", + "INTERMEDIATE", + "ADVANCED" + ] + }, + "public.enrollment_role": { + "name": "enrollment_role", + "schema": "public", + "values": [ + "PARTICIPANT", + "AMBASSADOR" + ] + }, + "public.enrollment_status": { + "name": "enrollment_status", + "schema": "public", + "values": [ + "ACTIVE", + "DROPPED", + "COMPLETED" + ] + }, + "public.pathway": { + "name": "pathway", + "schema": "public", + "values": [ + "PYTHON", + "RUST", + "GAME_DEV", + "HARDWARE", + "DESIGN", + "GENERAL_CODING" + ] + }, + "public.payout_status": { + "name": "payout_status", + "schema": "public", + "values": [ + "DRAFT", + "PENDING", + "PAID", + "CANCELED" + ] + }, + "public.ship_status": { + "name": "ship_status", + "schema": "public", + "values": [ + "PLANNED", + "IN_PROGRESS", + "SHIPPED", + "MISSED" + ] + }, + "public.shop_item_type": { + "name": "shop_item_type", + "schema": "public", + "values": [ + "PHYSICAL", + "DIGITAL" + ] + }, + "public.shop_order_status": { + "name": "shop_order_status", + "schema": "public", + "values": [ + "PENDING", + "PROCESSING", + "FULFILLED", + "CANCELED" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/_journal.json b/resolution-frontend/drizzle/meta/_journal.json index 004721c..d4688f0 100644 --- a/resolution-frontend/drizzle/meta/_journal.json +++ b/resolution-frontend/drizzle/meta/_journal.json @@ -36,6 +36,13 @@ "when": 1776130671597, "tag": "0004_left_pretty_boy", "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1778007365517, + "tag": "0005_nosy_bloodstrike", + "breakpoints": true } ] } \ No newline at end of file From cd9fcc5a9cddc235375ba603b3e5e37a52772c5a Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 15:42:22 -0400 Subject: [PATCH 05/52] chore: scaffold pathway shop page route --- .../src/routes/app/pathway/[pathway]/shop/+page.svelte | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte new file mode 100644 index 0000000..2e9131b --- /dev/null +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte @@ -0,0 +1,2 @@ + + From 562f3d13b2dd2f439301aa6bd76fd840e5698bf1 Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 15:43:04 -0400 Subject: [PATCH 06/52] feat: add load function + template --- .../pathway/[pathway]/shop/+page.server.ts | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts new file mode 100644 index 0000000..12cc9f9 --- /dev/null +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -0,0 +1,116 @@ +import type { PageServerLoad, Actions } from './$types'; +import { db } from '$lib/server/db'; +import { + userPathway, + pathwayShop, + shopItem, + shopOrder, + transactionLedger +} from '$lib/server/db/schema'; +import { and, eq, sql, desc, gte } from 'drizzle-orm'; +import { error, fail, redirect } from '@sveltejs/kit'; +import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; +import { z } from 'zod'; +import { addressSchema, type AddressInput } from '$lib/server/validation'; +import { ambassadorPathway } from '$lib/server/db/schema'; + + +const purchaseSchema = z.object({ + itemId: z.string().min(1), + userNotes: z.string().max(500).optional(), + shippingAddress: addressSchema.optional() // optional as user might not actually be buying a physical item, in which case i don't think we need it +}); + +const cancelSchema = z.object({ + orderId: z.string().min(1) // needs to be a valid string +}); + +export const load: PageServerLoad = async ({ params, parent }) => { + const { user } = await parent(); + const pathwayId = params.pathway.toUpperCase(); + if (!PATHWAY_IDS.includes(pathwayId as PathwayId)) throw error(404, 'Pathway not found'); + const typedPathwayId = pathwayId as PathwayId; + + const membership = await db + .select() + .from(userPathway) + .where(and(eq(userPathway.userId, user.id), eq(userPathway.pathway, typedPathwayId))) + .limit(1); + + if (membership.length === 0) throw redirect(302, '/app'); + + const pathwayShopRow = await db + .select() + .from(pathwayShop) + .where(eq(pathwayShop.pathway, typedPathwayId)) + .limit(1); + + if (pathwayShopRow.length === 0 || !pathwayShopRow[0].isEnabled) { + throw error(404); + } + const shop = pathwayShopRow[0]; + + const pathwayItems = await db + .select() + .from(shopItem) + .where(and(eq(shopItem.pathway, typedPathwayId), eq(shopItem.isActive, true))); // return all items minus inactive + + const [{ balance }] = await db + .select({ + balance: sql`COALESCE(SUM(${transactionLedger.amount}), 0)`.mapWith(Number) + }) + .from(transactionLedger) + .where(and( + eq(transactionLedger.userId, user.id), + eq(transactionLedger.pathway, typedPathwayId) + )); + + const recentOrders = await db + .select() + .from(shopOrder) + .where(and(eq(shopOrder.userId, user.id), eq(shopOrder.pathway, typedPathwayId))) + .orderBy(desc(shopOrder.createdAt)) + .limit(5); // link to a seperate orders page, so we only need a preview + + return { + pathwayId: typedPathwayId, + shop: { + isEnabled: shop.isEnabled, + currencyName: shop.currencyName, + currencyNamePlural: shop.currencyNamePlural + }, + items: pathwayItems, + balance, // this is the user bal btw just so we're clear + orders: recentOrders + }; +}; + +// ── Actions ─────────────────────────────────────────────────────────── +export const actions: Actions = { + purchase: async ({ request, params, locals }) => { + // 1. auth + pathway validation (same as load) + // 2. parse FormData → purchaseSchema.safeParse → fail(400) on error + // 3. db.transaction(async (tx) => { + // a. re-fetch shop (isEnabled), item (isActive, pathway match) + // b. if item.itemType === 'PHYSICAL' require shippingAddress + // c. stock check: null (unlimited) OR > 0 → decrement + // d. recompute balance, ensure >= item.price + // e. insert shopOrder with snapshots (price/type/name/total) + // f. insert transactionLedger { amount: -price, reason:'PURCHASE', + // refType:'SHOP', refId: order.id } + // }) + // 4. return { success: true, orderId } or fail(...) with message + }, + + cancel: async ({ request, params, locals }) => { + // 1. auth + pathway validation + // 2. parse FormData → cancelSchema + // 3. db.transaction: + // a. load order; assert userId === user.id and status === 'PENDING' + // b. set status = 'CANCELED', cancelledReason + // c. restore item.stock if not null + // d. insert transactionLedger { amount: +price, reason:'REFUND', + // refType:'SHOP', refId: order.id } + // 4. return { success: true } or fail(...) + } +}; From a451758cfe0b83d7d0ecb03b6ea1f1564b8df26a Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 15:56:46 -0400 Subject: [PATCH 07/52] feat: move checking stuff to dedicated function --- .../pathway/[pathway]/shop/+page.server.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts index 12cc9f9..58bc297 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -25,18 +25,18 @@ const cancelSchema = z.object({ orderId: z.string().min(1) // needs to be a valid string }); -export const load: PageServerLoad = async ({ params, parent }) => { - const { user } = await parent(); - const pathwayId = params.pathway.toUpperCase(); +// guard for load + actions +// returns pathway ID and the shop item +async function assertShopAccess(userId: string, pathwayParam: string) { + const pathwayId = pathwayParam.toUpperCase(); if (!PATHWAY_IDS.includes(pathwayId as PathwayId)) throw error(404, 'Pathway not found'); const typedPathwayId = pathwayId as PathwayId; - + const membership = await db .select() .from(userPathway) - .where(and(eq(userPathway.userId, user.id), eq(userPathway.pathway, typedPathwayId))) + .where(and(eq(userPathway.userId, userId), eq(userPathway.pathway, typedPathwayId))) .limit(1); - if (membership.length === 0) throw redirect(302, '/app'); const pathwayShopRow = await db @@ -44,11 +44,16 @@ export const load: PageServerLoad = async ({ params, parent }) => { .from(pathwayShop) .where(eq(pathwayShop.pathway, typedPathwayId)) .limit(1); - if (pathwayShopRow.length === 0 || !pathwayShopRow[0].isEnabled) { throw error(404); } - const shop = pathwayShopRow[0]; + + return { typedPathwayId, shop: pathwayShopRow[0] }; +} + +export const load: PageServerLoad = async ({ params, parent }) => { + const { user } = await parent(); + const { typedPathwayId, shop } = await assertShopAccess(user.id, params.pathway); const pathwayItems = await db .select() @@ -85,10 +90,11 @@ export const load: PageServerLoad = async ({ params, parent }) => { }; }; -// ── Actions ─────────────────────────────────────────────────────────── export const actions: Actions = { purchase: async ({ request, params, locals }) => { - // 1. auth + pathway validation (same as load) + if (!locals.user) throw redirect(302, '/api/auth/login'); + const { typedPathwayId, shop } = await assertShopAccess(locals.user.id, params.pathway); + // 2. parse FormData → purchaseSchema.safeParse → fail(400) on error // 3. db.transaction(async (tx) => { // a. re-fetch shop (isEnabled), item (isActive, pathway match) @@ -103,7 +109,9 @@ export const actions: Actions = { }, cancel: async ({ request, params, locals }) => { - // 1. auth + pathway validation + if (!locals.user) throw redirect(302, '/api/auth/login'); + const { typedPathwayId } = await assertShopAccess(locals.user.id, params.pathway); + // 2. parse FormData → cancelSchema // 3. db.transaction: // a. load order; assert userId === user.id and status === 'PENDING' From b11361c89f773341020926d2b50ca47d06bc77e0 Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 21:59:03 -0400 Subject: [PATCH 08/52] feat: purchase function Co-authored-by: Copilot --- .../pathway/[pathway]/shop/+page.server.ts | 114 +++++++++++++++--- 1 file changed, 94 insertions(+), 20 deletions(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts index 58bc297..4def765 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -7,12 +7,18 @@ import { shopOrder, transactionLedger } from '$lib/server/db/schema'; -import { and, eq, sql, desc, gte } from 'drizzle-orm'; +import { and, eq, sql, desc } from 'drizzle-orm'; import { error, fail, redirect } from '@sveltejs/kit'; import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; import { z } from 'zod'; -import { addressSchema, type AddressInput } from '$lib/server/validation'; -import { ambassadorPathway } from '$lib/server/db/schema'; +import { addressSchema, validateFormData } from '$lib/server/validation'; + +// thrown inside transactions to abort + roll back; caught outside to convert to fail() +class ShopError extends Error { + constructor(public status: number, public body: { message: string }) { + super(body.message); + } +} const purchaseSchema = z.object({ @@ -25,21 +31,24 @@ const cancelSchema = z.object({ orderId: z.string().min(1) // needs to be a valid string }); +type DbOrTx = typeof db | Parameters[0]>[0]; + // guard for load + actions -// returns pathway ID and the shop item -async function assertShopAccess(userId: string, pathwayParam: string) { +// returns pathway ID and the shop item +// pass `tx` when calling inside a transaction so the re-check uses the same snapshot +async function assertShopAccess(userId: string, pathwayParam: string, conn: DbOrTx = db) { const pathwayId = pathwayParam.toUpperCase(); if (!PATHWAY_IDS.includes(pathwayId as PathwayId)) throw error(404, 'Pathway not found'); const typedPathwayId = pathwayId as PathwayId; - const membership = await db + const membership = await conn .select() .from(userPathway) .where(and(eq(userPathway.userId, userId), eq(userPathway.pathway, typedPathwayId))) .limit(1); if (membership.length === 0) throw redirect(302, '/app'); - const pathwayShopRow = await db + const pathwayShopRow = await conn .select() .from(pathwayShop) .where(eq(pathwayShop.pathway, typedPathwayId)) @@ -93,19 +102,84 @@ export const load: PageServerLoad = async ({ params, parent }) => { export const actions: Actions = { purchase: async ({ request, params, locals }) => { if (!locals.user) throw redirect(302, '/api/auth/login'); - const { typedPathwayId, shop } = await assertShopAccess(locals.user.id, params.pathway); - - // 2. parse FormData → purchaseSchema.safeParse → fail(400) on error - // 3. db.transaction(async (tx) => { - // a. re-fetch shop (isEnabled), item (isActive, pathway match) - // b. if item.itemType === 'PHYSICAL' require shippingAddress - // c. stock check: null (unlimited) OR > 0 → decrement - // d. recompute balance, ensure >= item.price - // e. insert shopOrder with snapshots (price/type/name/total) - // f. insert transactionLedger { amount: -price, reason:'PURCHASE', - // refType:'SHOP', refId: order.id } - // }) - // 4. return { success: true, orderId } or fail(...) with message + const userId = locals.user.id; + + const purchaseData = await validateFormData(purchaseSchema, request); + + let orderId: string; + try { + orderId = await db.transaction(async (tx) => { + const { typedPathwayId } = await assertShopAccess(userId, params.pathway, tx); + + const [item] = await tx + .select() + .from(shopItem) + .where(and( + eq(shopItem.id, purchaseData.itemId), + eq(shopItem.pathway, typedPathwayId), + eq(shopItem.isActive, true) + )) + .limit(1); + + if (!item) throw new ShopError(400, { message: 'Item not found' }); + + if (item.itemType === 'PHYSICAL' && !purchaseData.shippingAddress) { + throw new ShopError(400, { message: 'Shipping address required for physical items' }); + } + + const [{ balance }] = await tx + .select({ + balance: sql`COALESCE(SUM(${transactionLedger.amount}), 0)`.mapWith(Number) + }) + .from(transactionLedger) + .where(and( + eq(transactionLedger.userId, userId), + eq(transactionLedger.pathway, typedPathwayId) + )); + + if (balance < item.price) { + throw new ShopError(400, { message: 'Not enough currency' }); + } + + if (item.stock !== null) { + if (item.stock <= 0) throw new ShopError(400, { message: 'No stock remaining' }); + await tx.update(shopItem) + .set({ stock: item.stock - 1 }) + .where(eq(shopItem.id, item.id)); + } + + const [order] = await tx + .insert(shopOrder) + .values({ + userId, + pathway: typedPathwayId, + totalAmount: item.price, + item: item.id, + itemPriceSnapshot: item.price, + itemTypeSnapshot: item.itemType, + itemNameSnapshot: item.name, + shippingAddress: purchaseData.shippingAddress ?? null, + userNotes: purchaseData.userNotes ?? null + }) + .returning({ id: shopOrder.id }); + + await tx.insert(transactionLedger).values({ + userId, + pathway: typedPathwayId, + amount: -item.price, + reason: 'PURCHASE', + refType: 'SHOP', + refId: order.id // ← ties the ledger entry to the order + }); + + return order.id; + }); + } catch (e) { + if (e instanceof ShopError) return fail(e.status, e.body); + throw e; + } + + return { success: true, orderId }; }, cancel: async ({ request, params, locals }) => { From 0c3f7d68f13e6edc5f7cf71d8888e71c34ae391c Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 22:09:46 -0400 Subject: [PATCH 09/52] feat: cancel function --- .../pathway/[pathway]/shop/+page.server.ts | 70 +++++++++++++++---- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts index 4def765..4fc7d40 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -7,7 +7,7 @@ import { shopOrder, transactionLedger } from '$lib/server/db/schema'; -import { and, eq, sql, desc } from 'drizzle-orm'; +import { and, eq, sql, desc, or } from 'drizzle-orm'; import { error, fail, redirect } from '@sveltejs/kit'; import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; import { z } from 'zod'; @@ -28,7 +28,8 @@ const purchaseSchema = z.object({ }); const cancelSchema = z.object({ - orderId: z.string().min(1) // needs to be a valid string + orderId: z.string().min(1), // needs to be a valid string + cancelReason: z.string().min(1) }); type DbOrTx = typeof db | Parameters[0]>[0]; @@ -184,15 +185,60 @@ export const actions: Actions = { cancel: async ({ request, params, locals }) => { if (!locals.user) throw redirect(302, '/api/auth/login'); - const { typedPathwayId } = await assertShopAccess(locals.user.id, params.pathway); - - // 2. parse FormData → cancelSchema - // 3. db.transaction: - // a. load order; assert userId === user.id and status === 'PENDING' - // b. set status = 'CANCELED', cancelledReason - // c. restore item.stock if not null - // d. insert transactionLedger { amount: +price, reason:'REFUND', - // refType:'SHOP', refId: order.id } - // 4. return { success: true } or fail(...) + const userId = locals.user.id; + + const cancelData = await validateFormData(cancelSchema, request); + + try { + await db.transaction(async (tx) => { + const { typedPathwayId } = await assertShopAccess(userId, params.pathway, tx); + + const [order] = await tx + .select() + .from(shopOrder) + .where(and( + eq(shopOrder.id, cancelData.orderId), + eq(shopOrder.userId, userId), + eq(shopOrder.pathway, typedPathwayId), + or(eq(shopOrder.status, 'PENDING'), eq(shopOrder.status, 'PROCESSING')) + )) + .limit(1); + + if (!order) throw new ShopError(404, { message: 'No such order' }); + + await tx.update(shopOrder) + .set({ status: 'CANCELED', cancelledReason: cancelData.cancelReason }) + .where(eq(shopOrder.id, order.id)); + + // restore stock if the item still exists and tracks stock + if (order.item) { + const [item] = await tx + .select() + .from(shopItem) + .where(eq(shopItem.id, order.item)) + .limit(1); + if (item && item.stock !== null) { + await tx.update(shopItem) + .set({ stock: item.stock + 1 }) + .where(eq(shopItem.id, item.id)); + } + } + + // refund: positive ledger entry equal to what was originally charged + await tx.insert(transactionLedger).values({ + userId, + pathway: typedPathwayId, + amount: order.itemPriceSnapshot, + reason: 'REFUND', + refType: 'SHOP', + refId: order.id + }); + }); + } catch (e) { + if (e instanceof ShopError) return fail(e.status, e.body); + throw e; + } + + return { success: true }; } }; From 183e4d762a48596a81c1c4de4864d148b025174d Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 22:10:44 -0400 Subject: [PATCH 10/52] fix: use totalAmount instead of itemPriceSnapshot --- .../src/routes/app/pathway/[pathway]/shop/+page.server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts index 4fc7d40..239d339 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -228,7 +228,7 @@ export const actions: Actions = { await tx.insert(transactionLedger).values({ userId, pathway: typedPathwayId, - amount: order.itemPriceSnapshot, + amount: order.totalAmount, // amount that they paid reason: 'REFUND', refType: 'SHOP', refId: order.id From 4dfe99ede4cf32a649b541213e16466000d684ec Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 22:11:40 -0400 Subject: [PATCH 11/52] fix: only allow cancellation while pending --- .../src/routes/app/pathway/[pathway]/shop/+page.server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts index 239d339..d68f3a7 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -200,7 +200,7 @@ export const actions: Actions = { eq(shopOrder.id, cancelData.orderId), eq(shopOrder.userId, userId), eq(shopOrder.pathway, typedPathwayId), - or(eq(shopOrder.status, 'PENDING'), eq(shopOrder.status, 'PROCESSING')) + eq(shopOrder.status, 'PENDING') )) .limit(1); From 8fb5ca7f72efa16d83fc8be87aa8ef93d782e8ca Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 16:15:24 -0400 Subject: [PATCH 12/52] feat: remove fufillers --- resolution-frontend/src/lib/server/db/schema.ts | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/resolution-frontend/src/lib/server/db/schema.ts b/resolution-frontend/src/lib/server/db/schema.ts index d1c2855..5e05d7b 100644 --- a/resolution-frontend/src/lib/server/db/schema.ts +++ b/resolution-frontend/src/lib/server/db/schema.ts @@ -203,16 +203,6 @@ export const shopOrder = pgTable('shop_orders', { updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow(), }) -export const fufillerPathway = pgTable('fufiller_pathway', { - id: text('id').primaryKey().$defaultFn(() => createId()), - userId: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }), - pathway: pathwayEnum('pathway').notNull(), // theres a chance we won't need to assign this, for now this is assigned - assignedAt: timestamp('assigned_at', { mode: 'date' }).notNull().defaultNow(), - assignedBy: text('assigned_by').notNull().references(() => user.id) -}, (table) => [ - uniqueIndex('fufiller_pathway_unique_idx').on(table.userId, table.pathway) -]); - // Relations export const userRelations = relations(user, ({ many }) => ({ pathways: many(userPathway), @@ -225,8 +215,7 @@ export const userRelations = relations(user, ({ many }) => ({ referralLinks: many(referralLink), reviewerAssignments: many(reviewerPathway), currencyTransactions: many(transactionLedger), - shopOrders: many(shopOrder), - fufillerAssignments: many(fufillerPathway) + shopOrders: many(shopOrder) })); export const sessionRelations = relations(session, ({ one }) => ({ @@ -394,8 +383,4 @@ export const shopOrderRelations = relations(shopOrder, ({ one }) => ({ fufiller: one(user, { fields: [shopOrder.fufilledBy], references: [user.id] }) })); -export const fufillerPathwayRelations = relations(fufillerPathway, ({ one }) => ({ - user: one(user, { fields: [fufillerPathway.userId], references: [user.id] }), - assignedByUser: one(user, { fields: [fufillerPathway.assignedBy], references: [user.id] }) -})); From 64c51afc6f3758f330904026d410346f4de6c5a1 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:42:11 -0400 Subject: [PATCH 13/52] feat: schema changes (accompanying the last thing) --- .../drizzle/0006_worried_hedge_knight.sql | 1 + .../drizzle/meta/0006_snapshot.json | 2143 +++++++++++++++++ .../drizzle/meta/_journal.json | 7 + 3 files changed, 2151 insertions(+) create mode 100644 resolution-frontend/drizzle/0006_worried_hedge_knight.sql create mode 100644 resolution-frontend/drizzle/meta/0006_snapshot.json diff --git a/resolution-frontend/drizzle/0006_worried_hedge_knight.sql b/resolution-frontend/drizzle/0006_worried_hedge_knight.sql new file mode 100644 index 0000000..08f6c9f --- /dev/null +++ b/resolution-frontend/drizzle/0006_worried_hedge_knight.sql @@ -0,0 +1 @@ +DROP TABLE "fufiller_pathway" CASCADE; \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/0006_snapshot.json b/resolution-frontend/drizzle/meta/0006_snapshot.json new file mode 100644 index 0000000..772a4b7 --- /dev/null +++ b/resolution-frontend/drizzle/meta/0006_snapshot.json @@ -0,0 +1,2143 @@ +{ + "id": "a0247220-e3e8-4974-96a1-81cf9283801b", + "prevId": "2fbbf398-fe52-49d5-bcef-27f714185921", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ambassador_pathway": { + "name": "ambassador_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "ambassador_pathway_unique_idx": { + "name": "ambassador_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ambassador_pathway_user_id_user_id_fk": { + "name": "ambassador_pathway_user_id_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_pathway_assigned_by_user_id_fk": { + "name": "ambassador_pathway_assigned_by_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout": { + "name": "ambassador_payout", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "payout_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_ambassador_id_user_id_fk": { + "name": "ambassador_payout_ambassador_id_user_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_season_id_program_season_id_fk": { + "name": "ambassador_payout_season_id_program_season_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout_item": { + "name": "ambassador_payout_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "payout_id": { + "name": "payout_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_count": { + "name": "completion_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "rate_cents_per_completion": { + "name": "rate_cents_per_completion", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_item_payout_id_ambassador_payout_id_fk": { + "name": "ambassador_payout_item_payout_id_ambassador_payout_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "ambassador_payout", + "columnsFrom": [ + "payout_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_item_workshop_id_workshop_id_fk": { + "name": "ambassador_payout_item_workshop_id_workshop_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_shop": { + "name": "pathway_shop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_name": { + "name": "currency_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wish'" + }, + "currency_name_plural": { + "name": "currency_name_plural", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wishes'" + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "pathway_shop_last_edited_by_user_id_fk": { + "name": "pathway_shop_last_edited_by_user_id_fk", + "tableFrom": "pathway_shop", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "pathway_shop_pathway_unique": { + "name": "pathway_shop_pathway_unique", + "nullsNotDistinct": false, + "columns": [ + "pathway" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_week_content": { + "name": "pathway_week_content", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "prize_image_url": { + "name": "prize_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_submissions_open": { + "name": "is_submissions_open", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pathway_week_content_unique_idx": { + "name": "pathway_week_content_unique_idx", + "columns": [ + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pathway_week_content_last_edited_by_user_id_fk": { + "name": "pathway_week_content_last_edited_by_user_id_fk", + "tableFrom": "pathway_week_content", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_enrollment": { + "name": "program_enrollment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "enrollment_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "enrollment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "starting_week": { + "name": "starting_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "enrollment_user_season_role_idx": { + "name": "enrollment_user_season_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_enrollment_user_id_user_id_fk": { + "name": "program_enrollment_user_id_user_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "program_enrollment_season_id_program_season_id_fk": { + "name": "program_enrollment_season_id_program_season_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_season": { + "name": "program_season", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signup_opens_at": { + "name": "signup_opens_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "signup_closes_at": { + "name": "signup_closes_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "total_weeks": { + "name": "total_weeks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 8 + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "program_season_slug_unique": { + "name": "program_season_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_link": { + "name": "referral_link", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "referral_link_ambassador_id_user_id_fk": { + "name": "referral_link_ambassador_id_user_id_fk", + "tableFrom": "referral_link", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "referral_link_code_unique": { + "name": "referral_link_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_signup": { + "name": "referral_signup", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "referral_link_id": { + "name": "referral_link_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "referral_signup_unique_idx": { + "name": "referral_signup_unique_idx", + "columns": [ + { + "expression": "referral_link_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "referral_signup_referral_link_id_referral_link_id_fk": { + "name": "referral_signup_referral_link_id_referral_link_id_fk", + "tableFrom": "referral_signup", + "tableTo": "referral_link", + "columnsFrom": [ + "referral_link_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_signup_user_id_user_id_fk": { + "name": "referral_signup_user_id_user_id_fk", + "tableFrom": "referral_signup", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reviewer_pathway": { + "name": "reviewer_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "reviewer_pathway_unique_idx": { + "name": "reviewer_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reviewer_pathway_user_id_user_id_fk": { + "name": "reviewer_pathway_user_id_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reviewer_pathway_assigned_by_user_id_fk": { + "name": "reviewer_pathway_assigned_by_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_item": { + "name": "shop_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_url": { + "name": "item_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price": { + "name": "item_price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_stock": { + "name": "item_stock", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "item_type": { + "name": "item_type", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_item_pathway_pathway_shop_pathway_fk": { + "name": "shop_item_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_item", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_item_last_edited_by_user_id_fk": { + "name": "shop_item_last_edited_by_user_id_fk", + "tableFrom": "shop_item", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_orders": { + "name": "shop_orders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "order_stauts": { + "name": "order_stauts", + "type": "shop_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "shop_item_id": { + "name": "shop_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price_snapshot": { + "name": "item_price_snapshot", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_type_enum": { + "name": "item_type_enum", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "item_name_snapshot": { + "name": "item_name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "shipping_address": { + "name": "shipping_address", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_notes": { + "name": "user_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufiller_notes": { + "name": "fufiller_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_by": { + "name": "fufilled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_at": { + "name": "fufilled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_reason": { + "name": "cancelled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_orders_user_id_user_id_fk": { + "name": "shop_orders_user_id_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_pathway_pathway_shop_pathway_fk": { + "name": "shop_orders_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_orders", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_orders_shop_item_id_shop_item_id_fk": { + "name": "shop_orders_shop_item_id_shop_item_id_fk", + "tableFrom": "shop_orders", + "tableTo": "shop_item", + "columnsFrom": [ + "shop_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_fufilled_by_user_id_fk": { + "name": "shop_orders_fufilled_by_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "fufilled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.currency_transactions": { + "name": "currency_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tx_user_id": { + "name": "tx_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_pathway": { + "name": "tx_pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_amount": { + "name": "tx_amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tx_reason": { + "name": "tx_reason", + "type": "currency_txn_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_note": { + "name": "tx_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_granted_by": { + "name": "tx_granted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_type": { + "name": "tx_ref_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_id": { + "name": "tx_ref_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "currency_transactions_tx_user_id_user_id_fk": { + "name": "currency_transactions_tx_user_id_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "currency_transactions_tx_pathway_pathway_shop_pathway_fk": { + "name": "currency_transactions_tx_pathway_pathway_shop_pathway_fk", + "tableFrom": "currency_transactions", + "tableTo": "pathway_shop", + "columnsFrom": [ + "tx_pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "currency_transactions_tx_granted_by_user_id_fk": { + "name": "currency_transactions_tx_granted_by_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hack_club_id": { + "name": "hack_club_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_id": { + "name": "slack_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verification_status": { + "name": "verification_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ysws_eligible": { + "name": "ysws_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_hack_club_id_unique": { + "name": "user_hack_club_id_unique", + "nullsNotDistinct": false, + "columns": [ + "hack_club_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_pathway": { + "name": "user_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_pathway_unique_idx": { + "name": "user_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_pathway_user_id_user_id_fk": { + "name": "user_pathway_user_id_user_id_fk", + "tableFrom": "user_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.weekly_ship": { + "name": "weekly_ship", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "goal_text": { + "name": "goal_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "ship_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PLANNED'" + }, + "proof_url": { + "name": "proof_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shipped_at": { + "name": "shipped_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ship_user_season_week_idx": { + "name": "ship_user_season_week_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "weekly_ship_user_id_user_id_fk": { + "name": "weekly_ship_user_id_user_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_season_id_program_season_id_fk": { + "name": "weekly_ship_season_id_program_season_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_workshop_id_workshop_id_fk": { + "name": "weekly_ship_workshop_id_workshop_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop": { + "name": "workshop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "difficulty": { + "name": "difficulty", + "type": "difficulty", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "estimated_hours": { + "name": "estimated_hours", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_author_id_user_id_fk": { + "name": "workshop_author_id_user_id_fk", + "tableFrom": "workshop", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_season_id_program_season_id_fk": { + "name": "workshop_season_id_program_season_id_fk", + "tableFrom": "workshop", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_analytics": { + "name": "workshop_analytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "starts": { + "name": "starts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completions": { + "name": "completions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "avg_completion_mins": { + "name": "avg_completion_mins", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_analytics_workshop_id_workshop_id_fk": { + "name": "workshop_analytics_workshop_id_workshop_id_fk", + "tableFrom": "workshop_analytics", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workshop_analytics_workshop_id_unique": { + "name": "workshop_analytics_workshop_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workshop_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_completion": { + "name": "workshop_completion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_url": { + "name": "project_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_workshop_participant_season_idx": { + "name": "completion_workshop_participant_season_idx", + "columns": [ + { + "expression": "workshop_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workshop_completion_workshop_id_workshop_id_fk": { + "name": "workshop_completion_workshop_id_workshop_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_participant_id_user_id_fk": { + "name": "workshop_completion_participant_id_user_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "user", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_season_id_program_season_id_fk": { + "name": "workshop_completion_season_id_program_season_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.currency_txn_reason": { + "name": "currency_txn_reason", + "schema": "public", + "values": [ + "GRANT", + "PURCHASE", + "REFUND", + "ADJUSTMENT", + "OTHER" + ] + }, + "public.difficulty": { + "name": "difficulty", + "schema": "public", + "values": [ + "BEGINNER", + "INTERMEDIATE", + "ADVANCED" + ] + }, + "public.enrollment_role": { + "name": "enrollment_role", + "schema": "public", + "values": [ + "PARTICIPANT", + "AMBASSADOR" + ] + }, + "public.enrollment_status": { + "name": "enrollment_status", + "schema": "public", + "values": [ + "ACTIVE", + "DROPPED", + "COMPLETED" + ] + }, + "public.pathway": { + "name": "pathway", + "schema": "public", + "values": [ + "PYTHON", + "RUST", + "GAME_DEV", + "HARDWARE", + "DESIGN", + "GENERAL_CODING" + ] + }, + "public.payout_status": { + "name": "payout_status", + "schema": "public", + "values": [ + "DRAFT", + "PENDING", + "PAID", + "CANCELED" + ] + }, + "public.ship_status": { + "name": "ship_status", + "schema": "public", + "values": [ + "PLANNED", + "IN_PROGRESS", + "SHIPPED", + "MISSED" + ] + }, + "public.shop_item_type": { + "name": "shop_item_type", + "schema": "public", + "values": [ + "PHYSICAL", + "DIGITAL" + ] + }, + "public.shop_order_status": { + "name": "shop_order_status", + "schema": "public", + "values": [ + "PENDING", + "PROCESSING", + "FULFILLED", + "CANCELED" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/_journal.json b/resolution-frontend/drizzle/meta/_journal.json index d4688f0..e808d7a 100644 --- a/resolution-frontend/drizzle/meta/_journal.json +++ b/resolution-frontend/drizzle/meta/_journal.json @@ -43,6 +43,13 @@ "when": 1778007365517, "tag": "0005_nosy_bloodstrike", "breakpoints": true + }, + { + "idx": 6, + "version": "7", + "when": 1778619728709, + "tag": "0006_worried_hedge_knight", + "breakpoints": true } ] } \ No newline at end of file From da6d6c38cbd5ea62990ea61edfe3446c1828f6a4 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:42:29 -0400 Subject: [PATCH 14/52] dev: shop seeding for dev --- resolution-frontend/src/hooks.server.ts | 4 ++ resolution-frontend/src/lib/server/devSeed.ts | 51 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 resolution-frontend/src/lib/server/devSeed.ts diff --git a/resolution-frontend/src/hooks.server.ts b/resolution-frontend/src/hooks.server.ts index 375a540..2a963a0 100644 --- a/resolution-frontend/src/hooks.server.ts +++ b/resolution-frontend/src/hooks.server.ts @@ -1,10 +1,14 @@ import { lucia } from '$lib/server/auth'; import type { Handle } from '@sveltejs/kit'; import { ensureSeasonFromEnv } from '$lib/server/season'; +import { seedDevShops } from '$lib/server/devSeed'; // Sync season from env on startup ensureSeasonFromEnv().catch(console.error); +// Seed pathway shops + a starter item per pathway when running in dev +seedDevShops().catch(console.error); + export const handle: Handle = async ({ event, resolve }) => { const sessionId = event.cookies.get(lucia.sessionCookieName); diff --git a/resolution-frontend/src/lib/server/devSeed.ts b/resolution-frontend/src/lib/server/devSeed.ts new file mode 100644 index 0000000..e0139ff --- /dev/null +++ b/resolution-frontend/src/lib/server/devSeed.ts @@ -0,0 +1,51 @@ +import { dev } from '$app/environment'; +import { db } from './db'; +import { pathwayShop, shopItem } from './db/schema'; +import { and, eq } from 'drizzle-orm'; +import { PATHWAYS } from '$lib/pathways'; + +/** + * Dev-only seed: ensure every pathway has an enabled `pathwayShop` and at + * least one `shopItem`. No-op in production. + */ +export async function seedDevShops() { + if (!dev) return; + + for (const { id, label } of PATHWAYS) { + const existingShop = await db.query.pathwayShop.findFirst({ + where: eq(pathwayShop.pathway, id) + }); + + if (!existingShop) { + await db.insert(pathwayShop).values({ + pathway: id, + isEnabled: true, + currencyName: 'wish', + currencyNamePlural: 'wishes' + }); + } else if (!existingShop.isEnabled) { + await db + .update(pathwayShop) + .set({ isEnabled: true }) + .where(eq(pathwayShop.pathway, id)); + } + + const existingItem = await db.query.shopItem.findFirst({ + where: and(eq(shopItem.pathway, id), eq(shopItem.isActive, true)) + }); + + if (!existingItem) { + await db.insert(shopItem).values({ + pathway: id, + name: `${label} starter sticker`, + description: `A test ${label} sticker seeded automatically in dev. Remove me before going live.`, + price: 5, + stock: 25, + itemType: 'PHYSICAL', + isActive: true + }); + } + } + + console.log('[dev seed] pathway shops + items ensured'); +} From 5db45460487ac4752a1d240411b9c91fc5283d92 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:42:53 -0400 Subject: [PATCH 15/52] feat: templating stuff --- .../app/ambassador/shop/+page.server.ts | 0 .../routes/app/ambassador/shop/+page.svelte | 0 .../ambassador/shop/fufill/+page.server.ts | 0 .../app/ambassador/shop/fufill/+page.svelte | 0 .../[pathway]/shop/[id]/+page.server.ts | 190 ++++++++ .../pathway/[pathway]/shop/[id]/+page.svelte | 447 ++++++++++++++++++ 6 files changed, 637 insertions(+) create mode 100644 resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts create mode 100644 resolution-frontend/src/routes/app/ambassador/shop/+page.svelte create mode 100644 resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts create mode 100644 resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte create mode 100644 resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts create mode 100644 resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte diff --git a/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts new file mode 100644 index 0000000..e69de29 diff --git a/resolution-frontend/src/routes/app/ambassador/shop/+page.svelte b/resolution-frontend/src/routes/app/ambassador/shop/+page.svelte new file mode 100644 index 0000000..e69de29 diff --git a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts new file mode 100644 index 0000000..e69de29 diff --git a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte b/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte new file mode 100644 index 0000000..e69de29 diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts new file mode 100644 index 0000000..b2d921f --- /dev/null +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts @@ -0,0 +1,190 @@ +import type { PageServerLoad, Actions } from './$types'; +import { db } from '$lib/server/db'; +import { + userPathway, + pathwayShop, + shopItem, + shopOrder, + transactionLedger +} from '$lib/server/db/schema'; +import { and, eq, sql } from 'drizzle-orm'; +import { error, fail, redirect } from '@sveltejs/kit'; +import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; +import { z } from 'zod'; +import { addressSchema, validateFormData } from '$lib/server/validation'; + +// thrown inside transactions to abort + roll back; caught outside to convert to fail() +class ShopError extends Error { + constructor(public status: number, public body: { message: string }) { + super(body.message); + } +} + +// always collect a shipping address regardless of item type +const buySchema = z.object({ + userNotes: z.string().max(500).optional().default(''), + ...addressSchema.shape +}); + +type DbOrTx = typeof db | Parameters[0]>[0]; + +async function assertShopAccess(userId: string, pathwayParam: string, conn: DbOrTx = db) { + const pathwayId = pathwayParam.toUpperCase(); + if (!PATHWAY_IDS.includes(pathwayId as PathwayId)) throw error(404, 'Pathway not found'); + const typedPathwayId = pathwayId as PathwayId; + + const membership = await conn + .select() + .from(userPathway) + .where(and(eq(userPathway.userId, userId), eq(userPathway.pathway, typedPathwayId))) + .limit(1); + if (membership.length === 0) throw redirect(302, '/app'); + + const pathwayShopRow = await conn + .select() + .from(pathwayShop) + .where(eq(pathwayShop.pathway, typedPathwayId)) + .limit(1); + if (pathwayShopRow.length === 0 || !pathwayShopRow[0].isEnabled) { + throw error(404); + } + + return { typedPathwayId, shop: pathwayShopRow[0] }; +} + +export const load: PageServerLoad = async ({ params, parent }) => { + const { user } = await parent(); + const { typedPathwayId, shop } = await assertShopAccess(user.id, params.pathway); + + const [item] = await db + .select() + .from(shopItem) + .where( + and( + eq(shopItem.id, params.id), + eq(shopItem.pathway, typedPathwayId), + eq(shopItem.isActive, true) + ) + ) + .limit(1); + + if (!item) throw error(404, 'Item not found'); + + const [{ balance }] = await db + .select({ + balance: sql`COALESCE(SUM(${transactionLedger.amount}), 0)`.mapWith(Number) + }) + .from(transactionLedger) + .where( + and(eq(transactionLedger.userId, user.id), eq(transactionLedger.pathway, typedPathwayId)) + ); + + return { + pathwayId: typedPathwayId, + shop: { + currencyName: shop.currencyName, + currencyNamePlural: shop.currencyNamePlural + }, + item, + balance, + user: { + firstName: user.firstName ?? '', + lastName: user.lastName ?? '', + email: user.email ?? '' + } + }; +}; + +export const actions = { + buy: async ({ request, params, locals }) => { + if (!locals.user) throw redirect(302, '/api/auth/login'); + const userId = locals.user.id; + + const buyData = await validateFormData(buySchema, request); + + const shippingAddress = { + addressLine1: buyData.addressLine1, + addressLine2: buyData.addressLine2, + city: buyData.city, + stateProvince: buyData.stateProvince, + country: buyData.country, + zipPostalCode: buyData.zipPostalCode + }; + + let orderId: string; + try { + orderId = await db.transaction(async (tx) => { + const { typedPathwayId } = await assertShopAccess(userId, params.pathway, tx); + + const [item] = await tx + .select() + .from(shopItem) + .where( + and( + eq(shopItem.id, params.id), + eq(shopItem.pathway, typedPathwayId), + eq(shopItem.isActive, true) + ) + ) + .limit(1); + + if (!item) throw new ShopError(404, { message: 'Item not found' }); + + const [{ balance }] = await tx + .select({ + balance: sql`COALESCE(SUM(${transactionLedger.amount}), 0)`.mapWith(Number) + }) + .from(transactionLedger) + .where( + and( + eq(transactionLedger.userId, userId), + eq(transactionLedger.pathway, typedPathwayId) + ) + ); + + if (balance < item.price) { + throw new ShopError(400, { message: 'Not enough currency' }); + } + + if (item.stock !== null) { + if (item.stock <= 0) throw new ShopError(400, { message: 'No stock remaining' }); + await tx + .update(shopItem) + .set({ stock: item.stock - 1 }) + .where(eq(shopItem.id, item.id)); + } + + const [order] = await tx + .insert(shopOrder) + .values({ + userId, + pathway: typedPathwayId, + totalAmount: item.price, + item: item.id, + itemPriceSnapshot: item.price, + itemTypeSnapshot: item.itemType, + itemNameSnapshot: item.name, + shippingAddress, + userNotes: buyData.userNotes || null + }) + .returning({ id: shopOrder.id }); + + await tx.insert(transactionLedger).values({ + userId, + pathway: typedPathwayId, + amount: -item.price, + reason: 'PURCHASE', + refType: 'SHOP', + refId: order.id + }); + + return order.id; + }); + } catch (e) { + if (e instanceof ShopError) return fail(e.status, e.body); + throw e; + } + + return { success: true, orderId }; + } +} satisfies Actions; diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte new file mode 100644 index 0000000..108becb --- /dev/null +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte @@ -0,0 +1,447 @@ + + + + {data.item.name} - Shop - Resolution + + + +
+ + Back + Back to shop + + + {#if form?.success} +
+

Order placed!

+

+ Your order for {data.item.name} has been received. + We'll get it to you as soon as we can. +

+ + Back to shop + +
+ {:else} +
{ + isSubmitting = true; + return async ({ update }) => { + await update(); + isSubmitting = false; + }; + }}> +
+
+ + {data.item.itemType === 'PHYSICAL' ? 'Physical item' : 'Digital item'} + +

Complete your order

+

{data.item.description}

+
+ + {#if form && !form.success && 'message' in form} +
{form.message}
+ {/if} + + {#if !canAfford} +
+ You don't have enough {data.shop.currencyNamePlural}. You need + {data.item.price - data.balance} more. +
+ {/if} + + {#if !inStock} +
This item is out of stock.
+ {/if} + +
+

Your details

+
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
+

Shipping address

+

Where should we send this?

+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+

Notes (optional)

+

Anything the fulfiller should know? (size, color, etc.)

+
+ +
+
+
+ +
+
+ {#if data.item.itemImageUrl} + {data.item.name} + {:else} +
No image
+ {/if} +
+ +
+
{data.item.name}
+
+ {data.item.price} {currencyLabel} +
+
+ You have {data.balance} {data.shop.currencyNamePlural} +
+
+ + +
+
+ {/if} +
+
+ + From ebbf321e64d423c17d31396e3ff09fe414aaeb34 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:43:09 -0400 Subject: [PATCH 16/52] feat: shop frontend (sorta) --- .../routes/app/pathway/[pathway]/+page.svelte | 67 +++ .../pathway/[pathway]/shop/+page.server.ts | 163 +++--- .../app/pathway/[pathway]/shop/+page.svelte | 500 +++++++++++++++++- 3 files changed, 648 insertions(+), 82 deletions(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/+page.svelte index 6c794fa..52e11cd 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/+page.svelte @@ -90,6 +90,25 @@

brought to you by {data.curator}

+ + +
+ Visit the shop + Spend what you've earned on swag and goodies +
+ +
+
{#each weeks as week} {@const published = isWeekPublished(week)} @@ -172,6 +191,54 @@ font-weight: 600; } + .shop-link { + display: flex; + align-items: center; + gap: 1rem; + width: 100%; + max-width: 800px; + padding: 1rem 1.25rem; + margin-bottom: 2rem; + background: rgba(255, 255, 255, 0.85); + border: 2px solid #af98ff; + border-radius: 16px; + text-decoration: none; + color: #1a1a2e; + font-family: 'Kodchasan', sans-serif; + transition: transform 0.15s, border-color 0.15s; + } + + .shop-link:hover { + transform: translateY(-2px); + border-color: #33d6a6; + } + + .shop-icon { + width: 32px; + height: 32px; + } + + .shop-text { + display: flex; + flex-direction: column; + flex: 1; + } + + .shop-title { + font-weight: 600; + font-size: 1rem; + } + + .shop-sub { + font-size: 0.85rem; + color: #8492a6; + } + + .shop-chevron { + width: 20px; + height: 20px; + } + .weeks-grid { display: grid; grid-template-columns: repeat(4, 1fr); diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts index d68f3a7..9283392 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -101,87 +101,88 @@ export const load: PageServerLoad = async ({ params, parent }) => { }; export const actions: Actions = { - purchase: async ({ request, params, locals }) => { - if (!locals.user) throw redirect(302, '/api/auth/login'); - const userId = locals.user.id; - - const purchaseData = await validateFormData(purchaseSchema, request); - - let orderId: string; - try { - orderId = await db.transaction(async (tx) => { - const { typedPathwayId } = await assertShopAccess(userId, params.pathway, tx); - - const [item] = await tx - .select() - .from(shopItem) - .where(and( - eq(shopItem.id, purchaseData.itemId), - eq(shopItem.pathway, typedPathwayId), - eq(shopItem.isActive, true) - )) - .limit(1); - - if (!item) throw new ShopError(400, { message: 'Item not found' }); - - if (item.itemType === 'PHYSICAL' && !purchaseData.shippingAddress) { - throw new ShopError(400, { message: 'Shipping address required for physical items' }); - } - - const [{ balance }] = await tx - .select({ - balance: sql`COALESCE(SUM(${transactionLedger.amount}), 0)`.mapWith(Number) - }) - .from(transactionLedger) - .where(and( - eq(transactionLedger.userId, userId), - eq(transactionLedger.pathway, typedPathwayId) - )); - - if (balance < item.price) { - throw new ShopError(400, { message: 'Not enough currency' }); - } - - if (item.stock !== null) { - if (item.stock <= 0) throw new ShopError(400, { message: 'No stock remaining' }); - await tx.update(shopItem) - .set({ stock: item.stock - 1 }) - .where(eq(shopItem.id, item.id)); - } - - const [order] = await tx - .insert(shopOrder) - .values({ - userId, - pathway: typedPathwayId, - totalAmount: item.price, - item: item.id, - itemPriceSnapshot: item.price, - itemTypeSnapshot: item.itemType, - itemNameSnapshot: item.name, - shippingAddress: purchaseData.shippingAddress ?? null, - userNotes: purchaseData.userNotes ?? null - }) - .returning({ id: shopOrder.id }); - - await tx.insert(transactionLedger).values({ - userId, - pathway: typedPathwayId, - amount: -item.price, - reason: 'PURCHASE', - refType: 'SHOP', - refId: order.id // ← ties the ledger entry to the order - }); - - return order.id; - }); - } catch (e) { - if (e instanceof ShopError) return fail(e.status, e.body); - throw e; - } - - return { success: true, orderId }; - }, + // commented out as this should probably be in ./[id] + // purchase: async ({ request, params, locals }) => { + // if (!locals.user) throw redirect(302, '/api/auth/login'); + // const userId = locals.user.id; + + // const purchaseData = await validateFormData(purchaseSchema, request); + + // let orderId: string; + // try { + // orderId = await db.transaction(async (tx) => { + // const { typedPathwayId } = await assertShopAccess(userId, params.pathway, tx); + + // const [item] = await tx + // .select() + // .from(shopItem) + // .where(and( + // eq(shopItem.id, purchaseData.itemId), + // eq(shopItem.pathway, typedPathwayId), + // eq(shopItem.isActive, true) + // )) + // .limit(1); + + // if (!item) throw new ShopError(400, { message: 'Item not found' }); + + // if (item.itemType === 'PHYSICAL' && !purchaseData.shippingAddress) { + // throw new ShopError(400, { message: 'Shipping address required for physical items' }); + // } + + // const [{ balance }] = await tx + // .select({ + // balance: sql`COALESCE(SUM(${transactionLedger.amount}), 0)`.mapWith(Number) + // }) + // .from(transactionLedger) + // .where(and( + // eq(transactionLedger.userId, userId), + // eq(transactionLedger.pathway, typedPathwayId) + // )); + + // if (balance < item.price) { + // throw new ShopError(400, { message: 'Not enough currency' }); + // } + + // if (item.stock !== null) { + // if (item.stock <= 0) throw new ShopError(400, { message: 'No stock remaining' }); + // await tx.update(shopItem) + // .set({ stock: item.stock - 1 }) + // .where(eq(shopItem.id, item.id)); + // } + + // const [order] = await tx + // .insert(shopOrder) + // .values({ + // userId, + // pathway: typedPathwayId, + // totalAmount: item.price, + // item: item.id, + // itemPriceSnapshot: item.price, + // itemTypeSnapshot: item.itemType, + // itemNameSnapshot: item.name, + // shippingAddress: purchaseData.shippingAddress ?? null, + // userNotes: purchaseData.userNotes ?? null + // }) + // .returning({ id: shopOrder.id }); + + // await tx.insert(transactionLedger).values({ + // userId, + // pathway: typedPathwayId, + // amount: -item.price, + // reason: 'PURCHASE', + // refType: 'SHOP', + // refId: order.id // ← ties the ledger entry to the order + // }); + + // return order.id; + // }); + // } catch (e) { + // if (e instanceof ShopError) return fail(e.status, e.body); + // throw e; + // } + + // return { success: true, orderId }; + // }, cancel: async ({ request, params, locals }) => { if (!locals.user) throw redirect(302, '/api/auth/login'); diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte index 2e9131b..9da66ee 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte @@ -1,2 +1,500 @@ - + + + + {pathway?.label ?? 'Pathway'} Shop - Resolution + + + +
+ + Back + Back to {pathway?.label ?? 'pathway'} + +
+ + +
+

Items

+ {#if data.items.length === 0} +
+

No items in the shop yet. Check back soon!

+
+ {:else} + + {#if showAll} + + {:else} + + {/if} + {/if} +
+ +
+

Recent orders

+ {#if data.orders.length === 0} +
+

You haven't placed any orders yet.

+
+ {:else} +
    + {#each data.orders as order (order.id)} +
  • +
    +
    {order.itemNameSnapshot}
    +
    + {new Date(order.createdAt).toLocaleDateString()} · + {priceLabel(order.totalAmount)} +
    +
    + + {statusLabel(order.status)} + +
  • + {/each} +
+ {/if} +
+
+
+
+ + From 1698d76d4cf5cc655666f17782b2b2746e74ebcc Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:48:12 -0400 Subject: [PATCH 17/52] dev: todos --- .../src/routes/app/ambassador/shop/+page.server.ts | 1 + resolution-frontend/src/routes/app/ambassador/shop/+page.svelte | 1 + .../src/routes/app/ambassador/shop/fufill/+page.server.ts | 1 + .../src/routes/app/ambassador/shop/fufill/+page.svelte | 1 + 4 files changed, 4 insertions(+) diff --git a/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts index e69de29..afd6fbc 100644 --- a/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts @@ -0,0 +1 @@ +//TODO: ACTUALLY DO THIS \ No newline at end of file diff --git a/resolution-frontend/src/routes/app/ambassador/shop/+page.svelte b/resolution-frontend/src/routes/app/ambassador/shop/+page.svelte index e69de29..8245c29 100644 --- a/resolution-frontend/src/routes/app/ambassador/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/ambassador/shop/+page.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts index e69de29..fa9d42b 100644 --- a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts +++ b/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts @@ -0,0 +1 @@ +// TODO: actually do this file \ No newline at end of file diff --git a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte b/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte index e69de29..8245c29 100644 --- a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte +++ b/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte @@ -0,0 +1 @@ + \ No newline at end of file From 58ef148e0cce449468d0fb60f3def8a7f0910afb Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:48:18 -0400 Subject: [PATCH 18/52] fix: minor naming change --- .../src/routes/app/pathway/[pathway]/shop/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte index 9da66ee..2b34963 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte @@ -48,7 +48,7 @@

Shop

- Spend your {data.shop.currencyNamePlural} on swag and other goodies. + Spend your {data.shop.currencyNamePlural} on goodies!

From bff2897b5a9559a8ba2b29b645c81a00a3398351 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:51:06 -0400 Subject: [PATCH 19/52] feat(accessibility): make it a button! (thanks amp) --- .../app/pathway/[pathway]/shop/+page.svelte | 47 ++++++++----------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte index 2b34963..cc9815c 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte @@ -28,6 +28,10 @@ return status; } } + + function switchShowState(e: MouseEvent) { + showAll = !showAll + } @@ -108,22 +112,10 @@ {/if} {/each}
- {#if showAll} - - {:else} - - {/if} + {/if} @@ -175,19 +167,13 @@ padding: 1.5rem; } - .show-more-div { - display: flex; - flex-direction: row; - justify-content: flex-end; - gap: 0.25rem; - align-items: center; - } - .show-more { all: unset; - display: block; + display: flex; + align-items: center; + gap: 0.25rem; width: fit-content; - /* margin-left: auto; */ + margin-left: auto; font-style: italic; cursor: pointer; } @@ -196,13 +182,18 @@ text-decoration: underline; } - .show-more:focus { + .show-more:focus-visible { outline: revert; } - .show-more-image { /* cursed name but we ball */ + .show-more-image { height: 1rem; width: 1rem; + transition: transform 0.15s ease; + } + + .show-more.open .show-more-image { + transform: rotate(90deg); } .back-link { From 379789e496e54de70db8d72e44787d3510ac9204 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:52:07 -0400 Subject: [PATCH 20/52] feat: only show show-more when more than 3 items --- .../src/routes/app/pathway/[pathway]/shop/+page.svelte | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte index cc9815c..faa8023 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte @@ -112,10 +112,12 @@ {/if} {/each} - + {#if data.items.length > 3} + + {/if} {/if} From 4d6ab5c92c8c13801951c4f596d945410cc21760 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:55:58 -0400 Subject: [PATCH 21/52] fix: ignore error because data is basically a one time load --- .../src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte index 108becb..bb446f3 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte @@ -14,8 +14,11 @@ let isSubmitting = $state(false); // prefill from week-submission flow style: name + email come from session user + // svelte-ignore state_referenced_locally let firstName = $state(data.user.firstName); + // svelte-ignore state_referenced_locally let lastName = $state(data.user.lastName); + // svelte-ignore state_referenced_locally let email = $state(data.user.email); // shipping address (only used for physical items) From 0ecb99f95b6de992ba2eb4920b33b222d13d10b4 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:58:09 -0400 Subject: [PATCH 22/52] fix: missing line-clamp --- .../src/routes/app/pathway/[pathway]/shop/+page.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte index faa8023..87499cd 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte @@ -379,6 +379,7 @@ font-size: 0.85rem; margin: 0; display: -webkit-box; + line-clamp: 2; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; From a88d851dbc3908275c08a9f5591da0546f12f1bf Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 12:43:06 -0400 Subject: [PATCH 23/52] feat: pathwayShop, shopItem, and transactionLedger schemas --- .../src/lib/server/db/schema.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/resolution-frontend/src/lib/server/db/schema.ts b/resolution-frontend/src/lib/server/db/schema.ts index 499ff96..413cee4 100644 --- a/resolution-frontend/src/lib/server/db/schema.ts +++ b/resolution-frontend/src/lib/server/db/schema.ts @@ -11,6 +11,9 @@ export const shipStatusEnum = pgEnum('ship_status', ['PLANNED', 'IN_PROGRESS', ' export const payoutStatusEnum = pgEnum('payout_status', ['DRAFT', 'PENDING', 'PAID', 'CANCELED']); export const warehouseOrderStatusEnum = pgEnum('warehouse_order_status', ['DRAFT', 'ESTIMATED', 'APPROVED', 'SHIPPED', 'CANCELLED']); export const warehouseBatchStatusEnum = pgEnum('warehouse_batch_status', ['AWAITING_MAPPING', 'MAPPED', 'PROCESSED']); +export const shopOrderStatusEnum = pgEnum('shop_order_status', ['PENDING', 'PROCESSING', 'FULFILLED', 'CANCELED']); // order tracking for frontend (users) +export const shopItemTypeEnum = pgEnum('shop_item_type', ['PHYSICAL', 'DIGITAL']); // for filtering +export const currencyTxnReasonEnum = pgEnum('currency_txn_reason', ['GRANT', 'PURCHASE', 'REFUND', 'ADJUSTMENT', 'OTHER']); // logging why transaction occured // Tables export const user = pgTable('user', { @@ -141,6 +144,45 @@ export const userPathway = pgTable('user_pathway', { uniqueIndex('user_pathway_unique_idx').on(table.userId, table.pathway) ]); +export const pathwayShop = pgTable('pathway_shop', { + id: text('id').primaryKey().$defaultFn(() => createId()), + pathway: pathwayEnum('pathway').notNull().unique(), + isEnabled: boolean('is_enabled').notNull().default(false), // default to not displaying unless ambassador flips switch + currencyName: text('currency_name').notNull().default('wish'), // bcs. idk. resolution = a wish or something idk + currencyNamePlural: text('currency_name_plural').notNull().default('wishes'), + lastEditedBy: text('last_edited_by').references(() => user.id), + createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow() +}); + +export const shopItem = pgTable('shop_item', { + id: text('id').primaryKey().$defaultFn(() => createId()), + pathway: pathwayEnum('pathway').notNull().references(() => pathwayShop.pathway, { onDelete: 'cascade' }), + name: text('name').notNull(), + description: text('description').notNull(), + itemImageUrl: text('item_url'), + price: integer('item_price').notNull(), + stock: integer('item_stock'), + itemType: shopItemTypeEnum('item_type').notNull(), + isActive: boolean('is_active').notNull().default(false), + lastEditedBy: text('last_edited_by').references(() => user.id), + createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow() +}); + +export const transactionLedger = pgTable('currency_transactions', { + id: text('id').primaryKey().$defaultFn(() => createId()), + userId: text('tx_user_id').references(() => user.id, { onDelete: 'set null' }), + pathway: pathwayEnum('tx_pathway').notNull().references(() => pathwayShop.pathway, { onDelete: 'cascade' }), + amount: integer('tx_amount').notNull(), + reason: currencyTxnReasonEnum('tx_reason').notNull(), + note: text('tx_note'), + grantedBy: text('tx_granted_by').references(() => user.id { onDelete: 'set null' }), + refType: text('tx_ref_type'), // SHOP, SHIP, etc. + refId: text('tx_ref_id'), // ID, such as for shop order + createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow() +}); + // Relations export const userRelations = relations(user, ({ many }) => ({ pathways: many(userPathway), From 23fe62b36ba4fae57925f431cbef386f5603135b Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 12:59:07 -0400 Subject: [PATCH 24/52] feat: add shopOrder table and address validation schema --- .../src/lib/server/db/schema.ts | 26 ++++++++++++++++++- .../src/lib/server/validation/schemas.ts | 18 ++++++++----- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/resolution-frontend/src/lib/server/db/schema.ts b/resolution-frontend/src/lib/server/db/schema.ts index 413cee4..2cf7612 100644 --- a/resolution-frontend/src/lib/server/db/schema.ts +++ b/resolution-frontend/src/lib/server/db/schema.ts @@ -1,6 +1,7 @@ -import { pgTable, text, timestamp, boolean, integer, real, pgEnum, uniqueIndex, index } from 'drizzle-orm/pg-core'; +import { pgTable, text, timestamp, boolean, integer, real, pgEnum, uniqueIndex, index, jsonb } from 'drizzle-orm/pg-core'; import { relations } from 'drizzle-orm'; import { createId } from '@paralleldrive/cuid2'; +import type { AddressInput } from '../validation'; // Enums export const enrollmentRoleEnum = pgEnum('enrollment_role', ['PARTICIPANT', 'AMBASSADOR']); @@ -183,6 +184,29 @@ export const transactionLedger = pgTable('currency_transactions', { createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow() }); +export const shopOrder = pgTable('shop_orders', { + id: text('id').primaryKey().$defaultFn(() => createId()), + userId: text('user_id').references(() => user.id, { onDelete: 'set null' }), + pathway: pathwayEnum('pathway').notNull().references(() => pathwayShop.pathway, { onDelete: 'cascade' }), + status: shopOrderStatusEnum('order_stauts').notNull().default("PENDING"), + totalAmount: integer('amount').notNull(), + item: text('shop_item_id').references(() => shopItem.id, { onDelete: 'set null' }), + shippingAddress: jsonb('shipping_address').$type(), + userNotes: text('user_notes'), + fufillerNotes: text('fufiller_notes'), + // claimedBy: + fufilledBy: text('fufilled_by'), + fufilledAt: timestamp('fufilled_at', { mode: 'date' }).defaultNow(), + cancelledReason: text('cancelled_reason'), + createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow(), +}) + +//currencyTransaction — id, userId, pathway, amount (signed integer), reason (enum), note (nullable), grantedBy (nullable userId), refType (nullable), refId (nullable), createdAt. Index on (userId, pathway) for fast balance lookup. +//- shopOrder — id, userId, pathway, status (enum, default PENDING), totalAmount, containsPhysical, shippingInfo (text nullable), userNotes (nullable), fulfillerNotes (nullable), claimedBy (nullable), fulfilledBy (nullable), fulfilledAt (nullable), canceledReason (nullable), createdAt, updatedAt. Index on (pathway, status) for queue queries. +//- shopOrderItem — id, orderId, itemId, quantity, unitPriceSnapshot, nameSnapshot, itemTypeSnapshot +//- fulfillerPathway — mirror [reviewerPathway](file:///Users/niko/coding-projects/resolution/resolution-frontend/src/lib/server/db/schema.ts#L224-L232) shape exactly: id, userId, pathway, assignedAt, assignedBy, with unique (userId, pathway) index + // Relations export const userRelations = relations(user, ({ many }) => ({ pathways: many(userPathway), diff --git a/resolution-frontend/src/lib/server/validation/schemas.ts b/resolution-frontend/src/lib/server/validation/schemas.ts index bb90e53..f5fec2b 100644 --- a/resolution-frontend/src/lib/server/validation/schemas.ts +++ b/resolution-frontend/src/lib/server/validation/schemas.ts @@ -64,6 +64,17 @@ const safeUrl = z.string().url('Please enter a valid URL').max(2000).refine( { message: 'URL must use http or https' } ); +export const addressSchema = z.object({ + addressLine1: z.string().min(1, 'Address is required').max(200), + addressLine2: z.string().max(200).optional().default(''), + city: z.string().min(1, 'City is required').max(100), + stateProvince: z.string().min(1, 'State / Province is required').max(100), + country: z.string().min(1, 'Country is required').max(100), + zipPostalCode: z.string().min(1, 'ZIP / Postal code is required').max(20) +}); + +export type AddressInput = z.infer; + export const projectSubmissionSchema = z.object({ codeUrl: safeUrl.refine( (val) => /^https:\/\/github\.com\/.+\/.+/.test(val), @@ -78,12 +89,7 @@ export const projectSubmissionSchema = z.object({ email: z.string().email('Please enter a valid email').max(254).transform((v) => v.trim().toLowerCase()), description: z.string().min(1, 'Description is required').max(2000), githubUsername: z.string().min(1, 'GitHub username is required').max(100), - addressLine1: z.string().min(1, 'Address is required').max(200), - addressLine2: z.string().max(200).optional().default(''), - city: z.string().min(1, 'City is required').max(100), - stateProvince: z.string().min(1, 'State / Province is required').max(100), - country: z.string().min(1, 'Country is required').max(100), - zipPostalCode: z.string().min(1, 'ZIP / Postal code is required').max(20), + ...addressSchema.shape, // needed for shop order validation birthday: z.string().min(1, 'Birthday is required').regex(/^\d{4}-\d{2}-\d{2}$/, 'Please enter a valid date'), hackatimeProject: z.string().min(1, 'Hackatime project is required').max(200), hoursSpent: z From 367127e56bb17cd6b0838efea09dc5a60a7f1bea Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 14:56:16 -0400 Subject: [PATCH 25/52] feat: relations + one more table --- .../src/lib/server/db/schema.ts | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/resolution-frontend/src/lib/server/db/schema.ts b/resolution-frontend/src/lib/server/db/schema.ts index 2cf7612..d8829f0 100644 --- a/resolution-frontend/src/lib/server/db/schema.ts +++ b/resolution-frontend/src/lib/server/db/schema.ts @@ -178,7 +178,7 @@ export const transactionLedger = pgTable('currency_transactions', { amount: integer('tx_amount').notNull(), reason: currencyTxnReasonEnum('tx_reason').notNull(), note: text('tx_note'), - grantedBy: text('tx_granted_by').references(() => user.id { onDelete: 'set null' }), + grantedBy: text('tx_granted_by').references(() => user.id, { onDelete: 'set null' }), refType: text('tx_ref_type'), // SHOP, SHIP, etc. refId: text('tx_ref_id'), // ID, such as for shop order createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow() @@ -191,21 +191,29 @@ export const shopOrder = pgTable('shop_orders', { status: shopOrderStatusEnum('order_stauts').notNull().default("PENDING"), totalAmount: integer('amount').notNull(), item: text('shop_item_id').references(() => shopItem.id, { onDelete: 'set null' }), + itemPriceSnapshot: integer('item_price_snapshot').notNull(), + itemTypeSnapshot: shopItemTypeEnum('item_type_enum'), + itemNameSnapshot: text('item_name_snapshot').notNull(), shippingAddress: jsonb('shipping_address').$type(), userNotes: text('user_notes'), fufillerNotes: text('fufiller_notes'), // claimedBy: - fufilledBy: text('fufilled_by'), - fufilledAt: timestamp('fufilled_at', { mode: 'date' }).defaultNow(), + fufilledBy: text('fufilled_by').references(() => user.id, { onDelete: 'set null' }), + fufilledAt: timestamp('fufilled_at', { mode: 'date' }), cancelledReason: text('cancelled_reason'), createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow(), }) -//currencyTransaction — id, userId, pathway, amount (signed integer), reason (enum), note (nullable), grantedBy (nullable userId), refType (nullable), refId (nullable), createdAt. Index on (userId, pathway) for fast balance lookup. -//- shopOrder — id, userId, pathway, status (enum, default PENDING), totalAmount, containsPhysical, shippingInfo (text nullable), userNotes (nullable), fulfillerNotes (nullable), claimedBy (nullable), fulfilledBy (nullable), fulfilledAt (nullable), canceledReason (nullable), createdAt, updatedAt. Index on (pathway, status) for queue queries. -//- shopOrderItem — id, orderId, itemId, quantity, unitPriceSnapshot, nameSnapshot, itemTypeSnapshot -//- fulfillerPathway — mirror [reviewerPathway](file:///Users/niko/coding-projects/resolution/resolution-frontend/src/lib/server/db/schema.ts#L224-L232) shape exactly: id, userId, pathway, assignedAt, assignedBy, with unique (userId, pathway) index +export const fufillerPathway = pgTable('fufiller_pathway', { + id: text('id').primaryKey().$defaultFn(() => createId()), + userId: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }), + pathway: pathwayEnum('pathway').notNull(), // theres a chance we won't need to assign this, for now this is assigned + assignedAt: timestamp('assigned_at', { mode: 'date' }).notNull().defaultNow(), + assignedBy: text('assigned_by').notNull().references(() => user.id) +}, (table) => [ + uniqueIndex('fufiller_pathway_unique_idx').on(table.userId, table.pathway) +]); // Relations export const userRelations = relations(user, ({ many }) => ({ @@ -217,8 +225,15 @@ export const userRelations = relations(user, ({ many }) => ({ weeklyShips: many(weeklyShip), payouts: many(ambassadorPayout), referralLinks: many(referralLink), +<<<<<<< HEAD warehouseOrders: many(warehouseOrder), reviewerAssignments: many(reviewerPathway) +======= + reviewerAssignments: many(reviewerPathway), + currencyTransactions: many(transactionLedger), + shopOrders: many(shopOrder), + fufillerAssignments: many(fufillerPathway) +>>>>>>> a909366 (feat: relations + one more table) })); export const sessionRelations = relations(session, ({ one }) => ({ @@ -542,5 +557,34 @@ export const warehouseBatchRelations = relations(warehouseBatch, ({ one, many }) export const warehouseBatchTagRelations = relations(warehouseBatchTag, ({ one }) => ({ batch: one(warehouseBatch, { fields: [warehouseBatchTag.batchId], references: [warehouseBatch.id] }) +export const pathwayShopRelations = relations(pathwayShop, ({ one, many }) => ({ + editor: one(user, { fields: [pathwayShop.lastEditedBy], references: [user.id] }), + items: many(shopItem), + transactions: many(transactionLedger), + orders: many(shopOrder) +})); + +export const shopItemRelations = relations(shopItem, ({ one, many }) => ({ + shop: one(pathwayShop, { fields: [shopItem.pathway], references: [pathwayShop.pathway] }), + editor: one(user, { fields: [shopItem.lastEditedBy], references: [user.id] }), + orders: many(shopOrder) +})); + +export const transactionLedgerRelations = relations(transactionLedger, ({ one }) => ({ + user: one(user, { fields: [transactionLedger.userId], references: [user.id] }), + grantedByUser: one(user, { fields: [transactionLedger.grantedBy], references: [user.id] }), + shop: one(pathwayShop, { fields: [transactionLedger.pathway], references: [pathwayShop.pathway] }) +})); + +export const shopOrderRelations = relations(shopOrder, ({ one }) => ({ + user: one(user, { fields: [shopOrder.userId], references: [user.id] }), + shop: one(pathwayShop, { fields: [shopOrder.pathway], references: [pathwayShop.pathway] }), + item: one(shopItem, { fields: [shopOrder.item], references: [shopItem.id] }), + fufiller: one(user, { fields: [shopOrder.fufilledBy], references: [user.id] }) +})); + +export const fufillerPathwayRelations = relations(fufillerPathway, ({ one }) => ({ + user: one(user, { fields: [fufillerPathway.userId], references: [user.id] }), + assignedByUser: one(user, { fields: [fufillerPathway.assignedBy], references: [user.id] }) })); From 496aeaa5cb4b54e151325413494144685a6814bd Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 14:58:00 -0400 Subject: [PATCH 26/52] misc: migrations for a73691e, 613992d, a909366 --- .../drizzle/0011_nosy_bloodstrike.sql | 84 + .../drizzle/meta/0011_snapshot.json | 2237 +++++++++++++++++ .../drizzle/meta/_journal.json | 7 + 3 files changed, 2328 insertions(+) create mode 100644 resolution-frontend/drizzle/0011_nosy_bloodstrike.sql create mode 100644 resolution-frontend/drizzle/meta/0011_snapshot.json diff --git a/resolution-frontend/drizzle/0011_nosy_bloodstrike.sql b/resolution-frontend/drizzle/0011_nosy_bloodstrike.sql new file mode 100644 index 0000000..df50141 --- /dev/null +++ b/resolution-frontend/drizzle/0011_nosy_bloodstrike.sql @@ -0,0 +1,84 @@ +CREATE TYPE "public"."currency_txn_reason" AS ENUM('GRANT', 'PURCHASE', 'REFUND', 'ADJUSTMENT', 'OTHER');--> statement-breakpoint +CREATE TYPE "public"."shop_item_type" AS ENUM('PHYSICAL', 'DIGITAL');--> statement-breakpoint +CREATE TYPE "public"."shop_order_status" AS ENUM('PENDING', 'PROCESSING', 'FULFILLED', 'CANCELED');--> statement-breakpoint +CREATE TABLE "fufiller_pathway" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "pathway" "pathway" NOT NULL, + "assigned_at" timestamp DEFAULT now() NOT NULL, + "assigned_by" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "pathway_shop" ( + "id" text PRIMARY KEY NOT NULL, + "pathway" "pathway" NOT NULL, + "is_enabled" boolean DEFAULT false NOT NULL, + "currency_name" text DEFAULT 'wish' NOT NULL, + "currency_name_plural" text DEFAULT 'wishes' NOT NULL, + "last_edited_by" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "pathway_shop_pathway_unique" UNIQUE("pathway") +); +--> statement-breakpoint +CREATE TABLE "shop_item" ( + "id" text PRIMARY KEY NOT NULL, + "pathway" "pathway" NOT NULL, + "name" text NOT NULL, + "description" text NOT NULL, + "item_url" text, + "item_price" integer NOT NULL, + "item_stock" integer, + "item_type" "shop_item_type" NOT NULL, + "is_active" boolean DEFAULT false NOT NULL, + "last_edited_by" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "shop_orders" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text, + "pathway" "pathway" NOT NULL, + "order_stauts" "shop_order_status" DEFAULT 'PENDING' NOT NULL, + "amount" integer NOT NULL, + "shop_item_id" text, + "item_price_snapshot" integer NOT NULL, + "item_type_enum" "shop_item_type", + "item_name_snapshot" text NOT NULL, + "shipping_address" jsonb, + "user_notes" text, + "fufiller_notes" text, + "fufilled_by" text, + "fufilled_at" timestamp, + "cancelled_reason" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "currency_transactions" ( + "id" text PRIMARY KEY NOT NULL, + "tx_user_id" text, + "tx_pathway" "pathway" NOT NULL, + "tx_amount" integer NOT NULL, + "tx_reason" "currency_txn_reason" NOT NULL, + "tx_note" text, + "tx_granted_by" text, + "tx_ref_type" text, + "tx_ref_id" text, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "fufiller_pathway" ADD CONSTRAINT "fufiller_pathway_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "fufiller_pathway" ADD CONSTRAINT "fufiller_pathway_assigned_by_user_id_fk" FOREIGN KEY ("assigned_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "pathway_shop" ADD CONSTRAINT "pathway_shop_last_edited_by_user_id_fk" FOREIGN KEY ("last_edited_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_item" ADD CONSTRAINT "shop_item_pathway_pathway_shop_pathway_fk" FOREIGN KEY ("pathway") REFERENCES "public"."pathway_shop"("pathway") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_item" ADD CONSTRAINT "shop_item_last_edited_by_user_id_fk" FOREIGN KEY ("last_edited_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_pathway_pathway_shop_pathway_fk" FOREIGN KEY ("pathway") REFERENCES "public"."pathway_shop"("pathway") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_shop_item_id_shop_item_id_fk" FOREIGN KEY ("shop_item_id") REFERENCES "public"."shop_item"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_fufilled_by_user_id_fk" FOREIGN KEY ("fufilled_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "currency_transactions" ADD CONSTRAINT "currency_transactions_tx_user_id_user_id_fk" FOREIGN KEY ("tx_user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "currency_transactions" ADD CONSTRAINT "currency_transactions_tx_pathway_pathway_shop_pathway_fk" FOREIGN KEY ("tx_pathway") REFERENCES "public"."pathway_shop"("pathway") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "currency_transactions" ADD CONSTRAINT "currency_transactions_tx_granted_by_user_id_fk" FOREIGN KEY ("tx_granted_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "fufiller_pathway_unique_idx" ON "fufiller_pathway" USING btree ("user_id","pathway"); \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/0011_snapshot.json b/resolution-frontend/drizzle/meta/0011_snapshot.json new file mode 100644 index 0000000..53da688 --- /dev/null +++ b/resolution-frontend/drizzle/meta/0011_snapshot.json @@ -0,0 +1,2237 @@ +{ + "id": "2fbbf398-fe52-49d5-bcef-27f714185921", + "prevId": "a3a209aa-49a0-47e6-9126-892277549771", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ambassador_pathway": { + "name": "ambassador_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "ambassador_pathway_unique_idx": { + "name": "ambassador_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ambassador_pathway_user_id_user_id_fk": { + "name": "ambassador_pathway_user_id_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_pathway_assigned_by_user_id_fk": { + "name": "ambassador_pathway_assigned_by_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout": { + "name": "ambassador_payout", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "payout_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_ambassador_id_user_id_fk": { + "name": "ambassador_payout_ambassador_id_user_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_season_id_program_season_id_fk": { + "name": "ambassador_payout_season_id_program_season_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout_item": { + "name": "ambassador_payout_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "payout_id": { + "name": "payout_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_count": { + "name": "completion_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "rate_cents_per_completion": { + "name": "rate_cents_per_completion", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_item_payout_id_ambassador_payout_id_fk": { + "name": "ambassador_payout_item_payout_id_ambassador_payout_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "ambassador_payout", + "columnsFrom": [ + "payout_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_item_workshop_id_workshop_id_fk": { + "name": "ambassador_payout_item_workshop_id_workshop_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.fufiller_pathway": { + "name": "fufiller_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "fufiller_pathway_unique_idx": { + "name": "fufiller_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fufiller_pathway_user_id_user_id_fk": { + "name": "fufiller_pathway_user_id_user_id_fk", + "tableFrom": "fufiller_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fufiller_pathway_assigned_by_user_id_fk": { + "name": "fufiller_pathway_assigned_by_user_id_fk", + "tableFrom": "fufiller_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_shop": { + "name": "pathway_shop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_name": { + "name": "currency_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wish'" + }, + "currency_name_plural": { + "name": "currency_name_plural", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wishes'" + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "pathway_shop_last_edited_by_user_id_fk": { + "name": "pathway_shop_last_edited_by_user_id_fk", + "tableFrom": "pathway_shop", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "pathway_shop_pathway_unique": { + "name": "pathway_shop_pathway_unique", + "nullsNotDistinct": false, + "columns": [ + "pathway" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_week_content": { + "name": "pathway_week_content", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "prize_image_url": { + "name": "prize_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_submissions_open": { + "name": "is_submissions_open", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pathway_week_content_unique_idx": { + "name": "pathway_week_content_unique_idx", + "columns": [ + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pathway_week_content_last_edited_by_user_id_fk": { + "name": "pathway_week_content_last_edited_by_user_id_fk", + "tableFrom": "pathway_week_content", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_enrollment": { + "name": "program_enrollment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "enrollment_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "enrollment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "starting_week": { + "name": "starting_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "enrollment_user_season_role_idx": { + "name": "enrollment_user_season_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_enrollment_user_id_user_id_fk": { + "name": "program_enrollment_user_id_user_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "program_enrollment_season_id_program_season_id_fk": { + "name": "program_enrollment_season_id_program_season_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_season": { + "name": "program_season", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signup_opens_at": { + "name": "signup_opens_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "signup_closes_at": { + "name": "signup_closes_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "total_weeks": { + "name": "total_weeks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 8 + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "program_season_slug_unique": { + "name": "program_season_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_link": { + "name": "referral_link", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "referral_link_ambassador_id_user_id_fk": { + "name": "referral_link_ambassador_id_user_id_fk", + "tableFrom": "referral_link", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "referral_link_code_unique": { + "name": "referral_link_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_signup": { + "name": "referral_signup", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "referral_link_id": { + "name": "referral_link_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "referral_signup_unique_idx": { + "name": "referral_signup_unique_idx", + "columns": [ + { + "expression": "referral_link_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "referral_signup_referral_link_id_referral_link_id_fk": { + "name": "referral_signup_referral_link_id_referral_link_id_fk", + "tableFrom": "referral_signup", + "tableTo": "referral_link", + "columnsFrom": [ + "referral_link_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_signup_user_id_user_id_fk": { + "name": "referral_signup_user_id_user_id_fk", + "tableFrom": "referral_signup", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reviewer_pathway": { + "name": "reviewer_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "reviewer_pathway_unique_idx": { + "name": "reviewer_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reviewer_pathway_user_id_user_id_fk": { + "name": "reviewer_pathway_user_id_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reviewer_pathway_assigned_by_user_id_fk": { + "name": "reviewer_pathway_assigned_by_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_item": { + "name": "shop_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_url": { + "name": "item_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price": { + "name": "item_price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_stock": { + "name": "item_stock", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "item_type": { + "name": "item_type", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_item_pathway_pathway_shop_pathway_fk": { + "name": "shop_item_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_item", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_item_last_edited_by_user_id_fk": { + "name": "shop_item_last_edited_by_user_id_fk", + "tableFrom": "shop_item", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_orders": { + "name": "shop_orders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "order_stauts": { + "name": "order_stauts", + "type": "shop_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "shop_item_id": { + "name": "shop_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price_snapshot": { + "name": "item_price_snapshot", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_type_enum": { + "name": "item_type_enum", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "item_name_snapshot": { + "name": "item_name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "shipping_address": { + "name": "shipping_address", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_notes": { + "name": "user_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufiller_notes": { + "name": "fufiller_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_by": { + "name": "fufilled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_at": { + "name": "fufilled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_reason": { + "name": "cancelled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_orders_user_id_user_id_fk": { + "name": "shop_orders_user_id_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_pathway_pathway_shop_pathway_fk": { + "name": "shop_orders_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_orders", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_orders_shop_item_id_shop_item_id_fk": { + "name": "shop_orders_shop_item_id_shop_item_id_fk", + "tableFrom": "shop_orders", + "tableTo": "shop_item", + "columnsFrom": [ + "shop_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_fufilled_by_user_id_fk": { + "name": "shop_orders_fufilled_by_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "fufilled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.currency_transactions": { + "name": "currency_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tx_user_id": { + "name": "tx_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_pathway": { + "name": "tx_pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_amount": { + "name": "tx_amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tx_reason": { + "name": "tx_reason", + "type": "currency_txn_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_note": { + "name": "tx_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_granted_by": { + "name": "tx_granted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_type": { + "name": "tx_ref_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_id": { + "name": "tx_ref_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "currency_transactions_tx_user_id_user_id_fk": { + "name": "currency_transactions_tx_user_id_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "currency_transactions_tx_pathway_pathway_shop_pathway_fk": { + "name": "currency_transactions_tx_pathway_pathway_shop_pathway_fk", + "tableFrom": "currency_transactions", + "tableTo": "pathway_shop", + "columnsFrom": [ + "tx_pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "currency_transactions_tx_granted_by_user_id_fk": { + "name": "currency_transactions_tx_granted_by_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hack_club_id": { + "name": "hack_club_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_id": { + "name": "slack_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verification_status": { + "name": "verification_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ysws_eligible": { + "name": "ysws_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_hack_club_id_unique": { + "name": "user_hack_club_id_unique", + "nullsNotDistinct": false, + "columns": [ + "hack_club_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_pathway": { + "name": "user_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_pathway_unique_idx": { + "name": "user_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_pathway_user_id_user_id_fk": { + "name": "user_pathway_user_id_user_id_fk", + "tableFrom": "user_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.weekly_ship": { + "name": "weekly_ship", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "goal_text": { + "name": "goal_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "ship_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PLANNED'" + }, + "proof_url": { + "name": "proof_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shipped_at": { + "name": "shipped_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ship_user_season_week_idx": { + "name": "ship_user_season_week_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "weekly_ship_user_id_user_id_fk": { + "name": "weekly_ship_user_id_user_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_season_id_program_season_id_fk": { + "name": "weekly_ship_season_id_program_season_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_workshop_id_workshop_id_fk": { + "name": "weekly_ship_workshop_id_workshop_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop": { + "name": "workshop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "difficulty": { + "name": "difficulty", + "type": "difficulty", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "estimated_hours": { + "name": "estimated_hours", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_author_id_user_id_fk": { + "name": "workshop_author_id_user_id_fk", + "tableFrom": "workshop", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_season_id_program_season_id_fk": { + "name": "workshop_season_id_program_season_id_fk", + "tableFrom": "workshop", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_analytics": { + "name": "workshop_analytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "starts": { + "name": "starts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completions": { + "name": "completions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "avg_completion_mins": { + "name": "avg_completion_mins", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_analytics_workshop_id_workshop_id_fk": { + "name": "workshop_analytics_workshop_id_workshop_id_fk", + "tableFrom": "workshop_analytics", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workshop_analytics_workshop_id_unique": { + "name": "workshop_analytics_workshop_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workshop_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_completion": { + "name": "workshop_completion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_url": { + "name": "project_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_workshop_participant_season_idx": { + "name": "completion_workshop_participant_season_idx", + "columns": [ + { + "expression": "workshop_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workshop_completion_workshop_id_workshop_id_fk": { + "name": "workshop_completion_workshop_id_workshop_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_participant_id_user_id_fk": { + "name": "workshop_completion_participant_id_user_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "user", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_season_id_program_season_id_fk": { + "name": "workshop_completion_season_id_program_season_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.currency_txn_reason": { + "name": "currency_txn_reason", + "schema": "public", + "values": [ + "GRANT", + "PURCHASE", + "REFUND", + "ADJUSTMENT", + "OTHER" + ] + }, + "public.difficulty": { + "name": "difficulty", + "schema": "public", + "values": [ + "BEGINNER", + "INTERMEDIATE", + "ADVANCED" + ] + }, + "public.enrollment_role": { + "name": "enrollment_role", + "schema": "public", + "values": [ + "PARTICIPANT", + "AMBASSADOR" + ] + }, + "public.enrollment_status": { + "name": "enrollment_status", + "schema": "public", + "values": [ + "ACTIVE", + "DROPPED", + "COMPLETED" + ] + }, + "public.pathway": { + "name": "pathway", + "schema": "public", + "values": [ + "PYTHON", + "RUST", + "GAME_DEV", + "HARDWARE", + "DESIGN", + "GENERAL_CODING" + ] + }, + "public.payout_status": { + "name": "payout_status", + "schema": "public", + "values": [ + "DRAFT", + "PENDING", + "PAID", + "CANCELED" + ] + }, + "public.ship_status": { + "name": "ship_status", + "schema": "public", + "values": [ + "PLANNED", + "IN_PROGRESS", + "SHIPPED", + "MISSED" + ] + }, + "public.shop_item_type": { + "name": "shop_item_type", + "schema": "public", + "values": [ + "PHYSICAL", + "DIGITAL" + ] + }, + "public.shop_order_status": { + "name": "shop_order_status", + "schema": "public", + "values": [ + "PENDING", + "PROCESSING", + "FULFILLED", + "CANCELED" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/_journal.json b/resolution-frontend/drizzle/meta/_journal.json index 66c0660..90daae1 100644 --- a/resolution-frontend/drizzle/meta/_journal.json +++ b/resolution-frontend/drizzle/meta/_journal.json @@ -78,6 +78,13 @@ "when": 1777000000000, "tag": "0010_warehouse_indexes_and_tags", "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1778007365517, + "tag": "0011_nosy_bloodstrike", + "breakpoints": true } ] } From 0525821b74278b7a4fe7cd2d177ba7418c26fbed Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 15:42:22 -0400 Subject: [PATCH 27/52] chore: scaffold pathway shop page route --- .../src/routes/app/pathway/[pathway]/shop/+page.svelte | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte new file mode 100644 index 0000000..2e9131b --- /dev/null +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte @@ -0,0 +1,2 @@ + + From 9203c92eab4cc6e9ef8d3d4c76aa5ea8842ebcf4 Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 15:43:04 -0400 Subject: [PATCH 28/52] feat: add load function + template --- .../pathway/[pathway]/shop/+page.server.ts | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts new file mode 100644 index 0000000..12cc9f9 --- /dev/null +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -0,0 +1,116 @@ +import type { PageServerLoad, Actions } from './$types'; +import { db } from '$lib/server/db'; +import { + userPathway, + pathwayShop, + shopItem, + shopOrder, + transactionLedger +} from '$lib/server/db/schema'; +import { and, eq, sql, desc, gte } from 'drizzle-orm'; +import { error, fail, redirect } from '@sveltejs/kit'; +import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; +import { z } from 'zod'; +import { addressSchema, type AddressInput } from '$lib/server/validation'; +import { ambassadorPathway } from '$lib/server/db/schema'; + + +const purchaseSchema = z.object({ + itemId: z.string().min(1), + userNotes: z.string().max(500).optional(), + shippingAddress: addressSchema.optional() // optional as user might not actually be buying a physical item, in which case i don't think we need it +}); + +const cancelSchema = z.object({ + orderId: z.string().min(1) // needs to be a valid string +}); + +export const load: PageServerLoad = async ({ params, parent }) => { + const { user } = await parent(); + const pathwayId = params.pathway.toUpperCase(); + if (!PATHWAY_IDS.includes(pathwayId as PathwayId)) throw error(404, 'Pathway not found'); + const typedPathwayId = pathwayId as PathwayId; + + const membership = await db + .select() + .from(userPathway) + .where(and(eq(userPathway.userId, user.id), eq(userPathway.pathway, typedPathwayId))) + .limit(1); + + if (membership.length === 0) throw redirect(302, '/app'); + + const pathwayShopRow = await db + .select() + .from(pathwayShop) + .where(eq(pathwayShop.pathway, typedPathwayId)) + .limit(1); + + if (pathwayShopRow.length === 0 || !pathwayShopRow[0].isEnabled) { + throw error(404); + } + const shop = pathwayShopRow[0]; + + const pathwayItems = await db + .select() + .from(shopItem) + .where(and(eq(shopItem.pathway, typedPathwayId), eq(shopItem.isActive, true))); // return all items minus inactive + + const [{ balance }] = await db + .select({ + balance: sql`COALESCE(SUM(${transactionLedger.amount}), 0)`.mapWith(Number) + }) + .from(transactionLedger) + .where(and( + eq(transactionLedger.userId, user.id), + eq(transactionLedger.pathway, typedPathwayId) + )); + + const recentOrders = await db + .select() + .from(shopOrder) + .where(and(eq(shopOrder.userId, user.id), eq(shopOrder.pathway, typedPathwayId))) + .orderBy(desc(shopOrder.createdAt)) + .limit(5); // link to a seperate orders page, so we only need a preview + + return { + pathwayId: typedPathwayId, + shop: { + isEnabled: shop.isEnabled, + currencyName: shop.currencyName, + currencyNamePlural: shop.currencyNamePlural + }, + items: pathwayItems, + balance, // this is the user bal btw just so we're clear + orders: recentOrders + }; +}; + +// ── Actions ─────────────────────────────────────────────────────────── +export const actions: Actions = { + purchase: async ({ request, params, locals }) => { + // 1. auth + pathway validation (same as load) + // 2. parse FormData → purchaseSchema.safeParse → fail(400) on error + // 3. db.transaction(async (tx) => { + // a. re-fetch shop (isEnabled), item (isActive, pathway match) + // b. if item.itemType === 'PHYSICAL' require shippingAddress + // c. stock check: null (unlimited) OR > 0 → decrement + // d. recompute balance, ensure >= item.price + // e. insert shopOrder with snapshots (price/type/name/total) + // f. insert transactionLedger { amount: -price, reason:'PURCHASE', + // refType:'SHOP', refId: order.id } + // }) + // 4. return { success: true, orderId } or fail(...) with message + }, + + cancel: async ({ request, params, locals }) => { + // 1. auth + pathway validation + // 2. parse FormData → cancelSchema + // 3. db.transaction: + // a. load order; assert userId === user.id and status === 'PENDING' + // b. set status = 'CANCELED', cancelledReason + // c. restore item.stock if not null + // d. insert transactionLedger { amount: +price, reason:'REFUND', + // refType:'SHOP', refId: order.id } + // 4. return { success: true } or fail(...) + } +}; From c8ee59a45d1997c876a7fc87581826770e033d63 Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 15:56:46 -0400 Subject: [PATCH 29/52] feat: move checking stuff to dedicated function --- .../pathway/[pathway]/shop/+page.server.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts index 12cc9f9..58bc297 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -25,18 +25,18 @@ const cancelSchema = z.object({ orderId: z.string().min(1) // needs to be a valid string }); -export const load: PageServerLoad = async ({ params, parent }) => { - const { user } = await parent(); - const pathwayId = params.pathway.toUpperCase(); +// guard for load + actions +// returns pathway ID and the shop item +async function assertShopAccess(userId: string, pathwayParam: string) { + const pathwayId = pathwayParam.toUpperCase(); if (!PATHWAY_IDS.includes(pathwayId as PathwayId)) throw error(404, 'Pathway not found'); const typedPathwayId = pathwayId as PathwayId; - + const membership = await db .select() .from(userPathway) - .where(and(eq(userPathway.userId, user.id), eq(userPathway.pathway, typedPathwayId))) + .where(and(eq(userPathway.userId, userId), eq(userPathway.pathway, typedPathwayId))) .limit(1); - if (membership.length === 0) throw redirect(302, '/app'); const pathwayShopRow = await db @@ -44,11 +44,16 @@ export const load: PageServerLoad = async ({ params, parent }) => { .from(pathwayShop) .where(eq(pathwayShop.pathway, typedPathwayId)) .limit(1); - if (pathwayShopRow.length === 0 || !pathwayShopRow[0].isEnabled) { throw error(404); } - const shop = pathwayShopRow[0]; + + return { typedPathwayId, shop: pathwayShopRow[0] }; +} + +export const load: PageServerLoad = async ({ params, parent }) => { + const { user } = await parent(); + const { typedPathwayId, shop } = await assertShopAccess(user.id, params.pathway); const pathwayItems = await db .select() @@ -85,10 +90,11 @@ export const load: PageServerLoad = async ({ params, parent }) => { }; }; -// ── Actions ─────────────────────────────────────────────────────────── export const actions: Actions = { purchase: async ({ request, params, locals }) => { - // 1. auth + pathway validation (same as load) + if (!locals.user) throw redirect(302, '/api/auth/login'); + const { typedPathwayId, shop } = await assertShopAccess(locals.user.id, params.pathway); + // 2. parse FormData → purchaseSchema.safeParse → fail(400) on error // 3. db.transaction(async (tx) => { // a. re-fetch shop (isEnabled), item (isActive, pathway match) @@ -103,7 +109,9 @@ export const actions: Actions = { }, cancel: async ({ request, params, locals }) => { - // 1. auth + pathway validation + if (!locals.user) throw redirect(302, '/api/auth/login'); + const { typedPathwayId } = await assertShopAccess(locals.user.id, params.pathway); + // 2. parse FormData → cancelSchema // 3. db.transaction: // a. load order; assert userId === user.id and status === 'PENDING' From 2e7611ae8bf057e3b8a201a8860f993505e8bec6 Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 21:59:03 -0400 Subject: [PATCH 30/52] feat: purchase function Co-authored-by: Copilot --- .../pathway/[pathway]/shop/+page.server.ts | 114 +++++++++++++++--- 1 file changed, 94 insertions(+), 20 deletions(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts index 58bc297..4def765 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -7,12 +7,18 @@ import { shopOrder, transactionLedger } from '$lib/server/db/schema'; -import { and, eq, sql, desc, gte } from 'drizzle-orm'; +import { and, eq, sql, desc } from 'drizzle-orm'; import { error, fail, redirect } from '@sveltejs/kit'; import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; import { z } from 'zod'; -import { addressSchema, type AddressInput } from '$lib/server/validation'; -import { ambassadorPathway } from '$lib/server/db/schema'; +import { addressSchema, validateFormData } from '$lib/server/validation'; + +// thrown inside transactions to abort + roll back; caught outside to convert to fail() +class ShopError extends Error { + constructor(public status: number, public body: { message: string }) { + super(body.message); + } +} const purchaseSchema = z.object({ @@ -25,21 +31,24 @@ const cancelSchema = z.object({ orderId: z.string().min(1) // needs to be a valid string }); +type DbOrTx = typeof db | Parameters[0]>[0]; + // guard for load + actions -// returns pathway ID and the shop item -async function assertShopAccess(userId: string, pathwayParam: string) { +// returns pathway ID and the shop item +// pass `tx` when calling inside a transaction so the re-check uses the same snapshot +async function assertShopAccess(userId: string, pathwayParam: string, conn: DbOrTx = db) { const pathwayId = pathwayParam.toUpperCase(); if (!PATHWAY_IDS.includes(pathwayId as PathwayId)) throw error(404, 'Pathway not found'); const typedPathwayId = pathwayId as PathwayId; - const membership = await db + const membership = await conn .select() .from(userPathway) .where(and(eq(userPathway.userId, userId), eq(userPathway.pathway, typedPathwayId))) .limit(1); if (membership.length === 0) throw redirect(302, '/app'); - const pathwayShopRow = await db + const pathwayShopRow = await conn .select() .from(pathwayShop) .where(eq(pathwayShop.pathway, typedPathwayId)) @@ -93,19 +102,84 @@ export const load: PageServerLoad = async ({ params, parent }) => { export const actions: Actions = { purchase: async ({ request, params, locals }) => { if (!locals.user) throw redirect(302, '/api/auth/login'); - const { typedPathwayId, shop } = await assertShopAccess(locals.user.id, params.pathway); - - // 2. parse FormData → purchaseSchema.safeParse → fail(400) on error - // 3. db.transaction(async (tx) => { - // a. re-fetch shop (isEnabled), item (isActive, pathway match) - // b. if item.itemType === 'PHYSICAL' require shippingAddress - // c. stock check: null (unlimited) OR > 0 → decrement - // d. recompute balance, ensure >= item.price - // e. insert shopOrder with snapshots (price/type/name/total) - // f. insert transactionLedger { amount: -price, reason:'PURCHASE', - // refType:'SHOP', refId: order.id } - // }) - // 4. return { success: true, orderId } or fail(...) with message + const userId = locals.user.id; + + const purchaseData = await validateFormData(purchaseSchema, request); + + let orderId: string; + try { + orderId = await db.transaction(async (tx) => { + const { typedPathwayId } = await assertShopAccess(userId, params.pathway, tx); + + const [item] = await tx + .select() + .from(shopItem) + .where(and( + eq(shopItem.id, purchaseData.itemId), + eq(shopItem.pathway, typedPathwayId), + eq(shopItem.isActive, true) + )) + .limit(1); + + if (!item) throw new ShopError(400, { message: 'Item not found' }); + + if (item.itemType === 'PHYSICAL' && !purchaseData.shippingAddress) { + throw new ShopError(400, { message: 'Shipping address required for physical items' }); + } + + const [{ balance }] = await tx + .select({ + balance: sql`COALESCE(SUM(${transactionLedger.amount}), 0)`.mapWith(Number) + }) + .from(transactionLedger) + .where(and( + eq(transactionLedger.userId, userId), + eq(transactionLedger.pathway, typedPathwayId) + )); + + if (balance < item.price) { + throw new ShopError(400, { message: 'Not enough currency' }); + } + + if (item.stock !== null) { + if (item.stock <= 0) throw new ShopError(400, { message: 'No stock remaining' }); + await tx.update(shopItem) + .set({ stock: item.stock - 1 }) + .where(eq(shopItem.id, item.id)); + } + + const [order] = await tx + .insert(shopOrder) + .values({ + userId, + pathway: typedPathwayId, + totalAmount: item.price, + item: item.id, + itemPriceSnapshot: item.price, + itemTypeSnapshot: item.itemType, + itemNameSnapshot: item.name, + shippingAddress: purchaseData.shippingAddress ?? null, + userNotes: purchaseData.userNotes ?? null + }) + .returning({ id: shopOrder.id }); + + await tx.insert(transactionLedger).values({ + userId, + pathway: typedPathwayId, + amount: -item.price, + reason: 'PURCHASE', + refType: 'SHOP', + refId: order.id // ← ties the ledger entry to the order + }); + + return order.id; + }); + } catch (e) { + if (e instanceof ShopError) return fail(e.status, e.body); + throw e; + } + + return { success: true, orderId }; }, cancel: async ({ request, params, locals }) => { From d7046898214ce157e970211f06f7fdbf4202c08a Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 22:09:46 -0400 Subject: [PATCH 31/52] feat: cancel function --- .../pathway/[pathway]/shop/+page.server.ts | 70 +++++++++++++++---- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts index 4def765..4fc7d40 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -7,7 +7,7 @@ import { shopOrder, transactionLedger } from '$lib/server/db/schema'; -import { and, eq, sql, desc } from 'drizzle-orm'; +import { and, eq, sql, desc, or } from 'drizzle-orm'; import { error, fail, redirect } from '@sveltejs/kit'; import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; import { z } from 'zod'; @@ -28,7 +28,8 @@ const purchaseSchema = z.object({ }); const cancelSchema = z.object({ - orderId: z.string().min(1) // needs to be a valid string + orderId: z.string().min(1), // needs to be a valid string + cancelReason: z.string().min(1) }); type DbOrTx = typeof db | Parameters[0]>[0]; @@ -184,15 +185,60 @@ export const actions: Actions = { cancel: async ({ request, params, locals }) => { if (!locals.user) throw redirect(302, '/api/auth/login'); - const { typedPathwayId } = await assertShopAccess(locals.user.id, params.pathway); - - // 2. parse FormData → cancelSchema - // 3. db.transaction: - // a. load order; assert userId === user.id and status === 'PENDING' - // b. set status = 'CANCELED', cancelledReason - // c. restore item.stock if not null - // d. insert transactionLedger { amount: +price, reason:'REFUND', - // refType:'SHOP', refId: order.id } - // 4. return { success: true } or fail(...) + const userId = locals.user.id; + + const cancelData = await validateFormData(cancelSchema, request); + + try { + await db.transaction(async (tx) => { + const { typedPathwayId } = await assertShopAccess(userId, params.pathway, tx); + + const [order] = await tx + .select() + .from(shopOrder) + .where(and( + eq(shopOrder.id, cancelData.orderId), + eq(shopOrder.userId, userId), + eq(shopOrder.pathway, typedPathwayId), + or(eq(shopOrder.status, 'PENDING'), eq(shopOrder.status, 'PROCESSING')) + )) + .limit(1); + + if (!order) throw new ShopError(404, { message: 'No such order' }); + + await tx.update(shopOrder) + .set({ status: 'CANCELED', cancelledReason: cancelData.cancelReason }) + .where(eq(shopOrder.id, order.id)); + + // restore stock if the item still exists and tracks stock + if (order.item) { + const [item] = await tx + .select() + .from(shopItem) + .where(eq(shopItem.id, order.item)) + .limit(1); + if (item && item.stock !== null) { + await tx.update(shopItem) + .set({ stock: item.stock + 1 }) + .where(eq(shopItem.id, item.id)); + } + } + + // refund: positive ledger entry equal to what was originally charged + await tx.insert(transactionLedger).values({ + userId, + pathway: typedPathwayId, + amount: order.itemPriceSnapshot, + reason: 'REFUND', + refType: 'SHOP', + refId: order.id + }); + }); + } catch (e) { + if (e instanceof ShopError) return fail(e.status, e.body); + throw e; + } + + return { success: true }; } }; From dcded5c5a2a67fab0cc40725db764d726a052b35 Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 22:10:44 -0400 Subject: [PATCH 32/52] fix: use totalAmount instead of itemPriceSnapshot --- .../src/routes/app/pathway/[pathway]/shop/+page.server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts index 4fc7d40..239d339 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -228,7 +228,7 @@ export const actions: Actions = { await tx.insert(transactionLedger).values({ userId, pathway: typedPathwayId, - amount: order.itemPriceSnapshot, + amount: order.totalAmount, // amount that they paid reason: 'REFUND', refType: 'SHOP', refId: order.id From 347d76afc0aaa1acb8a873c195d787a2ecdb8027 Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Tue, 5 May 2026 22:11:40 -0400 Subject: [PATCH 33/52] fix: only allow cancellation while pending --- .../src/routes/app/pathway/[pathway]/shop/+page.server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts index 239d339..d68f3a7 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -200,7 +200,7 @@ export const actions: Actions = { eq(shopOrder.id, cancelData.orderId), eq(shopOrder.userId, userId), eq(shopOrder.pathway, typedPathwayId), - or(eq(shopOrder.status, 'PENDING'), eq(shopOrder.status, 'PROCESSING')) + eq(shopOrder.status, 'PENDING') )) .limit(1); From db9ba6a6b5dbd5f27fb2ec302e486e97377c8bda Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 16:15:24 -0400 Subject: [PATCH 34/52] feat: remove fufillers --- .../src/lib/server/db/schema.ts | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/resolution-frontend/src/lib/server/db/schema.ts b/resolution-frontend/src/lib/server/db/schema.ts index d8829f0..7ed4694 100644 --- a/resolution-frontend/src/lib/server/db/schema.ts +++ b/resolution-frontend/src/lib/server/db/schema.ts @@ -205,16 +205,6 @@ export const shopOrder = pgTable('shop_orders', { updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow(), }) -export const fufillerPathway = pgTable('fufiller_pathway', { - id: text('id').primaryKey().$defaultFn(() => createId()), - userId: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }), - pathway: pathwayEnum('pathway').notNull(), // theres a chance we won't need to assign this, for now this is assigned - assignedAt: timestamp('assigned_at', { mode: 'date' }).notNull().defaultNow(), - assignedBy: text('assigned_by').notNull().references(() => user.id) -}, (table) => [ - uniqueIndex('fufiller_pathway_unique_idx').on(table.userId, table.pathway) -]); - // Relations export const userRelations = relations(user, ({ many }) => ({ pathways: many(userPathway), @@ -225,16 +215,11 @@ export const userRelations = relations(user, ({ many }) => ({ weeklyShips: many(weeklyShip), payouts: many(ambassadorPayout), referralLinks: many(referralLink), -<<<<<<< HEAD warehouseOrders: many(warehouseOrder), - reviewerAssignments: many(reviewerPathway) -======= reviewerAssignments: many(reviewerPathway), currencyTransactions: many(transactionLedger), - shopOrders: many(shopOrder), - fufillerAssignments: many(fufillerPathway) ->>>>>>> a909366 (feat: relations + one more table) - })); + shopOrders: many(shopOrder) +})); export const sessionRelations = relations(session, ({ one }) => ({ user: one(user, { fields: [session.userId], references: [user.id] }) @@ -583,8 +568,4 @@ export const shopOrderRelations = relations(shopOrder, ({ one }) => ({ fufiller: one(user, { fields: [shopOrder.fufilledBy], references: [user.id] }) })); -export const fufillerPathwayRelations = relations(fufillerPathway, ({ one }) => ({ - user: one(user, { fields: [fufillerPathway.userId], references: [user.id] }), - assignedByUser: one(user, { fields: [fufillerPathway.assignedBy], references: [user.id] }) -})); From c9552a715ce8dcb07669ebd877bdbaea8c1b02b0 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:42:11 -0400 Subject: [PATCH 35/52] feat: schema changes (accompanying the last thing) --- .../drizzle/0006_worried_hedge_knight.sql | 1 + .../drizzle/meta/0006_snapshot.json | 2143 +++++++++++++++++ .../drizzle/meta/_journal.json | 7 + 3 files changed, 2151 insertions(+) create mode 100644 resolution-frontend/drizzle/0006_worried_hedge_knight.sql create mode 100644 resolution-frontend/drizzle/meta/0006_snapshot.json diff --git a/resolution-frontend/drizzle/0006_worried_hedge_knight.sql b/resolution-frontend/drizzle/0006_worried_hedge_knight.sql new file mode 100644 index 0000000..08f6c9f --- /dev/null +++ b/resolution-frontend/drizzle/0006_worried_hedge_knight.sql @@ -0,0 +1 @@ +DROP TABLE "fufiller_pathway" CASCADE; \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/0006_snapshot.json b/resolution-frontend/drizzle/meta/0006_snapshot.json new file mode 100644 index 0000000..772a4b7 --- /dev/null +++ b/resolution-frontend/drizzle/meta/0006_snapshot.json @@ -0,0 +1,2143 @@ +{ + "id": "a0247220-e3e8-4974-96a1-81cf9283801b", + "prevId": "2fbbf398-fe52-49d5-bcef-27f714185921", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ambassador_pathway": { + "name": "ambassador_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "ambassador_pathway_unique_idx": { + "name": "ambassador_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ambassador_pathway_user_id_user_id_fk": { + "name": "ambassador_pathway_user_id_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_pathway_assigned_by_user_id_fk": { + "name": "ambassador_pathway_assigned_by_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout": { + "name": "ambassador_payout", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "payout_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_ambassador_id_user_id_fk": { + "name": "ambassador_payout_ambassador_id_user_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_season_id_program_season_id_fk": { + "name": "ambassador_payout_season_id_program_season_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout_item": { + "name": "ambassador_payout_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "payout_id": { + "name": "payout_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_count": { + "name": "completion_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "rate_cents_per_completion": { + "name": "rate_cents_per_completion", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_item_payout_id_ambassador_payout_id_fk": { + "name": "ambassador_payout_item_payout_id_ambassador_payout_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "ambassador_payout", + "columnsFrom": [ + "payout_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_item_workshop_id_workshop_id_fk": { + "name": "ambassador_payout_item_workshop_id_workshop_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_shop": { + "name": "pathway_shop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_name": { + "name": "currency_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wish'" + }, + "currency_name_plural": { + "name": "currency_name_plural", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wishes'" + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "pathway_shop_last_edited_by_user_id_fk": { + "name": "pathway_shop_last_edited_by_user_id_fk", + "tableFrom": "pathway_shop", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "pathway_shop_pathway_unique": { + "name": "pathway_shop_pathway_unique", + "nullsNotDistinct": false, + "columns": [ + "pathway" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_week_content": { + "name": "pathway_week_content", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "prize_image_url": { + "name": "prize_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_submissions_open": { + "name": "is_submissions_open", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pathway_week_content_unique_idx": { + "name": "pathway_week_content_unique_idx", + "columns": [ + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pathway_week_content_last_edited_by_user_id_fk": { + "name": "pathway_week_content_last_edited_by_user_id_fk", + "tableFrom": "pathway_week_content", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_enrollment": { + "name": "program_enrollment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "enrollment_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "enrollment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "starting_week": { + "name": "starting_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "enrollment_user_season_role_idx": { + "name": "enrollment_user_season_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_enrollment_user_id_user_id_fk": { + "name": "program_enrollment_user_id_user_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "program_enrollment_season_id_program_season_id_fk": { + "name": "program_enrollment_season_id_program_season_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_season": { + "name": "program_season", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signup_opens_at": { + "name": "signup_opens_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "signup_closes_at": { + "name": "signup_closes_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "total_weeks": { + "name": "total_weeks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 8 + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "program_season_slug_unique": { + "name": "program_season_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_link": { + "name": "referral_link", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "referral_link_ambassador_id_user_id_fk": { + "name": "referral_link_ambassador_id_user_id_fk", + "tableFrom": "referral_link", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "referral_link_code_unique": { + "name": "referral_link_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_signup": { + "name": "referral_signup", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "referral_link_id": { + "name": "referral_link_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "referral_signup_unique_idx": { + "name": "referral_signup_unique_idx", + "columns": [ + { + "expression": "referral_link_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "referral_signup_referral_link_id_referral_link_id_fk": { + "name": "referral_signup_referral_link_id_referral_link_id_fk", + "tableFrom": "referral_signup", + "tableTo": "referral_link", + "columnsFrom": [ + "referral_link_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_signup_user_id_user_id_fk": { + "name": "referral_signup_user_id_user_id_fk", + "tableFrom": "referral_signup", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reviewer_pathway": { + "name": "reviewer_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "reviewer_pathway_unique_idx": { + "name": "reviewer_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reviewer_pathway_user_id_user_id_fk": { + "name": "reviewer_pathway_user_id_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reviewer_pathway_assigned_by_user_id_fk": { + "name": "reviewer_pathway_assigned_by_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_item": { + "name": "shop_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_url": { + "name": "item_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price": { + "name": "item_price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_stock": { + "name": "item_stock", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "item_type": { + "name": "item_type", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_item_pathway_pathway_shop_pathway_fk": { + "name": "shop_item_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_item", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_item_last_edited_by_user_id_fk": { + "name": "shop_item_last_edited_by_user_id_fk", + "tableFrom": "shop_item", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_orders": { + "name": "shop_orders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "order_stauts": { + "name": "order_stauts", + "type": "shop_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "shop_item_id": { + "name": "shop_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price_snapshot": { + "name": "item_price_snapshot", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_type_enum": { + "name": "item_type_enum", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "item_name_snapshot": { + "name": "item_name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "shipping_address": { + "name": "shipping_address", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_notes": { + "name": "user_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufiller_notes": { + "name": "fufiller_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_by": { + "name": "fufilled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_at": { + "name": "fufilled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_reason": { + "name": "cancelled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_orders_user_id_user_id_fk": { + "name": "shop_orders_user_id_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_pathway_pathway_shop_pathway_fk": { + "name": "shop_orders_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_orders", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_orders_shop_item_id_shop_item_id_fk": { + "name": "shop_orders_shop_item_id_shop_item_id_fk", + "tableFrom": "shop_orders", + "tableTo": "shop_item", + "columnsFrom": [ + "shop_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_fufilled_by_user_id_fk": { + "name": "shop_orders_fufilled_by_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "fufilled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.currency_transactions": { + "name": "currency_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tx_user_id": { + "name": "tx_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_pathway": { + "name": "tx_pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_amount": { + "name": "tx_amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tx_reason": { + "name": "tx_reason", + "type": "currency_txn_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_note": { + "name": "tx_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_granted_by": { + "name": "tx_granted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_type": { + "name": "tx_ref_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_id": { + "name": "tx_ref_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "currency_transactions_tx_user_id_user_id_fk": { + "name": "currency_transactions_tx_user_id_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "currency_transactions_tx_pathway_pathway_shop_pathway_fk": { + "name": "currency_transactions_tx_pathway_pathway_shop_pathway_fk", + "tableFrom": "currency_transactions", + "tableTo": "pathway_shop", + "columnsFrom": [ + "tx_pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "currency_transactions_tx_granted_by_user_id_fk": { + "name": "currency_transactions_tx_granted_by_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hack_club_id": { + "name": "hack_club_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_id": { + "name": "slack_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verification_status": { + "name": "verification_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ysws_eligible": { + "name": "ysws_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_hack_club_id_unique": { + "name": "user_hack_club_id_unique", + "nullsNotDistinct": false, + "columns": [ + "hack_club_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_pathway": { + "name": "user_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_pathway_unique_idx": { + "name": "user_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_pathway_user_id_user_id_fk": { + "name": "user_pathway_user_id_user_id_fk", + "tableFrom": "user_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.weekly_ship": { + "name": "weekly_ship", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "goal_text": { + "name": "goal_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "ship_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PLANNED'" + }, + "proof_url": { + "name": "proof_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shipped_at": { + "name": "shipped_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ship_user_season_week_idx": { + "name": "ship_user_season_week_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "weekly_ship_user_id_user_id_fk": { + "name": "weekly_ship_user_id_user_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_season_id_program_season_id_fk": { + "name": "weekly_ship_season_id_program_season_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_workshop_id_workshop_id_fk": { + "name": "weekly_ship_workshop_id_workshop_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop": { + "name": "workshop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "difficulty": { + "name": "difficulty", + "type": "difficulty", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "estimated_hours": { + "name": "estimated_hours", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_author_id_user_id_fk": { + "name": "workshop_author_id_user_id_fk", + "tableFrom": "workshop", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_season_id_program_season_id_fk": { + "name": "workshop_season_id_program_season_id_fk", + "tableFrom": "workshop", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_analytics": { + "name": "workshop_analytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "starts": { + "name": "starts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completions": { + "name": "completions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "avg_completion_mins": { + "name": "avg_completion_mins", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_analytics_workshop_id_workshop_id_fk": { + "name": "workshop_analytics_workshop_id_workshop_id_fk", + "tableFrom": "workshop_analytics", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workshop_analytics_workshop_id_unique": { + "name": "workshop_analytics_workshop_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workshop_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_completion": { + "name": "workshop_completion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_url": { + "name": "project_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_workshop_participant_season_idx": { + "name": "completion_workshop_participant_season_idx", + "columns": [ + { + "expression": "workshop_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workshop_completion_workshop_id_workshop_id_fk": { + "name": "workshop_completion_workshop_id_workshop_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_participant_id_user_id_fk": { + "name": "workshop_completion_participant_id_user_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "user", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_season_id_program_season_id_fk": { + "name": "workshop_completion_season_id_program_season_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.currency_txn_reason": { + "name": "currency_txn_reason", + "schema": "public", + "values": [ + "GRANT", + "PURCHASE", + "REFUND", + "ADJUSTMENT", + "OTHER" + ] + }, + "public.difficulty": { + "name": "difficulty", + "schema": "public", + "values": [ + "BEGINNER", + "INTERMEDIATE", + "ADVANCED" + ] + }, + "public.enrollment_role": { + "name": "enrollment_role", + "schema": "public", + "values": [ + "PARTICIPANT", + "AMBASSADOR" + ] + }, + "public.enrollment_status": { + "name": "enrollment_status", + "schema": "public", + "values": [ + "ACTIVE", + "DROPPED", + "COMPLETED" + ] + }, + "public.pathway": { + "name": "pathway", + "schema": "public", + "values": [ + "PYTHON", + "RUST", + "GAME_DEV", + "HARDWARE", + "DESIGN", + "GENERAL_CODING" + ] + }, + "public.payout_status": { + "name": "payout_status", + "schema": "public", + "values": [ + "DRAFT", + "PENDING", + "PAID", + "CANCELED" + ] + }, + "public.ship_status": { + "name": "ship_status", + "schema": "public", + "values": [ + "PLANNED", + "IN_PROGRESS", + "SHIPPED", + "MISSED" + ] + }, + "public.shop_item_type": { + "name": "shop_item_type", + "schema": "public", + "values": [ + "PHYSICAL", + "DIGITAL" + ] + }, + "public.shop_order_status": { + "name": "shop_order_status", + "schema": "public", + "values": [ + "PENDING", + "PROCESSING", + "FULFILLED", + "CANCELED" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/_journal.json b/resolution-frontend/drizzle/meta/_journal.json index 90daae1..d663ffa 100644 --- a/resolution-frontend/drizzle/meta/_journal.json +++ b/resolution-frontend/drizzle/meta/_journal.json @@ -85,6 +85,13 @@ "when": 1778007365517, "tag": "0011_nosy_bloodstrike", "breakpoints": true + }, + { + "idx": 6, + "version": "7", + "when": 1778619728709, + "tag": "0006_worried_hedge_knight", + "breakpoints": true } ] } From 4f832c87216ba7bf2dc7322a5e554ea9fbd72b81 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:42:29 -0400 Subject: [PATCH 36/52] dev: shop seeding for dev --- resolution-frontend/src/hooks.server.ts | 4 ++ resolution-frontend/src/lib/server/devSeed.ts | 51 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 resolution-frontend/src/lib/server/devSeed.ts diff --git a/resolution-frontend/src/hooks.server.ts b/resolution-frontend/src/hooks.server.ts index 375a540..2a963a0 100644 --- a/resolution-frontend/src/hooks.server.ts +++ b/resolution-frontend/src/hooks.server.ts @@ -1,10 +1,14 @@ import { lucia } from '$lib/server/auth'; import type { Handle } from '@sveltejs/kit'; import { ensureSeasonFromEnv } from '$lib/server/season'; +import { seedDevShops } from '$lib/server/devSeed'; // Sync season from env on startup ensureSeasonFromEnv().catch(console.error); +// Seed pathway shops + a starter item per pathway when running in dev +seedDevShops().catch(console.error); + export const handle: Handle = async ({ event, resolve }) => { const sessionId = event.cookies.get(lucia.sessionCookieName); diff --git a/resolution-frontend/src/lib/server/devSeed.ts b/resolution-frontend/src/lib/server/devSeed.ts new file mode 100644 index 0000000..e0139ff --- /dev/null +++ b/resolution-frontend/src/lib/server/devSeed.ts @@ -0,0 +1,51 @@ +import { dev } from '$app/environment'; +import { db } from './db'; +import { pathwayShop, shopItem } from './db/schema'; +import { and, eq } from 'drizzle-orm'; +import { PATHWAYS } from '$lib/pathways'; + +/** + * Dev-only seed: ensure every pathway has an enabled `pathwayShop` and at + * least one `shopItem`. No-op in production. + */ +export async function seedDevShops() { + if (!dev) return; + + for (const { id, label } of PATHWAYS) { + const existingShop = await db.query.pathwayShop.findFirst({ + where: eq(pathwayShop.pathway, id) + }); + + if (!existingShop) { + await db.insert(pathwayShop).values({ + pathway: id, + isEnabled: true, + currencyName: 'wish', + currencyNamePlural: 'wishes' + }); + } else if (!existingShop.isEnabled) { + await db + .update(pathwayShop) + .set({ isEnabled: true }) + .where(eq(pathwayShop.pathway, id)); + } + + const existingItem = await db.query.shopItem.findFirst({ + where: and(eq(shopItem.pathway, id), eq(shopItem.isActive, true)) + }); + + if (!existingItem) { + await db.insert(shopItem).values({ + pathway: id, + name: `${label} starter sticker`, + description: `A test ${label} sticker seeded automatically in dev. Remove me before going live.`, + price: 5, + stock: 25, + itemType: 'PHYSICAL', + isActive: true + }); + } + } + + console.log('[dev seed] pathway shops + items ensured'); +} From 15f5b0c455a8d59848e001853506e774841c64f4 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:42:53 -0400 Subject: [PATCH 37/52] feat: templating stuff --- .../app/ambassador/shop/+page.server.ts | 0 .../routes/app/ambassador/shop/+page.svelte | 0 .../ambassador/shop/fufill/+page.server.ts | 0 .../app/ambassador/shop/fufill/+page.svelte | 0 .../[pathway]/shop/[id]/+page.server.ts | 190 ++++++++ .../pathway/[pathway]/shop/[id]/+page.svelte | 447 ++++++++++++++++++ 6 files changed, 637 insertions(+) create mode 100644 resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts create mode 100644 resolution-frontend/src/routes/app/ambassador/shop/+page.svelte create mode 100644 resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts create mode 100644 resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte create mode 100644 resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts create mode 100644 resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte diff --git a/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts new file mode 100644 index 0000000..e69de29 diff --git a/resolution-frontend/src/routes/app/ambassador/shop/+page.svelte b/resolution-frontend/src/routes/app/ambassador/shop/+page.svelte new file mode 100644 index 0000000..e69de29 diff --git a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts new file mode 100644 index 0000000..e69de29 diff --git a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte b/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte new file mode 100644 index 0000000..e69de29 diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts new file mode 100644 index 0000000..b2d921f --- /dev/null +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts @@ -0,0 +1,190 @@ +import type { PageServerLoad, Actions } from './$types'; +import { db } from '$lib/server/db'; +import { + userPathway, + pathwayShop, + shopItem, + shopOrder, + transactionLedger +} from '$lib/server/db/schema'; +import { and, eq, sql } from 'drizzle-orm'; +import { error, fail, redirect } from '@sveltejs/kit'; +import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; +import { z } from 'zod'; +import { addressSchema, validateFormData } from '$lib/server/validation'; + +// thrown inside transactions to abort + roll back; caught outside to convert to fail() +class ShopError extends Error { + constructor(public status: number, public body: { message: string }) { + super(body.message); + } +} + +// always collect a shipping address regardless of item type +const buySchema = z.object({ + userNotes: z.string().max(500).optional().default(''), + ...addressSchema.shape +}); + +type DbOrTx = typeof db | Parameters[0]>[0]; + +async function assertShopAccess(userId: string, pathwayParam: string, conn: DbOrTx = db) { + const pathwayId = pathwayParam.toUpperCase(); + if (!PATHWAY_IDS.includes(pathwayId as PathwayId)) throw error(404, 'Pathway not found'); + const typedPathwayId = pathwayId as PathwayId; + + const membership = await conn + .select() + .from(userPathway) + .where(and(eq(userPathway.userId, userId), eq(userPathway.pathway, typedPathwayId))) + .limit(1); + if (membership.length === 0) throw redirect(302, '/app'); + + const pathwayShopRow = await conn + .select() + .from(pathwayShop) + .where(eq(pathwayShop.pathway, typedPathwayId)) + .limit(1); + if (pathwayShopRow.length === 0 || !pathwayShopRow[0].isEnabled) { + throw error(404); + } + + return { typedPathwayId, shop: pathwayShopRow[0] }; +} + +export const load: PageServerLoad = async ({ params, parent }) => { + const { user } = await parent(); + const { typedPathwayId, shop } = await assertShopAccess(user.id, params.pathway); + + const [item] = await db + .select() + .from(shopItem) + .where( + and( + eq(shopItem.id, params.id), + eq(shopItem.pathway, typedPathwayId), + eq(shopItem.isActive, true) + ) + ) + .limit(1); + + if (!item) throw error(404, 'Item not found'); + + const [{ balance }] = await db + .select({ + balance: sql`COALESCE(SUM(${transactionLedger.amount}), 0)`.mapWith(Number) + }) + .from(transactionLedger) + .where( + and(eq(transactionLedger.userId, user.id), eq(transactionLedger.pathway, typedPathwayId)) + ); + + return { + pathwayId: typedPathwayId, + shop: { + currencyName: shop.currencyName, + currencyNamePlural: shop.currencyNamePlural + }, + item, + balance, + user: { + firstName: user.firstName ?? '', + lastName: user.lastName ?? '', + email: user.email ?? '' + } + }; +}; + +export const actions = { + buy: async ({ request, params, locals }) => { + if (!locals.user) throw redirect(302, '/api/auth/login'); + const userId = locals.user.id; + + const buyData = await validateFormData(buySchema, request); + + const shippingAddress = { + addressLine1: buyData.addressLine1, + addressLine2: buyData.addressLine2, + city: buyData.city, + stateProvince: buyData.stateProvince, + country: buyData.country, + zipPostalCode: buyData.zipPostalCode + }; + + let orderId: string; + try { + orderId = await db.transaction(async (tx) => { + const { typedPathwayId } = await assertShopAccess(userId, params.pathway, tx); + + const [item] = await tx + .select() + .from(shopItem) + .where( + and( + eq(shopItem.id, params.id), + eq(shopItem.pathway, typedPathwayId), + eq(shopItem.isActive, true) + ) + ) + .limit(1); + + if (!item) throw new ShopError(404, { message: 'Item not found' }); + + const [{ balance }] = await tx + .select({ + balance: sql`COALESCE(SUM(${transactionLedger.amount}), 0)`.mapWith(Number) + }) + .from(transactionLedger) + .where( + and( + eq(transactionLedger.userId, userId), + eq(transactionLedger.pathway, typedPathwayId) + ) + ); + + if (balance < item.price) { + throw new ShopError(400, { message: 'Not enough currency' }); + } + + if (item.stock !== null) { + if (item.stock <= 0) throw new ShopError(400, { message: 'No stock remaining' }); + await tx + .update(shopItem) + .set({ stock: item.stock - 1 }) + .where(eq(shopItem.id, item.id)); + } + + const [order] = await tx + .insert(shopOrder) + .values({ + userId, + pathway: typedPathwayId, + totalAmount: item.price, + item: item.id, + itemPriceSnapshot: item.price, + itemTypeSnapshot: item.itemType, + itemNameSnapshot: item.name, + shippingAddress, + userNotes: buyData.userNotes || null + }) + .returning({ id: shopOrder.id }); + + await tx.insert(transactionLedger).values({ + userId, + pathway: typedPathwayId, + amount: -item.price, + reason: 'PURCHASE', + refType: 'SHOP', + refId: order.id + }); + + return order.id; + }); + } catch (e) { + if (e instanceof ShopError) return fail(e.status, e.body); + throw e; + } + + return { success: true, orderId }; + } +} satisfies Actions; diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte new file mode 100644 index 0000000..108becb --- /dev/null +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte @@ -0,0 +1,447 @@ + + + + {data.item.name} - Shop - Resolution + + + +
+ + Back + Back to shop + + + {#if form?.success} +
+

Order placed!

+

+ Your order for {data.item.name} has been received. + We'll get it to you as soon as we can. +

+ + Back to shop + +
+ {:else} +
{ + isSubmitting = true; + return async ({ update }) => { + await update(); + isSubmitting = false; + }; + }}> +
+
+ + {data.item.itemType === 'PHYSICAL' ? 'Physical item' : 'Digital item'} + +

Complete your order

+

{data.item.description}

+
+ + {#if form && !form.success && 'message' in form} +
{form.message}
+ {/if} + + {#if !canAfford} +
+ You don't have enough {data.shop.currencyNamePlural}. You need + {data.item.price - data.balance} more. +
+ {/if} + + {#if !inStock} +
This item is out of stock.
+ {/if} + +
+

Your details

+
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
+

Shipping address

+

Where should we send this?

+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+

Notes (optional)

+

Anything the fulfiller should know? (size, color, etc.)

+
+ +
+
+
+ +
+
+ {#if data.item.itemImageUrl} + {data.item.name} + {:else} +
No image
+ {/if} +
+ +
+
{data.item.name}
+
+ {data.item.price} {currencyLabel} +
+
+ You have {data.balance} {data.shop.currencyNamePlural} +
+
+ + +
+
+ {/if} +
+
+ + From a2d527e269d588f0ef824d2730a09d03033629c0 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:43:09 -0400 Subject: [PATCH 38/52] feat: shop frontend (sorta) --- .../routes/app/pathway/[pathway]/+page.svelte | 67 +++ .../pathway/[pathway]/shop/+page.server.ts | 163 +++--- .../app/pathway/[pathway]/shop/+page.svelte | 500 +++++++++++++++++- 3 files changed, 648 insertions(+), 82 deletions(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/+page.svelte index 6c794fa..52e11cd 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/+page.svelte @@ -90,6 +90,25 @@

brought to you by {data.curator}

+ + +
+ Visit the shop + Spend what you've earned on swag and goodies +
+ +
+
{#each weeks as week} {@const published = isWeekPublished(week)} @@ -172,6 +191,54 @@ font-weight: 600; } + .shop-link { + display: flex; + align-items: center; + gap: 1rem; + width: 100%; + max-width: 800px; + padding: 1rem 1.25rem; + margin-bottom: 2rem; + background: rgba(255, 255, 255, 0.85); + border: 2px solid #af98ff; + border-radius: 16px; + text-decoration: none; + color: #1a1a2e; + font-family: 'Kodchasan', sans-serif; + transition: transform 0.15s, border-color 0.15s; + } + + .shop-link:hover { + transform: translateY(-2px); + border-color: #33d6a6; + } + + .shop-icon { + width: 32px; + height: 32px; + } + + .shop-text { + display: flex; + flex-direction: column; + flex: 1; + } + + .shop-title { + font-weight: 600; + font-size: 1rem; + } + + .shop-sub { + font-size: 0.85rem; + color: #8492a6; + } + + .shop-chevron { + width: 20px; + height: 20px; + } + .weeks-grid { display: grid; grid-template-columns: repeat(4, 1fr); diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts index d68f3a7..9283392 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -101,87 +101,88 @@ export const load: PageServerLoad = async ({ params, parent }) => { }; export const actions: Actions = { - purchase: async ({ request, params, locals }) => { - if (!locals.user) throw redirect(302, '/api/auth/login'); - const userId = locals.user.id; - - const purchaseData = await validateFormData(purchaseSchema, request); - - let orderId: string; - try { - orderId = await db.transaction(async (tx) => { - const { typedPathwayId } = await assertShopAccess(userId, params.pathway, tx); - - const [item] = await tx - .select() - .from(shopItem) - .where(and( - eq(shopItem.id, purchaseData.itemId), - eq(shopItem.pathway, typedPathwayId), - eq(shopItem.isActive, true) - )) - .limit(1); - - if (!item) throw new ShopError(400, { message: 'Item not found' }); - - if (item.itemType === 'PHYSICAL' && !purchaseData.shippingAddress) { - throw new ShopError(400, { message: 'Shipping address required for physical items' }); - } - - const [{ balance }] = await tx - .select({ - balance: sql`COALESCE(SUM(${transactionLedger.amount}), 0)`.mapWith(Number) - }) - .from(transactionLedger) - .where(and( - eq(transactionLedger.userId, userId), - eq(transactionLedger.pathway, typedPathwayId) - )); - - if (balance < item.price) { - throw new ShopError(400, { message: 'Not enough currency' }); - } - - if (item.stock !== null) { - if (item.stock <= 0) throw new ShopError(400, { message: 'No stock remaining' }); - await tx.update(shopItem) - .set({ stock: item.stock - 1 }) - .where(eq(shopItem.id, item.id)); - } - - const [order] = await tx - .insert(shopOrder) - .values({ - userId, - pathway: typedPathwayId, - totalAmount: item.price, - item: item.id, - itemPriceSnapshot: item.price, - itemTypeSnapshot: item.itemType, - itemNameSnapshot: item.name, - shippingAddress: purchaseData.shippingAddress ?? null, - userNotes: purchaseData.userNotes ?? null - }) - .returning({ id: shopOrder.id }); - - await tx.insert(transactionLedger).values({ - userId, - pathway: typedPathwayId, - amount: -item.price, - reason: 'PURCHASE', - refType: 'SHOP', - refId: order.id // ← ties the ledger entry to the order - }); - - return order.id; - }); - } catch (e) { - if (e instanceof ShopError) return fail(e.status, e.body); - throw e; - } - - return { success: true, orderId }; - }, + // commented out as this should probably be in ./[id] + // purchase: async ({ request, params, locals }) => { + // if (!locals.user) throw redirect(302, '/api/auth/login'); + // const userId = locals.user.id; + + // const purchaseData = await validateFormData(purchaseSchema, request); + + // let orderId: string; + // try { + // orderId = await db.transaction(async (tx) => { + // const { typedPathwayId } = await assertShopAccess(userId, params.pathway, tx); + + // const [item] = await tx + // .select() + // .from(shopItem) + // .where(and( + // eq(shopItem.id, purchaseData.itemId), + // eq(shopItem.pathway, typedPathwayId), + // eq(shopItem.isActive, true) + // )) + // .limit(1); + + // if (!item) throw new ShopError(400, { message: 'Item not found' }); + + // if (item.itemType === 'PHYSICAL' && !purchaseData.shippingAddress) { + // throw new ShopError(400, { message: 'Shipping address required for physical items' }); + // } + + // const [{ balance }] = await tx + // .select({ + // balance: sql`COALESCE(SUM(${transactionLedger.amount}), 0)`.mapWith(Number) + // }) + // .from(transactionLedger) + // .where(and( + // eq(transactionLedger.userId, userId), + // eq(transactionLedger.pathway, typedPathwayId) + // )); + + // if (balance < item.price) { + // throw new ShopError(400, { message: 'Not enough currency' }); + // } + + // if (item.stock !== null) { + // if (item.stock <= 0) throw new ShopError(400, { message: 'No stock remaining' }); + // await tx.update(shopItem) + // .set({ stock: item.stock - 1 }) + // .where(eq(shopItem.id, item.id)); + // } + + // const [order] = await tx + // .insert(shopOrder) + // .values({ + // userId, + // pathway: typedPathwayId, + // totalAmount: item.price, + // item: item.id, + // itemPriceSnapshot: item.price, + // itemTypeSnapshot: item.itemType, + // itemNameSnapshot: item.name, + // shippingAddress: purchaseData.shippingAddress ?? null, + // userNotes: purchaseData.userNotes ?? null + // }) + // .returning({ id: shopOrder.id }); + + // await tx.insert(transactionLedger).values({ + // userId, + // pathway: typedPathwayId, + // amount: -item.price, + // reason: 'PURCHASE', + // refType: 'SHOP', + // refId: order.id // ← ties the ledger entry to the order + // }); + + // return order.id; + // }); + // } catch (e) { + // if (e instanceof ShopError) return fail(e.status, e.body); + // throw e; + // } + + // return { success: true, orderId }; + // }, cancel: async ({ request, params, locals }) => { if (!locals.user) throw redirect(302, '/api/auth/login'); diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte index 2e9131b..9da66ee 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte @@ -1,2 +1,500 @@ - + + + + {pathway?.label ?? 'Pathway'} Shop - Resolution + + + +
+ + Back + Back to {pathway?.label ?? 'pathway'} + +
+ + +
+

Items

+ {#if data.items.length === 0} +
+

No items in the shop yet. Check back soon!

+
+ {:else} + + {#if showAll} + + {:else} + + {/if} + {/if} +
+ +
+

Recent orders

+ {#if data.orders.length === 0} +
+

You haven't placed any orders yet.

+
+ {:else} +
    + {#each data.orders as order (order.id)} +
  • +
    +
    {order.itemNameSnapshot}
    +
    + {new Date(order.createdAt).toLocaleDateString()} · + {priceLabel(order.totalAmount)} +
    +
    + + {statusLabel(order.status)} + +
  • + {/each} +
+ {/if} +
+
+
+
+ + From 8425dd5f4e9eec88622e9ac715cfe02d7d866b30 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:48:12 -0400 Subject: [PATCH 39/52] dev: todos --- .../src/routes/app/ambassador/shop/+page.server.ts | 1 + resolution-frontend/src/routes/app/ambassador/shop/+page.svelte | 1 + .../src/routes/app/ambassador/shop/fufill/+page.server.ts | 1 + .../src/routes/app/ambassador/shop/fufill/+page.svelte | 1 + 4 files changed, 4 insertions(+) diff --git a/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts index e69de29..afd6fbc 100644 --- a/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts @@ -0,0 +1 @@ +//TODO: ACTUALLY DO THIS \ No newline at end of file diff --git a/resolution-frontend/src/routes/app/ambassador/shop/+page.svelte b/resolution-frontend/src/routes/app/ambassador/shop/+page.svelte index e69de29..8245c29 100644 --- a/resolution-frontend/src/routes/app/ambassador/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/ambassador/shop/+page.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts index e69de29..fa9d42b 100644 --- a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts +++ b/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts @@ -0,0 +1 @@ +// TODO: actually do this file \ No newline at end of file diff --git a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte b/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte index e69de29..8245c29 100644 --- a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte +++ b/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte @@ -0,0 +1 @@ + \ No newline at end of file From cfbdeaabda37127503e04b64007415e8fa5d2b0b Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:48:18 -0400 Subject: [PATCH 40/52] fix: minor naming change --- .../src/routes/app/pathway/[pathway]/shop/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte index 9da66ee..2b34963 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte @@ -48,7 +48,7 @@

Shop

- Spend your {data.shop.currencyNamePlural} on swag and other goodies. + Spend your {data.shop.currencyNamePlural} on goodies!

From e095330b8b9ba0dedcd5b7a07a4a79081ebb3343 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:51:06 -0400 Subject: [PATCH 41/52] feat(accessibility): make it a button! (thanks amp) --- .../app/pathway/[pathway]/shop/+page.svelte | 47 ++++++++----------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte index 2b34963..cc9815c 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte @@ -28,6 +28,10 @@ return status; } } + + function switchShowState(e: MouseEvent) { + showAll = !showAll + } @@ -108,22 +112,10 @@ {/if} {/each}
- {#if showAll} - - {:else} - - {/if} + {/if} @@ -175,19 +167,13 @@ padding: 1.5rem; } - .show-more-div { - display: flex; - flex-direction: row; - justify-content: flex-end; - gap: 0.25rem; - align-items: center; - } - .show-more { all: unset; - display: block; + display: flex; + align-items: center; + gap: 0.25rem; width: fit-content; - /* margin-left: auto; */ + margin-left: auto; font-style: italic; cursor: pointer; } @@ -196,13 +182,18 @@ text-decoration: underline; } - .show-more:focus { + .show-more:focus-visible { outline: revert; } - .show-more-image { /* cursed name but we ball */ + .show-more-image { height: 1rem; width: 1rem; + transition: transform 0.15s ease; + } + + .show-more.open .show-more-image { + transform: rotate(90deg); } .back-link { From f8f0090af214e6f8d2e63324d184bddb80e5f6fc Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:52:07 -0400 Subject: [PATCH 42/52] feat: only show show-more when more than 3 items --- .../src/routes/app/pathway/[pathway]/shop/+page.svelte | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte index cc9815c..faa8023 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte @@ -112,10 +112,12 @@ {/if} {/each} - + {#if data.items.length > 3} + + {/if} {/if} From 5b804c85131dfc5598ecad5516781ba51c489db2 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:55:58 -0400 Subject: [PATCH 43/52] fix: ignore error because data is basically a one time load --- .../src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte index 108becb..bb446f3 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte @@ -14,8 +14,11 @@ let isSubmitting = $state(false); // prefill from week-submission flow style: name + email come from session user + // svelte-ignore state_referenced_locally let firstName = $state(data.user.firstName); + // svelte-ignore state_referenced_locally let lastName = $state(data.user.lastName); + // svelte-ignore state_referenced_locally let email = $state(data.user.email); // shipping address (only used for physical items) From 1033258749cbf32217d36acf67f35b05895815e0 Mon Sep 17 00:00:00 2001 From: Niko Yu Date: Tue, 12 May 2026 17:58:09 -0400 Subject: [PATCH 44/52] fix: missing line-clamp --- .../src/routes/app/pathway/[pathway]/shop/+page.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte index faa8023..87499cd 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte @@ -379,6 +379,7 @@ font-size: 0.85rem; margin: 0; display: -webkit-box; + line-clamp: 2; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; From 69a39a9b85f19dc86b50d400118422ee08411ecf Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Wed, 13 May 2026 17:59:04 -0400 Subject: [PATCH 45/52] fix: stop migrations from being cooked --- .../0000_curly_raza.sql | 0 .../0001_burly_caretaker.sql | 0 .../0002_luxuriant_maria_hill.sql | 0 .../0003_misty_week_prize_image.sql | 0 .../0004_left_pretty_boy.sql | 0 .../0005_add_package_type_and_orders.sql | 0 .../0006_add_templates_and_batches.sql | 0 .../0006_worried_hedge_knight.sql | 0 .../0007_add_label_tracking_fields.sql | 0 .../0007_tense_captain_cross.sql | 1 + .../0008_add_hs_code.sql | 0 .../0009_add_packaging_columns.sql | 0 .../0010_warehouse_indexes_and_tags.sql | 0 .../0011_nosy_bloodstrike.sql | 0 .../drizzle.backup/meta/0000_snapshot.json | 1344 +++++++ .../meta/0001_snapshot.json | 0 .../meta/0002_snapshot.json | 0 .../meta/0004_snapshot.json | 0 .../meta/0006_snapshot.json | 0 .../drizzle.backup/meta/0007_snapshot.json | 3111 +++++++++++++++++ .../meta/0011_snapshot.json | 0 .../drizzle.backup/meta/_journal.json | 104 + resolution-frontend/drizzle/0000_baseline.sql | 421 +++ .../drizzle/meta/0000_snapshot.json | 1975 ++++++++++- .../drizzle/meta/_journal.json | 90 +- .../src/lib/server/db/schema.ts | 3 + .../[pathway]/shop/[id]/+page.server.ts | 5 + .../pathway/[pathway]/shop/[id]/+page.svelte | 6 + 28 files changed, 6869 insertions(+), 191 deletions(-) rename resolution-frontend/{drizzle => drizzle.backup}/0000_curly_raza.sql (100%) rename resolution-frontend/{drizzle => drizzle.backup}/0001_burly_caretaker.sql (100%) rename resolution-frontend/{drizzle => drizzle.backup}/0002_luxuriant_maria_hill.sql (100%) rename resolution-frontend/{drizzle => drizzle.backup}/0003_misty_week_prize_image.sql (100%) rename resolution-frontend/{drizzle => drizzle.backup}/0004_left_pretty_boy.sql (100%) rename resolution-frontend/{drizzle => drizzle.backup}/0005_add_package_type_and_orders.sql (100%) rename resolution-frontend/{drizzle => drizzle.backup}/0006_add_templates_and_batches.sql (100%) rename resolution-frontend/{drizzle => drizzle.backup}/0006_worried_hedge_knight.sql (100%) rename resolution-frontend/{drizzle => drizzle.backup}/0007_add_label_tracking_fields.sql (100%) create mode 100644 resolution-frontend/drizzle.backup/0007_tense_captain_cross.sql rename resolution-frontend/{drizzle => drizzle.backup}/0008_add_hs_code.sql (100%) rename resolution-frontend/{drizzle => drizzle.backup}/0009_add_packaging_columns.sql (100%) rename resolution-frontend/{drizzle => drizzle.backup}/0010_warehouse_indexes_and_tags.sql (100%) rename resolution-frontend/{drizzle => drizzle.backup}/0011_nosy_bloodstrike.sql (100%) create mode 100644 resolution-frontend/drizzle.backup/meta/0000_snapshot.json rename resolution-frontend/{drizzle => drizzle.backup}/meta/0001_snapshot.json (100%) rename resolution-frontend/{drizzle => drizzle.backup}/meta/0002_snapshot.json (100%) rename resolution-frontend/{drizzle => drizzle.backup}/meta/0004_snapshot.json (100%) rename resolution-frontend/{drizzle => drizzle.backup}/meta/0006_snapshot.json (100%) create mode 100644 resolution-frontend/drizzle.backup/meta/0007_snapshot.json rename resolution-frontend/{drizzle => drizzle.backup}/meta/0011_snapshot.json (100%) create mode 100644 resolution-frontend/drizzle.backup/meta/_journal.json create mode 100644 resolution-frontend/drizzle/0000_baseline.sql diff --git a/resolution-frontend/drizzle/0000_curly_raza.sql b/resolution-frontend/drizzle.backup/0000_curly_raza.sql similarity index 100% rename from resolution-frontend/drizzle/0000_curly_raza.sql rename to resolution-frontend/drizzle.backup/0000_curly_raza.sql diff --git a/resolution-frontend/drizzle/0001_burly_caretaker.sql b/resolution-frontend/drizzle.backup/0001_burly_caretaker.sql similarity index 100% rename from resolution-frontend/drizzle/0001_burly_caretaker.sql rename to resolution-frontend/drizzle.backup/0001_burly_caretaker.sql diff --git a/resolution-frontend/drizzle/0002_luxuriant_maria_hill.sql b/resolution-frontend/drizzle.backup/0002_luxuriant_maria_hill.sql similarity index 100% rename from resolution-frontend/drizzle/0002_luxuriant_maria_hill.sql rename to resolution-frontend/drizzle.backup/0002_luxuriant_maria_hill.sql diff --git a/resolution-frontend/drizzle/0003_misty_week_prize_image.sql b/resolution-frontend/drizzle.backup/0003_misty_week_prize_image.sql similarity index 100% rename from resolution-frontend/drizzle/0003_misty_week_prize_image.sql rename to resolution-frontend/drizzle.backup/0003_misty_week_prize_image.sql diff --git a/resolution-frontend/drizzle/0004_left_pretty_boy.sql b/resolution-frontend/drizzle.backup/0004_left_pretty_boy.sql similarity index 100% rename from resolution-frontend/drizzle/0004_left_pretty_boy.sql rename to resolution-frontend/drizzle.backup/0004_left_pretty_boy.sql diff --git a/resolution-frontend/drizzle/0005_add_package_type_and_orders.sql b/resolution-frontend/drizzle.backup/0005_add_package_type_and_orders.sql similarity index 100% rename from resolution-frontend/drizzle/0005_add_package_type_and_orders.sql rename to resolution-frontend/drizzle.backup/0005_add_package_type_and_orders.sql diff --git a/resolution-frontend/drizzle/0006_add_templates_and_batches.sql b/resolution-frontend/drizzle.backup/0006_add_templates_and_batches.sql similarity index 100% rename from resolution-frontend/drizzle/0006_add_templates_and_batches.sql rename to resolution-frontend/drizzle.backup/0006_add_templates_and_batches.sql diff --git a/resolution-frontend/drizzle/0006_worried_hedge_knight.sql b/resolution-frontend/drizzle.backup/0006_worried_hedge_knight.sql similarity index 100% rename from resolution-frontend/drizzle/0006_worried_hedge_knight.sql rename to resolution-frontend/drizzle.backup/0006_worried_hedge_knight.sql diff --git a/resolution-frontend/drizzle/0007_add_label_tracking_fields.sql b/resolution-frontend/drizzle.backup/0007_add_label_tracking_fields.sql similarity index 100% rename from resolution-frontend/drizzle/0007_add_label_tracking_fields.sql rename to resolution-frontend/drizzle.backup/0007_add_label_tracking_fields.sql diff --git a/resolution-frontend/drizzle.backup/0007_tense_captain_cross.sql b/resolution-frontend/drizzle.backup/0007_tense_captain_cross.sql new file mode 100644 index 0000000..1ffd014 --- /dev/null +++ b/resolution-frontend/drizzle.backup/0007_tense_captain_cross.sql @@ -0,0 +1 @@ +ALTER TABLE "shop_orders" ADD COLUMN "phone" text; diff --git a/resolution-frontend/drizzle/0008_add_hs_code.sql b/resolution-frontend/drizzle.backup/0008_add_hs_code.sql similarity index 100% rename from resolution-frontend/drizzle/0008_add_hs_code.sql rename to resolution-frontend/drizzle.backup/0008_add_hs_code.sql diff --git a/resolution-frontend/drizzle/0009_add_packaging_columns.sql b/resolution-frontend/drizzle.backup/0009_add_packaging_columns.sql similarity index 100% rename from resolution-frontend/drizzle/0009_add_packaging_columns.sql rename to resolution-frontend/drizzle.backup/0009_add_packaging_columns.sql diff --git a/resolution-frontend/drizzle/0010_warehouse_indexes_and_tags.sql b/resolution-frontend/drizzle.backup/0010_warehouse_indexes_and_tags.sql similarity index 100% rename from resolution-frontend/drizzle/0010_warehouse_indexes_and_tags.sql rename to resolution-frontend/drizzle.backup/0010_warehouse_indexes_and_tags.sql diff --git a/resolution-frontend/drizzle/0011_nosy_bloodstrike.sql b/resolution-frontend/drizzle.backup/0011_nosy_bloodstrike.sql similarity index 100% rename from resolution-frontend/drizzle/0011_nosy_bloodstrike.sql rename to resolution-frontend/drizzle.backup/0011_nosy_bloodstrike.sql diff --git a/resolution-frontend/drizzle.backup/meta/0000_snapshot.json b/resolution-frontend/drizzle.backup/meta/0000_snapshot.json new file mode 100644 index 0000000..b667fb0 --- /dev/null +++ b/resolution-frontend/drizzle.backup/meta/0000_snapshot.json @@ -0,0 +1,1344 @@ +{ + "id": "ee4874ac-fef8-4ca8-ab84-17f57639d40b", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ambassador_pathway": { + "name": "ambassador_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "ambassador_pathway_unique_idx": { + "name": "ambassador_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ambassador_pathway_user_id_user_id_fk": { + "name": "ambassador_pathway_user_id_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_pathway_assigned_by_user_id_fk": { + "name": "ambassador_pathway_assigned_by_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout": { + "name": "ambassador_payout", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "payout_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_ambassador_id_user_id_fk": { + "name": "ambassador_payout_ambassador_id_user_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_season_id_program_season_id_fk": { + "name": "ambassador_payout_season_id_program_season_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout_item": { + "name": "ambassador_payout_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "payout_id": { + "name": "payout_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_count": { + "name": "completion_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "rate_cents_per_completion": { + "name": "rate_cents_per_completion", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_item_payout_id_ambassador_payout_id_fk": { + "name": "ambassador_payout_item_payout_id_ambassador_payout_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "ambassador_payout", + "columnsFrom": [ + "payout_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_item_workshop_id_workshop_id_fk": { + "name": "ambassador_payout_item_workshop_id_workshop_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_week_content": { + "name": "pathway_week_content", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pathway_week_content_unique_idx": { + "name": "pathway_week_content_unique_idx", + "columns": [ + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pathway_week_content_last_edited_by_user_id_fk": { + "name": "pathway_week_content_last_edited_by_user_id_fk", + "tableFrom": "pathway_week_content", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_enrollment": { + "name": "program_enrollment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "enrollment_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "enrollment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "starting_week": { + "name": "starting_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "enrollment_user_season_role_idx": { + "name": "enrollment_user_season_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_enrollment_user_id_user_id_fk": { + "name": "program_enrollment_user_id_user_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "program_enrollment_season_id_program_season_id_fk": { + "name": "program_enrollment_season_id_program_season_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_season": { + "name": "program_season", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signup_opens_at": { + "name": "signup_opens_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "signup_closes_at": { + "name": "signup_closes_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "total_weeks": { + "name": "total_weeks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 8 + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "program_season_slug_unique": { + "name": "program_season_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hack_club_id": { + "name": "hack_club_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_id": { + "name": "slack_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verification_status": { + "name": "verification_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ysws_eligible": { + "name": "ysws_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_hack_club_id_unique": { + "name": "user_hack_club_id_unique", + "nullsNotDistinct": false, + "columns": [ + "hack_club_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_pathway": { + "name": "user_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_pathway_unique_idx": { + "name": "user_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_pathway_user_id_user_id_fk": { + "name": "user_pathway_user_id_user_id_fk", + "tableFrom": "user_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.weekly_ship": { + "name": "weekly_ship", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "goal_text": { + "name": "goal_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "ship_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PLANNED'" + }, + "proof_url": { + "name": "proof_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shipped_at": { + "name": "shipped_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ship_user_season_week_idx": { + "name": "ship_user_season_week_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "weekly_ship_user_id_user_id_fk": { + "name": "weekly_ship_user_id_user_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_season_id_program_season_id_fk": { + "name": "weekly_ship_season_id_program_season_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_workshop_id_workshop_id_fk": { + "name": "weekly_ship_workshop_id_workshop_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop": { + "name": "workshop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "difficulty": { + "name": "difficulty", + "type": "difficulty", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "estimated_hours": { + "name": "estimated_hours", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_author_id_user_id_fk": { + "name": "workshop_author_id_user_id_fk", + "tableFrom": "workshop", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_season_id_program_season_id_fk": { + "name": "workshop_season_id_program_season_id_fk", + "tableFrom": "workshop", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_analytics": { + "name": "workshop_analytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "starts": { + "name": "starts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completions": { + "name": "completions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "avg_completion_mins": { + "name": "avg_completion_mins", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_analytics_workshop_id_workshop_id_fk": { + "name": "workshop_analytics_workshop_id_workshop_id_fk", + "tableFrom": "workshop_analytics", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workshop_analytics_workshop_id_unique": { + "name": "workshop_analytics_workshop_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workshop_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_completion": { + "name": "workshop_completion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_url": { + "name": "project_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_workshop_participant_season_idx": { + "name": "completion_workshop_participant_season_idx", + "columns": [ + { + "expression": "workshop_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workshop_completion_workshop_id_workshop_id_fk": { + "name": "workshop_completion_workshop_id_workshop_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_participant_id_user_id_fk": { + "name": "workshop_completion_participant_id_user_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "user", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_season_id_program_season_id_fk": { + "name": "workshop_completion_season_id_program_season_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.difficulty": { + "name": "difficulty", + "schema": "public", + "values": [ + "BEGINNER", + "INTERMEDIATE", + "ADVANCED" + ] + }, + "public.enrollment_role": { + "name": "enrollment_role", + "schema": "public", + "values": [ + "PARTICIPANT", + "AMBASSADOR" + ] + }, + "public.enrollment_status": { + "name": "enrollment_status", + "schema": "public", + "values": [ + "ACTIVE", + "DROPPED", + "COMPLETED" + ] + }, + "public.pathway": { + "name": "pathway", + "schema": "public", + "values": [ + "PYTHON", + "WEB_DEV", + "GAME_DEV", + "HARDWARE", + "DESIGN", + "GENERAL_CODING" + ] + }, + "public.payout_status": { + "name": "payout_status", + "schema": "public", + "values": [ + "DRAFT", + "PENDING", + "PAID", + "CANCELED" + ] + }, + "public.ship_status": { + "name": "ship_status", + "schema": "public", + "values": [ + "PLANNED", + "IN_PROGRESS", + "SHIPPED", + "MISSED" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/0001_snapshot.json b/resolution-frontend/drizzle.backup/meta/0001_snapshot.json similarity index 100% rename from resolution-frontend/drizzle/meta/0001_snapshot.json rename to resolution-frontend/drizzle.backup/meta/0001_snapshot.json diff --git a/resolution-frontend/drizzle/meta/0002_snapshot.json b/resolution-frontend/drizzle.backup/meta/0002_snapshot.json similarity index 100% rename from resolution-frontend/drizzle/meta/0002_snapshot.json rename to resolution-frontend/drizzle.backup/meta/0002_snapshot.json diff --git a/resolution-frontend/drizzle/meta/0004_snapshot.json b/resolution-frontend/drizzle.backup/meta/0004_snapshot.json similarity index 100% rename from resolution-frontend/drizzle/meta/0004_snapshot.json rename to resolution-frontend/drizzle.backup/meta/0004_snapshot.json diff --git a/resolution-frontend/drizzle/meta/0006_snapshot.json b/resolution-frontend/drizzle.backup/meta/0006_snapshot.json similarity index 100% rename from resolution-frontend/drizzle/meta/0006_snapshot.json rename to resolution-frontend/drizzle.backup/meta/0006_snapshot.json diff --git a/resolution-frontend/drizzle.backup/meta/0007_snapshot.json b/resolution-frontend/drizzle.backup/meta/0007_snapshot.json new file mode 100644 index 0000000..b6e44d5 --- /dev/null +++ b/resolution-frontend/drizzle.backup/meta/0007_snapshot.json @@ -0,0 +1,3111 @@ +{ + "id": "d61c6fea-7cc9-40a4-a375-fe0686294e7d", + "prevId": "2fbbf398-fe52-49d5-bcef-27f714185921", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ambassador_pathway": { + "name": "ambassador_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "ambassador_pathway_unique_idx": { + "name": "ambassador_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ambassador_pathway_user_id_user_id_fk": { + "name": "ambassador_pathway_user_id_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_pathway_assigned_by_user_id_fk": { + "name": "ambassador_pathway_assigned_by_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout": { + "name": "ambassador_payout", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "payout_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_ambassador_id_user_id_fk": { + "name": "ambassador_payout_ambassador_id_user_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_season_id_program_season_id_fk": { + "name": "ambassador_payout_season_id_program_season_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout_item": { + "name": "ambassador_payout_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "payout_id": { + "name": "payout_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_count": { + "name": "completion_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "rate_cents_per_completion": { + "name": "rate_cents_per_completion", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_item_payout_id_ambassador_payout_id_fk": { + "name": "ambassador_payout_item_payout_id_ambassador_payout_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "ambassador_payout", + "columnsFrom": [ + "payout_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_item_workshop_id_workshop_id_fk": { + "name": "ambassador_payout_item_workshop_id_workshop_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_shop": { + "name": "pathway_shop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_name": { + "name": "currency_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wish'" + }, + "currency_name_plural": { + "name": "currency_name_plural", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wishes'" + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "pathway_shop_last_edited_by_user_id_fk": { + "name": "pathway_shop_last_edited_by_user_id_fk", + "tableFrom": "pathway_shop", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "pathway_shop_pathway_unique": { + "name": "pathway_shop_pathway_unique", + "nullsNotDistinct": false, + "columns": [ + "pathway" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_week_content": { + "name": "pathway_week_content", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "prize_image_url": { + "name": "prize_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_submissions_open": { + "name": "is_submissions_open", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pathway_week_content_unique_idx": { + "name": "pathway_week_content_unique_idx", + "columns": [ + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pathway_week_content_last_edited_by_user_id_fk": { + "name": "pathway_week_content_last_edited_by_user_id_fk", + "tableFrom": "pathway_week_content", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_enrollment": { + "name": "program_enrollment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "enrollment_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "enrollment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "starting_week": { + "name": "starting_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "enrollment_user_season_role_idx": { + "name": "enrollment_user_season_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_enrollment_user_id_user_id_fk": { + "name": "program_enrollment_user_id_user_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "program_enrollment_season_id_program_season_id_fk": { + "name": "program_enrollment_season_id_program_season_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_season": { + "name": "program_season", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signup_opens_at": { + "name": "signup_opens_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "signup_closes_at": { + "name": "signup_closes_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "total_weeks": { + "name": "total_weeks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 8 + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "program_season_slug_unique": { + "name": "program_season_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_link": { + "name": "referral_link", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "referral_link_ambassador_id_user_id_fk": { + "name": "referral_link_ambassador_id_user_id_fk", + "tableFrom": "referral_link", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "referral_link_code_unique": { + "name": "referral_link_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_signup": { + "name": "referral_signup", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "referral_link_id": { + "name": "referral_link_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "referral_signup_unique_idx": { + "name": "referral_signup_unique_idx", + "columns": [ + { + "expression": "referral_link_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "referral_signup_referral_link_id_referral_link_id_fk": { + "name": "referral_signup_referral_link_id_referral_link_id_fk", + "tableFrom": "referral_signup", + "tableTo": "referral_link", + "columnsFrom": [ + "referral_link_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_signup_user_id_user_id_fk": { + "name": "referral_signup_user_id_user_id_fk", + "tableFrom": "referral_signup", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reviewer_pathway": { + "name": "reviewer_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "reviewer_pathway_unique_idx": { + "name": "reviewer_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reviewer_pathway_user_id_user_id_fk": { + "name": "reviewer_pathway_user_id_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reviewer_pathway_assigned_by_user_id_fk": { + "name": "reviewer_pathway_assigned_by_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_item": { + "name": "shop_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_url": { + "name": "item_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price": { + "name": "item_price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_stock": { + "name": "item_stock", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "item_type": { + "name": "item_type", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_item_pathway_pathway_shop_pathway_fk": { + "name": "shop_item_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_item", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_item_last_edited_by_user_id_fk": { + "name": "shop_item_last_edited_by_user_id_fk", + "tableFrom": "shop_item", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_orders": { + "name": "shop_orders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "order_stauts": { + "name": "order_stauts", + "type": "shop_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "shop_item_id": { + "name": "shop_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price_snapshot": { + "name": "item_price_snapshot", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_type_enum": { + "name": "item_type_enum", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "item_name_snapshot": { + "name": "item_name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "shipping_address": { + "name": "shipping_address", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_notes": { + "name": "user_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufiller_notes": { + "name": "fufiller_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_by": { + "name": "fufilled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_at": { + "name": "fufilled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_reason": { + "name": "cancelled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_orders_user_id_user_id_fk": { + "name": "shop_orders_user_id_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_pathway_pathway_shop_pathway_fk": { + "name": "shop_orders_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_orders", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_orders_shop_item_id_shop_item_id_fk": { + "name": "shop_orders_shop_item_id_shop_item_id_fk", + "tableFrom": "shop_orders", + "tableTo": "shop_item", + "columnsFrom": [ + "shop_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_fufilled_by_user_id_fk": { + "name": "shop_orders_fufilled_by_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "fufilled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.currency_transactions": { + "name": "currency_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tx_user_id": { + "name": "tx_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_pathway": { + "name": "tx_pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_amount": { + "name": "tx_amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tx_reason": { + "name": "tx_reason", + "type": "currency_txn_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_note": { + "name": "tx_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_granted_by": { + "name": "tx_granted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_type": { + "name": "tx_ref_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_id": { + "name": "tx_ref_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "currency_transactions_tx_user_id_user_id_fk": { + "name": "currency_transactions_tx_user_id_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "currency_transactions_tx_pathway_pathway_shop_pathway_fk": { + "name": "currency_transactions_tx_pathway_pathway_shop_pathway_fk", + "tableFrom": "currency_transactions", + "tableTo": "pathway_shop", + "columnsFrom": [ + "tx_pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "currency_transactions_tx_granted_by_user_id_fk": { + "name": "currency_transactions_tx_granted_by_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hack_club_id": { + "name": "hack_club_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_id": { + "name": "slack_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verification_status": { + "name": "verification_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ysws_eligible": { + "name": "ysws_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_hack_club_id_unique": { + "name": "user_hack_club_id_unique", + "nullsNotDistinct": false, + "columns": [ + "hack_club_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_pathway": { + "name": "user_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_pathway_unique_idx": { + "name": "user_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_pathway_user_id_user_id_fk": { + "name": "user_pathway_user_id_user_id_fk", + "tableFrom": "user_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_batch": { + "name": "warehouse_batch", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "warehouse_batch_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'AWAITING_MAPPING'" + }, + "csv_data": { + "name": "csv_data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_mapping": { + "name": "field_mapping", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_count": { + "name": "address_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_batch_created_by_id_user_id_fk": { + "name": "warehouse_batch_created_by_id_user_id_fk", + "tableFrom": "warehouse_batch", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "warehouse_batch_template_id_warehouse_order_template_id_fk": { + "name": "warehouse_batch_template_id_warehouse_order_template_id_fk", + "tableFrom": "warehouse_batch", + "tableTo": "warehouse_order_template", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_batch_tag": { + "name": "warehouse_batch_tag", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "batch_id": { + "name": "batch_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "warehouse_batch_tag_unique_idx": { + "name": "warehouse_batch_tag_unique_idx", + "columns": [ + { + "expression": "batch_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "warehouse_batch_tag_batch_id_warehouse_batch_id_fk": { + "name": "warehouse_batch_tag_batch_id_warehouse_batch_id_fk", + "tableFrom": "warehouse_batch_tag", + "tableTo": "warehouse_batch", + "columnsFrom": [ + "batch_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_category": { + "name": "warehouse_category", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_item": { + "name": "warehouse_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sku": { + "name": "sku", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sizing": { + "name": "sizing", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_type": { + "name": "package_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'box'" + }, + "length_in": { + "name": "length_in", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "width_in": { + "name": "width_in", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "height_in": { + "name": "height_in", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "weight_grams": { + "name": "weight_grams", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "hs_code": { + "name": "hs_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_item_category_id_warehouse_category_id_fk": { + "name": "warehouse_item_category_id_warehouse_category_id_fk", + "tableFrom": "warehouse_item", + "tableTo": "warehouse_category", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "warehouse_item_sku_unique": { + "name": "warehouse_item_sku_unique", + "nullsNotDistinct": false, + "columns": [ + "sku" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order": { + "name": "warehouse_order", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "fulfillment_id": { + "name": "fulfillment_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "identity": { + "type": "always", + "name": "warehouse_order_fulfillment_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "batch_id": { + "name": "batch_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "warehouse_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_line_1": { + "name": "address_line_1", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address_line_2": { + "name": "address_line_2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_province": { + "name": "state_province", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "postal_code": { + "name": "postal_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "estimated_shipping_cents": { + "name": "estimated_shipping_cents", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "estimated_duties_cents": { + "name": "estimated_duties_cents", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "estimated_service_name": { + "name": "estimated_service_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_service_code": { + "name": "estimated_service_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_package_type": { + "name": "estimated_package_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_total_length_in": { + "name": "estimated_total_length_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "estimated_total_width_in": { + "name": "estimated_total_width_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "estimated_total_height_in": { + "name": "estimated_total_height_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "estimated_total_weight_grams": { + "name": "estimated_total_weight_grams", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_category": { + "name": "packaging_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "packaging_label": { + "name": "packaging_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "packaging_length_in": { + "name": "packaging_length_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_width_in": { + "name": "packaging_width_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_height_in": { + "name": "packaging_height_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_subject_to_change": { + "name": "packaging_subject_to_change", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tracking_number": { + "name": "tracking_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "label_url": { + "name": "label_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shipping_method": { + "name": "shipping_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_status": { + "name": "billing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + }, + "billing_failure_reason": { + "name": "billing_failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_order_created_by_id_user_id_fk": { + "name": "warehouse_order_created_by_id_user_id_fk", + "tableFrom": "warehouse_order", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "warehouse_order_fulfillment_id_unique": { + "name": "warehouse_order_fulfillment_id_unique", + "nullsNotDistinct": false, + "columns": [ + "fulfillment_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order_item": { + "name": "warehouse_order_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "order_id": { + "name": "order_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "warehouse_item_id": { + "name": "warehouse_item_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "sizing_choice": { + "name": "sizing_choice", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "warehouse_order_item_order_id_idx": { + "name": "warehouse_order_item_order_id_idx", + "columns": [ + { + "expression": "order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "warehouse_order_item_warehouse_item_id_idx": { + "name": "warehouse_order_item_warehouse_item_id_idx", + "columns": [ + { + "expression": "warehouse_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "warehouse_order_item_order_id_warehouse_order_id_fk": { + "name": "warehouse_order_item_order_id_warehouse_order_id_fk", + "tableFrom": "warehouse_order_item", + "tableTo": "warehouse_order", + "columnsFrom": [ + "order_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "warehouse_order_item_warehouse_item_id_warehouse_item_id_fk": { + "name": "warehouse_order_item_warehouse_item_id_warehouse_item_id_fk", + "tableFrom": "warehouse_order_item", + "tableTo": "warehouse_item", + "columnsFrom": [ + "warehouse_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order_tag": { + "name": "warehouse_order_tag", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "order_id": { + "name": "order_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "warehouse_order_tag_unique_idx": { + "name": "warehouse_order_tag_unique_idx", + "columns": [ + { + "expression": "order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "warehouse_order_tag_tag_idx": { + "name": "warehouse_order_tag_tag_idx", + "columns": [ + { + "expression": "tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "warehouse_order_tag_order_id_warehouse_order_id_fk": { + "name": "warehouse_order_tag_order_id_warehouse_order_id_fk", + "tableFrom": "warehouse_order_tag", + "tableTo": "warehouse_order", + "columnsFrom": [ + "order_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order_template": { + "name": "warehouse_order_template", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_order_template_created_by_id_user_id_fk": { + "name": "warehouse_order_template_created_by_id_user_id_fk", + "tableFrom": "warehouse_order_template", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order_template_item": { + "name": "warehouse_order_template_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "warehouse_item_id": { + "name": "warehouse_item_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_order_template_item_template_id_warehouse_order_template_id_fk": { + "name": "warehouse_order_template_item_template_id_warehouse_order_template_id_fk", + "tableFrom": "warehouse_order_template_item", + "tableTo": "warehouse_order_template", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "warehouse_order_template_item_warehouse_item_id_warehouse_item_id_fk": { + "name": "warehouse_order_template_item_warehouse_item_id_warehouse_item_id_fk", + "tableFrom": "warehouse_order_template_item", + "tableTo": "warehouse_item", + "columnsFrom": [ + "warehouse_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.weekly_ship": { + "name": "weekly_ship", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "goal_text": { + "name": "goal_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "ship_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PLANNED'" + }, + "proof_url": { + "name": "proof_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shipped_at": { + "name": "shipped_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ship_user_season_week_idx": { + "name": "ship_user_season_week_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "weekly_ship_user_id_user_id_fk": { + "name": "weekly_ship_user_id_user_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_season_id_program_season_id_fk": { + "name": "weekly_ship_season_id_program_season_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_workshop_id_workshop_id_fk": { + "name": "weekly_ship_workshop_id_workshop_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop": { + "name": "workshop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "difficulty": { + "name": "difficulty", + "type": "difficulty", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "estimated_hours": { + "name": "estimated_hours", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_author_id_user_id_fk": { + "name": "workshop_author_id_user_id_fk", + "tableFrom": "workshop", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_season_id_program_season_id_fk": { + "name": "workshop_season_id_program_season_id_fk", + "tableFrom": "workshop", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_analytics": { + "name": "workshop_analytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "starts": { + "name": "starts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completions": { + "name": "completions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "avg_completion_mins": { + "name": "avg_completion_mins", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_analytics_workshop_id_workshop_id_fk": { + "name": "workshop_analytics_workshop_id_workshop_id_fk", + "tableFrom": "workshop_analytics", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workshop_analytics_workshop_id_unique": { + "name": "workshop_analytics_workshop_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workshop_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_completion": { + "name": "workshop_completion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_url": { + "name": "project_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_workshop_participant_season_idx": { + "name": "completion_workshop_participant_season_idx", + "columns": [ + { + "expression": "workshop_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workshop_completion_workshop_id_workshop_id_fk": { + "name": "workshop_completion_workshop_id_workshop_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_participant_id_user_id_fk": { + "name": "workshop_completion_participant_id_user_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "user", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_season_id_program_season_id_fk": { + "name": "workshop_completion_season_id_program_season_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.currency_txn_reason": { + "name": "currency_txn_reason", + "schema": "public", + "values": [ + "GRANT", + "PURCHASE", + "REFUND", + "ADJUSTMENT", + "OTHER" + ] + }, + "public.difficulty": { + "name": "difficulty", + "schema": "public", + "values": [ + "BEGINNER", + "INTERMEDIATE", + "ADVANCED" + ] + }, + "public.enrollment_role": { + "name": "enrollment_role", + "schema": "public", + "values": [ + "PARTICIPANT", + "AMBASSADOR" + ] + }, + "public.enrollment_status": { + "name": "enrollment_status", + "schema": "public", + "values": [ + "ACTIVE", + "DROPPED", + "COMPLETED" + ] + }, + "public.pathway": { + "name": "pathway", + "schema": "public", + "values": [ + "PYTHON", + "RUST", + "GAME_DEV", + "HARDWARE", + "DESIGN", + "GENERAL_CODING" + ] + }, + "public.payout_status": { + "name": "payout_status", + "schema": "public", + "values": [ + "DRAFT", + "PENDING", + "PAID", + "CANCELED" + ] + }, + "public.ship_status": { + "name": "ship_status", + "schema": "public", + "values": [ + "PLANNED", + "IN_PROGRESS", + "SHIPPED", + "MISSED" + ] + }, + "public.shop_item_type": { + "name": "shop_item_type", + "schema": "public", + "values": [ + "PHYSICAL", + "DIGITAL" + ] + }, + "public.shop_order_status": { + "name": "shop_order_status", + "schema": "public", + "values": [ + "PENDING", + "PROCESSING", + "FULFILLED", + "CANCELED" + ] + }, + "public.warehouse_batch_status": { + "name": "warehouse_batch_status", + "schema": "public", + "values": [ + "AWAITING_MAPPING", + "MAPPED", + "PROCESSED" + ] + }, + "public.warehouse_order_status": { + "name": "warehouse_order_status", + "schema": "public", + "values": [ + "DRAFT", + "ESTIMATED", + "APPROVED", + "SHIPPED", + "CANCELLED" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/0011_snapshot.json b/resolution-frontend/drizzle.backup/meta/0011_snapshot.json similarity index 100% rename from resolution-frontend/drizzle/meta/0011_snapshot.json rename to resolution-frontend/drizzle.backup/meta/0011_snapshot.json diff --git a/resolution-frontend/drizzle.backup/meta/_journal.json b/resolution-frontend/drizzle.backup/meta/_journal.json new file mode 100644 index 0000000..2206f32 --- /dev/null +++ b/resolution-frontend/drizzle.backup/meta/_journal.json @@ -0,0 +1,104 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1770227074880, + "tag": "0000_curly_raza", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1771512581182, + "tag": "0001_burly_caretaker", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1772636037511, + "tag": "0002_luxuriant_maria_hill", + "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1775185421664, + "tag": "0003_misty_week_prize_image", + "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1776130671597, + "tag": "0004_left_pretty_boy", + "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1776500000000, + "tag": "0005_add_package_type_and_orders", + "breakpoints": true + }, + { + "idx": 6, + "version": "7", + "when": 1776600000000, + "tag": "0006_add_templates_and_batches", + "breakpoints": true + }, + { + "idx": 7, + "version": "7", + "when": 1776700000000, + "tag": "0007_add_label_tracking_fields", + "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1776800000000, + "tag": "0008_add_hs_code", + "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1776900000000, + "tag": "0009_add_packaging_columns", + "breakpoints": true + }, + { + "idx": 10, + "version": "7", + "when": 1777000000000, + "tag": "0010_warehouse_indexes_and_tags", + "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1778007365517, + "tag": "0011_nosy_bloodstrike", + "breakpoints": true + }, + { + "idx": 6, + "version": "7", + "when": 1778619728709, + "tag": "0006_worried_hedge_knight", + "breakpoints": true + }, + { + "idx": 7, + "version": "7", + "when": 1778708714355, + "tag": "0007_tense_captain_cross", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/resolution-frontend/drizzle/0000_baseline.sql b/resolution-frontend/drizzle/0000_baseline.sql new file mode 100644 index 0000000..bcd78bd --- /dev/null +++ b/resolution-frontend/drizzle/0000_baseline.sql @@ -0,0 +1,421 @@ +CREATE TYPE "public"."currency_txn_reason" AS ENUM('GRANT', 'PURCHASE', 'REFUND', 'ADJUSTMENT', 'OTHER');--> statement-breakpoint +CREATE TYPE "public"."difficulty" AS ENUM('BEGINNER', 'INTERMEDIATE', 'ADVANCED');--> statement-breakpoint +CREATE TYPE "public"."enrollment_role" AS ENUM('PARTICIPANT', 'AMBASSADOR');--> statement-breakpoint +CREATE TYPE "public"."enrollment_status" AS ENUM('ACTIVE', 'DROPPED', 'COMPLETED');--> statement-breakpoint +CREATE TYPE "public"."pathway" AS ENUM('PYTHON', 'RUST', 'GAME_DEV', 'HARDWARE', 'DESIGN', 'GENERAL_CODING');--> statement-breakpoint +CREATE TYPE "public"."payout_status" AS ENUM('DRAFT', 'PENDING', 'PAID', 'CANCELED');--> statement-breakpoint +CREATE TYPE "public"."ship_status" AS ENUM('PLANNED', 'IN_PROGRESS', 'SHIPPED', 'MISSED');--> statement-breakpoint +CREATE TYPE "public"."shop_item_type" AS ENUM('PHYSICAL', 'DIGITAL');--> statement-breakpoint +CREATE TYPE "public"."shop_order_status" AS ENUM('PENDING', 'PROCESSING', 'FULFILLED', 'CANCELED');--> statement-breakpoint +CREATE TYPE "public"."warehouse_batch_status" AS ENUM('AWAITING_MAPPING', 'MAPPED', 'PROCESSED');--> statement-breakpoint +CREATE TYPE "public"."warehouse_order_status" AS ENUM('DRAFT', 'ESTIMATED', 'APPROVED', 'SHIPPED', 'CANCELLED');--> statement-breakpoint +CREATE TABLE "ambassador_pathway" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "pathway" "pathway" NOT NULL, + "assigned_at" timestamp DEFAULT now() NOT NULL, + "assigned_by" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "ambassador_payout" ( + "id" text PRIMARY KEY NOT NULL, + "ambassador_id" text NOT NULL, + "season_id" text NOT NULL, + "amount_cents" integer NOT NULL, + "status" "payout_status" DEFAULT 'DRAFT' NOT NULL, + "period_start" timestamp NOT NULL, + "period_end" timestamp NOT NULL, + "paid_at" timestamp, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "ambassador_payout_item" ( + "id" text PRIMARY KEY NOT NULL, + "payout_id" text NOT NULL, + "workshop_id" text NOT NULL, + "completion_count" integer NOT NULL, + "rate_cents_per_completion" integer NOT NULL, + "amount_cents" integer NOT NULL +); +--> statement-breakpoint +CREATE TABLE "pathway_shop" ( + "id" text PRIMARY KEY NOT NULL, + "pathway" "pathway" NOT NULL, + "is_enabled" boolean DEFAULT false NOT NULL, + "currency_name" text DEFAULT 'wish' NOT NULL, + "currency_name_plural" text DEFAULT 'wishes' NOT NULL, + "last_edited_by" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "pathway_shop_pathway_unique" UNIQUE("pathway") +); +--> statement-breakpoint +CREATE TABLE "pathway_week_content" ( + "id" text PRIMARY KEY NOT NULL, + "pathway" "pathway" NOT NULL, + "week_number" integer NOT NULL, + "title" text DEFAULT '' NOT NULL, + "content" text DEFAULT '' NOT NULL, + "prize_image_url" text, + "is_published" boolean DEFAULT false NOT NULL, + "is_submissions_open" boolean DEFAULT true NOT NULL, + "last_edited_by" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "program_enrollment" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "season_id" text NOT NULL, + "role" "enrollment_role" NOT NULL, + "status" "enrollment_status" DEFAULT 'ACTIVE' NOT NULL, + "joined_at" timestamp DEFAULT now() NOT NULL, + "starting_week" integer DEFAULT 1 NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "program_season" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "slug" text NOT NULL, + "signup_opens_at" timestamp NOT NULL, + "signup_closes_at" timestamp NOT NULL, + "starts_at" timestamp NOT NULL, + "ends_at" timestamp NOT NULL, + "total_weeks" integer DEFAULT 8 NOT NULL, + "is_active" boolean DEFAULT true NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "program_season_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "referral_link" ( + "id" text PRIMARY KEY NOT NULL, + "ambassador_id" text NOT NULL, + "pathway" "pathway" NOT NULL, + "code" text NOT NULL, + "label" text, + "is_active" boolean DEFAULT true NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "referral_link_code_unique" UNIQUE("code") +); +--> statement-breakpoint +CREATE TABLE "referral_signup" ( + "id" text PRIMARY KEY NOT NULL, + "referral_link_id" text NOT NULL, + "user_id" text NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "reviewer_pathway" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "pathway" "pathway" NOT NULL, + "assigned_at" timestamp DEFAULT now() NOT NULL, + "assigned_by" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "session" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "expires_at" timestamp NOT NULL +); +--> statement-breakpoint +CREATE TABLE "shop_item" ( + "id" text PRIMARY KEY NOT NULL, + "pathway" "pathway" NOT NULL, + "name" text NOT NULL, + "description" text NOT NULL, + "item_url" text, + "item_price" integer NOT NULL, + "item_stock" integer, + "item_type" "shop_item_type" NOT NULL, + "is_active" boolean DEFAULT false NOT NULL, + "last_edited_by" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "shop_orders" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text, + "pathway" "pathway" NOT NULL, + "order_stauts" "shop_order_status" DEFAULT 'PENDING' NOT NULL, + "amount" integer NOT NULL, + "shop_item_id" text, + "item_price_snapshot" integer NOT NULL, + "item_type_enum" "shop_item_type", + "item_name_snapshot" text NOT NULL, + "shipping_address" jsonb, + "phone" text, + "user_notes" text, + "fufiller_notes" text, + "fufilled_by" text, + "fufilled_at" timestamp, + "cancelled_reason" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "currency_transactions" ( + "id" text PRIMARY KEY NOT NULL, + "tx_user_id" text, + "tx_pathway" "pathway" NOT NULL, + "tx_amount" integer NOT NULL, + "tx_reason" "currency_txn_reason" NOT NULL, + "tx_note" text, + "tx_granted_by" text, + "tx_ref_type" text, + "tx_ref_id" text, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "user" ( + "id" text PRIMARY KEY NOT NULL, + "email" text NOT NULL, + "hack_club_id" text NOT NULL, + "first_name" text, + "last_name" text, + "slack_id" text, + "verification_status" text, + "ysws_eligible" boolean DEFAULT false NOT NULL, + "is_admin" boolean DEFAULT false NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "user_email_unique" UNIQUE("email"), + CONSTRAINT "user_hack_club_id_unique" UNIQUE("hack_club_id") +); +--> statement-breakpoint +CREATE TABLE "user_pathway" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "pathway" "pathway" NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "warehouse_batch" ( + "id" text PRIMARY KEY NOT NULL, + "created_by_id" text NOT NULL, + "template_id" text NOT NULL, + "title" text, + "status" "warehouse_batch_status" DEFAULT 'AWAITING_MAPPING' NOT NULL, + "csv_data" text NOT NULL, + "field_mapping" text, + "address_count" integer DEFAULT 0 NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "warehouse_batch_tag" ( + "id" text PRIMARY KEY NOT NULL, + "batch_id" text NOT NULL, + "tag" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "warehouse_category" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "sort_order" integer DEFAULT 0 NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "warehouse_item" ( + "id" text PRIMARY KEY NOT NULL, + "category_id" text, + "name" text NOT NULL, + "sku" text NOT NULL, + "sizing" text, + "package_type" text DEFAULT 'box' NOT NULL, + "length_in" real NOT NULL, + "width_in" real NOT NULL, + "height_in" real NOT NULL, + "weight_grams" real NOT NULL, + "cost_cents" integer NOT NULL, + "hs_code" text DEFAULT '' NOT NULL, + "quantity" integer DEFAULT 0 NOT NULL, + "image_url" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "warehouse_item_sku_unique" UNIQUE("sku") +); +--> statement-breakpoint +CREATE TABLE "warehouse_order" ( + "id" text PRIMARY KEY NOT NULL, + "fulfillment_id" integer GENERATED ALWAYS AS IDENTITY (sequence name "warehouse_order_fulfillment_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1), + "created_by_id" text NOT NULL, + "batch_id" text, + "status" "warehouse_order_status" DEFAULT 'DRAFT' NOT NULL, + "first_name" text NOT NULL, + "last_name" text NOT NULL, + "email" text NOT NULL, + "phone" text, + "address_line_1" text NOT NULL, + "address_line_2" text, + "city" text NOT NULL, + "state_province" text NOT NULL, + "postal_code" text, + "country" text NOT NULL, + "estimated_shipping_cents" integer, + "estimated_duties_cents" integer, + "estimated_service_name" text, + "estimated_service_code" text, + "estimated_package_type" text, + "estimated_total_length_in" real, + "estimated_total_width_in" real, + "estimated_total_height_in" real, + "estimated_total_weight_grams" real, + "packaging_category" text, + "packaging_label" text, + "packaging_length_in" real, + "packaging_width_in" real, + "packaging_height_in" real, + "packaging_subject_to_change" boolean DEFAULT false NOT NULL, + "tracking_number" text, + "label_url" text, + "shipping_method" text, + "billing_status" text DEFAULT 'PENDING' NOT NULL, + "billing_failure_reason" text, + "notes" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "warehouse_order_fulfillment_id_unique" UNIQUE("fulfillment_id") +); +--> statement-breakpoint +CREATE TABLE "warehouse_order_item" ( + "id" text PRIMARY KEY NOT NULL, + "order_id" text NOT NULL, + "warehouse_item_id" text NOT NULL, + "quantity" integer DEFAULT 1 NOT NULL, + "sizing_choice" text +); +--> statement-breakpoint +CREATE TABLE "warehouse_order_tag" ( + "id" text PRIMARY KEY NOT NULL, + "order_id" text NOT NULL, + "tag" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "warehouse_order_template" ( + "id" text PRIMARY KEY NOT NULL, + "created_by_id" text NOT NULL, + "name" text NOT NULL, + "is_public" boolean DEFAULT false NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "warehouse_order_template_item" ( + "id" text PRIMARY KEY NOT NULL, + "template_id" text NOT NULL, + "warehouse_item_id" text NOT NULL, + "quantity" integer DEFAULT 1 NOT NULL +); +--> statement-breakpoint +CREATE TABLE "weekly_ship" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "season_id" text NOT NULL, + "workshop_id" text, + "week_number" integer NOT NULL, + "goal_text" text NOT NULL, + "status" "ship_status" DEFAULT 'PLANNED' NOT NULL, + "proof_url" text, + "notes" text, + "shipped_at" timestamp, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "workshop" ( + "id" text PRIMARY KEY NOT NULL, + "author_id" text NOT NULL, + "season_id" text NOT NULL, + "title" text NOT NULL, + "description" text NOT NULL, + "pathway" "pathway" NOT NULL, + "difficulty" "difficulty" NOT NULL, + "estimated_hours" integer NOT NULL, + "published" boolean DEFAULT false NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "workshop_analytics" ( + "id" text PRIMARY KEY NOT NULL, + "workshop_id" text NOT NULL, + "views" integer DEFAULT 0 NOT NULL, + "starts" integer DEFAULT 0 NOT NULL, + "completions" integer DEFAULT 0 NOT NULL, + "avg_completion_mins" real, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "workshop_analytics_workshop_id_unique" UNIQUE("workshop_id") +); +--> statement-breakpoint +CREATE TABLE "workshop_completion" ( + "id" text PRIMARY KEY NOT NULL, + "workshop_id" text NOT NULL, + "participant_id" text NOT NULL, + "season_id" text NOT NULL, + "project_url" text, + "started_at" timestamp DEFAULT now() NOT NULL, + "completed_at" timestamp +); +--> statement-breakpoint +ALTER TABLE "ambassador_pathway" ADD CONSTRAINT "ambassador_pathway_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ambassador_pathway" ADD CONSTRAINT "ambassador_pathway_assigned_by_user_id_fk" FOREIGN KEY ("assigned_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ambassador_payout" ADD CONSTRAINT "ambassador_payout_ambassador_id_user_id_fk" FOREIGN KEY ("ambassador_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ambassador_payout" ADD CONSTRAINT "ambassador_payout_season_id_program_season_id_fk" FOREIGN KEY ("season_id") REFERENCES "public"."program_season"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ambassador_payout_item" ADD CONSTRAINT "ambassador_payout_item_payout_id_ambassador_payout_id_fk" FOREIGN KEY ("payout_id") REFERENCES "public"."ambassador_payout"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ambassador_payout_item" ADD CONSTRAINT "ambassador_payout_item_workshop_id_workshop_id_fk" FOREIGN KEY ("workshop_id") REFERENCES "public"."workshop"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "pathway_shop" ADD CONSTRAINT "pathway_shop_last_edited_by_user_id_fk" FOREIGN KEY ("last_edited_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "pathway_week_content" ADD CONSTRAINT "pathway_week_content_last_edited_by_user_id_fk" FOREIGN KEY ("last_edited_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "program_enrollment" ADD CONSTRAINT "program_enrollment_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "program_enrollment" ADD CONSTRAINT "program_enrollment_season_id_program_season_id_fk" FOREIGN KEY ("season_id") REFERENCES "public"."program_season"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "referral_link" ADD CONSTRAINT "referral_link_ambassador_id_user_id_fk" FOREIGN KEY ("ambassador_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "referral_signup" ADD CONSTRAINT "referral_signup_referral_link_id_referral_link_id_fk" FOREIGN KEY ("referral_link_id") REFERENCES "public"."referral_link"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "referral_signup" ADD CONSTRAINT "referral_signup_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "reviewer_pathway" ADD CONSTRAINT "reviewer_pathway_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "reviewer_pathway" ADD CONSTRAINT "reviewer_pathway_assigned_by_user_id_fk" FOREIGN KEY ("assigned_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_item" ADD CONSTRAINT "shop_item_pathway_pathway_shop_pathway_fk" FOREIGN KEY ("pathway") REFERENCES "public"."pathway_shop"("pathway") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_item" ADD CONSTRAINT "shop_item_last_edited_by_user_id_fk" FOREIGN KEY ("last_edited_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_pathway_pathway_shop_pathway_fk" FOREIGN KEY ("pathway") REFERENCES "public"."pathway_shop"("pathway") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_shop_item_id_shop_item_id_fk" FOREIGN KEY ("shop_item_id") REFERENCES "public"."shop_item"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_fufilled_by_user_id_fk" FOREIGN KEY ("fufilled_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "currency_transactions" ADD CONSTRAINT "currency_transactions_tx_user_id_user_id_fk" FOREIGN KEY ("tx_user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "currency_transactions" ADD CONSTRAINT "currency_transactions_tx_pathway_pathway_shop_pathway_fk" FOREIGN KEY ("tx_pathway") REFERENCES "public"."pathway_shop"("pathway") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "currency_transactions" ADD CONSTRAINT "currency_transactions_tx_granted_by_user_id_fk" FOREIGN KEY ("tx_granted_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "user_pathway" ADD CONSTRAINT "user_pathway_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_batch" ADD CONSTRAINT "warehouse_batch_created_by_id_user_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_batch" ADD CONSTRAINT "warehouse_batch_template_id_warehouse_order_template_id_fk" FOREIGN KEY ("template_id") REFERENCES "public"."warehouse_order_template"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_batch_tag" ADD CONSTRAINT "warehouse_batch_tag_batch_id_warehouse_batch_id_fk" FOREIGN KEY ("batch_id") REFERENCES "public"."warehouse_batch"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_item" ADD CONSTRAINT "warehouse_item_category_id_warehouse_category_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."warehouse_category"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order" ADD CONSTRAINT "warehouse_order_created_by_id_user_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order_item" ADD CONSTRAINT "warehouse_order_item_order_id_warehouse_order_id_fk" FOREIGN KEY ("order_id") REFERENCES "public"."warehouse_order"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order_item" ADD CONSTRAINT "warehouse_order_item_warehouse_item_id_warehouse_item_id_fk" FOREIGN KEY ("warehouse_item_id") REFERENCES "public"."warehouse_item"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order_tag" ADD CONSTRAINT "warehouse_order_tag_order_id_warehouse_order_id_fk" FOREIGN KEY ("order_id") REFERENCES "public"."warehouse_order"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order_template" ADD CONSTRAINT "warehouse_order_template_created_by_id_user_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order_template_item" ADD CONSTRAINT "warehouse_order_template_item_template_id_warehouse_order_template_id_fk" FOREIGN KEY ("template_id") REFERENCES "public"."warehouse_order_template"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order_template_item" ADD CONSTRAINT "warehouse_order_template_item_warehouse_item_id_warehouse_item_id_fk" FOREIGN KEY ("warehouse_item_id") REFERENCES "public"."warehouse_item"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "weekly_ship" ADD CONSTRAINT "weekly_ship_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "weekly_ship" ADD CONSTRAINT "weekly_ship_season_id_program_season_id_fk" FOREIGN KEY ("season_id") REFERENCES "public"."program_season"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "weekly_ship" ADD CONSTRAINT "weekly_ship_workshop_id_workshop_id_fk" FOREIGN KEY ("workshop_id") REFERENCES "public"."workshop"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workshop" ADD CONSTRAINT "workshop_author_id_user_id_fk" FOREIGN KEY ("author_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workshop" ADD CONSTRAINT "workshop_season_id_program_season_id_fk" FOREIGN KEY ("season_id") REFERENCES "public"."program_season"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workshop_analytics" ADD CONSTRAINT "workshop_analytics_workshop_id_workshop_id_fk" FOREIGN KEY ("workshop_id") REFERENCES "public"."workshop"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workshop_completion" ADD CONSTRAINT "workshop_completion_workshop_id_workshop_id_fk" FOREIGN KEY ("workshop_id") REFERENCES "public"."workshop"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workshop_completion" ADD CONSTRAINT "workshop_completion_participant_id_user_id_fk" FOREIGN KEY ("participant_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workshop_completion" ADD CONSTRAINT "workshop_completion_season_id_program_season_id_fk" FOREIGN KEY ("season_id") REFERENCES "public"."program_season"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "ambassador_pathway_unique_idx" ON "ambassador_pathway" USING btree ("user_id","pathway");--> statement-breakpoint +CREATE UNIQUE INDEX "pathway_week_content_unique_idx" ON "pathway_week_content" USING btree ("pathway","week_number");--> statement-breakpoint +CREATE UNIQUE INDEX "enrollment_user_season_role_idx" ON "program_enrollment" USING btree ("user_id","season_id","role");--> statement-breakpoint +CREATE UNIQUE INDEX "referral_signup_unique_idx" ON "referral_signup" USING btree ("referral_link_id","user_id");--> statement-breakpoint +CREATE UNIQUE INDEX "reviewer_pathway_unique_idx" ON "reviewer_pathway" USING btree ("user_id","pathway");--> statement-breakpoint +CREATE UNIQUE INDEX "user_pathway_unique_idx" ON "user_pathway" USING btree ("user_id","pathway");--> statement-breakpoint +CREATE UNIQUE INDEX "warehouse_batch_tag_unique_idx" ON "warehouse_batch_tag" USING btree ("batch_id","tag");--> statement-breakpoint +CREATE INDEX "warehouse_order_item_order_id_idx" ON "warehouse_order_item" USING btree ("order_id");--> statement-breakpoint +CREATE INDEX "warehouse_order_item_warehouse_item_id_idx" ON "warehouse_order_item" USING btree ("warehouse_item_id");--> statement-breakpoint +CREATE UNIQUE INDEX "warehouse_order_tag_unique_idx" ON "warehouse_order_tag" USING btree ("order_id","tag");--> statement-breakpoint +CREATE INDEX "warehouse_order_tag_tag_idx" ON "warehouse_order_tag" USING btree ("tag");--> statement-breakpoint +CREATE INDEX "ship_user_season_week_idx" ON "weekly_ship" USING btree ("user_id","season_id","week_number");--> statement-breakpoint +CREATE UNIQUE INDEX "completion_workshop_participant_season_idx" ON "workshop_completion" USING btree ("workshop_id","participant_id","season_id"); \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/0000_snapshot.json b/resolution-frontend/drizzle/meta/0000_snapshot.json index b667fb0..5b167e5 100644 --- a/resolution-frontend/drizzle/meta/0000_snapshot.json +++ b/resolution-frontend/drizzle/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "ee4874ac-fef8-4ca8-ab84-17f57639d40b", + "id": "e2384d76-2d19-41ed-bd29-897f1528c49d", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", @@ -271,6 +271,95 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.pathway_shop": { + "name": "pathway_shop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_name": { + "name": "currency_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wish'" + }, + "currency_name_plural": { + "name": "currency_name_plural", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wishes'" + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "pathway_shop_last_edited_by_user_id_fk": { + "name": "pathway_shop_last_edited_by_user_id_fk", + "tableFrom": "pathway_shop", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "pathway_shop_pathway_unique": { + "name": "pathway_shop_pathway_unique", + "nullsNotDistinct": false, + "columns": [ + "pathway" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.pathway_week_content": { "name": "pathway_week_content", "schema": "", @@ -308,6 +397,12 @@ "notNull": true, "default": "''" }, + "prize_image_url": { + "name": "prize_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, "is_published": { "name": "is_published", "type": "boolean", @@ -315,6 +410,13 @@ "notNull": true, "default": false }, + "is_submissions_open": { + "name": "is_submissions_open", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, "last_edited_by": { "name": "last_edited_by", "type": "text", @@ -574,11 +676,1523 @@ "foreignKeys": {}, "compositePrimaryKeys": {}, "uniqueConstraints": { - "program_season_slug_unique": { - "name": "program_season_slug_unique", + "program_season_slug_unique": { + "name": "program_season_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_link": { + "name": "referral_link", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "referral_link_ambassador_id_user_id_fk": { + "name": "referral_link_ambassador_id_user_id_fk", + "tableFrom": "referral_link", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "referral_link_code_unique": { + "name": "referral_link_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_signup": { + "name": "referral_signup", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "referral_link_id": { + "name": "referral_link_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "referral_signup_unique_idx": { + "name": "referral_signup_unique_idx", + "columns": [ + { + "expression": "referral_link_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "referral_signup_referral_link_id_referral_link_id_fk": { + "name": "referral_signup_referral_link_id_referral_link_id_fk", + "tableFrom": "referral_signup", + "tableTo": "referral_link", + "columnsFrom": [ + "referral_link_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_signup_user_id_user_id_fk": { + "name": "referral_signup_user_id_user_id_fk", + "tableFrom": "referral_signup", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reviewer_pathway": { + "name": "reviewer_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "reviewer_pathway_unique_idx": { + "name": "reviewer_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reviewer_pathway_user_id_user_id_fk": { + "name": "reviewer_pathway_user_id_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reviewer_pathway_assigned_by_user_id_fk": { + "name": "reviewer_pathway_assigned_by_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_item": { + "name": "shop_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_url": { + "name": "item_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price": { + "name": "item_price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_stock": { + "name": "item_stock", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "item_type": { + "name": "item_type", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_item_pathway_pathway_shop_pathway_fk": { + "name": "shop_item_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_item", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_item_last_edited_by_user_id_fk": { + "name": "shop_item_last_edited_by_user_id_fk", + "tableFrom": "shop_item", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_orders": { + "name": "shop_orders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "order_stauts": { + "name": "order_stauts", + "type": "shop_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "shop_item_id": { + "name": "shop_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price_snapshot": { + "name": "item_price_snapshot", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_type_enum": { + "name": "item_type_enum", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "item_name_snapshot": { + "name": "item_name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "shipping_address": { + "name": "shipping_address", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_notes": { + "name": "user_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufiller_notes": { + "name": "fufiller_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_by": { + "name": "fufilled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_at": { + "name": "fufilled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_reason": { + "name": "cancelled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_orders_user_id_user_id_fk": { + "name": "shop_orders_user_id_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_pathway_pathway_shop_pathway_fk": { + "name": "shop_orders_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_orders", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_orders_shop_item_id_shop_item_id_fk": { + "name": "shop_orders_shop_item_id_shop_item_id_fk", + "tableFrom": "shop_orders", + "tableTo": "shop_item", + "columnsFrom": [ + "shop_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_fufilled_by_user_id_fk": { + "name": "shop_orders_fufilled_by_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "fufilled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.currency_transactions": { + "name": "currency_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tx_user_id": { + "name": "tx_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_pathway": { + "name": "tx_pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_amount": { + "name": "tx_amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tx_reason": { + "name": "tx_reason", + "type": "currency_txn_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_note": { + "name": "tx_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_granted_by": { + "name": "tx_granted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_type": { + "name": "tx_ref_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_id": { + "name": "tx_ref_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "currency_transactions_tx_user_id_user_id_fk": { + "name": "currency_transactions_tx_user_id_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "currency_transactions_tx_pathway_pathway_shop_pathway_fk": { + "name": "currency_transactions_tx_pathway_pathway_shop_pathway_fk", + "tableFrom": "currency_transactions", + "tableTo": "pathway_shop", + "columnsFrom": [ + "tx_pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "currency_transactions_tx_granted_by_user_id_fk": { + "name": "currency_transactions_tx_granted_by_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hack_club_id": { + "name": "hack_club_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_id": { + "name": "slack_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verification_status": { + "name": "verification_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ysws_eligible": { + "name": "ysws_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_hack_club_id_unique": { + "name": "user_hack_club_id_unique", + "nullsNotDistinct": false, + "columns": [ + "hack_club_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_pathway": { + "name": "user_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_pathway_unique_idx": { + "name": "user_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_pathway_user_id_user_id_fk": { + "name": "user_pathway_user_id_user_id_fk", + "tableFrom": "user_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_batch": { + "name": "warehouse_batch", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "warehouse_batch_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'AWAITING_MAPPING'" + }, + "csv_data": { + "name": "csv_data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_mapping": { + "name": "field_mapping", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_count": { + "name": "address_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_batch_created_by_id_user_id_fk": { + "name": "warehouse_batch_created_by_id_user_id_fk", + "tableFrom": "warehouse_batch", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "warehouse_batch_template_id_warehouse_order_template_id_fk": { + "name": "warehouse_batch_template_id_warehouse_order_template_id_fk", + "tableFrom": "warehouse_batch", + "tableTo": "warehouse_order_template", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_batch_tag": { + "name": "warehouse_batch_tag", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "batch_id": { + "name": "batch_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "warehouse_batch_tag_unique_idx": { + "name": "warehouse_batch_tag_unique_idx", + "columns": [ + { + "expression": "batch_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "warehouse_batch_tag_batch_id_warehouse_batch_id_fk": { + "name": "warehouse_batch_tag_batch_id_warehouse_batch_id_fk", + "tableFrom": "warehouse_batch_tag", + "tableTo": "warehouse_batch", + "columnsFrom": [ + "batch_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_category": { + "name": "warehouse_category", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_item": { + "name": "warehouse_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sku": { + "name": "sku", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sizing": { + "name": "sizing", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_type": { + "name": "package_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'box'" + }, + "length_in": { + "name": "length_in", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "width_in": { + "name": "width_in", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "height_in": { + "name": "height_in", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "weight_grams": { + "name": "weight_grams", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "hs_code": { + "name": "hs_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_item_category_id_warehouse_category_id_fk": { + "name": "warehouse_item_category_id_warehouse_category_id_fk", + "tableFrom": "warehouse_item", + "tableTo": "warehouse_category", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "warehouse_item_sku_unique": { + "name": "warehouse_item_sku_unique", + "nullsNotDistinct": false, + "columns": [ + "sku" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order": { + "name": "warehouse_order", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "fulfillment_id": { + "name": "fulfillment_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "identity": { + "type": "always", + "name": "warehouse_order_fulfillment_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "batch_id": { + "name": "batch_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "warehouse_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_line_1": { + "name": "address_line_1", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address_line_2": { + "name": "address_line_2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_province": { + "name": "state_province", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "postal_code": { + "name": "postal_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "estimated_shipping_cents": { + "name": "estimated_shipping_cents", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "estimated_duties_cents": { + "name": "estimated_duties_cents", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "estimated_service_name": { + "name": "estimated_service_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_service_code": { + "name": "estimated_service_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_package_type": { + "name": "estimated_package_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_total_length_in": { + "name": "estimated_total_length_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "estimated_total_width_in": { + "name": "estimated_total_width_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "estimated_total_height_in": { + "name": "estimated_total_height_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "estimated_total_weight_grams": { + "name": "estimated_total_weight_grams", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_category": { + "name": "packaging_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "packaging_label": { + "name": "packaging_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "packaging_length_in": { + "name": "packaging_length_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_width_in": { + "name": "packaging_width_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_height_in": { + "name": "packaging_height_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_subject_to_change": { + "name": "packaging_subject_to_change", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tracking_number": { + "name": "tracking_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "label_url": { + "name": "label_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shipping_method": { + "name": "shipping_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_status": { + "name": "billing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + }, + "billing_failure_reason": { + "name": "billing_failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_order_created_by_id_user_id_fk": { + "name": "warehouse_order_created_by_id_user_id_fk", + "tableFrom": "warehouse_order", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "warehouse_order_fulfillment_id_unique": { + "name": "warehouse_order_fulfillment_id_unique", "nullsNotDistinct": false, "columns": [ - "slug" + "fulfillment_id" ] } }, @@ -586,8 +2200,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.session": { - "name": "session", + "public.warehouse_order_item": { + "name": "warehouse_order_item", "schema": "", "columns": { "id": { @@ -596,33 +2210,90 @@ "primaryKey": true, "notNull": true }, - "user_id": { - "name": "user_id", + "order_id": { + "name": "order_id", "type": "text", "primaryKey": false, "notNull": true }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", + "warehouse_item_id": { + "name": "warehouse_item_id", + "type": "text", "primaryKey": false, "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "sizing_choice": { + "name": "sizing_choice", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "warehouse_order_item_order_id_idx": { + "name": "warehouse_order_item_order_id_idx", + "columns": [ + { + "expression": "order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "warehouse_order_item_warehouse_item_id_idx": { + "name": "warehouse_order_item_warehouse_item_id_idx", + "columns": [ + { + "expression": "warehouse_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} } }, - "indexes": {}, "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", + "warehouse_order_item_order_id_warehouse_order_id_fk": { + "name": "warehouse_order_item_order_id_warehouse_order_id_fk", + "tableFrom": "warehouse_order_item", + "tableTo": "warehouse_order", "columnsFrom": [ - "user_id" + "order_id" ], "columnsTo": [ "id" ], "onDelete": "cascade", "onUpdate": "no action" + }, + "warehouse_order_item_warehouse_item_id_warehouse_item_id_fk": { + "name": "warehouse_order_item_warehouse_item_id_warehouse_item_id_fk", + "tableFrom": "warehouse_order_item", + "tableTo": "warehouse_item", + "columnsFrom": [ + "warehouse_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" } }, "compositePrimaryKeys": {}, @@ -631,8 +2302,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.user": { - "name": "user", + "public.warehouse_order_tag": { + "name": "warehouse_order_tag", "schema": "", "columns": { "id": { @@ -641,58 +2312,116 @@ "primaryKey": true, "notNull": true }, - "email": { - "name": "email", + "order_id": { + "name": "order_id", "type": "text", "primaryKey": false, "notNull": true }, - "hack_club_id": { - "name": "hack_club_id", + "tag": { + "name": "tag", "type": "text", "primaryKey": false, "notNull": true + } + }, + "indexes": { + "warehouse_order_tag_unique_idx": { + "name": "warehouse_order_tag_unique_idx", + "columns": [ + { + "expression": "order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} }, - "first_name": { - "name": "first_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_name": { - "name": "last_name", + "warehouse_order_tag_tag_idx": { + "name": "warehouse_order_tag_tag_idx", + "columns": [ + { + "expression": "tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "warehouse_order_tag_order_id_warehouse_order_id_fk": { + "name": "warehouse_order_tag_order_id_warehouse_order_id_fk", + "tableFrom": "warehouse_order_tag", + "tableTo": "warehouse_order", + "columnsFrom": [ + "order_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order_template": { + "name": "warehouse_order_template", + "schema": "", + "columns": { + "id": { + "name": "id", "type": "text", - "primaryKey": false, - "notNull": false + "primaryKey": true, + "notNull": true }, - "slack_id": { - "name": "slack_id", + "created_by_id": { + "name": "created_by_id", "type": "text", "primaryKey": false, - "notNull": false + "notNull": true }, - "verification_status": { - "name": "verification_status", + "name": { + "name": "name", "type": "text", "primaryKey": false, - "notNull": false + "notNull": true }, - "ysws_eligible": { - "name": "ysws_eligible", + "is_public": { + "name": "is_public", "type": "boolean", "primaryKey": false, "notNull": true, "default": false }, - "is_admin": { - "name": "is_admin", - "type": "boolean", + "created_at": { + "name": "created_at", + "type": "timestamp", "primaryKey": false, "notNull": true, - "default": false + "default": "now()" }, - "created_at": { - "name": "created_at", + "updated_at": { + "name": "updated_at", "type": "timestamp", "primaryKey": false, "notNull": true, @@ -700,30 +2429,29 @@ } }, "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_unique": { - "name": "user_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - }, - "user_hack_club_id_unique": { - "name": "user_hack_club_id_unique", - "nullsNotDistinct": false, - "columns": [ - "hack_club_id" - ] + "foreignKeys": { + "warehouse_order_template_created_by_id_user_id_fk": { + "name": "warehouse_order_template_created_by_id_user_id_fk", + "tableFrom": "warehouse_order_template", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" } }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, "policies": {}, "checkConstraints": {}, "isRLSEnabled": false }, - "public.user_pathway": { - "name": "user_pathway", + "public.warehouse_order_template_item": { + "name": "warehouse_order_template_item", "schema": "", "columns": { "id": { @@ -732,63 +2460,53 @@ "primaryKey": true, "notNull": true }, - "user_id": { - "name": "user_id", + "template_id": { + "name": "template_id", "type": "text", "primaryKey": false, "notNull": true }, - "pathway": { - "name": "pathway", - "type": "pathway", - "typeSchema": "public", + "warehouse_item_id": { + "name": "warehouse_item_id", + "type": "text", "primaryKey": false, "notNull": true }, - "created_at": { - "name": "created_at", - "type": "timestamp", + "quantity": { + "name": "quantity", + "type": "integer", "primaryKey": false, "notNull": true, - "default": "now()" - } - }, - "indexes": { - "user_pathway_unique_idx": { - "name": "user_pathway_unique_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "pathway", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} + "default": 1 } }, + "indexes": {}, "foreignKeys": { - "user_pathway_user_id_user_id_fk": { - "name": "user_pathway_user_id_user_id_fk", - "tableFrom": "user_pathway", - "tableTo": "user", + "warehouse_order_template_item_template_id_warehouse_order_template_id_fk": { + "name": "warehouse_order_template_item_template_id_warehouse_order_template_id_fk", + "tableFrom": "warehouse_order_template_item", + "tableTo": "warehouse_order_template", "columnsFrom": [ - "user_id" + "template_id" ], "columnsTo": [ "id" ], "onDelete": "cascade", "onUpdate": "no action" + }, + "warehouse_order_template_item_warehouse_item_id_warehouse_item_id_fk": { + "name": "warehouse_order_template_item_warehouse_item_id_warehouse_item_id_fk", + "tableFrom": "warehouse_order_template_item", + "tableTo": "warehouse_item", + "columnsFrom": [ + "warehouse_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" } }, "compositePrimaryKeys": {}, @@ -1272,6 +2990,17 @@ } }, "enums": { + "public.currency_txn_reason": { + "name": "currency_txn_reason", + "schema": "public", + "values": [ + "GRANT", + "PURCHASE", + "REFUND", + "ADJUSTMENT", + "OTHER" + ] + }, "public.difficulty": { "name": "difficulty", "schema": "public", @@ -1303,7 +3032,7 @@ "schema": "public", "values": [ "PYTHON", - "WEB_DEV", + "RUST", "GAME_DEV", "HARDWARE", "DESIGN", @@ -1329,6 +3058,44 @@ "SHIPPED", "MISSED" ] + }, + "public.shop_item_type": { + "name": "shop_item_type", + "schema": "public", + "values": [ + "PHYSICAL", + "DIGITAL" + ] + }, + "public.shop_order_status": { + "name": "shop_order_status", + "schema": "public", + "values": [ + "PENDING", + "PROCESSING", + "FULFILLED", + "CANCELED" + ] + }, + "public.warehouse_batch_status": { + "name": "warehouse_batch_status", + "schema": "public", + "values": [ + "AWAITING_MAPPING", + "MAPPED", + "PROCESSED" + ] + }, + "public.warehouse_order_status": { + "name": "warehouse_order_status", + "schema": "public", + "values": [ + "DRAFT", + "ESTIMATED", + "APPROVED", + "SHIPPED", + "CANCELLED" + ] } }, "schemas": {}, diff --git a/resolution-frontend/drizzle/meta/_journal.json b/resolution-frontend/drizzle/meta/_journal.json index d663ffa..ae2f559 100644 --- a/resolution-frontend/drizzle/meta/_journal.json +++ b/resolution-frontend/drizzle/meta/_journal.json @@ -5,93 +5,9 @@ { "idx": 0, "version": "7", - "when": 1770227074880, - "tag": "0000_curly_raza", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1771512581182, - "tag": "0001_burly_caretaker", - "breakpoints": true - }, - { - "idx": 2, - "version": "7", - "when": 1772636037511, - "tag": "0002_luxuriant_maria_hill", - "breakpoints": true - }, - { - "idx": 3, - "version": "7", - "when": 1775185421664, - "tag": "0003_misty_week_prize_image", - "breakpoints": true - }, - { - "idx": 4, - "version": "7", - "when": 1776130671597, - "tag": "0004_left_pretty_boy", - "breakpoints": true - }, - { - "idx": 5, - "version": "7", - "when": 1776500000000, - "tag": "0005_add_package_type_and_orders", - "breakpoints": true - }, - { - "idx": 6, - "version": "7", - "when": 1776600000000, - "tag": "0006_add_templates_and_batches", - "breakpoints": true - }, - { - "idx": 7, - "version": "7", - "when": 1776700000000, - "tag": "0007_add_label_tracking_fields", - "breakpoints": true - }, - { - "idx": 8, - "version": "7", - "when": 1776800000000, - "tag": "0008_add_hs_code", - "breakpoints": true - }, - { - "idx": 9, - "version": "7", - "when": 1776900000000, - "tag": "0009_add_packaging_columns", - "breakpoints": true - }, - { - "idx": 10, - "version": "7", - "when": 1777000000000, - "tag": "0010_warehouse_indexes_and_tags", - "breakpoints": true - }, - { - "idx": 11, - "version": "7", - "when": 1778007365517, - "tag": "0011_nosy_bloodstrike", - "breakpoints": true - }, - { - "idx": 6, - "version": "7", - "when": 1778619728709, - "tag": "0006_worried_hedge_knight", + "when": 1778709057473, + "tag": "0000_baseline", "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/resolution-frontend/src/lib/server/db/schema.ts b/resolution-frontend/src/lib/server/db/schema.ts index 7ed4694..995841f 100644 --- a/resolution-frontend/src/lib/server/db/schema.ts +++ b/resolution-frontend/src/lib/server/db/schema.ts @@ -195,6 +195,7 @@ export const shopOrder = pgTable('shop_orders', { itemTypeSnapshot: shopItemTypeEnum('item_type_enum'), itemNameSnapshot: text('item_name_snapshot').notNull(), shippingAddress: jsonb('shipping_address').$type(), + phone: text('phone'), userNotes: text('user_notes'), fufillerNotes: text('fufiller_notes'), // claimedBy: @@ -542,6 +543,8 @@ export const warehouseBatchRelations = relations(warehouseBatch, ({ one, many }) export const warehouseBatchTagRelations = relations(warehouseBatchTag, ({ one }) => ({ batch: one(warehouseBatch, { fields: [warehouseBatchTag.batchId], references: [warehouseBatch.id] }) +})); + export const pathwayShopRelations = relations(pathwayShop, ({ one, many }) => ({ editor: one(user, { fields: [pathwayShop.lastEditedBy], references: [user.id] }), items: many(shopItem), diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts index b2d921f..1422239 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts @@ -23,6 +23,10 @@ class ShopError extends Error { // always collect a shipping address regardless of item type const buySchema = z.object({ userNotes: z.string().max(500).optional().default(''), + phone: z + .string() + .min(1, 'Phone number is required') + .max(30, 'Phone number is too long'), ...addressSchema.shape }); @@ -165,6 +169,7 @@ export const actions = { itemTypeSnapshot: item.itemType, itemNameSnapshot: item.name, shippingAddress, + phone: buyData.phone, userNotes: buyData.userNotes || null }) .returning({ id: shopOrder.id }); diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte index bb446f3..f719b53 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte @@ -20,6 +20,7 @@ let lastName = $state(data.user.lastName); // svelte-ignore state_referenced_locally let email = $state(data.user.email); + let phone = $state(''); // shipping address (only used for physical items) let addressLine1 = $state(''); @@ -103,6 +104,10 @@ +
+ + +
@@ -295,6 +300,7 @@ input[type='text'], input[type='email'], + input[type='tel'], textarea { width: 100%; padding: 0.625rem 0.75rem; From cc676e83f443996f1745c94a4e62adc51abde64a Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Fri, 15 May 2026 23:56:49 -0400 Subject: [PATCH 46/52] feat: a lot of work but in short: work on backend for /create and / for ambassadors (so ambassador stuff) fix some stuff update schema --- .../src/lib/server/db/schema.ts | 18 +- resolution-frontend/src/lib/server/devSeed.ts | 1 - resolution-frontend/src/lib/shop/utils.ts | 40 +++ .../[pathway]/shop/+layout.server.ts | 24 ++ .../ambassador/[pathway]/shop/+page.server.ts | 40 +++ .../{ => [pathway]}/shop/+page.svelte | 0 .../[pathway]/shop/create/+page.server.ts | 247 ++++++++++++++++++ .../[pathway]/shop/create/+page.svelte | 0 .../shop/fufill/+page.server.ts | 0 .../{ => [pathway]}/shop/fufill/+page.svelte | 0 .../app/ambassador/shop/+page.server.ts | 1 - .../pathway/[pathway]/shop/+page.server.ts | 43 +-- .../app/pathway/[pathway]/shop/+page.svelte | 3 - .../[pathway]/shop/[id]/+page.server.ts | 1 - .../pathway/[pathway]/shop/[id]/+page.svelte | 3 - 15 files changed, 365 insertions(+), 56 deletions(-) create mode 100644 resolution-frontend/src/lib/shop/utils.ts create mode 100644 resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+layout.server.ts create mode 100644 resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+page.server.ts rename resolution-frontend/src/routes/app/ambassador/{ => [pathway]}/shop/+page.svelte (100%) create mode 100644 resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.server.ts create mode 100644 resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.svelte rename resolution-frontend/src/routes/app/ambassador/{ => [pathway]}/shop/fufill/+page.server.ts (100%) rename resolution-frontend/src/routes/app/ambassador/{ => [pathway]}/shop/fufill/+page.svelte (100%) delete mode 100644 resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts diff --git a/resolution-frontend/src/lib/server/db/schema.ts b/resolution-frontend/src/lib/server/db/schema.ts index 995841f..ae064d9 100644 --- a/resolution-frontend/src/lib/server/db/schema.ts +++ b/resolution-frontend/src/lib/server/db/schema.ts @@ -13,7 +13,7 @@ export const payoutStatusEnum = pgEnum('payout_status', ['DRAFT', 'PENDING', 'PA export const warehouseOrderStatusEnum = pgEnum('warehouse_order_status', ['DRAFT', 'ESTIMATED', 'APPROVED', 'SHIPPED', 'CANCELLED']); export const warehouseBatchStatusEnum = pgEnum('warehouse_batch_status', ['AWAITING_MAPPING', 'MAPPED', 'PROCESSED']); export const shopOrderStatusEnum = pgEnum('shop_order_status', ['PENDING', 'PROCESSING', 'FULFILLED', 'CANCELED']); // order tracking for frontend (users) -export const shopItemTypeEnum = pgEnum('shop_item_type', ['PHYSICAL', 'DIGITAL']); // for filtering +export const shopItemSourceEnum = pgEnum('shop_item_source', ['CUSTOM', 'WAREHOUSE_ITEM', 'WAREHOUSE_TEMPLATE']); // discriminator: where the item's fulfillment data comes from export const currencyTxnReasonEnum = pgEnum('currency_txn_reason', ['GRANT', 'PURCHASE', 'REFUND', 'ADJUSTMENT', 'OTHER']); // logging why transaction occured // Tables @@ -153,7 +153,7 @@ export const pathwayShop = pgTable('pathway_shop', { currencyNamePlural: text('currency_name_plural').notNull().default('wishes'), lastEditedBy: text('last_edited_by').references(() => user.id), createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), - updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow() + updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow().$onUpdate(() => new Date()) }); export const shopItem = pgTable('shop_item', { @@ -161,14 +161,17 @@ export const shopItem = pgTable('shop_item', { pathway: pathwayEnum('pathway').notNull().references(() => pathwayShop.pathway, { onDelete: 'cascade' }), name: text('name').notNull(), description: text('description').notNull(), - itemImageUrl: text('item_url'), + itemImageUrl: text('item_image_url'), price: integer('item_price').notNull(), + // stock is nullable — null means unlimited stock stock: integer('item_stock'), - itemType: shopItemTypeEnum('item_type').notNull(), isActive: boolean('is_active').notNull().default(false), + sourceType: shopItemSourceEnum('source_type').notNull().default('CUSTOM'), + linkedWarehouseItemId: text('linked_warehouse_item_id').references(() => warehouseItem.id, { onDelete: 'cascade' }), // TODO: ADD WARNING WHEN DELETING WAREHOUSE ITEM OR TEMPLATE + linkedWarehouseTemplateId: text('linked_warehouse_template_id').references(() => warehouseOrderTemplate.id, { onDelete: 'cascade' }), lastEditedBy: text('last_edited_by').references(() => user.id), createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), - updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow() + updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow().$onUpdate(() => new Date()) }); export const transactionLedger = pgTable('currency_transactions', { @@ -188,11 +191,10 @@ export const shopOrder = pgTable('shop_orders', { id: text('id').primaryKey().$defaultFn(() => createId()), userId: text('user_id').references(() => user.id, { onDelete: 'set null' }), pathway: pathwayEnum('pathway').notNull().references(() => pathwayShop.pathway, { onDelete: 'cascade' }), - status: shopOrderStatusEnum('order_stauts').notNull().default("PENDING"), + status: shopOrderStatusEnum('order_status').notNull().default("PENDING"), totalAmount: integer('amount').notNull(), item: text('shop_item_id').references(() => shopItem.id, { onDelete: 'set null' }), itemPriceSnapshot: integer('item_price_snapshot').notNull(), - itemTypeSnapshot: shopItemTypeEnum('item_type_enum'), itemNameSnapshot: text('item_name_snapshot').notNull(), shippingAddress: jsonb('shipping_address').$type(), phone: text('phone'), @@ -203,7 +205,7 @@ export const shopOrder = pgTable('shop_orders', { fufilledAt: timestamp('fufilled_at', { mode: 'date' }), cancelledReason: text('cancelled_reason'), createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), - updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow().$onUpdate(() => new Date()), }) // Relations diff --git a/resolution-frontend/src/lib/server/devSeed.ts b/resolution-frontend/src/lib/server/devSeed.ts index e0139ff..382034f 100644 --- a/resolution-frontend/src/lib/server/devSeed.ts +++ b/resolution-frontend/src/lib/server/devSeed.ts @@ -41,7 +41,6 @@ export async function seedDevShops() { description: `A test ${label} sticker seeded automatically in dev. Remove me before going live.`, price: 5, stock: 25, - itemType: 'PHYSICAL', isActive: true }); } diff --git a/resolution-frontend/src/lib/shop/utils.ts b/resolution-frontend/src/lib/shop/utils.ts new file mode 100644 index 0000000..ad81c03 --- /dev/null +++ b/resolution-frontend/src/lib/shop/utils.ts @@ -0,0 +1,40 @@ +import { db } from '$lib/server/db'; +import { + userPathway, + pathwayShop, +} from '$lib/server/db/schema'; +import { and, eq } from 'drizzle-orm'; +import { error, redirect } from '@sveltejs/kit'; +import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; + +type DbOrTx = typeof db | Parameters[0]>[0]; + +export class shopError extends Error { + constructor(public status: number, public body: { message: string }) { + super(body.message); + } +} + +export async function assertShopAccess(userId: string, pathwayParam: string, conn: DbOrTx = db) { + const pathwayId = pathwayParam.toUpperCase(); + if (!PATHWAY_IDS.includes(pathwayId as PathwayId)) throw error(404, 'Pathway not found'); + const typedPathwayId = pathwayId as PathwayId; + + const membership = await conn + .select() + .from(userPathway) + .where(and(eq(userPathway.userId, userId), eq(userPathway.pathway, typedPathwayId))) + .limit(1); + if (membership.length === 0) throw redirect(302, '/app'); + + const pathwayShopRow = await conn + .select() + .from(pathwayShop) + .where(eq(pathwayShop.pathway, typedPathwayId)) + .limit(1); + if (pathwayShopRow.length === 0 || !pathwayShopRow[0].isEnabled) { + throw error(404); + } + + return { typedPathwayId, shop: pathwayShopRow[0] }; +} \ No newline at end of file diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+layout.server.ts b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+layout.server.ts new file mode 100644 index 0000000..2c03a96 --- /dev/null +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+layout.server.ts @@ -0,0 +1,24 @@ +import type { LayoutServerLoad } from './$types'; +import { db } from '$lib/server/db'; +import { ambassadorPathway } from '$lib/server/db/schema'; +import { and, eq } from 'drizzle-orm'; +import { error } from '@sveltejs/kit'; +import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; + +export const load: LayoutServerLoad = async ({ params, parent }) => { + const { user } = await parent(); + + const raw = params.pathway.toUpperCase(); + if (!PATHWAY_IDS.includes(raw)) throw error(404, 'Pathway not found'); + const pathwayId = raw as PathwayId; + + const isAmbassador = await db.query.ambassadorPathway.findFirst({ + where: and( + eq(ambassadorPathway.userId, user.id), + eq(ambassadorPathway.pathway, pathwayId) + ) + }); + if (!isAmbassador) throw error(403, 'You are not an ambassador for this pathway'); + + return { pathwayId }; +}; diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+page.server.ts new file mode 100644 index 0000000..96ee189 --- /dev/null +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+page.server.ts @@ -0,0 +1,40 @@ +import type { PageServerLoad } from './$types'; +import { db } from '$lib/server/db'; +import { pathwayShop, shopItem, shopOrder } from '$lib/server/db/schema'; +import { and, desc, eq, notInArray } from 'drizzle-orm'; +import { error } from '@sveltejs/kit'; + +export const load: PageServerLoad = async ({ parent }) => { + const { pathwayId } = await parent(); + + const shop = await db.query.pathwayShop.findFirst({ + where: eq(pathwayShop.pathway, pathwayId) + }); + if (!shop) throw error(404, 'Shop not found for this pathway'); + + const items = await db + .select() + .from(shopItem) + .where(eq(shopItem.pathway, pathwayId)) + .orderBy(desc(shopItem.createdAt)); + + const totalOrders = await db.$count(shopOrder, eq(shopOrder.pathway, pathwayId)); + const awaitingFufillment = await db.$count( + shopOrder, + and( + eq(shopOrder.pathway, pathwayId), + notInArray(shopOrder.status, ['FULFILLED', 'CANCELED']) + ) + ); + + return { + shop: { + isEnabled: shop.isEnabled, + currencyName: shop.currencyName, + currencyNamePlural: shop.currencyNamePlural + }, + items, + totalOrders, + awaitingFufillment + }; +}; diff --git a/resolution-frontend/src/routes/app/ambassador/shop/+page.svelte b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+page.svelte similarity index 100% rename from resolution-frontend/src/routes/app/ambassador/shop/+page.svelte rename to resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+page.svelte diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.server.ts new file mode 100644 index 0000000..8ef124c --- /dev/null +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.server.ts @@ -0,0 +1,247 @@ +//TODO: ACTUALLY DO THIS +import type { PageServerLoad, Actions } from './$types'; +import { db } from '$lib/server/db'; +import { + shopItem, + pathwayEnum, + ambassadorPathway, + warehouseOrderTemplate, + warehouseOrderTemplateItem, + warehouseItem +} from '$lib/server/db/schema'; +import { and, eq, or } from 'drizzle-orm'; +import { error, redirect } from '@sveltejs/kit'; +// import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; +import { z } from 'zod'; +import { PATHWAYS, type PathwayId } from '$lib/pathways'; + +const pathwayIdSchema = z.enum( + PATHWAYS.map((p) => p.id) as [PathwayId, ...PathwayId[]] +); + +const addCustomItemSchema = z.object({ // for grants + name: z.string().min(1).max(50), + description: z.string().min(1).max(2000), + imageUrl: z.url(), + price: z.coerce.number().int(), + stock: z.coerce.number().int(), + isActive: z.coerce.boolean().default(false) // used because ambassador may not want it to be instantly available +}) + +const addWarehouseItemSchema = z.object({ + id: z.string().min(1), // item ID from warehouse + // auto pull from warehouse but add optional overrides + name: z.string().min(1).max(50).or(z.string().max(0)), + description: z.string().min(1), // required bcs warehouse doesn't provide + imageUrl: z.url().optional(), + price: z.int(), + // stock should be auto populated + // stock: z.int().optional(), // optional stock override, check later that this is not over current stock + isActive: z.boolean().default(false) +}) + +const addWarehouseTemplateSchema = z.object({ + id: z.string().min(1), // template ID from warehouse + name: z.string().min(1).max(50).or(z.string().max(0)), // not necessarily going to match template name + description: z.string().min(1).max(2000), + imageUrl: z.url(), + price: z.int(), + // should be autopopulated + // stock: z.int(), // set max as current stock + isActive: z.boolean().default(false) +}) + +export const load: PageServerLoad = async ({ parent }) => { + const { user, pathwayId } = await parent(); + + const warehouseTemplates = await db + .select() + .from(warehouseOrderTemplate) + .where(or(eq(warehouseOrderTemplate.createdById, user.id), eq(warehouseOrderTemplate.isPublic, true))); + + const warehouseItems = await db.select().from(warehouseItem); + + return { warehouseTemplates, warehouseItems, pathwayId }; +}; + +export const actions: Actions = { + createCustom: async ({ request, params, locals }) => { + if (!locals.user) throw redirect(302, '/api/auth/login'); + const userId = locals.user.id; + + // pathway validation from param + const rawPathway = params.pathway?.toUpperCase() ?? ''; + const pathwayParsed = pathwayIdSchema.safeParse(rawPathway); + if (!pathwayParsed.success) throw error(404, 'Pathway not found'); + const pathwayId = pathwayParsed.data; + + // make sure its gated to ambassadors + const assignment = await db.query.ambassadorPathway.findFirst({ + where: and(eq(ambassadorPathway.userId, userId), eq(ambassadorPathway.pathway, pathwayId)) + }); + if (!assignment) throw error(403, 'You are not authorized to do this action'); + + const form = Object.fromEntries(await request.formData()); + const addItemObj = addCustomItemSchema.safeParse(form); + if (!addItemObj.success) throw error(400, 'Invalid request data'); + + const data = addItemObj.data; + + const [inserted] = await db + .insert(shopItem) + .values({ + pathway: pathwayId, + name: data.name, + description: data.description, + itemImageUrl: data.imageUrl, + price: data.price, + stock: data.stock, + isActive: data.isActive, + sourceType: 'CUSTOM', + lastEditedBy: userId + }) + .returning(); + + return { success: true, item: inserted }; + }, + createWarehouse: async ({ request, params, locals }) => { + if (!locals.user) throw redirect(302, '/api/auth/login'); + const userId = locals.user.id; + + // pathway validation from param + const rawPathway = params.pathway?.toUpperCase() ?? ''; + const pathwayParsed = pathwayIdSchema.safeParse(rawPathway); + if (!pathwayParsed.success) throw error(404, 'Pathway not found'); + const pathwayId = pathwayParsed.data; + + // make sure its gated to ambassadors + const assignment = await db.query.ambassadorPathway.findFirst({ + where: and(eq(ambassadorPathway.userId, userId), eq(ambassadorPathway.pathway, pathwayId)) + }); + if (!assignment) throw error(403, 'You are not authorized to do this action'); + + const form = Object.fromEntries(await request.formData()); + const addItemObj = addWarehouseItemSchema.safeParse(form); + if (!addItemObj.success) throw error(400, 'Invalid request data'); + + const data = addItemObj.data; + + const wh = await db.query.warehouseItem.findFirst({ + where: eq(warehouseItem.id, data.id) + }); + if (!wh) throw error(404, 'Warehouse item not found'); + + // form values win when non-empty/non-null; otherwise fall back to the warehouse row + const [inserted] = await db + .insert(shopItem) + .values({ + pathway: pathwayId, + name: data.name?.trim() || wh.name, + description: data.description, // required from form + itemImageUrl: data.imageUrl ?? wh.imageUrl, + price: data.price, + stock: wh.quantity, + isActive: data.isActive, + sourceType: 'WAREHOUSE_ITEM', + linkedWarehouseItemId: wh.id, + lastEditedBy: userId + }) + .returning(); + + return { success: true, item: inserted }; + + }, + createWarehouseTemplate: async ({ request, params, locals }) => { + if (!locals.user) throw redirect(302, '/api/auth/login'); + const userId = locals.user.id; + + // pathway validation from param + const rawPathway = params.pathway?.toUpperCase() ?? ''; + const pathwayParsed = pathwayIdSchema.safeParse(rawPathway); + if (!pathwayParsed.success) throw error(404, 'Pathway not found'); + const pathwayId = pathwayParsed.data; + + // make sure its gated to ambassadors + const assignment = await db.query.ambassadorPathway.findFirst({ + where: and(eq(ambassadorPathway.userId, userId), eq(ambassadorPathway.pathway, pathwayId)) + }); + if (!assignment) throw error(403, 'You are not authorized to do this action'); + + const form = Object.fromEntries(await request.formData()); + const addItemObj = addWarehouseTemplateSchema.safeParse(form); + if (!addItemObj.success) throw error(400, 'Invalid request data'); + + const data = addItemObj.data; + + // fetch template, ensure access + const tpl = await db.query.warehouseOrderTemplate.findFirst({ + where: eq(warehouseOrderTemplate.id, data.id) + }); + if (!tpl) throw error(404, 'Warehouse template not found'); + if (tpl.createdById !== userId && !tpl.isPublic) { + throw error(403, 'You cannot use this template'); + } + + // find max stock based on the stock of items within the template + const tplItems = await db + .select({ + perOrder: warehouseOrderTemplateItem.quantity, + available: warehouseItem.quantity + }) + .from(warehouseOrderTemplateItem) + .innerJoin( + warehouseItem, + eq(warehouseItem.id, warehouseOrderTemplateItem.warehouseItemId) + ) + .where(eq(warehouseOrderTemplateItem.templateId, tpl.id)); + + if (tplItems.length === 0) throw error(400, 'Template has no items'); + + // should be autopopulated + const stock = Math.min( + ...tplItems.map((i) => Math.floor(i.available / i.perOrder)) + ); + + // form values win when provided; otherwise fall back to the template's name. + const [inserted] = await db + .insert(shopItem) + .values({ + pathway: pathwayId, + name: data.name?.trim() || tpl.name, + description: data.description, + itemImageUrl: data.imageUrl, + price: data.price, + stock, + isActive: data.isActive, + sourceType: 'WAREHOUSE_TEMPLATE', + linkedWarehouseTemplateId: tpl.id, + lastEditedBy: userId + }) + .returning(); + + return { success: true, item: inserted }; + } +} + +// TODO: use this on item loads +// const items = await db +// .select({ +// id: shopItem.id, +// name: shopItem.name, +// sourceType: shopItem.sourceType, +// // … +// effectiveStock: sql` +// CASE +// WHEN ${shopItem.sourceType} = 'WAREHOUSE_ITEM' THEN ${warehouseItem.stock} +// WHEN ${shopItem.sourceType} = 'WAREHOUSE_TEMPLATE' THEN ( +// SELECT MIN(wi.stock / wt.quantity) +// FROM warehouse_order_template_item wt +// JOIN warehouse_item wi ON wi.id = wt.warehouse_item_id +// WHERE wt.template_id = ${shopItem.linkedWarehouseTemplateId} +// ) +// ELSE ${shopItem.stock} +// END +// ` +// }) +// .from(shopItem) +// .leftJoin(warehouseItem, eq(warehouseItem.id, shopItem.linkedWarehouseItemId)); \ No newline at end of file diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.svelte b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.svelte new file mode 100644 index 0000000..e69de29 diff --git a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts similarity index 100% rename from resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.server.ts rename to resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts diff --git a/resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.svelte similarity index 100% rename from resolution-frontend/src/routes/app/ambassador/shop/fufill/+page.svelte rename to resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.svelte diff --git a/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts deleted file mode 100644 index afd6fbc..0000000 --- a/resolution-frontend/src/routes/app/ambassador/shop/+page.server.ts +++ /dev/null @@ -1 +0,0 @@ -//TODO: ACTUALLY DO THIS \ No newline at end of file diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts index 9283392..dd633cc 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.server.ts @@ -12,14 +12,8 @@ import { error, fail, redirect } from '@sveltejs/kit'; import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; import { z } from 'zod'; import { addressSchema, validateFormData } from '$lib/server/validation'; - -// thrown inside transactions to abort + roll back; caught outside to convert to fail() -class ShopError extends Error { - constructor(public status: number, public body: { message: string }) { - super(body.message); - } -} - +import { assertShopAccess, shopError } from '$lib/shop/utils' +import { guardAdminOrAmbassador } from '$lib/server/auth/guard'; const purchaseSchema = z.object({ itemId: z.string().min(1), @@ -32,35 +26,6 @@ const cancelSchema = z.object({ cancelReason: z.string().min(1) }); -type DbOrTx = typeof db | Parameters[0]>[0]; - -// guard for load + actions -// returns pathway ID and the shop item -// pass `tx` when calling inside a transaction so the re-check uses the same snapshot -async function assertShopAccess(userId: string, pathwayParam: string, conn: DbOrTx = db) { - const pathwayId = pathwayParam.toUpperCase(); - if (!PATHWAY_IDS.includes(pathwayId as PathwayId)) throw error(404, 'Pathway not found'); - const typedPathwayId = pathwayId as PathwayId; - - const membership = await conn - .select() - .from(userPathway) - .where(and(eq(userPathway.userId, userId), eq(userPathway.pathway, typedPathwayId))) - .limit(1); - if (membership.length === 0) throw redirect(302, '/app'); - - const pathwayShopRow = await conn - .select() - .from(pathwayShop) - .where(eq(pathwayShop.pathway, typedPathwayId)) - .limit(1); - if (pathwayShopRow.length === 0 || !pathwayShopRow[0].isEnabled) { - throw error(404); - } - - return { typedPathwayId, shop: pathwayShopRow[0] }; -} - export const load: PageServerLoad = async ({ params, parent }) => { const { user } = await parent(); const { typedPathwayId, shop } = await assertShopAccess(user.id, params.pathway); @@ -205,7 +170,7 @@ export const actions: Actions = { )) .limit(1); - if (!order) throw new ShopError(404, { message: 'No such order' }); + if (!order) throw new shopError(404, { message: 'No such order' }); await tx.update(shopOrder) .set({ status: 'CANCELED', cancelledReason: cancelData.cancelReason }) @@ -236,7 +201,7 @@ export const actions: Actions = { }); }); } catch (e) { - if (e instanceof ShopError) return fail(e.status, e.body); + if (e instanceof shopError) return fail(e.status, e.body); throw e; } diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte index 87499cd..c05efd7 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/+page.svelte @@ -94,9 +94,6 @@

{item.name}

- - {item.itemType === 'PHYSICAL' ? 'Physical' : 'Digital'} -

{item.description}

diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts index 1422239..216eb97 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.server.ts @@ -166,7 +166,6 @@ export const actions = { totalAmount: item.price, item: item.id, itemPriceSnapshot: item.price, - itemTypeSnapshot: item.itemType, itemNameSnapshot: item.name, shippingAddress, phone: buyData.phone, diff --git a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte index f719b53..19416f7 100644 --- a/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte +++ b/resolution-frontend/src/routes/app/pathway/[pathway]/shop/[id]/+page.svelte @@ -66,9 +66,6 @@ }}>
- - {data.item.itemType === 'PHYSICAL' ? 'Physical item' : 'Digital item'} -

Complete your order

{data.item.description}

From 124554594f82e1d7e0234a4e0f921968cbd90c0c Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Fri, 15 May 2026 23:58:13 -0400 Subject: [PATCH 47/52] feat(db): regen baseline db migration --- .../0000_baseline.sql | 0 .../drizzle.backup2/meta/0000_snapshot.json | 3111 +++++++++++++++++ .../drizzle.backup2/meta/_journal.json | 13 + .../drizzle/0000_redundant_gamora.sql | 424 +++ .../drizzle/meta/0000_snapshot.json | 79 +- .../drizzle/meta/_journal.json | 4 +- 6 files changed, 3606 insertions(+), 25 deletions(-) rename resolution-frontend/{drizzle => drizzle.backup2}/0000_baseline.sql (100%) create mode 100644 resolution-frontend/drizzle.backup2/meta/0000_snapshot.json create mode 100644 resolution-frontend/drizzle.backup2/meta/_journal.json create mode 100644 resolution-frontend/drizzle/0000_redundant_gamora.sql diff --git a/resolution-frontend/drizzle/0000_baseline.sql b/resolution-frontend/drizzle.backup2/0000_baseline.sql similarity index 100% rename from resolution-frontend/drizzle/0000_baseline.sql rename to resolution-frontend/drizzle.backup2/0000_baseline.sql diff --git a/resolution-frontend/drizzle.backup2/meta/0000_snapshot.json b/resolution-frontend/drizzle.backup2/meta/0000_snapshot.json new file mode 100644 index 0000000..5b167e5 --- /dev/null +++ b/resolution-frontend/drizzle.backup2/meta/0000_snapshot.json @@ -0,0 +1,3111 @@ +{ + "id": "e2384d76-2d19-41ed-bd29-897f1528c49d", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ambassador_pathway": { + "name": "ambassador_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "ambassador_pathway_unique_idx": { + "name": "ambassador_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ambassador_pathway_user_id_user_id_fk": { + "name": "ambassador_pathway_user_id_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_pathway_assigned_by_user_id_fk": { + "name": "ambassador_pathway_assigned_by_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout": { + "name": "ambassador_payout", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "payout_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_ambassador_id_user_id_fk": { + "name": "ambassador_payout_ambassador_id_user_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_season_id_program_season_id_fk": { + "name": "ambassador_payout_season_id_program_season_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout_item": { + "name": "ambassador_payout_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "payout_id": { + "name": "payout_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_count": { + "name": "completion_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "rate_cents_per_completion": { + "name": "rate_cents_per_completion", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_item_payout_id_ambassador_payout_id_fk": { + "name": "ambassador_payout_item_payout_id_ambassador_payout_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "ambassador_payout", + "columnsFrom": [ + "payout_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_item_workshop_id_workshop_id_fk": { + "name": "ambassador_payout_item_workshop_id_workshop_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_shop": { + "name": "pathway_shop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_name": { + "name": "currency_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wish'" + }, + "currency_name_plural": { + "name": "currency_name_plural", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wishes'" + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "pathway_shop_last_edited_by_user_id_fk": { + "name": "pathway_shop_last_edited_by_user_id_fk", + "tableFrom": "pathway_shop", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "pathway_shop_pathway_unique": { + "name": "pathway_shop_pathway_unique", + "nullsNotDistinct": false, + "columns": [ + "pathway" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_week_content": { + "name": "pathway_week_content", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "prize_image_url": { + "name": "prize_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_submissions_open": { + "name": "is_submissions_open", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pathway_week_content_unique_idx": { + "name": "pathway_week_content_unique_idx", + "columns": [ + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pathway_week_content_last_edited_by_user_id_fk": { + "name": "pathway_week_content_last_edited_by_user_id_fk", + "tableFrom": "pathway_week_content", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_enrollment": { + "name": "program_enrollment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "enrollment_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "enrollment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "starting_week": { + "name": "starting_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "enrollment_user_season_role_idx": { + "name": "enrollment_user_season_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_enrollment_user_id_user_id_fk": { + "name": "program_enrollment_user_id_user_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "program_enrollment_season_id_program_season_id_fk": { + "name": "program_enrollment_season_id_program_season_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_season": { + "name": "program_season", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signup_opens_at": { + "name": "signup_opens_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "signup_closes_at": { + "name": "signup_closes_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "total_weeks": { + "name": "total_weeks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 8 + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "program_season_slug_unique": { + "name": "program_season_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_link": { + "name": "referral_link", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "referral_link_ambassador_id_user_id_fk": { + "name": "referral_link_ambassador_id_user_id_fk", + "tableFrom": "referral_link", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "referral_link_code_unique": { + "name": "referral_link_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_signup": { + "name": "referral_signup", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "referral_link_id": { + "name": "referral_link_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "referral_signup_unique_idx": { + "name": "referral_signup_unique_idx", + "columns": [ + { + "expression": "referral_link_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "referral_signup_referral_link_id_referral_link_id_fk": { + "name": "referral_signup_referral_link_id_referral_link_id_fk", + "tableFrom": "referral_signup", + "tableTo": "referral_link", + "columnsFrom": [ + "referral_link_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_signup_user_id_user_id_fk": { + "name": "referral_signup_user_id_user_id_fk", + "tableFrom": "referral_signup", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reviewer_pathway": { + "name": "reviewer_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "reviewer_pathway_unique_idx": { + "name": "reviewer_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reviewer_pathway_user_id_user_id_fk": { + "name": "reviewer_pathway_user_id_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reviewer_pathway_assigned_by_user_id_fk": { + "name": "reviewer_pathway_assigned_by_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_item": { + "name": "shop_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_url": { + "name": "item_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price": { + "name": "item_price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_stock": { + "name": "item_stock", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "item_type": { + "name": "item_type", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_item_pathway_pathway_shop_pathway_fk": { + "name": "shop_item_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_item", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_item_last_edited_by_user_id_fk": { + "name": "shop_item_last_edited_by_user_id_fk", + "tableFrom": "shop_item", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_orders": { + "name": "shop_orders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "order_stauts": { + "name": "order_stauts", + "type": "shop_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "shop_item_id": { + "name": "shop_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price_snapshot": { + "name": "item_price_snapshot", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_type_enum": { + "name": "item_type_enum", + "type": "shop_item_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "item_name_snapshot": { + "name": "item_name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "shipping_address": { + "name": "shipping_address", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_notes": { + "name": "user_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufiller_notes": { + "name": "fufiller_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_by": { + "name": "fufilled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_at": { + "name": "fufilled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_reason": { + "name": "cancelled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_orders_user_id_user_id_fk": { + "name": "shop_orders_user_id_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_pathway_pathway_shop_pathway_fk": { + "name": "shop_orders_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_orders", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_orders_shop_item_id_shop_item_id_fk": { + "name": "shop_orders_shop_item_id_shop_item_id_fk", + "tableFrom": "shop_orders", + "tableTo": "shop_item", + "columnsFrom": [ + "shop_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_fufilled_by_user_id_fk": { + "name": "shop_orders_fufilled_by_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "fufilled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.currency_transactions": { + "name": "currency_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tx_user_id": { + "name": "tx_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_pathway": { + "name": "tx_pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_amount": { + "name": "tx_amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tx_reason": { + "name": "tx_reason", + "type": "currency_txn_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_note": { + "name": "tx_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_granted_by": { + "name": "tx_granted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_type": { + "name": "tx_ref_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_id": { + "name": "tx_ref_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "currency_transactions_tx_user_id_user_id_fk": { + "name": "currency_transactions_tx_user_id_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "currency_transactions_tx_pathway_pathway_shop_pathway_fk": { + "name": "currency_transactions_tx_pathway_pathway_shop_pathway_fk", + "tableFrom": "currency_transactions", + "tableTo": "pathway_shop", + "columnsFrom": [ + "tx_pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "currency_transactions_tx_granted_by_user_id_fk": { + "name": "currency_transactions_tx_granted_by_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hack_club_id": { + "name": "hack_club_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_id": { + "name": "slack_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verification_status": { + "name": "verification_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ysws_eligible": { + "name": "ysws_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_hack_club_id_unique": { + "name": "user_hack_club_id_unique", + "nullsNotDistinct": false, + "columns": [ + "hack_club_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_pathway": { + "name": "user_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_pathway_unique_idx": { + "name": "user_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_pathway_user_id_user_id_fk": { + "name": "user_pathway_user_id_user_id_fk", + "tableFrom": "user_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_batch": { + "name": "warehouse_batch", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "warehouse_batch_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'AWAITING_MAPPING'" + }, + "csv_data": { + "name": "csv_data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_mapping": { + "name": "field_mapping", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_count": { + "name": "address_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_batch_created_by_id_user_id_fk": { + "name": "warehouse_batch_created_by_id_user_id_fk", + "tableFrom": "warehouse_batch", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "warehouse_batch_template_id_warehouse_order_template_id_fk": { + "name": "warehouse_batch_template_id_warehouse_order_template_id_fk", + "tableFrom": "warehouse_batch", + "tableTo": "warehouse_order_template", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_batch_tag": { + "name": "warehouse_batch_tag", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "batch_id": { + "name": "batch_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "warehouse_batch_tag_unique_idx": { + "name": "warehouse_batch_tag_unique_idx", + "columns": [ + { + "expression": "batch_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "warehouse_batch_tag_batch_id_warehouse_batch_id_fk": { + "name": "warehouse_batch_tag_batch_id_warehouse_batch_id_fk", + "tableFrom": "warehouse_batch_tag", + "tableTo": "warehouse_batch", + "columnsFrom": [ + "batch_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_category": { + "name": "warehouse_category", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_item": { + "name": "warehouse_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sku": { + "name": "sku", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sizing": { + "name": "sizing", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_type": { + "name": "package_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'box'" + }, + "length_in": { + "name": "length_in", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "width_in": { + "name": "width_in", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "height_in": { + "name": "height_in", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "weight_grams": { + "name": "weight_grams", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "hs_code": { + "name": "hs_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_item_category_id_warehouse_category_id_fk": { + "name": "warehouse_item_category_id_warehouse_category_id_fk", + "tableFrom": "warehouse_item", + "tableTo": "warehouse_category", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "warehouse_item_sku_unique": { + "name": "warehouse_item_sku_unique", + "nullsNotDistinct": false, + "columns": [ + "sku" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order": { + "name": "warehouse_order", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "fulfillment_id": { + "name": "fulfillment_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "identity": { + "type": "always", + "name": "warehouse_order_fulfillment_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "batch_id": { + "name": "batch_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "warehouse_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_line_1": { + "name": "address_line_1", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address_line_2": { + "name": "address_line_2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_province": { + "name": "state_province", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "postal_code": { + "name": "postal_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "estimated_shipping_cents": { + "name": "estimated_shipping_cents", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "estimated_duties_cents": { + "name": "estimated_duties_cents", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "estimated_service_name": { + "name": "estimated_service_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_service_code": { + "name": "estimated_service_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_package_type": { + "name": "estimated_package_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_total_length_in": { + "name": "estimated_total_length_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "estimated_total_width_in": { + "name": "estimated_total_width_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "estimated_total_height_in": { + "name": "estimated_total_height_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "estimated_total_weight_grams": { + "name": "estimated_total_weight_grams", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_category": { + "name": "packaging_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "packaging_label": { + "name": "packaging_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "packaging_length_in": { + "name": "packaging_length_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_width_in": { + "name": "packaging_width_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_height_in": { + "name": "packaging_height_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_subject_to_change": { + "name": "packaging_subject_to_change", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tracking_number": { + "name": "tracking_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "label_url": { + "name": "label_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shipping_method": { + "name": "shipping_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_status": { + "name": "billing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + }, + "billing_failure_reason": { + "name": "billing_failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_order_created_by_id_user_id_fk": { + "name": "warehouse_order_created_by_id_user_id_fk", + "tableFrom": "warehouse_order", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "warehouse_order_fulfillment_id_unique": { + "name": "warehouse_order_fulfillment_id_unique", + "nullsNotDistinct": false, + "columns": [ + "fulfillment_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order_item": { + "name": "warehouse_order_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "order_id": { + "name": "order_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "warehouse_item_id": { + "name": "warehouse_item_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "sizing_choice": { + "name": "sizing_choice", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "warehouse_order_item_order_id_idx": { + "name": "warehouse_order_item_order_id_idx", + "columns": [ + { + "expression": "order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "warehouse_order_item_warehouse_item_id_idx": { + "name": "warehouse_order_item_warehouse_item_id_idx", + "columns": [ + { + "expression": "warehouse_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "warehouse_order_item_order_id_warehouse_order_id_fk": { + "name": "warehouse_order_item_order_id_warehouse_order_id_fk", + "tableFrom": "warehouse_order_item", + "tableTo": "warehouse_order", + "columnsFrom": [ + "order_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "warehouse_order_item_warehouse_item_id_warehouse_item_id_fk": { + "name": "warehouse_order_item_warehouse_item_id_warehouse_item_id_fk", + "tableFrom": "warehouse_order_item", + "tableTo": "warehouse_item", + "columnsFrom": [ + "warehouse_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order_tag": { + "name": "warehouse_order_tag", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "order_id": { + "name": "order_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "warehouse_order_tag_unique_idx": { + "name": "warehouse_order_tag_unique_idx", + "columns": [ + { + "expression": "order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "warehouse_order_tag_tag_idx": { + "name": "warehouse_order_tag_tag_idx", + "columns": [ + { + "expression": "tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "warehouse_order_tag_order_id_warehouse_order_id_fk": { + "name": "warehouse_order_tag_order_id_warehouse_order_id_fk", + "tableFrom": "warehouse_order_tag", + "tableTo": "warehouse_order", + "columnsFrom": [ + "order_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order_template": { + "name": "warehouse_order_template", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_order_template_created_by_id_user_id_fk": { + "name": "warehouse_order_template_created_by_id_user_id_fk", + "tableFrom": "warehouse_order_template", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order_template_item": { + "name": "warehouse_order_template_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "warehouse_item_id": { + "name": "warehouse_item_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_order_template_item_template_id_warehouse_order_template_id_fk": { + "name": "warehouse_order_template_item_template_id_warehouse_order_template_id_fk", + "tableFrom": "warehouse_order_template_item", + "tableTo": "warehouse_order_template", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "warehouse_order_template_item_warehouse_item_id_warehouse_item_id_fk": { + "name": "warehouse_order_template_item_warehouse_item_id_warehouse_item_id_fk", + "tableFrom": "warehouse_order_template_item", + "tableTo": "warehouse_item", + "columnsFrom": [ + "warehouse_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.weekly_ship": { + "name": "weekly_ship", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "goal_text": { + "name": "goal_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "ship_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PLANNED'" + }, + "proof_url": { + "name": "proof_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shipped_at": { + "name": "shipped_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ship_user_season_week_idx": { + "name": "ship_user_season_week_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "weekly_ship_user_id_user_id_fk": { + "name": "weekly_ship_user_id_user_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_season_id_program_season_id_fk": { + "name": "weekly_ship_season_id_program_season_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_workshop_id_workshop_id_fk": { + "name": "weekly_ship_workshop_id_workshop_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop": { + "name": "workshop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "difficulty": { + "name": "difficulty", + "type": "difficulty", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "estimated_hours": { + "name": "estimated_hours", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_author_id_user_id_fk": { + "name": "workshop_author_id_user_id_fk", + "tableFrom": "workshop", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_season_id_program_season_id_fk": { + "name": "workshop_season_id_program_season_id_fk", + "tableFrom": "workshop", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_analytics": { + "name": "workshop_analytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "starts": { + "name": "starts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completions": { + "name": "completions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "avg_completion_mins": { + "name": "avg_completion_mins", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_analytics_workshop_id_workshop_id_fk": { + "name": "workshop_analytics_workshop_id_workshop_id_fk", + "tableFrom": "workshop_analytics", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workshop_analytics_workshop_id_unique": { + "name": "workshop_analytics_workshop_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workshop_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_completion": { + "name": "workshop_completion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_url": { + "name": "project_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_workshop_participant_season_idx": { + "name": "completion_workshop_participant_season_idx", + "columns": [ + { + "expression": "workshop_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workshop_completion_workshop_id_workshop_id_fk": { + "name": "workshop_completion_workshop_id_workshop_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_participant_id_user_id_fk": { + "name": "workshop_completion_participant_id_user_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "user", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_season_id_program_season_id_fk": { + "name": "workshop_completion_season_id_program_season_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.currency_txn_reason": { + "name": "currency_txn_reason", + "schema": "public", + "values": [ + "GRANT", + "PURCHASE", + "REFUND", + "ADJUSTMENT", + "OTHER" + ] + }, + "public.difficulty": { + "name": "difficulty", + "schema": "public", + "values": [ + "BEGINNER", + "INTERMEDIATE", + "ADVANCED" + ] + }, + "public.enrollment_role": { + "name": "enrollment_role", + "schema": "public", + "values": [ + "PARTICIPANT", + "AMBASSADOR" + ] + }, + "public.enrollment_status": { + "name": "enrollment_status", + "schema": "public", + "values": [ + "ACTIVE", + "DROPPED", + "COMPLETED" + ] + }, + "public.pathway": { + "name": "pathway", + "schema": "public", + "values": [ + "PYTHON", + "RUST", + "GAME_DEV", + "HARDWARE", + "DESIGN", + "GENERAL_CODING" + ] + }, + "public.payout_status": { + "name": "payout_status", + "schema": "public", + "values": [ + "DRAFT", + "PENDING", + "PAID", + "CANCELED" + ] + }, + "public.ship_status": { + "name": "ship_status", + "schema": "public", + "values": [ + "PLANNED", + "IN_PROGRESS", + "SHIPPED", + "MISSED" + ] + }, + "public.shop_item_type": { + "name": "shop_item_type", + "schema": "public", + "values": [ + "PHYSICAL", + "DIGITAL" + ] + }, + "public.shop_order_status": { + "name": "shop_order_status", + "schema": "public", + "values": [ + "PENDING", + "PROCESSING", + "FULFILLED", + "CANCELED" + ] + }, + "public.warehouse_batch_status": { + "name": "warehouse_batch_status", + "schema": "public", + "values": [ + "AWAITING_MAPPING", + "MAPPED", + "PROCESSED" + ] + }, + "public.warehouse_order_status": { + "name": "warehouse_order_status", + "schema": "public", + "values": [ + "DRAFT", + "ESTIMATED", + "APPROVED", + "SHIPPED", + "CANCELLED" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/resolution-frontend/drizzle.backup2/meta/_journal.json b/resolution-frontend/drizzle.backup2/meta/_journal.json new file mode 100644 index 0000000..ae2f559 --- /dev/null +++ b/resolution-frontend/drizzle.backup2/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1778709057473, + "tag": "0000_baseline", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/resolution-frontend/drizzle/0000_redundant_gamora.sql b/resolution-frontend/drizzle/0000_redundant_gamora.sql new file mode 100644 index 0000000..a3675cb --- /dev/null +++ b/resolution-frontend/drizzle/0000_redundant_gamora.sql @@ -0,0 +1,424 @@ +CREATE TYPE "public"."currency_txn_reason" AS ENUM('GRANT', 'PURCHASE', 'REFUND', 'ADJUSTMENT', 'OTHER');--> statement-breakpoint +CREATE TYPE "public"."difficulty" AS ENUM('BEGINNER', 'INTERMEDIATE', 'ADVANCED');--> statement-breakpoint +CREATE TYPE "public"."enrollment_role" AS ENUM('PARTICIPANT', 'AMBASSADOR');--> statement-breakpoint +CREATE TYPE "public"."enrollment_status" AS ENUM('ACTIVE', 'DROPPED', 'COMPLETED');--> statement-breakpoint +CREATE TYPE "public"."pathway" AS ENUM('PYTHON', 'RUST', 'GAME_DEV', 'HARDWARE', 'DESIGN', 'GENERAL_CODING');--> statement-breakpoint +CREATE TYPE "public"."payout_status" AS ENUM('DRAFT', 'PENDING', 'PAID', 'CANCELED');--> statement-breakpoint +CREATE TYPE "public"."ship_status" AS ENUM('PLANNED', 'IN_PROGRESS', 'SHIPPED', 'MISSED');--> statement-breakpoint +CREATE TYPE "public"."shop_item_source" AS ENUM('CUSTOM', 'WAREHOUSE_ITEM', 'WAREHOUSE_TEMPLATE');--> statement-breakpoint +CREATE TYPE "public"."shop_order_status" AS ENUM('PENDING', 'PROCESSING', 'FULFILLED', 'CANCELED');--> statement-breakpoint +CREATE TYPE "public"."warehouse_batch_status" AS ENUM('AWAITING_MAPPING', 'MAPPED', 'PROCESSED');--> statement-breakpoint +CREATE TYPE "public"."warehouse_order_status" AS ENUM('DRAFT', 'ESTIMATED', 'APPROVED', 'SHIPPED', 'CANCELLED');--> statement-breakpoint +CREATE TABLE "ambassador_pathway" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "pathway" "pathway" NOT NULL, + "assigned_at" timestamp DEFAULT now() NOT NULL, + "assigned_by" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "ambassador_payout" ( + "id" text PRIMARY KEY NOT NULL, + "ambassador_id" text NOT NULL, + "season_id" text NOT NULL, + "amount_cents" integer NOT NULL, + "status" "payout_status" DEFAULT 'DRAFT' NOT NULL, + "period_start" timestamp NOT NULL, + "period_end" timestamp NOT NULL, + "paid_at" timestamp, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "ambassador_payout_item" ( + "id" text PRIMARY KEY NOT NULL, + "payout_id" text NOT NULL, + "workshop_id" text NOT NULL, + "completion_count" integer NOT NULL, + "rate_cents_per_completion" integer NOT NULL, + "amount_cents" integer NOT NULL +); +--> statement-breakpoint +CREATE TABLE "pathway_shop" ( + "id" text PRIMARY KEY NOT NULL, + "pathway" "pathway" NOT NULL, + "is_enabled" boolean DEFAULT false NOT NULL, + "currency_name" text DEFAULT 'wish' NOT NULL, + "currency_name_plural" text DEFAULT 'wishes' NOT NULL, + "last_edited_by" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "pathway_shop_pathway_unique" UNIQUE("pathway") +); +--> statement-breakpoint +CREATE TABLE "pathway_week_content" ( + "id" text PRIMARY KEY NOT NULL, + "pathway" "pathway" NOT NULL, + "week_number" integer NOT NULL, + "title" text DEFAULT '' NOT NULL, + "content" text DEFAULT '' NOT NULL, + "prize_image_url" text, + "is_published" boolean DEFAULT false NOT NULL, + "is_submissions_open" boolean DEFAULT true NOT NULL, + "last_edited_by" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "program_enrollment" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "season_id" text NOT NULL, + "role" "enrollment_role" NOT NULL, + "status" "enrollment_status" DEFAULT 'ACTIVE' NOT NULL, + "joined_at" timestamp DEFAULT now() NOT NULL, + "starting_week" integer DEFAULT 1 NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "program_season" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "slug" text NOT NULL, + "signup_opens_at" timestamp NOT NULL, + "signup_closes_at" timestamp NOT NULL, + "starts_at" timestamp NOT NULL, + "ends_at" timestamp NOT NULL, + "total_weeks" integer DEFAULT 8 NOT NULL, + "is_active" boolean DEFAULT true NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "program_season_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "referral_link" ( + "id" text PRIMARY KEY NOT NULL, + "ambassador_id" text NOT NULL, + "pathway" "pathway" NOT NULL, + "code" text NOT NULL, + "label" text, + "is_active" boolean DEFAULT true NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "referral_link_code_unique" UNIQUE("code") +); +--> statement-breakpoint +CREATE TABLE "referral_signup" ( + "id" text PRIMARY KEY NOT NULL, + "referral_link_id" text NOT NULL, + "user_id" text NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "reviewer_pathway" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "pathway" "pathway" NOT NULL, + "assigned_at" timestamp DEFAULT now() NOT NULL, + "assigned_by" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "session" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "expires_at" timestamp NOT NULL +); +--> statement-breakpoint +CREATE TABLE "shop_item" ( + "id" text PRIMARY KEY NOT NULL, + "pathway" "pathway" NOT NULL, + "name" text NOT NULL, + "description" text NOT NULL, + "item_image_url" text, + "item_price" integer NOT NULL, + "item_stock" integer, + "is_active" boolean DEFAULT false NOT NULL, + "source_type" "shop_item_source" DEFAULT 'CUSTOM' NOT NULL, + "linked_warehouse_item_id" text, + "linked_warehouse_template_id" text, + "last_edited_by" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "shop_orders" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text, + "pathway" "pathway" NOT NULL, + "order_status" "shop_order_status" DEFAULT 'PENDING' NOT NULL, + "amount" integer NOT NULL, + "shop_item_id" text, + "item_price_snapshot" integer NOT NULL, + "item_name_snapshot" text NOT NULL, + "shipping_address" jsonb, + "phone" text, + "user_notes" text, + "fufiller_notes" text, + "fufilled_by" text, + "fufilled_at" timestamp, + "cancelled_reason" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "currency_transactions" ( + "id" text PRIMARY KEY NOT NULL, + "tx_user_id" text, + "tx_pathway" "pathway" NOT NULL, + "tx_amount" integer NOT NULL, + "tx_reason" "currency_txn_reason" NOT NULL, + "tx_note" text, + "tx_granted_by" text, + "tx_ref_type" text, + "tx_ref_id" text, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "user" ( + "id" text PRIMARY KEY NOT NULL, + "email" text NOT NULL, + "hack_club_id" text NOT NULL, + "first_name" text, + "last_name" text, + "slack_id" text, + "verification_status" text, + "ysws_eligible" boolean DEFAULT false NOT NULL, + "is_admin" boolean DEFAULT false NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "user_email_unique" UNIQUE("email"), + CONSTRAINT "user_hack_club_id_unique" UNIQUE("hack_club_id") +); +--> statement-breakpoint +CREATE TABLE "user_pathway" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "pathway" "pathway" NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "warehouse_batch" ( + "id" text PRIMARY KEY NOT NULL, + "created_by_id" text NOT NULL, + "template_id" text NOT NULL, + "title" text, + "status" "warehouse_batch_status" DEFAULT 'AWAITING_MAPPING' NOT NULL, + "csv_data" text NOT NULL, + "field_mapping" text, + "address_count" integer DEFAULT 0 NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "warehouse_batch_tag" ( + "id" text PRIMARY KEY NOT NULL, + "batch_id" text NOT NULL, + "tag" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "warehouse_category" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "sort_order" integer DEFAULT 0 NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "warehouse_item" ( + "id" text PRIMARY KEY NOT NULL, + "category_id" text, + "name" text NOT NULL, + "sku" text NOT NULL, + "sizing" text, + "package_type" text DEFAULT 'box' NOT NULL, + "length_in" real NOT NULL, + "width_in" real NOT NULL, + "height_in" real NOT NULL, + "weight_grams" real NOT NULL, + "cost_cents" integer NOT NULL, + "hs_code" text DEFAULT '' NOT NULL, + "quantity" integer DEFAULT 0 NOT NULL, + "image_url" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "warehouse_item_sku_unique" UNIQUE("sku") +); +--> statement-breakpoint +CREATE TABLE "warehouse_order" ( + "id" text PRIMARY KEY NOT NULL, + "fulfillment_id" integer GENERATED ALWAYS AS IDENTITY (sequence name "warehouse_order_fulfillment_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1), + "created_by_id" text NOT NULL, + "batch_id" text, + "status" "warehouse_order_status" DEFAULT 'DRAFT' NOT NULL, + "first_name" text NOT NULL, + "last_name" text NOT NULL, + "email" text NOT NULL, + "phone" text, + "address_line_1" text NOT NULL, + "address_line_2" text, + "city" text NOT NULL, + "state_province" text NOT NULL, + "postal_code" text, + "country" text NOT NULL, + "estimated_shipping_cents" integer, + "estimated_duties_cents" integer, + "estimated_service_name" text, + "estimated_service_code" text, + "estimated_package_type" text, + "estimated_total_length_in" real, + "estimated_total_width_in" real, + "estimated_total_height_in" real, + "estimated_total_weight_grams" real, + "packaging_category" text, + "packaging_label" text, + "packaging_length_in" real, + "packaging_width_in" real, + "packaging_height_in" real, + "packaging_subject_to_change" boolean DEFAULT false NOT NULL, + "tracking_number" text, + "label_url" text, + "shipping_method" text, + "billing_status" text DEFAULT 'PENDING' NOT NULL, + "billing_failure_reason" text, + "notes" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "warehouse_order_fulfillment_id_unique" UNIQUE("fulfillment_id") +); +--> statement-breakpoint +CREATE TABLE "warehouse_order_item" ( + "id" text PRIMARY KEY NOT NULL, + "order_id" text NOT NULL, + "warehouse_item_id" text NOT NULL, + "quantity" integer DEFAULT 1 NOT NULL, + "sizing_choice" text +); +--> statement-breakpoint +CREATE TABLE "warehouse_order_tag" ( + "id" text PRIMARY KEY NOT NULL, + "order_id" text NOT NULL, + "tag" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "warehouse_order_template" ( + "id" text PRIMARY KEY NOT NULL, + "created_by_id" text NOT NULL, + "name" text NOT NULL, + "is_public" boolean DEFAULT false NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "warehouse_order_template_item" ( + "id" text PRIMARY KEY NOT NULL, + "template_id" text NOT NULL, + "warehouse_item_id" text NOT NULL, + "quantity" integer DEFAULT 1 NOT NULL +); +--> statement-breakpoint +CREATE TABLE "weekly_ship" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "season_id" text NOT NULL, + "workshop_id" text, + "week_number" integer NOT NULL, + "goal_text" text NOT NULL, + "status" "ship_status" DEFAULT 'PLANNED' NOT NULL, + "proof_url" text, + "notes" text, + "shipped_at" timestamp, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "workshop" ( + "id" text PRIMARY KEY NOT NULL, + "author_id" text NOT NULL, + "season_id" text NOT NULL, + "title" text NOT NULL, + "description" text NOT NULL, + "pathway" "pathway" NOT NULL, + "difficulty" "difficulty" NOT NULL, + "estimated_hours" integer NOT NULL, + "published" boolean DEFAULT false NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "workshop_analytics" ( + "id" text PRIMARY KEY NOT NULL, + "workshop_id" text NOT NULL, + "views" integer DEFAULT 0 NOT NULL, + "starts" integer DEFAULT 0 NOT NULL, + "completions" integer DEFAULT 0 NOT NULL, + "avg_completion_mins" real, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "workshop_analytics_workshop_id_unique" UNIQUE("workshop_id") +); +--> statement-breakpoint +CREATE TABLE "workshop_completion" ( + "id" text PRIMARY KEY NOT NULL, + "workshop_id" text NOT NULL, + "participant_id" text NOT NULL, + "season_id" text NOT NULL, + "project_url" text, + "started_at" timestamp DEFAULT now() NOT NULL, + "completed_at" timestamp +); +--> statement-breakpoint +ALTER TABLE "ambassador_pathway" ADD CONSTRAINT "ambassador_pathway_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ambassador_pathway" ADD CONSTRAINT "ambassador_pathway_assigned_by_user_id_fk" FOREIGN KEY ("assigned_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ambassador_payout" ADD CONSTRAINT "ambassador_payout_ambassador_id_user_id_fk" FOREIGN KEY ("ambassador_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ambassador_payout" ADD CONSTRAINT "ambassador_payout_season_id_program_season_id_fk" FOREIGN KEY ("season_id") REFERENCES "public"."program_season"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ambassador_payout_item" ADD CONSTRAINT "ambassador_payout_item_payout_id_ambassador_payout_id_fk" FOREIGN KEY ("payout_id") REFERENCES "public"."ambassador_payout"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ambassador_payout_item" ADD CONSTRAINT "ambassador_payout_item_workshop_id_workshop_id_fk" FOREIGN KEY ("workshop_id") REFERENCES "public"."workshop"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "pathway_shop" ADD CONSTRAINT "pathway_shop_last_edited_by_user_id_fk" FOREIGN KEY ("last_edited_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "pathway_week_content" ADD CONSTRAINT "pathway_week_content_last_edited_by_user_id_fk" FOREIGN KEY ("last_edited_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "program_enrollment" ADD CONSTRAINT "program_enrollment_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "program_enrollment" ADD CONSTRAINT "program_enrollment_season_id_program_season_id_fk" FOREIGN KEY ("season_id") REFERENCES "public"."program_season"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "referral_link" ADD CONSTRAINT "referral_link_ambassador_id_user_id_fk" FOREIGN KEY ("ambassador_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "referral_signup" ADD CONSTRAINT "referral_signup_referral_link_id_referral_link_id_fk" FOREIGN KEY ("referral_link_id") REFERENCES "public"."referral_link"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "referral_signup" ADD CONSTRAINT "referral_signup_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "reviewer_pathway" ADD CONSTRAINT "reviewer_pathway_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "reviewer_pathway" ADD CONSTRAINT "reviewer_pathway_assigned_by_user_id_fk" FOREIGN KEY ("assigned_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_item" ADD CONSTRAINT "shop_item_pathway_pathway_shop_pathway_fk" FOREIGN KEY ("pathway") REFERENCES "public"."pathway_shop"("pathway") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_item" ADD CONSTRAINT "shop_item_linked_warehouse_item_id_warehouse_item_id_fk" FOREIGN KEY ("linked_warehouse_item_id") REFERENCES "public"."warehouse_item"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_item" ADD CONSTRAINT "shop_item_linked_warehouse_template_id_warehouse_order_template_id_fk" FOREIGN KEY ("linked_warehouse_template_id") REFERENCES "public"."warehouse_order_template"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_item" ADD CONSTRAINT "shop_item_last_edited_by_user_id_fk" FOREIGN KEY ("last_edited_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_pathway_pathway_shop_pathway_fk" FOREIGN KEY ("pathway") REFERENCES "public"."pathway_shop"("pathway") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_shop_item_id_shop_item_id_fk" FOREIGN KEY ("shop_item_id") REFERENCES "public"."shop_item"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "shop_orders" ADD CONSTRAINT "shop_orders_fufilled_by_user_id_fk" FOREIGN KEY ("fufilled_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "currency_transactions" ADD CONSTRAINT "currency_transactions_tx_user_id_user_id_fk" FOREIGN KEY ("tx_user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "currency_transactions" ADD CONSTRAINT "currency_transactions_tx_pathway_pathway_shop_pathway_fk" FOREIGN KEY ("tx_pathway") REFERENCES "public"."pathway_shop"("pathway") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "currency_transactions" ADD CONSTRAINT "currency_transactions_tx_granted_by_user_id_fk" FOREIGN KEY ("tx_granted_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "user_pathway" ADD CONSTRAINT "user_pathway_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_batch" ADD CONSTRAINT "warehouse_batch_created_by_id_user_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_batch" ADD CONSTRAINT "warehouse_batch_template_id_warehouse_order_template_id_fk" FOREIGN KEY ("template_id") REFERENCES "public"."warehouse_order_template"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_batch_tag" ADD CONSTRAINT "warehouse_batch_tag_batch_id_warehouse_batch_id_fk" FOREIGN KEY ("batch_id") REFERENCES "public"."warehouse_batch"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_item" ADD CONSTRAINT "warehouse_item_category_id_warehouse_category_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."warehouse_category"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order" ADD CONSTRAINT "warehouse_order_created_by_id_user_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order_item" ADD CONSTRAINT "warehouse_order_item_order_id_warehouse_order_id_fk" FOREIGN KEY ("order_id") REFERENCES "public"."warehouse_order"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order_item" ADD CONSTRAINT "warehouse_order_item_warehouse_item_id_warehouse_item_id_fk" FOREIGN KEY ("warehouse_item_id") REFERENCES "public"."warehouse_item"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order_tag" ADD CONSTRAINT "warehouse_order_tag_order_id_warehouse_order_id_fk" FOREIGN KEY ("order_id") REFERENCES "public"."warehouse_order"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order_template" ADD CONSTRAINT "warehouse_order_template_created_by_id_user_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order_template_item" ADD CONSTRAINT "warehouse_order_template_item_template_id_warehouse_order_template_id_fk" FOREIGN KEY ("template_id") REFERENCES "public"."warehouse_order_template"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "warehouse_order_template_item" ADD CONSTRAINT "warehouse_order_template_item_warehouse_item_id_warehouse_item_id_fk" FOREIGN KEY ("warehouse_item_id") REFERENCES "public"."warehouse_item"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "weekly_ship" ADD CONSTRAINT "weekly_ship_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "weekly_ship" ADD CONSTRAINT "weekly_ship_season_id_program_season_id_fk" FOREIGN KEY ("season_id") REFERENCES "public"."program_season"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "weekly_ship" ADD CONSTRAINT "weekly_ship_workshop_id_workshop_id_fk" FOREIGN KEY ("workshop_id") REFERENCES "public"."workshop"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workshop" ADD CONSTRAINT "workshop_author_id_user_id_fk" FOREIGN KEY ("author_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workshop" ADD CONSTRAINT "workshop_season_id_program_season_id_fk" FOREIGN KEY ("season_id") REFERENCES "public"."program_season"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workshop_analytics" ADD CONSTRAINT "workshop_analytics_workshop_id_workshop_id_fk" FOREIGN KEY ("workshop_id") REFERENCES "public"."workshop"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workshop_completion" ADD CONSTRAINT "workshop_completion_workshop_id_workshop_id_fk" FOREIGN KEY ("workshop_id") REFERENCES "public"."workshop"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workshop_completion" ADD CONSTRAINT "workshop_completion_participant_id_user_id_fk" FOREIGN KEY ("participant_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workshop_completion" ADD CONSTRAINT "workshop_completion_season_id_program_season_id_fk" FOREIGN KEY ("season_id") REFERENCES "public"."program_season"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "ambassador_pathway_unique_idx" ON "ambassador_pathway" USING btree ("user_id","pathway");--> statement-breakpoint +CREATE UNIQUE INDEX "pathway_week_content_unique_idx" ON "pathway_week_content" USING btree ("pathway","week_number");--> statement-breakpoint +CREATE UNIQUE INDEX "enrollment_user_season_role_idx" ON "program_enrollment" USING btree ("user_id","season_id","role");--> statement-breakpoint +CREATE UNIQUE INDEX "referral_signup_unique_idx" ON "referral_signup" USING btree ("referral_link_id","user_id");--> statement-breakpoint +CREATE UNIQUE INDEX "reviewer_pathway_unique_idx" ON "reviewer_pathway" USING btree ("user_id","pathway");--> statement-breakpoint +CREATE UNIQUE INDEX "user_pathway_unique_idx" ON "user_pathway" USING btree ("user_id","pathway");--> statement-breakpoint +CREATE UNIQUE INDEX "warehouse_batch_tag_unique_idx" ON "warehouse_batch_tag" USING btree ("batch_id","tag");--> statement-breakpoint +CREATE INDEX "warehouse_order_item_order_id_idx" ON "warehouse_order_item" USING btree ("order_id");--> statement-breakpoint +CREATE INDEX "warehouse_order_item_warehouse_item_id_idx" ON "warehouse_order_item" USING btree ("warehouse_item_id");--> statement-breakpoint +CREATE UNIQUE INDEX "warehouse_order_tag_unique_idx" ON "warehouse_order_tag" USING btree ("order_id","tag");--> statement-breakpoint +CREATE INDEX "warehouse_order_tag_tag_idx" ON "warehouse_order_tag" USING btree ("tag");--> statement-breakpoint +CREATE INDEX "ship_user_season_week_idx" ON "weekly_ship" USING btree ("user_id","season_id","week_number");--> statement-breakpoint +CREATE UNIQUE INDEX "completion_workshop_participant_season_idx" ON "workshop_completion" USING btree ("workshop_id","participant_id","season_id"); \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/0000_snapshot.json b/resolution-frontend/drizzle/meta/0000_snapshot.json index 5b167e5..630239c 100644 --- a/resolution-frontend/drizzle/meta/0000_snapshot.json +++ b/resolution-frontend/drizzle/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "e2384d76-2d19-41ed-bd29-897f1528c49d", + "id": "04f6d62e-e75a-447d-9462-ec816fc41e19", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", @@ -1023,8 +1023,8 @@ "primaryKey": false, "notNull": true }, - "item_url": { - "name": "item_url", + "item_image_url": { + "name": "item_image_url", "type": "text", "primaryKey": false, "notNull": false @@ -1041,13 +1041,6 @@ "primaryKey": false, "notNull": false }, - "item_type": { - "name": "item_type", - "type": "shop_item_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, "is_active": { "name": "is_active", "type": "boolean", @@ -1055,6 +1048,26 @@ "notNull": true, "default": false }, + "source_type": { + "name": "source_type", + "type": "shop_item_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'CUSTOM'" + }, + "linked_warehouse_item_id": { + "name": "linked_warehouse_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_warehouse_template_id": { + "name": "linked_warehouse_template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, "last_edited_by": { "name": "last_edited_by", "type": "text", @@ -1091,6 +1104,32 @@ "onDelete": "cascade", "onUpdate": "no action" }, + "shop_item_linked_warehouse_item_id_warehouse_item_id_fk": { + "name": "shop_item_linked_warehouse_item_id_warehouse_item_id_fk", + "tableFrom": "shop_item", + "tableTo": "warehouse_item", + "columnsFrom": [ + "linked_warehouse_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_item_linked_warehouse_template_id_warehouse_order_template_id_fk": { + "name": "shop_item_linked_warehouse_template_id_warehouse_order_template_id_fk", + "tableFrom": "shop_item", + "tableTo": "warehouse_order_template", + "columnsFrom": [ + "linked_warehouse_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, "shop_item_last_edited_by_user_id_fk": { "name": "shop_item_last_edited_by_user_id_fk", "tableFrom": "shop_item", @@ -1134,8 +1173,8 @@ "primaryKey": false, "notNull": true }, - "order_stauts": { - "name": "order_stauts", + "order_status": { + "name": "order_status", "type": "shop_order_status", "typeSchema": "public", "primaryKey": false, @@ -1160,13 +1199,6 @@ "primaryKey": false, "notNull": true }, - "item_type_enum": { - "name": "item_type_enum", - "type": "shop_item_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": false - }, "item_name_snapshot": { "name": "item_name_snapshot", "type": "text", @@ -3059,12 +3091,13 @@ "MISSED" ] }, - "public.shop_item_type": { - "name": "shop_item_type", + "public.shop_item_source": { + "name": "shop_item_source", "schema": "public", "values": [ - "PHYSICAL", - "DIGITAL" + "CUSTOM", + "WAREHOUSE_ITEM", + "WAREHOUSE_TEMPLATE" ] }, "public.shop_order_status": { diff --git a/resolution-frontend/drizzle/meta/_journal.json b/resolution-frontend/drizzle/meta/_journal.json index ae2f559..ba7545e 100644 --- a/resolution-frontend/drizzle/meta/_journal.json +++ b/resolution-frontend/drizzle/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "7", - "when": 1778709057473, - "tag": "0000_baseline", + "when": 1778903874976, + "tag": "0000_redundant_gamora", "breakpoints": true } ] From 7da318c789383f1a77dbbd14319689e42ff15808 Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Sat, 16 May 2026 00:04:28 -0400 Subject: [PATCH 48/52] feat: add some TODOs and template some pgaes --- .../src/lib/server/db/schema.ts | 1 + .../[pathway]/shop/create/+page.server.ts | 2 +- .../[pathway]/shop/fufill/+page.server.ts | 22 ++++++++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/resolution-frontend/src/lib/server/db/schema.ts b/resolution-frontend/src/lib/server/db/schema.ts index ae064d9..4504679 100644 --- a/resolution-frontend/src/lib/server/db/schema.ts +++ b/resolution-frontend/src/lib/server/db/schema.ts @@ -187,6 +187,7 @@ export const transactionLedger = pgTable('currency_transactions', { createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow() }); +//TODO: evalute if this should have the shipping address encrypted export const shopOrder = pgTable('shop_orders', { id: text('id').primaryKey().$defaultFn(() => createId()), userId: text('user_id').references(() => user.id, { onDelete: 'set null' }), diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.server.ts index 8ef124c..6dff3ee 100644 --- a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.server.ts +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.server.ts @@ -1,4 +1,4 @@ -//TODO: ACTUALLY DO THIS +// TODO: write tests! import type { PageServerLoad, Actions } from './$types'; import { db } from '$lib/server/db'; import { diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts index fa9d42b..b7de13c 100644 --- a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts @@ -1 +1,21 @@ -// TODO: actually do this file \ No newline at end of file +// TODO: actually do this file +import type { PageServerLoad, Actions } from './$types'; +import { db } from '$lib/server/db'; +import { + shopItem, + pathwayEnum, + ambassadorPathway, + warehouseOrderTemplate, + warehouseOrderTemplateItem, + warehouseItem +} from '$lib/server/db/schema'; +import { and, eq, or } from 'drizzle-orm'; +import { error, redirect } from '@sveltejs/kit'; +// import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; +import { z } from 'zod'; +import { PATHWAYS, type PathwayId } from '$lib/pathways'; + +// steps: +// 1. get all orders which having a state of 'PENDING' +// 2. get associated order data +// 3. display to frontend \ No newline at end of file From 0946c22a3a96aac70705a21542b27dc220119447 Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Sat, 16 May 2026 13:02:03 -0400 Subject: [PATCH 49/52] feat: fufillment backend + some migrations --- .../drizzle/0001_sloppy_namora.sql | 4 + .../drizzle/meta/0001_snapshot.json | 3170 +++++++++++++++++ .../drizzle/meta/_journal.json | 7 + .../src/lib/server/db/schema.ts | 22 +- .../api/fulfillment/get-label/+server.ts | 16 +- .../[pathway]/shop/fufill/+page.server.ts | 486 ++- 6 files changed, 3691 insertions(+), 14 deletions(-) create mode 100644 resolution-frontend/drizzle/0001_sloppy_namora.sql create mode 100644 resolution-frontend/drizzle/meta/0001_snapshot.json diff --git a/resolution-frontend/drizzle/0001_sloppy_namora.sql b/resolution-frontend/drizzle/0001_sloppy_namora.sql new file mode 100644 index 0000000..89b9647 --- /dev/null +++ b/resolution-frontend/drizzle/0001_sloppy_namora.sql @@ -0,0 +1,4 @@ +ALTER TYPE "public"."shop_order_status" ADD VALUE 'REJECTED';--> statement-breakpoint +ALTER TABLE "shop_orders" ADD COLUMN "tracking_number" text;--> statement-breakpoint +ALTER TABLE "warehouse_order" ADD COLUMN "shop_order_id" text;--> statement-breakpoint +ALTER TABLE "warehouse_order" ADD CONSTRAINT "warehouse_order_shop_order_id_shop_orders_id_fk" FOREIGN KEY ("shop_order_id") REFERENCES "public"."shop_orders"("id") ON DELETE set null ON UPDATE no action; \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/0001_snapshot.json b/resolution-frontend/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..ad799f3 --- /dev/null +++ b/resolution-frontend/drizzle/meta/0001_snapshot.json @@ -0,0 +1,3170 @@ +{ + "id": "c85863ec-bd59-4e37-bd13-da29f490bb62", + "prevId": "04f6d62e-e75a-447d-9462-ec816fc41e19", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ambassador_pathway": { + "name": "ambassador_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "ambassador_pathway_unique_idx": { + "name": "ambassador_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ambassador_pathway_user_id_user_id_fk": { + "name": "ambassador_pathway_user_id_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_pathway_assigned_by_user_id_fk": { + "name": "ambassador_pathway_assigned_by_user_id_fk", + "tableFrom": "ambassador_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout": { + "name": "ambassador_payout", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "payout_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_ambassador_id_user_id_fk": { + "name": "ambassador_payout_ambassador_id_user_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_season_id_program_season_id_fk": { + "name": "ambassador_payout_season_id_program_season_id_fk", + "tableFrom": "ambassador_payout", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ambassador_payout_item": { + "name": "ambassador_payout_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "payout_id": { + "name": "payout_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_count": { + "name": "completion_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "rate_cents_per_completion": { + "name": "rate_cents_per_completion", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ambassador_payout_item_payout_id_ambassador_payout_id_fk": { + "name": "ambassador_payout_item_payout_id_ambassador_payout_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "ambassador_payout", + "columnsFrom": [ + "payout_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ambassador_payout_item_workshop_id_workshop_id_fk": { + "name": "ambassador_payout_item_workshop_id_workshop_id_fk", + "tableFrom": "ambassador_payout_item", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_shop": { + "name": "pathway_shop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_name": { + "name": "currency_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wish'" + }, + "currency_name_plural": { + "name": "currency_name_plural", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wishes'" + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "pathway_shop_last_edited_by_user_id_fk": { + "name": "pathway_shop_last_edited_by_user_id_fk", + "tableFrom": "pathway_shop", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "pathway_shop_pathway_unique": { + "name": "pathway_shop_pathway_unique", + "nullsNotDistinct": false, + "columns": [ + "pathway" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pathway_week_content": { + "name": "pathway_week_content", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "prize_image_url": { + "name": "prize_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_submissions_open": { + "name": "is_submissions_open", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pathway_week_content_unique_idx": { + "name": "pathway_week_content_unique_idx", + "columns": [ + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pathway_week_content_last_edited_by_user_id_fk": { + "name": "pathway_week_content_last_edited_by_user_id_fk", + "tableFrom": "pathway_week_content", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_enrollment": { + "name": "program_enrollment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "enrollment_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "enrollment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "starting_week": { + "name": "starting_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "enrollment_user_season_role_idx": { + "name": "enrollment_user_season_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_enrollment_user_id_user_id_fk": { + "name": "program_enrollment_user_id_user_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "program_enrollment_season_id_program_season_id_fk": { + "name": "program_enrollment_season_id_program_season_id_fk", + "tableFrom": "program_enrollment", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_season": { + "name": "program_season", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signup_opens_at": { + "name": "signup_opens_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "signup_closes_at": { + "name": "signup_closes_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "total_weeks": { + "name": "total_weeks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 8 + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "program_season_slug_unique": { + "name": "program_season_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_link": { + "name": "referral_link", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ambassador_id": { + "name": "ambassador_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "referral_link_ambassador_id_user_id_fk": { + "name": "referral_link_ambassador_id_user_id_fk", + "tableFrom": "referral_link", + "tableTo": "user", + "columnsFrom": [ + "ambassador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "referral_link_code_unique": { + "name": "referral_link_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_signup": { + "name": "referral_signup", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "referral_link_id": { + "name": "referral_link_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "referral_signup_unique_idx": { + "name": "referral_signup_unique_idx", + "columns": [ + { + "expression": "referral_link_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "referral_signup_referral_link_id_referral_link_id_fk": { + "name": "referral_signup_referral_link_id_referral_link_id_fk", + "tableFrom": "referral_signup", + "tableTo": "referral_link", + "columnsFrom": [ + "referral_link_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_signup_user_id_user_id_fk": { + "name": "referral_signup_user_id_user_id_fk", + "tableFrom": "referral_signup", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reviewer_pathway": { + "name": "reviewer_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "reviewer_pathway_unique_idx": { + "name": "reviewer_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reviewer_pathway_user_id_user_id_fk": { + "name": "reviewer_pathway_user_id_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reviewer_pathway_assigned_by_user_id_fk": { + "name": "reviewer_pathway_assigned_by_user_id_fk", + "tableFrom": "reviewer_pathway", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_item": { + "name": "shop_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_image_url": { + "name": "item_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price": { + "name": "item_price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_stock": { + "name": "item_stock", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "source_type": { + "name": "source_type", + "type": "shop_item_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'CUSTOM'" + }, + "linked_warehouse_item_id": { + "name": "linked_warehouse_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_warehouse_template_id": { + "name": "linked_warehouse_template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_edited_by": { + "name": "last_edited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_item_pathway_pathway_shop_pathway_fk": { + "name": "shop_item_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_item", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_item_linked_warehouse_item_id_warehouse_item_id_fk": { + "name": "shop_item_linked_warehouse_item_id_warehouse_item_id_fk", + "tableFrom": "shop_item", + "tableTo": "warehouse_item", + "columnsFrom": [ + "linked_warehouse_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_item_linked_warehouse_template_id_warehouse_order_template_id_fk": { + "name": "shop_item_linked_warehouse_template_id_warehouse_order_template_id_fk", + "tableFrom": "shop_item", + "tableTo": "warehouse_order_template", + "columnsFrom": [ + "linked_warehouse_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_item_last_edited_by_user_id_fk": { + "name": "shop_item_last_edited_by_user_id_fk", + "tableFrom": "shop_item", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shop_orders": { + "name": "shop_orders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "order_status": { + "name": "order_status", + "type": "shop_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "shop_item_id": { + "name": "shop_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_price_snapshot": { + "name": "item_price_snapshot", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_name_snapshot": { + "name": "item_name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "shipping_address": { + "name": "shipping_address", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_notes": { + "name": "user_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufiller_notes": { + "name": "fufiller_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_by": { + "name": "fufilled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fufilled_at": { + "name": "fufilled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "tracking_number": { + "name": "tracking_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cancelled_reason": { + "name": "cancelled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shop_orders_user_id_user_id_fk": { + "name": "shop_orders_user_id_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_pathway_pathway_shop_pathway_fk": { + "name": "shop_orders_pathway_pathway_shop_pathway_fk", + "tableFrom": "shop_orders", + "tableTo": "pathway_shop", + "columnsFrom": [ + "pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shop_orders_shop_item_id_shop_item_id_fk": { + "name": "shop_orders_shop_item_id_shop_item_id_fk", + "tableFrom": "shop_orders", + "tableTo": "shop_item", + "columnsFrom": [ + "shop_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shop_orders_fufilled_by_user_id_fk": { + "name": "shop_orders_fufilled_by_user_id_fk", + "tableFrom": "shop_orders", + "tableTo": "user", + "columnsFrom": [ + "fufilled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.currency_transactions": { + "name": "currency_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tx_user_id": { + "name": "tx_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_pathway": { + "name": "tx_pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_amount": { + "name": "tx_amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tx_reason": { + "name": "tx_reason", + "type": "currency_txn_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tx_note": { + "name": "tx_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_granted_by": { + "name": "tx_granted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_type": { + "name": "tx_ref_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tx_ref_id": { + "name": "tx_ref_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "currency_transactions_tx_user_id_user_id_fk": { + "name": "currency_transactions_tx_user_id_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "currency_transactions_tx_pathway_pathway_shop_pathway_fk": { + "name": "currency_transactions_tx_pathway_pathway_shop_pathway_fk", + "tableFrom": "currency_transactions", + "tableTo": "pathway_shop", + "columnsFrom": [ + "tx_pathway" + ], + "columnsTo": [ + "pathway" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "currency_transactions_tx_granted_by_user_id_fk": { + "name": "currency_transactions_tx_granted_by_user_id_fk", + "tableFrom": "currency_transactions", + "tableTo": "user", + "columnsFrom": [ + "tx_granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hack_club_id": { + "name": "hack_club_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_id": { + "name": "slack_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verification_status": { + "name": "verification_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ysws_eligible": { + "name": "ysws_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_hack_club_id_unique": { + "name": "user_hack_club_id_unique", + "nullsNotDistinct": false, + "columns": [ + "hack_club_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_pathway": { + "name": "user_pathway", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_pathway_unique_idx": { + "name": "user_pathway_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pathway", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_pathway_user_id_user_id_fk": { + "name": "user_pathway_user_id_user_id_fk", + "tableFrom": "user_pathway", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_batch": { + "name": "warehouse_batch", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "warehouse_batch_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'AWAITING_MAPPING'" + }, + "csv_data": { + "name": "csv_data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_mapping": { + "name": "field_mapping", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_count": { + "name": "address_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_batch_created_by_id_user_id_fk": { + "name": "warehouse_batch_created_by_id_user_id_fk", + "tableFrom": "warehouse_batch", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "warehouse_batch_template_id_warehouse_order_template_id_fk": { + "name": "warehouse_batch_template_id_warehouse_order_template_id_fk", + "tableFrom": "warehouse_batch", + "tableTo": "warehouse_order_template", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_batch_tag": { + "name": "warehouse_batch_tag", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "batch_id": { + "name": "batch_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "warehouse_batch_tag_unique_idx": { + "name": "warehouse_batch_tag_unique_idx", + "columns": [ + { + "expression": "batch_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "warehouse_batch_tag_batch_id_warehouse_batch_id_fk": { + "name": "warehouse_batch_tag_batch_id_warehouse_batch_id_fk", + "tableFrom": "warehouse_batch_tag", + "tableTo": "warehouse_batch", + "columnsFrom": [ + "batch_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_category": { + "name": "warehouse_category", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_item": { + "name": "warehouse_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sku": { + "name": "sku", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sizing": { + "name": "sizing", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_type": { + "name": "package_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'box'" + }, + "length_in": { + "name": "length_in", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "width_in": { + "name": "width_in", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "height_in": { + "name": "height_in", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "weight_grams": { + "name": "weight_grams", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "hs_code": { + "name": "hs_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_item_category_id_warehouse_category_id_fk": { + "name": "warehouse_item_category_id_warehouse_category_id_fk", + "tableFrom": "warehouse_item", + "tableTo": "warehouse_category", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "warehouse_item_sku_unique": { + "name": "warehouse_item_sku_unique", + "nullsNotDistinct": false, + "columns": [ + "sku" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order": { + "name": "warehouse_order", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "fulfillment_id": { + "name": "fulfillment_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "identity": { + "type": "always", + "name": "warehouse_order_fulfillment_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "shop_order_id": { + "name": "shop_order_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "batch_id": { + "name": "batch_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "warehouse_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_line_1": { + "name": "address_line_1", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address_line_2": { + "name": "address_line_2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_province": { + "name": "state_province", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "postal_code": { + "name": "postal_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "estimated_shipping_cents": { + "name": "estimated_shipping_cents", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "estimated_duties_cents": { + "name": "estimated_duties_cents", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "estimated_service_name": { + "name": "estimated_service_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_service_code": { + "name": "estimated_service_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_package_type": { + "name": "estimated_package_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_total_length_in": { + "name": "estimated_total_length_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "estimated_total_width_in": { + "name": "estimated_total_width_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "estimated_total_height_in": { + "name": "estimated_total_height_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "estimated_total_weight_grams": { + "name": "estimated_total_weight_grams", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_category": { + "name": "packaging_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "packaging_label": { + "name": "packaging_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "packaging_length_in": { + "name": "packaging_length_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_width_in": { + "name": "packaging_width_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_height_in": { + "name": "packaging_height_in", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "packaging_subject_to_change": { + "name": "packaging_subject_to_change", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tracking_number": { + "name": "tracking_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "label_url": { + "name": "label_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shipping_method": { + "name": "shipping_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_status": { + "name": "billing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + }, + "billing_failure_reason": { + "name": "billing_failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_order_created_by_id_user_id_fk": { + "name": "warehouse_order_created_by_id_user_id_fk", + "tableFrom": "warehouse_order", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "warehouse_order_shop_order_id_shop_orders_id_fk": { + "name": "warehouse_order_shop_order_id_shop_orders_id_fk", + "tableFrom": "warehouse_order", + "tableTo": "shop_orders", + "columnsFrom": [ + "shop_order_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "warehouse_order_fulfillment_id_unique": { + "name": "warehouse_order_fulfillment_id_unique", + "nullsNotDistinct": false, + "columns": [ + "fulfillment_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order_item": { + "name": "warehouse_order_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "order_id": { + "name": "order_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "warehouse_item_id": { + "name": "warehouse_item_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "sizing_choice": { + "name": "sizing_choice", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "warehouse_order_item_order_id_idx": { + "name": "warehouse_order_item_order_id_idx", + "columns": [ + { + "expression": "order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "warehouse_order_item_warehouse_item_id_idx": { + "name": "warehouse_order_item_warehouse_item_id_idx", + "columns": [ + { + "expression": "warehouse_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "warehouse_order_item_order_id_warehouse_order_id_fk": { + "name": "warehouse_order_item_order_id_warehouse_order_id_fk", + "tableFrom": "warehouse_order_item", + "tableTo": "warehouse_order", + "columnsFrom": [ + "order_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "warehouse_order_item_warehouse_item_id_warehouse_item_id_fk": { + "name": "warehouse_order_item_warehouse_item_id_warehouse_item_id_fk", + "tableFrom": "warehouse_order_item", + "tableTo": "warehouse_item", + "columnsFrom": [ + "warehouse_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order_tag": { + "name": "warehouse_order_tag", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "order_id": { + "name": "order_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "warehouse_order_tag_unique_idx": { + "name": "warehouse_order_tag_unique_idx", + "columns": [ + { + "expression": "order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "warehouse_order_tag_tag_idx": { + "name": "warehouse_order_tag_tag_idx", + "columns": [ + { + "expression": "tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "warehouse_order_tag_order_id_warehouse_order_id_fk": { + "name": "warehouse_order_tag_order_id_warehouse_order_id_fk", + "tableFrom": "warehouse_order_tag", + "tableTo": "warehouse_order", + "columnsFrom": [ + "order_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order_template": { + "name": "warehouse_order_template", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_order_template_created_by_id_user_id_fk": { + "name": "warehouse_order_template_created_by_id_user_id_fk", + "tableFrom": "warehouse_order_template", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.warehouse_order_template_item": { + "name": "warehouse_order_template_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "warehouse_item_id": { + "name": "warehouse_item_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + } + }, + "indexes": {}, + "foreignKeys": { + "warehouse_order_template_item_template_id_warehouse_order_template_id_fk": { + "name": "warehouse_order_template_item_template_id_warehouse_order_template_id_fk", + "tableFrom": "warehouse_order_template_item", + "tableTo": "warehouse_order_template", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "warehouse_order_template_item_warehouse_item_id_warehouse_item_id_fk": { + "name": "warehouse_order_template_item_warehouse_item_id_warehouse_item_id_fk", + "tableFrom": "warehouse_order_template_item", + "tableTo": "warehouse_item", + "columnsFrom": [ + "warehouse_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.weekly_ship": { + "name": "weekly_ship", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "week_number": { + "name": "week_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "goal_text": { + "name": "goal_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "ship_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'PLANNED'" + }, + "proof_url": { + "name": "proof_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shipped_at": { + "name": "shipped_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ship_user_season_week_idx": { + "name": "ship_user_season_week_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "weekly_ship_user_id_user_id_fk": { + "name": "weekly_ship_user_id_user_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_season_id_program_season_id_fk": { + "name": "weekly_ship_season_id_program_season_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "weekly_ship_workshop_id_workshop_id_fk": { + "name": "weekly_ship_workshop_id_workshop_id_fk", + "tableFrom": "weekly_ship", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop": { + "name": "workshop", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pathway": { + "name": "pathway", + "type": "pathway", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "difficulty": { + "name": "difficulty", + "type": "difficulty", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "estimated_hours": { + "name": "estimated_hours", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_author_id_user_id_fk": { + "name": "workshop_author_id_user_id_fk", + "tableFrom": "workshop", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_season_id_program_season_id_fk": { + "name": "workshop_season_id_program_season_id_fk", + "tableFrom": "workshop", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_analytics": { + "name": "workshop_analytics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "starts": { + "name": "starts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completions": { + "name": "completions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "avg_completion_mins": { + "name": "avg_completion_mins", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workshop_analytics_workshop_id_workshop_id_fk": { + "name": "workshop_analytics_workshop_id_workshop_id_fk", + "tableFrom": "workshop_analytics", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workshop_analytics_workshop_id_unique": { + "name": "workshop_analytics_workshop_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workshop_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workshop_completion": { + "name": "workshop_completion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workshop_id": { + "name": "workshop_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_url": { + "name": "project_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_workshop_participant_season_idx": { + "name": "completion_workshop_participant_season_idx", + "columns": [ + { + "expression": "workshop_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workshop_completion_workshop_id_workshop_id_fk": { + "name": "workshop_completion_workshop_id_workshop_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "workshop", + "columnsFrom": [ + "workshop_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_participant_id_user_id_fk": { + "name": "workshop_completion_participant_id_user_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "user", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workshop_completion_season_id_program_season_id_fk": { + "name": "workshop_completion_season_id_program_season_id_fk", + "tableFrom": "workshop_completion", + "tableTo": "program_season", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.currency_txn_reason": { + "name": "currency_txn_reason", + "schema": "public", + "values": [ + "GRANT", + "PURCHASE", + "REFUND", + "ADJUSTMENT", + "OTHER" + ] + }, + "public.difficulty": { + "name": "difficulty", + "schema": "public", + "values": [ + "BEGINNER", + "INTERMEDIATE", + "ADVANCED" + ] + }, + "public.enrollment_role": { + "name": "enrollment_role", + "schema": "public", + "values": [ + "PARTICIPANT", + "AMBASSADOR" + ] + }, + "public.enrollment_status": { + "name": "enrollment_status", + "schema": "public", + "values": [ + "ACTIVE", + "DROPPED", + "COMPLETED" + ] + }, + "public.pathway": { + "name": "pathway", + "schema": "public", + "values": [ + "PYTHON", + "RUST", + "GAME_DEV", + "HARDWARE", + "DESIGN", + "GENERAL_CODING" + ] + }, + "public.payout_status": { + "name": "payout_status", + "schema": "public", + "values": [ + "DRAFT", + "PENDING", + "PAID", + "CANCELED" + ] + }, + "public.ship_status": { + "name": "ship_status", + "schema": "public", + "values": [ + "PLANNED", + "IN_PROGRESS", + "SHIPPED", + "MISSED" + ] + }, + "public.shop_item_source": { + "name": "shop_item_source", + "schema": "public", + "values": [ + "CUSTOM", + "WAREHOUSE_ITEM", + "WAREHOUSE_TEMPLATE" + ] + }, + "public.shop_order_status": { + "name": "shop_order_status", + "schema": "public", + "values": [ + "PENDING", + "PROCESSING", + "FULFILLED", + "CANCELED", + "REJECTED" + ] + }, + "public.warehouse_batch_status": { + "name": "warehouse_batch_status", + "schema": "public", + "values": [ + "AWAITING_MAPPING", + "MAPPED", + "PROCESSED" + ] + }, + "public.warehouse_order_status": { + "name": "warehouse_order_status", + "schema": "public", + "values": [ + "DRAFT", + "ESTIMATED", + "APPROVED", + "SHIPPED", + "CANCELLED" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/resolution-frontend/drizzle/meta/_journal.json b/resolution-frontend/drizzle/meta/_journal.json index ba7545e..4004dd0 100644 --- a/resolution-frontend/drizzle/meta/_journal.json +++ b/resolution-frontend/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1778903874976, "tag": "0000_redundant_gamora", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1778950267393, + "tag": "0001_sloppy_namora", + "breakpoints": true } ] } \ No newline at end of file diff --git a/resolution-frontend/src/lib/server/db/schema.ts b/resolution-frontend/src/lib/server/db/schema.ts index 4504679..9f702c3 100644 --- a/resolution-frontend/src/lib/server/db/schema.ts +++ b/resolution-frontend/src/lib/server/db/schema.ts @@ -12,7 +12,7 @@ export const shipStatusEnum = pgEnum('ship_status', ['PLANNED', 'IN_PROGRESS', ' export const payoutStatusEnum = pgEnum('payout_status', ['DRAFT', 'PENDING', 'PAID', 'CANCELED']); export const warehouseOrderStatusEnum = pgEnum('warehouse_order_status', ['DRAFT', 'ESTIMATED', 'APPROVED', 'SHIPPED', 'CANCELLED']); export const warehouseBatchStatusEnum = pgEnum('warehouse_batch_status', ['AWAITING_MAPPING', 'MAPPED', 'PROCESSED']); -export const shopOrderStatusEnum = pgEnum('shop_order_status', ['PENDING', 'PROCESSING', 'FULFILLED', 'CANCELED']); // order tracking for frontend (users) +export const shopOrderStatusEnum = pgEnum('shop_order_status', ['PENDING', 'PROCESSING', 'FULFILLED', 'CANCELED', 'REJECTED']); // order tracking for frontend (users) export const shopItemSourceEnum = pgEnum('shop_item_source', ['CUSTOM', 'WAREHOUSE_ITEM', 'WAREHOUSE_TEMPLATE']); // discriminator: where the item's fulfillment data comes from export const currencyTxnReasonEnum = pgEnum('currency_txn_reason', ['GRANT', 'PURCHASE', 'REFUND', 'ADJUSTMENT', 'OTHER']); // logging why transaction occured @@ -204,6 +204,10 @@ export const shopOrder = pgTable('shop_orders', { // claimedBy: fufilledBy: text('fufilled_by').references(() => user.id, { onDelete: 'set null' }), fufilledAt: timestamp('fufilled_at', { mode: 'date' }), + // Populated by the warehouse fulfillment flow when a label is created. + // Mirrors warehouseOrder.trackingNumber so the participant can see it on + // their order without needing access to the warehouse tables. + trackingNumber: text('tracking_number'), cancelledReason: text('cancelled_reason'), createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow().$onUpdate(() => new Date()), @@ -399,6 +403,12 @@ export const warehouseOrder = pgTable('warehouse_order', { id: text('id').primaryKey().$defaultFn(() => createId()), fulfillmentId: integer('fulfillment_id').generatedAlwaysAsIdentity().unique(), createdById: text('created_by_id').notNull().references(() => user.id, { onDelete: 'cascade' }), + // Back reference to the originating shop order, set when a warehouse + // order is created via the shop fufill flow. Null for warehouse orders + // created directly (admin or ambassador swag). On shipping success the + // fulfillment endpoint flips the linked shop order to FULFILLED and + // copies trackingNumber across so the participant can see it. + shopOrderId: text('shop_order_id').references(() => shopOrder.id, { onDelete: 'set null' }), batchId: text('batch_id'), status: warehouseOrderStatusEnum('status').notNull().default('DRAFT'), firstName: text('first_name').notNull(), @@ -468,6 +478,7 @@ export const warehouseOrderTag = pgTable('warehouse_order_tag', { export const warehouseOrderRelations = relations(warehouseOrder, ({ one, many }) => ({ createdBy: one(user, { fields: [warehouseOrder.createdById], references: [user.id] }), + shopOrder: one(shopOrder, { fields: [warehouseOrder.shopOrderId], references: [shopOrder.id] }), items: many(warehouseOrderItem), tags: many(warehouseOrderTag) })); @@ -568,10 +579,11 @@ export const transactionLedgerRelations = relations(transactionLedger, ({ one }) })); export const shopOrderRelations = relations(shopOrder, ({ one }) => ({ - user: one(user, { fields: [shopOrder.userId], references: [user.id] }), - shop: one(pathwayShop, { fields: [shopOrder.pathway], references: [pathwayShop.pathway] }), - item: one(shopItem, { fields: [shopOrder.item], references: [shopItem.id] }), - fufiller: one(user, { fields: [shopOrder.fufilledBy], references: [user.id] }) + user: one(user, { fields: [shopOrder.userId], references: [user.id] }), + shop: one(pathwayShop, { fields: [shopOrder.pathway], references: [pathwayShop.pathway] }), + item: one(shopItem, { fields: [shopOrder.item], references: [shopItem.id] }), + fufiller: one(user, { fields: [shopOrder.fufilledBy], references: [user.id] }), + warehouseOrder: one(warehouseOrder, { fields: [shopOrder.id], references: [warehouseOrder.shopOrderId] }) })); diff --git a/resolution-frontend/src/routes/api/fulfillment/get-label/+server.ts b/resolution-frontend/src/routes/api/fulfillment/get-label/+server.ts index dc9a3e5..ec55dcc 100644 --- a/resolution-frontend/src/routes/api/fulfillment/get-label/+server.ts +++ b/resolution-frontend/src/routes/api/fulfillment/get-label/+server.ts @@ -1,7 +1,7 @@ import { env } from '$env/dynamic/private'; import { json, error } from '@sveltejs/kit'; import { db } from '$lib/server/db'; -import { warehouseOrder, ambassadorPathway } from '$lib/server/db/schema'; +import { warehouseOrder, ambassadorPathway, shopOrder } from '$lib/server/db/schema'; import { eq, and } from 'drizzle-orm'; import type { RequestHandler } from './$types'; import { requireAuth } from '$lib/server/auth/guard'; @@ -359,6 +359,20 @@ export const POST: RequestHandler = async (event) => { }) .where(eq(warehouseOrder.id, orderId)); + // If this warehouse order was created from a shop order, flip the linked + // shop order to FULFILLED and copy the tracking number so the participant + // can see it on their order page. Lettermail can ship without a tracking + // number, so trackingNumber may legitimately be null here. + if (order.shopOrderId) { + await db.update(shopOrder) + .set({ + status: 'FULFILLED', + fufilledAt: new Date(), + trackingNumber: trackingNumber ?? null + }) + .where(eq(shopOrder.id, order.shopOrderId)); + } + return json({ trackingNumber, labelUrl, diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts index b7de13c..9c0a58e 100644 --- a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts @@ -1,21 +1,491 @@ -// TODO: actually do this file +// NOTE: MOST OF THE HCB LOGIC IS VIBED. I DON'T TRUST MYSELF WITH WAREHOUSE OR HCB. import type { PageServerLoad, Actions } from './$types'; import { db } from '$lib/server/db'; import { shopItem, pathwayEnum, ambassadorPathway, + warehouseOrder, + warehouseOrderItem, warehouseOrderTemplate, warehouseOrderTemplateItem, - warehouseItem + warehouseItem, + shopOrder } from '$lib/server/db/schema'; -import { and, eq, or } from 'drizzle-orm'; -import { error, redirect } from '@sveltejs/kit'; +import { and, eq, gte, inArray, or, sql } from 'drizzle-orm'; +import { error, fail, redirect } from '@sveltejs/kit'; // import { PATHWAY_IDS, type PathwayId } from '$lib/pathways'; import { z } from 'zod'; import { PATHWAYS, type PathwayId } from '$lib/pathways'; +import { createHcbTransfer, getOrgIdForPathway } from '$lib/server/hcb'; +import { selectPackaging } from '$lib/server/packaging'; +import { fetchCheapestRate } from '$lib/server/canada-post'; +import { resolveCountryCode } from '$lib/server/countries'; -// steps: -// 1. get all orders which having a state of 'PENDING' -// 2. get associated order data -// 3. display to frontend \ No newline at end of file +// Hard cap on the auto fetched shipping estimate. Mirrors the limit in +// /warehouse/orders/new so a rogue rate response cannot trigger a huge +// HCB transfer. +const MAX_SHIPPING_CENTS = 50_000; + +const pathwayIdSchema = z.enum( + PATHWAYS.map((p) => p.id) as [PathwayId, ...PathwayId[]] +); + +const approveSchema = z.object({ + // pathway: pathwayIdSchema, + id: z.string().min(1).max(100), // id for the order + note: z.string().optional() +}) + +const rejectSchema = z.object({ + // pathway: pathwayIdSchema + id: z.string().min(1).max(100), + note: z.string() +}) + +export const load: PageServerLoad = async ({ params, parent }) => { + const { user, pathwayId } = await parent(); + + const pendingOrders = await db.query.shopOrder.findMany({ + where: and(eq(shopOrder.pathway, pathwayId), eq(shopOrder.status, 'PENDING')), + with: { item: true } + }); + + // Collect every warehouse item and template needed across all orders so + // we can batch the lookups instead of N round trips. + const directItemIds = new Set(); + const templateIds = new Set(); + for (const o of pendingOrders) { + if (!o.item) continue; + if (o.item.sourceType === 'WAREHOUSE_ITEM' && o.item.linkedWarehouseItemId) { + directItemIds.add(o.item.linkedWarehouseItemId); + } else if (o.item.sourceType === 'WAREHOUSE_TEMPLATE' && o.item.linkedWarehouseTemplateId) { + templateIds.add(o.item.linkedWarehouseTemplateId); + } + } + + const templateItemRows = templateIds.size > 0 + ? await db + .select() + .from(warehouseOrderTemplateItem) + .where(inArray(warehouseOrderTemplateItem.templateId, [...templateIds])) + : []; + + const templateMap = new Map(); + for (const ti of templateItemRows) { + const list = templateMap.get(ti.templateId) ?? []; + list.push({ warehouseItemId: ti.warehouseItemId, quantity: ti.quantity }); + templateMap.set(ti.templateId, list); + directItemIds.add(ti.warehouseItemId); + } + + const stockRows = directItemIds.size > 0 + ? await db + .select({ + id: warehouseItem.id, + costCents: warehouseItem.costCents, + lengthIn: warehouseItem.lengthIn, + widthIn: warehouseItem.widthIn, + heightIn: warehouseItem.heightIn, + weightGrams: warehouseItem.weightGrams + }) + .from(warehouseItem) + .where(inArray(warehouseItem.id, [...directItemIds])) + : []; + const stockMap = new Map(stockRows.map((s) => [s.id, s])); + + // Quote each order in parallel. Shipping calls the carrier APIs so this + // can be slow on big lists; CUSTOM orders are skipped entirely. + const estimates = await Promise.all(pendingOrders.map(async (o) => { + if (!o.item) return null; + if (o.item.sourceType === 'CUSTOM') return null; + + let lineItems: { warehouseItemId: string; quantity: number }[] = []; + if (o.item.sourceType === 'WAREHOUSE_ITEM' && o.item.linkedWarehouseItemId) { + lineItems = [{ warehouseItemId: o.item.linkedWarehouseItemId, quantity: 1 }]; + } else if (o.item.sourceType === 'WAREHOUSE_TEMPLATE' && o.item.linkedWarehouseTemplateId) { + lineItems = templateMap.get(o.item.linkedWarehouseTemplateId) ?? []; + } + if (lineItems.length === 0) return null; + + let itemTotalCents = 0; + const packagingItems = []; + for (const li of lineItems) { + const s = stockMap.get(li.warehouseItemId); + if (!s) return null; + itemTotalCents += s.costCents * li.quantity; + packagingItems.push({ + lengthIn: s.lengthIn, + widthIn: s.widthIn, + heightIn: s.heightIn, + weightGrams: s.weightGrams, + quantity: li.quantity + }); + } + + let estimatedShippingCents: number | null = null; + if (o.shippingAddress) { + try { + const packaging = selectPackaging(packagingItems); + const quote = await fetchCheapestRate({ + country: resolveCountryCode(o.shippingAddress.country), + postalCode: o.shippingAddress.zipPostalCode || undefined, + province: o.shippingAddress.stateProvince, + weightGrams: packaging.weightGrams, + lengthIn: packaging.lengthIn, + widthIn: packaging.widthIn, + heightIn: packaging.heightIn, + packageType: packaging.category === 'box' ? 'box' : 'flat' + }); + if (quote) { + estimatedShippingCents = Math.min( + Math.round(quote.shippingCostUsd * 100), + MAX_SHIPPING_CENTS + ); + } + } catch (e) { + console.error(`Shipping quote failed for order ${o.id}:`, e); + } + } + + return { + orderId: o.id, + itemTotalCents, + estimatedShippingCents, + totalCents: itemTotalCents + (estimatedShippingCents ?? 0) + }; + })); + + const estimatesByOrderId: Record = {}; + for (const e of estimates) { + if (e) estimatesByOrderId[e.orderId] = { + itemTotalCents: e.itemTotalCents, + estimatedShippingCents: e.estimatedShippingCents, + totalCents: e.totalCents + }; + } + + return { pendingOrders, estimatesByOrderId }; +}; + +export const actions: Actions = { + approve: async ({request, params, locals}) => { + // frontend should send ID + // 1. gate to ambassadors only (done) + // 2. confirm ID exists (done) + // 3. check if item is linked to wh template or item (done) + // 3a. if 3 is yes, order (done) + // 3b. (frontend) if 3 is no, notify ambassador they have to manually fufill (done) + if (!locals.user) throw redirect(302, '/api/auth/login'); + const userId = locals.user.id; + + // pathway validation from param + const rawPathway = params.pathway?.toUpperCase() ?? ''; + const pathwayParsed = pathwayIdSchema.safeParse(rawPathway); + if (!pathwayParsed.success) throw error(404, 'Pathway not found'); + const pathwayId = pathwayParsed.data; + + // make sure its gated to ambassadors + const assignment = await db.query.ambassadorPathway.findFirst({ + where: and(eq(ambassadorPathway.userId, userId), eq(ambassadorPathway.pathway, pathwayId)) + }); + if (!assignment) throw error(403, 'You are not authorized to do this action'); + + const form = Object.fromEntries(await request.formData()); + const orderObj = approveSchema.safeParse(form); + if (!orderObj.success) throw error(400, 'Invalid request data'); + + const data = orderObj.data + const order = await db.query.shopOrder.findFirst({ + where: and(eq(shopOrder.pathway, pathwayId), eq(shopOrder.id, data.id)), + with: { item: true, user: true } + }); + + if (!order) throw error(404, 'Order not found') + if (!order.item) throw error(400, 'Nonexistant order item') + if (!order.user) throw error(400, 'Order has no associated user') + if (!order.shippingAddress) throw error(400, 'Order missing shipping address') + + // need to build the warehouse items + type LineItem = { warehouseItemId: string; quantity: number; sizingChoice: string | null }; + let lineItems: LineItem[] = []; + + if (order.item.sourceType === 'WAREHOUSE_ITEM') { + if (!order.item.linkedWarehouseItemId) { + throw error(400, 'Shop item is missing its linked warehouse item'); + } + lineItems = [{ + warehouseItemId: order.item.linkedWarehouseItemId, + quantity: 1, + sizingChoice: null + }]; + } + else if (order.item.sourceType === 'WAREHOUSE_TEMPLATE') { + if (!order.item.linkedWarehouseTemplateId) { + throw error(400, 'Shop item is missing its linked warehouse template'); + } + const tplItems = await db + .select() + .from(warehouseOrderTemplateItem) + .where(eq(warehouseOrderTemplateItem.templateId, order.item.linkedWarehouseTemplateId)); + if (tplItems.length === 0) { + throw error(400, 'Linked warehouse template has no items'); + } + lineItems = tplItems.map((t) => ({ + warehouseItemId: t.warehouseItemId, + quantity: t.quantity, + sizingChoice: null + })); + } + else { + // nothing to ship so just mark it as done + try { + await db.transaction(async (tx) => { + await tx.update(shopOrder) + .set({ + status: 'FULFILLED', + fufilledBy: userId, + fufilledAt: new Date(), + fufillerNotes: data.note ?? null + }) + .where(eq(shopOrder.id, order.id)); + }); + } catch (e: any) { + return fail(500, { error: e?.message || 'Failed to mark order as fulfilled' }); + } + return { success: true }; + } + + // make sure stock is available before continuing + const itemIds = lineItems.map((i) => i.warehouseItemId); + const stock = await db + .select({ + id: warehouseItem.id, + name: warehouseItem.name, + quantity: warehouseItem.quantity, + costCents: warehouseItem.costCents, + lengthIn: warehouseItem.lengthIn, + widthIn: warehouseItem.widthIn, + heightIn: warehouseItem.heightIn, + weightGrams: warehouseItem.weightGrams, + packageType: warehouseItem.packageType + }) + .from(warehouseItem) + .where(inArray(warehouseItem.id, itemIds)); + const stockMap = new Map(stock.map((s) => [s.id, s])); + for (const li of lineItems) { + const s = stockMap.get(li.warehouseItemId); + if (!s || s.quantity < li.quantity) { + const name = s?.name ?? li.warehouseItemId; + const available = s?.quantity ?? 0; + return fail(400, { + error: `Insufficient stock for "${name}": ${available} available, ${li.quantity} requested` + }); + } + } + + const addr = order.shippingAddress; + const firstName = order.user.firstName ?? ''; + const lastName = order.user.lastName ?? ''; + const countryCode = resolveCountryCode(addr.country); + + // Build packaging from item dimensions, then quote the cheapest + // carrier rate. Result is in USD dollars; warehouseOrder stores cents + // so we convert and cap. Failures fall back to a 0 estimate, which + // matches the behavior of the warehouse new order form when the + // ambassador skips the quote step. + const packagingItems = lineItems.map((li) => { + const s = stockMap.get(li.warehouseItemId)!; + return { + lengthIn: s.lengthIn, + widthIn: s.widthIn, + heightIn: s.heightIn, + weightGrams: s.weightGrams, + quantity: li.quantity + }; + }); + const packaging = selectPackaging(packagingItems); + + let estimatedShippingCents: number | null = null; + let estimatedServiceName: string | null = null; + try { + const quote = await fetchCheapestRate({ + country: countryCode, + postalCode: addr.zipPostalCode || undefined, + province: addr.stateProvince, + weightGrams: packaging.weightGrams, + lengthIn: packaging.lengthIn, + widthIn: packaging.widthIn, + heightIn: packaging.heightIn, + packageType: packaging.category === 'box' ? 'box' : 'flat' + }); + if (quote) { + const cents = Math.round(quote.shippingCostUsd * 100); + estimatedShippingCents = Math.min(cents, MAX_SHIPPING_CENTS); + estimatedServiceName = quote.serviceName; + } + } catch (e) { + console.error(`Shipping rate quote failed for shop order ${order.id}:`, e); + } + + let warehouseOrderId: string; + try { + warehouseOrderId = await db.transaction(async (tx) => { + const [wo] = await tx.insert(warehouseOrder).values({ + createdById: userId, + shopOrderId: order.id, + firstName, + lastName, + email: order.user!.email, + phone: order.phone ?? null, + addressLine1: addr.addressLine1, + addressLine2: addr.addressLine2 || null, + city: addr.city, + stateProvince: addr.stateProvince, + postalCode: addr.zipPostalCode ?? null, + country: addr.country, + status: 'APPROVED', + billingStatus: 'PENDING', + estimatedShippingCents, + estimatedServiceName, + packagingCategory: packaging.category, + packagingLabel: packaging.label, + packagingLengthIn: packaging.lengthIn, + packagingWidthIn: packaging.widthIn, + packagingHeightIn: packaging.heightIn, + packagingSubjectToChange: packaging.subjectToChange ?? false, + notes: `Shop order ${order.id}` + }).returning({ id: warehouseOrder.id }); + + await Promise.all( + lineItems.map((li) => + tx.insert(warehouseOrderItem).values({ + orderId: wo.id, + warehouseItemId: li.warehouseItemId, + quantity: li.quantity, + sizingChoice: li.sizingChoice + }) + ) + ); + + // ensure no overselling + for (const li of lineItems) { + const s = stockMap.get(li.warehouseItemId); + const name = s?.name ?? li.warehouseItemId; + const [updated] = await tx + .update(warehouseItem) + .set({ quantity: sql`${warehouseItem.quantity} - ${li.quantity}` }) + .where(and( + eq(warehouseItem.id, li.warehouseItemId), + gte(warehouseItem.quantity, li.quantity) + )) + .returning({ id: warehouseItem.id }); + if (!updated) { + throw new Error(`Insufficient stock for "${name}", another order claimed it. Please retry.`); + } + } + + await tx.update(shopOrder) + .set({ status: 'PROCESSING', fufilledBy: userId, fufillerNotes: data.note ?? null }) + .where(eq(shopOrder.id, order.id)); + + return wo.id; + }); + } catch (e: any) { + return fail(409, { error: e?.message || 'Order creation failed. Please try again.' }); + } + + // Bill the pathway's HCB org for item cost plus the estimated + // shipping we just quoted. Mirrors /warehouse/orders/new: this is an + // estimate!! + // Failures are recorded on the warehouse order for manual + // reconciliation rather than rolling back the order, since the items + // are already reserved. + const orgId = getOrgIdForPathway(pathwayId); + if (orgId) { + let itemsTotalCents = 0; + for (const li of lineItems) { + const s = stockMap.get(li.warehouseItemId); + if (s) itemsTotalCents += s.costCents * li.quantity; + } + const totalCents = itemsTotalCents + (estimatedShippingCents ?? 0); + + if (totalCents > 0) { + try { + await createHcbTransfer( + orgId, + totalCents, + `Shop fulfillment: warehouse order ${warehouseOrderId} (shop order ${order.id})` + ); + await db.update(warehouseOrder) + .set({ billingStatus: 'CHARGED', updatedAt: new Date() }) + .where(eq(warehouseOrder.id, warehouseOrderId)); + } catch (e: any) { + const reason = e?.message ? String(e.message).slice(0, 500) : 'Unknown error'; + console.error(`HCB transfer failed for warehouse order ${warehouseOrderId}:`, reason); + await db.update(warehouseOrder) + .set({ + billingStatus: 'FAILED', + billingFailureReason: reason, + updatedAt: new Date() + }) + .where(eq(warehouseOrder.id, warehouseOrderId)); + } + } else { + await db.update(warehouseOrder) + .set({ billingStatus: 'CHARGED', updatedAt: new Date() }) + .where(eq(warehouseOrder.id, warehouseOrderId)); + } + } else { + // No HCB org mapped for this pathway, mark as not applicable so + // the order is not stuck in PENDING forever. + await db.update(warehouseOrder) + .set({ billingStatus: 'NOT_APPLICABLE', updatedAt: new Date() }) + .where(eq(warehouseOrder.id, warehouseOrderId)); + } + + return { success: true, warehouseOrderId }; + }, + deny: async ({request, params, locals}) => { + // 1: mark the order as rejected + // 2: add reason + // 3: yeah + if (!locals.user) throw redirect(302, '/api/auth/login'); + const userId = locals.user.id; + + // pathway validation from param + const rawPathway = params.pathway?.toUpperCase() ?? ''; + const pathwayParsed = pathwayIdSchema.safeParse(rawPathway); + if (!pathwayParsed.success) throw error(404, 'Pathway not found'); + const pathwayId = pathwayParsed.data; + + // make sure its gated to ambassadors + const assignment = await db.query.ambassadorPathway.findFirst({ + where: and(eq(ambassadorPathway.userId, userId), eq(ambassadorPathway.pathway, pathwayId)) + }); + if (!assignment) throw error(403, 'You are not authorized to do this action'); + + const form = Object.fromEntries(await request.formData()); + const orderObj = rejectSchema.safeParse(form); + if (!orderObj.success) throw error(400, 'Invalid request data'); + + const data = orderObj.data + const order = await db.query.shopOrder.findFirst({ + where: and(eq(shopOrder.pathway, pathwayId), eq(shopOrder.id, data.id)), + with: { item: true, user: true } + }); + + if (!order) throw error(400, 'Order ID does not exist'); + + await db.update(shopOrder) + .set({ status: 'REJECTED', cancelledReason: data.note}) + .where(eq(shopOrder.id, data.id)); + + return { success: true, orderId: data.id } + + } +} \ No newline at end of file From 42e2ad78fb7f62436af7ab354075314412d6665c Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Sat, 16 May 2026 13:06:25 -0400 Subject: [PATCH 50/52] formatting: oops remove extra space --- .../routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts index 9c0a58e..e643f0c 100644 --- a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts @@ -486,6 +486,5 @@ export const actions: Actions = { .where(eq(shopOrder.id, data.id)); return { success: true, orderId: data.id } - } } \ No newline at end of file From 9f4b09c413ae1a8aa4331fea4c53caa081f764fc Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Sat, 16 May 2026 14:51:49 -0400 Subject: [PATCH 51/52] feat: frontend for ambassadors + some odd bits and ends --- resolution-frontend/src/lib/server/devSeed.ts | 93 +++- .../src/routes/app/ambassador/+page.svelte | 30 ++ .../ambassador/[pathway]/shop/+page.svelte | 309 ++++++++++++- .../[pathway]/shop/create/+page.server.ts | 10 +- .../[pathway]/shop/create/+page.svelte | 296 ++++++++++++ .../[pathway]/shop/create/page.server.test.ts | 250 +++++++++++ .../[pathway]/shop/fufill/+page.server.ts | 2 +- .../[pathway]/shop/fufill/+page.svelte | 329 +++++++++++++- .../[pathway]/shop/fufill/page.server.test.ts | 422 ++++++++++++++++++ 9 files changed, 1721 insertions(+), 20 deletions(-) create mode 100644 resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/page.server.test.ts create mode 100644 resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/page.server.test.ts diff --git a/resolution-frontend/src/lib/server/devSeed.ts b/resolution-frontend/src/lib/server/devSeed.ts index 382034f..9d19a40 100644 --- a/resolution-frontend/src/lib/server/devSeed.ts +++ b/resolution-frontend/src/lib/server/devSeed.ts @@ -1,16 +1,49 @@ import { dev } from '$app/environment'; import { db } from './db'; -import { pathwayShop, shopItem } from './db/schema'; +import { + pathwayShop, + shopItem, + shopOrder, + user, + userPathway +} from './db/schema'; import { and, eq } from 'drizzle-orm'; import { PATHWAYS } from '$lib/pathways'; +const TEST_USER_ID = 'dev-seed-participant'; +const TEST_USER_HACK_CLUB_ID = 'dev-seed-hackclub-id'; +const TEST_USER_EMAIL = 'dev-seed-participant@example.com'; + +const TEST_ADDRESS = { + addressLine1: '15 Falls Rd', + addressLine2: '', + city: 'Shelburne', + stateProvince: 'VT', + country: 'United States', + zipPostalCode: '05482' +}; + /** - * Dev-only seed: ensure every pathway has an enabled `pathwayShop` and at - * least one `shopItem`. No-op in production. + * Dev-only seed: ensure every pathway has an enabled `pathwayShop`, at least + * one `shopItem`, and a couple of pending `shopOrder`s pointed at a fake + * participant so the fulfill flow has something to render. No-op in production. */ export async function seedDevShops() { if (!dev) return; + // Make sure a stable fake participant exists so we can hang seeded orders + // off a real user row. Insert is idempotent via ON CONFLICT DO NOTHING. + await db + .insert(user) + .values({ + id: TEST_USER_ID, + email: TEST_USER_EMAIL, + hackClubId: TEST_USER_HACK_CLUB_ID, + firstName: 'Devon', + lastName: 'Seeder' + }) + .onConflictDoNothing(); + for (const { id, label } of PATHWAYS) { const existingShop = await db.query.pathwayShop.findFirst({ where: eq(pathwayShop.pathway, id) @@ -30,21 +63,57 @@ export async function seedDevShops() { .where(eq(pathwayShop.pathway, id)); } - const existingItem = await db.query.shopItem.findFirst({ + let item = await db.query.shopItem.findFirst({ where: and(eq(shopItem.pathway, id), eq(shopItem.isActive, true)) }); - if (!existingItem) { - await db.insert(shopItem).values({ + if (!item) { + [item] = await db + .insert(shopItem) + .values({ + pathway: id, + name: `${label} starter sticker`, + description: `A test ${label} sticker seeded automatically in dev. Remove me before going live.`, + price: 5, + stock: 25, + isActive: true + }) + .returning(); + } + + // Make sure the test participant belongs to this pathway. Cheap insert, + // guarded by the unique (user, pathway) index. + await db + .insert(userPathway) + .values({ userId: TEST_USER_ID, pathway: id }) + .onConflictDoNothing(); + + // Seed exactly one pending order per pathway/item pair so the fulfill + // page has something to render. Skip if we've already seeded one. + const existingOrder = await db.query.shopOrder.findFirst({ + where: and( + eq(shopOrder.pathway, id), + eq(shopOrder.userId, TEST_USER_ID), + eq(shopOrder.item, item!.id), + eq(shopOrder.status, 'PENDING') + ) + }); + + if (!existingOrder) { + await db.insert(shopOrder).values({ + userId: TEST_USER_ID, pathway: id, - name: `${label} starter sticker`, - description: `A test ${label} sticker seeded automatically in dev. Remove me before going live.`, - price: 5, - stock: 25, - isActive: true + status: 'PENDING', + totalAmount: item!.price, + item: item!.id, + itemPriceSnapshot: item!.price, + itemNameSnapshot: item!.name, + shippingAddress: TEST_ADDRESS, + phone: '555-0100', + userNotes: `Auto-seeded test order for ${label}` }); } } - console.log('[dev seed] pathway shops + items ensured'); + console.log('[dev seed] pathway shops + items + sample orders ensured'); } diff --git a/resolution-frontend/src/routes/app/ambassador/+page.svelte b/resolution-frontend/src/routes/app/ambassador/+page.svelte index 464dd4e..2a157bf 100644 --- a/resolution-frontend/src/routes/app/ambassador/+page.svelte +++ b/resolution-frontend/src/routes/app/ambassador/+page.svelte @@ -61,6 +61,18 @@ height="32" />

{info.label}

+ + + Shop +
@@ -188,6 +200,24 @@ margin-bottom: 1.25rem; } + .shop-btn { + margin-left: auto; + display: inline-flex; + align-items: center; + gap: 0.4rem; + padding: 0.4rem 0.85rem; + border-radius: 20px; + background: rgba(255, 255, 255, 0.8); + border: 1px solid #a633d6; + color: #a633d6; + font-family: 'Kodchasan', sans-serif; + text-decoration: none; + font-size: 0.85rem; + } + .shop-btn:hover { + background: #fff; + } + h2 { font-size: 1.25rem; margin: 0; diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+page.svelte b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+page.svelte index 8245c29..756db1a 100644 --- a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+page.svelte +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/+page.svelte @@ -1 +1,308 @@ - \ No newline at end of file + + + + {info?.label ?? data.pathwayId} Shop - Ambassador + + +
+ + Back + Back to Ambassador Dashboard + + +
+
+
+ + {info?.label ?? data.pathwayId} + +

Shop Management

+

+ Currency: {data.shop.currencyName} / {data.shop.currencyNamePlural} · + {#if data.shop.isEnabled} + Enabled + {:else} + Disabled + {/if} +

+
+ +
+
+ +
+
+
Total orders
+
{data.totalOrders}
+
+ +
Awaiting fulfillment
+
0}>{data.awaitingFufillment}
+
+
+
Active items
+
{data.items.filter((i) => i.isActive).length}
+
+
+ +
+

Items ({data.items.length})

+ {#if data.items.length === 0} +
+

No items yet.

+ Create your first item +
+ {:else} +
    + {#each data.items as item (item.id)} +
  • +
    + {#if item.itemImageUrl} + {item.name} + {:else} +
    No image
    + {/if} +
    +
    +
    +

    {item.name}

    + {sourceLabel(item.sourceType)} + {#if item.isActive} + Active + {:else} + Inactive + {/if} +
    +

    {item.description}

    +
    + {priceLabel(item.price)} + · + + {#if item.stock === null} + Unlimited stock + {:else} + {item.stock} in stock + {/if} + +
    +
    +
  • + {/each} +
+ {/if} +
+
+ + diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.server.ts index 6dff3ee..f2d9ac0 100644 --- a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.server.ts +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.server.ts @@ -33,11 +33,11 @@ const addWarehouseItemSchema = z.object({ // auto pull from warehouse but add optional overrides name: z.string().min(1).max(50).or(z.string().max(0)), description: z.string().min(1), // required bcs warehouse doesn't provide - imageUrl: z.url().optional(), - price: z.int(), + imageUrl: z.union([z.url(), z.literal('')]).optional(), + price: z.coerce.number().int(), // stock should be auto populated // stock: z.int().optional(), // optional stock override, check later that this is not over current stock - isActive: z.boolean().default(false) + isActive: z.coerce.boolean().default(false) }) const addWarehouseTemplateSchema = z.object({ @@ -45,10 +45,10 @@ const addWarehouseTemplateSchema = z.object({ name: z.string().min(1).max(50).or(z.string().max(0)), // not necessarily going to match template name description: z.string().min(1).max(2000), imageUrl: z.url(), - price: z.int(), + price: z.coerce.number().int(), // should be autopopulated // stock: z.int(), // set max as current stock - isActive: z.boolean().default(false) + isActive: z.coerce.boolean().default(false) }) export const load: PageServerLoad = async ({ parent }) => { diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.svelte b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.svelte index e69de29..c03b9ca 100644 --- a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.svelte +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/+page.svelte @@ -0,0 +1,296 @@ + + + + Add Shop Item - {info?.label ?? data.pathwayId} + + +
+ + Back + Back to Shop + + +
+

Add a Shop Item

+

Add an item that participants can spend their pathway currency on.

+
+ + {#if form?.success} +
+ Created item {form.item?.name}. + Back to shop +
+ {/if} + +
+ + + +
+ + {#if activeTab === 'custom'} +
+

Custom item

+

A pure custom listing (e.g., a grant or a digital reward). You handle fulfillment manually.

+ +
+ + + +
+ + +
+ + +
+
+ {:else if activeTab === 'warehouse'} +
+

Warehouse item

+

Link directly to a single warehouse item. Stock is pulled from the warehouse.

+ + {#if data.warehouseItems.length === 0} +

No warehouse items available.

+ {:else} +
+ + + + + + + +
+ {/if} +
+ {:else} +
+

Warehouse template

+

Link to a warehouse template (a bundle of items). Stock is computed from template components.

+ + {#if data.warehouseTemplates.length === 0} +

No warehouse templates available.

+ {:else} +
+ + + + + + + +
+ {/if} +
+ {/if} +
+ + diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/page.server.test.ts b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/page.server.test.ts new file mode 100644 index 0000000..11db9aa --- /dev/null +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/create/page.server.test.ts @@ -0,0 +1,250 @@ +/** + * Action-level tests for the shop item creation page. Covers all three + * "source" types — CUSTOM, WAREHOUSE_ITEM, and WAREHOUSE_TEMPLATE — with + * the db, including warehouse lookups, fully mocked. + */ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +type Queue = unknown[]; + +function makeChain(queue: Queue) { + const handler: ProxyHandler = { + get(_, prop) { + if (prop === 'then') { + const value = queue.length > 0 ? queue.shift() : undefined; + const p = Promise.resolve(value); + return p.then.bind(p); + } + return () => proxy; + } + }; + const proxy: object = new Proxy({}, handler); + return proxy; +} + +const insertQueue: Queue = []; +const selectQueue: Queue = []; + +const mockAmbassadorFindFirst = vi.fn(); +const mockWarehouseItemFindFirst = vi.fn(); +const mockWarehouseTemplateFindFirst = vi.fn(); + +vi.mock('$lib/server/db', () => ({ + db: { + query: { + ambassadorPathway: { + findFirst: (...args: unknown[]) => mockAmbassadorFindFirst(...args) + }, + warehouseItem: { + findFirst: (...args: unknown[]) => mockWarehouseItemFindFirst(...args) + }, + warehouseOrderTemplate: { + findFirst: (...args: unknown[]) => mockWarehouseTemplateFindFirst(...args) + } + }, + select: () => makeChain(selectQueue), + insert: () => makeChain(insertQueue) + } +})); + +const { actions } = await import('./+page.server'); + +function makeFormData(data: Record) { + return { + formData: async () => { + const fd = new FormData(); + for (const [k, v] of Object.entries(data)) fd.append(k, v); + return fd; + } + }; +} + +function makeEvent(opts: { + form: Record; + pathway?: string; + user?: { id: string } | null; +}) { + const { form, pathway = 'python', user = { id: 'amb-1' } } = opts; + return { + request: makeFormData(form), + params: { pathway }, + locals: { user } + } as any; +} + +beforeEach(() => { + insertQueue.length = 0; + selectQueue.length = 0; + vi.clearAllMocks(); +}); + +// ---- createCustom ----------------------------------------------------------- + +describe('createCustom action', () => { + const validForm = { + name: 'Free Sticker', + description: 'A sticker as a grant reward.', + imageUrl: 'https://example.com/sticker.png', + price: '0', + stock: '10' + }; + + it('inserts a CUSTOM shop item and returns it', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + insertQueue.push([{ id: 'item-1', name: 'Free Sticker', sourceType: 'CUSTOM' }]); + + const event = makeEvent({ form: validForm }); + const result = await actions.createCustom(event); + + expect(result).toEqual({ + success: true, + item: { id: 'item-1', name: 'Free Sticker', sourceType: 'CUSTOM' } + }); + }); + + it('rejects unauthenticated users with a redirect', async () => { + const event = makeEvent({ form: validForm, user: null }); + await expect(actions.createCustom(event)).rejects.toMatchObject({ status: 302 }); + }); + + it('throws 404 for an invalid pathway slug', async () => { + const event = makeEvent({ form: validForm, pathway: 'imaginary' }); + await expect(actions.createCustom(event)).rejects.toMatchObject({ status: 404 }); + }); + + it('throws 403 when the caller is not an ambassador for the pathway', async () => { + mockAmbassadorFindFirst.mockResolvedValue(undefined); + + const event = makeEvent({ form: validForm }); + await expect(actions.createCustom(event)).rejects.toMatchObject({ status: 403 }); + }); + + it('throws 400 on invalid form data', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + + const event = makeEvent({ + form: { ...validForm, imageUrl: 'not a url' } + }); + await expect(actions.createCustom(event)).rejects.toMatchObject({ status: 400 }); + }); +}); + +// ---- createWarehouse -------------------------------------------------------- + +describe('createWarehouse action', () => { + const validForm = { + id: 'wh-1', + name: '', + description: 'Pulled from warehouse stock.', + price: '15' + }; + + it('inserts a WAREHOUSE_ITEM-backed shop item using warehouse defaults', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockWarehouseItemFindFirst.mockResolvedValue({ + id: 'wh-1', + name: 'Holographic Sticker', + imageUrl: 'https://example.com/holo.png', + quantity: 42 + }); + insertQueue.push([ + { id: 'shop-1', name: 'Holographic Sticker', sourceType: 'WAREHOUSE_ITEM' } + ]); + + const event = makeEvent({ form: validForm }); + const result = await actions.createWarehouse(event); + + expect(result).toMatchObject({ + success: true, + item: { sourceType: 'WAREHOUSE_ITEM' } + }); + }); + + it('throws 404 when the linked warehouse item does not exist', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockWarehouseItemFindFirst.mockResolvedValue(undefined); + + const event = makeEvent({ form: validForm }); + await expect(actions.createWarehouse(event)).rejects.toMatchObject({ status: 404 }); + }); + + it('throws 403 for non-ambassadors', async () => { + mockAmbassadorFindFirst.mockResolvedValue(undefined); + const event = makeEvent({ form: validForm }); + await expect(actions.createWarehouse(event)).rejects.toMatchObject({ status: 403 }); + }); +}); + +// ---- createWarehouseTemplate ------------------------------------------------ + +describe('createWarehouseTemplate action', () => { + const validForm = { + id: 'tpl-1', + name: 'Starter Pack', + description: 'Bundle of stickers and a pin.', + imageUrl: 'https://example.com/bundle.png', + price: '50' + }; + + it('computes max stock from the cheapest template component and inserts the shop item', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockWarehouseTemplateFindFirst.mockResolvedValue({ + id: 'tpl-1', + name: 'Starter Pack', + createdById: 'amb-1', + isPublic: false + }); + // Template-items join lookup: 3 stickers (stock 30 → 10) and 1 pin (stock 8 → 8). + // min(10, 8) = 8 expected stock. + selectQueue.push([ + { perOrder: 3, available: 30 }, + { perOrder: 1, available: 8 } + ]); + insertQueue.push([ + { id: 'shop-2', name: 'Starter Pack', sourceType: 'WAREHOUSE_TEMPLATE', stock: 8 } + ]); + + const event = makeEvent({ form: validForm }); + const result = await actions.createWarehouseTemplate(event); + + expect(result).toMatchObject({ + success: true, + item: { sourceType: 'WAREHOUSE_TEMPLATE', stock: 8 } + }); + }); + + it('throws 400 if the template has no items', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockWarehouseTemplateFindFirst.mockResolvedValue({ + id: 'tpl-1', + name: 'Empty', + createdById: 'amb-1', + isPublic: false + }); + selectQueue.push([]); + + const event = makeEvent({ form: validForm }); + await expect(actions.createWarehouseTemplate(event)).rejects.toMatchObject({ status: 400 }); + }); + + it('throws 403 when caller is not the template owner and template is private', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockWarehouseTemplateFindFirst.mockResolvedValue({ + id: 'tpl-1', + name: 'Other', + createdById: 'somebody-else', + isPublic: false + }); + + const event = makeEvent({ form: validForm }); + await expect(actions.createWarehouseTemplate(event)).rejects.toMatchObject({ status: 403 }); + }); + + it('throws 404 when template not found', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockWarehouseTemplateFindFirst.mockResolvedValue(undefined); + + const event = makeEvent({ form: validForm }); + await expect(actions.createWarehouseTemplate(event)).rejects.toMatchObject({ status: 404 }); + }); +}); diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts index e643f0c..ee9839d 100644 --- a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.server.ts @@ -48,7 +48,7 @@ export const load: PageServerLoad = async ({ params, parent }) => { const pendingOrders = await db.query.shopOrder.findMany({ where: and(eq(shopOrder.pathway, pathwayId), eq(shopOrder.status, 'PENDING')), - with: { item: true } + with: { item: true, user: true } }); // Collect every warehouse item and template needed across all orders so diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.svelte b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.svelte index 8245c29..2e39b15 100644 --- a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.svelte +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/+page.svelte @@ -1 +1,328 @@ - \ No newline at end of file + + + + Fulfill Orders - {info?.label ?? data.pathwayId} + + +
+ + Back + Back to Shop + + +
+

Fulfill Orders

+

+ {data.pendingOrders.length} pending order{data.pendingOrders.length === 1 ? '' : 's'} for {info?.label ?? data.pathwayId}. +

+
+ + {#if form && 'error' in form && form.error} +
{form.error}
+ {/if} + {#if form?.success} +
+ Order updated.{#if 'warehouseOrderId' in form && form.warehouseOrderId} + Warehouse order created: {form.warehouseOrderId} + {/if} +
+ {/if} + + {#if data.pendingOrders.length === 0} +
+

No pending orders right now. 🎉

+
+ {:else} +
    + {#each data.pendingOrders as order (order.id)} + {@const est = data.estimatesByOrderId[order.id]} + {@const sourceType = order.item?.sourceType ?? 'CUSTOM'} + {@const isCustom = sourceType === 'CUSTOM'} +
  • +
    +
    +

    {order.itemNameSnapshot}

    +
    + {sourceLabel(sourceType)} + · + {order.totalAmount} {order.totalAmount === 1 ? 'wish' : 'wishes'} + · + {new Date(order.createdAt).toLocaleString()} +
    +
    +
    #{order.id.slice(0, 8)}
    +
    + +
    +
    +
    Buyer
    + {#if order.user} +
    {order.user.firstName ?? ''} {order.user.lastName ?? ''}
    +
    {order.user.email}
    + {:else} +
    User no longer exists
    + {/if} + {#if order.phone} +
    {order.phone}
    + {/if} +
    + +
    +
    Shipping address
    + {#if order.shippingAddress} +
    {order.shippingAddress.addressLine1}
    + {#if order.shippingAddress.addressLine2} +
    {order.shippingAddress.addressLine2}
    + {/if} +
    + {order.shippingAddress.city}, {order.shippingAddress.stateProvince} + {order.shippingAddress.zipPostalCode} +
    +
    {order.shippingAddress.country}
    + {:else} +
    No shipping address (digital / grant)
    + {/if} +
    + + {#if !isCustom} +
    +
    Estimated cost
    + {#if est} +
    Items: {formatCents(est.itemTotalCents)}
    +
    + Shipping: + {formatCents(est.estimatedShippingCents)} + {#if est.estimatedShippingCents == null} + (no quote) + {/if} +
    +
    Total: {formatCents(est.totalCents)}
    + {:else} +
    No estimate available
    + {/if} +
    + {/if} +
    + + {#if order.userNotes} +
    + Buyer note: {order.userNotes} +
    + {/if} + + {#if isCustom} +
    + This is a custom item. Approving will mark the order fulfilled — you handle delivery manually. +
    + {/if} + +
    +
    + + + +
    + + {#if denyOpenFor === order.id} +
    + + + + +
    + {:else} + + {/if} +
    +
  • + {/each} +
+ {/if} +
+ + diff --git a/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/page.server.test.ts b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/page.server.test.ts new file mode 100644 index 0000000..5df352c --- /dev/null +++ b/resolution-frontend/src/routes/app/ambassador/[pathway]/shop/fufill/page.server.test.ts @@ -0,0 +1,422 @@ +/** + * Action-level tests for the shop fulfill page. We mock the db (drizzle's + * fluent builder), packaging/shipping helpers, and HCB so the tests run + * with no real warehouse items and no live HCB integration — mirroring a + * local dev environment. + */ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +// ---- db mock ---------------------------------------------------------------- + +type Queue = unknown[]; + +/** + * Drizzle's query builder is chainable (e.g. db.select().from().where()). + * This proxy lets any chain end in an awaited value pulled from a queue, + * so each test can pre-seed the values its action will see. + */ +function makeChain(queue: Queue) { + const handler: ProxyHandler = { + get(_, prop) { + if (prop === 'then') { + const value = queue.length > 0 ? queue.shift() : undefined; + const p = Promise.resolve(value); + return p.then.bind(p); + } + if (prop === 'catch') { + const p = Promise.resolve(undefined); + return p.catch.bind(p); + } + return () => proxy; + } + }; + const proxy: object = new Proxy({}, handler); + return proxy; +} + +const selectQueue: Queue = []; +const insertQueue: Queue = []; +const updateQueue: Queue = []; + +const txSelectQueue: Queue = []; +const txInsertQueue: Queue = []; +const txUpdateQueue: Queue = []; + +const mockAmbassadorFindFirst = vi.fn(); +const mockShopOrderFindFirst = vi.fn(); + +vi.mock('$lib/server/db', () => ({ + db: { + query: { + ambassadorPathway: { + findFirst: (...args: unknown[]) => mockAmbassadorFindFirst(...args) + }, + shopOrder: { + findFirst: (...args: unknown[]) => mockShopOrderFindFirst(...args) + } + }, + select: () => makeChain(selectQueue), + insert: () => makeChain(insertQueue), + update: () => makeChain(updateQueue), + transaction: async (cb: (tx: unknown) => Promise) => { + const tx = { + select: () => makeChain(txSelectQueue), + insert: () => makeChain(txInsertQueue), + update: () => makeChain(txUpdateQueue) + }; + return cb(tx); + } + } +})); + +// ---- external helper mocks -------------------------------------------------- + +const mockSelectPackaging = vi.fn(); +const mockFetchCheapestRate = vi.fn(); +const mockGetOrgIdForPathway = vi.fn(); +const mockCreateHcbTransfer = vi.fn(); +const mockResolveCountryCode = vi.fn(); + +vi.mock('$lib/server/packaging', () => ({ + selectPackaging: (...args: unknown[]) => mockSelectPackaging(...args) +})); +vi.mock('$lib/server/canada-post', () => ({ + fetchCheapestRate: (...args: unknown[]) => mockFetchCheapestRate(...args) +})); +vi.mock('$lib/server/hcb', () => ({ + getOrgIdForPathway: (...args: unknown[]) => mockGetOrgIdForPathway(...args), + createHcbTransfer: (...args: unknown[]) => mockCreateHcbTransfer(...args) +})); +vi.mock('$lib/server/countries', () => ({ + resolveCountryCode: (...args: unknown[]) => mockResolveCountryCode(...args) +})); + +const { actions } = await import('./+page.server'); + +// ---- helpers ---------------------------------------------------------------- + +function makeFormData(data: Record) { + return { + formData: async () => { + const fd = new FormData(); + for (const [k, v] of Object.entries(data)) fd.append(k, v); + return fd; + } + }; +} + +function makeEvent(opts: { + form: Record; + pathway?: string; + user?: { id: string } | null; +}) { + const { form, pathway = 'python', user = { id: 'amb-1' } } = opts; + return { + request: makeFormData(form), + params: { pathway }, + locals: { user } + } as any; +} + +const SAMPLE_ADDRESS = { + addressLine1: '15 Falls Rd', + addressLine2: '', + city: 'Shelburne', + stateProvince: 'VT', + country: 'United States', + zipPostalCode: '05482' +}; + +const SAMPLE_USER = { + id: 'user-1', + email: 'buyer@example.com', + firstName: 'Buyer', + lastName: 'Person' +}; + +const SAMPLE_PACKAGING = { + category: 'box' as const, + label: 'small box', + lengthIn: 6, + widthIn: 4, + heightIn: 2, + weightGrams: 200, + subjectToChange: false +}; + +function reset() { + selectQueue.length = 0; + insertQueue.length = 0; + updateQueue.length = 0; + txSelectQueue.length = 0; + txInsertQueue.length = 0; + txUpdateQueue.length = 0; + vi.clearAllMocks(); + mockGetOrgIdForPathway.mockReturnValue(null); // simulate no HCB linked + mockResolveCountryCode.mockReturnValue('US'); + mockSelectPackaging.mockReturnValue(SAMPLE_PACKAGING); + mockFetchCheapestRate.mockResolvedValue({ shippingCostUsd: 5, serviceName: 'Test Mail' }); +} + +beforeEach(reset); + +// ---- deny tests ------------------------------------------------------------- + +describe('deny action', () => { + it('marks the order as REJECTED with the provided reason', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockShopOrderFindFirst.mockResolvedValue({ + id: 'order-1', + pathway: 'PYTHON', + item: null, + user: SAMPLE_USER + }); + // `db.update(shopOrder).set(...).where(...)` — pop one value (unused). + updateQueue.push(undefined); + + const event = makeEvent({ form: { id: 'order-1', note: 'spammy address' } }); + const result = await actions.deny(event); + + expect(result).toEqual({ success: true, orderId: 'order-1' }); + }); + + it('throws 403 when caller is not an ambassador for the pathway', async () => { + mockAmbassadorFindFirst.mockResolvedValue(undefined); + + const event = makeEvent({ form: { id: 'order-1', note: 'no' } }); + await expect(actions.deny(event)).rejects.toMatchObject({ status: 403 }); + }); + + it('throws 400 when input validation fails (missing required reason)', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + + const event = makeEvent({ form: { id: 'order-1' } }); + await expect(actions.deny(event)).rejects.toMatchObject({ status: 400 }); + }); + + it('redirects to login when not authenticated', async () => { + const event = makeEvent({ form: { id: 'order-1', note: 'x' }, user: null }); + await expect(actions.deny(event)).rejects.toMatchObject({ status: 302 }); + }); + + it('throws 404 for unknown pathway in URL', async () => { + const event = makeEvent({ form: { id: 'order-1', note: 'x' }, pathway: 'not_a_pathway' }); + await expect(actions.deny(event)).rejects.toMatchObject({ status: 404 }); + }); + + it('throws 400 when order id does not exist', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockShopOrderFindFirst.mockResolvedValue(undefined); + + const event = makeEvent({ form: { id: 'missing', note: 'x' } }); + await expect(actions.deny(event)).rejects.toMatchObject({ status: 400 }); + }); +}); + +// ---- approve tests ---------------------------------------------------------- + +describe('approve action — CUSTOM items', () => { + it('marks the order FULFILLED without touching the warehouse', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockShopOrderFindFirst.mockResolvedValue({ + id: 'order-1', + pathway: 'PYTHON', + item: { id: 'item-1', sourceType: 'CUSTOM', linkedWarehouseItemId: null, linkedWarehouseTemplateId: null }, + user: SAMPLE_USER, + shippingAddress: SAMPLE_ADDRESS + }); + // The CUSTOM branch wraps the shopOrder update in a transaction. + txUpdateQueue.push(undefined); + + const event = makeEvent({ form: { id: 'order-1', note: 'gifted manually' } }); + const result = await actions.approve(event); + + expect(result).toEqual({ success: true }); + expect(mockFetchCheapestRate).not.toHaveBeenCalled(); + expect(mockCreateHcbTransfer).not.toHaveBeenCalled(); + }); +}); + +describe('approve action — WAREHOUSE_ITEM', () => { + const baseOrder = { + id: 'order-2', + pathway: 'PYTHON', + item: { + id: 'shop-item-1', + sourceType: 'WAREHOUSE_ITEM', + linkedWarehouseItemId: 'wh-1', + linkedWarehouseTemplateId: null + }, + user: SAMPLE_USER, + shippingAddress: SAMPLE_ADDRESS, + phone: '555-0100' + }; + + it('creates a warehouse order and decrements stock when HCB is not linked', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockShopOrderFindFirst.mockResolvedValue(baseOrder); + // Stock lookup: one warehouse item with plenty of stock. + selectQueue.push([ + { + id: 'wh-1', + name: 'Sticker', + quantity: 10, + costCents: 100, + lengthIn: 6, + widthIn: 4, + heightIn: 0.5, + weightGrams: 50, + packageType: 'flat' + } + ]); + // Inside transaction: warehouseOrder insert returns the new id, then + // per-line insert returns nothing, then the stock-decrement update + // must return a row to satisfy the "another order claimed it" check, + // then the shopOrder update is awaited. + txInsertQueue.push([{ id: 'wo-1' }]); // warehouseOrder insert + txInsertQueue.push(undefined); // warehouseOrderItem insert + txUpdateQueue.push([{ id: 'wh-1' }]); // decrement stock + txUpdateQueue.push(undefined); // shopOrder update + // After tx, billing branch: getOrgIdForPathway → null (no HCB), so we + // hit `db.update(warehouseOrder).set({billingStatus: 'NOT_APPLICABLE'})`. + updateQueue.push(undefined); + + const event = makeEvent({ form: { id: 'order-2' } }); + const result = await actions.approve(event); + + expect(result).toEqual({ success: true, warehouseOrderId: 'wo-1' }); + expect(mockGetOrgIdForPathway).toHaveBeenCalledWith('PYTHON'); + expect(mockCreateHcbTransfer).not.toHaveBeenCalled(); + expect(mockFetchCheapestRate).toHaveBeenCalled(); + }); + + it('returns a 400 ActionFailure when stock is insufficient', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockShopOrderFindFirst.mockResolvedValue(baseOrder); + // Stock lookup: not enough quantity (0 < 1). + selectQueue.push([ + { + id: 'wh-1', + name: 'Sticker', + quantity: 0, + costCents: 100, + lengthIn: 6, + widthIn: 4, + heightIn: 0.5, + weightGrams: 50, + packageType: 'flat' + } + ]); + + const event = makeEvent({ form: { id: 'order-2' } }); + const result = await actions.approve(event); + + // fail() returns an ActionFailure object, not a thrown error. + expect(result).toMatchObject({ + status: 400, + data: { error: expect.stringContaining('Insufficient stock') } + }); + }); + + it('errors when the shop item lost its warehouse link', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockShopOrderFindFirst.mockResolvedValue({ + ...baseOrder, + item: { ...baseOrder.item, linkedWarehouseItemId: null } + }); + + const event = makeEvent({ form: { id: 'order-2' } }); + await expect(actions.approve(event)).rejects.toMatchObject({ status: 400 }); + }); +}); + +describe('approve action — WAREHOUSE_TEMPLATE', () => { + const baseOrder = { + id: 'order-3', + pathway: 'PYTHON', + item: { + id: 'shop-item-2', + sourceType: 'WAREHOUSE_TEMPLATE', + linkedWarehouseItemId: null, + linkedWarehouseTemplateId: 'tpl-1' + }, + user: SAMPLE_USER, + shippingAddress: SAMPLE_ADDRESS, + phone: null + }; + + it('expands the template into multiple line items and creates the warehouse order', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockShopOrderFindFirst.mockResolvedValue(baseOrder); + // Template items lookup (`db.select().from(warehouseOrderTemplateItem)`). + selectQueue.push([ + { id: 'ti-1', templateId: 'tpl-1', warehouseItemId: 'wh-1', quantity: 2 }, + { id: 'ti-2', templateId: 'tpl-1', warehouseItemId: 'wh-2', quantity: 1 } + ]); + // Stock lookup for both warehouse items. + selectQueue.push([ + { + id: 'wh-1', name: 'Sticker', quantity: 50, + costCents: 100, lengthIn: 6, widthIn: 4, heightIn: 0.5, + weightGrams: 50, packageType: 'flat' + }, + { + id: 'wh-2', name: 'Pin', quantity: 30, + costCents: 250, lengthIn: 1, widthIn: 1, heightIn: 0.5, + weightGrams: 20, packageType: 'flat' + } + ]); + // Transaction queues: + txInsertQueue.push([{ id: 'wo-2' }]); // warehouseOrder + txInsertQueue.push(undefined); // warehouseOrderItem insert (wh-1) + txInsertQueue.push(undefined); // warehouseOrderItem insert (wh-2) + // Two stock decrement updates, then the shopOrder transition. + txUpdateQueue.push([{ id: 'wh-1' }]); + txUpdateQueue.push([{ id: 'wh-2' }]); + txUpdateQueue.push(undefined); // shopOrder update + // Post-tx billing branch: no HCB, so one update to mark NOT_APPLICABLE. + updateQueue.push(undefined); + + const event = makeEvent({ form: { id: 'order-3' } }); + const result = await actions.approve(event); + + expect(result).toEqual({ success: true, warehouseOrderId: 'wo-2' }); + // 1 wo + 2 line item inserts inside the tx. + expect(mockCreateHcbTransfer).not.toHaveBeenCalled(); + }); + + it('errors when the template has no items', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockShopOrderFindFirst.mockResolvedValue(baseOrder); + selectQueue.push([]); // empty template + + const event = makeEvent({ form: { id: 'order-3' } }); + await expect(actions.approve(event)).rejects.toMatchObject({ status: 400 }); + }); +}); + +describe('approve action — auth & validation', () => { + it('redirects to login when not authenticated', async () => { + const event = makeEvent({ form: { id: 'order-1' }, user: null }); + await expect(actions.approve(event)).rejects.toMatchObject({ status: 302 }); + }); + + it('throws 403 when caller is not an ambassador', async () => { + mockAmbassadorFindFirst.mockResolvedValue(undefined); + const event = makeEvent({ form: { id: 'order-1' } }); + await expect(actions.approve(event)).rejects.toMatchObject({ status: 403 }); + }); + + it('throws 404 when pathway is not recognized', async () => { + const event = makeEvent({ form: { id: 'order-1' }, pathway: 'martian' }); + await expect(actions.approve(event)).rejects.toMatchObject({ status: 404 }); + }); + + it('throws 404 when the order id is not found', async () => { + mockAmbassadorFindFirst.mockResolvedValue({ id: 'ap-1' }); + mockShopOrderFindFirst.mockResolvedValue(undefined); + + const event = makeEvent({ form: { id: 'missing' } }); + await expect(actions.approve(event)).rejects.toMatchObject({ status: 404 }); + }); +}); From 1455232718049ce4114b5e2db9932fb294d0b006 Mon Sep 17 00:00:00 2001 From: Niko Yu <137782638+thesleepyniko@users.noreply.github.com> Date: Sat, 16 May 2026 14:58:09 -0400 Subject: [PATCH 52/52] fix: test failing --- resolution-frontend/src/hooks.server.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resolution-frontend/src/hooks.server.test.ts b/resolution-frontend/src/hooks.server.test.ts index 40d7756..1672ec6 100644 --- a/resolution-frontend/src/hooks.server.test.ts +++ b/resolution-frontend/src/hooks.server.test.ts @@ -17,6 +17,10 @@ vi.mock('$lib/server/season', () => ({ ensureSeasonFromEnv: () => Promise.resolve() })); +vi.mock('$lib/server/devSeed', () => ({ + seedDevShops: () => Promise.resolve() +})); + const { handle } = await import('./hooks.server'); function createMockEvent() {