Files

130 строки
4.2 KiB
Python

from pathlib import Path
import shutil
from fastapi import APIRouter, File, Form, HTTPException, UploadFile
from backend.runtime_logs import add_log
router = APIRouter(prefix='/api/publish-files', tags=['publish-files'])
ALLOWED_TARGETS: dict[str, dict] = {
'firebase_android': {
'label': 'Firebase Android config',
'filename': 'google-services.json',
'relative_path': 'android/app/google-services.json',
'extensions': {'.json'},
},
'firebase_ios': {
'label': 'Firebase iOS config',
'filename': 'GoogleService-Info.plist',
'relative_path': 'ios/Runner/GoogleService-Info.plist',
'extensions': {'.plist'},
},
'android_release_key': {
'label': 'Android release keystore',
'filename': 'release-key.jks',
'relative_path': 'android/app/release-key.jks',
'extensions': {'.jks', '.keystore'},
},
'android_key_properties': {
'label': 'Android key properties',
'filename': 'key.properties',
'relative_path': 'android/key.properties',
'extensions': {'.properties'},
},
'play_store_service_account': {
'label': 'Google Play service account',
'filename': 'play-service-account.json',
'relative_path': 'android/play-service-account.json',
'extensions': {'.json'},
},
'publish_extra': {
'label': 'Extra publish file',
'filename': None,
'relative_path': 'publish-files',
'extensions': {'.json', '.plist', '.jks', '.keystore', '.properties', '.pem', '.p12', '.mobileprovision'},
'directory': True,
},
}
def _safe_workspace(workspace: str) -> Path:
path = Path(workspace).resolve()
if not path.exists() or not path.is_dir():
raise HTTPException(status_code=400, detail='Workspace path does not exist')
return path
def _safe_destination(workspace: Path, relative_path: str) -> Path:
destination = (workspace / relative_path).resolve()
try:
destination.relative_to(workspace)
except ValueError as exc:
raise HTTPException(status_code=400, detail='Invalid destination path') from exc
return destination
@router.get('/targets')
async def publish_file_targets():
return {
'success': True,
'targets': [
{
'id': target_id,
'label': config['label'],
'filename': config.get('filename'),
'relative_path': config['relative_path'],
'extensions': sorted(config['extensions']),
'directory': bool(config.get('directory')),
}
for target_id, config in ALLOWED_TARGETS.items()
],
}
@router.post('/upload')
async def upload_publish_file(
workspace: str = Form(...),
target: str = Form(...),
file: UploadFile = File(...),
):
config = ALLOWED_TARGETS.get(target)
if not config:
raise HTTPException(status_code=400, detail='Unknown publish file target')
source_name = Path(file.filename or '').name
source_ext = Path(source_name).suffix.lower()
if source_ext not in config['extensions']:
raise HTTPException(
status_code=400,
detail=f'Unsupported file extension for {config["label"]}',
)
workspace_path = _safe_workspace(workspace)
if config.get('directory'):
directory = _safe_destination(workspace_path, config['relative_path'])
directory.mkdir(parents=True, exist_ok=True)
destination = (directory / source_name).resolve()
try:
destination.relative_to(directory)
except ValueError as exc:
raise HTTPException(status_code=400, detail='Invalid upload filename') from exc
else:
destination = _safe_destination(workspace_path, config['relative_path'])
destination.parent.mkdir(parents=True, exist_ok=True)
with destination.open('wb') as output:
shutil.copyfileobj(file.file, output)
add_log(f'Publish file uploaded: {config["label"]} -> {destination}')
return {
'success': True,
'target': target,
'label': config['label'],
'path': destination.as_posix(),
'relative_path': destination.relative_to(workspace_path).as_posix(),
}