diff --git a/.gitignore b/.gitignore index 4870e931..dc953184 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ node_modules/ .cache sandbox +.adminjs diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 33b9f0a0..5acb761b 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/Caddyfile b/Caddyfile deleted file mode 100644 index 6827535c..00000000 --- a/Caddyfile +++ /dev/null @@ -1,15 +0,0 @@ -{ - servers { - trusted_proxies static private_ranges - } -} - -:80 { - @trpc path_regexp ^/trpc(/|$) - reverse_proxy @trpc localhost:2021 - - @admin path_regexp ^/admin(/|$) - reverse_proxy @admin localhost:4000 - - reverse_proxy localhost:3000 -} diff --git a/Dockerfile.compact b/Dockerfile.compact deleted file mode 100644 index d010d896..00000000 --- a/Dockerfile.compact +++ /dev/null @@ -1,56 +0,0 @@ -FROM node:20-alpine AS custom-node - -RUN apk add -f --update --no-cache --virtual .gyp nano bash libc6-compat python3 make g++ \ - && yarn global add turbo \ - && apk del .gyp - - -FROM custom-node AS pruned -WORKDIR /app - -COPY . . - -RUN turbo prune --scope=admin --scope=frontend --scope=backend --docker - -FROM custom-node AS installer -WORKDIR /app - -COPY --from=pruned /app/out/json/ . -COPY --from=pruned /app/out/yarn.lock /app/yarn.lock - -RUN \ - --mount=type=cache,target=/usr/local/share/.cache/yarn/v6,sharing=private \ - yarn - -FROM custom-node as builder -WORKDIR /app -ARG API_URL -ARG COMMIT - -ENV COMMIT=${COMMIT} -ENV API_URL=${API_URL} - -COPY --from=installer --link /app . - -COPY --from=pruned /app/out/full/ . -COPY turbo.json turbo.json -COPY tsconfig.json tsconfig.json -COPY ecosystem.config.js ecosystem.config.js - -RUN turbo run build --no-cache - -RUN \ - --mount=type=cache,target=/usr/local/share/.cache/yarn/v6,sharing=private \ - yarn --frozen-lockfile - -############################################# -FROM node:20-alpine AS runner -WORKDIR /app - -RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories \ - && apk add -f --update caddy - -COPY --from=builder /app . -COPY Caddyfile /etc/caddy/Caddyfile - -CMD ["sh", "-c", "caddy run --config /etc/caddy/Caddyfile & yarn start"] diff --git a/apps/admin/.gitignore b/apps/admin/.gitignore deleted file mode 100644 index 0e82ba90..00000000 --- a/apps/admin/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.vercel -.env -node_modules -.vercel -dist -.adminjs -lib -dist diff --git a/apps/admin/package.json b/apps/admin/package.json deleted file mode 100644 index 786526df..00000000 --- a/apps/admin/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "admin", - "version": "3.0.0", - "main": "./dist/index.js", - "type": "module", - "author": "Younes Benaomar", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "scripts": { - "dev-admin": "dotenv -e ../../.env -- tsup --watch --silent --onSuccess 'node dist/index.js'", - "build": "tsup", - "start": "node dist" - }, - "dependencies": { - "@adminjs/express": "^6.0.1", - "@adminjs/import-export": "^3.0.0", - "@adminjs/prisma": "^5.0.1", - "@adminjs/themes": "^1.0.1", - "@celluloid/passport": "workspace:*", - "@celluloid/prisma": "workspace:*", - "@celluloid/utils": "workspace:*", - "@tiptap/extension-text-style": "^2.8.0", - "adminjs": "^7.2.2", - "cors": "^2.8.5", - "express": "^4.19.2", - "express-formidable": "^1.2.0", - "express-session": "^1.17.3" - }, - "devDependencies": { - "@celluloid/config": "workspace:*", - "@types/cors": "^2.8.13", - "@types/express": "^4.17.17", - "@types/node": "^18.14.2", - "copyfiles": "^2.4.1", - "del-cli": "^5.0.0", - "dotenv-cli": "^7.3.0", - "np": "^7.7.0", - "tsup": "^8.3.0", - "typescript": "^5.6.2" - } -} diff --git a/apps/admin/public/assets/fonts/lexend-variablefont_wght-webfont.woff b/apps/admin/public/assets/fonts/lexend-variablefont_wght-webfont.woff deleted file mode 100644 index c22589e1..00000000 Binary files a/apps/admin/public/assets/fonts/lexend-variablefont_wght-webfont.woff and /dev/null differ diff --git a/apps/admin/public/assets/fonts/lexend-variablefont_wght-webfont.woff2 b/apps/admin/public/assets/fonts/lexend-variablefont_wght-webfont.woff2 deleted file mode 100644 index caf67957..00000000 Binary files a/apps/admin/public/assets/fonts/lexend-variablefont_wght-webfont.woff2 and /dev/null differ diff --git a/apps/admin/src/index.ts b/apps/admin/src/index.ts deleted file mode 100644 index 23f8245e..00000000 --- a/apps/admin/src/index.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { createSession, passport } from "@celluloid/passport"; -import { UserRole } from "@celluloid/prisma"; -import cors from "cors"; -import express, { - type NextFunction, - type Request, - type Response, -} from "express"; -import path from "path"; -import * as url from "url"; - -import getAdminRouter from "./server.js"; - -const PORT = process.env.PORT || 4000; - -const start = async () => { - const app = express(); - app.enable("trust proxy"); - app.use(cors({ credentials: true, origin: true })); - app.use(createSession()); - app.use(passport.authenticate("session")); - - // Define the CORS middleware function - const corsMiddleware = (req: Request, res: Response, next: NextFunction) => { - // Set the CORS headers - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT"); - res.setHeader("Access-Control-Allow-Headers", "Content-Type"); - // Call the next middleware function in the chain - next(); - }; - - // Add the CORS middleware function to the application - app.use(corsMiddleware); - - const adminRouter = await getAdminRouter({ - rootPath: "/admin", - }); - - const isAuthenticated = (req, res, next) => { - if (req.user && req.user.role === UserRole.Admin) return next(); - res.redirect("/"); - }; - - app.use("/admin", isAuthenticated, adminRouter); - - const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); - - app.use("/admin", express.static(path.join(__dirname, "../public"))); - - app.listen(PORT, () => { - console.log(`AdminJS started on localhost:${PORT}`); - }); -}; - -start(); diff --git a/apps/admin/src/passport.ts b/apps/admin/src/passport.ts deleted file mode 100644 index 5239cb28..00000000 --- a/apps/admin/src/passport.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { prisma, type User, UserRole } from "@celluloid/prisma"; -import bcrypt from "bcryptjs"; -import passport from "passport"; -import { Strategy as LocalStrategy } from "passport-local"; - - - -passport.serializeUser((user: User, done) => { - done(null, user.id); -}); - -passport.deserializeUser(async (id: string, done) => { - const user = await prisma.user.findUnique({ where: { id } }); - if (user) { - return done(null, user); - } else { - console.error( - `Deserialize user failed: user with id` + ` ${id} does not exist`, - ); - return done(new Error("InvalidUser")); - } -}); - -passport.use( - new LocalStrategy(async (username: string, password: string, done) => { - const user = await prisma.user.findUnique({ - where: { username: username }, - }); - if (!user) { - return done(new Error("InvalidUser")); - } - if (!bcrypt.compareSync(password, user.password)) { - return done(new Error("InvalidUser")); - } - if (!user.confirmed && user.role !== UserRole.Student) { - return done(new Error("UserNotConfirmed")); - } - return done(null, user); - }), -); - -const loginStrategy = new LocalStrategy( - { usernameField: "login" }, - async (login, password, done) => { - const user = await prisma.user.findUnique({ - where: { - OR: [{ email: login }, { username: login }], - }, - }); - - if (!user) { - return Promise.resolve(done(new Error("InvalidUser"))); - } - if (!bcrypt.compareSync(password, user.password)) { - console.error( - `Login failed for user ${user.username}: incorrect password`, - ); - return Promise.resolve(done(new Error("InvalidUser"))); - } - if (!user.confirmed && user.role !== UserRole.Student) { - console.error(`Login failed: ${user.username} is not confirmed`); - return Promise.resolve(done(new Error("UserNotConfirmed"))); - } - return Promise.resolve(done(null, user)); - }, -); - -passport.use("login", loginStrategy); - -export default passport; diff --git a/apps/admin/src/session.ts b/apps/admin/src/session.ts deleted file mode 100644 index c104af89..00000000 --- a/apps/admin/src/session.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { env } from "@celluloid/utils" -import RedisStore from "connect-redis" -import session from "express-session" -import { createClient } from "redis" - -export function createSession() { - - - // Initialize client. - const redisClient = createClient({ - url: env.REDIS_URL - }) - redisClient.connect().catch(console.error) - - // Initialize store. - const redisStore = new RedisStore({ - client: redisClient, - }) - - return session({ - store: redisStore, - name: env.COOKIE_NAME, - cookie: { - domain: env.COOKIE_DOMAIN, - secure: env.COOKIE_SECURE, - maxAge: 30 * 24 * 3600 * 1000, - httpOnly: true, - }, - secret: env.COOKIE_SECRET, - resave: false, - saveUninitialized: true, - }); -} diff --git a/apps/admin/tsconfig.json b/apps/admin/tsconfig.json deleted file mode 100644 index 1418dab3..00000000 --- a/apps/admin/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "jsx": "react", - "module": "NodeNext", - "moduleResolution": "nodenext", - "declaration": true, - "removeComments": true, - "allowSyntheticDefaultImports": true, - "target": "es6", - "lib": [ - "esnext" - ], - "sourceMap": true, - "outDir": "dist", - "skipLibCheck": true, - "esModuleInterop": true - }, - "exclude": [ - "node_modules", - "src/**/theme.bundle.js" - ], - "include": [ - "src" - ] -} diff --git a/apps/admin/tsup.config.ts b/apps/admin/tsup.config.ts deleted file mode 100644 index 791c65b4..00000000 --- a/apps/admin/tsup.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineConfig } from "tsup"; - -const isProduction = process.env.NODE_ENV === "production"; - -export default defineConfig({ - clean: true, - dts: false, - entry: ["src/index.ts", "src/my-input.tsx"], - format: ["esm"], - target: "esnext", - minify: isProduction, - sourcemap: false, -}); diff --git a/apps/frontend/package.json b/apps/frontend/package.json index dbf9bccd..29ec7a4e 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -21,6 +21,9 @@ "with-env": "dotenv -e ../../.env --" }, "dependencies": { + "@adminjs/express": "^6.1.0", + "@adminjs/prisma": "^5.0.3", + "@adminjs/themes": "^1.0.1", "@celluloid/prisma": "workspace:*", "@celluloid/queue": "workspace:*", "@celluloid/react-player": "2.14.0", @@ -40,6 +43,7 @@ "@trpc/react-query": "^10.45.2", "@trpc/server": "^10.45.2", "@types/linkify-urls": "^3.1.1", + "adminjs": "^7.8.13", "change-case": "^4.1.2", "cookie-parser": "^1.4.7", "copy-to-clipboard": "^3.3.3", diff --git a/apps/admin/public/assets/images/logo.svg b/apps/frontend/public/logo-admin.svg similarity index 100% rename from apps/admin/public/assets/images/logo.svg rename to apps/frontend/public/logo-admin.svg diff --git a/apps/admin/public/assets/styles/override.css b/apps/frontend/public/override.css similarity index 82% rename from apps/admin/public/assets/styles/override.css rename to apps/frontend/public/override.css index 0258058b..0132c9f3 100644 --- a/apps/admin/public/assets/styles/override.css +++ b/apps/frontend/public/override.css @@ -4,38 +4,38 @@ --sidebar-color: white; --sidebar-link-color: orange; } - + section[data-css="sidebar"] { background-color: var(--sidebar-bg-color) !important; color: var(--sidebar-color); border: none; } - + section[data-css="sidebar"] svg { fill: var(--sidebar-color) !important; } - + a[data-css="sidebar-logo"] { background-color: var(--sidebar-bg-color) !important; } - + section[data-css="sidebar-resources"] { background: var(--sidebar-bg-color) !important; } - + [data-css="sidebar"] section a { background: var(--sidebar-bg-color) !important; color: var(--sidebar-color); } - + [data-css="sidebar"] a:hover { color: var(--sidebar-link-color); } @font-face { font-family: 'lexendregular'; - src: url('../fonts/lexend-variablefont_wght-webfont.woff2') format('woff2'), - url('../fonts/lexend-variablefont_wght-webfont.woff') format('woff'); + src: url('fonts/lexend-variablefont_wght-webfont.woff2') format('woff2'), + url('fonts/lexend-variablefont_wght-webfont.woff') format('woff'); font-weight: normal; font-style: normal; - } \ No newline at end of file + } diff --git a/apps/admin/src/server.ts b/apps/frontend/src/server/admin.ts similarity index 69% rename from apps/admin/src/server.ts rename to apps/frontend/src/server/admin.ts index 46a19394..a4de8f29 100644 --- a/apps/admin/src/server.ts +++ b/apps/frontend/src/server/admin.ts @@ -2,8 +2,19 @@ import AdminJSExpress from "@adminjs/express"; import { Database, getModelByName, Resource } from '@adminjs/prisma'; import { dark, light, noSidebar } from '@adminjs/themes' import PrismaModule, { prisma } from '@celluloid/prisma'; -import AdminJS, { type AdminJSOptions, type ThemeConfig } from "adminjs"; +import AdminJS, { type ThemeConfig } from "adminjs"; +// @ts-expect-error +BigInt.prototype.toJSON = function () { const int = Number.parseInt(this.toString()); return int ?? this.toString() }; + +import { ComponentLoader } from 'adminjs' + +const componentLoader = new ComponentLoader() + +const Components = { + Dashboard: componentLoader.add('Dashboard', './dashboard'), + // other custom components +} export const overrides: ThemeConfig['overrides'] = { colors: { @@ -39,7 +50,7 @@ export const overrides: ThemeConfig['overrides'] = { -const getAdminRouter = (options: Partial = {}) => { +const getAdminRouter = () => { AdminJS.registerAdapter({ @@ -47,16 +58,16 @@ const getAdminRouter = (options: Partial = {}) => { Database: Database, }); - - const adminOptions = { + const admin = new AdminJS({ + rootPath: "/admin", branding: { companyName: 'Celluloid', withMadeWithLove: false, - logo: '/admin/assets/images/logo.svg', + logo: '/images/logo-admin.svg', theme: overrides }, assets: { - styles: ['/admin/assets/styles/override.css'], + styles: ['/styles/override.css'], }, defaultTheme: noSidebar.id, availableThemes: [dark, light, noSidebar], @@ -108,10 +119,7 @@ const getAdminRouter = (options: Partial = {}) => { { resource: { model: getModelByName('Playlist', PrismaModule), client: prisma }, options: { - navigation: { - name: 'Playlists', - icon: 'Play', - }, + navigation: "Projects", listProperties: ['title', 'description', 'user'], filterProperties: ['title', 'description', 'user'], editProperties: ['title', 'description'], @@ -131,6 +139,29 @@ const getAdminRouter = (options: Partial = {}) => { }, }, }, + { + resource: { model: getModelByName('Chapter', PrismaModule), client: prisma }, + options: { + navigation: "Projects", + listProperties: ['title', 'description', 'project', 'thumbnail', 'startTime', 'endTime'], + filterProperties: ['title', 'description', 'project'], + editProperties: ['title', 'description'], + actions: { + new: { + isAccessible: false, + isVisible: false, + }, + }, + properties: { + description: { + type: 'textarea', + props: { + rows: 10, + }, + }, + }, + }, + }, { resource: { model: getModelByName('Annotation', PrismaModule), client: prisma }, options: { @@ -177,12 +208,45 @@ const getAdminRouter = (options: Partial = {}) => { }, } }, - ], - ...options - }; - + { + resource: { model: getModelByName('Storage', PrismaModule), client: prisma }, + options: { + navigation: "Storage", + actions: { + new: { + isAccessible: false, + isVisible: false, + }, + edit: { + isAccessible: false, + isVisible: false, + } + }, + } + }, + { + resource: { model: getModelByName('QueueJob', PrismaModule), client: prisma }, + options: { + navigation: "Job", + actions: { + new: { + isAccessible: false, + isVisible: false, + }, + edit: { + isAccessible: false, + isVisible: false, + } + }, + }, - const admin = new AdminJS(adminOptions); + } + ], + dashboard: { + component: Components.Dashboard, + }, + componentLoader, + }); if (process.env.NODE_ENV === "developement") admin.watch() return AdminJSExpress.buildRouter(admin); diff --git a/apps/frontend/src/server/dashboard.tsx b/apps/frontend/src/server/dashboard.tsx new file mode 100644 index 00000000..748bd1b4 --- /dev/null +++ b/apps/frontend/src/server/dashboard.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { useEffect } from "react"; + +// just some regular React component +const DashboardComponent = () => { + useEffect(() => { + window.location.href = "/admin/resources/Project"; + }, []); + + return
Redirecting...
; +}; + +export default DashboardComponent; diff --git a/apps/frontend/src/server/main.ts b/apps/frontend/src/server/main.ts index c99d04d7..ce020ec4 100644 --- a/apps/frontend/src/server/main.ts +++ b/apps/frontend/src/server/main.ts @@ -9,6 +9,9 @@ import cors from 'cors'; const app = express(); import { emailQueue, chaptersQueue } from "@celluloid/queue"; +import getAdminRouter from "./admin"; +import { UserRole } from "@celluloid/prisma"; + const trpcApiEndpoint = '/api/trpc' declare module 'http' { @@ -54,9 +57,16 @@ app.use((req, res, next) => { // }); -app.get("/hello", (_, res) => { - res.send("Hello Vite + React + TypeScript!"); -}); +const adminRouter = await getAdminRouter(); + +const isAuthenticated = (req: express.Request, res: express.Response, next: express.NextFunction): void => { + // biome-ignore lint/correctness/noVoidTypeReturn: + if (req.user && (req.user as { role?: UserRole }).role === UserRole.Admin) return next(); + res.redirect("/"); +}; + +app.use("/admin", isAuthenticated, adminRouter); + app.use( trpcApiEndpoint, diff --git a/ecosystem.config.js b/ecosystem.config.js deleted file mode 100644 index bccf9eac..00000000 --- a/ecosystem.config.js +++ /dev/null @@ -1,33 +0,0 @@ -module.exports = { - apps: [ - { - name: "PrismaMigrate", - script: "yarn", - args: "prisma migrate:deploy", - interpreter: "sh", - autorestart: false, - watch: false, - }, - { - name: "frontend", - script: "yarn", - args: "frontend start", - interpreter: "sh", - watch: false, - }, - { - name: "admin", - script: "yarn", - args: "admin start", - interpreter: "sh", - watch: false, - }, - { - name: "backend", - script: "yarn", - args: "backend start", - interpreter: "sh", - watch: false, - }, - ], -}; diff --git a/package.json b/package.json index 008a1cad..4ec42a97 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "packages/*" ], "scripts": { - "dev": "dotenv -- turbo dev", + "dev": "dotenv -- turbo watch dev", "clean": "for package in $(ls packages); do (cd packages/${package} && yarn clean); done", "build": "dotenv -- turbo run build --no-cache", "eslint": "eslint --ext .js,.jsx,.ts,.tsx", diff --git a/packages/prisma/package.json b/packages/prisma/package.json index 12b5ff85..943b2e71 100644 --- a/packages/prisma/package.json +++ b/packages/prisma/package.json @@ -52,7 +52,7 @@ "@types/randomcolor": "^0.5.7", "dotenv-cli": "^7.3.0", "eslint": "^8.50.0", - "prisma": "^5.6.0", + "prisma": "^5.20.0", "rimraf": "^5.0.1", "tsup": "^8.3.0", "typescript": "^5.6.2" diff --git a/packages/prisma/src/index.ts b/packages/prisma/src/index.ts index 7f3afc31..b586e6ef 100644 --- a/packages/prisma/src/index.ts +++ b/packages/prisma/src/index.ts @@ -65,3 +65,4 @@ export const prisma = prismaClient; export * from "@prisma/client"; export default PrismaModule; + diff --git a/stack.yml b/stack.yml index 84d96b3b..b8b56a0f 100644 --- a/stack.yml +++ b/stack.yml @@ -18,10 +18,10 @@ services: POSTGRES_DB: celluloid celluloid: - image: celluloid-compact:latest + image: celluloid:latest # build: # context: . - # dockerfile: ./Dockerfile.compact + # dockerfile: ./apps/frontend/Dockerfile depends_on: - postgres ports: diff --git a/yarn.lock b/yarn.lock index 53baa176..fb3d27e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -52,7 +52,7 @@ __metadata: languageName: node linkType: hard -"@adminjs/express@npm:^6.0.1": +"@adminjs/express@npm:^6.1.0": version: 6.1.0 resolution: "@adminjs/express@npm:6.1.0" peerDependencies: @@ -80,7 +80,7 @@ __metadata: languageName: node linkType: hard -"@adminjs/prisma@npm:^5.0.1": +"@adminjs/prisma@npm:^5.0.3": version: 5.0.3 resolution: "@adminjs/prisma@npm:5.0.3" peerDependencies: @@ -1816,7 +1816,7 @@ __metadata: "@types/randomcolor": "npm:^0.5.7" dotenv-cli: "npm:^7.3.0" eslint: "npm:^8.50.0" - prisma: "npm:^5.6.0" + prisma: "npm:^5.20.0" randomcolor: "npm:^0.6.2" rimraf: "npm:^5.0.1" tsup: "npm:^8.3.0" @@ -6862,9 +6862,9 @@ __metadata: version: 0.0.0-use.local resolution: "admin@workspace:apps/admin" dependencies: - "@adminjs/express": "npm:^6.0.1" + "@adminjs/express": "npm:^6.1.0" "@adminjs/import-export": "npm:^3.0.0" - "@adminjs/prisma": "npm:^5.0.1" + "@adminjs/prisma": "npm:^5.0.3" "@adminjs/themes": "npm:^1.0.1" "@celluloid/config": "workspace:*" "@celluloid/passport": "workspace:*" @@ -6874,7 +6874,7 @@ __metadata: "@types/cors": "npm:^2.8.13" "@types/express": "npm:^4.17.17" "@types/node": "npm:^18.14.2" - adminjs: "npm:^7.2.2" + adminjs: "npm:^7.8.13" copyfiles: "npm:^2.4.1" cors: "npm:^2.8.5" del-cli: "npm:^5.0.0" @@ -6888,7 +6888,7 @@ __metadata: languageName: unknown linkType: soft -"adminjs@npm:^7.2.2": +"adminjs@npm:^7.8.13": version: 7.8.13 resolution: "adminjs@npm:7.8.13" dependencies: @@ -12416,6 +12416,9 @@ __metadata: version: 0.0.0-use.local resolution: "frontend@workspace:apps/frontend" dependencies: + "@adminjs/express": "npm:^6.1.0" + "@adminjs/prisma": "npm:^5.0.3" + "@adminjs/themes": "npm:^1.0.1" "@celluloid/prisma": "workspace:*" "@celluloid/queue": "workspace:*" "@celluloid/react-player": "npm:2.14.0" @@ -12461,6 +12464,7 @@ __metadata: "@typescript-eslint/eslint-plugin": "npm:^5.57.1" "@typescript-eslint/parser": "npm:^5.57.1" "@vitejs/plugin-react": "npm:^4.1.0" + adminjs: "npm:^7.8.13" change-case: "npm:^4.1.2" cookie-parser: "npm:^1.4.7" copy-to-clipboard: "npm:^3.3.3" @@ -20089,7 +20093,7 @@ __metadata: languageName: node linkType: hard -"prisma@npm:^5.6.0": +"prisma@npm:^5.20.0": version: 5.20.0 resolution: "prisma@npm:5.20.0" dependencies: