import { postgresAdapter } from "@payloadcms/db-postgres"; import { lexicalEditor } from "@payloadcms/richtext-lexical"; import path from "path"; import { buildConfig } from "payload"; import { fileURLToPath } from "url"; import { s3Storage } from "@payloadcms/storage-s3"; import { Tenants } from "./collections/Tenants"; import Users from "./collections/Users"; import { Residents } from "./collections/Residents"; import { MealOrders } from "./collections/MealOrders"; import { Meals } from "./collections/Meals"; import { Media } from "./collections/Media"; import { multiTenantPlugin } from "@payloadcms/plugin-multi-tenant"; import { isSuperAdmin } from "./access/isSuperAdmin"; import type { Config } from "./payload-types"; import { getUserTenantIDs } from "./utilities/getUserTenantIDs"; import { seed } from "./seed"; const filename = fileURLToPath(import.meta.url); const dirname = path.dirname(filename); // Use PostgreSQL when DATABASE_URI is set, SQLite only for local development // Migration commands: // pnpm payload migrate:create - Create migration file // pnpm payload migrate - Run pending migrations // pnpm payload migrate:fresh - Drop all & re-run migrations // pnpm payload migrate:reset - Rollback all migrations // pnpm payload migrate:status - Check migration status const getDatabaseAdapter = async () => { if (process.env.DATABASE_URI) { // Conditionally import migrations only when using PostgreSQL const { migrations } = await import("./migrations"); return postgresAdapter({ pool: { connectionString: process.env.DATABASE_URI, }, // Use migration files from src/migrations/ instead of auto-push push: false, migrationDir: path.resolve(dirname, "migrations"), prodMigrations: migrations, }); } // Only load SQLite in development (no DATABASE_URI set) // Dynamic import to avoid loading libsql in production/Docker const { sqliteAdapter } = await import("@payloadcms/db-sqlite"); return sqliteAdapter({ client: { url: "file:./meal-planner.db", }, // Use push mode for SQLite in development (auto-sync schema) push: true, }); }; // eslint-disable-next-line no-restricted-exports export default buildConfig({ admin: { user: "users", meta: { titleSuffix: "- Meal Planner", }, components: { views: { kitchenDashboard: { Component: "@/app/(payload)/admin/views/KitchenDashboard#KitchenDashboard", path: "/kitchen-dashboard", }, }, }, }, collections: [Users, Tenants, Residents, MealOrders, Meals, Media], db: await getDatabaseAdapter(), onInit: async (payload) => { // Run migrations automatically on startup (for Docker/production) payload.logger.info("Running database migrations..."); try { await payload.db.migrate(); payload.logger.info("Migrations completed successfully"); } catch (err) { const message = err instanceof Error ? err.message : String(err); payload.logger.error(`Migration failed: ${message}`); // Don't throw - migrations may already be applied } // Seed database if SEED_DB is set if (process.env.SEED_DB) { await seed(payload); } }, editor: lexicalEditor({}), graphQL: { schemaOutputFile: path.resolve(dirname, "generated-schema.graphql"), }, secret: process.env.PAYLOAD_SECRET as string, typescript: { outputFile: path.resolve(dirname, "payload-types.ts"), }, plugins: [ // Conditionally add S3 storage when MINIO_ENDPOINT is set (Docker environment) ...(process.env.MINIO_ENDPOINT ? [ s3Storage({ collections: { media: true, }, bucket: process.env.S3_BUCKET || "meal-planner", config: { endpoint: process.env.MINIO_ENDPOINT, region: process.env.S3_REGION || "us-east-1", credentials: { accessKeyId: process.env.S3_ACCESS_KEY_ID || "", secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || "", }, forcePathStyle: true, // Required for MinIO }, }), ] : []), multiTenantPlugin({ collections: { // Enable multi-tenancy for residents, meal orders, and meals residents: {}, "meal-orders": {}, meals: {}, }, tenantField: { access: { read: () => true, update: ({ req }) => { if (isSuperAdmin(req.user)) { return true; } return getUserTenantIDs(req.user).length > 0; }, }, }, tenantsArrayField: { includeDefaultField: false, }, userHasAccessToAllTenants: (user) => isSuperAdmin(user), }), ], });