1111
1212from pathlib import Path
1313
14- from fastapi import APIRouter , HTTPException
15- from fastapi .responses import FileResponse
14+ from fastapi import APIRouter , HTTPException , Response
1615from loguru import logger
1716
1817from basic_memory .deps import (
3029 ResourceResponse ,
3130)
3231from basic_memory .utils import validate_project_path
33- from datetime import datetime
3432
3533router = APIRouter (prefix = "/resource" , tags = ["resources-v2" ])
3634
@@ -42,7 +40,7 @@ async def get_resource_content(
4240 config : ProjectConfigV2Dep ,
4341 entity_service : EntityServiceV2Dep ,
4442 file_service : FileServiceV2Dep ,
45- ) -> FileResponse :
43+ ) -> Response :
4644 """Get raw resource content by entity ID.
4745
4846 Args:
@@ -53,7 +51,7 @@ async def get_resource_content(
5351 file_service: File service for reading file content
5452
5553 Returns:
56- FileResponse with entity content
54+ Response with entity content
5755
5856 Raises:
5957 HTTPException: 404 if entity or file not found
@@ -76,14 +74,18 @@ async def get_resource_content(
7674 detail = "Entity contains invalid file path" ,
7775 )
7876
79- file_path = Path ( f" { config . home } / { entity . file_path } " )
80- if not file_path .exists ():
77+ # Check file exists via file_service (for cloud compatibility )
78+ if not await file_service .exists (entity . file_path ):
8179 raise HTTPException (
8280 status_code = 404 ,
83- detail = f"File not found: { file_path } " ,
81+ detail = f"File not found: { entity . file_path } " ,
8482 )
8583
86- return FileResponse (path = file_path )
84+ # Read content via file_service as bytes (works with both local and S3)
85+ content = await file_service .read_file_bytes (entity .file_path )
86+ content_type = file_service .content_type (entity .file_path )
87+
88+ return Response (content = content , media_type = content_type )
8789
8890
8991@router .post ("" , response_model = ResourceResponse )
@@ -143,7 +145,7 @@ async def create_resource(
143145 checksum = await file_service .write_file (full_path , data .content )
144146
145147 # Get file info
146- file_stats = file_service .file_stats (full_path )
148+ file_metadata = await file_service .get_file_metadata (full_path )
147149
148150 # Determine file details
149151 file_name = Path (data .file_path ).name
@@ -157,8 +159,8 @@ async def create_resource(
157159 content_type = content_type ,
158160 file_path = data .file_path ,
159161 checksum = checksum ,
160- created_at = datetime . fromtimestamp ( file_stats . st_ctime ). astimezone () ,
161- updated_at = datetime . fromtimestamp ( file_stats . st_mtime ). astimezone () ,
162+ created_at = file_metadata . created_at ,
163+ updated_at = file_metadata . modified_at ,
162164 )
163165 entity = await entity_repository .add (entity )
164166
@@ -170,9 +172,9 @@ async def create_resource(
170172 entity_id = entity .id ,
171173 file_path = data .file_path ,
172174 checksum = checksum ,
173- size = file_stats . st_size ,
174- created_at = file_stats . st_ctime ,
175- modified_at = file_stats . st_mtime ,
175+ size = file_metadata . size ,
176+ created_at = file_metadata . created_at . timestamp () ,
177+ modified_at = file_metadata . modified_at . timestamp () ,
176178 )
177179 except HTTPException :
178180 # Re-raise HTTP exceptions without wrapping
@@ -233,17 +235,16 @@ async def update_resource(
233235 )
234236
235237 # Get full paths
236- old_full_path = Path (f"{ config .home } /{ entity .file_path } " )
237238 new_full_path = Path (f"{ config .home } /{ target_file_path } " )
238239
239240 # If moving file, handle the move
240241 if data .file_path and data .file_path != entity .file_path :
241242 # Ensure new parent directory exists
242243 new_full_path .parent .mkdir (parents = True , exist_ok = True )
243244
244- # If old file exists, remove it
245- if old_full_path .exists ():
246- old_full_path . unlink ( )
245+ # If old file exists, remove it via file_service (for cloud compatibility)
246+ if await file_service .exists (entity . file_path ):
247+ await file_service . delete_file ( entity . file_path )
247248 else :
248249 # Ensure directory exists for in-place update
249250 new_full_path .parent .mkdir (parents = True , exist_ok = True )
@@ -252,7 +253,7 @@ async def update_resource(
252253 checksum = await file_service .write_file (new_full_path , data .content )
253254
254255 # Get file info
255- file_stats = file_service .file_stats (new_full_path )
256+ file_metadata = await file_service .get_file_metadata (new_full_path )
256257
257258 # Determine file details
258259 file_name = Path (target_file_path ).name
@@ -268,7 +269,7 @@ async def update_resource(
268269 "content_type" : content_type ,
269270 "file_path" : target_file_path ,
270271 "checksum" : checksum ,
271- "updated_at" : datetime . fromtimestamp ( file_stats . st_mtime ). astimezone () ,
272+ "updated_at" : file_metadata . modified_at ,
272273 },
273274 )
274275
@@ -280,9 +281,9 @@ async def update_resource(
280281 entity_id = entity_id ,
281282 file_path = target_file_path ,
282283 checksum = checksum ,
283- size = file_stats . st_size ,
284- created_at = file_stats . st_ctime ,
285- modified_at = file_stats . st_mtime ,
284+ size = file_metadata . size ,
285+ created_at = file_metadata . created_at . timestamp () ,
286+ modified_at = file_metadata . modified_at . timestamp () ,
286287 )
287288 except HTTPException :
288289 # Re-raise HTTP exceptions without wrapping
0 commit comments