Skip to content

Commit 3379617

Browse files
Switch from @astrojs/db to drizzle (#45)
* Switch from @astrojs/db to drizzle * Update ci.yml
1 parent 37959d0 commit 3379617

17 files changed

Lines changed: 644 additions & 238 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ jobs:
8080
run: pnpm exec playwright install --with-deps
8181
- name: Run Playwright tests
8282
run: pnpm exec playwright test
83+
env:
84+
ASTRO_DB_REMOTE_URL: ${{ secrets.ASTRO_DB_REMOTE_URL }}
85+
ASTRO_DB_APP_TOKEN: ${{ secrets.ASTRO_DB_APP_TOKEN }}
8386
- uses: actions/upload-artifact@v4
8487
if: always()
8588
with:

CLAUDE.md

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
# CLAUDE.md
22

3-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
3+
This file provides guidance to Claude Code (claude.ai/code) when working with
4+
code in this repository.
45

56
## What is Starpod?
67

7-
Starpod is an open-source Astro-based podcast website generator. It creates a full podcast site from an RSS feed and a `starpod.config.ts` configuration file. The reference deployment is [whiskey.fm](https://whiskey.fm) (Whiskey Web and Whatnot podcast).
8+
Starpod is an open-source Astro-based podcast website generator. It creates a
9+
full podcast site from an RSS feed and a `starpod.config.ts` configuration file.
10+
The reference deployment is [whiskey.fm](https://whiskey.fm) (Whiskey Web and
11+
Whatnot podcast).
812

913
## Commands
1014

1115
- **Dev server:** `pnpm dev` (runs on localhost:4321)
12-
- **Build:** `pnpm build` (runs `astro check` then `astro build --remote`)
16+
- **Build:** `pnpm build` (runs `astro check` then `astro build`)
1317
- **Lint:** `pnpm lint` (ESLint with caching)
1418
- **Lint fix:** `pnpm lint:fix`
1519
- **All tests:** `pnpm test` (runs unit + e2e concurrently)
1620
- **Unit tests only:** `pnpm test:unit` (Vitest)
1721
- **Single unit test:** `pnpm exec vitest run tests/unit/Player.test.tsx`
1822
- **E2E tests only:** `pnpm test:e2e` (Playwright, auto-starts dev server)
1923
- **Seed remote DB:** `pnpm db:seed`
24+
- **Push schema to DB:** `pnpm db:push`
25+
- **Drizzle Studio:** `pnpm db:studio`
2026

2127
## Architecture
2228

@@ -25,37 +31,60 @@ Starpod is an open-source Astro-based podcast website generator. It creates a fu
2531
- **Astro 5** with static output, deployed to Vercel
2632
- **Preact** for interactive components (player, search, contact form)
2733
- **Tailwind CSS v4** via Vite plugin
28-
- **Astro DB** (Turso/libSQL) for episode guests and sponsors
34+
- **Drizzle ORM** with Turso/libSQL for episode guests and sponsors
2935
- **Valibot** for config validation
3036

3137
### Key Configuration
3238

33-
- `starpod.config.ts` — podcast metadata (hosts, platforms, RSS feed URL, description). Uses `defineStarpodConfig()` from `src/utils/config.ts` for type safety and validation.
34-
- `astro.config.mjs` — Astro config with Vercel adapter, Preact, sitemap, and DB integrations.
39+
- `starpod.config.ts` — podcast metadata (hosts, platforms, RSS feed URL,
40+
description). Uses `defineStarpodConfig()` from `src/utils/config.ts` for type
41+
safety and validation.
42+
- `astro.config.mjs` — Astro config with Vercel adapter, Preact, and sitemap
43+
integrations.
44+
- `drizzle.config.ts` — Drizzle Kit config for schema push, migrations, and
45+
studio.
3546

3647
### Data Flow
3748

38-
Episodes are fetched from the RSS feed at build time via `src/lib/rss.ts`. Guest/sponsor data lives in `db/data/` as TypeScript files and is seeded to Turso via `db/seed.ts`. The DB schema is in `db/config.ts` with tables: Episode, Person, HostOrGuest, Sponsor, SponsorForEpisode.
49+
Episodes are fetched from the RSS feed at build time via `src/lib/rss.ts`.
50+
Guest/sponsor data lives in `db/data/` as TypeScript files and is seeded to
51+
Turso via `db/seed.ts`. The DB schema is in `db/schema.ts` (Drizzle ORM) with
52+
tables: Episode, Person, HostOrGuest, Sponsor, SponsorForEpisode. The DB
53+
connection is configured in `db/index.ts`.
3954

4055
### Source Structure
4156

42-
- `src/pages/` — Astro pages and API routes. Dynamic episode pages use `[episode].astro`. LLM-friendly `.html.md.ts` endpoints generate markdown versions.
43-
- `src/components/` — Mix of `.astro` (static) and `.tsx` (Preact interactive) components. The audio player (`src/components/player/`) and search dialog are Preact.
57+
- `src/pages/` — Astro pages and API routes. Dynamic episode pages use
58+
`[episode].astro`. LLM-friendly `.html.md.ts` endpoints generate markdown
59+
versions.
60+
- `src/components/` — Mix of `.astro` (static) and `.tsx` (Preact interactive)
61+
components. The audio player (`src/components/player/`) and search dialog are
62+
Preact.
4463
- `src/components/state.ts` — Preact signals for shared player state.
45-
- `src/lib/` — Core utilities: RSS fetching, image optimization, LLM content generation.
46-
- `src/content/transcripts/` — Markdown transcript files named by episode number.
64+
- `src/lib/` — Core utilities: RSS fetching, image optimization, LLM content
65+
generation.
66+
- `src/content/transcripts/` — Markdown transcript files named by episode
67+
number.
4768
- `src/layouts/Layout.astro` — Single shared layout.
69+
- `db/` — Database schema (`schema.ts`), connection (`index.ts`), seed script
70+
(`seed.ts`), and static data files (`data/`).
4871

4972
### Testing
5073

51-
- **Unit tests** (`tests/unit/`): Vitest + jsdom + @testing-library/preact. Setup file at `tests/unit/test-setup.ts`.
52-
- **E2E tests** (`tests/e2e/`): Playwright testing against chromium, firefox, and webkit.
74+
- **Unit tests** (`tests/unit/`): Vitest + jsdom + @testing-library/preact.
75+
Setup file at `tests/unit/test-setup.ts`.
76+
- **E2E tests** (`tests/e2e/`): Playwright testing against chromium, firefox,
77+
and webkit.
5378

5479
### TypeScript
5580

56-
Strict mode with `baseUrl: "."` allowing bare `src/...` imports. JSX is configured for Preact (`jsxImportSource: "preact"`).
81+
Strict mode with `baseUrl: "."` allowing bare `src/...` imports. JSX is
82+
configured for Preact (`jsxImportSource: "preact"`).
5783

5884
## Environment Variables
5985

60-
- `DISCORD_WEBHOOK` — Used by the contact form API route (`src/pages/api/contact.ts`) to post to Discord.
61-
- Astro DB connection requires `ASTRO_STUDIO_APP_TOKEN` for remote operations (build, seed).
86+
- `DISCORD_WEBHOOK` — Used by the contact form API route
87+
(`src/pages/api/contact.ts`) to post to Discord.
88+
- `ASTRO_DB_REMOTE_URL` — Turso/libSQL database URL (e.g.,
89+
`libsql://your-db.turso.io`).
90+
- `ASTRO_DB_APP_TOKEN` — Authentication token for Turso database.

astro.config.mjs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { defineConfig, fontProviders } from 'astro/config';
2-
import db from '@astrojs/db';
32
import preact from '@astrojs/preact';
43
import sitemap from '@astrojs/sitemap';
54
import tailwindcss from '@tailwindcss/vite';
@@ -62,7 +61,6 @@ export default defineConfig({
6261
site: 'https://whiskey.fm',
6362
trailingSlash: 'never',
6463
integrations: [
65-
db(),
6664
preact(),
6765
sitemap({
6866
filter: (page) => {

db/config.ts

Lines changed: 0 additions & 49 deletions
This file was deleted.

db/data/people-per-episode.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,25 @@ import people from './people';
33
type PersonId = (typeof people)[number]['id'];
44

55
export default {
6+
// 237
7+
'the-transactional-trap-how-97-of-developers-are-using-ai-wrong-w-leon-noel-danny-thompson':
8+
[
9+
{ id: 'robbiethewagner' },
10+
{ id: 'argyleink' },
11+
{ id: 'leonnoel' },
12+
{ id: 'dthompsondev' }
13+
],
14+
// 236
15+
'the-manager-has-become-the-managed-presented-by-warp': [
16+
{ id: 'robbiethewagner' },
17+
{ id: 'argyleink' },
18+
{ id: 'wattenberger' }
19+
],
20+
// 235
21+
'hot-pockets-pro-max-presented-by-warp': [
22+
{ id: 'robbiethewagner' },
23+
{ id: 'argyleink' }
24+
],
625
// 234
726
'pay-no-attention-to-the-llm-behind-the-terminal-w-zach-lloyd': [
827
{ id: 'robbiethewagner' },

db/data/people.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ export const people = [
7171
name: 'Diego Gonzalez',
7272
img: 'diegogonzalez.jpg'
7373
},
74+
{
75+
id: 'dthompsondev',
76+
name: 'Danny Thompson',
77+
img: 'dthompsondev.jpg'
78+
},
7479
{
7580
id: 'engineering_bae',
7681
name: 'Taylor Poindexter',
@@ -151,6 +156,11 @@ export const people = [
151156
name: 'Kelly Vaughn',
152157
img: 'kvlly.jpg'
153158
},
159+
{
160+
id: 'leonnoel',
161+
name: 'Leon Noel',
162+
img: 'leonnoel.jpg'
163+
},
154164
{
155165
id: 'madisonkanna',
156166
name: 'Madison Kanna',
@@ -296,6 +306,7 @@ export const people = [
296306
img: 'typecraft_dev.jpg'
297307
},
298308
{ id: 'wagslane', name: 'Lane Wagner', img: 'wagslane.jpg' },
309+
{ id: 'wattenberger', name: 'Amelia Wattenberger', img: 'wattenberger.jpg' },
299310
{ id: 'wesbos', name: 'Wes Bos', img: 'wesbos.jpg' },
300311
{ id: 'willjohnsonio', name: 'Will Johnson', img: 'willjohnsonio.jpg' },
301312
{ id: 'zachlloyd', name: 'Zach Lloyd', img: 'zachlloyd.jpg' },

db/data/sponsors-per-episode.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
export default {
2+
// 237
3+
'the-transactional-trap-how-97-of-developers-are-using-ai-wrong-w-leon-noel-danny-thompson':
4+
[{ id: 'warp' }],
5+
// 236
6+
'the-manager-has-become-the-managed-presented-by-warp': [{ id: 'warp' }],
27
// 235
3-
// '': [{ id: 'warp' }],
8+
'hot-pockets-pro-max-presented-by-warp': [{ id: 'warp' }],
49
// 234
510
'pay-no-attention-to-the-llm-behind-the-terminal-w-zach-lloyd': [
611
{ id: 'cascadiajs' }

db/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { drizzle } from 'drizzle-orm/libsql';
2+
3+
import * as schema from './schema';
4+
5+
// Uses ASTRO_DB_REMOTE_URL and ASTRO_DB_APP_TOKEN from environment.
6+
// In Astro files, these are available via import.meta.env.
7+
// In standalone scripts (seed), they are loaded via process.env.
8+
export function createDb(url: string, authToken: string) {
9+
return drizzle({
10+
connection: { url, authToken },
11+
schema
12+
});
13+
}
14+
15+
export type Database = ReturnType<typeof createDb>;

db/schema.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {
2+
integer,
3+
sqliteTable,
4+
text,
5+
uniqueIndex
6+
} from 'drizzle-orm/sqlite-core';
7+
8+
export const Episode = sqliteTable('Episode', {
9+
episodeSlug: text().primaryKey()
10+
});
11+
12+
export const Person = sqliteTable('Person', {
13+
id: text().primaryKey(),
14+
img: text(),
15+
name: text().notNull()
16+
});
17+
18+
export const HostOrGuest = sqliteTable(
19+
'HostOrGuest',
20+
{
21+
_id: integer('_id').primaryKey(),
22+
episodeSlug: text()
23+
.notNull()
24+
.references(() => Episode.episodeSlug),
25+
isHost: integer({ mode: 'boolean' }).notNull(),
26+
personId: text()
27+
.notNull()
28+
.references(() => Person.id)
29+
},
30+
(table) => [
31+
uniqueIndex('HostOrGuest_episodeSlug_personId_idx').on(
32+
table.episodeSlug,
33+
table.personId
34+
)
35+
]
36+
);
37+
38+
export const Sponsor = sqliteTable('Sponsor', {
39+
id: text().primaryKey(),
40+
img: text(),
41+
name: text().notNull(),
42+
url: text().notNull()
43+
});
44+
45+
export const SponsorForEpisode = sqliteTable(
46+
'SponsorForEpisode',
47+
{
48+
_id: integer('_id').primaryKey(),
49+
episodeSlug: text()
50+
.notNull()
51+
.references(() => Episode.episodeSlug),
52+
sponsorId: text()
53+
.notNull()
54+
.references(() => Sponsor.id)
55+
},
56+
(table) => [
57+
uniqueIndex('SponsorForEpisode_episodeSlug_sponsorId_idx').on(
58+
table.episodeSlug,
59+
table.sponsorId
60+
)
61+
]
62+
);

db/seed.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
1+
import 'dotenv/config';
2+
3+
import { sql } from 'drizzle-orm';
4+
5+
import { createDb } from './index';
16
import {
2-
db,
37
Episode,
48
HostOrGuest,
59
Person,
610
Sponsor,
7-
SponsorForEpisode,
8-
sql
9-
} from 'astro:db';
11+
SponsorForEpisode
12+
} from './schema';
1013

1114
import { getAllEpisodes } from '../src/lib/rss';
1215
import people from './data/people';
1316
import peoplePerEpisode from './data/people-per-episode';
1417
import sponsors from './data/sponsors';
1518
import sponsorsPerEpisode from './data/sponsors-per-episode';
1619

17-
// https://astro.build/db/seed
18-
export default async function seed() {
20+
const db = createDb(
21+
process.env.ASTRO_DB_REMOTE_URL!,
22+
process.env.ASTRO_DB_APP_TOKEN!
23+
);
24+
25+
async function seed() {
1926
await db
2027
.insert(Person)
2128
.values(people as any)
@@ -82,4 +89,11 @@ export default async function seed() {
8289
.insert(SponsorForEpisode)
8390
.values(sponsorsForEpisodesToInsert)
8491
.onConflictDoNothing();
92+
93+
console.log('Seed complete!');
8594
}
95+
96+
seed().catch((err) => {
97+
console.error('Seed failed:', err);
98+
process.exit(1);
99+
});

0 commit comments

Comments
 (0)