Skip to content

Commit

Permalink
* Rebase against develop
Browse files Browse the repository at this point in the history
* Teams csv download.
* Projects csv download.
* Teams join date.
  • Loading branch information
prabinoid committed Jan 20, 2025
1 parent 6fcc416 commit 618eae8
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 16 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONFAULTHANDLER=1 \
PATH="/home/appuser/.local/bin:$PATH" \
PYTHONPATH="/usr/src/app:$PYTHONPATH" \
PYTHON_LIB="/home/appuser/.local/lib/python$PYTHON_IMG_TAG/site-packages" \
SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \
REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
Expand Down
95 changes: 93 additions & 2 deletions backend/api/teams/resources.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import csv
import io
from distutils.util import strtobool
from datetime import datetime

from databases import Database
from fastapi import APIRouter, Body, Depends, Request
from fastapi import APIRouter, Body, Depends, Request, Response, Query
from fastapi.responses import JSONResponse
from loguru import logger

Expand Down Expand Up @@ -118,7 +121,7 @@ async def patch(
return JSONResponse(content={"Error": str(e)}, status_code=402)


@router.get("/{team_id}/")
@router.get("/{team_id:int}/")
async def retrieve_team(
request: Request,
team_id: int,
Expand Down Expand Up @@ -413,3 +416,91 @@ async def post(
)
except TeamServiceError as e:
return JSONResponse(content={"Error": str(e)}, status_code=400)


@router.get("/join_requests/")
async def get(
request: Request,
team_id: int = Query(..., description="ID of the team to filter by"),
db: Database = Depends(get_db),
user: AuthUserDTO = Depends(login_required),
):
"""
Downloads join requests for a specific team as a CSV.
---
tags:
- teams
produces:
- text/csv
parameters:
- in: query
name: team_id
description: ID of the team to filter by
required: true
type: integer
responses:
200:
description: CSV file with inactive team members
400:
description: Missing or invalid parameters
401:
description: Unauthorized access
500:
description: Internal server error
"""
try:
query = """
SELECT
u.username AS username,
tm.joined_date AS joined_date,
t.name AS team_name
FROM
team_members tm
INNER JOIN
users u ON tm.user_id = u.id
INNER JOIN
teams t ON tm.team_id = t.id
WHERE
tm.team_id = :team_id
AND tm.active = FALSE
"""
team_members = await db.fetch_all(query=query, values={"team_id": int(team_id)})

if not team_members:
return JSONResponse(
content={"message": "No inactive members found for the specified team"},
status_code=200,
)

csv_output = io.StringIO()
writer = csv.writer(csv_output)
writer.writerow(["Username", "Date Joined (UTC)", "Team Name"])

for member in team_members:
joined_date = getattr(member, "joined_date")
joined_date_str = (
joined_date.strftime("%Y-%m-%dT%H:%M:%S") if joined_date else "N/A"
)
writer.writerow(
[
getattr(member, "username"),
joined_date_str,
getattr(member, "team_name"),
]
)

csv_output.seek(0)
return Response(
content=csv_output.getvalue(),
media_type="text/csv",
headers={
"Content-Disposition": (
f"attachment; filename=join_requests_{team_id}_"
f"{datetime.now().strftime('%Y%m%d')}.csv"
)
},
)
except Exception as e:
return JSONResponse(
content={"message": f"Error occurred: {str(e)}"}, status_code=500
)
4 changes: 3 additions & 1 deletion backend/models/dtos/team_dto.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import List, Optional

from datetime import datetime
from fastapi import HTTPException
from pydantic import BaseModel, Field, field_validator

Expand Down Expand Up @@ -68,13 +68,15 @@ class TeamMembersDTO(BaseModel):
default=False, alias="joinRequestNotifications"
)
picture_url: Optional[str] = Field(None, alias="pictureUrl")
joined_date: Optional[datetime] = Field(None, alias="joinedDate")

@field_validator("function")
def validate_function(cls, value):
return validate_team_member_function(value)

class Config:
populate_by_name = True
json_encoders = {datetime: lambda v: v.isoformat() + "Z" if v else None}


class TeamProjectDTO(BaseModel):
Expand Down
22 changes: 11 additions & 11 deletions backend/models/postgis/team.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
from databases import Database
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from sqlalchemy import Column, Integer, BigInteger, Boolean, ForeignKey, String, DateTime
from sqlalchemy import (
Column,
Integer,
BigInteger,
Boolean,
Column,
ForeignKey,
Integer,
String,
DateTime,
)
from sqlalchemy import (
insert,
select,
)
from sqlalchemy.orm import backref, relationship

Expand All @@ -32,8 +32,6 @@
)
from backend.models.postgis.user import User
from backend.models.postgis.utils import timestamp
from backend.db import Base, get_session
from sqlalchemy import select


