Files
ServerKit/backend/app/api/modules.py
T
Juan DenisandClaude Opus 4.8 3e373511b7 feat(extensions): Phase 1 — seed marketplace (GPU + Workflow), module toggles
- One-shot auto-install migration (extension_migration.py): converted builtins
  auto-install on upgrade, marketplace-only on fresh install, user uninstall
  respected
- Convert GPU Monitor → serverkit-gpu and Workflow Builder → serverkit-workflows
  (manifest-driven nav/route/title/palette; pages + backend stay core, D2)
- Module toggles (#14): module_service + /api/v1/modules + 503 guards on
  email/wordpress; frontend useModules hook, sidebar gating, ModuleRoute guard,
  Settings → Modules card
- Marketplace curation polish (#15): by-ServerKit badge, real category chips,
  detail modal with icon + screenshots
- Tests: test_extension_migration.py, test_module_toggles.py (backend suite green)

Tab-grouped conversions (#8/#9/#10/#12/#13) deferred to after the Phase 3
tab-slot primitive (#27) to avoid a grouped-UX regression — see plan doc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:34:54 -04:00

52 lines
1.6 KiB
Python

"""Modules API — toggle heavy core verticals (Email, WordPress) on/off.
Read is available to any authenticated user (the frontend needs it to hide nav
and guard routes); flipping a toggle is admin-only.
"""
from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required
from app.services import module_service
from app.services.audit_service import AuditService
from app.models.audit_log import AuditLog
modules_bp = Blueprint('modules', __name__)
def _current_user():
from flask_jwt_extended import get_jwt_identity
from app.models.user import User
return User.query.get(get_jwt_identity())
@modules_bp.route('', methods=['GET'])
@modules_bp.route('/', methods=['GET'])
@jwt_required()
def list_modules():
return jsonify({'modules': module_service.list_modules()})
@modules_bp.route('/<name>', methods=['PUT'])
@jwt_required()
def set_module(name):
user = _current_user()
if not user or not user.is_admin:
return jsonify({'error': 'Admin access required'}), 403
if name not in module_service.MODULES:
return jsonify({'error': f'Unknown module: {name}'}), 404
data = request.get_json() or {}
if 'enabled' not in data:
return jsonify({'error': 'enabled (boolean) required'}), 400
enabled = module_service.set_module_enabled(name, bool(data['enabled']), user_id=user.id)
AuditService.log(
action=AuditLog.ACTION_RESOURCE_ENABLE if enabled else AuditLog.ACTION_RESOURCE_DISABLE,
user_id=user.id,
target_type='module',
target_id=None,
details={'module': name, 'enabled': enabled},
)
return jsonify({'name': name, 'enabled': enabled})