349 lines
11 KiB
Python
349 lines
11 KiB
Python
from typing import Optional
|
|
|
|
from django.db.models import Count, Exists, OuterRef, Prefetch
|
|
from django.http import Http404, HttpResponse
|
|
from django.shortcuts import aget_object_or_404
|
|
from ninja import Router
|
|
from ninja.errors import HttpError
|
|
from ninja.pagination import paginate
|
|
|
|
from apps.organizations_ext.constants import OrganizationUserRole
|
|
from apps.organizations_ext.models import Organization, OrganizationUser
|
|
from apps.projects.models import Project
|
|
from apps.shared.types import MeID
|
|
from glitchtip.api.authentication import AuthHttpRequest
|
|
from glitchtip.api.permissions import has_permission
|
|
|
|
from .models import Team
|
|
from .schema import ProjectTeamSchema, TeamIn, TeamProjectSchema, TeamSchema
|
|
|
|
router = Router()
|
|
|
|
|
|
"""
|
|
OSS Sentry supported
|
|
GET /teams/{org}/{team}/
|
|
PUT /teams/{org}/{team}/
|
|
DELETE /teams/{org}/{team}/
|
|
GET /teams/{org}/{team}/members/ (See organizations)
|
|
GET /teams/{org}/{team}/projects/ (See projects)
|
|
GET /teams/{org}/{team}/stats/ (Not implemented)
|
|
GET /organizations/{org}/teams/
|
|
POST /organizations/{org}/teams/
|
|
POST /organizations/{org}/members/{me|member_id}/teams/{team}/ (join)
|
|
DELETE /organizations/{org}/members/{me|member_id}/teams/{team}/ (leave)
|
|
GET /api/0/projects/{organization_slug}/{project_slug}/teams/ (Not documented)
|
|
POST /api/0/projects/{organization_slug}/{project_slug}/teams/{team_slug}/
|
|
DELETE /api/0/projects/{organization_slug}/{project_slug}/teams/{team_slug}/
|
|
"""
|
|
|
|
|
|
def get_team_queryset(
|
|
organization_slug: str,
|
|
team_slug: Optional[str] = None,
|
|
project_slug: Optional[str] = None,
|
|
user_id: Optional[int] = None,
|
|
id: Optional[int] = None,
|
|
add_details=False,
|
|
add_projects=False,
|
|
):
|
|
qs = Team.objects.filter(organization__slug=organization_slug)
|
|
if team_slug:
|
|
qs = qs.filter(slug=team_slug)
|
|
if project_slug:
|
|
qs = qs.filter(projects__slug=project_slug)
|
|
if id:
|
|
qs = qs.filter(id=id)
|
|
if user_id:
|
|
qs = qs.filter(organization__users=user_id)
|
|
if add_details:
|
|
qs = qs.annotate(
|
|
is_member=Exists(
|
|
OrganizationUser.objects.filter(
|
|
teams=OuterRef("pk"), user_id=user_id
|
|
)
|
|
),
|
|
member_count=Count("members"),
|
|
)
|
|
if add_projects:
|
|
qs = qs.prefetch_related(
|
|
Prefetch(
|
|
"projects",
|
|
queryset=Project.objects.annotate(
|
|
is_member=Exists(
|
|
OrganizationUser.objects.filter(
|
|
teams__members=OuterRef("pk"), user_id=user_id
|
|
)
|
|
),
|
|
),
|
|
)
|
|
)
|
|
return qs
|
|
|
|
|
|
@router.get(
|
|
"teams/{slug:organization_slug}/{slug:team_slug}/",
|
|
response=TeamProjectSchema,
|
|
by_alias=True,
|
|
)
|
|
@has_permission(["team:read", "team:write", "team:admin"])
|
|
async def get_team(request: AuthHttpRequest, organization_slug: str, team_slug: str):
|
|
user_id = request.auth.user_id
|
|
return await aget_object_or_404(
|
|
get_team_queryset(
|
|
organization_slug,
|
|
user_id=user_id,
|
|
team_slug=team_slug,
|
|
add_details=True,
|
|
add_projects=True,
|
|
)
|
|
)
|
|
|
|
|
|
@router.put(
|
|
"teams/{slug:organization_slug}/{slug:team_slug}/",
|
|
response=TeamProjectSchema,
|
|
by_alias=True,
|
|
)
|
|
@has_permission(["team:write", "team:admin"])
|
|
async def update_team(
|
|
request: AuthHttpRequest, organization_slug: str, team_slug: str, payload: TeamIn
|
|
):
|
|
user_id = request.auth.user_id
|
|
team = await aget_object_or_404(
|
|
get_team_queryset(
|
|
organization_slug,
|
|
user_id=user_id,
|
|
team_slug=team_slug,
|
|
add_details=True,
|
|
add_projects=True,
|
|
)
|
|
)
|
|
team.slug = payload.slug
|
|
await team.asave()
|
|
return team
|
|
|
|
|
|
@router.delete("teams/{slug:organization_slug}/{slug:team_slug}/", response={204: None})
|
|
@has_permission(["team:admin"])
|
|
async def delete_team(request: AuthHttpRequest, organization_slug: str, team_slug: str):
|
|
result, _ = (
|
|
await get_team_queryset(
|
|
organization_slug, team_slug=team_slug, user_id=request.auth.user_id
|
|
)
|
|
.filter(
|
|
organization__organization_users__role__gte=OrganizationUserRole.ADMIN,
|
|
)
|
|
.adelete()
|
|
)
|
|
if not result:
|
|
raise Http404
|
|
return 204, None
|
|
|
|
|
|
@router.get(
|
|
"/organizations/{slug:organization_slug}/teams/",
|
|
response=list[TeamProjectSchema],
|
|
by_alias=True,
|
|
)
|
|
@paginate
|
|
@has_permission(
|
|
["team:read", "team:write", "team:admin", "org:read", "org:write", "org:admin"]
|
|
)
|
|
async def list_teams(
|
|
request: AuthHttpRequest, response: HttpResponse, organization_slug: str
|
|
):
|
|
return get_team_queryset(
|
|
organization_slug,
|
|
user_id=request.auth.user_id,
|
|
add_details=True,
|
|
add_projects=True,
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/organizations/{slug:organization_slug}/teams/",
|
|
response={201: TeamProjectSchema},
|
|
by_alias=True,
|
|
)
|
|
@has_permission(["team:write", "team:admin", "org:admin", "org:write"])
|
|
async def create_team(
|
|
request: AuthHttpRequest, organization_slug: str, payload: TeamIn
|
|
):
|
|
user_id = request.auth.user_id
|
|
organization = await aget_object_or_404(
|
|
Organization,
|
|
slug=organization_slug,
|
|
users=user_id,
|
|
organization_users__role__gte=OrganizationUserRole.ADMIN,
|
|
)
|
|
team = await Team.objects.acreate(organization=organization, slug=payload.slug)
|
|
org_user = await organization.organization_users.filter(user=user_id).afirst()
|
|
await team.members.aadd(org_user)
|
|
return await get_team_queryset(
|
|
organization_slug,
|
|
user_id=user_id,
|
|
id=team.id,
|
|
add_details=True,
|
|
add_projects=True,
|
|
).aget()
|
|
|
|
|
|
async def modify_member_for_team(
|
|
organization_slug: str,
|
|
member_id: MeID,
|
|
team_slug: str,
|
|
user_id: int,
|
|
add_member=True,
|
|
):
|
|
team = await aget_object_or_404(
|
|
get_team_queryset(
|
|
organization_slug,
|
|
user_id=user_id,
|
|
team_slug=team_slug,
|
|
add_details=True,
|
|
add_projects=True,
|
|
)
|
|
)
|
|
org_user_qs = OrganizationUser.objects.filter(
|
|
organization__slug=organization_slug
|
|
).select_related("organization")
|
|
if member_id == "me":
|
|
org_user = await org_user_qs.aget(user_id=user_id)
|
|
else:
|
|
org_user = await aget_object_or_404(org_user_qs, id=member_id)
|
|
|
|
open_membership = org_user.organization.open_membership
|
|
is_self = org_user.user_id == user_id
|
|
|
|
if not (open_membership and is_self):
|
|
in_team = await team.members.filter(user_id=user_id).aexists()
|
|
if in_team:
|
|
required_role = OrganizationUserRole.ADMIN
|
|
else:
|
|
required_role = OrganizationUserRole.MANAGER
|
|
|
|
if not await OrganizationUser.objects.filter(
|
|
user_id=user_id, organization=org_user.organization, role__gte=required_role
|
|
).aexists():
|
|
raise HttpError(403, "Must be admin to modify teams")
|
|
|
|
if add_member:
|
|
await team.members.aadd(org_user)
|
|
team.is_member = True
|
|
else:
|
|
await team.members.aremove(org_user)
|
|
return team
|
|
|
|
|
|
@router.post(
|
|
"/organizations/{slug:organization_slug}/members/{slug:member_id}/teams/{slug:team_slug}/",
|
|
response={201: TeamProjectSchema},
|
|
by_alias=True,
|
|
)
|
|
@has_permission(["team:write", "team:admin"])
|
|
async def add_member_to_team(
|
|
request: AuthHttpRequest, organization_slug: str, member_id: MeID, team_slug: str
|
|
):
|
|
return 201, await modify_member_for_team(
|
|
organization_slug, member_id, team_slug, request.auth.user_id, True
|
|
)
|
|
|
|
|
|
@router.delete(
|
|
"/organizations/{slug:organization_slug}/members/{slug:member_id}/teams/{slug:team_slug}/",
|
|
response=TeamProjectSchema,
|
|
by_alias=True,
|
|
)
|
|
@has_permission(["team:write", "team:admin"])
|
|
async def delete_member_from_team(
|
|
request: AuthHttpRequest, organization_slug: str, member_id: MeID, team_slug: str
|
|
):
|
|
return await modify_member_for_team(
|
|
organization_slug, member_id, team_slug, request.auth.user_id, False
|
|
)
|
|
|
|
|
|
@router.get(
|
|
"/projects/{slug:organization_slug}/{slug:project_slug}/teams/",
|
|
response=list[TeamSchema],
|
|
by_alias=True,
|
|
)
|
|
@paginate
|
|
@has_permission(
|
|
["team:read", "team:write", "team:admin", "org:read", "org:write", "org:admin"]
|
|
)
|
|
async def list_project_teams(
|
|
request: AuthHttpRequest,
|
|
response: HttpResponse,
|
|
organization_slug: str,
|
|
project_slug: str,
|
|
):
|
|
return get_team_queryset(
|
|
organization_slug,
|
|
user_id=request.auth.user_id,
|
|
project_slug=project_slug,
|
|
add_details=True,
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/projects/{slug:organization_slug}/{slug:project_slug}/teams/{slug:team_slug}/",
|
|
response={201: ProjectTeamSchema},
|
|
by_alias=True,
|
|
)
|
|
@has_permission(["project.write", "project:admin"])
|
|
async def add_team_to_project(
|
|
request: AuthHttpRequest, organization_slug: str, project_slug: str, team_slug: str
|
|
):
|
|
"""Add team to project"""
|
|
user_id = request.auth.user_id
|
|
project = await aget_object_or_404(
|
|
Project,
|
|
slug=project_slug,
|
|
organization__slug=organization_slug,
|
|
organization__users=user_id,
|
|
organization__organization_users__role__gte=OrganizationUserRole.MANAGER,
|
|
)
|
|
team = await aget_object_or_404(
|
|
get_team_queryset(organization_slug, team_slug=team_slug)
|
|
)
|
|
await project.teams.aadd(team)
|
|
project = await (
|
|
Project.annotate_is_member(Project.objects, user_id)
|
|
.prefetch_related("teams")
|
|
.aget(id=project.id)
|
|
)
|
|
return 201, project
|
|
|
|
|
|
@router.delete(
|
|
"/projects/{slug:organization_slug}/{slug:project_slug}/teams/{slug:team_slug}/",
|
|
response=ProjectTeamSchema,
|
|
by_alias=True,
|
|
)
|
|
@has_permission(["project.write", "project:admin"])
|
|
async def delete_team_from_project(
|
|
request: AuthHttpRequest, organization_slug: str, project_slug: str, team_slug: str
|
|
):
|
|
"""Remove team from project"""
|
|
user_id = request.auth.user_id
|
|
team = await aget_object_or_404(
|
|
get_team_queryset(
|
|
organization_slug, project_slug=project_slug, team_slug=team_slug
|
|
)
|
|
)
|
|
project = await aget_object_or_404(
|
|
Project,
|
|
slug=project_slug,
|
|
organization__slug=organization_slug,
|
|
organization__users=user_id,
|
|
organization__organization_users__role__gte=OrganizationUserRole.MANAGER,
|
|
)
|
|
await project.teams.aremove(team)
|
|
return await (
|
|
Project.annotate_is_member(Project.objects, user_id)
|
|
.prefetch_related("teams")
|
|
.aget(id=project.id)
|
|
)
|