Restore api/migrations/models/services source files accidentally removed in gitignore commit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c8fad1cf36
commit
907243c2cd
31 changed files with 29855 additions and 0 deletions
118
backend/api/public.py
Normal file
118
backend/api/public.py
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
"""
|
||||
Public API endpoints - NO AUTHENTICATION REQUIRED.
|
||||
|
||||
These endpoints are designed for sharing with external parties (e.g., suppliers)
|
||||
via hash-based URLs that don't require login.
|
||||
"""
|
||||
import os
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import Response
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from fastapi import Depends
|
||||
|
||||
from database import get_db
|
||||
from models.dispute import DisputeAttachment
|
||||
from models.settings import KitchenSettings
|
||||
from services.nextcloud_service import NextcloudService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/attachments/{public_hash}")
|
||||
async def get_public_attachment(
|
||||
public_hash: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
View a dispute attachment publicly via its hash.
|
||||
|
||||
This endpoint does NOT require authentication, allowing suppliers
|
||||
to view attached images/documents via shareable links in emails.
|
||||
"""
|
||||
# Find attachment by public hash
|
||||
result = await db.execute(
|
||||
select(DisputeAttachment).where(DisputeAttachment.public_hash == public_hash)
|
||||
)
|
||||
attachment = result.scalar_one_or_none()
|
||||
|
||||
if not attachment:
|
||||
raise HTTPException(status_code=404, detail="Attachment not found")
|
||||
|
||||
# Get file content
|
||||
content = None
|
||||
|
||||
# Try local file first
|
||||
if attachment.file_storage_location == "local" and attachment.file_path:
|
||||
if os.path.exists(attachment.file_path):
|
||||
with open(attachment.file_path, 'rb') as f:
|
||||
content = f.read()
|
||||
|
||||
# Try Nextcloud if local not found
|
||||
if content is None and attachment.file_storage_location == "nextcloud" and attachment.nextcloud_path:
|
||||
# Get kitchen settings for Nextcloud credentials
|
||||
settings_result = await db.execute(
|
||||
select(KitchenSettings).where(KitchenSettings.kitchen_id == attachment.kitchen_id)
|
||||
)
|
||||
settings = settings_result.scalar_one_or_none()
|
||||
|
||||
if settings and settings.nextcloud_enabled:
|
||||
nc = NextcloudService(
|
||||
settings.nextcloud_host,
|
||||
settings.nextcloud_username,
|
||||
settings.nextcloud_password,
|
||||
""
|
||||
)
|
||||
success, nc_content = await nc.download_file(attachment.nextcloud_path)
|
||||
await nc.close()
|
||||
|
||||
if success:
|
||||
content = nc_content
|
||||
|
||||
if content is None:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
# Determine if browser should display inline or download
|
||||
# Images and PDFs display inline, others download
|
||||
inline_types = [
|
||||
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
|
||||
'application/pdf'
|
||||
]
|
||||
|
||||
disposition = "inline" if attachment.file_type in inline_types else "attachment"
|
||||
|
||||
return Response(
|
||||
content=content,
|
||||
media_type=attachment.file_type,
|
||||
headers={
|
||||
"Content-Disposition": f'{disposition}; filename="{attachment.file_name}"',
|
||||
"Cache-Control": "private, max-age=3600" # Cache for 1 hour
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.get("/attachments/{public_hash}/info")
|
||||
async def get_public_attachment_info(
|
||||
public_hash: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Get attachment metadata without downloading the file.
|
||||
Useful for email previews or link unfurling.
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(DisputeAttachment).where(DisputeAttachment.public_hash == public_hash)
|
||||
)
|
||||
attachment = result.scalar_one_or_none()
|
||||
|
||||
if not attachment:
|
||||
raise HTTPException(status_code=404, detail="Attachment not found")
|
||||
|
||||
return {
|
||||
"file_name": attachment.file_name,
|
||||
"file_type": attachment.file_type,
|
||||
"file_size_bytes": attachment.file_size_bytes,
|
||||
"attachment_type": attachment.attachment_type,
|
||||
"description": attachment.description,
|
||||
"uploaded_at": attachment.uploaded_at.isoformat() if attachment.uploaded_at else None
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue