mirror of
https://github.com/langgenius/dify.git
synced 2026-10-16 15:35:32 +08:00
152 lines
5.7 KiB
Python
152 lines
5.7 KiB
Python
"""GET /openapi/v1/apps and per-app reads."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from http import HTTPStatus
|
|
from typing import Any, Final
|
|
|
|
from flask_restx import Resource
|
|
from werkzeug.exceptions import NotFound
|
|
|
|
from constants.oauth_bearer import Scope
|
|
from controllers.common.rbac import PlainApp, RBACCheck, RBACPermission
|
|
from controllers.openapi import openapi_ns
|
|
from controllers.openapi._app_response import app_list_row, build_app_describe_response
|
|
from controllers.openapi._contract import Example, Kind, endpoint, op_of
|
|
from controllers.openapi._models import AppDescribeQuery, AppDescribeResponse, AppListQuery, AppListResponse, Hint
|
|
from controllers.openapi.app_info import (
|
|
AdvancedChatAppInfoApi,
|
|
AgentAppInfoApi,
|
|
AgentChatAppInfoApi,
|
|
AgentServiceApiApi,
|
|
ChatAppInfoApi,
|
|
CompletionAppInfoApi,
|
|
ServiceApiApi,
|
|
WorkflowAppInfoApi,
|
|
)
|
|
from controllers.openapi.app_webapp import (
|
|
AdvancedChatWebAppApi,
|
|
AgentChatWebAppApi,
|
|
AgentWebAppAccessApi,
|
|
AgentWebAppApi,
|
|
ChatWebAppApi,
|
|
CompletionWebAppApi,
|
|
WebAppAccessApi,
|
|
WorkflowWebAppApi,
|
|
)
|
|
from controllers.openapi.auth.requirements import (
|
|
CheckAppApiEnabled,
|
|
CheckRBACPermission,
|
|
CheckScope,
|
|
CheckSubject,
|
|
CheckWebAppAuthEnterprise,
|
|
CheckWorkspaceMember,
|
|
)
|
|
from controllers.openapi.auth.subjects import AccountSubject, ResourceAccessSubject
|
|
from extensions.ext_application_services import application_services
|
|
from machinery.context import AppRequestContext, RequestContext
|
|
from models.model import AppMode
|
|
from services.app.query_service import AppDiscoveryQuery
|
|
from services.errors.app import AppDiscoveryNotFoundError
|
|
|
|
_SETTINGS_VIEWS: Final[dict[AppMode, tuple[Callable[..., object], ...]]] = {
|
|
AppMode.WORKFLOW: (WorkflowAppInfoApi.get, WorkflowWebAppApi.get, ServiceApiApi.get, WebAppAccessApi.get),
|
|
AppMode.ADVANCED_CHAT: (
|
|
AdvancedChatAppInfoApi.get,
|
|
AdvancedChatWebAppApi.get,
|
|
ServiceApiApi.get,
|
|
WebAppAccessApi.get,
|
|
),
|
|
AppMode.CHAT: (ChatAppInfoApi.get, ChatWebAppApi.get, ServiceApiApi.get, WebAppAccessApi.get),
|
|
AppMode.AGENT_CHAT: (AgentChatAppInfoApi.get, AgentChatWebAppApi.get, ServiceApiApi.get, WebAppAccessApi.get),
|
|
AppMode.COMPLETION: (CompletionAppInfoApi.get, CompletionWebAppApi.get, ServiceApiApi.get, WebAppAccessApi.get),
|
|
AppMode.AGENT: (AgentAppInfoApi.get, AgentWebAppApi.get, AgentServiceApiApi.get, AgentWebAppAccessApi.get),
|
|
}
|
|
|
|
|
|
def _webapp_auth_gated(view: Any) -> bool:
|
|
return any(isinstance(r, CheckWebAppAuthEnterprise) for r in view.__spec__.requirements)
|
|
|
|
|
|
def settings_hints(app_id: str, mode: str) -> list[Hint]:
|
|
"""A stored mode this build doesn't know gets no hints, and the web-app access setting only where it exists."""
|
|
try:
|
|
app_mode = AppMode(mode)
|
|
except ValueError:
|
|
return []
|
|
webapp_auth = CheckWebAppAuthEnterprise.available()
|
|
return [
|
|
Hint(summary="Read this setting", op=op_of(view), input={"app_id": app_id})
|
|
for view in _SETTINGS_VIEWS.get(app_mode, ())
|
|
if webapp_auth or not _webapp_auth_gated(view)
|
|
]
|
|
|
|
|
|
@openapi_ns.route("/apps/<string:app_id>")
|
|
class AppDescribeApi(Resource):
|
|
@endpoint(
|
|
context="app",
|
|
op="describe.console_app",
|
|
kind=Kind.OBJECT,
|
|
summary="App detail, parameters and runtime input_schema",
|
|
examples=(
|
|
Example(title="Describe an app: info, parameters and input_schema", input={"app_id": "<app_id>"}),
|
|
Example(
|
|
title="Only the runtime input_schema of an app",
|
|
input={"app_id": "<app_id>", "fields": "input_schema"},
|
|
),
|
|
),
|
|
requirements=(
|
|
CheckSubject(allowed=(AccountSubject, ResourceAccessSubject)),
|
|
CheckAppApiEnabled(),
|
|
CheckWorkspaceMember(),
|
|
CheckScope(Scope.APPS_READ),
|
|
CheckRBACPermission(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp())),
|
|
),
|
|
query=AppDescribeQuery,
|
|
returns=(200, AppDescribeResponse, "App description"),
|
|
)
|
|
def get(self, ctx: AppRequestContext, app_id: str, *, query: AppDescribeQuery):
|
|
try:
|
|
result = application_services().apps.discovery.describe(ctx, query.fields)
|
|
except AppDiscoveryNotFoundError as exc:
|
|
raise NotFound(str(exc)) from exc
|
|
response = build_app_describe_response(result, query.fields)
|
|
response.hints = settings_hints(result.app.id, result.app.mode)
|
|
return response
|
|
|
|
|
|
@openapi_ns.route("/apps")
|
|
class AppListApi(Resource):
|
|
@endpoint(
|
|
context="workspace",
|
|
op="get.console_app",
|
|
kind=Kind.LIST,
|
|
summary="List apps in a workspace",
|
|
examples=(
|
|
Example(title="List apps in the pinned workspace, first page", input={"page": 1, "limit": 20}),
|
|
Example(
|
|
title="Find workflow apps whose name contains a word",
|
|
input={"mode": "workflow", "name": "summary"},
|
|
),
|
|
),
|
|
requirements=(
|
|
CheckSubject(allowed=(AccountSubject, ResourceAccessSubject)),
|
|
CheckScope(Scope.APPS_READ),
|
|
CheckWorkspaceMember(),
|
|
),
|
|
query=AppListQuery,
|
|
returns=(HTTPStatus.OK, AppListResponse, "App list"),
|
|
)
|
|
def get(self, ctx: RequestContext, *, query: AppListQuery):
|
|
result = application_services().apps.discovery.list_apps(
|
|
ctx, AppDiscoveryQuery(query.page, query.limit, query.mode.value if query.mode else None, query.name)
|
|
)
|
|
return AppListResponse.build(
|
|
page=result.page,
|
|
limit=result.per_page,
|
|
total=result.total,
|
|
items=[app_list_row(entry) for entry in result.items],
|
|
)
|