multiple media upload and smartphone preview
This commit is contained in:
@@ -1533,6 +1533,14 @@ async def post_detail_page(request: Request, post_id: str):
|
||||
if post.critic_feedback and len(post.critic_feedback) > 0:
|
||||
final_feedback = post.critic_feedback[-1]
|
||||
|
||||
# Convert media_items to dicts for JSON serialization in template
|
||||
media_items_dict = []
|
||||
if post.media_items:
|
||||
media_items_dict = [
|
||||
item.model_dump(mode='json') if hasattr(item, 'model_dump') else (item.dict() if hasattr(item, 'dict') else item)
|
||||
for item in post.media_items
|
||||
]
|
||||
|
||||
return templates.TemplateResponse("post_detail.html", {
|
||||
"request": request,
|
||||
"page": "posts",
|
||||
@@ -1544,7 +1552,8 @@ async def post_detail_page(request: Request, post_id: str):
|
||||
"post_type": post_type,
|
||||
"post_type_analysis": post_type_analysis,
|
||||
"final_feedback": final_feedback,
|
||||
"profile_picture_url": profile_picture_url
|
||||
"profile_picture_url": profile_picture_url,
|
||||
"media_items_dict": media_items_dict
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading post detail: {e}")
|
||||
@@ -2679,6 +2688,14 @@ async def company_manage_post_detail(request: Request, post_id: str, employee_id
|
||||
|
||||
profile = await db.get_profile(emp_profile.id)
|
||||
|
||||
# Convert media_items to dicts for JSON serialization in template
|
||||
media_items_dict = []
|
||||
if post.media_items:
|
||||
media_items_dict = [
|
||||
item.model_dump(mode='json') if hasattr(item, 'model_dump') else (item.dict() if hasattr(item, 'dict') else item)
|
||||
for item in post.media_items
|
||||
]
|
||||
|
||||
return templates.TemplateResponse("company_manage_post_detail.html", {
|
||||
"request": request,
|
||||
"page": "manage",
|
||||
@@ -2686,7 +2703,8 @@ async def company_manage_post_detail(request: Request, post_id: str, employee_id
|
||||
"employee_id": employee_id,
|
||||
"employee_name": emp_user.linkedin_name or emp_profile.display_name or emp_user.email,
|
||||
"profile": profile,
|
||||
"post": post
|
||||
"post": post,
|
||||
"media_items_dict": media_items_dict
|
||||
})
|
||||
|
||||
|
||||
@@ -3433,61 +3451,18 @@ async def unschedule_post(request: Request, post_id: str):
|
||||
|
||||
@user_router.post("/api/posts/{post_id}/image")
|
||||
async def upload_post_image(request: Request, post_id: str):
|
||||
"""Upload or replace an image for a post."""
|
||||
session = require_user_session(request)
|
||||
if not session:
|
||||
raise HTTPException(status_code=401, detail="Not authenticated")
|
||||
"""DEPRECATED: Upload or replace an image for a post.
|
||||
|
||||
try:
|
||||
post = await db.get_generated_post(UUID(post_id))
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="Post not found")
|
||||
Use /api/posts/{post_id}/media instead. This endpoint is kept for backward compatibility.
|
||||
"""
|
||||
# Delegate to new media upload endpoint
|
||||
result = await upload_post_media(request, post_id)
|
||||
|
||||
# Check authorization: owner or company owner
|
||||
is_owner = session.user_id and str(post.user_id) == session.user_id
|
||||
is_company_owner = False
|
||||
if not is_owner and session.account_type == "company" and session.company_id:
|
||||
profile = await db.get_profile(post.user_id)
|
||||
if profile and profile.company_id and str(profile.company_id) == session.company_id:
|
||||
is_company_owner = True
|
||||
if not is_owner and not is_company_owner:
|
||||
raise HTTPException(status_code=403, detail="Not authorized")
|
||||
# Return legacy format for compatibility
|
||||
if result.get("success") and result.get("media_item"):
|
||||
return {"success": True, "image_url": result["media_item"]["url"]}
|
||||
|
||||
# Read file from form data
|
||||
form = await request.form()
|
||||
image = form.get("image")
|
||||
if not image or not hasattr(image, "read"):
|
||||
raise HTTPException(status_code=400, detail="No image file provided")
|
||||
|
||||
file_content = await image.read()
|
||||
content_type = image.content_type or "application/octet-stream"
|
||||
|
||||
# Delete old image if exists
|
||||
if post.image_url:
|
||||
try:
|
||||
await storage.delete_image(post.image_url)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to delete old image: {e}")
|
||||
|
||||
# Upload new image
|
||||
image_url = await storage.upload_image(
|
||||
file_content=file_content,
|
||||
content_type=content_type,
|
||||
user_id=str(post.user_id),
|
||||
)
|
||||
|
||||
# Update post record
|
||||
await db.update_generated_post(UUID(post_id), {"image_url": image_url})
|
||||
|
||||
return {"success": True, "image_url": image_url}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed to upload post image: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
return result
|
||||
|
||||
|
||||
@user_router.delete("/api/posts/{post_id}/image")
|
||||
@@ -3531,6 +3506,209 @@ async def delete_post_image(request: Request, post_id: str):
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
# ==================== POST MEDIA UPLOAD (MULTI-MEDIA) ====================
|
||||
|
||||
@user_router.post("/api/posts/{post_id}/media")
|
||||
async def upload_post_media(request: Request, post_id: str):
|
||||
"""Upload a media item (image or video). Max 3 items per post."""
|
||||
session = require_user_session(request)
|
||||
if not session:
|
||||
raise HTTPException(status_code=401, detail="Not authenticated")
|
||||
|
||||
try:
|
||||
post = await db.get_generated_post(UUID(post_id))
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="Post not found")
|
||||
|
||||
# Check authorization: owner or company owner
|
||||
is_owner = session.user_id and str(post.user_id) == session.user_id
|
||||
is_company_owner = False
|
||||
if not is_owner and session.account_type == "company" and session.company_id:
|
||||
profile = await db.get_profile(post.user_id)
|
||||
if profile and profile.company_id and str(profile.company_id) == session.company_id:
|
||||
is_company_owner = True
|
||||
if not is_owner and not is_company_owner:
|
||||
raise HTTPException(status_code=403, detail="Not authorized")
|
||||
|
||||
# Get current media items (convert to dicts with JSON-serializable datetime)
|
||||
media_items = [
|
||||
item.model_dump(mode='json') if hasattr(item, 'model_dump') else (item.dict() if hasattr(item, 'dict') else item)
|
||||
for item in (post.media_items or [])
|
||||
]
|
||||
|
||||
# Validate: Max 3 items
|
||||
if len(media_items) >= 3:
|
||||
raise HTTPException(status_code=400, detail="Maximal 3 Medien erlaubt")
|
||||
|
||||
# Read file from form data
|
||||
form = await request.form()
|
||||
file = form.get("file") or form.get("image") # Support both 'file' and 'image' keys
|
||||
if not file or not hasattr(file, "read"):
|
||||
raise HTTPException(status_code=400, detail="No file provided")
|
||||
|
||||
file_content = await file.read()
|
||||
content_type = file.content_type or "application/octet-stream"
|
||||
|
||||
# Validate media type consistency (no mixing)
|
||||
is_video = content_type.startswith("video/")
|
||||
has_videos = any(item.get("type") == "video" for item in media_items)
|
||||
has_images = any(item.get("type") == "image" for item in media_items)
|
||||
|
||||
if (is_video and has_images) or (not is_video and has_videos):
|
||||
raise HTTPException(status_code=400, detail="Kann nicht Bilder und Videos mischen")
|
||||
|
||||
# Only allow 1 video max
|
||||
if is_video and len(media_items) > 0:
|
||||
raise HTTPException(status_code=400, detail="Nur 1 Video pro Post erlaubt")
|
||||
|
||||
# Upload to storage
|
||||
media_url = await storage.upload_media(
|
||||
file_content=file_content,
|
||||
content_type=content_type,
|
||||
user_id=str(post.user_id),
|
||||
)
|
||||
|
||||
# Add to array
|
||||
new_item = {
|
||||
"type": "video" if is_video else "image",
|
||||
"url": media_url,
|
||||
"order": len(media_items),
|
||||
"content_type": content_type,
|
||||
"uploaded_at": datetime.now(timezone.utc).isoformat(),
|
||||
"metadata": None
|
||||
}
|
||||
media_items.append(new_item)
|
||||
|
||||
# Update DB
|
||||
await db.update_generated_post(UUID(post_id), {"media_items": media_items})
|
||||
|
||||
# Also update image_url for backward compatibility (first image)
|
||||
if new_item["type"] == "image" and len([i for i in media_items if i["type"] == "image"]) == 1:
|
||||
await db.update_generated_post(UUID(post_id), {"image_url": media_url})
|
||||
|
||||
return {"success": True, "media_item": new_item, "media_items": media_items}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed to upload post media: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@user_router.delete("/api/posts/{post_id}/media/{media_index}")
|
||||
async def delete_post_media(request: Request, post_id: str, media_index: int):
|
||||
"""Delete a specific media item by index."""
|
||||
session = require_user_session(request)
|
||||
if not session:
|
||||
raise HTTPException(status_code=401, detail="Not authenticated")
|
||||
|
||||
try:
|
||||
post = await db.get_generated_post(UUID(post_id))
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="Post not found")
|
||||
|
||||
# Check authorization: owner or company owner
|
||||
is_owner = session.user_id and str(post.user_id) == session.user_id
|
||||
is_company_owner = False
|
||||
if not is_owner and session.account_type == "company" and session.company_id:
|
||||
profile = await db.get_profile(post.user_id)
|
||||
if profile and profile.company_id and str(profile.company_id) == session.company_id:
|
||||
is_company_owner = True
|
||||
if not is_owner and not is_company_owner:
|
||||
raise HTTPException(status_code=403, detail="Not authorized")
|
||||
|
||||
# Get current media items (convert to dicts with JSON-serializable datetime)
|
||||
media_items = [
|
||||
item.model_dump(mode='json') if hasattr(item, 'model_dump') else (item.dict() if hasattr(item, 'dict') else item)
|
||||
for item in (post.media_items or [])
|
||||
]
|
||||
|
||||
if media_index < 0 or media_index >= len(media_items):
|
||||
raise HTTPException(status_code=404, detail="Media item nicht gefunden")
|
||||
|
||||
# Delete from storage
|
||||
item_to_delete = media_items[media_index]
|
||||
try:
|
||||
await storage.delete_image(item_to_delete["url"])
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to delete media from storage: {e}")
|
||||
|
||||
# Remove and re-index
|
||||
media_items.pop(media_index)
|
||||
for i, item in enumerate(media_items):
|
||||
item["order"] = i
|
||||
|
||||
# Update DB
|
||||
await db.update_generated_post(UUID(post_id), {"media_items": media_items})
|
||||
|
||||
# Update image_url for backward compatibility
|
||||
first_image = next((item for item in media_items if item["type"] == "image"), None)
|
||||
await db.update_generated_post(UUID(post_id), {"image_url": first_image["url"] if first_image else None})
|
||||
|
||||
return {"success": True, "media_items": media_items}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed to delete post media: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@user_router.put("/api/posts/{post_id}/media/reorder")
|
||||
async def reorder_post_media(request: Request, post_id: str):
|
||||
"""Reorder media items. Expects JSON: {"order": [2, 0, 1]}"""
|
||||
session = require_user_session(request)
|
||||
if not session:
|
||||
raise HTTPException(status_code=401, detail="Not authenticated")
|
||||
|
||||
try:
|
||||
post = await db.get_generated_post(UUID(post_id))
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="Post not found")
|
||||
|
||||
# Check authorization: owner or company owner
|
||||
is_owner = session.user_id and str(post.user_id) == session.user_id
|
||||
is_company_owner = False
|
||||
if not is_owner and session.account_type == "company" and session.company_id:
|
||||
profile = await db.get_profile(post.user_id)
|
||||
if profile and profile.company_id and str(profile.company_id) == session.company_id:
|
||||
is_company_owner = True
|
||||
if not is_owner and not is_company_owner:
|
||||
raise HTTPException(status_code=403, detail="Not authorized")
|
||||
|
||||
# Get current media items (convert to dicts with JSON-serializable datetime)
|
||||
media_items = [
|
||||
item.model_dump(mode='json') if hasattr(item, 'model_dump') else (item.dict() if hasattr(item, 'dict') else item)
|
||||
for item in (post.media_items or [])
|
||||
]
|
||||
|
||||
# Parse request body
|
||||
body = await request.json()
|
||||
new_order = body.get("order", [])
|
||||
|
||||
# Validate order array
|
||||
if len(new_order) != len(media_items) or set(new_order) != set(range(len(media_items))):
|
||||
raise HTTPException(status_code=400, detail="Invalid order indices")
|
||||
|
||||
# Reorder
|
||||
reordered = [media_items[i] for i in new_order]
|
||||
for i, item in enumerate(reordered):
|
||||
item["order"] = i
|
||||
|
||||
# Update DB
|
||||
await db.update_generated_post(UUID(post_id), {"media_items": reordered})
|
||||
|
||||
return {"success": True, "media_items": reordered}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed to reorder post media: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
# ==================== IMAGE PROXY FOR HTTPS ====================
|
||||
|
||||
@user_router.get("/proxy/image/{bucket}/{path:path}")
|
||||
|
||||
Reference in New Issue
Block a user