/** * Initial PokeShare schema: extends the default `users` auth collection and * creates `public_users`, `mycards` and `friendships` with their API rules. * Mirrors the production schema (pb_schema.json export). * Applied automatically by `pocketbase serve` on first boot. */ migrate((app) => { // --- users (auth collection): extra profile fields, self-only access --- const users = app.findCollectionByNameOrId("users"); users.fields.add( { name: "username", type: "text", required: true, min: 3, max: 20 }, { name: "theme_preference", type: "select", values: ["System", "Light", "Dark"], maxSelect: 1, }, { name: "visibility", type: "select", values: ["Public", "Friends", "Private"], maxSelect: 1, }, ); users.indexes.push( "CREATE UNIQUE INDEX idx_users_username ON users (username COLLATE NOCASE)", ); users.listRule = "id = @request.auth.id"; users.viewRule = "id = @request.auth.id"; users.updateRule = "id = @request.auth.id"; // No public registration — accounts are created from the admin UI (/_/) users.createRule = null; app.save(users); // --- public_users: read-only view used by the friend search --- const publicUsers = new Collection({ name: "public_users", type: "view", viewQuery: `SELECT id, username, name, visibility FROM users WHERE visibility != 'Private'`, listRule: '@request.auth.id != ""', viewRule: null, }); app.save(publicUsers); // --- mycards: one row per owned card --- // listRule enforces collection visibility at the database level: // owner, or anyone if the owner is Public, or accepted friends if Friends. const mycards = new Collection({ name: "mycards", type: "base", fields: [ { name: "card_id", type: "text" }, { name: "card_name", type: "text" }, { name: "user", type: "relation", collectionId: "_pb_users_auth_", maxSelect: 1, cascadeDelete: true, }, { name: "card_image", type: "url" }, { name: "variant", type: "text" }, { name: "quantity", type: "number" }, { name: "set_name", type: "text" }, { name: "notes", type: "text" }, { name: "condition", type: "text" }, ], listRule: "@request.auth.id = user || (user.visibility = 'Public') || (user.visibility = 'Friends' && ((@collection.friendships.requester = user && @collection.friendships.addressee = @request.auth.id && @collection.friendships.status = 'accepted') || (@collection.friendships.addressee = user && @collection.friendships.requester = @request.auth.id && @collection.friendships.status = 'accepted')))", viewRule: "@request.auth.id = user || (@collection.friendships.requester = user && @collection.friendships.addressee = @request.auth.id && @collection.friendships.status = 'accepted') || (@collection.friendships.addressee = user && @collection.friendships.requester = @request.auth.id && @collection.friendships.status = 'accepted')", createRule: '@request.auth.id != "" && user = @request.auth.id', updateRule: '@request.auth.id != "" && user = @request.auth.id', deleteRule: '@request.auth.id != "" && user = @request.auth.id', }); app.save(mycards); // --- friendships --- const friendships = new Collection({ name: "friendships", type: "base", fields: [ { name: "requester", type: "relation", collectionId: "_pb_users_auth_", maxSelect: 1, cascadeDelete: true, }, { name: "addressee", type: "relation", collectionId: "_pb_users_auth_", maxSelect: 1, cascadeDelete: true, }, { name: "status", type: "select", values: ["accepted", "pending", "declined"], maxSelect: 1, }, ], listRule: "@request.auth.id = requester || @request.auth.id = addressee", viewRule: "@request.auth.id = requester || @request.auth.id = addressee", createRule: "@request.auth.id = requester && addressee != requester", updateRule: "@request.auth.id = addressee || @request.auth.id = requester", deleteRule: "@request.auth.id = addressee || @request.auth.id = requester", }); app.save(friendships); }, (app) => { // Down: drop the custom collections. The added `users` fields are left in // place — removing them would destroy data and they are harmless. for (const name of ["friendships", "mycards", "public_users"]) { try { app.delete(app.findCollectionByNameOrId(name)); } catch { // already missing } } });