package store import ( "context" "database/sql" "encoding/json" "errors" "fmt" "github.com/google/uuid" "github.com/jmoiron/sqlx" "git.hangman-lab.top/hzhang/Dialectic.Backend/internal/models" ) type SignupStore struct { db *sqlx.DB } func NewSignupStore(db *sqlx.DB) *SignupStore { return &SignupStore{db: db} } type UpsertSignupInput struct { TopicID string AgentID string WillingCamps []models.Camp PreValidated bool } // Upsert creates or updates an agent's signup for a topic. Re-signup // replaces willing_camps (intentional: lets an agent change their mind // before signup_close_at). func (s *SignupStore) Upsert(ctx context.Context, in UpsertSignupInput) (*models.SignupView, error) { if len(in.WillingCamps) == 0 { return nil, fmt.Errorf("willing_camps must be non-empty") } for _, c := range in.WillingCamps { if !models.IsCampValid(c) { return nil, fmt.Errorf("invalid camp %q", c) } } raw, err := json.Marshal(in.WillingCamps) if err != nil { return nil, err } // Try insert; on duplicate (topic, agent), update. id := uuid.NewString() _, err = s.db.ExecContext(ctx, ` INSERT INTO signups (id, topic_id, agent_id, willing_camps, pre_validated) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE willing_camps = VALUES(willing_camps), pre_validated = VALUES(pre_validated)`, id, in.TopicID, in.AgentID, raw, in.PreValidated) if err != nil { return nil, fmt.Errorf("upsert signup: %w", err) } return s.GetByPair(ctx, in.TopicID, in.AgentID) } func (s *SignupStore) GetByPair(ctx context.Context, topicID, agentID string) (*models.SignupView, error) { var row models.Signup err := s.db.GetContext(ctx, &row, `SELECT * FROM signups WHERE topic_id = ? AND agent_id = ?`, topicID, agentID) if errors.Is(err, sql.ErrNoRows) { return nil, ErrNotFound } if err != nil { return nil, err } v, err := row.View() if err != nil { return nil, err } return &v, nil } // ListByTopic returns all signups for a topic. Used by the allocation // algorithm at signup_close_at and by the topic-detail UI. func (s *SignupStore) ListByTopic(ctx context.Context, topicID string) ([]models.SignupView, error) { var rows []models.Signup if err := s.db.SelectContext(ctx, &rows, `SELECT * FROM signups WHERE topic_id = ? ORDER BY created_at ASC`, topicID); err != nil { return nil, err } out := make([]models.SignupView, 0, len(rows)) for _, r := range rows { v, err := r.View() if err != nil { return nil, err } out = append(out, v) } return out, nil }