amber-backend/pb_migrations/1791500000_notices.js

74 lines
2.8 KiB
JavaScript
Raw Permalink Normal View History

/// <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);
},
);