mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-20 00:14:50 +00:00
Roles audit and revamp
This commit is contained in:
1 parent
208d34abc7
commit
98afa5d93c
134 files changed
+13725
-1702
No files matched your search
@@ -6,7 +6,14 @@ from flask_restx import fields, Namespace, Resource
|
||||
|
||||
from docsgpt.api import api
|
||||
from docsgpt.api.pat.rules import filter_listing
|
||||
from docsgpt.api.user.team_sharing import team_access_for, visible_with_access
|
||||
from docsgpt.api.user.resource_access import (
|
||||
AccessDenied,
|
||||
delete_settings,
|
||||
payload_for,
|
||||
require,
|
||||
settings_many,
|
||||
)
|
||||
from docsgpt.api.user.team_sharing import visible_with_access
|
||||
from docsgpt.storage.db.repositories.prompts import PromptsRepository
|
||||
from docsgpt.prompts.composer import compose_preset, is_composed_preset
|
||||
from docsgpt.storage.db.session import db_readonly, db_session
|
||||
@@ -17,6 +24,16 @@ prompts_ns = Namespace(
|
||||
)
|
||||
|
||||
|
||||
def _denied(err: AccessDenied):
|
||||
"""JSON response for an :class:`AccessDenied` (403 or 404)."""
|
||||
return make_response(jsonify({"success": False, "message": err.message}), err.status)
|
||||
|
||||
|
||||
def _iso(value):
|
||||
"""ISO-8601 string for a timestamp (``expected_updated_at`` round-trips it)."""
|
||||
return value.isoformat() if hasattr(value, "isoformat") else value
|
||||
|
||||
|
||||
@prompts_ns.route("/create_prompt")
|
||||
class CreatePrompt(Resource):
|
||||
create_prompt_model = api.model(
|
||||
@@ -67,26 +84,35 @@ class GetPrompts(Resource):
|
||||
team_shared = visible_with_access(conn, user, "prompt")
|
||||
shared_ids = [pid for pid in team_shared if pid not in owned_ids]
|
||||
shared_prompts = repo.list_by_ids(shared_ids)
|
||||
switches = settings_many(
|
||||
conn, "prompt", [*owned_ids, *(str(p["id"]) for p in shared_prompts)]
|
||||
)
|
||||
list_prompts = [
|
||||
{"id": "default", "name": "default", "type": "public"},
|
||||
{"id": "creative", "name": "creative", "type": "public"},
|
||||
{"id": "strict", "name": "strict", "type": "public"},
|
||||
]
|
||||
for prompt in prompts:
|
||||
pid = str(prompt["id"])
|
||||
list_prompts.append(
|
||||
{
|
||||
"id": str(prompt["id"]),
|
||||
"id": pid,
|
||||
"name": prompt["name"],
|
||||
"type": "private",
|
||||
"updated_at": _iso(prompt.get("updated_at")),
|
||||
**payload_for("prompt", "owner", switches.get(pid)),
|
||||
}
|
||||
)
|
||||
for prompt in shared_prompts:
|
||||
pid = str(prompt["id"])
|
||||
list_prompts.append(
|
||||
{
|
||||
"id": str(prompt["id"]),
|
||||
"id": pid,
|
||||
"name": prompt["name"],
|
||||
"type": "team",
|
||||
"team_access": team_shared.get(str(prompt["id"])),
|
||||
"team_access": team_shared.get(pid),
|
||||
"updated_at": _iso(prompt.get("updated_at")),
|
||||
**payload_for("prompt", team_shared.get(pid), switches.get(pid)),
|
||||
}
|
||||
)
|
||||
except Exception as err:
|
||||
@@ -115,19 +141,28 @@ class GetSinglePrompt(Resource):
|
||||
jsonify({"content": compose_preset(prompt_id)}), 200
|
||||
)
|
||||
with db_readonly() as conn:
|
||||
repo = PromptsRepository(conn)
|
||||
prompt = repo.get_any(prompt_id, user)
|
||||
if not prompt and team_access_for(conn, user, "prompt", prompt_id):
|
||||
# Team fallback: ownerless fetch only after a grant check.
|
||||
prompt = repo.get_for_rendering(prompt_id)
|
||||
ra = require(conn, "prompt", prompt_id, user, "use")
|
||||
prompt = PromptsRepository(conn).get_any(ra.resource_id, ra.owner_id)
|
||||
if not prompt:
|
||||
return make_response(
|
||||
jsonify({"success": False, "message": "Prompt not found"}), 404
|
||||
)
|
||||
except AccessDenied as err:
|
||||
return _denied(err)
|
||||
except Exception as err:
|
||||
current_app.logger.error(f"Error retrieving prompt: {err}", exc_info=True)
|
||||
return make_response(jsonify({"success": False}), 400)
|
||||
return make_response(jsonify({"content": prompt["content"]}), 200)
|
||||
return make_response(
|
||||
jsonify(
|
||||
{
|
||||
"content": prompt["content"],
|
||||
"name": prompt.get("name"),
|
||||
"updated_at": _iso(prompt.get("updated_at")),
|
||||
**ra.payload(),
|
||||
}
|
||||
),
|
||||
200,
|
||||
)
|
||||
|
||||
|
||||
@prompts_ns.route("/delete_prompt")
|
||||
@@ -151,14 +186,12 @@ class DeletePrompt(Resource):
|
||||
return missing_fields
|
||||
try:
|
||||
with db_session() as conn:
|
||||
repo = PromptsRepository(conn)
|
||||
prompt = repo.get_any(data["id"], user)
|
||||
if not prompt:
|
||||
return make_response(
|
||||
jsonify({"success": False, "message": "Prompt not found"}),
|
||||
404,
|
||||
)
|
||||
repo.delete(str(prompt["id"]), user)
|
||||
ra = require(conn, "prompt", data["id"], user, "delete")
|
||||
# Grants go with the row (delete trigger); switches have no FK.
|
||||
PromptsRepository(conn).delete(ra.resource_id, ra.owner_id)
|
||||
delete_settings(conn, "prompt", ra.resource_id)
|
||||
except AccessDenied as err:
|
||||
return _denied(err)
|
||||
except Exception as err:
|
||||
current_app.logger.error(f"Error deleting prompt: {err}", exc_info=True)
|
||||
return make_response(jsonify({"success": False}), 400)
|
||||
@@ -192,43 +225,26 @@ class UpdatePrompt(Resource):
|
||||
return missing_fields
|
||||
try:
|
||||
with db_session() as conn:
|
||||
repo = PromptsRepository(conn)
|
||||
prompt = repo.get_any(data["id"], user)
|
||||
if prompt:
|
||||
repo.update(str(prompt["id"]), user, data["name"], data["content"])
|
||||
else:
|
||||
# Team editor write path (viewer is read-only).
|
||||
access = team_access_for(conn, user, "prompt", data["id"])
|
||||
if access == "editor":
|
||||
result = repo.update_by_id(
|
||||
data["id"],
|
||||
data["name"],
|
||||
data["content"],
|
||||
expected_updated_at=data.get("expected_updated_at"),
|
||||
)
|
||||
if result is None:
|
||||
return make_response(
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": "Prompt was modified by someone else",
|
||||
"code": "stale_write",
|
||||
}
|
||||
),
|
||||
409,
|
||||
)
|
||||
elif access == "viewer":
|
||||
return make_response(
|
||||
jsonify(
|
||||
{"success": False, "message": "Read-only: editor access required"}
|
||||
),
|
||||
403,
|
||||
)
|
||||
else:
|
||||
return make_response(
|
||||
jsonify({"success": False, "message": "Prompt not found"}),
|
||||
404,
|
||||
)
|
||||
ra = require(conn, "prompt", data["id"], user, "edit")
|
||||
result = PromptsRepository(conn).update_by_id(
|
||||
ra.resource_id,
|
||||
data["name"],
|
||||
data["content"],
|
||||
expected_updated_at=data.get("expected_updated_at"),
|
||||
)
|
||||
if result is None:
|
||||
return make_response(
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": "Prompt was modified by someone else",
|
||||
"code": "stale_write",
|
||||
}
|
||||
),
|
||||
409,
|
||||
)
|
||||
except AccessDenied as err:
|
||||
return _denied(err)
|
||||
except Exception as err:
|
||||
current_app.logger.error(f"Error updating prompt: {err}", exc_info=True)
|
||||
return make_response(jsonify({"success": False}), 400)
|
||||
|
||||
Reference in new issue
Block a user