# CastForge

CastForge is a podcast hosting platform for publishing, managing, and distributing audio shows. It provides episode hosting, RSS feed generation, analytics, private feeds, scheduled publishing, and team-based show management.

## Overview

CastForge is designed for creators, networks, and organizations that need a self-hosted podcast publishing workflow. The platform stores uploaded audio files, generates standards-compliant podcast RSS feeds, exposes public show pages, and provides an admin dashboard for managing episodes, hosts, sponsors, and distribution settings.

Typical use cases include:

- Hosting public podcast feeds for Apple Podcasts, Spotify, and other directories
- Publishing subscriber-only or internal company podcasts
- Managing multiple shows from a single dashboard
- Tracking downloads, listener clients, and geographic trends
- Scheduling episodes and maintaining show metadata

## Features

- Multi-show podcast hosting
- Episode upload and metadata management
- Automatic RSS feed generation
- Scheduled publishing
- Public podcast pages
- Private podcast feeds with signed URLs
- Team roles and permissions
- Download analytics and listener reports
- Audio file validation
- S3-compatible object storage support
- Custom domain support
- Webhook events for publishing workflows
- Configurable CDN asset URLs
- PostgreSQL-backed application data
- Redis-backed queues and caching

## Requirements

- Node.js 20 or newer
- PostgreSQL 15 or newer
- Redis 7 or newer
- S3-compatible storage bucket
- npm 10 or newer

## Install

Clone the repository:

```bash
git clone https://github.com/example/castforge.git
cd castforge
```

Install dependencies:

```bash
npm install
```

Create the environment file:

```bash
cp .env.example .env
```

Run database migrations:

```bash
npm run db:migrate
```

Create an admin user:

```bash
npm run users:create-admin
```

Start the development server:

```bash
npm run dev
```

The application will be available at:

```text
http://localhost:3000
```

## Usage

### Create a show

Sign in to the admin dashboard and create a new show from **Shows > New Show**. Add the show title, description, artwork, language, category, author name, and owner email.

### Upload an episode

Open a show and select **Episodes > New Episode**. Upload an MP3 or AAC audio file, enter the episode title and description, then choose whether to publish immediately or schedule for later.

### Publish the RSS feed

Each show has a generated RSS feed URL:

```text
https://podcasts.example.com/feeds/{show-slug}.xml
```

Submit this feed URL to podcast directories such as Apple Podcasts, Spotify, Amazon Music, and Pocket Casts.

### Manage private feeds

Private feeds can be enabled per show. When enabled, each subscriber receives a unique feed URL that can be revoked from the dashboard.

Example private feed URL:

```text
https://podcasts.example.com/private/{token}/feed.xml
```

### View analytics

Analytics are available from the show dashboard. CastForge tracks downloads by episode, client, platform, country, and referrer while filtering common bots and duplicate requests.

## Configuration

CastForge is configured with environment variables.

### Application

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `APP_URL` | Yes | `http://localhost:3000` | Public URL for the web application |
| `APP_ENV` | No | `development` | Application environment |
| `PORT` | No | `3000` | HTTP server port |
| `SESSION_SECRET` | Yes |  | Secret used to sign session cookies |
| `ADMIN_EMAIL` | No |  | Default admin contact email |

### Database

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `DATABASE_URL` | Yes |  | PostgreSQL connection string |
| `DB_POOL_MIN` | No | `2` | Minimum database connections |
| `DB_POOL_MAX` | No | `10` | Maximum database connections |

Example:

```env
DATABASE_URL=postgres://castforge:password@localhost:5432/castforge
```

### Redis

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `REDIS_URL` | Yes |  | Redis connection string |
| `QUEUE_CONCURRENCY` | No | `5` | Number of background jobs processed concurrently |

Example:

```env
REDIS_URL=redis://localhost:6379
```

### Storage

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `STORAGE_DRIVER` | Yes | `s3` | Storage backend |
| `S3_ENDPOINT` | No |  | Custom S3 endpoint |
| `S3_REGION` | Yes |  | S3 region |
| `S3_BUCKET` | Yes |  | Bucket used for audio and artwork |
| `S3_ACCESS_KEY_ID` | Yes |  | S3 access key |
| `S3_SECRET_ACCESS_KEY` | Yes |  | S3 secret key |
| `S3_FORCE_PATH_STYLE` | No | `false` | Use path-style S3 URLs |
| `ASSET_BASE_URL` | No |  | CDN or public asset base URL |

### Email

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `SMTP_HOST` | No |  | SMTP server hostname |
| `SMTP_PORT` | No | `587` | SMTP server port |
| `SMTP_USER` | No |  | SMTP username |
| `SMTP_PASSWORD` | No |  | SMTP password |
| `EMAIL_FROM` | No |  | Sender address for system emails |

### RSS

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `FEED_CACHE_TTL_SECONDS` | No | `300` | RSS feed cache duration |
| `PRIVATE_FEED_TOKEN_TTL_DAYS` | No | `0` | Expiration for private feed tokens; `0` disables expiration |
| `MAX_EPISODE_FILE_SIZE_MB` | No | `512` | Maximum audio upload size |

### Webhooks

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `WEBHOOK_SECRET` | No |  | Secret used to sign outgoing webhook payloads |
| `WEBHOOK_TIMEOUT_MS` | No | `5000` | Webhook request timeout |

## Scripts

```bash
npm run dev              # Start the development server
npm run build            # Build the production bundle
npm run start            # Start the production server
npm run db:migrate       # Run database migrations
npm run db:rollback      # Roll back the latest migration
npm run worker           # Start the background worker
npm run test             # Run the test suite
npm run lint             # Run lint checks
```

## Production Deployment

Build the application:

```bash
npm run build
```

Run migrations:

```bash
npm run db:migrate
```

Start the web server:

```bash
npm run start
```

Start the background worker in a separate process:

```bash
npm run worker
```

For production, run CastForge behind a reverse proxy such as Nginx or Caddy, enable HTTPS, configure a durable PostgreSQL instance, and serve uploaded assets through a CDN when possible.

## License

MIT