Files
ttstd-infinite-canvas/backend/internal/repository/comfy_bridge.go

199 lines
7.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package repository
import (
"errors"
"time"
"infinite-canvas/backend/internal/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
var ErrComfyBridgeRequestOwnership = errors.New("comfy bridge request belongs to another bridge")
var ErrComfyBridgeRequestState = errors.New("comfy bridge request state conflict")
// ComfyBridgeForUser 按用户作用域读取 Bridge,避免管理接口意外越权访问其他用户的设备。
func (r *Repository) ComfyBridgeForUser(userID string, id string) (*model.ComfyBridge, error) {
var bridge model.ComfyBridge
if err := r.db.First(&bridge, "id = ? AND user_id = ?", id, userID).Error; err != nil {
return nil, err
}
return &bridge, nil
}
func (r *Repository) ComfyBridgesForUser(userID string) ([]model.ComfyBridge, error) {
var bridges []model.ComfyBridge
// 已撤销的 Bridge 仍保留数据库记录用于审计,但不能再出现在可选设备列表中。
err := r.db.Where("user_id = ? AND enabled = ?", userID, true).Order("created_at desc").Find(&bridges).Error
return bridges, err
}
// ComfyBridgeByTokenHash 只返回启用的 Bridge。Token 明文不会进入数据库查询或日志。
func (r *Repository) ComfyBridgeByTokenHash(tokenHash string) (*model.ComfyBridge, error) {
var bridge model.ComfyBridge
if err := r.db.First(&bridge, "token_hash = ? AND enabled = ?", tokenHash, true).Error; err != nil {
return nil, err
}
return &bridge, nil
}
func (r *Repository) DisableComfyBridge(userID string, id string, now time.Time) error {
result := r.db.Model(&model.ComfyBridge{}).
Where("id = ? AND user_id = ? AND enabled = ?", id, userID, true).
Updates(map[string]any{"enabled": false, "updated_at": now})
if result.Error != nil {
return result.Error
}
if result.RowsAffected != 1 {
return gorm.ErrRecordNotFound
}
return nil
}
func (r *Repository) TouchComfyBridge(id string, now time.Time) error {
result := r.db.Model(&model.ComfyBridge{}).Where("id = ? AND enabled = ?", id, true).
Updates(map[string]any{"last_seen_at": now, "updated_at": now})
if result.Error != nil {
return result.Error
}
if result.RowsAffected != 1 {
return gorm.ErrRecordNotFound
}
return nil
}
func (r *Repository) UpdateComfyBridgeHeartbeat(id string, capabilitiesJSON string, now time.Time) error {
updates := map[string]any{"last_seen_at": now, "updated_at": now}
if capabilitiesJSON != "" {
updates["capabilities_json"] = capabilitiesJSON
}
result := r.db.Model(&model.ComfyBridge{}).Where("id = ? AND enabled = ?", id, true).Updates(updates)
if result.Error != nil {
return result.Error
}
if result.RowsAffected != 1 {
return gorm.ErrRecordNotFound
}
return nil
}
func (r *Repository) MarkComfyBridgeTask(id string, now time.Time) error {
result := r.db.Model(&model.ComfyBridge{}).Where("id = ? AND enabled = ?", id, true).
Updates(map[string]any{"last_task_at": now, "last_seen_at": now, "updated_at": now})
if result.Error != nil {
return result.Error
}
if result.RowsAffected != 1 {
return gorm.ErrRecordNotFound
}
return nil
}
func (r *Repository) CreateOrGetComfyBridgeRequest(request *model.ComfyBridgeRequest) (*model.ComfyBridgeRequest, bool, error) {
result := r.db.Clauses(clause.OnConflict{DoNothing: true}).Create(request)
if result.Error != nil {
return nil, false, result.Error
}
if result.RowsAffected == 1 {
return request, true, nil
}
var existing model.ComfyBridgeRequest
if err := r.db.First(&existing, "id = ?", request.ID).Error; err != nil {
return nil, false, err
}
return &existing, false, nil
}
func (r *Repository) ComfyBridgeRequest(id string) (*model.ComfyBridgeRequest, error) {
var request model.ComfyBridgeRequest
if err := r.db.First(&request, "id = ?", id).Error; err != nil {
return nil, err
}
return &request, nil
}
func (r *Repository) PendingComfyBridgeRequestCount(bridgeID string, now time.Time) (int64, error) {
var count int64
err := r.db.Model(&model.ComfyBridgeRequest{}).
Where("bridge_id = ? AND status IN ? AND expires_at > ?", bridgeID, []string{"queued", "claimed"}, now).
Count(&count).Error
return count, err
}
// ClaimNextComfyBridgeRequest 以数据库状态机为队列真相,后端重启后仍可继续投递未领取请求。
func (r *Repository) ClaimNextComfyBridgeRequest(bridgeID string, now time.Time) (*model.ComfyBridgeRequest, error) {
var request model.ComfyBridgeRequest
err := r.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&model.ComfyBridgeRequest{}).
Where("status IN ? AND expires_at <= ?", []string{"queued", "claimed"}, now).
Updates(map[string]any{"status": "failed", "error": "本地 ComfyUI Bridge 请求已过期", "completed_at": now, "updated_at": now}).Error; err != nil {
return err
}
query := tx.Where("bridge_id = ? AND status = ? AND expires_at > ?", bridgeID, "queued", now).
Order("created_at asc").Limit(1)
if r.Dialect() == "postgres" {
query = query.Clauses(clause.Locking{Strength: "UPDATE", Options: "SKIP LOCKED"})
}
result := query.Find(&request)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
request = model.ComfyBridgeRequest{}
return nil
}
updated := tx.Model(&model.ComfyBridgeRequest{}).
Where("id = ? AND status = ?", request.ID, "queued").
Updates(map[string]any{"status": "claimed", "claimed_at": now, "updated_at": now})
if updated.Error != nil {
return updated.Error
}
if updated.RowsAffected != 1 {
request = model.ComfyBridgeRequest{}
return nil
}
return tx.First(&request, "id = ?", request.ID).Error
})
if err != nil || request.ID == "" {
return nil, err
}
return &request, nil
}
func (r *Repository) CompleteComfyBridgeRequest(bridgeID string, id string, status string, resultJSON string, errorText string, now time.Time) (*model.ComfyBridgeRequest, error) {
var request model.ComfyBridgeRequest
err := r.db.Transaction(func(tx *gorm.DB) error {
if err := tx.First(&request, "id = ?", id).Error; err != nil {
return err
}
if request.BridgeID != bridgeID {
return ErrComfyBridgeRequestOwnership
}
if request.Status == "succeeded" || request.Status == "failed" || request.Status == "cancelled" {
return nil
}
if request.Status != "claimed" {
return ErrComfyBridgeRequestState
}
if err := tx.Model(&model.ComfyBridgeRequest{}).Where("id = ? AND status = ?", id, "claimed").Updates(map[string]any{
"status": status, "result_json": resultJSON, "error": errorText, "completed_at": now, "updated_at": now,
}).Error; err != nil {
return err
}
return tx.First(&request, "id = ?", id).Error
})
return &request, err
}
func (r *Repository) CancelComfyBridgeRequest(id string, now time.Time) error {
return r.db.Model(&model.ComfyBridgeRequest{}).
Where("id = ? AND status IN ?", id, []string{"queued", "claimed"}).
Updates(map[string]any{"status": "cancelled", "error": "任务已取消", "completed_at": now, "updated_at": now}).Error
}
func (r *Repository) FailComfyBridgeRequests(bridgeID string, errorText string, now time.Time) error {
return r.db.Model(&model.ComfyBridgeRequest{}).
Where("bridge_id = ? AND status IN ?", bridgeID, []string{"queued", "claimed"}).
Updates(map[string]any{"status": "failed", "error": errorText, "completed_at": now, "updated_at": now}).Error
}