Files
2026-06-10 00:40:26 -04:00

5.5 KiB

Spark Slop

A small, fast web app for capturing project ideas and growing them with timestamped notes — like a comment thread for each idea. Ideas are private to your account by default, with opt-in sharing.

Built with Bun, TypeScript, and SQLite (bun:sqlite). No external runtime dependencies — the whole backend is the Bun standard library.

Stack

  • Bun.serve — HTTP server + static file serving
  • bun:sqlite — embedded database (single spark_slop.db file, WAL mode)
  • OAuth sign-in (GitHub and/or Google) with cookie sessions — no passwords stored
  • Vanilla TS/JS frontend — no build step, no framework

Auth & privacy

  • Sign in with GitHub or Google. Both are optional — the login screen only shows providers you've configured. Sessions are HttpOnly cookies (30 days); no passwords are ever stored.
  • Private by default. Every idea belongs to its owner and is invisible to everyone else.
  • Per-idea public toggle. Flip an idea to public to list it on the Explore feed (/explore), browsable by anyone — including logged-out visitors.
  • Shareable read-only links. Generate a secret link (/share/<token>) that lets anyone view one idea and its notes without an account. Works independently of public/private status, and can be revoked anytime.

Setup

  1. Install dev types (already done if you cloned with bun.lock):

    bun install
    
  2. Configure at least one OAuth provider. Copy the example env file and fill in the credentials:

    cp .env.example .env
    

    GitHub: create an OAuth app at https://github.com/settings/developers with callback URL http://localhost:3000/auth/github/callback.

    Google: create OAuth credentials at https://console.cloud.google.com/apis/credentials (Web application) with authorized redirect URI http://localhost:3000/auth/google/callback.

    Bun auto-loads .env. If you change BASE_URL (e.g. for production), update the callback/redirect URLs in the provider settings to match.

Run it

bun run dev     # auto-reload on file changes
# or
bun run start   # plain run

Then open http://localhost:3000. Set PORT to change the port:

PORT=8080 bun run start

The SQLite database file (spark_slop.db) is created automatically on first run. (If you ran an older version without auth, the schema self-migrates to add the new columns — existing ideas will have no owner; delete spark_slop.db for a clean slate.)

Project layout

src/
  db.ts        # SQLite schema, migration guard, ownership-scoped queries
  auth.ts      # OAuth (GitHub/Google) flows, sessions, cookies
  server.ts    # Bun.serve: auth routes + JSON API + static files
public/
  index.html   # landing page
  app.html     # the app (auth-gated: idea list, notes, privacy controls)
  app.js       # app logic (login gate, ideas, notes, visibility, sharing)
  explore.html # public Explore feed
  explore.js
  share.html   # read-only viewer (shared links + public ideas)
  share.js
  common.js    # shared helpers (API client, time formatting, rendering)
  styles.css   # modern dark UI

API

Public (no auth)

Method Path Description
GET /api/auth/providers Which OAuth providers are configured
GET /api/me Current user (401 if signed out)
GET /api/explore List all public ideas
GET /api/public/ideas/:id A public idea + its notes (read-only)
GET /api/shared/:token An idea + notes via secret share token

Auth

Method Path Description
GET /auth/github Start GitHub OAuth
GET /auth/github/callback GitHub redirect target
GET /auth/google Start Google OAuth
GET /auth/google/callback Google redirect target
GET /auth/logout Clear session and sign out

Authenticated (owner-scoped)

Method Path Description
GET /api/ideas List your ideas (with note counts)
POST /api/ideas Create an idea {title, description}
GET /api/ideas/:id Get one of your ideas
PUT /api/ideas/:id Update an idea
DELETE /api/ideas/:id Delete an idea (cascades notes)
PUT /api/ideas/:id/visibility Set {visibility: "public"|"private"}
POST /api/ideas/:id/share Create/rotate a share link
DELETE /api/ideas/:id/share Revoke the share link
GET /api/ideas/:id/notes List notes for an idea
POST /api/ideas/:id/notes Add a note {body}
DELETE /api/notes/:id Delete a note

All authenticated routes return 401 when signed out and 404 for ideas/notes you don't own — ownership is enforced in the SQL, not just the route layer.