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>
This commit is contained in:
@@ -314,8 +314,9 @@ describe('public (e2e)', () => {
|
||||
});
|
||||
disabledToken = disabled.publicToken;
|
||||
|
||||
// banner 相册:album_assets(kind=banner) + 真实 MinIO 对象(fixture 直种,
|
||||
// 上传链路属 P1b)。
|
||||
// banner 相册:album_assets(kind=banner, state=effective) + 真实 MinIO
|
||||
// 对象(fixture 直种;P1b Task 3 起公开查询过滤 state='effective',且
|
||||
// 走语言偏好解析——此行为全局(locale NULL),任何 resolved locale 都回退到它)。
|
||||
const banner = await seedAlbum({
|
||||
alias: 'pub-banner',
|
||||
translations: [
|
||||
@@ -327,7 +328,13 @@ describe('public (e2e)', () => {
|
||||
const bannerKey = `${KEY_ROOT}/albums/${banner.id}/assets/banner.jpg`;
|
||||
await storage.put(bannerKey, await makeJpeg(1200, 400), 'image/jpeg');
|
||||
await prisma.album_assets.create({
|
||||
data: { albumId: banner.id, kind: 'banner', objectKey: bannerKey },
|
||||
data: {
|
||||
albumId: banner.id,
|
||||
kind: 'banner',
|
||||
locale: null,
|
||||
objectKey: bannerKey,
|
||||
state: 'effective',
|
||||
},
|
||||
});
|
||||
|
||||
const noBanner = await seedAlbum({
|
||||
|
||||
Reference in New Issue
Block a user