Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ DATABASE_URL=postgres://${DB_USERNAME}:${DB_PASSWORD}@localhost:${DB_PORT}/${DB_
REDIS_HOST=127.0.0.1
REDIS_PORT=7000

JWT_AUDIENCE="task-tracker-client"

JWT_ACCESS_SECRET=same-same-same-same-same
JWT_ACCESS_EXPIRES_IN=15m

Expand Down
13 changes: 11 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
extends: ['plugin:@typescript-eslint/recommended'],
root: true,
env: {
node: true,
Expand All @@ -19,8 +19,17 @@ module.exports = {
afterEach: 'readonly',
vi: 'readonly',
},
ignorePatterns: ['.eslintrc.js', 'dist', 'node_modules'],
ignorePatterns: [
'.eslintrc.js',
'*.config.{js,ts}',
'migrations',
'infra',
'.github',
'dist',
'node_modules',
],
rules: {
'prettier/prettier': 'off',
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
Expand Down
50 changes: 25 additions & 25 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
name: CI

on:
pull_request:
branches: [dev, main, "feat/**"]
push:
branches: [dev, main, "feat/**"]
pull_request:
branches: [dev, main, 'feat/**']
push:
branches: [dev, main, 'feat/**']

jobs:
quality-check:
name: Lint & Test
runs-on: ubuntu-latest
quality-check:
name: Lint & Test
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run Lint
run: pnpm run lint
- name: Run Lint
run: pnpm run lint

- name: Type Check
run: pnpm exec tsc --noEmit
- name: Type Check
run: pnpm exec tsc --noEmit

- name: Run Tests
run: pnpm run test
- name: Run Tests
run: pnpm run test
4 changes: 2 additions & 2 deletions .lintstagedrc.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default {
'*.{ts,js}': ['eslint --fix'],
'*.{json,css,md}': ['prettier --write'],
'*.{ts,js}': ['eslint --fix', 'prettier --write'],
'*.{json,css,md,yaml,yml}': ['prettier --write'],
};
214 changes: 105 additions & 109 deletions infra/dev/compose.dev.yaml
Original file line number Diff line number Diff line change
@@ -1,121 +1,117 @@
version: "3.9"
version: '3.9'

name: task-tracker-api

services:
api:
hostname: api
container_name: api
image: ghcr.io/task-tracker-lab/task-tracker-backend:feat-user
env_file:
- .env
ports:
- "3000:3000"
depends_on:
database:
condition: service_healthy
redis:
condition: service_healthy
networks:
- backend
deploy:
resources:
limits:
cpus: "2.0"
memory: 1024M
reservations:
cpus: "0.5"
memory: 256M
api:
hostname: api
container_name: api
image: ghcr.io/task-tracker-lab/task-tracker-backend:feat-user
env_file:
- .env
ports:
- '3000:3000'
depends_on:
database:
condition: service_healthy
redis:
condition: service_healthy
networks:
- backend
deploy:
resources:
limits:
cpus: '2.0'
memory: 1024M
reservations:
cpus: '0.5'
memory: 256M

database:
hostname: database
container_name: database
image: postgres:16-alpine
restart: always
env_file:
- .env
environment:
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_DATABASE}
ports:
- "6000:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- backend
healthcheck:
test:
[
"CMD-SHELL",
'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -q || exit 1',
]
interval: 5s
timeout: 5s
retries: 5
profiles: ["infra"]
database:
hostname: database
container_name: database
image: postgres:16-alpine
restart: always
env_file:
- .env
environment:
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_DATABASE}
ports:
- '6000:5432'
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- backend
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -q || exit 1']
interval: 5s
timeout: 5s
retries: 5
profiles: ['infra']

redis:
hostname: redis
container_name: redis
image: redis:7-alpine
restart: always
ports:
- "7000:6379"
command: redis-server --save 60 1 --loglevel notice
volumes:
- redis_data:/data
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
profiles: ["infra"]
redis:
hostname: redis
container_name: redis
image: redis:7-alpine
restart: always
ports:
- '7000:6379'
command: redis-server --save 60 1 --loglevel notice
volumes:
- redis_data:/data
networks:
- backend
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 3s
retries: 5
profiles: ['infra']

