artifacts-dashboard/backend/app/models/pipeline.py
Paweł Orzech 75313b83c0
Add multi-user workflows/pipelines and error tracking
Add multi-user automation features and per-user error tracking.

- Database migrations: add workflow_configs/workflow_runs (004), app_errors (005), pipeline_configs/pipeline_runs (006), and add user_token_hash to app_errors (007).
- Backend: introduce per-request token handling (X-API-Token) via app.api.deps and update many API routes (auth, automations, bank, characters, dashboard, events, exchange, logs) to use user-scoped Artifacts client and character scoping. Auth endpoints no longer store tokens server-side (validate-only); clear is a no-op on server.
- New Errors API and services: endpoint to list, filter, resolve, and report errors scoped to the requesting user; add error models, schemas, middleware/error handler and error_service for recording/hashing tokens.
- Pipelines & Workflows: add API routers, models, schemas and engine modules (pipeline/worker/coordinator, workflow runner/conditions) and action_executor updates to support workflow/pipeline execution.
- Logs: logs endpoint now prefers fetching recent action logs from the game API (with fallback to local DB), supports paging and filtering, and scopes results to the user.
- Frontend: add pipeline/workflow builders, lists, progress components and hooks (use-errors, use-pipelines, use-workflows), sentry client config, and updates to API client/constants/types.
- Misc: add middleware error handler, various engine strategy tweaks, tests adjusted.

Overall this change enables per-user API tokens, scopes DB queries to each user, introduces pipelines/workflows runtime support, and centralizes application error tracking.
2026-03-01 23:02:34 +01:00

98 lines
3.4 KiB
Python

from datetime import datetime
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, JSON, String, Text, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class PipelineConfig(Base):
__tablename__ = "pipeline_configs"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(100), nullable=False)
description: Mapped[str] = mapped_column(Text, nullable=False, default="")
stages: Mapped[list] = mapped_column(
JSON,
nullable=False,
default=list,
comment="JSON array of pipeline stages, each with id, name, character_steps[]",
)
loop: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
max_loops: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
nullable=False,
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
onupdate=func.now(),
nullable=False,
)
runs: Mapped[list["PipelineRun"]] = relationship(
back_populates="pipeline",
cascade="all, delete-orphan",
order_by="PipelineRun.started_at.desc()",
)
def __repr__(self) -> str:
return (
f"<PipelineConfig(id={self.id}, name={self.name!r}, "
f"stages={len(self.stages)})>"
)
class PipelineRun(Base):
__tablename__ = "pipeline_runs"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
pipeline_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("pipeline_configs.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
status: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="running",
comment="Status: running, paused, stopped, completed, error",
)
current_stage_index: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
current_stage_id: Mapped[str] = mapped_column(String(100), nullable=False, default="")
loop_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
total_actions_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
character_states: Mapped[dict] = mapped_column(
JSON,
nullable=False,
default=dict,
comment="Per-character state: {char_name: {status, step_id, actions_count, error}}",
)
stage_history: Mapped[list] = mapped_column(
JSON,
nullable=False,
default=list,
comment="JSON array of completed stage records",
)
started_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
nullable=False,
)
stopped_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True),
nullable=True,
)
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
pipeline: Mapped["PipelineConfig"] = relationship(back_populates="runs")
def __repr__(self) -> str:
return (
f"<PipelineRun(id={self.id}, pipeline_id={self.pipeline_id}, "
f"status={self.status!r}, stage={self.current_stage_index})>"
)