Files
mal/docs/superpowers/plans/2026-05-06-refactor-import-watchlist.md

2.7 KiB

Refactor ImportWatchlist Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Refactor ImportWatchlist to use a database transaction for the import loop and improve logging.

Architecture: Use db.BeginTx to wrap the loop. ensureAnimeExists continues to use s.db to avoid long-running transactions during external API calls.

Tech Stack: Go, database/sql, csv.


Task 1: Refactor ImportWatchlist with Transaction

Files:

  • Modify: api/watchlist/service.go

  • Step 1: Update ImportWatchlist implementation

func (s *Service) ImportWatchlist(ctx context.Context, userID string, r io.Reader) error {
	txQueries, tx, err := db.BeginTx(ctx, s.sqlDB)
	if err != nil {
		return fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback()

	reader := csv.NewReader(r)
	if _, err := reader.Read(); err != nil {
		return fmt.Errorf("failed to read csv header: %w", err)
	}

	records, err := reader.ReadAll()
	if err != nil {
		return fmt.Errorf("failed to read csv records: %w", err)
	}

	for i, record := range records {
		if len(record) < 4 {
			log.Printf("skipping row %d: insufficient columns", i+2) // i+2 because i is 0-indexed record after header
			continue
		}

		animeID, err := strconv.ParseInt(record[0], 10, 64)
		if err != nil {
			return fmt.Errorf("row %d: invalid anime id: %w", i+2, err)
		}

		status := record[1]
		if _, ok := validStatuses[status]; !ok {
			status = "plan_to_watch"
		}

		currentEpisode, _ := strconv.ParseInt(record[2], 10, 64)
		currentTimeSeconds, _ := strconv.ParseFloat(record[3], 64)

		if err := s.ensureAnimeExists(ctx, animeID); err != nil {
			return fmt.Errorf("row %d: failed to ensure anime: %w", i+2, err)
		}

		_, err = txQueries.UpsertWatchListEntry(ctx, db.UpsertWatchListEntryParams{
			ID:                 uuid.New().String(),
			UserID:             userID,
			AnimeID:            animeID,
			Status:             status,
			CurrentEpisode:     sql.NullInt64{Int64: currentEpisode, Valid: currentEpisode > 0},
			CurrentTimeSeconds: currentTimeSeconds,
		})
		if err != nil {
			return fmt.Errorf("row %d: failed to upsert entry: %w", i+2, err)
		}
	}

	return tx.Commit()
}
  • Step 2: Add missing import log

  • Step 3: Verify compilation

Run: go build ./api/watchlist/... Expected: Success (no output)

  • Step 4: Verify tests

Run: go test ./api/watchlist/... Expected: Success

  • Step 5: Commit
git add api/watchlist/service.go
git commit -m "refactor: wrap watchlist import in transaction"