227 lines
5.8 KiB
Go
227 lines
5.8 KiB
Go
package plugins
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"verstak/internal/core/nodes"
|
|
|
|
lua "github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
// nodeToTable converts a Node to a Lua table.
|
|
func nodeToTable(L *lua.LState, n nodeOrErr) *lua.LTable {
|
|
tbl := L.NewTable()
|
|
if n.err != nil {
|
|
return tbl
|
|
}
|
|
tbl.RawSetString("id", lua.LString(n.node.ID))
|
|
tbl.RawSetString("title", lua.LString(n.node.Title))
|
|
tbl.RawSetString("type", lua.LString(n.node.Type))
|
|
tbl.RawSetString("slug", lua.LString(n.node.Slug))
|
|
tbl.RawSetString("sort_order", lua.LNumber(n.node.SortOrder))
|
|
tbl.RawSetString("archived", lua.LBool(n.node.Archived))
|
|
tbl.RawSetString("revision", lua.LNumber(n.node.Revision))
|
|
tbl.RawSetString("created_at", lua.LString(n.node.CreatedAt.Format(timeFormat)))
|
|
tbl.RawSetString("updated_at", lua.LString(n.node.UpdatedAt.Format(timeFormat)))
|
|
if n.node.ParentID != nil {
|
|
tbl.RawSetString("parent_id", lua.LString(*n.node.ParentID))
|
|
}
|
|
if n.node.DeletedAt != nil {
|
|
tbl.RawSetString("deleted_at", lua.LString(n.node.DeletedAt.Format(timeFormat)))
|
|
}
|
|
return tbl
|
|
}
|
|
|
|
// registerNodeAPI registers the verstak.node.* API and returns the table.
|
|
func registerNodeAPI(vm *LuaVM) *lua.LTable {
|
|
svc := vm.Services
|
|
if svc == nil || svc.NodeRepo == nil {
|
|
// No services available — return empty table but still register it
|
|
emptyTbl := vm.L.NewTable()
|
|
vm.L.SetGlobal("verstak_node", emptyTbl)
|
|
return emptyTbl
|
|
}
|
|
L := vm.L
|
|
tbl := L.NewTable()
|
|
|
|
// verstak.node.get(id) → table or nil
|
|
tbl.RawSetString("get", L.NewFunction(func(L *lua.LState) int {
|
|
id := L.CheckString(1)
|
|
n, err := svc.NodeRepo.GetActive(id)
|
|
if err != nil {
|
|
return pushError(L, err)
|
|
}
|
|
tbl := nodeToTable(L, nodeOrErr{node: n})
|
|
L.Push(tbl)
|
|
return 1
|
|
}))
|
|
|
|
// verstak.node.list(parent_id) → array of tables
|
|
tbl.RawSetString("list", L.NewFunction(func(L *lua.LState) int {
|
|
parentID := L.CheckString(1)
|
|
children, err := svc.NodeRepo.ListChildren(parentID, false)
|
|
if err != nil {
|
|
return pushError(L, err)
|
|
}
|
|
arr := L.NewTable()
|
|
for i, n := range children {
|
|
tbl := nodeToTable(L, nodeOrErr{node: &n})
|
|
arr.RawSetInt(i+1, tbl)
|
|
}
|
|
L.Push(arr)
|
|
return 1
|
|
}))
|
|
|
|
// verstak.node.create(parent_id, title, type) → table
|
|
tbl.RawSetString("create", L.NewFunction(func(L *lua.LState) int {
|
|
parentID := lua.LNil
|
|
if L.GetTop() >= 1 && L.Get(1) != lua.LNil {
|
|
parentID = L.Get(1)
|
|
}
|
|
title := L.CheckString(2)
|
|
typ := L.OptString(3, "document")
|
|
_ = L.OptString(4, "") // props (reserved)
|
|
|
|
var pID *string
|
|
if parentID != lua.LNil {
|
|
s := lua.LVAsString(parentID)
|
|
if s != "" {
|
|
pID = &s
|
|
}
|
|
}
|
|
n, err := svc.NodeRepo.Create(pID, typ, title, 0, "", "")
|
|
if err != nil {
|
|
return pushError(L, err)
|
|
}
|
|
tbl := nodeToTable(L, nodeOrErr{node: n})
|
|
L.Push(tbl)
|
|
return 1
|
|
}))
|
|
|
|
// verstak.node.update(id, fields) → success/error
|
|
tbl.RawSetString("update", L.NewFunction(func(L *lua.LState) int {
|
|
id := L.CheckString(1)
|
|
fields := L.CheckTable(2)
|
|
|
|
titleVal := fields.RawGetString("title")
|
|
if titleVal != lua.LNil {
|
|
if err := svc.NodeRepo.UpdateTitle(id, lua.LVAsString(titleVal)); err != nil {
|
|
return pushError(L, err)
|
|
}
|
|
}
|
|
L.Push(lua.LBool(true))
|
|
return 1
|
|
}))
|
|
|
|
// verstak.node.delete(id) → success/error
|
|
tbl.RawSetString("delete", L.NewFunction(func(L *lua.LState) int {
|
|
id := L.CheckString(1)
|
|
if err := svc.NodeRepo.SoftDelete(id); err != nil {
|
|
return pushError(L, err)
|
|
}
|
|
L.Push(lua.LBool(true))
|
|
return 1
|
|
}))
|
|
|
|
// verstak.node.search(query) → array of tables
|
|
tbl.RawSetString("search", L.NewFunction(func(L *lua.LState) int {
|
|
query := L.CheckString(1)
|
|
limit := L.OptInt(2, 20)
|
|
results, err := svc.NodeRepo.Search(query, limit)
|
|
if err != nil {
|
|
return pushError(L, err)
|
|
}
|
|
arr := L.NewTable()
|
|
for i, n := range results {
|
|
tbl := nodeToTable(L, nodeOrErr{node: &n})
|
|
arr.RawSetInt(i+1, tbl)
|
|
}
|
|
L.Push(arr)
|
|
return 1
|
|
}))
|
|
|
|
// verstak.node.roots() → array of root-level nodes
|
|
tbl.RawSetString("roots", L.NewFunction(func(L *lua.LState) int {
|
|
roots, err := svc.NodeRepo.ListRoots(false)
|
|
if err != nil {
|
|
return pushError(L, err)
|
|
}
|
|
arr := L.NewTable()
|
|
for i, n := range roots {
|
|
tbl := nodeToTable(L, nodeOrErr{node: &n})
|
|
arr.RawSetInt(i+1, tbl)
|
|
}
|
|
L.Push(arr)
|
|
return 1
|
|
}))
|
|
|
|
// verstak.node.meta — sub-table
|
|
metaTbl := L.NewTable()
|
|
metaTbl.RawSetString("get", L.NewFunction(func(L *lua.LState) int {
|
|
nodeID := L.CheckString(1)
|
|
key := L.CheckString(2)
|
|
v, ok, err := svc.NodeRepo.MetaGet(nodeID, key)
|
|
if err != nil {
|
|
return pushError(L, err)
|
|
}
|
|
if !ok {
|
|
L.Push(lua.LNil)
|
|
} else {
|
|
L.Push(lua.LString(v))
|
|
}
|
|
return 1
|
|
}))
|
|
metaTbl.RawSetString("set", L.NewFunction(func(L *lua.LState) int {
|
|
nodeID := L.CheckString(1)
|
|
key := L.CheckString(2)
|
|
value := L.CheckString(3)
|
|
if err := svc.NodeRepo.MetaSet(nodeID, key, value); err != nil {
|
|
return pushError(L, err)
|
|
}
|
|
L.Push(lua.LBool(true))
|
|
return 1
|
|
}))
|
|
metaTbl.RawSetString("list", L.NewFunction(func(L *lua.LState) int {
|
|
nodeID := L.CheckString(1)
|
|
metas, err := svc.NodeRepo.MetaList(nodeID)
|
|
if err != nil {
|
|
return pushError(L, err)
|
|
}
|
|
arr := L.NewTable()
|
|
for i, m := range metas {
|
|
entry := L.NewTable()
|
|
entry.RawSetString("key", lua.LString(m.Key))
|
|
entry.RawSetString("value", lua.LString(m.Value))
|
|
arr.RawSetInt(i+1, entry)
|
|
}
|
|
L.Push(arr)
|
|
return 1
|
|
}))
|
|
tbl.RawSetString("meta", metaTbl)
|
|
|
|
// Set the global
|
|
L.SetGlobal("verstak_node", tbl)
|
|
|
|
// Also add to the main verstak table if it exists
|
|
mainTbl := L.GetGlobal("verstak")
|
|
if mainTbl != lua.LNil {
|
|
if tbl2, ok := mainTbl.(*lua.LTable); ok {
|
|
tbl2.RawSetString("node", tbl)
|
|
}
|
|
}
|
|
|
|
return tbl
|
|
}
|
|
|
|
// nodeOrErr is a helper to keep node + potential error together.
|
|
type nodeOrErr struct {
|
|
node *nodes.Node
|
|
err error
|
|
}
|
|
|
|
// timeFormat for Lua output.
|
|
const timeFormat = "2006-01-02T15:04:05Z07:00"
|
|
|
|
// This function is used in the text below — comment to satisfy unused import.
|
|
var _ = fmt.Sprintf
|