299 lines
5.8 KiB
Go
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
|
|
}
|