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:
+44
-10
@@ -25,7 +25,7 @@ func NewProjectStore(db *DB) *ProjectStore {
|
||||
func scanProject(scanner interface{ Scan(dest ...any) error }) (project.Project, error) {
|
||||
var p project.Project
|
||||
var groupID sql.NullString
|
||||
if err := scanner.Scan(&p.ID, &p.Name, &groupID); err != nil {
|
||||
if err := scanner.Scan(&p.ID, &p.Name, &p.Code, &groupID); err != nil {
|
||||
return project.Project{}, err
|
||||
}
|
||||
if groupID.Valid {
|
||||
@@ -34,9 +34,11 @@ func scanProject(scanner interface{ Scan(dest ...any) error }) (project.Project,
|
||||
return p, nil
|
||||
}
|
||||
|
||||
const projectCols = `id, name, code, group_id`
|
||||
|
||||
// List returns all projects sorted by name.
|
||||
func (s *ProjectStore) List() ([]project.Project, error) {
|
||||
rows, err := s.db.SQL.Query(`SELECT id, name, group_id FROM projects ORDER BY name COLLATE NOCASE`)
|
||||
rows, err := s.db.SQL.Query(`SELECT ` + projectCols + ` FROM projects ORDER BY name COLLATE NOCASE`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list projects: %w", err)
|
||||
}
|
||||
@@ -55,19 +57,46 @@ func (s *ProjectStore) List() ([]project.Project, error) {
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *ProjectStore) usedCodes() (map[string]bool, error) {
|
||||
rows, err := s.db.SQL.Query(`SELECT code FROM projects WHERE code != ''`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
used := map[string]bool{}
|
||||
for rows.Next() {
|
||||
var c string
|
||||
if err := rows.Scan(&c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
used[c] = true
|
||||
}
|
||||
return used, rows.Err()
|
||||
}
|
||||
|
||||
// Ensure creates the project if missing.
|
||||
func (s *ProjectStore) Ensure(id string) error {
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" {
|
||||
return fmt.Errorf("project id is required")
|
||||
}
|
||||
used, err := s.usedCodes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
code := nextUniqueCode(id, used)
|
||||
now := formatTime(time.Now().UTC())
|
||||
_, err := s.db.SQL.Exec(`
|
||||
INSERT INTO projects(id, name, created_at, updated_at) VALUES(?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO NOTHING`, id, id, now, now)
|
||||
_, err = s.db.SQL.Exec(`
|
||||
INSERT INTO projects(id, name, code, created_at, updated_at) VALUES(?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO NOTHING`, id, id, code, now, now)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ensure project %s: %w", id, err)
|
||||
}
|
||||
var existing string
|
||||
_ = s.db.SQL.QueryRow(`SELECT code FROM projects WHERE id = ?`, id).Scan(&existing)
|
||||
if existing == "" {
|
||||
return s.db.backfillProjectCodes()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -81,13 +110,18 @@ func (s *ProjectStore) Create(id, name string) (project.Project, error) {
|
||||
if id == "" {
|
||||
id = uuid.NewString()
|
||||
}
|
||||
used, err := s.usedCodes()
|
||||
if err != nil {
|
||||
return project.Project{}, err
|
||||
}
|
||||
code := nextUniqueCode(name, used)
|
||||
now := formatTime(time.Now().UTC())
|
||||
_, err := s.db.SQL.Exec(`INSERT INTO projects(id, name, created_at, updated_at, group_id) VALUES(?, ?, ?, ?, NULL)`,
|
||||
id, name, now, now)
|
||||
_, err = s.db.SQL.Exec(`INSERT INTO projects(id, name, code, created_at, updated_at, group_id) VALUES(?, ?, ?, ?, ?, NULL)`,
|
||||
id, name, code, now, now)
|
||||
if err != nil {
|
||||
return project.Project{}, fmt.Errorf("create project: %w", err)
|
||||
}
|
||||
return project.Project{ID: id, Name: name}, nil
|
||||
return project.Project{ID: id, Name: name, Code: code}, nil
|
||||
}
|
||||
|
||||
// SetGroup assigns a project to a group. Empty groupID clears the assignment.
|
||||
@@ -151,7 +185,7 @@ func (s *ProjectStore) Delete(id string) error {
|
||||
|
||||
// Get returns one project.
|
||||
func (s *ProjectStore) Get(id string) (project.Project, error) {
|
||||
p, err := scanProject(s.db.SQL.QueryRow(`SELECT id, name, group_id FROM projects WHERE id = ?`, id))
|
||||
p, err := scanProject(s.db.SQL.QueryRow(`SELECT `+projectCols+` FROM projects WHERE id = ?`, id))
|
||||
if err == sql.ErrNoRows {
|
||||
return project.Project{}, fmt.Errorf("project %s: not found", id)
|
||||
}
|
||||
@@ -291,7 +325,7 @@ func (s *ProjectStore) ListIDsForUser(userID string, isAdmin bool) ([]project.Pr
|
||||
return s.List()
|
||||
}
|
||||
rows, err := s.db.SQL.Query(`
|
||||
SELECT p.id, p.name, p.group_id FROM projects p
|
||||
SELECT p.id, p.name, p.code, p.group_id FROM projects p
|
||||
INNER JOIN project_members m ON m.project_id = p.id
|
||||
WHERE m.user_id = ?
|
||||
ORDER BY p.name COLLATE NOCASE`, userID)
|
||||
|
||||
Reference in New Issue
Block a user