dify/api/models/app_asset.py
Yansong Zhang 44491e427c feat(api): enable all sandbox/skill controller routes and resolve dependencies (P0)
Resolve the full dependency chain to enable all previously disabled controllers:

Enabled routes:
- sandbox_files: sandbox file browser API
- sandbox_providers: sandbox provider management API
- app_asset: app asset management API
- skills: skill extraction API
- CLI API blueprint: DifyCli callback endpoints (/cli/api/*)

Dependencies extracted (64 files, ~8000 lines):
- models/sandbox.py, models/app_asset.py: DB models
- core/zip_sandbox/: zip-based sandbox execution
- core/session/: CLI API session management
- core/memory/: base memory + node token buffer
- core/helper/creators.py: helper utilities
- core/llm_generator/: context models, output models, utils
- core/workflow/nodes/command/: command node type
- core/workflow/nodes/file_upload/: file upload node type
- core/app/entities/: app_asset_entities, app_bundle_entities, llm_generation_entities
- services/: asset_content, skill, workflow_collaboration, workflow_comment
- controllers/console/app/error.py: AppAsset error classes
- core/tools/utils/system_encryption.py

Import fixes:
- dify_graph.enums -> graphon.enums in skill_service.py
- get_signed_file_url_for_plugin -> get_signed_file_url in cli_api.py

All 5 controllers verified: import OK, Flask starts successfully.
46 existing tests still pass.

Made-with: Cursor
2026-04-09 09:36:16 +08:00

90 lines
3.6 KiB
Python

from datetime import datetime
from uuid import uuid4
import sqlalchemy as sa
from sqlalchemy import DateTime, Integer, String, func
from sqlalchemy.orm import Mapped, mapped_column
from core.app.entities.app_asset_entities import AppAssetFileTree
from .base import Base
from .types import LongText, StringUUID
class AppAssets(Base):
__tablename__ = "app_assets"
__table_args__ = (
sa.PrimaryKeyConstraint("id", name="app_assets_pkey"),
sa.Index("app_assets_version_idx", "tenant_id", "app_id", "version"),
)
VERSION_DRAFT = "draft"
VERSION_PUBLISHED = "published"
id: Mapped[str] = mapped_column(StringUUID, default=lambda: str(uuid4()))
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
version: Mapped[str] = mapped_column(String(255), nullable=False)
_asset_tree: Mapped[str] = mapped_column("asset_tree", LongText, nullable=False, default='{"nodes":[]}')
created_by: Mapped[str] = mapped_column(StringUUID, nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, server_default=func.current_timestamp())
updated_by: Mapped[str | None] = mapped_column(StringUUID)
updated_at: Mapped[datetime] = mapped_column(
DateTime,
nullable=False,
default=func.current_timestamp(),
server_default=func.current_timestamp(),
onupdate=func.current_timestamp(),
)
@property
def asset_tree(self) -> AppAssetFileTree:
if not self._asset_tree:
return AppAssetFileTree()
return AppAssetFileTree.model_validate_json(self._asset_tree)
@asset_tree.setter
def asset_tree(self, value: AppAssetFileTree) -> None:
self._asset_tree = value.model_dump_json()
def __repr__(self) -> str:
return f"<AppAssets(id={self.id}, app_id={self.app_id}, version={self.version})>"
class AppAssetContent(Base):
"""Inline content cache for app asset draft files.
Acts as a read-through cache for S3: text-like asset content is dual-written
here on save and read from DB first (falling back to S3 on miss with sync backfill).
Keyed by (tenant_id, app_id, node_id) — stores only the current draft content,
not published snapshots.
See core/app_assets/content_accessor.py for the accessor abstraction that
manages the DB/S3 read-through and dual-write logic.
"""
__tablename__ = "app_asset_contents"
__table_args__ = (
sa.PrimaryKeyConstraint("id", name="app_asset_contents_pkey"),
sa.UniqueConstraint("tenant_id", "app_id", "node_id", name="uq_asset_content_node"),
sa.Index("idx_asset_content_app", "tenant_id", "app_id"),
)
id: Mapped[str] = mapped_column(StringUUID, default=lambda: str(uuid4()))
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
node_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
content: Mapped[str] = mapped_column(LongText, nullable=False, default="")
size: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, server_default=func.current_timestamp())
updated_at: Mapped[datetime] = mapped_column(
DateTime,
nullable=False,
default=func.current_timestamp(),
server_default=func.current_timestamp(),
onupdate=func.current_timestamp(),
)
def __repr__(self) -> str:
return f"<AppAssetContent(id={self.id}, node_id={self.node_id})>"