No description
  • Python 51.2%
  • HTML 44.6%
  • JavaScript 2.6%
  • Dockerfile 0.6%
  • Nix 0.6%
  • Other 0.4%
Find a file
Diyon b0855e89b0
Some checks failed
CI/CD Workflow / Test Backend (Python) (push) Successful in 35s
CI/CD Workflow / Test Frontend (Node.js) (push) Successful in 10s
CI/CD Workflow / Build and Publish Docker Image (push) Failing after 8s
CI/CD Workflow / Trigger Dockhand Deployment (push) Has been skipped
CI: Ensure Buildx uses authenticated Docker Hub session to bypass rate limits
2026-05-11 18:09:55 +00:00
.forgejo/workflows CI: Ensure Buildx uses authenticated Docker Hub session to bypass rate limits 2026-05-11 18:09:55 +00:00
.opencode Complete webhook feature: import tab, bookmarklet page, tests 2026-03-26 17:56:34 +01:00
.sisyphus/plans Complete webhook feature: import tab, bookmarklet page, tests 2026-03-26 17:56:34 +01:00
backend CI: Fix Dockerfile to use Alpine as base and correct package manager to bypass Docker Hub rate limits 2026-05-11 18:08:56 +00:00
data Fix: Episode edit accessibility and list view filter behavior 2026-05-11 13:54:09 +00:00
frontend UI: Add desktop preview sidebar and card selection 2026-05-11 15:46:23 +00:00
mcp-server adding security fixes 2026-03-25 09:27:36 +01:00
n8n Add n8n Telegram Bot Integration 2026-03-24 23:17:18 +00:00
test_reports Complete webhook feature: import tab, bookmarklet page, tests 2026-03-26 17:56:34 +01:00
traefik fix: set production URLs for pcm.f4mily.net deployment 2026-04-24 10:50:14 +00:00
.env.example adding authentication via OAuth provider 2026-03-25 08:41:45 +01:00
.gitignore Add .gitignore (removed .env from history) 2026-03-25 20:05:05 +00:00
AGENTS.md Complete webhook feature: import tab, bookmarklet page, tests 2026-03-26 17:56:34 +01:00
docker-compose.override.yml Fix: addLink() and API token datetime comparison 2026-03-26 19:29:37 +00:00
docker-compose.yml Fix: Episode edit accessibility and list view filter behavior 2026-05-11 13:54:09 +00:00
flake.lock Complete webhook feature: import tab, bookmarklet page, tests 2026-03-26 17:56:34 +01:00
flake.nix Complete webhook feature: import tab, bookmarklet page, tests 2026-03-26 17:56:34 +01:00
import_data.py Initial commit of PCM Podcast Manager 2026-03-24 09:42:37 +00:00
pytest.ini Fix test infrastructure 2026-03-26 18:07:35 +01:00
README.md docs: complete README with full API docs and configuration 2026-03-28 13:49:26 +01:00

PCM - Podcast Content Manager

A lightweight web app for managing podcast episode research — collecting news links, assigning them to episodes, and archiving after broadcast.

Features

  • Inbox → Planned → Archived workflow for news items
  • Episode management with episode numbers and air dates
  • Tagging & Topics for categorizing news with colors
  • One-click archive when an episode airs (archives all assigned news)
  • Responsive UI with Tailwind CSS card layout
  • OAuth2 authentication (Google, GitHub, OIDC providers)
  • Multi-user support with isolated data
  • API tokens for programmatic access
  • MCP server for AI assistant integration
  • Incoming links with webhook processing

Tech Stack

  • Backend: Python 3.11, FastAPI, SQLite
  • Frontend: Vanilla JS, Tailwind CSS
  • Deployment: Docker Compose / Kubernetes

Quick Start

docker compose up -d --build

Data is persisted in ./data/pcm.db (SQLite).

Configuration

Copy .env.example to .env and configure:

cp .env.example .env

Environment Variables

Variable Default Description
HOST (required for Traefik) Your domain, e.g. pcm.example.com
TRAEFIK_EXTERNAL false Set true to join existing Traefik network
TRAEFIK_NETWORK pcm-network Docker network name
CERT_RESOLVER hetzner Traefik certificate resolver name
API_BASE_URL /api Path prefix for the API
DATABASE_PATH /app/data/pcm.db SQLite database path
BACKEND_URL http://localhost:8000 Backend URL for CORS and OAuth
FRONTEND_URL http://localhost:8080 Frontend URL for OAuth redirect
CORS_ORIGINS http://localhost:8080 Comma-separated allowed origins
JWT_SECRET_KEY (auto-generated) Secret for JWT tokens
JWT_EXPIRE_DAYS 7 JWT token expiration
SECURE_COOKIES true Require HTTPS for cookies

OAuth Providers

Configure at least one provider to enable login:

Google OAuth

GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret

Get credentials at: https://console.cloud.google.com/apis/credentials

GitHub OAuth

GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret

Get credentials at: https://github.com/settings/developers

Generic OIDC (Keycloak, Authentik, etc.)

OIDC_ISSUER=https://your-idp.example.com
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
OIDC_NAME=Keycloak

Deployment

Traefik (Automated HTTPS)

Deploy behind an existing Traefik reverse proxy. Containers join Traefik's Docker network and are auto-discovered.

Prerequisites on the host:

  1. Traefik is running with its Docker provider enabled
  2. The network Traefik uses is named pcm-network

