This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS "admin_app_permissions" (
|
||||||
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"admin_id" UUID NOT NULL,
|
||||||
|
"app_id" UUID NOT NULL,
|
||||||
|
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "admin_app_permissions_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "admin_app_permissions_admin_id_app_id_key"
|
||||||
|
ON "admin_app_permissions"("admin_id", "app_id");
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS "admin_app_permissions_app_id_idx"
|
||||||
|
ON "admin_app_permissions"("app_id");
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint WHERE conname = 'admin_app_permissions_admin_id_fkey'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE "admin_app_permissions"
|
||||||
|
ADD CONSTRAINT "admin_app_permissions_admin_id_fkey"
|
||||||
|
FOREIGN KEY ("admin_id") REFERENCES "admins"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint WHERE conname = 'admin_app_permissions_app_id_fkey'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE "admin_app_permissions"
|
||||||
|
ADD CONSTRAINT "admin_app_permissions_app_id_fkey"
|
||||||
|
FOREIGN KEY ("app_id") REFERENCES "apps"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
@@ -74,11 +74,25 @@ model Admin {
|
|||||||
sessions AdminSession[]
|
sessions AdminSession[]
|
||||||
auditLogs AdminAuditLog[]
|
auditLogs AdminAuditLog[]
|
||||||
sshKeys AdminSshKey[]
|
sshKeys AdminSshKey[]
|
||||||
|
appPermissions AdminAppPermission[]
|
||||||
|
|
||||||
@@index([isActive, role])
|
@@index([isActive, role])
|
||||||
@@map("admins")
|
@@map("admins")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model AdminAppPermission {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
adminId String @map("admin_id") @db.Uuid
|
||||||
|
appId String @map("app_id") @db.Uuid
|
||||||
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
||||||
|
admin Admin @relation(fields: [adminId], references: [id], onDelete: Cascade)
|
||||||
|
app App @relation(fields: [appId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([adminId, appId])
|
||||||
|
@@index([appId])
|
||||||
|
@@map("admin_app_permissions")
|
||||||
|
}
|
||||||
|
|
||||||
model AdminSshKey {
|
model AdminSshKey {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
adminId String @map("admin_id") @db.Uuid
|
adminId String @map("admin_id") @db.Uuid
|
||||||
@@ -142,6 +156,7 @@ model App {
|
|||||||
releases AppRelease[]
|
releases AppRelease[]
|
||||||
deployments DeploymentJob[]
|
deployments DeploymentJob[]
|
||||||
runtimeProfiles RuntimeEnvProfile[]
|
runtimeProfiles RuntimeEnvProfile[]
|
||||||
|
adminPermissions AdminAppPermission[]
|
||||||
|
|
||||||
@@map("apps")
|
@@map("apps")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ export class AdminAuthService {
|
|||||||
role: true,
|
role: true,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
passwordHash: true,
|
passwordHash: true,
|
||||||
|
appPermissions: {
|
||||||
|
select: { appId: true },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,6 +60,7 @@ export class AdminAuthService {
|
|||||||
email: admin.email,
|
email: admin.email,
|
||||||
name: admin.name,
|
name: admin.name,
|
||||||
role: admin.role,
|
role: admin.role,
|
||||||
|
appPermissions: admin.appPermissions.map((permission) => permission.appId),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, Param, Patch, Post, Put } from '@nestjs/common';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CreateAdminSshKeyDto,
|
CreateAdminSshKeyDto,
|
||||||
CreateAdminDto,
|
CreateAdminDto,
|
||||||
ResetAdminPasswordDto,
|
ResetAdminPasswordDto,
|
||||||
|
UpdateAdminAppPermissionsDto,
|
||||||
UpdateAdminRoleDto,
|
UpdateAdminRoleDto,
|
||||||
UpdateAdminStatusDto,
|
UpdateAdminStatusDto,
|
||||||
} from './dto';
|
} from './dto';
|
||||||
@@ -46,6 +47,16 @@ export class AdminsController {
|
|||||||
return this.adminsService.resetPassword(id, body);
|
return this.adminsService.resetPassword(id, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get(':id/app-permissions')
|
||||||
|
listAppPermissions(@Param('id') id: string) {
|
||||||
|
return this.adminsService.listAppPermissions(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':id/app-permissions')
|
||||||
|
updateAppPermissions(@Param('id') id: string, @Body() body: UpdateAdminAppPermissionsDto) {
|
||||||
|
return this.adminsService.updateAppPermissions(id, body);
|
||||||
|
}
|
||||||
|
|
||||||
@Post(':id/ssh-keys')
|
@Post(':id/ssh-keys')
|
||||||
createSshKey(@Param('id') id: string, @Body() body: CreateAdminSshKeyDto) {
|
createSshKey(@Param('id') id: string, @Body() body: CreateAdminSshKeyDto) {
|
||||||
return this.adminsService.createSshKey(id, body);
|
return this.adminsService.createSshKey(id, body);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
CreateAdminSshKeyDto,
|
CreateAdminSshKeyDto,
|
||||||
CreateAdminDto,
|
CreateAdminDto,
|
||||||
ResetAdminPasswordDto,
|
ResetAdminPasswordDto,
|
||||||
|
UpdateAdminAppPermissionsDto,
|
||||||
UpdateAdminRoleDto,
|
UpdateAdminRoleDto,
|
||||||
UpdateAdminStatusDto,
|
UpdateAdminStatusDto,
|
||||||
} from './dto';
|
} from './dto';
|
||||||
@@ -63,6 +64,9 @@ export class AdminsService {
|
|||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
select: adminSshKeySelect,
|
select: adminSshKeySelect,
|
||||||
},
|
},
|
||||||
|
appPermissions: {
|
||||||
|
select: { appId: true },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -132,6 +136,55 @@ export class AdminsService {
|
|||||||
return { admin };
|
return { admin };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async listAppPermissions(id: string) {
|
||||||
|
await this.ensureExists(id);
|
||||||
|
|
||||||
|
const permissions = await this.prisma.adminAppPermission.findMany({
|
||||||
|
where: { adminId: id },
|
||||||
|
orderBy: { createdAt: 'asc' },
|
||||||
|
select: { appId: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
return { appIds: permissions.map((permission) => permission.appId) };
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAppPermissions(id: string, body: UpdateAdminAppPermissionsDto) {
|
||||||
|
const admin = await this.prisma.admin.findUnique({
|
||||||
|
where: { id },
|
||||||
|
select: { id: true, role: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!admin) {
|
||||||
|
throw new NotFoundException('관리자를 찾을 수 없습니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (admin.role === AdminRole.OWNER) {
|
||||||
|
throw new BadRequestException('오너 권한은 앱 권한을 개별 설정하지 않습니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const appIds = [...new Set(body.appIds.filter(Boolean))];
|
||||||
|
const existingApps = await this.prisma.app.findMany({
|
||||||
|
where: { id: { in: appIds } },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
const existingAppIds = new Set(existingApps.map((app) => app.id));
|
||||||
|
const validAppIds = appIds.filter((appId) => existingAppIds.has(appId));
|
||||||
|
|
||||||
|
await this.prisma.$transaction([
|
||||||
|
this.prisma.adminAppPermission.deleteMany({ where: { adminId: id } }),
|
||||||
|
...(validAppIds.length
|
||||||
|
? [
|
||||||
|
this.prisma.adminAppPermission.createMany({
|
||||||
|
data: validAppIds.map((appId) => ({ adminId: id, appId })),
|
||||||
|
skipDuplicates: true,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { appIds: validAppIds };
|
||||||
|
}
|
||||||
|
|
||||||
async createSshKey(adminId: string, body: CreateAdminSshKeyDto) {
|
async createSshKey(adminId: string, body: CreateAdminSshKeyDto) {
|
||||||
await this.ensureExists(adminId);
|
await this.ensureExists(adminId);
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -1,4 +1,4 @@
|
|||||||
import { IsBoolean, IsEmail, IsEnum, IsOptional, IsString, MinLength } from 'class-validator';
|
import { IsArray, IsBoolean, IsEmail, IsEnum, IsOptional, IsString, IsUUID, MinLength } from 'class-validator';
|
||||||
import { AdminRole } from './admin-role';
|
import { AdminRole } from './admin-role';
|
||||||
|
|
||||||
export class CreateAdminDto {
|
export class CreateAdminDto {
|
||||||
@@ -44,3 +44,9 @@ export class CreateAdminSshKeyDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
giteaUsername?: string;
|
giteaUsername?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdateAdminAppPermissionsDto {
|
||||||
|
@IsArray()
|
||||||
|
@IsUUID('4', { each: true })
|
||||||
|
appIds!: string[];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user