This commit is contained in:
Aidan Kim 2024-10-29 23:30:02 +00:00 committed by GitHub
commit e85f90c955
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 724 additions and 647 deletions

View File

@ -1,4 +1,6 @@
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from backend.models.user_model import User
from ..services import ResourceService, UserService from ..services import ResourceService, UserService
from ..models.resource_model import Resource from ..models.resource_model import Resource
@ -15,12 +17,40 @@ openapi_tags = {
# TODO: Add security using HTTP Bearer Tokens # TODO: Add security using HTTP Bearer Tokens
# TODO: Enable authorization by passing user uuid to API # TODO: Enable authorization by passing user uuid to API
# TODO: Create custom exceptions # TODO: Create custom exceptions
@api.post("", response_model=Resource, tags=["Resource"])
def create(
uuid: str, resource: Resource, user_svc: UserService = Depends(), resource_svc: ResourceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
return resource_svc.create(subject, resource)
@api.get("", response_model=List[Resource], tags=["Resource"]) @api.get("", response_model=List[Resource], tags=["Resource"])
def get_all( def get_all(
user_id: str, uuid: str, user_svc: UserService = Depends(), resource_svc: ResourceService = Depends()
resource_svc: ResourceService = Depends(),
user_svc: UserService = Depends(),
): ):
subject = user_svc.get_user_by_uuid(user_id) subject = user_svc.get_user_by_uuid(uuid)
return resource_svc.get_resource_by_user(subject) return resource_svc.get_resource_by_user(subject)
@api.get("/{name}", response_model=Resource, tags=["Resource"])
def get_by_name(
name:str, uuid:str, user_svc: UserService = Depends(), resource_svc: ResourceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
return resource_svc.get_resource_by_name(name, subject)
@api.put("", response_model=Resource, tags=["Resource"])
def update(
uuid: str, resource: Resource, user_svc: UserService = Depends(), resource_svc: ResourceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
return resource_svc.update(subject, resource)
@api.delete("", response_model=None, tags=["Resource"])
def delete(
uuid: str, resource: Resource, user_svc: UserService = Depends(), resource_svc: ResourceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
resource_svc.delete(subject, resource)

View File

@ -1,4 +1,6 @@
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from backend.models.user_model import User
from ..services import ServiceService, UserService from ..services import ServiceService, UserService
from ..models.service_model import Service from ..models.service_model import Service
@ -15,12 +17,38 @@ openapi_tags = {
# TODO: Add security using HTTP Bearer Tokens # TODO: Add security using HTTP Bearer Tokens
# TODO: Enable authorization by passing user uuid to API # TODO: Enable authorization by passing user uuid to API
# TODO: Create custom exceptions # TODO: Create custom exceptions
@api.post("", response_model=Service, tags=["Service"])
def create(
uuid: str, service: Service, user_svc: UserService = Depends(), service_svc: ServiceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
return service_svc.create(subject, service)
@api.get("", response_model=List[Service], tags=["Service"]) @api.get("", response_model=List[Service], tags=["Service"])
def get_all( def get_all(
user_id: str, uuid: str, user_svc: UserService = Depends(), service_svc: ServiceService = Depends()
service_svc: ServiceService = Depends(),
user_svc: UserService = Depends(),
): ):
subject = user_svc.get_user_by_uuid(user_id) subject = user_svc.get_user_by_uuid(uuid)
return service_svc.get_service_by_user(subject) return service_svc.get_service_by_user(subject)
@api.get("/{name}", response_model=Service, tags=["Service"])
def get_by_name(
name: str, uuid: str, user_svc: UserService = Depends(), service_svc: ServiceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
return service_svc.get_service_by_name(name, subject)
@api.put("", response_model=Service, tags=["Service"])
def update(
uuid: str, service: Service, user_svc: UserService = Depends(), service_svc: ServiceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
return service_svc.update(subject, service)
@api.delete("", response_model=None, tags=["Service"])
def delete(
uuid: str, service: Service, user_svc: UserService = Depends(), service_svc: ServiceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
service_svc.delete(subject, service)

51
backend/api/tag.py Normal file
View File

@ -0,0 +1,51 @@
from fastapi import APIRouter, Depends
from backend.models.tag_model import Tag
from backend.models.user_model import User
from backend.services.tag import TagService
from ..services import ResourceService, UserService
from ..models.resource_model import Resource
from typing import List
api = APIRouter(prefix="/api/tag")
openapi_tags = {
"name": "Tag",
"description": "Tag CRUD operations.",
}
# TODO: Add security using HTTP Bearer Tokens
# TODO: Enable authorization by passing user uuid to API
# TODO: Create custom exceptions
@api.post("", response_model=Tag, tags=["Tag"])
def create(
subject: User,
tag: Tag,
tag_service: TagService=Depends()
):
return tag_service.create(subject, tag)
@api.get("", response_model=List[Tag], tags=["Tag"])
def get_all(
subject: User,
tag_svc: TagService=Depends()
):
return tag_svc.get_all()
@api.put("", response_model=Tag, tags=["Tag"])
def update(
subject: User,
tag: Tag,
tag_svc: TagService=Depends()
):
return tag_svc.delete(subject, tag)
@api.delete("", response_model=None, tags=["Tag"])
def delete(
subject: User,
tag: Tag,
tag_svc: TagService=Depends()
):
tag_svc.delete(subject, tag)

View File

@ -23,3 +23,4 @@ class ServiceTagEntity(EntityBase):
# relationships # relationships
service: Mapped["ServiceEntity"] = relationship(back_populates="serviceTags") service: Mapped["ServiceEntity"] = relationship(back_populates="serviceTags")
tag: Mapped["TagEntity"] = relationship(back_populates="serviceTags") tag: Mapped["TagEntity"] = relationship(back_populates="serviceTags")

View File

@ -45,6 +45,7 @@ class TagEntity(EntityBase):
return cls( return cls(
id=model.id, id=model.id,
created_at=model.created_at,
content=model.id, content=model.id,
) )
@ -58,8 +59,6 @@ class TagEntity(EntityBase):
return Tag( return Tag(
id=self.id, id=self.id,
create_at=self.created_at,
content=self.content, content=self.content,
) )

View File

@ -2,7 +2,8 @@ from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi.middleware.gzip import GZipMiddleware from fastapi.middleware.gzip import GZipMiddleware
from .api import user, health, service, resource
from .api import user, health, service, resource, tag
description = """ description = """
Welcome to the **COMPASS** RESTful Application Programming Interface. Welcome to the **COMPASS** RESTful Application Programming Interface.
@ -17,12 +18,13 @@ app = FastAPI(
health.openapi_tags, health.openapi_tags,
service.openapi_tags, service.openapi_tags,
resource.openapi_tags, resource.openapi_tags,
tag.openapi_tags
], ],
) )
app.add_middleware(GZipMiddleware) app.add_middleware(GZipMiddleware)
feature_apis = [user, health, service, resource] feature_apis = [user, health, service, resource, tag]
for feature_api in feature_apis: for feature_api in feature_apis:
app.include_router(feature_api.api) app.include_router(feature_api.api)

View File

@ -20,6 +20,8 @@ class UserPermissionException(Exception):
class ServiceNotFoundException(Exception): class ServiceNotFoundException(Exception):
"""Exception for when the service being requested is not in the table.""" """Exception for when the service being requested is not in the table."""
class TagNotFoundException(Exception):
"""Exception for when the tag being requested is not in the table."""
class ProgramNotAssignedException(Exception): class ProgramNotAssignedException(Exception):
"""Exception for when the user does not have correct access for requested services.""" """Exception for when the user does not have correct access for requested services."""

View File

@ -1,12 +1,12 @@
from fastapi import Depends from fastapi import Depends
from ..database import db_session from ..database import db_session
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import select from sqlalchemy import and_, select
from ..models.resource_model import Resource from ..models.resource_model import Resource
from ..entities.resource_entity import ResourceEntity from ..entities.resource_entity import ResourceEntity
from ..models.user_model import User, UserTypeEnum from ..models.user_model import User, UserTypeEnum
from .exceptions import ResourceNotFoundException from .exceptions import ProgramNotAssignedException, ResourceNotFoundException
class ResourceService: class ResourceService:
@ -14,25 +14,40 @@ class ResourceService:
def __init__(self, session: Session = Depends(db_session)): def __init__(self, session: Session = Depends(db_session)):
self._session = session self._session = session
def get_resource_by_user(self, subject: User): def get_resource_by_user(self, subject: User) -> list[Resource]:
"""Resource method getting all of the resources that a user has access to based on role""" """Resource method getting all of the resources that a user has access to based on role"""
if subject.role != UserTypeEnum.VOLUNTEER: if subject.role != UserTypeEnum.VOLUNTEER:
query = select(ResourceEntity) query = select(ResourceEntity)
entities = self._session.scalars(query).all() entities = self._session.scalars(query).all()
return [resource.to_model() for resource in entities] return [resource.to_model() for resource in entities]
else: else:
programs = subject.program programs = subject.program
resources = [] resources = []
for program in programs: for program in programs:
query = select(ResourceEntity).filter(ResourceEntity.program == program) entities = (
entities = self._session.scalars(query).all() self._session.query(ResourceEntity)
.where(ResourceEntity.program == program)
.all()
)
for entity in entities: for entity in entities:
resources.append(entity) resources.append(entity.to_model())
return [resource for resource in resources]
return [resource.to_model() for resource in resources] def get_resource_by_name(self, name: str, subject: User) -> Resource:
"""Get a resource by name."""
query = select(ResourceEntity).where(
and_(
ResourceEntity.name == name, ResourceEntity.program.in_(subject.program)
)
)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise ResourceNotFoundException(
f"Resource with name: {name} does not exist or program has not been assigned."
)
return entity.to_model()
def create(self, user: User, resource: Resource) -> Resource: def create(self, subject: User, resource: Resource) -> Resource:
""" """
Creates a resource based on the input object and adds it to the table if the user has the right permissions. Creates a resource based on the input object and adds it to the table if the user has the right permissions.
@ -43,47 +58,16 @@ class ResourceService:
Returns: Returns:
Resource: Object added to table Resource: Object added to table
""" """
if resource.role != user.role or resource.group != user.group: if subject.role != UserTypeEnum.ADMIN:
raise PermissionError( raise ProgramNotAssignedException(
"User does not have permission to add resources in this role or group." f"User is not {UserTypeEnum.ADMIN}, cannot update service"
) )
resource_entity = ResourceEntity.from_model(resource) resource_entity = ResourceEntity.from_model(resource)
self._session.add(resource_entity) self._session.add(resource_entity)
self._session.commit() self._session.commit()
return resource_entity.to_model() return resource_entity.to_model()
def get_by_id(self, user: User, id: int) -> Resource: def update(self, subject: User, resource: Resource) -> Resource:
"""
Gets a resource based on the resource id that the user has access to
Parameters:
user: a valid User model representing the currently logged in User
id: int, the id of the resource
Returns:
Resource
Raises:
ResourceNotFoundException: If no resource is found with id
"""
resource = (
self._session.query(ResourceEntity)
.filter(
ResourceEntity.id == id,
ResourceEntity.role == user.role,
ResourceEntity.group == user.group,
)
.one_or_none()
)
if resource is None:
raise ResourceNotFoundException(f"No resource found with id: {id}")
return resource.to_model()
def update(self, user: User, resource: ResourceEntity) -> Resource:
""" """
Update the resource if the user has access Update the resource if the user has access
@ -97,24 +81,24 @@ class ResourceService:
Raises: Raises:
ResourceNotFoundException: If no resource is found with the corresponding ID ResourceNotFoundException: If no resource is found with the corresponding ID
""" """
if resource.role != user.role or resource.group != user.group: if subject.role != UserTypeEnum.ADMIN:
raise PermissionError( raise ProgramNotAssignedException(
"User does not have permission to update this resource." f"User is not {UserTypeEnum.ADMIN}, cannot update service"
) )
query = select(ResourceEntity).where(ResourceEntity.id == resource.id)
obj = self._session.get(ResourceEntity, resource.id) if resource.id else None entity = self._session.scalars(query).one_or_none()
if entity is None:
if obj is None:
raise ResourceNotFoundException( raise ResourceNotFoundException(
f"No resource found with matching id: {resource.id}" f"No resource found with matching id: {resource.id}"
) )
entity.name = resource.name
obj.update_from_model(resource) # Assuming an update method exists entity.summary = resource.summary
entity.link = resource.link
entity.program = resource.program
self._session.commit() self._session.commit()
return entity.to_model()
return obj.to_model() def delete(self, subject: User, resource: Resource) -> None:
def delete(self, user: User, id: int) -> None:
""" """
Delete resource based on id that the user has access to Delete resource based on id that the user has access to
@ -125,41 +109,15 @@ class ResourceService:
Raises: Raises:
ResourceNotFoundException: If no resource is found with the corresponding id ResourceNotFoundException: If no resource is found with the corresponding id
""" """
resource = ( if subject.role != UserTypeEnum.ADMIN:
self._session.query(ResourceEntity) raise ProgramNotAssignedException(
.filter( f"User is not {UserTypeEnum.ADMIN}, cannot update service"
ResourceEntity.id == id,
ResourceEntity.role == user.role,
ResourceEntity.group == user.group,
) )
.one_or_none() query = select(ResourceEntity).where(ResourceEntity.id == resource.id)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise ResourceNotFoundException(
f"No resource found with matching id: {resource.id}"
) )
self._session.delete(entity)
if resource is None:
raise ResourceNotFoundException(f"No resource found with matching id: {id}")
self._session.delete(resource)
self._session.commit() self._session.commit()
def get_by_slug(self, user: User, search_string: str) -> list[Resource]:
"""
Get a list of resources given a search string that the user has access to
Parameters:
user: a valid User model representing the currently logged in User
search_string: a string to search resources by
Returns:
list[Resource]: list of resources relating to the string
Raises:
ResourceNotFoundException if no resource is found with the corresponding slug
"""
query = select(ResourceEntity).where(
ResourceEntity.title.ilike(f"%{search_string}%"),
ResourceEntity.role == user.role,
ResourceEntity.group == user.group,
)
entities = self._session.scalars(query).all()
return [entity.to_model() for entity in entities]

View File

@ -19,62 +19,34 @@ class ServiceService:
def __init__(self, session: Session = Depends(db_session)): def __init__(self, session: Session = Depends(db_session)):
self._session = session self._session = session
def get_service_by_program(self, program: ProgramTypeEnum) -> list[Service]: def get_service_by_user(self, subject: User) -> list[Service]:
"""Service method getting services belonging to a particular program.""" """Resource method getting all of the resources that a user has access to based on role"""
query = select(ServiceEntity).filter(ServiceEntity.program == program)
entities = self._session.scalars(query)
return [entity.to_model() for entity in entities]
def get_service_by_id(self, id: int) -> Service:
"""Service method getting services by id."""
query = select(ServiceEntity).filter(ServiceEntity.id == id)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise ServiceNotFoundException(f"Service with id: {id} does not exist")
return entity.to_model()
def get_service_by_name(self, name: str) -> Service:
"""Service method getting services by id."""
query = select(ServiceEntity).filter(ServiceEntity.name == name)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise ServiceNotFoundException(f"Service with name: {name} does not exist")
return entity.to_model()
def get_service_by_user(self, subject: User):
"""Service method getting all of the services that a user has access to based on role"""
if subject.role != UserTypeEnum.VOLUNTEER: if subject.role != UserTypeEnum.VOLUNTEER:
query = select(ServiceEntity) query = select(ServiceEntity)
entities = self._session.scalars(query).all() entities = self._session.scalars(query).all()
return [service.to_model() for service in entities] return [service.to_model() for service in entities]
else: else:
programs = subject.program programs = subject.program
services = [] services = []
for program in programs: for program in programs:
query = select(ServiceEntity).filter(ServiceEntity.program == program) entities = self._session.query(ServiceEntity).where(ServiceEntity.program == program).all()
entities = self._session.scalars(query).all()
for entity in entities: for entity in entities:
services.append(entity) services.append(entity.to_model())
return [service for service in services]
return [service.to_model() for service in services] def get_service_by_name(self, name: str, subject: User) -> Service:
"""Service method getting services by id."""
def get_all(self, subject: User) -> list[Service]: query = select(ServiceEntity).where(
"""Service method retrieving all of the services in the table.""" and_(
if subject.role == UserTypeEnum.VOLUNTEER: ServiceEntity.name == name, ServiceEntity.program.in_(subject.program)
raise ProgramNotAssignedException(
f"User is not {UserTypeEnum.ADMIN} or {UserTypeEnum.VOLUNTEER}, cannot get all"
) )
)
entity = self._session.scalars(query).one_or_none()
query = select(ServiceEntity) if entity is None:
entities = self._session.scalars(query).all() raise ServiceNotFoundException(f"Service with name: {name} does not exist or program has not been assigned")
return [service.to_model() for service in entities] return entity.to_model()
def create(self, subject: User, service: Service) -> Service: def create(self, subject: User, service: Service) -> Service:
"""Creates/adds a service to the table.""" """Creates/adds a service to the table."""
@ -95,33 +67,35 @@ class ServiceService:
f"User is not {UserTypeEnum.ADMIN}, cannot update service" f"User is not {UserTypeEnum.ADMIN}, cannot update service"
) )
service_entity = self._session.get(ServiceEntity, service.id) query = select(ServiceEntity).where(ServiceEntity.id == service.id)
entity = self._session.scalars(query).one_or_none()
if service_entity is None: if entity is None:
raise ServiceNotFoundException( raise ServiceNotFoundException(
"The service you are searching for does not exist." "The service you are searching for does not exist."
) )
service_entity.name = service.name entity.name = service.name
service_entity.status = service.status entity.status = service.status
service_entity.summary = service.summary entity.summary = service.summary
service_entity.requirements = service.requirements entity.requirements = service.requirements
service_entity.program = service.program entity.program = service.program
self._session.commit() self._session.commit()
return service_entity.to_model() return entity.to_model()
def delete(self, subject: User, service: Service) -> None: def delete(self, subject: User, service: Service) -> None:
"""Deletes a service from the table.""" """Deletes a service from the table."""
if subject.role != UserTypeEnum.ADMIN: if subject.role != UserTypeEnum.ADMIN:
raise ProgramNotAssignedException(f"User is not {UserTypeEnum.ADMIN}") raise ProgramNotAssignedException(f"User is not {UserTypeEnum.ADMIN}")
service_entity = self._session.get(ServiceEntity, service.id)
if service_entity is None: query = select(ServiceEntity).where(ServiceEntity.id == service.id)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise ServiceNotFoundException( raise ServiceNotFoundException(
"The service you are searching for does not exist." "The service you are searching for does not exist."
) )
self._session.delete(service_entity) self._session.delete(entity)
self._session.commit() self._session.commit()

View File

@ -1,20 +1,52 @@
from fastapi import Depends from fastapi import Depends
from backend.models.enum_for_models import UserTypeEnum
from backend.models.user_model import User
from backend.services.exceptions import TagNotFoundException
from ..database import db_session from ..database import db_session
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from ..models.tag_model import Tag from ..models.tag_model import Tag
from ..entities.tag_entity import TagEntity from ..entities.tag_entity import TagEntity
from sqlalchemy import select from sqlalchemy import select
# Add in checks for user permission?
class TagService: class TagService:
def __init__(self, session: Session = Depends(db_session)): def __init__(self, session: Session = Depends(db_session)):
self._session = session self._session = session
def all(self) -> list[Tag]: def get_all(self) -> list[Tag]:
"""Returns a list of all Tags""" """Returns a list of all Tags"""
query = select(TagEntity) query = select(TagEntity)
entities = self._session.scalars(query).all() entities = self._session.scalars(query).all()
return [entity.to_model() for entity in entities] return [entity.to_model() for entity in entities]
def create(self, subject: User, tag: Tag) -> Tag:
entity = TagEntity.from_model(tag)
self._session.add(entity)
self._session.commit()
return entity.to_model()
def update(self, subject: User, tag: Tag) -> Tag:
query = select(TagEntity).where(TagEntity.id == tag.id)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise TagNotFoundException(f"Tag with id {tag.id} does not exist")
entity.content = tag.content
self._session.commit()
return entity.to_model()
def delete(self, subject: User, tag: Tag) -> None:
query = select(TagEntity).where(TagEntity.id == tag.id)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise TagNotFoundException(f"Tag with id {tag.id} does not exist")
self._session.delete(entity)
self._session.commit()