class TeamMembers(Base):
Expand Down Expand Up @@ -64,6 +62,7 @@ async def create(self, db: Database):
function=self.function,
active=self.active,
join_request_notifications=False,
joined_date=timestamp(),
)
)
return team_member
Expand Down Expand Up @@ -138,14 +137,14 @@ async def create(self, db: Database):
)
.returning(Team.__table__.c.id)
)

if team_id and self.members:
members_to_insert = [
{
"team_id": team_id,
"user_id": member.user_id,
"function": member.function,
"active": member.active,
"joined_date": member.joined_date,
"join_request_notifications": member.join_request_notifications,
}
for member in self.members
Expand Down Expand Up @@ -176,7 +175,6 @@ async def create_from_dto(cls, new_team_dto: NewTeamDTO, db: Database):
new_member.active = True
new_member.joined_date = timestamp()
new_member.join_request_notifications = False
new_team.members.append(new_member)

team = await Team.create(new_team, db)
return team
Expand Down Expand Up @@ -301,7 +299,7 @@ async def as_dto_team_member(
if not user:
raise NotFound(sub_code="USER_NOT_FOUND", user_id=user_id)
member_query = """
SELECT function, active, join_request_notifications
SELECT function, active, join_request_notifications, joined_date
FROM team_members WHERE user_id = :user_id AND team_id = :team_id
"""
member = await db.fetch_one(
Expand All @@ -316,6 +314,7 @@ async def as_dto_team_member(
picture_url=user["picture_url"],
active=member["active"],
join_request_notifications=member["join_request_notifications"],
joined_date=member["joined_date"],
)

def as_dto_team_project(project) -> TeamProjectDTO:
Expand All @@ -332,7 +331,7 @@ async def _get_team_members(self, db: Database):

# SQL query to fetch all members of the team, including their username, picture_url, function, and active status
query = """
SELECT u.username, u.picture_url, tm.function, tm.active
SELECT u.username, u.picture_url, tm.function, tm.active, tm.joined_date
FROM team_members tm
JOIN users u ON tm.user_id = u.id
WHERE tm.team_id = :team_id
Expand All @@ -348,6 +347,7 @@ async def _get_team_members(self, db: Database):
"pictureUrl": row["picture_url"],
"function": TeamMemberFunctions(row["function"]).name,
"active": row["active"],
"joined_date": row["joined_date"],
}
for row in rows
]
Expand Down
2 changes: 0 additions & 2 deletions backend/services/project_search_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,6 @@ async def search_projects_as_csv(
"locale": search_dto.preferred_locale or "en",
},
)
row["author"] = row["author_name"] or row["author_username"]

row["project_name"] = result["name"] if result else None

Expand All @@ -296,7 +295,6 @@ async def search_projects_as_csv(
"tasks_mapped",
"tasks_validated",
"total_tasks",
"centroid",
"author_name",
"author_username",
]
Expand Down

0 comments on commit 618eae8

Please sign in to comment.