diff --git a/packages/api/src/controllers/asset.test.ts b/packages/api/src/controllers/asset.test.ts index 1eddc69ab2..49261ac898 100644 --- a/packages/api/src/controllers/asset.test.ts +++ b/packages/api/src/controllers/asset.test.ts @@ -97,6 +97,7 @@ describe("controllers/asset", () => { "asset_playbackId", "asset_playbackRecordingId", "asset_sourceAssetId", + "asset_source_url", "asset_storage_ipfs_cid", "asset_storage_ipfs_nftMetadata_cid", "asset_userId", diff --git a/packages/api/src/controllers/playback.test.ts b/packages/api/src/controllers/playback.test.ts index dbd7bc1245..b2e5260d1a 100644 --- a/packages/api/src/controllers/playback.test.ts +++ b/packages/api/src/controllers/playback.test.ts @@ -174,6 +174,48 @@ describe("controllers/playback", () => { }, }); }); + + it("should return playback URL assets from CID based on source URL lookup", async () => { + const cid = "bafyfoobar"; + await db.asset.update(asset.id, { + playbackRecordingId: "mock_recording_id_2", + source: { + type: "url", + url: "ipfs://" + cid, + }, + status: { + phase: "ready", + updatedAt: 1234, + }, + }); + const res = await client.get(`/playback/${cid}`); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ + type: "vod", + meta: { + source: [ + { + hrn: "HLS (TS)", + type: "html5/application/vnd.apple.mpegurl", + url: `${ingest}/recordings/mock_recording_id_2/index.m3u8`, + }, + ], + }, + }); + }); + + it("should return 404 for CID based on source URL lookup if asset is not ready", async () => { + const cid = "bafyfoobar"; + await db.asset.update(asset.id, { + playbackRecordingId: "mock_recording_id_2", + source: { + type: "url", + url: "ipfs://" + cid, + }, + }); + const res = await client.get(`/playback/${cid}`); + expect(res.status).toBe(404); + }); }); describe("for recordings", () => { diff --git a/packages/api/src/controllers/playback.ts b/packages/api/src/controllers/playback.ts index a90bba20eb..4d4ade9c14 100644 --- a/packages/api/src/controllers/playback.ts +++ b/packages/api/src/controllers/playback.ts @@ -47,7 +47,8 @@ const getAssetPlaybackUrl = async ( cid: boolean ) => { const asset = cid - ? await db.asset.getByIpfsCid(id) + ? (await db.asset.getByIpfsCid(id)) ?? + (await db.asset.getBySourceURL("ipfs://" + id)) : await db.asset.getByPlaybackId(id); if (!asset || asset.deleted) { return null; diff --git a/packages/api/src/schema/schema.yaml b/packages/api/src/schema/schema.yaml index 93b4c86b8b..9ab4fd32a2 100644 --- a/packages/api/src/schema/schema.yaml +++ b/packages/api/src/schema/schema.yaml @@ -1039,6 +1039,7 @@ components: enum: [url] url: type: string + index: true description: URL from which the asset was uploaded - additionalProperties: false required: [type] diff --git a/packages/api/src/store/asset-table.ts b/packages/api/src/store/asset-table.ts index f3789a6fa2..a7ad62fa63 100644 --- a/packages/api/src/store/asset-table.ts +++ b/packages/api/src/store/asset-table.ts @@ -53,4 +53,21 @@ export default class AssetTable extends Table> { } return assets[0]; } + + async getBySourceURL(url: string): Promise> { + const query = [ + sql`asset.data->'source'->>'type' = 'url'`, + sql`asset.data->'source'->>'url' = ${url}`, + sql`asset.data->>'deleted' IS NULL`, + sql`asset.data->'status'->>'phase' = 'ready'`, + ]; + const [assets] = await this.find(query, { + limit: 2, + order: "coalesce((asset.data->'createdAt')::bigint, 0) ASC", + }); + if (!assets || assets.length < 1) { + return null; + } + return assets[0]; + } } diff --git a/packages/api/src/store/table.ts b/packages/api/src/store/table.ts index a6f27fb677..21c47f535e 100644 --- a/packages/api/src/store/table.ts +++ b/packages/api/src/store/table.ts @@ -342,6 +342,13 @@ export default class Table { // avoid creating indexes in production right now... return; } + if (prop.oneOf?.length) { + return Promise.all( + prop.oneOf.map((oneSchema) => + this.ensureIndex(propName, oneSchema, parents) + ) + ); + } if (!prop.index && !prop.unique) { if (prop.properties && this.name === "asset") {