You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CONTRIBUTING.md
+5-6Lines changed: 5 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,8 +18,7 @@ phases is easier to reason about.
18
18
multi-tenant migration). Created via `/auth/signup` (post-Phase 3) or
19
19
by the operator via CLI. Many users per deployment.
20
20
21
-
Pre-multi-tenant (≤ v0.6) the operator and user are the same person.
22
-
Post v0.7, they diverge: the operator administers the server; users sign
21
+
As of v0.7, they diverge: the operator administers the server; users sign
23
22
up and use the product. Auth code paths use `User` (a dataclass in
24
23
`app/auth.py`) to carry the user identity through requests.
25
24
@@ -33,7 +32,7 @@ Basically anything that makes the app better for someone self-hosting it. A non-
33
32
-**i18n / localization** — the Slot `kind` labels are German (`Vorlesung`, `Übung`) by default. Making those user-configurable or translatable would help non-EU users a lot.
34
33
-**New MCP tools** — if you find yourself wishing Claude could do `X` and there's a natural way to expose it, just add it. Pattern is in `app/mcp_tools.py`.
35
34
-**Performance / bundle size** — the frontend chunk is bigger than it needs to be; someone who knows their way around Vite code-splitting could shave a lot.
36
-
-**Tests** — there's no suite yet. Adding pytest + Vitest scaffolding is its own welcome PR.
35
+
-**Tests** — the backend has 318 pytest tests; the frontend has none yet. A Vitest suite for the frontend is its own welcome PR.
37
36
38
37
## Things worth a quick issue first
39
38
@@ -42,7 +41,7 @@ Not "no" — just "let's talk first so you don't waste a weekend":
42
41
- Major framework swaps (React → Svelte, FastAPI → Django).
43
42
- Replacing the `psycopg` async pool with a different DB driver (SQLAlchemy, asyncpg, etc.) — the pool is small but it's load-bearing; every service file goes through it.
44
43
- New top-level entities beyond the current data model (Course / Schedule slot / Lecture / Study topic / Deliverable / Task / Klausur).
45
-
-Multi-user / team / sharing features — the single-user-per-deploy assumption is load-bearing in a bunch of places, and shifting it is a big project.
44
+
-Replacing the multi-tenant data model — every owned table has a `user_id` FK; any change to that assumption touches a lot of files and is a big conversation first.
46
45
47
46
A one-liner issue like *"would you take a PR that X?"* is all it takes.
48
47
@@ -51,7 +50,7 @@ A one-liner issue like *"would you take a PR that X?"* is all it takes.
51
50
See [INSTALL.md](./INSTALL.md) for the full walkthrough. TL;DR:
52
51
53
52
```bash
54
-
cp .env.example .env # fill APP_PASSWORD_HASH, SESSION_SECRET
53
+
cp .env.example .env # fill OPERATOR_EMAIL, APP_PASSWORD_HASH, SESSION_SECRET, SECRETS_ENCRYPTION_KEY
55
54
cat > .env.docker <<EOF # Postgres credentials
56
55
POSTGRES_USER=openstudy
57
56
POSTGRES_PASSWORD=$(openssl rand -hex 24)
@@ -75,7 +74,7 @@ cd web && pnpm install && pnpm dev
75
74
76
75
## Testing
77
76
78
-
The backend has a pytest suite (213 tests as of v0.6.0) that runs against a real Postgres testcontainer with per-test transaction rollback — every test gets a clean DB state without paying for container churn. Service-layer, MCP-tool, and end-to-end OAuth/login flows are all covered.
77
+
The backend has a pytest suite (318 tests as of v0.7.0) that runs against a real Postgres testcontainer with per-test transaction rollback — every test gets a clean DB state without paying for container churn. Service-layer, MCP-tool, and end-to-end OAuth/login flows are all covered.
|`PUBLIC_URL`| The public origin of your deploy, e.g. `https://your-domain.tld`. Used in OAuth callbacks and email links. Leave blank for local dev (derived from the inbound request). |
68
79
69
-
# Argon2id-hash a login password you'll use to sign in
`SIGNUPS_ENABLED` defaults to `false` (operator-only). Set to `true` to open public registration.
77
83
78
-
# Generate a strong Postgres password and write the docker-only env file
84
+
`EMAIL_BACKEND` defaults to `console` (emails print to stdout). For real email:
85
+
86
+
```
87
+
EMAIL_BACKEND=gmail_smtp
88
+
GMAIL_SMTP_USER=you@gmail.com
89
+
GMAIL_SMTP_APP_PASSWORD=<16-char app password from myaccount.google.com/apppasswords>
90
+
EMAIL_FROM=you@gmail.com
91
+
EMAIL_FROM_NAME=Your Name
92
+
```
93
+
94
+
Telegram credentials are per-user — each user configures their own bot token and chat ID via **Settings → Telegram** after logging in. No env vars needed.
95
+
96
+
### `.env.docker` — Postgres credentials
97
+
98
+
```bash
79
99
cat > .env.docker <<EOF
80
100
POSTGRES_USER=openstudy
81
101
POSTGRES_PASSWORD=$(openssl rand -hex 24)
@@ -84,10 +104,13 @@ EOF
84
104
chmod 600 .env .env.docker
85
105
```
86
106
87
-
The other variables in `.env` are optional and document themselves —
88
-
the internal API secret used by webhooks, the public URL the app
89
-
advertises in OAuth flows. Telegram credentials are configured per-user
90
-
via **Settings → Telegram** after first login (no env vars needed).
107
+
Optionally bake your domain into the frontend image at build time (affects canonical tags, OG metadata, sitemap, and manifest):
108
+
109
+
```
110
+
PUBLIC_SITE_URL=https://your-domain.tld
111
+
PUBLIC_SITE_NAME=Your Site Name
112
+
PUBLIC_SHOW_LANDING=false
113
+
```
91
114
92
115
---
93
116
@@ -123,20 +146,27 @@ You should also see the running containers (`openstudy`, `openstudy-postgres`,
### Five themes — pick the one that fits your brain
20
20
@@ -114,7 +114,7 @@ The dashboard is where I see things. Claude is how I edit them. Same database be
114
114
115
115
The MCP server ships with **44 tools — anything you can do in the UI, Claude can do too**. Create a study topic, mark something studied, upload a file, render a PDF as images, whatever.
116
116
117
-
-**Multi-tenant SaaS-capable:**per-user data isolation enforced at both the service layer (WHERE filters) and the schema layer (composite FKs + RLS policies). Self-host as single-user or invite many users — same codebase.
117
+
-**Multi-tenant:**every owned table carries a `user_id` FK. Data isolation is enforced at the service layer (WHERE filters) and the schema layer (composite FKs + RLS policies). Run it solo or flip `SIGNUPS_ENABLED=true` to open registration — same codebase, same deploy.
118
118
119
119
Plug it into Claude.ai as a custom connector (full OAuth 2.1) and those tools are live in **Claude Code on your laptop, claude.ai in your browser, and the Claude iOS app on your phone**. Open Claude anywhere and it has the same view of your coursework that you do.
120
120
@@ -143,25 +143,36 @@ cd web && pnpm install && cd ..
143
143
144
144
**2. Generate secrets and write `.env` files.**
145
145
146
+
Open `.env` and fill in the required values:
147
+
146
148
```bash
147
149
cp .env.example .env
148
150
149
-
# Pick a login password, hash it with Argon2id, and paste the result as APP_PASSWORD_HASH:
150
-
docker run --rm python:3.12-slim sh -c 'pip install -q argon2-cffi && python -c "from argon2 import PasswordHasher; print(PasswordHasher().hash(input(\"password: \")))"'
151
+
# Set your login email (this becomes your username):
152
+
# OPERATOR_EMAIL=you@example.com
153
+
154
+
# Hash a password for first login and paste as APP_PASSWORD_HASH:
155
+
uv run python -m app.tools.hashpw
156
+
# → paste the resulting $argon2id$... string as APP_PASSWORD_HASH
# Generate a Postgres password and write the Docker-only env file:
156
167
cat > .env.docker <<EOF
157
168
POSTGRES_USER=openstudy
158
169
POSTGRES_PASSWORD=$(openssl rand -hex 24)
159
170
POSTGRES_DB=openstudy
160
171
EOF
161
-
chmod 600 .env.docker
172
+
chmod 600 .env .env.docker
162
173
```
163
174
164
-
`.env` holds your application secrets (password hash, session secret, optional Telegram tokens). `.env.docker` holds the Postgres credentials that compose injects into the postgres container. Both are in `.gitignore`.
175
+
`.env` holds your application secrets. `.env.docker` holds the Postgres credentials that compose injects into the postgres container. Both are in `.gitignore`.
165
176
166
177
**3. Bring up the stack.**
167
178
@@ -417,25 +428,36 @@ cd web && pnpm install && cd ..
417
428
418
429
**2. Secrets generieren und `.env`-Dateien schreiben.**
419
430
431
+
`.env` öffnen und die Pflichtfelder ausfüllen:
432
+
420
433
```bash
421
434
cp .env.example .env
422
435
423
-
# Login-Passwort wählen, mit Argon2id hashen und als APP_PASSWORD_HASH einfügen:
424
-
docker run --rm python:3.12-slim sh -c 'pip install -q argon2-cffi && python -c "from argon2 import PasswordHasher; print(PasswordHasher().hash(input(\"password: \")))"'
436
+
# Login-E-Mail setzen (wird der Benutzername):
437
+
# OPERATOR_EMAIL=du@beispiel.de
438
+
439
+
# Passwort hashen und als APP_PASSWORD_HASH einfügen:
440
+
uv run python -m app.tools.hashpw
441
+
# → den $argon2id$...-String als APP_PASSWORD_HASH einfügen
# Postgres-Passwort generieren und die Docker-spezifische env-Datei schreiben:
430
452
cat > .env.docker <<EOF
431
453
POSTGRES_USER=openstudy
432
454
POSTGRES_PASSWORD=$(openssl rand -hex 24)
433
455
POSTGRES_DB=openstudy
434
456
EOF
435
-
chmod 600 .env.docker
457
+
chmod 600 .env .env.docker
436
458
```
437
459
438
-
`.env` enthält deine App-Secrets (Passwort-Hash, Session-Secret, optional Telegram-Tokens). `.env.docker` enthält die Postgres-Credentials, die Compose in den Postgres-Container injiziert. Beide sind in `.gitignore`.
460
+
`.env` enthält deine App-Secrets. `.env.docker` enthält die Postgres-Credentials, die Compose in den Postgres-Container injiziert. Beide sind in `.gitignore`.
0 commit comments