Migration 060 (paliad.user_pinned_projects): per-user, RLS owner-only, ON
DELETE CASCADE on both FKs.
PinService (Pin / Unpin / IsPinned / PinnedSet / ListPinned): visibility-
gates pin (can't pin what you can't see) but not unpin (so users can clean
up after losing access). PinnedSet returns a map for O(1) lookups during
tree stitching.
ProjectService.BuildTreeWithOptions extends BuildTree with chip-driven
filtering. New ProjectTreeNode fields are additive (Pinned,
InheritedVisibility, OpenDeadlinesSubtree, OverdueDeadlinesSubtree,
MatchKind) so the old BuildTree(ctx, userID) call still works for legacy
callers. New options:
Scope: All / Mine / Pinned (Mine + Pinned both expand to path-closure
with InheritedVisibility flag on greyed ancestors)
StatusIn / TypeIn: chip-narrowing whitelists
HasOpenDeadlines: per-node or subtree-aggregated, depending on
IncludeSubtreeCounts
SearchTerm: case-fold contains on title/reference/clientmatter, then
prune to {matches ∪ ancestors ∪ descendants} with match_kind tagged
IncludeSubtreeCounts: post-order DFS sums, O(N)
GET /api/projects/tree gains query params: scope, status, type,
has_open_deadlines, q, subtree_counts. Zero query string preserves
legacy behaviour.
POST/DELETE /api/projects/{id}/pin and GET /api/user-pinned-projects
wired. Service registered in cmd/server/main.go and dbServices.
build + vet clean.
Design: docs/design-projects-page-2026-05-07.md §4.7, §8.1, §8.3.
54 lines
2.2 KiB
SQL
54 lines
2.2 KiB
SQL
-- t-paliad-149 PR 1: per-user project pins.
|
|
--
|
|
-- Design: docs/design-projects-page-2026-05-07.md §4.7 (godel,
|
|
-- m-locked 2026-05-07).
|
|
--
|
|
-- Stores per-user pinned projects. A user pins a project to mark it
|
|
-- as a favourite for the /projects page (chip "Angepinnt" filters
|
|
-- the tree to pinned-only; star marker on every row toggles state).
|
|
--
|
|
-- RLS scopes every operation to the calling user — pins are personal
|
|
-- working state, not project-team metadata. There is no cross-user
|
|
-- visibility in v1; no global_admin override.
|
|
--
|
|
-- ON DELETE CASCADE on both FKs: project deletion removes pin rows;
|
|
-- user deletion removes their pins. No referential drift possible.
|
|
--
|
|
-- Sections:
|
|
-- 1. CREATE paliad.user_pinned_projects (with RLS).
|
|
-- 2. Indexes.
|
|
|
|
-- ============================================================================
|
|
-- 1. paliad.user_pinned_projects
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE paliad.user_pinned_projects (
|
|
user_id uuid NOT NULL REFERENCES paliad.users(id) ON DELETE CASCADE,
|
|
project_id uuid NOT NULL REFERENCES paliad.projects(id) ON DELETE CASCADE,
|
|
pinned_at timestamptz NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (user_id, project_id)
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- 2. Indexes
|
|
-- ============================================================================
|
|
|
|
-- Hot path: list pins for a user, most-recently-pinned first. The PK
|
|
-- already covers (user_id, project_id), but a separate index ordered
|
|
-- by pinned_at DESC keeps the "Angepinnt" filter chip fast even as a
|
|
-- user accumulates many pins.
|
|
CREATE INDEX user_pinned_projects_user_idx
|
|
ON paliad.user_pinned_projects (user_id, pinned_at DESC);
|
|
|
|
-- ============================================================================
|
|
-- 3. RLS
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE paliad.user_pinned_projects ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Owner-only access. No global_admin override.
|
|
CREATE POLICY user_pinned_projects_owner_all
|
|
ON paliad.user_pinned_projects FOR ALL
|
|
USING (user_id = auth.uid())
|
|
WITH CHECK (user_id = auth.uid());
|