feat(center): guild join and guild members APIs; stop auto-joining all guilds

This commit is contained in:
nav
2026-05-14 16:57:57 +00:00
parent ebc3571823
commit 1eb30348a2
2 changed files with 60 additions and 18 deletions

View File

@@ -1,4 +1,4 @@
import { Body, Controller, Get, Headers, Post, UnauthorizedException } from '@nestjs/common';
import { Body, Controller, Get, Headers, Param, Post, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto.login.dto';
import { RefreshDto } from './dto.refresh.dto';
@@ -30,6 +30,20 @@ export class AuthController {
return this.authService.listMyGuilds(token);
}
@Post('me/guilds/join')
joinGuild(@Headers('authorization') authorization: string | undefined, @Body() body: { guildNodeId?: string }) {
const token = authorization?.startsWith('Bearer ') ? authorization.slice(7) : '';
if (!token) throw new UnauthorizedException('missing bearer token');
return this.authService.joinGuild(token, String(body?.guildNodeId ?? ''));
}
@Get('guilds/:guildNodeId/members')
guildMembers(@Headers('authorization') authorization: string | undefined, @Param('guildNodeId') guildNodeId: string) {
const token = authorization?.startsWith('Bearer ') ? authorization.slice(7) : '';
if (!token) throw new UnauthorizedException('missing bearer token');
return this.authService.listGuildMembers(token, guildNodeId);
}
@Post('introspect')
introspect(@Body() body: { token?: string; guildNodeId?: string }) {
return this.authService.introspectGuildToken(body?.token ?? '', body?.guildNodeId ?? '');

View File

@@ -50,26 +50,10 @@ export class AuthService {
) {}
private async getUserGuildsAndTokens(userId: string, email: string) {
let memberships = await this.guildUserRepo.find({
const memberships = await this.guildUserRepo.find({
where: { userId, status: 'active' },
});
if (!memberships.length) {
const activeNodes = await this.guildNodeRepo.find({ where: { status: 'active' } });
if (activeNodes.length) {
await this.guildUserRepo.save(
activeNodes.map((n) =>
this.guildUserRepo.create({
userId,
guildNodeId: n.nodeId,
status: 'active',
}),
),
);
memberships = await this.guildUserRepo.find({ where: { userId, status: 'active' } });
}
}
const nodeIds = memberships.map((x) => x.guildNodeId);
if (!nodeIds.length) {
return { guilds: [], guildAccessTokens: [] as Array<{ guildNodeId: string; token: string; tokenType: string }> };
@@ -170,6 +154,50 @@ export class AuthService {
return this.getUserGuildsAndTokens(userId, email);
}
async joinGuild(accessToken: string, guildNodeId: string) {
const payload = this.verifyCenterAccessToken(accessToken);
const userId = String(payload.sub ?? '');
if (!userId) throw new UnauthorizedException('invalid access token');
const node = await this.guildNodeRepo.findOne({ where: { nodeId: guildNodeId, status: 'active' } });
if (!node) throw new UnauthorizedException('guild node not found or inactive');
const existed = await this.guildUserRepo.findOne({ where: { userId, guildNodeId } });
if (!existed) {
await this.guildUserRepo.save(
this.guildUserRepo.create({ userId, guildNodeId, status: 'active' }),
);
} else if (existed.status !== 'active') {
existed.status = 'active';
await this.guildUserRepo.save(existed);
}
return { status: 'ok' as const };
}
async listGuildMembers(accessToken: string, guildNodeId: string) {
const payload = this.verifyCenterAccessToken(accessToken);
const userId = String(payload.sub ?? '');
if (!userId) throw new UnauthorizedException('invalid access token');
const selfMembership = await this.guildUserRepo.findOne({ where: { userId, guildNodeId, status: 'active' } });
if (!selfMembership) throw new UnauthorizedException('not a guild member');
const members = await this.guildUserRepo.find({ where: { guildNodeId, status: 'active' } });
const userIds = [...new Set(members.map((m) => m.userId))];
if (!userIds.length) return [];
const users = await this.userRepo
.createQueryBuilder('u')
.where('u.id IN (:...userIds)', { userIds })
.getMany();
const userMap = new Map(users.map((u) => [u.id, u]));
return members
.map((m) => ({ userId: m.userId, email: userMap.get(m.userId)?.email ?? '', status: m.status }))
.filter((x) => !!x.email);
}
verifyCenterAccessToken(accessToken: string): jwt.JwtPayload {
try {
const payload = jwt.verify(accessToken, process.env.FABRIC_BACKEND_CENTER_JWT_ACCESS_SECRET as string) as jwt.JwtPayload;