Initial commit: LACA parking management system

This commit is contained in:
2025-08-13 10:05:36 +07:00
commit 8b07467b61
275 changed files with 66828 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
import {
createParamDecorator,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common';
import { JwtAccessTokenPayload } from './type';
export type AuthUserPayloadType = JwtAccessTokenPayload;
export const AuthUser = createParamDecorator(
async (_: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return user;
},
);
export const AuthTenant = createParamDecorator(
async (_: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
if (user.type !== 'tenant') throw new UnauthorizedException();
return user;
},
);
export const AuthAdmin = createParamDecorator(
async (_: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
if (user.type !== 'admin') throw new UnauthorizedException();
return user;
},
);

View File

@@ -0,0 +1,20 @@
export enum eUserType {
USER,
TENANT,
ADMIN,
}
export type TokenUserType = eUserType.ADMIN | eUserType.TENANT | eUserType.USER;
export type JwtAccessTokenPayload = {
id: number;
roleId: number;
tokenId: string;
type: TokenUserType;
};
export type JwtRefreshTokenPayload = {
id: number;
tokenId: string;
type: TokenUserType;
};

View File

@@ -0,0 +1,57 @@
import { ApiExtraModels, ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsNumber, IsOptional, IsString, Max, Min } from 'class-validator';
export enum Order {
Desc = 'DESC',
Asc = 'ASC',
}
@ApiExtraModels()
export class PagingReqDto {
@ApiProperty({ example: 'abc', required: false, type: String })
@IsOptional()
@IsString()
search? = '';
@ApiProperty({ example: 20, required: false, type: Number })
@IsOptional()
@IsNumber()
@Min(0)
@Max(100)
@Type(() => Number)
pageSize = 10;
@ApiProperty({ example: 20, required: false, type: Number })
@IsOptional()
@IsNumber()
@Type(() => Number)
@Min(1)
pageNumber = 1;
@ApiProperty({
name: 'sort',
type: 'object',
additionalProperties: { type: 'string' },
description: 'Data object with key-value pairs',
required: false,
example: {
created_at: 'asc',
},
})
@IsOptional()
sort: Record<string, string> = {
created_at: 'asc',
};
}
export class PagingRes {
@ApiProperty({ example: 100 })
pages: number;
@ApiProperty({ example: 1000 })
count: number;
@ApiProperty({ example: 5 })
pageNumber: number;
}

View File

@@ -0,0 +1,34 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
HttpException,
} from '@nestjs/common';
import { Observable, catchError } from 'rxjs';
@Injectable()
export class TransformErrorInterceptor implements NestInterceptor {
private readonly logger = new Logger(TransformErrorInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// next.handle() is an Observable of the controller's result value
return next.handle().pipe(
catchError((error) => {
const response = error.response || {};
const { statusCode, message } = response;
throw new HttpException(
{
status: statusCode,
code: error['code'],
details: {
type: 'error',
message: message,
},
},
statusCode,
);
}),
);
}
}

View File

@@ -0,0 +1,41 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
StreamableFile,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { DEFAULT_SUCCESS_MSG } from 'src/share/constans';
export interface Response<T> {
statusCode: number;
message: string;
data: T;
}
@Injectable()
export class TransformResponseInterceptor<T>
implements NestInterceptor<T, Response<T> | StreamableFile>
{
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<Response<any> | StreamableFile> {
return next.handle().pipe(
map((data) => {
if (data instanceof StreamableFile) {
return data;
}
return {
statusCode:
<number>data?.statusCode ||
<number>context.switchToHttp().getResponse().statusCode,
message: <string>data?.message || DEFAULT_SUCCESS_MSG,
data: data,
};
}),
);
}
}

View File

@@ -0,0 +1,28 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
StreamableFile,
} from '@nestjs/common';
import { Observable } from 'rxjs';
export interface Response<T> {
statusCode: number;
message: string;
data: T;
}
@Injectable()
export class UUIDRequestInterceptor<T>
implements NestInterceptor<T, Response<T> | StreamableFile>
{
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<Response<any> | StreamableFile> {
const request = context.switchToHttp().getRequest();
request.headers['x-request-id'] = 'asf';
return next.handle();
}
}