Initial commit: Meshkee CMS API
NestJS backend with Prisma, Docker Compose for Postgres/Redis, and deploy docs for the production VM.
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import { IsIn, IsOptional } from 'class-validator';
|
||||
|
||||
export class ListRolesDto {
|
||||
@IsOptional()
|
||||
@IsIn(['global', 'team'])
|
||||
scope?: 'global' | 'team' = 'global';
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||
import { AuthUser } from '../auth/auth.types';
|
||||
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import { ListRolesDto } from './dto/list-roles.dto';
|
||||
import { RolesService } from './roles.service';
|
||||
|
||||
@Controller('roles')
|
||||
export class RolesController {
|
||||
constructor(private readonly rolesService: RolesService) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
list(@Query() query: ListRolesDto, @CurrentUser() user: AuthUser) {
|
||||
return this.rolesService.list(query, user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { RolesController } from './roles.controller';
|
||||
import { RolesService } from './roles.service';
|
||||
|
||||
@Module({
|
||||
imports: [AuthModule],
|
||||
controllers: [RolesController],
|
||||
providers: [RolesService],
|
||||
})
|
||||
export class RolesModule {}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { ForbiddenException, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
ASSIGNABLE_GLOBAL_ROLES,
|
||||
ASSIGNABLE_TEAM_ROLES,
|
||||
AuthUser,
|
||||
} from '../auth/auth.types';
|
||||
import { PermissionsService } from '../auth/permissions.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { ListRolesDto } from './dto/list-roles.dto';
|
||||
|
||||
@Injectable()
|
||||
export class RolesService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly permissions: PermissionsService,
|
||||
) {}
|
||||
|
||||
async list(query: ListRolesDto, actor: AuthUser) {
|
||||
const scope = query.scope ?? 'global';
|
||||
|
||||
if (scope === 'global') {
|
||||
if (!(await this.permissions.isSuperAdmin(actor.id))) {
|
||||
throw new ForbiddenException('Super admin access required');
|
||||
}
|
||||
|
||||
const roles = await this.prisma.role.findMany({
|
||||
where: { slug: { in: [...ASSIGNABLE_GLOBAL_ROLES] } },
|
||||
orderBy: { name: 'asc' },
|
||||
select: { id: true, slug: true, name: true, description: true },
|
||||
});
|
||||
|
||||
return { scope, items: roles };
|
||||
}
|
||||
|
||||
const canListTeamRoles = await this.canListTeamRoles(actor);
|
||||
if (!canListTeamRoles) {
|
||||
throw new ForbiddenException('You cannot list team roles');
|
||||
}
|
||||
|
||||
const roles = await this.prisma.role.findMany({
|
||||
where: { slug: { in: [...ASSIGNABLE_TEAM_ROLES] } },
|
||||
orderBy: { name: 'asc' },
|
||||
select: { id: true, slug: true, name: true, description: true },
|
||||
});
|
||||
|
||||
return { scope, items: roles };
|
||||
}
|
||||
|
||||
private async canListTeamRoles(actor: AuthUser): Promise<boolean> {
|
||||
if (await this.permissions.isSuperAdmin(actor.id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (actor.roles.includes('business_owner')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const business of actor.businesses) {
|
||||
if (business.permissions.includes('business.team.read')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user