210 lines
9.7 KiB
Markdown
210 lines
9.7 KiB
Markdown
# Mathew — a wiki for mathematics
|
|
|
|
A collaborative wiki of mathematical concepts. Articles are written in **Markdown** with
|
|
**LaTeX** (KaTeX), stored in MongoDB behind an Express API, and rendered by a Nuxt 4 app
|
|
as an open book with a table of contents, a table of topics, a link graph, and a
|
|
review-then-publish flow for edits.
|
|
|
|
The interface speaks five languages — English, Spanish, Catalan, French, German — chosen
|
|
per account, and articles themselves are multilingual: one article can exist in several
|
|
languages, each with its own address.
|
|
|
|
```
|
|
mathew/
|
|
├── dev.sh # start/stop both dev servers together
|
|
├── docker-compose.yml # nginx + frontend + backend (prebuilt images)
|
|
├── nginx.conf # reverse proxy: / → frontend, /api/ → backend
|
|
├── backend/ # Express 5 + Mongoose API (port 5000)
|
|
└── frontend/ # Nuxt 4 / Vue 3 wiki UI (port 3000)
|
|
```
|
|
|
|
## Quick start
|
|
|
|
Requires **MongoDB** on `localhost:27017` (database `mathew`) and **Node 20+**.
|
|
|
|
```bash
|
|
# 1. API
|
|
cd backend
|
|
npm install
|
|
cp .env.example .env # then set JWT_SECRET
|
|
npm run seed # optional: sample articles with LaTeX
|
|
npm run dev # → http://localhost:5000
|
|
|
|
# 2. UI
|
|
cd ../frontend
|
|
npm install
|
|
npm run dev # → http://localhost:3000
|
|
```
|
|
|
|
Or run both together from the repo root:
|
|
|
|
```bash
|
|
./dev.sh # both dev servers in the foreground (Ctrl+C stops both)
|
|
./dev.sh start|stop|restart|status
|
|
./dev.sh logs [all|backend|frontend]
|
|
```
|
|
|
|
### Scripts
|
|
|
|
| Where | Command | What it does |
|
|
| --- | --- | --- |
|
|
| root | `./dev.sh [dev]` | Both dev servers, foreground, tagged output, one Ctrl+C stops both |
|
|
| `backend/` | `npm run dev` | API with `node --watch` (no build step) |
|
|
| `backend/` | `npm start` | API without watch |
|
|
| `backend/` | `npm run seed` | A handful of sample articles |
|
|
| `backend/` | `npm run seed:bulk` | 100 cross-linked articles for load-testing the UI and graph (idempotent) |
|
|
| `frontend/` | `npm run dev` / `build` / `preview` | Nuxt dev server, production build, preview of the build |
|
|
|
|
There is **no test suite, linter, or typecheck script** configured.
|
|
|
|
### Configuration
|
|
|
|
| Where | Variable | Default | Meaning |
|
|
| --- | --- | --- | --- |
|
|
| `backend/.env` | `PORT` | `5000` | API port |
|
|
| `backend/.env` | `MONGO_URI` | `mongodb://127.0.0.1:27017/mathew` | Mongo connection |
|
|
| `backend/.env` | `JWT_SECRET` | *(insecure dev key)* | Signs session tokens — set this in production |
|
|
| `backend/.env` | `AUTH_TOKEN_TTL` | `7d` | How long a session lasts |
|
|
| `frontend` env | `NUXT_PUBLIC_API_BASE` | `http://localhost:5000` | Where the UI calls the API |
|
|
|
|
## How it works
|
|
|
|
**Reading is open; writing takes an account.** Anyone can register, and the **first account
|
|
ever created becomes the admin** (on a database whose accounts all predate roles, the oldest
|
|
one gets it at startup). The admin hands the role on or takes it back from `/admin`; nobody
|
|
may change their own role, so the dashboard always has a door.
|
|
|
|
- An **admin**'s save applies immediately.
|
|
- A **member**'s save is answered `202` and filed as an **edit proposal** instead — the live
|
|
article does not move. Saving again while a proposal waits *revises* it, and each proposal
|
|
keeps a snapshot of the article as it read when the request was sent, so the desk can show
|
|
a diff and warn when the page has moved since.
|
|
- Proposals include **new articles**: a request for a page the wiki does not have yet keeps
|
|
the address it asks for and appears only when approved.
|
|
- Approving or rejecting happens at `/admin/proposals`, and approval runs the same code an
|
|
admin's direct write runs — so a retitle re-slugs, and a language the article already has
|
|
in another version is refused.
|
|
- **Deleting is admin-only**, and it settles that article's waiting proposals with it.
|
|
|
|
Articles name no author, so they outlive the account that wrote them.
|
|
|
|
### Languages
|
|
|
|
Two language ideas, deliberately apart:
|
|
|
|
- The **interface language** (`en`, `es`, `ca`, `fr`, `de`) is a preference and never part of
|
|
a URL. Precedence: the signed-in account's choice → the `mathew-locale` cookie → the
|
|
browser → English. Pick it in the account popup.
|
|
- The **article language** appears in the address: `/wiki/<lang>/<identity>`. Every version
|
|
of an article has its own address, English included — `/wiki/en/bayes-theorem` and
|
|
`/wiki/fr/bayes-theorem` are one article two ways. The short form `/wiki/<slug>` redirects
|
|
onto the version the reader should see: their own language if the article speaks it,
|
|
English failing that.
|
|
|
|
Switching an article's language turns the page of the open book rather than opening a second
|
|
one, and "recently viewed" remembers article + language as a single entry.
|
|
|
|
### Themes
|
|
|
|
Light and dark, plus eight accent colours, chosen in the settings popup and applied before
|
|
first paint so there is no flash. Colours are CSS custom properties swapped by
|
|
`data-theme` / `data-accent` on `<html>`.
|
|
|
|
## Pages
|
|
|
|
| Route | What it is |
|
|
| --- | --- |
|
|
| `/` | Search, recently viewed, a way into the shelf |
|
|
| `/articles` | The shelf, filed by topic — one folder per topic, opening onto a batch-at-a-time grid of cards; language and topic filters are applied by the API |
|
|
| `/graph` | The wiki's link map: one node per article, one edge per linked pair |
|
|
| `/wiki/<lang>/<slug>` | One language version of an article, drawn as a page of the book with its table of contents |
|
|
| `/wiki/<slug>` | The short form, naming no language — turned onto the version the slug speaks |
|
|
| `/wiki/<slug>/edit` | Markdown editor with live preview (Write / Split / Preview) |
|
|
| `/new` | Create a new article |
|
|
| `/admin` | Accounts and roles (admin only) |
|
|
| `/admin/proposals` | The edit-request desk: diff, approve, reject (admin only) |
|
|
|
|
## Writing articles
|
|
|
|
GitHub-ish Markdown — headings, tables, lists, blockquotes, fenced code with syntax
|
|
highlighting — plus LaTeX: `$E = mc^2$` inline, `$$ … $$` in display mode. Number-set
|
|
macros `\R`, `\N`, `\Z`, `\Q`, `\C` are available. Raw HTML in an article is escaped, and
|
|
everything rendered is sanitized with DOMPurify.
|
|
|
|
Link between articles with wiki links — `[[Bayes' theorem]]`, `[[Fourier series#definition]]`,
|
|
`[[Zeno's paradoxes|Zeno]]` — or with a normal Markdown link to `/wiki/<slug>` or
|
|
`/wiki/<lang>/<slug>`. Both feed the graph view.
|
|
|
|
Shortcuts: `/` focuses search on the home page, `Ctrl/Cmd+K` opens the search popup,
|
|
`Ctrl/Cmd+S` saves in the editor. Changing an article's title can change its slug — old
|
|
links then 404, as no redirects are kept.
|
|
|
|
## API
|
|
|
|
`GET /health` answers `{ status, mongo }`. Everything below lives under `/api`.
|
|
Reads are public; writes need `Authorization: Bearer <token>` from `/auth/login` or
|
|
`/auth/register`. All errors answer `{ message }`.
|
|
|
|
### Accounts
|
|
|
|
| Method | Endpoint | Notes |
|
|
| --- | --- | --- |
|
|
| POST | `/auth/register` | Creates the account and returns its session; the first account ever is an admin |
|
|
| POST | `/auth/login` | A wrong name and a wrong password answer the same way |
|
|
| GET | `/auth/me` | Who a token belongs to |
|
|
| PUT | `/auth/me` | Rename |
|
|
| PUT | `/auth/password` | Needs the current password |
|
|
| PUT | `/auth/locale` | Interface language — one of `en es ca fr de` |
|
|
| PUT | `/auth/avatar` | A small data URL; `""` clears it |
|
|
|
|
### Articles
|
|
|
|
Articles are addressed by **slug**, not by Mongo id.
|
|
|
|
| Method | Endpoint | Notes |
|
|
| --- | --- | --- |
|
|
| GET | `/articles` | `{ articles, total, hasMore }`, paged **by article** — `?q=`, `?tag=`, `?lang=`, `?limit=`, `?offset=`; `?prefer=` picks which version stands for an article, `?preferFirst` ranks those versions first; no `limit` means the whole shelf |
|
|
| GET | `/articles/facets` | `{ total, topics, languages }` counted over the whole shelf |
|
|
| GET | `/articles/random` | One random article |
|
|
| GET | `/articles/graph` | `{ nodes, edges }` — the link map built from wiki and Markdown links |
|
|
| GET | `/articles/slug/:slug` | One article |
|
|
| POST | `/articles` | Create (`title` required; the slug is generated and uniquified). Admin applies; a member is answered `202 { proposal }` |
|
|
| PUT | `/articles/slug/:slug` | Update. Admin applies; a member is answered `202 { proposal }` |
|
|
| DELETE | `/articles/slug/:slug` | Admin only |
|
|
|
|
### Administration
|
|
|
|
Everything behind `requireAuth, requireAdmin`.
|
|
|
|
| Method | Endpoint | Notes |
|
|
| --- | --- | --- |
|
|
| GET | `/admin/users` | Every account, newest first |
|
|
| PUT | `/admin/users/:id/role` | `role` is `admin` or `member`; never your own |
|
|
| DELETE | `/admin/users/:id` | Close an account — never your own; its articles stay |
|
|
| GET | `/admin/proposals` | Every request, decided ones included, with its diff material |
|
|
| GET | `/admin/proposals/count` | Just `{ pending }` — what the sidebar's badge wears |
|
|
| PUT | `/admin/proposals/:id/approve` | Writes the change; a conflict refuses it and the request keeps waiting |
|
|
| PUT | `/admin/proposals/:id/reject` | Refuses; the page stays put |
|
|
|
|
`/api/items` is a small demo resource (CRUD over one schema) left in as a reference for how
|
|
a router, model and error path fit together.
|
|
|
|
## Deploy
|
|
|
|
`docker-compose.yml` runs nginx in front of prebuilt frontend and backend images: nginx
|
|
listens on `3000` and routes `/api/` to the backend and everything else to the Nuxt server,
|
|
passing the standard forwarded headers. Point `NUXT_PUBLIC_API_BASE` and `MONGO_URI` at the
|
|
deployment's real addresses and set a real `JWT_SECRET` — the fallback key is a development
|
|
convenience only, and anyone holding it can mint a session.
|
|
|
|
## Notes for contributors
|
|
|
|
[`AGENTS.md`](AGENTS.md) is the working map of the codebase: file-by-file layout, the
|
|
backend and frontend conventions, and the gotchas that are easy to get wrong (article
|
|
identity vs. document slug, versioned addresses, MongoDB's reserved `language` field,
|
|
localization rules).
|
|
|
|
## License
|
|
|
|
MIT.
|