full project: admin panel, backend modules, docs
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { IsOptional, IsInt, Min, Max } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class PaginationDto {
|
||||
@ApiPropertyOptional({ default: 1 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page?: number = 1;
|
||||
|
||||
@ApiPropertyOptional({ default: 20 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
limit?: number = 20;
|
||||
}
|
||||
|
||||
export interface PaginatedResult<T> {
|
||||
data: T[];
|
||||
meta: {
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
export function paginate<T>(
|
||||
data: T[],
|
||||
total: number,
|
||||
page: number,
|
||||
limit: number,
|
||||
): PaginatedResult<T> {
|
||||
return {
|
||||
data,
|
||||
meta: {
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit) || 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Injectable, CanActivate, ExecutionContext, ForbiddenException, SetMetadata } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
export const OWNERSHIP_KEY = 'ownership';
|
||||
export const Ownership = (param: string, model: string, field: string) =>
|
||||
SetMetadata(OWNERSHIP_KEY, { param, model, field });
|
||||
|
||||
@Injectable()
|
||||
export class OwnershipGuard implements CanActivate {
|
||||
constructor(
|
||||
private reflector: Reflector,
|
||||
private prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const metadata = this.reflector.get(OWNERSHIP_KEY, context.getHandler());
|
||||
if (!metadata) return true;
|
||||
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const resourceId = request.params[metadata.param];
|
||||
const userId = request.user.sub;
|
||||
|
||||
const record = await (this.prisma as any)[metadata.model].findUnique({
|
||||
where: { id: resourceId },
|
||||
});
|
||||
|
||||
if (!record || record[metadata.field] !== userId) {
|
||||
throw new ForbiddenException('No tienes permiso para modificar este recurso');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
|
||||
export const ROLES_KEY = 'roles';
|
||||
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { ROLES_KEY } from './roles.decorator';
|
||||
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
constructor(private reflector: Reflector) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
if (!requiredRoles) return true;
|
||||
const { user } = context.switchToHttp().getRequest();
|
||||
return requiredRoles.some((role) => user?.role === role);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user