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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# --- APP ---
PORT=3000
NODE_ENV=development
CORS_ALLOWED_ORIGINS=http://localhost:3000

# --- POSTGRES ---
DB_USERNAME=admin
Expand Down
41 changes: 41 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: "Bug Report"
description: "Сообщить об ошибке в работе приложения"
labels: ["bug", "triage"]
body:
- type: markdown
attributes:
value: |
Спасибо, что решили помочь сделать проект лучше!
- type: input
id: version
attributes:
label: "Версия приложения"
description: "Какую версию вы используете? (например, 0.0.1)"
placeholder: "0.0.x"
validations:
required: true
- type: textarea
id: steps
attributes:
label: "Шаги воспроизведения"
description: "Как нам увидеть эту ошибку?"
placeholder: |
1. Запустить docker-compose
2. Отправить POST запрос на /api/v1/auth...
validations:
required: true
- type: dropdown
id: environment
attributes:
label: "Окружение"
options:
- Docker
- Local (pnpm)
- Production
validations:
required: true
- type: textarea
id: expected
attributes:
label: "Ожидаемое поведение"
placeholder: "Что должно было произойти?"
10 changes: 10 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
blank_issues_enabled: false

contact_links:
- name: "❓ Вопросы по использованию"
url: "https://github.com/Task-Tracker-Lab/task-tracker-backend/discussions/new?category=q-a"
about: "Если вы не уверены, баг это или нет, или вам нужна помощь в настройке — спросите здесь."

- name: "💡 Идеи и предложения"
url: "https://github.com/Task-Tracker-Lab/task-tracker-backend/discussions/new?category=ideas"
about: "Хотите обсудить новую крутую фичу перед тем, как заводить задачу? Вам сюда."
18 changes: 18 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: "🚀 Feature Request"
description: "Предложить новую идею или улучшение"
labels: ["enhancement"]
body:
- type: textarea
id: problem
attributes:
label: "Какую проблему мы решаем?"
description: "Опишите, почему текущего функционала недостаточно."
validations:
required: true
- type: textarea
id: solution
attributes:
label: "Ваше предложение"
description: "Как именно вы видите реализацию этой фичи?"
validations:
required: true
49 changes: 49 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Build and Push

on:
push:
branches: [dev, main]

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-push:
runs-on: ubuntu-latest
permissions:
contents: read
# packages: write

steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

# - name: Log in to the Container registry
# uses: docker/login-action@v3
# with:
# registry: ${{ env.REGISTRY }}
# username: ${{ github.actor }}
# password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=sha,format=short

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.prod
push: false # add true, if your setup variables
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: CI

on:
pull_request:
branches: [dev, main]
push:
branches: [dev, main]

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

steps:
- name: Checkout repository
uses: actions/checkout@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: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run Lint
run: pnpm run lint

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

- name: Run Tests
run: pnpm run test
33 changes: 33 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: "CodeQL"

