diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..daf4c28e --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# Optional Podroll endpoint settings +# Default mount path in indiekit.config.mjs is /podrollapi +PODROLL_MOUNT_PATH=/podrollapi + +# FreshRSS greader API URL for episode sync +# Example: https://freshrss.example/api/query.php?user=USER&t=TOKEN&f=greader +PODROLL_EPISODES_URL= + +# FreshRSS OPML export URL for source sync +# Example: https://freshrss.example/api/query.php?user=USER&t=TOKEN&f=opml +PODROLL_OPML_URL= diff --git a/README.md b/README.md index aabdb6cc..69a1dbb5 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ - GitHub activity + API: `/github` - Funkwhale activity + API: `/funkwhale` - Last.fm activity + API: `/lastfmapi` +- Podroll dashboard + API: `/podrollapi` - ActivityPub federation + admin reader: `/activitypub` - ActivityPub discovery: `/.well-known/webfinger`, `/nodeinfo/2.1` @@ -82,6 +83,15 @@ - If `FUNKWHALE_INSTANCE` points to a host that does not expose Funkwhale's API routes, API responses now degrade to empty data instead of repeated 500 errors. - If these variables are missing, the endpoints still exist but return empty activity until credentials are configured. +## Podroll endpoint + +- Podroll endpoint is enabled via `@rmdes/indiekit-endpoint-podroll` and mounted at `/podrollapi` by default. +- Optional environment variables: +- `PODROLL_MOUNT_PATH` (default `/podrollapi`) +- `PODROLL_EPISODES_URL` (FreshRSS greader endpoint URL used for episode sync) +- `PODROLL_OPML_URL` (FreshRSS OPML export URL used for podcast source sync) +- If `PODROLL_EPISODES_URL` and `PODROLL_OPML_URL` are not set, the endpoint still loads and can be configured from its admin dashboard. + ## ActivityPub - ActivityPub federation is enabled via `@rmdes/indiekit-endpoint-activitypub`. diff --git a/indiekit.config.mjs b/indiekit.config.mjs index 6ad7a3ca..a253e20f 100644 --- a/indiekit.config.mjs +++ b/indiekit.config.mjs @@ -1,6 +1,7 @@ import "dotenv/config"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import PodrollEndpoint from "@rmdes/indiekit-endpoint-podroll"; const mongoUsername = process.env.MONGO_USERNAME || process.env.MONGO_USER || ""; const mongoPassword = process.env.MONGO_PASSWORD || ""; @@ -84,6 +85,9 @@ const activityPubDebugDashboard = const activityPubDebugPassword = process.env.AP_DEBUG_PASSWORD || ""; const activityPubAlsoKnownAs = process.env.AP_ALSO_KNOWN_AS || ""; const redisUrl = process.env.REDIS_URL || ""; +const podrollMountPath = process.env.PODROLL_MOUNT_PATH || "/podrollapi"; +const podrollEpisodesUrl = process.env.PODROLL_EPISODES_URL || ""; +const podrollOpmlUrl = process.env.PODROLL_OPML_URL || ""; const configDir = path.dirname(fileURLToPath(import.meta.url)); const homepageContentDir = process.env.HOMEPAGE_CONTENT_DIR || @@ -182,6 +186,11 @@ export default { "@rmdes/indiekit-endpoint-conversations", "@rmdes/indiekit-endpoint-funkwhale", "@rmdes/indiekit-endpoint-lastfm", + new PodrollEndpoint({ + mountPath: podrollMountPath, + episodesUrl: podrollEpisodesUrl, + opmlUrl: podrollOpmlUrl, + }), "@rmdes/indiekit-endpoint-activitypub", ], "@indiekit/store-github": { diff --git a/package-lock.json b/package-lock.json index c19339dd..7f92735e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@rmdes/indiekit-endpoint-github": "^1.2.3", "@rmdes/indiekit-endpoint-homepage": "^1.0.22", "@rmdes/indiekit-endpoint-lastfm": "^1.0.12", + "@rmdes/indiekit-endpoint-podroll": "^1.0.11", "@rmdes/indiekit-endpoint-posts": "^1.0.0-beta.25", "@rmdes/indiekit-endpoint-webmention-io": "^1.0.7", "@rmdes/indiekit-post-type-page": "^1.0.4", @@ -2200,6 +2201,24 @@ "@indiekit/indiekit": ">=1.0.0-beta.25" } }, + "node_modules/@rmdes/indiekit-endpoint-podroll": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@rmdes/indiekit-endpoint-podroll/-/indiekit-endpoint-podroll-1.0.11.tgz", + "integrity": "sha512-ShCVRfeGntKhXUtCDOIKbAAWBHM+ssj+QVCCGNq7rFL5tSdFEKFtwVCIYS/nvrQztzN1tCxT5AjjGGrXc9Xz9g==", + "license": "MIT", + "dependencies": { + "@indiekit/error": "^1.0.0-beta.25", + "express": "^5.0.0", + "sanitize-html": "^2.13.0", + "xml2js": "^0.6.2" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@indiekit/indiekit": ">=1.0.0-beta.25" + } + }, "node_modules/@rmdes/indiekit-endpoint-posts": { "version": "1.0.0-beta.25", "resolved": "https://registry.npmjs.org/@rmdes/indiekit-endpoint-posts/-/indiekit-endpoint-posts-1.0.0-beta.25.tgz", @@ -6937,6 +6956,28 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xss": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", diff --git a/package.json b/package.json index c07b9064..b18d3e0a 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@rmdes/indiekit-endpoint-github": "^1.2.3", "@rmdes/indiekit-endpoint-homepage": "^1.0.22", "@rmdes/indiekit-endpoint-lastfm": "^1.0.12", + "@rmdes/indiekit-endpoint-podroll": "^1.0.11", "@rmdes/indiekit-endpoint-posts": "^1.0.0-beta.25", "@rmdes/indiekit-endpoint-webmention-io": "^1.0.7", "@rmdes/indiekit-post-type-page": "^1.0.4",