Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backend project #36

Merged
merged 36 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d4507f8
adds project schemes,routes,services into backend (not complete)
DRIESASTER Mar 4, 2024
a0181e2
router + services, added create delete, get not yet update
DRIESASTER Mar 4, 2024
eaac905
cleanup + all projects for certain subject overview added
DRIESASTER Mar 4, 2024
448fe6d
project update
DRIESASTER Mar 4, 2024
53fd83f
Merge remote-tracking branch 'origin/main' into backend_project
DRIESASTER Mar 5, 2024
e68320f
Merge remote-tracking branch 'origin/dev' into backend_project
DRIESASTER Mar 5, 2024
c2ee3ff
exceptions split up, cleanup
DRIESASTER Mar 5, 2024
2ed95d5
actual patches/fixes now that im running on linux
DRIESASTER Mar 5, 2024
d87943a
api voor router gezet
DRIESASTER Mar 10, 2024
4f44ee3
Merge remote-tracking branch 'origin/dev' into backend_project
DRIESASTER Mar 11, 2024
b0635bf
Merge branch 'dev' into backend_project
xerbalind Mar 11, 2024
43520c9
alembic added
DRIESASTER Mar 11, 2024
b4d5047
Merge remote-tracking branch 'origin/backend_project' into backend_pr…
DRIESASTER Mar 11, 2024
2a1ff7c
alembic added
DRIESASTER Mar 11, 2024
ecbfa62
autopep8
DRIESASTER Mar 11, 2024
6ae0215
adds project schemes,routes,services into backend (not complete)
DRIESASTER Mar 4, 2024
edf2606
router + services, added create delete, get not yet update
DRIESASTER Mar 4, 2024
278024e
cleanup + all projects for certain subject overview added
DRIESASTER Mar 4, 2024
56d35db
project update
DRIESASTER Mar 4, 2024
c7df7b9
exceptions split up, cleanup
DRIESASTER Mar 5, 2024
96ce5cc
actual patches/fixes now that im running on linux
DRIESASTER Mar 5, 2024
6a3373e
api voor router gezet
DRIESASTER Mar 10, 2024
c5bd3b5
alembic added
DRIESASTER Mar 11, 2024
6cee923
alembic added
DRIESASTER Mar 11, 2024
7a37a92
autopep8
DRIESASTER Mar 11, 2024
2ac848e
Merge remote-tracking branch 'origin/backend_project' into backend_pr…
DRIESASTER Mar 11, 2024
bb5adea
Merge remote-tracking branch 'origin/dev' into backend_project
DRIESASTER Mar 11, 2024
91e701f
style
DRIESASTER Mar 11, 2024
31bf4b5
alembic import op ignore (style error)
DRIESASTER Mar 11, 2024
d73dfa7
pyright fixes
DRIESASTER Mar 11, 2024
366a7a8
pyright fixes
DRIESASTER Mar 11, 2024
e5b4010
style fixes
DRIESASTER Mar 11, 2024
009962a
style fixes
DRIESASTER Mar 12, 2024
25afa96
linterrrrrrrrrrr
DRIESASTER Mar 12, 2024
9137de1
requirements.txt
xerbalind Mar 12, 2024
74787fe
Fix problems with project
xerbalind Mar 12, 2024
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
2 changes: 2 additions & 0 deletions backend/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
from starlette.middleware.sessions import SessionMiddleware
from src.subject.router import router as subject_router
from src.user.router import router as user_router
from src.project.router import router as project_router

app = FastAPI()

app.add_middleware(SessionMiddleware, secret_key="!secret")

app.include_router(subject_router)
app.include_router(user_router)
app.include_router(project_router)


@app.get("/api")
Expand Down
29 changes: 29 additions & 0 deletions backend/src/project/dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from fastapi import Depends
from sqlalchemy.orm import Session
from src.dependencies import get_db
from src.user.dependencies import get_authenticated_user
from src.user.exceptions import NotAuthorized
from src.user.schemas import User
from src.subject.schemas import Subject
from src.subject.schemas import SubjectList


from . import service


async def retrieve_subjects(
user: User = Depends(get_authenticated_user), db: Session = Depends(get_db)
) -> SubjectList:
teacher_subjects, student_subjects = await service.get_subjects(db, user.uid)
return SubjectList(as_teacher=teacher_subjects, as_student=student_subjects)


async def user_permission_validation(
subject_id: int,
user: User = Depends(get_authenticated_user),
db: Session = Depends(get_db),
):
if not user.is_admin:
teachers = await service.get_teachers(db, subject_id)
if not list(filter(lambda teacher: teacher.id == user.uid, teachers)):
raise NotAuthorized()
23 changes: 23 additions & 0 deletions backend/src/project/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# exceptions.py

from fastapi import HTTPException


def NoProjectsFoundException(subject_id: int):
return HTTPException(status_code=404, detail=f"No projects found for subject {subject_id}")


def UnauthorizedToCreateProjectException():
return HTTPException(status_code=403, detail="User is not authorized to create projects for this subject")


def ProjectNotFoundException():
return HTTPException(status_code=404, detail="Project not found")


def UnauthorizedToDeleteProjectException():
return HTTPException(status_code=403, detail="User is not authorized to delete this project")


def UnauthorizedToUpdateProjectException():
return HTTPException(status_code=403, detail="User is not authorized to update projects for this subject")
17 changes: 17 additions & 0 deletions backend/src/project/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from sqlalchemy import Column, BigInteger, String, Date, ForeignKey
from sqlalchemy.orm import relationship
from src.database import Base


class Project(Base):
__tablename__ = 'project'

