├── .dockerignore ├── .env.example ├── .gitignore ├── .npmrc ├── .vscode └── settings.json ├── Dockerfile.migrator ├── README.md ├── apps ├── cms │ ├── .dockerignore │ ├── .env.example │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .prettierrc.json │ ├── Dockerfile │ ├── README.md │ ├── next.config.mjs │ ├── package.json │ ├── src │ │ ├── app │ │ │ └── (payload) │ │ │ │ ├── admin │ │ │ │ └── [[...segments]] │ │ │ │ │ ├── not-found.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── api │ │ │ │ ├── [...slug] │ │ │ │ │ └── route.ts │ │ │ │ ├── graphql-playground │ │ │ │ │ └── route.ts │ │ │ │ └── graphql │ │ │ │ │ └── route.ts │ │ │ │ ├── custom.scss │ │ │ │ └── layout.tsx │ │ ├── config │ │ │ ├── collections │ │ │ │ ├── Media.ts │ │ │ │ └── Users.ts │ │ │ ├── payload-types.ts │ │ │ └── payload.config.ts │ │ └── migrations │ │ │ ├── 20240705_163535_init.json │ │ │ ├── 20240705_163535_init.ts │ │ │ ├── 20240706_165421.json │ │ │ ├── 20240706_165421.ts │ │ │ ├── 20240706_173841.json │ │ │ └── 20240706_173841.ts │ └── tsconfig.json ├── migrator │ ├── package.json │ └── tsconfig.json └── web │ ├── .dockerignore │ ├── .env.example │ ├── .gitignore │ ├── .npmrc │ ├── .prettierignore │ ├── .prettierrc │ ├── Dockerfile │ ├── README.md │ ├── components.json │ ├── eslint.config.js │ ├── package.json │ ├── payload │ ├── collections │ │ ├── Media.ts │ │ └── Users.ts │ ├── payload-types.ts │ └── payload.config.ts │ ├── playwright.config.ts │ ├── postcss.config.js │ ├── src │ ├── app.d.ts │ ├── app.html │ ├── app.pcss │ ├── lib │ │ ├── components │ │ │ ├── ui │ │ │ │ ├── alert-dialog │ │ │ │ │ ├── alert-dialog-action.svelte │ │ │ │ │ ├── alert-dialog-cancel.svelte │ │ │ │ │ ├── alert-dialog-content.svelte │ │ │ │ │ ├── alert-dialog-description.svelte │ │ │ │ │ ├── alert-dialog-footer.svelte │ │ │ │ │ ├── alert-dialog-header.svelte │ │ │ │ │ ├── alert-dialog-overlay.svelte │ │ │ │ │ ├── alert-dialog-portal.svelte │ │ │ │ │ ├── alert-dialog-title.svelte │ │ │ │ │ └── index.ts │ │ │ │ ├── button │ │ │ │ │ ├── button.svelte │ │ │ │ │ └── index.ts │ │ │ │ ├── card │ │ │ │ │ ├── card-content.svelte │ │ │ │ │ ├── card-description.svelte │ │ │ │ │ ├── card-footer.svelte │ │ │ │ │ ├── card-header.svelte │ │ │ │ │ ├── card-title.svelte │ │ │ │ │ ├── card.svelte │ │ │ │ │ └── index.ts │ │ │ │ ├── input │ │ │ │ │ ├── index.ts │ │ │ │ │ └── input.svelte │ │ │ │ └── sonner │ │ │ │ │ ├── index.ts │ │ │ │ │ └── sonner.svelte │ │ │ └── utils.ts │ │ └── server │ │ │ └── payload.ts │ └── routes │ │ ├── +layout.svelte │ │ ├── +layout.ts │ │ ├── +page.server.ts │ │ └── +page.svelte │ ├── static │ └── favicon.png │ ├── svelte.config.js │ ├── tailwind.config.ts │ ├── tests │ └── test.ts │ ├── tsconfig.json │ └── vite.config.ts ├── docker-compose.db.yml ├── docker-compose.prod.yml ├── docker-compose.yml ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts └── watcher.js └── turbo.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | db_data 3 | scripts 4 | 5 | Dockerfile 6 | Dockerfile.* 7 | docker-compose.yml 8 | docker-compose.*.yml 9 | 10 | .vscode 11 | 12 | # Output 13 | out 14 | dist 15 | .next 16 | .turbo 17 | .output 18 | .vercel 19 | .svelte-kit 20 | build 21 | 22 | # OS 23 | .DS_Store 24 | Thumbs.db 25 | 26 | # Env 27 | .env 28 | .env.* 29 | 30 | # Vite 31 | vite.config.js.timestamp-* 32 | vite.config.ts.timestamp-* 33 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # PayloadCMS 2 | DATABASE_URI=postgressql://postgres_user:postgres_S3cret@127.0.0.1:5432/backend 3 | PAYLOAD_SECRET=YOUR_SECRET_HERE 4 | 5 | # Postgres 6 | POSTGRES_USER=postgres_user 7 | POSTGRES_PASSWORD=postgres_S3cret 8 | POSTGRES_DB=backend -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Turbo 19 | .turbo 20 | 21 | # Vercel 22 | .vercel 23 | 24 | # Build Outputs 25 | .next/ 26 | out/ 27 | build 28 | dist 29 | 30 | # Database 31 | db_data 32 | 33 | # Debug 34 | npm-debug.log* 35 | yarn-debug.log* 36 | yarn-error.log* 37 | 38 | # Misc 39 | .DS_Store 40 | *.pem 41 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcastrovilli/sveltekit-payload-3-starterkit/f003749ade92db1971ea19ac06f3ca10ba22a835/.npmrc -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [ 3 | { 4 | "mode": "auto" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /Dockerfile.migrator: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine3.19 2 | 3 | WORKDIR /app 4 | 5 | ENV PNPM_HOME="/pnpm" 6 | ENV PATH="$PNPM_HOME:$PATH" 7 | RUN corepack enable 8 | 9 | COPY ./apps/migrator/package.json ./package.json 10 | COPY ./apps/migrator/tsconfig.json ./tsconfig.json 11 | 12 | RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod 13 | 14 | COPY ./apps/cms/src/config ./src/config 15 | COPY ./apps/cms/src/migrations ./src/migrations -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sveltekit + Payload CMS Starter Kit 2 | 3 | This is a simple boilerplate to get started with Sveltekit & Payload CMS 3.0. 4 | 5 | ## Features 6 | 7 | - [Turborepo](https://turbo.build/repo) for local development 8 | - [Sveltekit](https://kit.svelte.dev/) for frontend (`apps/web`) 9 | - TypeScript 10 | - Svelte 5 11 | - Tailwind CSS & [shadcn-svelte](https://shadcn-svelte.com/) 12 | - [Payload CMS](https://payloadcms.com/) for backend (`apps/cms`) 13 | - TypeScript 14 | - Next.js 15 | - Payload CMS 3.0 16 | - Postgres DB for local development 17 | - Docker Compose for production 18 | 19 | ## Getting Started 20 | 21 | ### Prerequisites 22 | 23 | - [Docker](https://docs.docker.com/get-docker/) 24 | - [Docker Compose](https://docs.docker.com/compose/install/) 25 | - [pnpm](https://pnpm.io/installation) 26 | - [Node.js](https://nodejs.org/en/download/) (>= 20) 27 | 28 | ### Installation 29 | 30 | Clone or fork this repository to your local machine. 31 | Then, run the following commands to install dependencies and start the local development server. 32 | 33 | ```bash 34 | pnpm install 35 | ``` 36 | 37 | ### Development 38 | 39 | ```bash 40 | pnpm db && pnpm dev 41 | ``` 42 | 43 | ### Production Testing 44 | 45 | ```bash 46 | pnpm prod 47 | ``` 48 | -------------------------------------------------------------------------------- /apps/cms/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | Dockerfile 4 | Dockerfile.* 5 | 6 | # dependencies 7 | /node_modules 8 | /.pnp 9 | .pnp.js 10 | .yarn/install-state.gz 11 | 12 | /.idea/* 13 | !/.idea/runConfigurations 14 | 15 | # testing 16 | /coverage 17 | 18 | # next.js 19 | /.next/ 20 | /out/ 21 | 22 | # production 23 | /build 24 | 25 | # misc 26 | .DS_Store 27 | *.pem 28 | 29 | # debug 30 | npm-debug.log* 31 | yarn-debug.log* 32 | yarn-error.log* 33 | 34 | # local env files 35 | .env*.local 36 | 37 | # vercel 38 | .vercel 39 | 40 | # typescript 41 | *.tsbuildinfo 42 | next-env.d.ts 43 | 44 | .env 45 | 46 | /media 47 | 48 | db_data 49 | 50 | web -------------------------------------------------------------------------------- /apps/cms/.env.example: -------------------------------------------------------------------------------- 1 | # PayloadCMS 2 | DATABASE_URI=postgressql://postgres_user:postgres_S3cret@127.0.0.1:5432/backend 3 | PAYLOAD_SECRET=YOUR_SECRET_HERE -------------------------------------------------------------------------------- /apps/cms/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('eslint').Linter.Config} */ 2 | module.exports = { 3 | extends: ['next/core-web-vitals'], 4 | parserOptions: { 5 | project: ['./tsconfig.json'], 6 | tsconfigRootDir: __dirname, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /apps/cms/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | /.idea/* 10 | !/.idea/runConfigurations 11 | 12 | # testing 13 | /coverage 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | 19 | # production 20 | /build 21 | 22 | # misc 23 | .DS_Store 24 | *.pem 25 | 26 | # debug 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | next-env.d.ts 40 | 41 | .env 42 | 43 | /media 44 | -------------------------------------------------------------------------------- /apps/cms/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 100, 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /apps/cms/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine3.19 AS image 2 | 3 | FROM image AS preparer 4 | WORKDIR /app 5 | 6 | ENV PNPM_HOME="/pnpm" 7 | ENV PATH="$PNPM_HOME:$PATH" 8 | RUN corepack enable 9 | # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. 10 | RUN apk update 11 | RUN apk add --no-cache libc6-compat 12 | 13 | COPY . . 14 | 15 | FROM preparer AS build 16 | RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install 17 | RUN pnpm run build 18 | 19 | FROM image AS prod 20 | WORKDIR /app 21 | 22 | ENV NODE_ENV=production 23 | ENV PAYLOAD_CONFIG_PATH=dist/payload.config.js 24 | 25 | # Don't run production as root 26 | RUN addgroup --system --gid 1001 nodejs 27 | RUN adduser --system --uid 1001 nextjs 28 | USER nextjs 29 | 30 | COPY --from=build --chown=nextjs:nodejs /app/.next/standalone /app 31 | COPY --from=build --chown=nextjs:nodejs /app/.next/static /app/.next/static 32 | 33 | EXPOSE 3000 34 | 35 | CMD node server.js -------------------------------------------------------------------------------- /apps/cms/README.md: -------------------------------------------------------------------------------- 1 | # Payload Blank Template 2 | 3 | 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). 4 | 5 | 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. 6 | 7 | ## Development 8 | 9 | To spin up the project locally, follow these steps: 10 | 11 | 1. First clone the repo 12 | 1. Then `cd YOUR_PROJECT_REPO && cp .env.example .env` 13 | 1. Next `yarn && yarn dev` (or `docker-compose up`, see [Docker](#docker)) 14 | 1. Now `open http://localhost:3000/admin` to access the admin panel 15 | 1. Create your first admin user using the form on the page 16 | 17 | That's it! Changes made in `./src` will be reflected in your app. 18 | 19 | ### Docker 20 | 21 | Alternatively, you can use [Docker](https://www.docker.com) to spin up this project locally. To do so, follow these steps: 22 | 23 | 1. Follow [steps 1 and 2 from above](#development), the docker-compose file will automatically use the `.env` file in your project root 24 | 1. Next run `docker-compose up` 25 | 1. Follow [steps 4 and 5 from above](#development) to login and create your first admin user 26 | 27 | That's it! The Docker instance will help you get up and running quickly while also standardizing the development environment across your teams. 28 | 29 | ## Production 30 | 31 | To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps: 32 | 33 | 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. 34 | 1. Then run `yarn serve` or `npm run serve` to run Node in production and serve Payload from the `./build` directory. 35 | 36 | ### Deployment 37 | 38 | 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. 39 | 40 | ## Questions 41 | 42 | 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). 43 | -------------------------------------------------------------------------------- /apps/cms/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { withPayload } from '@payloadcms/next/withPayload' 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | output: 'standalone', 6 | reactStrictMode: true, 7 | redirects: async () => { 8 | return [ 9 | { 10 | source: '/', 11 | destination: '/admin', 12 | permanent: false, 13 | }, 14 | ] 15 | }, 16 | } 17 | 18 | export default withPayload(nextConfig) 19 | -------------------------------------------------------------------------------- /apps/cms/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cms", 3 | "version": "1.0.0", 4 | "description": "A blank template to get started with Payload 3.0", 5 | "license": "MIT", 6 | "type": "module", 7 | "scripts": { 8 | "build": "cross-env NODE_OPTIONS=--no-deprecation next build", 9 | "dev": "cross-env NODE_OPTIONS=--no-deprecation next dev", 10 | "devsafe": "rm -rf .next && cross-env NODE_OPTIONS=--no-deprecation next dev", 11 | "generate:types": "payload generate:types", 12 | "lint": "cross-env NODE_OPTIONS=--no-deprecation next lint", 13 | "payload": "cross-env NODE_OPTIONS=--no-deprecation payload", 14 | "start": "cross-env NODE_OPTIONS=--no-deprecation next start" 15 | }, 16 | "dependencies": { 17 | "payload": "beta", 18 | "@payloadcms/next": "beta", 19 | "@payloadcms/plugin-cloud": "beta", 20 | "@payloadcms/richtext-lexical": "beta", 21 | "@payloadcms/db-postgres": "beta", 22 | "cross-env": "^7.0.3", 23 | "graphql": "^16.9.0", 24 | "next": "15.0.0-canary.53", 25 | "react": "rc", 26 | "react-dom": "rc", 27 | "sharp": "0.33.4" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "^20.14.9", 31 | "@types/react": "npm:types-react@rc", 32 | "@types/react-dom": "npm:types-react-dom@rc", 33 | "dotenv": "^16.4.5", 34 | "eslint": "^9", 35 | "eslint-config-next": "15.0.0-canary.53", 36 | "typescript": "5.5.3" 37 | }, 38 | "engines": { 39 | "node": "^18.20.2 || >=20.9.0" 40 | }, 41 | "overrides": { 42 | "@types/react": "npm:types-react@rc", 43 | "@types/react-dom": "npm:types-react-dom@rc" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/cms/src/app/(payload)/admin/[[...segments]]/not-found.tsx: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | import type { Metadata } from 'next' 3 | 4 | import config from '@/config/payload.config' 5 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ 6 | import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views' 7 | 8 | type Args = { 9 | params: { 10 | segments: string[] 11 | } 12 | searchParams: { 13 | [key: string]: string | string[] 14 | } 15 | } 16 | 17 | export const generateMetadata = ({ params, searchParams }: Args): Promise => 18 | generatePageMetadata({ config, params, searchParams }) 19 | 20 | const NotFound = ({ params, searchParams }: Args) => NotFoundPage({ config, params, searchParams }) 21 | 22 | export default NotFound 23 | -------------------------------------------------------------------------------- /apps/cms/src/app/(payload)/admin/[[...segments]]/page.tsx: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | import type { Metadata } from 'next' 3 | 4 | import config from '@/config/payload.config' 5 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ 6 | import { RootPage, generatePageMetadata } from '@payloadcms/next/views' 7 | 8 | type Args = { 9 | params: { 10 | segments: string[] 11 | } 12 | searchParams: { 13 | [key: string]: string | string[] 14 | } 15 | } 16 | 17 | export const generateMetadata = ({ params, searchParams }: Args): Promise => 18 | generatePageMetadata({ config, params, searchParams }) 19 | 20 | const Page = ({ params, searchParams }: Args) => RootPage({ config, params, searchParams }) 21 | 22 | export default Page 23 | -------------------------------------------------------------------------------- /apps/cms/src/app/(payload)/api/[...slug]/route.ts: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | /* DO NOT MODIFY it because it could be re-written at any time. */ 3 | import config from '@/config/payload.config' 4 | import { REST_DELETE, REST_GET, REST_OPTIONS, REST_PATCH, REST_POST } from '@payloadcms/next/routes' 5 | 6 | export const GET = REST_GET(config) 7 | export const POST = REST_POST(config) 8 | export const DELETE = REST_DELETE(config) 9 | export const PATCH = REST_PATCH(config) 10 | export const OPTIONS = REST_OPTIONS(config) 11 | -------------------------------------------------------------------------------- /apps/cms/src/app/(payload)/api/graphql-playground/route.ts: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | /* DO NOT MODIFY it because it could be re-written at any time. */ 3 | import config from '@/config/payload.config' 4 | import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes' 5 | 6 | export const GET = GRAPHQL_PLAYGROUND_GET(config) 7 | -------------------------------------------------------------------------------- /apps/cms/src/app/(payload)/api/graphql/route.ts: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | /* DO NOT MODIFY it because it could be re-written at any time. */ 3 | import config from '@/config/payload.config' 4 | import { GRAPHQL_POST } from '@payloadcms/next/routes' 5 | 6 | export const POST = GRAPHQL_POST(config) 7 | -------------------------------------------------------------------------------- /apps/cms/src/app/(payload)/custom.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcastrovilli/sveltekit-payload-3-starterkit/f003749ade92db1971ea19ac06f3ca10ba22a835/apps/cms/src/app/(payload)/custom.scss -------------------------------------------------------------------------------- /apps/cms/src/app/(payload)/layout.tsx: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | import configPromise from '@/config/payload.config' 3 | import '@payloadcms/next/css' 4 | import { RootLayout } from '@payloadcms/next/layouts' 5 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ 6 | import React from 'react' 7 | 8 | import './custom.scss' 9 | 10 | type Args = { 11 | children: React.ReactNode 12 | } 13 | 14 | const Layout = ({ children }: Args) => {children} 15 | 16 | export default Layout 17 | -------------------------------------------------------------------------------- /apps/cms/src/config/collections/Media.ts: -------------------------------------------------------------------------------- 1 | import type { CollectionConfig } from 'payload' 2 | 3 | export const Media: CollectionConfig = { 4 | slug: 'media', 5 | access: { 6 | read: () => true, 7 | }, 8 | fields: [ 9 | { 10 | name: 'alt', 11 | type: 'text', 12 | required: true, 13 | }, 14 | ], 15 | upload: true, 16 | } 17 | -------------------------------------------------------------------------------- /apps/cms/src/config/collections/Users.ts: -------------------------------------------------------------------------------- 1 | import type { CollectionConfig } from 'payload' 2 | 3 | export const Users: CollectionConfig = { 4 | slug: 'users', 5 | admin: { 6 | useAsTitle: 'username', 7 | }, 8 | auth: true, 9 | fields: [ 10 | { 11 | name: 'username', 12 | type: 'text', 13 | label: 'Username', 14 | required: true, 15 | unique: true, 16 | }, 17 | // Email added by default 18 | // Add more fields as needed 19 | ], 20 | } 21 | -------------------------------------------------------------------------------- /apps/cms/src/config/payload-types.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | * This file was automatically generated by Payload. 5 | * DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config, 6 | * and re-run `payload generate:types` to regenerate this file. 7 | */ 8 | 9 | export interface Config { 10 | auth: { 11 | users: UserAuthOperations; 12 | }; 13 | collections: { 14 | users: User; 15 | media: Media; 16 | 'payload-preferences': PayloadPreference; 17 | 'payload-migrations': PayloadMigration; 18 | }; 19 | globals: {}; 20 | locale: null; 21 | user: User & { 22 | collection: 'users'; 23 | }; 24 | } 25 | export interface UserAuthOperations { 26 | forgotPassword: { 27 | email: string; 28 | }; 29 | login: { 30 | password: string; 31 | email: string; 32 | }; 33 | registerFirstUser: { 34 | email: string; 35 | password: string; 36 | }; 37 | } 38 | /** 39 | * This interface was referenced by `Config`'s JSON-Schema 40 | * via the `definition` "users". 41 | */ 42 | export interface User { 43 | id: number; 44 | username: string; 45 | updatedAt: string; 46 | createdAt: string; 47 | email: string; 48 | resetPasswordToken?: string | null; 49 | resetPasswordExpiration?: string | null; 50 | salt?: string | null; 51 | hash?: string | null; 52 | loginAttempts?: number | null; 53 | lockUntil?: string | null; 54 | password?: string | null; 55 | } 56 | /** 57 | * This interface was referenced by `Config`'s JSON-Schema 58 | * via the `definition` "media". 59 | */ 60 | export interface Media { 61 | id: number; 62 | alt: string; 63 | updatedAt: string; 64 | createdAt: string; 65 | url?: string | null; 66 | thumbnailURL?: string | null; 67 | filename?: string | null; 68 | mimeType?: string | null; 69 | filesize?: number | null; 70 | width?: number | null; 71 | height?: number | null; 72 | focalX?: number | null; 73 | focalY?: number | null; 74 | } 75 | /** 76 | * This interface was referenced by `Config`'s JSON-Schema 77 | * via the `definition` "payload-preferences". 78 | */ 79 | export interface PayloadPreference { 80 | id: number; 81 | user: { 82 | relationTo: 'users'; 83 | value: number | User; 84 | }; 85 | key?: string | null; 86 | value?: 87 | | { 88 | [k: string]: unknown; 89 | } 90 | | unknown[] 91 | | string 92 | | number 93 | | boolean 94 | | null; 95 | updatedAt: string; 96 | createdAt: string; 97 | } 98 | /** 99 | * This interface was referenced by `Config`'s JSON-Schema 100 | * via the `definition` "payload-migrations". 101 | */ 102 | export interface PayloadMigration { 103 | id: number; 104 | name?: string | null; 105 | batch?: number | null; 106 | updatedAt: string; 107 | createdAt: string; 108 | } 109 | /** 110 | * This interface was referenced by `Config`'s JSON-Schema 111 | * via the `definition` "auth". 112 | */ 113 | export interface Auth { 114 | [k: string]: unknown; 115 | } 116 | 117 | 118 | declare module 'payload' { 119 | export interface GeneratedTypes extends Config {} 120 | } -------------------------------------------------------------------------------- /apps/cms/src/config/payload.config.ts: -------------------------------------------------------------------------------- 1 | // storage-adapter-import-placeholder 2 | import { postgresAdapter } from '@payloadcms/db-postgres' 3 | // import { lexicalEditor } from '@payloadcms/richtext-lexical' 4 | import path from 'path' 5 | import { buildConfig } from 'payload' 6 | import { fileURLToPath } from 'url' 7 | // import sharp from 'sharp' 8 | 9 | import { Users } from './collections/Users' 10 | import { Media } from './collections/Media' 11 | 12 | const filename = fileURLToPath(import.meta.url) 13 | const dirname = path.dirname(filename) 14 | 15 | export default buildConfig({ 16 | admin: { 17 | user: Users.slug, 18 | }, 19 | collections: [Users, Media], 20 | // editor: lexicalEditor(), 21 | secret: process.env.PAYLOAD_SECRET || '', 22 | typescript: { 23 | outputFile: path.resolve(dirname, 'payload-types.ts'), 24 | }, 25 | db: postgresAdapter({ 26 | pool: { 27 | connectionString: process.env.DATABASE_URI || '', 28 | }, 29 | }), 30 | // sharp, 31 | plugins: [ 32 | // storage-adapter-placeholder 33 | ], 34 | }) 35 | -------------------------------------------------------------------------------- /apps/cms/src/migrations/20240705_163535_init.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0c223872-b2e7-44f2-888f-45268302ba4b", 3 | "prevId": "00000000-0000-0000-0000-000000000000", 4 | "version": "5", 5 | "dialect": "pg", 6 | "tables": { 7 | "users": { 8 | "name": "users", 9 | "schema": "", 10 | "columns": { 11 | "id": { 12 | "name": "id", 13 | "type": "serial", 14 | "primaryKey": true, 15 | "notNull": true 16 | }, 17 | "updated_at": { 18 | "name": "updated_at", 19 | "type": "timestamp(3) with time zone", 20 | "primaryKey": false, 21 | "notNull": true, 22 | "default": "now()" 23 | }, 24 | "created_at": { 25 | "name": "created_at", 26 | "type": "timestamp(3) with time zone", 27 | "primaryKey": false, 28 | "notNull": true, 29 | "default": "now()" 30 | }, 31 | "email": { 32 | "name": "email", 33 | "type": "varchar", 34 | "primaryKey": false, 35 | "notNull": true 36 | }, 37 | "reset_password_token": { 38 | "name": "reset_password_token", 39 | "type": "varchar", 40 | "primaryKey": false, 41 | "notNull": false 42 | }, 43 | "reset_password_expiration": { 44 | "name": "reset_password_expiration", 45 | "type": "timestamp(3) with time zone", 46 | "primaryKey": false, 47 | "notNull": false 48 | }, 49 | "salt": { 50 | "name": "salt", 51 | "type": "varchar", 52 | "primaryKey": false, 53 | "notNull": false 54 | }, 55 | "hash": { 56 | "name": "hash", 57 | "type": "varchar", 58 | "primaryKey": false, 59 | "notNull": false 60 | }, 61 | "login_attempts": { 62 | "name": "login_attempts", 63 | "type": "numeric", 64 | "primaryKey": false, 65 | "notNull": false 66 | }, 67 | "lock_until": { 68 | "name": "lock_until", 69 | "type": "timestamp(3) with time zone", 70 | "primaryKey": false, 71 | "notNull": false 72 | } 73 | }, 74 | "indexes": { 75 | "users_created_at_idx": { 76 | "name": "users_created_at_idx", 77 | "columns": [ 78 | "created_at" 79 | ], 80 | "isUnique": false 81 | }, 82 | "users_email_idx": { 83 | "name": "users_email_idx", 84 | "columns": [ 85 | "email" 86 | ], 87 | "isUnique": true 88 | } 89 | }, 90 | "foreignKeys": {}, 91 | "compositePrimaryKeys": {}, 92 | "uniqueConstraints": {} 93 | }, 94 | "media": { 95 | "name": "media", 96 | "schema": "", 97 | "columns": { 98 | "id": { 99 | "name": "id", 100 | "type": "serial", 101 | "primaryKey": true, 102 | "notNull": true 103 | }, 104 | "alt": { 105 | "name": "alt", 106 | "type": "varchar", 107 | "primaryKey": false, 108 | "notNull": true 109 | }, 110 | "updated_at": { 111 | "name": "updated_at", 112 | "type": "timestamp(3) with time zone", 113 | "primaryKey": false, 114 | "notNull": true, 115 | "default": "now()" 116 | }, 117 | "created_at": { 118 | "name": "created_at", 119 | "type": "timestamp(3) with time zone", 120 | "primaryKey": false, 121 | "notNull": true, 122 | "default": "now()" 123 | }, 124 | "url": { 125 | "name": "url", 126 | "type": "varchar", 127 | "primaryKey": false, 128 | "notNull": false 129 | }, 130 | "thumbnail_u_r_l": { 131 | "name": "thumbnail_u_r_l", 132 | "type": "varchar", 133 | "primaryKey": false, 134 | "notNull": false 135 | }, 136 | "filename": { 137 | "name": "filename", 138 | "type": "varchar", 139 | "primaryKey": false, 140 | "notNull": false 141 | }, 142 | "mime_type": { 143 | "name": "mime_type", 144 | "type": "varchar", 145 | "primaryKey": false, 146 | "notNull": false 147 | }, 148 | "filesize": { 149 | "name": "filesize", 150 | "type": "numeric", 151 | "primaryKey": false, 152 | "notNull": false 153 | }, 154 | "width": { 155 | "name": "width", 156 | "type": "numeric", 157 | "primaryKey": false, 158 | "notNull": false 159 | }, 160 | "height": { 161 | "name": "height", 162 | "type": "numeric", 163 | "primaryKey": false, 164 | "notNull": false 165 | }, 166 | "focal_x": { 167 | "name": "focal_x", 168 | "type": "numeric", 169 | "primaryKey": false, 170 | "notNull": false 171 | }, 172 | "focal_y": { 173 | "name": "focal_y", 174 | "type": "numeric", 175 | "primaryKey": false, 176 | "notNull": false 177 | } 178 | }, 179 | "indexes": { 180 | "media_created_at_idx": { 181 | "name": "media_created_at_idx", 182 | "columns": [ 183 | "created_at" 184 | ], 185 | "isUnique": false 186 | }, 187 | "media_filename_idx": { 188 | "name": "media_filename_idx", 189 | "columns": [ 190 | "filename" 191 | ], 192 | "isUnique": true 193 | } 194 | }, 195 | "foreignKeys": {}, 196 | "compositePrimaryKeys": {}, 197 | "uniqueConstraints": {} 198 | }, 199 | "payload_preferences": { 200 | "name": "payload_preferences", 201 | "schema": "", 202 | "columns": { 203 | "id": { 204 | "name": "id", 205 | "type": "serial", 206 | "primaryKey": true, 207 | "notNull": true 208 | }, 209 | "key": { 210 | "name": "key", 211 | "type": "varchar", 212 | "primaryKey": false, 213 | "notNull": false 214 | }, 215 | "value": { 216 | "name": "value", 217 | "type": "jsonb", 218 | "primaryKey": false, 219 | "notNull": false 220 | }, 221 | "updated_at": { 222 | "name": "updated_at", 223 | "type": "timestamp(3) with time zone", 224 | "primaryKey": false, 225 | "notNull": true, 226 | "default": "now()" 227 | }, 228 | "created_at": { 229 | "name": "created_at", 230 | "type": "timestamp(3) with time zone", 231 | "primaryKey": false, 232 | "notNull": true, 233 | "default": "now()" 234 | } 235 | }, 236 | "indexes": { 237 | "payload_preferences_key_idx": { 238 | "name": "payload_preferences_key_idx", 239 | "columns": [ 240 | "key" 241 | ], 242 | "isUnique": false 243 | }, 244 | "payload_preferences_created_at_idx": { 245 | "name": "payload_preferences_created_at_idx", 246 | "columns": [ 247 | "created_at" 248 | ], 249 | "isUnique": false 250 | } 251 | }, 252 | "foreignKeys": {}, 253 | "compositePrimaryKeys": {}, 254 | "uniqueConstraints": {} 255 | }, 256 | "payload_preferences_rels": { 257 | "name": "payload_preferences_rels", 258 | "schema": "", 259 | "columns": { 260 | "id": { 261 | "name": "id", 262 | "type": "serial", 263 | "primaryKey": true, 264 | "notNull": true 265 | }, 266 | "order": { 267 | "name": "order", 268 | "type": "integer", 269 | "primaryKey": false, 270 | "notNull": false 271 | }, 272 | "parent_id": { 273 | "name": "parent_id", 274 | "type": "integer", 275 | "primaryKey": false, 276 | "notNull": true 277 | }, 278 | "path": { 279 | "name": "path", 280 | "type": "varchar", 281 | "primaryKey": false, 282 | "notNull": true 283 | }, 284 | "users_id": { 285 | "name": "users_id", 286 | "type": "integer", 287 | "primaryKey": false, 288 | "notNull": false 289 | } 290 | }, 291 | "indexes": { 292 | "payload_preferences_rels_order_idx": { 293 | "name": "payload_preferences_rels_order_idx", 294 | "columns": [ 295 | "order" 296 | ], 297 | "isUnique": false 298 | }, 299 | "payload_preferences_rels_parent_idx": { 300 | "name": "payload_preferences_rels_parent_idx", 301 | "columns": [ 302 | "parent_id" 303 | ], 304 | "isUnique": false 305 | }, 306 | "payload_preferences_rels_path_idx": { 307 | "name": "payload_preferences_rels_path_idx", 308 | "columns": [ 309 | "path" 310 | ], 311 | "isUnique": false 312 | } 313 | }, 314 | "foreignKeys": { 315 | "payload_preferences_rels_parent_fk": { 316 | "name": "payload_preferences_rels_parent_fk", 317 | "tableFrom": "payload_preferences_rels", 318 | "tableTo": "payload_preferences", 319 | "columnsFrom": [ 320 | "parent_id" 321 | ], 322 | "columnsTo": [ 323 | "id" 324 | ], 325 | "onDelete": "cascade", 326 | "onUpdate": "no action" 327 | }, 328 | "payload_preferences_rels_users_fk": { 329 | "name": "payload_preferences_rels_users_fk", 330 | "tableFrom": "payload_preferences_rels", 331 | "tableTo": "users", 332 | "columnsFrom": [ 333 | "users_id" 334 | ], 335 | "columnsTo": [ 336 | "id" 337 | ], 338 | "onDelete": "cascade", 339 | "onUpdate": "no action" 340 | } 341 | }, 342 | "compositePrimaryKeys": {}, 343 | "uniqueConstraints": {} 344 | }, 345 | "payload_migrations": { 346 | "name": "payload_migrations", 347 | "schema": "", 348 | "columns": { 349 | "id": { 350 | "name": "id", 351 | "type": "serial", 352 | "primaryKey": true, 353 | "notNull": true 354 | }, 355 | "name": { 356 | "name": "name", 357 | "type": "varchar", 358 | "primaryKey": false, 359 | "notNull": false 360 | }, 361 | "batch": { 362 | "name": "batch", 363 | "type": "numeric", 364 | "primaryKey": false, 365 | "notNull": false 366 | }, 367 | "updated_at": { 368 | "name": "updated_at", 369 | "type": "timestamp(3) with time zone", 370 | "primaryKey": false, 371 | "notNull": true, 372 | "default": "now()" 373 | }, 374 | "created_at": { 375 | "name": "created_at", 376 | "type": "timestamp(3) with time zone", 377 | "primaryKey": false, 378 | "notNull": true, 379 | "default": "now()" 380 | } 381 | }, 382 | "indexes": { 383 | "payload_migrations_created_at_idx": { 384 | "name": "payload_migrations_created_at_idx", 385 | "columns": [ 386 | "created_at" 387 | ], 388 | "isUnique": false 389 | } 390 | }, 391 | "foreignKeys": {}, 392 | "compositePrimaryKeys": {}, 393 | "uniqueConstraints": {} 394 | } 395 | }, 396 | "enums": {}, 397 | "schemas": {}, 398 | "_meta": { 399 | "schemas": {}, 400 | "tables": {}, 401 | "columns": {} 402 | } 403 | } -------------------------------------------------------------------------------- /apps/cms/src/migrations/20240705_163535_init.ts: -------------------------------------------------------------------------------- 1 | import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres' 2 | 3 | export async function up({ payload, req }: MigrateUpArgs): Promise { 4 | await payload.db.drizzle.execute(sql` 5 | CREATE TABLE IF NOT EXISTS "users" ( 6 | "id" serial PRIMARY KEY NOT NULL, 7 | "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, 8 | "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL, 9 | "email" varchar NOT NULL, 10 | "reset_password_token" varchar, 11 | "reset_password_expiration" timestamp(3) with time zone, 12 | "salt" varchar, 13 | "hash" varchar, 14 | "login_attempts" numeric, 15 | "lock_until" timestamp(3) with time zone 16 | ); 17 | 18 | CREATE TABLE IF NOT EXISTS "media" ( 19 | "id" serial PRIMARY KEY NOT NULL, 20 | "alt" varchar NOT NULL, 21 | "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, 22 | "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL, 23 | "url" varchar, 24 | "thumbnail_u_r_l" varchar, 25 | "filename" varchar, 26 | "mime_type" varchar, 27 | "filesize" numeric, 28 | "width" numeric, 29 | "height" numeric, 30 | "focal_x" numeric, 31 | "focal_y" numeric 32 | ); 33 | 34 | CREATE TABLE IF NOT EXISTS "payload_preferences" ( 35 | "id" serial PRIMARY KEY NOT NULL, 36 | "key" varchar, 37 | "value" jsonb, 38 | "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, 39 | "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL 40 | ); 41 | 42 | CREATE TABLE IF NOT EXISTS "payload_preferences_rels" ( 43 | "id" serial PRIMARY KEY NOT NULL, 44 | "order" integer, 45 | "parent_id" integer NOT NULL, 46 | "path" varchar NOT NULL, 47 | "users_id" integer 48 | ); 49 | 50 | CREATE TABLE IF NOT EXISTS "payload_migrations" ( 51 | "id" serial PRIMARY KEY NOT NULL, 52 | "name" varchar, 53 | "batch" numeric, 54 | "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, 55 | "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL 56 | ); 57 | 58 | CREATE INDEX IF NOT EXISTS "users_created_at_idx" ON "users" ("created_at"); 59 | CREATE UNIQUE INDEX IF NOT EXISTS "users_email_idx" ON "users" ("email"); 60 | CREATE INDEX IF NOT EXISTS "media_created_at_idx" ON "media" ("created_at"); 61 | CREATE UNIQUE INDEX IF NOT EXISTS "media_filename_idx" ON "media" ("filename"); 62 | CREATE INDEX IF NOT EXISTS "payload_preferences_key_idx" ON "payload_preferences" ("key"); 63 | CREATE INDEX IF NOT EXISTS "payload_preferences_created_at_idx" ON "payload_preferences" ("created_at"); 64 | CREATE INDEX IF NOT EXISTS "payload_preferences_rels_order_idx" ON "payload_preferences_rels" ("order"); 65 | CREATE INDEX IF NOT EXISTS "payload_preferences_rels_parent_idx" ON "payload_preferences_rels" ("parent_id"); 66 | CREATE INDEX IF NOT EXISTS "payload_preferences_rels_path_idx" ON "payload_preferences_rels" ("path"); 67 | CREATE INDEX IF NOT EXISTS "payload_migrations_created_at_idx" ON "payload_migrations" ("created_at"); 68 | DO $$ BEGIN 69 | 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; 70 | EXCEPTION 71 | WHEN duplicate_object THEN null; 72 | END $$; 73 | 74 | DO $$ BEGIN 75 | 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; 76 | EXCEPTION 77 | WHEN duplicate_object THEN null; 78 | END $$; 79 | `) 80 | }; 81 | 82 | export async function down({ payload, req }: MigrateDownArgs): Promise { 83 | await payload.db.drizzle.execute(sql` 84 | DROP TABLE "users"; 85 | DROP TABLE "media"; 86 | DROP TABLE "payload_preferences"; 87 | DROP TABLE "payload_preferences_rels"; 88 | DROP TABLE "payload_migrations";`) 89 | }; 90 | -------------------------------------------------------------------------------- /apps/cms/src/migrations/20240706_165421.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "b3bc25ac-2357-46f1-a0aa-620799492502", 3 | "prevId": "00000000-0000-0000-0000-000000000000", 4 | "version": "5", 5 | "dialect": "pg", 6 | "tables": { 7 | "users": { 8 | "name": "users", 9 | "schema": "", 10 | "columns": { 11 | "id": { 12 | "name": "id", 13 | "type": "serial", 14 | "primaryKey": true, 15 | "notNull": true 16 | }, 17 | "username": { 18 | "name": "username", 19 | "type": "varchar", 20 | "primaryKey": false, 21 | "notNull": false 22 | }, 23 | "updated_at": { 24 | "name": "updated_at", 25 | "type": "timestamp(3) with time zone", 26 | "primaryKey": false, 27 | "notNull": true, 28 | "default": "now()" 29 | }, 30 | "created_at": { 31 | "name": "created_at", 32 | "type": "timestamp(3) with time zone", 33 | "primaryKey": false, 34 | "notNull": true, 35 | "default": "now()" 36 | }, 37 | "email": { 38 | "name": "email", 39 | "type": "varchar", 40 | "primaryKey": false, 41 | "notNull": true 42 | }, 43 | "reset_password_token": { 44 | "name": "reset_password_token", 45 | "type": "varchar", 46 | "primaryKey": false, 47 | "notNull": false 48 | }, 49 | "reset_password_expiration": { 50 | "name": "reset_password_expiration", 51 | "type": "timestamp(3) with time zone", 52 | "primaryKey": false, 53 | "notNull": false 54 | }, 55 | "salt": { 56 | "name": "salt", 57 | "type": "varchar", 58 | "primaryKey": false, 59 | "notNull": false 60 | }, 61 | "hash": { 62 | "name": "hash", 63 | "type": "varchar", 64 | "primaryKey": false, 65 | "notNull": false 66 | }, 67 | "login_attempts": { 68 | "name": "login_attempts", 69 | "type": "numeric", 70 | "primaryKey": false, 71 | "notNull": false 72 | }, 73 | "lock_until": { 74 | "name": "lock_until", 75 | "type": "timestamp(3) with time zone", 76 | "primaryKey": false, 77 | "notNull": false 78 | } 79 | }, 80 | "indexes": { 81 | "users_created_at_idx": { 82 | "name": "users_created_at_idx", 83 | "columns": [ 84 | "created_at" 85 | ], 86 | "isUnique": false 87 | }, 88 | "users_email_idx": { 89 | "name": "users_email_idx", 90 | "columns": [ 91 | "email" 92 | ], 93 | "isUnique": true 94 | } 95 | }, 96 | "foreignKeys": {}, 97 | "compositePrimaryKeys": {}, 98 | "uniqueConstraints": {} 99 | }, 100 | "media": { 101 | "name": "media", 102 | "schema": "", 103 | "columns": { 104 | "id": { 105 | "name": "id", 106 | "type": "serial", 107 | "primaryKey": true, 108 | "notNull": true 109 | }, 110 | "alt": { 111 | "name": "alt", 112 | "type": "varchar", 113 | "primaryKey": false, 114 | "notNull": true 115 | }, 116 | "updated_at": { 117 | "name": "updated_at", 118 | "type": "timestamp(3) with time zone", 119 | "primaryKey": false, 120 | "notNull": true, 121 | "default": "now()" 122 | }, 123 | "created_at": { 124 | "name": "created_at", 125 | "type": "timestamp(3) with time zone", 126 | "primaryKey": false, 127 | "notNull": true, 128 | "default": "now()" 129 | }, 130 | "url": { 131 | "name": "url", 132 | "type": "varchar", 133 | "primaryKey": false, 134 | "notNull": false 135 | }, 136 | "thumbnail_u_r_l": { 137 | "name": "thumbnail_u_r_l", 138 | "type": "varchar", 139 | "primaryKey": false, 140 | "notNull": false 141 | }, 142 | "filename": { 143 | "name": "filename", 144 | "type": "varchar", 145 | "primaryKey": false, 146 | "notNull": false 147 | }, 148 | "mime_type": { 149 | "name": "mime_type", 150 | "type": "varchar", 151 | "primaryKey": false, 152 | "notNull": false 153 | }, 154 | "filesize": { 155 | "name": "filesize", 156 | "type": "numeric", 157 | "primaryKey": false, 158 | "notNull": false 159 | }, 160 | "width": { 161 | "name": "width", 162 | "type": "numeric", 163 | "primaryKey": false, 164 | "notNull": false 165 | }, 166 | "height": { 167 | "name": "height", 168 | "type": "numeric", 169 | "primaryKey": false, 170 | "notNull": false 171 | }, 172 | "focal_x": { 173 | "name": "focal_x", 174 | "type": "numeric", 175 | "primaryKey": false, 176 | "notNull": false 177 | }, 178 | "focal_y": { 179 | "name": "focal_y", 180 | "type": "numeric", 181 | "primaryKey": false, 182 | "notNull": false 183 | } 184 | }, 185 | "indexes": { 186 | "media_created_at_idx": { 187 | "name": "media_created_at_idx", 188 | "columns": [ 189 | "created_at" 190 | ], 191 | "isUnique": false 192 | }, 193 | "media_filename_idx": { 194 | "name": "media_filename_idx", 195 | "columns": [ 196 | "filename" 197 | ], 198 | "isUnique": true 199 | } 200 | }, 201 | "foreignKeys": {}, 202 | "compositePrimaryKeys": {}, 203 | "uniqueConstraints": {} 204 | }, 205 | "payload_preferences": { 206 | "name": "payload_preferences", 207 | "schema": "", 208 | "columns": { 209 | "id": { 210 | "name": "id", 211 | "type": "serial", 212 | "primaryKey": true, 213 | "notNull": true 214 | }, 215 | "key": { 216 | "name": "key", 217 | "type": "varchar", 218 | "primaryKey": false, 219 | "notNull": false 220 | }, 221 | "value": { 222 | "name": "value", 223 | "type": "jsonb", 224 | "primaryKey": false, 225 | "notNull": false 226 | }, 227 | "updated_at": { 228 | "name": "updated_at", 229 | "type": "timestamp(3) with time zone", 230 | "primaryKey": false, 231 | "notNull": true, 232 | "default": "now()" 233 | }, 234 | "created_at": { 235 | "name": "created_at", 236 | "type": "timestamp(3) with time zone", 237 | "primaryKey": false, 238 | "notNull": true, 239 | "default": "now()" 240 | } 241 | }, 242 | "indexes": { 243 | "payload_preferences_key_idx": { 244 | "name": "payload_preferences_key_idx", 245 | "columns": [ 246 | "key" 247 | ], 248 | "isUnique": false 249 | }, 250 | "payload_preferences_created_at_idx": { 251 | "name": "payload_preferences_created_at_idx", 252 | "columns": [ 253 | "created_at" 254 | ], 255 | "isUnique": false 256 | } 257 | }, 258 | "foreignKeys": {}, 259 | "compositePrimaryKeys": {}, 260 | "uniqueConstraints": {} 261 | }, 262 | "payload_preferences_rels": { 263 | "name": "payload_preferences_rels", 264 | "schema": "", 265 | "columns": { 266 | "id": { 267 | "name": "id", 268 | "type": "serial", 269 | "primaryKey": true, 270 | "notNull": true 271 | }, 272 | "order": { 273 | "name": "order", 274 | "type": "integer", 275 | "primaryKey": false, 276 | "notNull": false 277 | }, 278 | "parent_id": { 279 | "name": "parent_id", 280 | "type": "integer", 281 | "primaryKey": false, 282 | "notNull": true 283 | }, 284 | "path": { 285 | "name": "path", 286 | "type": "varchar", 287 | "primaryKey": false, 288 | "notNull": true 289 | }, 290 | "users_id": { 291 | "name": "users_id", 292 | "type": "integer", 293 | "primaryKey": false, 294 | "notNull": false 295 | } 296 | }, 297 | "indexes": { 298 | "payload_preferences_rels_order_idx": { 299 | "name": "payload_preferences_rels_order_idx", 300 | "columns": [ 301 | "order" 302 | ], 303 | "isUnique": false 304 | }, 305 | "payload_preferences_rels_parent_idx": { 306 | "name": "payload_preferences_rels_parent_idx", 307 | "columns": [ 308 | "parent_id" 309 | ], 310 | "isUnique": false 311 | }, 312 | "payload_preferences_rels_path_idx": { 313 | "name": "payload_preferences_rels_path_idx", 314 | "columns": [ 315 | "path" 316 | ], 317 | "isUnique": false 318 | } 319 | }, 320 | "foreignKeys": { 321 | "payload_preferences_rels_parent_fk": { 322 | "name": "payload_preferences_rels_parent_fk", 323 | "tableFrom": "payload_preferences_rels", 324 | "tableTo": "payload_preferences", 325 | "columnsFrom": [ 326 | "parent_id" 327 | ], 328 | "columnsTo": [ 329 | "id" 330 | ], 331 | "onDelete": "cascade", 332 | "onUpdate": "no action" 333 | }, 334 | "payload_preferences_rels_users_fk": { 335 | "name": "payload_preferences_rels_users_fk", 336 | "tableFrom": "payload_preferences_rels", 337 | "tableTo": "users", 338 | "columnsFrom": [ 339 | "users_id" 340 | ], 341 | "columnsTo": [ 342 | "id" 343 | ], 344 | "onDelete": "cascade", 345 | "onUpdate": "no action" 346 | } 347 | }, 348 | "compositePrimaryKeys": {}, 349 | "uniqueConstraints": {} 350 | }, 351 | "payload_migrations": { 352 | "name": "payload_migrations", 353 | "schema": "", 354 | "columns": { 355 | "id": { 356 | "name": "id", 357 | "type": "serial", 358 | "primaryKey": true, 359 | "notNull": true 360 | }, 361 | "name": { 362 | "name": "name", 363 | "type": "varchar", 364 | "primaryKey": false, 365 | "notNull": false 366 | }, 367 | "batch": { 368 | "name": "batch", 369 | "type": "numeric", 370 | "primaryKey": false, 371 | "notNull": false 372 | }, 373 | "updated_at": { 374 | "name": "updated_at", 375 | "type": "timestamp(3) with time zone", 376 | "primaryKey": false, 377 | "notNull": true, 378 | "default": "now()" 379 | }, 380 | "created_at": { 381 | "name": "created_at", 382 | "type": "timestamp(3) with time zone", 383 | "primaryKey": false, 384 | "notNull": true, 385 | "default": "now()" 386 | } 387 | }, 388 | "indexes": { 389 | "payload_migrations_created_at_idx": { 390 | "name": "payload_migrations_created_at_idx", 391 | "columns": [ 392 | "created_at" 393 | ], 394 | "isUnique": false 395 | } 396 | }, 397 | "foreignKeys": {}, 398 | "compositePrimaryKeys": {}, 399 | "uniqueConstraints": {} 400 | } 401 | }, 402 | "enums": {}, 403 | "schemas": {}, 404 | "_meta": { 405 | "schemas": {}, 406 | "tables": {}, 407 | "columns": {} 408 | } 409 | } -------------------------------------------------------------------------------- /apps/cms/src/migrations/20240706_165421.ts: -------------------------------------------------------------------------------- 1 | import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres' 2 | 3 | export async function up({ payload, req }: MigrateUpArgs): Promise { 4 | await payload.db.drizzle.execute(sql` 5 | ALTER TABLE "users" ADD COLUMN "username" varchar;`) 6 | }; 7 | 8 | export async function down({ payload, req }: MigrateDownArgs): Promise { 9 | await payload.db.drizzle.execute(sql` 10 | ALTER TABLE "users" DROP COLUMN IF EXISTS "username";`) 11 | }; 12 | -------------------------------------------------------------------------------- /apps/cms/src/migrations/20240706_173841.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "b9633e24-0c10-4e6f-b974-72165682f2e4", 3 | "prevId": "00000000-0000-0000-0000-000000000000", 4 | "version": "5", 5 | "dialect": "pg", 6 | "tables": { 7 | "users": { 8 | "name": "users", 9 | "schema": "", 10 | "columns": { 11 | "id": { 12 | "name": "id", 13 | "type": "serial", 14 | "primaryKey": true, 15 | "notNull": true 16 | }, 17 | "username": { 18 | "name": "username", 19 | "type": "varchar", 20 | "primaryKey": false, 21 | "notNull": true 22 | }, 23 | "updated_at": { 24 | "name": "updated_at", 25 | "type": "timestamp(3) with time zone", 26 | "primaryKey": false, 27 | "notNull": true, 28 | "default": "now()" 29 | }, 30 | "created_at": { 31 | "name": "created_at", 32 | "type": "timestamp(3) with time zone", 33 | "primaryKey": false, 34 | "notNull": true, 35 | "default": "now()" 36 | }, 37 | "email": { 38 | "name": "email", 39 | "type": "varchar", 40 | "primaryKey": false, 41 | "notNull": true 42 | }, 43 | "reset_password_token": { 44 | "name": "reset_password_token", 45 | "type": "varchar", 46 | "primaryKey": false, 47 | "notNull": false 48 | }, 49 | "reset_password_expiration": { 50 | "name": "reset_password_expiration", 51 | "type": "timestamp(3) with time zone", 52 | "primaryKey": false, 53 | "notNull": false 54 | }, 55 | "salt": { 56 | "name": "salt", 57 | "type": "varchar", 58 | "primaryKey": false, 59 | "notNull": false 60 | }, 61 | "hash": { 62 | "name": "hash", 63 | "type": "varchar", 64 | "primaryKey": false, 65 | "notNull": false 66 | }, 67 | "login_attempts": { 68 | "name": "login_attempts", 69 | "type": "numeric", 70 | "primaryKey": false, 71 | "notNull": false 72 | }, 73 | "lock_until": { 74 | "name": "lock_until", 75 | "type": "timestamp(3) with time zone", 76 | "primaryKey": false, 77 | "notNull": false 78 | } 79 | }, 80 | "indexes": { 81 | "users_username_idx": { 82 | "name": "users_username_idx", 83 | "columns": [ 84 | "username" 85 | ], 86 | "isUnique": true 87 | }, 88 | "users_created_at_idx": { 89 | "name": "users_created_at_idx", 90 | "columns": [ 91 | "created_at" 92 | ], 93 | "isUnique": false 94 | }, 95 | "users_email_idx": { 96 | "name": "users_email_idx", 97 | "columns": [ 98 | "email" 99 | ], 100 | "isUnique": true 101 | } 102 | }, 103 | "foreignKeys": {}, 104 | "compositePrimaryKeys": {}, 105 | "uniqueConstraints": {} 106 | }, 107 | "media": { 108 | "name": "media", 109 | "schema": "", 110 | "columns": { 111 | "id": { 112 | "name": "id", 113 | "type": "serial", 114 | "primaryKey": true, 115 | "notNull": true 116 | }, 117 | "alt": { 118 | "name": "alt", 119 | "type": "varchar", 120 | "primaryKey": false, 121 | "notNull": true 122 | }, 123 | "updated_at": { 124 | "name": "updated_at", 125 | "type": "timestamp(3) with time zone", 126 | "primaryKey": false, 127 | "notNull": true, 128 | "default": "now()" 129 | }, 130 | "created_at": { 131 | "name": "created_at", 132 | "type": "timestamp(3) with time zone", 133 | "primaryKey": false, 134 | "notNull": true, 135 | "default": "now()" 136 | }, 137 | "url": { 138 | "name": "url", 139 | "type": "varchar", 140 | "primaryKey": false, 141 | "notNull": false 142 | }, 143 | "thumbnail_u_r_l": { 144 | "name": "thumbnail_u_r_l", 145 | "type": "varchar", 146 | "primaryKey": false, 147 | "notNull": false 148 | }, 149 | "filename": { 150 | "name": "filename", 151 | "type": "varchar", 152 | "primaryKey": false, 153 | "notNull": false 154 | }, 155 | "mime_type": { 156 | "name": "mime_type", 157 | "type": "varchar", 158 | "primaryKey": false, 159 | "notNull": false 160 | }, 161 | "filesize": { 162 | "name": "filesize", 163 | "type": "numeric", 164 | "primaryKey": false, 165 | "notNull": false 166 | }, 167 | "width": { 168 | "name": "width", 169 | "type": "numeric", 170 | "primaryKey": false, 171 | "notNull": false 172 | }, 173 | "height": { 174 | "name": "height", 175 | "type": "numeric", 176 | "primaryKey": false, 177 | "notNull": false 178 | }, 179 | "focal_x": { 180 | "name": "focal_x", 181 | "type": "numeric", 182 | "primaryKey": false, 183 | "notNull": false 184 | }, 185 | "focal_y": { 186 | "name": "focal_y", 187 | "type": "numeric", 188 | "primaryKey": false, 189 | "notNull": false 190 | } 191 | }, 192 | "indexes": { 193 | "media_created_at_idx": { 194 | "name": "media_created_at_idx", 195 | "columns": [ 196 | "created_at" 197 | ], 198 | "isUnique": false 199 | }, 200 | "media_filename_idx": { 201 | "name": "media_filename_idx", 202 | "columns": [ 203 | "filename" 204 | ], 205 | "isUnique": true 206 | } 207 | }, 208 | "foreignKeys": {}, 209 | "compositePrimaryKeys": {}, 210 | "uniqueConstraints": {} 211 | }, 212 | "payload_preferences": { 213 | "name": "payload_preferences", 214 | "schema": "", 215 | "columns": { 216 | "id": { 217 | "name": "id", 218 | "type": "serial", 219 | "primaryKey": true, 220 | "notNull": true 221 | }, 222 | "key": { 223 | "name": "key", 224 | "type": "varchar", 225 | "primaryKey": false, 226 | "notNull": false 227 | }, 228 | "value": { 229 | "name": "value", 230 | "type": "jsonb", 231 | "primaryKey": false, 232 | "notNull": false 233 | }, 234 | "updated_at": { 235 | "name": "updated_at", 236 | "type": "timestamp(3) with time zone", 237 | "primaryKey": false, 238 | "notNull": true, 239 | "default": "now()" 240 | }, 241 | "created_at": { 242 | "name": "created_at", 243 | "type": "timestamp(3) with time zone", 244 | "primaryKey": false, 245 | "notNull": true, 246 | "default": "now()" 247 | } 248 | }, 249 | "indexes": { 250 | "payload_preferences_key_idx": { 251 | "name": "payload_preferences_key_idx", 252 | "columns": [ 253 | "key" 254 | ], 255 | "isUnique": false 256 | }, 257 | "payload_preferences_created_at_idx": { 258 | "name": "payload_preferences_created_at_idx", 259 | "columns": [ 260 | "created_at" 261 | ], 262 | "isUnique": false 263 | } 264 | }, 265 | "foreignKeys": {}, 266 | "compositePrimaryKeys": {}, 267 | "uniqueConstraints": {} 268 | }, 269 | "payload_preferences_rels": { 270 | "name": "payload_preferences_rels", 271 | "schema": "", 272 | "columns": { 273 | "id": { 274 | "name": "id", 275 | "type": "serial", 276 | "primaryKey": true, 277 | "notNull": true 278 | }, 279 | "order": { 280 | "name": "order", 281 | "type": "integer", 282 | "primaryKey": false, 283 | "notNull": false 284 | }, 285 | "parent_id": { 286 | "name": "parent_id", 287 | "type": "integer", 288 | "primaryKey": false, 289 | "notNull": true 290 | }, 291 | "path": { 292 | "name": "path", 293 | "type": "varchar", 294 | "primaryKey": false, 295 | "notNull": true 296 | }, 297 | "users_id": { 298 | "name": "users_id", 299 | "type": "integer", 300 | "primaryKey": false, 301 | "notNull": false 302 | } 303 | }, 304 | "indexes": { 305 | "payload_preferences_rels_order_idx": { 306 | "name": "payload_preferences_rels_order_idx", 307 | "columns": [ 308 | "order" 309 | ], 310 | "isUnique": false 311 | }, 312 | "payload_preferences_rels_parent_idx": { 313 | "name": "payload_preferences_rels_parent_idx", 314 | "columns": [ 315 | "parent_id" 316 | ], 317 | "isUnique": false 318 | }, 319 | "payload_preferences_rels_path_idx": { 320 | "name": "payload_preferences_rels_path_idx", 321 | "columns": [ 322 | "path" 323 | ], 324 | "isUnique": false 325 | } 326 | }, 327 | "foreignKeys": { 328 | "payload_preferences_rels_parent_fk": { 329 | "name": "payload_preferences_rels_parent_fk", 330 | "tableFrom": "payload_preferences_rels", 331 | "tableTo": "payload_preferences", 332 | "columnsFrom": [ 333 | "parent_id" 334 | ], 335 | "columnsTo": [ 336 | "id" 337 | ], 338 | "onDelete": "cascade", 339 | "onUpdate": "no action" 340 | }, 341 | "payload_preferences_rels_users_fk": { 342 | "name": "payload_preferences_rels_users_fk", 343 | "tableFrom": "payload_preferences_rels", 344 | "tableTo": "users", 345 | "columnsFrom": [ 346 | "users_id" 347 | ], 348 | "columnsTo": [ 349 | "id" 350 | ], 351 | "onDelete": "cascade", 352 | "onUpdate": "no action" 353 | } 354 | }, 355 | "compositePrimaryKeys": {}, 356 | "uniqueConstraints": {} 357 | }, 358 | "payload_migrations": { 359 | "name": "payload_migrations", 360 | "schema": "", 361 | "columns": { 362 | "id": { 363 | "name": "id", 364 | "type": "serial", 365 | "primaryKey": true, 366 | "notNull": true 367 | }, 368 | "name": { 369 | "name": "name", 370 | "type": "varchar", 371 | "primaryKey": false, 372 | "notNull": false 373 | }, 374 | "batch": { 375 | "name": "batch", 376 | "type": "numeric", 377 | "primaryKey": false, 378 | "notNull": false 379 | }, 380 | "updated_at": { 381 | "name": "updated_at", 382 | "type": "timestamp(3) with time zone", 383 | "primaryKey": false, 384 | "notNull": true, 385 | "default": "now()" 386 | }, 387 | "created_at": { 388 | "name": "created_at", 389 | "type": "timestamp(3) with time zone", 390 | "primaryKey": false, 391 | "notNull": true, 392 | "default": "now()" 393 | } 394 | }, 395 | "indexes": { 396 | "payload_migrations_created_at_idx": { 397 | "name": "payload_migrations_created_at_idx", 398 | "columns": [ 399 | "created_at" 400 | ], 401 | "isUnique": false 402 | } 403 | }, 404 | "foreignKeys": {}, 405 | "compositePrimaryKeys": {}, 406 | "uniqueConstraints": {} 407 | } 408 | }, 409 | "enums": {}, 410 | "schemas": {}, 411 | "_meta": { 412 | "schemas": {}, 413 | "tables": {}, 414 | "columns": {} 415 | } 416 | } -------------------------------------------------------------------------------- /apps/cms/src/migrations/20240706_173841.ts: -------------------------------------------------------------------------------- 1 | import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres' 2 | 3 | export async function up({ payload, req }: MigrateUpArgs): Promise { 4 | await payload.db.drizzle.execute(sql` 5 | ALTER TABLE "users" ALTER COLUMN "username" SET NOT NULL; 6 | CREATE UNIQUE INDEX IF NOT EXISTS "users_username_idx" ON "users" ("username");`) 7 | }; 8 | 9 | export async function down({ payload, req }: MigrateDownArgs): Promise { 10 | await payload.db.drizzle.execute(sql` 11 | DROP INDEX IF EXISTS "users_username_idx"; 12 | ALTER TABLE "users" ALTER COLUMN "username" DROP NOT NULL;`) 13 | }; 14 | -------------------------------------------------------------------------------- /apps/cms/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"], 23 | "@payload-config": ["src/config/payload.config.ts"] 24 | }, 25 | "target": "ES2017" 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /apps/migrator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "migrator", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "payload": "cross-env NODE_OPTIONS=--no-deprecation payload" 6 | }, 7 | "keywords": [], 8 | "author": "", 9 | "license": "ISC", 10 | "dependencies": { 11 | "cross-env": "^7.0.3", 12 | "payload": "beta", 13 | "@payloadcms/db-postgres": "beta", 14 | "typescript": "^5.5.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/migrator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "paths": { 16 | "@/*": ["./src/*"], 17 | "@payload-config": ["src/config/payload.config.ts"] 18 | }, 19 | "target": "ES2017" 20 | }, 21 | "include": ["**/*.ts", "**/*.tsx"], 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /apps/web/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | db_data 3 | 4 | Dockerfile 5 | Dockerfile.* 6 | 7 | # Output 8 | .output 9 | .vercel 10 | /.svelte-kit 11 | /build 12 | 13 | # OS 14 | .DS_Store 15 | Thumbs.db 16 | 17 | # Env 18 | .env 19 | .env.* 20 | 21 | # Vite 22 | vite.config.js.timestamp-* 23 | vite.config.ts.timestamp-* 24 | -------------------------------------------------------------------------------- /apps/web/.env.example: -------------------------------------------------------------------------------- 1 | # Sveltekit 2 | ORIGIN=http://localhost:5001 3 | 4 | # PayloadCMS 5 | DATABASE_URI=postgressql://postgres_user:postgres_S3cret@127.0.0.1:5432/backend 6 | PAYLOAD_SECRET=secret -------------------------------------------------------------------------------- /apps/web/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | /.svelte-kit 7 | /build 8 | 9 | # OS 10 | .DS_Store 11 | Thumbs.db 12 | 13 | # Env 14 | .env 15 | .env.* 16 | !.env.example 17 | !.env.test 18 | 19 | # Vite 20 | vite.config.js.timestamp-* 21 | vite.config.ts.timestamp-* 22 | -------------------------------------------------------------------------------- /apps/web/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /apps/web/.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /apps/web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /apps/web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine3.19 AS image 2 | 3 | FROM image AS preparer 4 | WORKDIR /app 5 | 6 | ENV PNPM_HOME="/pnpm" 7 | ENV PATH="$PNPM_HOME:$PATH" 8 | RUN corepack enable 9 | 10 | COPY . . 11 | 12 | FROM preparer AS prod-deps 13 | RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod 14 | 15 | FROM preparer AS builder 16 | RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install 17 | RUN pnpm run check 18 | RUN pnpm run build 19 | 20 | FROM image AS prod 21 | WORKDIR /app 22 | 23 | # Don't run production as root 24 | RUN addgroup --system --gid 1001 nodejs 25 | RUN adduser --system --uid 1001 svelte 26 | USER svelte 27 | 28 | # Copy from repo 29 | COPY --chown=svelte:nodejs ./package.json ./package.json 30 | COPY --chown=svelte:nodejs ./tsconfig.json ./tsconfig.json 31 | COPY --chown=svelte:nodejs ./payload ./payload 32 | 33 | # Copy from build 34 | COPY --chown=svelte:nodejs --from=builder /app/build ./build 35 | COPY --chown=svelte:nodejs --from=builder /app/.svelte-kit ./.svelte-kit 36 | COPY --chown=svelte:nodejs --from=prod-deps /app/node_modules ./node_modules 37 | 38 | EXPOSE 3000 39 | 40 | CMD node build -------------------------------------------------------------------------------- /apps/web/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /apps/web/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://shadcn-svelte.com/schema.json", 3 | "style": "new-york", 4 | "tailwind": { 5 | "config": "tailwind.config.ts", 6 | "css": "src\\app.pcss", 7 | "baseColor": "zinc" 8 | }, 9 | "aliases": { 10 | "components": "$lib/components", 11 | "utils": "$lib/components/utils" 12 | }, 13 | "typescript": true 14 | } -------------------------------------------------------------------------------- /apps/web/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import ts from 'typescript-eslint'; 3 | import svelte from 'eslint-plugin-svelte'; 4 | import prettier from 'eslint-config-prettier'; 5 | import globals from 'globals'; 6 | 7 | /** @type {import('eslint').Linter.FlatConfig[]} */ 8 | export default [ 9 | js.configs.recommended, 10 | ...ts.configs.recommended, 11 | ...svelte.configs['flat/recommended'], 12 | prettier, 13 | ...svelte.configs['flat/prettier'], 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.browser, 18 | ...globals.node 19 | } 20 | } 21 | }, 22 | { 23 | files: ['**/*.svelte'], 24 | languageOptions: { 25 | parserOptions: { 26 | parser: ts.parser 27 | } 28 | } 29 | }, 30 | { 31 | ignores: ['build/', '.svelte-kit/', 'dist/'] 32 | } 33 | ]; 34 | -------------------------------------------------------------------------------- /apps/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "db": "docker compose -f docker-compose.db.yml up -d", 7 | "dev": "vite dev", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "test": "playwright test", 11 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 12 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 13 | "lint": "prettier --check . && eslint .", 14 | "format": "prettier --write ." 15 | }, 16 | "devDependencies": { 17 | "@playwright/test": "^1.28.1", 18 | "@sveltejs/adapter-node": "^5.2.0", 19 | "@sveltejs/kit": "^2.0.0", 20 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 21 | "@tailwindcss/typography": "^0.5.13", 22 | "@types/eslint": "^8.56.7", 23 | "autoprefixer": "^10.4.19", 24 | "eslint": "^9.0.0", 25 | "eslint-config-prettier": "^9.1.0", 26 | "eslint-plugin-svelte": "^2.36.0", 27 | "globals": "^15.0.0", 28 | "postcss": "^8.4.38", 29 | "prettier": "^3.1.1", 30 | "prettier-plugin-svelte": "^3.1.2", 31 | "prettier-plugin-tailwindcss": "^0.6.4", 32 | "svelte": "^5.0.0-next.1", 33 | "svelte-check": "^3.6.0", 34 | "tailwindcss": "^3.4.4", 35 | "tslib": "^2.4.1", 36 | "typescript-eslint": "^8.0.0-alpha.20", 37 | "vite": "^5.0.3" 38 | }, 39 | "dependencies": { 40 | "@payloadcms/db-postgres": "beta", 41 | "bits-ui": "^0.21.11", 42 | "clsx": "^2.1.1", 43 | "mode-watcher": "^0.3.1", 44 | "payload": "beta", 45 | "svelte-sonner": "^0.3.25", 46 | "tailwind-merge": "^2.3.0", 47 | "tailwind-variants": "^0.2.1", 48 | "typescript": "^5.5.3" 49 | }, 50 | "type": "module" 51 | } 52 | -------------------------------------------------------------------------------- /apps/web/payload/collections/Media.ts: -------------------------------------------------------------------------------- 1 | import type { CollectionConfig } from 'payload' 2 | 3 | export const Media: CollectionConfig = { 4 | slug: 'media', 5 | access: { 6 | read: () => true, 7 | }, 8 | fields: [ 9 | { 10 | name: 'alt', 11 | type: 'text', 12 | required: true, 13 | }, 14 | ], 15 | upload: true, 16 | } 17 | -------------------------------------------------------------------------------- /apps/web/payload/collections/Users.ts: -------------------------------------------------------------------------------- 1 | import type { CollectionConfig } from 'payload' 2 | 3 | export const Users: CollectionConfig = { 4 | slug: 'users', 5 | admin: { 6 | useAsTitle: 'username', 7 | }, 8 | auth: true, 9 | fields: [ 10 | { 11 | name: 'username', 12 | type: 'text', 13 | label: 'Username', 14 | required: true, 15 | unique: true, 16 | }, 17 | // Email added by default 18 | // Add more fields as needed 19 | ], 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/payload/payload-types.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | * This file was automatically generated by Payload. 5 | * DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config, 6 | * and re-run `payload generate:types` to regenerate this file. 7 | */ 8 | 9 | export interface Config { 10 | auth: { 11 | users: UserAuthOperations; 12 | }; 13 | collections: { 14 | users: User; 15 | media: Media; 16 | 'payload-preferences': PayloadPreference; 17 | 'payload-migrations': PayloadMigration; 18 | }; 19 | globals: {}; 20 | locale: null; 21 | user: User & { 22 | collection: 'users'; 23 | }; 24 | } 25 | export interface UserAuthOperations { 26 | forgotPassword: { 27 | email: string; 28 | }; 29 | login: { 30 | password: string; 31 | email: string; 32 | }; 33 | registerFirstUser: { 34 | email: string; 35 | password: string; 36 | }; 37 | } 38 | /** 39 | * This interface was referenced by `Config`'s JSON-Schema 40 | * via the `definition` "users". 41 | */ 42 | export interface User { 43 | id: number; 44 | username: string; 45 | updatedAt: string; 46 | createdAt: string; 47 | email: string; 48 | resetPasswordToken?: string | null; 49 | resetPasswordExpiration?: string | null; 50 | salt?: string | null; 51 | hash?: string | null; 52 | loginAttempts?: number | null; 53 | lockUntil?: string | null; 54 | password?: string | null; 55 | } 56 | /** 57 | * This interface was referenced by `Config`'s JSON-Schema 58 | * via the `definition` "media". 59 | */ 60 | export interface Media { 61 | id: number; 62 | alt: string; 63 | updatedAt: string; 64 | createdAt: string; 65 | url?: string | null; 66 | thumbnailURL?: string | null; 67 | filename?: string | null; 68 | mimeType?: string | null; 69 | filesize?: number | null; 70 | width?: number | null; 71 | height?: number | null; 72 | focalX?: number | null; 73 | focalY?: number | null; 74 | } 75 | /** 76 | * This interface was referenced by `Config`'s JSON-Schema 77 | * via the `definition` "payload-preferences". 78 | */ 79 | export interface PayloadPreference { 80 | id: number; 81 | user: { 82 | relationTo: 'users'; 83 | value: number | User; 84 | }; 85 | key?: string | null; 86 | value?: 87 | | { 88 | [k: string]: unknown; 89 | } 90 | | unknown[] 91 | | string 92 | | number 93 | | boolean 94 | | null; 95 | updatedAt: string; 96 | createdAt: string; 97 | } 98 | /** 99 | * This interface was referenced by `Config`'s JSON-Schema 100 | * via the `definition` "payload-migrations". 101 | */ 102 | export interface PayloadMigration { 103 | id: number; 104 | name?: string | null; 105 | batch?: number | null; 106 | updatedAt: string; 107 | createdAt: string; 108 | } 109 | /** 110 | * This interface was referenced by `Config`'s JSON-Schema 111 | * via the `definition` "auth". 112 | */ 113 | export interface Auth { 114 | [k: string]: unknown; 115 | } 116 | 117 | 118 | declare module 'payload' { 119 | export interface GeneratedTypes extends Config {} 120 | } -------------------------------------------------------------------------------- /apps/web/payload/payload.config.ts: -------------------------------------------------------------------------------- 1 | // storage-adapter-import-placeholder 2 | import { postgresAdapter } from '@payloadcms/db-postgres' 3 | // import { lexicalEditor } from '@payloadcms/richtext-lexical' 4 | import path from 'path' 5 | import { buildConfig } from 'payload' 6 | import { fileURLToPath } from 'url' 7 | // import sharp from 'sharp' 8 | 9 | import { Users } from './collections/Users' 10 | import { Media } from './collections/Media' 11 | 12 | const filename = fileURLToPath(import.meta.url) 13 | const dirname = path.dirname(filename) 14 | 15 | export default buildConfig({ 16 | admin: { 17 | user: Users.slug, 18 | }, 19 | collections: [Users, Media], 20 | // editor: lexicalEditor(), 21 | secret: process.env.PAYLOAD_SECRET || '', 22 | typescript: { 23 | outputFile: path.resolve(dirname, 'payload-types.ts'), 24 | }, 25 | db: postgresAdapter({ 26 | pool: { 27 | connectionString: process.env.DATABASE_URI || '', 28 | }, 29 | }), 30 | // sharp, 31 | plugins: [ 32 | // storage-adapter-placeholder 33 | ], 34 | }) 35 | -------------------------------------------------------------------------------- /apps/web/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 4173 7 | }, 8 | testDir: 'tests', 9 | testMatch: /(.+\.)?(test|spec)\.[jt]s/ 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /apps/web/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /apps/web/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /apps/web/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/web/src/app.pcss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 240 10% 3.9%; 9 | 10 | --muted: 240 4.8% 95.9%; 11 | --muted-foreground: 240 3.8% 46.1%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 240 10% 3.9%; 15 | 16 | --card: 0 0% 100%; 17 | --card-foreground: 240 10% 3.9%; 18 | 19 | --border: 240 5.9% 90%; 20 | --input: 240 5.9% 90%; 21 | 22 | --primary: 240 5.9% 10%; 23 | --primary-foreground: 0 0% 98%; 24 | 25 | --secondary: 240 4.8% 95.9%; 26 | --secondary-foreground: 240 5.9% 10%; 27 | 28 | --accent: 240 4.8% 95.9%; 29 | --accent-foreground: 240 5.9% 10%; 30 | 31 | --destructive: 0 72.2% 50.6%; 32 | --destructive-foreground: 0 0% 98%; 33 | 34 | --ring: 240 10% 3.9%; 35 | 36 | --radius: 0.5rem; 37 | } 38 | 39 | .dark { 40 | --background: 240 10% 3.9%; 41 | --foreground: 0 0% 98%; 42 | 43 | --muted: 240 3.7% 15.9%; 44 | --muted-foreground: 240 5% 64.9%; 45 | 46 | --popover: 240 10% 3.9%; 47 | --popover-foreground: 0 0% 98%; 48 | 49 | --card: 240 10% 3.9%; 50 | --card-foreground: 0 0% 98%; 51 | 52 | --border: 240 3.7% 15.9%; 53 | --input: 240 3.7% 15.9%; 54 | 55 | --primary: 0 0% 98%; 56 | --primary-foreground: 240 5.9% 10%; 57 | 58 | --secondary: 240 3.7% 15.9%; 59 | --secondary-foreground: 0 0% 98%; 60 | 61 | --accent: 240 3.7% 15.9%; 62 | --accent-foreground: 0 0% 98%; 63 | 64 | --destructive: 0 62.8% 30.6%; 65 | --destructive-foreground: 0 0% 98%; 66 | 67 | --ring: 240 4.9% 83.9%; 68 | } 69 | } 70 | 71 | @layer base { 72 | * { 73 | @apply border-border; 74 | } 75 | body { 76 | @apply bg-background text-foreground; 77 | } 78 | } -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
15 | 16 |
17 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/alert-dialog/index.ts: -------------------------------------------------------------------------------- 1 | import { AlertDialog as AlertDialogPrimitive } from "bits-ui"; 2 | 3 | import Title from "./alert-dialog-title.svelte"; 4 | import Action from "./alert-dialog-action.svelte"; 5 | import Cancel from "./alert-dialog-cancel.svelte"; 6 | import Portal from "./alert-dialog-portal.svelte"; 7 | import Footer from "./alert-dialog-footer.svelte"; 8 | import Header from "./alert-dialog-header.svelte"; 9 | import Overlay from "./alert-dialog-overlay.svelte"; 10 | import Content from "./alert-dialog-content.svelte"; 11 | import Description from "./alert-dialog-description.svelte"; 12 | 13 | const Root = AlertDialogPrimitive.Root; 14 | const Trigger = AlertDialogPrimitive.Trigger; 15 | 16 | export { 17 | Root, 18 | Title, 19 | Action, 20 | Cancel, 21 | Portal, 22 | Footer, 23 | Header, 24 | Trigger, 25 | Overlay, 26 | Content, 27 | Description, 28 | // 29 | Root as AlertDialog, 30 | Title as AlertDialogTitle, 31 | Action as AlertDialogAction, 32 | Cancel as AlertDialogCancel, 33 | Portal as AlertDialogPortal, 34 | Footer as AlertDialogFooter, 35 | Header as AlertDialogHeader, 36 | Trigger as AlertDialogTrigger, 37 | Overlay as AlertDialogOverlay, 38 | Content as AlertDialogContent, 39 | Description as AlertDialogDescription, 40 | }; 41 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/button/button.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/button/index.ts: -------------------------------------------------------------------------------- 1 | import type { Button as ButtonPrimitive } from "bits-ui"; 2 | import { type VariantProps, tv } from "tailwind-variants"; 3 | import Root from "./button.svelte"; 4 | 5 | const buttonVariants = tv({ 6 | base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", 7 | variants: { 8 | variant: { 9 | default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", 10 | destructive: 11 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 12 | outline: 13 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 14 | secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 15 | ghost: "hover:bg-accent hover:text-accent-foreground", 16 | link: "text-primary underline-offset-4 hover:underline", 17 | }, 18 | size: { 19 | default: "h-9 px-4 py-2", 20 | sm: "h-8 rounded-md px-3 text-xs", 21 | lg: "h-10 rounded-md px-8", 22 | icon: "h-9 w-9", 23 | }, 24 | }, 25 | defaultVariants: { 26 | variant: "default", 27 | size: "default", 28 | }, 29 | }); 30 | 31 | type Variant = VariantProps["variant"]; 32 | type Size = VariantProps["size"]; 33 | 34 | type Props = ButtonPrimitive.Props & { 35 | variant?: Variant; 36 | size?: Size; 37 | }; 38 | 39 | type Events = ButtonPrimitive.Events; 40 | 41 | export { 42 | Root, 43 | type Props, 44 | type Events, 45 | // 46 | Root as Button, 47 | type Props as ButtonProps, 48 | type Events as ButtonEvents, 49 | buttonVariants, 50 | }; 51 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/card/card-content.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/card/card-description.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |

12 | 13 |

14 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/card/card-footer.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/card/card-header.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/card/card-title.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/card/card.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 |
21 | 22 |
23 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/card/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./card.svelte"; 2 | import Content from "./card-content.svelte"; 3 | import Description from "./card-description.svelte"; 4 | import Footer from "./card-footer.svelte"; 5 | import Header from "./card-header.svelte"; 6 | import Title from "./card-title.svelte"; 7 | 8 | export { 9 | Root, 10 | Content, 11 | Description, 12 | Footer, 13 | Header, 14 | Title, 15 | // 16 | Root as Card, 17 | Content as CardContent, 18 | Description as CardDescription, 19 | Footer as CardFooter, 20 | Header as CardHeader, 21 | Title as CardTitle, 22 | }; 23 | 24 | export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; 25 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/input/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./input.svelte"; 2 | 3 | export type FormInputEvent = T & { 4 | currentTarget: EventTarget & HTMLInputElement; 5 | }; 6 | export type InputEvents = { 7 | blur: FormInputEvent; 8 | change: FormInputEvent; 9 | click: FormInputEvent; 10 | focus: FormInputEvent; 11 | focusin: FormInputEvent; 12 | focusout: FormInputEvent; 13 | keydown: FormInputEvent; 14 | keypress: FormInputEvent; 15 | keyup: FormInputEvent; 16 | mouseover: FormInputEvent; 17 | mouseenter: FormInputEvent; 18 | mouseleave: FormInputEvent; 19 | mousemove: FormInputEvent; 20 | paste: FormInputEvent; 21 | input: FormInputEvent; 22 | wheel: FormInputEvent; 23 | }; 24 | 25 | export { 26 | Root, 27 | // 28 | Root as Input, 29 | }; 30 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/input/input.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 43 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/sonner/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Toaster } from "./sonner.svelte"; 2 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/ui/sonner/sonner.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | -------------------------------------------------------------------------------- /apps/web/src/lib/components/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | import { cubicOut } from "svelte/easing"; 4 | import type { TransitionConfig } from "svelte/transition"; 5 | 6 | export function cn(...inputs: ClassValue[]) { 7 | return twMerge(clsx(inputs)); 8 | } 9 | 10 | type FlyAndScaleParams = { 11 | y?: number; 12 | x?: number; 13 | start?: number; 14 | duration?: number; 15 | }; 16 | 17 | export const flyAndScale = ( 18 | node: Element, 19 | params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 } 20 | ): TransitionConfig => { 21 | const style = getComputedStyle(node); 22 | const transform = style.transform === "none" ? "" : style.transform; 23 | 24 | const scaleConversion = ( 25 | valueA: number, 26 | scaleA: [number, number], 27 | scaleB: [number, number] 28 | ) => { 29 | const [minA, maxA] = scaleA; 30 | const [minB, maxB] = scaleB; 31 | 32 | const percentage = (valueA - minA) / (maxA - minA); 33 | const valueB = percentage * (maxB - minB) + minB; 34 | 35 | return valueB; 36 | }; 37 | 38 | const styleToString = ( 39 | style: Record 40 | ): string => { 41 | return Object.keys(style).reduce((str, key) => { 42 | if (style[key] === undefined) return str; 43 | return str + `${key}:${style[key]};`; 44 | }, ""); 45 | }; 46 | 47 | return { 48 | duration: params.duration ?? 200, 49 | delay: 0, 50 | css: (t) => { 51 | const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]); 52 | const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]); 53 | const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]); 54 | 55 | return styleToString({ 56 | transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`, 57 | opacity: t 58 | }); 59 | }, 60 | easing: cubicOut 61 | }; 62 | }; -------------------------------------------------------------------------------- /apps/web/src/lib/server/payload.ts: -------------------------------------------------------------------------------- 1 | import { BasePayload, getPayload, type SanitizedConfig } from 'payload'; 2 | import { importConfig } from 'payload/node'; 3 | import path from 'node:path'; 4 | import { fileURLToPath } from 'node:url'; 5 | import { building } from '$app/environment'; 6 | 7 | let payloadInstance: BasePayload | null = null; 8 | 9 | async function initPayload() { 10 | if (!payloadInstance && !building) { 11 | try { 12 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 13 | const payloadConfigPath: string = path.join(__dirname, '../../../payload/payload.config.ts'); 14 | 15 | console.log(payloadConfigPath); 16 | 17 | const awaitedConfig: SanitizedConfig = await importConfig(payloadConfigPath); 18 | payloadInstance = await getPayload({ config: awaitedConfig }); 19 | } catch (error) { 20 | console.log('error', error); 21 | payloadInstance = null; 22 | } 23 | return payloadInstance; 24 | } 25 | } 26 | 27 | export const payload = payloadInstance ? payloadInstance : await initPayload(); 28 | -------------------------------------------------------------------------------- /apps/web/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /apps/web/src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | export const prerender = false; 2 | -------------------------------------------------------------------------------- /apps/web/src/routes/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { PageServerLoad } from './$types'; 2 | import { payload } from '$lib/server/payload'; 3 | import type { Actions } from '@sveltejs/kit'; 4 | import type { PaginatedDocs } from 'payload'; 5 | import type { User } from '$payload/payload-types.ts'; 6 | 7 | export const load: PageServerLoad = async () => { 8 | if (!payload) return; 9 | const users: PaginatedDocs = await payload.find({ 10 | collection: 'users', 11 | limit: 10 12 | }); 13 | return { 14 | users: users 15 | }; 16 | }; 17 | 18 | export const actions: Actions = { 19 | create: async ({ request }) => { 20 | if (!payload) return; 21 | const form = await request.formData(); 22 | const email: string = form.get('email') as string; 23 | const username: string = form.get('username') as string; 24 | const password: string = form.get('password') as string; 25 | try { 26 | const user: User = await payload.create({ 27 | collection: 'users', 28 | data: { 29 | email: email, 30 | username: username, 31 | password: password 32 | } 33 | }); 34 | return { 35 | user 36 | }; 37 | } catch (error) { 38 | console.log(error); 39 | } 40 | }, 41 | delete: async ({ request }) => { 42 | if (!payload) return; 43 | const form = await request.formData(); 44 | const id: string = form.get('id') as string; 45 | try { 46 | const user: User = await payload.delete({ 47 | collection: 'users', 48 | id: id 49 | }); 50 | return { 51 | user 52 | }; 53 | } catch (error) { 54 | console.log(error); 55 | } 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /apps/web/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 |
21 |

Sveltekit + Payload CMS

22 | 23 | 24 | User Table {users ? '🎉' : '⛔'} 25 | Doing a simple query to check if we can connect to Payload CMS. 28 | 29 | 30 | {#if users} 31 |
32 |

Create User

33 |
{ 37 | return async ({ result, update }) => { 38 | if (result.type === 'success') { 39 | if (result.data) { 40 | toast.success('User created successfully'); 41 | } else { 42 | toast.error('User creation failed'); 43 | } 44 | update(); 45 | } 46 | }; 47 | }} 48 | class="flex flex-col gap-2 py-2" 49 | > 50 | 51 | 52 | 53 | 54 |
55 |
56 |
57 |

All Users

58 |
61 | ID 62 | Email 63 | Delete 64 |
65 | {#each users.docs as user} 66 |
67 | {user.id} 68 |
69 | {user.email} 70 | {user.username} 71 |
72 | 73 | 74 | 75 | 76 | Are you absolutely sure? 77 | 78 | This action cannot be undone. This will permanently delete `{user.email}` account from our servers. 81 | 82 | 83 | 84 | Cancel 85 |
{ 89 | return async ({ result, update }) => { 90 | if (result.type === 'success') { 91 | if (result.data) { 92 | toast.success('User deleted successfully'); 93 | } else { 94 | toast.error('User deletion failed'); 95 | } 96 | update(); 97 | } 98 | }; 99 | }} 100 | > 101 | 102 | Continue 103 |
104 |
105 |
106 |
107 |
108 | {/each} 109 |
110 | {:else} 111 |

Can't connect to Payload CMS 😢

112 | {/if} 113 |
114 |
115 |
116 | -------------------------------------------------------------------------------- /apps/web/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcastrovilli/sveltekit-payload-3-starterkit/f003749ade92db1971ea19ac06f3ca10ba22a835/apps/web/static/favicon.png -------------------------------------------------------------------------------- /apps/web/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-node'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter(), 15 | alias: { 16 | $payload: './payload' 17 | } 18 | } 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /apps/web/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import { fontFamily } from "tailwindcss/defaultTheme"; 2 | import type { Config } from "tailwindcss"; 3 | 4 | const config: Config = { 5 | darkMode: ["class"], 6 | content: ["./src/**/*.{html,js,svelte,ts}"], 7 | safelist: ["dark"], 8 | theme: { 9 | container: { 10 | center: true, 11 | padding: "2rem", 12 | screens: { 13 | "2xl": "1400px" 14 | } 15 | }, 16 | extend: { 17 | colors: { 18 | border: "hsl(var(--border) / )", 19 | input: "hsl(var(--input) / )", 20 | ring: "hsl(var(--ring) / )", 21 | background: "hsl(var(--background) / )", 22 | foreground: "hsl(var(--foreground) / )", 23 | primary: { 24 | DEFAULT: "hsl(var(--primary) / )", 25 | foreground: "hsl(var(--primary-foreground) / )" 26 | }, 27 | secondary: { 28 | DEFAULT: "hsl(var(--secondary) / )", 29 | foreground: "hsl(var(--secondary-foreground) / )" 30 | }, 31 | destructive: { 32 | DEFAULT: "hsl(var(--destructive) / )", 33 | foreground: "hsl(var(--destructive-foreground) / )" 34 | }, 35 | muted: { 36 | DEFAULT: "hsl(var(--muted) / )", 37 | foreground: "hsl(var(--muted-foreground) / )" 38 | }, 39 | accent: { 40 | DEFAULT: "hsl(var(--accent) / )", 41 | foreground: "hsl(var(--accent-foreground) / )" 42 | }, 43 | popover: { 44 | DEFAULT: "hsl(var(--popover) / )", 45 | foreground: "hsl(var(--popover-foreground) / )" 46 | }, 47 | card: { 48 | DEFAULT: "hsl(var(--card) / )", 49 | foreground: "hsl(var(--card-foreground) / )" 50 | } 51 | }, 52 | borderRadius: { 53 | lg: "var(--radius)", 54 | md: "calc(var(--radius) - 2px)", 55 | sm: "calc(var(--radius) - 4px)" 56 | }, 57 | fontFamily: { 58 | sans: [...fontFamily.sans] 59 | } 60 | } 61 | }, 62 | }; 63 | 64 | export default config; 65 | -------------------------------------------------------------------------------- /apps/web/tests/test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test('home page has expected h1', async ({ page }) => { 4 | await page.goto('/'); 5 | await expect(page.locator('h1')).toBeVisible(); 6 | }); 7 | -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /docker-compose.db.yml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | image: postgres:16.3-alpine3.20 4 | container_name: db 5 | ports: 6 | - 5432:5432 7 | volumes: 8 | - db_data:/var/lib/postgresql/data 9 | environment: 10 | - POSTGRES_USER=${POSTGRES_USER} 11 | - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} 12 | - POSTGRES_DB=${POSTGRES_DB} 13 | healthcheck: 14 | test: 15 | [ 16 | "CMD-SHELL", 17 | "sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'", 18 | ] 19 | interval: 10s 20 | timeout: 5s 21 | retries: 5 22 | 23 | volumes: 24 | db_data: 25 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | image: postgres:16.3-alpine3.20 4 | container_name: db 5 | volumes: 6 | - ./db_data:/var/lib/postgresql/data 7 | environment: 8 | - POSTGRES_USER=${POSTGRES_USER} 9 | - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} 10 | - POSTGRES_DB=${POSTGRES_DB} 11 | healthcheck: 12 | test: 13 | [ 14 | "CMD-SHELL", 15 | "sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'", 16 | ] 17 | interval: 10s 18 | timeout: 5s 19 | retries: 5 20 | migrator: 21 | build: 22 | context: ./ 23 | dockerfile: Dockerfile.migrator 24 | container_name: migrator 25 | command: ["pnpm", "payload", "migrate"] 26 | restart: no 27 | environment: 28 | - PAYLOAD_SECRET=${PAYLOAD_SECRET} 29 | - DATABASE_URI=postgressql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:${DB_PORT:-5432}/${POSTGRES_DB} 30 | depends_on: 31 | db: 32 | condition: service_healthy 33 | restart: true 34 | cms: 35 | build: ./apps/cms 36 | container_name: cms 37 | environment: 38 | - PAYLOAD_SECRET=${PAYLOAD_SECRET} 39 | - DATABASE_URI=postgressql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:${DB_PORT:-5432}/${POSTGRES_DB} 40 | depends_on: 41 | migrator: 42 | condition: service_completed_successfully 43 | db: 44 | condition: service_healthy 45 | restart: true 46 | web: 47 | build: ./apps/web 48 | container_name: web 49 | environment: 50 | - NODE_ENV=production 51 | - ORIGIN=${ORIGIN} 52 | - PAYLOAD_SECRET=${PAYLOAD_SECRET} 53 | - DATABASE_URI=postgressql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:${DB_PORT:-5432}/${POSTGRES_DB} 54 | depends_on: 55 | migrator: 56 | condition: service_completed_successfully 57 | db: 58 | condition: service_healthy 59 | restart: true 60 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - ./docker-compose.db.yml 3 | 4 | services: 5 | cms: 6 | build: ./apps/cms 7 | container_name: cms 8 | environment: 9 | - PAYLOAD_SECRET=${PAYLOAD_SECRET} 10 | - DATABASE_URI=postgressql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} 11 | ports: 12 | - ${CMS_PORT:-5000}:3000 13 | depends_on: 14 | db: 15 | condition: service_healthy 16 | restart: true 17 | 18 | web: 19 | build: ./apps/web 20 | container_name: web 21 | environment: 22 | - NODE_ENV=production 23 | - ORIGIN=${ORIGIN} 24 | - PAYLOAD_SECRET=${PAYLOAD_SECRET} 25 | - DATABASE_URI=postgressql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} 26 | ports: 27 | - ${WEB_PORT:-5001}:3000 28 | depends_on: 29 | db: 30 | condition: service_healthy 31 | restart: true 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "turbo-starterkit", 3 | "private": true, 4 | "scripts": { 5 | "db": "docker compose -f docker-compose.db.yml up -d", 6 | "db:down": "docker compose -f docker-compose.db.yml down", 7 | "prod": "pnpm run sync-config && docker compose -f docker-compose.prod.yml up --build", 8 | "down": "docker compose -f docker-compose.prod.yml down", 9 | "build": "pnpm run sync-config && turbo build --filter=cms --filter=web", 10 | "payload": "cd apps/cms && pnpm payload", 11 | "dev": "pnpm run sync-config && turbo dev --filter=cms --filter=web", 12 | "lint": "turbo lint", 13 | "format": "prettier --write \"**/*.{ts,tsx,md}\"", 14 | "sync-config": "node scripts/watcher.js" 15 | }, 16 | "devDependencies": { 17 | "fs-extra": "^11.2.0", 18 | "turbo": "latest" 19 | }, 20 | "packageManager": "pnpm@9.1.1", 21 | "engines": { 22 | "node": ">=20" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" 3 | -------------------------------------------------------------------------------- /scripts/watcher.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs-extra"); 2 | const path = require("path"); 3 | 4 | const sourceDir = path.join(__dirname, "../apps/cms/src/config"); 5 | const destDir = path.join(__dirname, "../apps/web/payload"); 6 | 7 | async function removeFile(filePath) { 8 | try { 9 | await fs.remove(filePath); 10 | console.log(`File removed at ${filePath}`); 11 | } catch (err) { 12 | console.error("Error during file removal:", err); 13 | } 14 | } 15 | 16 | async function syncDirectories(srcDir, destDir) { 17 | try { 18 | // Copy files from source to destination 19 | await fs.copy(srcDir, destDir, { 20 | overwrite: true, 21 | errorOnExist: false, 22 | }); 23 | 24 | // Remove files from destination that do not exist in source 25 | const srcFiles = await fs.readdir(srcDir); 26 | const destFiles = await fs.readdir(destDir); 27 | 28 | for (const file of destFiles) { 29 | if (!srcFiles.includes(file)) { 30 | await removeFile(path.join(destDir, file)); 31 | } 32 | } 33 | 34 | console.log(`Directories ${srcDir} and ${destDir} are now in sync`); 35 | } catch (err) { 36 | console.error("Error during directory sync:", err); 37 | } 38 | } 39 | 40 | // Sync directories at startup 41 | syncDirectories(sourceDir, destDir); 42 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "tasks": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "inputs": ["$TURBO_DEFAULT$", ".env*"], 7 | "outputs": [ 8 | "dist/**", 9 | "build/**", 10 | "public/**", 11 | ".next/**", 12 | "!.next/cache/**" 13 | ] 14 | }, 15 | "lint": { 16 | "dependsOn": ["^lint"] 17 | }, 18 | "dev": { 19 | "cache": false, 20 | "persistent": true 21 | } 22 | } 23 | } 24 | --------------------------------------------------------------------------------