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.
prabinoid committed Jan 20, 2025
1 parent 6fcc416 commit 618eae8
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
@@ -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
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

@@ -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,
@@ -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

@@ -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):
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

@@ -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):
@@ -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
@@ -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
@@ -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
@@ -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(
@@ -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:
@@ -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
@@ -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
]
2 changes: 0 additions & 2 deletions backend/services/project_search_service.py
Original file line number Diff line number Diff line change
@@ -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

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

0 comments on commit 618eae8

Please sign in to comment.