Accounts, API, Architecture: guests and demo copies (#428)
- Accounts: what a guest picks on the sign-in screen, what they get and
do not get, and how an administrator makes a demo copy and offers
demo material.
- API: the demo routes, the PATCH {demo} of the four file kinds, what
answers without a session, and a section on demo copies and demo
material.
- Architecture: DemoStore, MemoryProjectStore, session, guestStores
and DemoFiles.php.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent
4dc3dfe528
commit
d29c6fee79
3 files changed
+63
-9
No files matched your search
+28
-7
@@ -10,7 +10,7 @@ All routes go through `api/index.php?r=…` (the site's nginx does not pass `PAT
|
||||
| POST | `projects` | `{name}` → project document (201) |
|
||||
| GET | `projects/{id}` | `{id,name,revision,updatedAt,project}` |
|
||||
| PUT | `projects/{id}` | `{revision, project}` → `{revision,updatedAt}` or **409** `{error,revision}` when stale |
|
||||
| DELETE | `projects/{id}` | 204, and the snapshots and the release of the project go with it. **403** `{error}` for an administrator who is not the owner |
|
||||
| DELETE | `projects/{id}` | 204, and the snapshots, the release and the demo copy of the project go with it. **403** `{error}` for an administrator who is not the owner |
|
||||
| GET | `projects/{id}/snapshots` | `[{id,name,revision,createdAt}]`, newest first — see [Concepts](Concepts) |
|
||||
| POST | `projects/{id}/snapshots` | `{name?}` → `{id,name,revision,createdAt}` (201). Copies the project as stored; a blank name becomes the UTC date and time. **422** over 120 characters |
|
||||
| DELETE | `projects/{id}/snapshots/{sid}` | 204 |
|
||||
@@ -19,9 +19,14 @@ All routes go through `api/index.php?r=…` (the site's nginx does not pass `PAT
|
||||
| POST | `projects/{id}/release` | → `{token,revision,publishedAt}`. Copies the project as stored; publishing again replaces the copy and keeps the token |
|
||||
| DELETE | `projects/{id}/release` | 204, the link is dead; **404** when the project was not released |
|
||||
| GET | `releases/{token}` | `{name,publishedAt,project}` — the released copy, **without a session**: the one route besides `auth/*` open to everybody. `project.id` is the token, not the project's id. **404** for an unknown or taken-down token |
|
||||
| GET | `textures` | `[{file,name,width,height,bytes,updatedAt,owner,deletable}]` — what the image picker offers: the caller's own files and nobody's, an administrator's every file (see below) |
|
||||
| GET | `projects/{id}/demo` | `{token,revision,publishedAt}`, or `null` while the project has no demo copy (#428). Administrators only |
|
||||
| POST | `projects/{id}/demo` | → `{token,revision,publishedAt}`. Copies the project as stored, like a release; doing it again replaces the copy and keeps the token. Administrators only |
|
||||
| DELETE | `projects/{id}/demo` | 204, guests no longer see the project; **404** when it had no demo copy. Administrators only |
|
||||
| GET | `demos` | `[{token,name,publishedAt}]`, by name: the demo copies a guest picks from, **without a session** |
|
||||
| GET | `demos/{token}` | `{name,publishedAt,project}`, **without a session**, like `releases/{token}`. **404** for an unknown or removed copy |
|
||||
| GET | `textures` | `[{file,name,width,height,bytes,updatedAt,owner,deletable,demo}]` — what the image picker offers: the caller's own files and nobody's, an administrator's every file, and everything offered as demo material (see below). Without a session: the demo material alone, `owner` null |
|
||||
| POST | `textures&name=hero.png` | **raw image bytes** → `{file,name,width,height,bytes}` (201), stored in the caller's folder; **422** for bytes that are no readable image |
|
||||
| DELETE | `textures/{file}` | 204. `{file}` is the stored name, folder and all. **403** unless it lies in the caller's folder or the caller is an administrator, **404** when there is no such file (asked only after the 403, so a stranger learns nothing of another folder), **409** while a project, snapshot or release of any account uses it |
|
||||
| DELETE | `textures/{file}` | 204. `{file}` is the stored name, folder and all. **403** unless it lies in the caller's folder or the caller is an administrator, **404** when there is no such file (asked only after the 403, so a stranger learns nothing of another folder), **409** while a project, snapshot, release or demo copy of any account uses it, and for anybody but an administrator while it is offered as demo material |
|
||||
| GET | `fonts` | `[{file,name,bytes,updatedAt,owner,deletable}]` — what the font picker offers |
|
||||
| POST | `fonts&name=Serif.ttf` | **raw font bytes** → `{file,name,bytes}` (201); **422** for bytes that are no font |
|
||||
| DELETE | `fonts/{file}` | 204, **403**, **409** like an image |
|
||||
@@ -32,8 +37,9 @@ All routes go through `api/index.php?r=…` (the site's nginx does not pass `PAT
|
||||
| POST | `videos&name=holiday.mov[&convert=0]` | **raw video bytes** → the same record (201). The name `{folder}/{sha256}.mp4` is the hash of the upload and known at once; the upload is kept as it came, and the worker converts it later (see [Videos](Videos)). With `convert=0` it is only kept, `unconverted`. The same bytes again answer with what is there, a failed one is tried again. **422** for bytes that cannot be a video (the start of an ISO/MP4, Matroska, AVI or MPEG-TS file), **413** above 2 GB. Unlike every other upload it is streamed to disk, so the 256 MB ceiling of the other routes does not apply |
|
||||
| POST | `videos/{file}/convert` | → the record: an `unconverted` upload is queued, a failed one is tried again, anything else answers as it is. **403** for somebody else's, **404** for no such video |
|
||||
| DELETE | `videos/{file}` | 204, **403**, **409** like an image; also for one that waits, failed or is only kept. The upload as it came goes with it |
|
||||
| PATCH | `textures/{file}`, `fonts/{file}`, `sounds/{file}`, `videos/{file}` | `{demo}` → 204: offers the file to everybody as demo material, or takes that back (#428). **404** for no such file, **422** without a boolean `demo`. Administrators only |
|
||||
| POST | `pixels` | **raw cel bytes** → `{file,bytes}` (201) — the pixels of one layer on one frame of [pixel art](Pixel-Art); **422** for anything that is not a cel. No listing and no delete: a cel is found by the name in the project |
|
||||
| GET | `plugins` | `[{file,bytes,updatedAt}]` — every `.js` in `data/plugins/`, sorted by file name |
|
||||
| GET | `plugins` | `[{file,bytes,updatedAt}]` — every `.js` in `data/plugins/`, sorted by file name. Also without a session: a guest's demo may switch one on |
|
||||
| GET | `plugins/{file}` | the module source as `text/javascript` (`nosniff`, `no-cache`); 404 for anything but a bare file name there — see [Plugins](Plugins) |
|
||||
| GET | `users` | `[{username,role,createdAt,projects:[{id,name,revision,updatedAt}]}]` — every account with its projects, never a password. Administrators only |
|
||||
| POST | `users` | `{username,password,role}` → `{username,role,createdAt}` (201). **409** for a taken name, **422** for a bad name, a password under 8 characters or an unknown role. Administrators only |
|
||||
@@ -41,15 +47,16 @@ All routes go through `api/index.php?r=…` (the site's nginx does not pass `PAT
|
||||
| DELETE | `users/{name}` | 204, and the account is signed out everywhere. **409** while it owns projects, **422** for the caller's own account and the last administrator. Administrators only |
|
||||
|
||||
**Everything but `auth/*` needs a session** and answers **401** `{"error":"Sign in first"}` without
|
||||
one. Projects belong to an account: `projects` lists only the caller's own, and somebody else's
|
||||
one. The exceptions only read: a released game and the demo copies (`releases/…`, `demos`,
|
||||
`demos/…`), the plugins, and the four file lists, which then hold the demo material alone. Projects belong to an account: `projects` lists only the caller's own, and somebody else's
|
||||
project answers **404**, not 403 — that an id exists is already more than a stranger should learn.
|
||||
An **administrator** (#180, #179) reads and saves every project with the same routes, and a save
|
||||
keeps the project's `owner`; their `projects` list stays their own. Deleting a project stays with
|
||||
its owner (#272): an administrator gets **403** `{"error":"Only its owner may delete a project"}`,
|
||||
also for a file without an owner. Snapshots and the release of the project are theirs to manage
|
||||
all the same. The `users` routes answer anybody but an administrator **403**
|
||||
`{"error":"Administrators only"}`. Cels and plugins are shared between accounts; being signed in
|
||||
is still the price of admission. See [Accounts](Accounts).
|
||||
`{"error":"Administrators only"}`. Cels and plugins are shared between accounts; uploading a cel
|
||||
still takes a session. See [Accounts](Accounts).
|
||||
|
||||
## What the server refuses (#427)
|
||||
|
||||
@@ -78,6 +85,20 @@ as somebody signed out, as the owner, as another account and as an administrator
|
||||
route list at the top of `api/index.php` and fails for a route the matrix does not ask about, so a
|
||||
new route has to be put there with what it expects.
|
||||
|
||||
## Demo copies and demo material (#428)
|
||||
|
||||
A guest tries the editor without an account (see [Accounts](Accounts)), and the server writes
|
||||
nothing for them: there is no guest session, no guest folder and no quota.
|
||||
|
||||
- A **demo copy** is a frozen copy of a project in `data/demos/`, stored exactly like a release
|
||||
(`ReleaseStore` on a directory of its own, 0640). An administrator makes, updates and removes it
|
||||
from the project's main menu. The project's own id stays hidden: the copy goes out under its token.
|
||||
- **Demo material** is a list in `data/demo-files.json` (`api/lib/DemoFiles.php`, 0640) of uploaded
|
||||
files an administrator offers to everybody. The files stay in their owner's folder. Every list
|
||||
carries them with `demo: true`, for accounts as well as guests. While a file is offered, only an
|
||||
administrator deletes it, since any account may have put it into a project by then.
|
||||
- A file a demo copy names counts as used (`FileUsage`), like one a release names.
|
||||
|
||||
## Uploaded files belong to an account (#344)
|
||||
|
||||
Every account uploads images, fonts, sounds and videos into a **folder of its own**, and the folder is part
|
||||
|
||||
+29
@@ -22,6 +22,35 @@ everything it held — its tabs, the project, the running plugins, the console l
|
||||
sign-in screen is drawn in its place. Signing in builds a fresh editor in the same canvas, so a
|
||||
second account starts from nothing the first one left behind.
|
||||
|
||||
## Trying it without an account
|
||||
|
||||
Below the form, **Try without an account** lists the demo projects (#428); picking one opens the
|
||||
editor as a **guest**. The window title names *Guest* as the account, the place of the save status
|
||||
in the toolbar says that nothing is saved, and a notice says the same in a sentence.
|
||||
|
||||
A guest works on a copy held in the tab. Everything can be changed, played and recorded, and
|
||||
nothing reaches the server: the editor sends no request that writes, and the server would refuse
|
||||
one anyway, since there is no session. A reload, or *Sign in…* in the Account menu, throws the
|
||||
changes away; picking the demo again starts from the copy as it was. What a guest does not get:
|
||||
|
||||
- *New project*, *Open project…*, *Snapshots…* and *Release…* in the main menu,
|
||||
- *Upload* and *Delete* in the pickers, which list the **demo material** alone, and a file dropped
|
||||
or pasted on the editor,
|
||||
- *Change password* and *Logout*: the Account menu holds *Sign in…* instead.
|
||||
|
||||
A recording a guest makes is saved as a download (an `.mkv` file), like one a released game makes.
|
||||
|
||||
**What guests get is the administrators' call.** In a project's main menu an administrator finds
|
||||
*Demo copy…*: it makes a frozen copy of the project as saved, like a release, and the dialog says
|
||||
how old the copy is and whether the project has changed since. *Update demo copy* replaces it,
|
||||
*Remove* takes it away. The copy is listed under the project's name. An empty project is just a
|
||||
demo that is empty.
|
||||
|
||||
In the image, font, sound and video pickers an administrator has **Offer as demo** (and *Stop
|
||||
offering*) for the picked file. Offered files show up in the pickers of every account and every
|
||||
guest, marked *demo material* under the list, and only an administrator deletes one while it is
|
||||
offered. See [API](API) for the routes.
|
||||
|
||||
## Changing the password
|
||||
|
||||
*Account → Change password* opens a dialog asking for the current password, the new one and its
|
||||
|
||||
+6
-2
@@ -153,6 +153,8 @@ src/core/store/ serialize (untrusted JSON → Project), projectPlugins (w
|
||||
http (one API call, `jsonBody`), ProjectStore/HttpProjectStore (load takes a `prepare` that sees the raw JSON first; a 409 becomes `StaleRevisionError` through `staleRevision`, and restoring a snapshot answers the same way),
|
||||
SnapshotStore/HttpSnapshotStore (the snapshots of a project, #231),
|
||||
ReleaseStore/HttpReleaseStore (a project's released copy and the link to it, #266),
|
||||
DemoStore/HttpDemoStore (the demo copies a guest picks from, and an administrator's half: making one, #428),
|
||||
MemoryProjectStore (a guest's project: the demo copy, held in the tab and saved nowhere else, #428),
|
||||
AuthStore/HttpAuthStore (signing in and out, #177; changing the password, #182),
|
||||
FileStore (what the server holds of one kind: upload and list, no delete) + HttpFileStore (one
|
||||
generic client over the four file collections) — TextureStore, FontStore, SoundStore and
|
||||
@@ -332,7 +334,7 @@ src/editor/ App → Editor (layout + ticker), PlaySession (the run),
|
||||
the hooks are grouped by area in debug/: ui, scene, graph, documents, pixel art, vector art,
|
||||
render, sound, pattern, song, routing, bake, video, curve — `tools/` sees one flat object),
|
||||
textProbe (finding widgets by their text — the ground floor of the test hook, shared with the sign-in screen),
|
||||
auth/LoginScreen + auth/PasswordDialog + auth/loginRules + auth/FormStatus (the sign-in screen and the change-password dialog in the canvas, what either may complain about, and the warning line and busy state both show, #177/#182), autosave,
|
||||
auth/LoginScreen + auth/PasswordDialog + auth/loginRules + auth/FormStatus (the sign-in screen and the change-password dialog in the canvas, what either may complain about, and the warning line and busy state both show, #177/#182), session (what a guest or an account may do, `sessionAllows`, #428), guestStores (a guest's stores: they list what the server offers and refuse every write before a request is made, #428), autosave,
|
||||
polygonEdit (points of an outline: what is under the pointer, whether a point is inside,
|
||||
put / move / take away — scene view and Texture tab; and the same for a ring of a vector art
|
||||
shape, where a point may carry Bezier handles: `ringGripAt`, splitting a curved segment
|
||||
@@ -543,7 +545,9 @@ api/ PHP backend: index.php (routing and the sign-in gate; `ro
|
||||
out as files, `SERVED` for the uploads),
|
||||
lib/{Auth,UserStore,SessionStore}.php (#177: accounts in data/users.json, sessions in data/sessions/)
|
||||
lib/SnapshotStore.php (#231: data/snapshots/{projectId}/, one copy of the project per file)
|
||||
lib/ReleaseStore.php (#266: data/releases/{token}.json and which token a project has)
|
||||
lib/ReleaseStore.php (#266: data/releases/{token}.json and which token a project has; the same
|
||||
class on data/demos/ holds the demo copies, #428)
|
||||
lib/DemoFiles.php (#428: which uploaded files are offered as demo material, data/demo-files.json)
|
||||
lib/LoginThrottle.php (#427: five wrong passwords from one address, then 15 minutes of
|
||||
429; data/sign-ins/), lib/Quota.php (#427: 1 GB per account for uploads, projects and
|
||||
snapshots, 413 for a write that would grow past it)
|
||||
|
||||
Reference in new issue
Block a user