Skip to content
Snippets Groups Projects
Commit d90ee51d authored by Benedikt Hermann's avatar Benedikt Hermann
Browse files

added episode list

parent 7281070f
Branches
No related tags found
No related merge requests found
/node_modules /node_modules
package-lock.json
.env
import { ActivityPubNote, ActivityPubOutbox, EpisodeResponse } from "./types";
export function transformEpisode(response: EpisodeResponse) {
const item = response.item;
return {
"@context": "https://www.w3.org/ns/activitystreams",
"id": item.assetId,
"type": "PodcastEpisode",
"published": item.published,
"updated": item.updated,
"attributedTo": item.show.coreId,
"externalId": item.url,
"title": item.title,
"description": {
"type": "Note",
"mediaType": "text/markdown",
"content": item.description
},
"image": item.imagesList.length > 0 ? {
"type": "Image",
"url": item.imagesList[0].url
} : undefined,
"audio": item.audioList.map(audio => ({
"type": "Audio",
"name": audio.title,
"mediaType": "audio/mpeg",
"url": audio.href
})),
"partOf": {
"type": "PodcastSeries",
"id": item.show.coreId,
"externalId": item.show.externalId,
"name": item.show.title,
"attributedTo": item.show.publisher.coreId,
"publisher": {
"type": "BroadcastService",
"name": item.show.publisher.title,
"url": item.show.publisher.url,
"organization": {
"type": "Organization",
"name": item.show.publisher.organization.name,
"url": item.show.publisher.organization.url
}
}
}
};
}
export function transformEpisodeList(input: any): ActivityPubOutbox {
const baseUrl = "https://example.com/outbox";
const actorUrl = "https://example.com/actor";
const orderedItems: ActivityPubNote[] = input.show.items.nodes.map((item: any) => ({
id: item.assetId,
type: "Note",
actor: actorUrl,
published: new Date().toISOString(),
to: ["https://www.w3.org/ns/activitystreams#Public"],
content: item.title,
url: item.url,
}));
return {
"@context": "https://www.w3.org/ns/activitystreams",
id: baseUrl,
type: "OrderedCollection",
orderedItems,
};
}
\ No newline at end of file
import express from 'express'; import express from 'express';
import { Client, cacheExchange, fetchExchange, gql } from '@urql/core'; import { Client, cacheExchange, fetchExchange, gql } from '@urql/core';
import { Response } from './types'; import { EpisodeResponse } from './types';
import { transformEpisode, transformEpisodeList } from './mappings';
const app = express(); const app = express();
const PORT = 3000; const PORT = 3000;
...@@ -53,8 +54,8 @@ const query = gql` ...@@ -53,8 +54,8 @@ const query = gql`
} }
`; `;
async function fetchData(query: any, variables: any) { async function fetchData<R>(query: any, variables: any) {
const result = await audiothekApi.query<Response>(query, variables).toPromise(); const result = await audiothekApi.query<R>(query, variables).toPromise();
if (result.error) { if (result.error) {
throw result.error; throw result.error;
} }
...@@ -62,57 +63,13 @@ async function fetchData(query: any, variables: any) { ...@@ -62,57 +63,13 @@ async function fetchData(query: any, variables: any) {
return result.data; return result.data;
} }
function transformEpisodeToActivityPub(response: Response) {
const item = response.item;
return {
"@context": "https://www.w3.org/ns/activitystreams",
"id": item.assetId,
"type": "PodcastEpisode",
"published": item.published,
"updated": item.updated,
"attributedTo": item.show.coreId,
"externalId": item.url,
"title": item.title,
"description": {
"type": "Note",
"mediaType": "text/markdown",
"content": item.description
},
"image": item.imagesList.length > 0 ? {
"type": "Image",
"url": item.imagesList[0].url
} : undefined,
"audio": item.audioList.map(audio => ({
"type": "Audio",
"name": audio.title,
"mediaType": "audio/mpeg",
"url": audio.href
})),
"partOf": {
"type": "PodcastSeries",
"id": item.show.coreId,
"externalId": item.show.externalId,
"name": item.show.title,
"attributedTo": item.show.publisher.coreId,
"publisher": {
"type": "BroadcastService",
"name": item.show.publisher.title,
"url": item.show.publisher.url,
"organization": {
"type": "Organization",
"name": item.show.publisher.organization.name,
"url": item.show.publisher.organization.url
}
}
}
};
}
async function main() { async function main() {
const response = await fetchData(query, { id: '14278953' }); const response = await fetchData(query, { id: '14278953' });
if (response) { if (response) {
const activityPubEpisode = transformEpisodeToActivityPub(response); const activityPubEpisode = transformEpisode(response);
console.log(JSON.stringify(activityPubEpisode, null, 2)); console.log(JSON.stringify(activityPubEpisode, null, 2));
} }
} }
...@@ -123,7 +80,7 @@ async function main() { ...@@ -123,7 +80,7 @@ async function main() {
app.get('/:actor/episodes/:id', async (req, res) => { app.get('/:actor/episodes/:id', async (req, res) => {
try { try {
const data = await fetchData(query, { id: req.params.id }); const data = await fetchData<EpisodeResponse>(query, { id: req.params.id });
if (!data) { if (!data) {
res.status(404).json({ error: 'Episode not found' }); res.status(404).json({ error: 'Episode not found' });
return; return;
...@@ -131,13 +88,67 @@ app.get('/:actor/episodes/:id', async (req, res) => { ...@@ -131,13 +88,67 @@ app.get('/:actor/episodes/:id', async (req, res) => {
// TODO: verifiy that the actor is the same as the attributedTo field // TODO: verifiy that the actor is the same as the attributedTo field
const activityPubDocument = transformEpisodeToActivityPub(data); const activityPubDocument = transformEpisode(data);
res.json(activityPubDocument);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch episode data' });
}
});
app.get('/:actor/episodes', async (req, res) => {
const query = gql`
query ShowWithEpisodes($showId: ID!, $first: Int = 10, $offset: Int) {
show(id: $showId) {
coreId
externalId
title
url
items(first: $first, orderBy: PUBLISH_DATE_DESC, offset: $offset, condition: {
isPublished: true
}) {
nodes {
assetId
title
url: sharingUrl
}
}
publisher: publicationService {
coreId
dvbServiceId
title
url
organizationName
organization {
name
url
}
}
}
}
`;
try {
// TODO: verifiy that the actor is the same as the attributedTo field
const data = await fetchData<any>(query, { showId: "urn:ard:show:a42d1ea0b4a07053" });
if (!data) {
res.status(404).json({ error: 'Episode not found' });
return;
}
const activityPubDocument = transformEpisodeList(data);
res.json(activityPubDocument); res.json(activityPubDocument);
} catch (error) { } catch (error) {
res.status(500).json({ error: 'Failed to fetch episode data' }); res.status(500).json({ error: 'Failed to fetch episode data' });
} }
}); });
app.get('/@blaue-couch', async (req, res) => { app.get('/@blaue-couch', async (req, res) => {
res.json({ res.json({
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
...@@ -159,6 +170,8 @@ app.get('/@blaue-couch', async (req, res) => { ...@@ -159,6 +170,8 @@ app.get('/@blaue-couch', async (req, res) => {
}); });
}); });
app.get('/', (req, res) => res.redirect('/@blaue-couch'));
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`); console.log(`Server running at http://localhost:${PORT}`);
}); });
...@@ -30,6 +30,23 @@ export type Item = { ...@@ -30,6 +30,23 @@ export type Item = {
show: Show; show: Show;
}; };
export type Response = { export type EpisodeResponse = {
item: Item; item: Item;
}; };
export type ActivityPubOutbox = {
"@context": string;
id: string;
type: string;
orderedItems: ActivityPubNote[];
};
export type ActivityPubNote = {
id: string;
type: string;
actor: string;
published: string;
to: string[];
content: string;
url: string;
};
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment