""" app/api/photos.py ----------------- Mobile API endpoint for uploading photos from the iPad app. POST /api/v1/photos/upload Accepts a multipart/form-data upload. Saves the file to the server's upload folder. Returns the relative server path used in subsequent inspection/issue submissions. The iPad app uploads photos BEFORE submitting the inspection or issue, then includes the returned server_path in the inspection/issue payload. """ import os import uuid import logging from flask import Blueprint, request, g, current_app from app.api.errors import api_ok, api_error from app.api.decorators import jwt_required logger = logging.getLogger(__name__) bp = Blueprint('api_photos', __name__) _ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif'} _ALLOWED_ROLES = {'admin', 'director', 'inspector', 'project_manager', 'auditor'} def _allowed_file(filename: str) -> bool: return ( '.' in filename and filename.rsplit('.', 1)[-1].lower() in _ALLOWED_EXTENSIONS ) # ── Upload Photo ────────────────────────────────────────────────────────────── @bp.route('/photos/upload', methods=['POST']) @jwt_required def upload_photo(): """ Upload a photo from the iPad app. Multipart form fields --------------------- file — binary image data (jpg / png / gif) entity_type — "inspection" | "issue" | "issue_result" (controls subfolder) Response 200 ------------ { "ok": true, "data": { "server_path": "uploads/inspection_photos/abc123.jpg" } } """ user = g.api_user if user.role not in _ALLOWED_ROLES: return api_error('Access denied', 403) if 'file' not in request.files: return api_error('No file provided', 400) file_obj = request.files['file'] entity_type = request.form.get('entity_type', 'inspection') if not file_obj or not file_obj.filename: return api_error('Empty file', 400) if not _allowed_file(file_obj.filename): return api_error( f'File type not allowed. Accepted: {", ".join(sorted(_ALLOWED_EXTENSIONS))}', 400 ) # Determine destination subfolder if entity_type == 'issue': subfolder = 'issue_photos' elif entity_type == 'issue_result': subfolder = 'issue_result_photos' else: subfolder = 'inspection_photos' ext = file_obj.filename.rsplit('.', 1)[-1].lower() filename = f'{uuid.uuid4().hex}.{ext}' dest_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], subfolder) os.makedirs(dest_dir, exist_ok=True) dest_path = os.path.join(dest_dir, filename) file_obj.save(dest_path) server_path = f'uploads/{subfolder}/{filename}' logger.info('API PHOTOS | uploaded | entity_type=%s | path=%s | user=%s', entity_type, server_path, user.username) return api_ok({'server_path': server_path})