This commit is contained in:
Nicholas Keller
2026-06-10 00:40:26 -04:00
parent 6cf48a9a9e
commit df1cd6836c
16 changed files with 1502 additions and 183 deletions
+93 -17
View File
@@ -1,7 +1,8 @@
# ✨ Spark Slop
A small, fast web app for capturing **project ideas** and growing them with
**timestamped notes** — like a comment thread for each idea.
**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.
@@ -10,8 +11,46 @@ runtime dependencies — the whole backend is the Bun standard library.
- **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
- A landing page (`/`) and the app (`/app`)
## 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`):
```bash
bun install
```
2. Configure at least one OAuth provider. Copy the example env file and fill in
the credentials:
```bash
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
@@ -28,29 +67,66 @@ 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 + typed query functions
server.ts # Bun.serve: JSON API + static files
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 (idea list + notes)
app.js # frontend logic (API client, rendering)
styles.css # modern dark UI
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
| Method | Path | Description |
| ------ | ------------------------- | -------------------------- |
| GET | `/api/ideas` | List ideas (with note counts) |
| POST | `/api/ideas` | Create an idea `{title, description}` |
| GET | `/api/ideas/:id` | Get one idea |
| PUT | `/api/ideas/:id` | Update an idea |
| DELETE | `/api/ideas/:id` | Delete an idea (cascades notes) |
| 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 |
### 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.