Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/notify.team.sh
.vscode/
.DS_Store
.env
.env
agent_docs/
73 changes: 62 additions & 11 deletions backend/db/crud_feedback.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
from datetime import datetime
from typing import List
from typing import List, Optional
from sqlalchemy import extract
from sqlalchemy.orm import Session
from models.feedback import Feedback
from models.feedback import FeedbackDict, FeedbackPayload
from models.user import User
from models.organisation import Organisation
from models.organisation_isco import OrganisationIsco


def add_feedback(session: Session, user: int,
payload: FeedbackPayload) -> FeedbackDict:
feedback = Feedback(id=None,
user=user,
title=payload["title"],
category=payload["category"],
content=payload["content"],
created=datetime.now())
def add_feedback(
session: Session, user: int, payload: FeedbackPayload
) -> FeedbackDict:
feedback = Feedback(
id=None,
user=user,
title=payload["title"],
category=payload["category"],
content=payload["content"],
created=datetime.now(),
)
session.add(feedback)
session.commit()
session.flush()
Expand All @@ -24,7 +31,51 @@ def get_feedback(session: Session) -> List[FeedbackDict]:
return session.query(Feedback).all()


def get_feedback_for_export(
session: Session,
isco_type_ids: Optional[List[int]] = None,
monitoring_round: Optional[int] = None,
):
query = (
session.query(
Feedback.created,
Feedback.title,
Feedback.category,
Feedback.content,
User.name.label("user_name"),
Organisation.name.label("organisation_name"),
)
.join(User, Feedback.user == User.id)
.join(Organisation, User.organisation == Organisation.id)
)

if isco_type_ids is not None:
query = query.join(
OrganisationIsco, Organisation.id == OrganisationIsco.organisation
).filter(OrganisationIsco.isco_type.in_(isco_type_ids))

if monitoring_round:
query = query.filter(
extract("year", Feedback.created) == monitoring_round
)

# Execute query
results = query.order_by(Feedback.created.desc()).all()
data = []
for r in results:
data.append({
"monitoring_round": r.created.year if r.created else None,
"user_name": r.user_name,
"organisation_name": r.organisation_name,
"title": r.title,
"category": r.category,
"content": r.content
})
return data


def delete_feedback_by_ids(session: Session, ids: List[int]):
session.query(Feedback).filter(
Feedback.id.in_(ids)).delete(synchronize_session='fetch')
session.query(Feedback).filter(Feedback.id.in_(ids)).delete(
synchronize_session="fetch"
)
session.commit()
72 changes: 70 additions & 2 deletions backend/routes/feedback.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
from fastapi import Depends, Request, APIRouter, HTTPException, BackgroundTasks
from fastapi.security import HTTPBearer
from fastapi.security import HTTPBasicCredentials as credentials
from typing import List
from typing import List, Optional
from sqlalchemy.orm import Session
import db.crud_feedback as crud
from db.connection import get_session
from db import crud_organisation
from models.feedback import FeedbackDict, FeedbackPayload, FeedbackCategory
from middleware import verify_editor, find_secretariat_admins
from middleware import verify_editor, find_secretariat_admins, verify_admin
from util.mailer import Email, MailTypeEnum
from util.sheets import generate_feedback_export
from fastapi.responses import StreamingResponse
from models.user import UserRole
from models.organisation_isco import OrganisationIsco
import os
from datetime import datetime

security = HTTPBearer()
feedback_route = APIRouter()
Expand Down Expand Up @@ -82,3 +88,65 @@ def get_data(req: Request,
credentials: credentials = Depends(security)):
feedbacks = crud.get_feedback(session=session)
return [f.serialize for f in feedbacks]


@feedback_route.get("/feedback/download",
summary="download feedback",
name="feedback:download",
tags=["Feedback"])
def download(req: Request,
isco_type_id: Optional[int] = None,
monitoring_round: Optional[int] = None,
session: Session = Depends(get_session),
credentials: credentials = Depends(security)):
user = verify_admin(session=session,
authenticated=req.state.authenticated)

if user.role != UserRole.secretariat_admin:
raise HTTPException(
status_code=403,
detail="Only secretariat_admin can download feedback")

if isco_type_id is None:
user_org_iscos = session.query(OrganisationIsco).filter(
OrganisationIsco.organisation == user.organisation
).all()
isco_type_ids = [o.isco_type for o in user_org_iscos]
else:
# Check if the requested isco_type_id is respected by the user
exists = session.query(OrganisationIsco).filter(
OrganisationIsco.organisation == user.organisation,
OrganisationIsco.isco_type == isco_type_id
).first()
if not exists:
raise HTTPException(
status_code=403,
detail="Not allowed to access this ISCO's feedback")
isco_type_ids = [isco_type_id]

results = crud.get_feedback_for_export(
session=session,
isco_type_ids=isco_type_ids,
monitoring_round=monitoring_round)

if not results:
raise HTTPException(
status_code=404, detail="No feedback data available")

date_str = datetime.now().strftime("%Y%m%d")
filename = f"feedback_export_{date_str}"
file_path = generate_feedback_export(filename, results)

