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:
Ali Reza
2026-07-21 17:52:36 +03:30
commit bb59d5e9ba
254 changed files with 37031 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
import { IsIn, IsOptional } from 'class-validator';
export class ListRolesDto {
@IsOptional()
@IsIn(['global', 'team'])
scope?: 'global' | 'team' = 'global';
}
+17
View File
@@ -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);
}
}
+11
View File
@@ -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 {}
+66
View File
@@ -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;
}
}