on:
push:
branches: [main, dev, feat/**, chore/**, build/**]
pull_request:
branches: [main]
schedule:
- cron: "15 13 * * 5"

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write

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

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript-typescript

- name: Autobuild
uses: github/codeql-action/autobuild@v3

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
18 changes: 18 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: release-please

on:
push:
branches:
- main

permissions:
contents: write
pull-requests: write

jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v4
with:
release-type: node
19 changes: 19 additions & 0 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: "Close stale issues and PRs"

on:
schedule:
- cron: "30 1 * * *"

jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
stale-issue-message: "Эта задача давно не обновлялась. Она будет закрыта через 5 дней, если не появится новой активности."
stale-pr-message: "Этот PR замер. Мы закроем его через 5 дней, чтобы не копить очередь, но вы всегда можете переоткрыть его позже."
days-before-stale: 30
days-before-close: 5
81 changes: 81 additions & 0 deletions libs/bootstrap/src/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { setupThrottler } from './setups/throttler';
import { DEFAULT_THROTTLER_OPTIONS } from './configs/throttler';
import { setupCors, setupSwagger } from './setups';
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
import type { BootstrapOptions } from './interfaces/options.interface';
import fastifyCookie from '@fastify/cookie';
import fastifyCompress from '@fastify/compress';

export async function bootstrapApp(options: BootstrapOptions) {
const adapter = new FastifyAdapter();

const {
appModule,
apiPrefix = 'api/v1',
serviceName = 'App',
portEnvKey = 'PORT',
defaultPort = 3000,
setupApp,
useCookieParser = true,
useCors = true,
throttlerOptions = DEFAULT_THROTTLER_OPTIONS,
swaggerOptions,
} = options;

let rootModule = appModule;

// TODO: Improve merging modules (in case of multiple features needed)
if (throttlerOptions) {
rootModule = setupThrottler(rootModule, throttlerOptions);
}

const app = await NestFactory.create<NestFastifyApplication>(rootModule, adapter, {
rawBody: true,
});
const logger = new Logger(serviceName[0].toUpperCase() + serviceName.slice(1));
const configService = app.get(ConfigService);
const port = configService.getOrThrow<number>(portEnvKey, defaultPort);
const origins = configService.getOrThrow('CORS_ALLOWED_ORIGINS');

app.enableShutdownHooks();

await app.register(fastifyCompress, {
global: true,
threshold: 1024,
});

if (apiPrefix) app.setGlobalPrefix(apiPrefix);
if (useCors) setupCors(app, origins);
if (swaggerOptions) {
const { path = 'docs', ...metadata } = swaggerOptions;

const domain = configService.get('DOMAIN');
const stage = configService.get('STAGE_DOMAIN');

const fullOptions = {
...metadata,
path,
server: {
port,
domain,
stage,
},
};

await setupSwagger(app, fullOptions);
}
if (useCookieParser) app.register(fastifyCookie, { secret: 'SAME-SECRET' });
if (setupApp) setupApp(app);

await app.listen(port, '0.0.0.0', (_err, address) => {
if (_err) {
logger.error(_err);
process.exit(1);
}

logger.verbose(`Application is running on: ${address}${apiPrefix ? '/' + apiPrefix : ''}`);
});
}
7 changes: 7 additions & 0 deletions libs/bootstrap/src/configs/swagger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { SwaggerOptions } from '../interfaces/options.interface';

export const SWAGGER_DEFAULTS: SwaggerOptions = {
title: 'API',
description: 'API Documentation',
version: '1.0.0',
};
9 changes: 9 additions & 0 deletions libs/bootstrap/src/configs/throttler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { ThrottlerModuleOptions } from '@nestjs/throttler';

export const DEFAULT_THROTTLER_OPTIONS: ThrottlerModuleOptions = [
{
ttl: 60000,
limit: 100,
skipIf: (context) => context.getType() !== 'http',
},
];
1 change: 1 addition & 0 deletions libs/bootstrap/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { bootstrapApp } from './bootstrap';
1 change: 1 addition & 0 deletions libs/bootstrap/src/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { BootstrapOptions, SwaggerOptions } from './options.interface';
35 changes: 35 additions & 0 deletions libs/bootstrap/src/interfaces/options.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Config } from '@libs/config';
import type { Type } from '@nestjs/common';
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
import type { ThrottlerModuleOptions } from '@nestjs/throttler';

export interface SwaggerMetadata {
title?: string;
description?: string;
version?: string;
path?: string;
}

export interface SwaggerInfrastructure {
server?: {
port?: string | number;
domain?: string;
stage?: string;
};
services?: { name: string; port: number }[];
}

export interface SwaggerOptions extends SwaggerMetadata, SwaggerInfrastructure {}

export interface BootstrapOptions {
apiPrefix?: string;
appModule: Type<unknown>;
defaultPort?: number;
portEnvKey?: keyof Config;
serviceName: string;
setupApp?: (app: NestFastifyApplication) => Promise<void> | void;
swaggerOptions?: SwaggerMetadata;
throttlerOptions?: ThrottlerModuleOptions;
useCookieParser?: boolean;
useCors?: boolean;
}
Loading
Loading