/// // Auto-update release catalog (epic #6, issue #16). // // One row per published build, keyed by (platform, variant, buildNumber). The // app's updater asks `/api/update/manifest` which build is latest for its // platform; the server picks the VARIANT from the caller's admin-set // `nsfwEnabled` (adult) vs clean — the client never chooses. // // Gating lives in the access rules: any signed-in user can read CLEAN releases; // only an `nsfwEnabled` account can read ADULT ones. Because the view rule needs // auth, PocketBase serves the attached file as a *protected* file (a short-lived // file token is required, and minting one re-checks the same rule) — so adult // bytes only ever reach flagged accounts, with no custom file streaming. Writes // are superuser-only (publish goes through the admin API; see scripts/publish). migrate((app) => { const releases = new Collection({ type: "base", name: "releases", // Read: signed-in; adult rows only for nsfwEnabled accounts. The file // inherits this (protected → download needs a file token that re-checks it). listRule: "@request.auth.id != '' && (variant = 'clean' || @request.auth.nsfwEnabled = true)", viewRule: "@request.auth.id != '' && (variant = 'clean' || @request.auth.nsfwEnabled = true)", // Publish / edit / remove: superuser only (null → not exposed to clients). createRule: null, updateRule: null, deleteRule: null, fields: [ { type: "select", name: "platform", required: true, maxSelect: 1, values: ["windows", "linux", "android"] }, { type: "select", name: "variant", required: true, maxSelect: 1, values: ["clean", "adult"] }, // Human semver shown to the user, e.g. "1.0.1". buildNumber is the // monotonic comparator the updater actually compares against. { type: "text", name: "version", required: true, max: 40 }, { type: "number", name: "buildNumber", required: true, min: 1, onlyInt: true }, // The artifact: desktop = a .zip of the release bundle, android = the .apk. { type: "file", name: "file", required: true, maxSelect: 1, maxSize: 524288000 }, // Lowercase hex SHA-256 of the artifact; the app verifies it before install. { type: "text", name: "sha256", required: true, min: 64, max: 64 }, { type: "number", name: "size", required: true, min: 0, onlyInt: true }, { type: "text", name: "notes", required: false, max: 4000 }, { type: "autodate", name: "created", onCreate: true }, { type: "autodate", name: "updated", onCreate: true, onUpdate: true }, ], indexes: [ "CREATE UNIQUE INDEX `idx_releases_pvb` ON `releases` (`platform`, `variant`, `buildNumber`)", ], }) app.save(releases) }, (app) => { // ---- Down migration ---------------------------------------------------- try { app.delete(app.findCollectionByNameOrId("releases")) } catch (_) { /* already gone */ } })