Files
ServerKit/backend/app/api/source_connections.py
T
Juan Denis 99abf8f147 Add repository manifest detection and templates
Introduce RepositoryManifestService to detect deployment manifests (ServerKit, Render, Railway, Compose, app.json, Dockerfile, Nixpacks) and recommend app/build settings. Add backend integration: analyze repo during import (apps.create_app_from_repository), new GitHub manifest API route, and SourceConnectionService helpers to list root files and fetch file contents from GitHub. Pass resolved dockerfile_path/custom build/start commands into build configuration. Add unit tests for the manifest service. Frontend: add template support and manifest UI in NewService.jsx, new API call inspectGithubRepositoryManifest, and related SCSS styles for templates/manifest display. Misc: updated imports and minor UX messaging changes.
2026-05-03 07:07:57 -04:00

144 lines
4.7 KiB
Python

"""Source provider connection API."""
from flask import Blueprint, jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity
from requests import HTTPError
from app.models import User
from app.middleware.rbac import admin_required
from app.services.source_connection_service import SourceConnectionService
source_connections_bp = Blueprint('source_connections', __name__)
@source_connections_bp.route('/github/status', methods=['GET'])
@jwt_required()
def github_status():
user_id = int(get_jwt_identity())
return jsonify(SourceConnectionService.get_status(user_id)), 200
@source_connections_bp.route('/github/authorize', methods=['GET'])
@jwt_required()
def github_authorize():
redirect_uri = request.args.get('redirect_uri', '')
if not redirect_uri:
return jsonify({'error': 'redirect_uri is required'}), 400
try:
auth_url, state = SourceConnectionService.generate_github_authorize_url(redirect_uri)
return jsonify({'auth_url': auth_url, 'state': state}), 200
except ValueError as exc:
return jsonify({'error': str(exc)}), 400
@source_connections_bp.route('/github/callback', methods=['POST'])
@jwt_required()
def github_callback():
user_id = int(get_jwt_identity())
data = request.get_json() or {}
code = data.get('code', '')
state = data.get('state', '')
redirect_uri = data.get('redirect_uri', '')
if not code or not state or not redirect_uri:
return jsonify({'error': 'code, state, and redirect_uri are required'}), 400
try:
connection = SourceConnectionService.complete_github_callback(
user_id=user_id,
code=code,
state=state,
redirect_uri=redirect_uri,
)
return jsonify({'connection': connection.to_dict()}), 200
except ValueError as exc:
return jsonify({'error': str(exc)}), 400
except HTTPError as exc:
return jsonify({'error': _github_error(exc)}), 400
@source_connections_bp.route('/github/repos', methods=['GET'])
@jwt_required()
def github_repositories():
user_id = int(get_jwt_identity())
search = request.args.get('search', '')
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 50, type=int)
try:
repos = SourceConnectionService.list_github_repositories(user_id, search, page, per_page)
return jsonify({'repos': repos}), 200
except ValueError as exc:
return jsonify({'error': str(exc)}), 400
except HTTPError as exc:
return jsonify({'error': _github_error(exc)}), 400
@source_connections_bp.route('/github/repos/<path:full_name>/branches', methods=['GET'])
@jwt_required()
def github_branches(full_name):
user_id = int(get_jwt_identity())
try:
branches = SourceConnectionService.list_github_branches(user_id, full_name)
return jsonify({'branches': branches}), 200
except ValueError as exc:
return jsonify({'error': str(exc)}), 400
except HTTPError as exc:
return jsonify({'error': _github_error(exc)}), 400
@source_connections_bp.route('/github/repos/<path:full_name>/manifest', methods=['GET'])
@jwt_required()
def github_repository_manifest(full_name):
user_id = int(get_jwt_identity())
ref = request.args.get('ref') or None
try:
manifest = SourceConnectionService.get_github_repository_manifest(user_id, full_name, ref)
return jsonify({'manifest': manifest}), 200
except ValueError as exc:
return jsonify({'error': str(exc)}), 400
except HTTPError as exc:
return jsonify({'error': _github_error(exc)}), 400
@source_connections_bp.route('/github', methods=['DELETE'])
@jwt_required()
def disconnect_github():
user_id = int(get_jwt_identity())
try:
SourceConnectionService.disconnect(user_id, 'github')
return jsonify({'message': 'GitHub disconnected'}), 200
except ValueError as exc:
return jsonify({'error': str(exc)}), 404
@source_connections_bp.route('/admin/github', methods=['GET'])
@admin_required
def github_admin_config():
return jsonify({'config': SourceConnectionService.get_github_config(redacted=True)}), 200
@source_connections_bp.route('/admin/github', methods=['PUT'])
@admin_required
def update_github_admin_config():
user = User.query.get(get_jwt_identity())
result = SourceConnectionService.update_github_config(
request.get_json() or {},
user_id=user.id if user else None,
)
return jsonify(result), 200
def _github_error(exc):
response = getattr(exc, 'response', None)
if response is None:
return str(exc)
try:
data = response.json()
return data.get('message') or str(exc)
except Exception:
return response.text or str(exc)