diff --git a/packages/create-payload-app/src/lib/configure-payload-config.ts b/packages/create-payload-app/src/lib/configure-payload-config.ts index b33ce8228a2..291f947f86f 100644 --- a/packages/create-payload-app/src/lib/configure-payload-config.ts +++ b/packages/create-payload-app/src/lib/configure-payload-config.ts @@ -10,6 +10,9 @@ import { dbReplacements, storageReplacements } from './replacements.js' /** Update payload config with necessary imports and adapters */ export async function configurePayloadConfig(args: { dbType?: DbType + envNames?: { + dbUri: string + } packageJsonName?: string projectDirOrConfigPath: { payloadConfigPath: string } | { projectDir: string } storageAdapter?: StorageAdapterType @@ -116,15 +119,14 @@ export async function configurePayloadConfig(args: { if (dbConfigEndLineIndex) dbConfigEndLineIndex += 1 } - if (!dbConfigStartLineIndex || !dbConfigEndLineIndex) { - warning('Unable to update payload.config.ts with database adapter import') - } else { - // Replaces lines between `// database-adapter-config-start` and `// database-adapter-config-end` + if (dbConfigStartLineIndex && dbConfigEndLineIndex) { configLines.splice( dbConfigStartLineIndex, dbConfigEndLineIndex - dbConfigStartLineIndex + 1, - ...dbReplacement.configReplacement, + ...dbReplacement.configReplacement(args.envNames?.dbUri), ) + } else { + warning('Unable to update payload.config.ts with database adapter import') } fse.writeFileSync(payloadConfigPath, configLines.join('\n')) diff --git a/packages/create-payload-app/src/lib/create-project.spec.ts b/packages/create-payload-app/src/lib/create-project.spec.ts index 96ffa0879bb..7bb9d29b813 100644 --- a/packages/create-payload-app/src/lib/create-project.spec.ts +++ b/packages/create-payload-app/src/lib/create-project.spec.ts @@ -124,7 +124,7 @@ describe('createProject', () => { expect(content).not.toContain('// database-adapter-config-start') expect(content).not.toContain('// database-adapter-config-end') - expect(content).toContain(dbReplacement.configReplacement.join('\n')) + expect(content).toContain(dbReplacement.configReplacement().join('\n')) }) }) }) diff --git a/packages/create-payload-app/src/lib/replacements.ts b/packages/create-payload-app/src/lib/replacements.ts index c4ecf72c4f7..109b12f447f 100644 --- a/packages/create-payload-app/src/lib/replacements.ts +++ b/packages/create-payload-app/src/lib/replacements.ts @@ -1,16 +1,16 @@ import type { DbType, StorageAdapterType } from '../types.js' type DbAdapterReplacement = { - configReplacement: string[] + configReplacement: (envName?: string) => string[] importReplacement: string packageName: string } const mongodbReplacement: DbAdapterReplacement = { // Replacement between `// database-adapter-config-start` and `// database-adapter-config-end` - configReplacement: [ + configReplacement: (envName = 'DATABASE_URI') => [ ' db: mongooseAdapter({', - " url: process.env.DATABASE_URI || '',", + ` url: process.env.${envName} || '',`, ' }),', ], importReplacement: "import { mongooseAdapter } from '@payloadcms/db-mongodb'", @@ -18,10 +18,10 @@ const mongodbReplacement: DbAdapterReplacement = { } const postgresReplacement: DbAdapterReplacement = { - configReplacement: [ + configReplacement: (envName = 'DATABASE_URI') => [ ' db: postgresAdapter({', ' pool: {', - " connectionString: process.env.DATABASE_URI || '',", + ` connectionString: process.env.${envName} || '',`, ' },', ' }),', ], diff --git a/scripts/generate-template-variations.ts b/scripts/generate-template-variations.ts index 9bf709eb602..256f7614049 100644 --- a/scripts/generate-template-variations.ts +++ b/scripts/generate-template-variations.ts @@ -1,7 +1,19 @@ +/** + * This script generates variations of the templates into the `templates` directory. + * + * How to use: + * + * pnpm run script:gen-templates + * + * NOTE: You will likely have to commit by using the `--no-verify` flag to avoid the repo linting + * There is no way currently to have lint-staged ignore the templates directory. + */ + import type { DbType, StorageAdapterType } from 'packages/create-payload-app/src/types.js' import { configurePayloadConfig } from 'create-payload-app/lib/configure-payload-config.js' import { copyRecursiveSync } from 'create-payload-app/utils/copy-recursive-sync.js' +import * as fs from 'node:fs/promises' import { fileURLToPath } from 'node:url' import path from 'path' @@ -15,8 +27,17 @@ type TemplateVariations = { dirname: string db: DbType storage: StorageAdapterType + vercelDeployButtonLink?: string + envNames?: { + dbUri: string + } } +main().catch((error) => { + console.error(error) + process.exit(1) +}) + async function main() { const templatesDir = path.resolve(dirname, '../templates') @@ -26,12 +47,38 @@ async function main() { dirname: 'with-vercel-postgres', db: 'postgres', storage: 'vercelBlobStorage', + vercelDeployButtonLink: + `https://vercel.com/new/clone?repository-url=` + + encodeURI( + 'https://github.com/payloadcms/vercel-deploy-payload-postgres' + + '&project-name=payload-project' + + '&env=PAYLOAD_SECRET' + + '&build-command=pnpm run ci' + + '&stores=[{"type":"postgres"},{"type":"blob"}]', // Postgres and Vercel Blob Storage + ), + envNames: { + // This will replace the process.env.DATABASE_URI to process.env.POSTGRES_URL + dbUri: 'POSTGRES_URL', + }, }, { name: 'payload-vercel-mongodb-template', dirname: 'with-vercel-mongodb', db: 'mongodb', storage: 'vercelBlobStorage', + vercelDeployButtonLink: + `https://vercel.com/new/clone?repository-url=` + + encodeURI( + 'https://github.com/payloadcms/vercel-deploy-payload-postgres' + + '&project-name=payload-project' + + '&env=PAYLOAD_SECRET' + + '&build-command=pnpm run ci' + + '&stores=[{"type":"blob"}]' + // Vercel Blob Storage + '&integration-ids=oac_jnzmjqM10gllKmSrG0SGrHOH', // MongoDB Atlas + ), + envNames: { + dbUri: 'MONGODB_URI', + }, }, { name: 'payload-cloud-mongodb-template', @@ -41,7 +88,7 @@ async function main() { }, ] - for (const { name, dirname, db, storage } of variations) { + for (const { name, dirname, db, storage, vercelDeployButtonLink, envNames } of variations) { console.log(`Generating ${name}...`) const destDir = path.join(templatesDir, dirname) copyRecursiveSync(path.join(templatesDir, 'blank-3.0'), destDir) @@ -53,13 +100,67 @@ async function main() { packageJsonName: name, projectDirOrConfigPath: { projectDir: destDir }, storageAdapter: storage, + envNames, + }) + + await generateReadme({ + destDir, + data: { + name, + description: name, // TODO: Add descriptions + attributes: { db, storage }, + ...(vercelDeployButtonLink && { vercelDeployButtonLink }), + }, }) + // Copy in initial migration if db is postgres. This contains user and media. + if (db === 'postgres') { + const migrationSrcDir = path.join(templatesDir, '_data/migrations') + const migrationDestDir = path.join(destDir, 'src/migrations') + + // Make directory if it doesn't exist + if ((await fs.stat(migrationDestDir).catch(() => null)) === null) { + await fs.mkdir(migrationDestDir, { recursive: true }) + } + console.log(`Copying migrations from ${migrationSrcDir} to ${migrationDestDir}`) + copyRecursiveSync(migrationSrcDir, migrationDestDir) + } + + // TODO: Email? + + // TODO: Sharp? + console.log(`Done configuring payload config for ${destDir}/src/payload.config.ts`) } } -main().catch((error) => { - console.error(error) - process.exit(1) -}) +async function generateReadme({ + destDir, + data: { name, description, attributes, vercelDeployButtonLink }, +}: { + destDir: string + data: { + name: string + description: string + attributes: Pick + vercelDeployButtonLink?: string + } +}) { + let header = `# ${name}\n` + if (vercelDeployButtonLink) { + header += `[![Deploy with Vercel](https://vercel.com/button)](${vercelDeployButtonLink})\n` + } + + const readmeContent = `${header} +${description} + +## Attributes + +- **Database**: ${attributes.db} +- **Storage Adapter**: ${attributes.storage} +` + + const readmePath = path.join(destDir, 'README.md') + await fs.writeFile(readmePath, readmeContent) + console.log(`Generated README.md in ${readmePath}`) +} diff --git a/templates/_data/migrations/initial.json b/templates/_data/migrations/initial.json new file mode 100644 index 00000000000..c9d7eee22aa --- /dev/null +++ b/templates/_data/migrations/initial.json @@ -0,0 +1,375 @@ +{ + "id": "8146d795-d1a9-49be-857d-4320898b38fb", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "5", + "dialect": "pg", + "tables": { + "users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "email": { + "name": "email", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "reset_password_token": { + "name": "reset_password_token", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "reset_password_expiration": { + "name": "reset_password_expiration", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "salt": { + "name": "salt", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "hash": { + "name": "hash", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "login_attempts": { + "name": "login_attempts", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "lock_until": { + "name": "lock_until", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "users_created_at_idx": { + "name": "users_created_at_idx", + "columns": ["created_at"], + "isUnique": false + }, + "users_email_idx": { + "name": "users_email_idx", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "media": { + "name": "media", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "alt": { + "name": "alt", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "thumbnail_u_r_l": { + "name": "thumbnail_u_r_l", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "mime_type": { + "name": "mime_type", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "filesize": { + "name": "filesize", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "focal_x": { + "name": "focal_x", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "focal_y": { + "name": "focal_y", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "media_created_at_idx": { + "name": "media_created_at_idx", + "columns": ["created_at"], + "isUnique": false + }, + "media_filename_idx": { + "name": "media_filename_idx", + "columns": ["filename"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "payload_preferences": { + "name": "payload_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "payload_preferences_key_idx": { + "name": "payload_preferences_key_idx", + "columns": ["key"], + "isUnique": false + }, + "payload_preferences_created_at_idx": { + "name": "payload_preferences_created_at_idx", + "columns": ["created_at"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "payload_preferences_rels": { + "name": "payload_preferences_rels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "users_id": { + "name": "users_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "payload_preferences_rels_order_idx": { + "name": "payload_preferences_rels_order_idx", + "columns": ["order"], + "isUnique": false + }, + "payload_preferences_rels_parent_idx": { + "name": "payload_preferences_rels_parent_idx", + "columns": ["parent_id"], + "isUnique": false + }, + "payload_preferences_rels_path_idx": { + "name": "payload_preferences_rels_path_idx", + "columns": ["path"], + "isUnique": false + } + }, + "foreignKeys": { + "payload_preferences_rels_parent_fk": { + "name": "payload_preferences_rels_parent_fk", + "tableFrom": "payload_preferences_rels", + "tableTo": "payload_preferences", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_preferences_rels_users_fk": { + "name": "payload_preferences_rels_users_fk", + "tableFrom": "payload_preferences_rels", + "tableTo": "users", + "columnsFrom": ["users_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "payload_migrations": { + "name": "payload_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "batch": { + "name": "batch", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "payload_migrations_created_at_idx": { + "name": "payload_migrations_created_at_idx", + "columns": ["created_at"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} diff --git a/templates/_data/migrations/initial.ts b/templates/_data/migrations/initial.ts new file mode 100644 index 00000000000..a256f4abb7a --- /dev/null +++ b/templates/_data/migrations/initial.ts @@ -0,0 +1,93 @@ +import type { MigrateDownArgs, MigrateUpArgs} from '@payloadcms/db-postgres'; + +import { sql } from '@payloadcms/db-postgres' + +export async function up({ payload }: MigrateUpArgs): Promise { + await payload.db.drizzle.execute(sql` + +CREATE TABLE IF NOT EXISTS "users" ( + "id" serial PRIMARY KEY NOT NULL, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "email" varchar NOT NULL, + "reset_password_token" varchar, + "reset_password_expiration" timestamp(3) with time zone, + "salt" varchar, + "hash" varchar, + "login_attempts" numeric, + "lock_until" timestamp(3) with time zone +); + +CREATE TABLE IF NOT EXISTS "media" ( + "id" serial PRIMARY KEY NOT NULL, + "alt" varchar NOT NULL, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "url" varchar, + "thumbnail_u_r_l" varchar, + "filename" varchar, + "mime_type" varchar, + "filesize" numeric, + "width" numeric, + "height" numeric, + "focal_x" numeric, + "focal_y" numeric +); + +CREATE TABLE IF NOT EXISTS "payload_preferences" ( + "id" serial PRIMARY KEY NOT NULL, + "key" varchar, + "value" jsonb, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL +); + +CREATE TABLE IF NOT EXISTS "payload_preferences_rels" ( + "id" serial PRIMARY KEY NOT NULL, + "order" integer, + "parent_id" integer NOT NULL, + "path" varchar NOT NULL, + "users_id" integer +); + +CREATE TABLE IF NOT EXISTS "payload_migrations" ( + "id" serial PRIMARY KEY NOT NULL, + "name" varchar, + "batch" numeric, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL +); + +CREATE INDEX IF NOT EXISTS "users_created_at_idx" ON "users" ("created_at"); +CREATE UNIQUE INDEX IF NOT EXISTS "users_email_idx" ON "users" ("email"); +CREATE INDEX IF NOT EXISTS "media_created_at_idx" ON "media" ("created_at"); +CREATE UNIQUE INDEX IF NOT EXISTS "media_filename_idx" ON "media" ("filename"); +CREATE INDEX IF NOT EXISTS "payload_preferences_key_idx" ON "payload_preferences" ("key"); +CREATE INDEX IF NOT EXISTS "payload_preferences_created_at_idx" ON "payload_preferences" ("created_at"); +CREATE INDEX IF NOT EXISTS "payload_preferences_rels_order_idx" ON "payload_preferences_rels" ("order"); +CREATE INDEX IF NOT EXISTS "payload_preferences_rels_parent_idx" ON "payload_preferences_rels" ("parent_id"); +CREATE INDEX IF NOT EXISTS "payload_preferences_rels_path_idx" ON "payload_preferences_rels" ("path"); +CREATE INDEX IF NOT EXISTS "payload_migrations_created_at_idx" ON "payload_migrations" ("created_at"); +DO $$ BEGIN + ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "payload_preferences"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +DO $$ BEGIN + ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +`) +} + +export async function down({ payload }: MigrateDownArgs): Promise { + await payload.db.drizzle.execute(sql` + +DROP TABLE "users"; +DROP TABLE "media"; +DROP TABLE "payload_preferences"; +DROP TABLE "payload_preferences_rels"; +DROP TABLE "payload_migrations";`) +} diff --git a/templates/with-payload-cloud/README.md b/templates/with-payload-cloud/README.md index ab064ec7cc1..bf376ea5003 100644 --- a/templates/with-payload-cloud/README.md +++ b/templates/with-payload-cloud/README.md @@ -1,42 +1,8 @@ -# Payload Blank Template +# payload-cloud-mongodb-template -A blank template for [Payload](https://github.com/payloadcms/payload) to help you get up and running quickly. This repo may have been created by running `npx create-payload-app@latest` and selecting the "blank" template or by cloning this template on [Payload Cloud](https://payloadcms.com/new/clone/blank). +payload-cloud-mongodb-template -See the official [Examples Directory](https://github.com/payloadcms/payload/tree/main/examples) for details on how to use Payload in a variety of different ways. +## Attributes -## Development - -To spin up the project locally, follow these steps: - -1. First clone the repo -1. Then `cd YOUR_PROJECT_REPO && cp .env.example .env` -1. Next `yarn && yarn dev` (or `docker-compose up`, see [Docker](#docker)) -1. Now `open http://localhost:3000/admin` to access the admin panel -1. Create your first admin user using the form on the page - -That's it! Changes made in `./src` will be reflected in your app. - -### Docker - -Alternatively, you can use [Docker](https://www.docker.com) to spin up this project locally. To do so, follow these steps: - -1. Follow [steps 1 and 2 from above](#development), the docker-compose file will automatically use the `.env` file in your project root -1. Next run `docker-compose up` -1. Follow [steps 4 and 5 from above](#development) to login and create your first admin user - -That's it! The Docker instance will help you get up and running quickly while also standardizing the development environment across your teams. - -## Production - -To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps: - -1. First invoke the `payload build` script by running `yarn build` or `npm run build` in your project root. This creates a `./build` directory with a production-ready admin bundle. -1. Then run `yarn serve` or `npm run serve` to run Node in production and serve Payload from the `./build` directory. - -### Deployment - -The easiest way to deploy your project is to use [Payload Cloud](https://payloadcms.com/new/import), a one-click hosting solution to deploy production-ready instances of your Payload apps directly from your GitHub repo. You can also deploy your app manually, check out the [deployment documentation](https://payloadcms.com/docs/production/deployment) for full details. - -## Questions - -If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/payload) or start a [GitHub discussion](https://github.com/payloadcms/payload/discussions). +- **Database**: mongodb +- **Storage Adapter**: payloadCloud diff --git a/templates/with-payload-cloud/src/payload.config.ts b/templates/with-payload-cloud/src/payload.config.ts index e3d501d1f21..df630a72f7f 100644 --- a/templates/with-payload-cloud/src/payload.config.ts +++ b/templates/with-payload-cloud/src/payload.config.ts @@ -36,5 +36,7 @@ export default buildConfig({ // sharp, - plugins: [payloadCloudPlugin()], + plugins: [ + payloadCloudPlugin(), + ], }) diff --git a/templates/with-vercel-mongodb/README.md b/templates/with-vercel-mongodb/README.md index ab064ec7cc1..7fecaafcbf1 100644 --- a/templates/with-vercel-mongodb/README.md +++ b/templates/with-vercel-mongodb/README.md @@ -1,42 +1,9 @@ -# Payload Blank Template +# payload-vercel-mongodb-template +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/payloadcms/vercel-deploy-payload-postgres&project-name=payload-project&env=PAYLOAD_SECRET&build-command=pnpm%20run%20ci&stores=%5B%7B%22type%22:%22blob%22%7D%5D&integration-ids=oac_jnzmjqM10gllKmSrG0SGrHOH) -A blank template for [Payload](https://github.com/payloadcms/payload) to help you get up and running quickly. This repo may have been created by running `npx create-payload-app@latest` and selecting the "blank" template or by cloning this template on [Payload Cloud](https://payloadcms.com/new/clone/blank). +payload-vercel-mongodb-template -See the official [Examples Directory](https://github.com/payloadcms/payload/tree/main/examples) for details on how to use Payload in a variety of different ways. +## Attributes -## Development - -To spin up the project locally, follow these steps: - -1. First clone the repo -1. Then `cd YOUR_PROJECT_REPO && cp .env.example .env` -1. Next `yarn && yarn dev` (or `docker-compose up`, see [Docker](#docker)) -1. Now `open http://localhost:3000/admin` to access the admin panel -1. Create your first admin user using the form on the page - -That's it! Changes made in `./src` will be reflected in your app. - -### Docker - -Alternatively, you can use [Docker](https://www.docker.com) to spin up this project locally. To do so, follow these steps: - -1. Follow [steps 1 and 2 from above](#development), the docker-compose file will automatically use the `.env` file in your project root -1. Next run `docker-compose up` -1. Follow [steps 4 and 5 from above](#development) to login and create your first admin user - -That's it! The Docker instance will help you get up and running quickly while also standardizing the development environment across your teams. - -## Production - -To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps: - -1. First invoke the `payload build` script by running `yarn build` or `npm run build` in your project root. This creates a `./build` directory with a production-ready admin bundle. -1. Then run `yarn serve` or `npm run serve` to run Node in production and serve Payload from the `./build` directory. - -### Deployment - -The easiest way to deploy your project is to use [Payload Cloud](https://payloadcms.com/new/import), a one-click hosting solution to deploy production-ready instances of your Payload apps directly from your GitHub repo. You can also deploy your app manually, check out the [deployment documentation](https://payloadcms.com/docs/production/deployment) for full details. - -## Questions - -If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/payload) or start a [GitHub discussion](https://github.com/payloadcms/payload/discussions). +- **Database**: mongodb +- **Storage Adapter**: vercelBlobStorage diff --git a/templates/with-vercel-mongodb/src/payload.config.ts b/templates/with-vercel-mongodb/src/payload.config.ts index 36e5f5d82a5..69354afd0dd 100644 --- a/templates/with-vercel-mongodb/src/payload.config.ts +++ b/templates/with-vercel-mongodb/src/payload.config.ts @@ -18,13 +18,13 @@ export default buildConfig({ user: Users.slug, }, collections: [Users, Media], - editor: lexicalEditor({}), + editor: lexicalEditor(), secret: process.env.PAYLOAD_SECRET || '', typescript: { outputFile: path.resolve(dirname, 'payload-types.ts'), }, db: mongooseAdapter({ - url: process.env.DATABASE_URI || '', + url: process.env.MONGODB_URI || '', }), // Sharp is now an optional dependency - diff --git a/templates/with-vercel-postgres/README.md b/templates/with-vercel-postgres/README.md index ab064ec7cc1..330d654a8eb 100644 --- a/templates/with-vercel-postgres/README.md +++ b/templates/with-vercel-postgres/README.md @@ -1,42 +1,9 @@ -# Payload Blank Template +# payload-vercel-postgres-template +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/payloadcms/vercel-deploy-payload-postgres&project-name=payload-project&env=PAYLOAD_SECRET&build-command=pnpm%20run%20ci&stores=%5B%7B%22type%22:%22postgres%22%7D,%7B%22type%22:%22blob%22%7D%5D) -A blank template for [Payload](https://github.com/payloadcms/payload) to help you get up and running quickly. This repo may have been created by running `npx create-payload-app@latest` and selecting the "blank" template or by cloning this template on [Payload Cloud](https://payloadcms.com/new/clone/blank). +payload-vercel-postgres-template -See the official [Examples Directory](https://github.com/payloadcms/payload/tree/main/examples) for details on how to use Payload in a variety of different ways. +## Attributes -## Development - -To spin up the project locally, follow these steps: - -1. First clone the repo -1. Then `cd YOUR_PROJECT_REPO && cp .env.example .env` -1. Next `yarn && yarn dev` (or `docker-compose up`, see [Docker](#docker)) -1. Now `open http://localhost:3000/admin` to access the admin panel -1. Create your first admin user using the form on the page - -That's it! Changes made in `./src` will be reflected in your app. - -### Docker - -Alternatively, you can use [Docker](https://www.docker.com) to spin up this project locally. To do so, follow these steps: - -1. Follow [steps 1 and 2 from above](#development), the docker-compose file will automatically use the `.env` file in your project root -1. Next run `docker-compose up` -1. Follow [steps 4 and 5 from above](#development) to login and create your first admin user - -That's it! The Docker instance will help you get up and running quickly while also standardizing the development environment across your teams. - -## Production - -To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps: - -1. First invoke the `payload build` script by running `yarn build` or `npm run build` in your project root. This creates a `./build` directory with a production-ready admin bundle. -1. Then run `yarn serve` or `npm run serve` to run Node in production and serve Payload from the `./build` directory. - -### Deployment - -The easiest way to deploy your project is to use [Payload Cloud](https://payloadcms.com/new/import), a one-click hosting solution to deploy production-ready instances of your Payload apps directly from your GitHub repo. You can also deploy your app manually, check out the [deployment documentation](https://payloadcms.com/docs/production/deployment) for full details. - -## Questions - -If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/payload) or start a [GitHub discussion](https://github.com/payloadcms/payload/discussions). +- **Database**: postgres +- **Storage Adapter**: vercelBlobStorage diff --git a/templates/with-vercel-postgres/src/migrations/initial.json b/templates/with-vercel-postgres/src/migrations/initial.json new file mode 100644 index 00000000000..c9d7eee22aa --- /dev/null +++ b/templates/with-vercel-postgres/src/migrations/initial.json @@ -0,0 +1,375 @@ +{ + "id": "8146d795-d1a9-49be-857d-4320898b38fb", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "5", + "dialect": "pg", + "tables": { + "users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "email": { + "name": "email", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "reset_password_token": { + "name": "reset_password_token", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "reset_password_expiration": { + "name": "reset_password_expiration", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "salt": { + "name": "salt", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "hash": { + "name": "hash", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "login_attempts": { + "name": "login_attempts", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "lock_until": { + "name": "lock_until", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "users_created_at_idx": { + "name": "users_created_at_idx", + "columns": ["created_at"], + "isUnique": false + }, + "users_email_idx": { + "name": "users_email_idx", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "media": { + "name": "media", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "alt": { + "name": "alt", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "thumbnail_u_r_l": { + "name": "thumbnail_u_r_l", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "mime_type": { + "name": "mime_type", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "filesize": { + "name": "filesize", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "focal_x": { + "name": "focal_x", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "focal_y": { + "name": "focal_y", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "media_created_at_idx": { + "name": "media_created_at_idx", + "columns": ["created_at"], + "isUnique": false + }, + "media_filename_idx": { + "name": "media_filename_idx", + "columns": ["filename"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "payload_preferences": { + "name": "payload_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "payload_preferences_key_idx": { + "name": "payload_preferences_key_idx", + "columns": ["key"], + "isUnique": false + }, + "payload_preferences_created_at_idx": { + "name": "payload_preferences_created_at_idx", + "columns": ["created_at"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "payload_preferences_rels": { + "name": "payload_preferences_rels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "users_id": { + "name": "users_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "payload_preferences_rels_order_idx": { + "name": "payload_preferences_rels_order_idx", + "columns": ["order"], + "isUnique": false + }, + "payload_preferences_rels_parent_idx": { + "name": "payload_preferences_rels_parent_idx", + "columns": ["parent_id"], + "isUnique": false + }, + "payload_preferences_rels_path_idx": { + "name": "payload_preferences_rels_path_idx", + "columns": ["path"], + "isUnique": false + } + }, + "foreignKeys": { + "payload_preferences_rels_parent_fk": { + "name": "payload_preferences_rels_parent_fk", + "tableFrom": "payload_preferences_rels", + "tableTo": "payload_preferences", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_preferences_rels_users_fk": { + "name": "payload_preferences_rels_users_fk", + "tableFrom": "payload_preferences_rels", + "tableTo": "users", + "columnsFrom": ["users_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "payload_migrations": { + "name": "payload_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "batch": { + "name": "batch", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "payload_migrations_created_at_idx": { + "name": "payload_migrations_created_at_idx", + "columns": ["created_at"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} diff --git a/templates/with-vercel-postgres/src/migrations/initial.ts b/templates/with-vercel-postgres/src/migrations/initial.ts new file mode 100644 index 00000000000..a256f4abb7a --- /dev/null +++ b/templates/with-vercel-postgres/src/migrations/initial.ts @@ -0,0 +1,93 @@ +import type { MigrateDownArgs, MigrateUpArgs} from '@payloadcms/db-postgres'; + +import { sql } from '@payloadcms/db-postgres' + +export async function up({ payload }: MigrateUpArgs): Promise { + await payload.db.drizzle.execute(sql` + +CREATE TABLE IF NOT EXISTS "users" ( + "id" serial PRIMARY KEY NOT NULL, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "email" varchar NOT NULL, + "reset_password_token" varchar, + "reset_password_expiration" timestamp(3) with time zone, + "salt" varchar, + "hash" varchar, + "login_attempts" numeric, + "lock_until" timestamp(3) with time zone +); + +CREATE TABLE IF NOT EXISTS "media" ( + "id" serial PRIMARY KEY NOT NULL, + "alt" varchar NOT NULL, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "url" varchar, + "thumbnail_u_r_l" varchar, + "filename" varchar, + "mime_type" varchar, + "filesize" numeric, + "width" numeric, + "height" numeric, + "focal_x" numeric, + "focal_y" numeric +); + +CREATE TABLE IF NOT EXISTS "payload_preferences" ( + "id" serial PRIMARY KEY NOT NULL, + "key" varchar, + "value" jsonb, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL +); + +CREATE TABLE IF NOT EXISTS "payload_preferences_rels" ( + "id" serial PRIMARY KEY NOT NULL, + "order" integer, + "parent_id" integer NOT NULL, + "path" varchar NOT NULL, + "users_id" integer +); + +CREATE TABLE IF NOT EXISTS "payload_migrations" ( + "id" serial PRIMARY KEY NOT NULL, + "name" varchar, + "batch" numeric, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL +); + +CREATE INDEX IF NOT EXISTS "users_created_at_idx" ON "users" ("created_at"); +CREATE UNIQUE INDEX IF NOT EXISTS "users_email_idx" ON "users" ("email"); +CREATE INDEX IF NOT EXISTS "media_created_at_idx" ON "media" ("created_at"); +CREATE UNIQUE INDEX IF NOT EXISTS "media_filename_idx" ON "media" ("filename"); +CREATE INDEX IF NOT EXISTS "payload_preferences_key_idx" ON "payload_preferences" ("key"); +CREATE INDEX IF NOT EXISTS "payload_preferences_created_at_idx" ON "payload_preferences" ("created_at"); +CREATE INDEX IF NOT EXISTS "payload_preferences_rels_order_idx" ON "payload_preferences_rels" ("order"); +CREATE INDEX IF NOT EXISTS "payload_preferences_rels_parent_idx" ON "payload_preferences_rels" ("parent_id"); +CREATE INDEX IF NOT EXISTS "payload_preferences_rels_path_idx" ON "payload_preferences_rels" ("path"); +CREATE INDEX IF NOT EXISTS "payload_migrations_created_at_idx" ON "payload_migrations" ("created_at"); +DO $$ BEGIN + ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "payload_preferences"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +DO $$ BEGIN + ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +`) +} + +export async function down({ payload }: MigrateDownArgs): Promise { + await payload.db.drizzle.execute(sql` + +DROP TABLE "users"; +DROP TABLE "media"; +DROP TABLE "payload_preferences"; +DROP TABLE "payload_preferences_rels"; +DROP TABLE "payload_migrations";`) +} diff --git a/templates/with-vercel-postgres/src/payload.config.ts b/templates/with-vercel-postgres/src/payload.config.ts index ed7ad8eb874..1cf376b3055 100644 --- a/templates/with-vercel-postgres/src/payload.config.ts +++ b/templates/with-vercel-postgres/src/payload.config.ts @@ -18,14 +18,14 @@ export default buildConfig({ user: Users.slug, }, collections: [Users, Media], - editor: lexicalEditor({}), + editor: lexicalEditor(), secret: process.env.PAYLOAD_SECRET || '', typescript: { outputFile: path.resolve(dirname, 'payload-types.ts'), }, db: postgresAdapter({ pool: { - connectionString: process.env.DATABASE_URI || '', + connectionString: process.env.POSTGRES_URL || '', }, }),