diff --git a/API.md b/API.md index d99d1fc..bda2bf8 100644 --- a/API.md +++ b/API.md @@ -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 diff --git a/Accounts.md b/Accounts.md index bbc9b65..261133c 100644 --- a/Accounts.md +++ b/Accounts.md @@ -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 diff --git a/Architecture.md b/Architecture.md index 8f71a7d..9318e61 100644 --- a/Architecture.md +++ b/Architecture.md @@ -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)