Organisations: shared repo namespace with guest/member/owner roles #1
Summary
As a user, I can create an organisation and invite other members. An organisation owns repositories just like a user does, and repo paths look identical to user paths:
- user repo:
alice/test - org repo:
my_org/test
Because orgs and users share one URL namespace, names must be collision-checked in both directions.
Member levels
| Level | Rights |
|---|---|
| guest | read-only (can read private org repos, no push) |
| member | read + write (push to org repos) |
| owner | admin (manage members, org settings, create/delete org repos, delete org) |
Current state
users.usernameisunique(V1__init.sql);UsernameServicerejects taken handles withUsernameTakenException(src/main/java/de/workaround/account/UsernameService.java).- All repo routes resolve
{owner}to aUserviafindByUsernameandRepository.owneris aUser(src/main/java/de/workaround/model/Repository.java). AccessPolicy(src/main/java/de/workaround/git/AccessPolicy.java) is the single authz rule set for HTTP, SSH and UI: owner-or-public.- Federation identifies actors by username (WebFinger,
ActorUris) — org names occupy the same handle namespace. - Related: repository collaborators (GitHub issue #10) — same authz extension point; org membership is the org-level analogue of per-repo collaborators.
Proposed implementation
Data model
Organisationentity: uniquename(same handle rules as usernames),displayName,createdAt.OrganisationMemberentity: (organisation, user, role ∈ {GUEST, MEMBER, OWNER}), unique on (organisation, user). Creator becomes OWNER. An org must always have ≥ 1 owner (block removing/downgrading the last one).Repository.ownermust support both owner kinds. Suggested: nullableowner_user_id+ nullableowner_org_idwith a CHECK that exactly one is set (avoids a polymorphic join table); unique repo name per owner either way.- Flyway migration
V11__organisations.sql(or next free number).
Name collision check
- One shared handle namespace: creating an org checks
users.usernameANDorganisations.name; picking a username during onboarding (and any future rename) checksorganisations.nametoo. - Extract the validation into a shared service (extend
UsernameServiceinto a handle service) so both paths use identical rules and error messages. - Enforce at DB level as well as app level (app check + unique indexes; a cross-table exclusion needs the app-level check to be authoritative, wrapped in the same transaction).
Routing
{owner}path segment resolves to user first, then org (names can't collide, so order is only a lookup detail). Applies to repo pages, git smart HTTP and SSH path parsing.
Authorization (AccessPolicy)
canRead: also true for org repos when the user is guest/member/owner of the owning org.canWrite: also true for org members with role MEMBER or OWNER.- Repo admin actions (delete, settings): org OWNER only.
- HTTP, SSH and UI all flow through
AccessPolicy, so git operations follow automatically.
UI
- "New organisation" action (e.g. from the home page next to "New repository"): form with org name, collision-checked.
- Org page at
/{org}: repo list (visibility-filtered like user profiles). - Org settings → Members (org owners only): add user by username with role select, change role, remove member. Errors: unknown username, already a member, last owner.
- "New repository" flow gains an owner selector: personal or any org where the user is OWNER (or MEMBER — decide; suggest OWNER-only for v1).
Out of scope (v1)
- Federation actors for orgs (WebFinger/ActivityPub org actor) — namespace is reserved by the collision check, actor support later.
- Teams, per-repo overrides inside orgs, org avatars, transferring repos between owners, email invitations with accept/decline (adding is immediate, like the collaborators feature).
Acceptance criteria
- [ ] User can create an org; creator becomes owner; org repo lives at
/{org}/{repo}. - [ ] Creating an org with an existing username (or org name) fails with a clear error; picking a username matching an existing org name fails likewise.
- [ ] Org owner can add/remove members and change roles via UI; last owner cannot be removed or downgraded.
- [ ] guest: can read private org repos (UI + clone via HTTP/SSH), cannot push.
- [ ] member: can push (HTTP/SSH); non-members cannot access private org repos.
- [ ] owner: can manage members, create and delete org repos.
- [ ] Failing tests first (handle-collision service, AccessPolicy roles, routing resolution, resource tests), green after implementation.
- [ ] Docs updated in the same PR:
docs/users/(orgs guide),docs/admins/(new tables), rootREADME.mdfeature list;docs/maintainers/if federation namespace reservation is documented there.
Migrated from https://github.com/workaround-org/git-shark/issues/12 Migrated from https://gitshark.ha1nz.de/repos/miggi/GitShark/issues/1 (original author: miggi, created 2026-07-09)