Files
meal-planner/src/payload.config.ts

146 lines
4.8 KiB
TypeScript

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<Config>({
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),
}),
],
});