minio:
hostname: minio
container_name: minio
image: minio/minio:latest
restart: always
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
ports:
- "9000:9000" # API
- "9001:9001" # Console (UI)
command: server /data --console-address ":9001"
volumes:
- minio_data:/data
networks:
- backend
profiles: [ "infra" ]
minio:
hostname: minio
container_name: minio
image: minio/minio:latest
restart: always
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
ports:
- '9000:9000' # API
- '9001:9001' # Console (UI)
command: server /data --console-address ":9001"
volumes:
- minio_data:/data
networks:
- backend
profiles: ['infra']

minio-init:
image: minio/mc:latest
depends_on:
- minio
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
networks:
- backend
profiles: [ "infra" ]
entrypoint: >
/bin/sh -c "
sleep 5;
mc alias set myminio http://minio:9000 ${S3_ACCESS_KEY} ${S3_SECRET_KEY};
mc mb myminio/${S3_BUCKET_NAME} --ignore-existing;
mc anonymous set download myminio/${S3_BUCKET_NAME};
exit 0;
"
minio-init:
image: minio/mc:latest
depends_on:
- minio
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
networks:
- backend
profiles: ['infra']
entrypoint: >
/bin/sh -c "
sleep 5;
mc alias set myminio http://minio:9000 ${S3_ACCESS_KEY} ${S3_SECRET_KEY};
mc mb myminio/${S3_BUCKET_NAME} --ignore-existing;
mc anonymous set download myminio/${S3_BUCKET_NAME};
exit 0;
"

volumes:
postgres_data:
redis_data:
minio_data:
postgres_data:
redis_data:
minio_data:

networks:
backend:
name: task-tracker-gateway
backend:
name: task-tracker-gateway
1 change: 0 additions & 1 deletion libs/bootstrap/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export async function bootstrapApp(options: BootstrapOptions) {

let rootModule = appModule;

// TODO: Improve merging modules (in case of multiple features needed) or migrate to fastify throttle
if (throttlerOptions) {
rootModule = setupThrottler(rootModule, throttlerOptions);
}
Expand Down
14 changes: 13 additions & 1 deletion libs/bootstrap/src/setups/swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@ import { cleanupOpenApiDoc } from 'nestjs-zod';
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
import type { SwaggerOptions } from '../interfaces';
import { SWAGGER_DEFAULTS } from '../configs/swagger';
import { GlobalErrorResponse } from 'src/shared/error/schema';
import { GlobalErrorResponse } from '@shared/error/schema';

async function getCustomCSS() {
const rawUrl = 'https://gist.githubusercontent.com/soorq/f745e5c44cfe27aa928048d6d4ccb18a/raw';
const res = await fetch(rawUrl);
if (!res.ok) {
return '';
}
return res.text();
}

export async function setupSwagger(app: NestFastifyApplication, options: SwaggerOptions = {}) {
const { title, description, version, path, server } = {
Expand All @@ -27,11 +36,14 @@ export async function setupSwagger(app: NestFastifyApplication, options: Swagger
extraModels: [GlobalErrorResponse.Output],
});

const customCss = await getCustomCSS();

SwaggerModule.setup(path, app, cleanupOpenApiDoc(document), {
jsonDocumentUrl: `${path}/s/json`,
yamlDocumentUrl: `${path}/s/yaml`,
useGlobalPrefix: true,
ui: true,
customCss,
swaggerOptions: {
persistAuthorization: true,
tagsSorter: 'alpha',
Expand Down
5 changes: 5 additions & 0 deletions libs/config/src/config.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export const ConfigSchema = z.object({
.min(1, "CORS_ALLOWED_ORIGINS can't be empty")
.transform((val) => val.split(',').map((s) => s.trim()))
.pipe(z.array(z.string().url('Each origin must be a valid URL'))),
JWT_AUDIENCE: z
.string({
error: 'JWT_AUDIENCE is required',
})
.min(1),
JWT_ACCESS_SECRET: z.string().refine(jwtSecretValidation, {
message:
'JWT_ACCESS_SECRET must be at least 32 characters long OR contain at least 5 words separated by hyphens',
Expand Down
Loading
Loading