auto-create users on mention and resolve user refs in relationships

This commit is contained in:
2026-03-27 18:11:13 +01:00
parent 74cc7c104a
commit 9b250c20f9
3 changed files with 69 additions and 6 deletions

View File

@@ -35,14 +35,21 @@ var createCmd = &cobra.Command{
} }
rels := make(map[models.RelType][]string) rels := make(map[models.RelType][]string)
relCreated := false
for _, r := range cRels { for _, r := range cRels {
rt, tgt, err := db.ParseRelFlag(r) rt, tgt, err := db.ParseRelFlag(r)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
return return
} }
if rt == models.RelCreated {
relCreated = true
}
rels[rt] = append(rels[rt], tgt) rels[rt] = append(rels[rt], tgt)
} }
if !relCreated {
rels[models.RelCreated] = append(rels[models.RelCreated], db.GetCurrentUser())
}
if n, err := d.CreateNode(db.CreateParams{Title: args[0], Content: cContent, DueDate: cDue, Tags: cTags, Rels: rels}); err != nil { if n, err := d.CreateNode(db.CreateParams{Title: args[0], Content: cContent, DueDate: cDue, Tags: cTags, Rels: rels}); err != nil {
fmt.Fprintln(os.Stderr, "failed to create:", err) fmt.Fprintln(os.Stderr, "failed to create:", err)

View File

@@ -35,6 +35,38 @@ func (db *DB) generateUniqueID() string {
} }
} }
func (db *DB) ensureUser(tx *sql.Tx, username 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::user'`, username).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, username, now, now); err != nil {
return "", err
}
if _, err := tx.Exec("INSERT INTO tags (node_id, tag) VALUES (?, '_type::user')", id); err != nil {
return "", err
}
return id, nil
}
func (db *DB) resolveUserRef(tx *sql.Tx, ref string) (string, error) {
if exists, _ := db.NodeExists(ref); exists {
return ref, nil
}
return db.ensureUser(tx, ref)
}
type CreateParams struct { type CreateParams struct {
Title, Content, DueDate string Title, Content, DueDate string
Tags []string Tags []string
@@ -64,6 +96,13 @@ func (db *DB) CreateNode(p CreateParams) (*models.Node, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, m := range parse.Mentions(p.Title + " " + p.Content) {
if _, err := db.ensureUser(tx, m); err != nil {
return nil, err
}
}
for _, t := range append(p.Tags, parse.Mentions(p.Title+" "+p.Content)...) { for _, t := range append(p.Tags, parse.Mentions(p.Title+" "+p.Content)...) {
if !strings.HasPrefix(t, "_") && strings.HasPrefix(t, "@") { if !strings.HasPrefix(t, "_") && strings.HasPrefix(t, "@") {
if _, err = tx.Exec("INSERT INTO tags (node_id, tag) VALUES (?, ?)", id, "_inbox::"+t[1:]); err != nil { if _, err = tx.Exec("INSERT INTO tags (node_id, tag) VALUES (?, ?)", id, "_inbox::"+t[1:]); err != nil {
@@ -75,6 +114,12 @@ func (db *DB) CreateNode(p CreateParams) (*models.Node, error) {
} }
for rt, tgts := range p.Rels { for rt, tgts := range p.Rels {
for _, tgt := range tgts { for _, tgt := range tgts {
if rt == models.RelAssignee || rt == models.RelCreated {
var err error
if tgt, err = db.resolveUserRef(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 { if _, err := tx.Exec("INSERT INTO rels (from_id, to_id, rel_type) VALUES (?, ?, ?)", id, tgt, rt); err != nil {
return nil, err return nil, err
} }
@@ -106,6 +151,11 @@ func (db *DB) UpdateNode(id string, p UpdateParams) error {
if err := upd("content", *p.Content); err != nil { if err := upd("content", *p.Content); err != nil {
return err return err
} }
for _, m := range parse.Mentions(*p.Content) {
if _, err := db.ensureUser(tx, m); err != nil {
return err
}
}
} }
if p.DueDate != nil { if p.DueDate != nil {
if err := upd("due_date", *p.DueDate); err != nil { if err := upd("due_date", *p.DueDate); err != nil {
@@ -131,6 +181,12 @@ func (db *DB) UpdateNode(id string, p UpdateParams) error {
} }
for rt, tgts := range p.AddRels { for rt, tgts := range p.AddRels {
for _, tgt := range tgts { for _, tgt := range tgts {
if rt == models.RelAssignee || rt == models.RelCreated {
var err error
if tgt, err = db.resolveUserRef(tx, tgt); err != nil {
return err
}
}
tx.Exec("INSERT OR IGNORE INTO rels (from_id, to_id, rel_type) VALUES (?, ?, ?)", id, tgt, rt) tx.Exec("INSERT OR IGNORE INTO rels (from_id, to_id, rel_type) VALUES (?, ?, ?)", id, tgt, rt)
} }
} }

View File

@@ -107,7 +107,7 @@ func PrintNodes(w io.Writer, nodes []*models.Node, jsonOut bool) error {
render(prioRM, n.GetProperty("prio"), true), render(prioRM, n.GetProperty("prio"), true),
render(statusRM, n.GetProperty("status"), true), render(statusRM, n.GetProperty("status"), true),
render(typeRM, n.GetProperty("type"), true), render(typeRM, n.GetProperty("type"), true),
cTitle.Sprint(truncate(n.Title, 35)), cTitle.Sprint(truncate(n.Title, 80)),
cDim.Sprint("["+n.GetProperty("namespace")+"]"), cDim.Sprint("["+n.GetProperty("namespace")+"]"),
) )
if len(tags) > 0 { if len(tags) > 0 {
@@ -147,14 +147,14 @@ func PrintNode(w io.Writer, n *models.Node, jsonOut bool) error {
} }
if tags := getDisplayTags(n); len(tags) > 0 { if tags := getDisplayTags(n); len(tags) > 0 {
fmt.Fprintf(w, "\n tags: %s\n\n", cPrimary.Sprint(strings.Join(tags, " • "))) fmt.Fprintf(w, "\n tags: %s\n", cPrimary.Sprint(strings.Join(tags, " • ")))
} }
if db, err := db.GetDB(); err == nil { if db, err := db.GetDB(); err == nil {
if len(n.Relations) > 0 { if len(n.Relations) > 0 {
for relType, ids := range n.Relations { for relType, ids := range n.Relations {
fmt.Fprintf(w, " %s\n", string(relType)) if relIcon, ok := relIcons[string(relType)]; ok {
if relIcon, ok := relIcons[string(relType)]; ok && relType != "created" { fmt.Fprintf(w, "\n %s\n", string(relType))
for _, id := range ids { for _, id := range ids {
node, err := db.NodeByID(id) node, err := db.NodeByID(id)
if err == nil { if err == nil {