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: 1 addition & 1 deletion drizzle.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default defineConfig({
strict: true,
verbose: true,
dbCredentials: {
url: process.env.DATABASE_URL!,
url: process.env['DATABASE_URL']!,
},
introspect: { casing: 'camel' },
schemaFilter: ['*'],
Expand Down
7 changes: 4 additions & 3 deletions libs/bootstrap/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import fastifyCompress from '@fastify/compress';
import fastifyMultipart from '@fastify/multipart';
import fastifyCsrf from '@fastify/csrf-protection';
import { createId } from '@paralleldrive/cuid2';
import type { IncomingMessage } from 'http';

export async function bootstrapApp(options: BootstrapOptions) {
const startTime = performance.now();
const adapter = new FastifyAdapter({
requestIdHeader: 'x-request-id',
requestIdLogLabel: 'request',
genReqId: (req) => {
genReqId: (req: IncomingMessage) => {
return (req.headers['x-request-id'] as string) || createId();
},
});
Expand Down Expand Up @@ -46,7 +47,7 @@ export async function bootstrapApp(options: BootstrapOptions) {
bufferLogs: false,
});

const logger = new Logger(serviceName[0].toUpperCase() + serviceName.slice(1));
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');
Expand Down Expand Up @@ -152,7 +153,7 @@ export async function bootstrapApp(options: BootstrapOptions) {
}

const startupTime = (performance.now() - startTime).toFixed(2);
logger.verbose(`Environment: ${process.env.NODE_ENV || 'development'}`);
logger.verbose(`Environment: ${process.env['NODE_ENV'] || 'development'}`);
logger.verbose(`API Endpoint: ${baseUrl}`);
logger.verbose(`Health Check: ${baseUrl}/health`);
logger.verbose(`Swagger UI: ${swaggerBase}/${swaggerPath}`);
Expand Down
4 changes: 2 additions & 2 deletions libs/bootstrap/src/configs/throttler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type { ThrottlerModuleOptions } from '@nestjs/throttler';

export const DEFAULT_THROTTLER_OPTIONS: ThrottlerModuleOptions = [
{
ttl: process.env.THROTTLE_TTL ? parseInt(process.env.THROTTLE_LIMIT) : 60000,
limit: process.env.THROTTLE_LIMIT ? parseInt(process.env.THROTTLE_LIMIT) : 100,
ttl: process.env['THROTTLE_TTL'] ? parseInt(process.env['THROTTLE_LIMIT'] ?? '') : 60000,
limit: process.env['THROTTLE_LIMIT'] ? parseInt(process.env['THROTTLE_LIMIT']) : 100,
skipIf: (context) => context.getType() !== 'http',
},
];
12 changes: 6 additions & 6 deletions libs/bootstrap/src/setups/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,26 +94,26 @@ export class LoggingInterceptor implements NestInterceptor {
);
}

private sanitize<T>(data: T) {
private sanitize<T>(data: T): T {
if (!data || typeof data !== 'object') return data;
if (Array.isArray(data)) return data.map((v) => this.sanitize(v));
if (Array.isArray(data)) return data.map((v) => this.sanitize(v)) as T;

const cleanData = JSON.parse(JSON.stringify(data));
const cleanData = JSON.parse(JSON.stringify(data)) as Record<string, unknown>;

return Object.keys(cleanData).reduce((acc, key) => {
return Object.keys(cleanData).reduce<Record<string, unknown>>((acc, key) => {
const isSensitive = this.sensitiveFields.some((field) =>
key.toLowerCase().includes(field),
);

if (isSensitive) {
acc[key] = '***';
} else if (typeof cleanData[key] === 'object') {
} else if (typeof cleanData[key] === 'object' && cleanData[key] !== null) {
acc[key] = this.sanitize(cleanData[key]);
} else {
acc[key] = cleanData[key];
}
return acc;
}, {});
}, {}) as T;
}
}

Expand Down
8 changes: 7 additions & 1 deletion libs/bootstrap/src/setups/swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ async function getCustomCSS() {
}

export async function setupSwagger(app: NestFastifyApplication, options: SwaggerOptions = {}) {
const { title, description, version, path, server } = {
const {
title = 'Api',
description = '',
version = 'v0.0.1',
path = 'api',
server,
} = {
...SWAGGER_DEFAULTS,
...options,
};
Expand Down
2 changes: 2 additions & 0 deletions libs/config/src/config.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ declare module '@nestjs/config' {
* Переопределяем метод get, чтобы он предлагал ключи из нашей схемы
*/
get<T extends keyof Config>(key: T): Config[T];
get<T extends keyof Config>(key: T, defaultValue: Config[T]): Config[T];

/**
* Переопределяем метод getOrThrow, чтобы он предлагал ключи из нашей схемы
*/
Expand Down
10 changes: 8 additions & 2 deletions libs/health/src/health.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ export class HealthService {

const results = await Promise.all(
Object.entries(indicators).map(async ([name, check]) => {
let timeoutId: NodeJS.Timeout;
if (!check || typeof check !== 'function') {
return { name, ok: false, error: 'Health check not configured' };
}

let timeoutId: NodeJS.Timeout | undefined;

const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => reject(new Error('Timeout')), 5000);
Expand All @@ -42,6 +46,8 @@ export class HealthService {
const isAllOk = results.every((r) => r.ok);
const components = Object.fromEntries(results.map((r) => [r.name, r.ok ? 'up' : 'down']));

const loaded = os.loadavg()[0];

return {
service: serviceName,
status: isAllOk,
Expand All @@ -56,7 +62,7 @@ export class HealthService {
uptime: this.formatUptime(uptimeSeconds),
uptimeSeconds: uptimeSeconds,
},
loaded: os.loadavg()[0].toFixed(2),
loaded: loaded?.toFixed(2),
};
}

Expand Down
4 changes: 2 additions & 2 deletions libs/metrics/src/metrics.controller.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Controller, Get, Res } from '@nestjs/common';
import * as client from 'prom-client';
import { FastifyReply } from 'fastify';
import { SkipContractHandle } from '@shared/decorators';
import { SkipContract } from '@shared/decorators';

@Controller('metrics')
export class MetricsController {
@Get()
@SkipContractHandle()
@SkipContract()
async getMetrics(@Res() reply: FastifyReply) {
const metrics = await client.register.metrics();
reply.type(client.register.contentType).send(metrics);
Expand Down
2 changes: 1 addition & 1 deletion libs/metrics/src/metrics.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { APP_INTERCEPTOR } from '@nestjs/core';
PrometheusModule.register({
controller: MetricsController,
defaultMetrics: {
enabled: process.env.NODE_ENV !== 'test',
enabled: process.env['NODE_ENV'] !== 'test',
},
}),
],
Expand Down
2 changes: 1 addition & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import { MetricsModule } from '@libs/metrics';
HealthModule.registerAsync({
inject: [DatabaseHealthService, S3Service, CACHE_SERVICE],
useFactory: (db: DatabaseHealthService, s3: S3Service, cache: ICacheService) => {
const version = process.env.npm_package_version;
const version = process.env['npm_package_version'] ?? '';

return {
serviceName: 'gateway',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export class AreaRepository implements IAreaRepository {
.values(data)
.returning({ id: schema.areas.id, slug: schema.areas.slug });

if (!area) {
throw new Error('Failed to create area: no area returned');
}

const statesData = DEFAULT_STATES.map((state) => ({
areaId: area.id,
title: state.title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export class StateRepository implements IStateRepository {
.values(data)
.returning({ id: schema.states.id });

if (!result) {
throw new Error('Failed to create state: no state returned');
}

return result;
}

Expand Down Expand Up @@ -107,12 +111,12 @@ export class StateRepository implements IStateRepository {
return result ?? null;
}

public async countByArea(areaId: string) {
public countByArea = async (areaId: string) => {
const [result] = await this.db
.select({ count: count() })
.from(schema.states)
.where(and(eq(schema.states.areaId, areaId), isNull(schema.states.deletedAt)));

return result.count;
}
return result?.count ?? 0;
};
}
6 changes: 3 additions & 3 deletions src/auth/application/auth.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ export class AuthFacade {
return this.signUpVerifyUseCase.execute(dto, device);
}

async signOut(userId: string) {
return this.signOutUseCase.execute(userId);
async signOut(token?: string) {
return this.signOutUseCase.execute(token);
}

async refreshTokens(token: string, device: DeviceMetadata) {
async refreshTokens(token: string | undefined, device: DeviceMetadata) {
return this.refreshTokensUseCase.execute(token, device);
}

Expand Down
3 changes: 2 additions & 1 deletion src/auth/application/controller/auth/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { ConfigService } from '@nestjs/config';
@ApiBaseController('auth', 'Auth')
export class AuthController {
private readonly isProduction: boolean = false;
private readonly domain: string | null = null;
private readonly domain?: string | null = null;

constructor(
private readonly facade: AuthFacade,
Expand Down Expand Up @@ -79,6 +79,7 @@ export class AuthController {
@PostLogoutSwagger()
async logout(@Res({ passthrough: true }) res: FastifyReply, @Req() req: FastifyRequest) {
const session = req.cookies?.['refresh'];

const response = await this.facade.signOut(session);

res.clearCookie('refresh', {
Expand Down
10 changes: 5 additions & 5 deletions src/auth/application/controller/oauth/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
import { BearerAuthGuard, OAuthGuard } from '@shared/guards';
import { AuthFacade } from '../../auth.facade';
import { getDeviceMeta } from '@core/auth/infrastructure/utils';
import { ApiBaseController, GetUserId, SkipContractHandle } from '@shared/decorators';
import { ApiBaseController, GetUserId, SkipContract } from '@shared/decorators';
import { ConfigService } from '@nestjs/config';

@ApiBaseController('auth/oauth', 'OAuth')
export class OAuthController {
private readonly isProduction: boolean = false;
private readonly domain: string | null = null;
private readonly domain?: string | null = null;

constructor(
private readonly facade: AuthFacade,
Expand All @@ -31,13 +31,13 @@ export class OAuthController {
@Get(':provider')
@OAuthLoginSwagger()
@UseGuards(OAuthGuard)
@SkipContractHandle()
@SkipContract()
async oauthLogin() {}

@Get(':provider/callback')
@OAuthCallbackSwagger()
@UseGuards(OAuthGuard)
@SkipContractHandle()
@SkipContract()
async oauthCallback(
@Query() query: { code?: string; state?: string },
@Param('provider') provider: 'google' | 'yandex' | 'github' | 'vkontakte',
Expand All @@ -63,7 +63,7 @@ export class OAuthController {

const baseUrl = `https://dev.${this.domain}`;

if (result.isSign) {
if (result.isSign && result.refresh) {
this.setRefreshCookie(res, result.refresh, result.expiresAt);
res.redirect(`${baseUrl}/oauth?${result.query.toString()}`, 302);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class ConfirmResetPasswordUseCase {
constructor(
@Inject(CACHE_SERVICE)
private readonly cacheService: ICacheService,
private readonly updatePasswordUserUseCase: UpdatePasswordUseCase,
private readonly updatePasswordUserUC: UpdatePasswordUseCase,
) {}

async execute(dto: PasswordResetConfirmDto) {
Expand Down Expand Up @@ -43,7 +43,7 @@ export class ConfirmResetPasswordUseCase {
}

const hashed = await argon.hash(dto.password);
const isUpdated = await this.updatePasswordUserUseCase.execute(dto.email, hashed);
const isUpdated = await this.updatePasswordUserUC.execute(dto.email, hashed);

if (!isUpdated) {
throw new BaseException(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IIdentitiesRepository } from '@core/auth/domain/repository';
import { IIdentityRepository } from '@core/auth/domain/repository';
import { FindUserQuery } from '@core/user';
import { HttpStatus, Inject, Injectable } from '@nestjs/common';
import { CACHE_SERVICE } from '@shared/adapters/cache/constants';
Expand All @@ -9,11 +9,11 @@ import { BaseException } from '@shared/error';
@Injectable()
export class ConnectOAuthProviderUseCase {
constructor(
@Inject('IIdentitiesRepository')
private readonly identitiesRepo: IIdentitiesRepository,
@Inject('IIdentityRepository')
private readonly identityRepo: IIdentityRepository,
@Inject(CACHE_SERVICE)
private readonly cacheService: ICacheService,
private readonly findUserQuery: FindUserQuery,
private readonly findUserQ: FindUserQuery,
) {}

async execute(dto: OAuthResponse, state: string) {
Expand All @@ -25,7 +25,7 @@ export class ConnectOAuthProviderUseCase {

await this.validateProviderNotConnected(user.id, dto.provider, dto.id);

await this.identitiesRepo.create({
await this.identityRepo.create({
userId: user.id,
avatarUrl: dto.avatar_url,
provider: dto.provider as any,
Expand Down Expand Up @@ -78,7 +78,7 @@ export class ConnectOAuthProviderUseCase {
}

private async getUser(userId: string) {
const result = await this.findUserQuery.execute({ id: userId });
const result = await this.findUserQ.execute({ id: userId });

if (!result?.user) {
throw new BaseException(
Expand All @@ -98,7 +98,7 @@ export class ConnectOAuthProviderUseCase {
provider: string,
providerUserId: string,
) {
const existingIdentity = await this.identitiesRepo.findByProvider(
const existingIdentity = await this.identityRepo.findByProvider(
provider as any,
providerUserId,
);
Expand All @@ -113,7 +113,7 @@ export class ConnectOAuthProviderUseCase {
);
}

const userIdentities = await this.identitiesRepo.findAllByUserId(userId);
const userIdentities = await this.identityRepo.findAllByUserId(userId);
const alreadyConnected = userIdentities.some((i) => i.provider === provider);

if (alreadyConnected) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import { createId } from '@paralleldrive/cuid2';
import { CACHE_SERVICE } from '@shared/adapters/cache/constants';
import { ICacheService } from '@shared/adapters/cache/ports';
import { BaseException } from '@shared/error';
import { IIdentitiesRepository } from '@core/auth/domain/repository';
import { IIdentityRepository } from '@core/auth/domain/repository';

@Injectable()
export class ConnectProviderUseCase {
constructor(
@Inject(CACHE_SERVICE)
private readonly cacheService: ICacheService,
@Inject('IIdentitiesRepository')
private readonly identitiesRepo: IIdentitiesRepository,
private readonly findUserQuery: FindUserQuery,
@Inject('IIdentityRepository')
private readonly identityRepo: IIdentityRepository,
private readonly findUserQ: FindUserQuery,
) {}

private readonly STATE_TTL = 180; // 3 минуты
Expand Down Expand Up @@ -57,7 +57,7 @@ export class ConnectProviderUseCase {
}

private async validateUser(userId: string) {
const entity = await this.findUserQuery.execute({ id: userId });
const entity = await this.findUserQ.execute({ id: userId });
if (!entity?.user) {
throw new BaseException(
{
Expand All @@ -70,7 +70,7 @@ export class ConnectProviderUseCase {
}

private async validateProviderNotConnected(userId: string, provider: string) {
const identities = await this.identitiesRepo.findAllByUserId(userId);
const identities = await this.identityRepo.findAllByUserId(userId);
const isConnected = identities.some((identity) => identity.provider === provider);

if (isConnected) {
Expand Down
Loading
Loading