rdev/internal/service/verify_service.go
jordan d69da6d627 feat: add structured logging infrastructure and SDLC extensions
Major changes:
- Add internal/logging package with field constants, context propagation,
  sensitive data auto-redaction, and per-component log levels
- Add worker timeout constants (TimeoutQuickOp, TimeoutHealthCheck, etc.)
- Extend SDLC with callback handlers, generate endpoints, and executor
- Add new cookbook trees for aeries and slackpath progression
- Add skeleton templates for queue, realtime, and microservices
- Add worker component template with async job processing
- Refactor services and handlers to use new logging infrastructure
- Split component.go into component_infra.go and component_listing.go

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 22:56:04 -07:00

140 lines
3.7 KiB
Go

// Package service provides business logic services.
package service
import (
"context"
"fmt"
"github.com/orchard9/rdev/internal/domain"
"github.com/orchard9/rdev/internal/logging"
"github.com/orchard9/rdev/internal/port"
)
// VerifyService orchestrates verify task submission and tracking.
// It coordinates between the work queue (execution) for visual captures.
type VerifyService struct {
queue port.WorkQueue
}
// NewVerifyService creates a new verify service.
func NewVerifyService(queue port.WorkQueue) *VerifyService {
return &VerifyService{
queue: queue,
}
}
// SubmitCapture validates spec and enqueues a verify task.
// Returns the task ID for status tracking.
func (s *VerifyService) SubmitCapture(ctx context.Context, projectID string, spec domain.VerifySpec) (string, error) {
if err := spec.Validate(); err != nil {
return "", fmt.Errorf("invalid verify spec: %w", err)
}
if projectID == "" {
return "", fmt.Errorf("project_id is required")
}
// Apply defaults
specWithDefaults := spec.WithDefaults()
// Build work task spec from verify spec
taskSpec := map[string]any{
"url": specWithDefaults.URL,
"viewports": specWithDefaults.Viewports,
"wait_for": specWithDefaults.WaitFor,
"wait_timeout": specWithDefaults.WaitTimeout,
"full_page": specWithDefaults.FullPage,
"video": specWithDefaults.Video,
}
if specWithDefaults.Evaluate {
taskSpec["evaluate"] = specWithDefaults.Evaluate
}
if specWithDefaults.Prompt != "" {
taskSpec["prompt"] = specWithDefaults.Prompt
}
if specWithDefaults.CallbackURL != "" {
taskSpec["callback_url"] = specWithDefaults.CallbackURL
}
// Create work task
task := &domain.WorkTask{
ProjectID: projectID,
Type: domain.WorkTaskTypeVerify,
Spec: taskSpec,
CallbackURL: specWithDefaults.CallbackURL,
MaxRetries: 1, // Verify tasks shouldn't retry by default
}
// Enqueue to work queue
taskID, err := s.queue.Enqueue(ctx, task)
if err != nil {
return "", fmt.Errorf("enqueue verify task: %w", err)
}
log := logging.FromContext(ctx).WithService("verify")
log.Info("verify task enqueued",
"task_id", taskID,
logging.FieldProjectID, projectID,
"url", specWithDefaults.URL,
"viewports", specWithDefaults.Viewports,
)
return taskID, nil
}
// GetCapture retrieves a verify task by ID.
func (s *VerifyService) GetCapture(ctx context.Context, taskID string) (*domain.WorkTask, error) {
task, err := s.queue.GetTask(ctx, taskID)
if err != nil {
return nil, err
}
// Verify it's actually a verify task
if task.Type != domain.WorkTaskTypeVerify {
return nil, domain.ErrWorkTaskNotFound
}
return task, nil
}
// ListCaptures returns verify tasks for a project.
func (s *VerifyService) ListCaptures(ctx context.Context, projectID string, opts domain.WorkListOptions) (*domain.WorkListResult, error) {
opts.Normalize()
// Get all tasks for the project
result, err := s.queue.ListByProject(ctx, projectID, nil, opts)
if err != nil {
return nil, err
}
// Filter to only verify tasks
var verifyTasks []*domain.WorkTask
for _, task := range result.Tasks {
if task.Type == domain.WorkTaskTypeVerify {
verifyTasks = append(verifyTasks, task)
}
}
return &domain.WorkListResult{
Tasks: verifyTasks,
Total: int64(len(verifyTasks)), // Note: this is filtered total, not DB total
Limit: result.Limit,
Offset: result.Offset,
}, nil
}
// CancelCapture cancels a pending verify task.
func (s *VerifyService) CancelCapture(ctx context.Context, taskID string) error {
// Verify it's a verify task first
task, err := s.queue.GetTask(ctx, taskID)
if err != nil {
return err
}
if task.Type != domain.WorkTaskTypeVerify {
return domain.ErrWorkTaskNotFound
}
return s.queue.Cancel(ctx, taskID)
}