"""File upload config logic.""" import logging from datetime import datetime, timezone from abacus_common_logic.connectors.database import db from marshmallow import ValidationError from owsresponse import response from sqlalchemy.exc import IntegrityError from abacus_file_upload.models import FileUploadConfig from abacus_file_upload.schemas import ( FileUploadConfigDetailSchema, FileUploadConfigDetailVerboseSchema, FileUploadConfigPostSchema, FileUploadConfigPutSchema, ) from abacus_file_upload.utils import validation_error from core.config import Config # Initialize schemas file_upload_config_detail_schema = FileUploadConfigDetailSchema() file_upload_config_detail_verbose_schema = FileUploadConfigDetailVerboseSchema() file_upload_config_post_schema = FileUploadConfigPostSchema() file_upload_config_put_schema = FileUploadConfigPutSchema() logger = logging.getLogger(Config.LOGGER_NAME) def create_file_upload_config(**params) -> response.Response: """Create a new file upload configuration. Args: **params: Configuration parameters including: - upload_type: Type identifier for the upload - s3_key_template: S3 path/key template - allowed_file_types: Array of allowed file extensions - max_file_size_bytes: Maximum file size in bytes - multipart_threshold_bytes: File size threshold for multipart upload - min_multipart_chunk_size_bytes: Minimum chunk size for multipart uploads - description: Description of what this upload config is for - event_name: An event that can be configured to trigger a lambda or an Airflow DAG ) Returns: Response with created config """ try: validated_data = file_upload_config_post_schema.load(params) except ValidationError as e: return validation_error(str(e.messages)) # Create the config. Uses server defaults where exist and custom value not given. try: # Start with required fields create_params = { 'upload_type': validated_data['upload_type'], 's3_key_template': validated_data['s3_key_template'], } # Add optional fields if explicitly provided optional_fields = [ 'allowed_file_types', 'max_file_size_bytes', 'multipart_threshold_bytes', 'min_multipart_chunk_size_bytes', 'description', 'event_name', ] for field in optional_fields: if field in validated_data: create_params[field] = validated_data[field] new_config = FileUploadConfig.create(**create_params) # Refresh to get DB-generated values db.session.refresh(new_config) return response.Response( message=file_upload_config_detail_schema.dump(new_config), status=201 ) except IntegrityError: db.session.rollback() logger.warning( f"Duplicate upload config: type '{validated_data['upload_type']}' already exists" ) return response.Response( message=f"Upload config with type '{validated_data['upload_type']}' already exists", status=409, ) except Exception as e: db.session.rollback() logger.error(f'Failed to create upload config: {e}') return response.Response(message=str(e), status=500) def delete_file_upload_config(file_upload_config_id: int) -> response.Response: """Soft delete file upload configuration. Args: file_upload_config_id: ID of the upload config Returns: Response confirming deletion """ FileUploadConfig.delete_by_id_or_error( file_upload_config_id, error_status=404, soft_delete=True ) return response.Response(status=204) def get_all_file_upload_configs(verbose: bool = False) -> response.Response: """Get all active file upload configurations. Args: verbose: If True, include audit fields (created_at, created_by, etc.) Returns: Response with list of configs """ try: configs = ( FileUploadConfig.query.filter(FileUploadConfig.deleted_at.is_(None)) .order_by(FileUploadConfig.upload_type) .all() ) schema = ( file_upload_config_detail_verbose_schema if verbose else file_upload_config_detail_schema ) return response.Response( message=[schema.dump(c) for c in configs], status=200, ) except Exception as e: logger.error(f'Failed to retrieve upload configs: {e}') return response.Response(message=str(e), status=500) def get_file_upload_config( file_upload_config_id: int, verbose: bool = False ) -> response.Response: """Get file upload configuration by ID. Args: file_upload_config_id: ID of the upload config verbose: If True, include audit fields (created_at, created_by, etc.) Returns: Response with config details """ config = FileUploadConfig.get_by_id(file_upload_config_id) if not config or (config.deleted_at is not None and config.deleted_by is not None): return response.Response( message=f"Upload config with ID '{file_upload_config_id}' not found", status=404, ) schema = ( file_upload_config_detail_verbose_schema if verbose else file_upload_config_detail_schema ) return response.Response(message=schema.dump(config), status=200) def get_file_upload_config_by_type( upload_type: str, verbose: bool = False ) -> response.Response: """Get file upload configuration by upload_type. Args: upload_type: Type identifier for the upload (e.g., 'adjustments', 'flowthrough') verbose: If True, include audit fields (created_at, created_by, etc.) Returns: Response with config details """ config = FileUploadConfig.find_by_upload_type(upload_type) if not config: return response.Response( message=f"Upload config with type '{upload_type}' not found", status=404 ) schema = ( file_upload_config_detail_verbose_schema if verbose else file_upload_config_detail_schema ) return response.Response(message=schema.dump(config), status=200) def update_file_upload_config( file_upload_config_id: int, **params ) -> response.Response: """Update file upload configuration. Args: file_upload_config_id: ID of the upload config **params: Fields to update Returns: Response with updated config """ config = FileUploadConfig.get_by_id(file_upload_config_id) if not config: return response.Response( message=f"Upload config with ID '{file_upload_config_id}' not found", status=404, ) try: validated_data = file_upload_config_put_schema.load(params) except ValidationError as e: return validation_error(str(e.messages)) try: # Update fields using update_attributes config.update_attributes(**validated_data) FileUploadConfig.commit_changes() # Refresh to get DB-generated values db.session.refresh(config) return response.Response( message=file_upload_config_detail_verbose_schema.dump(config), status=200 ) except Exception as e: db.session.rollback() logger.error(f'Failed to update upload config {file_upload_config_id}: {e}') return response.Response(message=str(e), status=500)