forked from wrenn/wrenn
Add team management endpoints
- Three-role model (owner/admin/member) with owner protection invariants - Team CRUD: create, rename (admin+), soft-delete with VM cleanup (owner only) - Member management: add by email, remove, role updates (admin+), leave - Switch-team endpoint re-issues JWT after DB membership verification - User email prefix search for add-member UI autocomplete - JWT carries role as a hint; all authorization decisions verified from DB - Team slug: immutable 12-char hex (e.g. a1b2c3-d1e2f3), reserved on soft-delete - Migration adds slug + deleted_at to teams; backfills existing rows
This commit is contained in:
@ -42,6 +42,47 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/v1/auth/switch-team:
|
||||
post:
|
||||
summary: Switch active team
|
||||
operationId: switchTeam
|
||||
tags: [auth]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
description: |
|
||||
Re-issues a JWT scoped to a different team. The user must be a member of
|
||||
the target team (verified from DB). Use the returned token for subsequent
|
||||
requests to that team's resources.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [team_id]
|
||||
properties:
|
||||
team_id:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: New JWT issued for the target team
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AuthResponse"
|
||||
"403":
|
||||
description: Not a member of this team
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"404":
|
||||
description: Team not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/v1/auth/login:
|
||||
post:
|
||||
summary: Log in with email and password
|
||||
@ -195,6 +236,340 @@ paths:
|
||||
"204":
|
||||
description: API key deleted
|
||||
|
||||
/v1/users/search:
|
||||
get:
|
||||
summary: Search users by email prefix
|
||||
operationId: searchUsers
|
||||
tags: [users]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
description: |
|
||||
Returns up to 10 users whose email starts with the given prefix.
|
||||
The prefix must contain "@". Intended for the add-member UI autocomplete.
|
||||
parameters:
|
||||
- name: email
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Email prefix (must contain "@", e.g. "alice@")
|
||||
responses:
|
||||
"200":
|
||||
description: Matching users
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/UserSearchResult"
|
||||
"400":
|
||||
description: Prefix does not contain "@"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/v1/teams:
|
||||
get:
|
||||
summary: List teams for the authenticated user
|
||||
operationId: listTeams
|
||||
tags: [teams]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Teams the user belongs to, each with their role
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/TeamWithRole"
|
||||
|
||||
post:
|
||||
summary: Create a new team
|
||||
operationId: createTeam
|
||||
tags: [teams]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [name]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: 1-128 chars; A-Z a-z 0-9 space _
|
||||
responses:
|
||||
"201":
|
||||
description: Team created (caller is owner)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TeamWithRole"
|
||||
"400":
|
||||
description: Invalid team name
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/v1/teams/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Team ID (must match the JWT's team_id)
|
||||
|
||||
get:
|
||||
summary: Get team info and member list
|
||||
operationId: getTeam
|
||||
tags: [teams]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Team details with members
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TeamDetail"
|
||||
"403":
|
||||
description: JWT team does not match requested team
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"404":
|
||||
description: Team not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
patch:
|
||||
summary: Rename the team
|
||||
operationId: renameTeam
|
||||
tags: [teams]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
description: Admin or owner role required (verified from DB).
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [name]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
description: Renamed
|
||||
"400":
|
||||
description: Invalid team name
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"403":
|
||||
description: Insufficient role
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
delete:
|
||||
summary: Delete the team
|
||||
operationId: deleteTeam
|
||||
tags: [teams]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
description: |
|
||||
Owner only. Soft-deletes the team and destroys all running/paused/starting
|
||||
sandboxes. All DB records are preserved. The team slug is permanently reserved.
|
||||
responses:
|
||||
"204":
|
||||
description: Team deleted
|
||||
"403":
|
||||
description: Caller is not the owner
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/v1/teams/{id}/members:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
|
||||
get:
|
||||
summary: List team members
|
||||
operationId: listTeamMembers
|
||||
tags: [teams]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Members with roles
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/TeamMember"
|
||||
|
||||
post:
|
||||
summary: Add a member by email
|
||||
operationId: addTeamMember
|
||||
tags: [teams]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
description: Admin or owner role required. User is added instantly as a member.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [email]
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
responses:
|
||||
"201":
|
||||
description: Member added
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TeamMember"
|
||||
"403":
|
||||
description: Insufficient role
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"404":
|
||||
description: No account with that email
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"400":
|
||||
description: User is already a member
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/v1/teams/{id}/members/{uid}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: uid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Target user ID
|
||||
|
||||
patch:
|
||||
summary: Update member role
|
||||
operationId: updateMemberRole
|
||||
tags: [teams]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
description: |
|
||||
Admin or owner required. Valid target roles: admin, member.
|
||||
The owner's role cannot be changed.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [role]
|
||||
properties:
|
||||
role:
|
||||
type: string
|
||||
enum: [admin, member]
|
||||
responses:
|
||||
"204":
|
||||
description: Role updated
|
||||
"403":
|
||||
description: Insufficient role or attempt to modify owner
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"404":
|
||||
description: User is not a member
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
delete:
|
||||
summary: Remove a member
|
||||
operationId: removeTeamMember
|
||||
tags: [teams]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
description: Admin or owner required. Owner cannot be removed.
|
||||
responses:
|
||||
"204":
|
||||
description: Member removed
|
||||
"403":
|
||||
description: Insufficient role or attempt to remove owner
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"404":
|
||||
description: User is not a member
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/v1/teams/{id}/leave:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
|
||||
post:
|
||||
summary: Leave the team
|
||||
operationId: leaveTeam
|
||||
tags: [teams]
|
||||
security:
|
||||
- bearerAuth: []
|
||||
description: The owner cannot leave; they must delete the team instead.
|
||||
responses:
|
||||
"204":
|
||||
description: Left the team
|
||||
"403":
|
||||
description: Owner cannot leave
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/v1/sandboxes:
|
||||
post:
|
||||
summary: Create a sandbox
|
||||
@ -1338,6 +1713,61 @@ components:
|
||||
tag:
|
||||
type: string
|
||||
|
||||
UserSearchResult:
|
||||
type: object
|
||||
properties:
|
||||
user_id:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
|
||||
Team:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
slug:
|
||||
type: string
|
||||
description: Immutable 12-char hex slug (e.g. a1b2c3-d1e2f3)
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
TeamWithRole:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/Team"
|
||||
- type: object
|
||||
properties:
|
||||
role:
|
||||
type: string
|
||||
enum: [owner, admin, member]
|
||||
|
||||
TeamMember:
|
||||
type: object
|
||||
properties:
|
||||
user_id:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
enum: [owner, admin, member]
|
||||
joined_at:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
TeamDetail:
|
||||
type: object
|
||||
properties:
|
||||
team:
|
||||
$ref: "#/components/schemas/Team"
|
||||
members:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/TeamMember"
|
||||
|
||||
Error:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in New Issue
Block a user