From 40910ab71fee6cad5a3fc4ba0bcda34c3a0f22a0 Mon Sep 17 00:00:00 2001 From: Yondon Fu Date: Wed, 26 Oct 2022 20:22:29 -0400 Subject: [PATCH] api: Support ar:// source URL lookups (#1372) * api: Support ar:// source URL lookups * api/playback: getByPlaybackId skip deleted assets * api/playback: Simplify asset fetching Keep it all in one function * api/asset-table: Bring back opts * .github: Run tests before coverage * api: Fix playback ID query * api/schema: Allow ipfs and ar schemas as input Co-authored-by: Victor Elias --- .github/workflows/test.yaml | 3 ++ packages/api/src/controllers/playback.test.ts | 42 +++++++++++++++++++ packages/api/src/controllers/playback.ts | 19 ++++----- packages/api/src/schema/schema.yaml | 2 +- packages/api/src/store/asset-table.ts | 18 ++++---- 5 files changed, 63 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ca705adfe3..febd1afc8e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -51,6 +51,9 @@ jobs: - name: yarn install run: yarn install --frozen-lockfile + - name: yarn tests without coverage + run: yarn run test + - name: yarn tests with coverage run: yarn run coverage diff --git a/packages/api/src/controllers/playback.test.ts b/packages/api/src/controllers/playback.test.ts index b2e5260d1a..2aa501a5c6 100644 --- a/packages/api/src/controllers/playback.test.ts +++ b/packages/api/src/controllers/playback.test.ts @@ -216,6 +216,48 @@ describe("controllers/playback", () => { const res = await client.get(`/playback/${cid}`); expect(res.status).toBe(404); }); + + it("should return playback URL assets from Arweave tx ID based on source URL lookup", async () => { + const txID = "bafyfoobar"; + await db.asset.update(asset.id, { + playbackRecordingId: "mock_recording_id_2", + source: { + type: "url", + url: "ar://" + txID, + }, + status: { + phase: "ready", + updatedAt: 1234, + }, + }); + const res = await client.get(`/playback/${txID}`); + 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 Arweave tx ID based on source URL lookup if asset is not ready", async () => { + const txID = "bafyfoobar"; + await db.asset.update(asset.id, { + playbackRecordingId: "mock_recording_id_2", + source: { + type: "url", + url: "ar://" + txID, + }, + }); + const res = await client.get(`/playback/${txID}`); + 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 4d4ade9c14..bd6e34b5c9 100644 --- a/packages/api/src/controllers/playback.ts +++ b/packages/api/src/controllers/playback.ts @@ -41,15 +41,12 @@ const newPlaybackInfo = ( }, }); -const getAssetPlaybackUrl = async ( - ingest: string, - id: string, - cid: boolean -) => { - const asset = cid - ? (await db.asset.getByIpfsCid(id)) ?? - (await db.asset.getBySourceURL("ipfs://" + id)) - : await db.asset.getByPlaybackId(id); +const getAssetPlaybackUrl = async (ingest: string, id: string) => { + const asset = + (await db.asset.getByPlaybackId(id)) ?? + (await db.asset.getByIpfsCid(id)) ?? + (await db.asset.getBySourceURL("ipfs://" + id)) ?? + (await db.asset.getBySourceURL("ar://" + id)); if (!asset || asset.deleted) { return null; } @@ -81,9 +78,7 @@ async function getPlaybackInfo( stream.isActive ? 1 : 0 ); } - const assetUrl = - (await getAssetPlaybackUrl(ingest, id, false)) ?? - (await getAssetPlaybackUrl(ingest, id, true)); + const assetUrl = await getAssetPlaybackUrl(ingest, id); if (assetUrl) { return newPlaybackInfo("vod", assetUrl); } diff --git a/packages/api/src/schema/schema.yaml b/packages/api/src/schema/schema.yaml index 9ab4fd32a2..61c8320c02 100644 --- a/packages/api/src/schema/schema.yaml +++ b/packages/api/src/schema/schema.yaml @@ -1359,7 +1359,7 @@ components: url: type: string format: uri - pattern: ^http(s)?:// + pattern: ^(https?|ipfs|ar):// description: URL where the asset contents can be retrieved. Only valid for the import task endpoint. diff --git a/packages/api/src/store/asset-table.ts b/packages/api/src/store/asset-table.ts index a7ad62fa63..2594232289 100644 --- a/packages/api/src/store/asset-table.ts +++ b/packages/api/src/store/asset-table.ts @@ -27,16 +27,18 @@ export default class AssetTable extends Table> { playbackId: string, opts?: QueryOptions ): Promise> { - const res: QueryResult = await this.db.queryWithOpts( - sql`SELECT id, data FROM asset WHERE data->>'playbackId' = ${playbackId}`.setName( - `${this.name}_by_playbackid` - ), - opts - ); - if (res.rowCount < 1) { + const query = [ + sql`asset.data->>'playbackId' = ${playbackId}`, + sql`asset.data->>'deleted' IS NULL`, + ]; + const [assets] = await this.find(query, { + ...opts, + limit: 2, + }); + if (assets.length < 1) { return null; } - return res.rows[0].data as WithID; + return assets[0]; } async getByIpfsCid(cid: string): Promise> {