Skip to content

Commit

Permalink
storagefiles: add a storagefile support in the db (#13)
Browse files Browse the repository at this point in the history
* storagefiles: add a storagefile support in the db

* storagefile: remove the need for extra base64str validation, move the ReturnType def to otypes

* Fix variable error in queries

* New document creation check should be case insensitive

* Fix create and edit function object creation error. Removed extra class for inputs of storage, and remove extra check for base64 string

* Chnage from Base64Str to Base64Bytes

* storagefile: otypes: using underscore for consistency

* storagefiles: query: add optional argument filetype

* storagefiles: switch back from base64 to using str

* Minor fix

* Pass inter communication secret to signed-url API

* Remove unwanted query and fix the storageFiles query

* re-add single storagefile API

* storagefiles: fix conflicts when rebasing master

* storagefiles: fix conflicts when rebasing master

* Static file delete, change variable names

---------

Co-authored-by: notpua <[email protected]>
Co-authored-by: Bhav Beri <[email protected]>
Co-authored-by: Bhav Beri <[email protected]>
  • Loading branch information
4 people authored Dec 9, 2024
1 parent 28dd13a commit 3d311e0
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 9 deletions.
1 change: 1 addition & 0 deletions db.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
# get database
db = client[MONGO_DATABASE]
ccdb = db.cc
docsstoragedb = db.docsstorage
26 changes: 25 additions & 1 deletion models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@

import strawberry
from bson import ObjectId
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator
from pydantic import (
BaseModel,
ConfigDict,
EmailStr,
Field,
TypeAdapter,
field_validator,
)
from pydantic_core import core_schema
from pytz import timezone

Expand Down Expand Up @@ -107,3 +114,20 @@ class CCRecruitment(BaseModel):
str_strip_whitespace=True,
validate_assignment=True,
)


class StorageFile(BaseModel):
id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
title: str
filename: str
filetype: str = "pdf"
modified_time: str = ""
creation_time: str = ""

model_config = ConfigDict(
populate_by_name=True,
arbitrary_types_allowed=True,
extra="forbid",
str_strip_whitespace=True,
validate_assignment=True,
)
124 changes: 121 additions & 3 deletions mutations.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
import os
import re
from datetime import datetime

import pytz
import strawberry
from fastapi.encoders import jsonable_encoder

from db import ccdb
from db import ccdb, docsstoragedb
from mailing import send_mail
from mailing_templates import (
APPLICANT_CONFIRMATION_BODY,
APPLICANT_CONFIRMATION_SUBJECT,
CC_APPLICANT_CONFIRMATION_BODY,
CC_APPLICANT_CONFIRMATION_SUBJECT,
)
from models import CCRecruitment
from models import CCRecruitment, StorageFile

# import all models and types
from otypes import CCRecruitmentInput, Info, MailInput
from otypes import (
CCRecruitmentInput,
Info,
MailInput,
StorageFileInput,
StorageFileType,
)
from utils import delete_file

inter_communication_secret_global = os.getenv("INTER_COMMUNICATION_SECRET")
ist = pytz.timezone("Asia/Kolkata")


# sample mutation
Expand Down Expand Up @@ -119,8 +130,115 @@ def ccApply(ccRecruitmentInput: CCRecruitmentInput, info: Info) -> bool:
return True


# StorageFile related mutations
@strawberry.mutation
def createStorageFile(
details: StorageFileInput, info: Info
) -> StorageFileType:
"""
Create a new storagefile
returns the created storagefile
Allowed Roles: ["cc"]
"""
user = info.context.user

if user is None or user.get("role") != "cc":
raise ValueError("You do not have permission to access this resource.")

# get time info
current_time = datetime.now(ist)
time_str = current_time.strftime("%d-%m-%Y %I:%M %p IST")

storagefile = StorageFile(
title=details.title,
filename=details.filename,
filetype=details.filetype,
modified_time=time_str,
creation_time=time_str,
)

# Check if any storagefile with same title already exists
if docsstoragedb.find_one(
{"title": {"$regex": f"^{re.escape(details.title)}$", "$options": "i"}}
):
raise ValueError("A storagefile already exists with this name.")

created_id = docsstoragedb.insert_one(
jsonable_encoder(storagefile)
).inserted_id
created_storagefile = docsstoragedb.find_one({"_id": created_id})

return StorageFileType.from_pydantic(
StorageFile.model_validate(created_storagefile)
)


@strawberry.mutation
def updateStorageFile(id: str, info: Info) -> bool:
"""
Update an existing storagefile
returns the updated storagefile
Allowed Roles: ["cc"]
"""
user = info.context.user

