diff --git a/apps/api/drizzle.presi.config.ts b/apps/api/drizzle.presi.config.ts new file mode 100644 index 000000000..f73c7e5ac --- /dev/null +++ b/apps/api/drizzle.presi.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + schema: './src/modules/presi/schema.ts', + out: './drizzle/presi', + dialect: 'postgresql', + dbCredentials: { + url: process.env.DATABASE_URL || 'postgresql://mana:devpassword@localhost:5432/mana_platform', + }, + schemaFilter: ['presi'], +}); diff --git a/apps/api/package.json b/apps/api/package.json index beac6ebc8..714b2e365 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -9,7 +9,8 @@ "start": "bun run dist/index.js", "type-check": "tsc --noEmit", "db:generate": "drizzle-kit generate", - "db:push": "drizzle-kit push" + "db:push": "drizzle-kit push", + "db:push:presi": "drizzle-kit push --config=drizzle.presi.config.ts" }, "dependencies": { "@ai-sdk/openai-compatible": "^2.0.41", diff --git a/apps/api/src/modules/presi/routes.ts b/apps/api/src/modules/presi/routes.ts index 641e18164..fcfd0a4f9 100644 --- a/apps/api/src/modules/presi/routes.ts +++ b/apps/api/src/modules/presi/routes.ts @@ -14,80 +14,20 @@ import type { AuthVariables } from '@mana/shared-hono'; import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; import { - pgSchema, - uuid, - text, - boolean, - timestamp, - integer, - jsonb, - index, -} from 'drizzle-orm/pg-core'; -import { relations } from 'drizzle-orm'; + decks, + slides, + themes, + sharedDecks, + decksRelations, + slidesRelations, + sharedDecksRelations, +} from './schema.js'; -// ─── DB Schema (read-only for share lookups) ──────────────── +// ─── DB Connection ───────────────────────────────────────── const DATABASE_URL = process.env.DATABASE_URL ?? 'postgresql://mana:devpassword@localhost:5432/mana_platform'; -const presiSchema = pgSchema('presi'); - -const decks = presiSchema.table('decks', { - id: uuid('id').primaryKey().defaultRandom(), - userId: text('user_id').notNull(), - title: text('title').notNull(), - description: text('description'), - themeId: uuid('theme_id'), - isPublic: boolean('is_public').default(false).notNull(), - createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), -}); - -const slides = presiSchema.table( - 'slides', - { - id: uuid('id').primaryKey().defaultRandom(), - deckId: uuid('deck_id').notNull(), - order: integer('order').default(0).notNull(), - content: jsonb('content'), - createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), - }, - (table) => [index('slides_deck_order_api_idx').on(table.deckId, table.order)] -); - -const themes = presiSchema.table('themes', { - id: uuid('id').primaryKey().defaultRandom(), - name: text('name').notNull(), - colors: jsonb('colors'), - fonts: jsonb('fonts'), - isDefault: boolean('is_default').default(false), -}); - -const sharedDecks = presiSchema.table( - 'shared_decks', - { - id: uuid('id').primaryKey().defaultRandom(), - deckId: uuid('deck_id').notNull(), - shareCode: text('share_code').notNull().unique(), - expiresAt: timestamp('expires_at', { withTimezone: true }), - createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), - }, - (table) => [index('shared_decks_deck_id_api_idx').on(table.deckId)] -); - -const decksRelations = relations(decks, ({ many }) => ({ - slides: many(slides), - sharedDecks: many(sharedDecks), -})); - -const slidesRelations = relations(slides, ({ one }) => ({ - deck: one(decks, { fields: [slides.deckId], references: [decks.id] }), -})); - -const sharedDecksRelations = relations(sharedDecks, ({ one }) => ({ - deck: one(decks, { fields: [sharedDecks.deckId], references: [decks.id] }), -})); - const connection = postgres(DATABASE_URL, { max: 5, idle_timeout: 20 }); const db = drizzle(connection, { schema: { diff --git a/apps/api/src/modules/presi/schema.ts b/apps/api/src/modules/presi/schema.ts new file mode 100644 index 000000000..946540224 --- /dev/null +++ b/apps/api/src/modules/presi/schema.ts @@ -0,0 +1,76 @@ +/** + * Presi module — DB schema + * + * Lives in mana_platform under its own pgSchema('presi'). + * Shared between routes.ts (runtime) and drizzle-kit (migrations/push). + */ + +import { + pgSchema, + uuid, + text, + boolean, + timestamp, + integer, + jsonb, + index, +} from 'drizzle-orm/pg-core'; +import { relations } from 'drizzle-orm'; + +export const presiSchema = pgSchema('presi'); + +export const decks = presiSchema.table('decks', { + id: uuid('id').primaryKey().defaultRandom(), + userId: text('user_id').notNull(), + title: text('title').notNull(), + description: text('description'), + themeId: uuid('theme_id'), + isPublic: boolean('is_public').default(false).notNull(), + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), +}); + +export const slides = presiSchema.table( + 'slides', + { + id: uuid('id').primaryKey().defaultRandom(), + deckId: uuid('deck_id').notNull(), + order: integer('order').default(0).notNull(), + content: jsonb('content'), + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), + }, + (table) => [index('slides_deck_order_api_idx').on(table.deckId, table.order)] +); + +export const themes = presiSchema.table('themes', { + id: uuid('id').primaryKey().defaultRandom(), + name: text('name').notNull(), + colors: jsonb('colors'), + fonts: jsonb('fonts'), + isDefault: boolean('is_default').default(false), +}); + +export const sharedDecks = presiSchema.table( + 'shared_decks', + { + id: uuid('id').primaryKey().defaultRandom(), + deckId: uuid('deck_id').notNull(), + shareCode: text('share_code').notNull().unique(), + expiresAt: timestamp('expires_at', { withTimezone: true }), + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), + }, + (table) => [index('shared_decks_deck_id_api_idx').on(table.deckId)] +); + +export const decksRelations = relations(decks, ({ many }) => ({ + slides: many(slides), + sharedDecks: many(sharedDecks), +})); + +export const slidesRelations = relations(slides, ({ one }) => ({ + deck: one(decks, { fields: [slides.deckId], references: [decks.id] }), +})); + +export const sharedDecksRelations = relations(sharedDecks, ({ one }) => ({ + deck: one(decks, { fields: [sharedDecks.deckId], references: [decks.id] }), +})); diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index dc685b741..f036200b3 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -10,5 +10,5 @@ "rootDir": "src", "types": ["bun-types"] }, - "include": ["src/**/*.ts", "scripts/**/*.ts"] + "include": ["src/**/*.ts", "scripts/**/*.ts", "drizzle.*.config.ts"] } diff --git a/apps/presi/package.json b/apps/presi/package.json index ea207f3a6..586403116 100644 --- a/apps/presi/package.json +++ b/apps/presi/package.json @@ -4,8 +4,8 @@ "private": true, "scripts": { "dev": "pnpm --filter '@presi/*' run dev", - "db:push": "pnpm --filter @presi/backend db:push", - "db:studio": "pnpm --filter @presi/backend db:studio" + "db:push": "pnpm --filter @mana/api db:push:presi", + "db:studio": "drizzle-kit studio --config=../../apps/api/drizzle.presi.config.ts" }, "devDependencies": { "typescript": "^5.7.2" diff --git a/scripts/setup-databases.sh b/scripts/setup-databases.sh index 95560c6cd..769adf4c3 100755 --- a/scripts/setup-databases.sh +++ b/scripts/setup-databases.sh @@ -54,9 +54,10 @@ create_schema_if_not_exists() { push_schema() { local filter=$1 local name=$2 + local script=${3:-db:push} echo -e "${YELLOW}Pushing schema for ${name}...${NC}" local output - output=$(pnpm --filter "$filter" db:push --force 2>&1) + output=$(pnpm --filter "$filter" "$script" --force 2>&1) local exit_code=$? if echo "$output" | grep -q "No projects matched the filters\|None of the selected packages has"; then echo -e " ${YELLOW}⊘ Skipped (no db:push script for ${filter})${NC}" @@ -117,7 +118,7 @@ setup_service() { push_schema "@traces/server" "traces" ;; presi) - push_schema "@presi/server" "presi" + push_schema "@mana/api" "presi" "db:push:presi" ;; uload) push_schema "@mana/uload-database" "uload"