change validate_app_token

pull/21891/head
ytqh 1 year ago
parent b33f8fbecb
commit 2f19e4efdd

@ -1,10 +1,9 @@
from flask_restful import Resource, marshal_with # type: ignore
from controllers.common import fields
from controllers.common import helpers as controller_helpers
from controllers.service_api_with_auth import api
from controllers.service_api_with_auth.app.error import AppUnavailableError
from controllers.service_api_with_auth.wraps import validate_app_token
from flask_restful import Resource, marshal_with # type: ignore
from models.model import App, AppMode
from services.app_service import AppService
@ -15,7 +14,26 @@ class AppParameterApi(Resource):
@validate_app_token
@marshal_with(fields.parameters_fields)
def get(self, app_model: App):
"""Retrieve app parameters."""
"""Retrieve app parameters.
---
tags:
- app/parameters
summary: Get app parameters
description: Retrieve parameters for the current application
security:
- ApiKeyAuth: []
responses:
200:
description: Parameters retrieved successfully
schema:
type: object
400:
description: Invalid request
401:
description: Invalid or missing token
404:
description: App unavailable
"""
if app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value}:
workflow = app_model.workflow
if workflow is None:
@ -40,14 +58,53 @@ class AppParameterApi(Resource):
class AppMetaApi(Resource):
@validate_app_token
def get(self, app_model: App):
"""Get app meta"""
"""Get app meta information.
---
tags:
- app/meta
summary: Get app meta
description: Retrieve meta information for the current application
security:
- ApiKeyAuth: []
responses:
200:
description: Meta information retrieved successfully
schema:
type: object
401:
description: Invalid or missing token
"""
return AppService().get_app_meta(app_model)
class AppInfoApi(Resource):
@validate_app_token
def get(self, app_model: App):
"""Get app information"""
"""Get app information.
---
tags:
- app/info
summary: Get app info
description: Retrieve basic information about the current application
security:
- ApiKeyAuth: []
responses:
200:
description: App information retrieved successfully
schema:
type: object
properties:
name:
type: string
description:
type: string
tags:
type: array
items:
type: string
401:
description: Invalid or missing token
"""
tags = [tag.name for tag in app_model.tags]
return {"name": app_model.name, "description": app_model.description, "tags": tags}

