docs: add watchlist export/import implementation plans
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
# 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**
|
||||
|
||||
```go
|
||||
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**
|
||||
|
||||
```bash
|
||||
git add api/watchlist/service.go
|
||||
git commit -m "refactor: wrap watchlist import in transaction"
|
||||
```
|
||||
Reference in New Issue
Block a user