123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- 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.models import (
- Organization,
- OrganizationUser,
- OrganizationUserRole,
- )
- 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)
- )
|