From 600436a23bcc2b0b652824c14a747fd6196b92ff Mon Sep 17 00:00:00 2001 From: Antarux Date: Wed, 10 Jun 2026 01:29:10 +0200 Subject: [PATCH 1/5] =?UTF-8?q?Prisma=20setup=20a=20konfigur=C3=A1cia=20&?= =?UTF-8?q?=20Docker=20redesign?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > cd.yml injektuje Github secret pre docker db > .dev compose súbor teraz obsahuje aj backend pre full testing > .prod compose súbor bol kompletne prepísaný a zjednodušenie je viditeľné + upustili sme od docker swarm keďže to lenvšetko komplikovalo > Dockerfile bol kompletne prepísaný a ktorky rozdelené do stagov, pre obe production a development image > Bola pridaná prisma, jej základná konfigurácia a prisma export z src/database/prisma.ts > env.ts odteraz vyžaduje DATABASE_URL > lint:fix prebehol na celý projekt, do commitu sa tak pridali aj súbory ktoré nie sú súčasťou, bola tam len zmenená štylizácia... --- .dockerignore | 21 +- .env.example | 3 +- .github/workflows/cd.yml | 22 +- .gitignore | 2 + Dockerfile | 36 ++- docker-compose.dev.yml | 28 +- docker-compose.prod.yml | 64 ++--- package-lock.json | 361 +++++++++++++++++++------- package.json | 2 + prisma.config.ts | 14 + prisma/schema.prisma | 9 + src/config/env.ts | 5 +- src/database/.gitkeep | 0 src/database/prisma.ts | 10 + src/features/health/health.routes.ts | 2 +- src/middleware/rateLimitMiddleware.ts | 9 +- src/utils/customErrors.ts | 2 +- src/utils/dateUtils.ts | 34 +-- src/utils/stringUtils.ts | 18 +- 19 files changed, 450 insertions(+), 192 deletions(-) create mode 100644 prisma.config.ts create mode 100644 prisma/schema.prisma delete mode 100644 src/database/.gitkeep create mode 100644 src/database/prisma.ts diff --git a/.dockerignore b/.dockerignore index 16cf8bf..bf5b88d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,20 +1,29 @@ node_modules + .git .github .gitignore +.vscode +.editorconfig +.prettier* +vitest.config.ts +eslint* + dist coverage +generated + *.log .DS_Store .env .env.* *.tar -.vscode -.editorconfig -.prettier* -eslint* +**/*.test.ts + README.md LICENSE -vitest.config.ts +*.md + +Dockerfile docker-compose*.yml -Dockerfile \ No newline at end of file +.dockerignore \ No newline at end of file diff --git a/.env.example b/.env.example index 49b6554..a838b79 100644 --- a/.env.example +++ b/.env.example @@ -4,4 +4,5 @@ CORS_ALLOWED_ORIGINS= POSTGRES_USER= POSTGRES_PASSWORD= -POSTGRES_DB= \ No newline at end of file +POSTGRES_DB= +DATABASE_URL= \ No newline at end of file diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 5a8c7a1..f005735 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -20,14 +20,14 @@ jobs: - name: Docker Buildx uses: docker/setup-buildx-action@v3 - + - name: GHCR Login uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - + - name: Docker Image Build & Push uses: docker/build-push-action@v6 with: @@ -44,10 +44,10 @@ jobs: username: deploy port: ${{ secrets.VPS_PORT }} key: ${{ secrets.VPS_SSH_KEY }} - source: "docker-compose.prod.yml" - target: "/opt/nti/api.antarux.dev/" + source: 'docker-compose.prod.yml' + target: '/opt/nti/api.antarux.dev/' overwrite: true - + - name: Deploy to VPS uses: appleboy/ssh-action@v1.0.3 with: @@ -59,5 +59,13 @@ jobs: set -e cd /opt/nti/api.antarux.dev - docker stack deploy -c docker-compose.prod.yml --with-registry-auth nti - docker image prune -f --filter "until=168h" \ No newline at end of file + cat > .env << 'EOF' + DATABASE_URL=${{ secrets.VPS_DATABASE_URL }} + NODE_ENV=production + PORT=3000 + EOF + + VERSION=${{ github.ref_name }} docker compose -f docker-compose.prod.yml pull + VERSION=${{ github.ref_name }} docker compose -f docker-compose.prod.yml up -d --remove-orphans + + docker image prune -f --filter "until=168h" diff --git a/.gitignore b/.gitignore index 438657a..17bf905 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ dist-ssr *.njsproj *.sln *.sw? + +/src/generated/prisma diff --git a/Dockerfile b/Dockerfile index 36fd1e3..81f52f2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,40 @@ -FROM node:22-alpine +FROM node:22-alpine AS base WORKDIR /app +# DEPENDENCIES +FROM base AS dev-deps COPY package*.json ./ RUN npm ci + +FROM base AS prod-deps +COPY package*.json ./ +RUN npm ci --omit=dev --ignore-scripts + +# DEFAULT BUILD STAGE +FROM dev-deps AS build COPY . . -RUN npm run build +RUN npx prisma generate && npm run build -USER node +# DEV STAGE +FROM base AS development +ENV NODE_ENV=development +COPY --from=dev-deps /app/node_modules ./node_modules +COPY . . +RUN npx prisma generate EXPOSE 3000 +CMD ["npm", "run", "dev"] -CMD ["npm", "start"] \ No newline at end of file +# PRODUCTION STAGE +FROM base AS production +ENV NODE_ENV=production +COPY --from=prod-deps /app/node_modules ./node_modules +COPY --from=build /app/dist ./dist +COPY --from=build /app/src/generated ./src/generated +COPY --from=build /app/prisma ./prisma +COPY --from=build /app/prisma.config.ts ./prisma.config.ts +COPY --from=build /app/node_modules/.bin/prisma ./node_modules/.bin/prisma +COPY --from=build /app/node_modules/prisma ./node_modules/prisma +COPY --from=build /app/node_modules/@prisma ./node_modules/@prisma +COPY package*.json ./ +EXPOSE 3000 +CMD ["sh", "-c", "npx prisma migrate deploy && npm start"] \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 53cf668..825e2b0 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -3,14 +3,32 @@ services: container_name: database image: postgres:latest environment: - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER:-nti_db} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} + POSTGRES_DB: ${POSTGRES_DB:-nti_development} ports: - - "5432:5432" + - '5432:5432' volumes: - postgres_data:/var/lib/postgresql restart: unless-stopped + backend: + build: + context: . + dockerfile: Dockerfile + target: development + ports: + - '3000:3000' + environment: + NODE_ENV: development + PORT: 3000 + DATABASE_URL: postgresql://nti_db:password@database:5432/nti_development?schema=public + CORS_ALLOWED_ORIGINS: http://localhost:5173,http://localhost:3000 + volumes: + - .:/app + - /app/node_modules + - /app/generated + command: sh -c "npx prisma generate && npx prisma migrate deploy && npm run dev" + volumes: - postgres_data: \ No newline at end of file + postgres_data: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index b76bc08..ab9ef4c 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,54 +1,28 @@ -version: '3.8' - services: - backend: - image: ghcr.io/antarux-dev/nti-backend:${VERSION:-latest} - deploy: - replicas: 1 - restart_policy: - condition: on-failure - resources: - limits: - cpus: '1.0' - memory: 512M - environment: - NODE_ENV: production - PORT: 3000 - CORS_ALLOWED_ORIGINS: https://antarux.dev, https://www.antarux.dev - POSTGRES_USER: nti_db - POSTGRES_DB: nti_production - POSTGRES_PASSWORD_FILE: /run/secrets/db_password - secrets: - - db_password - ports: - - '3000:3000' - networks: - - nti-net - database: image: postgres:latest - deploy: - replicas: 1 - restart_policy: - condition: any + container_name: nti-database + restart: unless-stopped environment: POSTGRES_USER: nti_db + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: nti_production - POSTGRES_PASSWORD_FILE: /run/secrets/db_password - secrets: - - db_password volumes: - - type: bind - source: /opt/nti/data/postgres - target: /var/lib/postgresql/data - networks: - - nti-net + - nti_postgres_data:/var/lib/postgresql + ports: + - '127.0.0.1:5432:5432' -secrets: - db_password: - external: true - name: nti_db_password + backend: + image: ghcr.io/antarux-dev/nti-backend:${VERSION:-latest} + restart: unless-stopped + environment: + NODE_ENV: production + PORT: 3000 + DATABASE_URL: ${DATABASE_URL} + env_file: + - .env + ports: + - '3000:3000' -networks: - nti-net: - driver: overlay \ No newline at end of file +volumes: + nti_postgres_data: diff --git a/package-lock.json b/package-lock.json index f2f71ca..8fa6f3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "nti-backend", "version": "0.0.0", "dependencies": { + "@prisma/adapter-pg": "^7.8.0", "@prisma/client": "^7.8.0", "cors": "^2.8.6", "dotenv": "^17.4.2", @@ -814,6 +815,18 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@prisma/adapter-pg": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.8.0.tgz", + "integrity": "sha512-ygb3UkerK3v8MDpXVgCISdRNDozpxh6+JVJgiIGbSr5KBgz10LLf5ejUskPGoXlsIjxsOu6nuy1JVQr2EKGSlg==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/driver-adapter-utils": "7.8.0", + "@types/pg": "^8.16.0", + "pg": "^8.16.3", + "postgres-array": "3.0.4" + } + }, "node_modules/@prisma/client": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz", @@ -861,7 +874,6 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.8.0.tgz", "integrity": "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==", - "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/dev": { @@ -890,12 +902,14 @@ "zeptomatch": "2.1.0" } }, - "node_modules/@prisma/dev/node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "devOptional": true, - "license": "MIT" + "node_modules/@prisma/driver-adapter-utils": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.8.0.tgz", + "integrity": "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.8.0" + } }, "node_modules/@prisma/engines": { "version": "7.8.0", @@ -1570,12 +1584,22 @@ "version": "25.9.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, + "node_modules/@types/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/qs": { "version": "6.15.1", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", @@ -1623,17 +1647,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.60.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz", - "integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz", + "integrity": "sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.60.1", - "@typescript-eslint/type-utils": "8.60.1", - "@typescript-eslint/utils": "8.60.1", - "@typescript-eslint/visitor-keys": "8.60.1", + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/type-utils": "8.61.0", + "@typescript-eslint/utils": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1646,22 +1670,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.60.1", + "@typescript-eslint/parser": "^8.61.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.60.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz", - "integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.0.tgz", + "integrity": "sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.60.1", - "@typescript-eslint/types": "8.60.1", - "@typescript-eslint/typescript-estree": "8.60.1", - "@typescript-eslint/visitor-keys": "8.60.1", + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", "debug": "^4.4.3" }, "engines": { @@ -1677,14 +1701,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.60.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz", - "integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.0.tgz", + "integrity": "sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.60.1", - "@typescript-eslint/types": "^8.60.1", + "@typescript-eslint/tsconfig-utils": "^8.61.0", + "@typescript-eslint/types": "^8.61.0", "debug": "^4.4.3" }, "engines": { @@ -1699,14 +1723,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.60.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz", - "integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.0.tgz", + "integrity": "sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.1", - "@typescript-eslint/visitor-keys": "8.60.1" + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1717,9 +1741,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.60.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz", - "integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.0.tgz", + "integrity": "sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==", "dev": true, "license": "MIT", "engines": { @@ -1734,15 +1758,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.60.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz", - "integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.0.tgz", + "integrity": "sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.1", - "@typescript-eslint/typescript-estree": "8.60.1", - "@typescript-eslint/utils": "8.60.1", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/utils": "8.61.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1759,9 +1783,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.60.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz", - "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.0.tgz", + "integrity": "sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==", "dev": true, "license": "MIT", "engines": { @@ -1773,16 +1797,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.60.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz", - "integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.0.tgz", + "integrity": "sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.60.1", - "@typescript-eslint/tsconfig-utils": "8.60.1", - "@typescript-eslint/types": "8.60.1", - "@typescript-eslint/visitor-keys": "8.60.1", + "@typescript-eslint/project-service": "8.61.0", + "@typescript-eslint/tsconfig-utils": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1801,16 +1825,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.60.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz", - "integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.0.tgz", + "integrity": "sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.60.1", - "@typescript-eslint/types": "8.60.1", - "@typescript-eslint/typescript-estree": "8.60.1" + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1825,13 +1849,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.60.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz", - "integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.0.tgz", + "integrity": "sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/types": "8.61.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3374,9 +3398,9 @@ } }, "node_modules/hono": { - "version": "4.12.23", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz", - "integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==", + "version": "4.12.25", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz", + "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", "devOptional": true, "license": "MIT", "engines": { @@ -4284,6 +4308,104 @@ "devOptional": true, "license": "MIT" }, + "node_modules/pg": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", + "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.13.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.14.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", + "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", + "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-types/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4372,6 +4494,45 @@ "url": "https://github.com/sponsors/porsager" } }, + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4383,9 +4544,9 @@ } }, "node_modules/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", + "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", "dev": true, "license": "MIT", "bin": { @@ -4751,9 +4912,9 @@ "peer": true }, "node_modules/semver": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", - "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.3.tgz", + "integrity": "sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==", "dev": true, "license": "ISC", "bin": { @@ -4844,14 +5005,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -4955,6 +5116,15 @@ "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", @@ -4982,10 +5152,10 @@ } }, "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", - "dev": true, + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "devOptional": true, "license": "MIT" }, "node_modules/tinybench": { @@ -5239,16 +5409,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.60.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.1.tgz", - "integrity": "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.0.tgz", + "integrity": "sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.60.1", - "@typescript-eslint/parser": "8.60.1", - "@typescript-eslint/typescript-estree": "8.60.1", - "@typescript-eslint/utils": "8.60.1" + "@typescript-eslint/eslint-plugin": "8.61.0", + "@typescript-eslint/parser": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/utils": "8.61.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5266,7 +5436,6 @@ "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -5480,6 +5649,13 @@ } } }, + "node_modules/vitest/node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5529,6 +5705,15 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 24f85ce..420c21b 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dev": "tsx watch src/index.ts", "build": "tsc && tsc-alias", "start": "node dist/index.js", + "prisma:generate": "prisma generate", "lint": "eslint . --ext .ts --max-warnings=20", "lint:fix": "eslint . --ext .ts --fix && prettier --write .", "type-check": "tsc --noEmit", @@ -14,6 +15,7 @@ "vitest:watch": "vitest watch" }, "dependencies": { + "@prisma/adapter-pg": "^7.8.0", "@prisma/client": "^7.8.0", "cors": "^2.8.6", "dotenv": "^17.4.2", diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 0000000..3a23188 --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,14 @@ +/// + +import 'dotenv/config'; +import { defineConfig } from 'prisma/config'; + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: process.env['DATABASE_URL'], + }, +}); diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..41094a4 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,9 @@ +generator client { + provider = "prisma-client" + output = "../src/generated/prisma" + moduleFormat = "esm" +} + +datasource db { + provider = "postgresql" +} diff --git a/src/config/env.ts b/src/config/env.ts index 9479f9b..8033272 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -8,9 +8,8 @@ dotenv.config({ const envSchema = z.object({ NODE_ENV: z.enum(['development', 'production']).default('development'), PORT: z.coerce.number().default(3000), - CORS_ALLOWED_ORIGINS: z - .string() - .default('http://localhost:5173,https://antarux.dev,https://www.antarux.dev'), + CORS_ALLOWED_ORIGINS: z.string().default('http://localhost:5173'), + DATABASE_URL: z.string().min(1, 'DATABASE_URL is required...'), }); const parsedEnv = envSchema.safeParse(process.env); diff --git a/src/database/.gitkeep b/src/database/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/database/prisma.ts b/src/database/prisma.ts new file mode 100644 index 0000000..ca29546 --- /dev/null +++ b/src/database/prisma.ts @@ -0,0 +1,10 @@ +import { PrismaClient } from '@/generated/prisma/client.js'; +import { PrismaPg } from '@prisma/adapter-pg'; +import { env } from '@config/env.js'; + +const adapter = new PrismaPg({ connectionString: env.DATABASE_URL }); +export const prisma = new PrismaClient({ adapter }); + +process.on('beforeExit', async () => { + await prisma.$disconnect(); +}); diff --git a/src/features/health/health.routes.ts b/src/features/health/health.routes.ts index 15534b1..e3a55e6 100644 --- a/src/features/health/health.routes.ts +++ b/src/features/health/health.routes.ts @@ -4,4 +4,4 @@ import { healthCheck } from './health.controller.js'; const router = Router(); router.get('/', healthCheck); -export default router; \ No newline at end of file +export default router; diff --git a/src/middleware/rateLimitMiddleware.ts b/src/middleware/rateLimitMiddleware.ts index 3be12ce..6e0ffd7 100644 --- a/src/middleware/rateLimitMiddleware.ts +++ b/src/middleware/rateLimitMiddleware.ts @@ -8,13 +8,13 @@ interface LimiterOptions { } export function customLimiter(options: LimiterOptions) { - const { windowMs, limit, message } = options + const { windowMs, limit, message } = options; return rateLimit({ windowMs: windowMs, limit: limit, handler: (req, res, next) => { - next(new ExceededRateLimitError(message || "")); + next(new ExceededRateLimitError(message || '')); }, }); } @@ -23,11 +23,10 @@ export const testLimiter = customLimiter({ windowMs: 10000, limit: 10, message: 'Yooo slow down!', -}) - +}); export const authLimiter = customLimiter({ windowMs: 10000, limit: 4, message: 'Too many attempts to authorize!', -}) +}); diff --git a/src/utils/customErrors.ts b/src/utils/customErrors.ts index a1c9aab..4256fcc 100644 --- a/src/utils/customErrors.ts +++ b/src/utils/customErrors.ts @@ -51,4 +51,4 @@ export class ExceededRateLimitError extends CustomError { constructor(message: string = 'Too many requests') { super(message, HTTP_STATUS.TOO_MANY_REQUESTS); } -} \ No newline at end of file +} diff --git a/src/utils/dateUtils.ts b/src/utils/dateUtils.ts index 5649dbd..1189189 100644 --- a/src/utils/dateUtils.ts +++ b/src/utils/dateUtils.ts @@ -1,33 +1,33 @@ -export type DateLike = Date | string | number +export type DateLike = Date | string | number; export function isValidDate(value: DateLike): boolean { - return !isNaN(new Date(value).getTime()) + return !isNaN(new Date(value).getTime()); } export function formatDate(value: DateLike, locale = 'sk-SK'): string { - return new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(new Date(value)) + return new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(new Date(value)); } export function toISODateString(value: DateLike): string { - return new Date(value).toISOString().slice(0, 10) + return new Date(value).toISOString().slice(0, 10); } export function isPast(value: DateLike): boolean { - return new Date(value).getTime() < Date.now() + return new Date(value).getTime() < Date.now(); } - + export function isFuture(value: DateLike): boolean { - return new Date(value).getTime() > Date.now() + return new Date(value).getTime() > Date.now(); } export function timeAgo(value: DateLike, locale = 'sk-SK'): string { - const diff = new Date(value).getTime() - Date.now() - const abs = Math.abs(diff) - const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }) - - if (abs < 60_000) return rtf.format(Math.round(diff / 1_000), 'second') - if (abs < 3_600_000) return rtf.format(Math.round(diff / 60_000), 'minute') - if (abs < 86_400_000) return rtf.format(Math.round(diff / 3_600_000), 'hour') - if (abs < 2_592_000_000) return rtf.format(Math.round(diff / 86_400_000), 'day') - return rtf.format(Math.round(diff / 2_592_000_000), 'month') -} \ No newline at end of file + const diff = new Date(value).getTime() - Date.now(); + const abs = Math.abs(diff); + const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }); + + if (abs < 60_000) return rtf.format(Math.round(diff / 1_000), 'second'); + if (abs < 3_600_000) return rtf.format(Math.round(diff / 60_000), 'minute'); + if (abs < 86_400_000) return rtf.format(Math.round(diff / 3_600_000), 'hour'); + if (abs < 2_592_000_000) return rtf.format(Math.round(diff / 86_400_000), 'day'); + return rtf.format(Math.round(diff / 2_592_000_000), 'month'); +} diff --git a/src/utils/stringUtils.ts b/src/utils/stringUtils.ts index 58a3006..951996c 100644 --- a/src/utils/stringUtils.ts +++ b/src/utils/stringUtils.ts @@ -1,6 +1,6 @@ -//odstranenie diakritiky +//odstranenie diakritiky export function removeAccents(value: string): string { - return value.normalize('NFD').replace(/\p{Mn}/gu, '') + return value.normalize('NFD').replace(/\p{Mn}/gu, ''); } // prerobenie na text pre link @@ -9,22 +9,22 @@ export function slugify(value: string): string { .toLowerCase() .replace(/[^a-z0-9\s-]/g, '') .trim() - .replace(/[\s-]+/g, '-') + .replace(/[\s-]+/g, '-'); } // prve pismo velke vo vete export function capitalize(value: string): string { - if (!value) return value - return value.charAt(0).toUpperCase() + value.slice(1) + if (!value) return value; + return value.charAt(0).toUpperCase() + value.slice(1); } // prve pismo velke v kazdom slove export function capitalizeWords(value: string): string { - return value.split(/\s+/).map(capitalize).join(' ') + return value.split(/\s+/).map(capitalize).join(' '); } // na skratenie slova kde sa da urcit ci chceme strikne skratit alebo skratit po najblizsiu medzeru export function truncate(value: string, maxLength: number, suffix = '…'): string { - if (value.length <= maxLength) return value - return value.slice(0, maxLength - suffix.length).trimEnd() + suffix -} \ No newline at end of file + if (value.length <= maxLength) return value; + return value.slice(0, maxLength - suffix.length).trimEnd() + suffix; +} From fb01be64e94339a1712575951f8472b36532dd53 Mon Sep 17 00:00:00 2001 From: Antarux Date: Wed, 10 Jun 2026 01:48:16 +0200 Subject: [PATCH 2/5] CI & CD update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > ci teraz bude generovat prismu a typecheck nebude padať > CD má pridané perms read pre runnera, takisto bolo aktualizované settings pre repozitár --- .github/workflows/cd.yml | 5 +++++ .github/workflows/ci.yml | 3 +++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f005735..e2e3dd1 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -10,6 +10,11 @@ jobs: name: Backend deployment runs-on: ubuntu-latest + + permissions: + contents: read + packages: read + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5dc65ec..65a6639 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,9 @@ jobs: - name: Install packages run: npm ci --prefer-offline --no-audit --ignore-scripts + - name: Generate Prisma Client + run: npx prisma generate + - name: NPM security audit run: npm audit --audit-level=critical From 0b2b17caaee05a0b201e7a96580244cc5980cf70 Mon Sep 17 00:00:00 2001 From: Antarux Date: Wed, 10 Jun 2026 02:05:52 +0200 Subject: [PATCH 3/5] Fix cd runner perms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > Packages teraz majú write perms, what can I say a typo lebo sak necitam co pisem --- .github/workflows/cd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index e2e3dd1..8722ea8 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -10,10 +10,9 @@ jobs: name: Backend deployment runs-on: ubuntu-latest - permissions: contents: read - packages: read + packages: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -63,6 +62,7 @@ jobs: script: | set -e cd /opt/nti/api.antarux.dev + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin cat > .env << 'EOF' DATABASE_URL=${{ secrets.VPS_DATABASE_URL }} From eb84a212c2c1685031a969c6ac5d2d7294dad284 Mon Sep 17 00:00:00 2001 From: Antarux Date: Wed, 10 Jun 2026 02:28:49 +0200 Subject: [PATCH 4/5] =?UTF-8?q?Pridan=C3=BD=20full=20db=20login=20flow=20p?= =?UTF-8?q?re=20CD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (totálne som na to vobec neyabudol) --- .github/workflows/cd.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 8722ea8..ee7f0b8 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -66,6 +66,9 @@ jobs: cat > .env << 'EOF' DATABASE_URL=${{ secrets.VPS_DATABASE_URL }} + POSTGRES_USER=nti_db + POSTGRES_PASSWORD=${{ secrets.VPS_POSTGRES_PASSWORD }} + POSTGRES_DB=nti_production NODE_ENV=production PORT=3000 EOF From 63bb9862277748f3e92d407867ca627ec14041f7 Mon Sep 17 00:00:00 2001 From: Antarux Date: Wed, 10 Jun 2026 05:29:50 +0200 Subject: [PATCH 5/5] =?UTF-8?q?Dynamick=C3=A9=20na=C4=8D=C3=ADtanie=20ENV?= =?UTF-8?q?=20premenn=C3=BDch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > CD ENV sa odteraz načítavjú dynamicky pomocou GitHub secretov > docker-compose .dev a .prod odteraz len referencujú .env file Closes #37 --- .env.example | 2 +- .github/workflows/cd.yml | 9 +++++---- docker-compose.dev.yml | 13 +++++-------- docker-compose.prod.yml | 8 ++------ 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/.env.example b/.env.example index a838b79..23a6c5d 100644 --- a/.env.example +++ b/.env.example @@ -5,4 +5,4 @@ CORS_ALLOWED_ORIGINS= POSTGRES_USER= POSTGRES_PASSWORD= POSTGRES_DB= -DATABASE_URL= \ No newline at end of file +DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@database:5432/${POSTGRES_DB}?schema=public \ No newline at end of file diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index ee7f0b8..ced961b 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -65,12 +65,13 @@ jobs: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin cat > .env << 'EOF' - DATABASE_URL=${{ secrets.VPS_DATABASE_URL }} - POSTGRES_USER=nti_db - POSTGRES_PASSWORD=${{ secrets.VPS_POSTGRES_PASSWORD }} - POSTGRES_DB=nti_production NODE_ENV=production PORT=3000 + CORS_ALLOWED_ORIGINS=${{ secrets.VPS_CORS_ALLOWED_ORIGINS }} + POSTGRES_USER=${{ secrets.VPS_POSTGRES_USERNAME }} + POSTGRES_PASSWORD=${{ secrets.VPS_POSTGRES_PASSWORD }} + POSTGRES_DB=${{ secrets.VPS_POSTGRES_DB }} + DATABASE_URL=postgresql://${{ secrets.VPS_POSTGRES_USERNAME }}:${{ secrets.VPS_POSTGRES_PASSWORD }}@database:5432/${{ secrets.VPS_POSTGRES_DB }}?schema=public EOF VERSION=${{ github.ref_name }} docker compose -f docker-compose.prod.yml pull diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 825e2b0..a259bf5 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -3,9 +3,9 @@ services: container_name: database image: postgres:latest environment: - POSTGRES_USER: ${POSTGRES_USER:-nti_db} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} - POSTGRES_DB: ${POSTGRES_DB:-nti_development} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} ports: - '5432:5432' volumes: @@ -19,11 +19,8 @@ services: target: development ports: - '3000:3000' - environment: - NODE_ENV: development - PORT: 3000 - DATABASE_URL: postgresql://nti_db:password@database:5432/nti_development?schema=public - CORS_ALLOWED_ORIGINS: http://localhost:5173,http://localhost:3000 + env_file: + - .env volumes: - .:/app - /app/node_modules diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index ab9ef4c..ef9aac0 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -4,9 +4,9 @@ services: container_name: nti-database restart: unless-stopped environment: - POSTGRES_USER: nti_db + POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: nti_production + POSTGRES_DB: ${POSTGRES_DB} volumes: - nti_postgres_data:/var/lib/postgresql ports: @@ -15,10 +15,6 @@ services: backend: image: ghcr.io/antarux-dev/nti-backend:${VERSION:-latest} restart: unless-stopped - environment: - NODE_ENV: production - PORT: 3000 - DATABASE_URL: ${DATABASE_URL} env_file: - .env ports: