@@ -3,16 +3,20 @@ import {
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
SetMetadata,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import {
|
||||
HQ_PERMISSION_CATALOG,
|
||||
HQ_ROLE_DEFAULT_PERMISSIONS,
|
||||
computeHqEffectivePermissionKeys,
|
||||
expandHqPermissionKeys,
|
||||
hasAnySystemSettingsPermission,
|
||||
type HqAdminRoleValue,
|
||||
type HqPermissionKey,
|
||||
} from '@dukang/shared-types';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.module';
|
||||
import type { AuthUser } from './jwt-auth.guard';
|
||||
|
||||
@@ -23,6 +27,37 @@ export const RequireHqPermissions = (...keys: string[]) =>
|
||||
export const RequireAnySystemSettings = () =>
|
||||
SetMetadata(HQ_PERMISSIONS_KEY, ['__any_system_settings__']);
|
||||
|
||||
/** null = 全国;[] = 无可见城市 */
|
||||
export type HqCityScope = bigint[] | null;
|
||||
|
||||
export function hqStoreCityWhere(scope: HqCityScope): Prisma.StoreWhereInput | undefined {
|
||||
if (scope === null) return undefined;
|
||||
if (!scope.length) return { id: { equals: BigInt(0) } };
|
||||
return { cityId: { in: scope } };
|
||||
}
|
||||
|
||||
export function mergeHqStoreCityWhere(
|
||||
where: Prisma.StoreWhereInput,
|
||||
scope: HqCityScope,
|
||||
requestedCityId?: string,
|
||||
): Prisma.StoreWhereInput {
|
||||
if (requestedCityId) {
|
||||
const cityId = BigInt(requestedCityId);
|
||||
assertHqCityInScope(scope, cityId);
|
||||
return { AND: [where, { cityId }] };
|
||||
}
|
||||
const scoped = hqStoreCityWhere(scope);
|
||||
if (!scoped) return where;
|
||||
return { AND: [where, scoped] };
|
||||
}
|
||||
|
||||
export function assertHqCityInScope(scope: HqCityScope, cityId: bigint) {
|
||||
if (scope === null) return;
|
||||
if (!scope.some((id) => id === cityId)) {
|
||||
throw new ForbiddenException('无权访问该城市的门店');
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class HqPermissionsResolver {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
@@ -41,39 +76,45 @@ export class HqPermissionsResolver {
|
||||
async resolveAccess(actorId: bigint): Promise<{
|
||||
keys: HqPermissionKey[];
|
||||
isSuperAdmin: boolean;
|
||||
adminRole: HqAdminRoleValue;
|
||||
}> {
|
||||
const account = await this.loadActiveAccount(actorId);
|
||||
const adminRole = account.adminRole as HqAdminRoleValue;
|
||||
|
||||
const userRows = await this.prisma.hqAccountPermission.findMany({
|
||||
where: { hqAccountId: actorId },
|
||||
select: { permissionKey: true },
|
||||
select: { permissionKey: true, effect: true },
|
||||
});
|
||||
const userKeys = userRows.map((r) => r.permissionKey);
|
||||
const grantKeys = userRows
|
||||
.filter((r) => r.effect !== 'DENY')
|
||||
.map((r) => r.permissionKey);
|
||||
const denyKeys = userRows.filter((r) => r.effect === 'DENY').map((r) => r.permissionKey);
|
||||
|
||||
if (account.adminRole === 'SUPER_ADMIN') {
|
||||
// 超管拥有权限目录内全部项(含后续新增),另含危险操作与用户级附加项
|
||||
if (adminRole === 'SUPER_ADMIN') {
|
||||
return {
|
||||
isSuperAdmin: true,
|
||||
adminRole,
|
||||
keys: expandHqPermissionKeys([
|
||||
...HQ_PERMISSION_CATALOG.map((p) => p.key),
|
||||
...userKeys,
|
||||
...grantKeys,
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
const roleRows = await this.prisma.hqRolePermission.findMany({
|
||||
where: { adminRole: account.adminRole },
|
||||
where: { adminRole },
|
||||
select: { permissionKey: true },
|
||||
});
|
||||
|
||||
const roleKeys =
|
||||
roleRows.length > 0
|
||||
? roleRows.map((r) => r.permissionKey)
|
||||
: [...(HQ_ROLE_DEFAULT_PERMISSIONS[account.adminRole] ?? [])];
|
||||
: [...(HQ_ROLE_DEFAULT_PERMISSIONS[adminRole] ?? [])];
|
||||
|
||||
return {
|
||||
isSuperAdmin: false,
|
||||
keys: expandHqPermissionKeys([...roleKeys, ...userKeys]),
|
||||
adminRole,
|
||||
keys: computeHqEffectivePermissionKeys(roleKeys, grantKeys, denyKeys),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -81,6 +122,35 @@ export class HqPermissionsResolver {
|
||||
const { keys } = await this.resolveAccess(actorId);
|
||||
return keys;
|
||||
}
|
||||
|
||||
async resolveCityScope(actorId: bigint): Promise<HqCityScope> {
|
||||
const account = await this.loadActiveAccount(actorId);
|
||||
if (account.adminRole === 'SUPER_ADMIN') return null;
|
||||
const rows = await this.prisma.hqAccountCity.findMany({
|
||||
where: { hqAccountId: actorId },
|
||||
select: { cityId: true },
|
||||
});
|
||||
if (!rows.length) {
|
||||
return account.adminRole === 'CITY_STORE_SERVICE' ? [] : null;
|
||||
}
|
||||
return rows.map((r) => r.cityId);
|
||||
}
|
||||
|
||||
async assertStoreCityInScope(actorId: bigint, cityId: bigint) {
|
||||
const scope = await this.resolveCityScope(actorId);
|
||||
assertHqCityInScope(scope, cityId);
|
||||
}
|
||||
|
||||
async assertStoreIdInScope(actorId: bigint, storeId: bigint) {
|
||||
const store = await this.prisma.store.findUnique({
|
||||
where: { id: storeId },
|
||||
select: { cityId: true },
|
||||
});
|
||||
if (!store) {
|
||||
throw new NotFoundException('门店不存在');
|
||||
}
|
||||
await this.assertStoreCityInScope(actorId, store.cityId);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
||||
Reference in New Issue
Block a user