If your Traefik uses a different network name, either rename it:

docker network create pcm-network
docker network connect pcm-network <your-traefik-container>

Or adjust your Traefik config to use pcm-network.

Deploy:

# Start with Traefik integration
TRAEFIK_EXTERNAL=true HOST=pcm.example.com docker compose up -d --build

Traefik labels on the containers handle routing, TLS, and certificate provisioning via your existing DNS challenge resolver.

Docker Compose Services

Service Port Description
backend 8000 FastAPI backend with health check
frontend 80 Static HTML/JS served by nginx
mcp-server (disabled) MCP server for AI assistants (enable with --profile mcp)

MCP Server (AI Integration)

The MCP server allows AI assistants (Claude Desktop, etc.) to add news items directly to PCM.

Enable MCP server:

docker compose --profile mcp up -d

Configure in Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "pcm": {
      "command": "docker",
      "args": ["exec", "-i", "pcm-mcp", "python3", "/app/server.py"],
      "env": {
        "DB_PATH": "/app/data/pcm.db"
      }
    }
  }
}

See mcp-server/README.md for full documentation.

API Documentation

All endpoints (except /health) require authentication via session cookie or API token.

Authentication

  • Session cookie: Login via /api/auth/login/{provider} → sets session_token cookie
  • API Token: Include header Authorization: Bearer <token> (for programmatic access)

Health Check

Method Endpoint Description
GET /health Database health check (no auth required)

Episodes

Method Endpoint Description
GET /api/episodes List all episodes with news count
POST /api/episodes Create episode {number, air_date, status}
PUT /api/episodes/{id} Update episode {air_date, status}
DELETE /api/episodes/{id} Delete episode (moves news to inbox)
POST /api/episodes/{id}/archive Mark aired + archive all planned news

News Items

Method Endpoint Description
GET /api/news List news items (filters: status, episode_id)
POST /api/news Create news item
PUT /api/news/{id} Update news fields
POST /api/news/{id}/assign/{ep_id} Assign to episode (status → planned)
DELETE /api/news/{id} Hard delete

Tags

Method Endpoint Description
GET /api/tags List all tags
POST /api/tags Create tag {name}
PUT /api/tags/{id} Update tag name (updates all news items)
DELETE /api/tags/{id} Delete tag (removes from all news items)

Topics (Labels)

Method Endpoint Description
GET /api/topics List all topics with colors
POST /api/topics Create topic {name, color}
PUT /api/topics/{id} Update topic {name, color}
DELETE /api/topics/{id} Delete topic (fails if in use)
GET /api/labels List topic names only (alias)

API Tokens

Method Endpoint Description
GET /api/tokens List user's API tokens
POST /api/tokens Create token {name, scopes, expires_in_days}
DELETE /api/tokens/{id} Revoke token

Scopes: read, write (default: both)

Method Endpoint Description
GET /api/links List incoming links (filter: status)
POST /api/links Submit URL for processing {url, title, summary, highlights}
GET /api/links/{id} Get link details
DELETE /api/links/{id} Delete link
POST /api/links/{id}/process Trigger processing
POST /api/links/{id}/retry Retry failed processing

Webhooks

Method Endpoint Description
GET /api/webhooks List webhook configs
POST /api/webhooks Create webhook {url, name}
PUT /api/webhooks/{id} Update webhook {url, name, is_active}
DELETE /api/webhooks/{id} Delete webhook
POST /api/webhooks/callback AI agent callback (creates news from processed link)

Database Schema

-- Users (OAuth accounts)
users (id, email, name, provider, provider_user_id, picture, created_at, last_login_at)

-- Episodes
episodes (id, number, air_date, status)

-- News items
news_items (id, url, title, summary, highlights, status, tags, label, episode_id, created_at)

-- Topics/Labels with colors
topics (id, name, color)

-- Tags
tags (id, name)

-- API tokens
api_tokens (id, name, token_hash, scopes, expires_at, last_used_at)

-- Incoming links queue
incoming_links (id, url, title, summary, highlights, status, error_message, processed_at)

-- Webhook configs
webhook_configs (id, url, name, is_active)

Kubernetes Deployment Example

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pcm
  namespace: pcm
spec:
  replicas: 1
  strategy:
    type: Recreate          # SQLite = RWO volume
  selector:
    matchLabels:
      app: pcm
  template:
    metadata:
      labels:
        app: pcm
    spec:
      containers:
      - name: backend
        image: ghcr.io/kreativmonkey/pcm-backend:latest
        ports:
        - containerPort: 8000
        volumeMounts:
        - name: data
          mountPath: /app/data
      - name: frontend
        image: ghcr.io/kreativmonkey/pcm-frontend:latest
        ports:
        - containerPort: 80
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: pcm-data
---
apiVersion: v1
kind: Service
metadata:
  name: pcm
  namespace: pcm
spec:
  selector:
    app: pcm
  ports:
  - name: api
    port: 8000
    targetPort: 8000
  - name: web
    port: 80
    targetPort: 80
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pcm-data
  namespace: pcm
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 1Gi

Note: Use strategy: Recreate because SQLite needs exclusive RWO access. For production, consider switching to PostgreSQL.

Future Plans

  • Drag & drop between columns
  • RSS feed import
  • Export to show notes (Markdown)
  • Browser Add-On
  • Teams integration