Добавил API загрузки publish-файлов
Этот коммит содержится в:
@@ -0,0 +1,129 @@
|
||||
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(),
|
||||
}
|
||||
Ссылка в новой задаче
Block a user