82 lines
4 KiB
JavaScript
82 lines
4 KiB
JavaScript
|
|
/// <reference path="../pb_data/types.d.ts" />
|
||
|
|
|
||
|
|
// The services a BYOC user has to buy, described in data rather than in markup.
|
||
|
|
//
|
||
|
|
// **Why a collection and not copy in index.html.** Two of these fields go stale
|
||
|
|
// on somebody else's schedule: the price, and the click-path through a signup
|
||
|
|
// form the provider is free to redesign. A wrong price on our own page is worse
|
||
|
|
// than no price — it reads as a promise — and the fix must not require editing
|
||
|
|
// HTML and redeploying the backend the family streams through. Everything here
|
||
|
|
// is editable in the admin UI, and `priceCheckedAt` is rendered next to the
|
||
|
|
// number so a stale figure is visibly stale instead of quietly wrong.
|
||
|
|
//
|
||
|
|
// **Why not onboarding_template.** That collection holds the family's actual
|
||
|
|
// credentials and is superuser-only on every rule for that reason. Public-facing
|
||
|
|
// marketing copy has no business sharing a table with secrets, and widening its
|
||
|
|
// read rule to serve a price list is exactly the kind of accident this comment
|
||
|
|
// exists to prevent.
|
||
|
|
//
|
||
|
|
// **Why `kind`.** TMDB is not a source and is not optional — no key means no
|
||
|
|
// posters and no descriptions — so the chooser must not offer it as one of three
|
||
|
|
// things to weigh up. `source` rows are choices; `metadata` rows are steps that
|
||
|
|
// happen regardless. Keeping it a token rather than a boolean leaves room for a
|
||
|
|
// third kind without another migration.
|
||
|
|
//
|
||
|
|
// **Why `price` is text.** These are quoted in different currencies and
|
||
|
|
// different periods ("5 $/měsíc", "199 Kč/měsíc", "$30/rok"), and the page only
|
||
|
|
// ever displays it. A number plus a currency column would be three fields to get
|
||
|
|
// wrong in exchange for arithmetic nobody does.
|
||
|
|
//
|
||
|
|
// Read is limited to signed-in users. There is no public signup — accounts are
|
||
|
|
// owner-created — so nobody who cannot log in has any use for this, and the
|
||
|
|
// tighter rule is free. Writing stays superuser-only: this is content the owner
|
||
|
|
// maintains, not something a user submits.
|
||
|
|
|
||
|
|
migrate(
|
||
|
|
(app) => {
|
||
|
|
const c = new Collection({
|
||
|
|
type: "base",
|
||
|
|
name: "providers",
|
||
|
|
listRule: "@request.auth.id != ''",
|
||
|
|
viewRule: "@request.auth.id != ''",
|
||
|
|
createRule: null,
|
||
|
|
updateRule: null,
|
||
|
|
deleteRule: null,
|
||
|
|
fields: [
|
||
|
|
// torbox | prehrajto | webshare | tmdb. The page keys its per-service
|
||
|
|
// wiring off this, so it is the one field that must not be edited
|
||
|
|
// casually — hence the note here rather than only in the UI.
|
||
|
|
{ type: "text", name: "slug", required: true, max: 40 },
|
||
|
|
{ type: "text", name: "name", required: true, max: 80 },
|
||
|
|
{ type: "url", name: "url", required: true },
|
||
|
|
{ type: "text", name: "kind", required: true, max: 20 },
|
||
|
|
// One line: what this unlocks, in the viewer's terms.
|
||
|
|
{ type: "text", name: "tagline", max: 300 },
|
||
|
|
{ type: "json", name: "pros", maxSize: 4000 },
|
||
|
|
{ type: "json", name: "cons", maxSize: 4000 },
|
||
|
|
{ type: "text", name: "price", max: 120 },
|
||
|
|
{ type: "text", name: "priceNote", max: 300 },
|
||
|
|
{ type: "date", name: "priceCheckedAt" },
|
||
|
|
// Ordered strings, rendered as a numbered list. Plain text on purpose:
|
||
|
|
// this is read by someone with a signup form open in the next tab, and
|
||
|
|
// markup would be one more thing to get wrong in an admin textarea.
|
||
|
|
{ type: "json", name: "steps", maxSize: 20000 },
|
||
|
|
{ type: "number", name: "sort" },
|
||
|
|
// A row is hidden rather than deleted when a service stops being worth
|
||
|
|
// recommending, so its guide survives for anyone who still has it.
|
||
|
|
{ type: "bool", name: "enabled" },
|
||
|
|
{ type: "bool", name: "recommended" },
|
||
|
|
{ type: "autodate", name: "created", onCreate: true },
|
||
|
|
{ type: "autodate", name: "updated", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
indexes: [
|
||
|
|
"CREATE UNIQUE INDEX idx_providers_slug ON providers (slug)",
|
||
|
|
],
|
||
|
|
})
|
||
|
|
app.save(c)
|
||
|
|
},
|
||
|
|
(app) => {
|
||
|
|
app.delete(app.findCollectionByNameOrId("providers"))
|
||
|
|
},
|
||
|
|
)
|