Orgs continuation #34

Merged
clement merged 7 commits from feat/org-sections into main 2026-08-04 21:56:12 +02:00
Owner
No description provided.
Backend
- create_section / list_sections in gear-core. Sections are auto-active, one level deep, and refuse `approve` / `reject`.
- Section creation gated on `is_admin OR parent.requested_by == actor` — a placeholder OrgAdmin check until memberships land.
- HTTP: GET/POST /api/organizations/{id}/sections. Tauri commands mirror the pair.

Frontend
- gear-client: CreateSectionRequest + listOrganizationSections + createOrganizationSection on both transports.
- OrganizationsAdminView renders sections indented under each active org; an "Add section" button opens a dialog (name / kind / description). EN + FR strings.

Tests
- 5 new gear-http integration tests (requester creates section, stranger denied, no nesting, approve refuses sections, list returns children only).
- 2 new gear-web specs (indent rendering, create-section dialog wiring).
User side
- New /organizations page under the account menu: lists the user's own requests grouped by status with a "New request" dialog.
- The requester can add + edit sections on their own active orgs, edit their pending root org's name / kind / description.

Admin side
- Edit dialog on any org or section (kind is root-only; sections inherit the parent's kind).
- Approve accepts pending or rejected → active (undo a mistaken rejection).
- Reject accepts pending or active → rejected (undo a mistaken approval); discoverable stays cleared.

Backend
- UpdateOrganizationInput gains `kind`; sections refuse an explicit kind override.
- ensure_can_update relaxed: a user's own section stays editable after auto-activation.
- Sections drop the `kind` from their creation body; server assigns the parent's kind.

Tests
- 3 new gear-http integration tests (rejected → approve, active → reject, cannot approve an already-active org).
- Vitest cover the user view load / empty / create / error paths + the section-add flow on an active org.
- New /directory page in the main drawer: lists orgs that are both `active` and `discoverable`, with a search box and their sections indented.
- Admin edit dialog gains a "Listed in the public directory" switch on active root orgs; wired through updateOrganization.
- EN + FR translations, DirectoryView vitest covers load / empty / error / search paths.
Backend
- New Membership { org_id, user_id, role: Member|Manager|OrgAdmin, joined_at } entity + migration m0010, indexed on org_id / user_id with a partial unique index that only covers active rows so re-joining after a leave stays possible.
- Service ops: join / set_role / remove / list_by_org / list_by_user + is_org_admin helper.
- Organization approve seeds the requester as OrgAdmin of the freshly-active org, idempotent under re-approval.
- Section create / update gates dropped the `requested_by(parent)` placeholder — they now consult the real OrgAdmin membership. Root read gate accepts members alongside admin + requester.

HTTP + Tauri
- GET/POST /api/organizations/{id}/members (enriched view: user displayName + email).
- PUT/DELETE /api/memberships/{id}, GET /api/me/memberships.
- Tauri commands mirror the same surface.

Frontend
- gear-client types + methods on both transports.
- "Members" dialog on active orgs in both /organizations (requester-side) and /admin/organizations (instance-admin side): list + add-by-email + change-role + remove.
- New "Other affiliations" card on /organizations listing orgs the user is a member of but did not request.
- EN + FR translations.

Tests
- 4 gear-http integration tests on /api/organizations/{id}/members and the section-gate switch (approve seeds OrgAdmin, add/remove by email, stranger denied, section create refused for a Member).
Backend
- MembershipStatus (pending | active) on Membership. Migration m0011 adds the column with default 'active' so slice-5 rows stay unaffected.
- Service split: join() keeps the admin path (auto-active), request_join() creates a pending row.
- POST /api/organizations/{id}/join for user-initiated requests (rejected on non-active orgs), POST /api/memberships/{id}/approve for OrgAdmin / instance-admin activation.
- is_org_admin + is_active_member now filter on status = active. A pending row grants no read access nor management power.

Frontend
- Directory: per-row Join button on active orgs, replaced by a Pending / Member chip once the caller has a row on the org.
- Members dialog (both user and admin views): dedicated "Pending join requests" section at the top with Approve / reject actions.
- Affiliations card on /organizations shows a Pending chip alongside the role.
- EN + FR strings.

Tests
- 2 new gear-http integration tests: request → approve flow (with read-gate flip), rejection of a join on a non-active org.
Backend
- Equipment.org_id: Option<EntityId> in gear-core, migration m0012 adds a nullable BLOB column with an idx_equipment_org_id index. Existing rows stay personal (NULL).
- Service create/update refuse to pin equipment to an org the actor is not an active member of.
- list_visible unions the caller's owned equipment with every piece belonging to orgs they actively belong to.
- GET /api/equipments/{id} accepts an active org member without a ShareGrant; RBAC still covers personal + individually-shared rows.

Frontend
- New useOrgContext Pinia store (persisted localStorage): `active: 'personal' | orgId`, cached list of active orgs, `reset()` on logout, auto-falls back to personal if the persisted org is no longer accessible.
- App-bar dropdown switches context globally (icon + label follow the selection). MainLayout refreshes the cache on mount and whenever isAuthenticated flips.
- EquipmentView drops its local per-page filter, reads orgContext.activeOrgId for filtering, and defaults the create form's orgId to the current context.
- EN + FR strings.

Tests
- New gear-http integration test: org-scoped equipment is only listable/gettable by active members (non-members get an empty list + 403 on the id).
- Existing suites keep passing (148 vitest, 8 equipment integration tests).
Backend
- Kit and Outing gain a nullable org_id, indexed via m0013/m0014.
- kit_service::list_visible + outing_service::list_visible union owner-scoped rows with every entity from the caller's active memberships.
- Create/update refuse pinning to an org the actor is not an active member of; get() accepts an active member without a ShareGrant, falling back to RBAC otherwise.

Frontend
- Kit + Outing UIs filter their lists by orgContext.activeOrgId and default the create form's orgId to the current context. Same shape as slice 7's EquipmentView.
- The per-form "Belongs to" selector is dropped everywhere (Equipment, Kit, Outing): scope is picked once via the app-bar switcher, not repeated on every create dialog.
- Ownership transfers now go through a dedicated "Move to…" icon on each row → dialog. update() preserves the existing orgId on regular saves so nothing rebinds by accident.

Tests
- Repository + model round-trip fixtures updated for the new field.
- OutingsView.spec sets up an active Pinia so useOrgContext resolves.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
clement/GearTrack!34
No description provided.