Explorar o código

feat: setup prisma and configure it for tests and development

Nicolas Meienberger %!s(int64=2) %!d(string=hai) anos
pai
achega
7c9bd4fab3

+ 13 - 10
Dockerfile

@@ -1,12 +1,11 @@
-FROM node:18-alpine3.16 AS builder
+ARG NODE_VERSION="18.12.1"
+ARG ALPINE_VERSION="3.16"
 
-# Required for argon2
-RUN apk --no-cache add g++
-RUN apk --no-cache add make
-RUN apk --no-cache add python3
+FROM node:${NODE_VERSION}-buster-slim AS builder
+
+RUN apt update
+RUN apt install -y openssl
 
-# Required for sharp
-RUN apk --no-cache add vips-dev=8.12.2-r5
 RUN npm install node-gyp -g
 
 WORKDIR /api
@@ -15,6 +14,7 @@ RUN npm i
 # ---
 WORKDIR /dashboard
 COPY ./packages/dashboard/package.json /dashboard/package.json
+COPY ./packages/dashboard/prisma/schema.prisma /dashboard/prisma/
 RUN npm i
 
 WORKDIR /api
@@ -25,7 +25,10 @@ WORKDIR /dashboard
 COPY ./packages/dashboard /dashboard
 RUN npm run build
 
-FROM node:18-alpine3.16 as app
+FROM node:${NODE_VERSION}-buster-slim as app
+
+RUN apt update
+RUN apt install -y openssl
 
 WORKDIR /
 
@@ -34,10 +37,10 @@ COPY ./packages/system-api/package.json /api/
 COPY --from=builder /api/dist /api/dist
 
 WORKDIR /dashboard
-COPY --from=builder /dashboard/next.config.js ./
+COPY --from=builder /dashboard/next.config.mjs ./
 COPY --from=builder /dashboard/public ./public
 COPY --from=builder /dashboard/package.json ./package.json
 COPY --from=builder --chown=node:node /dashboard/.next/standalone ./
 COPY --from=builder --chown=node:node /dashboard/.next/static ./.next/static
 
-WORKDIR /
+WORKDIR /

+ 11 - 6
Dockerfile.dev

@@ -1,16 +1,21 @@
-FROM node:18-alpine3.16
+ARG NODE_VERSION="18.12.1"
+ARG ALPINE_VERSION="3.16"
 
+FROM node:${NODE_VERSION}-buster-slim 
 WORKDIR /
 
-RUN apk --no-cache add g++ make
-RUN npm install node-gyp -g
+RUN apt update
+RUN apt install -y openssl
 
-WORKDIR /api
-COPY ./packages/system-api/package*.json /api/
-RUN npm install
+RUN npm install node-gyp -g
 
 WORKDIR /dashboard
 COPY ./packages/dashboard/package*.json /dashboard/
+COPY ./packages/dashboard/prisma/schema.prisma /dashboard/prisma/
+RUN npm install
+
+WORKDIR /api
+COPY ./packages/system-api/package*.json /api/
 RUN npm install
 
 COPY ./packages/system-api /api

+ 5 - 3
docker-compose.dev.yml

@@ -101,6 +101,8 @@ services:
     command: /bin/sh -c "cd /dashboard && npm run dev"
     container_name: dashboard
     depends_on:
+      tipi-db:
+        condition: service_healthy
       api:
         condition: service_started
     environment:
@@ -109,9 +111,9 @@ services:
       JWT_SECRET: ${JWT_SECRET}
       NGINX_PORT: ${NGINX_PORT}
       POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
-      POSTGRES_USERNAME: tipi
-      POSTGRES_DBNAME: tipi
-      POSTGRES_HOST: tipi-db
+      POSTGRES_USERNAME: ${POSTGRES_USERNAME}
+      POSTGRES_DBNAME: ${POSTGRES_DBNAME}
+      POSTGRES_HOST: ${POSTGRES_HOST}
       APPS_REPO_ID: ${APPS_REPO_ID}
       APPS_REPO_URL: ${APPS_REPO_URL}
       DOMAIN: ${DOMAIN}

+ 8 - 3
docker-compose.rc.yml

@@ -100,6 +100,8 @@ services:
     networks:
       - tipi_main_network
     depends_on:
+      tipi-db:
+        condition: service_healthy
       api:
         condition: service_started
     environment:
@@ -109,13 +111,16 @@ services:
       JWT_SECRET: ${JWT_SECRET}
       NGINX_PORT: ${NGINX_PORT}
       POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
-      POSTGRES_USERNAME: tipi
-      POSTGRES_DBNAME: tipi
-      POSTGRES_HOST: tipi-db
+      POSTGRES_USERNAME: ${POSTGRES_USERNAME}
+      POSTGRES_DBNAME: ${POSTGRES_DBNAME}
+      POSTGRES_HOST: ${POSTGRES_HOST}
       APPS_REPO_ID: ${APPS_REPO_ID}
       APPS_REPO_URL: ${APPS_REPO_URL}
       DOMAIN: ${DOMAIN}
       ARCHITECTURE: ${ARCHITECTURE}
+    volumes:
+      - ${PWD}/state:/runtipi/state
+      - ${PWD}/logs:/app/logs
     labels:
       traefik.enable: true
       # Web

+ 10 - 5
docker-compose.test.yml

@@ -93,7 +93,7 @@ services:
       traefik.http.routers.api-secure.middlewares: api-stripprefix
       traefik.http.services.api-secure.loadbalancer.server.port: 3001
       # Middlewares
-      traefik.http.middlewares.api-stripprefix.stripprefix.prefixes: /api
+      traefik.http.middlewares.api-stripprefix.stripprefix.prefixes: /api-legacy
 
   dashboard:
     build:
@@ -105,6 +105,8 @@ services:
     networks:
       - tipi_main_network
     depends_on:
+      tipi-db:
+        condition: service_healthy
       api:
         condition: service_started
     environment:
@@ -114,13 +116,16 @@ services:
       JWT_SECRET: ${JWT_SECRET}
       NGINX_PORT: ${NGINX_PORT}
       POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
-      POSTGRES_USERNAME: tipi
-      POSTGRES_DBNAME: tipi
-      POSTGRES_HOST: tipi-db
+      POSTGRES_USERNAME: ${POSTGRES_USERNAME}
+      POSTGRES_DBNAME: ${POSTGRES_DBNAME}
+      POSTGRES_HOST: ${POSTGRES_HOST}
       APPS_REPO_ID: ${APPS_REPO_ID}
       APPS_REPO_URL: ${APPS_REPO_URL}
       DOMAIN: ${DOMAIN}
       ARCHITECTURE: ${ARCHITECTURE}
+    volumes:
+      - ${PWD}/state:/runtipi/state
+      - ${PWD}/logs:/app/logs
     labels:
       traefik.enable: true
 
