refactor: add tag and relation methods to node to enforce integrity
This commit is contained in:
@@ -5,9 +5,11 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -27,6 +29,16 @@ var migrations = []string{
|
||||
`CREATE INDEX IF NOT EXISTS idx_tags_tag ON tags(tag)`, `CREATE INDEX IF NOT EXISTS idx_rels_from ON rels(from_id)`, `CREATE INDEX IF NOT EXISTS idx_rels_to ON rels(to_id)`,
|
||||
}
|
||||
|
||||
var mentionRegex = regexp.MustCompile(`@([a-z0-9_]+)`)
|
||||
|
||||
func mentions(t string) []string {
|
||||
seen := make(map[string]bool)
|
||||
for _, m := range mentionRegex.FindAllStringSubmatch(t, -1) {
|
||||
seen[m[1]] = true
|
||||
}
|
||||
return slices.Collect(maps.Keys(seen))
|
||||
}
|
||||
|
||||
func InitSqliteDB(path string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
@@ -67,7 +79,7 @@ func GetSqliteDB(cfg Config) (*sql.DB, error) {
|
||||
}
|
||||
|
||||
func (s *sqliteNodeService) GetByID(id string) (*models.Node, error) {
|
||||
n := &models.Node{Relations: make(map[string][]string)}
|
||||
n := models.NewNode()
|
||||
q := s.db.QueryRow("SELECT id, title, COALESCE(content, ''), COALESCE(due_date, ''), created_at, updated_at FROM nodes WHERE id = ?", id)
|
||||
if err := q.Scan(&n.ID, &n.Title, &n.Content, &n.DueDate, &n.CreatedAt, &n.UpdatedAt); err != nil {
|
||||
return nil, err
|
||||
@@ -78,7 +90,7 @@ func (s *sqliteNodeService) GetByID(id string) (*models.Node, error) {
|
||||
for rows.Next() {
|
||||
var tag string
|
||||
rows.Scan(&tag)
|
||||
n.Tags = append(n.Tags, tag)
|
||||
n.AddTag(tag)
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
@@ -89,7 +101,7 @@ func (s *sqliteNodeService) GetByID(id string) (*models.Node, error) {
|
||||
for rows.Next() {
|
||||
var toID, relType string
|
||||
rows.Scan(&toID, &relType)
|
||||
n.Relations[relType] = append(n.Relations[relType], toID)
|
||||
n.AddRelation(models.RelType(relType), toID)
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
@@ -299,27 +311,31 @@ func (s *sqliteNodeService) Update(node *models.Node) error {
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range current.Tags {
|
||||
if !slices.Contains(node.Tags, t) {
|
||||
currentTags := current.Tags()
|
||||
nodeTags := node.Tags()
|
||||
for _, t := range currentTags {
|
||||
if !slices.Contains(nodeTags, t) {
|
||||
tx.Exec("DELETE FROM tags WHERE node_id = ? AND tag = ?", node.ID, t)
|
||||
}
|
||||
}
|
||||
for _, t := range node.Tags {
|
||||
if !slices.Contains(current.Tags, t) {
|
||||
for _, t := range nodeTags {
|
||||
if !slices.Contains(currentTags, t) {
|
||||
tx.Exec("INSERT OR IGNORE INTO tags (node_id, tag) VALUES (?, ?)", node.ID, t)
|
||||
}
|
||||
}
|
||||
|
||||
for rt, tgts := range current.Relations {
|
||||
currentRels := current.Relations()
|
||||
nodeRels := node.Relations()
|
||||
for rt, tgts := range currentRels {
|
||||
for _, tgt := range tgts {
|
||||
if node.Relations[rt] == nil || !slices.Contains(node.Relations[rt], tgt) {
|
||||
if nodeRels[rt] == nil || !slices.Contains(nodeRels[rt], tgt) {
|
||||
tx.Exec("DELETE FROM rels WHERE from_id = ? AND to_id = ? AND rel_type = ?", node.ID, tgt, rt)
|
||||
}
|
||||
}
|
||||
}
|
||||
for rt, tgts := range node.Relations {
|
||||
for rt, tgts := range nodeRels {
|
||||
for _, tgt := range tgts {
|
||||
if current.Relations[rt] == nil || !slices.Contains(current.Relations[rt], tgt) {
|
||||
if currentRels[rt] == nil || !slices.Contains(currentRels[rt], tgt) {
|
||||
resolvedTgt := tgt
|
||||
if models.RelType(rt) == models.RelAssignee || models.RelType(rt) == models.RelCreated {
|
||||
var err error
|
||||
@@ -402,31 +418,6 @@ func (s *sqliteNodeService) List(opts ...ListOption) ([]*models.Node, error) {
|
||||
havingConds = append(havingConds, "SUM(CASE WHEN "+cond[:len(cond)-4]+" THEN 1 ELSE 0 END) >= ?")
|
||||
havingArgs = append(havingArgs, len(f.tagPrefixes))
|
||||
|
||||
if f.assignee != "" {
|
||||
userID, err := s.resolveUserIDByName(f.assignee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if userID == "" {
|
||||
return []*models.Node{}, nil
|
||||
}
|
||||
joins = append(joins, "JOIN rels r_assign ON n.id = r_assign.from_id")
|
||||
whereConds = append(whereConds, "r_assign.to_id = ? AND r_assign.rel_type = ?")
|
||||
whereArgs = append(whereArgs, userID, models.RelAssignee)
|
||||
}
|
||||
if f.mentionsUser != "" {
|
||||
userID, err := s.resolveUserIDByName(f.mentionsUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if userID == "" {
|
||||
return []*models.Node{}, nil
|
||||
}
|
||||
joins = append(joins, "JOIN rels r_mentions ON n.id = r_mentions.from_id")
|
||||
whereConds = append(whereConds, "r_mentions.to_id = ? AND r_mentions.rel_type = ?")
|
||||
whereArgs = append(whereArgs, userID, models.RelMentions)
|
||||
}
|
||||
|
||||
if len(joins) > 0 {
|
||||
q += " " + strings.Join(joins, " ") + " "
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user