Skip to content

Add micro blog section with D1 storage and social syndication#228

Open
just-be-dev wants to merge 30 commits intomainfrom
add-micro-blog-section
Open

Add micro blog section with D1 storage and social syndication#228
just-be-dev wants to merge 30 commits intomainfrom
add-micro-blog-section

Conversation

@just-be-dev
Copy link
Owner

@just-be-dev just-be-dev commented Feb 21, 2026

Summary

Adds a complete micro blog feature to the site with D1 database storage, CLI tooling, and automatic syndication to Bluesky and Twitter.

Features

Infrastructure

  • D1 Database with Drizzle ORM - Persistent storage for micro posts with typed schema
  • Astro Remote Loader - Fetches posts from D1 at build time for static generation
  • Navigation Link - Added micro tab with 'm' keyboard shortcut

CLI Package (@just-be/micro)

  • TUI Browser (default) - Browse all posts, open on platforms, delete posts
  • TUI Editor - Compose posts with 280 character validation
  • Direct Posting - Quick post creation via command line
  • Social Syndication - Automatic posting to Bluesky and Twitter with ID tracking

User-Facing Features

  • /micro page - Infinite scroll display of all micro posts
  • RSS Feed - Dedicated feed at /micro/rss.xml
  • Short URLs - Permanent URLs with 'm' prefix (e.g., /m1234/micro#1234)

Technical Details

Database Schema

{
  id: integer (auto-increment),
  content: text (max 280 chars),
  createdAt: integer (timestamp),
  updatedAt: integer (timestamp),
  syndicatedTo: text (JSON array with platform, id, url)
}

Syndication

  • Posts automatically syndicate to Bluesky and Twitter on creation
  • Failures don't block post creation (graceful degradation)
  • Platform post IDs stored for linking back to original posts
  • Clear error messages shown in CLI

Environment Variables Required

Build (Astro):

  • D1_DATABASE_ID
  • CLOUDFLARE_ACCOUNT_ID
  • CLOUDFLARE_API_TOKEN

CLI (Syndication):

  • BLUESKY_IDENTIFIER
  • BLUESKY_PASSWORD
  • TWITTER_APP_KEY
  • TWITTER_APP_SECRET
  • TWITTER_ACCESS_TOKEN
  • TWITTER_ACCESS_SECRET

Files Changed

Database & Schema:

  • db/schema.ts - Drizzle schema with micro_posts table
  • db/migrations/ - D1 migration files
  • drizzle.config.ts - Drizzle configuration
  • wrangler.toml - D1 database binding

Content & Loaders:

  • src/loaders/micro-loader.ts - Astro remote loader for D1
  • src/content/schemas.ts - Micro post schema
  • src/content.config.ts - Micro collection configuration

CLI Package:

  • packages/micro/ - Complete CLI package with TUI
  • Dependencies: @atproto/api, twitter-api-v2, @clack/prompts, drizzle-orm

Web Pages:

  • src/pages/micro.astro - Micro blog page with infinite scroll
  • src/pages/micro/rss.xml.ts - RSS feed generator
  • src/components/Nav.astro - Updated with micro link

URL System:

  • scripts/gen-codes.ts - Updated for micro posts
  • scripts/gen-url-manifest.ts - Updated for micro posts
  • src/middleware.ts - Updated for /m{code} redirects

Testing

All functionality verified:

  • ✅ D1 database migrations applied (local + remote)
  • ✅ CLI package builds and runs
  • ✅ Astro loader fetches from D1
  • ✅ Page renders with infinite scroll
  • ✅ RSS feed generates correctly
  • ✅ Short URLs redirect properly
  • ✅ Syndication module integrates correctly
  • ✅ Navigation link works with keyboard shortcut
  • ✅ 152 tests passing

Usage

# Create post directly
bunx @just-be/micro post "Hello world!"

# Open TUI editor
bunx @just-be/micro post

# Browse posts (TUI)
bunx @just-be/micro

Deployment Steps

  1. Install CLI dependencies: cd packages/micro && bun install
  2. Apply migrations: bun run drizzle-kit migrate
  3. Generate codes: mise run gen-codes && mise run gen-url-manifest
  4. Build: mise run build
  5. Deploy: mise run deploy

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Feb 21, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
just-be-dev 7cc2a9a Commit Preview URL

Branch Preview URL
Mar 18 2026, 12:57 PM

@just-be-dev just-be-dev force-pushed the add-micro-blog-section branch from bb5a2be to 48ff568 Compare February 22, 2026 00:52
@just-be-dev just-be-dev force-pushed the add-micro-blog-section branch 2 times, most recently from 01a7020 to a5d74c4 Compare March 18, 2026 09:19
just-be-dev and others added 20 commits March 18, 2026 06:00
…micro package

- Move db/schema.ts, db/migrations/, and drizzle.config.ts into packages/micro/
- Add loader-db.ts with a D1 HTTP adapter so drizzle can be used at build time
- Rewrite micro-loader to import getMicroPosts from @just-be/micro/loader-db
  instead of hitting the Cloudflare API directly with raw SQL
- Add exports field to micro package.json for the loader-db entrypoint
- Add mise tasks for micro:db:generate and micro:db:migrate
- Remove drizzle-kit, drizzle-orm, and db:* scripts from root package.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…m proxy

Use getD1Database() (getPlatformProxy) instead of a hand-rolled Cloudflare
API adapter. The loader no longer accepts credentials — wrangler handles the
D1 connection, keeping all database logic inside the micro package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add micro-loader.ts to fetch micro posts from D1 at build time via HTTP API.
Includes microSchema with 280 char validation and collection registration.
Also fixes pre-existing js-yaml dependency issue that was blocking builds.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Two issues:
- db.ts used a dynamic import("wrangler") which fails in Astro's content
  loader context because Vite's module runner has already closed by the
  time the async import resolves. Switch to a static import.
- micro-loader was throwing on any error, causing the entire build to fail
  when there is no local wrangler D1 state in CI. Downgrade to a warning
  and return so the build succeeds with an empty micro collection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implement CLI tool for managing micro blog posts with:
- TUI browser for viewing/deleting posts
- TUI editor for composing posts (280 char limit)
- Direct post creation from command line
- D1 database integration via Drizzle ORM

Files added:
- packages/micro/package.json
- packages/micro/index.ts
- packages/micro/src/db.ts
- packages/micro/src/post.ts
- packages/micro/src/browse.ts
- packages/micro/README.md

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create src/pages/micro/rss.xml.ts following existing RSS pattern
- Feed includes all micro posts sorted by createdAt descending
- Each item has content, pubDate, and link to /micro/{id}
- Install missing @unocss/reset dependency that was blocking builds

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace the D1 database and Drizzle ORM with a JSONL file stored in a
new R2 bucket named "micro". All R2 and syndication logic moves to the
Astro site; the micro CLI becomes a thin HTTP client.

- Create `micro` R2 bucket and bind as MICRO_BUCKET in wrangler.toml
- Add src/lib/micro-r2.ts: createMicroStorage(bucket) with read/write/
  add/update/delete using read-modify-write on micro-posts.jsonl
- Add src/lib/micro-syndicate.ts: syndication logic (moved from micro pkg)
- Add src/env.d.ts: typed Worker env for MICRO_BUCKET, MICRO_SECRET,
  and syndication credential bindings
- POST /micro (Authorization: Bearer): creates post + syndicates
- GET /micro (Authorization + Accept: application/json): returns JSON list
- DELETE /micro/:id (Authorization: Bearer): deletes post from R2
- Update micro-loader to use createMicroStorage + getPlatformProxy directly
- Slim micro CLI to HTTP fetch calls using MICRO_SECRET + MICRO_SITE_URL
- Remove drizzle-orm, drizzle-kit, wrangler, @atproto/api, twitter-api-v2
  from micro package; move atproto/twitter deps to root package.json
- Delete schema.ts, db.ts, drizzle.config.ts, migrations, syndicate.ts,
  loader-db.ts, r2.ts from micro package
- Remove micro:db:generate, micro:db:migrate, deploy:micro mise tasks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add src/pages/micro.astro with infinite scroll pagination
- Display micro posts chronologically (newest first)
- Implement IntersectionObserver-based lazy loading
- Add relative timestamp formatting (e.g., "5m ago", "2d ago")
- Include syndication info display
- Follow TUI theme styling with UnoCSS
- Mobile responsive layout

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace the Cloudflare D1 REST API query with a getPlatformProxy() call
to read micro-posts.jsonl from the MICRO_BUCKET R2 binding, consistent
with how the content loader accesses posts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 'M' kind to Code utility for micro posts
- Update gen-codes.ts to handle micro directory
- Extend gen-url-manifest.ts to fetch micro posts from D1
- Update middleware to recognize 'm' codes in URLs
- Short URLs like /m1234 now redirect to /micro#1234

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Replace build-time content collection with live R2 reads in micro.astro,
  [id].astro, and rss.xml.ts so new posts appear immediately without a rebuild
- Remove micro-loader.ts and micro collection from content.config.ts/schemas.ts
- Fix #post-${id} anchor URL in browse.ts (was not matching article id attribute)
- Rename shadowed clack prompts variable in browse.ts find() callback
- Add title field to RSS items (truncated content excerpt)
- Show syndicated platform names as links instead of raw URLs
- Replace all emojis in gen-url-manifest.ts console output with plain text
- Fix misleading comment about anchor URL format in gen-url-manifest.ts
- Update README to remove stale D1/Drizzle references

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add database schema and migrations for storing micro posts with syndication tracking. Update micro CLI commands to read/write posts from D1 database and track syndication results (platform, post ID, and URL) for each successful post. Configure Drizzle ORM and bind D1 database in Wrangler. Add micro navigation link to site header.
Instead of an env var, the CLI now derives the site URL from an optional
--branch / -b flag. No branch (or "main") resolves to https://just-be.dev;
any other branch resolves to the Cloudflare Workers preview URL pattern
https://{branch}-just-be-dev.just-be.workers.dev.

siteUrl is threaded as a required parameter into browse() and post() rather
than being read from the environment inside each function.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch from client-side infinite scroll with offset-based pagination to server-side cursor-based pagination. This approach:
- Uses timestamp cursors for stable pagination across data changes
- Supports bidirectional navigation (next/previous)
- Removes reliance on client-side JavaScript and Intersection Observer
- Provides explicit navigation buttons instead of automatic loading
- Maintains correct chronological ordering when navigating between pages
Makes R2Bucket and other Cloudflare Worker globals available across
the project without per-file imports, fixing the type error in
src/lib/micro-r2.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Validate cursor param in micro.astro; redirect to /micro on NaN input
- Add id attribute to article elements for anchor URL support
- Guard JSON.parse of syndicated_to with try/catch and type validation
- Validate D1 API response structure before accessing result rows
- Fix middleware to handle hash fragments in redirect targets without
  URL-encoding the '#' via URL.pathname setter
- Create /micro/[id] route that redirects to the correct paginated page
  with anchor, so RSS links resolve even for non-first-page posts
- Restore rss.xml.ts link to /micro/${post.id} path style
- Remove unused skippedCount variable in gen-url-manifest.ts
- Add 'M' kind coverage to code.test.ts (fromDate, getKind, getCollection)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The packages/micro CLI is no longer needed — posts can be created by
POSTing directly to the /micro endpoint with an Authorization header.
Browsing is available at /micro in the browser.

Also remove the 'micro' mise task that invoked the CLI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Set migrations_dir = "db/migrations" in wrangler.toml so that
  wrangler d1 migrations apply finds the correct migrations path
- Add mise run micro task to invoke packages/micro/index.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the interactive @clack/prompts TUI with a plain CLI:
- micro list               - print all posts to stdout
- micro post "text"        - post directly from argument
- micro post               - read content from stdin
- micro delete <id>        - delete a post
- --branch / -b flag still supported for targeting preview deploys

No dependencies — just fetch + process.stdin/stdout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@just-be-dev just-be-dev force-pushed the add-micro-blog-section branch from d03a2c9 to af89319 Compare March 18, 2026 10:00
just-be-dev and others added 3 commits March 18, 2026 06:02
…micro package

- Move db/schema.ts, db/migrations/, and drizzle.config.ts into packages/micro/
- Add loader-db.ts with a D1 HTTP adapter so drizzle can be used at build time
- Rewrite micro-loader to import getMicroPosts from @just-be/micro/loader-db
  instead of hitting the Cloudflare API directly with raw SQL
- Add exports field to micro package.json for the loader-db entrypoint
- Add mise tasks for micro:db:generate and micro:db:migrate
- Remove drizzle-kit, drizzle-orm, and db:* scripts from root package.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the D1 database and Drizzle ORM with a JSONL file stored in a
new R2 bucket named "micro". All R2 and syndication logic moves to the
Astro site; the micro CLI becomes a thin HTTP client.

- Create `micro` R2 bucket and bind as MICRO_BUCKET in wrangler.toml
- Add src/lib/micro-r2.ts: createMicroStorage(bucket) with read/write/
  add/update/delete using read-modify-write on micro-posts.jsonl
- Add src/lib/micro-syndicate.ts: syndication logic (moved from micro pkg)
- Add src/env.d.ts: typed Worker env for MICRO_BUCKET, MICRO_SECRET,
  and syndication credential bindings
- POST /micro (Authorization: Bearer): creates post + syndicates
- GET /micro (Authorization + Accept: application/json): returns JSON list
- DELETE /micro/:id (Authorization: Bearer): deletes post from R2
- Update micro-loader to use createMicroStorage + getPlatformProxy directly
- Slim micro CLI to HTTP fetch calls using MICRO_SECRET + MICRO_SITE_URL
- Remove drizzle-orm, drizzle-kit, wrangler, @atproto/api, twitter-api-v2
  from micro package; move atproto/twitter deps to root package.json
- Delete schema.ts, db.ts, drizzle.config.ts, migrations, syndicate.ts,
  loader-db.ts, r2.ts from micro package
- Remove micro:db:generate, micro:db:migrate, deploy:micro mise tasks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
just-be-dev and others added 7 commits March 18, 2026 06:02
Replace the Cloudflare D1 REST API query with a getPlatformProxy() call
to read micro-posts.jsonl from the MICRO_BUCKET R2 binding, consistent
with how the content loader accesses posts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace build-time content collection with live R2 reads in micro.astro,
  [id].astro, and rss.xml.ts so new posts appear immediately without a rebuild
- Remove micro-loader.ts and micro collection from content.config.ts/schemas.ts
- Fix #post-${id} anchor URL in browse.ts (was not matching article id attribute)
- Rename shadowed clack prompts variable in browse.ts find() callback
- Add title field to RSS items (truncated content excerpt)
- Show syndicated platform names as links instead of raw URLs
- Replace all emojis in gen-url-manifest.ts console output with plain text
- Fix misleading comment about anchor URL format in gen-url-manifest.ts
- Update README to remove stale D1/Drizzle references

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of an env var, the CLI now derives the site URL from an optional
--branch / -b flag. No branch (or "main") resolves to https://just-be.dev;
any other branch resolves to the Cloudflare Workers preview URL pattern
https://{branch}-just-be-dev.just-be.workers.dev.

siteUrl is threaded as a required parameter into browse() and post() rather
than being read from the environment inside each function.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The packages/micro CLI is no longer needed — posts can be created by
POSTing directly to the /micro endpoint with an Authorization header.
Browsing is available at /micro in the browser.

Also remove the 'micro' mise task that invoked the CLI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Makes R2Bucket and other Cloudflare Worker globals available across
the project without per-file imports, fixing the type error in
src/lib/micro-r2.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the R2-based JSONL file storage with a D1 SQLite database.
Removes micro-r2.ts, adds micro.ts with a D1-backed microStore factory,
creates the initial migration, and wires up the new MICRO_DB binding
across all consumers. Drops unused assetsSchema and microSchema from
schemas.ts, and demotes D1 unavailability in gen-url-manifest to a
warning so fresh clones without local migrations don't break the build.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolves conflicts in favor of D1 over R2 for micro post storage.
Retains the restored micro CLI from the remote (packages/micro/).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant