kanban-dev-webapp/pocketbase/pb_migrations/1789600500_created_cards.js
Dave ba990f0109 feat(schema): all collections + role-scoped API rules on remote PB
- migrations/ documented in pocketbase/pb_migrations/ for local PB
- scripts/setup-remote.ts applies them via admin API to remote PB
- collections: clients, projects, columns, cards, subtasks, comments, activity, settings
- user extension: role (owner|client_manager), client (relation)
- writes: owner only for app collections, superuser-only for activity and settings
2026-09-16 09:41:20 +02:00

97 lines
No EOL
3.2 KiB
JavaScript

/// <reference path="../pb_data/types.d.ts" />
/**
* `cards` — units of work.
*
* Two fields belong to the client, not the developer:
* priority — the client sets it directly
* target_date — the client sets it directly
* Everything else is the developer's. That split is enforced by the field
* whitelist in src/lib/cards/client-patch.ts, because PocketBase rules are
* record-level and cannot express it. See the domain primer at the top of
* this plan.
*
* Two fields are written now and first read in Phase 2:
* start_date — bounds which commits a scope rule may claim, so a new card
* cannot retroactively swallow a year of old commits.
* scope_rule — the conventional-commit scope this card owns, e.g. "stock"
* matching `feat(stock): ...`.
*/
migrate(
(app) => {
const projects = app.findCollectionByNameOrId("projects");
const columns = app.findCollectionByNameOrId("columns");
const users = app.findCollectionByNameOrId("users");
const collection = new Collection({
name: "cards",
type: "base",
listRule: "@request.auth.id != ''",
viewRule: "@request.auth.id != ''",
createRule: null,
updateRule: null,
deleteRule: null,
fields: [
{
name: "project",
type: "relation",
required: true,
collectionId: projects.id,
cascadeDelete: true,
minSelect: 0,
maxSelect: 1,
},
{
name: "column",
type: "relation",
required: true,
collectionId: columns.id,
cascadeDelete: false,
minSelect: 0,
maxSelect: 1,
},
{ name: "title", type: "text", required: true, max: 200, presentable: true },
{ name: "description", type: "text", required: false, max: 50000 },
{ name: "position", type: "number", required: true },
{
name: "priority",
type: "select",
required: true,
maxSelect: 1,
values: ["critical", "high", "medium", "low"],
},
{ name: "target_date", type: "date", required: false },
{
name: "created_by",
type: "relation",
required: false,
collectionId: users.id,
cascadeDelete: false,
minSelect: 0,
maxSelect: 1,
},
{
name: "created_by_role",
type: "select",
required: true,
maxSelect: 1,
values: ["owner", "client"],
},
{ name: "accepted", type: "bool", required: false },
{ name: "start_date", type: "date", required: false },
{ name: "scope_rule", type: "text", required: false, max: 60 },
{ name: "archived", type: "bool", required: false },
{ name: "created", type: "autodate", onCreate: true, onUpdate: false },
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
],
indexes: [
"CREATE INDEX idx_cards_column ON cards (`column`, position)",
"CREATE INDEX idx_cards_project ON cards (project, archived)",
],
});
return app.save(collection);
},
(app) => {
return app.delete(app.findCollectionByNameOrId("cards"));
}
);