package sqlite import ( "context" "database/sql" "cmroubao/backend-api/internal/domain" "cmroubao/backend-api/internal/usecase" ) const assetUploadOperation = "UPLOAD_TASK_REFERENCE" func (s *Store) CreateAssetIdempotent( ctx context.Context, candidate domain.Asset, idempotencyKey string, requestHash string, ) (domain.Asset, bool, error) { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return domain.Asset{}, false, repositoryFailure(err) } defer func() { _ = tx.Rollback() }() existingHash, resourceID, found, err := lookupIdempotency( ctx, tx, candidate.CreatorSubject, assetUploadOperation, idempotencyKey, ) if err != nil { return domain.Asset{}, false, err } if found { if existingHash != requestHash { return domain.Asset{}, false, usecase.ErrIdempotencyConflict } existing, err := getAssetByID( ctx, tx, candidate.CreatorSubject, resourceID, ) if err != nil { return domain.Asset{}, false, err } if err := tx.Commit(); err != nil { return domain.Asset{}, false, repositoryFailure(err) } return existing, false, nil } _, err = tx.ExecContext( ctx, `INSERT INTO assets ( id, creator_subject, purpose, media_type, size_bytes, sha256, storage_key, created_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, candidate.ID, candidate.CreatorSubject, candidate.Purpose, candidate.MediaType, candidate.SizeBytes, candidate.SHA256, candidate.StorageKey, formatTimestamp(candidate.CreatedAt), ) if err != nil { return domain.Asset{}, false, repositoryFailure(err) } if err := insertIdempotency( ctx, tx, candidate.CreatorSubject, assetUploadOperation, idempotencyKey, requestHash, "ASSET", candidate.ID, candidate.CreatedAt, ); err != nil { return domain.Asset{}, false, err } if err := tx.Commit(); err != nil { return domain.Asset{}, false, repositoryFailure(err) } return candidate, true, nil } func (s *Store) GetAsset( ctx context.Context, creatorSubject string, assetID string, ) (domain.Asset, error) { return getAssetByID(ctx, s.db, creatorSubject, assetID) } var _ usecase.AssetRepository = (*Store)(nil) var _ queryRower = (*sql.Tx)(nil)