@@ -145,4 +150,4 @@ networks:
         - subnet: 10.21.21.0/24
 
 volumes:
-  pgdata:
+  pgdata:

+ 8 - 3
docker-compose.yml

@@ -101,6 +101,8 @@ services:
     networks:
       - tipi_main_network
     depends_on:
+      tipi-db:
+        condition: service_healthy
       api:
         condition: service_started
     environment:
@@ -110,13 +112,16 @@ services:
       JWT_SECRET: ${JWT_SECRET}
       NGINX_PORT: ${NGINX_PORT}
       POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
-      POSTGRES_USERNAME: tipi
-      POSTGRES_DBNAME: tipi
-      POSTGRES_HOST: tipi-db
+      POSTGRES_USERNAME: ${POSTGRES_USERNAME}
+      POSTGRES_DBNAME: ${POSTGRES_DBNAME}
+      POSTGRES_HOST: ${POSTGRES_HOST}
       APPS_REPO_ID: ${APPS_REPO_ID}
       APPS_REPO_URL: ${APPS_REPO_URL}
       DOMAIN: ${DOMAIN}
       ARCHITECTURE: ${ARCHITECTURE}
+    volumes:
+      - ${PWD}/state:/runtipi/state
+      - ${PWD}/logs:/app/logs
     labels:
       traefik.enable: true
       # Web

+ 18 - 0
packages/dashboard/.env.example

@@ -0,0 +1,18 @@
+# Only edit this file if you know what you are doing!
+# It will be overwritten on update.
+
+APPS_REPO_ID=7a92c8307e0a8074763c80be1fcfa4f87da6641daea9211aea6743b0116aba3b
+APPS_REPO_URL=https://github.com/meienberger/runtipi-appstore
+TZ=UTC
+INTERNAL_IP=localhost
+DNS_IP=9.9.9.9
+ARCHITECTURE=arm64
+TIPI_VERSION=0.8.0
+JWT_SECRET=secret
+ROOT_FOLDER_HOST=/Users/nicolas/Projects/runtipi
+NGINX_PORT=3000
+NGINX_PORT_SSL=443
+POSTGRES_PASSWORD=postgres
+DOMAIN=tipi.localhost
+STORAGE_PATH=/Users/nicolas/Projects/runtipi
+REDIS_HOST=tipi-redis

+ 7 - 0
packages/dashboard/.env.test

@@ -0,0 +1,7 @@
+POSTGRES_HOST=localhost
+POSTGRES_DBNAME=postgres
+POSTGRES_USERNAME=postgres
+POSTGRES_PASSWORD=postgres
+POSTGRES_PORT=5433
+
+DATABASE_URL="postgresql://postgres:postgres@localhost:5433/postgres"

+ 8 - 3
packages/dashboard/package.json

