switch namespaces from tags to relations with auto-creation

This commit is contained in:
2026-03-28 04:15:36 +01:00
parent 9b250c20f9
commit f907657c6f
6 changed files with 128 additions and 66 deletions

View File

@@ -67,6 +67,50 @@ func (db *DB) resolveUserRef(tx *sql.Tx, ref string) (string, error) {
return db.ensureUser(tx, ref)
}
func (db *DB) ensureNamespace(tx *sql.Tx, name string) (string, error) {
var existingID string
err := tx.QueryRow(`
SELECT n.id FROM nodes n
JOIN tags t ON n.id = t.node_id
WHERE n.title = ? AND t.tag = '_type::namespace'`, name).Scan(&existingID)
if err == nil {
return existingID, nil
}
if err != sql.ErrNoRows {
return "", err
}
id := db.generateUniqueID()
now := time.Now().UTC().Format(time.RFC3339)
if _, err := tx.Exec("INSERT INTO nodes (id, title, created_at, updated_at) VALUES (?, ?, ?, ?)",
id, name, now, now); err != nil {
return "", err
}
if _, err := tx.Exec("INSERT INTO tags (node_id, tag) VALUES (?, '_type::namespace')", id); err != nil {
return "", err
}
if _, err := tx.Exec("INSERT INTO rels (from_id, to_id, rel_type) VALUES (?, ?, ?)",
id, id, models.RelInNamespace); err != nil {
return "", err
}
userID, err := db.resolveUserRef(tx, GetCurrentUser())
if err != nil {
return "", err
}
if _, err := tx.Exec("INSERT INTO rels (from_id, to_id, rel_type) VALUES (?, ?, ?)",
id, userID, models.RelCreated); err != nil {
return "", err
}
return id, nil
}
func (db *DB) resolveNamespaceRef(tx *sql.Tx, ref string) (string, error) {
if exists, _ := db.NodeExists(ref); exists {
return ref, nil
}
return db.ensureNamespace(tx, ref)
}
type CreateParams struct {
Title, Content, DueDate string
Tags []string
@@ -120,6 +164,12 @@ func (db *DB) CreateNode(p CreateParams) (*models.Node, error) {
return nil, err
}
}
if rt == models.RelInNamespace {
var err error
if tgt, err = db.resolveNamespaceRef(tx, tgt); err != nil {
return nil, err
}
}
if _, err := tx.Exec("INSERT INTO rels (from_id, to_id, rel_type) VALUES (?, ?, ?)", id, tgt, rt); err != nil {
return nil, err
}
@@ -187,6 +237,12 @@ func (db *DB) UpdateNode(id string, p UpdateParams) error {
return err
}
}
if rt == models.RelInNamespace {
var err error
if tgt, err = db.resolveNamespaceRef(tx, tgt); err != nil {
return err
}
}
tx.Exec("INSERT OR IGNORE INTO rels (from_id, to_id, rel_type) VALUES (?, ?, ?)", id, tgt, rt)
}
}
@@ -255,7 +311,9 @@ func (db *DB) ListNodes(f ListFilter) ([]*models.Node, error) {
args = append(args, len(f.TagPrefixes))
if f.Assignee != "" {
joins, conds, args = append(joins, "JOIN rels r_assign ON n.id = r_assign.from_id"), append(conds, "r_assign.to_id = ? AND r_assign.rel_type = ?"), append(args, f.Assignee, models.RelAssignee)
joins = append(joins, "JOIN rels r_assign ON n.id = r_assign.from_id")
conds = append(conds, "r_assign.to_id = ? AND r_assign.rel_type = ?")
args = append(args, f.Assignee, models.RelAssignee)
}
if len(joins) > 0 {
@@ -309,22 +367,3 @@ func (db *DB) CanClose(id string) (bool, []string, error) {
}
return len(blocking) == 0, blocking, nil
}
func (db *DB) GetSubtasks(parentID string) ([]*models.Node, error) {
rows, err := db.Query("SELECT to_id FROM rels WHERE from_id = ? AND rel_type = ?", parentID, models.RelSubtask)
if err != nil {
return nil, err
}
defer rows.Close()
var nodes []*models.Node
for rows.Next() {
var id string
rows.Scan(&id)
if n, err := db.NodeByID(id); err == nil {
nodes = append(nodes, n)
} else {
return nil, err
}
}
return nodes, nil
}