id = Column(BigInteger, primary_key=True, autoincrement=True, index=True)
deadline = Column(Date, nullable=False, check_constraint='deadline >= CURRENT_DATE')
name = Column(String, nullable=False)
subjectId = Column(String, ForeignKey(
'Subject.subjectId', ondelete="SET NULL"), nullable=True)
description = Column(String, nullable=True)

# Relationships
teams = relationship("Team", back_populates="project")
85 changes: 85 additions & 0 deletions backend/src/project/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from src.user.models import User
from src.dependencies import get_db
from src.user.dependencies import get_authenticated_user
from .schemas import ProjectCreate, ProjectResponse, ProjectUpdate
from .service import create_project, get_project, delete_project, update_project, get_projects_for_subject
from ..subject.service import is_teacher_of_subject
from . import exceptions

router = APIRouter(
prefix="/subjects/{subject_id}/projects",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hier moet nog /api voor, zodat de endpoints zowel lokaal als op de server hetzelfde zijn

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in orde

tags=["projects"],
responses={404: {"description": "Not found"}},
)


@router.get("/", response_model=list[ProjectResponse])
async def list_projects_for_subject(
subject_id: int,
user: User = Depends(get_authenticated_user),
db: AsyncSession = Depends(get_db)
):
# Optional: You may want to check if the user has access to the subject (e.g., is a teacher or a student of the subject)
projects = await get_projects_for_subject(db, subject_id)
if not projects:
raise NoProjectsFoundException(subject_id)
return projects


@router.post("/", response_model=ProjectResponse)
async def create_project_for_subject(
subject_id: int,
project_in: ProjectCreate,
user: User = Depends(get_authenticated_user),
db: AsyncSession = Depends(get_db)
):
if not await is_teacher_of_subject(db, user.id, subject_id):
raise UnauthorizedToCreateProjectException()

project = await create_project(db=db, project_in=project_in, user_id=user.id)
return project


@router.get("/{project_id}", response_model=ProjectResponse)
async def get_project_for_subject(
project_id: int,
db: AsyncSession = Depends(get_db)
):
project = await get_project(db, project_id)
if not project:
raise ProjectNotFoundException()
return project


@router.delete("/{project_id}")
async def delete_project_for_subject(
subject_id: int,
project_id: int,
user: User = Depends(get_authenticated_user),
db: AsyncSession = Depends(get_db)
):
if not await is_teacher_of_subject(db, user.id, subject_id):
raise UnauthorizedToUpdateProjectException()

await delete_project(db, project_id)
return {"message": "Project deleted successfully"}


@router.patch("/{project_id}", response_model=ProjectResponse)
async def patch_project_for_subject(
subject_id: int,
project_id: int,
project_update: ProjectUpdate,
user: User = Depends(get_authenticated_user),
db: AsyncSession = Depends(get_db)
):
# Check if the user is authorized to update the project
if not await is_teacher_of_subject(db, user.id, subject_id):
raise UnauthorizedToUpdateProjectException()

updated_project = await update_project(db, project_id, project_update)
if not updated_project:
raise ProjectNotFoundException()
return updated_project
35 changes: 35 additions & 0 deletions backend/src/project/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from datetime import date
from typing import Optional

from pydantic import BaseModel, Field, validator


class ProjectCreate(BaseModel):
name: str = Field(..., min_length=1)
deadline: date
subject_id: int
description: str

# Check if deadline is not in the past
@validator('deadline', pre=True, always=True)
def validate_deadline(cls, value):
if value < date.today():
raise ValueError('The deadline cannot be in the past')
return value


class ProjectResponse(BaseModel):
id: int
name: str
deadline: date
subject_id: int
description: str

class Config:
orm_mode = True


class ProjectUpdate(BaseModel):
name: Optional[str] = None
deadline: Optional[date] = None
description: Optional[str] = None
48 changes: 48 additions & 0 deletions backend/src/project/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from sqlalchemy.orm import Session
from . import models, schemas
from .models import Project
from .schemas import ProjectCreate


async def create_project(db: Session, project_in: ProjectCreate, user_id: str) -> Project:
new_project = Project(
name=project_in.name,
deadline=project_in.deadline,
subject_id=project_in.subject_id,
description=project_in.description
)
db.add(new_project)
db.commit()
db.refresh(new_project)
return new_project


async def get_project(db: Session, project_id: int) -> Project:
return db.query(Project).filter(Project.id == project_id).first()


async def get_projects_for_subject(db: Session, subject_id: int) -> list[models.Project]:
projects = (
db.query(models.Project)
.filter(models.Project.subject_id == subject_id)
.all()
)
return projects


async def delete_project(db: Session, project_id: int):
project = db.query(Project).filter(Project.id == project_id).first()
if project:
db.delete(project)
db.commit()


async def update_project(db: Session, project_id: int, project_update: ProjectCreate) -> Project:
project = db.query(Project).filter(Project.id == project_id).first()
if project:
project.name = project_update.name
project.deadline = project_update.deadline
project.description = project_update.description
db.commit()
db.refresh(project)
return project
6 changes: 6 additions & 0 deletions backend/src/subject/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,9 @@ async def delete_subject(db: Session, subject_id: int):
"""Remove a subject"""
db.query(models.Subject).filter_by(id=subject_id).delete()
db.commit()


async def is_teacher_of_subject(db: Session, user_id: str, subject_id: int) -> bool:
"""Check if a user is a teacher of the subject."""
teachers = await get_subject_teachers(db, subject_id)
return any(teacher.uid == user_id for teacher in teachers)
Loading