Assign readable ticket keys from project code and sequence.
Replace UUID-looking sync labels in TUI and web with keys like TAS-1, while keeping the remote UUID for task-master matching. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// backfillTicketKeys assigns project codes and sequential ticket keys for rows
|
||||
// that predate migration 005. Safe to run repeatedly (no-ops when already set).
|
||||
func (db *DB) backfillTicketKeys() error {
|
||||
if err := db.backfillProjectCodes(); err != nil {
|
||||
return err
|
||||
}
|
||||
return db.backfillTicketNumbers()
|
||||
}
|
||||
|
||||
func (db *DB) backfillProjectCodes() error {
|
||||
rows, err := db.SQL.Query(`SELECT id, name, code FROM projects`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("backfill codes: list: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
type row struct {
|
||||
id, name, code string
|
||||
}
|
||||
var all []row
|
||||
used := map[string]bool{}
|
||||
for rows.Next() {
|
||||
var r row
|
||||
if err := rows.Scan(&r.id, &r.name, &r.code); err != nil {
|
||||
return err
|
||||
}
|
||||
all = append(all, r)
|
||||
if r.code != "" {
|
||||
used[r.code] = true
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, r := range all {
|
||||
if r.code != "" {
|
||||
continue
|
||||
}
|
||||
code := nextUniqueCode(r.name, used)
|
||||
used[code] = true
|
||||
if _, err := db.SQL.Exec(`UPDATE projects SET code = ? WHERE id = ?`, code, r.id); err != nil {
|
||||
return fmt.Errorf("backfill code %s: %w", r.id, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) backfillTicketNumbers() error {
|
||||
type proj struct {
|
||||
id, code string
|
||||
seq int
|
||||
}
|
||||
var projects []proj
|
||||
{
|
||||
projRows, err := db.SQL.Query(`SELECT id, code, ticket_seq FROM projects`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("backfill tickets: projects: %w", err)
|
||||
}
|
||||
for projRows.Next() {
|
||||
var p proj
|
||||
if err := projRows.Scan(&p.id, &p.code, &p.seq); err != nil {
|
||||
projRows.Close()
|
||||
return err
|
||||
}
|
||||
projects = append(projects, p)
|
||||
}
|
||||
err = projRows.Err()
|
||||
projRows.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range projects {
|
||||
type trow struct {
|
||||
id, key string
|
||||
number sql.NullInt64
|
||||
}
|
||||
var tickets []trow
|
||||
{
|
||||
tRows, err := db.SQL.Query(`
|
||||
SELECT id, number, key FROM tickets
|
||||
WHERE project_id = ?
|
||||
ORDER BY created_at ASC, id ASC`, p.id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for tRows.Next() {
|
||||
var tr trow
|
||||
if err := tRows.Scan(&tr.id, &tr.number, &tr.key); err != nil {
|
||||
tRows.Close()
|
||||
return err
|
||||
}
|
||||
tickets = append(tickets, tr)
|
||||
}
|
||||
err = tRows.Err()
|
||||
tRows.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
seq := p.seq
|
||||
for _, tr := range tickets {
|
||||
if tr.number.Valid && tr.key != "" {
|
||||
if int(tr.number.Int64) > seq {
|
||||
seq = int(tr.number.Int64)
|
||||
}
|
||||
continue
|
||||
}
|
||||
seq++
|
||||
newKey := fmt.Sprintf("%s-%d", p.code, seq)
|
||||
if _, err := db.SQL.Exec(`UPDATE tickets SET number = ?, key = ? WHERE id = ?`, seq, newKey, tr.id); err != nil {
|
||||
return fmt.Errorf("backfill ticket %s: %w", tr.id, err)
|
||||
}
|
||||
}
|
||||
if seq != p.seq {
|
||||
if _, err := db.SQL.Exec(`UPDATE projects SET ticket_seq = ? WHERE id = ?`, seq, p.id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user