Add field management module with database schema and validation
- Introduced `FieldModule` to manage cycles and plans, including read and write controllers. - Created database migrations for `company_settings`, `cycles`, `cycle_weekdays`, `cycle_destinations`, `plans`, `plan_destinations`, `plan_invoices`, and `plan_packing_slips` tables, including constraints and unique indexes. - Developed service and repository layers for handling cycle and plan data operations. - Added unit tests for the cycles and plans services, repositories, and controllers to ensure functionality and correctness. - Updated application module to include the new `FieldModule` for better organization.
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import type { AuthUser } from '../../../common/auth/auth-user';
|
||||
import type { PrivilegeAction } from '../../privileges/privilege-action';
|
||||
import { PrivilegesService } from '../../privileges/privileges.service';
|
||||
import {
|
||||
fieldPrivilegeKey,
|
||||
isFieldPurpose,
|
||||
type FieldPurpose,
|
||||
type FieldResource,
|
||||
} from './field-purpose';
|
||||
import {
|
||||
REQUIRE_FIELD_PRIVILEGE_KEY,
|
||||
type RequireFieldPrivilegeMeta,
|
||||
} from './field-privilege.decorator';
|
||||
|
||||
@Injectable()
|
||||
export class FieldPrivilegeGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly reflector: Reflector,
|
||||
private readonly privilegesService: PrivilegesService,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const required = this.reflector.getAllAndOverride<
|
||||
RequireFieldPrivilegeMeta | undefined
|
||||
>(REQUIRE_FIELD_PRIVILEGE_KEY, [context.getHandler(), context.getClass()]);
|
||||
|
||||
if (!required) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const request = context.switchToHttp().getRequest<{
|
||||
user?: AuthUser;
|
||||
body?: { purpose?: string };
|
||||
query?: { purpose?: string };
|
||||
}>();
|
||||
const user = request.user;
|
||||
if (!user) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
if (user.isSuperadmin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const purposeRaw = request.body?.purpose ?? request.query?.purpose;
|
||||
const allowed = await this.allowedPurposes(
|
||||
user.id,
|
||||
required.resource,
|
||||
required.action,
|
||||
);
|
||||
if (purposeRaw !== undefined && purposeRaw !== '') {
|
||||
if (!isFieldPurpose(purposeRaw)) {
|
||||
throw new ForbiddenException('Insufficient privilege');
|
||||
}
|
||||
if (!allowed.includes(purposeRaw)) {
|
||||
throw new ForbiddenException('Insufficient privilege');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (allowed.length === 0) {
|
||||
throw new ForbiddenException('Insufficient privilege');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async allowedPurposes(
|
||||
userId: string,
|
||||
resource: FieldResource,
|
||||
action: PrivilegeAction,
|
||||
): Promise<FieldPurpose[]> {
|
||||
const purposes: FieldPurpose[] = ['sales', 'logistics'];
|
||||
const matches: FieldPurpose[] = [];
|
||||
for (const purpose of purposes) {
|
||||
const ok = await this.privilegesService.checkPermission(
|
||||
userId,
|
||||
fieldPrivilegeKey(resource, purpose),
|
||||
action,
|
||||
);
|
||||
if (ok) {
|
||||
matches.push(purpose);
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user