Skip to content

Commit

Permalink
Merge pull request #27 from Q-Niranjan/develop-0.4
Browse files Browse the repository at this point in the history
Implement Document Upload Functionality for MinIO
  • Loading branch information
shibu-narayanan authored Nov 5, 2024
2 parents cb9d109 + 2ab1362 commit e4aad1e
Show file tree
Hide file tree
Showing 14 changed files with 483 additions and 2 deletions.
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ classifiers = [
dependencies = [
"openg2p-fastapi-common",
"openg2p-fastapi-auth",
"boto3",
"slugify",
]
dynamic = ["version"]

Expand Down
5 changes: 4 additions & 1 deletion src/openg2p_portal_api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@

from .controllers.auth_controller import AuthController
from .controllers.discovery_controller import DiscoveryController
from .controllers.document_file_controller import DocumentFileController
from .controllers.form_controller import FormController
from .controllers.oauth_controller import OAuthController
from .controllers.program_controller import ProgramController
from .models.orm.program_registrant_info_orm import ProgramRegistrantInfoDraftORM
from .services.document_file_service import DocumentFileService
from .services.form_service import FormService
from .services.membership_service import MembershipService
from .services.partner_service import PartnerService
Expand All @@ -28,11 +30,12 @@ def initialize(self, **kwargs):
MembershipService()
ProgramService()
FormService()
DocumentFileService()

DiscoveryController().post_init()
ProgramController().post_init()
FormController().post_init()

DocumentFileController().post_init()
AuthController().post_init()
OAuthController().post_init()

Expand Down
2 changes: 2 additions & 0 deletions src/openg2p_portal_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ class Settings(AuthSettings, Settings):
auth_api_get_program_summary: ApiAuthSettings = ApiAuthSettings(enabled=True)
auth_api_get_application_details: ApiAuthSettings = ApiAuthSettings(enabled=True)
auth_api_get_benefit_details: ApiAuthSettings = ApiAuthSettings(enabled=True)
auth_api_get_document_by_id: ApiAuthSettings = ApiAuthSettings(enabled=True)
auth_api_upload_document: ApiAuthSettings = ApiAuthSettings(enabled=True)
81 changes: 81 additions & 0 deletions src/openg2p_portal_api/controllers/document_file_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from typing import Annotated

from fastapi import APIRouter, Depends, File, UploadFile
from openg2p_fastapi_auth.dependencies import JwtBearerAuth
from openg2p_fastapi_auth.models.credentials import AuthCredentials
from openg2p_fastapi_common.controller import BaseController
from openg2p_fastapi_common.errors.http_exceptions import (
BadRequestError,
UnauthorizedError,
)

from openg2p_portal_api.models.document_file import DocumentFile

from ..config import Settings
from ..services.document_file_service import DocumentFileService

_config = Settings.get_config()


class DocumentFileController(BaseController):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._file_service = DocumentFileService.get_component()

self.router = APIRouter()
self.router.tags += ["document"]
self.router.add_api_route(
"/uploadDocument/{programid}",
self.upload_document,
responses={200: {"model": DocumentFile}},
methods=["POST"],
)

self.router.add_api_route(
"/getDocument/{document_id}",
self.get_document_by_id,
responses={200: {"model": DocumentFile}},
methods=["GET"],
)

@property
def file_service(self):
if not self._file_service:
self._file_service = DocumentFileService.get_component()
return self._file_service

async def upload_document(
self,
programid: int,
auth: Annotated[AuthCredentials, Depends(JwtBearerAuth())],
file_tag: str = None,
file: UploadFile = File(...),
):
if not auth.partner_id:
raise UnauthorizedError(
message="Unauthorized. Partner Not Found in Registry."
)

try:
message = await self.file_service.upload_document(
file=file, programid=programid, file_tag=file_tag
)
return message
except Exception:
raise BadRequestError(message="File upload failed!") from None

async def get_document_by_id(
self,
document_id: int,
auth: Annotated[AuthCredentials, Depends(JwtBearerAuth())],
):
if not auth.partner_id:
raise UnauthorizedError(
message="Unauthorized. Partner Not Found in Registry."
)

try:
document = await self.file_service.get_document_by_id(document_id)
return document
except Exception:
raise BadRequestError(message="Failed to retrieve document by ID") from None
1 change: 1 addition & 0 deletions src/openg2p_portal_api/controllers/form_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ async def submit_form(
program = await self.program_service.get_program_by_id_service(
programid, auth.partner_id
)

if not program.is_portal_form_mapped:
raise BadRequestError(
message="Form submission is not allowed. Portal form is not mapped to this program."
Expand Down
6 changes: 6 additions & 0 deletions src/openg2p_portal_api/exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from openg2p_fastapi_common.errors.http_exceptions import BadRequestError


def handle_exception(e, message_prefix="Error"):
"""Helper function to raise BadRequestError with a formatted message."""
raise BadRequestError(message=f"{message_prefix}: {str(e)}") from None
6 changes: 6 additions & 0 deletions src/openg2p_portal_api/models/document_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from pydantic import BaseModel, ConfigDict


class DocumentFile(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str
29 changes: 29 additions & 0 deletions src/openg2p_portal_api/models/orm/document_file_orm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from openg2p_fastapi_common.models import BaseORMModel
from sqlalchemy import Boolean, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column, relationship


class DocumentFileORM(BaseORMModel):
__tablename__ = "storage_file"

id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String, nullable=False, index=True)
slug: Mapped[str] = mapped_column(String, nullable=True, index=True)
relative_path: Mapped[str] = mapped_column(String, nullable=True)
file_size: Mapped[int] = mapped_column(Integer, nullable=True)
human_file_size: Mapped[str] = mapped_column(String, nullable=True)
checksum: Mapped[str] = mapped_column(String(40), nullable=True, index=True)
filename: Mapped[str] = mapped_column(String, nullable=True)
extension: Mapped[str] = mapped_column(String, nullable=True)
mimetype: Mapped[str] = mapped_column(String, nullable=True)
to_delete: Mapped[bool] = mapped_column(Boolean, default=False)
active: Mapped[bool] = mapped_column(Boolean, default=True)
file_type: Mapped[str] = mapped_column(String, nullable=True)

backend_id: Mapped[int] = mapped_column(
ForeignKey("storage_backend.id"), nullable=False, index=True
)
company_id: Mapped[int] = mapped_column(ForeignKey("res_partner.id"), nullable=True)

backend = relationship("DocumentStoreORM", back_populates="documents")
partner = relationship("PartnerORM", back_populates="documents")
20 changes: 20 additions & 0 deletions src/openg2p_portal_api/models/orm/document_store_orm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import Dict, List

from openg2p_fastapi_common.models import BaseORMModel
from sqlalchemy import JSON, Boolean, Integer, String
from sqlalchemy.orm import Mapped, mapped_column, relationship

from openg2p_portal_api.models.orm.document_file_orm import DocumentFileORM


class DocumentStoreORM(BaseORMModel):
__tablename__ = "storage_backend"

id: Mapped[int] = mapped_column(Integer, primary_key=True)
name: Mapped[str] = mapped_column(String, nullable=False, index=True)
server_env_defaults: Mapped[Dict[str, str]] = mapped_column(JSON, nullable=False)
is_public: Mapped[bool] = mapped_column(Boolean, default=False)

documents: Mapped[List["DocumentFileORM"]] = relationship(
"DocumentFileORM", back_populates="backend"
)
10 changes: 10 additions & 0 deletions src/openg2p_portal_api/models/orm/document_tag_orm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from openg2p_fastapi_common.models import BaseORMModel
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column


class DocumentTagORM(BaseORMModel):
__tablename__ = "g2p_document_tag"

id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String, nullable=False, index=True)
6 changes: 6 additions & 0 deletions src/openg2p_portal_api/models/orm/partner_orm.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from sqlalchemy.ext.asyncio import async_sessionmaker
from sqlalchemy.orm import Mapped, mapped_column, relationship

from openg2p_portal_api.models.orm.document_file_orm import DocumentFileORM

from .reg_id_orm import RegIDORM


Expand All @@ -41,6 +43,10 @@ class PartnerORM(BaseORMModelWithId):
is_registrant: Mapped[bool] = mapped_column(Boolean(), default=True)
is_group: Mapped[bool] = mapped_column(Boolean(), default=False)

documents: Mapped[List["DocumentFileORM"]] = relationship(
"DocumentFileORM", back_populates="partner"
)

@classmethod
async def get_partner_data(cls, id: int):
async_session_maker = async_sessionmaker(dbengine.get())
Expand Down
14 changes: 13 additions & 1 deletion src/openg2p_portal_api/models/orm/program_orm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@

from openg2p_fastapi_common.context import dbengine
from openg2p_fastapi_common.models import BaseORMModelWithId
from sqlalchemy import DateTime, ForeignKey, String, and_, desc, func, or_, select
from sqlalchemy import (
DateTime,
ForeignKey,
Integer,
String,
and_,
desc,
func,
or_,
select,
)
from sqlalchemy.ext.asyncio import async_sessionmaker
from sqlalchemy.orm import Mapped, mapped_column, relationship, selectinload

Expand All @@ -26,6 +36,8 @@ class ProgramORM(BaseORMModelWithId):
is_reimbursement_program: Mapped[bool] = mapped_column()
active: Mapped[bool] = mapped_column()
create_date: Mapped[datetime] = mapped_column(DateTime())
company_id: Mapped[int] = mapped_column(Integer)
supporting_documents_store: Mapped[int] = mapped_column(Integer)
membership: Mapped[Optional[List["ProgramMembershipORM"]]] = relationship(
back_populates="program"
)
Expand Down
Loading

0 comments on commit e4aad1e

Please sign in to comment.