···11+# Deployment Guide
22+33+This guide covers deploying the AT Protocol Podcast Host to Railway (or any Node.js hosting platform).
44+55+## Overview
66+77+The application is structured as:
88+- **Backend**: Express API server (Node.js)
99+- **Frontend**: React app built with Vite
1010+- **Deployment**: Single server serves both API and built React app
1111+1212+## Quick Deploy to Railway
1313+1414+### Prerequisites
1515+1616+1. [Railway account](https://railway.app)
1717+2. Git repository with your code
1818+3. AT Protocol credentials (Bluesky account + app password)
1919+2020+### Step-by-Step Deployment
2121+2222+#### 1. Prepare Your Repository
2323+2424+Make sure all code is committed:
2525+2626+```bash
2727+git add .
2828+git commit -m "Prepare for Railway deployment"
2929+git push origin main
3030+```
3131+3232+#### 2. Create Railway Project
3333+3434+1. Go to [Railway](https://railway.app)
3535+2. Click "New Project"
3636+3. Select "Deploy from GitHub repo"
3737+4. Choose your repository
3838+5. Railway will automatically detect it's a Node.js project
3939+4040+#### 3. Configure Environment Variables
4141+4242+In Railway dashboard, add these variables:
4343+4444+```env
4545+PORT=5000
4646+ATPROTO_SERVICE=https://bsky.social
4747+ATPROTO_IDENTIFIER=your-handle.bsky.social
4848+ATPROTO_PASSWORD=your-app-password
4949+NODE_ENV=production
5050+```
5151+5252+**Important:** Use an **app password**, not your main password!
5353+Get it from: Bluesky App → Settings → App Passwords
5454+5555+#### 4. Configure Build & Start Commands
5656+5757+Railway should auto-detect these, but verify:
5858+5959+**Build Command:**
6060+```bash
6161+npm run build
6262+```
6363+6464+**Start Command:**
6565+```bash
6666+npm start
6767+```
6868+6969+#### 5. Deploy
7070+7171+Railway will automatically:
7272+1. Install dependencies (`npm install`)
7373+2. Install client dependencies (`cd client && npm install`)
7474+3. Build React app (`npm run build`)
7575+4. Start the server (`npm start`)
7676+7777+Your app will be live at: `https://your-app.railway.app`
7878+7979+## Local Production Testing
8080+8181+Test the production build locally before deploying:
8282+8383+```bash
8484+# Build the React app
8585+npm run build
8686+8787+# Start the server (will serve built React app)
8888+npm start
8989+```
9090+9191+Visit `http://localhost:5000` - you should see the React app served by the backend.
9292+9393+## Environment Variables Reference
9494+9595+| Variable | Description | Required | Example |
9696+|----------|-------------|----------|---------|
9797+| `PORT` | Server port | No (defaults to 5000) | `5000` |
9898+| `ATPROTO_SERVICE` | AT Protocol service URL | Yes | `https://bsky.social` |
9999+| `ATPROTO_IDENTIFIER` | Your handle or DID | Yes | `yourname.bsky.social` |
100100+| `ATPROTO_PASSWORD` | App password | Yes | `xxxx-xxxx-xxxx-xxxx` |
101101+| `NODE_ENV` | Environment | No | `production` |
102102+103103+## How It Works
104104+105105+### Development Mode
106106+107107+```bash
108108+npm run dev:all
109109+```
110110+111111+- Backend runs on `http://localhost:5000`
112112+- React dev server on `http://localhost:3000`
113113+- React proxies API calls to backend
114114+115115+### Production Mode
116116+117117+```bash
118118+npm run build
119119+npm start
120120+```
121121+122122+- Single server on port 5000
123123+- Serves built React app from `client/dist/`
124124+- API routes at `/api/*`
125125+- React app handles all other routes
126126+127127+### Build Process
128128+129129+1. `npm install` - Install root dependencies
130130+2. `npm run build` - Runs `client:build`
131131+3. `client:build` - Runs `cd client && npm run build`
132132+4. Vite builds React app to `client/dist/`
133133+5. Express serves static files from `client/dist/`
134134+135135+## File Structure
136136+137137+```
138138+atproto-podcast/
139139+├── client/ # React frontend
140140+│ ├── dist/ # Built React app (generated)
141141+│ ├── src/ # React source
142142+│ ├── package.json
143143+│ └── vite.config.js
144144+├── src/ # Express backend
145145+│ ├── index.js # Main server (serves React + API)
146146+│ └── routes/
147147+├── package.json # Root package.json
148148+├── railway.json # Railway config
149149+└── nixpacks.toml # Build config
150150+```
151151+152152+## Deployment Architecture
153153+154154+```
155155+┌─────────────────────────────────────┐
156156+│ Railway Server │
157157+│ │
158158+│ ┌──────────────────────────────┐ │
159159+│ │ Express Server (Port 5000) │ │
160160+│ │ │ │
161161+│ │ ┌────────────┐ ┌──────────┐│ │
162162+│ │ │ React App │ │ API ││ │
163163+│ │ │ (Static) │ │ Routes ││ │
164164+│ │ │ / │ │ /api/* ││ │
165165+│ │ └────────────┘ └──────────┘│ │
166166+│ └──────────────────────────────┘ │
167167+└─────────────────────────────────────┘
168168+```
169169+170170+## Troubleshooting
171171+172172+### Build Fails on Railway
173173+174174+**Problem:** Build fails with "Cannot find module"
175175+176176+**Solution:** Ensure `postinstall` script in root `package.json`:
177177+```json
178178+"postinstall": "cd client && npm install"
179179+```
180180+181181+### App Shows API JSON Instead of React
182182+183183+**Problem:** Visiting root shows `{"name": "AT Protocol Podcast Host"}`
184184+185185+**Solution:** Build the React app first:
186186+```bash
187187+npm run build
188188+```
189189+190190+### Environment Variables Not Working
191191+192192+**Problem:** Server can't connect to AT Protocol
193193+194194+**Solution:**
195195+1. Check Railway environment variables are set
196196+2. Verify `ATPROTO_PASSWORD` is an **app password**
197197+3. Check Railway logs for connection errors
198198+199199+### Port Binding Issues
200200+201201+**Problem:** Railway shows "Address already in use"
202202+203203+**Solution:** Ensure server uses `process.env.PORT`:
204204+```javascript
205205+const PORT = process.env.PORT || 5000;
206206+```
207207+208208+## Alternative Deployment Platforms
209209+210210+### Render
211211+212212+1. Create new Web Service
213213+2. Build Command: `npm run build`
214214+3. Start Command: `npm start`
215215+4. Add environment variables
216216+217217+### Heroku
218218+219219+```bash
220220+heroku create your-app-name
221221+heroku config:set ATPROTO_SERVICE=https://bsky.social
222222+heroku config:set ATPROTO_IDENTIFIER=your-handle
223223+heroku config:set ATPROTO_PASSWORD=your-password
224224+git push heroku main
225225+```
226226+227227+### DigitalOcean App Platform
228228+229229+1. Create new app from GitHub
230230+2. Set build command: `npm run build`
231231+3. Set run command: `npm start`
232232+4. Configure environment variables
233233+234234+## Production Checklist
235235+236236+- [ ] Environment variables configured
237237+- [ ] Using app password (not main password)
238238+- [ ] Build succeeds locally
239239+- [ ] API endpoints accessible
240240+- [ ] React app loads correctly
241241+- [ ] File uploads work
242242+- [ ] RSS feed generates
243243+- [ ] AT Protocol publishing works
244244+245245+## Monitoring & Logs
246246+247247+### Railway Logs
248248+249249+View real-time logs in Railway dashboard:
250250+```
251251+Settings → Deployments → View Logs
252252+```
253253+254254+### Health Check Endpoint
255255+256256+Add to your backend:
257257+```javascript
258258+app.get('/health', (req, res) => {
259259+ res.json({ status: 'ok', timestamp: new Date() });
260260+});
261261+```
262262+263263+## Updating Your Deployment
264264+265265+```bash
266266+git add .
267267+git commit -m "Update podcast app"
268268+git push origin main
269269+```
270270+271271+Railway will automatically rebuild and redeploy.
272272+273273+## Custom Domain
274274+275275+1. Railway → Settings → Networking
276276+2. Add custom domain
277277+3. Update DNS records as shown
278278+4. Wait for SSL certificate
279279+280280+## Cost Estimation
281281+282282+Railway pricing (as of 2025):
283283+- **Hobby Plan**: $5/month
284284+- **Pro Plan**: $20/month + usage
285285+- Free tier available with limitations
286286+287287+Storage considerations:
288288+- Podcast files stored in AT Protocol (blob storage)
289289+- Minimal server storage needed
290290+291291+## Support
292292+293293+- Railway Docs: https://docs.railway.app
294294+- AT Protocol Docs: https://atproto.com/docs
295295+- Project Issues: https://github.com/your-repo/issues
+251
QUICKSTART.md
···11+# Quick Start Guide
22+33+Get your AT Protocol podcast hosting running in minutes!
44+55+## Local Development
66+77+### 1. Install Dependencies
88+99+```bash
1010+npm install
1111+```
1212+1313+This automatically installs both backend and frontend dependencies.
1414+1515+### 2. Configure Environment
1616+1717+Copy the example environment file:
1818+1919+```bash
2020+cp .env.example .env
2121+```
2222+2323+Edit `.env` with your AT Protocol credentials:
2424+2525+```env
2626+PORT=5000
2727+ATPROTO_SERVICE=https://bsky.social
2828+ATPROTO_IDENTIFIER=your-handle.bsky.social
2929+ATPROTO_PASSWORD=your-app-password
3030+```
3131+3232+**Get an app password:**
3333+1. Open Bluesky app
3434+2. Go to Settings → App Passwords
3535+3. Create new app password
3636+4. Copy and paste into `.env`
3737+3838+### 3. Run Development Servers
3939+4040+**Option A: Both servers at once (recommended)**
4141+4242+```bash
4343+npm run dev:all
4444+```
4545+4646+- Backend API: http://localhost:5000
4747+- React frontend: http://localhost:3000
4848+4949+**Option B: Run separately**
5050+5151+```bash
5252+# Terminal 1 - Backend
5353+npm run dev
5454+5555+# Terminal 2 - Frontend
5656+npm run client
5757+```
5858+5959+### 4. Access the App
6060+6161+Open http://localhost:3000 in your browser.
6262+6363+## Production Build
6464+6565+### Build the App
6666+6767+```bash
6868+npm run build
6969+```
7070+7171+This builds the React app to `client/dist/`
7272+7373+### Start Production Server
7474+7575+```bash
7676+npm start
7777+```
7878+7979+Access at http://localhost:5000 (both React app + API)
8080+8181+## Deploy to Railway
8282+8383+### 1. Push to GitHub
8484+8585+```bash
8686+git init
8787+git add .
8888+git commit -m "Initial commit"
8989+git remote add origin https://github.com/yourusername/your-repo.git
9090+git push -u origin main
9191+```
9292+9393+### 2. Deploy on Railway
9494+9595+1. Go to https://railway.app
9696+2. Click "New Project"
9797+3. Select "Deploy from GitHub repo"
9898+4. Choose your repository
9999+5. Add environment variables:
100100+ - `ATPROTO_SERVICE`
101101+ - `ATPROTO_IDENTIFIER`
102102+ - `ATPROTO_PASSWORD`
103103+6. Deploy!
104104+105105+Railway will automatically:
106106+- Install dependencies
107107+- Build the React app
108108+- Start the server
109109+110110+Your app will be live at `https://your-app.railway.app`
111111+112112+## First Steps After Deployment
113113+114114+### 1. Configure Your Podcast
115115+116116+1. Go to Settings page
117117+2. Fill in podcast details:
118118+ - Title
119119+ - Description
120120+ - Website
121121+ - Language
122122+123123+3. Click "Save Settings"
124124+125125+### 2. Upload Your First Episode
126126+127127+1. Click "Create New" button (or go to Episodes page)
128128+2. Select audio file (MP3, M4A, WAV, etc.)
129129+3. Enter episode title and description
130130+4. Click "Upload Episode"
131131+132132+### 3. Publish RSS Feed
133133+134134+1. Go to Settings page
135135+2. Scroll to "RSS Feed Distribution"
136136+3. Click "Publish to AT Protocol"
137137+4. Copy your `at://` URI
138138+139139+Your podcast is now decentralized! 🎉
140140+141141+## Next Steps
142142+143143+### Share Your Podcast
144144+145145+**For AT Protocol apps:**
146146+Share your `at://` URI:
147147+```
148148+at://did:plc:your-did/app.podcast.feed/self
149149+```
150150+151151+**For traditional podcast apps:**
152152+Share the HTTP proxy URL:
153153+```
154154+https://your-app.railway.app/api/feed/at-rss
155155+```
156156+157157+### Add to Podcast Directories
158158+159159+Submit your HTTP proxy URL to:
160160+- Apple Podcasts
161161+- Spotify
162162+- Google Podcasts
163163+- Pocket Casts
164164+- etc.
165165+166166+## Troubleshooting
167167+168168+### Can't Connect to AT Protocol
169169+170170+**Error:** "Authentication failed"
171171+172172+**Solution:**
173173+- Verify your handle is correct (e.g., `yourname.bsky.social`)
174174+- Ensure you're using an **app password**, not your main password
175175+- Check that `ATPROTO_SERVICE` is `https://bsky.social`
176176+177177+### Upload Fails
178178+179179+**Error:** "File too large"
180180+181181+**Solution:**
182182+- Check file format (MP3, M4A, WAV supported)
183183+- AT Protocol blob limit is ~1GB per file
184184+- Try compressing the audio file
185185+186186+### RSS Feed Not Publishing
187187+188188+**Error:** "Failed to publish RSS feed"
189189+190190+**Solution:**
191191+- Upload at least one episode first
192192+- Configure podcast metadata
193193+- Check AT Protocol connection
194194+195195+### Port Already in Use
196196+197197+**Error:** "EADDRINUSE: address already in use"
198198+199199+**Solution:**
200200+```bash
201201+# Kill the process on port 5000
202202+npx kill-port 5000
203203+204204+# Or change PORT in .env
205205+PORT=3001
206206+```
207207+208208+## Commands Reference
209209+210210+| Command | Description |
211211+|---------|-------------|
212212+| `npm install` | Install all dependencies |
213213+| `npm run dev` | Start backend only |
214214+| `npm run client` | Start React dev server |
215215+| `npm run dev:all` | Start both servers |
216216+| `npm run build` | Build React for production |
217217+| `npm start` | Start production server |
218218+219219+## File Structure
220220+221221+```
222222+atproto-podcast/
223223+├── client/ # React frontend
224224+│ ├── src/
225225+│ │ ├── components/ # UI components
226226+│ │ ├── pages/ # Page views
227227+│ │ └── services/ # API calls
228228+│ └── dist/ # Built app (production)
229229+├── src/ # Express backend
230230+│ ├── index.js # Server entry point
231231+│ └── routes/ # API routes
232232+├── .env # Environment config
233233+└── package.json # Dependencies & scripts
234234+```
235235+236236+## Getting Help
237237+238238+- **Deployment Issues:** See [DEPLOYMENT.md](DEPLOYMENT.md)
239239+- **RSS Feed Details:** See [RSS_AT_PROTOCOL.md](RSS_AT_PROTOCOL.md)
240240+- **API Documentation:** See [README.md](README.md#api-endpoints)
241241+242242+## Resources
243243+244244+- [AT Protocol Docs](https://atproto.com/docs)
245245+- [Bluesky](https://bsky.app)
246246+- [Railway Docs](https://docs.railway.app)
247247+- [Vite Docs](https://vitejs.dev)
248248+249249+---
250250+251251+**Ready to podcast on the decentralized web!** 🎙️
+273
README.md
···11+# AT Protocol Podcast Host
22+33+A podcast hosting platform that stores media files and RSS feeds using AT Protocol blob storage. Stream your podcasts directly from `at://` URIs!
44+55+## Features
66+77+- 🎙️ Upload podcast episodes to AT Protocol blob storage
88+- 📡 Generate RSS feeds from AT Protocol-stored content
99+- 🎵 Stream media files from `at://` URIs
1010+- 🔐 Secure authentication with Bluesky/AT Protocol
1111+- 📱 RESTful API for podcast management
1212+- 🌐 CORS-enabled for web player integration
1313+1414+## Prerequisites
1515+1616+- Node.js 18+
1717+- An AT Protocol account (e.g., Bluesky account)
1818+- App password for your AT Protocol account
1919+2020+## Setup
2121+2222+### 1. Clone and Install
2323+2424+```bash
2525+cd atproto-podcast
2626+npm install
2727+```
2828+2929+### 2. Configure Environment
3030+3131+Copy the example environment file:
3232+3333+```bash
3434+cp .env.example .env
3535+```
3636+3737+Edit `.env` with your AT Protocol credentials:
3838+3939+```env
4040+PORT=3000
4141+ATPROTO_SERVICE=https://bsky.social
4242+ATPROTO_IDENTIFIER=your-handle.bsky.social
4343+ATPROTO_PASSWORD=your-app-password
4444+```
4545+4646+**Note:** Get an app password from your Bluesky account settings, not your main password.
4747+4848+### 3. Run the Application
4949+5050+#### Development Mode (React + API)
5151+5252+Run both the React frontend and backend API concurrently:
5353+```bash
5454+npm run dev:all
5555+```
5656+5757+This starts:
5858+- Backend API on `http://localhost:5000`
5959+- React frontend on `http://localhost:3000` (with proxy to API)
6060+6161+Or run them separately:
6262+```bash
6363+# Terminal 1 - Backend API
6464+npm run dev
6565+6666+# Terminal 2 - React Frontend
6767+npm run client
6868+```
6969+7070+#### Production Mode
7171+7272+First, build the React application:
7373+```bash
7474+npm run build
7575+```
7676+7777+Then start the server:
7878+```bash
7979+npm start
8080+```
8181+8282+The server will serve the built React app and API on `http://localhost:5000`
8383+8484+## API Endpoints
8585+8686+### Upload Episode
8787+```
8888+POST /api/upload/episode
8989+Content-Type: multipart/form-data
9090+9191+Fields:
9292+- audio: Audio file (required)
9393+- title: Episode title (required)
9494+- description: Episode description (optional)
9595+- pubDate: Publication date (optional, defaults to now)
9696+```
9797+9898+### Get RSS Feed
9999+```
100100+GET /api/feed/rss
101101+102102+Returns: RSS 2.0 XML feed with podcast episodes
103103+```
104104+105105+### Update Feed Metadata
106106+```
107107+POST /api/feed/metadata
108108+Content-Type: application/json
109109+110110+{
111111+ "title": "Your Podcast Name",
112112+ "description": "Podcast description",
113113+ "link": "https://yoursite.com",
114114+ "language": "en"
115115+}
116116+```
117117+118118+### Get Feed Metadata
119119+```
120120+GET /api/feed/metadata
121121+122122+Returns: Current feed metadata
123123+```
124124+125125+### Stream Media
126126+```
127127+GET /api/media/stream/:did/:cid
128128+129129+Parameters:
130130+- did: Decentralized identifier
131131+- cid: Content identifier (from AT Protocol blob)
132132+```
133133+134134+### Get All Episodes
135135+```
136136+GET /api/media/episodes
137137+138138+Returns: List of all episodes with streaming URLs
139139+```
140140+141141+### Get Episode by ID
142142+```
143143+GET /api/media/episodes/:id
144144+145145+Returns: Single episode details
146146+```
147147+148148+## Usage Example
149149+150150+### Upload an Episode
151151+152152+```bash
153153+curl -X POST http://localhost:3000/api/upload/episode \
154154+ -F "audio=@/path/to/episode.mp3" \
155155+ -F "title=My First Episode" \
156156+ -F "description=This is my first podcast episode!"
157157+```
158158+159159+### Get RSS Feed
160160+161161+```bash
162162+curl http://localhost:3000/api/feed/rss
163163+```
164164+165165+### Stream an Episode
166166+167167+```bash
168168+curl http://localhost:3000/api/media/stream/{did}/{cid} --output episode.mp3
169169+```
170170+171171+## Project Structure
172172+173173+```
174174+atproto-podcast/
175175+├── client/ # React Frontend (Vite)
176176+│ ├── src/
177177+│ │ ├── components/ # React components (Header, Sidebar, etc.)
178178+│ │ ├── pages/ # Page components (Dashboard, Episodes, Settings)
179179+│ │ ├── services/ # API service layer
180180+│ │ ├── App.jsx # Main App component
181181+│ │ └── main.jsx # Entry point
182182+│ ├── public/ # Static assets
183183+│ ├── package.json
184184+│ └── vite.config.js # Vite configuration
185185+├── src/ # Backend API (Express)
186186+│ ├── index.js # Main server
187187+│ ├── atproto-client.js # AT Protocol client
188188+│ ├── routes/
189189+│ │ ├── upload.js # Upload endpoints
190190+│ │ ├── feed.js # RSS feed endpoints
191191+│ │ └── media.js # Media streaming endpoints
192192+│ └── storage/
193193+│ └── metadata.js # Episode metadata storage
194194+├── public/ # Legacy vanilla JS (deprecated)
195195+│ ├── index.html
196196+│ ├── app.js
197197+│ └── styles.css
198198+├── .env.example # Environment template
199199+├── .gitignore
200200+├── package.json
201201+└── README.md
202202+```
203203+204204+## How It Works
205205+206206+1. **Upload**: Audio files are uploaded via multipart form data
207207+2. **Store**: Files are uploaded to AT Protocol as blobs, receiving a CID (Content Identifier)
208208+3. **Metadata**: Episode metadata (title, description, CID) is stored locally
209209+4. **RSS**: RSS feed is dynamically generated from stored metadata
210210+5. **Stream**: Episodes are streamed directly from AT Protocol blob storage using `at://` URIs
211211+212212+## AT Protocol Integration
213213+214214+This project uses AT Protocol's blob storage to host podcast files:
215215+216216+- Audio files are stored as blobs in your AT Protocol repository
217217+- Each blob gets a unique CID (Content Identifier)
218218+- Files are accessible via `at://{did}/{cid}` URIs
219219+- The server acts as a bridge between HTTP and AT Protocol
220220+221221+## Development
222222+223223+The project uses ES modules and includes:
224224+225225+- Express.js for the web server
226226+- React + Vite for the frontend
227227+- @atproto/api for AT Protocol integration
228228+- Multer for file uploads
229229+- RSS package for feed generation
230230+231231+## Deployment
232232+233233+### Quick Deploy to Railway
234234+235235+1. Push your code to GitHub
236236+2. Connect to Railway: https://railway.app
237237+3. Set environment variables (see [DEPLOYMENT.md](DEPLOYMENT.md))
238238+4. Railway will automatically build and deploy
239239+240240+**One Command Production Build:**
241241+```bash
242242+npm run build # Builds React app
243243+npm start # Serves production app on port 5000
244244+```
245245+246246+### Deployment Platforms
247247+248248+This app works on:
249249+- ✅ Railway (recommended)
250250+- ✅ Render
251251+- ✅ Heroku
252252+- ✅ DigitalOcean App Platform
253253+- ✅ Any Node.js hosting
254254+255255+See [DEPLOYMENT.md](DEPLOYMENT.md) for detailed instructions.
256256+257257+## RSS Feed on AT Protocol
258258+259259+Your podcast RSS feed can be published as a decentralized AT Protocol record:
260260+261261+- Get an `at://` URI for your feed
262262+- Access it from any AT Protocol node
263263+- Maintain full ownership and portability
264264+265265+See [RSS_AT_PROTOCOL.md](RSS_AT_PROTOCOL.md) for details.
266266+267267+## License
268268+269269+MIT
270270+271271+## Contributing
272272+273273+Contributions welcome! Please feel free to submit a Pull Request.
+205
RSS_AT_PROTOCOL.md
···11+# RSS Feed on AT Protocol
22+33+This document explains how podcast RSS feeds are stored and distributed using AT Protocol's decentralized infrastructure.
44+55+## Overview
66+77+Your podcast RSS feed can be published as a custom AT Protocol record, making it:
88+- **Decentralized**: Not dependent on any single server
99+- **Portable**: Accessible via `at://` URIs from any AT Protocol client
1010+- **Verifiable**: Cryptographically linked to your DID (Decentralized Identifier)
1111+- **Updatable**: Can be republished when episodes change
1212+1313+## How It Works
1414+1515+### 1. RSS Feed Generation
1616+1717+The RSS feed is generated dynamically from:
1818+- Podcast metadata (title, description, language, etc.)
1919+- Episode records stored in AT Protocol
2020+- Media files stored as AT Protocol blobs
2121+2222+### 2. Publishing to AT Protocol
2323+2424+When you click "Publish to AT Protocol" in settings:
2525+2626+1. The RSS XML is generated with AT Protocol-specific metadata
2727+2. A custom record is created with collection type `app.podcast.feed`
2828+3. The record is stored at `at://[YOUR_DID]/app.podcast.feed/self`
2929+4. The record contains:
3030+ - RSS XML content
3131+ - Creation timestamp
3232+ - Feed metadata
3333+3434+```javascript
3535+{
3636+ $type: 'app.podcast.feed',
3737+ content: '<rss>...</rss>',
3838+ createdAt: '2025-10-22T02:30:00.000Z',
3939+ metadata: { /* podcast info */ }
4040+}
4141+```
4242+4343+### 3. Accessing the Feed
4444+4545+**AT Protocol URI (Decentralized):**
4646+```
4747+at://did:plc:your-did-here/app.podcast.feed/self
4848+```
4949+5050+**HTTP Proxy (For Compatibility):**
5151+```
5252+http://localhost:5000/api/feed/at-rss
5353+```
5454+5555+## API Endpoints
5656+5757+### Publish Feed to AT Protocol
5858+5959+```bash
6060+POST /api/feed/publish
6161+```
6262+6363+Creates or updates the RSS feed record on AT Protocol.
6464+6565+**Response:**
6666+```json
6767+{
6868+ "success": true,
6969+ "uri": "at://did:plc:xyz123/app.podcast.feed/self",
7070+ "cid": "bafyreiabc123...",
7171+ "message": "RSS feed published to AT Protocol"
7272+}
7373+```
7474+7575+### Get Feed URI Information
7676+7777+```bash
7878+GET /api/feed/uri
7979+```
8080+8181+Returns information about the published feed.
8282+8383+**Response:**
8484+```json
8585+{
8686+ "atUri": "at://did:plc:xyz123/app.podcast.feed/self",
8787+ "httpProxy": "http://localhost:5000/api/feed/at-rss",
8888+ "did": "did:plc:xyz123",
8989+ "published": true
9090+}
9191+```
9292+9393+### Fetch Feed from AT Protocol
9494+9595+```bash
9696+GET /api/feed/at-rss
9797+```
9898+9999+Retrieves the RSS feed from the AT Protocol record.
100100+101101+**Response:** RSS XML (application/rss+xml)
102102+103103+### Legacy HTTP Endpoint
104104+105105+```bash
106106+GET /api/feed/rss
107107+```
108108+109109+Generates RSS feed on-the-fly (does not require publishing).
110110+111111+## AT Protocol RSS Extensions
112112+113113+The RSS feed includes custom AT Protocol elements:
114114+115115+```xml
116116+<rss xmlns:atproto="https://atproto.com/ns/1.0">
117117+ <channel>
118118+ <link>at://did:plc:xyz123/app.podcast.feed/self</link>
119119+ <item>
120120+ <title>Episode 1</title>
121121+ <atproto:cid>bafyreiabc123...</atproto:cid>
122122+ <atproto:uri>at://did:plc:xyz123/app.podcast.episode/bafyreiabc123</atproto:uri>
123123+ <atproto:did>did:plc:xyz123</atproto:did>
124124+ </item>
125125+ </channel>
126126+</rss>
127127+```
128128+129129+## Benefits
130130+131131+### For Podcasters
132132+133133+1. **Ownership**: Your feed is tied to your DID, not a hosting service
134134+2. **Portability**: Move your podcast without changing feed URLs
135135+3. **Resilience**: Feed can be accessed from any AT Protocol node
136136+4. **Transparency**: All changes are recorded on the protocol
137137+138138+### For Listeners
139139+140140+1. **Trust**: Verify feed authenticity via cryptographic signatures
141141+2. **Availability**: Access from multiple sources/mirrors
142142+3. **Privacy**: Can access via AT Protocol without centralized tracking
143143+144144+## Usage in Podcast Apps
145145+146146+### AT Protocol-Native Apps
147147+148148+Apps that understand `at://` URIs can directly fetch the feed:
149149+150150+```javascript
151151+const agent = new BskyAgent({ service: 'https://bsky.social' });
152152+const record = await agent.com.atproto.repo.getRecord({
153153+ repo: 'did:plc:xyz123',
154154+ collection: 'app.podcast.feed',
155155+ rkey: 'self'
156156+});
157157+const rssXml = record.value.content;
158158+```
159159+160160+### Legacy Podcast Apps
161161+162162+Use the HTTP proxy URL which fetches from AT Protocol:
163163+```
164164+http://localhost:5000/api/feed/at-rss
165165+```
166166+167167+## Technical Details
168168+169169+### Record Structure
170170+171171+- **Collection**: `app.podcast.feed`
172172+- **Record Key**: `self` (fixed, only one feed per user)
173173+- **Type**: Custom record type (not an official lexicon yet)
174174+- **Content**: RSS XML as string
175175+- **Metadata**: Podcast information object
176176+177177+### CID (Content Identifier)
178178+179179+Each time you publish/update the feed:
180180+- A new CID is generated
181181+- The CID uniquely identifies this version of the feed
182182+- Old versions remain accessible if CID is known
183183+184184+### DID (Decentralized Identifier)
185185+186186+Your DID is your permanent identity on AT Protocol:
187187+- Example: `did:plc:xyz123abc456`
188188+- Portable across different services
189189+- Cryptographically verifiable
190190+191191+## Future Enhancements
192192+193193+- [ ] Subscribe to feed updates via AT Protocol subscriptions
194194+- [ ] Episode-level records (each episode as separate record)
195195+- [ ] Podcast discovery via AT Protocol feeds
196196+- [ ] Analytics without centralized tracking
197197+- [ ] Support for podcast:* namespace tags
198198+- [ ] Automatic re-publishing on episode upload
199199+200200+## Learn More
201201+202202+- [AT Protocol Docs](https://atproto.com/docs)
203203+- [AT Protocol Repository Specification](https://atproto.com/specs/repository)
204204+- [RSS 2.0 Specification](https://www.rssboard.org/rss-specification)
205205+- [Podcast Namespace](https://github.com/Podcastindex-org/podcast-namespace)
+91
WARP.md
···11+# WARP.md
22+33+This file provides guidance to WARP (warp.dev) when working with code in this repository.
44+55+## Project Overview
66+77+AT Protocol Podcast Host is a podcast hosting platform that stores media files and RSS feeds using AT Protocol blob storage. Audio files are uploaded as blobs to AT Protocol, generating unique CIDs (Content Identifiers) accessible via `at://` URIs. The server acts as a bridge between HTTP clients and AT Protocol storage.
88+99+## Common Development Commands
1010+1111+### Running the Server
1212+- **Development mode** (with auto-reload): `npm run dev`
1313+- **Production mode**: `npm start`
1414+- Server runs on `http://localhost:3000` by default
1515+1616+### Installation
1717+- Install dependencies: `npm install`
1818+- Copy environment template: `cp .env.example .env`
1919+2020+### Testing API Endpoints
2121+- **Upload episode**:
2222+ ```powershell
2323+ curl -X POST http://localhost:3000/api/upload/episode `
2424+ -F "audio=@/path/to/episode.mp3" `
2525+ -F "title=Episode Title" `
2626+ -F "description=Episode description"
2727+ ```
2828+- **Get RSS feed**: `curl http://localhost:3000/api/feed/rss`
2929+- **Stream episode**: `curl http://localhost:3000/api/media/stream/{did}/{cid} --output episode.mp3`
3030+3131+## Architecture
3232+3333+### Core Components
3434+3535+**AT Protocol Integration** (`src/atproto-client.js`):
3636+- Uses `@atproto/api` BskyAgent for authentication and blob operations
3737+- Manages session state with `isAuthenticated` flag
3838+- Key methods: `getAgent()`, `uploadBlob()`, `getBlob()`
3939+- Authenticates once per server lifecycle using credentials from `.env`
4040+4141+**Storage Layer** (`src/storage/metadata.js`):
4242+- File-based metadata storage in `podcast-metadata.json` at project root
4343+- Stores episode metadata (title, description, CID, pubDate) and feed configuration
4444+- Episodes sorted by pubDate in descending order
4545+- No database required; metadata persists locally
4646+4747+**Route Structure** (`src/routes/`):
4848+- **upload.js**: Handles multipart file uploads via Multer, uploads blobs to AT Protocol, stores metadata, cleans up temp files
4949+- **feed.js**: Generates RSS 2.0 XML feeds dynamically from stored metadata, includes custom `atproto:cid` elements
5050+- **media.js**: Streams blobs from AT Protocol using DID/CID pairs, provides episode listing endpoints
5151+5252+### Key Architectural Patterns
5353+5454+1. **Blob Storage Flow**: Client uploads → Multer temp storage → AT Protocol blob upload → CID generation → Local metadata storage → Temp file cleanup
5555+2. **Streaming Flow**: Client requests DID/CID → Server fetches from AT Protocol XRPC endpoint → Proxies binary data to client
5656+3. **Authentication**: Single agent instance shared across all modules, lazy authentication on first API call
5757+4. **URL Construction**: RSS feed and API responses construct full URLs using `req.protocol` and `req.get('host')` for proper streaming links
5858+5959+## Environment Configuration
6060+6161+Required environment variables in `.env`:
6262+- `PORT`: Server port (default: 3000)
6363+- `ATPROTO_SERVICE`: AT Protocol service URL (usually `https://bsky.social`)
6464+- `ATPROTO_IDENTIFIER`: Your Bluesky/AT Protocol handle
6565+- `ATPROTO_PASSWORD`: App password (NOT main account password) from Bluesky settings
6666+6767+The server requires valid AT Protocol credentials to start. Obtain an app password from Bluesky account settings for secure authentication.
6868+6969+## Project Structure
7070+7171+```
7272+src/
7373+├── index.js # Express server initialization, route registration
7474+├── atproto-client.js # AT Protocol authentication and blob operations
7575+├── routes/
7676+│ ├── upload.js # Episode upload endpoint
7777+│ ├── feed.js # RSS feed generation and metadata management
7878+│ └── media.js # Blob streaming and episode retrieval
7979+└── storage/
8080+ └── metadata.js # JSON-based metadata persistence
8181+```
8282+8383+## Important Notes
8484+8585+- Project uses ES modules (`"type": "module"` in package.json)
8686+- No test framework is currently configured
8787+- No linter or type checker configured
8888+- Temporary uploads go to `public/uploads/` (cleaned up after blob upload)
8989+- Metadata file `podcast-metadata.json` is created automatically if missing
9090+- AT Protocol blobs are immutable; reuploading creates new CIDs
9191+- DID (Decentralized Identifier) comes from authenticated agent session
···11+# React + Vite
22+33+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
44+55+Currently, two official plugins are available:
66+77+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
88+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
99+1010+## React Compiler
1111+1212+The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
1313+1414+## Expanding the ESLint configuration
1515+1616+If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.