merge: integrate WP6-02 dependency for TASK-WP6-04

# Conflicts:
#	apps/api/src/app.ts
#	apps/web/src/main.tsx
This commit is contained in:
suyx
2026-08-04 11:01:15 +08:00
11 changed files with 1412 additions and 5 deletions
+195 -3
View File
@@ -10,6 +10,8 @@ import {
AccountProfileUpdateResponseSchema,
AccountSettingsResponseSchema,
AdminAuthenticatedUserSchema,
AdminGenerationRecordSchema,
AdminGenerationListResponseSchema,
AdminOverviewResponseSchema,
AdminCreditParamsSchema,
AdminLoginCompleteRequestSchema,
@@ -61,6 +63,10 @@ import {
ModelConfigUpdateRequestSchema,
ModelParamsSchema,
ModelConfigUpdateHeadersSchema,
PrivateContentGenerationParamsSchema,
PrivateContentNoticeAckRequestSchema,
PrivateContentNoticeAckResponseSchema,
PrivateContentPromptResponseSchema,
FailedEmptyTrashRequestSchema,
FailedEmptyTrashResponseSchema,
ExportFormatSchema,
@@ -184,6 +190,7 @@ import type { ModelConfigurationService } from "./model-configuration.js";
import { StickerReleaseError } from "./sticker-release-errors.js";
import type { StickerReleaseService } from "./sticker-releases.js";
import type { ManagedStorage } from "./managed-storage.js";
import { PrivateContentError, PrivateContentService } from "./private-content.js";
const defaultBootstrap: BootstrapResponse = {
app_version: "0.0.0",
@@ -227,6 +234,7 @@ export interface CreateAppOptions {
releaseVersion: string;
resourceId: string;
}) => boolean | Promise<boolean>;
privateContent?: PrivateContentService;
registration?: RegistrationService;
stickers?: StickerReleaseService;
}
@@ -665,6 +673,13 @@ function sendBrowserUnsupported(
export async function createApp(options: CreateAppOptions = {}) {
const eventHub = options.eventHub ?? new EventHub();
const bootstrap = options.bootstrap ?? (() => defaultBootstrap);
const privateContent = options.privateContent ?? (options.registration
? new PrivateContentService(
options.registration.database,
options.registration.options.currentPrivacyNoticeVersion,
options.registration.options.clock,
)
: undefined);
const browserGate = options.browserGate ?? true;
const browserSupportSecret = options.browserSupportSecret ?? randomBytes(32);
const browserSupportRelease = options.browserSupportRelease;
@@ -716,6 +731,12 @@ export async function createApp(options: CreateAppOptions = {}) {
AdminLoginCompleteRequestSchema,
AdminLoginCompleteResponseSchema,
AdminSessionResponseSchema,
AdminGenerationRecordSchema,
AdminGenerationListResponseSchema,
PrivateContentNoticeAckRequestSchema,
PrivateContentNoticeAckResponseSchema,
PrivateContentPromptResponseSchema,
PrivateContentGenerationParamsSchema,
AdminOverviewResponseSchema,
CreditSummarySchema,
CreditEntryTypeSchema,
@@ -858,6 +879,167 @@ export async function createApp(options: CreateAppOptions = {}) {
status: "ready",
}));
const readAdminRequestSession = (request: { headers: Record<string, string | string[] | undefined> }) => {
const token = cookieValue(headerValue(request.headers.cookie), adminSessionCookieName);
return token && options.registration ? options.registration.readAdminSession(token) : undefined;
};
const privateContentNoticeRequired = (reply: FastifyReply, correlationId: string) => {
const notice = privateContent?.currentNotice();
return reply.code(428).send(createErrorEnvelope({
code: "PRIVATE_CONTENT_NOTICE_ACK_REQUIRED",
correlationId,
details: { latest_version: notice?.version ?? "" },
}));
};
app.post(
"/api/v1/admin/private-content-notice/ack",
{
attachValidation: true,
schema: {
body: Type.Ref(PrivateContentNoticeAckRequestSchema),
headers: Type.Intersect([Type.Ref(CsrfHeadersSchema), Type.Ref(RegistrationCompleteHeadersSchema)]),
operationId: "ackPrivateContentNotice",
response: {
200: Type.Ref(PrivateContentNoticeAckResponseSchema),
400: Type.Ref(ErrorEnvelopeSchema),
401: Type.Ref(ErrorEnvelopeSchema),
403: Type.Ref(ErrorEnvelopeSchema),
428: Type.Ref(ErrorEnvelopeSchema),
503: Type.Ref(ErrorEnvelopeSchema),
},
tags: ["Admin Private Content"],
},
},
async (request, reply) => {
if (request.validationError || !headerValue(request.headers["idempotency-key"])) {
return reply.code(400).send(createErrorEnvelope({
code: "REGISTRATION_REQUEST_INVALID",
correlationId: request.id,
details: { field_errors: [{ field: "headers", message_key: "request.headers.invalid" }] },
}));
}
if (!privateContent || !options.registration) return reply.code(503).send(createErrorEnvelope({ code: "AUTH_SERVICE_UNAVAILABLE", correlationId: request.id }));
const token = cookieValue(headerValue(request.headers.cookie), adminSessionCookieName);
if (!token) return reply.code(401).send(createErrorEnvelope({ code: "AUTH_SESSION_INVALID", correlationId: request.id }));
try {
const admin = options.registration.authorizeAdminMutation({
csrfToken: headerValue(request.headers["x-csrf-token"]) ?? "",
sessionToken: token,
});
const result = privateContent.acknowledge(admin.userId, (request.body as { expected_notice_version: string }).expected_notice_version);
return { acknowledged_at: result.acknowledgedAt, notice_version: result.noticeVersion, status: "acknowledged" as const };
} catch (error) {
if (error instanceof RegistrationError) return registrationFailure(reply, request.id, error);
if (error instanceof PrivateContentError && error.code === "notice_version_conflict") return privateContentNoticeRequired(reply, request.id);
return reply.code(503).send(createErrorEnvelope({ code: "AUTH_SERVICE_UNAVAILABLE", correlationId: request.id }));
}
},
);
app.get(
"/api/v1/admin/generations",
{
schema: {
operationId: "listAdminGenerations",
response: {
200: Type.Ref(AdminGenerationListResponseSchema),
401: Type.Ref(ErrorEnvelopeSchema),
428: Type.Ref(ErrorEnvelopeSchema),
503: Type.Ref(ErrorEnvelopeSchema),
},
tags: ["Admin Private Content"],
},
},
async (request, reply) => {
if (!privateContent || !options.registration) return reply.code(503).send(createErrorEnvelope({ code: "AUTH_SERVICE_UNAVAILABLE", correlationId: request.id }));
const session = readAdminRequestSession(request);
if (!session) return reply.code(401).send(createErrorEnvelope({ code: "AUTH_SESSION_INVALID", correlationId: request.id }));
try {
privateContent.requireAcknowledgement(session.user_id);
return privateContent.listGenerations();
} catch (error) {
if (error instanceof PrivateContentError && error.code === "notice_required") return privateContentNoticeRequired(reply, request.id);
return reply.code(503).send(createErrorEnvelope({ code: "AUTH_SERVICE_UNAVAILABLE", correlationId: request.id }));
}
},
);
app.get(
"/api/v1/admin/private-content/generations/:generationId/prompt",
{
attachValidation: true,
schema: {
params: Type.Ref(PrivateContentGenerationParamsSchema),
operationId: "openAdminGenerationPrompt",
response: {
200: Type.Ref(PrivateContentPromptResponseSchema),
401: Type.Ref(ErrorEnvelopeSchema),
404: Type.Null(),
428: Type.Ref(ErrorEnvelopeSchema),
503: Type.Ref(ErrorEnvelopeSchema),
},
tags: ["Admin Private Content"],
},
},
async (request, reply) => {
if (request.validationError) return reply.code(404).send(null);
if (!privateContent || !options.registration) return reply.code(503).send(createErrorEnvelope({ code: "AUTH_SERVICE_UNAVAILABLE", correlationId: request.id }));
const session = readAdminRequestSession(request);
if (!session) return reply.code(401).send(createErrorEnvelope({ code: "AUTH_SESSION_INVALID", correlationId: request.id }));
try {
const generationId = (request.params as { generationId: string }).generationId;
const value = privateContent.readPrompt(session.user_id, generationId);
reply.header("Cache-Control", "private, no-store");
return { content_type: "prompt" as const, generation_id: value.generationId, prompt: value.prompt };
} catch (error) {
if (error instanceof PrivateContentError && error.code === "notice_required") return privateContentNoticeRequired(reply, request.id);
if (error instanceof PrivateContentError && error.code === "not_found") return reply.code(404).send(null);
return reply.code(503).send(createErrorEnvelope({ code: "AUTH_SERVICE_UNAVAILABLE", correlationId: request.id }));
}
},
);
app.get(
"/api/v1/admin/private-content/generations/:generationId/image",
{
attachValidation: true,
schema: {
params: Type.Ref(PrivateContentGenerationParamsSchema),
operationId: "openAdminGenerationImage",
produces: ["application/octet-stream"],
response: {
200: Type.String({ format: "binary" }),
401: Type.Ref(ErrorEnvelopeSchema),
404: Type.Null(),
428: Type.Ref(ErrorEnvelopeSchema),
503: Type.Ref(ErrorEnvelopeSchema),
},
tags: ["Admin Private Content"],
},
},
async (request, reply) => {
if (request.validationError) return reply.code(404).send(null);
if (!privateContent || !options.registration || !options.latestExports) return reply.code(503).send(createErrorEnvelope({ code: "AUTH_SERVICE_UNAVAILABLE", correlationId: request.id }));
const session = readAdminRequestSession(request);
if (!session) return reply.code(401).send(createErrorEnvelope({ code: "AUTH_SESSION_INVALID", correlationId: request.id }));
try {
const generationId = (request.params as { generationId: string }).generationId;
const target = privateContent.readImageTarget(session.user_id, generationId);
const item = options.latestExports.getOriginal(target.ownerId, target.projectId, target.imageId);
const extension = item.mime_type === "image/jpeg" ? "jpg" : item.mime_type === "image/webp" ? "webp" : "png";
reply.header("Cache-Control", "private, no-store");
reply.header("Content-Disposition", `inline; filename="dada-generation.${extension}"`);
reply.type(item.mime_type);
return reply.send(createReadStream(item.path));
} catch (error) {
if (error instanceof PrivateContentError && error.code === "notice_required") return privateContentNoticeRequired(reply, request.id);
if (error instanceof PrivateContentError && error.code === "not_found") return reply.code(404).send(null);
return latestExportFailure(reply, request.id, error);
}
},
);
app.get(
"/api/v1/static-stickers/current",
{ schema: { hide: true } },
@@ -1211,6 +1393,13 @@ export async function createApp(options: CreateAppOptions = {}) {
})
: false;
if (userSession?.userId !== resource.ownerId && !controlledAdmin) return reply.code(404).send();
if (adminSession && controlledAdmin && privateContent) {
try {
privateContent.recordPrivateAssetAccess(adminSession.user_id, resource.ownerId, resource.resourceId);
} catch {
return reply.code(503).send(createErrorEnvelope({ code: "AUTH_SERVICE_UNAVAILABLE", correlationId: request.id }));
}
}
reply.type(resource.mimeType);
reply.header("Cache-Control", "private, no-store");
reply.header("Content-Disposition", "inline");
@@ -1444,15 +1633,18 @@ export async function createApp(options: CreateAppOptions = {}) {
if (!session) {
return reply.code(401).send(createErrorEnvelope({ code: "AUTH_SESSION_INVALID", correlationId: request.id }));
}
const acknowledgement = privateContent?.readAcknowledgement(session.user_id);
const notice = privateContent?.currentNotice();
return {
acknowledged_private_content_notice_version: null,
acknowledged_private_content_notice_version: acknowledgement?.version ?? null,
admin: { role: "super_admin" as const, status: "active" as const, user_id: session.user_id },
audience: "admin" as const,
authenticated: true as const,
csrf_token: options.registration.issueAdminCsrfToken(token!),
current_private_content_notice_version: null,
...(notice ? { current_private_content_notice_message_key: notice.messageKey } : {}),
current_private_content_notice_version: notice?.version ?? null,
expires_at: new Date(session.expires_at).toISOString(),
notice_acknowledged: false,
notice_acknowledged: notice ? acknowledgement?.version === notice.version : false,
};
},
);