门店账户多账号

This commit is contained in:
2026-07-12 12:24:34 +08:00
parent 06b1cb22e0
commit 54a15d6da7
39 changed files with 1962 additions and 311 deletions
@@ -13,6 +13,8 @@ export interface AuthUser {
clientApp: ClientApp;
sub: string;
phoneVerified: boolean;
/** Selected store after POST /shop/auth/select-store */
storeId?: bigint;
}
@Injectable()
@@ -41,6 +43,9 @@ export class JwtAuthGuard implements CanActivate {
clientApp,
sub: payload.sub,
phoneVerified: !!payload.phoneVerified,
...(payload.storeId != null && payload.storeId !== ''
? { storeId: BigInt(payload.storeId) }
: {}),
} satisfies AuthUser;
return true;
} catch (err) {
@@ -29,6 +29,9 @@ export class OptionalJwtAuthGuard implements CanActivate {
clientApp,
sub: payload.sub,
phoneVerified: !!payload.phoneVerified,
...(payload.storeId != null && payload.storeId !== ''
? { storeId: BigInt(payload.storeId) }
: {}),
} satisfies AuthUser;
} catch {
/* ignore invalid token */
@@ -0,0 +1,15 @@
import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common';
import type { AuthUser } from './jwt-auth.guard';
/** Shop business APIs require JWT claim storeId (after select-store). */
@Injectable()
export class ShopStoreGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
const user = req.user as AuthUser | undefined;
if (!user?.storeId) {
throw new ForbiddenException('请先选择门店');
}
return true;
}
}
@@ -0,0 +1,27 @@
import { ForbiddenException, Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.module';
import type { AuthUser } from '../guards/jwt-auth.guard';
@Injectable()
export class StoreMembershipService {
constructor(private readonly prisma: PrismaService) {}
async assertStoreMembership(accountId: bigint, storeId: bigint) {
const binding = await this.prisma.storeAccountStore.findUnique({
where: {
storeAccountId_storeId: { storeAccountId: accountId, storeId },
},
});
if (!binding) {
throw new ForbiddenException('无权访问该门店');
}
return binding;
}
requireShopStoreId(user: AuthUser): bigint {
if (!user.storeId) {
throw new ForbiddenException('请先选择门店');
}
return user.storeId;
}
}