Notes2/node.go
2024-12-18 19:12:10 +01:00

299 lines
5.8 KiB
Go

package main
import (
// External
"github.com/jmoiron/sqlx"
// Standard
"database/sql"
"time"
)
type ChecklistItem struct {
ID int
GroupID int `db:"checklist_group_id"`
Order int
Label string
Checked bool
}
type ChecklistGroup struct {
ID int
NodeID int `db:"node_id"`
Order int
Label string
Items []ChecklistItem
}
type TreeNode struct {
UUID string
ParentUUID string `db:"parent_uuid"`
Name string
Created time.Time
Updated time.Time
Deleted bool
CreatedSeq uint64 `db:"created_seq"`
UpdatedSeq uint64 `db:"updated_seq"`
DeletedSeq sql.NullInt64 `db:"deleted_seq"`
}
type Node struct {
UUID string
UserID int `db:"user_id"`
ParentUUID string `db:"parent_uuid"`
Name string
Created time.Time
Updated time.Time
Deleted bool
CreatedSeq uint64 `db:"created_seq"`
UpdatedSeq uint64 `db:"updated_seq"`
DeletedSeq sql.NullInt64 `db:"deleted_seq"`
Content string
ContentEncrypted string `db:"content_encrypted" json:"-"`
Markdown bool
// CryptoKeyID int `db:"crypto_key_id"`
//Files []File
//ChecklistGroups []ChecklistGroup
}
func NodeTree(userID, offset int, synced uint64) (nodes []TreeNode, maxSeq uint64, moreRowsExist bool, err error) { // {{{
const LIMIT = 100
var rows *sqlx.Rows
rows, err = db.Queryx(`
SELECT
uuid,
COALESCE(parent_uuid, '') AS parent_uuid,
name,
created,
updated,
deleted IS NOT NULL AS deleted,
created_seq,
updated_seq,
deleted_seq
FROM
public.node
WHERE
user_id = $1 AND
NOT history AND (
created_seq > $4 OR
updated_seq > $4 OR
deleted_seq > $4
)
ORDER BY
created ASC
LIMIT $2 OFFSET $3
`,
userID,
LIMIT+1,
offset,
synced,
)
if err != nil {
return
}
defer rows.Close()
nodes = []TreeNode{}
numNodes := 0
for rows.Next() {
// Query selects up to one more row than the decided limit.
// Saves one SQL query for row counting.
// Thus if numNodes is larger than the limit, more rows exist for the next call.
numNodes++
if numNodes > LIMIT {
moreRowsExist = true
return
}
node := TreeNode{}
if err = rows.StructScan(&node); err != nil {
return
}
nodes = append(nodes, node)
// DeletedSeq will be 0 if invalid, and thus not be a problem for the max function.
maxSeq = max(maxSeq, node.CreatedSeq, node.UpdatedSeq, uint64(node.DeletedSeq.Int64))
}
return
} // }}}
func Nodes(userID, offset int, synced uint64, clientUUID string) (nodes []Node, maxSeq uint64, moreRowsExist bool, err error) { // {{{
var rows *sqlx.Rows
rows, err = db.Queryx(`
SELECT
uuid,
COALESCE(parent_uuid, '') AS parent_uuid,
name,
created,
updated,
deleted IS NOT NULL AS deleted,
created_seq,
updated_seq,
deleted_seq,
content,
content_encrypted,
markdown
FROM
public.node
WHERE
user_id = $1 AND
client != $5 AND
NOT history AND (
created_seq > $4 OR
updated_seq > $4 OR
deleted_seq > $4
)
ORDER BY
id ASC
LIMIT $2 OFFSET $3
`,
userID,
SYNC_PAGINATION+1,
offset,
synced,
clientUUID,
)
if err != nil {
return
}
defer rows.Close()
nodes = []Node{}
numNodes := 0
for rows.Next() {
// Query selects up to one more row than the decided limit.
// Saves one SQL query for row counting.
// Thus if numNodes is larger than the limit, more rows exist for the next call.
numNodes++
if numNodes > SYNC_PAGINATION {
moreRowsExist = true
return
}
node := Node{}
if err = rows.StructScan(&node); err != nil {
return
}
nodes = append(nodes, node)
// DeletedSeq will be 0 if invalid, and thus not be a problem for the max function.
maxSeq = max(maxSeq, node.CreatedSeq, node.UpdatedSeq, uint64(node.DeletedSeq.Int64))
}
return
} // }}}
func RetrieveNode(userID int, nodeUUID string) (node Node, err error) { // {{{
var rows *sqlx.Row
rows = db.QueryRowx(`
SELECT
uuid,
user_id,
COALESCE(parent_uuid, '') AS parent_uuid,
/*COALESCE(crypto_key_id, 0) AS crypto_key_id,*/
name,
content,
content_encrypted,
markdown,
0 AS level
FROM node
WHERE
user_id = $1 AND
uuid = $2
`,
userID,
nodeUUID,
)
node = Node{}
if err = rows.StructScan(&node); err != nil {
return
}
return
} // }}}
func NodeCrumbs(nodeUUID string) (nodes []Node, err error) { // {{{
var rows *sqlx.Rows
rows, err = db.Queryx(`
WITH RECURSIVE nodes AS (
SELECT
uuid,
COALESCE(parent_uuid, '') AS parent_uuid,
name
FROM node
WHERE
uuid = $1
UNION
SELECT
n.uuid,
COALESCE(n.parent_uuid, 0) AS parent_uuid,
n.name
FROM node n
INNER JOIN nodes nr ON n.uuid = nr.parent_uuid
)
SELECT * FROM nodes
`, nodeUUID)
if err != nil {
return
}
defer rows.Close()
nodes = []Node{}
for rows.Next() {
node := Node{}
if err = rows.StructScan(&node); err != nil {
return
}
nodes = append(nodes, node)
}
return
} // }}}
func TestData() (err error) {
for range 10 {
hash1, name1, _ := generateOneTestNode("", "G")
for range 10 {
hash2, name2, _ := generateOneTestNode(hash1, name1)
for range 10 {
hash3, name3, _ := generateOneTestNode(hash2, name2)
for range 10 {
generateOneTestNode(hash3, name3)
}
}
}
}
return
}
func generateOneTestNode(parentUUID, parentPath string) (hash, name string, err error) {
var sqlParentUUID sql.NullString
if parentUUID != "" {
sqlParentUUID.String = parentUUID
sqlParentUUID.Valid = true
}
query := `
INSERT INTO node(user_id, parent_uuid, name)
VALUES(
1,
$1,
CONCAT(
$2::text,
'-',
LPAD(nextval('test_data')::text, 4, '0')
)
)
RETURNING uuid, name`
var row *sql.Row
row = db.QueryRow(query, sqlParentUUID, parentPath)
err = row.Scan(&hash, &name)
if err != nil {
return
}
return
}