def iterfile():
with open(file_path, mode="rb") as file_like:
yield from file_like
if os.path.exists(file_path):
os.remove(file_path)

return StreamingResponse(
iterfile(),
media_type="application/vnd.openxmlformats-officedocument"
".spreadsheetml.sheet",
headers={
"Content-Disposition": f"attachment; filename={filename}.xlsx"
})
125 changes: 86 additions & 39 deletions backend/routes/isco_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,68 +10,115 @@
from models.isco_type import IscoTypeDict, IscoTypePayload
from middleware import verify_super_admin

# from models.user import UserRole
from models.organisation_isco import OrganisationIsco
from middleware import verify_admin

security = HTTPBearer()
isco_type_route = APIRouter()


@isco_type_route.post("/isco_type",
response_model=IscoTypeDict,
summary="add new member type",
name="isco_type:create",
tags=["Isco Type"])
def add(req: Request, payload: IscoTypePayload,
session: Session = Depends(get_session),
credentials: credentials = Depends(security)):
@isco_type_route.post(
"/isco_type",
response_model=IscoTypeDict,
summary="add new member type",
name="isco_type:create",
tags=["Isco Type"],
)
def add(
req: Request,
payload: IscoTypePayload,
session: Session = Depends(get_session),
credentials: credentials = Depends(security),
):
verify_super_admin(session=session, authenticated=req.state.authenticated)
isco_type = crud.add_isco_type(session=session, payload=payload)
return isco_type.serialize


@isco_type_route.get("/isco_type/",
response_model=List[IscoTypeDict],
summary="get all member types",
name="isco_type:get_all",
tags=["Isco Type"])
@isco_type_route.get(
"/isco_type/",
response_model=List[IscoTypeDict],
summary="get all member types",
name="isco_type:get_all",
tags=["Isco Type"],
)
def get(req: Request, session: Session = Depends(get_session)):
isco_type = crud.get_isco_type(session=session)
return [mt.serialize for mt in isco_type]


@isco_type_route.get("/isco_type/{id:path}",
response_model=IscoTypeBase,
summary="get member type by id",
name="isco_type:get_by_id",
tags=["Isco Type"])
@isco_type_route.get(
"/isco_type/mine",
response_model=List[IscoTypeDict],
summary="get all isco types for current user",
name="isco_type:get_mine",
tags=["Isco Type"],
)
def get_mine(
req: Request,
session: Session = Depends(get_session),
credentials: credentials = Depends(security),
):
user = verify_admin(session=session, authenticated=req.state.authenticated)
# if user.role == UserRole.secretariat_admin:
# isco_type = crud.get_isco_type(session=session)
# return [mt.serialize for mt in isco_type]
# Filter by user organisation
res = (
session.query(OrganisationIsco)
.filter(OrganisationIsco.organisation == user.organisation)
.all()
)
# Return as IscoTypeDict
return [{"id": r.isco_type, "name": r.isco.name} for r in res]


@isco_type_route.get(
"/isco_type/{id:path}",
response_model=IscoTypeBase,
summary="get member type by id",
name="isco_type:get_by_id",
tags=["Isco Type"],
)
def get_by_id(req: Request, id: int, session: Session = Depends(get_session)):
isco_type = crud.get_isco_type_by_id(session=session, id=id)
return isco_type.serialize


@isco_type_route.put("/isco_type/{id:path}",
response_model=IscoTypeDict,
summary="update member type",
name="isco_type:put",
tags=["Isco Type"])
def update(req: Request, id: int, payload: IscoTypePayload,
session: Session = Depends(get_session),
credentials: credentials = Depends(security)):
@isco_type_route.put(
"/isco_type/{id:path}",
response_model=IscoTypeDict,
summary="update member type",
name="isco_type:put",
tags=["Isco Type"],
)
def update(
req: Request,
id: int,
payload: IscoTypePayload,
session: Session = Depends(get_session),
credentials: credentials = Depends(security),
):
verify_super_admin(session=session, authenticated=req.state.authenticated)
isco_type = crud.update_isco_type(session=session,
id=id,
payload=payload)
isco_type = crud.update_isco_type(session=session, id=id, payload=payload)
return isco_type.serialize


@isco_type_route.delete("/isco_type/{id:path}",
responses={204: {
"model": None
}},
status_code=HTTPStatus.NO_CONTENT,
summary="delete member type by id",
name="isco_type:delete",
tags=["Isco Type"])
def delete(req: Request, id: int, session: Session = Depends(get_session),
credentials: credentials = Depends(security)):
@isco_type_route.delete(
"/isco_type/{id:path}",
responses={204: {"model": None}},
status_code=HTTPStatus.NO_CONTENT,
summary="delete member type by id",
name="isco_type:delete",
tags=["Isco Type"],
)
def delete(
req: Request,
id: int,
session: Session = Depends(get_session),
credentials: credentials = Depends(security),
):
verify_super_admin(session=session, authenticated=req.state.authenticated)
crud.delete_isco_type(session=session, id=id)
return Response(status_code=HTTPStatus.NO_CONTENT.value)
Loading