@ -1,9 +1,5 @@
import logging
from flask import request
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import InternalServerError
import services
from controllers.service_api_with_auth import api
from controllers.service_api_with_auth.app.error import (
@ -17,9 +13,15 @@ from controllers.service_api_with_auth.app.error import (
ProviderQuotaExceededError,
UnsupportedAudioTypeError,
)
from controllers.service_api_with_auth.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from controllers.service_api_with_auth.wraps import validate_app_token
from core.errors.error import (
ModelCurrentlyNotSupportError,
ProviderTokenNotInitError,
QuotaExceededError,
)
from core.model_runtime.errors.invoke import InvokeError
from flask import request
from flask_restful import Resource, reqparse # type: ignore
from models.model import App, AppMode, EndUser
from services.audio_service import AudioService
from services.errors.audio import (
@ -28,11 +30,46 @@ from services.errors.audio import (
ProviderNotSupportSpeechToTextServiceError,
UnsupportedAudioTypeServiceError,
)
from werkzeug.exceptions import InternalServerError
class AudioApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.FORM))
@validate_app_token
def post(self, app_model: App, end_user: EndUser):
"""Transcribe audio to text.
---
tags:
- app/audio
summary: Transcribe audio
description: Convert audio file to text using speech-to-text
security:
- ApiKeyAuth: []
consumes:
- multipart/form-data
parameters:
- name: file
in: formData
required: true
type: file
description: The audio file to transcribe
responses:
200:
description: Audio transcribed successfully
schema:
type: object
properties:
text:
type: string
description: Transcribed text
400:
description: Invalid request, no audio uploaded, or unsupported audio type
401:
description: Invalid or missing token
413:
description: Audio file too large
500:
description: Provider error or internal server error
"""
file = request.files["file"]
try:
@ -66,8 +103,51 @@ class AudioApi(Resource):
class TextApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON))
@validate_app_token
def post(self, app_model: App, end_user: EndUser):
"""Convert text to speech.
---
tags:
- app/audio
summary: Text to speech
description: Convert text to speech audio
security:
- ApiKeyAuth: []
parameters:
- name: body
in: body
required: true
schema:
type: object
required:
- text
properties:
text:
type: string
description: Text to convert to speech
voice:
type: string
description: Voice ID to use for speech synthesis
streaming:
type: boolean
default: false
description: Whether to stream the audio response
responses:
200:
description: Text converted to speech successfully
schema:
type: object
properties:
audio_url:
type: string
description: URL to the generated audio file
400:
description: Invalid request
401:
description: Invalid or missing token
500:
description: Provider error or internal server error
"""
try:
parser = reqparse.RequestParser()
parser.add_argument("message_id", type=str, required=False, location="json")
@ -87,7 +167,11 @@ class TextApi(Resource):
voice = args.get("voice") or text_to_speech.get("voice")
else:
try:
voice = args.get("voice") or app_model.app_model_config.text_to_speech_dict.get("voice")
voice = args.get("voice") or (
app_model.app_model_config.text_to_speech_dict.get("voice")
if app_model.app_model_config
else None
)
except Exception:
voice = None
response = AudioService.transcript_tts(

@ -1,9 +1,5 @@
import logging
from libs.login import login_required
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import InternalServerError, NotFound
import services
from controllers.service_api_with_auth import api
from controllers.service_api_with_auth.app.error import (
@ -15,7 +11,11 @@ from controllers.service_api_with_auth.app.error import (
ProviderNotInitializeError,
ProviderQuotaExceededError,
)
from controllers.service_api_with_auth.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from controllers.service_api_with_auth.wraps import (
FetchUserArg,
WhereisUserArg,
validate_app_token,
)
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import (
@ -24,15 +24,62 @@ from core.errors.error import (
QuotaExceededError,
)
from core.model_runtime.errors.invoke import InvokeError
from flask_restful import Resource, reqparse # type: ignore
from libs import helper
from libs.helper import uuid_value
from libs.login import login_required
from models.model import App, AppMode, EndUser
from services.app_generate_service import AppGenerateService
from werkzeug.exceptions import InternalServerError, NotFound
class CompletionApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON, required=True))
@validate_app_token
def post(self, app_model: App, end_user: EndUser):
"""Generate completion response.
---
tags:
- app/completion
summary: Generate completion
description: Generate a completion response for the provided inputs
security:
- ApiKeyAuth: []
parameters:
- name: body
in: body
required: true
schema:
type: object
required:
- inputs
properties:
inputs:
type: object
description: Input variables for the completion
query:
type: string
description: User query text
files:
type: array
description: List of files to process
response_mode:
type: string
enum: [blocking, streaming]
description: Response delivery mode
retriever_from:
type: string
default: dev
description: Source of the retriever
responses:
200:
description: Completion generated successfully
400:
description: Invalid request
401:
description: Invalid or missing token
404:
description: App unavailable or conversation not found
"""
if app_model.mode != "completion":
raise AppUnavailableError()
@ -82,8 +129,36 @@ class CompletionApi(Resource):
class CompletionStopApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON, required=True))
@validate_app_token
def post(self, app_model: App, end_user: EndUser, task_id):
"""Stop a running completion task.
---
tags:
- app/completion
summary: Stop completion task
description: Stop a running completion generation task
security:
- ApiKeyAuth: []
parameters:
- name: task_id
in: path
required: true
type: string
description: ID of the task to stop
responses:
200:
description: Task stopped successfully
schema:
type: object
properties:
result:
type: string
example: success
401:
description: Invalid or missing token
404:
description: App unavailable
"""
if app_model.mode != "completion":
raise AppUnavailableError()
@ -93,8 +168,61 @@ class CompletionStopApi(Resource):
class ChatApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON, required=True))
@validate_app_token
def post(self, app_model: App, end_user: EndUser):
"""Generate chat response.
---
tags:
- app/chat
summary: Generate chat response
description: Generate a chat response for the provided inputs and query
security:
- ApiKeyAuth: []
parameters:
- name: body
in: body
required: true
schema:
type: object
required:
- inputs
- query
properties:
inputs:
type: object
description: Input variables for the chat
query:
type: string
description: User query text
files:
type: array
description: List of files to process
response_mode:
type: string
enum: [blocking, streaming]
description: Response delivery mode
conversation_id:
type: string
format: uuid
description: ID of an existing conversation to continue
retriever_from:
type: string
default: dev
description: Source of the retriever
auto_generate_name:
type: boolean
default: true
description: Whether to automatically generate a name for the conversation
responses:
200:
description: Chat response generated successfully
400:
description: Invalid request
401:
description: Invalid or missing token
404:
description: App unavailable or conversation not found
"""
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
raise NotChatAppError()
@ -141,8 +269,36 @@ class ChatApi(Resource):
class ChatStopApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON, required=True))
@validate_app_token
def post(self, app_model: App, end_user: EndUser, task_id):
"""Stop a running chat task.
---
tags:
- app/chat
summary: Stop chat task
description: Stop a running chat generation task
security:
- ApiKeyAuth: []
parameters:
- name: task_id
in: path
required: true
type: string
description: ID of the task to stop
responses:
200:
description: Task stopped successfully
schema:
type: object
properties:
result:
type: string
example: success
401:
description: Invalid or missing token
404:
description: App unavailable or not a chat app
"""
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
raise NotChatAppError()

@ -1,12 +1,11 @@
from flask_restful import Resource, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound
import services
from controllers.service_api_with_auth import api
from controllers.service_api_with_auth.app.error import NotChatAppError
from controllers.service_api_with_auth.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from controllers.service_api_with_auth.wraps import (
FetchUserArg,
WhereisUserArg,
validate_app_token,
)
from core.app.entities.app_invoke_entities import InvokeFrom
from extensions.ext_database import db
from fields.conversation_fields import (
@ -14,15 +13,63 @@ from fields.conversation_fields import (
conversation_infinite_scroll_pagination_fields,
simple_conversation_fields,
)
from flask_restful import Resource, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from libs.helper import uuid_value
from models.model import App, AppMode, EndUser
from services.conversation_service import ConversationService
from sqlalchemy.orm import Session # type: ignore
from werkzeug.exceptions import NotFound
class ConversationApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY))
@validate_app_token
@marshal_with(conversation_infinite_scroll_pagination_fields)
def get(self, app_model: App, end_user: EndUser):
"""Get conversations list.
---
tags:
- app/conversation
summary: List conversations
description: Get a paginated list of conversations for the current user
security:
- ApiKeyAuth: []
parameters:
- name: last_id
in: query
type: string
format: uuid
description: ID of the last conversation for pagination
- name: limit
in: query
type: integer
minimum: 1
maximum: 100
default: 20
description: Number of conversations to return
- name: sort_by
in: query
type: string
enum: [created_at, -created_at, updated_at, -updated_at]
default: -updated_at
description: Field to sort by, prefix with - for descending order
responses:
200:
description: Conversations retrieved successfully
schema:
type: object
properties:
data:
type: array
items:
type: object
has_more:
type: boolean
401:
description: Invalid or missing token
404:
description: Not a chat app
"""
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
raise NotChatAppError()
@ -56,9 +103,38 @@ class ConversationApi(Resource):
class ConversationDetailApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON))
@validate_app_token
@marshal_with(conversation_delete_fields)
def delete(self, app_model: App, end_user: EndUser, c_id):
"""Delete a conversation.
---
tags:
- app/conversation
summary: Delete conversation
description: Delete a specific conversation
security:
- ApiKeyAuth: []
parameters:
- name: c_id
in: path
required: true
type: string
format: uuid
description: ID of the conversation to delete
responses:
200:
description: Conversation deleted successfully
schema:
type: object
properties:
result:
type: string
example: success
401:
description: Invalid or missing token
404:
description: Conversation not found or not a chat app
"""
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
raise NotChatAppError()
@ -73,9 +149,57 @@ class ConversationDetailApi(Resource):
class ConversationRenameApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON))
@validate_app_token
@marshal_with(simple_conversation_fields)
def post(self, app_model: App, end_user: EndUser, c_id):
"""Rename a conversation.
---
tags:
- app/conversation
summary: Rename conversation
description: Change the name of a specific conversation
security:
- ApiKeyAuth: []
parameters:
- name: c_id
in: path
required: true
type: string
format: uuid
description: ID of the conversation to rename
- name: body
in: body
required: true
schema:
type: object
required:
- name
properties:
name:
type: string
description: New name for the conversation
auto_generate:
type: boolean
default: false
description: Whether to auto-generate the name
responses:
200:
description: Conversation renamed successfully
schema:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
400:
description: Invalid request
401:
description: Invalid or missing token
404:
description: Conversation not found or not a chat app
"""
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
raise NotChatAppError()