@@ -3,9 +3,11 @@
   "version": "0.8.1",
   "private": true,
   "scripts": {
-    "test": "jest --colors",
-    "test:client": "jest --colors --config=jest.config.client.js",
-    "test:server": "jest --colors --config=jest.config.server.js",
+    "migrate:postgres:test": "dotenv -e .env.test -- npx prisma migrate dev --name postgres-init",
+    "test": "npm run migrate:postgres:test && jest --colors",
+    "test:client": "jest --colors --selectProjects client --",
+    "test:server": "jest --colors --selectProjects server --",
+    "postinstall": "prisma generate",
     "dev": "next dev",
     "build": "next build",
     "start": "next start",
@@ -16,6 +18,7 @@
   "dependencies": {
     "@apollo/client": "^3.6.8",
     "@hookform/resolvers": "^2.9.10",
+    "@prisma/client": "^4.8.0",
     "@tabler/core": "1.0.0-beta16",
     "@tabler/icons": "^1.109.0",
     "@tanstack/react-query": "^4.20.4",
@@ -73,6 +76,7 @@
     "@types/validator": "^13.7.2",
     "@typescript-eslint/eslint-plugin": "^5.47.1",
     "@typescript-eslint/parser": "^5.47.1",
+    "dotenv-cli": "^6.0.0",
     "eslint": "8.30.0",
     "eslint-config-airbnb": "^19.0.4",
     "eslint-config-airbnb-typescript": "^17.0.0",
@@ -86,6 +90,7 @@
     "jest-environment-jsdom": "^29.3.1",
     "msw": "^0.49.2",
     "next-router-mock": "^0.8.0",
+    "prisma": "^4.8.0",
     "ts-jest": "^29.0.3",
     "ts-node": "^10.9.1",
     "typescript": "4.9.4",

+ 61 - 0
packages/dashboard/prisma/migrations/20221226205928_postgres_init/migration.sql

@@ -0,0 +1,61 @@
+-- CreateEnum
+CREATE TYPE "app_status_enum" AS ENUM ('running', 'stopped', 'installing', 'uninstalling', 'stopping', 'starting', 'missing', 'updating');
+
+-- CreateEnum
+CREATE TYPE "update_status_enum" AS ENUM ('FAILED', 'SUCCESS');
+
+-- CreateTable
+CREATE TABLE "app" (
+    "id" VARCHAR NOT NULL,
+    "status" "app_status_enum" NOT NULL DEFAULT 'stopped',
+    "lastOpened" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
+    "numOpened" INTEGER NOT NULL DEFAULT 0,
+    "config" JSONB NOT NULL,
+    "createdAt" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updatedAt" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "version" INTEGER NOT NULL DEFAULT 1,
+    "exposed" BOOLEAN NOT NULL DEFAULT false,
+    "domain" VARCHAR,
+
+    CONSTRAINT "PK_9478629fc093d229df09e560aea" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "migrations" (
+    "id" SERIAL NOT NULL,
+    "timestamp" BIGINT NOT NULL,
+    "name" VARCHAR NOT NULL,
+
+    CONSTRAINT "PK_8c82d7f526340ab734260ea46be" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "update" (
+    "id" SERIAL NOT NULL,
+    "name" VARCHAR NOT NULL,
+    "status" "update_status_enum" NOT NULL,
+    "createdAt" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updatedAt" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+    CONSTRAINT "PK_575f77a0576d6293bc1cb752847" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "user" (
+    "id" SERIAL NOT NULL,
+    "username" VARCHAR NOT NULL,
+    "password" VARCHAR NOT NULL,
+    "createdAt" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updatedAt" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+    CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "UQ_9478629fc093d229df09e560aea" ON "app"("id");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "UQ_6e7d7ecccdc972caa0ad33cb014" ON "update"("name");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "UQ_78a916df40e02a9deb1c4b75edb" ON "user"("username");

+ 3 - 0
packages/dashboard/prisma/migrations/migration_lock.toml

@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "postgresql"

+ 67 - 0
packages/dashboard/prisma/schema.prisma

@@ -0,0 +1,67 @@
+generator client {
+  provider = "prisma-client-js"
+}
+
+datasource db {
+  provider = "postgresql"
+  url      = env("DATABASE_URL")
+}
+
+model App {
+  id         String          @id(map: "PK_9478629fc093d229df09e560aea") @unique(map: "UQ_9478629fc093d229df09e560aea") @db.VarChar
+  status     app_status_enum @default(stopped)
+  lastOpened DateTime?       @default(now()) @db.Timestamptz(6)
+  numOpened  Int             @default(0)
+  config     Json
+  createdAt  DateTime        @default(now()) @db.Timestamp(6)
+  updatedAt  DateTime        @default(now()) @db.Timestamp(6)
+  version    Int             @default(1)
+  exposed    Boolean         @default(false)
+  domain     String?         @db.VarChar
+
+  @@map("app")
+}
+
+model Migrations {
+  id        Int    @id(map: "PK_8c82d7f526340ab734260ea46be") @default(autoincrement())
+  timestamp BigInt
+  name      String @db.VarChar
+
+  @@map("migrations")
+}
+
+model Update {
+  id        Int                @id(map: "PK_575f77a0576d6293bc1cb752847") @default(autoincrement())
+  name      String             @unique(map: "UQ_6e7d7ecccdc972caa0ad33cb014") @db.VarChar
+  status    update_status_enum
+  createdAt DateTime           @default(now()) @db.Timestamp(6)
+  updatedAt DateTime           @default(now()) @db.Timestamp(6)
+
+  @@map("update")
+}
+
+model User {
+  id        Int      @id(map: "PK_cace4a159ff9f2512dd42373760") @default(autoincrement())
+  username  String   @unique(map: "UQ_78a916df40e02a9deb1c4b75edb") @db.VarChar
+  password  String   @db.VarChar
+  createdAt DateTime @default(now()) @db.Timestamp(6)
+  updatedAt DateTime @default(now()) @db.Timestamp(6)
+
+  @@map("user")
+}
+
+enum app_status_enum {
+  running
+  stopped
+  installing
+  uninstalling
+  stopping
+  starting
+  missing
+  updating
+}
+
+enum update_status_enum {
+  FAILED
+  SUCCESS
+}

+ 2 - 0
packages/dashboard/src/server/context.ts

@@ -1,6 +1,7 @@
 import { inferAsyncReturnType } from '@trpc/server';
 import { CreateNextContextOptions } from '@trpc/server/adapters/next';
 import { getServerAuthSession } from './common/get-server-auth-session';
+import { prisma } from './db/client';
 
 type Session = {
   userId?: number;
@@ -18,6 +19,7 @@ type CreateContextOptions = {
  * */
 export const createContextInner = async (opts: CreateContextOptions) => ({
   session: opts.session,
+  prisma,
 });
 
 /**

+ 6 - 6
packages/dashboard/src/server/core/Logger/Logger.ts

@@ -1,7 +1,6 @@
 import fs from 'fs-extra';
 import path from 'path';
 import { createLogger, format, transports } from 'winston';
-import { getConfig } from '../TipiConfig';
 
 const { align, printf, timestamp, combine, colorize } = format;
 
@@ -23,8 +22,9 @@ const combinedLogFormatDev = combine(
 );
 
 const productionLogger = () => {
-  if (!fs.existsSync(getConfig().logs.LOGS_FOLDER)) {
-    fs.mkdirSync(getConfig().logs.LOGS_FOLDER);
+  const logsFolder = '/app/logs';
+  if (!fs.existsSync(logsFolder)) {
+    fs.mkdirSync(logsFolder);
   }
   return createLogger({
     level: 'info',
@@ -35,14 +35,14 @@ const productionLogger = () => {
       // - Write all logs error (and below) to `error.log`.
       //
       new transports.File({
-        filename: path.join(getConfig().logs.LOGS_FOLDER, getConfig().logs.LOGS_ERROR),
+        filename: path.join(logsFolder, 'error.log'),
         level: 'error',
       }),
       new transports.File({
-        filename: path.join(getConfig().logs.LOGS_FOLDER, getConfig().logs.LOGS_APP),
+        filename: path.join(logsFolder, 'app.log'),
       }),
     ],
-    exceptionHandlers: [new transports.File({ filename: path.join(getConfig().logs.LOGS_FOLDER, getConfig().logs.LOGS_ERROR) })],
+    exceptionHandlers: [new transports.File({ filename: path.join(logsFolder, 'error.log') })],
   });
 };
 

+ 0 - 3
packages/dashboard/src/server/core/TipiConfig/TipiConfig.test.ts

@@ -18,9 +18,6 @@ describe('Test: getConfig', () => {
 
     expect(config).toBeDefined();
     expect(config.NODE_ENV).toBe('test');
-    expect(config.logs.LOGS_FOLDER).toBe('/app/logs');
-    expect(config.logs.LOGS_APP).toBe('app.log');
-    expect(config.logs.LOGS_ERROR).toBe('error.log');
     expect(config.dnsIp).toBe('9.9.9.9');
     expect(config.rootFolder).toBe('/runtipi');
     expect(config.internalIp).toBe('localhost');

+ 24 - 21
packages/dashboard/src/server/core/TipiConfig/TipiConfig.ts

@@ -12,9 +12,6 @@ enum AppSupportedArchitecturesEnum {
 
 const { serverRuntimeConfig } = nextConfig();
 const {
-  LOGS_FOLDER = '/app/logs',
-  LOGS_APP = 'app.log',
-  LOGS_ERROR = 'error.log',
   NODE_ENV,
   JWT_SECRET,
   INTERNAL_IP,
@@ -25,6 +22,11 @@ const {
   REDIS_HOST,
   STORAGE_PATH = '/runtipi',
   ARCHITECTURE = 'amd64',
+  POSTGRES_HOST,
+  POSTGRES_DBNAME,
+  POSTGRES_USERNAME,
+  POSTGRES_PASSWORD,
+  POSTGRES_PORT = 5432,
 } = serverRuntimeConfig;
 
 const configSchema = z.object({
@@ -32,11 +34,6 @@ const configSchema = z.object({
   REDIS_HOST: z.string(),
   status: z.union([z.literal('RUNNING'), z.literal('UPDATING'), z.literal('RESTARTING')]),
   architecture: z.nativeEnum(AppSupportedArchitecturesEnum),
-  logs: z.object({
-    LOGS_FOLDER: z.string(),
-    LOGS_APP: z.string(),
-    LOGS_ERROR: z.string(),
-  }),
   dnsIp: z.string(),
   rootFolder: z.string(),
   internalIp: z.string(),
@@ -46,14 +43,16 @@ const configSchema = z.object({
   appsRepoUrl: z.string(),
   domain: z.string(),
   storagePath: z.string(),
+  postgresHost: z.string(),
+  postgresDatabase: z.string(),
+  postgresUsername: z.string(),
+  postgresPassword: z.string(),
+  postgresPort: z.number(),
 });
 
-export const formatErrors = (errors: z.ZodFormattedError<Map<string, string>, string>) =>
-  Object.entries(errors)
-    .map(([name, value]) => {
-      if (value && '_errors' in value) return `${name}: ${value._errors.join(', ')}\n`;
-      return null;
-    })
+export const formatErrors = (errors: { fieldErrors: Record<string, string[]> }) =>
+  Object.entries(errors.fieldErrors)
+    .map(([name, value]) => `${name}: ${value[0]}`)
     .filter(Boolean)
     .join('\n');
 
@@ -64,11 +63,11 @@ export class TipiConfig {
 
   constructor() {
     const envConfig: z.infer<typeof configSchema> = {
-      logs: {
-        LOGS_FOLDER,
-        LOGS_APP,
-        LOGS_ERROR,
-      },
+      postgresHost: POSTGRES_HOST,
+      postgresDatabase: POSTGRES_DBNAME,
+      postgresUsername: POSTGRES_USERNAME,
+      postgresPassword: POSTGRES_PASSWORD,
+      postgresPort: Number(POSTGRES_PORT),
       REDIS_HOST,
       NODE_ENV,
       architecture: ARCHITECTURE as z.infer<typeof configSchema>['architecture'],
@@ -92,11 +91,15 @@ export class TipiConfig {
       if (parsedConfig.success) {
         this.config = parsedConfig.data;
       } else {
-        Logger.error(`❌ Invalid env config\n${formatErrors(parsedConfig.error.format())}`);
+        const errors = formatErrors(parsedConfig.error.flatten());
+        console.error(`❌ Invalid env config\n${errors}`);
+        Logger.error(`❌ Invalid env config\n\n${errors}`);
         throw new Error('Invalid env config');
       }
     } else {
-      Logger.error(`❌ Invalid settings.json file:\n${formatErrors(parsedFileConfig.error.format())}`);
+      const errors = formatErrors(parsedFileConfig.error.flatten());
+      console.error(`❌ Invalid settings.json file:\n${errors}`);
+      Logger.error(`❌ Invalid settings.json file:\n${errors}`);
       throw new Error('Invalid settings.json file');
     }
   }

+ 25 - 0
packages/dashboard/src/server/db/client.ts

@@ -0,0 +1,25 @@
+/* eslint-disable vars-on-top */
+import { PrismaClient } from '@prisma/client';
+
+import { getConfig } from '../core/TipiConfig/TipiConfig';
+
+declare global {
+  // eslint-disable-next-line no-var
+  var prisma: PrismaClient | undefined;
+}
+
+export const prisma =
+  global.prisma ||
+  new PrismaClient({
+    log: getConfig().NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
+    datasources: {
+      db: {
+        url: `postgresql://${getConfig().postgresUsername}:${getConfig().postgresPassword}@${getConfig().postgresHost}:${getConfig().postgresPort}/${getConfig().postgresDatabase}`,
+        // url: `postgresql://${getConfig().postgresUsername}:${getConfig().postgresPassword}@${getConfig().postgresHost}:${getConfig().postgresPort}/${getConfig().postgresDatabase}`,
+      },
+    },
+  });
+
+if (getConfig().NODE_ENV !== 'production') {
+  global.prisma = prisma;
+}

+ 2 - 0
packages/dashboard/tests/server/jest.setup.ts

@@ -1,6 +1,8 @@
 import { EventDispatcher } from '../../src/server/core/EventDispatcher';
 
 global.fetch = jest.fn();
+console.error = jest.fn();
+
 // Mock Logger
 jest.mock('../../src/server/core/Logger', () => ({
   Logger: {

+ 2 - 1
packages/dashboard/tsconfig.json

@@ -24,7 +24,8 @@
     "types": [
       "jest",
       "@testing-library/jest-dom"
-    ]
+    ],
+    "experimentalDecorators": true,
   },
   "include": [
     "next-env.d.ts",

+ 128 - 77
pnpm-lock.yaml

@@ -26,6 +26,7 @@ importers:
       '@graphql-codegen/typescript-operations': ^2.4.2
       '@graphql-codegen/typescript-react-apollo': ^3.2.16
       '@hookform/resolvers': ^2.9.10
+      '@prisma/client': ^4.8.0
       '@tabler/core': 1.0.0-beta16
       '@tabler/icons': ^1.109.0
       '@tanstack/react-query': ^4.20.4
@@ -50,6 +51,7 @@ importers:
       '@typescript-eslint/eslint-plugin': ^5.47.1
       '@typescript-eslint/parser': ^5.47.1
       clsx: ^1.1.1
+      dotenv-cli: ^6.0.0
       eslint: 8.30.0
       eslint-config-airbnb: ^19.0.4
       eslint-config-airbnb-typescript: ^17.0.0
@@ -69,6 +71,7 @@ importers:
       msw: ^0.49.2
       next: 13.1.1
       next-router-mock: ^0.8.0
+      prisma: ^4.8.0
       react: 18.2.0
       react-dom: 18.2.0
       react-hook-form: ^7.38.0
@@ -95,6 +98,7 @@ importers:
     dependencies:
       '@apollo/client': 3.6.8_o264z5epwuajru7y4dsijkqr44
       '@hookform/resolvers': 2.9.10_react-hook-form@7.40.0
+      '@prisma/client': 4.8.0_prisma@4.8.0
       '@tabler/core': 1.0.0-beta16_biqbaboplfbrettd7655fr4n2y
       '@tabler/icons': 1.116.1_biqbaboplfbrettd7655fr4n2y
       '@tanstack/react-query': 4.20.4_biqbaboplfbrettd7655fr4n2y
@@ -151,6 +155,7 @@ importers:
       '@types/validator': 13.7.2
       '@typescript-eslint/eslint-plugin': 5.47.1_txmweb6yn7coi7nfrp22gpyqmy
       '@typescript-eslint/parser': 5.47.1_lzzuuodtsqwxnvqeq4g4likcqa
+      dotenv-cli: 6.0.0
       eslint: 8.30.0
       eslint-config-airbnb: 19.0.4_j3uyvjk2vb2gkfzhvqukeu5rlq
       eslint-config-airbnb-typescript: 17.0.0_qipeoi3mvzxgzndpeo4r6kwevy
@@ -164,6 +169,7 @@ importers:
       jest-environment-jsdom: 29.3.1
       msw: 0.49.2_typescript@4.9.4
       next-router-mock: 0.8.0_next@13.1.1+react@18.2.0
+      prisma: 4.8.0
       ts-jest: 29.0.3_fckurgq3dkz2wyxkuqw26iqkyi
       ts-node: 10.9.1_awa2wsr5thmg3i7jqycphctjfq
       typescript: 4.9.4
@@ -294,6 +300,7 @@ packages:
     dependencies:
       '@jridgewell/gen-mapping': 0.1.1
       '@jridgewell/trace-mapping': 0.3.10
+    dev: true
 
   /@apollo/client/3.6.8_o264z5epwuajru7y4dsijkqr44:
     resolution: {integrity: sha512-p/J6KRHZZPGX0bZtMLvRFAIcReYsRYGg+Jz9MkgabWPy0L8rwgyolq9fvKsNqkH888Tj9Yvwrxz9V84KfcORJA==}
@@ -491,6 +498,7 @@ packages:
   /@babel/compat-data/7.17.10:
     resolution: {integrity: sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==}
     engines: {node: '>=6.9.0'}
+    dev: true
 
   /@babel/core/7.17.10:
     resolution: {integrity: sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==}
@@ -513,6 +521,7 @@ packages:
       semver: 6.3.0
     transitivePeerDependencies:
       - supports-color
+    dev: true
 
   /@babel/generator/7.17.10:
     resolution: {integrity: sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==}
@@ -521,6 +530,7 @@ packages:
       '@babel/types': 7.17.10
       '@jridgewell/gen-mapping': 0.1.1
       jsesc: 2.5.2
+    dev: true
 
   /@babel/generator/7.18.2:
     resolution: {integrity: sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==}
@@ -549,6 +559,7 @@ packages:
       '@babel/helper-validator-option': 7.16.7
       browserslist: 4.20.3
       semver: 6.3.0
+    dev: true
 
   /@babel/helper-create-class-features-plugin/7.18.0_@babel+core@7.17.10:
     resolution: {integrity: sha512-Kh8zTGR9de3J63e5nS0rQUdRs/kbtwoeQQ0sriS0lItjC96u8XXZN6lKpuyWd2coKSU13py/y+LTmThLuVX0Pg==}
@@ -573,6 +584,7 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/types': 7.17.10
+    dev: true
 
   /@babel/helper-environment-visitor/7.18.2:
     resolution: {integrity: sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==}
@@ -585,12 +597,14 @@ packages:
     dependencies:
       '@babel/template': 7.16.7
       '@babel/types': 7.17.10
+    dev: true
 
   /@babel/helper-hoist-variables/7.16.7:
     resolution: {integrity: sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==}
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/types': 7.17.10
+    dev: true
 
   /@babel/helper-member-expression-to-functions/7.17.7:
     resolution: {integrity: sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==}
@@ -619,6 +633,7 @@ packages:
       '@babel/types': 7.17.10
     transitivePeerDependencies:
       - supports-color
+    dev: true
 
   /@babel/helper-module-transforms/7.18.0:
     resolution: {integrity: sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==}
@@ -670,6 +685,7 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/types': 7.17.10
+    dev: true
 
   /@babel/helper-simple-access/7.18.2:
     resolution: {integrity: sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==}
@@ -690,6 +706,7 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/types': 7.17.10
+    dev: true
 
   /@babel/helper-validator-identifier/7.16.7:
     resolution: {integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==}
@@ -698,6 +715,7 @@ packages:
   /@babel/helper-validator-option/7.16.7:
     resolution: {integrity: sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==}
     engines: {node: '>=6.9.0'}
+    dev: true
 
   /@babel/helpers/7.17.9:
     resolution: {integrity: sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==}
@@ -708,6 +726,7 @@ packages:
       '@babel/types': 7.17.10
     transitivePeerDependencies:
       - supports-color
+    dev: true
 
   /@babel/highlight/7.17.9:
     resolution: {integrity: sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==}
@@ -723,6 +742,7 @@ packages:
     hasBin: true
     dependencies:
       '@babel/types': 7.17.10
+    dev: true
 
   /@babel/parser/7.18.5:
     resolution: {integrity: sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw==}
@@ -1153,6 +1173,7 @@ packages:
       '@babel/code-frame': 7.16.7
       '@babel/parser': 7.17.10
       '@babel/types': 7.17.10
+    dev: true
 
   /@babel/traverse/7.17.10:
     resolution: {integrity: sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==}
@@ -1170,6 +1191,7 @@ packages:
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
+    dev: true
 
   /@babel/traverse/7.18.5:
     resolution: {integrity: sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA==}
@@ -1398,6 +1420,7 @@ packages:
     engines: {node: '>=12'}
     dependencies:
       '@jridgewell/trace-mapping': 0.3.9
+    dev: true
 
   /@dabh/diagnostics/2.0.3:
     resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
@@ -2728,6 +2751,7 @@ packages:
     dependencies:
       '@jridgewell/set-array': 1.1.1
       '@jridgewell/sourcemap-codec': 1.4.13
+    dev: true
 
   /@jridgewell/gen-mapping/0.3.1:
     resolution: {integrity: sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==}
@@ -2741,26 +2765,32 @@ packages:
   /@jridgewell/resolve-uri/3.0.7:
     resolution: {integrity: sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==}
     engines: {node: '>=6.0.0'}
+    dev: true
 
   /@jridgewell/resolve-uri/3.1.0:
     resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
     engines: {node: '>=6.0.0'}
+    dev: true
 
   /@jridgewell/set-array/1.1.1:
     resolution: {integrity: sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==}
     engines: {node: '>=6.0.0'}
+    dev: true
 
   /@jridgewell/sourcemap-codec/1.4.13:
     resolution: {integrity: sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==}
+    dev: true
 
   /@jridgewell/sourcemap-codec/1.4.14:
     resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
+    dev: true
 
   /@jridgewell/trace-mapping/0.3.10:
     resolution: {integrity: sha512-Q0YbBd6OTsXm8Y21+YUSDXupHnodNC2M4O18jtd3iwJ3+vMZNdKGols0a9G6JOK0dcJ3IdUUHoh908ZI6qhk8Q==}
     dependencies:
       '@jridgewell/resolve-uri': 3.0.7
       '@jridgewell/sourcemap-codec': 1.4.13
+    dev: true
 
   /@jridgewell/trace-mapping/0.3.17:
     resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
@@ -2774,6 +2804,7 @@ packages:
     dependencies:
       '@jridgewell/resolve-uri': 3.1.0
       '@jridgewell/sourcemap-codec': 1.4.14
+    dev: true
 
   /@mapbox/node-pre-gyp/1.0.9:
     resolution: {integrity: sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==}
@@ -2827,6 +2858,7 @@ packages:
 
   /@next/env/13.1.1:
     resolution: {integrity: sha512-vFMyXtPjSAiOXOywMojxfKIqE3VWN5RCAx+tT3AS3pcKjMLFTCJFUWsKv8hC+87Z1F4W3r68qTwDFZIFmd5Xkw==}
+    dev: false
 
   /@next/eslint-plugin-next/13.1.1:
     resolution: {integrity: sha512-SBrOFS8PC3nQ5aeZmawJkjKkWjwK9RoxvBSv/86nZp0ubdoVQoko8r8htALd9ufp16NhacCdqhu9bzZLDWtALQ==}
@@ -2840,6 +2872,7 @@ packages:
     cpu: [arm]
     os: [android]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@next/swc-android-arm64/13.1.1:
@@ -2848,6 +2881,7 @@ packages:
     cpu: [arm64]
     os: [android]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@next/swc-darwin-arm64/13.1.1:
@@ -2856,6 +2890,7 @@ packages:
     cpu: [arm64]
     os: [darwin]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@next/swc-darwin-x64/13.1.1:
@@ -2864,6 +2899,7 @@ packages:
     cpu: [x64]
     os: [darwin]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@next/swc-freebsd-x64/13.1.1:
@@ -2872,6 +2908,7 @@ packages:
     cpu: [x64]
     os: [freebsd]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@next/swc-linux-arm-gnueabihf/13.1.1:
@@ -2880,6 +2917,7 @@ packages:
     cpu: [arm]
     os: [linux]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@next/swc-linux-arm64-gnu/13.1.1:
@@ -2888,6 +2926,7 @@ packages:
     cpu: [arm64]
     os: [linux]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@next/swc-linux-arm64-musl/13.1.1:
@@ -2896,6 +2935,7 @@ packages:
     cpu: [arm64]
     os: [linux]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@next/swc-linux-x64-gnu/13.1.1:
@@ -2904,6 +2944,7 @@ packages:
     cpu: [x64]
     os: [linux]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@next/swc-linux-x64-musl/13.1.1:
@@ -2912,6 +2953,7 @@ packages:
     cpu: [x64]
     os: [linux]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@next/swc-win32-arm64-msvc/13.1.1:
@@ -2920,6 +2962,7 @@ packages:
     cpu: [arm64]
     os: [win32]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@next/swc-win32-ia32-msvc/13.1.1:
@@ -2928,6 +2971,7 @@ packages:
     cpu: [ia32]
     os: [win32]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@next/swc-win32-x64-msvc/13.1.1:
@@ -2936,6 +2980,7 @@ packages:
     cpu: [x64]
     os: [win32]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@nodelib/fs.scandir/2.1.5:
@@ -2984,6 +3029,29 @@ packages:
     resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
     dev: false
 
+  /@prisma/client/4.8.0_prisma@4.8.0:
+    resolution: {integrity: sha512-Y1riB0p2W52kh3zgssP/YAhln3RjBFcJy3uwEiyjmU+TQYh6QTZDRFBo3JtBWuq2FyMOl1Rye8jxzUP+n0l5Cg==}
+    engines: {node: '>=14.17'}
+    requiresBuild: true
+    peerDependencies:
+      prisma: '*'
+    peerDependenciesMeta:
+      prisma:
+        optional: true
+    dependencies:
+      '@prisma/engines-version': 4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe
+      prisma: 4.8.0
+    dev: false
+
+  /@prisma/engines-version/4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe:
+    resolution: {integrity: sha512-MHSOSexomRMom8QN4t7bu87wPPD+pa+hW9+71JnVcF3DqyyO/ycCLhRL1we3EojRpZxKvuyGho2REQsMCvxcJw==}
+    dev: false
+
+  /@prisma/engines/4.8.0:
+    resolution: {integrity: sha512-A1Asn2rxZMlLAj1HTyfaCv0VQrLUv034jVay05QlqZg1qiHPeA3/pGTfNMijbsMYCsGVxfWEJuaZZuNxXGMCrA==}
+    requiresBuild: true
+    dev: true
+
   /@protobufjs/aspromise/1.1.2:
     resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
     dev: false
@@ -3131,6 +3199,7 @@ packages:
     resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==}
     dependencies:
       tslib: 2.4.0
+    dev: false
 
   /@szmarczak/http-timer/1.1.2:
     resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==}
@@ -3343,15 +3412,19 @@ packages:
 
   /@tsconfig/node10/1.0.9:
     resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
+    dev: true
 
   /@tsconfig/node12/1.0.11:
     resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
+    dev: true
 
   /@tsconfig/node14/1.0.3:
     resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
+    dev: true
 
   /@tsconfig/node16/1.0.3:
     resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
+    dev: true
 
   /@types/accepts/1.3.5:
     resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==}
@@ -3769,26 +3842,6 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/parser/5.22.0_ftmxxwdguf2d7rwvxvrtykx44u:
-    resolution: {integrity: sha512-piwC4krUpRDqPaPbFaycN70KCP87+PC5WZmrWs+DlVOxxmF+zI6b6hETv7Quy4s9wbkV16ikMeZgXsvzwI3icQ==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    peerDependencies:
-      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-    dependencies:
-      '@typescript-eslint/scope-manager': 5.22.0
-      '@typescript-eslint/types': 5.22.0
-      '@typescript-eslint/typescript-estree': 5.22.0_typescript@4.9.4
-      debug: 4.3.4
-      eslint: 8.12.0
-      typescript: 4.9.4
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /@typescript-eslint/parser/5.22.0_hcfsmds2fshutdssjqluwm76uu:
     resolution: {integrity: sha512-piwC4krUpRDqPaPbFaycN70KCP87+PC5WZmrWs+DlVOxxmF+zI6b6hETv7Quy4s9wbkV16ikMeZgXsvzwI3icQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3929,7 +3982,7 @@ packages:
       debug: 4.3.4
       globby: 11.1.0
       is-glob: 4.0.3
-      semver: 7.3.8
+      semver: 7.3.7
       tsutils: 3.21.0_typescript@4.9.4
       typescript: 4.9.4
     transitivePeerDependencies:
@@ -4116,6 +4169,7 @@ packages:
   /acorn-walk/8.2.0:
     resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
     engines: {node: '>=0.4.0'}
+    dev: true
 
   /acorn/8.7.1:
     resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==}
@@ -4126,6 +4180,7 @@ packages:
     resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==}
     engines: {node: '>=0.4.0'}
     hasBin: true
+    dev: true
 
   /agent-base/6.0.2:
     resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
@@ -4392,6 +4447,7 @@ packages:
 
   /arg/4.1.3:
     resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
+    dev: true
 
   /argon2/0.29.1:
     resolution: {integrity: sha512-bWXzAsQA0B6EFWZh5li+YBk+muoknAb8KacAi1h/bC6Gigy9p5ANbrPvpnjTIb7i9I11/8Df6FeSxpJDK3vy4g==}
@@ -4811,6 +4867,7 @@ packages:
       escalade: 3.1.1
       node-releases: 2.0.4
       picocolors: 1.0.0
+    dev: true
 
   /bs-logger/0.2.6:
     resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==}
@@ -5124,6 +5181,7 @@ packages:
 
   /client-only/0.0.1:
     resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+    dev: false
 
   /cliui/6.0.0:
     resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
@@ -5361,8 +5419,8 @@ packages:
     engines: {node: '>=10'}
     hasBin: true
     dependencies:
-      JSONStream: 1.3.5
       is-text-path: 1.0.1
+      JSONStream: 1.3.5
       lodash: 4.17.21
       meow: 8.1.2
       split2: 3.2.2
@@ -5447,6 +5505,7 @@ packages:
 
   /create-require/1.1.1:
     resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+    dev: true
 
   /cross-fetch/3.1.5:
     resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==}
@@ -5769,6 +5828,7 @@ packages:
   /diff/4.0.2:
     resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
     engines: {node: '>=0.3.1'}
+    dev: true
 
   /diff/5.1.0:
     resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
@@ -5828,6 +5888,21 @@ packages:
       is-obj: 2.0.0
     dev: true
 
+  /dotenv-cli/6.0.0:
+    resolution: {integrity: sha512-qXlCOi3UMDhCWFKe0yq5sg3X+pJAz+RQDiFN38AMSbUrnY3uZshSfDJUAge951OS7J9gwLZGfsBlWRSOYz/TRg==}
+    hasBin: true
+    dependencies:
+      cross-spawn: 7.0.3
+      dotenv: 16.0.0
+      dotenv-expand: 8.0.3
+      minimist: 1.2.6
+    dev: true
+
+  /dotenv-expand/8.0.3:
+    resolution: {integrity: sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==}
+    engines: {node: '>=12'}
+    dev: true
+
   /dotenv/16.0.0:
     resolution: {integrity: sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==}
     engines: {node: '>=12'}
@@ -5852,6 +5927,7 @@ packages:
 
   /electron-to-chromium/1.4.136:
     resolution: {integrity: sha512-GnITX8rHnUrIVnTxU9UlsTnSemHUA2iF+6QrRqxFbp/mf0vfuSc/goEyyQhUX3TUUCE3mv/4BNuXOtaJ4ur0eA==}
+    dev: true
 
   /elegant-spinner/1.0.1:
     resolution: {integrity: sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==}
@@ -6255,7 +6331,7 @@ packages:
       eslint-import-resolver-webpack:
         optional: true
     dependencies:
-      '@typescript-eslint/parser': 5.22.0_ftmxxwdguf2d7rwvxvrtykx44u
+      '@typescript-eslint/parser': 5.22.0_hcfsmds2fshutdssjqluwm76uu
       debug: 3.2.7
       eslint-import-resolver-node: 0.3.6
       find-up: 2.1.0
@@ -6493,16 +6569,6 @@ packages:
       estraverse: 5.3.0
     dev: true
 
-  /eslint-utils/3.0.0_eslint@8.12.0:
-    resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
-    engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
-    peerDependencies:
-      eslint: '>=5'
-    dependencies:
-      eslint: 8.12.0
-      eslint-visitor-keys: 2.1.0
-    dev: true
-
   /eslint-utils/3.0.0_eslint@8.15.0:
     resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
     engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
@@ -6533,50 +6599,6 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
-  /eslint/8.12.0:
-    resolution: {integrity: sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    hasBin: true
-    dependencies:
-      '@eslint/eslintrc': 1.2.3
-      '@humanwhocodes/config-array': 0.9.5
-      ajv: 6.12.6
-      chalk: 4.1.2
-      cross-spawn: 7.0.3
-      debug: 4.3.4
-      doctrine: 3.0.0
-      escape-string-regexp: 4.0.0
-      eslint-scope: 7.1.1
-      eslint-utils: 3.0.0_eslint@8.12.0
-      eslint-visitor-keys: 3.3.0
-      espree: 9.3.2
-      esquery: 1.4.0
-      esutils: 2.0.3
-      fast-deep-equal: 3.1.3
-      file-entry-cache: 6.0.1
-      functional-red-black-tree: 1.0.1
-      glob-parent: 6.0.2
-      globals: 13.13.0
-      ignore: 5.2.0
-      import-fresh: 3.3.0
-      imurmurhash: 0.1.4
-      is-glob: 4.0.3
-      js-yaml: 4.1.0
-      json-stable-stringify-without-jsonify: 1.0.1
-      levn: 0.4.1
-      lodash.merge: 4.6.2
-      minimatch: 3.1.2
-      natural-compare: 1.4.0
-      optionator: 0.9.1
-      regexpp: 3.2.0
-      strip-ansi: 6.0.1
-      strip-json-comments: 3.1.1
-      text-table: 0.2.0
-      v8-compile-cache: 2.3.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /eslint/8.15.0:
     resolution: {integrity: sha512-GG5USZ1jhCu8HJkzGgeK8/+RGnHaNYZGrGDzUtigK3BsGESW/rs2az23XqE0WVwDxy1VRvvjSSGu5nB0Bu+6SA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -7172,6 +7194,7 @@ packages:
   /gensync/1.0.0-beta.2:
     resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
     engines: {node: '>=6.9.0'}
+    dev: true
 
   /get-caller-file/2.0.5:
     resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
@@ -7325,6 +7348,7 @@ packages:
   /globals/11.12.0:
     resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
     engines: {node: '>=4'}
+    dev: true
 
   /globals/13.13.0:
     resolution: {integrity: sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==}
@@ -7693,6 +7717,7 @@ packages:
 
   /immutable/4.1.0:
     resolution: {integrity: sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==}
+    dev: false
 
   /import-fresh/3.3.0:
     resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
@@ -9165,6 +9190,7 @@ packages:
     resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
     engines: {node: '>=4'}
     hasBin: true
+    dev: true
 
   /json-buffer/3.0.0:
     resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==}
@@ -9210,6 +9236,7 @@ packages:
     resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==}
     engines: {node: '>=6'}
     hasBin: true
+    dev: true
 
   /jsonfile/6.1.0:
     resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
@@ -9570,6 +9597,7 @@ packages:
 
   /make-error/1.3.6:
     resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+    dev: true
 
   /makeerror/1.0.12:
     resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
@@ -10332,6 +10360,7 @@ packages:
     resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
+    dev: false
 
   /napi-build-utils/1.0.2:
     resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
@@ -10403,6 +10432,7 @@ packages:
     transitivePeerDependencies:
       - '@babel/core'
       - babel-plugin-macros
+    dev: false
 
   /no-case/3.0.4:
     resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
@@ -10449,6 +10479,7 @@ packages:
 
   /node-releases/2.0.4:
     resolution: {integrity: sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==}
+    dev: true
 
   /nodemon/2.0.16:
     resolution: {integrity: sha512-zsrcaOfTWRuUzBn3P44RDliLlp263Z/76FPoHFr3cFFkOz0lTPAcIw8dCzfdVIx/t3AtDYCZRCDkoCojJqaG3w==}
@@ -11028,6 +11059,7 @@ packages:
       nanoid: 3.3.4
       picocolors: 1.0.0
       source-map-js: 1.0.2
+    dev: false
 
   /postgres-array/2.0.0:
     resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
@@ -11122,6 +11154,15 @@ packages:
       react-is: 18.1.0
     dev: true
 
+  /prisma/4.8.0:
+    resolution: {integrity: sha512-DWIhxvxt8f4h6MDd35mz7BJff+fu7HItW3WPDIEpCR3RzcOWyiHBbLQW5/DOgmf+pRLTjwXQob7kuTZVYUAw5w==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      '@prisma/engines': 4.8.0
+    dev: true
+
   /promise/7.3.1:
     resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
     dependencies:
@@ -11238,6 +11279,7 @@ packages:
       loose-envify: 1.4.0
       react: 18.2.0
       scheduler: 0.23.0
+    dev: false
 
   /react-hook-form/7.40.0_react@18.2.0:
     resolution: {integrity: sha512-0rokdxMPJs0k9bvFtY6dbcSydyNhnZNXCR49jgDr/aR03FDHFOK6gfh8ccqB3fl696Mk7lqh04xdm+agqWXKSw==}
@@ -11347,6 +11389,7 @@ packages:
     engines: {node: '>=0.10.0'}
     dependencies:
       loose-envify: 1.4.0
+    dev: false
 
   /read-pkg-up/7.0.1:
     resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}
@@ -11676,6 +11719,7 @@ packages:
       chokidar: 3.5.3
       immutable: 4.1.0
       source-map-js: 1.0.2
+    dev: false
 
   /sax/1.2.4:
     resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
@@ -11692,6 +11736,7 @@ packages:
     resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
     dependencies:
       loose-envify: 1.4.0
+    dev: false
 
   /scuid/1.1.0:
     resolution: {integrity: sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg==}
@@ -11878,6 +11923,7 @@ packages:
   /source-map-js/1.0.2:
     resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
     engines: {node: '>=0.10.0'}
+    dev: false
 
   /source-map-support/0.5.13:
     resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==}
@@ -12132,6 +12178,7 @@ packages:
       '@babel/core': 7.17.10
       client-only: 0.0.1
       react: 18.2.0
+    dev: false
 
   /stylis/4.0.13:
     resolution: {integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==}
@@ -12545,6 +12592,7 @@ packages:
       typescript: 4.6.4
       v8-compile-cache-lib: 3.0.1
       yn: 3.1.1
+    dev: true
 
   /ts-node/9.1.1_typescript@4.9.4:
     resolution: {integrity: sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==}
@@ -12777,6 +12825,7 @@ packages:
     resolution: {integrity: sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==}
     engines: {node: '>=4.2.0'}
     hasBin: true
+    dev: true
 
   /typescript/4.9.4:
     resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==}
@@ -13039,6 +13088,7 @@ packages:
 
   /v8-compile-cache-lib/3.0.1:
     resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
+    dev: true
 
   /v8-compile-cache/2.3.0:
     resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
@@ -13466,6 +13516,7 @@ packages:
   /yn/3.1.1:
     resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
     engines: {node: '>=6'}
+    dev: true
 
   /yocto-queue/0.1.0:
     resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}

+ 9 - 1
scripts/start-dev.sh

@@ -22,6 +22,10 @@ ARCHITECTURE="$(uname -m)"
 TZ="UTC"
 JWT_SECRET=secret
 POSTGRES_PASSWORD=postgres
+POSTGRES_USERNAME=tipi
+POSTGRES_DBNAME=tipi
+POSTGRES_PORT=5432
+POSTGRES_HOST=tipi-db
 TIPI_VERSION=$(get_json_field "${ROOT_FOLDER}/package.json" version)
 INTERNAL_IP=localhost
 storage_path="${ROOT_FOLDER}"
@@ -96,11 +100,15 @@ for template in ${ENV_FILE}; do
     sed "${sed_args[@]}" "s/<architecture>/${ARCHITECTURE}/g" "${template}"
     sed "${sed_args[@]}" "s/<nginx_port>/${NGINX_PORT}/g" "${template}"
     sed "${sed_args[@]}" "s/<nginx_port_ssl>/${NGINX_PORT_SSL}/g" "${template}"
-    sed "${sed_args[@]}" "s/<postgres_password>/${POSTGRES_PASSWORD}/g" "${template}"
     sed "${sed_args[@]}" "s/<apps_repo_id>/${REPO_ID}/g" "${template}"
     sed "${sed_args[@]}" "s/<apps_repo_url>/${APPS_REPOSITORY_ESCAPED}/g" "${template}"
     sed "${sed_args[@]}" "s/<domain>/${DOMAIN}/g" "${template}"
     sed "${sed_args[@]}" "s/<storage_path>/${STORAGE_PATH_ESCAPED}/g" "${template}"
+    sed "${sed_args[@]}" "s/<postgres_password>/${POSTGRES_PASSWORD}/g" "${template}"
+    sed "${sed_args[@]}" "s/<postgres_username>/${POSTGRES_USERNAME}/g" "${template}"
+    sed "${sed_args[@]}" "s/<postgres_dbname>/${POSTGRES_DBNAME}/g" "${template}"
+    sed "${sed_args[@]}" "s/<postgres_port>/${POSTGRES_PORT}/g" "${template}"
+    sed "${sed_args[@]}" "s/<postgres_host>/${POSTGRES_HOST}/g" "${template}"
 done
 
 mv -f "$ENV_FILE" "$ROOT_FOLDER/.env.dev"

+ 6 - 2
templates/env-sample

@@ -12,6 +12,10 @@ JWT_SECRET=<jwt_secret>
 ROOT_FOLDER_HOST=<root_folder>
 NGINX_PORT=<nginx_port>
 NGINX_PORT_SSL=<nginx_port_ssl>
-POSTGRES_PASSWORD=<postgres_password>
 DOMAIN=<domain>
-STORAGE_PATH=<storage_path>
+STORAGE_PATH=<storage_path>
+POSTGRES_HOST=<postgres_host>
+POSTGRES_DBNAME=<postgres_dbname>
+POSTGRES_USERNAME=<postgres_username>
+POSTGRES_PASSWORD=<postgres_password>
+POSTGRES_PORT=<postgres_port>