mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-11 12:11:45 +00:00
Admins read and set the instance default, team allowances and user overrides under /api/admin/quotas. A user's endpoint also returns the limits those layers resolve to, the layer each came from and the usage against them. The overview lists catalog models used this period that have no price, since a cost limit cannot see them. Every write is audited. GET /api/user/quota gives a user their own limited buckets, usage and reset time without naming the policies behind them; any valid token may call it.
82 lines
3.1 KiB
Python
82 lines
3.1 KiB
Python
"""Current-principal endpoint.
|
|
|
|
``GET /api/user/me`` returns the caller's user id and resolved roles, sourced
|
|
only from ``request.decoded_token`` (already populated and role-resolved by the
|
|
auth chokepoint in ``app.py``). Auth-mode-agnostic. ``email``/``name``/
|
|
``picture`` are OIDC-only and optional — they are echoed from the token and are
|
|
never present for ``simple_jwt``/``session_jwt``/no-auth modes.
|
|
|
|
``GET /api/user/quota`` returns the caller's usage against the limits an admin
|
|
set for them, without naming the policies behind those limits.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from flask import jsonify, make_response, request
|
|
from flask_restx import Namespace, Resource
|
|
|
|
from docsgpt.api.pat.tokens import is_pat
|
|
from docsgpt.core.settings import settings
|
|
from docsgpt.quotas.service import REQUEST_BUCKETS, QuotaService
|
|
|
|
me_ns = Namespace("me", description="Current user identity and roles", path="/api")
|
|
|
|
|
|
@me_ns.route("/user/me")
|
|
class MeResource(Resource):
|
|
def get(self):
|
|
"""Return ``{user_id, roles, email?, name?, picture?}`` for the caller."""
|
|
decoded_token = getattr(request, "decoded_token", None)
|
|
if not decoded_token:
|
|
return make_response(jsonify({"success": False}), 401)
|
|
body = {
|
|
"success": True,
|
|
"user_id": decoded_token.get("sub"),
|
|
"roles": decoded_token.get("roles") or ["user"],
|
|
}
|
|
for field in ("email", "name", "picture"):
|
|
value = decoded_token.get(field)
|
|
if value:
|
|
body[field] = value
|
|
if is_pat(decoded_token):
|
|
# Lets a CLI or pipeline confirm what its token is allowed to do.
|
|
body["auth_method"] = "pat"
|
|
body["token"] = {
|
|
"id": decoded_token.get("pat_id"),
|
|
"name": decoded_token.get("pat_name"),
|
|
"scopes": decoded_token.get("scopes") or [],
|
|
"resource_filter": decoded_token.get("resource_filter") or {},
|
|
}
|
|
return make_response(jsonify(body), 200)
|
|
|
|
|
|
def _own_budget(budget: dict) -> dict:
|
|
return {"limit": budget["limit"], "used": budget["used"]}
|
|
|
|
|
|
@me_ns.route("/user/quota")
|
|
class MyQuotaResource(Resource):
|
|
def get(self):
|
|
"""Return the caller's limited buckets: ``{bucket, tokens, cost, resets_at}`` each."""
|
|
decoded_token = getattr(request, "decoded_token", None)
|
|
user_id = decoded_token.get("sub") if decoded_token else None
|
|
if not user_id:
|
|
return make_response(jsonify({"success": False}), 401)
|
|
statuses = QuotaService.status(user_id, ("all", *REQUEST_BUCKETS))
|
|
buckets = []
|
|
for status in statuses:
|
|
if status.limits.unlimited:
|
|
continue
|
|
data = status.to_dict()
|
|
buckets.append(
|
|
{
|
|
"bucket": data["bucket"],
|
|
"tokens": _own_budget(data["tokens"]),
|
|
"cost": _own_budget(data["cost"]),
|
|
"resets_at": data["resets_at"],
|
|
}
|
|
)
|
|
return make_response(
|
|
jsonify({"success": True, "period": settings.QUOTA_PERIOD, "buckets": buckets}), 200
|
|
)
|