Adds the server side of auto-update (issue #16, was epic #6's [H]). - pb_migrations/1786500000_releases.js: a superuser-only 'releases' collection (platform, variant, version, buildNumber, file, sha256, size, notes). Read is gated in the rules — any signed-in account sees clean builds; only an nsfwEnabled account sees adult ones — so PocketBase's native protected-file serving hands adult bytes only to flagged accounts (no custom streaming). - pb_hooks/update.pb.js: GET /api/update/manifest?platform=… (auth). Picks the variant server-side from the caller's nsfwEnabled (adult) vs clean — the client can't request adult — and returns the latest build's version/buildNumber/notes/ sha256/size + the protected downloadPath. - scripts/publish-release.sh: uploads a built artifact as a superuser (computes sha256 + size, multipart POST). Token or email+password via env. - docs/auto-update-contract.md: the collection, endpoint, download flow, gating. Migration + hook + script syntax-checked. Live verification pends deploying this to the PB (collection auto-applies on boot, hook loads from pb_hooks/).
53 lines
2.1 KiB
JavaScript
53 lines
2.1 KiB
JavaScript
/// <reference path="../pb_data/types.d.ts" />
|
|
|
|
// Auto-update manifest (epic #6, issue #16).
|
|
//
|
|
// GET /api/update/manifest?platform=windows|linux|android (auth: users)
|
|
//
|
|
// Returns the latest published build for the caller's platform. The VARIANT is
|
|
// chosen server-side from the account's admin-set `nsfwEnabled` (adult) vs clean
|
|
// — the client never asks for adult. This is belt-and-suspenders on top of the
|
|
// `releases` collection rules, which already hide adult rows from non-flagged
|
|
// accounts. The download is PocketBase's native protected file endpoint: the app
|
|
// mints a short-lived file token (`POST /api/files/token`) and appends it to
|
|
// `downloadPath`, and the token grant re-checks the same gating rule.
|
|
//
|
|
// NOTE: PocketBase runs each routerAdd handler in its own isolated JSVM, so the
|
|
// small helpers live inside the handler.
|
|
routerAdd("GET", "/api/update/manifest", (e) => {
|
|
const ALLOWED = ["windows", "linux", "android"]
|
|
const platform = (e.request.url.query().get("platform") || "").toLowerCase().trim()
|
|
if (ALLOWED.indexOf(platform) === -1) throw new BadRequestError("bad or missing platform")
|
|
|
|
// The account flag decides the channel; a client can't opt into adult.
|
|
const variant = e.auth.getBool("nsfwEnabled") ? "adult" : "clean"
|
|
|
|
let rec = null
|
|
try {
|
|
const rows = $app.findRecordsByFilter(
|
|
"releases",
|
|
"platform = {:p} && variant = {:v}",
|
|
"-buildNumber",
|
|
1, 0,
|
|
{ p: platform, v: variant }
|
|
)
|
|
if (rows && rows.length) rec = rows[0]
|
|
} catch (_) { /* no matching release */ }
|
|
|
|
if (!rec) return e.json(200, { available: false })
|
|
|
|
const filename = rec.getString("file")
|
|
return e.json(200, {
|
|
available: true,
|
|
platform: platform,
|
|
variant: variant,
|
|
version: rec.getString("version"),
|
|
buildNumber: rec.getInt("buildNumber"),
|
|
notes: rec.getString("notes"),
|
|
sha256: rec.getString("sha256"),
|
|
size: rec.getInt("size"),
|
|
filename: filename,
|
|
// Native protected-file path; the app appends `?token=<file token>`.
|
|
downloadPath: "/api/files/releases/" + rec.id + "/" + filename,
|
|
})
|
|
}, $apis.requireAuth("users"))
|