v2
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user