From be8ed1e0d6aaa00c336acd59ba97a226f3ab258e Mon Sep 17 00:00:00 2001 From: Samudra Date: Wed, 3 Sep 2025 20:30:09 +0530 Subject: [PATCH 1/3] added the feature for image upload for security and efficiency (w/o migration) --- backend/.gitignore | 72 +++ backend/app/config.py | 17 +- backend/app/groups/routes.py | 56 +- backend/app/groups/service.py | 39 ++ backend/app/services/__init__.py | 0 backend/app/services/image_processor.py | 117 ++++ .../515cbd9044bb484a8a6c15f21a0b96d2.jpeg | Bin 0 -> 40447 bytes .../bb277bd912ac4479bb8f7eb2bfed63a5.jpeg | Bin 0 -> 40447 bytes .../c08ce27a168a467993d65906b7ca609d.jpeg | Bin 0 -> 40447 bytes backend/app/services/schemas.py | 8 + backend/app/services/storage.py | 406 +++++++++++++ backend/app/user/routes.py | 60 +- backend/app/user/service.py | 10 +- backend/requirements.txt | 2 + backend/tests/groups/test_groups_service.py | 175 ++++++ backend/tests/services/__init__.py | 0 .../tests/services/test_image_processor.py | 94 +++ .../tests/services/test_storage_service.py | 537 ++++++++++++++++++ backend/tests/user/test_user_service.py | 88 ++- 19 files changed, 1674 insertions(+), 7 deletions(-) create mode 100644 backend/.gitignore create mode 100644 backend/app/services/__init__.py create mode 100644 backend/app/services/image_processor.py create mode 100644 backend/app/services/quarantine/users/6890d54586de0847249a248a/515cbd9044bb484a8a6c15f21a0b96d2.jpeg create mode 100644 backend/app/services/quarantine/users/6890d54586de0847249a248a/bb277bd912ac4479bb8f7eb2bfed63a5.jpeg create mode 100644 backend/app/services/quarantine/users/6890d54586de0847249a248a/c08ce27a168a467993d65906b7ca609d.jpeg create mode 100644 backend/app/services/schemas.py create mode 100644 backend/app/services/storage.py create mode 100644 backend/tests/services/__init__.py create mode 100644 backend/tests/services/test_image_processor.py create mode 100644 backend/tests/services/test_storage_service.py diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 00000000..4299ee12 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,72 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +firebase-debug.log* +firebase-debug.*.log* + +# Firebase cache +.firebase/ + +# Firebase config + +# Uncomment this if you'd like others to create their own Firebase project. +# For a team working on the same Firebase project(s), it is recommended to leave +# it commented so all members can deploy to the same project(s) in .firebaserc. +# .firebaserc + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# dataconnect generated files +.dataconnect + +# firebase-config files +app/services/firebase \ No newline at end of file diff --git a/backend/app/config.py b/backend/app/config.py index 3ee6f809..cc98e6e2 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -4,10 +4,16 @@ from logging.config import dictConfig from typing import Optional +from pydantic import Field from pydantic_settings import BaseSettings from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request +from PIL import Image, ImageFile + +from dotenv import load_dotenv +load_dotenv() + class Settings(BaseSettings): # Database @@ -20,8 +26,9 @@ class Settings(BaseSettings): access_token_expire_minutes: int = 15 refresh_token_expire_days: int = 30 # Firebase - firebase_project_id: Optional[str] = None - firebase_service_account_path: str = "./firebase-service-account.json" + use_firebase_emulator: bool = Field(default=False, env="USE_FIREBASE_EMULATOR") #type:ignore + firebase_project_id: Optional[str] = Field(default=None, env="FIREBASE_PROJECT_ID") #type:ignore + firebase_service_account_path: str = Field(default="./firebase-service-account.json", env="FIREBASE_SERVICE_ACCOUNT_PATH") #type:ignore # Firebase service account credentials as environment variables firebase_type: Optional[str] = None firebase_private_key_id: Optional[str] = None @@ -32,6 +39,12 @@ class Settings(BaseSettings): firebase_token_uri: Optional[str] = None firebase_auth_provider_x509_cert_url: Optional[str] = None firebase_client_x509_cert_url: Optional[str] = None + #Image validation configs + LOAD_TRUNCATED_IMAGES: bool = False + MAX_IMAGE_PIXELS: int = 50_00_000 + MAX_FILE_SIZE: int = 5 * 1024 * 1024 + SIGNED_URL_EXPIRY_SECONDS: int = Field(default=3600, env="SIGNED_URL_EXPIRY_SECONDS") #type:ignore + CLAMAV_ENABLED: bool = False # App debug: bool = False diff --git a/backend/app/groups/routes.py b/backend/app/groups/routes.py index 4c36707b..364cdffb 100644 --- a/backend/app/groups/routes.py +++ b/backend/app/groups/routes.py @@ -14,8 +14,10 @@ MemberRoleUpdateRequest, RemoveMemberResponse, ) +from app.services.schemas import ImageUploadResponse +from app.services.storage import storage_service from app.groups.service import group_service -from fastapi import APIRouter, Depends, HTTPException, status +from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, BackgroundTasks router = APIRouter(prefix="/groups", tags=["Groups"]) @@ -145,3 +147,55 @@ async def remove_group_member( if not removed: raise HTTPException(status_code=400, detail="Failed to remove member") return RemoveMemberResponse(success=True, message="Member removed successfully") + +@router.post("/{group_id}/image", response_model=ImageUploadResponse) +async def upload_group_image(group_id: str, file: UploadFile = File(...), + background_tasks: BackgroundTasks = BackgroundTasks(), + current_user: dict = Depends(get_current_user)): + + await group_service.ensure_user_in_group(group_id, current_user["_id"]) + + try: + urls = await storage_service.upload_image_workflow(file=file, folder="groups", entity_id=group_id) + + except ValueError as ve: + raise HTTPException(status_code=400, detail=str(ve)) + except Exception: + raise HTTPException(status_code=500, detail="Group image upload failed") + + background_tasks.add_task(group_service.update_group_image_url, group_id, urls.get("full")) + + return ImageUploadResponse( + success=True, + urls=urls, + message="Group image uploaded successfully." + ) + +@router.delete("/{group_id}/image", response_model=DeleteGroupResponse) +async def delete_group_avatar( + group_id: str, + current_user: dict = Depends(get_current_user), + background_tasks: BackgroundTasks = BackgroundTasks() +): + group = await group_service.get_group_by_id(group_id, current_user["_id"]) + if not group: + raise HTTPException(status_code=404, detail="Group not found") + + await group_service.ensure_user_in_group(group_id, current_user["_id"]) + + image_url = group.get("imageUrl") + if not image_url: + raise HTTPException(status_code=404, detail="Group avatar not found") + + try: + file_path = storage_service.extract_path_from_url(image_url) + deleted = await storage_service.delete_image(file_path) + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to delete group avatar") + + if not deleted: + raise HTTPException(status_code=500, detail="Failed to delete group avatar") + + background_tasks.add_task(group_service.update_group_image_url, group_id, None) + + return DeleteGroupResponse(success=True, message="Group image deleted successfully.") diff --git a/backend/app/groups/service.py b/backend/app/groups/service.py index 8e8a4274..fc6f5eca 100644 --- a/backend/app/groups/service.py +++ b/backend/app/groups/service.py @@ -431,6 +431,45 @@ async def remove_member(self, group_id: str, member_id: str, user_id: str) -> bo {"_id": obj_id}, {"$pull": {"members": {"userId": member_id}}} ) return result.modified_count == 1 + + async def ensure_user_in_group(self, group_id: str, user_id: str) -> dict: + """Ensure that the user is a member of the group. Raises HTTPException if not.""" + db = self.get_db() + + try: + obj_id = ObjectId(group_id) + + except errors.InvalidId: + logger.warning(f"Invalid group_id: {group_id}") + raise HTTPException(status_code=400, detail="Invalid group ID format") + except Exception as e: + logger.error(f"Unexpected error converting group_id to ObjectId: {e}") + raise HTTPException(status_code=500, detail="Internal server error") + + group = await db.groups.find_one({ + "_id": obj_id, + "members": {"$elemMatch": {"userId": user_id}} + }) + + if not group: + raise HTTPException(status_code=403, detail="You are not a member of this group") + return group # Optional return if route needs to read group data + + async def update_group_image_url(self, group_id: str, image_url: str) -> bool: + """Update the group's image URL in the database.""" + db = self.get_db() + + try: + obj_id = ObjectId(group_id) + except errors.InvalidId: + logger.warning(f"Invalid group_id: {group_id}") + return False + + result = await db.groups.update_one( + {"_id": obj_id}, + {"$set": {"imageUrl": image_url}} + ) + return result.modified_count == 1 group_service = GroupService() diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/app/services/image_processor.py b/backend/app/services/image_processor.py new file mode 100644 index 00000000..174fcefa --- /dev/null +++ b/backend/app/services/image_processor.py @@ -0,0 +1,117 @@ +import imghdr +from PIL import Image, UnidentifiedImageError, ImageFile +from io import BytesIO +from typing import Dict, Tuple + +from app.config import logger + +# Optional watermark path +WATERMARK_PATH = None # Example: "app/assets/watermark.png" + +# Resize targets (square thumbnail + larger sizes) +RESIZE_CONFIG = { + "thumbnail": (150, 150), + "medium": (300, 300), + "full": (800, 800), +} + +#Defining image file restrictions +ImageFile.LOAD_TRUNCATED_IMAGES = False +Image.MAX_IMAGE_PIXELS = 50_00_000 #50MB in worst case + +def strip_exif(image: Image.Image) -> Image.Image: + """ + Returns a copy of the image with EXIF metadata stripped. + """ + clean_image = Image.new(image.mode, image.size) + clean_image.putdata(list(image.getdata())) + return clean_image + +def validate_magic_bytes(file_content: bytes): + """ + Validates the actual file type of image + """ + fmt = imghdr.what(None, h=file_content) + if fmt not in ["jpeg", "png", "webp"]: + raise ValueError("Invalid or unsupported image type.") + +def add_watermark(image: Image.Image, watermark: Image.Image) -> Image.Image: + """ + Adds watermark (bottom-right). Image and watermark must be RGBA. + """ + image = image.convert("RGBA") + watermark = watermark.convert("RGBA") + + # Resize watermark if larger than image + wm_width = min(watermark.width, int(image.width * 0.3)) + wm_height = int(watermark.height * (wm_width / watermark.width)) + watermark = watermark.resize((wm_width, wm_height), Image.Resampling.LANCZOS) + + # Paste watermark at bottom-right + position = (image.width - wm_width - 10, image.height - wm_height - 10) + image.alpha_composite(watermark, dest=position) + return image + + +def resize_image(image: Image.Image, size: Tuple[int, int]) -> Image.Image: + """ + Resize image while maintaining aspect ratio and padding to square if needed. + """ + image.thumbnail(size, Image.Resampling.LANCZOS) + + # Pad to square if needed (for thumbnails) + if size[0] == size[1]: + padded = Image.new("RGB", size, (255, 255, 255)) + offset = ((size[0] - image.width) // 2, (size[1] - image.height) // 2) + padded.paste(image, offset) + return padded + + return image + + +async def process_image(file_content: bytes) -> Dict[str, bytes]: + """ + Validates, processes, resizes, strips metadata, compresses to WebP, + and optionally watermarks the image. + Returns a dict of resized images in WebP format. + """ + try: + validate_magic_bytes(file_content) + + img = Image.open(BytesIO(file_content)) + img_format = img.format.upper() + + # Validate format + if img_format not in ["JPEG", "PNG", "WEBP"]: + raise ValueError(f"Unsupported image format: {img_format}") + + img = strip_exif(img) + + if WATERMARK_PATH: + watermark = Image.open(WATERMARK_PATH) + else: + watermark = None + + results = {} + + for label, size in RESIZE_CONFIG.items(): + resized = resize_image(img.copy(), size) + + if watermark: + resized = add_watermark(resized, watermark) + + # Save to memory in WebP format + buffer = BytesIO() + resized.save(buffer, format="WEBP", quality=85, method=6) # High quality with compression + buffer.seek(0) + + results[label] = buffer.read() + + return results + + except UnidentifiedImageError: + logger.exception("Uploaded file is not a valid image.") + raise ValueError("Invalid image content.") + except Exception as e: + logger.exception(f"Image processing error: {e}") + raise RuntimeError("Image processing failed.") diff --git a/backend/app/services/quarantine/users/6890d54586de0847249a248a/515cbd9044bb484a8a6c15f21a0b96d2.jpeg b/backend/app/services/quarantine/users/6890d54586de0847249a248a/515cbd9044bb484a8a6c15f21a0b96d2.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..0a19ceb60810076e9d0379e4fffd5f9331e7089e GIT binary patch literal 40447 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 literal 0 HcmV?d00001 diff --git a/backend/app/services/quarantine/users/6890d54586de0847249a248a/bb277bd912ac4479bb8f7eb2bfed63a5.jpeg b/backend/app/services/quarantine/users/6890d54586de0847249a248a/bb277bd912ac4479bb8f7eb2bfed63a5.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..0a19ceb60810076e9d0379e4fffd5f9331e7089e GIT binary patch literal 40447 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 literal 0 HcmV?d00001 diff --git a/backend/app/services/quarantine/users/6890d54586de0847249a248a/c08ce27a168a467993d65906b7ca609d.jpeg b/backend/app/services/quarantine/users/6890d54586de0847249a248a/c08ce27a168a467993d65906b7ca609d.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..0a19ceb60810076e9d0379e4fffd5f9331e7089e GIT binary patch literal 40447 zcmce;bzEG_vM@SWLV(~B+}+(FxVw9BclQK$4G;!*7~CB~umAyqy99T4eM7SMx%=L8 z?(coy`{S*u>DAp;-Cf;X?Y-uC_IVY6EG;f24uF7w1mJ-I@Vo#J0lb2Sc?ARg>J`kZ z*RNl}!Xd-Ky?FzNj);VSjD`O09Tqw!CJsJ1Ar3An9wsIc9T6!7B{dB-HX%I|Jrxr< z6*bjMBoMD(zlMW_LxY1uqr$<&q58jmp1%Q5U?GYjSD+wB0FWpUP$&@3Juf0cLP3BZ zz`q_S2uQHJuVKMdoEOT=^DiO*0ul;LeVzv(KtTW?k)eYA>#ky z{6DclyaYpGCi=|_frbW-4FEv@?fPGhzl4$e!}%WdUrB30!ow^76k<`03lM$-OGflR zU(x}R|BXvb_L3K8M+;qT1Y*;zsVHJ& z7~MrGU9~8fs87a3IS&5-Pbyt1eK9Zr4(l^G?_aaOs#b}03%$q=c77L&qSIx5`s6t!sv^oz#)7Ej+ko!u`uJhFj-KH;j|p7bipa5 zpa%C)^eo9l3ZE%9(^HJsF`%dYPU9tI#kRp_@T-nNM)E@`BMiyfI3o;vuR24ISnDI* zZQI2CmdACG}em%--3rQb*aD(SOyv84P`re_yKSpZJCAFUW9m&=q2A9 z$~Iy~C-sqqWkDV>nSL9$p2z1;;RacE41S#oSoVaTI|W~EKVq+`TQvYNoXDxaUXbr^ zL^pF@>~mn&vWWi3mvz)U?Ek!V9GZJpzkPk8PLJ>7h#5d4hL=XD)fjh~N!aRio*4-) z&amo_)p3f=rtkrMqH+4BIZiaX+Hzqf4*9b*+UmjQvlFZ~AHI)(G--4rFh{t4-5#~^ z|5{_R&#F6_+wP1gIGh=2W$EShY20pXy1+9KTD_A)Pxo|9-&oEtPN7uXp3CTisg&JN zt0+p}HE%!tG19J<`Z>bn?Cw{x*3hL-Df(E2RwBq|Sfb*WF)1R&)a*sM+uEV zUya0FuL+J$!HV3)ItDS~U}Xk$2J{dim?)4gEi1EfkB}jamdQ+9mIA>u0Fl&Zw;XK( zAKILGqSQ{hy7Rr~GIYQ3z*0wNdZDO2M&1yH*{oWIW&YBPC7tcId}zt3o&dq9m_-(6 zVf}cM68RJ`CuM<~HMco8l)$=K)lU2ZPnJ=>4+rzngq0DIdfP0d7W||d;7zH5` zljTLP;gV6=%~H$t8+#N4I+e$MwKtC5r3Cl!vL*F~i*XI)4t*}PE}*V895Rq~t^QRc z>&iGlw-R7ps9`-)E7ev`iA1wrn-Y(+Ik`6N-s`kron&!8l-fdEcAl>~ca(b^lBj-9w?=rpq@?&>VOFy}378O7tHTukf;xH@L{XrMpD@0Q1(zar=@4DY~R0?W% z48`KNv{e{ZFiXhabkovm^N7lavjWMTN5Z47#%MF=>6#C761B=f6I$C6K%kR^!I8K9B>KnsNW@PiL9XW2CE_k#ioBb7=zq+Rile82PFT8JzmQn6p_#vi60U zBHvU)1&IpK$#S9cz=-6av!NjdT?-M?Ua}xd!AL?U1)P<As?&Y9I7zkRbmGY=p16{@ z{ZzVqfkfTe-pJ40&V}0vKVa9iJ@0GJ&>_!nChyB~-61D4v>(6zD9EVt3u8J9d%vWx zxwOo(LUYpe|LO6Kf=;pU@M4tAI=aEBcY6GfN5Y?Nq7eLm`a&S-#_T*NxF zb|68Y7&5!lnI+F&ySh7C%5C6<5NY0Nz3WeOG3<+tXP4eM6_#RRn$xVilv{PNHDZr6 z?V*(=*BU*3e%Tp(-Hh}$blz;q?is+CY+Q2SGgCEE4czaq(DaNZVG}cthBS|6X9XJ7 zis!K4tbVu3H4mM09viIWEsf4=Oq*E37a)joY#{H)C8+^|oC#N3mY5HX<10gpB;Y&G zGt+ep=ml2N_8>ihHq{d{Xq@LUP z=M7yE!VK+uF(>iE?ndznJ6&n>(VZSFD~NkLb>YL7rM4*UCjNBtGB;MB7}&47c}n)K z-K=F45V$7!;hXUcV7X(v>f9t$YH{4Ix@z0Xoc%E8q(V{ERi4gvYse zbRe#M(-G49%Zx3;6L@`oUvi$Zy)c$OBzGapFg%uiTO_Z4xq@@4_wZDkx^`}#<(p~P z&h-die^wETBr$~q>2Tpa|YzREw?EresP^Ik9%gR$hrKqmRrB{sqk`Q z={OdUk$dZ}aO*#|?HjS%J=W7H^MiRuZ49^DHo=4=L_i_u>__XiU-!WNa7JBzfB9t= z*B#yQ*@_H->8P7aZ(pe~>Db;Gh1Y#d=MMyzJ!h!OFGMvjvn>FH6@n%JIt~Cp*8G^j z2AiU$>T*%vpZTp|+iK#-+&=T*8f(;_eDqEp;qun0-JG;&bR)q!3?Le$4OP@Q3V*;u zC^)6x*S0*|^5lNd#@4b4N-gxB!{2GrySjY_h~JFPEV-QV+-LCBKa!&b`Yom#*VV7H z&mQra6KfY`P?@bW{^TPpv7dTanQ~{{yM=zFOkrI!-r*E*^JP|kSiO>MI;E^xwN7Db z*dK*K$d&tc(`jze6-}b*}J<{K#w`5)HgXUa?-vdv)aCB{E%i| zVVCSmaSmVDD40>`g7Z!H)=Mmtle1~GgOGM~Y3u41`%UBz0Bqbxiplr^$jL7pE;smZ zO5Ngnfd-YQ;?;*mJ#W2nJm2t3x82Kws;e0qnR+S;Mbh$kRmL|xWT z0@Zs??-b&K%@<(n_UQZ9efR3Szj^u9C-&zpkc?((OpiQth;@{zcDz|0=r}6xo7@^9Q{cLuVf-ALc5;Ljr%EMLg1)KB5VAtsHB3|xy~Z$LX`_8GS8ZC zxnk*vD?<+3>}vBzvpZ{O=K6T1QL1T4&!{`;CswS3JL%^P zf&3|ADzqUP#34)2yRQ0$Y36kkevU_cSPE&_9HR=ad3cxm6-NEYdC)v~_Pysb}a zUS7;(q85cE%)S!5I%sLJBViA*V1{`~w=^YX_VfAuT_vlIaR!sEJB_vKh1UbWfHkFg zZ}T0;Y2$Lzl*iPj>l9C4AkzdWhYh>yC^J5B})e>QXoxqe|QrGg@7VI94Cn84=YJ1SV8>l>DzFV1yo z|G=H(=G|{K-&&?O`fgh=+p+1?%awhj&l}&LdsjbmI$cBWBx$9?(-U#V&2eIs_UAU= zw!?4=r{1CGHk(kUu5v03zV2;ewz(yTSMTPNH?Ql`gByiU)0UmKEnmYB#- z=Q_#*G^vy$Ex2%gSE-A>zxBn=tc!z;j40tfIe2*~4N#^-Z>EO+h$bey0ox!jpfJD6&Ok&#nvkbLzAuUU2>Bk&5(jt#0Fb@B z|9;4zz)NrzZ~zzp1QZk$G$aHR%nR*h366pagNBZbK}Ig1dR@Q06a z;H5b%c)<<<1NjU%g-2lO{*9vpA@2;u9?KH{AB1=2h5ys%C1hsm7NIUr@In3tn8mTVk`-x=W`cH0!M$ zP7P--DKK~`R8lo#5D&M(a1lo&FS5>S-!%c1^Z@>C&h!HALAnIuWsk(RtRwd^{^A^p zl%dp#1~!AlH)_oB&1)wXpwgQ>r@ouJW{faXAu^+(2iF^vVI?&ba(BlbmaB>H7Kw~( zNfDA4MqaMCgAtBhBR7tJYA95}9lMFTOP(I1*f8{KWGKI&oNUx1=>b=rH(FRH^bAQ@ ztK;CvI0^sYMmGYl6gKJ9Q}Zn@UvZr5PEi_2Jg$pG4(XI&!$?4bA8BO=%o0U7smw9k z$o7=lBf3#~e|qyrFS?M+X%ke%L5?Cdban~`frylZYB%_#$iQwA@@l$LJh*^n9L9kV zgm8U#RAyOTJFZ;Z)7uu?6?~klc1&@i1ZH21Lm>eP=^nli*sIFvP4S|*w3QEutoR?C zN;59y_#6>xD(Pm)yz?)O;Lmb7k(M0((nr_=A7fi;^b+D>#f?tg*shU{&RZuEyw6^x zQA}OcMc8D{iH)K2mRa8-B*gd58&vOZ8+prk%P_meGWuQD)3^Po%O!9I+%(=`p}UNz zATW)}iZUw%f(zrXR?sB?eWO0o%lm`I|#5d9$b#|i1m8-Hf?icA7>Em;>ZgR0xkY(!nhfx12?!PREZ@Ji3Cj#Yv zzXI@(*Fw?3?H7^CQ6vIS@oxt$8qc8Vx7vGkfnZ8TiY03+($B?fZ+JxbW_HrVtpCQ*4C;HY`tk2sfDS zE~M@sIsEd_<&*VusGd?L>9~1FnBv=cK%~rpGmllWIq72qCufCok=4@byWwQ_PU2nsI2xtjN215@$}a6lp~W;Y9!~i%8`} z6mJBO0#_&5--;+saudJEX!*CGZrs=ueK9Yd7S19-ytu7UwkMtk@~84a1vZ%@k;Ov( zk**^SB!~3#ZT@dv+asP0tRw06N>#xGwWNg553^DT%JE&$;yLB)g>~xU1weI{Smu*oTQB~?$L&4v{tAJk=Gtpa1qKZafMKL6<&TNxL%xSeI5bzS z5sXs#9gImV4^;OQUU*w8g#PKQ!W4L^P07I`)6%pJ3EGQ^IEp~#wl9c#$uj)*qJ(omMM)Q)K zn-=AZlnkuYZ%%GeYA`j83rguKPs!IOvQI1KnzcQA`=CJmn~NuScNDLk$s%`QH(1gk!TOm5qk~9jfABcJ((`ycu$+3ooJXB(=LYS9Laoz z^J7wDgk@}f*vqbLhgh}mRq{Xc*an^E)y&)-kAfDgiDMpYwdkvfDslR{%YAErN*VP< zmA*A}Xy-2H5>O1T;U;*SUcIDi-YprSQb!!OLNn;sO&oTWdeyVdY&**F#wpDv+keA{ z?GHzqyvc}%f$iKYU(U$+ijt0mt8djtDIh4qWDa7eO%j%pGKl$FUoWuu$Lb!8E=L{p zkUZ5L9#G4j(`Rc5jT!Y-Zq#+B!BSSU9+Tl9u9)Qs)@rvN zxjPOlZ%6g3+r$HH`=TtRCJc~ny07Iu16b2|7^MAj?n2rgRe581$MJ6!D2xZboE7pl zk1{wVw$``W@G_&!ln+((Lo?gIs~rQCYdI3?7f5C6XDD9Hn`WjO^cM+71D~d}knBH7 z%kr`I#T{s&^7c!eVDXcz)UC->{YquCw0B3aeDpSd7X{Qpn14cFc2fsgcpaM`?s2fP%j(N4cGHftUQbPLYMQy1 zamg19hQN-%f{m4sBCW)X3xya#?rVjU4C)0rP>g(4=OyDnR{z0wQUT_CrLUl*eQs~| zFT3ikI`RY?VRH)D&=q6lYbn&dwzV!=$o>iqD+35;!XlIJ)&yAah4x`kTK1j+aGN)z zcF7X~I`>{exG6OQQ+c*^TG=YCx<_B#b9l!Py;c3PuE=WW66(2yBO|q23tY3NGGnQ! zTz2vcX}Xy0GF)QXxzZyivZr0XBUzW!T+GYbTNgx^OngqxK&LdJzc42GMfN)E5?4)C z+FnCSS)HcP5867($1`kKf#|N5N%}0I=jIUw*7}-YIjy`>p3U0&+qhj8{NN)GNAqR8 z8Z9ek{<~RYA?+CX9tqnvWz5`#x+RSWGl0ZwfmyZQcBKw%} zJq|~k4!zZOM0%E7Shy6(1G;{kH6p%TU9xsk?R>xPS`*nFe3H;IquR;t*|SQzCZ)Uq zd(#puvIxrDR=uWaJz|$|l`f`K1r;@^pRbH7{velRuCCa7n6!^Fsi;drLmMA zoQ67=xF^mn8lc9frGT=gqS7tW8~tWgyec}9bm5n%0M8YgLs{v%vQLjv9?JI^@o#;V z(^nj#R-}qOytXsyMuYR9-ot!j!w~fw_WDPG*PQcO!%Kwg^F=+oZWye`7k%mP+~#>G zxU}Pm*J{(O`eF|HjA+{|kT9WoKc0Tzvp^hOoHZ7;R_YWhjhn`wHWXe_OK|hGII#a! zsWpKgL8YKTic0mRCpl*!77aOuI+I*#@+94(Or%i{#+4%mh%z!J=!x64drvi`N_m@r z31Urr$34)BGKfyvsud+u|5+#L=Q(Yrt@?eNHcrG-nXN%5RK z6XR&M3AqQ}3n^MsoDFARo~$KDB_;k3AyO-Nu6NdS(z}}%Jt*lh45*yNNt1`70S>}$ zxjmxG_CPyxa-xheDM5mbEOuC6bPP99RKi*DF1cr~>S0b-A#p%q!f0=Uz~wWbKi_tf zT~q0dg^G~z72Z3ybzY^6;*m)SE7n?+`KZG_5>h#QBh1QFT~|_vm<& zSe1tLz!(~ijd-E+{RkSv*AMBRH#(Z~X@@bGwVNUsbDsgdIL@UAd|0ewM+UU}Qmqn`GXglMk@UuwUZ`KtO;O@zm|Dm#6niDs`J71H%{|+fNd{q zYX4$q({(RQo_Vw}>_Vi(VD1OwvUQAcQVdRYJ$uI&^E?%@rp36&c8w>V@VGleWdSD| z%N~7t-jkf=iO+)$RRx+ZnYFxv&0UftF5{{JJW8Oy^7<#oe@u%6b~&~cz5hycoU>j1 z+f$H5`)AQPJZDEGPwjf~HbqLgPa4JbH1o$97rgzhh&FLV|RB z<03KY1swgXqJ9o+yIg=P#~VKkoDCn81csa#%KKO-M(qdAsQTHm$mg{ll z+X%fy?eB$MX;Db7sluo)aqKKPyI2V>QAwI+C5v}epjjcwx}G6F3-zJ&G{ z689FpO4qr;J3#q15_ZToE)#8>o06tQ$@;r~$IrNHogEh)7rB`pS;2Fv2De2A$6oQg zpX%+BXxMvUym+w{#cn3VmA!Iks?rI~s2Nl+?fS%(%^#Yf=J`MG_0`ON5oaHq-yP}2 z?67$HelfP<H#^TXF#=pI*x+F9s|C%39mAKiD)HL5(Q?XZT+0Gund30jLD<8 zx(l#JZ7ryCGW8AGp%g8TPERS69Gmml-z~(lFzwcTM(%P3?mCjJrNLCl_Pb{V-^2*t z5?}W)>V#thu*eZn(jJr@Eqce;s;^`>+fLi&mqojH92kkXXl_L^GzY)x;>>Mbb*d=d zKSEQ66CI6J5B9D-Ma;1wdTEmlo{Tc#T+ucMQsW~gnE8Ef^s`dmKEQS3kvyTY5Ips^&B zio>E4{Yl4plrY20F9NWOJZUh6zpMt`ibN>I7zi^j^mzuTh0)@;8DqAYN58wt_fAu__4ug}D-Ro^ zP%3zn`!rS~UVIT3n>xUG_Y44U^)a9TAR!?jVZb|mPyndkyMPdoC{U=x(8y@aO6WvF zBup%Z!piSIIDYCn`Mu2tzPTd^@eHudjiAbnxEd2fS2Fme1UCOO`p#2Tn*K+kck%BS zz>)laL6_Eug4urtt+d4$iJ;1>79eNr@saKAkqt%7+OWCSpQmzZO>G)x$o9I{3pe@P zn$DnyT6BJt+pIxze|z&~ZO)zFOa$xQnVfjYxcZM~eTe~Fr~agM6O;UMZ%VH2Gn1yK zg6b^3f&=*|PvdyoCny(c%7Yflo^QPL{e4~7O{t~}nOM0}>7ti5!EXdVIh^w_4osL! z-SYEAF(-2>Wa{qWGHh(Ox6fuA^`$M{J8pO^d!dF*_ z2$D@TjbC6)LZ^zLz>A@kD!rnvqHl3?`$V0TN0CrPzDf43xM$ zCp3OqG4lG62rLiay)TaU)}BRnurZ7B77|`fSB%sL%KRf_$tUk0t2aV zhMX-aVU6@biSFD#Pb1kFcsN8xP#(7S7y&CCQ_xPqC-49L;#@DEZalIi7m@Wqsk)Jt zZqkZ9Lj>e1pM8d}0& zK^`Sv4Eh`ozOhnxVYMl4lR-6yNUO0S6VB8A-I2YPUtW;Rq@aaejHg3!n9GlDAX!V0 z!Oj+RP0~E3o484N;oNh*cqdnOCl?`dWa`eK=&b}&tc#YN9BJDNJ9Uo8RdzbQw#XA< zdsk&m+J((UwR^{#(3QYH(=dce_Qd}sU)d~c&Y<=gz?XUaS&xOQWv~?~Fu}!(E;J`~ z*Wj}Pe5fNWTdfFArHP04E!Nv`_tf1}ezCv2z{v z8#16xSd{Wfj%1vJ-7{mr&jZiq?s{tcV#}=T&FBRDuc*XvV@Ynz4RCJ3MMzoFzj4X+ z5F6gtifP4;6D^)QjC`VHQyY`K>y$Ilv59fuGp1Xbfu<9VCx}#F6>pZOR6J>gtI(`; zVi6iB%7@S8!Q*h^@@rN34+T@q#fm+`&5G4zj9;{d0umiX|A=CF}%TH6dcKkNk6Z38#{?(#&qrW_em*;a+m_TS|6J1N!0N z;h~pkB&rG4j!pB7H}a_& z=-j)5njyNsapawy_@tDf@<7MuJ7xQ>hVMcQsbW2UQ^9Olq;*=Rn|Rc$MxsoUtO<28 zG~lrW(=pJ*xa%1J4^mQA9av?5RDem=0vCa4vcAmgIRt)Ejg5uyy0BHLxPX9ynQ-{+ z>UZ2I0aUf4MrD}@W`|#z>=23)Mt3QKIry;KrUwym`jUyMfhIm}2)R|CFl z@KTuc>B%GV^767+i3Y;oQf8~hsmc=R+KMwNvhv&>a(+?%&|I5|CwUnX*?@8siBkD_ zQu#kE=-Lu&YG3q@9Va$+?aT+a>Wb2*tn7SpEx(!k^pS;2IsBH?Iuk*1=#g|$e4P`! zX?WVM)-|ncDW%p)e|W$fKNeN$mb2hsIi=S1o;<*U$lf9}y`xVa z0^jwnddQWwy#w&ZmB` zdP{)MK*498uU6V?;QicSC64kh*6nY;*m*Mg_Inj(TEH`CFGDY2`jU`S2S`8 zsQ!g6(lrJ{#-5m~^1(PTyJq~)j0pnpN)UW7`fHoARa-qD*f%mp_zXa5ycD9@byjm$ zXR3IeFb(aifKlX=qhrez*o^hAEN)dPVg}`_o!BU=1srT|}gN+$EHNG&MJS!Wbpohn-t z89n_-r$F!3Ii^?xtZAr>rA>C@NXvN7NK!P zO^CL?UTdRbXa@9PVThLxUV03x{ZOSdfLsKr?K{=WkJOfaC9!Gxbj~9L8kMjXY3xba zDa{r!!#rM7W57yu&~DidZ+7?2zQX3xRcwS$g^`QL@p0jK$8*@`YXl0SPU1)XJk_i4 zYqe5s-0(f$B9>R$*;?6CTHNMQ*^yhiHS&S7wPv%v`XlbS2Hx}|hR&h~^t-O9rnPdl z;#xv?Pvz!LssnlUX8=)We?U17gGH0~otU3xHHA6Pb*ZA&GazXo{&kN)X_?ZiLyW_5 z`v<`#Nx=G7!oB(oiW3|P`$Y^N`SAO8_cV$N*f276rfS-{w@8Hs{m4f6FvCHJtZT1qG!2>L=?@Flt>&-D0{JnB?tB-Hj|& zN$vXe;#TI3l)O3tQzmKWy!X^Roj3iL+Wp;bsc8Wf;jN6`rS4v(kOfFE)TQ?>?LNI$ zQ$sphgM}i27OhQg^gZ28j_Ts29xxTAixu1;l|46L6K`l!Ms_uwkRoacQ8m4>pevo- zk+MzdfOa&KfZirxNjwtBt5#w_rHXxjM}4dY%^0pZH&uelkTqi0%oduQF7+^|ApJUUj|G1-(^q^_mB{N!DnlBLeRv^&z zSHu5ChR}Bs@fL`EEEA~ z)NcpUw1-Mq96QaoCQ=nqmD>-aoawp9e<4KoA)2TnneH)|5kublVb&O#Vd|f{d%Q6& zJyVbUxaFUF#g7WlyEvt?o6Xhb-L=f1oq^rKpxut(sayK<8L+nbWSlD{Z5wig1iz}m zN$%_fHiDb!=m!k#q_<2e%%rzG)iM7QzViHME^q&n!|)~l|4ym>r?+m!m{YTEg$8Xa z7H!ht<{vw_HNkufl|?;}n-qYpW1*Aq?IV2<6k+Pesm9#g+AAjoyX+3g*4Z84W=ykdE|P?2bUtZ8^w5QZYq=4nOAp>lg+~maevGL z;q6fXR??PoW!pI36u*EWdlPE8WZA-uF&_2SvY%OD?iC<KjzdrrCY&Wn$)Z74DLQ2rux|(hhMD!v;=K| zIby7|yJRCqB!Ep@Rq4}cf4`dN${2=q51uMnA`QDOP3IS8##T-0KYS%j(*2Q4RLx25 z?E|{HRXZFovXgeUX{KG+RHZx1CQ0QCQ(+rBB(8o2gvohxDb5YFr_-yG1c>iVGSnvz z-82<_iy&?<`WieX`q8-ioeUmDd!CW7pl?JF`j#k~WxFeqz{ITrr>Om)N?G75s-9)I z5ZU2^S7KL6{BZk#hTS;`me-O2#0$$9dzb#Xk4aAv&j2z1+|Dt-p(~9$+DDA1$Sa+i z{{jQJrG&V4(UfyTZNhL6Z3lM!GBkDYJ>#6-P6ak?i1e?7)_UuS2p6+S=?L~b@sJ>T z#rvJ~&%BCRpWCs2Dos#W)nd8vyvHgy8z!~z~icyy$}4=T&qlHVgIUB)||Ol zYtN=JP(>st+C@1ppx}YcXG|{`H0hq=Zl{6#(UY0j{YWkxVYo=9r2%!e<(r6?vBBF4 z;%prHNcFOX@8$aO=pMuBOnmboac%lb+xngy>;Ok%Rrly$><6sYL4D!9?3`@+8g)Ar zwSD7_V=ET+xcx@EF&iXI^ClasSP~X&iRnen^17}1V_#|MqXst0zq+P4a7mL77mQe5 z!k2g-3OnTRp}Fr}+8tZ%tgyl)CWK7LsJMfeA=2o=J+8i5W}%!rAHCyJ5Zw1mZc=3L_MwZ-1j5%)~y*b4k~ zbj{%>(cDoo>V_Hqd1vgcLblpIl1GBQD1!2O85!y=H7vxk#2_Mijp#=nIX&WqvAkC5AL%bcH3`rY)( zZy(0Wio9@UEY^o#eKHdTx`P_V>`+>Kr02X!T$17Wbect3+qZO+)_fq;^8^Mb!%4d) zC+zUb9f_scc=Zl*ub4KMl^9C+Q+g0*TsO7D*0$a68{s2?@3ohVZf5#A*u7Kws$1FX z+^j7QrpJaX)PI)e_5pW3q78holQA2mv6g-3OuDH!2ZzAX-scSE#L{!;VF|4mH1mg^2?xV(?~afC!mT(%mWTv`SveI4*9dU@5E6-Q_zb853FbcoTGU7{a-IS8Dupi9 zw^Ka+Ql|+;H&F_hF!Jf$BXFL=XuZOi$8@$PE*hvdzXfaFwnjYUAYkKhizp`7Jpz0I_r-$9p5nlP0XF@q}9*Xt4N!jE&^|)Usy0&et!%1-^Yt4AS-%K3 zyJ@(mMTW}!mbbcl_NjW9Xlw6c&>S6?V?9;IUJ;{VLjQE3O0+%mRaxhN23px*KPGwg zIH@|zn>~?s%hsT)gU!0XKJ7xrYDd zs%$P>hpR)aCVEqcYFVu&as%uVbABie88J&CnqH(z7kG~o=w~v01f+qzkH8xq78+*7 z4O6;(4F3oUsN|+M?>wuvx(L0gR_E`1- zZ?eVUpx;NTw`frc@q}@mIlSR+ryR3Dxn-43S^p5>z~&;SlxuWj))v7t7y-iLU)AOt zOj))VgYAf+hX!Gk!Ik=AB8k;<)5j0kNas`2u;rnWznlMj6LO86yR|$>psSk75(u+( zmTm~CzOr z9oADF5#W?}(J(GxJ6o<=>PWcTU@f+LSbuTylbuzzXOeHvM_RKDo0`qCZ&k?kJ#~W< z&JvfvQfr!FWn9&}hEa0M;Y$^pv|HinX&sJ@kUS@4PC5u_W&A;B|1Y5T;b`rpJ7-(! zWmLv&Mws>aE9OfhnZ#;kCpLAeiKC+5)8z2Df=EO-a(LmBt1k_DTr$3VTS-6Jh+M;I z`jPwnWCbIgto;2BV|{P0FWya$>dwJANH{Q6)duBOx~J5YosBb zZ&Pa25sxyPKjjHzZ(?D$O12us5b>=KPo68i0&WZBVxjO5Z0$k}e+)rRQc|St2O+Ic zxk%%NDW=Kf9gv3il4kQRi-j< z#6oKqIX0q5GFZTR7nyI!{_SENwn&T#xl>w>Sk|g033x7K^XA72MR$5_ZM9p-$A<`a+&w!dxqPi0)u}DyYo_be>S+Rrk$=O^W~|RuOYV zJ}vwVU_hYiBxXg_pqzy&Bl)c2y#&m0+%n5o?K!_jbpR$>uq~$V;!T!QH^=(gLCAJN zow6%{;M7f*(ObF`^0m)YYk@av<=b-IFH>i_0k!A>`LA-sx#!K=HFL+gS?crdL`b5i zxpS=K)n+nAKazV>=_6T`pKy@vvCG~LC_>C$Ra64%4ST4n%MmO`7qt?duEi!0LKwX( zlwCY}PT#`z?wmqf7(Mn_Fh<(MK=|T!cTE1IH-=?@B^_-iqgJRz6ThWB_OA3DO)ssd zc9aG+ZiKT31_KR{@?7mzk@<}H^{Uu{6w?KKe+mRL6-S{chqyxr8O zHbS$-uwq8xkr?Ep6!qPflb&=Lg$V+&m74<^o0W0ZvH_$a;b_Ed+0$duO%58_SIgm{ zA16tFgfoTb1ur%n^;C(7PaWDycAGWog~J?@ z*TPm~{XaOXn?c`{jE!8t-|YmwZkj=>SxYsMgesK0v>Gc@0e>y_HkCe+MF+DyPKT&L z$fjL=bjs!tY<#%X#qeS&0p-d&zmcSGZWcT=4VpNI}=6vqPh$80A8h5AZkZ&yu9G28>{26h#GZD^1 zP`S`Y)t_Rplx6yBmzq_`o0YV~7#EBCHVZ}fs=P`#OqH=u)pD=mUs*-5tO=)mg#u26 zB9A0u5lMwlJyg7@U=~fMpsH@Vk+Kpw_tjsmP)lf{BP9w|s} zKWyl*eIy0y%`JzATdt-v)MwMJ(5{86rD;#aO)e1rR(O}ehGszNsl8pszbTETv)cZd zmi{(Fbk;7)$R#X=y$*r{cgT$9h(YB8GV#M`yEjE<9QYI>9T{wNABnc90StBeQzJDmxu7q@@*_t&Xgr@$gI5l$ZaUMKo*Fx#0*$&Uc%{{IO{9Ri zPV!oCueTY5XsR8JQD((qgcZM952ZP&Yb0s{u~%Q-nU+!;GJQjfEa_t)Ui3w1B;r7R zxoDiEKC{5b{z1&$Vw&Y13GQoy3}h)LWEIAzA*kfn)N^jq+onfff4*t1*Uk^*ANX}d zkv7%!0Un^KmRaGl7FMORR*m5?pvCY(g{;}?BW9T(7KWw>d->b$DJ#*}&5pXlYad+> zAV5*>9C9hx#2lzyC#TAR05-cs%xaH^ak10ffxKfmBr}=tjvohS$I3fPxTO}imGX6F z_AQq+v?l7`%fr=z)L+v^tmJtuH1unx|@{qcz_vJtDLiFh10F&JW)g(p&(N;p4*SQ_z2b*IXD;ARas-K=0aqr(u)@ee|8AhIX8iGdGmV!2 z@8o|ALV5QQ`VX7nHuz6GkTWFzL$o}^u<~KTF2ZO}9{~Wgr=Q?U1sVSq6EUp94fqqu zKQcW7>cC9@OZsS!?;-yVF1QGQRrnKzQuKe}|C{$e=lK5*s?YyzA_B0&FU2n(_a|$= zGluUxN_3e}=G3Y5f0PkdQyhnA!bLXg?-vU@KSG<|uFGp^jsGP{2>F~*gOUep>Mc%J z6$g8kbW3E_JXha*RJW`g{FN>T_WxTFu33o03i{ByS3p!L4g&<#{JAf+Cl((tMmBB? ze}~15>}524TXYcH-6%RQN3lL^f6{8(Fg=X$uN?iQUzC#n)R+uyPlTa@=mH0l1xY*m z&jPCWG&_QGWb0b-JT_=d<(yxw{vW=+0lJdz+w;b@ZQJRvV>{`vgBzoxj&0jX$F^DcJl9joW|_s#pyd$VRuRjsPJr_QZ9*!y6g-`;ylAUvJxXvbM4a9d@)O?YZZD$+#y z$2cC2#iyb3YUe)y7&J_gC0>dD4>j;9`JcQ{xMu{Kn4i^&RI*BfEXF`-&e(Mw2M0BI z!penTb7q_01V0&vMHbs^$77(Y-wB|<`UcW)QE+cC(PnjvkFBvSXQMG9|xuRW@o z|7y%r@$4HxSVvB0znU)>E&rcfq6X1ufin1?T;?Ez<#@1O)Id+6PDTH)`!Y5gfII2z zaTk#7<-3>=_&|~nBNP{OeA~7dPUy-GKD-ot^=JU$Jjm;UT8tfxGv+uPM3pa}C?2MB z30wKeUIj#1d=$9Nn^sdFL(3$I*Z+-VK)g&Nvjkg=A}xtwZhzc03rdt?%oyW>PLO?6 z_pS&02(4b7JQHQ8QS?MIY0$99#IN2Sa;~ET_K;V!=iL_CJKVWna7ssc z1>L{C3rW2PzR?Pe-U;hqPPI7kNpQIFf_Qu2Vv9}M?Y;{sBtCKiYoquZC~H7_?Zsgq zwO1T-2gQB%52RxQ2NSzG+JCB>ycr2G=hBT#u|OWB4~on}3HZNB{h!hzgJqXoqbhw6 zcrr~$IPig|3LqWkBxpNel1N@5F3jtiExbQA6E)O%Gck&^YOa)xSdf@G7faEw#){`Q zP}Pk_b4C?`R1rZ@>TV~bre$(Mo+`+O_i5G7gNP>W+F%v)^BOoCqHrp}*$J_T z9o_IHA2>zm3zsJsQn)U%cfuQFnIfgv4uVks;-#?WnT|DWqc0>xU0032p`{PRR}QuX zeL#M@neF2ub}gU;rBWe`dANm=^elv%!TDJ!aWiI%Nqqmf5v{>y-M!^!om^Dv=C#R6 zkGoI0Li47b=3HySWhm)7>Miba0*G2b+?fNul5#Hd+N)twR+1r_iGbv6VzQ;jJO_z+SXc*dS*J5u&Z4_JbMoizXcMCfbtte z8uzmapBv$)^lpzqJguiz;rskSGkMft_q#Z1i~>qB0X$bzZyoum;4mXW??13LWe_un zt~Dt6uPsyLE8!n!+_@6kD#~kRSNaa}oS^{~wIj_ZWmQ_&xxWxit$+Xu=?RkSb9gJO z0)hr2Tm9PhK?@rSAoZ}9u>VRcjihC9rnMTe%XZQ`-1x^Z8ZXBfpI>k#rOZd88S8oD#NiKkYq z*P0wHG2Pw-2hw9C;&@&hf;izBEFvvZdDdSWZ*&m@Zv<&nGGm?JaU=4^yo3FCEPoqI zzl3c*^u{Q>%Wq@?mHbHmQT}6}KxF>|INkvOKK||yAD@HB zWWQ$~(aECjt3)w;vQhA@Ku*=meqpPoM!}?OqF#Du(vh~hrlV=h;TB8wSU(R7Z%JJs zM_^xM=_W_U0XcGx9IWae5y;zp}iFOR(fj7lO60kQ9LNyJi4k4Hm*gGmr2; z=LrW12R((UVly$(L=S!dV*7>tKzp!oFwM2nuF@~5rxH;pT`VU|byiHCJ~w{YrDS_V?MzoD@^X@*aTMq~>EJqCo}OdkVPdRK0Xb zq~&!z;VLH^j{t{LUsbkv8`H3a?7$LJgKtOH6_t-j_j0BtT|~ZkZ%yu`;u<0Gl*K5a zT;zL@dkZ}VzRg=~Xx!5?61R`c=7Fc*Be5*h)~&%L9irGOHAw24!#e%Yb?G6;8%;Ep zkgK;P!X$98{|A6tWC`y_#rg+8LgYmZx8BF^Mq>wgv6IzF47Xx(ED=eeI&}vtTwm&8 zdG9@y^dj84n$wF#-oQk=SUHdvxX8`J*BP@?p<>@GzXQ4NfBpF~L@ zcL_Y|V*BbAUVP?!J|3q+@y1-`4YxHG_FVM%6mi!r$@=IZw?2h<)4+@BgLyXvVNE$( z%E+^vNbWM_%>@T%|G}B*fpog@>^p4DPVJ$XA1`>b8%{%S)w2IqC-SoY$~C_}Oi6F- zj%(-zr7uPlZ+LB@#WoRyxat5{aX58_%|2yaNBv8JPI6_l{jOLvGEeVky#8xYi$p}B zVfEn3dP1u`?=|44iZ&ni!y;vSp-~%y}FFEh+uVuQs_aXE`)LcSUsGT zaLAfFcx%Gx^M%LZyRC#l^((?m#6+xQh$$f(&26TXgWhZixz4y$(NB|{#`&Th%U~)& zR?K^XW3}a-gFa5)HaM*es}fD|$~28x9Y=0{u%^J-JCnj?GjBwwg1N@@eA~V2<8t*$ zh@?EQLx~IA;q3&_jIfdmFf}RAp3-1jWzsZ-j6aa;x!UFKVq)l#WW2 zzYpXOA11TC5@!-V-6{#mpv(?l9pLatsnpM%BF|rQ@$PuuSXB-xlqqTB*YdigX(R~O zAUZR|_c+~>baWo!q9Vn{>(tQu#oSi82XkvCU*BN18zwb0CDa5Mn@(0e zSgBO24{7DCgMXdYpzi~WUO?iIRv03CU@V_@GR14z5-f6X)}N(PBN0PIYBI*n``C|o z45EeLQOcLQ@(d#9m);)j<(nL6k5G-4-B#nc)}Ro%Y~y%qT`H%2pZu=(L=;VVZ=^ltHcv;t-{;mzTSEhjxm|2-JR}2{Dvy%Xr#cn;YxnvH&Mlf+{eRk`sFaamqYlJ(5P9gjuy5Zs9T zL@(=f_8iyp9*W0q|X6St zm}kZcu8Ah>@Elq-N)#y+Un4TLcn{*@*(hMW9dS+2#O&8yb1u^CU~F77B?tCN#ZpPX z$$AKP!hNAg)`^R|jg>jtB@HiLE4<@&q}4jYaC)E6ClXzG?c&&UrcX4q}K^{kZdPH1pW3VW|)lfPsi&w})Z#Lq>V=W@uBW0tOlvs zRdpHk{!q;C5_EVVG+1l;1E3Uwnj>I)rt)YA-12!!b!~nB!mG*PC0-ep7?&{h&YbfS zcHky;A^m-=B~o7YXg6hM7|*kFKAMZJ?&pHcGtOnCFqP4aWU7XenGrMkFpkDyI)Tg% z(W}&XPwS=FHd>{bN0>=kQP?690-D5*zi^JyS&s+{%jrAZ+Zn3$EK32}d$r~C5(_lT zN=JId&QQ=jHm%VRq(Dgd{*w-^&f!jK{a76bSODi&4=#NVuJ39a@`zl zzi>I^X2C1@Ygk+_wL9k!T9*O=`cF?K6IwcM!+9Bz#qZi_scI_oaq$UXAs@J&DIZZ> zWJ}Dl(_h0fTgZDod+R;nxNjEF-uC2O3-;_~@r*UHe!L(NEUWe}mY8QeaFzhynDdg= zidZ02G*(3gVLiJ6X^dKDJ>unh!JG8m0Zoc3{=d^3WkOCLK^kxaJ{8b2xn;3Mci z&m69|MBBSTWrmCoOAL*l8kkclsiaHiQ~m&gn6dZS-D-5tW8)Cbh<21HjM3_?DKNR^ zn|`F4&>z+=tEB%FE?v``6&yXOrIh}*G~oaoZg76~C0h$`()u(2C^5}ID*W*=UQ2v7 zr&SV{Cakjl=o#+Yamcd$E{<@=)>(b|t%{9t@D^A4Tg@`bWOq^3Dt^5iybVxvOvP2k zN7qy}p~6l)Xu*upj6z0Sf1;S6?&d=w#rJokIi4fO_yt;Lp5Rg70Ug+4}iM}gKIQ|86w#$q(wzXE=9v!=GN9SgR-U2{`|oUNwMrvJZ!>V<%As5 z(u#X}GkwJAgnS~Q+NCP0`S|foY^|eZWwu87C10oj&qbojLc661+7SBftuKhZ!1xuZFQDLjt?YGK1i z5%T*`Q~#A{+^#Q|3=qV=YLrp+JHIr^Ac#A=#x^+DaIQXu?Y(mIuy(L!mJe0KOTat# z2YqBt6aK^zP@P*ZhMJuOU~e8=k}IdG!F(^#pwqE*L551$MI+xTJ*(Zt&QEBoYNAO` zB-3xU!>-0Ka8g*`j7qJ-U3k!1@(9xS(KaNUKiRWnZ?A|QrFt(Bm*Zs2;y9}!>QHAu zt+|!sP#wJfel0SQU4{0w03VFY@oP#BdJX0359UdeVV$xS1Py(uIcngtci|F>dPrz` z_}%9SyD#BPs~hn8c6T=}F zDJ?=H25qzo2zS0I*GGbZd3_as_>sPnBn$lFX!xs{f@t=%RHDO%g^&R^lbcFD9rsoZ zCPSiui{l3A>?>zj0nvcG5^B3uUYO_)YDvCINqK1bFNVF`=KF65I`hMG6Ja%Bbo5;Z z56sF%bYK#<8s=Syl2AtuO9nejKeMZJe}Bl{qQyB(;FvA2xG+dZbm_d@CBqeFB(ulf zF~X65*$EmYN)mCO4EgLC?dMG**@Iu;8EgK8RvH)CguB!>+IIjOs7Gi`R$f8=o3Js{ zpmJzXftw>tQGk~?kEJzu-gFzd{TTj+`u^lya?K!~79pxS(DYhXuRY;1M^q79`bc7l`m z(moCaLsi!oXqEJ?C20M+yJ2k%q97!^J&z{w4^-dqoQc?LjOWB>HJD?i!@$$9py_(pgqmbu=~sdH70fk+JB#|voYWb`z4CNXKWcC3Y86`yV$U1^sNLjZ z?{kq?P3#s)+v0pjcQw?2pW#X=KCfk>g0uy@`eV7>WQ!>Dle_LfKxwTW&sDH#)#2eMJf?m2q*9R@~?0% z?nkrXZ?VE^`jLgCDU`@W`M`P=N1hCAr}SSdqf2-bsNp+a>piCc=mnLSvzcUq@E>lH z+uf7Cr8B1-!3Pd+v0EdT#)$`xUNLs5gV1H+XCJ*VV`{CLqhf1;-LgaFK7p}ut2cx# zPTZYhNu|r@R26e64Vt-E;9Z0LsSf-sY=Mi;xvP#P&_-_wFL&i_1QRi7XdUbW4)wSl z$cvKQyP_eFxPs#MaDx6M9EbC$4V&dauJ{P)XZ%W#YRhDL5Bg?qAXDspUcjSAWyR2* z@JkO!=?QV{4NUG2Nc@j^dq>P?US1ZLj~*iZJK!W6j6+2>SzvW5IF@qs(#n#2Qi0Mi zTQrObdS#DtvGb0)C#_&|{5_$CD0p}>^ujnZV~S@pTyZLDMYeIuc#(c@QX$3(uD>-T za+cd03LN3I4vw}cnDz**<{ehYjlD69e=sl?8yy>KVi^Li_#~P z`A6~|&abo-UcsK-t3Lo!8%0FD5z_B0n zW30&iWO4yDtCP{`?dKrNex3@oRfH}AQG|6_6`|1F(^_M;r-|U18fjL1DEWcO-GB#;^aiC8{Y@;LtZI578_)_jbq^k` zInQ2$x}ELJaX{srU6e!BCb4&p<-5*p?sER`x!*6CoT@r^j}&9Nbvcz33EZo=6Ap)i z0_b8KuJP~ES*aOp_q9YbwNSU8(gs+o*i`6%YuXfmULDk!KY(>IgPKZx@#yg2Zl${5 zeC7IYP8cpMxX~|t-HCU^nDz1rV@}j%msI5-l^#W~Y&{Q9y8b3kKSDp)9hW_Bj*T$+ zC2~i5V-g0QGGGhUK8H3vFti^xx1N>yl*Z(RPF!Gkkx^DPdOlTV93+xxFhV9X=?t$f z=-fW$KH8-xDT*kA(UGSvvSq9Ee_yetn#$+=F$~)u(ad0ydeSSi*MUvkoTd=v7`3dv zt!9W+NqERw#t-39cfu-rU^}Uem70@6MrSW&$l1vu8Yr`@N5-*0x?5qFdrv-~w%`y> zX6U(!qBKq2Q)%e^U73VZOR@i(I%}Ol-fP@)*0uc;Tutr3E3%ZoFJA0?-L=IeBSs*# zn0Pz`3xymBYe7U%22x~g+6+Q~qX3dlC&D4hu~TH}TZpKpzhz4RN$9{g@LJU5q}09O zdfrgf9YWZz5tQLH{#|T1K+G@knL(G52bYvnooECt($Leg_5yhH+Beq$a_G4UdW9J5 z;LzMDXz7OxVJ`AAxP8!sZctj8m7GJ`~ zMBc=nMt%o%Che2-A1kz#iCcQYj;0>V$rQWcsbWoe__IAbeIFGr>)?Z!g@iP+0wD-@ zYX#EK|^1 zm5$6}PsORmL!4IQk!TK^!1A(7o12>uv_7b?JX&($Hd7j$geRagCY<2rcl-hTYBo#t z?|h>$^I^-%N_*%GC%gILgXt{^o4|-8K6HB=y3&(*q(S_} z-($;tWb2sR(;c=v~e zU;A{Y1UaT1{20{wr#=FozH$hpLdv5v??>$r-9ZeA!3Rv9ZTv1V9%NDfpxD64b~K5Q1=turt2yCwTSYHYqg7TSvZ8t zvgkn-xq&kmLqyLhU4VwgP~8nb)fWh8=4G#4(3 z(OSvJYU3f0^54kIB@eS+<%`RbX+A2CnrqlheU zs?L$SYKU_c)Cl`DX~%oC5W3oP@4UIj(tJKnh%uqX(VV-Gp&9VfU}}e_G`*R(U$@;pFn?g4WT{nT7xFzwc=9gw7MLES;+Sz zQXIJ}7y1LZt$=ZSLc$+mBrg4)*N!2Inu}oNh^i>A_K4Y7edcYu)L*3`9IFUy=Gd2W zV@cs{K=mw?j)WjQx^;Y@yKnXF0$aZ82Zx%m)+oeaeUio!kLiW1z06LaBLyck&4P`? zWt>kx-3f(=PaidXuDH~(t}@4E^^q^?2j`p2>{H2BT-p36F+H%N zW?EV0blz&K^|EKI{6VMs3^D@ItEsOb@?NbFA9!>A-HJa%BlY$b0;8-mO{3-tT%@DD zKj0oyXpGk}&0Gt^qk}5OTE5VLFX^cK51{hEied3d^xVu)k0;RaXiZ3#v*Gou$jz%O|e`lcRd9s*!?iv-saD zg;}7FJMY=xoz0w%YPkO8BoM6RABJMxegny$5m`Sw^a@1K=)d;<0U&{T=r^8$zv2Jx zZ^9s~2Ll?2kHH|oA)!GQ7eM19&<4VKp#BAWkUGR)`{&io?f!*&^cnugECcEvzzj&- z3tDAj2dzP01A@o|4FCO&n$eH(_8I2t4GaV|kOqZa!;1PkfnJFVip_!xiSyL^1QY1T z0?D%TCIaPr*KfiHt{OAkkFnc?!u6#yu zEgdvg&&WZ0U83l3ue*O2#soM;zi>uQ7SS$$$5-uuXZ-<$?_U zgRWN28u+QG!RY{(Dz2fFtK;Gg^~mUc2RF@Ggk}&AIZ9b29t%wBrdP?IAiu`1GeWT< z_=L$G+&V!!(-+XY07THH4N2^oC@;Z7@ z3Jfkj<{-|a0z#*be+I&#w!6gecsU};TAi#EHMx8d2AQXHBN{rhwnh0M+-ru_mz<|H zK*h-yz#NvxX=8TZ#2@AP1Z9+O@upNE!g87(!nT6ce1@JRKe71)Pn#Pl+C@DnPx+lM zH@*W$qc^cPdEgGmb+(_H+Gctu6MG+#W&}`KJAi})s$3W1P(v3Oqhl8s(8a7RZ%bpx%aP2;1=A|jL2aGPvA8HV2w7z0XLzJ*9aRsh9!7Z4*Ap$(EIO|+#r&x%41 zqd#~pih#aU;yS4o=UG|cCn1eIiQDI@A&R{hii57_LDB5wzw*ZjtK}Q~&t(Og(}o4y zQxuAN?+%dJAOinhPNG|R!-ND7wdKJME2Ng--Wc1Qs)|6Ex z7LZXPl5UR|O#do5kswsa1GJ2kKm64?2k*jGHAY_kvw#LtF>qCz4!`sFuUsvS93!24 zW9zDet8n*gLr(S@FPWy=b=w|v^Ey$NA*NvlyW??z9PiLiZ*=ovp=~fBKrvd|1Fdce z40*2xG-*i!%V>ylOz*U=cFwgw%)m#&hZqmm)Hj-s=Tum{;L?8 zhg7D&`p-kM8YD+TZwW9h){VvaG~=6TZFu>Zo}%G)g(LBO*^WXaXG5fRCK`Duxj*9d zi9?N!LoN~wAf(o3z5Tt7QKHw7#&J zsmm7XoQWVlGM|Z%_Y={(9g~i%M}LK#w_%c&q@?DOo#dvl#SUtZ`}PHzba)Rty6-7> z1@qgcAnN+athaF9>I+K29f{O2R~x^D1g6edmT8szrX@Ht&9Sq5&UVckYRCh&nP%$B zB?S~lwq*6n`vdM7m$0X0d!RT;o9Hdj93uM@wga~wyk7ET{YI29F7yKU8H>{Bc7uLS zV*MS~789ph__GJ9a~%&KcAz83AdJOMY#yilB36^xcxbzrdEYGDxk>PsY4PU}`)wXO z=05-tJr>&ir{8x3-`?LpINc2QZQ33TF z4)J-{hW7zXckg7SV}Uvyw|c_a}9OxAgwR?aUQ+X>Iu$m0-0|l)MM=hZg}(A&ukI-a0iSrRp4>#IBpp zoUDz0+!S`|GrnJjj{avnPqLN*0%l*~Hc%SY)`8hAT1Ea8a~<*BceMd#NjCYCnbv~@l(-=w#Q{1=d$1QpM@k)%FS5c}- z1;)n4pb@$$ZBSeQT%9;IwZS+N357dcCK*GfxJ412Tp)sb#Ys(}H;F^wfB=aMiG+1L zXvogS#>Sph#In2Pl6B<1X3)^sWBST6=M(ZJn>lbJ?;(9a`WtIfGtXVf41$i}PL>m_ z_%t-!ph|*xs|aM==i~$#NE3w?v_7QNPv$t`YIa`ivQyZQG`Svxd87;CBT$raa`z|& z&Tv*%H6f!HP!qRmRc;UB=tmv@}Lv944#psIZ|>Y&K57i6j#_d9f+~^~+*ZPQ(NGue>4A>{y>zYY$2x*gk{}|a(|80~;2}Fo zL$V7{YzY&C#dxi5>R3_w2&gI)?(<`K(7(0Ng(&!k3Cl_hPD!2XF6-Gm-8VKt*c{mi z)nAq>O5!meiM4q@1vxoMbJqBf3rbHhvd4m`F%WhTFx;hE$0u&(_1NmIkUDb{^>B{Q z`H=@|d&I;C$ykH>;R@yy#5qq|?zz0{>f9>(m~06#=<&(hML{Dr)F5(la%v-3CA=gb z8J^x%)O@e1ym zyIox3S=3xI+NZJTwz3NIi@_FXW#R8w6BT6K0%Xet3}ooyU(b7BfPZks4yZ={@&Dk8 zSwX;J5U%*2#^Num7`05bC!t+aHkn?%Ott8LUYx3%{vV*2O@h=rsz6YkV$@4w8%R2w{ox9~Z(C1F9 zPXnN2ljjUZ0rZ-xJ=dVIbmxED8vky)Z#0?aC?DzFf1mCEd}+Zh-jrS^?pqZxVr3Tg zdEZH54Wd97@0(2D-yMEn{q~tO$R)@z7=M#D1R5^mybe4W94)+nA}V;i(ZGQs2*ni^ zmfS#`G5p=$W&yi*p>P9FpXnIx{s8bsPXC@xrdcmCePV|lKJ5MgK(9opgqM|_=7`z{ z4fFw*Btw7_kvEaxKY-AlP$iOmU(3dQ-usz?r#}Gb4~|$R;%q@{kQ;+1NC59Nkq<}E zF0Z%oc1h0Mo5u%7IPWw@5a^4siyy*L`^YnIiQq7?*WJ2#9{^$!b-KI1(dYgq(tr2u zGqG^r`x);v$)HvtD9KX9*(6V&{dg6Ll#pX#2MGCy^XIpF!HZ-+P$8ZTUXPYR^q|eqI&gwhc}rx=yj@jcYeqAp>|p?BK`KM za+PF^aByL)$eI)>I9M68%XUv5?!K@vB>BPvhr7IrBpR*0Xe6NvnS7PYc_GAscTlof zsTk~aa_d5Dk!%Y7Vz|rpp|l}WFIFzq^0LXZL|EYJq{sHBL>##nTx^}eqx|nXqKE+f z5wppbQLVBS#4o~NVq;18m~4kUlTA$$4?{N5@eAAyn7@T-%#R8A{#GNXvR;A*PQfrU zg>vm*)IbF@a7;*pTrg`C6pkySLXJKY$t^|AtrX>DHXz#0>CKG)9f3@#EhlQs>)Qld z-!X{HMPW((zTE{Xn8!DxbSvw$Aef%2NjBx6=RO++gi>hx=Q-}eMQ~$4&fO@PKYey{ zw)@#4;Y1__H*f;~0@dTkMT4>nD;?Ls^o60;tSj6!K0o&>!-5xkGC*$JHp_4Rcanc5 zLlXo4$_Snr8+~FAHCs@cpxV2Xn=*pUvh!l7L^{|32B#M^(f2XH`&7E8hW+yDtCM;u zDyghLIAp1&5+w3DDI$18f2Wl4P&<+#64^H6yS4wEr1RYI9Y?A?l&18^Bv{Cx9K#8` zFDgXnJdCmvN+~i%s6@A)_h(HB*>E~(n8xaGRm;cRQD%{tz}4>u^&S$su-a@&@#|L0AlwQ5JgB(nuU>Bqs&BIZIYXTuRRoOFW|nDXGq$my zc?AaRO6#vPo~#%oM?CF8>t5PMmr$_h7S7k{FFh?eRv=6LJ(B_YvLQ%;p6?4S3j{CD>ukM0`@{*tC-aIu0RXvG3Sa;l z3j{Q%!_p!ZwJO(u@&!vjXFUG=O!N*`qw;~qi!DnHY+`2J`EqDGHvn24<1yt5G?1@n z^IE>e@uK}|#zjL0dmMz*q4ty%>1EZvG)FL7lr8T_bqXSpQETG&=7Cb*)foVp9!z(A zq~a_orxK@iix!;m*L|1u-Lie@t9(P*z}N)OUE}VDt!5&i>*~iXv6it-_QbQ5_~7wV z1+r3{>bPFXvPd6Ae9~*3X~aMaHZ?1j1%_x5^qa92xe`3Et;9T@H-6ppj1hz;!pg?L z=^XMe$i4W{{M~HM3u$xx>pmkH9XmLeC0kUFL2ugEJhM8EE%@pILIKHN=yg)8hql!!S1!~jJK>!iq{^_D$W|9>@G$dv6SLERf zft*PH@wMg7JdL7uYmT9g9H!V#SQlVJ6F){BKN0fag*VTuAvp5k?B~m}uzhzI?V#b7 zG}Wn}4MO_iIielwY1V3Ev_L|v+2qV7tI^3zQwNFYGUg_m`c&iFVcUnffVWKIoyzYN zD3lB9j1V;Uuf&%eO|zaxsm8{VlSI(n(5y()@DN3ZTh%r!s)EH*>DOT-KL?(pIGrFvnV|6*%Mn(3MkjCoAjd$?LNKVe0U5SL|C;VoatHwEA_VKC+GUn64a)cBYJMTj#M zeOo6yH!oZ+K*jp=LV{i$s`6`q;{frBf1Q?e4W zyv>6Jb-%(TxmciPr8eD?VYe>j92>Yc{gdi=o65VEFjlkDBK>3Ley#BIK4+BpJxtxu zy2<)nvPnmnwKX*S^!#4OkI}#{NIWwOV_b?Aglc$PmT6S_nBl+r31m4KJUOR zX)x7Vqa63SrOys{kTZKwq-&Xqg{ugQ$maH-eXarar%Dr1&V<(5}tMYO_5^`y{cBA1x8A!r$F=>Aa(Lz=jWb@Z1@B=4Uz4 zXQ#ph5lpGZ1}+J0%gt$Ed)G%WKgcz09bvBx%WBMMvDMn0C#az)fAFaIyVxVByWktT z0~?kPYA|l35S;|h4p9OKLWyVN_>HfbU6QaF0=aRy1F>!rTw$5dqVpURguTlPmQ-|I zVPBPUVmHxRMmPA41o%MQw+?$QR_|hN20BP}kf(ie@t*vm)%jr#UC7($-Wdw~Kgvo~ zxWgL|uOe*W_Apw+Pv9sMkdtMTag9>+8#QuU8ZUg=!%8D27+Fj2a6K4>`~Lupg0&`` zn;{x017JxLU`YdHD6=Q|J;>QGGRUIvE-Tffpd_oRZ&X#NR9yBAF!m9>FIgaxEcN{0 z%HTyIi982$&D%fKjLQk#3uC-v-5}ih4$mnHuAmJ1Ol{vJG($*2_r-kCEQM+2;AEDe zfAYsvV?F#T#tHXBX)V6j6Ev%k5}brtWf@_sb))s_`!#`u5o{MmU>0gN(hCP>n}(Zu zh^T-~D#@pxiG-7L#buW&L~r?+V&~EO6jd9_8*LhlNkH4F%e-%SbjI_!cNm9c#&$#;C_*&XESRQT7+d)~WB)O?{+k_@_D8kYwH zHhBM}yOc>@c9nNKTpgtg)KidxB(iZhA)A}R#BVzco+vJ4O}Nh#Y9EB-%A8$qFcZj? zu%y?(+Dg@00*^1(VrI=uaZu!HA=)D7voYjW&U}WE6n?jwjcTNXA_CP=se|aqkb|b$ z6#?j9+$;8Gy+p~=Yr1YbCz_!3%|Ca~Bd;98VDKDbHtIaY9+efcGvUayB|WU%?F6mh zQQ}5S53!!DP@E8Y4_#;UUNv1@!jRT63bV1Yqw$g&GE(8|qvE|><9DMqTo=3(Jdzyu zwEh9eypO!hTnaDwI(+23#@>TM6k_kg{{X;#lJ@Kzybohtn4y&P`5|N_@G#*=_I_Nk;C^9!OtPO>A1P9eLN#$JCf-soud({RNqb@ zeR89a)OEf_HBHYiPco{Q8-Jj2Q~!gn!1oRClp&y0sSa#WfC5o%;h`B^VqzzD;3+)q z=Sc{L^9X@~r_^G?b>p9uqjK86tk8W1FQRa)KY!;?Gxu{T`hfQjxn=dBvJ4u+eq7DuZWpu6wg~+ zk|V*i_TV9eY7_9>bE9wCKkdb+G3}$}nry|R%ukK@aTo{?dymC>&o0BBWVdZ(QNSd* zdsVvm0|@^xzaasManOW+ze>`5ETX<2pS;d}TnHcMX$m^rU}d5taRcS}2_CJ(jJ`avXe)&1zC9e?8K8|XC8zLa}3knV7o)^gLaHQ5eUC>DygLh4W zz}PoHpG1>UoTD0(hFEK!pVL8{R&QU1vAx_7%a*Y#iJ+aAjOd|keB%}hsoL-(bcvru z>%5ERtc6bdyl>+CvYwaGe!mf`9$Mi&Z&Mr7baN6ebO}(qbAyYkn8E4t~FYJ$90d zAdBqq01Mw|s#vXym!}gwLv)Q>I+jY*>hdHQ->>_ZNO%$*G6Z_F!ASCZ9F%N1fDz=8 zm)fn}rnW@&_KW`f$K`bi+j*$2CZ$3l^28^ym4}>znMd>II&VYLgjwK_Q`WZA)$4x# z>yhs^(I0^MC4bKD7C+6)7Sp-PbJ)-CQ0OGAjz8yGF5zoy>leOv_;#zXtv#36CCD)M?sKBCP#+%n~g^a4nqT}*iG;NLX^B< zwF>l89sIWP77MlRue&J&MVsQ}Di)Ip2){Yt(Tdoa!!t2YjgkbiYUk)JqOxdJu3?(w z5p;Odh7H^YsZ$@#j!&TL#k*05_B?#Qzx0LR$dj0l7MyjCbYhhO{u2=Nq zYi8gTH1u)mSWF{n`Pa4Ur&6sI=ohvmBNuko80GA$T~T35kuP-i(^B-p@~}uIX?J)A zvbH3qVxd_*w8(Lv*3w0=S0z1}Rr{oM7Ec-ZF?ped0!9|*r*Onz>f&7OLuND6c?wC` zw67(bnvEWuTKBI$4RmWTO$G7%d}?W=JJRJ~sVi?9(TO(ugp2Bql8Cv$ozkCi6U6p? z1+yK;db%}yviAUe)ux%a8yn358lbahC^^GrHWdK@VYTukSv}d*ish~XaK%n`Ob?DM zL4Q{k-LPs74Vx`LR@ltvCHs_Xh2P1Il4i1Aeoj-*ao5CV1>piEVxgL)P}E2A?RFxS znG+tfEKRehMW<<6cZr*E)ktckX6VEOtJhv^urB-y zeI4uY*R=j67zj1KG!xUPAPFAPWj9LcMyNF0NG_;T^sT4gDw{dS6~v)5L^d4ygR>VA ziCecfBb|m+*18iQ9L;9p^pP2uVflg)jsovw9A?Ne1i=-3(CBV8$ZcFC?d!YS2q6xm zbNtEzh(QytbUy>=@6v6L*&d(EWp%G=boB0)`1iJCv)4O}zrnjG(G z5N2ijhNRhd-b8o;x1@PTcdXxpSXsW;H+nyV9}SAczO7Cxs+_k%qHiuRQfjmd?pQlQ zQ7q-$es!^;5ZF1HRS$t4Us)`=LUb;#MZoun|Wu+8z)B`m-95GlCu));W$9kOed`MK&D6(XGEzE-9?$cuhl27Z-!r+WnJtyBtytR zM#LXrXc)xvyHy7(oWAa7rcyu6?-^>BOvo&X?aZe+#>+blEOBAS zQ!K8wf~r%a_Y*buj{>x1x=k9WSPu|Px{N-VZcw$LlTKtaLXGeF8*{Tbo0qa!N%lTmH~bR&Cjzj-afG*ZlJp;Yg6L&W-`Ogu5krAg$pNdiP_n$jIfDaqM8DaqlJzttbW*F0@s zdL(3(X=Y#Xl9;2r5#a+YOWUXnaK$(imZVG`t4GtusQ(mGy+z2Nz+~8gzqp(T& zFj1z|S}2=D42UGW0Mp6l+T9#8?hkD)Hc0J7Zph!v_GrgDt%t&48R?}Cy&U3n*S0QGvJ~P> zdz7?ylUxm0sSdef>{3z_Crg(gz=UD@u~73A$*+f!B@_qfbP?Kz*G8L5t%OY&)pyMl zTtYaTlR7$F7v^warQv_Q)Ui9aru#zU7P*ndCSjmWA=W4d>IIRz;+B{1{uqQk&-KUE z806{Kucb3|DiG$mH<)z-184^*XvBFe)<= zuxI9wekNSLCE#t)x8he+0Yc?&{iD(F8r?Y^;vcOje-<5~pimnG#>Kd3W?Z3^+C6}= z%RUK#VC`vtBQ4F3lCS0`NHmmIt%Su3P~9tp+i@vkk^6L68`Cn4z(7L{7z?CQi>gN769} ztW#Krm^LU{Oh$zxqyc6fp)8|E+b~{mpspwYw;ZElj(u zov0^L@iHA9+NQ``Fx|5vC9dBKN_wc+ z{{W%S#}9QZ0gP3nvP)cL;h+}daKXiSqFL$31`AKbUN4#w{$M&V!%V31Bn|qTuNqx3 zBS2#$S;RXTLxZL=qfyt(_C%mqrmRz3M8+^iX7KK@Dzk+_6{zH__d?}NTe)9yKF#zA zj%stJpm2DgO$oq_XmS{KW6J%*aB~OkiTq4%j~;`9@ie>fFxVN`3O>@MY_B2rJ`%{q zRTqUU=Mt;Io%g$x#R$Ah<03pa!!b%T(PGuu2xz|(192lnFw~;b=arUBx|~XCUmwIs ziX5^2C-@$hE@un9NGw+Jx*%{Gv3FR;-2pUAhVf8YL zy2q{cBaOU;gtQ!rZRa`S3*8 zHg6Iyvp3_GFdKf@1>8RHH{T>n?J>kEF&&vg2h{+rnqw8r)r>NWuCRtaLUqp_H{<(C zvVRZUG~dJb35Y81iGjMEh+FVxRv4?7FEc9{HdWV*q4z*VsN`yD+ZM1uhKY*Y(1%LO zM+KVp7Oq#sXFg+CB6$n*7nJjwQN4)9QIS+NNG>C(<|k=p3x4c;fq<`Wtx7A%65^~= z%NDR?_&SUq87&QxtT3y5r1sY9xi%N!>{bqvo2`J<-Op#|Qh#iN_F6moa z8FEcod3;6EFJpkyl*6m5*I&5`UWI8t~BF3f*Xl90>Z)eUxawDljYar+p@%>Mw< ztBtSK0^SLVnKK0-a|1C(1#HyEznAuhXvI%sa1EH)EPA*Qb83qATwjRPDBC||L5Xhu zNgw3c7gZI-*QNEouUxX&6LL5(Fo@lP3@E^AV9Rx_6k9sC4uyH;y7ebzQHp=>-HvcdHiu2yszeGgT>B8dI5##?Qj;T%$6)MyB*4> zX5}sI=YhkvXD>m)D)U0)gh%rbJ*N7C^{s(U)Sr}c7tCQOiy+qXgHRF$LWraOP+*^7 zLbJ`lR0!@p)Bt}o0(l`~3rY*^mlpz91FkTwrTaZ^iANV|Th4 zf6)qZ{{ZD=5S?h$H|kuv$*4sG*naRts9S=Z)U;})MC?krV~K5Q1>xntgzEKrECBq6 z2b|}e^P{*eMHDW8b_@p{d~{QC4}b-qCX4Yt`w{n#_-FZ!`|Io@@_+8q8{m(*iSPU~ zNV)*Ss5;!Z@?#A~%`^zWEW+@6IM+DEpz&~(tH)q)VlEomxgD{GSYv~XSuj$_BB7#W zI~g6{66>p3=GT!NW7*ohn8IgGP3dY}xo+~^-*Wm9Dlh1e+?@eI2*x4OC;<|@L@tU} zILcI8;}(U$$6OV8X>l+~pnThy`VBsciZSgQuUSjx!paA3@u1s^Cf5uk1r-Oi+^DnJ zt1DDpSRb@KKm#^q333t19OC3e7_NU*91-gVJ2{ls8B*88I$Al+W1rqok3$#+O?ZIM zR0p24c#O<80*$CVN`rjvrgP>006h=r)Bbv8o|LlE)=+M0c8Wuav9H8Q$8)dj=sFk} zJG9N{$mR^#X+!(4h2`m}7l2=5=}wmh%55RxVtsej$o|z6Wn1*OKn1I#9aC^4w9WmRCqk?k|d;f?*j=?%@(Z<>jAq5c@mig+^*%T{Q{=mana zvq%f)f+GN=Ve=KU;r?OSbXxKk7({iMgojZ=p|*D@38oFa#4hVpf%ewI^V%c;ofv|O|OuAoQ(i1|(+HN#`#B2v^&z4OO zn9NPs{8&t-#$Jh{o>$C2lhv}`wrk%?-a}gJ#d*-~kx3{v{{Sgwy0WJsve^jMY%N(0 zQG!9J#$7Mq+3Tekn^d4RV2a)md(oSXb;_W^MzeGlB&*+T+ zx)At)3ZNRX*am{&P9cLA4BS|KSL7@!gPebH{{T7ngTFOT%x=NSIqG14rTwF&{#?sS zOULaMh1gz*irmN8E>ZTI6Uotnq2CVpPdG^@bUUHn2m$K2d5@cN^3Z3neWG;Eg}jRh z6oLI}25&Gg7YGD222hjCEndaTR$jTBTZdRB1(Sc7xj05tR zRpx)pXZ`dO0eq#`W>W)!lb7NH`=)rWEX@`TnE~8O^9W^IYRpXLdm;E`4?uSYHSWDY zI+rwDZAeT-%#INK$zqBkTFQoZM{<-qL4+U;PZc+RQzSx4tOtkKl$A!|1PCZSqt^{( zNT3RpZRr)ks89m_qY5;OBlLxnmD}WtYFD>9fu~ z@x=({I>%duVrJf)1K`qUfyeBbuLt)>{P*5Jt^JYz05SKD!~J)QYX1OTGkG9nzAeB04S@3;}>n?61A@LecFQMwS!5`<60Z z-*|`J1|)rn_JozDt*{)QwJ~ORgx5a{D0=Hc2rMxMIKE=Gwq~co22ya4v0;QMq!!Yq z*eKD-;gq#w6|)ENH6X`U;09a`GIUnojR5_|dolUBc5w`{s|?Gr1gt>1nS3z!4#dZJ zX{B!lz6d`t%I$@J1o!Hr-65EV{9zj_`|0lpq}6t=v?VZBld~Gd*Ah@M8ViBL4AHg& z?-^K?D$L#nYpkikTbS(~h^W}HR(B)a9_aT@QSSc$zyL9Z9)k)3PCyuaLlAd#o$!)L zuJfEb`biwI1QN>+z%YQ7up3q50}+5b8R;pn6&zXGXiDXZz)x<_9KF)(TDFu`nanz> z*QQ{sM|rRIEHUv9+;Sq9p;Pcgf7T-Z077BUpvNQ+a~I_ISdG_RHk%Aq{@kL=zry87 z-Bz_>kHr4^#m&L>o-@`ymwKTrZeS!GRQZ4$*@onFE-GyKso6 z)z;wjRD53Vcjgod*#609HRyAIqaZPjT#ja5!_`IaEs-thd#}U-0dY(Gept-qNTcBK zC~F-KB6GD$f&^G&Yp&7mj4=1dx^nsh404ZjGzqzEIF@jfs@4Y)($>_`VQ)cnNLT3$c=8gI@yt!G<&g_L-41N9G{6 zHb>@R9nAe0!VP7LF9P4haxtBJz_xQ?kWeoW=&uOj%YDWTN-{~VQO@oHe3Q;9`yjTi z-{2-0-j95HCSVOl%z|w zB-Veal6hN#hzwCYRLtYLRzvd3vkqeKxFw%{iDiBu`KN~B-=rszeN z7@>0!Lm9czM}Xd_e&tB+5EB=&4oCCRAfEcAm30dC3g4JTk;Paq3MQK9ExrT@a`PqM z99{@G{7(?aT{F(Lbbtt}NyfJ}FuEhR>}nF+%jFlf)pGW{To)VuZjXh&>~&F~Lg$U| zYz1A_Rs^=K&T*OU3a?~SE_90~?ri4Ax-iY9_xhpAa{5BQQ#o{UfuGt2V3+{96w7Z9 z;dqjO7`jY~4~Su$H7oQC8qUYm0DVnRFBFzu6O_wLSJ|r#0??~_8eaQh69%&Sd{Sih z(;m!zW+3=GU8^&DBXOqA7l83wWeel5%6h{5#K6379^SvWjtn$v4S>MtED2~1D8iBXG^^CpEZe2mPjshUmw1K zR12HG+wBlDx+7Ta1IQ{=c}Mf%nSqBj;#jtmg%;Qswxu|^HKTSPFNgBg?XKqVp= z67-f_Ur~r`swty(0-^L~9Aaf4qgOB41_kP1eWAPNs{R~j!PZVqd_^(*C-B5veYHc* zzF^BcOvlNmh^GpwId07w19__+Sm;n)b|Bv@06GP=D=Bmd;Hi!~k2?TnyG!=ADxRr^ z;f4vGUoQK(EoXSlrACFQHqPmfwx4iB0t7eK&4DqKU`0V%zjee5wiPeP?7}r>DBoR( zsjo~~k@g^{wey(tmoS}v(0XU#hR4$k$9JlLtrHLfc4pdUQU$=Zr9mzIp|(e5kCprg z&&hC!(Ve0<@eIbVHKtJfIHni7gj#GlhRiADil|vxr5bjSw-wlfJlkHilzL|we;drO^TfXqS7x3G<4MkVDa)9zoQ zV%Bbggn?@b17<&KE&?(osKzwa{jK_pr9fTamzV42l{OVu-Zf(u!m*jW@RLC>nOXO1%Tcad|Vq+VTBFqEd^}T$ic~Ex5QQ49*98z*6*~HL3BQ#!1R)v zS9}WbA1kFp_Eg0HjuzQa4mkn#f(fZ+AokzHd0tL>L1{qppYybT$`8$f;DVwjKz*^g zhHBd!wq}50Qy2p%;)TL3R(EBaw~h)V8Q|(*ms=285u9<49)UDIMF*wkWS)q#p(N}mRZP-0 zXh$$U#Y~oW_8a+|1LbG=x$!lA2lWH@#%H0y6YR%0LAoD!xqJ$^q7~PjKMU(?ZqS_r z13xn}E>-r89c@Xdy4z64)W2=BNqiGs`>(peD9+XV!3=YJe|Bi2upfeBIx5M=0i+?& zgsD=cbg5FM5dc`=klFSQmGoCbtxK%ky-cidLVdQe{tA^UR0w|y^}j_*>0J_3NRcLZ z`4cf-Gwmvq@}NpBUsz*|M$#bWJ`aoXGLq8X$Rxlh42Y4yYXA%l@vy3u)F41KTQ!Ub z5G6pB(xpn3DpanODpanO(xr5+wGOrXWY_qg;i+9~twu47dQ?eLr9_ATB84ucrH0V{ rHtBQbPuvCVI8jul#0V0OWx#s81UqyWt{NMlC-m$r+ literal 0 HcmV?d00001 diff --git a/backend/app/services/schemas.py b/backend/app/services/schemas.py new file mode 100644 index 00000000..15808fb1 --- /dev/null +++ b/backend/app/services/schemas.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel +from typing import Dict, Optional + +class ImageUploadResponse(BaseModel): + success: bool + urls: Dict[str, str] # {"thumbnail": "url", "medium": "url", "full": "url"} + message: str + processing_id: Optional[str] = None \ No newline at end of file diff --git a/backend/app/services/storage.py b/backend/app/services/storage.py new file mode 100644 index 00000000..1fbfb986 --- /dev/null +++ b/backend/app/services/storage.py @@ -0,0 +1,406 @@ +import os +import io +import firebase_admin +import re +import datetime +import asyncio +import subprocess +from typing import Dict, Union +from pathlib import Path +from fastapi import UploadFile +import uuid +from uuid import uuid4 +from PIL import Image, UnidentifiedImageError +from urllib.parse import urlparse + +from app.services.image_processor import process_image +from app.config import logger, settings +from firebase_admin import credentials, storage +from firebase_admin.exceptions import FirebaseError + +from dotenv import load_dotenv +load_dotenv() + +try: + use_emulator = settings.use_firebase_emulator + service_account_path = settings.firebase_service_account_path + project_id = settings.firebase_project_id + + if use_emulator: + os.environ["FIREBASE_STORAGE_EMULATOR_HOST"] = "http://127.0.0.1:4000" + logger.info("Using Firebase Storage Emulator") + + + if not project_id: + raise ValueError("FIREBASE_PROJECT_ID environment variable is not set.") + + if not use_emulator and (not service_account_path or not os.path.exists(service_account_path)): + raise FileNotFoundError("Service account path is missing or invalid.") + + + # Prevent multiple initializations + try: + firebase_admin.get_app() + logger.info("Firebase already initialized.") + except ValueError: + if use_emulator: + # No credentials, no bucket + firebase_admin.initialize_app(options={"projectId": project_id}) + logger.info("Firebase initialized with emulator (no credentials).") + else: + cred = credentials.Certificate(service_account_path) + firebase_admin.initialize_app(cred, { + 'storageBucket': f"{project_id}.firebasestorage.app" + }) + logger.info("Firebase initialized with service account file.") + +except Exception as e: + logger.exception(f"Firebase initialization failed: {e}") + raise +""" +This Storage Class implements the following functions: +1. upload_image_workflow +2. validate_file +3. process_image +4. upload_to_firebase +5. delete_image +""" + +PIL_FORMAT_TO_MIME = { + "JPEG": "image/jpeg", + "PNG": "image/png", + "WEBP": "image/webp", +} + + +MAX_IMAGE_PIXELS = settings.MAX_IMAGE_PIXELS +LOAD_TRUNCATED_IMAGES = settings.LOAD_TRUNCATED_IMAGES +SIGNED_URL_EXPIRY_SECONDS = settings.SIGNED_URL_EXPIRY_SECONDS +CLAMAV_ENABLED = settings.CLAMAV_ENABLED #Default False +BASE_QUARANTINE_DIR = Path(__file__).resolve().parent / "quarantine" + +class FirebaseStorageService: + def __init__(self): + pass + + def get_bucket(self) -> str: + return f"{project_id}.firebasestorage.app" + + def generate_secure_file_path(self, entity_id: str, folder: str, filename: str) -> str: + """ + Generates a unique file path for Firebase Storage + Ex: users//.webp or groups//.webp + """ + unique_id = uuid4().hex + filename = filename or "" + ext = os.path.splitext(filename)[-1].lower() or ".webp" + safe_filename = f"{unique_id}{ext}" + return f"{folder}/{entity_id}/{safe_filename}" + + def extract_path_from_url(self, url: str) -> str: + """ + Extracts the Firebase Storage blob path from a full public URL. + + """ + parsed_url = urlparse(url) + path = parsed_url.path.lstrip("/") + + # Remove the bucket name if included in path + if path.startswith(f"{project_id}.firebasestorage.app/"): + path = path.replace(f"{project_id}.firebasestorage.app/", "", 1) + + # Strip the _.webp suffix (e.g., _thumbnail.webp, _medium.webp, etc.) + path = re.sub(r"_(thumbnail|medium|full)\.webp$", "", path) + + return path + + '''async def _scan_with_clamav_async(self, content: bytes) -> bool: + """ + Optional inline ClamAV scan. Returns: + - True => scanned and clean + - False => infected or scanning failed + - None => scanning not attempted (CLAMAV_ENABLED False) + """ + if not CLAMAV_ENABLED: + logger.debug("ClamAV inline scanning disabled.") + return None + + try: + import tempfile + + def run_scan(tmp_path: str): + completed = subprocess.run( + ["clamscan", "--no-summary", tmp_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=False, + ) + return completed + + with tempfile.NamedTemporaryFile(delete=True) as tmp: + tmp.write(content) + tmp.flush() + completed = await asyncio.to_thread(run_scan, tmp.name) + + stdout = completed.stdout or "" + # clamscan returns non-zero when infected, but we check text for robustness + if "FOUND" in stdout: + logger.warning("ClamAV detected malware in uploaded file.") + return False + if completed.returncode not in (0, 1): + logger.exception(f"ClamAV returned unexpected code {completed.returncode}. stdout: {stdout}") + return False + + logger.debug("ClamAV inline scan passed.") + return True + + except Exception as e: + logger.exception(f"ClamAV scan failed: {e}") + return False''' + + async def validate_file(self, file: UploadFile) -> bool: + """ + Validates file type (JPEG, PNG, WebP) and max size (5MB). + Uses pillow for validation. + """ + try: + content = await file.read() + await file.seek(0) + + if len(content) > settings.MAX_FILE_SIZE: + logger.warning("File size larger than 5MB.") + return False + + try: + img = Image.open(io.BytesIO(content)) + img.load() + except UnidentifiedImageError: + logger.warning("Failed to identify image format with Pillow.") + return False + except Exception: + logger.warning("Pillow failed to fully load the image (possibly corrupt or too large).") + return False + + img_format = img.format + if img_format is None or PIL_FORMAT_TO_MIME.get(img_format) not in ["image/jpeg", "image/png", "image/webp"]: + logger.warning(f"Rejected image due to invalid format: {img_format}") + return False + + if img.width * img.height > MAX_IMAGE_PIXELS: + logger.warning("Image pixel count exceeds allowed threshold.") + return False + + return True + + except Exception as e: + logger.exception(f"File validation failed: {e}") + return False + + + + async def save_to_quarantine(self,file: Union[UploadFile, bytes],folder: str, + entity_id: str,original_filename: str) -> str: + """ + Saves a file to the local quarantine directory for scanning/processing later. + Uses the same secure naming convention as generate_secure_file_path. + + Returns: + str: Full path to the saved file. + """ + # Generate secure name + secure_relative_path = self.generate_secure_file_path( + entity_id=entity_id, + folder=folder, + filename=original_filename + ) + + # Convert storage path to local quarantine path + target_path = BASE_QUARANTINE_DIR / secure_relative_path + target_path.parent.mkdir(parents=True, exist_ok=True) + + # Handle UploadFile or raw bytes + try: + if isinstance(file, UploadFile): + content = await file.read() + elif isinstance(file, bytes): + content = file + else: + raise TypeError( + f"Invalid file type: {type(file)}. Must be UploadFile or bytes." + ) + except Exception as e: + logger.exception(f"Failed to read file content: {e}") + raise + + # Save locally + try: + with open(target_path, "wb") as f: + f.write(content) + except Exception as e: + logger.exception(f"Failed to write file to quarantine: {e}") + raise RuntimeError("Failed to save file to quarantine.") from e + + return str(target_path) + + + async def generate_signed_url(self, blob, expires_seconds: int = SIGNED_URL_EXPIRY_SECONDS) -> str: + """ + Generate a signed URL for a blob (works with google-cloud-storage Blob). + """ + try: + expiry = datetime.timedelta(seconds=expires_seconds) + + def gen(): + # google-cloud-storage Blob.generate_signed_url + return blob.generate_signed_url(expiration=expiry, method="GET") + + url = await asyncio.to_thread(gen) + return url + except Exception as e: + logger.exception("Failed to generate signed URL.") + raise RuntimeError("Failed to generate signed URL.") from e + + + + async def upload_to_firebase(self, processed_images: Dict[str, bytes], file_path: str) -> Dict[str, str]: + """ + Uploads processed images (thumbnail/medium/full) to a public path, returns signed URLs. + Does NOT call blob.make_public(). + """ + urls = {} + try: + bucket_name = self.get_bucket() + bucket = storage.bucket(bucket_name) + for size, content in processed_images.items(): + # Ensure file names end with .webp + blob_path = f"{file_path}_{size}.webp" + blob = bucket.blob(blob_path) + blob.upload_from_string(content, content_type="image/webp") + + if use_emulator: + url = f"http://127.0.0.1:4000/storage/{bucket_name}/o/{blob_path.replace('/', '%2F')}?alt=media" + else: + url = await self.generate_signed_url(blob, expires_seconds=SIGNED_URL_EXPIRY_SECONDS) + + urls[size] = url + logger.info(f"Uploaded image (signed): {blob_path}") + return urls + + except FirebaseError as fe: + logger.exception("Firebase error during image upload.") + raise + except Exception as e: + logger.exception("Image upload to Firebase failed.") + raise RuntimeError("Failed to upload image to Firebase.") from e + + async def move_quarantine_to_public(self, quarantine_path: str, public_base_path: str) -> Dict[str, str]: + """ + Worker-friendly method: + - loads the quarantine file from local disk + - optionally runs ClamAV inline + - processes the image (re-encode + sizes) + - uploads processed images to public path + - deletes quarantine file + Returns signed URLs for the uploaded images. + """ + try: + if not os.path.exists(quarantine_path): + logger.error(f"Quarantine file not found: {quarantine_path}") + raise ValueError("Quarantine file not found.") + + # Read file bytes + with open(quarantine_path, "rb") as f: + content = f.read() + + '''# Inline scan if enabled else skipped + scan_result = await self._scan_with_clamav_async(content) + if scan_result is False: + try: + os.remove(quarantine_path) + except Exception: + logger.warning("Failed to delete infected quarantine file.") + logger.warning("Quarantine file marked as infected and removed.") + raise ValueError("File failed virus scan.")''' + + # Process the image + processed = await self.process_image(content) + + # Upload processed images to Firebase public path + urls = await self.upload_to_firebase(processed, public_base_path) + + # Cleanup local quarantine file + try: + os.remove(quarantine_path) + except Exception: + logger.warning("Failed to delete quarantine file after processing.") + + return urls + + except Exception as e: + logger.exception("Failed to move file from quarantine to public.") + raise + + async def process_image(self, file_content: bytes) -> Dict[str, bytes]: + """ + Generates 3 resized, optimized images (thumbnail, medium, full) in WebP format. + """ + try: + return await process_image(file_content) # Calls image_proccessor.py's process_image + except Exception as e: + logger.exception("Image processing failed.") + raise RuntimeError("Failed to process image.") from e + + async def delete_image(self, file_path: str) -> bool: + """ + Deletes all size variants (thumbnail, medium, full) from Firebase Storage. + """ + try: + bucket_name = self.get_bucket() + bucket = storage.bucket(bucket_name) + for size in ["thumbnail", "medium", "full"]: + blob_path = f"{file_path}_{size}.webp" + blob = bucket.blob(blob_path) + blob.delete() + logger.info(f"Deleted image: {blob_path}") + return True + + except Exception as e: + logger.exception(f"Failed to delete images at {file_path}") + return False + + async def upload_image_workflow(self, file: UploadFile, folder: str, entity_id: str) -> Dict[str, str]: + """ + Workflow: + - Validate (magic bytes + Pillow checks) + - Save raw to quarantine + - If ClamAV enabled: scan + process inline + - If ClamAV disabled: leave in quarantine for later processing + """ + try: + if not await self.validate_file(file): + logger.error("Invalid file provided to upload_image_workflow.") + raise ValueError("Invalid file type or size.") + + content = await file.read() + file_path = self.generate_secure_file_path(entity_id, folder, file.filename) + logger.info(f"Generated secure path: {file_path}") + + quarantine_path = await self.save_to_quarantine(content, folder, entity_id, file.filename) + + if CLAMAV_ENABLED: + # Original path: scan + process inline + urls = await self.move_quarantine_to_public(quarantine_path, file_path) + logger.info(f"Upload workflow successful for {folder} entity {entity_id}") + return urls + else: + # Directly process image and upload (no scanning) + logger.info("ClamAV disabled: processing image directly.") + urls = await self.move_quarantine_to_public(quarantine_path, file_path) + return urls + + except Exception as e: + logger.exception("Upload image workflow failed.") + raise RuntimeError("Image upload failed.") from e + +storage_service = FirebaseStorageService() \ No newline at end of file diff --git a/backend/app/user/routes.py b/backend/app/user/routes.py index 6f0b283f..4f32c126 100644 --- a/backend/app/user/routes.py +++ b/backend/app/user/routes.py @@ -4,10 +4,13 @@ from app.user.schemas import ( DeleteUserResponse, UserProfileResponse, - UserProfileUpdateRequest, + UserProfileUpdateRequest ) +from app.services.schemas import ImageUploadResponse from app.user.service import user_service -from fastapi import APIRouter, Depends, HTTPException, status +from app.services.storage import storage_service +from app.config import logger +from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, BackgroundTasks router = APIRouter(prefix="/users", tags=["User"]) @@ -55,3 +58,56 @@ async def delete_user_account(current_user: Dict[str, Any] = Depends(get_current return DeleteUserResponse( success=True, message="User account scheduled for deletion." ) + +@router.post("/me/avatar", response_model=ImageUploadResponse) +async def upload_user_avatar(file: UploadFile = File(...),background_tasks: BackgroundTasks = BackgroundTasks(), + current_user: dict = Depends(get_current_user)): + user_id = str(current_user["_id"]) + + try: + # Validate, process, upload + urls = await storage_service.upload_image_workflow( + file=file, + folder="users", + entity_id=user_id + ) + except ValueError as ve: + raise HTTPException(status_code=400, detail=str(ve)) + except FileNotFoundError: + raise HTTPException(status_code=404, detail="Storage location not found.") + except Exception as e: + logger.exception(f"Unexpected error during avatar upload for user {user_id}") + raise HTTPException(status_code=500, detail="Image upload failed due to an internal error.") + + # Update DB in background + background_tasks.add_task(user_service.update_user_avatar_url, user_id, urls.get("full")) + + return ImageUploadResponse( + success=True, + urls=urls, + message="Avatar uploaded successfully." + ) + + +@router.delete("/me/avatar", response_model=DeleteUserResponse) +async def delete_user_avatar(current_user: dict = Depends(get_current_user), + background_tasks: BackgroundTasks = BackgroundTasks()): + + user_id = str(current_user["_id"]) + + user = await user_service.get_user_by_id(user_id) + if not user: + raise HTTPException(status_code=404, detail="User not found") + + image_url = user.get("imageUrl") + + if not image_url: + raise HTTPException(status_code=404, detail="User avatar not found") + + file_path = storage_service.extract_path_from_url(image_url) + if not await storage_service.delete_image(file_path): + raise HTTPException(status_code=500, detail="Failed to delete image") + + background_tasks.add_task(user_service.update_user_avatar_url, user_id, None) + + return DeleteUserResponse(success=True, message="User avatar deleted successfully.") diff --git a/backend/app/user/service.py b/backend/app/user/service.py index 8ce83dd5..4aef569f 100644 --- a/backend/app/user/service.py +++ b/backend/app/user/service.py @@ -1,5 +1,6 @@ from datetime import datetime, timezone from typing import Any, Dict, Optional +from fastapi import UploadFile from app.config import logger from app.database import get_database @@ -92,5 +93,12 @@ async def delete_user(self, user_id: str) -> bool: result = await db.users.delete_one({"_id": obj_id}) return result.deleted_count > 0 + async def update_user_avatar_url(self, user_id: str, image_url: str) -> bool: + db = self.get_db() + result = await db.users.update_one( + {"_id": ObjectId(user_id)}, + {"$set": {"imageUrl": image_url}} + ) + return result.modified_count == 1 -user_service = UserService() +user_service = UserService() \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index 14825b14..4fd8e542 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -18,3 +18,5 @@ mongomock-motor pytest-env pytest-cov pytest-mock +pillow>=10.0.0 +python-magic>=0.4.27 \ No newline at end of file diff --git a/backend/tests/groups/test_groups_service.py b/backend/tests/groups/test_groups_service.py index 7548b86a..426818cd 100644 --- a/backend/tests/groups/test_groups_service.py +++ b/backend/tests/groups/test_groups_service.py @@ -477,3 +477,178 @@ def test_transform_group_document_partial_input(self): assert result["name"] == "Partial Group" assert result["currency"] == "USD" # default fallback assert result["members"] == [] # default fallback + + # Test cases for ensure_user_in_group function + @pytest.mark.asyncio + async def test_ensure_user_in_group_success(self): + """Test successful user membership validation""" + mock_db = AsyncMock() + mock_collection = AsyncMock() + mock_db.groups = mock_collection + + group_id = "642f1e4a9b3c2d1f6a1b2c3d" + user_id = "user123" + + expected_group = { + "_id": ObjectId(group_id), + "name": "Test Group", + "members": [ + { + "userId": user_id, + "role": "member", + "joinedAt": "2023-01-01T00:00:00Z", + } + ], + } + + mock_collection.find_one.return_value = expected_group + + with patch.object(self.service, "get_db", return_value=mock_db): + result = await self.service.ensure_user_in_group(group_id, user_id) + + assert result == expected_group + mock_collection.find_one.assert_called_once_with({ + "_id": ObjectId(group_id), + "members": {"$elemMatch": {"userId": user_id}} + }) + + @pytest.mark.asyncio + async def test_ensure_user_in_group_invalid_group_id_format(self): + """Test with invalid ObjectId format""" + mock_db = AsyncMock() + + invalid_group_id = "invalid_object_id" + user_id = "user123" + + with patch.object(self.service, "get_db", return_value=mock_db): + with pytest.raises(HTTPException) as exc_info: + await self.service.ensure_user_in_group(invalid_group_id, user_id) + + assert exc_info.value.status_code == 400 + assert "Invalid group ID format" in str(exc_info.value.detail) + + @pytest.mark.asyncio + async def test_ensure_user_in_group_user_not_member(self): + """Test when user is not a member of the group""" + mock_db = AsyncMock() + mock_collection = AsyncMock() + mock_db.groups = mock_collection + + group_id = "642f1e4a9b3c2d1f6a1b2c3d" + user_id = "user123" + + mock_collection.find_one.return_value = None + + with patch.object(self.service, "get_db", return_value=mock_db): + with pytest.raises(HTTPException) as exc_info: + await self.service.ensure_user_in_group(group_id, user_id) + + assert exc_info.value.status_code == 403 + assert "You are not a member of this group" in str(exc_info.value.detail) + + # Test cases for update_group_image_url function + + @pytest.mark.asyncio + async def test_update_group_image_url_success(self): + """Test successful group image URL update""" + mock_db = AsyncMock() + mock_collection = AsyncMock() + mock_db.groups = mock_collection + + group_id = "642f1e4a9b3c2d1f6a1b2c3d" + image_url = "https://example.com/image.jpg" + + # Mock successful update (1 document modified) + mock_result = AsyncMock() + mock_result.modified_count = 1 + mock_collection.update_one.return_value = mock_result + + with patch.object(self.service, "get_db", return_value=mock_db): + result = await self.service.update_group_image_url(group_id, image_url) + + assert result is True + mock_collection.update_one.assert_called_once_with( + {"_id": ObjectId(group_id)}, + {"$set": {"imageUrl": image_url}} + ) + + @pytest.mark.asyncio + async def test_update_group_image_url_no_document_modified(self): + """Test when no document is modified (group not found)""" + mock_db = AsyncMock() + mock_collection = AsyncMock() + mock_db.groups = mock_collection + + group_id = "642f1e4a9b3c2d1f6a1b2c3d" + image_url = "https://example.com/image.jpg" + + # Mock no documents modified + mock_result = AsyncMock() + mock_result.modified_count = 0 + mock_collection.update_one.return_value = mock_result + + with patch.object(self.service, "get_db", return_value=mock_db): + result = await self.service.update_group_image_url(group_id, image_url) + + assert result is False + + @pytest.mark.asyncio + async def test_update_group_image_url_invalid_group_id_format(self): + """Test with invalid ObjectId format""" + mock_db = AsyncMock() + + invalid_group_id = "invalid_object_id" + image_url = "https://example.com/image.jpg" + + with patch.object(self.service, "get_db", return_value=mock_db): + result = await self.service.update_group_image_url(invalid_group_id, image_url) + + assert result is False + + @pytest.mark.asyncio + async def test_update_group_image_url_empty_image_url(self): + """Test updating with empty image URL""" + mock_db = AsyncMock() + mock_collection = AsyncMock() + mock_db.groups = mock_collection + + group_id = "642f1e4a9b3c2d1f6a1b2c3d" + image_url = "" + + # Mock successful update + mock_result = AsyncMock() + mock_result.modified_count = 1 + mock_collection.update_one.return_value = mock_result + + with patch.object(self.service, "get_db", return_value=mock_db): + result = await self.service.update_group_image_url(group_id, image_url) + + assert result is True + mock_collection.update_one.assert_called_once_with( + {"_id": ObjectId(group_id)}, + {"$set": {"imageUrl": image_url}} + ) + + @pytest.mark.asyncio + async def test_update_group_image_url_none_image_url(self): + """Test updating with None image URL""" + mock_db = AsyncMock() + mock_collection = AsyncMock() + mock_db.groups = mock_collection + + group_id = "642f1e4a9b3c2d1f6a1b2c3d" + image_url = None + + # Mock successful update + mock_result = AsyncMock() + mock_result.modified_count = 1 + mock_collection.update_one.return_value = mock_result + + with patch.object(self.service, "get_db", return_value=mock_db): + result = await self.service.update_group_image_url(group_id, image_url) + + assert result is True + mock_collection.update_one.assert_called_once_with( + {"_id": ObjectId(group_id)}, + {"$set": {"imageUrl": image_url}} + ) \ No newline at end of file diff --git a/backend/tests/services/__init__.py b/backend/tests/services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/tests/services/test_image_processor.py b/backend/tests/services/test_image_processor.py new file mode 100644 index 00000000..0d579a1a --- /dev/null +++ b/backend/tests/services/test_image_processor.py @@ -0,0 +1,94 @@ +import pytest +from io import BytesIO +from PIL import Image +from app.services import image_processor as ip + + +def make_test_image(fmt="PNG", size=(200, 100), color=(255, 0, 0)): + """Utility to generate an in-memory test image.""" + img = Image.new("RGB", size, color) + buf = BytesIO() + img.save(buf, format=fmt) + return buf.getvalue() + + +def test_strip_exif_returns_new_image(): + """strip_exif should return a new image with identical pixels but no metadata.""" + img = Image.new("RGB", (10, 10), (123, 222, 111)) + clean = ip.strip_exif(img) + assert clean.size == img.size + assert list(clean.getdata()) == list(img.getdata()) + assert clean is not img + + +@pytest.mark.parametrize("fmt", ["jpeg", "png", "webp"]) +def test_validate_magic_bytes_valid(fmt): + """validate_magic_bytes should pass for JPEG, PNG, and WebP formats.""" + content = make_test_image(fmt.upper()) + ip.validate_magic_bytes(content) # should not raise + + +@pytest.mark.parametrize("content", [b"notanimage", make_test_image("GIF")]) +def test_validate_magic_bytes_invalid(content): + """validate_magic_bytes should raise ValueError for unsupported formats or random bytes.""" + with pytest.raises(ValueError, match="Invalid or unsupported image type"): + ip.validate_magic_bytes(content) + + +def test_resize_image_square_padding(): + """resize_image should pad non-square images to square when requested.""" + img = Image.new("RGB", (100, 50), (0, 0, 255)) + resized = ip.resize_image(img, (150, 150)) + assert resized.size == (150, 150) + center_pixel = resized.getpixel((75, 75)) + assert center_pixel == (0, 0, 255) + + +def test_add_watermark_places_watermark(): + """add_watermark should overlay watermark in bottom-right of image.""" + base = Image.new("RGBA", (200, 200), (255, 255, 255, 255)) + watermark = Image.new("RGBA", (50, 50), (0, 255, 0, 128)) + out = ip.add_watermark(base, watermark) + px = out.getpixel((190, 190)) + assert px[1] > 200 # green applied + +@pytest.mark.asyncio +async def test_process_image_happy_path(): + """process_image should return resized WebP images for valid input.""" + content = make_test_image("PNG", size=(400, 400)) + results = await ip.process_image(content) + assert set(results.keys()) == {"thumbnail", "medium", "full"} + assert all(len(data) > 0 for data in results.values()) + + +@pytest.mark.asyncio +async def test_process_image_invalid_format(): + """process_image should raise RuntimeError for unsupported image formats (e.g. GIF).""" + bad_content = make_test_image("GIF") + with pytest.raises(RuntimeError, match="Image processing failed"): + await ip.process_image(bad_content) + + +@pytest.mark.asyncio +async def test_process_image_corrupted_bytes(): + """process_image should raise RuntimeError when given corrupted bytes.""" + with pytest.raises(RuntimeError, match="Image processing failed."): + await ip.process_image(b"notanimage") + + +@pytest.mark.asyncio +async def test_process_image_with_watermark(monkeypatch, tmp_path): + """process_image should apply watermark if WATERMARK_PATH is set.""" + watermark_file = tmp_path / "wm.png" + Image.new("RGBA", (30, 30), (0, 255, 0, 255)).save(watermark_file, "PNG") + + monkeypatch.setattr(ip, "WATERMARK_PATH", str(watermark_file)) + + content = make_test_image("PNG", size=(300, 300), color=(255, 255, 255)) + results = await ip.process_image(content) + + img = Image.open(BytesIO(results["thumbnail"])) + px = img.getpixel((140, 140)) # bottom-right area + assert px != (255, 255, 255) # watermark altered pixel + + monkeypatch.setattr(ip, "WATERMARK_PATH", None) # reset global diff --git a/backend/tests/services/test_storage_service.py b/backend/tests/services/test_storage_service.py new file mode 100644 index 00000000..9054f2ba --- /dev/null +++ b/backend/tests/services/test_storage_service.py @@ -0,0 +1,537 @@ +import io +import pytest +import app.services.storage as storage_module +from uuid import UUID +from PIL import Image +from pathlib import Path +from fastapi import UploadFile +from unittest.mock import AsyncMock, patch, MagicMock, mock_open +from firebase_admin.exceptions import FirebaseError + +'''# Apply monkeypatch as a fixture +@pytest.fixture(autouse=True) +def patch_firebase(monkeypatch): + monkeypatch.setattr("app.services.storage.storage", object()) + monkeypatch.setattr("app.services.storage.project_id", "dummy-project") + + monkeypatch.setattr(storage_module, "uuid4", lambda: UUID("12345678123456781234567812345678")) + + yield ''' + +from app.services.storage import FirebaseStorageService + +service = FirebaseStorageService() + +@pytest.fixture +def fixed_uuid(monkeypatch): + monkeypatch.setattr("app.services.storage.uuid4", lambda: UUID("12345678123456781234567812345678")) + yield + +def test_generate_secure_file_path_with_extension(fixed_uuid): + result = service.generate_secure_file_path("user123", "users", "image.png") + assert result.startswith("users/user123/12345678123456781234567812345678") + assert result.endswith(".png") + +def test_generate_secure_file_path_without_extension(fixed_uuid): + result = service.generate_secure_file_path("user123", "users", "image") + assert result.endswith(".webp") + +def test_generate_secure_file_path_empty_entity(fixed_uuid): + result = service.generate_secure_file_path("", "users", "image.png") + assert result.startswith("users//12345678123456781234567812345678") + assert result.endswith(".png") + +def test_generate_secure_file_path_empty_folder(fixed_uuid): + result = service.generate_secure_file_path("user123", "", "image.png") + assert result.startswith("/user123/12345678123456781234567812345678") + assert result.endswith(".png") + +def test_generate_secure_file_path_empty_filename(fixed_uuid): + result = service.generate_secure_file_path("user123", "users", "") + assert result.endswith(".webp") + +def test_generate_secure_file_path_none_filename(fixed_uuid): + result = service.generate_secure_file_path("user123", "users", None) + assert result.endswith(".webp") + assert "12345678123456781234567812345678.webp" in result + +def test_extract_path_from_url_normal(): + """Extracts path from a normal Firebase URL without suffix.""" + url = "https://dummy-project.firebasestorage.app/o/users%2Fuser123%2Ffile.webp?alt=media" + result = service.extract_path_from_url(url) + assert result == "o/users%2Fuser123%2Ffile.webp" + +def test_extract_path_from_url_with_suffixes(): + """Removes _thumbnail.webp, _medium.webp, and _full.webp suffixes from URL.""" + for suffix in ["_thumbnail.webp", "_medium.webp", "_full.webp"]: + url = f"https://dummy-project.firebasestorage.app/o/users%2Fuser123%2Ffile{suffix}?alt=media" + result = service.extract_path_from_url(url) + assert result == "o/users%2Fuser123%2Ffile" + +def test_extract_path_from_url_already_clean(): + """Returns path unchanged if no bucket name or suffix is present.""" + url = "https://example.com/path/to/file.webp" + result = service.extract_path_from_url(url) + assert result == "path/to/file.webp" + +def test_extract_path_from_url_malformed(): + """Handles malformed URLs gracefully by returning whatever path can be parsed.""" + url = "not a valid url" + result = service.extract_path_from_url(url) + # urlparse still returns a path string, even if malformed + assert result == "not a valid url" + +def test_extract_path_from_url_unexpected_suffix(): + """Leaves URL unchanged if suffix is not thumbnail/medium/full.""" + url = "https://dummy-project.firebasestorage.app/o/users%2Fuser123%2Ffile_large.webp?alt=media" + result = service.extract_path_from_url(url) + assert result == "o/users%2Fuser123%2Ffile_large.webp" + +@pytest.mark.asyncio +async def test_validate_file_valid_png(monkeypatch): + """Returns True for a valid PNG file under max size and pixel limit.""" + # Create a small in-memory PNG + img_bytes = io.BytesIO() + img = Image.new("RGB", (10, 10)) + img.save(img_bytes, format="PNG") + img_bytes = img_bytes.getvalue() + + # Mock UploadFile + file = AsyncMock(spec=UploadFile) + file.read.return_value = img_bytes + file.seek.return_value = None + + # Patch limits + monkeypatch.setattr("app.services.storage.settings", type("obj", (), {"MAX_FILE_SIZE": 5*1024*1024})) + monkeypatch.setattr("app.services.storage.settings", type("obj", (), {"MAX_FILE_SIZE": 5*1024*1024, "MAX_IMAGE_PIXELS": 10000})) + + result = await service.validate_file(file) + assert result is True + + +@pytest.mark.asyncio +async def test_validate_file_oversized(monkeypatch): + """Returns False for files exceeding MAX_FILE_SIZE.""" + img_bytes = b"x" * (5*1024*1024) # 5MB + + file = AsyncMock(spec=UploadFile) + file.read.return_value = img_bytes + file.seek.return_value = None + + monkeypatch.setattr("app.services.storage.settings", type("obj", (), {"MAX_FILE_SIZE": 5*1024*1024})) + + result = await service.validate_file(file) + assert result is False + + +@pytest.mark.asyncio +async def test_validate_file_corrupt_image(monkeypatch): + """Returns False if Pillow cannot identify the image (corrupt).""" + file = AsyncMock(spec=UploadFile) + file.read.return_value = b"not an image" + file.seek.return_value = None + + result = await service.validate_file(file) + assert result is False + + +@pytest.mark.asyncio +async def test_validate_file_unsupported_format(monkeypatch): + """Returns False if image format is not JPEG/PNG/WebP.""" + # Create an image with BMP format (unsupported) + img_bytes = io.BytesIO() + img = Image.new("RGB", (10, 10)) + img.save(img_bytes, format="BMP") + img_bytes = img_bytes.getvalue() + + file = AsyncMock(spec=UploadFile) + file.read.return_value = img_bytes + file.seek.return_value = None + + monkeypatch.setattr("app.services.storage.PIL_FORMAT_TO_MIME", {"BMP": "image/bmp"}) + + result = await service.validate_file(file) + assert result is False + + +@pytest.mark.asyncio +async def test_validate_file_too_many_pixels(monkeypatch): + """Returns False if image width*height exceeds MAX_IMAGE_PIXELS.""" + img_bytes = io.BytesIO() + img = Image.new("RGB", (200, 200)) # 40,000 pixels + img.save(img_bytes, format="PNG") + img_bytes = img_bytes.getvalue() + + file = AsyncMock(spec=UploadFile) + file.read.return_value = img_bytes + file.seek.return_value = None + + monkeypatch.setattr("app.services.storage.MAX_IMAGE_PIXELS", 1000) + + result = await service.validate_file(file) + assert result is False + +@pytest.mark.asyncio +async def test_save_to_quarantine_uploadfile(monkeypatch): + """Saves an UploadFile to the quarantine directory using a secure path.""" + file_content = b"dummy content" + file = AsyncMock(spec=UploadFile) + file.read.return_value = file_content + + # Patch generate_secure_file_path to return a fixed filename + monkeypatch.setattr(service, "generate_secure_file_path", lambda *args, **kwargs: "folder/entity/file.webp") + + # Patch BASE_QUARANTINE_DIR at the module level + mock_path = MagicMock(spec=Path) + mock_path.__truediv__.return_value = mock_path # for / + monkeypatch.setattr("app.services.storage.BASE_QUARANTINE_DIR", mock_path) + mock_path.parent.mkdir.return_value = None + + m_open = mock_open() + with patch("builtins.open", m_open): + result = await service.save_to_quarantine(file, "folder", "entity", "file.webp") + + assert result == str(mock_path) + m_open.assert_called_once_with(mock_path, "wb") + m_open().write.assert_called_once_with(file_content) + + +@pytest.mark.asyncio +async def test_save_to_quarantine_bytes(monkeypatch): + """Saves raw bytes to the quarantine directory using a secure path.""" + file_content = b"raw bytes content" + + monkeypatch.setattr(service, "generate_secure_file_path", lambda *args, **kwargs: "folder/entity/file.webp") + + # Patch BASE_QUARANTINE_DIR at the module level + mock_path = MagicMock(spec=Path) + mock_path.__truediv__.return_value = mock_path + monkeypatch.setattr("app.services.storage.BASE_QUARANTINE_DIR", mock_path) + mock_path.parent.mkdir.return_value = None + + m_open = mock_open() + with patch("builtins.open", m_open): + result = await service.save_to_quarantine(file_content, "folder", "entity", "file.webp") + + assert result == str(mock_path) + m_open.assert_called_once_with(mock_path, "wb") + m_open().write.assert_called_once_with(file_content) + +@pytest.mark.asyncio +async def test_save_to_quarantine_invalid_type(monkeypatch): + """Raises TypeError if file is neither UploadFile nor bytes.""" + monkeypatch.setattr(service, "generate_secure_file_path", lambda *a, **kw: "folder/entity/file.webp") + with pytest.raises(TypeError, match="Invalid file type"): + await service.save_to_quarantine(12345, "folder", "entity", "file.webp") + +@pytest.mark.asyncio +async def test_save_to_quarantine_read_failure(monkeypatch): + """Raises exception if UploadFile.read() fails.""" + file = AsyncMock(spec=UploadFile) + file.read.side_effect = Exception("Read failed") + + monkeypatch.setattr(service, "generate_secure_file_path", lambda *a, **kw: "folder/entity/file.webp") + monkeypatch.setattr("app.services.storage.BASE_QUARANTINE_DIR", MagicMock(spec=Path)) + + with pytest.raises(Exception, match="Read failed"): + await service.save_to_quarantine(file, "folder", "entity", "file.webp") + + +@pytest.mark.asyncio +async def test_save_to_quarantine_write_failure(monkeypatch): + """Raises RuntimeError if writing file fails.""" + file_content = b"dummy content" + file = AsyncMock(spec=UploadFile) + file.read.return_value = file_content + + monkeypatch.setattr(service, "generate_secure_file_path", lambda *a, **kw: "folder/entity/file.webp") + mock_path = MagicMock(spec=Path) + monkeypatch.setattr("app.services.storage.BASE_QUARANTINE_DIR", mock_path) + mock_path.__truediv__.return_value = mock_path + mock_path.parent.mkdir.return_value = None + + # Patch open to raise IOError + m_open = mock_open() + m_open.side_effect = IOError("Write failed") + with patch("builtins.open", m_open): + with pytest.raises(RuntimeError, match="Failed to save file to quarantine"): + await service.save_to_quarantine(file, "folder", "entity", "file.webp") + +@pytest.mark.asyncio +async def test_generate_signed_url_happy(): + """Returns the signed URL when blob.generate_signed_url succeeds.""" + blob = MagicMock() + blob.generate_signed_url.return_value = "http://signed-url.com/file.webp" + + url = await service.generate_signed_url(blob, expires_seconds=60) + assert url == "http://signed-url.com/file.webp" + blob.generate_signed_url.assert_called_once() + + +@pytest.mark.asyncio +async def test_generate_signed_url_unhappy(): + """Raises RuntimeError if blob.generate_signed_url fails.""" + blob = MagicMock() + blob.generate_signed_url.side_effect = Exception("Firebase error") + + with pytest.raises(RuntimeError, match="Failed to generate signed URL"): + await service.generate_signed_url(blob, expires_seconds=60) + blob.generate_signed_url.assert_called_once() + +@pytest.mark.asyncio +async def test_upload_to_firebase_emulator(monkeypatch): + """Returns emulator URLs when use_emulator=True.""" + monkeypatch.setattr(storage_module, "use_emulator", True) + + processed_images = {"thumbnail": b"thumb", "full": b"full"} + mock_blob = MagicMock() + mock_bucket = MagicMock() + mock_bucket.blob.return_value = mock_blob + + monkeypatch.setattr(storage_module.storage, "bucket", lambda name: mock_bucket) + monkeypatch.setattr(service, "get_bucket", lambda: "bucket_name") + + urls = await service.upload_to_firebase(processed_images, "file/path") + + for size in processed_images: + assert size in urls + assert urls[size].startswith("http://127.0.0.1:4000/storage/") + + assert mock_blob.upload_from_string.call_count == len(processed_images) + + +@pytest.mark.asyncio +async def test_upload_to_firebase_signed_url(monkeypatch): + """Returns signed URLs when use_emulator=False.""" + monkeypatch.setattr(storage_module, "use_emulator", False) + + processed_images = {"thumbnail": b"thumb"} + mock_blob = MagicMock() + mock_bucket = MagicMock() + mock_bucket.blob.return_value = mock_blob + + monkeypatch.setattr(storage_module.storage, "bucket", lambda name: mock_bucket) + monkeypatch.setattr(service, "get_bucket", lambda: "bucket_name") + monkeypatch.setattr(service, "generate_signed_url", AsyncMock(return_value="http://signed-url.com")) + + urls = await service.upload_to_firebase(processed_images, "file/path") + + assert urls["thumbnail"] == "http://signed-url.com" + mock_blob.upload_from_string.assert_called_once_with(b"thumb", content_type="image/webp") + service.generate_signed_url.assert_awaited_once_with(mock_blob, expires_seconds=storage_module.SIGNED_URL_EXPIRY_SECONDS) + + +@pytest.mark.asyncio +async def test_upload_to_firebase_generate_signed_url_failure(monkeypatch): + """Raises RuntimeError if generate_signed_url fails.""" + monkeypatch.setattr(storage_module, "use_emulator", False) + + processed_images = {"thumbnail": b"thumb"} + mock_blob = MagicMock() + mock_bucket = MagicMock() + mock_bucket.blob.return_value = mock_blob + + monkeypatch.setattr(storage_module.storage, "bucket", lambda name: mock_bucket) + monkeypatch.setattr(service, "get_bucket", lambda: "bucket_name") + monkeypatch.setattr(service, "generate_signed_url", AsyncMock(side_effect=Exception("signed_url fail"))) + + with pytest.raises(RuntimeError, match="Failed to upload image to Firebase"): + await service.upload_to_firebase(processed_images, "file/path") + + +@pytest.mark.asyncio +async def test_upload_to_firebase_upload_failure(monkeypatch): + """Raises RuntimeError if blob.upload_from_string fails.""" + monkeypatch.setattr(storage_module, "use_emulator", True) + + processed_images = {"thumbnail": b"thumb"} + mock_blob = MagicMock() + mock_blob.upload_from_string.side_effect = Exception("upload fail") + mock_bucket = MagicMock() + mock_bucket.blob.return_value = mock_blob + + monkeypatch.setattr(storage_module.storage, "bucket", lambda name: mock_bucket) + monkeypatch.setattr(service, "get_bucket", lambda: "bucket_name") + + with pytest.raises(RuntimeError, match="Failed to upload image to Firebase"): + await service.upload_to_firebase(processed_images, "file/path") + + +@pytest.mark.asyncio +async def test_upload_to_firebase_firebase_error(monkeypatch): + """Raises FirebaseError if storage.bucket fails with FirebaseError.""" + monkeypatch.setattr(storage_module, "use_emulator", True) + + processed_images = {"thumbnail": b"thumb"} + + def raise_firebase_error(name): + raise FirebaseError("unknown", "Firebase error") + + monkeypatch.setattr(storage_module.storage, "bucket", raise_firebase_error) + monkeypatch.setattr(service, "get_bucket", lambda: "bucket_name") + + with pytest.raises(FirebaseError): + await service.upload_to_firebase(processed_images, "file/path") + +@pytest.mark.asyncio +async def test_move_quarantine_to_public_success(monkeypatch, tmp_path): + """Successfully moves a file from quarantine to public: processes image, uploads, and deletes local file.""" + # Create a dummy quarantine file + quarantine_file = tmp_path / "file.webp" + quarantine_file.write_bytes(b"dummy content") + + public_path = "public/folder/path" + + # Patch process_image and upload_to_firebase + monkeypatch.setattr(service, "process_image", AsyncMock(return_value={"thumbnail": b"thumb", "full": b"full"})) + monkeypatch.setattr(service, "upload_to_firebase", AsyncMock(return_value={"thumbnail": "url_thumb", "full": "url_full"})) + + urls = await service.move_quarantine_to_public(str(quarantine_file), public_path) + + # Assertions + assert urls == {"thumbnail": "url_thumb", "full": "url_full"} + assert not quarantine_file.exists() # file should be deleted + +@pytest.mark.asyncio +async def test_move_quarantine_to_public_file_missing(monkeypatch): + """Raises ValueError if the quarantine file does not exist.""" + with pytest.raises(ValueError, match="Quarantine file not found"): + await service.move_quarantine_to_public("nonexistent_file.webp", "public/folder/path") + +@pytest.mark.asyncio +async def test_move_quarantine_to_public_process_failure(monkeypatch, tmp_path): + """Raises RuntimeError if image processing fails; quarantine file remains on disk.""" + quarantine_file = tmp_path / "file.webp" + quarantine_file.write_bytes(b"dummy content") + + # Patch process_image to raise an exception + monkeypatch.setattr(service, "process_image", AsyncMock(side_effect=RuntimeError("Processing failed"))) + + with pytest.raises(RuntimeError, match="Processing failed"): + await service.move_quarantine_to_public(str(quarantine_file), "public/folder/path") + + # Quarantine file should still exist if processing fails + assert quarantine_file.exists() + +@pytest.mark.asyncio +async def test_move_quarantine_to_public_upload_failure(monkeypatch, tmp_path): + """Raises RuntimeError if upload_to_firebase fails; quarantine file remains on disk.""" + quarantine_file = tmp_path / "file.webp" + quarantine_file.write_bytes(b"dummy content") + + monkeypatch.setattr(service, "process_image", AsyncMock(return_value={"thumbnail": b"thumb"})) + monkeypatch.setattr(service, "upload_to_firebase", AsyncMock(side_effect=RuntimeError("Upload failed"))) + + with pytest.raises(RuntimeError, match="Upload failed"): + await service.move_quarantine_to_public(str(quarantine_file), "public/folder/path") + + # Quarantine file should still exist if upload fails + assert quarantine_file.exists() + +@pytest.mark.asyncio +async def test_process_image_success(monkeypatch): + """Returns processed image dictionary when image processing succeeds.""" + dummy_content = b"dummy image bytes" + + # Patch the actual process_image in image_processor.py + monkeypatch.setattr("app.services.storage.process_image", AsyncMock(return_value={ + "thumbnail": b"thumb", + "medium": b"medium", + "full": b"full" + })) + + result = await service.process_image(dummy_content) + + assert result == { + "thumbnail": b"thumb", + "medium": b"medium", + "full": b"full" + } + +@pytest.mark.asyncio +async def test_process_image_failure(monkeypatch): + """Raises RuntimeError if the underlying image processing function fails.""" + dummy_content = b"dummy image bytes" + + # Patch the actual process_image to raise an exception + monkeypatch.setattr("app.services.storage.process_image", AsyncMock(side_effect=Exception("Some error"))) + + with pytest.raises(RuntimeError, match="Failed to process image."): + await service.process_image(dummy_content) + +@pytest.mark.asyncio +async def test_delete_image_success(monkeypatch): + """Deletes all image size variants successfully and returns True.""" + # Mock bucket and blob + mock_blob = MagicMock() + mock_bucket = MagicMock() + mock_bucket.blob.return_value = mock_blob + + monkeypatch.setattr(service, "get_bucket", lambda: "bucket_name") + monkeypatch.setattr(storage_module.storage, "bucket", lambda name: mock_bucket) + + result = await service.delete_image("folder/file") + + assert result is True + assert mock_blob.delete.call_count == 3 + expected_calls = [f"folder/file_{size}.webp" for size in ["thumbnail", "medium", "full"]] + actual_calls = [call[0][0] for call in mock_bucket.blob.call_args_list] + assert actual_calls == expected_calls + +@pytest.mark.asyncio +async def test_delete_image_failure(monkeypatch): + """Returns False if deleting images fails due to an exception.""" + monkeypatch.setattr(service, "get_bucket", lambda: "bucket_name") + def raise_error(name): + raise Exception("Firebase failure") + monkeypatch.setattr(storage_module.storage, "bucket", raise_error) + + result = await service.delete_image("folder/file") + + assert result is False + +@pytest.mark.asyncio +async def test_upload_image_workflow_success(monkeypatch, tmp_path): + """Successfully validates, saves to quarantine, processes, and uploads an image.""" + dummy_file = UploadFile(filename="image.png", file=tmp_path.joinpath("dummy").open("wb+")) + dummy_file.file.write(b"dummy content") + dummy_file.file.seek(0) + + # Patch validate_file, save_to_quarantine, move_quarantine_to_public + monkeypatch.setattr(service, "validate_file", AsyncMock(return_value=True)) + monkeypatch.setattr(service, "save_to_quarantine", AsyncMock(return_value=str(tmp_path / "quarantine_file.webp"))) + monkeypatch.setattr(service, "move_quarantine_to_public", AsyncMock(return_value={"thumbnail": "url_thumb"})) + monkeypatch.setattr(service, "generate_secure_file_path", lambda entity_id, folder, filename: f"{folder}/{entity_id}/secure_id.png") + + urls = await service.upload_image_workflow(dummy_file, "users", "user123") + + assert urls == {"thumbnail": "url_thumb"} + service.validate_file.assert_awaited_once_with(dummy_file) + service.save_to_quarantine.assert_awaited_once() + service.move_quarantine_to_public.assert_awaited_once() + +@pytest.mark.asyncio +async def test_upload_image_workflow_invalid_file(monkeypatch): + """Raises RuntimeError when the file is invalid.""" + dummy_file = AsyncMock() + dummy_file.read = AsyncMock(return_value=b"dummy content") + + monkeypatch.setattr(service, "validate_file", AsyncMock(return_value=False)) + + with pytest.raises(RuntimeError, match="Image upload failed."): + await service.upload_image_workflow(dummy_file, "users", "user123") + +@pytest.mark.asyncio +async def test_upload_image_workflow_processing_failure(monkeypatch, tmp_path): + """Raises RuntimeError if moving file from quarantine to public fails.""" + dummy_file = UploadFile(filename="image.png", file=tmp_path.joinpath("dummy").open("wb+")) + dummy_file.file.write(b"dummy content") + dummy_file.file.seek(0) + + monkeypatch.setattr(service, "validate_file", AsyncMock(return_value=True)) + monkeypatch.setattr(service, "save_to_quarantine", AsyncMock(return_value=str(tmp_path / "quarantine_file.webp"))) + monkeypatch.setattr(service, "move_quarantine_to_public", AsyncMock(side_effect=RuntimeError("Upload failed"))) + monkeypatch.setattr(service, "generate_secure_file_path", lambda entity_id, folder, filename: f"{folder}/{entity_id}/secure_id.png") + + with pytest.raises(RuntimeError, match="Image upload failed."): + await service.upload_image_workflow(dummy_file, "users", "user123") \ No newline at end of file diff --git a/backend/tests/user/test_user_service.py b/backend/tests/user/test_user_service.py index 88c93bbd..103ea436 100644 --- a/backend/tests/user/test_user_service.py +++ b/backend/tests/user/test_user_service.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta, timezone -from unittest.mock import AsyncMock, MagicMock +from unittest.mock import AsyncMock, MagicMock, patch import pytest from app.database import get_database @@ -300,3 +300,89 @@ async def test_delete_user_invalid_object_id(mock_db_client, mock_get_database): # Expected result: False and never hit the DB mock_db_client.users.delete_one.assert_not_called() assert result is False + +# --- Tests for update_user_avatar_url --- + +@pytest.mark.asyncio +async def test_update_user_avatar_url_success(mock_db_client, mock_get_database): + """Test successful user avatar URL update""" + user_id = "642f1e4a9b3c2d1f6a1b2c3d" + image_url = "https://example.com/avatar.jpg" + + # Mock successful update (1 document modified) + mock_result = AsyncMock() + mock_result.modified_count = 1 + mock_db_client.users.update_one.return_value = mock_result + + result = await user_service.update_user_avatar_url(user_id, image_url) + + assert result is True + mock_db_client.users.update_one.assert_called_once_with( + {"_id": ObjectId(user_id)}, + {"$set": {"imageUrl": image_url}} + ) + +@pytest.mark.asyncio +async def test_update_user_avatar_url_no_document_modified(mock_db_client, mock_get_database): + """Test when no document is modified (user not found)""" + user_id = "642f1e4a9b3c2d1f6a1b2c3d" + image_url = "https://example.com/avatar.jpg" + + # Mock no documents modified + mock_result = AsyncMock() + mock_result.modified_count = 0 + mock_db_client.users.update_one.return_value = mock_result + + result = await user_service.update_user_avatar_url(user_id, image_url) + + assert result is False + +@pytest.mark.asyncio +async def test_update_user_avatar_url_invalid_object_id(mock_db_client, mock_get_database): + """Test with invalid ObjectId format""" + invalid_user_id = "invalid_object_id" # Not a 24-char hex string + image_url = "https://example.com/avatar.jpg" + + with pytest.raises(Exception): # ObjectId will raise an exception + await user_service.update_user_avatar_url(invalid_user_id, image_url) + + # Should never hit the database + mock_db_client.users.update_one.assert_not_called() + +@pytest.mark.asyncio +async def test_update_user_avatar_url_empty_image_url(mock_db_client, mock_get_database): + """Test updating with empty image URL""" + user_id = "642f1e4a9b3c2d1f6a1b2c3d" + image_url = "" + + # Mock successful update + mock_result = AsyncMock() + mock_result.modified_count = 1 + mock_db_client.users.update_one.return_value = mock_result + + result = await user_service.update_user_avatar_url(user_id, image_url) + + assert result is True + mock_db_client.users.update_one.assert_called_once_with( + {"_id": ObjectId(user_id)}, + {"$set": {"imageUrl": image_url}} + ) + +@pytest.mark.asyncio +async def test_update_user_avatar_url_none_image_url(mock_db_client, mock_get_database): + """Test updating with None image URL""" + user_id = "642f1e4a9b3c2d1f6a1b2c3d" + image_url = None + + # Mock successful update + mock_result = AsyncMock() + mock_result.modified_count = 1 + mock_db_client.users.update_one.return_value = mock_result + + result = await user_service.update_user_avatar_url(user_id, image_url) + + assert result is True + mock_db_client.users.update_one.assert_called_once_with( + {"_id": ObjectId(user_id)}, + {"$set": {"imageUrl": image_url}} + ) \ No newline at end of file From 90c42da1ed34f1f83dcd53b628d089b4443d5824 Mon Sep 17 00:00:00 2001 From: Samudra Date: Wed, 3 Sep 2025 20:58:54 +0530 Subject: [PATCH 2/3] chore: remove quarantine dir from repo and add to .gitignore --- backend/.gitignore | 5 ++++- .../515cbd9044bb484a8a6c15f21a0b96d2.jpeg | Bin 40447 -> 0 bytes .../bb277bd912ac4479bb8f7eb2bfed63a5.jpeg | Bin 40447 -> 0 bytes .../c08ce27a168a467993d65906b7ca609d.jpeg | Bin 40447 -> 0 bytes 4 files changed, 4 insertions(+), 1 deletion(-) delete mode 100644 backend/app/services/quarantine/users/6890d54586de0847249a248a/515cbd9044bb484a8a6c15f21a0b96d2.jpeg delete mode 100644 backend/app/services/quarantine/users/6890d54586de0847249a248a/bb277bd912ac4479bb8f7eb2bfed63a5.jpeg delete mode 100644 backend/app/services/quarantine/users/6890d54586de0847249a248a/c08ce27a168a467993d65906b7ca609d.jpeg diff --git a/backend/.gitignore b/backend/.gitignore index 4299ee12..89d5d882 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -69,4 +69,7 @@ node_modules/ .dataconnect # firebase-config files -app/services/firebase \ No newline at end of file +app/services/firebase + +# quarantine files +app/services/quarantine/ \ No newline at end of file diff --git a/backend/app/services/quarantine/users/6890d54586de0847249a248a/515cbd9044bb484a8a6c15f21a0b96d2.jpeg b/backend/app/services/quarantine/users/6890d54586de0847249a248a/515cbd9044bb484a8a6c15f21a0b96d2.jpeg deleted file mode 100644 index 0a19ceb60810076e9d0379e4fffd5f9331e7089e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40447 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 diff --git a/backend/app/services/quarantine/users/6890d54586de0847249a248a/bb277bd912ac4479bb8f7eb2bfed63a5.jpeg b/backend/app/services/quarantine/users/6890d54586de0847249a248a/bb277bd912ac4479bb8f7eb2bfed63a5.jpeg deleted file mode 100644 index 0a19ceb60810076e9d0379e4fffd5f9331e7089e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40447 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 diff --git a/backend/app/services/quarantine/users/6890d54586de0847249a248a/c08ce27a168a467993d65906b7ca609d.jpeg b/backend/app/services/quarantine/users/6890d54586de0847249a248a/c08ce27a168a467993d65906b7ca609d.jpeg deleted file mode 100644 index 0a19ceb60810076e9d0379e4fffd5f9331e7089e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40447 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 From 6c455f9c5ab32b2bf8dc60f44d01841120ae0d22 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:30:32 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- backend/app/config.py | 23 +- backend/app/groups/routes.py | 43 ++- backend/app/groups/service.py | 19 +- backend/app/services/image_processor.py | 19 +- backend/app/services/schemas.py | 6 +- backend/app/services/storage.py | 128 +++++---- backend/app/user/routes.py | 52 ++-- backend/app/user/service.py | 8 +- .../tests/services/test_image_processor.py | 6 +- .../tests/services/test_storage_service.py | 269 +++++++++++++----- backend/tests/user/test_user_service.py | 29 +- 11 files changed, 404 insertions(+), 198 deletions(-) diff --git a/backend/app/config.py b/backend/app/config.py index cc98e6e2..e0ed318c 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -4,14 +4,13 @@ from logging.config import dictConfig from typing import Optional +from dotenv import load_dotenv +from PIL import Image, ImageFile from pydantic import Field from pydantic_settings import BaseSettings from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request -from PIL import Image, ImageFile - -from dotenv import load_dotenv load_dotenv() @@ -26,9 +25,15 @@ class Settings(BaseSettings): access_token_expire_minutes: int = 15 refresh_token_expire_days: int = 30 # Firebase - use_firebase_emulator: bool = Field(default=False, env="USE_FIREBASE_EMULATOR") #type:ignore - firebase_project_id: Optional[str] = Field(default=None, env="FIREBASE_PROJECT_ID") #type:ignore - firebase_service_account_path: str = Field(default="./firebase-service-account.json", env="FIREBASE_SERVICE_ACCOUNT_PATH") #type:ignore + use_firebase_emulator: bool = Field( + default=False, env="USE_FIREBASE_EMULATOR" + ) # type:ignore + firebase_project_id: Optional[str] = Field( + default=None, env="FIREBASE_PROJECT_ID" + ) # type:ignore + firebase_service_account_path: str = Field( + default="./firebase-service-account.json", env="FIREBASE_SERVICE_ACCOUNT_PATH" + ) # type:ignore # Firebase service account credentials as environment variables firebase_type: Optional[str] = None firebase_private_key_id: Optional[str] = None @@ -39,11 +44,13 @@ class Settings(BaseSettings): firebase_token_uri: Optional[str] = None firebase_auth_provider_x509_cert_url: Optional[str] = None firebase_client_x509_cert_url: Optional[str] = None - #Image validation configs + # Image validation configs LOAD_TRUNCATED_IMAGES: bool = False MAX_IMAGE_PIXELS: int = 50_00_000 MAX_FILE_SIZE: int = 5 * 1024 * 1024 - SIGNED_URL_EXPIRY_SECONDS: int = Field(default=3600, env="SIGNED_URL_EXPIRY_SECONDS") #type:ignore + SIGNED_URL_EXPIRY_SECONDS: int = Field( + default=3600, env="SIGNED_URL_EXPIRY_SECONDS" + ) # type:ignore CLAMAV_ENABLED: bool = False # App diff --git a/backend/app/groups/routes.py b/backend/app/groups/routes.py index 364cdffb..ed1b2efe 100644 --- a/backend/app/groups/routes.py +++ b/backend/app/groups/routes.py @@ -14,10 +14,18 @@ MemberRoleUpdateRequest, RemoveMemberResponse, ) +from app.groups.service import group_service from app.services.schemas import ImageUploadResponse from app.services.storage import storage_service -from app.groups.service import group_service -from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, BackgroundTasks +from fastapi import ( + APIRouter, + BackgroundTasks, + Depends, + File, + HTTPException, + UploadFile, + status, +) router = APIRouter(prefix="/groups", tags=["Groups"]) @@ -148,39 +156,46 @@ async def remove_group_member( raise HTTPException(status_code=400, detail="Failed to remove member") return RemoveMemberResponse(success=True, message="Member removed successfully") + @router.post("/{group_id}/image", response_model=ImageUploadResponse) -async def upload_group_image(group_id: str, file: UploadFile = File(...), - background_tasks: BackgroundTasks = BackgroundTasks(), - current_user: dict = Depends(get_current_user)): +async def upload_group_image( + group_id: str, + file: UploadFile = File(...), + background_tasks: BackgroundTasks = BackgroundTasks(), + current_user: dict = Depends(get_current_user), +): await group_service.ensure_user_in_group(group_id, current_user["_id"]) try: - urls = await storage_service.upload_image_workflow(file=file, folder="groups", entity_id=group_id) + urls = await storage_service.upload_image_workflow( + file=file, folder="groups", entity_id=group_id + ) except ValueError as ve: raise HTTPException(status_code=400, detail=str(ve)) except Exception: raise HTTPException(status_code=500, detail="Group image upload failed") - background_tasks.add_task(group_service.update_group_image_url, group_id, urls.get("full")) + background_tasks.add_task( + group_service.update_group_image_url, group_id, urls.get("full") + ) return ImageUploadResponse( - success=True, - urls=urls, - message="Group image uploaded successfully." + success=True, urls=urls, message="Group image uploaded successfully." ) + @router.delete("/{group_id}/image", response_model=DeleteGroupResponse) async def delete_group_avatar( group_id: str, current_user: dict = Depends(get_current_user), - background_tasks: BackgroundTasks = BackgroundTasks() + background_tasks: BackgroundTasks = BackgroundTasks(), ): group = await group_service.get_group_by_id(group_id, current_user["_id"]) if not group: raise HTTPException(status_code=404, detail="Group not found") - + await group_service.ensure_user_in_group(group_id, current_user["_id"]) image_url = group.get("imageUrl") @@ -198,4 +213,6 @@ async def delete_group_avatar( background_tasks.add_task(group_service.update_group_image_url, group_id, None) - return DeleteGroupResponse(success=True, message="Group image deleted successfully.") + return DeleteGroupResponse( + success=True, message="Group image deleted successfully." + ) diff --git a/backend/app/groups/service.py b/backend/app/groups/service.py index 895c3a22..02003019 100644 --- a/backend/app/groups/service.py +++ b/backend/app/groups/service.py @@ -472,7 +472,7 @@ async def remove_member(self, group_id: str, member_id: str, user_id: str) -> bo {"_id": obj_id}, {"$pull": {"members": {"userId": member_id}}} ) return result.modified_count == 1 - + async def ensure_user_in_group(self, group_id: str, user_id: str) -> dict: """Ensure that the user is a member of the group. Raises HTTPException if not.""" db = self.get_db() @@ -487,16 +487,17 @@ async def ensure_user_in_group(self, group_id: str, user_id: str) -> dict: logger.error(f"Unexpected error converting group_id to ObjectId: {e}") raise HTTPException(status_code=500, detail="Internal server error") - group = await db.groups.find_one({ - "_id": obj_id, - "members": {"$elemMatch": {"userId": user_id}} - }) + group = await db.groups.find_one( + {"_id": obj_id, "members": {"$elemMatch": {"userId": user_id}}} + ) if not group: - raise HTTPException(status_code=403, detail="You are not a member of this group") + raise HTTPException( + status_code=403, detail="You are not a member of this group" + ) return group # Optional return if route needs to read group data - + async def update_group_image_url(self, group_id: str, image_url: str) -> bool: """Update the group's image URL in the database.""" db = self.get_db() @@ -508,9 +509,9 @@ async def update_group_image_url(self, group_id: str, image_url: str) -> bool: return False result = await db.groups.update_one( - {"_id": obj_id}, - {"$set": {"imageUrl": image_url}} + {"_id": obj_id}, {"$set": {"imageUrl": image_url}} ) return result.modified_count == 1 + group_service = GroupService() diff --git a/backend/app/services/image_processor.py b/backend/app/services/image_processor.py index 174fcefa..280de5d7 100644 --- a/backend/app/services/image_processor.py +++ b/backend/app/services/image_processor.py @@ -1,11 +1,11 @@ import imghdr -from PIL import Image, UnidentifiedImageError, ImageFile from io import BytesIO from typing import Dict, Tuple from app.config import logger +from PIL import Image, ImageFile, UnidentifiedImageError -# Optional watermark path +# Optional watermark path WATERMARK_PATH = None # Example: "app/assets/watermark.png" # Resize targets (square thumbnail + larger sizes) @@ -15,9 +15,10 @@ "full": (800, 800), } -#Defining image file restrictions +# Defining image file restrictions ImageFile.LOAD_TRUNCATED_IMAGES = False -Image.MAX_IMAGE_PIXELS = 50_00_000 #50MB in worst case +Image.MAX_IMAGE_PIXELS = 50_00_000 # 50MB in worst case + def strip_exif(image: Image.Image) -> Image.Image: """ @@ -27,6 +28,7 @@ def strip_exif(image: Image.Image) -> Image.Image: clean_image.putdata(list(image.getdata())) return clean_image + def validate_magic_bytes(file_content: bytes): """ Validates the actual file type of image @@ -34,7 +36,8 @@ def validate_magic_bytes(file_content: bytes): fmt = imghdr.what(None, h=file_content) if fmt not in ["jpeg", "png", "webp"]: raise ValueError("Invalid or unsupported image type.") - + + def add_watermark(image: Image.Image, watermark: Image.Image) -> Image.Image: """ Adds watermark (bottom-right). Image and watermark must be RGBA. @@ -77,7 +80,7 @@ async def process_image(file_content: bytes) -> Dict[str, bytes]: """ try: validate_magic_bytes(file_content) - + img = Image.open(BytesIO(file_content)) img_format = img.format.upper() @@ -102,7 +105,9 @@ async def process_image(file_content: bytes) -> Dict[str, bytes]: # Save to memory in WebP format buffer = BytesIO() - resized.save(buffer, format="WEBP", quality=85, method=6) # High quality with compression + resized.save( + buffer, format="WEBP", quality=85, method=6 + ) # High quality with compression buffer.seek(0) results[label] = buffer.read() diff --git a/backend/app/services/schemas.py b/backend/app/services/schemas.py index 15808fb1..8050f918 100644 --- a/backend/app/services/schemas.py +++ b/backend/app/services/schemas.py @@ -1,8 +1,10 @@ -from pydantic import BaseModel from typing import Dict, Optional +from pydantic import BaseModel + + class ImageUploadResponse(BaseModel): success: bool urls: Dict[str, str] # {"thumbnail": "url", "medium": "url", "full": "url"} message: str - processing_id: Optional[str] = None \ No newline at end of file + processing_id: Optional[str] = None diff --git a/backend/app/services/storage.py b/backend/app/services/storage.py index 1fbfb986..0c2815ba 100644 --- a/backend/app/services/storage.py +++ b/backend/app/services/storage.py @@ -1,42 +1,42 @@ -import os +import asyncio +import datetime import io -import firebase_admin +import os import re -import datetime -import asyncio import subprocess -from typing import Dict, Union -from pathlib import Path -from fastapi import UploadFile import uuid -from uuid import uuid4 -from PIL import Image, UnidentifiedImageError +from pathlib import Path +from typing import Dict, Union from urllib.parse import urlparse +from uuid import uuid4 -from app.services.image_processor import process_image +import firebase_admin from app.config import logger, settings +from app.services.image_processor import process_image +from dotenv import load_dotenv +from fastapi import UploadFile from firebase_admin import credentials, storage from firebase_admin.exceptions import FirebaseError +from PIL import Image, UnidentifiedImageError -from dotenv import load_dotenv load_dotenv() try: use_emulator = settings.use_firebase_emulator service_account_path = settings.firebase_service_account_path project_id = settings.firebase_project_id - + if use_emulator: os.environ["FIREBASE_STORAGE_EMULATOR_HOST"] = "http://127.0.0.1:4000" logger.info("Using Firebase Storage Emulator") - if not project_id: raise ValueError("FIREBASE_PROJECT_ID environment variable is not set.") - if not use_emulator and (not service_account_path or not os.path.exists(service_account_path)): + if not use_emulator and ( + not service_account_path or not os.path.exists(service_account_path) + ): raise FileNotFoundError("Service account path is missing or invalid.") - # Prevent multiple initializations try: @@ -49,9 +49,9 @@ logger.info("Firebase initialized with emulator (no credentials).") else: cred = credentials.Certificate(service_account_path) - firebase_admin.initialize_app(cred, { - 'storageBucket': f"{project_id}.firebasestorage.app" - }) + firebase_admin.initialize_app( + cred, {"storageBucket": f"{project_id}.firebasestorage.app"} + ) logger.info("Firebase initialized with service account file.") except Exception as e: @@ -76,17 +76,20 @@ MAX_IMAGE_PIXELS = settings.MAX_IMAGE_PIXELS LOAD_TRUNCATED_IMAGES = settings.LOAD_TRUNCATED_IMAGES SIGNED_URL_EXPIRY_SECONDS = settings.SIGNED_URL_EXPIRY_SECONDS -CLAMAV_ENABLED = settings.CLAMAV_ENABLED #Default False +CLAMAV_ENABLED = settings.CLAMAV_ENABLED # Default False BASE_QUARANTINE_DIR = Path(__file__).resolve().parent / "quarantine" + class FirebaseStorageService: def __init__(self): pass def get_bucket(self) -> str: return f"{project_id}.firebasestorage.app" - - def generate_secure_file_path(self, entity_id: str, folder: str, filename: str) -> str: + + def generate_secure_file_path( + self, entity_id: str, folder: str, filename: str + ) -> str: """ Generates a unique file path for Firebase Storage Ex: users//.webp or groups//.webp @@ -96,7 +99,7 @@ def generate_secure_file_path(self, entity_id: str, folder: str, filename: str) ext = os.path.splitext(filename)[-1].lower() or ".webp" safe_filename = f"{unique_id}{ext}" return f"{folder}/{entity_id}/{safe_filename}" - + def extract_path_from_url(self, url: str) -> str: """ Extracts the Firebase Storage blob path from a full public URL. @@ -158,7 +161,7 @@ def run_scan(tmp_path: str): except Exception as e: logger.exception(f"ClamAV scan failed: {e}") return False''' - + async def validate_file(self, file: UploadFile) -> bool: """ Validates file type (JPEG, PNG, WebP) and max size (5MB). @@ -179,11 +182,17 @@ async def validate_file(self, file: UploadFile) -> bool: logger.warning("Failed to identify image format with Pillow.") return False except Exception: - logger.warning("Pillow failed to fully load the image (possibly corrupt or too large).") + logger.warning( + "Pillow failed to fully load the image (possibly corrupt or too large)." + ) return False img_format = img.format - if img_format is None or PIL_FORMAT_TO_MIME.get(img_format) not in ["image/jpeg", "image/png", "image/webp"]: + if img_format is None or PIL_FORMAT_TO_MIME.get(img_format) not in [ + "image/jpeg", + "image/png", + "image/webp", + ]: logger.warning(f"Rejected image due to invalid format: {img_format}") return False @@ -196,23 +205,24 @@ async def validate_file(self, file: UploadFile) -> bool: except Exception as e: logger.exception(f"File validation failed: {e}") return False - - - async def save_to_quarantine(self,file: Union[UploadFile, bytes],folder: str, - entity_id: str,original_filename: str) -> str: + async def save_to_quarantine( + self, + file: Union[UploadFile, bytes], + folder: str, + entity_id: str, + original_filename: str, + ) -> str: """ Saves a file to the local quarantine directory for scanning/processing later. Uses the same secure naming convention as generate_secure_file_path. - + Returns: str: Full path to the saved file. """ # Generate secure name secure_relative_path = self.generate_secure_file_path( - entity_id=entity_id, - folder=folder, - filename=original_filename + entity_id=entity_id, folder=folder, filename=original_filename ) # Convert storage path to local quarantine path @@ -243,8 +253,9 @@ async def save_to_quarantine(self,file: Union[UploadFile, bytes],folder: str, return str(target_path) - - async def generate_signed_url(self, blob, expires_seconds: int = SIGNED_URL_EXPIRY_SECONDS) -> str: + async def generate_signed_url( + self, blob, expires_seconds: int = SIGNED_URL_EXPIRY_SECONDS + ) -> str: """ Generate a signed URL for a blob (works with google-cloud-storage Blob). """ @@ -261,9 +272,9 @@ def gen(): logger.exception("Failed to generate signed URL.") raise RuntimeError("Failed to generate signed URL.") from e - - - async def upload_to_firebase(self, processed_images: Dict[str, bytes], file_path: str) -> Dict[str, str]: + async def upload_to_firebase( + self, processed_images: Dict[str, bytes], file_path: str + ) -> Dict[str, str]: """ Uploads processed images (thumbnail/medium/full) to a public path, returns signed URLs. Does NOT call blob.make_public(). @@ -281,7 +292,9 @@ async def upload_to_firebase(self, processed_images: Dict[str, bytes], file_path if use_emulator: url = f"http://127.0.0.1:4000/storage/{bucket_name}/o/{blob_path.replace('/', '%2F')}?alt=media" else: - url = await self.generate_signed_url(blob, expires_seconds=SIGNED_URL_EXPIRY_SECONDS) + url = await self.generate_signed_url( + blob, expires_seconds=SIGNED_URL_EXPIRY_SECONDS + ) urls[size] = url logger.info(f"Uploaded image (signed): {blob_path}") @@ -293,8 +306,10 @@ async def upload_to_firebase(self, processed_images: Dict[str, bytes], file_path except Exception as e: logger.exception("Image upload to Firebase failed.") raise RuntimeError("Failed to upload image to Firebase.") from e - - async def move_quarantine_to_public(self, quarantine_path: str, public_base_path: str) -> Dict[str, str]: + + async def move_quarantine_to_public( + self, quarantine_path: str, public_base_path: str + ) -> Dict[str, str]: """ Worker-friendly method: - loads the quarantine file from local disk @@ -313,7 +328,7 @@ async def move_quarantine_to_public(self, quarantine_path: str, public_base_path with open(quarantine_path, "rb") as f: content = f.read() - '''# Inline scan if enabled else skipped + """# Inline scan if enabled else skipped scan_result = await self._scan_with_clamav_async(content) if scan_result is False: try: @@ -321,7 +336,7 @@ async def move_quarantine_to_public(self, quarantine_path: str, public_base_path except Exception: logger.warning("Failed to delete infected quarantine file.") logger.warning("Quarantine file marked as infected and removed.") - raise ValueError("File failed virus scan.")''' + raise ValueError("File failed virus scan.")""" # Process the image processed = await self.process_image(content) @@ -346,11 +361,13 @@ async def process_image(self, file_content: bytes) -> Dict[str, bytes]: Generates 3 resized, optimized images (thumbnail, medium, full) in WebP format. """ try: - return await process_image(file_content) # Calls image_proccessor.py's process_image + return await process_image( + file_content + ) # Calls image_proccessor.py's process_image except Exception as e: logger.exception("Image processing failed.") - raise RuntimeError("Failed to process image.") from e - + raise RuntimeError("Failed to process image.") from e + async def delete_image(self, file_path: str) -> bool: """ Deletes all size variants (thumbnail, medium, full) from Firebase Storage. @@ -364,12 +381,14 @@ async def delete_image(self, file_path: str) -> bool: blob.delete() logger.info(f"Deleted image: {blob_path}") return True - + except Exception as e: logger.exception(f"Failed to delete images at {file_path}") return False - async def upload_image_workflow(self, file: UploadFile, folder: str, entity_id: str) -> Dict[str, str]: + async def upload_image_workflow( + self, file: UploadFile, folder: str, entity_id: str + ) -> Dict[str, str]: """ Workflow: - Validate (magic bytes + Pillow checks) @@ -386,12 +405,16 @@ async def upload_image_workflow(self, file: UploadFile, folder: str, entity_id: file_path = self.generate_secure_file_path(entity_id, folder, file.filename) logger.info(f"Generated secure path: {file_path}") - quarantine_path = await self.save_to_quarantine(content, folder, entity_id, file.filename) + quarantine_path = await self.save_to_quarantine( + content, folder, entity_id, file.filename + ) if CLAMAV_ENABLED: # Original path: scan + process inline urls = await self.move_quarantine_to_public(quarantine_path, file_path) - logger.info(f"Upload workflow successful for {folder} entity {entity_id}") + logger.info( + f"Upload workflow successful for {folder} entity {entity_id}" + ) return urls else: # Directly process image and upload (no scanning) @@ -402,5 +425,6 @@ async def upload_image_workflow(self, file: UploadFile, folder: str, entity_id: except Exception as e: logger.exception("Upload image workflow failed.") raise RuntimeError("Image upload failed.") from e - -storage_service = FirebaseStorageService() \ No newline at end of file + + +storage_service = FirebaseStorageService() diff --git a/backend/app/user/routes.py b/backend/app/user/routes.py index 4f32c126..29c4aeb1 100644 --- a/backend/app/user/routes.py +++ b/backend/app/user/routes.py @@ -1,16 +1,24 @@ from typing import Any, Dict from app.auth.security import get_current_user +from app.config import logger +from app.services.schemas import ImageUploadResponse +from app.services.storage import storage_service from app.user.schemas import ( DeleteUserResponse, UserProfileResponse, - UserProfileUpdateRequest + UserProfileUpdateRequest, ) -from app.services.schemas import ImageUploadResponse from app.user.service import user_service -from app.services.storage import storage_service -from app.config import logger -from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, BackgroundTasks +from fastapi import ( + APIRouter, + BackgroundTasks, + Depends, + File, + HTTPException, + UploadFile, + status, +) router = APIRouter(prefix="/users", tags=["User"]) @@ -59,17 +67,19 @@ async def delete_user_account(current_user: Dict[str, Any] = Depends(get_current success=True, message="User account scheduled for deletion." ) + @router.post("/me/avatar", response_model=ImageUploadResponse) -async def upload_user_avatar(file: UploadFile = File(...),background_tasks: BackgroundTasks = BackgroundTasks(), - current_user: dict = Depends(get_current_user)): +async def upload_user_avatar( + file: UploadFile = File(...), + background_tasks: BackgroundTasks = BackgroundTasks(), + current_user: dict = Depends(get_current_user), +): user_id = str(current_user["_id"]) try: # Validate, process, upload urls = await storage_service.upload_image_workflow( - file=file, - folder="users", - entity_id=user_id + file=file, folder="users", entity_id=user_id ) except ValueError as ve: raise HTTPException(status_code=400, detail=str(ve)) @@ -77,28 +87,32 @@ async def upload_user_avatar(file: UploadFile = File(...),background_tasks: Back raise HTTPException(status_code=404, detail="Storage location not found.") except Exception as e: logger.exception(f"Unexpected error during avatar upload for user {user_id}") - raise HTTPException(status_code=500, detail="Image upload failed due to an internal error.") + raise HTTPException( + status_code=500, detail="Image upload failed due to an internal error." + ) # Update DB in background - background_tasks.add_task(user_service.update_user_avatar_url, user_id, urls.get("full")) + background_tasks.add_task( + user_service.update_user_avatar_url, user_id, urls.get("full") + ) return ImageUploadResponse( - success=True, - urls=urls, - message="Avatar uploaded successfully." + success=True, urls=urls, message="Avatar uploaded successfully." ) @router.delete("/me/avatar", response_model=DeleteUserResponse) -async def delete_user_avatar(current_user: dict = Depends(get_current_user), - background_tasks: BackgroundTasks = BackgroundTasks()): - +async def delete_user_avatar( + current_user: dict = Depends(get_current_user), + background_tasks: BackgroundTasks = BackgroundTasks(), +): + user_id = str(current_user["_id"]) user = await user_service.get_user_by_id(user_id) if not user: raise HTTPException(status_code=404, detail="User not found") - + image_url = user.get("imageUrl") if not image_url: diff --git a/backend/app/user/service.py b/backend/app/user/service.py index 4aef569f..c6957564 100644 --- a/backend/app/user/service.py +++ b/backend/app/user/service.py @@ -1,10 +1,10 @@ from datetime import datetime, timezone from typing import Any, Dict, Optional -from fastapi import UploadFile from app.config import logger from app.database import get_database from bson import ObjectId, errors +from fastapi import UploadFile class UserService: @@ -96,9 +96,9 @@ async def delete_user(self, user_id: str) -> bool: async def update_user_avatar_url(self, user_id: str, image_url: str) -> bool: db = self.get_db() result = await db.users.update_one( - {"_id": ObjectId(user_id)}, - {"$set": {"imageUrl": image_url}} + {"_id": ObjectId(user_id)}, {"$set": {"imageUrl": image_url}} ) return result.modified_count == 1 -user_service = UserService() \ No newline at end of file + +user_service = UserService() diff --git a/backend/tests/services/test_image_processor.py b/backend/tests/services/test_image_processor.py index 0d579a1a..1467afcb 100644 --- a/backend/tests/services/test_image_processor.py +++ b/backend/tests/services/test_image_processor.py @@ -1,7 +1,8 @@ -import pytest from io import BytesIO -from PIL import Image + +import pytest from app.services import image_processor as ip +from PIL import Image def make_test_image(fmt="PNG", size=(200, 100), color=(255, 0, 0)): @@ -52,6 +53,7 @@ def test_add_watermark_places_watermark(): px = out.getpixel((190, 190)) assert px[1] > 200 # green applied + @pytest.mark.asyncio async def test_process_image_happy_path(): """process_image should return resized WebP images for valid input.""" diff --git a/backend/tests/services/test_storage_service.py b/backend/tests/services/test_storage_service.py index 9054f2ba..0a762232 100644 --- a/backend/tests/services/test_storage_service.py +++ b/backend/tests/services/test_storage_service.py @@ -1,14 +1,15 @@ import io -import pytest -import app.services.storage as storage_module -from uuid import UUID -from PIL import Image from pathlib import Path +from unittest.mock import AsyncMock, MagicMock, mock_open, patch +from uuid import UUID + +import app.services.storage as storage_module +import pytest from fastapi import UploadFile -from unittest.mock import AsyncMock, patch, MagicMock, mock_open from firebase_admin.exceptions import FirebaseError +from PIL import Image -'''# Apply monkeypatch as a fixture +"""# Apply monkeypatch as a fixture @pytest.fixture(autouse=True) def patch_firebase(monkeypatch): monkeypatch.setattr("app.services.storage.storage", object()) @@ -16,51 +17,62 @@ def patch_firebase(monkeypatch): monkeypatch.setattr(storage_module, "uuid4", lambda: UUID("12345678123456781234567812345678")) - yield ''' + yield """ from app.services.storage import FirebaseStorageService service = FirebaseStorageService() + @pytest.fixture def fixed_uuid(monkeypatch): - monkeypatch.setattr("app.services.storage.uuid4", lambda: UUID("12345678123456781234567812345678")) + monkeypatch.setattr( + "app.services.storage.uuid4", lambda: UUID("12345678123456781234567812345678") + ) yield + def test_generate_secure_file_path_with_extension(fixed_uuid): result = service.generate_secure_file_path("user123", "users", "image.png") assert result.startswith("users/user123/12345678123456781234567812345678") assert result.endswith(".png") + def test_generate_secure_file_path_without_extension(fixed_uuid): result = service.generate_secure_file_path("user123", "users", "image") assert result.endswith(".webp") + def test_generate_secure_file_path_empty_entity(fixed_uuid): result = service.generate_secure_file_path("", "users", "image.png") assert result.startswith("users//12345678123456781234567812345678") assert result.endswith(".png") + def test_generate_secure_file_path_empty_folder(fixed_uuid): result = service.generate_secure_file_path("user123", "", "image.png") assert result.startswith("/user123/12345678123456781234567812345678") assert result.endswith(".png") + def test_generate_secure_file_path_empty_filename(fixed_uuid): result = service.generate_secure_file_path("user123", "users", "") assert result.endswith(".webp") + def test_generate_secure_file_path_none_filename(fixed_uuid): result = service.generate_secure_file_path("user123", "users", None) assert result.endswith(".webp") assert "12345678123456781234567812345678.webp" in result + def test_extract_path_from_url_normal(): """Extracts path from a normal Firebase URL without suffix.""" url = "https://dummy-project.firebasestorage.app/o/users%2Fuser123%2Ffile.webp?alt=media" result = service.extract_path_from_url(url) assert result == "o/users%2Fuser123%2Ffile.webp" + def test_extract_path_from_url_with_suffixes(): """Removes _thumbnail.webp, _medium.webp, and _full.webp suffixes from URL.""" for suffix in ["_thumbnail.webp", "_medium.webp", "_full.webp"]: @@ -68,12 +80,14 @@ def test_extract_path_from_url_with_suffixes(): result = service.extract_path_from_url(url) assert result == "o/users%2Fuser123%2Ffile" + def test_extract_path_from_url_already_clean(): """Returns path unchanged if no bucket name or suffix is present.""" url = "https://example.com/path/to/file.webp" result = service.extract_path_from_url(url) assert result == "path/to/file.webp" + def test_extract_path_from_url_malformed(): """Handles malformed URLs gracefully by returning whatever path can be parsed.""" url = "not a valid url" @@ -81,12 +95,14 @@ def test_extract_path_from_url_malformed(): # urlparse still returns a path string, even if malformed assert result == "not a valid url" + def test_extract_path_from_url_unexpected_suffix(): """Leaves URL unchanged if suffix is not thumbnail/medium/full.""" url = "https://dummy-project.firebasestorage.app/o/users%2Fuser123%2Ffile_large.webp?alt=media" result = service.extract_path_from_url(url) assert result == "o/users%2Fuser123%2Ffile_large.webp" + @pytest.mark.asyncio async def test_validate_file_valid_png(monkeypatch): """Returns True for a valid PNG file under max size and pixel limit.""" @@ -102,8 +118,14 @@ async def test_validate_file_valid_png(monkeypatch): file.seek.return_value = None # Patch limits - monkeypatch.setattr("app.services.storage.settings", type("obj", (), {"MAX_FILE_SIZE": 5*1024*1024})) - monkeypatch.setattr("app.services.storage.settings", type("obj", (), {"MAX_FILE_SIZE": 5*1024*1024, "MAX_IMAGE_PIXELS": 10000})) + monkeypatch.setattr( + "app.services.storage.settings", + type("obj", (), {"MAX_FILE_SIZE": 5 * 1024 * 1024}), + ) + monkeypatch.setattr( + "app.services.storage.settings", + type("obj", (), {"MAX_FILE_SIZE": 5 * 1024 * 1024, "MAX_IMAGE_PIXELS": 10000}), + ) result = await service.validate_file(file) assert result is True @@ -112,13 +134,16 @@ async def test_validate_file_valid_png(monkeypatch): @pytest.mark.asyncio async def test_validate_file_oversized(monkeypatch): """Returns False for files exceeding MAX_FILE_SIZE.""" - img_bytes = b"x" * (5*1024*1024) # 5MB + img_bytes = b"x" * (5 * 1024 * 1024) # 5MB file = AsyncMock(spec=UploadFile) file.read.return_value = img_bytes file.seek.return_value = None - monkeypatch.setattr("app.services.storage.settings", type("obj", (), {"MAX_FILE_SIZE": 5*1024*1024})) + monkeypatch.setattr( + "app.services.storage.settings", + type("obj", (), {"MAX_FILE_SIZE": 5 * 1024 * 1024}), + ) result = await service.validate_file(file) assert result is False @@ -171,6 +196,7 @@ async def test_validate_file_too_many_pixels(monkeypatch): result = await service.validate_file(file) assert result is False + @pytest.mark.asyncio async def test_save_to_quarantine_uploadfile(monkeypatch): """Saves an UploadFile to the quarantine directory using a secure path.""" @@ -179,7 +205,11 @@ async def test_save_to_quarantine_uploadfile(monkeypatch): file.read.return_value = file_content # Patch generate_secure_file_path to return a fixed filename - monkeypatch.setattr(service, "generate_secure_file_path", lambda *args, **kwargs: "folder/entity/file.webp") + monkeypatch.setattr( + service, + "generate_secure_file_path", + lambda *args, **kwargs: "folder/entity/file.webp", + ) # Patch BASE_QUARANTINE_DIR at the module level mock_path = MagicMock(spec=Path) @@ -201,7 +231,11 @@ async def test_save_to_quarantine_bytes(monkeypatch): """Saves raw bytes to the quarantine directory using a secure path.""" file_content = b"raw bytes content" - monkeypatch.setattr(service, "generate_secure_file_path", lambda *args, **kwargs: "folder/entity/file.webp") + monkeypatch.setattr( + service, + "generate_secure_file_path", + lambda *args, **kwargs: "folder/entity/file.webp", + ) # Patch BASE_QUARANTINE_DIR at the module level mock_path = MagicMock(spec=Path) @@ -211,27 +245,37 @@ async def test_save_to_quarantine_bytes(monkeypatch): m_open = mock_open() with patch("builtins.open", m_open): - result = await service.save_to_quarantine(file_content, "folder", "entity", "file.webp") + result = await service.save_to_quarantine( + file_content, "folder", "entity", "file.webp" + ) assert result == str(mock_path) m_open.assert_called_once_with(mock_path, "wb") m_open().write.assert_called_once_with(file_content) + @pytest.mark.asyncio async def test_save_to_quarantine_invalid_type(monkeypatch): """Raises TypeError if file is neither UploadFile nor bytes.""" - monkeypatch.setattr(service, "generate_secure_file_path", lambda *a, **kw: "folder/entity/file.webp") + monkeypatch.setattr( + service, "generate_secure_file_path", lambda *a, **kw: "folder/entity/file.webp" + ) with pytest.raises(TypeError, match="Invalid file type"): await service.save_to_quarantine(12345, "folder", "entity", "file.webp") + @pytest.mark.asyncio async def test_save_to_quarantine_read_failure(monkeypatch): """Raises exception if UploadFile.read() fails.""" file = AsyncMock(spec=UploadFile) file.read.side_effect = Exception("Read failed") - monkeypatch.setattr(service, "generate_secure_file_path", lambda *a, **kw: "folder/entity/file.webp") - monkeypatch.setattr("app.services.storage.BASE_QUARANTINE_DIR", MagicMock(spec=Path)) + monkeypatch.setattr( + service, "generate_secure_file_path", lambda *a, **kw: "folder/entity/file.webp" + ) + monkeypatch.setattr( + "app.services.storage.BASE_QUARANTINE_DIR", MagicMock(spec=Path) + ) with pytest.raises(Exception, match="Read failed"): await service.save_to_quarantine(file, "folder", "entity", "file.webp") @@ -244,7 +288,9 @@ async def test_save_to_quarantine_write_failure(monkeypatch): file = AsyncMock(spec=UploadFile) file.read.return_value = file_content - monkeypatch.setattr(service, "generate_secure_file_path", lambda *a, **kw: "folder/entity/file.webp") + monkeypatch.setattr( + service, "generate_secure_file_path", lambda *a, **kw: "folder/entity/file.webp" + ) mock_path = MagicMock(spec=Path) monkeypatch.setattr("app.services.storage.BASE_QUARANTINE_DIR", mock_path) mock_path.__truediv__.return_value = mock_path @@ -257,6 +303,7 @@ async def test_save_to_quarantine_write_failure(monkeypatch): with pytest.raises(RuntimeError, match="Failed to save file to quarantine"): await service.save_to_quarantine(file, "folder", "entity", "file.webp") + @pytest.mark.asyncio async def test_generate_signed_url_happy(): """Returns the signed URL when blob.generate_signed_url succeeds.""" @@ -278,6 +325,7 @@ async def test_generate_signed_url_unhappy(): await service.generate_signed_url(blob, expires_seconds=60) blob.generate_signed_url.assert_called_once() + @pytest.mark.asyncio async def test_upload_to_firebase_emulator(monkeypatch): """Returns emulator URLs when use_emulator=True.""" @@ -312,13 +360,19 @@ async def test_upload_to_firebase_signed_url(monkeypatch): monkeypatch.setattr(storage_module.storage, "bucket", lambda name: mock_bucket) monkeypatch.setattr(service, "get_bucket", lambda: "bucket_name") - monkeypatch.setattr(service, "generate_signed_url", AsyncMock(return_value="http://signed-url.com")) + monkeypatch.setattr( + service, "generate_signed_url", AsyncMock(return_value="http://signed-url.com") + ) urls = await service.upload_to_firebase(processed_images, "file/path") assert urls["thumbnail"] == "http://signed-url.com" - mock_blob.upload_from_string.assert_called_once_with(b"thumb", content_type="image/webp") - service.generate_signed_url.assert_awaited_once_with(mock_blob, expires_seconds=storage_module.SIGNED_URL_EXPIRY_SECONDS) + mock_blob.upload_from_string.assert_called_once_with( + b"thumb", content_type="image/webp" + ) + service.generate_signed_url.assert_awaited_once_with( + mock_blob, expires_seconds=storage_module.SIGNED_URL_EXPIRY_SECONDS + ) @pytest.mark.asyncio @@ -333,7 +387,11 @@ async def test_upload_to_firebase_generate_signed_url_failure(monkeypatch): monkeypatch.setattr(storage_module.storage, "bucket", lambda name: mock_bucket) monkeypatch.setattr(service, "get_bucket", lambda: "bucket_name") - monkeypatch.setattr(service, "generate_signed_url", AsyncMock(side_effect=Exception("signed_url fail"))) + monkeypatch.setattr( + service, + "generate_signed_url", + AsyncMock(side_effect=Exception("signed_url fail")), + ) with pytest.raises(RuntimeError, match="Failed to upload image to Firebase"): await service.upload_to_firebase(processed_images, "file/path") @@ -373,92 +431,123 @@ def raise_firebase_error(name): with pytest.raises(FirebaseError): await service.upload_to_firebase(processed_images, "file/path") + @pytest.mark.asyncio async def test_move_quarantine_to_public_success(monkeypatch, tmp_path): """Successfully moves a file from quarantine to public: processes image, uploads, and deletes local file.""" # Create a dummy quarantine file quarantine_file = tmp_path / "file.webp" quarantine_file.write_bytes(b"dummy content") - + public_path = "public/folder/path" - + # Patch process_image and upload_to_firebase - monkeypatch.setattr(service, "process_image", AsyncMock(return_value={"thumbnail": b"thumb", "full": b"full"})) - monkeypatch.setattr(service, "upload_to_firebase", AsyncMock(return_value={"thumbnail": "url_thumb", "full": "url_full"})) - + monkeypatch.setattr( + service, + "process_image", + AsyncMock(return_value={"thumbnail": b"thumb", "full": b"full"}), + ) + monkeypatch.setattr( + service, + "upload_to_firebase", + AsyncMock(return_value={"thumbnail": "url_thumb", "full": "url_full"}), + ) + urls = await service.move_quarantine_to_public(str(quarantine_file), public_path) - + # Assertions assert urls == {"thumbnail": "url_thumb", "full": "url_full"} assert not quarantine_file.exists() # file should be deleted + @pytest.mark.asyncio async def test_move_quarantine_to_public_file_missing(monkeypatch): """Raises ValueError if the quarantine file does not exist.""" with pytest.raises(ValueError, match="Quarantine file not found"): - await service.move_quarantine_to_public("nonexistent_file.webp", "public/folder/path") + await service.move_quarantine_to_public( + "nonexistent_file.webp", "public/folder/path" + ) + @pytest.mark.asyncio async def test_move_quarantine_to_public_process_failure(monkeypatch, tmp_path): """Raises RuntimeError if image processing fails; quarantine file remains on disk.""" quarantine_file = tmp_path / "file.webp" quarantine_file.write_bytes(b"dummy content") - + # Patch process_image to raise an exception - monkeypatch.setattr(service, "process_image", AsyncMock(side_effect=RuntimeError("Processing failed"))) - + monkeypatch.setattr( + service, + "process_image", + AsyncMock(side_effect=RuntimeError("Processing failed")), + ) + with pytest.raises(RuntimeError, match="Processing failed"): - await service.move_quarantine_to_public(str(quarantine_file), "public/folder/path") - + await service.move_quarantine_to_public( + str(quarantine_file), "public/folder/path" + ) + # Quarantine file should still exist if processing fails assert quarantine_file.exists() + @pytest.mark.asyncio async def test_move_quarantine_to_public_upload_failure(monkeypatch, tmp_path): """Raises RuntimeError if upload_to_firebase fails; quarantine file remains on disk.""" quarantine_file = tmp_path / "file.webp" quarantine_file.write_bytes(b"dummy content") - - monkeypatch.setattr(service, "process_image", AsyncMock(return_value={"thumbnail": b"thumb"})) - monkeypatch.setattr(service, "upload_to_firebase", AsyncMock(side_effect=RuntimeError("Upload failed"))) - + + monkeypatch.setattr( + service, "process_image", AsyncMock(return_value={"thumbnail": b"thumb"}) + ) + monkeypatch.setattr( + service, + "upload_to_firebase", + AsyncMock(side_effect=RuntimeError("Upload failed")), + ) + with pytest.raises(RuntimeError, match="Upload failed"): - await service.move_quarantine_to_public(str(quarantine_file), "public/folder/path") - + await service.move_quarantine_to_public( + str(quarantine_file), "public/folder/path" + ) + # Quarantine file should still exist if upload fails assert quarantine_file.exists() + @pytest.mark.asyncio async def test_process_image_success(monkeypatch): """Returns processed image dictionary when image processing succeeds.""" dummy_content = b"dummy image bytes" - + # Patch the actual process_image in image_processor.py - monkeypatch.setattr("app.services.storage.process_image", AsyncMock(return_value={ - "thumbnail": b"thumb", - "medium": b"medium", - "full": b"full" - })) - + monkeypatch.setattr( + "app.services.storage.process_image", + AsyncMock( + return_value={"thumbnail": b"thumb", "medium": b"medium", "full": b"full"} + ), + ) + result = await service.process_image(dummy_content) - - assert result == { - "thumbnail": b"thumb", - "medium": b"medium", - "full": b"full" - } + + assert result == {"thumbnail": b"thumb", "medium": b"medium", "full": b"full"} + @pytest.mark.asyncio async def test_process_image_failure(monkeypatch): """Raises RuntimeError if the underlying image processing function fails.""" dummy_content = b"dummy image bytes" - + # Patch the actual process_image to raise an exception - monkeypatch.setattr("app.services.storage.process_image", AsyncMock(side_effect=Exception("Some error"))) - + monkeypatch.setattr( + "app.services.storage.process_image", + AsyncMock(side_effect=Exception("Some error")), + ) + with pytest.raises(RuntimeError, match="Failed to process image."): await service.process_image(dummy_content) + @pytest.mark.asyncio async def test_delete_image_success(monkeypatch): """Deletes all image size variants successfully and returns True.""" @@ -466,42 +555,62 @@ async def test_delete_image_success(monkeypatch): mock_blob = MagicMock() mock_bucket = MagicMock() mock_bucket.blob.return_value = mock_blob - + monkeypatch.setattr(service, "get_bucket", lambda: "bucket_name") monkeypatch.setattr(storage_module.storage, "bucket", lambda name: mock_bucket) - + result = await service.delete_image("folder/file") assert result is True assert mock_blob.delete.call_count == 3 - expected_calls = [f"folder/file_{size}.webp" for size in ["thumbnail", "medium", "full"]] + expected_calls = [ + f"folder/file_{size}.webp" for size in ["thumbnail", "medium", "full"] + ] actual_calls = [call[0][0] for call in mock_bucket.blob.call_args_list] assert actual_calls == expected_calls + @pytest.mark.asyncio async def test_delete_image_failure(monkeypatch): """Returns False if deleting images fails due to an exception.""" monkeypatch.setattr(service, "get_bucket", lambda: "bucket_name") + def raise_error(name): raise Exception("Firebase failure") + monkeypatch.setattr(storage_module.storage, "bucket", raise_error) - + result = await service.delete_image("folder/file") - + assert result is False + @pytest.mark.asyncio async def test_upload_image_workflow_success(monkeypatch, tmp_path): """Successfully validates, saves to quarantine, processes, and uploads an image.""" - dummy_file = UploadFile(filename="image.png", file=tmp_path.joinpath("dummy").open("wb+")) + dummy_file = UploadFile( + filename="image.png", file=tmp_path.joinpath("dummy").open("wb+") + ) dummy_file.file.write(b"dummy content") dummy_file.file.seek(0) # Patch validate_file, save_to_quarantine, move_quarantine_to_public monkeypatch.setattr(service, "validate_file", AsyncMock(return_value=True)) - monkeypatch.setattr(service, "save_to_quarantine", AsyncMock(return_value=str(tmp_path / "quarantine_file.webp"))) - monkeypatch.setattr(service, "move_quarantine_to_public", AsyncMock(return_value={"thumbnail": "url_thumb"})) - monkeypatch.setattr(service, "generate_secure_file_path", lambda entity_id, folder, filename: f"{folder}/{entity_id}/secure_id.png") + monkeypatch.setattr( + service, + "save_to_quarantine", + AsyncMock(return_value=str(tmp_path / "quarantine_file.webp")), + ) + monkeypatch.setattr( + service, + "move_quarantine_to_public", + AsyncMock(return_value={"thumbnail": "url_thumb"}), + ) + monkeypatch.setattr( + service, + "generate_secure_file_path", + lambda entity_id, folder, filename: f"{folder}/{entity_id}/secure_id.png", + ) urls = await service.upload_image_workflow(dummy_file, "users", "user123") @@ -510,28 +619,44 @@ async def test_upload_image_workflow_success(monkeypatch, tmp_path): service.save_to_quarantine.assert_awaited_once() service.move_quarantine_to_public.assert_awaited_once() + @pytest.mark.asyncio async def test_upload_image_workflow_invalid_file(monkeypatch): """Raises RuntimeError when the file is invalid.""" dummy_file = AsyncMock() dummy_file.read = AsyncMock(return_value=b"dummy content") - + monkeypatch.setattr(service, "validate_file", AsyncMock(return_value=False)) with pytest.raises(RuntimeError, match="Image upload failed."): await service.upload_image_workflow(dummy_file, "users", "user123") + @pytest.mark.asyncio async def test_upload_image_workflow_processing_failure(monkeypatch, tmp_path): """Raises RuntimeError if moving file from quarantine to public fails.""" - dummy_file = UploadFile(filename="image.png", file=tmp_path.joinpath("dummy").open("wb+")) + dummy_file = UploadFile( + filename="image.png", file=tmp_path.joinpath("dummy").open("wb+") + ) dummy_file.file.write(b"dummy content") dummy_file.file.seek(0) monkeypatch.setattr(service, "validate_file", AsyncMock(return_value=True)) - monkeypatch.setattr(service, "save_to_quarantine", AsyncMock(return_value=str(tmp_path / "quarantine_file.webp"))) - monkeypatch.setattr(service, "move_quarantine_to_public", AsyncMock(side_effect=RuntimeError("Upload failed"))) - monkeypatch.setattr(service, "generate_secure_file_path", lambda entity_id, folder, filename: f"{folder}/{entity_id}/secure_id.png") + monkeypatch.setattr( + service, + "save_to_quarantine", + AsyncMock(return_value=str(tmp_path / "quarantine_file.webp")), + ) + monkeypatch.setattr( + service, + "move_quarantine_to_public", + AsyncMock(side_effect=RuntimeError("Upload failed")), + ) + monkeypatch.setattr( + service, + "generate_secure_file_path", + lambda entity_id, folder, filename: f"{folder}/{entity_id}/secure_id.png", + ) with pytest.raises(RuntimeError, match="Image upload failed."): - await service.upload_image_workflow(dummy_file, "users", "user123") \ No newline at end of file + await service.upload_image_workflow(dummy_file, "users", "user123") diff --git a/backend/tests/user/test_user_service.py b/backend/tests/user/test_user_service.py index 103ea436..eaf42cae 100644 --- a/backend/tests/user/test_user_service.py +++ b/backend/tests/user/test_user_service.py @@ -301,8 +301,10 @@ async def test_delete_user_invalid_object_id(mock_db_client, mock_get_database): mock_db_client.users.delete_one.assert_not_called() assert result is False + # --- Tests for update_user_avatar_url --- + @pytest.mark.asyncio async def test_update_user_avatar_url_success(mock_db_client, mock_get_database): """Test successful user avatar URL update""" @@ -318,12 +320,14 @@ async def test_update_user_avatar_url_success(mock_db_client, mock_get_database) assert result is True mock_db_client.users.update_one.assert_called_once_with( - {"_id": ObjectId(user_id)}, - {"$set": {"imageUrl": image_url}} + {"_id": ObjectId(user_id)}, {"$set": {"imageUrl": image_url}} ) + @pytest.mark.asyncio -async def test_update_user_avatar_url_no_document_modified(mock_db_client, mock_get_database): +async def test_update_user_avatar_url_no_document_modified( + mock_db_client, mock_get_database +): """Test when no document is modified (user not found)""" user_id = "642f1e4a9b3c2d1f6a1b2c3d" image_url = "https://example.com/avatar.jpg" @@ -337,8 +341,11 @@ async def test_update_user_avatar_url_no_document_modified(mock_db_client, mock_ assert result is False + @pytest.mark.asyncio -async def test_update_user_avatar_url_invalid_object_id(mock_db_client, mock_get_database): +async def test_update_user_avatar_url_invalid_object_id( + mock_db_client, mock_get_database +): """Test with invalid ObjectId format""" invalid_user_id = "invalid_object_id" # Not a 24-char hex string image_url = "https://example.com/avatar.jpg" @@ -349,8 +356,11 @@ async def test_update_user_avatar_url_invalid_object_id(mock_db_client, mock_get # Should never hit the database mock_db_client.users.update_one.assert_not_called() + @pytest.mark.asyncio -async def test_update_user_avatar_url_empty_image_url(mock_db_client, mock_get_database): +async def test_update_user_avatar_url_empty_image_url( + mock_db_client, mock_get_database +): """Test updating with empty image URL""" user_id = "642f1e4a9b3c2d1f6a1b2c3d" image_url = "" @@ -364,10 +374,10 @@ async def test_update_user_avatar_url_empty_image_url(mock_db_client, mock_get_d assert result is True mock_db_client.users.update_one.assert_called_once_with( - {"_id": ObjectId(user_id)}, - {"$set": {"imageUrl": image_url}} + {"_id": ObjectId(user_id)}, {"$set": {"imageUrl": image_url}} ) + @pytest.mark.asyncio async def test_update_user_avatar_url_none_image_url(mock_db_client, mock_get_database): """Test updating with None image URL""" @@ -383,6 +393,5 @@ async def test_update_user_avatar_url_none_image_url(mock_db_client, mock_get_da assert result is True mock_db_client.users.update_one.assert_called_once_with( - {"_id": ObjectId(user_id)}, - {"$set": {"imageUrl": image_url}} - ) \ No newline at end of file + {"_id": ObjectId(user_id)}, {"$set": {"imageUrl": image_url}} + )