@@ -0,0 +1,7 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
import type { PartnerPermissionKey } from '@dukang/shared-types';
|
||||
|
||||
export const PARTNER_PERMISSIONS_KEY = 'partner_permissions';
|
||||
|
||||
export const RequirePartnerPermissions = (...permissions: PartnerPermissionKey[]) =>
|
||||
SetMetadata(PARTNER_PERMISSIONS_KEY, permissions);
|
||||
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import type { PartnerPermissionKey } from '@dukang/shared-types';
|
||||
import { PARTNER_PERMISSIONS_KEY } from '../decorators/partner-permission.decorator';
|
||||
import { PrismaService } from '../prisma/prisma.module';
|
||||
import { AuthUser, JwtAuthGuard } from './jwt-auth.guard';
|
||||
|
||||
@Injectable()
|
||||
export class PartnerPermissionGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly jwtAuthGuard: JwtAuthGuard,
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly reflector: Reflector,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
await this.jwtAuthGuard.canActivate(context);
|
||||
const req = context.switchToHttp().getRequest();
|
||||
const user = req.user as AuthUser;
|
||||
if (user.actorType !== 'PARTNER') {
|
||||
throw new ForbiddenException('仅合伙人可操作');
|
||||
}
|
||||
const account = await this.prisma.partnerAccount.findUniqueOrThrow({
|
||||
where: { id: user.actorId },
|
||||
});
|
||||
if (account.isPrimary === 1) return true;
|
||||
|
||||
const required = this.reflector.getAllAndOverride<PartnerPermissionKey[]>(
|
||||
PARTNER_PERMISSIONS_KEY,
|
||||
[context.getHandler(), context.getClass()],
|
||||
);
|
||||
if (!required?.length) return true;
|
||||
|
||||
const perms = Array.isArray(account.permissions)
|
||||
? (account.permissions as string[])
|
||||
: [];
|
||||
if (required.some((p) => perms.includes(p))) return true;
|
||||
throw new ForbiddenException('当前子账号无此操作权限');
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import { PhoneVerifiedGuard } from '../../common/guards/phone-verified.guard';
|
||||
import { OptionalJwtAuthGuard } from '../../common/guards/optional-jwt-auth.guard';
|
||||
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
|
||||
import { PartnerPrimaryGuard } from '../../common/guards/partner-primary.guard';
|
||||
import { PartnerPermissionGuard } from '../../common/guards/partner-permission.guard';
|
||||
import { ShopStoreGuard } from '../../common/guards/shop-store.guard';
|
||||
import { StoreMembershipService } from '../../common/guards/store-membership.service';
|
||||
|
||||
@@ -54,6 +55,7 @@ import { StoreMembershipService } from '../../common/guards/store-membership.ser
|
||||
OptionalJwtAuthGuard,
|
||||
HqAuthGuard,
|
||||
PartnerPrimaryGuard,
|
||||
PartnerPermissionGuard,
|
||||
ShopStoreGuard,
|
||||
],
|
||||
exports: [
|
||||
@@ -68,6 +70,7 @@ import { StoreMembershipService } from '../../common/guards/store-membership.ser
|
||||
OptionalJwtAuthGuard,
|
||||
HqAuthGuard,
|
||||
PartnerPrimaryGuard,
|
||||
PartnerPermissionGuard,
|
||||
ShopStoreGuard,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -263,6 +263,8 @@ export class PartnerMeController {
|
||||
staffRole: account.staffRole ?? undefined,
|
||||
permissions: Array.isArray(account.permissions) ? account.permissions : undefined,
|
||||
primaryAccountId: primary.id.toString(),
|
||||
primaryPhone: primary.phone,
|
||||
primaryName: primary.name,
|
||||
companyName: primary.companyName ?? undefined,
|
||||
hasWechat: !!account.wxOpenId,
|
||||
};
|
||||
|
||||
@@ -3,6 +3,8 @@ import { StoreService } from './store.service';
|
||||
import { RedeemService } from '../redeem/redeem.service';
|
||||
import { JwtAuthGuard, AuthUser } from '../../common/guards/jwt-auth.guard';
|
||||
import { PartnerPrimaryGuard } from '../../common/guards/partner-primary.guard';
|
||||
import { RequirePartnerPermissions } from '../../common/decorators/partner-permission.decorator';
|
||||
import { PartnerPermissionGuard } from '../../common/guards/partner-permission.guard';
|
||||
import { ShopStoreGuard } from '../../common/guards/shop-store.guard';
|
||||
import { CurrentUser } from '../../common/decorators/current-user.decorator';
|
||||
|
||||
@@ -76,7 +78,8 @@ export class PartnerStoreController {
|
||||
}
|
||||
|
||||
@Controller('partner/dashboard')
|
||||
@UseGuards(JwtAuthGuard, PartnerPrimaryGuard)
|
||||
@UseGuards(JwtAuthGuard, PartnerPermissionGuard)
|
||||
@RequirePartnerPermissions('store:create', 'store:manage', 'order:view', 'warehouse:manage')
|
||||
export class PartnerDashboardController {
|
||||
constructor(private readonly storeService: StoreService) {}
|
||||
|
||||
|
||||
@@ -468,33 +468,56 @@ export class StoreService {
|
||||
|
||||
async partnerDashboard(partnerAccountId: bigint) {
|
||||
const { account, primaryId } = await this.resolvePartnerScope(partnerAccountId);
|
||||
this.assertPrimaryAccount(account);
|
||||
const partnerStoreIds = await this.prisma.store.findMany({
|
||||
where: { partnerAccountId: primaryId },
|
||||
select: { id: true },
|
||||
const primaryAccount = await this.prisma.partnerAccount.findUniqueOrThrow({
|
||||
where: { id: primaryId },
|
||||
});
|
||||
const storeIds = partnerStoreIds.map((s) => s.id);
|
||||
const [storeCount, orderCount, recentStores, pendingAuditCount, recentNotices] = await Promise.all([
|
||||
this.prisma.store.count({ where: { partnerAccountId: primaryId } }),
|
||||
this.prisma.order.count({
|
||||
where: await this.partnerCityService.buildPartnerOrderWhere(primaryId),
|
||||
}),
|
||||
const orderCount = await this.prisma.order.count({
|
||||
where: await this.partnerCityService.buildPartnerOrderWhere(primaryId),
|
||||
});
|
||||
|
||||
let storeWhere: { partnerAccountId: bigint; id?: { in: bigint[] } } = {
|
||||
partnerAccountId: primaryId,
|
||||
};
|
||||
if (this.isSubAccount(account)) {
|
||||
const storeIds = await this.getStoreIdsCreatedByAccount(partnerAccountId);
|
||||
if (storeIds.length === 0) {
|
||||
return {
|
||||
storeCount: 0,
|
||||
orderCount,
|
||||
companyName: primaryAccount.companyName ?? '',
|
||||
recentStores: [],
|
||||
pendingAuditCount: 0,
|
||||
notifications: [],
|
||||
};
|
||||
}
|
||||
storeWhere = { partnerAccountId: primaryId, id: { in: storeIds } };
|
||||
}
|
||||
|
||||
const storeIdsForNotices = (
|
||||
await this.prisma.store.findMany({
|
||||
where: storeWhere,
|
||||
select: { id: true },
|
||||
})
|
||||
).map((s) => s.id);
|
||||
|
||||
const [storeCount, recentStores, pendingAuditCount, recentNotices] = await Promise.all([
|
||||
this.prisma.store.count({ where: storeWhere }),
|
||||
this.prisma.store.findMany({
|
||||
where: { partnerAccountId: primaryId },
|
||||
where: storeWhere,
|
||||
select: { id: true, name: true, status: true, auditStatus: true, createdAt: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 10,
|
||||
}),
|
||||
this.prisma.store.count({
|
||||
where: { partnerAccountId: primaryId, auditStatus: 'PENDING' },
|
||||
where: { ...storeWhere, auditStatus: 'PENDING' },
|
||||
}),
|
||||
storeIds.length === 0
|
||||
storeIdsForNotices.length === 0
|
||||
? Promise.resolve([])
|
||||
: this.prisma.commonEvent.findMany({
|
||||
where: {
|
||||
eventType: 'STORE_AUDIT',
|
||||
refType: 'STORE',
|
||||
refId: { in: storeIds },
|
||||
refId: { in: storeIdsForNotices },
|
||||
status: { in: ['APPROVED', 'REJECTED'] },
|
||||
actorType: 'HQ',
|
||||
},
|
||||
@@ -515,7 +538,7 @@ export class StoreService {
|
||||
return {
|
||||
storeCount,
|
||||
orderCount,
|
||||
companyName: account.companyName ?? '',
|
||||
companyName: primaryAccount.companyName ?? '',
|
||||
recentStores: serializeBigInt(recentStores),
|
||||
pendingAuditCount,
|
||||
notifications: serializeBigInt(
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { Request } from 'express';
|
||||
import { TradeService } from './trade.service';
|
||||
import { JwtAuthGuard, AuthUser } from '../../common/guards/jwt-auth.guard';
|
||||
import { PartnerPrimaryGuard } from '../../common/guards/partner-primary.guard';
|
||||
import { RequirePartnerPermissions } from '../../common/decorators/partner-permission.decorator';
|
||||
import { PartnerPermissionGuard } from '../../common/guards/partner-permission.guard';
|
||||
import { CurrentUser } from '../../common/decorators/current-user.decorator';
|
||||
import {
|
||||
PartnerProxyOrderCreateDto,
|
||||
@@ -70,7 +72,8 @@ export class TradeController {
|
||||
}
|
||||
|
||||
@Controller('partner/orders')
|
||||
@UseGuards(JwtAuthGuard, PartnerPrimaryGuard)
|
||||
@UseGuards(JwtAuthGuard, PartnerPermissionGuard)
|
||||
@RequirePartnerPermissions('order:view', 'warehouse:manage')
|
||||
export class PartnerOrderController {
|
||||
constructor(private readonly tradeService: TradeService) {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user