47fb81ad31
- contracts: add NOT_FOUND error code (framework-level 404 for unknown routes); Error enum + regen committed - api: 404 default mapping in AllExceptionsFilter now NOT_FOUND (was ALBUM_NOT_FOUND); business 404s unchanged via explicit ApiException codes - api: extract configureApp (prefix/CORS/x-powered-by) shared by main-cn bootstrap and e2e; disable x-powered-by, asserted in e2e - api: move parseCursor/parseLimit (+page-size bounds) from albums.controller to shared/pagination; members/uploads/public re-pointed - infra: bind compose published ports to 127.0.0.1 (mysql/redis/minio); .env.example endpoints follow suit - e2e: unknown-route 404 NOT_FOUND + x-powered-by absent cases added (api e2e 120 -> 122) Co-Authored-By: Claude Code <noreply@anthropic.com>
70 lines
2.0 KiB
TypeScript
70 lines
2.0 KiB
TypeScript
// services/api/src/albums/members.controller.ts —— /albums/{albumId}/members
|
||
// 端点(Task 17)。方法名与契约 operationId 一一对应(addAlbumMember/
|
||
// listAlbumMembers/removeAlbumMember)。控制器级 SessionGuard:三端点全需
|
||
// 认证。控制器只做认证用户透传 + 查询参数规整(shared/pagination 的
|
||
// parseLimit/parseCursor,同一契约 bounds/游标语义);权限与 HTTP 映射由
|
||
// MembersService 抛 ApiException(404 ALBUM_NOT_FOUND / 403 AUTH_FORBIDDEN /
|
||
// 409 MEMBER_CONFLICT)。
|
||
import {
|
||
Body,
|
||
Controller,
|
||
Delete,
|
||
Get,
|
||
HttpCode,
|
||
HttpStatus,
|
||
Param,
|
||
Post,
|
||
Query,
|
||
Req,
|
||
UseGuards,
|
||
} from '@nestjs/common';
|
||
import { AuthenticatedRequest, SessionGuard } from '../auth/session.guard';
|
||
import { userOf } from './albums.controller';
|
||
import { parseCursor, parseLimit } from '../shared/pagination';
|
||
import { MemberAddRequestDto } from './dto';
|
||
import {
|
||
type MemberListResponse,
|
||
type MemberResponse,
|
||
MembersService,
|
||
} from './members.service';
|
||
|
||
@Controller('albums/:albumId/members')
|
||
@UseGuards(SessionGuard)
|
||
export class MembersController {
|
||
constructor(private readonly membersService: MembersService) {}
|
||
|
||
@Post()
|
||
addAlbumMember(
|
||
@Param('albumId') albumId: string,
|
||
@Body() dto: MemberAddRequestDto,
|
||
@Req() req: AuthenticatedRequest,
|
||
): Promise<MemberResponse> {
|
||
return this.membersService.add(userOf(req), albumId, dto);
|
||
}
|
||
|
||
@Get()
|
||
listAlbumMembers(
|
||
@Param('albumId') albumId: string,
|
||
@Query('cursor') cursor: string | undefined,
|
||
@Query('limit') limit: string | undefined,
|
||
@Req() req: AuthenticatedRequest,
|
||
): Promise<MemberListResponse> {
|
||
return this.membersService.list(
|
||
userOf(req),
|
||
albumId,
|
||
parseCursor(cursor),
|
||
parseLimit(limit),
|
||
);
|
||
}
|
||
|
||
@Delete(':userId')
|
||
@HttpCode(HttpStatus.NO_CONTENT)
|
||
removeAlbumMember(
|
||
@Param('albumId') albumId: string,
|
||
@Param('userId') userId: string,
|
||
@Req() req: AuthenticatedRequest,
|
||
): Promise<void> {
|
||
return this.membersService.remove(userOf(req), albumId, userId);
|
||
}
|
||
}
|