feat(api): photo, upload, outbox schema
Task 10: photos/photo_variants/upload_sessions/photo_jobs/outbox_events on the CN schema (flat string FKs, no Prisma relations); migration 20260917120827_photo_upload_domain rewritten to utf8mb4_0900_bin per the 固化 collation recipe; reset.ts table order extended; one semantics e2e case for photo_jobs @@unique([photoId, type]) and the upload_sessions A03 idempotency anchor @@unique([userId, clientTaskId]). Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,9 @@
|
||||
// 5) 相册域(Task 9 增补):多语言翻译按 @@unique([albumId, locale]) 双向
|
||||
// 精确命中、publicToken 唯一约束拒绝重复(P2002)——相册域为扁平字符串
|
||||
// 外键(无 Prisma relation),完整 CRUD 语义由 Task 15 展开。
|
||||
// 6) 照片与上传域(Task 10 增补):photo_jobs 按 @@unique([photoId, type])
|
||||
// 拒绝重复作业、upload_sessions 按 @@unique([userId, clientTaskId]) 拒绝
|
||||
// 重复会话(P2002)——后者是断点续传幂等锚点(A03)。
|
||||
import { PrismaClient } from '@photo/db-cn';
|
||||
import { resetDb } from './reset';
|
||||
|
||||
@@ -164,4 +167,55 @@ describe('db semantics (e2e)', () => {
|
||||
}),
|
||||
).rejects.toMatchObject({ code: 'P2002' });
|
||||
});
|
||||
|
||||
it('photo_jobs and upload_sessions enforce their unique anchors (Task 10)', async () => {
|
||||
const photo = await prisma.photos.create({
|
||||
data: {
|
||||
albumId: 'album-jobs-e2e',
|
||||
uploaderId: 'uploader-flat-fk',
|
||||
objectKey: 'originals/album-jobs-e2e/photo-001.jpg',
|
||||
bytes: 1024,
|
||||
},
|
||||
});
|
||||
await prisma.photo_jobs.create({
|
||||
data: { photoId: photo.id, type: 'process' },
|
||||
});
|
||||
|
||||
// 同一照片同类作业唯一(worker 任务领取的防重入锚点)。
|
||||
await expect(
|
||||
prisma.photo_jobs.create({
|
||||
data: { photoId: photo.id, type: 'process' },
|
||||
}),
|
||||
).rejects.toMatchObject({ code: 'P2002' });
|
||||
|
||||
await prisma.upload_sessions.create({
|
||||
data: {
|
||||
userId: 'uploader-flat-fk',
|
||||
albumId: 'album-jobs-e2e',
|
||||
clientTaskId: 'task-0001',
|
||||
photoId: photo.id,
|
||||
objectKey: 'staging/album-jobs-e2e/task-0001.jpg',
|
||||
expectedBytes: 1024,
|
||||
contentType: 'image/jpeg',
|
||||
expiresAt: new Date(Date.now() + 60_000),
|
||||
},
|
||||
});
|
||||
|
||||
// 同一 (userId, clientTaskId) 唯一——断点续传幂等锚点(A03)。
|
||||
// photoId/objectKey 换新值,确保 P2002 只可能来自复合唯一键本身。
|
||||
await expect(
|
||||
prisma.upload_sessions.create({
|
||||
data: {
|
||||
userId: 'uploader-flat-fk',
|
||||
albumId: 'album-jobs-e2e',
|
||||
clientTaskId: 'task-0001',
|
||||
photoId: 'other-photo-cuid',
|
||||
objectKey: 'staging/album-jobs-e2e/task-0002.jpg',
|
||||
expectedBytes: 2048,
|
||||
contentType: 'image/jpeg',
|
||||
expiresAt: new Date(Date.now() + 60_000),
|
||||
},
|
||||
}),
|
||||
).rejects.toMatchObject({ code: 'P2002' });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user