/// PocketBase migration: IngTrak sync collections (custom labels + annotations). /// /// Apply on the server (from /opt/pocketbase or wherever the binary lives): /// /// sudo cp this_file /opt/pocketbase/pb_migrations/ /// sudo chown pocketbase:pocketbase /opt/pocketbase/pb_migrations/*.js /// sudo -u pocketbase /opt/pocketbase/pocketbase migrate up /// sudo systemctl restart pocketbase /// /// Auth collection must already exist as `usersapp`. /// Built-in labels stay in the Flutter app — only custom labels sync. migrate((app) => { const users = app.findCollectionByNameOrId("usersapp"); const ownerRules = { listRule: "user = @request.auth.id", viewRule: "user = @request.auth.id", createRule: "@request.auth.id != '' && user = @request.auth.id", updateRule: "user = @request.auth.id", deleteRule: "user = @request.auth.id", }; // --- custom labels (TxLabel where custom == true + labelParents) --- const labels = new Collection({ type: "base", name: "labels", ...ownerRules, fields: [ { type: "relation", name: "user", required: true, collectionId: users.id, cascadeDelete: true, maxSelect: 1, }, { // App-side id, e.g. "custom_ah" (not the PocketBase record id) type: "text", name: "client_id", required: true, min: 1, max: 80, }, { type: "text", name: "name", required: true, min: 1, max: 80, }, { // Flutter Color.value (ARGB int) type: "number", name: "color", required: true, onlyInt: true, }, { // Parent label client_id (builtin or custom); empty = root type: "text", name: "parent_client_id", required: false, max: 80, }, ], indexes: [ "CREATE UNIQUE INDEX idx_labels_user_client ON labels (user, client_id)", ], }); app.save(labels); // --- per-transaction overlays (TxMetaStoreData.byId) --- const txMeta = new Collection({ type: "base", name: "tx_meta", ...ownerRules, fields: [ { type: "relation", name: "user", required: true, collectionId: users.id, cascadeDelete: true, maxSelect: 1, }, { // BankTransaction.id → "date|currency|amount|description" type: "text", name: "tx_id", required: true, min: 1, max: 2000, }, { type: "json", name: "label_ids", required: false, }, { type: "bool", name: "recurring", required: false, }, { type: "bool", name: "internal_transfer", required: false, }, { type: "text", name: "notes", required: false, max: 4000, }, ], indexes: [ "CREATE UNIQUE INDEX idx_tx_meta_user_tx ON tx_meta (user, tx_id)", ], }); app.save(txMeta); // --- merchant auto-label / notes rules (labelsByMerchant + notesByMerchant) --- const merchantRules = new Collection({ type: "base", name: "merchant_rules", ...ownerRules, fields: [ { type: "relation", name: "user", required: true, collectionId: users.id, cascadeDelete: true, maxSelect: 1, }, { // merchantKeyFromDescription(...) e.g. "albert heijn" type: "text", name: "merchant_key", required: true, min: 1, max: 200, }, { type: "json", name: "label_ids", required: false, }, { type: "text", name: "notes", required: false, max: 4000, }, ], indexes: [ "CREATE UNIQUE INDEX idx_merchant_rules_user_key ON merchant_rules (user, merchant_key)", ], }); app.save(merchantRules); // --- exact amount+description templates (exactTemplates / TxExactTemplate) --- const exactTemplates = new Collection({ type: "base", name: "exact_templates", ...ownerRules, fields: [ { type: "relation", name: "user", required: true, collectionId: users.id, cascadeDelete: true, maxSelect: 1, }, { // BankTransaction.exactKey → "descriptionKey|amount|currency" type: "text", name: "exact_key", required: true, min: 1, max: 2000, }, { type: "text", name: "notes", required: false, max: 4000, }, { type: "bool", name: "recurring", required: false, }, { type: "bool", name: "internal_transfer", required: false, }, ], indexes: [ "CREATE UNIQUE INDEX idx_exact_templates_user_key ON exact_templates (user, exact_key)", ], }); app.save(exactTemplates); }, (app) => { for (const name of [ "exact_templates", "merchant_rules", "tx_meta", "labels", ]) { try { app.delete(app.findCollectionByNameOrId(name)); } catch (_) {} } });