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.dbfile, 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
-
Install dev types (already done if you cloned with
bun.lock):bun install -
Configure at least one OAuth provider. Copy the example env file and fill in the credentials:
cp .env.example .envGitHub: 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 changeBASE_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.