選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

admin.py 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. from collections.abc import Callable
  2. from functools import wraps
  3. from typing import ParamSpec, TypeVar
  4. from flask import request
  5. from flask_restx import Resource, fields, reqparse
  6. from sqlalchemy import select
  7. from sqlalchemy.orm import Session
  8. from werkzeug.exceptions import NotFound, Unauthorized
  9. P = ParamSpec("P")
  10. R = TypeVar("R")
  11. from configs import dify_config
  12. from constants.languages import supported_language
  13. from controllers.console import api, console_ns
  14. from controllers.console.wraps import only_edition_cloud
  15. from extensions.ext_database import db
  16. from models.model import App, InstalledApp, RecommendedApp
  17. def admin_required(view: Callable[P, R]):
  18. @wraps(view)
  19. def decorated(*args: P.args, **kwargs: P.kwargs):
  20. if not dify_config.ADMIN_API_KEY:
  21. raise Unauthorized("API key is invalid.")
  22. auth_header = request.headers.get("Authorization")
  23. if auth_header is None:
  24. raise Unauthorized("Authorization header is missing.")
  25. if " " not in auth_header:
  26. raise Unauthorized("Invalid Authorization header format. Expected 'Bearer <api-key>' format.")
  27. auth_scheme, auth_token = auth_header.split(None, 1)
  28. auth_scheme = auth_scheme.lower()
  29. if auth_scheme != "bearer":
  30. raise Unauthorized("Invalid Authorization header format. Expected 'Bearer <api-key>' format.")
  31. if auth_token != dify_config.ADMIN_API_KEY:
  32. raise Unauthorized("API key is invalid.")
  33. return view(*args, **kwargs)
  34. return decorated
  35. @console_ns.route("/admin/insert-explore-apps")
  36. class InsertExploreAppListApi(Resource):
  37. @api.doc("insert_explore_app")
  38. @api.doc(description="Insert or update an app in the explore list")
  39. @api.expect(
  40. api.model(
  41. "InsertExploreAppRequest",
  42. {
  43. "app_id": fields.String(required=True, description="Application ID"),
  44. "desc": fields.String(description="App description"),
  45. "copyright": fields.String(description="Copyright information"),
  46. "privacy_policy": fields.String(description="Privacy policy"),
  47. "custom_disclaimer": fields.String(description="Custom disclaimer"),
  48. "language": fields.String(required=True, description="Language code"),
  49. "category": fields.String(required=True, description="App category"),
  50. "position": fields.Integer(required=True, description="Display position"),
  51. },
  52. )
  53. )
  54. @api.response(200, "App updated successfully")
  55. @api.response(201, "App inserted successfully")
  56. @api.response(404, "App not found")
  57. @only_edition_cloud
  58. @admin_required
  59. def post(self):
  60. parser = reqparse.RequestParser()
  61. parser.add_argument("app_id", type=str, required=True, nullable=False, location="json")
  62. parser.add_argument("desc", type=str, location="json")
  63. parser.add_argument("copyright", type=str, location="json")
  64. parser.add_argument("privacy_policy", type=str, location="json")
  65. parser.add_argument("custom_disclaimer", type=str, location="json")
  66. parser.add_argument("language", type=supported_language, required=True, nullable=False, location="json")
  67. parser.add_argument("category", type=str, required=True, nullable=False, location="json")
  68. parser.add_argument("position", type=int, required=True, nullable=False, location="json")
  69. args = parser.parse_args()
  70. app = db.session.execute(select(App).where(App.id == args["app_id"])).scalar_one_or_none()
  71. if not app:
  72. raise NotFound(f"App '{args['app_id']}' is not found")
  73. site = app.site
  74. if not site:
  75. desc = args["desc"] or ""
  76. copy_right = args["copyright"] or ""
  77. privacy_policy = args["privacy_policy"] or ""
  78. custom_disclaimer = args["custom_disclaimer"] or ""
  79. else:
  80. desc = site.description or args["desc"] or ""
  81. copy_right = site.copyright or args["copyright"] or ""
  82. privacy_policy = site.privacy_policy or args["privacy_policy"] or ""
  83. custom_disclaimer = site.custom_disclaimer or args["custom_disclaimer"] or ""
  84. with Session(db.engine) as session:
  85. recommended_app = session.execute(
  86. select(RecommendedApp).where(RecommendedApp.app_id == args["app_id"])
  87. ).scalar_one_or_none()
  88. if not recommended_app:
  89. recommended_app = RecommendedApp(
  90. app_id=app.id,
  91. description=desc,
  92. copyright=copy_right,
  93. privacy_policy=privacy_policy,
  94. custom_disclaimer=custom_disclaimer,
  95. language=args["language"],
  96. category=args["category"],
  97. position=args["position"],
  98. )
  99. db.session.add(recommended_app)
  100. app.is_public = True
  101. db.session.commit()
  102. return {"result": "success"}, 201
  103. else:
  104. recommended_app.description = desc
  105. recommended_app.copyright = copy_right
  106. recommended_app.privacy_policy = privacy_policy
  107. recommended_app.custom_disclaimer = custom_disclaimer
  108. recommended_app.language = args["language"]
  109. recommended_app.category = args["category"]
  110. recommended_app.position = args["position"]
  111. app.is_public = True
  112. db.session.commit()
  113. return {"result": "success"}, 200
  114. @console_ns.route("/admin/insert-explore-apps/<uuid:app_id>")
  115. class InsertExploreAppApi(Resource):
  116. @api.doc("delete_explore_app")
  117. @api.doc(description="Remove an app from the explore list")
  118. @api.doc(params={"app_id": "Application ID to remove"})
  119. @api.response(204, "App removed successfully")
  120. @only_edition_cloud
  121. @admin_required
  122. def delete(self, app_id):
  123. with Session(db.engine) as session:
  124. recommended_app = session.execute(
  125. select(RecommendedApp).where(RecommendedApp.app_id == str(app_id))
  126. ).scalar_one_or_none()
  127. if not recommended_app:
  128. return {"result": "success"}, 204
  129. with Session(db.engine) as session:
  130. app = session.execute(select(App).where(App.id == recommended_app.app_id)).scalar_one_or_none()
  131. if app:
  132. app.is_public = False
  133. with Session(db.engine) as session:
  134. installed_apps = (
  135. session.execute(
  136. select(InstalledApp).where(
  137. InstalledApp.app_id == recommended_app.app_id,
  138. InstalledApp.tenant_id != InstalledApp.app_owner_tenant_id,
  139. )
  140. )
  141. .scalars()
  142. .all()
  143. )
  144. for installed_app in installed_apps:
  145. session.delete(installed_app)
  146. db.session.delete(recommended_app)
  147. db.session.commit()
  148. return {"result": "success"}, 204