Reviewed-on: #5 |
||
|---|---|---|
| .agents/skills | ||
| .cdp | ||
| .claude/skills | ||
| .windsurf/skills | ||
| app | ||
| components | ||
| hooks | ||
| lib | ||
| prisma | ||
| public | ||
| shots | ||
| types | ||
| .dockerignore | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| Dockerfile | ||
| README.md | ||
| auth.ts | ||
| components.json | ||
| docker-compose.yml | ||
| docker-entrypoint.sh | ||
| eslint.config.mjs | ||
| next.config.ts | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| prisma.config.ts | ||
| proxy.ts | ||
| skills-lock.json | ||
| software.md | ||
| tsconfig.json | ||
README.md
Organize
A self-hosted, installable PWA for staying organized: an extended to-do list and notes organizer laid out as a Kanban-style board.
- Categories are the board's lanes (e.g. "To Do", "In Progress", "Done").
- Groups are the cards within a lane — each has a color, a markdown notes panel, and a to-do list, and can be dragged between lanes.
- To-Dos live inside a Group, with an optional details field.
Accounts are required (there's no anonymous/local-only mode — the board needs a database to persist to), and the first account ever created on a given instance automatically becomes its administrator.
Features
- Kanban board with drag-and-drop: reorder Categories, and drag Groups between/within Categories
- Per-Group markdown notes and a to-do list with a completion progress indicator
- Light/dark theme (follows system by default)
- Installable PWA (add-to-home-screen); requires a live connection to the server for its database, so there's no offline mode
- Credentials-based accounts (email + password), with an Admin page to:
- change a user's role (Administrator / User / Pending), email, or password, or delete their account
- control how the site handles new sign-ups (see Sign-up modes below)
Tech stack
Next.js 16 (App Router) · React 19 · TypeScript · Tailwind CSS 4 · PostgreSQL via Prisma 7 · Auth.js v5 (Credentials provider, JWT sessions) · dnd-kit · base-ui
Quick start (Docker)
This is the recommended way to run Organize. All you need is Docker with the Compose plugin.
-
Clone the repo
git clone https://git.brianfertig.com/brianfertig/Organize.git cd Organize -
Create your
.envcp .env.example .envThen open
.envand:- Generate a real
AUTH_SECRET(used to sign session cookies) — for example withopenssl rand -base64 32— and paste it in. - Optionally change
POSTGRES_PASSWORDfrom the placeholder.
See Configuration below for what each variable does.
- Generate a real
-
Start everything
docker compose up -dThis builds the app image, starts Postgres, waits for it to be healthy, then starts the app — which applies any pending database migrations automatically on every boot before serving traffic. No separate migration or setup step is needed.
-
Open the app at http://localhost:3000 (or whatever
APP_PORTyou set) and sign up. The very first account created becomes the site's administrator; everyone after that follows whatever sign-up mode is currently set (Open, by default).
Updating
git pull
docker compose up -d --build
--build matters here: without it, Compose will keep running whatever
image it already built and won't notice that the source changed.
Stopping
docker compose down
Postgres's data lives in ./data/postgres on the host (a bind mount, not
a Docker volume), so it survives docker compose down — back up that
directory if you want a safety net before major upgrades.
Configuration
All configuration is via environment variables, read from .env by
Docker Compose (see .env.example for the template).
| Variable | Used by | Purpose |
|---|---|---|
AUTH_SECRET |
app |
Encrypts/signs session cookies. Required in production — generate with openssl rand -base64 32. |
POSTGRES_USER |
db |
Postgres superuser created on first init. |
POSTGRES_PASSWORD |
db |
Password for POSTGRES_USER. Change this from the .env.example placeholder. |
POSTGRES_DB |
db |
Database name created on first init. |
APP_PORT |
app (host mapping only) |
Host port the app is published on, e.g. 3000 for http://localhost:3000. Defaults to 3000 if unset. The container always listens on 3000 internally regardless of this value. |
DATABASE_URL |
host-side tooling only | Prisma connection string for running things like npx prisma studio from your own machine. Not used by the app container itself — see note below. |
Note on
DATABASE_URL: insidedocker-compose.yml, theappservice is given its ownDATABASE_URLthat points at thedbservice over the Compose network (db:5432), built from thePOSTGRES_*variables — it ignores theDATABASE_URLin.env. The.envvalue (pointed atlocalhost:5432) is only there for convenience if you want to run Prisma commands, or the app itself, directly on the host. Since the bundleddbservice doesn't publish port 5432 to the host by default, that only works once you either add aports: ["5432:5432"]mapping to thedbservice, or pointDATABASE_URLat a Postgres instance you're running yourself.
Sign-up modes
The Admin page (/admin, administrators only) has a "New sign-ups"
setting with four modes:
- Open (default) — anyone can sign up and immediately gets the
Userrole. - Approved — anyone can sign up, but starts as
Pendingand can't log in until an administrator changes their role toUserorAdministrator. - Confirmed — not implemented yet (shown disabled in the UI); intended to gate new accounts on email confirmation instead of manual approval.
- Closed — sign-ups are turned off entirely: the sign-up form and any links to it are removed, and the sign-up action itself refuses new accounts even if called directly.
The very first account on a fresh instance always becomes an administrator, regardless of the current sign-up mode — otherwise there'd be no one able to approve anyone.
Local development (without Docker)
Running the app directly on your host is mainly useful for editing code with hot reload. You'll still need a Postgres instance it can reach.
- Node 24 (matching the Docker image; no
enginesfield enforces this, but it's what's tested) andnpm install. - A reachable Postgres. The simplest option is to add a port mapping
to the
dbservice indocker-compose.yml(ports: ["5432:5432"]) and run just that service:docker compose up -d db. Then setDATABASE_URLin.envto match (the.env.exampledefault ofpostgresql://organize:organize@localhost:5432/organizewill work as-is if you keep the default Postgres credentials). - Apply migrations:
Against a fresh database this also runsnpx prisma migrate devprisma/seed.tsautomatically, creatingdev@example.com/password123as an administrator with a small sample board. If it doesn't (e.g. you'd already applied every migration before), runnpx prisma db seedto trigger it explicitly. Don't run either against a database you care about. - Run the dev server:
Open http://localhost:3000.npm run dev
Other useful scripts: npm run build (production build), npm run start
(serve a production build), npm run lint.
Project structure
app/ Next.js App Router routes: (app) is the signed-in board + admin,
(auth) is login/signup
components/board/ The Kanban board: categories, groups, to-dos, drag-and-drop
components/admin/ Admin page: user table, role menu, sign-up mode settings
lib/actions/ Server Actions (auth, categories, groups, todos, notes, admin)
lib/ Shared server helpers (db client, auth helpers, settings, colors)
prisma/ Schema, migrations, and the dev-only seed script
auth.ts Auth.js configuration (Credentials provider, JWT session callbacks)
proxy.ts Route protection (redirects signed-out users to /login, etc.)