Skip to content

Commit bda6663

Browse files
authored
feat/refactor: modularize architecture and finalize projects module
* chore(projects): initialize module boilerplate * feat(projects): implement data access layer and database schema * chore: update dependencies, add tests and enhance swagger styling * feat(projects): implement CRUD, Swagger documentation and Zod validation * refactor(teams): restructure team routes and modularize architecture * refactor: split projects and teams into specialized controllers * refactor(auth): split access and recovery controllers and fix bug * refactor(user): separate profile and settings logic / bump bug with imports * chore: restructure services and use type-only imports * feat(project): add service logic, update entity and format code * refactor(core): unify exception filter and resolve technical debt (#22) * refactor(core): unify exception filter and resolve technical debt * refactor: finalize unified error handling and sync test suites
1 parent be55453 commit bda6663

122 files changed

Lines changed: 5501 additions & 1842 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ DATABASE_URL=postgres://${DB_USERNAME}:${DB_PASSWORD}@localhost:${DB_PORT}/${DB_
2525
REDIS_HOST=127.0.0.1
2626
REDIS_PORT=7000
2727

28+
JWT_AUDIENCE="task-tracker-client"
29+
2830
JWT_ACCESS_SECRET=same-same-same-same-same
2931
JWT_ACCESS_EXPIRES_IN=15m
3032

.eslintrc.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module.exports = {
66
sourceType: 'module',
77
},
88
plugins: ['@typescript-eslint/eslint-plugin'],
9-
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
9+
extends: ['plugin:@typescript-eslint/recommended'],
1010
root: true,
1111
env: {
1212
node: true,
@@ -19,8 +19,17 @@ module.exports = {
1919
afterEach: 'readonly',
2020
vi: 'readonly',
2121
},
22-
ignorePatterns: ['.eslintrc.js', 'dist', 'node_modules'],
22+
ignorePatterns: [
23+
'.eslintrc.js',
24+
'*.config.{js,ts}',
25+
'migrations',
26+
'infra',
27+
'.github',
28+
'dist',
29+
'node_modules',
30+
],
2331
rules: {
32+
'prettier/prettier': 'off',
2433
'@typescript-eslint/interface-name-prefix': 'off',
2534
'@typescript-eslint/explicit-function-return-type': 'off',
2635
'@typescript-eslint/explicit-module-boundary-types': 'off',

.github/workflows/ci.yml

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,37 @@
11
name: CI
22

33
on:
4-
pull_request:
5-
branches: [dev, main, "feat/**"]
6-
push:
7-
branches: [dev, main, "feat/**"]
4+
pull_request:
5+
branches: [dev, main, 'feat/**']
6+
push:
7+
branches: [dev, main, 'feat/**']
88

99
jobs:
10-
quality-check:
11-
name: Lint & Test
12-
runs-on: ubuntu-latest
10+
quality-check:
11+
name: Lint & Test
12+
runs-on: ubuntu-latest
1313

14-
steps:
15-
- name: Checkout repository
16-
uses: actions/checkout@v4
14+
steps:
15+
- name: Checkout repository
16+
uses: actions/checkout@v4
1717

18-
- name: Install pnpm
19-
uses: pnpm/action-setup@v4
18+
- name: Install pnpm
19+
uses: pnpm/action-setup@v4
2020

21-
- name: Setup Node.js
22-
uses: actions/setup-node@v4
23-
with:
24-
node-version: 20
25-
cache: "pnpm"
21+
- name: Setup Node.js
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: 20
25+
cache: 'pnpm'
2626

27-
- name: Install dependencies
28-
run: pnpm install --frozen-lockfile
27+
- name: Install dependencies
28+
run: pnpm install --frozen-lockfile
2929

30-
- name: Run Lint
31-
run: pnpm run lint
30+
- name: Run Lint
31+
run: pnpm run lint
3232

33-
- name: Type Check
34-
run: pnpm exec tsc --noEmit
33+
- name: Type Check
34+
run: pnpm exec tsc --noEmit
3535

36-
- name: Run Tests
37-
run: pnpm run test
36+
- name: Run Tests
37+
run: pnpm run test

.lintstagedrc.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export default {
2-
'*.{ts,js}': ['eslint --fix'],
3-
'*.{json,css,md}': ['prettier --write'],
2+
'*.{ts,js}': ['eslint --fix', 'prettier --write'],
3+
'*.{json,css,md,yaml,yml}': ['prettier --write'],
44
};

infra/dev/compose.dev.yaml

Lines changed: 105 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,117 @@
1-
version: "3.9"
1+
version: '3.9'
22

33
name: task-tracker-api
44

55
services:
6-
api:
7-
hostname: api
8-
container_name: api
9-
image: ghcr.io/task-tracker-lab/task-tracker-backend:feat-user
10-
env_file:
11-
- .env
12-
ports:
13-
- "3000:3000"
14-
depends_on:
15-
database:
16-
condition: service_healthy
17-
redis:
18-
condition: service_healthy
19-
networks:
20-
- backend
21-
deploy:
22-
resources:
23-
limits:
24-
cpus: "2.0"
25-
memory: 1024M
26-
reservations:
27-
cpus: "0.5"
28-
memory: 256M
6+
api:
7+
hostname: api
8+
container_name: api
9+
image: ghcr.io/task-tracker-lab/task-tracker-backend:feat-user
10+
env_file:
11+
- .env
12+
ports:
13+
- '3000:3000'
14+
depends_on:
15+
database:
16+
condition: service_healthy
17+
redis:
18+
condition: service_healthy
19+
networks:
20+
- backend
21+
deploy:
22+
resources:
23+
limits:
24+
cpus: '2.0'
25+
memory: 1024M
26+
reservations:
27+
cpus: '0.5'
28+
memory: 256M
2929

30-
database:
31-
hostname: database
32-
container_name: database
33-
image: postgres:16-alpine
34-
restart: always
35-
env_file:
36-
- .env
37-
environment:
38-
POSTGRES_USER: ${DB_USERNAME}
39-
POSTGRES_PASSWORD: ${DB_PASSWORD}
40-
POSTGRES_DB: ${DB_DATABASE}
41-
ports:
42-
- "6000:5432"
43-
volumes:
44-
- postgres_data:/var/lib/postgresql/data
45-
networks:
46-
- backend
47-
healthcheck:
48-
test:
49-
[
50-
"CMD-SHELL",
51-
'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -q || exit 1',
52-
]
53-
interval: 5s
54-
timeout: 5s
55-
retries: 5
56-
profiles: ["infra"]
30+
database:
31+
hostname: database
32+
container_name: database
33+
image: postgres:16-alpine
34+
restart: always
35+
env_file:
36+
- .env
37+
environment:
38+
POSTGRES_USER: ${DB_USERNAME}
39+
POSTGRES_PASSWORD: ${DB_PASSWORD}
40+
POSTGRES_DB: ${DB_DATABASE}
41+
ports:
42+
- '6000:5432'
43+
volumes:
44+
- postgres_data:/var/lib/postgresql/data
45+
networks:
46+
- backend
47+
healthcheck:
48+
test: ['CMD-SHELL', 'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -q || exit 1']
49+
interval: 5s
50+
timeout: 5s
51+
retries: 5
52+
profiles: ['infra']
5753

58-
redis:
59-
hostname: redis
60-
container_name: redis
61-
image: redis:7-alpine
62-
restart: always
63-
ports:
64-
- "7000:6379"
65-
command: redis-server --save 60 1 --loglevel notice
66-
volumes:
67-
- redis_data:/data
68-
networks:
69-
- backend
70-
healthcheck:
71-
test: ["CMD", "redis-cli", "ping"]
72-
interval: 5s
73-
timeout: 3s
74-
retries: 5
75-
profiles: ["infra"]
54+
redis:
55+
hostname: redis
56+
container_name: redis
57+
image: redis:7-alpine
58+
restart: always
59+
ports:
60+
- '7000:6379'
61+
command: redis-server --save 60 1 --loglevel notice
62+
volumes:
63+
- redis_data:/data
64+
networks:
65+
- backend
66+
healthcheck:
67+
test: ['CMD', 'redis-cli', 'ping']
68+
interval: 5s
69+
timeout: 3s
70+
retries: 5
71+
profiles: ['infra']
7672

77-
minio:
78-
hostname: minio
79-
container_name: minio
80-
image: minio/minio:latest
81-
restart: always
82-
environment:
83-
MINIO_ROOT_USER: ${S3_ACCESS_KEY}
84-
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
85-
ports:
86-
- "9000:9000" # API
87-
- "9001:9001" # Console (UI)
88-
command: server /data --console-address ":9001"
89-
volumes:
90-
- minio_data:/data
91-
networks:
92-
- backend
93-
profiles: [ "infra" ]
73+
minio:
74+
hostname: minio
75+
container_name: minio
76+
image: minio/minio:latest
77+
restart: always
78+
environment:
79+
MINIO_ROOT_USER: ${S3_ACCESS_KEY}
80+
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
81+
ports:
82+
- '9000:9000' # API
83+
- '9001:9001' # Console (UI)
84+
command: server /data --console-address ":9001"
85+
volumes:
86+
- minio_data:/data
87+
networks:
88+
- backend
89+
profiles: ['infra']
9490

95-
minio-init:
96-
image: minio/mc:latest
97-
depends_on:
98-
- minio
99-
environment:
100-
MINIO_ROOT_USER: ${S3_ACCESS_KEY}
101-
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
102-
networks:
103-
- backend
104-
profiles: [ "infra" ]
105-
entrypoint: >
106-
/bin/sh -c "
107-
sleep 5;
108-
mc alias set myminio http://minio:9000 ${S3_ACCESS_KEY} ${S3_SECRET_KEY};
109-
mc mb myminio/${S3_BUCKET_NAME} --ignore-existing;
110-
mc anonymous set download myminio/${S3_BUCKET_NAME};
111-
exit 0;
112-
"
91+
minio-init:
92+
image: minio/mc:latest
93+
depends_on:
94+
- minio
95+
environment:
96+
MINIO_ROOT_USER: ${S3_ACCESS_KEY}
97+
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
98+
networks:
99+
- backend
100+
profiles: ['infra']
101+
entrypoint: >
102+
/bin/sh -c "
103+
sleep 5;
104+
mc alias set myminio http://minio:9000 ${S3_ACCESS_KEY} ${S3_SECRET_KEY};
105+
mc mb myminio/${S3_BUCKET_NAME} --ignore-existing;
106+
mc anonymous set download myminio/${S3_BUCKET_NAME};
107+
exit 0;
108+
"
113109
114110
volumes:
115-
postgres_data:
116-
redis_data:
117-
minio_data:
111+
postgres_data:
112+
redis_data:
113+
minio_data:
118114

119115
networks:
120-
backend:
121-
name: task-tracker-gateway
116+
backend:
117+
name: task-tracker-gateway

libs/bootstrap/src/bootstrap.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export async function bootstrapApp(options: BootstrapOptions) {
3636

3737
let rootModule = appModule;
3838

39-
// TODO: Improve merging modules (in case of multiple features needed) or migrate to fastify throttle
4039
if (throttlerOptions) {
4140
rootModule = setupThrottler(rootModule, throttlerOptions);
4241
}

libs/bootstrap/src/setups/swagger.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@ import { cleanupOpenApiDoc } from 'nestjs-zod';
33
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
44
import type { SwaggerOptions } from '../interfaces';
55
import { SWAGGER_DEFAULTS } from '../configs/swagger';
6-
import { GlobalErrorResponse } from 'src/shared/error/schema';
6+
import { GlobalErrorResponse } from '@shared/error/schema';
7+
8+
async function getCustomCSS() {
9+
const rawUrl = 'https://gist.githubusercontent.com/soorq/f745e5c44cfe27aa928048d6d4ccb18a/raw';
10+
const res = await fetch(rawUrl);
11+
if (!res.ok) {
12+
return '';
13+
}
14+
return res.text();
15+
}
716

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

39+
const customCss = await getCustomCSS();
40+
3041
SwaggerModule.setup(path, app, cleanupOpenApiDoc(document), {
3142
jsonDocumentUrl: `${path}/s/json`,
3243
yamlDocumentUrl: `${path}/s/yaml`,
3344
useGlobalPrefix: true,
3445
ui: true,
46+
customCss,
3547
swaggerOptions: {
3648
persistAuthorization: true,
3749
tagsSorter: 'alpha',

libs/config/src/config.schema.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ export const ConfigSchema = z.object({
3535
.min(1, "CORS_ALLOWED_ORIGINS can't be empty")
3636
.transform((val) => val.split(',').map((s) => s.trim()))
3737
.pipe(z.array(z.string().url('Each origin must be a valid URL'))),
38+
JWT_AUDIENCE: z
39+
.string({
40+
error: 'JWT_AUDIENCE is required',
41+
})
42+
.min(1),
3843
JWT_ACCESS_SECRET: z.string().refine(jwtSecretValidation, {
3944
message:
4045
'JWT_ACCESS_SECRET must be at least 32 characters long OR contain at least 5 words separated by hyphens',

0 commit comments

Comments
 (0)