Files
photo-live/services/api/src/albums/assets.controller.ts
T
浅念 361a72f300 feat(api): album asset upload chain
P1b Task 3: initAlbumAssetUpload / completeAlbumAssetUpload (tags
[assets]) over album_assets with in-row one-shot sessions.

- migration asset_state: album_assets + state (pending|effective),
  expectedBytes, contentType (utf8mb4_0900_bin); pre-existing rows
  backfilled to effective. Complete head-verifies bytes AND
  contentType vs the init declaration (P1a Task 11 server-side
  fallback), then conditionally flips pending->effective with the
  winner writing the album.asset.add audit (resourceId=albumId,
  metadata assetId/kind/locale). Effective rows re-complete
  idempotently (re-signed URL, no re-verify, survives object
  cleanup).
- object key is the final key (albums/{albumId}/assets/{assetId}.{ext});
  the pending row is the logical staging - no copy step, keys embed
  assetId so re-init can never overwrite another row's object.
- permissions: owner/admin only (collaborator 403, outsider 404);
  uploadEnabled does NOT gate assets (switch governs photo uploads
  per 4.9 - both endpoints carry no ALBUM_UPLOAD_DISABLED).
- public banner now locale-aware (req 4.7): effective rows resolved
  as display-locale row -> global (locale NULL) row -> null, latest
  wins per tier (same-kind re-upload replaces, old rows retained).
- e2e: test/assets.e2e-spec.ts (14 cases, real jpegs via sharp,
  MinIO round-trips); public spec seed marked effective.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-18 08:40:32 +08:00

52 lines
1.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// services/api/src/albums/assets.controller.ts —— /albums/{albumId}/assets
// 端点(P1b Task 3)。方法名与契约 operationId 一一对应(initAlbumAssetUpload/
// completeAlbumAssetUpload)。控制器级 SessionGuard:两端点全需认证。控制器只做
// 认证用户与路径参数透传;权限与 HTTP 映射由 AssetsService 抛 ApiException
// (404 ALBUM_NOT_FOUND / 403 AUTH_FORBIDDEN / 400 VALIDATION_FAILED 或
// UPLOAD_VERIFY_FAILED)。
import {
Body,
Controller,
HttpCode,
HttpStatus,
Param,
Post,
Req,
UseGuards,
} from '@nestjs/common';
import { AuthenticatedRequest, SessionGuard } from '../auth/session.guard';
import { userOf } from './albums.controller';
import { AssetInitRequestDto } from './dto';
import {
AssetsService,
type AssetCompleteResponse,
type AssetInitResponse,
} from './assets.service';
@Controller('albums/:albumId/assets')
@UseGuards(SessionGuard)
export class AssetsController {
constructor(private readonly assetsService: AssetsService) {}
/** 契约 201:会话行已建(每次 init 新行新键,无幂等键)。 */
@Post()
initAlbumAssetUpload(
@Param('albumId') albumId: string,
@Body() dto: AssetInitRequestDto,
@Req() req: AuthenticatedRequest,
): Promise<AssetInitResponse> {
return this.assetsService.init(userOf(req), albumId, dto);
}
/** 完成核验:幂等(已生效行现签新 URL 返回,即签名刷新入口)。 */
@Post(':assetId/complete')
@HttpCode(HttpStatus.OK)
completeAlbumAssetUpload(
@Param('albumId') albumId: string,
@Param('assetId') assetId: string,
@Req() req: AuthenticatedRequest,
): Promise<AssetCompleteResponse> {
return this.assetsService.complete(userOf(req), albumId, assetId);
}
}