@ -1,6 +1,3 @@
from flask import request
from flask_restful import Resource, marshal_with # type: ignore
import services
from controllers.common.errors import FilenameNotExistsError
from controllers.service_api_with_auth import api
@ -10,16 +7,66 @@ from controllers.service_api_with_auth.app.error import (
TooManyFilesError,
UnsupportedFileTypeError,
)
from controllers.service_api_with_auth.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from controllers.service_api_with_auth.wraps import (
FetchUserArg,
WhereisUserArg,
validate_app_token,
)
from fields.file_fields import file_fields
from flask import request
from flask_restful import Resource, marshal_with # type: ignore
from models.model import App, EndUser
from services.file_service import FileService
class FileApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.FORM))
@validate_app_token
@marshal_with(file_fields)
def post(self, app_model: App, end_user: EndUser):
"""Upload a file.
---
tags:
- app/file
summary: Upload file
description: Upload a file to be used with the application
security:
- ApiKeyAuth: []
consumes:
- multipart/form-data
parameters:
- name: file
in: formData
required: true
type: file
description: The file to upload
responses:
201:
description: File uploaded successfully
schema:
type: object
properties:
id:
type: string
name:
type: string
size:
type: integer
extension:
type: string
mime_type:
type: string
url:
type: string
created_at:
type: string
format: date-time
400:
description: Invalid request, no file uploaded, unsupported file type, or too many files
401:
description: Invalid or missing token
413:
description: File too large
"""
file = request.files["file"]
# check file

@ -1,20 +1,23 @@
import logging
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from werkzeug.exceptions import BadRequest, InternalServerError, NotFound
import services
from controllers.service_api_with_auth import api
from controllers.service_api_with_auth.app.error import NotChatAppError
from controllers.service_api_with_auth.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from controllers.service_api_with_auth.wraps import (
FetchUserArg,
WhereisUserArg,
validate_app_token,
)
from core.app.entities.app_invoke_entities import InvokeFrom
from fields.conversation_fields import message_file_fields
from fields.raws import FilesContainedField
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from libs.helper import TimestampField, uuid_value
from models.model import App, AppMode, EndUser
from services.errors.message import SuggestedQuestionsAfterAnswerDisabledError
from services.message_service import MessageService
from werkzeug.exceptions import BadRequest, InternalServerError, NotFound
class MessageListApi(Resource):
@ -74,9 +77,57 @@ class MessageListApi(Resource):
"data": fields.List(fields.Nested(message_fields)),
}
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY))
@validate_app_token
@marshal_with(message_infinite_scroll_pagination_fields)
def get(self, app_model: App, end_user: EndUser):
"""Get messages list.
---
tags:
- app/message
summary: List messages
description: Get a paginated list of messages for a conversation
security:
- ApiKeyAuth: []
parameters:
- name: conversation_id
in: query
required: true
type: string
format: uuid
description: ID of the conversation to get messages for
- name: first_id
in: query
type: string
format: uuid
description: ID of the first message for pagination
- name: limit
in: query
type: integer
minimum: 1
maximum: 100
default: 20
description: Number of messages to return
responses:
200:
description: Messages retrieved successfully
schema:
type: object
properties:
limit:
type: integer
has_more:
type: boolean
data:
type: array
items:
type: object
400:
description: Invalid request
401:
description: Invalid or missing token
404:
description: Conversation not found or not a chat app
"""
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
raise NotChatAppError()
@ -98,8 +149,54 @@ class MessageListApi(Resource):
class MessageFeedbackApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON, required=True))
@validate_app_token
def post(self, app_model: App, end_user: EndUser, message_id):
"""Submit feedback for a message.
---
tags:
- app/message
summary: Submit message feedback
description: Submit user feedback for a specific message
security:
- ApiKeyAuth: []
parameters:
- name: message_id
in: path
required: true
type: string
format: uuid
description: ID of the message to provide feedback for
- name: body
in: body
required: true
schema:
type: object
required:
- rating
properties:
rating:
type: string
enum: [like, dislike, null]
description: User's rating of the message
content:
type: string
description: Additional feedback content
responses:
200:
description: Feedback submitted successfully
schema:
type: object
properties:
result:
type: string
example: success
400:
description: Invalid request
401:
description: Invalid or missing token
404:
description: Message not found or not a chat app
"""
message_id = str(message_id)
parser = reqparse.RequestParser()
@ -122,8 +219,43 @@ class MessageFeedbackApi(Resource):
class MessageSuggestedApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY, required=True))
@validate_app_token
def get(self, app_model: App, end_user: EndUser, message_id):
"""Get suggested questions for a message.
---
tags:
- app/message
summary: Get suggested questions
description: Get suggested follow-up questions for a specific message
security:
- ApiKeyAuth: []
parameters:
- name: message_id
in: path
required: true
type: string
format: uuid
description: ID of the message to get suggestions for
responses:
200:
description: Suggested questions retrieved successfully
schema:
type: object
properties:
result:
type: string
example: success
data:
type: array
items:
type: string
400:
description: Invalid request or suggestions disabled
401:
description: Invalid or missing token
404:
description: Message not found or not a chat app
"""
message_id = str(message_id)
app_mode = AppMode.value_of(app_model.mode)
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:

@ -1,9 +1,5 @@
import logging
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from werkzeug.exceptions import InternalServerError
from controllers.service_api_with_auth import api
from controllers.service_api_with_auth.app.error import (
CompletionRequestError,
@ -12,7 +8,11 @@ from controllers.service_api_with_auth.app.error import (
ProviderNotInitializeError,
ProviderQuotaExceededError,
)
from controllers.service_api_with_auth.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from controllers.service_api_with_auth.wraps import (
FetchUserArg,
WhereisUserArg,
validate_app_token,
)
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import (
@ -23,11 +23,14 @@ from core.errors.error import (
from core.model_runtime.errors.invoke import InvokeError
from extensions.ext_database import db
from fields.workflow_app_log_fields import workflow_app_log_pagination_fields
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore
from flask_restful.inputs import int_range # type: ignore
from libs import helper
from models.model import App, AppMode, EndUser
from models.workflow import WorkflowRun
from services.app_generate_service import AppGenerateService
from services.workflow_app_service import WorkflowAppService
from werkzeug.exceptions import InternalServerError
logger = logging.getLogger(__name__)
@ -50,8 +53,55 @@ class WorkflowRunDetailApi(Resource):
@validate_app_token
@marshal_with(workflow_run_fields)
def get(self, app_model: App, workflow_id: str):
"""
Get a workflow task running detail
"""Get workflow run details.
---
tags:
- app/workflow
summary: Get workflow run details
description: Retrieve details of a specific workflow run
security:
- ApiKeyAuth: []
parameters:
- name: workflow_id
in: path
required: true
type: string
description: ID of the workflow run to retrieve
responses:
200:
description: Workflow run details retrieved successfully
schema:
type: object
properties:
id:
type: string
workflow_id:
type: string
status:
type: string
inputs:
type: object
outputs:
type: object
error:
type: string
total_steps:
type: integer
total_tokens:
type: integer
created_at:
type: string
format: date-time
finished_at:
type: string
format: date-time
elapsed_time:
type: number
format: float
401:
description: Invalid or missing token
404:
description: Workflow run not found or not a workflow app
"""
app_mode = AppMode.value_of(app_model.mode)
if app_mode != AppMode.WORKFLOW:
@ -62,10 +112,41 @@ class WorkflowRunDetailApi(Resource):
class WorkflowRunApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON, required=True))
@validate_app_token
def post(self, app_model: App, end_user: EndUser):
"""
Run workflow
"""Run a workflow.
---
tags:
- app/workflow
summary: Run workflow
description: Execute a workflow with the provided inputs
security:
- ApiKeyAuth: []
parameters:
- name: body
in: body
required: true
schema:
type: object
required:
- inputs
properties:
inputs:
type: object
description: Input variables for the workflow
response_mode:
type: string
enum: [blocking, streaming]
description: Response delivery mode
responses:
200:
description: Workflow executed successfully
400:
description: Invalid request
401:
description: Invalid or missing token
404:
description: Not a workflow app
"""
app_mode = AppMode.value_of(app_model.mode)
if app_mode != AppMode.WORKFLOW:
@ -101,10 +182,35 @@ class WorkflowRunApi(Resource):
class WorkflowTaskStopApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON, required=True))
@validate_app_token
def post(self, app_model: App, end_user: EndUser, task_id: str):
"""
Stop workflow task
"""Stop a running workflow task.
---
tags:
- app/workflow
summary: Stop workflow task
description: Stop a running workflow task
security:
- ApiKeyAuth: []
parameters:
- name: task_id
in: path
required: true
type: string
description: ID of the task to stop
responses:
200:
description: Task stopped successfully
schema:
type: object
properties:
result:
type: string
example: success
401:
description: Invalid or missing token
404:
description: Not a workflow app
"""
app_mode = AppMode.value_of(app_model.mode)
if app_mode != AppMode.WORKFLOW:
@ -119,8 +225,50 @@ class WorkflowAppLogApi(Resource):
@validate_app_token
@marshal_with(workflow_app_log_pagination_fields)
def get(self, app_model: App):
"""
Get workflow app logs
"""Get workflow app logs.
---
tags:
- app/workflow
summary: Get workflow logs
description: Retrieve logs for workflow app executions
security:
- ApiKeyAuth: []
parameters:
- name: page
in: query
type: integer
minimum: 1
default: 1
description: Page number for pagination
- name: limit
in: query
type: integer
minimum: 1
maximum: 100
default: 20
description: Number of logs per page
responses:
200:
description: Workflow logs retrieved successfully
schema:
type: object
properties:
data:
type: array
items:
type: object
has_more:
type: boolean
limit:
type: integer
total:
type: integer
page:
type: integer
401:
description: Invalid or missing token
404:
description: Not a workflow app
"""
parser = reqparse.RequestParser()
parser.add_argument("keyword", type=str, location="args")

@ -9,6 +9,7 @@ from flask import current_app, request
from flask_login import user_logged_in # type: ignore
from flask_restful import Resource # type: ignore
from libs.login import _get_user
from libs.passport import PassportService
from models.account import Account, Tenant, TenantAccountJoin, TenantStatus
from models.model import ApiToken, App, EndUser
from pydantic import BaseModel # type: ignore
@ -33,16 +34,34 @@ class FetchUserArg(BaseModel):
required: bool = False
# TODO: add auth jwt token check
def validate_app_token(
view: Optional[Callable] = None, *, fetch_user_arg: Optional[FetchUserArg] = None
):
def validate_app_token(view: Optional[Callable] = None):
def decorator(view_func):
@wraps(view_func)
def decorated_view(*args, **kwargs):
api_token = validate_and_get_api_token("app")
# Extract user info from Bearer token
auth_header = request.headers.get("Authorization")
if auth_header is None or " " not in auth_header:
raise Unauthorized("Authorization header must be provided and start with 'Bearer'")
auth_scheme, auth_token = auth_header.split(None, 1)
auth_scheme = auth_scheme.lower()
if auth_scheme != "bearer":
raise Unauthorized("Authorization scheme must be 'Bearer'")
# Decode the JWT token to extract user info
try:
decoded = PassportService().verify(auth_token)
user_id = decoded.get("user_id")
if not user_id:
raise Unauthorized("Invalid token: missing user_id")
except Exception as e:
raise Unauthorized(f"Failed to extract user_id from token: {str(e)}")
# Get app model using hardcoded ID
app_id = "b278ba96-fa8e-48a8-b3e9-debe34468be0" # TODO: ytqh Replace with your actual hardcoded app ID
app_model = db.session.query(App).filter(App.id == app_id).first()
app_model = db.session.query(App).filter(App.id == api_token.app_id).first()
if not app_model:
raise Forbidden("The app no longer exists.")
@ -52,11 +71,7 @@ def validate_app_token(
if not app_model.enable_api:
raise Forbidden("The app's API service has been disabled.")
tenant = (
db.session.query(Tenant)
.filter(Tenant.id == app_model.tenant_id)
.first()
)
tenant = db.session.query(Tenant).filter(Tenant.id == app_model.tenant_id).first()
if tenant is None:
raise ValueError("Tenant does not exist.")
if tenant.status == TenantStatus.ARCHIVE:
@ -64,26 +79,7 @@ def validate_app_token(
kwargs["app_model"] = app_model
if fetch_user_arg:
if fetch_user_arg.fetch_from == WhereisUserArg.QUERY:
user_id = request.args.get("user")
elif fetch_user_arg.fetch_from == WhereisUserArg.JSON:
user_id = request.get_json().get("user")
elif fetch_user_arg.fetch_from == WhereisUserArg.FORM:
user_id = request.form.get("user")
else:
# use default-user
user_id = None
if not user_id and fetch_user_arg.required:
raise ValueError("Arg user must be provided.")
if user_id:
user_id = str(user_id)
kwargs["end_user"] = create_or_update_end_user_for_user_id(
app_model, user_id
)
kwargs["end_user"] = create_or_update_end_user_for_user_id(app_model, user_id)
return view_func(*args, **kwargs)
@ -108,27 +104,13 @@ def cloud_edition_billing_resource_check(resource: str, api_token_type: str):
documents_upload_quota = features.documents_upload_quota
if resource == "members" and 0 < members.limit <= members.size:
raise Forbidden(
"The number of members has reached the limit of your subscription."
)
raise Forbidden("The number of members has reached the limit of your subscription.")
elif resource == "apps" and 0 < apps.limit <= apps.size:
raise Forbidden(
"The number of apps has reached the limit of your subscription."
)
elif (
resource == "vector_space"
and 0 < vector_space.limit <= vector_space.size
):
raise Forbidden(
"The capacity of the vector space has reached the limit of your subscription."
)
elif (
resource == "documents"
and 0 < documents_upload_quota.limit <= documents_upload_quota.size
):
raise Forbidden(
"The number of documents has reached the limit of your subscription."
)
raise Forbidden("The number of apps has reached the limit of your subscription.")
elif resource == "vector_space" and 0 < vector_space.limit <= vector_space.size:
raise Forbidden("The capacity of the vector space has reached the limit of your subscription.")
elif resource == "documents" and 0 < documents_upload_quota.limit <= documents_upload_quota.size:
raise Forbidden("The number of documents has reached the limit of your subscription.")
else:
return view(*args, **kwargs)
@ -204,9 +186,7 @@ def validate_and_get_api_token(scope: str | None = None):
"""
auth_header = request.headers.get("Authorization")
if auth_header is None or " " not in auth_header:
raise Unauthorized(
"Authorization header must be provided and start with 'Bearer'"
)
raise Unauthorized("Authorization header must be provided and start with 'Bearer'")
auth_scheme, auth_token = auth_header.split(None, 1)
auth_scheme = auth_scheme.lower()
@ -221,10 +201,7 @@ def validate_and_get_api_token(scope: str | None = None):
update(ApiToken)
.where(
ApiToken.token == auth_token,
(
ApiToken.last_used_at.is_(None)
| (ApiToken.last_used_at < cutoff_time)
),
(ApiToken.last_used_at.is_(None) | (ApiToken.last_used_at < cutoff_time)),
ApiToken.type == scope,
)
.values(last_used_at=current_time)
@ -234,9 +211,7 @@ def validate_and_get_api_token(scope: str | None = None):
api_token = result.scalar_one_or_none()
if not api_token:
stmt = select(ApiToken).where(
ApiToken.token == auth_token, ApiToken.type == scope
)
stmt = select(ApiToken).where(ApiToken.token == auth_token, ApiToken.type == scope)
api_token = session.scalar(stmt)
if not api_token:
raise Unauthorized("Access token is invalid")
@ -246,9 +221,7 @@ def validate_and_get_api_token(scope: str | None = None):
return api_token
def create_or_update_end_user_for_user_id(
app_model: App, user_id: Optional[str] = None
) -> EndUser:
def create_or_update_end_user_for_user_id(app_model: App, user_id: Optional[str] = None) -> EndUser:
"""
Create or update session terminal based on user ID.
"""

Loading…
Cancel
Save