if user is None or user.get("role") != "cc":
raise ValueError("You do not have permission to access this resource.")

# get time info
current_time = datetime.now(ist)
time_str = current_time.strftime("%d-%m-%Y %I:%M %p IST")

storagefile = docsstoragedb.find_one({"_id": id})
if storagefile is None:
raise ValueError("StorageFile not found.")

updated_storagefile = StorageFile(
_id=id,
title=storagefile["title"],
filename=storagefile["filename"],
filetype=storagefile["filetype"],
modified_time=time_str,
creation_time=storagefile["creation_time"],
)

docsstoragedb.find_one_and_update(
{"_id": id}, {"$set": jsonable_encoder(updated_storagefile)}
)
return True


@strawberry.mutation
def deleteStorageFile(id: str, info: Info) -> bool:
"""
Delete an existing storagefile
returns a boolean indicating success
Allowed Roles: ["cc"]
"""
user = info.context.user

if user is None or user.get("role") != "cc":
raise ValueError("You do not have permission to access this resource.")

storagefile = docsstoragedb.find_one({"_id": id})
if storagefile is None:
raise ValueError("StorageFile not found.")

# delete the file from storage
delete_file(storagefile["filename"])

docsstoragedb.delete_one({"_id": id})
return True


# register all mutations
mutations = [
sendMail,
ccApply,
createStorageFile,
updateStorageFile,
deleteStorageFile,
]
21 changes: 20 additions & 1 deletion otypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from strawberry.types import Info as _Info
from strawberry.types.info import RootValueType

from models import CCRecruitment, Mails, PyObjectId
from models import CCRecruitment, Mails, PyObjectId, StorageFile


# custom context class
Expand Down Expand Up @@ -80,3 +80,22 @@ class CCRecruitmentType:
@strawberry.type
class SignedURL:
url: str


@strawberry.input
class SignedURLInput:
static_file: bool = False
filename: str | None = None


# StorageFile Types
@strawberry.experimental.pydantic.input(
model=StorageFile, fields=["title", "filename", "filetype"]
)
class StorageFileInput:
pass


@strawberry.experimental.pydantic.type(model=StorageFile, all_fields=True)
class StorageFileType:
pass
46 changes: 42 additions & 4 deletions queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@
import requests
import strawberry

from db import ccdb
from models import CCRecruitment
from db import ccdb, docsstoragedb
from models import CCRecruitment, StorageFile

# import all models and types
from otypes import CCRecruitmentType, Info, SignedURL
from otypes import (
CCRecruitmentType,
Info,
SignedURL,
SignedURLInput,
StorageFileType,
)

inter_communication_secret = os.getenv("INTER_COMMUNICATION_SECRET")


# fetch signed url from the files service
@strawberry.field
def signedUploadURL(info: Info) -> SignedURL:
def signedUploadURL(details: SignedURLInput, info: Info) -> SignedURL:
user = info.context.user
if not user:
raise Exception("Not logged in!")
Expand All @@ -26,6 +32,8 @@ def signedUploadURL(info: Info) -> SignedURL:
"http://files/signed-url",
params={
"user": json.dumps(user),
"static_file": "true" if details.static_file else "false",
"filename": details.filename,
"inter_communication_secret": inter_communication_secret,
},
)
Expand Down Expand Up @@ -70,9 +78,39 @@ def haveAppliedForCC(info: Info) -> bool:
return False


# Storagefile queries


@strawberry.field
def storagefiles(filetype: str) -> List[StorageFileType]:
"""
Get all storage files
Returns a list of storage files with basic info (id and title)
"""
storage_files = docsstoragedb.find({"filetype": filetype})
return [
StorageFileType.from_pydantic(StorageFile.model_validate(storage_file))
for storage_file in storage_files
]


@strawberry.field
def storagefile(file_id: str) -> StorageFileType:
"""
Get a single storage file by id
Returns a single storage file with all info
"""
storage_file = docsstoragedb.find_one({"_id": file_id})
return StorageFileType.from_pydantic(
StorageFile.model_validate(storage_file)
)


# register all queries
queries = [
signedUploadURL,
ccApplications,
haveAppliedForCC,
storagefiles,
storagefile,
]
20 changes: 20 additions & 0 deletions utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import os

import requests

inter_communication_secret = os.getenv("INTER_COMMUNICATION_SECRET")

def delete_file(filename):
response = requests.post(
"http://files/delete-file",
params={
"filename": filename,
"inter_communication_secret": inter_communication_secret,
"static_file": "true",
},
)

if response.status_code != 200:
raise Exception(response.text)

return response.text

0 comments on commit 3d311e0

Please sign in to comment.