74 lines
2.8 KiB
JavaScript
74 lines
2.8 KiB
JavaScript
|
|
/// <reference path="../pb_data/types.d.ts" />
|
||
|
|
|
||
|
|
// A short message the owner broadcasts into the app, for breakage he cannot fix.
|
||
|
|
//
|
||
|
|
// **Why this exists.** AniList disabled their public GraphQL API with no notice
|
||
|
|
// and the family's Anime tab stopped working. The app had nothing to say: the
|
||
|
|
// cause was outside it, no retry would help, and the only honest message was
|
||
|
|
// "this is broken, it is not your television, and it is not ours to fix today".
|
||
|
|
// The same shape covers every outage we do not control — prehraj.to down, a
|
||
|
|
// TorBox key expired, the backend being upgraded this evening — and most of
|
||
|
|
// those the app cannot detect at all. The owner can. This is how he says so.
|
||
|
|
//
|
||
|
|
// **Why read is PUBLIC.** A notice has to survive the case where signing in is
|
||
|
|
// itself what is broken, which is exactly when an explanation is worth most. A
|
||
|
|
// rule of `@request.auth.id != ''` would hide the message from the only people
|
||
|
|
// who need it. The cost is that anyone who finds this backend can read it, so:
|
||
|
|
//
|
||
|
|
// NOTHING SENSITIVE GOES IN A NOTICE. No hostnames, no account names, no
|
||
|
|
// "the TorBox key expired" — that last one names a service the household pays
|
||
|
|
// for and tells a reader what to go looking for. Write what a viewer needs
|
||
|
|
// ("anime is not working today, it is an outage at the provider"), not what an
|
||
|
|
// operator would want.
|
||
|
|
//
|
||
|
|
// Writing stays superuser-only. This is the owner's voice, and a compromised
|
||
|
|
// user account must not be able to put words in front of the family.
|
||
|
|
//
|
||
|
|
// **Why `endsAt` as well as `active`.** The predictable failure is not a wrong
|
||
|
|
// notice, it is a stale one: the outage ends, the message stays, and within a
|
||
|
|
// week everybody has learned to ignore the banner. `active` is the switch the
|
||
|
|
// owner flips; `endsAt` is the one he does not have to remember. The client
|
||
|
|
// enforces both, so a forgotten row expires even if nobody touches it again.
|
||
|
|
//
|
||
|
|
// There is deliberately no severity, no title, no dismissal state and no
|
||
|
|
// targeting. Every one of those is a field to get wrong in exchange for
|
||
|
|
// something a single sentence already does, and dismissal is per-run in the
|
||
|
|
// client precisely so it needs no storage here.
|
||
|
|
|
||
|
|
migrate(
|
||
|
|
(app) => {
|
||
|
|
const c = new Collection({
|
||
|
|
type: "base",
|
||
|
|
name: "notices",
|
||
|
|
listRule: "",
|
||
|
|
viewRule: "",
|
||
|
|
createRule: null,
|
||
|
|
updateRule: null,
|
||
|
|
deleteRule: null,
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: "message",
|
||
|
|
type: "text",
|
||
|
|
required: true,
|
||
|
|
max: 400,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "active",
|
||
|
|
type: "bool",
|
||
|
|
required: false,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "endsAt",
|
||
|
|
type: "date",
|
||
|
|
required: false,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
});
|
||
|
|
app.save(c);
|
||
|
|
},
|
||
|
|
(app) => {
|
||
|
|
const c = app.findCollectionByNameOrId("notices");
|
||
|
|
app.delete(c);
|
||
|
|
},
|
||
|
|
);
|