Initial commit: Office365 web management platform
This commit is contained in:
291
office365_admin/routes.py
Normal file
291
office365_admin/routes.py
Normal file
@@ -0,0 +1,291 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import wraps
|
||||
import logging
|
||||
|
||||
from flask import Blueprint, current_app, jsonify, render_template, request, session
|
||||
|
||||
from .batch import BatchInputError, parse_identifier_content, parse_table_content
|
||||
from .services import Office365Service, ServiceConfigurationError, ServiceOperationError
|
||||
from .tasks import BackgroundTaskManager, TaskNotFoundError
|
||||
|
||||
|
||||
bp = Blueprint("office365_admin", __name__)
|
||||
logger = logging.getLogger("office365_admin.routes")
|
||||
|
||||
|
||||
def _settings():
|
||||
return current_app.config["SETTINGS"]
|
||||
|
||||
|
||||
def _service() -> Office365Service:
|
||||
return current_app.extensions["office365_service"]
|
||||
|
||||
|
||||
def _task_manager() -> BackgroundTaskManager:
|
||||
return current_app.extensions["task_manager"]
|
||||
|
||||
|
||||
def _success(data=None, message: str = "ok", status: int = 200):
|
||||
return jsonify({"success": True, "message": message, "data": data}), status
|
||||
|
||||
|
||||
def _error(message: str, status: int = 400, details=None):
|
||||
payload = {"success": False, "message": message}
|
||||
if details is not None:
|
||||
payload["details"] = details
|
||||
return jsonify(payload), status
|
||||
|
||||
|
||||
def _authenticated() -> bool:
|
||||
settings = _settings()
|
||||
if not settings.effective_auth_enabled:
|
||||
return True
|
||||
return bool(session.get("authenticated"))
|
||||
|
||||
|
||||
def require_auth(view_func):
|
||||
@wraps(view_func)
|
||||
def wrapped(*args, **kwargs):
|
||||
if not _authenticated():
|
||||
return _error("请先登录后台管理平台。", status=401)
|
||||
return view_func(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def _handle_service_call(callback):
|
||||
try:
|
||||
return callback()
|
||||
except BatchInputError as exc:
|
||||
return _error(str(exc), status=400)
|
||||
except ServiceConfigurationError as exc:
|
||||
return _error(str(exc), status=503)
|
||||
except ServiceOperationError as exc:
|
||||
return _error(exc.message, status=exc.status_code, details=exc.details)
|
||||
except ValueError as exc:
|
||||
return _error(str(exc), status=400)
|
||||
|
||||
|
||||
def _json_payload() -> dict:
|
||||
return request.get_json(silent=True) or {}
|
||||
|
||||
|
||||
def _text_payload() -> str:
|
||||
payload = _json_payload()
|
||||
if payload.get("content"):
|
||||
return str(payload["content"])
|
||||
|
||||
if request.form.get("content"):
|
||||
return request.form["content"]
|
||||
|
||||
uploaded_file = request.files.get("file")
|
||||
if uploaded_file and uploaded_file.filename:
|
||||
return uploaded_file.read().decode("utf-8-sig")
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
@bp.get("/")
|
||||
def index():
|
||||
return render_template("index.html", bootstrap=_settings().to_public_dict())
|
||||
|
||||
|
||||
@bp.get("/api/health")
|
||||
def health():
|
||||
settings = _settings()
|
||||
return _success(
|
||||
{
|
||||
"platform": settings.to_public_dict(),
|
||||
"authenticated": _authenticated(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@bp.get("/api/session")
|
||||
def session_info():
|
||||
return _success(
|
||||
{
|
||||
"authenticated": _authenticated(),
|
||||
"authEnabled": _settings().effective_auth_enabled,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@bp.post("/api/login")
|
||||
def login():
|
||||
settings = _settings()
|
||||
if not settings.effective_auth_enabled:
|
||||
session["authenticated"] = True
|
||||
session.permanent = True
|
||||
return _success({"authenticated": True}, message="当前平台未启用登录保护。")
|
||||
|
||||
payload = _json_payload()
|
||||
username = str(payload.get("username", "")).strip()
|
||||
password = str(payload.get("password", "")).strip()
|
||||
|
||||
if username == settings.admin_username and password == settings.admin_password:
|
||||
session["authenticated"] = True
|
||||
session.permanent = True
|
||||
return _success({"authenticated": True}, message="登录成功。")
|
||||
return _error("用户名或密码错误。", status=401)
|
||||
|
||||
|
||||
@bp.post("/api/logout")
|
||||
def logout():
|
||||
session.clear()
|
||||
return _success({"authenticated": False}, message="已退出登录。")
|
||||
|
||||
|
||||
@bp.get("/api/config")
|
||||
@require_auth
|
||||
def config_info():
|
||||
return _success(_settings().to_public_dict())
|
||||
|
||||
|
||||
@bp.get("/api/tasks/<task_id>")
|
||||
@require_auth
|
||||
def task_status(task_id: str):
|
||||
try:
|
||||
return _success(_task_manager().get_task(task_id))
|
||||
except TaskNotFoundError:
|
||||
return _error("任务不存在。", status=404)
|
||||
|
||||
|
||||
@bp.get("/api/licenses")
|
||||
@require_auth
|
||||
def licenses():
|
||||
return _handle_service_call(lambda: _success(_service().list_licenses()))
|
||||
|
||||
|
||||
@bp.get("/api/users")
|
||||
@require_auth
|
||||
def list_users():
|
||||
search = request.args.get("search", "").strip()
|
||||
try:
|
||||
page = int(request.args.get("page", "1"))
|
||||
page_size = int(request.args.get("pageSize", str(_settings().default_page_size)))
|
||||
except ValueError:
|
||||
return _error("page 和 pageSize 必须是整数。", status=400)
|
||||
|
||||
return _handle_service_call(
|
||||
lambda: _success(_service().list_users(search=search, page=page, page_size=page_size))
|
||||
)
|
||||
|
||||
|
||||
@bp.get("/api/users/selection")
|
||||
@require_auth
|
||||
def list_user_identifiers():
|
||||
search = request.args.get("search", "").strip()
|
||||
return _handle_service_call(
|
||||
lambda: _success(_service().list_user_identifiers(search=search))
|
||||
)
|
||||
|
||||
|
||||
@bp.get("/api/users/<path:identifier>")
|
||||
@require_auth
|
||||
def get_user(identifier: str):
|
||||
return _handle_service_call(lambda: _success(_service().get_user(identifier)))
|
||||
|
||||
|
||||
@bp.post("/api/users")
|
||||
@require_auth
|
||||
def create_user():
|
||||
payload = _json_payload()
|
||||
return _handle_service_call(
|
||||
lambda: _success(_service().create_user(payload), message="用户创建成功。", status=201)
|
||||
)
|
||||
|
||||
|
||||
@bp.patch("/api/users/<path:identifier>")
|
||||
@require_auth
|
||||
def update_user(identifier: str):
|
||||
payload = _json_payload()
|
||||
return _handle_service_call(
|
||||
lambda: _success(_service().update_user(identifier, payload), message="用户更新成功。")
|
||||
)
|
||||
|
||||
|
||||
@bp.delete("/api/users/<path:identifier>")
|
||||
@require_auth
|
||||
def delete_user(identifier: str):
|
||||
return _handle_service_call(
|
||||
lambda: _success(_service().delete_user(identifier), message="用户删除成功。")
|
||||
)
|
||||
|
||||
|
||||
@bp.post("/api/users/<path:identifier>/reset-password")
|
||||
@require_auth
|
||||
def reset_password(identifier: str):
|
||||
payload = _json_payload()
|
||||
return _handle_service_call(
|
||||
lambda: _success(_service().reset_password(identifier, payload), message="密码重置成功。")
|
||||
)
|
||||
|
||||
|
||||
@bp.post("/api/users/batch/create")
|
||||
@require_auth
|
||||
def batch_create():
|
||||
payload = _json_payload()
|
||||
rows = payload.get("rows")
|
||||
if rows is None:
|
||||
rows = parse_table_content(_text_payload())
|
||||
return _handle_service_call(lambda: _submit_batch_task("create", rows))
|
||||
|
||||
|
||||
@bp.post("/api/users/batch/update")
|
||||
@require_auth
|
||||
def batch_update():
|
||||
payload = _json_payload()
|
||||
rows = payload.get("rows")
|
||||
if rows is None:
|
||||
rows = parse_table_content(_text_payload())
|
||||
return _handle_service_call(lambda: _submit_batch_task("update", rows))
|
||||
|
||||
|
||||
@bp.post("/api/users/batch/delete")
|
||||
@require_auth
|
||||
def batch_delete():
|
||||
payload = _json_payload()
|
||||
identifiers = payload.get("identifiers")
|
||||
if identifiers is None:
|
||||
identifiers = parse_identifier_content(_text_payload())
|
||||
return _handle_service_call(lambda: _submit_batch_task("delete", identifiers))
|
||||
|
||||
|
||||
@bp.post("/api/users/batch/reset-password")
|
||||
@require_auth
|
||||
def batch_reset_password():
|
||||
payload = _json_payload()
|
||||
rows = payload.get("rows")
|
||||
if rows is None:
|
||||
text = _text_payload()
|
||||
try:
|
||||
rows = parse_table_content(text)
|
||||
except BatchInputError:
|
||||
rows = parse_identifier_content(text)
|
||||
return _handle_service_call(lambda: _submit_batch_task("reset-password", rows))
|
||||
|
||||
|
||||
def _submit_batch_task(operation: str, items):
|
||||
service = _service()
|
||||
task_manager = _task_manager()
|
||||
total = len(items)
|
||||
if operation == "create":
|
||||
runner = lambda progress: service.batch_create(items, progress_callback=progress)
|
||||
message = "批量创建任务已提交。"
|
||||
elif operation == "update":
|
||||
runner = lambda progress: service.batch_update(items, progress_callback=progress)
|
||||
message = "批量更新任务已提交。"
|
||||
elif operation == "delete":
|
||||
runner = lambda progress: service.batch_delete(items, progress_callback=progress)
|
||||
message = "批量删除任务已提交。"
|
||||
elif operation == "reset-password":
|
||||
runner = lambda progress: service.batch_reset_password(items, progress_callback=progress)
|
||||
message = "批量重置密码任务已提交。"
|
||||
else:
|
||||
raise ValueError("不支持的批量任务类型。")
|
||||
|
||||
logger.info("Submitting batch task: operation=%s total=%s", operation, total)
|
||||
task = task_manager.submit(operation=operation, total=total, runner=runner)
|
||||
return _success(task, message=message, status=202)
|
||||
Reference in New Issue
Block a user