feat: harden deployment and public api handoff
This commit is contained in:
@@ -215,7 +215,8 @@ export async function syncImageJob(jobId: string, origin: string): Promise<Gener
|
||||
source: sourceForCapability(job.capability),
|
||||
capability: job.capability,
|
||||
jobId: job.id,
|
||||
index
|
||||
index,
|
||||
tags: assetTagsForJob(job)
|
||||
}));
|
||||
}
|
||||
if (assets.length) {
|
||||
@@ -300,7 +301,8 @@ async function syncEvolinkImageJob(job: GenerationJob, origin: string): Promise<
|
||||
source: sourceForCapability(job.capability),
|
||||
capability: job.capability,
|
||||
jobId: job.id,
|
||||
index
|
||||
index,
|
||||
tags: assetTagsForJob(job)
|
||||
}));
|
||||
}
|
||||
await recordUsageEvent({
|
||||
@@ -346,6 +348,7 @@ async function completeMockJob(job: GenerationJob, origin: string): Promise<Gene
|
||||
source: sourceForCapability(job.capability),
|
||||
capability: job.capability,
|
||||
jobId: job.id,
|
||||
tags: assetTagsForJob(job),
|
||||
metadata: {
|
||||
mock: true
|
||||
}
|
||||
@@ -376,6 +379,10 @@ function sourceForCapability(capability: string) {
|
||||
return "generated";
|
||||
}
|
||||
|
||||
function assetTagsForJob(job: GenerationJob): string[] {
|
||||
return job.externalClientId ? [job.capability, `api-client:${job.externalClientId}`] : [job.capability];
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
? value as Record<string, unknown>
|
||||
|
||||
45
lib/server/public-api-assets.ts
Normal file
45
lib/server/public-api-assets.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { getAsset, listAssets, listGenerationJobsFiltered } from "@/lib/server/data-store";
|
||||
import { DEFAULT_OWNER_ID } from "@/lib/server/runtime";
|
||||
import type { Asset, GenerationJob } from "@/lib/types";
|
||||
|
||||
const JOB_LOOKUP_LIMIT = 200;
|
||||
|
||||
export async function listPublicApiAssets(clientId: string): Promise<Asset[]> {
|
||||
const [assets, jobs] = await Promise.all([
|
||||
listAssets(DEFAULT_OWNER_ID),
|
||||
listGenerationJobsFiltered({
|
||||
ownerId: DEFAULT_OWNER_ID,
|
||||
externalClientId: clientId,
|
||||
limit: JOB_LOOKUP_LIMIT
|
||||
})
|
||||
]);
|
||||
const accessibleIds = assetIdsFromJobs(jobs);
|
||||
return assets.filter((asset) => canAccessAsset(clientId, asset, accessibleIds));
|
||||
}
|
||||
|
||||
export async function getPublicApiAsset(clientId: string, assetId: string): Promise<Asset | null> {
|
||||
const asset = await getAsset(assetId);
|
||||
if (!asset || asset.ownerId !== DEFAULT_OWNER_ID) return null;
|
||||
if (asset.tags.includes(apiClientTag(clientId))) return asset;
|
||||
const jobs = await listGenerationJobsFiltered({
|
||||
ownerId: DEFAULT_OWNER_ID,
|
||||
externalClientId: clientId,
|
||||
limit: JOB_LOOKUP_LIMIT
|
||||
});
|
||||
return assetIdsFromJobs(jobs).has(asset.id) ? asset : null;
|
||||
}
|
||||
|
||||
function canAccessAsset(clientId: string, asset: Asset, accessibleIds: Set<string>): boolean {
|
||||
return asset.ownerId === DEFAULT_OWNER_ID && (
|
||||
asset.tags.includes(apiClientTag(clientId)) ||
|
||||
accessibleIds.has(asset.id)
|
||||
);
|
||||
}
|
||||
|
||||
function assetIdsFromJobs(jobs: GenerationJob[]): Set<string> {
|
||||
return new Set(jobs.flatMap((job) => [...job.inputAssetIds, ...job.outputAssetIds]));
|
||||
}
|
||||
|
||||
function apiClientTag(clientId: string): string {
|
||||
return `api-client:${clientId}`;
|
||||
}
|
||||
@@ -87,6 +87,7 @@ export async function importRemoteImageAsAsset(input: {
|
||||
capability: string;
|
||||
jobId: string;
|
||||
index: number;
|
||||
tags?: string[];
|
||||
}): Promise<Asset> {
|
||||
const response = await fetch(input.url);
|
||||
if (!response.ok) {
|
||||
@@ -104,6 +105,7 @@ export async function importRemoteImageAsAsset(input: {
|
||||
source: input.source,
|
||||
capability: input.capability,
|
||||
jobId: input.jobId,
|
||||
tags: input.tags,
|
||||
metadata: {
|
||||
importedFrom: input.url
|
||||
}
|
||||
@@ -119,6 +121,7 @@ export async function importRemoteAssetAsAsset(input: {
|
||||
jobId: string;
|
||||
index: number;
|
||||
fallbackContentType?: string;
|
||||
tags?: string[];
|
||||
}): Promise<Asset> {
|
||||
const response = await fetch(input.url);
|
||||
if (!response.ok) {
|
||||
@@ -136,6 +139,7 @@ export async function importRemoteAssetAsAsset(input: {
|
||||
source: input.source,
|
||||
capability: input.capability,
|
||||
jobId: input.jobId,
|
||||
tags: input.tags,
|
||||
metadata: {
|
||||
importedFrom: input.url
|
||||
}
|
||||
|
||||
@@ -131,7 +131,8 @@ export async function syncVideoJob(jobId: string, origin: string): Promise<Gener
|
||||
capability: "video.generate",
|
||||
jobId: job.id,
|
||||
index: 0,
|
||||
fallbackContentType: "video/mp4"
|
||||
fallbackContentType: "video/mp4",
|
||||
tags: assetTagsForJob(job)
|
||||
});
|
||||
await recordUsageEvent({
|
||||
ownerId: job.ownerId,
|
||||
@@ -171,7 +172,7 @@ async function completeMockVideoJob(job: GenerationJob): Promise<GenerationJob>
|
||||
name: `mock-video-${job.id}.mp4`,
|
||||
url: "/mock/seedance-mock.mp4",
|
||||
source: "generated",
|
||||
tags: ["video.generate", "mock"],
|
||||
tags: [...assetTagsForJob(job), "mock"],
|
||||
metadata: {
|
||||
mock: true,
|
||||
capability: "video.generate",
|
||||
@@ -203,3 +204,7 @@ function asRecord(value: unknown): Record<string, unknown> {
|
||||
? value as Record<string, unknown>
|
||||
: {};
|
||||
}
|
||||
|
||||
function assetTagsForJob(job: GenerationJob): string[] {
|
||||
return job.externalClientId ? ["video.generate", `api-client:${job.externalClientId}`] : ["video.generate"];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user