mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 17:52:24 +00:00
Compare commits
40 Commits
4a58845211
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d4413c433f | |||
| d9a4bb7871 | |||
| 8e017af3ed | |||
| ef6023330d | |||
| 5474b22fc8 | |||
| 6cd678d9f1 | |||
| 856d3b418c | |||
| 5734ca7a67 | |||
| 608c5aed4a | |||
|
|
d4d04115f3 | ||
|
|
4b916f4fc9 | ||
| 54cc496c39 | |||
| f7b0014a37 | |||
| 54eb5eec6a | |||
| 6cc24a1e7f | |||
| ea41c435dd | |||
| d24e1a94ae | |||
| 846dc06601 | |||
| 740fbbff78 | |||
| 40be3c8d09 | |||
| 9c140abc6d | |||
| e90233aec4 | |||
| df1ef57769 | |||
| 4c840c40bb | |||
| 57f35e8f33 | |||
| f0c591f325 | |||
| 36ee320c45 | |||
| ee6fd205d5 | |||
| bed0471cc4 | |||
| e3812a18a6 | |||
| b7d939d5d7 | |||
| c737e80b8f | |||
| 5783a756c3 | |||
| ba47ee4219 | |||
| 5d49e0afc7 | |||
| 76fed578ff | |||
| 975c52b58e | |||
| 4e75d48f1d | |||
| 65af07fffa | |||
| 1252634420 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -12,6 +12,8 @@ com/_config.lua
|
||||
Taskfile.yml
|
||||
config.yaml
|
||||
|
||||
wiki
|
||||
|
||||
# Garbage
|
||||
com/_Access/GetMasterAccess.lua
|
||||
com/_Zones/GetZoneInfo.lua
|
||||
com/_*
|
||||
com/test.lua
|
||||
49
Makefile
49
Makefile
@@ -4,7 +4,10 @@ GOPATH := $(shell go env GOPATH)
|
||||
export CONFIG_PATH := ./config.yaml
|
||||
export NODE_PATH := $(shell pwd)
|
||||
|
||||
LDFLAGS := -X 'github.com/akyaiy/GoSally-mvp/internal/engine/config.NodeVersion=v0.0.1-dev'
|
||||
NODE_VERSION := v0.0.1-dev
|
||||
SV1_VERSION := v0.0.1-dev
|
||||
|
||||
LDFLAGS := -X 'github.com/akyaiy/GoSally-mvp/src/internal/engine/config.NodeVersion=$(NODE_VERSION)' -X 'github.com/akyaiy/GoSally-mvp/src/internal/server/sv1.SV1Version=$(SV1_VERSION)'
|
||||
CGO_CFLAGS := -I/usr/local/include
|
||||
CGO_LDFLAGS := -L/usr/local/lib -llua5.1 -lm -ldl
|
||||
.PHONY: all build run runq test fmt vet lint check clean
|
||||
@@ -29,15 +32,15 @@ build:
|
||||
@echo "Building..."
|
||||
@# @echo "CGO_CFLAGS is: '$(CGO_CFLAGS)'"
|
||||
@# @echo "CGO_LDFLAGS is: '$(CGO_LDFLAGS)'"
|
||||
@# CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)"
|
||||
@go build -trimpath -ldflags "-w -s $(LDFLAGS)" -o $(BIN_DIR)/$(APP_NAME) ./
|
||||
@if ! command -v upx >/dev/null 2>&1; then \
|
||||
echo "upx not found, skipping compression."; \
|
||||
elif upx -t $(BIN_DIR)/$(APP_NAME) >/dev/null 2>&1; then \
|
||||
echo "$(BIN_DIR)/$(APP_NAME) already compressed, skipping."; \
|
||||
else \
|
||||
upx $(BIN_DIR)/$(APP_NAME) >/dev/null 2>&1 || true; \
|
||||
fi
|
||||
@# CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)"
|
||||
cd src && go build -trimpath -ldflags "-w -s $(LDFLAGS)" -o ../$(BIN_DIR)/$(APP_NAME) ./
|
||||
# @if ! command -v upx >/dev/null 2>&1; then \
|
||||
# echo "upx not found, skipping compression."; \
|
||||
# elif upx -t $(BIN_DIR)/$(APP_NAME) >/dev/null 2>&1; then \
|
||||
# echo "$(BIN_DIR)/$(APP_NAME) already compressed, skipping."; \
|
||||
# else \
|
||||
# upx $(BIN_DIR)/$(APP_NAME) >/dev/null 2>&1 || true; \
|
||||
# fi
|
||||
|
||||
run: build
|
||||
@echo "Running!"
|
||||
@@ -52,30 +55,22 @@ pure-run:
|
||||
exec ./$(BIN_DIR)/$(APP_NAME)
|
||||
|
||||
test:
|
||||
@go test ./... | grep -v '^?' || true
|
||||
@cd src && go test ./... | grep -v '^?' || true
|
||||
|
||||
fmt:
|
||||
@go fmt ./internal/./...
|
||||
@go fmt ./cmd/./...
|
||||
@go fmt ./hooks/./...
|
||||
@$(GOPATH)/bin/goimports -w ./internal/
|
||||
@$(GOPATH)/bin/goimports -w ./cmd/
|
||||
@$(GOPATH)/bin/goimports -w ./hooks/
|
||||
@cd src && go fmt .
|
||||
@cd src && $(GOPATH)/bin/goimports -w .
|
||||
|
||||
vet:
|
||||
@go vet ./...
|
||||
|
||||
lint:
|
||||
@$(GOPATH)/bin/golangci-lint run
|
||||
|
||||
@cd src && go vet ./...
|
||||
check: fmt vet lint test
|
||||
|
||||
licenses:
|
||||
lint:
|
||||
@cd src && $(GOPATH)/bin/golangci-lint run ./...
|
||||
@$(GOPATH)/bin/go-licenses save ./... --save_path=third_party/licenses --force
|
||||
@echo "Licenses have been exported to third_party/licenses"
|
||||
|
||||
clean:
|
||||
@rm -rf bin
|
||||
|
||||
licenses:
|
||||
@cd src && $(GOPATH)/bin/go-licenses save ./... --save_path=../third_party/licenses --force
|
||||
@echo "Licenses have been exported to third_party/licenses"
|
||||
help:
|
||||
@echo "Available commands: $$(cat Makefile | grep -E '^[a-zA-Z_-]+:.*?' | grep -v -- '-setup:' | sed 's/:.*//g' | sort | uniq | tr '\n' ' ')"
|
||||
|
||||
147
README.md
147
README.md
@@ -1,51 +1,132 @@
|
||||
# Go Sally MVP (Minimum/Minimal Viable Product)
|
||||
[]()
|
||||
[](https://go.dev/)
|
||||
[](https://www.lua.org/manual/5.1/)
|
||||
[](https://pkg.go.dev/github.com/akyaiy/GoSally-mvp)
|
||||
[](LICENSE)
|
||||
|
||||
### What is this?
|
||||
System that allows you to build your own infrastructure based on identical nodes and various scripts written using built-in Lua 5.1, shebang scripts (scripts that start with the `#!` symbols), compiled binaries.
|
||||
[]()
|
||||
[]()
|
||||
[](https://github.com/akyaiy/GoSally-mvp/wiki)
|
||||
|
||||
### Features
|
||||
Go Sally is not viable at the moment, but it already has the ability to run embedded scripts, log slog events to stdout, handle RPC like requests, and independent automatic update from the repository (my pride, to be honest).
|
||||
|
||||
### Example of use
|
||||
The basic directory tree looks something like this
|
||||
```
|
||||
.
|
||||
├── bin
|
||||
│ └── node Node core binary file
|
||||
├── com
|
||||
│ ├── echo.lua
|
||||
│ ├── _globals.lua Declaring global variables and functions for all internal scripts (also required for luarc to work correctly)
|
||||
│ └── _prepare.lua Script that is executed before each script launch
|
||||
└── config.yaml
|
||||
> ⚡ **What, Why, Why Care?**
|
||||
|
||||
3 directories, 5 files
|
||||
> **What:** Go Sally is a lightweight decentralized node system with Lua scripting and JSON-RPC2.0.
|
||||
|
||||
```
|
||||
Launch by command
|
||||
> **Why:** Large admin tools are too heavy, and Raspberry Pi and small servers require a lightweight, modular architecture.
|
||||
|
||||
> **Why Care:** Create, automate, and expand your infrastructure quickly, without unnecessary software or dependencies.
|
||||
|
||||
## Navigation
|
||||
* [Core features](#core-features)
|
||||
* [Quick start](#quick-start)
|
||||
* [Test it](#test-it)
|
||||
* [Concept](#concept)
|
||||
* [API](#api)
|
||||
* [License](#license)
|
||||
* [Wiki →](https://github.com/akyaiy/GoSally-mvp/wiki)
|
||||
|
||||
> [!NOTE]
|
||||
> If you see "💡" in the text, it means the information below is about plans for the future of the project.
|
||||
|
||||
## Core features
|
||||
- **Decentralized nodes**<details>this means that *multiple GS[^1] nodes can be located on a single machine*, provided no attempt is made to disrupt, sabotage, or bypass the built-in protection mechanism against running a node under the same identifier as one already running in the system. Identification plays a role in node communication. 💡 In the future, we plan to create tools for conveniently building distributed systems using node identification.
|
||||
**Why Care?** Multiple nodes on one machine allow testing, experimentation, and scaling small infrastructures without extra hardware or complex setup.</details>
|
||||
- **RPC request processing**<details>the GS operates *using HTTP/https and the JSONRPC2.0 protocol.* Unlike gRPC, jsonrpc is extremely simple, allows for easy sending of requests from the browser, and does not require any additional code compilation. **Why Care?** Easy-to-use API means you can control nodes from anywhere, including lightweight web clients, without compiling extra code.</details>
|
||||
- **Lua script-based methods**<details>*The gopher-lua library is used, providing full support for Lua 5.1.* scripts implement libraries for interacting with sessions (receiving parameters and sending responses), hashing, logging, and more. This allows you to quickly write business logic on the fly without touching the lower layers of abstraction, which also eliminates unnecessary compilation and the risk of breaking the codebase.
|
||||
Example of the "echo" method:
|
||||
```lua
|
||||
local session = require("internal.session")
|
||||
-- import the internal library for interacting with sessions
|
||||
|
||||
session.response.send(session.request.params.get())
|
||||
-- send everything passed in the parameters in response.
|
||||
```
|
||||
**Why Care?** You can extend node behavior dynamically, write custom logic fast, and iterate without recompiling — perfect for experiments or automation.
|
||||
</details>
|
||||
- **Relatively flexible configuration** <details>
|
||||
you can configure the server port, address, name, node settings, and more. 💡 More settings are planned in the future. **Why Care?** Configure nodes for any environment, from Raspberry Pi to VPS, without touching the source code. obviously :)</details>
|
||||
- ***And more in the future***
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This is the beginning of the project's development, and some aspects of it may be unstable, unfinished, and the text about it may be overly ambitious. It's just a matter of time.
|
||||
|
||||
## Quick start
|
||||
```bash
|
||||
git clone https://github.com/akyaiy/GoSally-mvp.git && \
|
||||
cd GoSally-mvp && \
|
||||
make build && \
|
||||
echo -e "node:\n com_dir: \"%path%/com\"" > config.yaml && \
|
||||
mkdir -p com && \
|
||||
echo -e 'local session = require("internal.session")\n\nsession.response.send(session.request.params.get())' > com/echo.lua && \
|
||||
./bin/node run
|
||||
```
|
||||
or for structured logs
|
||||
```bash
|
||||
./bin/node run | jq
|
||||
```
|
||||
|
||||
Example of GET request to server
|
||||
If you have problems, make sure you have all [dependencies](https://github.com/akyaiy/GoSally-mvp/wiki/Getting-started#installing-dependencies) installed, otherwise [file an issue report](https://github.com/akyaiy/GoSally-mvp/issues)
|
||||
|
||||
### Test it
|
||||
```bash
|
||||
curl -s http://localhost:8080/api/v1/com/echo?msg=Hello
|
||||
curl -X POST http://localhost:8080/com \
|
||||
-d '{"jsonrpc":"2.0","context-version": "v1","method":"echo","params":["Hi!!"],"id":1}'
|
||||
```
|
||||
Then the response from the server
|
||||
Expected response:
|
||||
```json
|
||||
{
|
||||
"ResponsibleAgentUUID": "4593a87000bbe088f4e79c477e9c90d3",
|
||||
"RequestedCommand": "echo",
|
||||
"Response": {
|
||||
"answer": "Hello",
|
||||
"status": "ok"
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": [
|
||||
"Hi!!"
|
||||
],
|
||||
"data": {
|
||||
"responsible-node": "a0e1c440473ffd4d87e32cff2717f5b3",
|
||||
"salt": "f26df732-a3be-4400-8e71-b8dc3ba705fc",
|
||||
"checksum-md5": "cd8bec6a365d1b8ee90773567cb3ad0a"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### How to install
|
||||
**You don't need it now, but you can figure it out with the Makefile**
|
||||
## Concept
|
||||
The project was originally conceived as a tool for building infrastructure using relatively *small nodes with limited functionality*. 💡 In the future, we plan to create a *web interface for interacting with nodes, administration, and configuration*. The concept is simple: suppose we have a node that manages Bind9. It has all the necessary methods for interacting with the service: creating new zones, viewing zone status, changing configuration, and server operation status. All of this works only through manual configuration, with the exception of larger solutions like Webmin and the BIND DNS Server module. The big problem is that while we only needed web configuration for Bind9, we have to pull in a massive amount of software just to implement one module. What if the service is hosted on a low-power Raspberry Pi? That's where GS nodes come in. By default, GS nodes communicate only through API calls, so 💡 in the future, we plan to create a dedicated, also programmable, web node that will provide convenient access to node management.
|
||||
|
||||
There's an obvious advantage here: transparency. The project is *completely open source and aims to support community-driven node functionality*. 💡 In the future, we plan to create a "store" similar to Docker Hub, which will contain scripts for configuring bind9, openvpn, and even custom projects.
|
||||
|
||||
## API
|
||||
As mentioned earlier, *the server handles [jsonrpc2.0](https://www.jsonrpc.org/specification) requests*
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"context-version": "v1",
|
||||
"method": "test",
|
||||
"params": [
|
||||
"Hi!!"
|
||||
],
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
This is a typical example of a request using the jsonrpc2.0 protocol.
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": [
|
||||
"Hi!!"
|
||||
],
|
||||
"data": {
|
||||
"responsible-node": "2ad6ebeaf579a7c52801fb6c9dd1b83d",
|
||||
"salt": "e7a81115-01c1-45b1-9618-0eae0ff26451",
|
||||
"checksum-md5": "cd8bec6a365d1b8ee90773567cb3ad0a"
|
||||
}
|
||||
}
|
||||
```
|
||||
In the result field, we see the echo method's response. Those familiar with the jsonrpc2.0 specification will notice that the data structure here is unclear. This is my extension, which has three functions:
|
||||
| Field | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `responsible-node` | string | ID of the node that executed the task |
|
||||
| `salt` | string | Random value for each request — can be used to check that the response is unique |
|
||||
| `checksum-md5` | string | MD5 hash of the result field — can be used to avoid processing identical results separately |
|
||||
|
||||
## License
|
||||
Distributed under the BSD 3-Clause License. See [`LICENSE`](./LICENSE) for more information.
|
||||
|
||||
[^1]: Go Sally
|
||||
|
||||
22
com/Access/_common.lua
Normal file
22
com/Access/_common.lua
Normal file
@@ -0,0 +1,22 @@
|
||||
-- File com/Access/_common.lua
|
||||
--
|
||||
-- Created at 2025-21-10
|
||||
--
|
||||
-- Description:
|
||||
--- Common functions for Unit module
|
||||
|
||||
local common = {}
|
||||
|
||||
function common.CheckMissingElement(arr, cmp)
|
||||
local is_missing = {}
|
||||
local ok = true
|
||||
for _, key in ipairs(arr) do
|
||||
if cmp[key] == nil then
|
||||
table.insert(is_missing, key)
|
||||
ok = false
|
||||
end
|
||||
end
|
||||
return ok, is_missing
|
||||
end
|
||||
|
||||
return common
|
||||
30
com/Access/_errors.lua
Normal file
30
com/Access/_errors.lua
Normal file
@@ -0,0 +1,30 @@
|
||||
-- File com/Access/_errors.lua
|
||||
--
|
||||
-- Created at 2025-21-10
|
||||
-- Description:
|
||||
--- Centralized error definitions for Access operations
|
||||
--- to keep API responses consistent and clean.
|
||||
|
||||
local errors = {
|
||||
-- Common validation
|
||||
MISSING_PARAMS = { code = -32602, message = "Missing params" },
|
||||
INVALID_FIELD_TYPE = { code = -32602, message = "'fields' must be a non-empty table" },
|
||||
INVALID_BY_PARAM = { code = -32602, message = "Invalid 'by' param" },
|
||||
NO_VALID_FIELDS = { code = -32604, message = "No valid fields to update" },
|
||||
|
||||
-- Existence / duplication
|
||||
UNIT_NOT_FOUND = { code = -32102, message = "Unit is not exists" },
|
||||
UNIT_EXISTS = { code = -32101, message = "Unit is already exists" },
|
||||
|
||||
-- Database & constraint
|
||||
UNIQUE_CONSTRAINT = { code = -32602, message = "Unique constraint failed" },
|
||||
DB_QUERY_FAILED = { code = -32001, message = "Database query failed" },
|
||||
DB_EXEC_FAILED = { code = -32002, message = "Database execution failed" },
|
||||
DB_INSERT_FAILED = { code = -32003, message = "Failed to create unit" },
|
||||
DB_DELETE_FAILED = { code = -32004, message = "Failed to delete unit" },
|
||||
|
||||
-- Generic fallback
|
||||
UNKNOWN = { code = -32099, message = "Unexpected internal error" },
|
||||
}
|
||||
|
||||
return errors
|
||||
@@ -15,6 +15,7 @@ local crypt = require("internal.crypt.bcrypt")
|
||||
local sha256 = require("internal.crypt.sha256")
|
||||
|
||||
local common = require("com/Unit/_common")
|
||||
local errors = require("com/Unit/_errors")
|
||||
|
||||
-- Preparing for first db query
|
||||
local function close_db()
|
||||
@@ -30,28 +31,12 @@ local params = session.request.params.get()
|
||||
local ok, mp = common.CheckMissingElement({"username", "password", "email"}, params)
|
||||
if not ok then
|
||||
close_db()
|
||||
session.response.send_error(-32602, "Missing params", mp)
|
||||
session.response.send_error(errors.MISSING_PARAMS.code, errors.MISSING_PARAMS.message, mp)
|
||||
end
|
||||
|
||||
local hashPass = crypt.generate(params.password, crypt.DefaultCost)
|
||||
local unitID = string.sub(sha256.hash(session.__seed), 1, 16)
|
||||
|
||||
-- First db query: check if username or email already exists
|
||||
local existing, err = db:query("SELECT 1 FROM units WHERE email = ? OR username = ? LIMIT 1", {
|
||||
params.email,
|
||||
params.username
|
||||
})
|
||||
|
||||
if err ~= nil then
|
||||
log.error("Email check failed: "..tostring(err))
|
||||
return session.response.send_error()
|
||||
end
|
||||
|
||||
if existing and #existing > 0 then
|
||||
return session.response.send_error(-32101, "Unit already exists")
|
||||
end
|
||||
|
||||
-- Second db query: insert new unit
|
||||
local ctx, err = db:exec(
|
||||
"INSERT INTO units (user_id, username, email, password) VALUES (?, ?, ?, ?)",
|
||||
{
|
||||
@@ -61,17 +46,23 @@ local ctx, err = db:exec(
|
||||
hashPass,
|
||||
}
|
||||
)
|
||||
|
||||
if err ~= nil then
|
||||
log.error("Insert failed: "..tostring(err))
|
||||
return session.response.send_error()
|
||||
close_db()
|
||||
session.response.send_error(errors.DB_INSERT_FAILED.code, errors.DB_INSERT_FAILED.message)
|
||||
end
|
||||
|
||||
local res, err = ctx:wait()
|
||||
local _, err = ctx:wait()
|
||||
if err ~= nil then
|
||||
log.error("Insert confirmation failed: "..tostring(err))
|
||||
return session.response.send_error()
|
||||
close_db()
|
||||
if tostring(err):match("UNIQUE constraint failed") then
|
||||
session.response.send_error(errors.UNIT_EXISTS.code, errors.UNIT_EXISTS.message)
|
||||
else
|
||||
log.error("Insert confirmation failed: "..tostring(err))
|
||||
session.response.send_error()
|
||||
end
|
||||
end
|
||||
|
||||
session.response.send({message = "Unit created successfully", unit_id = unitID})
|
||||
|
||||
close_db()
|
||||
close_db()
|
||||
session.response.send({unit_id = unitID})
|
||||
@@ -0,0 +1,77 @@
|
||||
-- File com/Unit/Delete.lua
|
||||
--
|
||||
-- Created at 2025-05-10 19:18
|
||||
--
|
||||
-- Updated at -
|
||||
|
||||
local log = require("internal.log")
|
||||
local db = require("internal.database.sqlite").connect("db/unit.db", {log = true})
|
||||
local session = require("internal.session")
|
||||
|
||||
local common = require("com/Unit/_common")
|
||||
local errors = require("com/Unit/_errors")
|
||||
|
||||
-- Preparing for first db query
|
||||
local function close_db()
|
||||
if db then
|
||||
log.debug("Closing DB connection")
|
||||
db:close()
|
||||
db = nil
|
||||
end
|
||||
end
|
||||
|
||||
local params = session.request.params.get()
|
||||
|
||||
local ok, mp = common.CheckMissingElement({"user_id"}, params)
|
||||
if not ok then
|
||||
close_db()
|
||||
session.response.send_error(errors.MISSING_PARAMS.code, errors.MISSING_PARAMS.message, mp)
|
||||
end
|
||||
|
||||
local existing, err = db:query([[
|
||||
SELECT 1
|
||||
FROM units
|
||||
WHERE user_id = ?
|
||||
AND entry_status != 'deleted'
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1
|
||||
]], {
|
||||
params.user_id
|
||||
})
|
||||
|
||||
if err ~= nil then
|
||||
log.error("Email check failed: "..tostring(err))
|
||||
close_db()
|
||||
session.response.send_error()
|
||||
end
|
||||
|
||||
if existing and #existing == 0 then
|
||||
close_db()
|
||||
session.response.send_error(errors.UNIT_NOT_FOUND.code, errors.UNIT_NOT_FOUND.message)
|
||||
end
|
||||
|
||||
local ctx, err = db:exec(
|
||||
[[
|
||||
UPDATE units
|
||||
SET entry_status = 'deleted',
|
||||
deleted_at = CURRENT_TIMESTAMP
|
||||
WHERE user_id = ? AND deleted_at is NULL
|
||||
]],
|
||||
{ params.user_id }
|
||||
)
|
||||
|
||||
if err ~= nil then
|
||||
log.error("Soft delete failed: " .. tostring(err))
|
||||
close_db()
|
||||
session.response.send_error(errors.DB_DELETE_FAILED.code, errors.DB_DELETE_FAILED.message)
|
||||
end
|
||||
|
||||
local res, err = ctx:wait()
|
||||
if err ~= nil then
|
||||
log.error("Soft delete confirmation failed: " .. tostring(err))
|
||||
close_db()
|
||||
session.response.send_error(errors.DB_DELETE_FAILED.code, errors.DB_DELETE_FAILED.message)
|
||||
end
|
||||
|
||||
close_db()
|
||||
session.response.send()
|
||||
@@ -0,0 +1,55 @@
|
||||
-- File com/Unit/Get.lua
|
||||
--
|
||||
-- Created at 2025-09-25 20:04
|
||||
--
|
||||
-- Updated at -
|
||||
|
||||
local log = require("internal.log")
|
||||
local db = require("internal.database.sqlite").connect("db/unit.db", {log = true})
|
||||
local session = require("internal.session")
|
||||
|
||||
local common = require("com/Unit/_common")
|
||||
local errors = require("com/Unit/_errors")
|
||||
|
||||
-- Preparing for first db query
|
||||
local function close_db()
|
||||
if db then
|
||||
log.debug("Closing DB connection")
|
||||
db:close()
|
||||
db = nil
|
||||
end
|
||||
end
|
||||
|
||||
local params = session.request.params.get()
|
||||
|
||||
local ok, mp = common.CheckMissingElement({"by", "value"}, params)
|
||||
if not ok then
|
||||
close_db()
|
||||
session.response.send_error(errors.MISSING_PARAMS.code, errors.MISSING_PARAMS.message, mp)
|
||||
end
|
||||
|
||||
if not (params.by == "email" or params.by == "username" or params.by == "user_id") then
|
||||
close_db()
|
||||
session.response.send_error(errors.INVALID_BY_PARAM.code, errors.INVALID_BY_PARAM.message)
|
||||
end
|
||||
|
||||
local unit, err = db:query_row(
|
||||
"SELECT user_id, username, email, created_at, updated_at, deleted_at, entry_status FROM units WHERE "..params.by.." = ? AND deleted_at IS NULL LIMIT 1",
|
||||
{
|
||||
params.value
|
||||
}
|
||||
)
|
||||
|
||||
if err then
|
||||
close_db()
|
||||
log.error("DB query error: " .. tostring(err))
|
||||
session.response.send_error()
|
||||
end
|
||||
|
||||
if not unit then
|
||||
close_db()
|
||||
session.response.send_error(errors.UNIT_NOT_FOUND.code, errors.UNIT_NOT_FOUND.message)
|
||||
end
|
||||
|
||||
close_db()
|
||||
session.response.send(unit)
|
||||
102
com/Unit/Update.lua
Normal file
102
com/Unit/Update.lua
Normal file
@@ -0,0 +1,102 @@
|
||||
-- File com/Unit/Update.lua
|
||||
--
|
||||
-- Created at 2025-10-10
|
||||
--
|
||||
|
||||
local log = require("internal.log")
|
||||
local db = require("internal.database.sqlite").connect("db/unit.db", { log = true })
|
||||
local session = require("internal.session")
|
||||
|
||||
local common = require("com/Unit/_common")
|
||||
local errors = require("com/Unit/_errors")
|
||||
|
||||
local function close_db()
|
||||
if db then
|
||||
log.debug("Closing DB connection")
|
||||
db:close()
|
||||
db = nil
|
||||
end
|
||||
end
|
||||
|
||||
local params = session.request.params.get()
|
||||
|
||||
local ok, mp = common.CheckMissingElement({"user_id", "fields"}, params)
|
||||
if not ok then
|
||||
close_db()
|
||||
session.response.send_error(errors.MISSING_PARAMS.code, errors.MISSING_PARAMS.message, mp)
|
||||
end
|
||||
|
||||
if type(params.fields) ~= "table" or next(params.fields) == nil then
|
||||
close_db()
|
||||
session.response.send_error(errors.INVALID_FIELD_TYPE.code, errors.INVALID_FIELD_TYPE.message)
|
||||
end
|
||||
|
||||
local allowed = {
|
||||
username = true,
|
||||
email = true,
|
||||
password = true,
|
||||
entry_status = true
|
||||
}
|
||||
|
||||
local exists = db:query_row(
|
||||
"SELECT 1 FROM units WHERE user_id = ? AND deleted_at IS NULL LIMIT 1",
|
||||
{ params.user_id }
|
||||
)
|
||||
|
||||
if not exists then
|
||||
close_db()
|
||||
session.response.send_error(errors.UNIT_NOT_FOUND.code, errors.UNIT_NOT_FOUND.message)
|
||||
end
|
||||
|
||||
local set_clauses = {}
|
||||
local values = {}
|
||||
|
||||
for k, v in pairs(params.fields) do
|
||||
if allowed[k] then
|
||||
if k == "password" then
|
||||
local crypt = require("internal.crypt.bcrypt")
|
||||
v = crypt.generate(v, crypt.DefaultCost)
|
||||
end
|
||||
table.insert(set_clauses, k .. " = ?")
|
||||
table.insert(values, v)
|
||||
else
|
||||
log.warn("Ignoring unsupported field: " .. k)
|
||||
end
|
||||
end
|
||||
|
||||
if #set_clauses == 0 then
|
||||
close_db()
|
||||
session.response.send_error(errors.NO_VALID_FIELDS.code, errors.NO_VALID_FIELDS.message)
|
||||
end
|
||||
|
||||
table.insert(set_clauses, "updated_at = CURRENT_TIMESTAMP")
|
||||
|
||||
local query = "UPDATE units SET " .. table.concat(set_clauses, ", ")
|
||||
.. " WHERE user_id = ? AND deleted_at IS NULL"
|
||||
|
||||
table.insert(values, params.user_id)
|
||||
|
||||
local ctx, err = db:exec(query, values)
|
||||
if not ctx then
|
||||
close_db()
|
||||
if tostring(err):match("UNIQUE constraint failed") then
|
||||
session.response.send_error(errors.UNIQUE_CONSTRAINT.code, errors.UNIQUE_CONSTRAINT.message)
|
||||
else
|
||||
session.response.send_error()
|
||||
end
|
||||
end
|
||||
|
||||
local _, err = ctx:wait()
|
||||
if err ~= nil then
|
||||
close_db()
|
||||
if tostring(err):match("UNIQUE constraint failed") then
|
||||
session.response.send_error(errors.UNIQUE_CONSTRAINT.code, errors.UNIQUE_CONSTRAINT.message)
|
||||
else
|
||||
log.error("Insert confirmation failed: "..tostring(err))
|
||||
session.response.send_error()
|
||||
end
|
||||
end
|
||||
|
||||
close_db()
|
||||
|
||||
session.response.send()
|
||||
30
com/Unit/_errors.lua
Normal file
30
com/Unit/_errors.lua
Normal file
@@ -0,0 +1,30 @@
|
||||
-- File com/Unit/_errors.lua
|
||||
--
|
||||
-- Created at 2025-10-10
|
||||
-- Description:
|
||||
--- Centralized error definitions for Unit operations
|
||||
--- to keep API responses consistent and clean.
|
||||
|
||||
local errors = {
|
||||
-- Common validation
|
||||
MISSING_PARAMS = { code = -32602, message = "Missing params" },
|
||||
INVALID_FIELD_TYPE = { code = -32602, message = "'fields' must be a non-empty table" },
|
||||
INVALID_BY_PARAM = { code = -32602, message = "Invalid 'by' param" },
|
||||
NO_VALID_FIELDS = { code = -32604, message = "No valid fields to update" },
|
||||
|
||||
-- Existence / duplication
|
||||
UNIT_NOT_FOUND = { code = -32102, message = "Unit is not exists" },
|
||||
UNIT_EXISTS = { code = -32101, message = "Unit is already exists" },
|
||||
|
||||
-- Database & constraint
|
||||
UNIQUE_CONSTRAINT = { code = -32602, message = "Unique constraint failed" },
|
||||
DB_QUERY_FAILED = { code = -32001, message = "Database query failed" },
|
||||
DB_EXEC_FAILED = { code = -32002, message = "Database execution failed" },
|
||||
DB_INSERT_FAILED = { code = -32003, message = "Failed to create unit" },
|
||||
DB_DELETE_FAILED = { code = -32004, message = "Failed to delete unit" },
|
||||
|
||||
-- Generic fallback
|
||||
UNKNOWN = { code = -32099, message = "Unexpected internal error" },
|
||||
}
|
||||
|
||||
return errors
|
||||
@@ -1,119 +0,0 @@
|
||||
-- com/DeleteUnit.lua
|
||||
|
||||
---@diagnostic disable: redefined-local
|
||||
local db = require("internal.database.sqlite").connect("db/user-database.db", {log = true})
|
||||
local log = require("internal.log")
|
||||
local session = require("internal.session")
|
||||
local crypt = require("internal.crypt.bcrypt")
|
||||
local jwt = require("internal.crypt.jwt")
|
||||
local sha256 = require("internal.crypt.sha256")
|
||||
|
||||
local params = session.request.params.get()
|
||||
local token = session.request.headers.get("authorization")
|
||||
|
||||
local function close_db()
|
||||
if db then
|
||||
db:close()
|
||||
db = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function error_response(message, code, data)
|
||||
session.response.error = {
|
||||
code = code or nil,
|
||||
message = message,
|
||||
data = data or nil
|
||||
}
|
||||
close_db()
|
||||
end
|
||||
|
||||
if not token or type(token) ~= "string" then
|
||||
return error_response("Access denied")
|
||||
end
|
||||
|
||||
local prefix = "Bearer "
|
||||
if token:sub(1, #prefix) ~= prefix then
|
||||
return error_response("Invalid Authorization scheme")
|
||||
end
|
||||
|
||||
local access_token = token:sub(#prefix + 1)
|
||||
|
||||
local err, data = jwt.decode(access_token, { secret = require("_config").token() })
|
||||
|
||||
if err or not data then
|
||||
session.response.error = {
|
||||
message = err
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
-- if data.session_uuid ~= session.id then
|
||||
-- return error_response("Access denied")
|
||||
-- end
|
||||
|
||||
-- if data.key ~= sha256.sum(session.request.address .. session.id .. session.request.headers.get("user-agent", "noagent")) then
|
||||
-- return error_response("Access denied")
|
||||
-- end
|
||||
|
||||
if not params then
|
||||
return error_response("no params provided")
|
||||
end
|
||||
|
||||
if not (params.username and params.email and params.password) then
|
||||
return error_response("no username/email/password provided")
|
||||
end
|
||||
|
||||
local existing, err = db:query(
|
||||
"SELECT password FROM users WHERE email = ? AND username = ? AND deleted = 0 LIMIT 1",
|
||||
{
|
||||
params.email,
|
||||
params.username
|
||||
}
|
||||
)
|
||||
|
||||
if err ~= nil then
|
||||
log.error("Password fetch failed: " .. tostring(err))
|
||||
return error_response("Database query failed: " .. tostring(err))
|
||||
end
|
||||
|
||||
if not existing or #existing == 0 then
|
||||
return error_response("Unit not found")
|
||||
end
|
||||
|
||||
local hashed_password = existing[1].password
|
||||
|
||||
local ok = crypt.compare(hashed_password, params.password)
|
||||
if not ok then
|
||||
log.warn("Wrong password attempt for: " .. params.username)
|
||||
return error_response("Invalid password")
|
||||
end
|
||||
|
||||
local ctx, err = db:exec(
|
||||
[[
|
||||
UPDATE users
|
||||
SET deleted = 1,
|
||||
deleted_at = CURRENT_TIMESTAMP
|
||||
WHERE email = ? AND username = ? AND deleted = 0
|
||||
]],
|
||||
{ params.email, params.username }
|
||||
)
|
||||
|
||||
if err ~= nil then
|
||||
log.error("Soft delete failed: " .. tostring(err))
|
||||
return error_response("Soft delete failed: " .. tostring(err))
|
||||
end
|
||||
|
||||
local res, err = ctx:wait()
|
||||
if err ~= nil then
|
||||
log.error("Soft delete confirmation failed: " .. tostring(err))
|
||||
return error_response("Soft delete confirmation failed: " .. tostring(err))
|
||||
end
|
||||
|
||||
session.response.result = {
|
||||
rows_affected = res,
|
||||
message = "Unit soft-deleted successfully"
|
||||
}
|
||||
|
||||
log.info("user " .. params.username .. " soft-deleted successfully")
|
||||
|
||||
close_db()
|
||||
@@ -1,76 +0,0 @@
|
||||
-- com/GetAccess
|
||||
|
||||
---@diagnostic disable: redefined-local
|
||||
local db = require("internal.database.sqlite").connect("db/user-database.db", {log = true})
|
||||
local log = require("internal.log")
|
||||
local session = require("internal.session")
|
||||
local crypt = require("internal.crypt.bcrypt")
|
||||
local jwt = require("internal.crypt.jwt")
|
||||
local sha256 = require("internal.crypt.sha256")
|
||||
|
||||
local params = session.request.params.get()
|
||||
local secret = require("_config").token()
|
||||
|
||||
local function close_db()
|
||||
if db then
|
||||
db:close()
|
||||
db = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function error_response(message, code, data)
|
||||
session.response.error = {
|
||||
code = code or nil,
|
||||
message = message,
|
||||
data = data or nil
|
||||
}
|
||||
close_db()
|
||||
end
|
||||
|
||||
if not params then
|
||||
return error_response("No params provided")
|
||||
end
|
||||
|
||||
if not (params.username and params.email and params.password) then
|
||||
return error_response("Missing username, email or password")
|
||||
end
|
||||
|
||||
local unit, err = db:query(
|
||||
"SELECT id, username, email, password, created_at FROM users WHERE email = ? AND username = ? AND deleted = 0 LIMIT 1",
|
||||
{
|
||||
params.email,
|
||||
params.username
|
||||
}
|
||||
)
|
||||
|
||||
if err then
|
||||
log.error("DB query error: " .. tostring(err))
|
||||
return error_response("Database query failed")
|
||||
end
|
||||
|
||||
if not unit or #unit == 0 then
|
||||
return error_response("Unit not found")
|
||||
end
|
||||
|
||||
unit = unit[1]
|
||||
|
||||
local ok = crypt.compare(unit.password, params.password)
|
||||
if not ok then
|
||||
log.warn("Login failed: wrong password for " .. params.username)
|
||||
return error_response("Invalid password")
|
||||
end
|
||||
|
||||
local token = jwt.encode({
|
||||
secret = secret,
|
||||
payload = { session_uuid = session.id,
|
||||
admin_user = params.username,
|
||||
key = sha256.sum(session.request.address .. session.id .. session.request.headers.get("user-agent", "noagent"))
|
||||
},
|
||||
expires_in = 3600
|
||||
})
|
||||
|
||||
session.response.result = {
|
||||
access_token = token
|
||||
}
|
||||
|
||||
close_db()
|
||||
@@ -1,109 +0,0 @@
|
||||
-- com/PutNewUnit.lua
|
||||
|
||||
---@diagnostic disable: redefined-local
|
||||
local db = require("internal.database.sqlite").connect("db/user-database.db", {log = true})
|
||||
local log = require("internal.log")
|
||||
local session = require("internal.session")
|
||||
local crypt = require("internal.crypt.bcrypt")
|
||||
local jwt = require("internal.crypt.jwt")
|
||||
local sha256 = require("internal.crypt.sha256")
|
||||
|
||||
local params = session.request.params.get()
|
||||
local token = session.request.headers.get("authorization")
|
||||
|
||||
local function close_db()
|
||||
if db then
|
||||
db:close()
|
||||
db = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function error_response(message, code, data)
|
||||
session.response.error = {
|
||||
code = code or nil,
|
||||
message = message,
|
||||
data = data or nil
|
||||
}
|
||||
close_db()
|
||||
end
|
||||
|
||||
if not token or type(token) ~= "string" then
|
||||
return error_response("Access denied")
|
||||
end
|
||||
|
||||
local prefix = "Bearer "
|
||||
if token:sub(1, #prefix) ~= prefix then
|
||||
return error_response("Invalid Authorization scheme")
|
||||
end
|
||||
|
||||
local access_token = token:sub(#prefix + 1)
|
||||
|
||||
local err, data = jwt.decode(access_token, { secret = require("_config").token() })
|
||||
|
||||
if err or not data then
|
||||
session.response.error = {
|
||||
message = err
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
if data.session_uuid ~= session.id then
|
||||
return error_response("Access denied")
|
||||
end
|
||||
|
||||
if data.key ~= sha256.sum(session.request.address .. session.id .. session.request.headers.get("user-agent", "noagent")) then
|
||||
return error_response("Access denied")
|
||||
end
|
||||
|
||||
if not params then
|
||||
return error_response("no params provided")
|
||||
end
|
||||
|
||||
if not (params.username and params.email and params.password) then
|
||||
return error_response("no username/email/password provided")
|
||||
end
|
||||
|
||||
local hashPass = crypt.generate(params.password, crypt.DefaultCost)
|
||||
|
||||
local existing, err = db:query("SELECT 1 FROM users WHERE deleted = 0 AND (email = ? OR username = ?) LIMIT 1", {
|
||||
params.email,
|
||||
params.username
|
||||
})
|
||||
|
||||
if err ~= nil then
|
||||
log.error("Email check failed: "..tostring(err))
|
||||
return error_response("Database check failed: "..tostring(err))
|
||||
end
|
||||
|
||||
if existing and #existing > 0 then
|
||||
return error_response("Unit already exists")
|
||||
end
|
||||
|
||||
local ctx, err = db:exec(
|
||||
"INSERT INTO users (username, email, password, first_name, last_name, phone_number) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
{
|
||||
params.username,
|
||||
params.email,
|
||||
hashPass,
|
||||
params.first_name or "",
|
||||
params.last_name or "",
|
||||
params.phone_number or ""
|
||||
}
|
||||
)
|
||||
if err ~= nil then
|
||||
log.error("Insert failed: "..tostring(err))
|
||||
return error_response("Insert failed: "..tostring(err))
|
||||
end
|
||||
|
||||
local res, err = ctx:wait()
|
||||
if err ~= nil then
|
||||
log.error("Insert confirmation failed: "..tostring(err))
|
||||
return error_response("Insert confirmation failed: "..tostring(err))
|
||||
end
|
||||
|
||||
session.response.result = {
|
||||
rows_affected = res,
|
||||
message = "Unit created successfully"
|
||||
}
|
||||
|
||||
close_db()
|
||||
@@ -1,66 +0,0 @@
|
||||
---@diagnostic disable: redefined-local
|
||||
local db = require("internal.database-sqlite").connect("db/test.db", {log = true})
|
||||
local log = require("internal.log")
|
||||
local session = require("internal.session")
|
||||
|
||||
if not (session.request.params.name and session.request.params.email) then
|
||||
session.response.error = {
|
||||
code = -32602,
|
||||
message = "Name and email are required"
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
local existing, err = db:query("SELECT 1 FROM users WHERE email = ? LIMIT 1", {
|
||||
session.request.params.email
|
||||
})
|
||||
if err ~= nil then
|
||||
session.response.error = {
|
||||
code = -32603,
|
||||
message = "Database check failed: "..tostring(err)
|
||||
}
|
||||
log.error("Email check failed: "..tostring(err))
|
||||
return
|
||||
end
|
||||
|
||||
if existing and #existing > 0 then
|
||||
session.response.error = {
|
||||
code = -32604,
|
||||
message = "Email already exists"
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
local ctx, err = db:exec(
|
||||
"INSERT INTO users (name, email) VALUES (?, ?)",
|
||||
{
|
||||
session.request.params.name,
|
||||
session.request.params.email
|
||||
}
|
||||
)
|
||||
if err ~= nil then
|
||||
session.response.error = {
|
||||
code = -32605,
|
||||
message = "Insert failed: "..tostring(err)
|
||||
}
|
||||
log.error("Insert failed: "..tostring(err))
|
||||
return
|
||||
end
|
||||
|
||||
local res, err = ctx:wait()
|
||||
if err ~= nil then
|
||||
session.response.error = {
|
||||
code = -32606,
|
||||
message = "Insert confirmation failed: "..tostring(err)
|
||||
}
|
||||
log.error("Insert confirmation failed: "..tostring(err))
|
||||
return
|
||||
end
|
||||
|
||||
session.response.result = {
|
||||
success = true,
|
||||
rows_affected = res,
|
||||
message = "User created successfully"
|
||||
}
|
||||
|
||||
db:close()
|
||||
@@ -1,29 +0,0 @@
|
||||
local session = require("internal.session")
|
||||
local net = require("internal.net")
|
||||
|
||||
local reqAddr
|
||||
local logReq = true
|
||||
|
||||
if session.request.params and session.request.params.url then
|
||||
reqAddr = session.request.params.url
|
||||
else
|
||||
session.response.error = {
|
||||
code = -32602,
|
||||
message = "no url provided"
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
local resp = net.http.get_request(logReq, reqAddr)
|
||||
if resp then
|
||||
session.response.result.answer = {
|
||||
status = resp.status,
|
||||
body = resp.body
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
session.response.error = {
|
||||
data = "error while requesting"
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
local session = require("internal.session")
|
||||
local net = require("internal.net")
|
||||
local log = require("internal.log")
|
||||
|
||||
local reqAddr
|
||||
local logReq = true
|
||||
local payload
|
||||
|
||||
log.debug(session.request.params)
|
||||
|
||||
if not (session.request.params and session.request.params.url) then
|
||||
session.response.error = {
|
||||
code = -32602,
|
||||
message = "no url or payload provided"
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
|
||||
reqAddr = session.request.params.url
|
||||
payload = session.request.params.payload
|
||||
|
||||
local resp = net.http.post_request(logReq, reqAddr, "application/json", payload)
|
||||
if resp then
|
||||
session.response.result.answer = {
|
||||
status = resp.status,
|
||||
body = resp.body
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
session.response.error = {
|
||||
data = "error while requesting"
|
||||
}
|
||||
44
com/test.lua
44
com/test.lua
@@ -1,44 +0,0 @@
|
||||
local sha256 = require("internal.crypt.sha256")
|
||||
local log = require("internal.log")
|
||||
local session = require("internal.session")
|
||||
|
||||
-- local secret = require("_config").token()
|
||||
|
||||
-- local token = jwt.encode({
|
||||
-- secret = secret,
|
||||
-- payload = { session_uuid = session.id },
|
||||
-- expires_in = 3600
|
||||
-- })
|
||||
|
||||
-- local err, data = jwt.decode(token, { secret = secret })
|
||||
|
||||
-- if not err then
|
||||
-- session.response.result = {
|
||||
-- token = token
|
||||
-- }
|
||||
-- return
|
||||
-- end
|
||||
|
||||
-- session.response.error = {
|
||||
-- message = "not sigma"
|
||||
-- }
|
||||
-- local array = session.request.params.get("array", "oops")
|
||||
-- function s()
|
||||
-- session.throw_error("dqdqwdqwdqiwhodiwqohdq", 10)
|
||||
-- end
|
||||
-- s()
|
||||
|
||||
-- session.response.__script_data.result = {
|
||||
-- data = {
|
||||
-- sewf = 1
|
||||
-- },
|
||||
-- 2
|
||||
-- }
|
||||
session.response.set_error()
|
||||
--session.response.send_error({1})
|
||||
-- session.response.set()
|
||||
-- session.response.__script_data.result = {
|
||||
-- status = "ok"
|
||||
-- }
|
||||
session.response.set(1)
|
||||
log.event("popi")
|
||||
@@ -1,30 +0,0 @@
|
||||
node:
|
||||
mode: dev
|
||||
name: "My gosally node"
|
||||
show_config: true
|
||||
com_dir: "%path%/com"
|
||||
|
||||
http_server:
|
||||
address: "0.0.0.0"
|
||||
port: "8080"
|
||||
session_ttl: 5s
|
||||
timeout: 3s
|
||||
idle_timeout: 30s
|
||||
|
||||
tls:
|
||||
enabled: true
|
||||
cert_file: "%path%/cert/fullchain.pem"
|
||||
key_file: "%path%/cert/privkey.pem"
|
||||
|
||||
updates:
|
||||
enabled: false
|
||||
check-interval: 1h
|
||||
repository_url: "https://repo.serve.lv/raw/go-sally"
|
||||
|
||||
log:
|
||||
json_format: false
|
||||
level: "debug"
|
||||
|
||||
disable_warnings:
|
||||
- --WNonStdTmpDir
|
||||
- --WUndefLogLevel
|
||||
Binary file not shown.
Binary file not shown.
BIN
db/root.db
BIN
db/root.db
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
// The cmd package is the main package where all the main hooks and methods are called.
|
||||
// The cmd package is the main package where all the main hooks and methods are called.
|
||||
// GoSally uses spf13/cobra to organize all the calls.
|
||||
package cmd
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/hooks"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/src/hooks"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/colors"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -24,7 +24,7 @@ scripts in a given directory. For more information, visit: https://gosally.oblat
|
||||
},
|
||||
}
|
||||
|
||||
// Execute prepares global log, loads cmdline args
|
||||
// Execute prepares global log, loads cmdline args
|
||||
// and executes rootCmd.Execute()
|
||||
func Execute() {
|
||||
log.SetOutput(os.Stdout)
|
||||
@@ -1,7 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/akyaiy/GoSally-mvp/hooks"
|
||||
"github.com/akyaiy/GoSally-mvp/src/hooks"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/sv1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -13,7 +14,8 @@ var verCmd = &cobra.Command{
|
||||
Aliases: []string{"ver", "v"},
|
||||
Short: "Return node version",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("GoSally node: %s\n", config.NodeVersion)
|
||||
fmt.Printf("Go Sally version: %s\n", config.NodeVersion)
|
||||
fmt.Printf("sv1 version: %s\n", sv1.SV1Version)
|
||||
fmt.Printf("Go version: %s\n", runtime.Version())
|
||||
fmt.Printf("Go OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||
},
|
||||
@@ -1,4 +1,4 @@
|
||||
module github.com/akyaiy/GoSally-mvp
|
||||
module github.com/akyaiy/GoSally-mvp/src
|
||||
|
||||
go 1.24.4
|
||||
|
||||
@@ -13,8 +13,6 @@ github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@@ -18,13 +18,13 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/colors"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/run_manager"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/logs"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
@@ -85,7 +85,7 @@ func InitUUIDHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
corestate.NODE_UUID = uuid32
|
||||
}
|
||||
|
||||
// The hook is responsible for checking the initialization stage
|
||||
// The hook is responsible for checking the initialization stage
|
||||
// and restarting in some cases
|
||||
func InitRuntimeHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
if *x.Config.Env.ParentStagePID != os.Getpid() {
|
||||
@@ -138,7 +138,7 @@ func InitRuntimeHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
}
|
||||
|
||||
// post-init stage
|
||||
// The hook creates a run.lock file, which contains information
|
||||
// The hook creates a run.lock file, which contains information
|
||||
// about the process and the node, in the runtime directory.
|
||||
func InitRunlockHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
NodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
@@ -192,7 +192,7 @@ func InitRunlockHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
}
|
||||
}
|
||||
|
||||
// The hook reads the configuration and replaces special expressions
|
||||
// The hook reads the configuration and replaces special expressions
|
||||
// (%tmp% and so on) in string fields with the required data.
|
||||
func InitConfigReplHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
if !slices.Contains(*x.Config.Conf.DisableWarnings, "--WNonStdTmpDir") && os.TempDir() != "/tmp" {
|
||||
@@ -218,7 +218,7 @@ func InitConfigReplHook(_ context.Context, cs *corestate.CoreState, x *app.AppX)
|
||||
}
|
||||
}
|
||||
|
||||
// The hook is responsible for outputting the
|
||||
// The hook is responsible for outputting the
|
||||
// final config and asking for confirmation.
|
||||
func InitConfigPrintHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
if *x.Config.Conf.Node.ShowConfig {
|
||||
@@ -234,7 +234,11 @@ func InitConfigPrintHook(ctx context.Context, cs *corestate.CoreState, x *app.Ap
|
||||
}
|
||||
}
|
||||
|
||||
x.Log.Printf("Starting \"%s\" node", *x.Config.Conf.Node.Name)
|
||||
if *x.Config.Conf.Node.Name == "noname" {
|
||||
x.Log.Printf("Starting node")
|
||||
} else {
|
||||
x.Log.Printf("Starting \"%s\" node", *x.Config.Conf.Node.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func InitSLogHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
@@ -250,7 +254,7 @@ func InitSLogHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
*x.SLog = *newSlog
|
||||
}
|
||||
|
||||
// The method goes through the entire config structure through
|
||||
// The method goes through the entire config structure through
|
||||
// reflection and replaces string fields with the required ones.
|
||||
func processConfig(conf any, replacements map[string]any) error {
|
||||
val := reflect.ValueOf(conf)
|
||||
@@ -11,17 +11,18 @@ import (
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/update"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/gateway"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/session"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/sv1"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/colors"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/run_manager"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/update"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/logs"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/gateway"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/session"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/sv1"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/sv2"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -29,6 +30,7 @@ import (
|
||||
)
|
||||
|
||||
var NodeApp = app.New()
|
||||
var AllowedCmdPattern = `^[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*$`
|
||||
|
||||
func Run(cmd *cobra.Command, args []string) {
|
||||
NodeApp.InitialHooks(
|
||||
@@ -60,17 +62,24 @@ func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
||||
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
|
||||
X: x,
|
||||
CS: cs,
|
||||
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+(>[a-zA-Z0-9]+)*$`),
|
||||
AllowedCmd: regexp.MustCompile(AllowedCmdPattern),
|
||||
Ver: "v1",
|
||||
})
|
||||
|
||||
sv2 := sv2.InitServer(&sv2.HandlerInitStruct{
|
||||
X: x,
|
||||
CS: cs,
|
||||
AllowedCmd: regexp.MustCompile(AllowedCmdPattern),
|
||||
Ver: "v2",
|
||||
})
|
||||
|
||||
session_manager := session.New(*x.Config.Conf.HTTPServer.SessionTTL)
|
||||
|
||||
s := gateway.InitGateway(&gateway.GatewayServerInit{
|
||||
SM: session_manager,
|
||||
CS: cs,
|
||||
X: x,
|
||||
}, serverv1)
|
||||
}, serverv1, sv2)
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
|
||||
)
|
||||
|
||||
// GetNodeUUID outputs the correct uuid from the file at the path specified in the arguments.
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
|
||||
)
|
||||
|
||||
type RunManagerContract interface {
|
||||
@@ -13,10 +13,10 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/run_manager"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
|
||||
)
|
||||
|
||||
func NewUUIDRaw(length int) ([]byte, error) {
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
|
||||
)
|
||||
|
||||
type AppContract interface {
|
||||
@@ -61,6 +61,7 @@ func (c *Compositor) LoadConf(path string) error {
|
||||
v.SetDefault("log.json_format", "false")
|
||||
v.SetDefault("log.level", "info")
|
||||
v.SetDefault("log.output", "%2%")
|
||||
v.SetDefault("disable_warnings", []string{})
|
||||
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return fmt.Errorf("error reading config: %w", err)
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/colors"
|
||||
)
|
||||
|
||||
func (c *Compositor) Print(v any) {
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ func NewLuaPool() *LuaPool {
|
||||
pool: sync.Pool{
|
||||
New: func() any {
|
||||
L := lua.NewState()
|
||||
|
||||
|
||||
return L
|
||||
},
|
||||
},
|
||||
@@ -30,6 +30,6 @@ func (lp *LuaPool) Put(L *lua.LState) {
|
||||
L.Close()
|
||||
|
||||
newL := lua.NewState()
|
||||
|
||||
|
||||
lp.pool.Put(newL)
|
||||
}
|
||||
@@ -3,21 +3,20 @@ package lua
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
|
||||
)
|
||||
|
||||
type LuaEngineDeps struct {
|
||||
HttpRequest *http.Request
|
||||
JSONRPCRequest *rpc.RPCRequest
|
||||
SessionUUID string
|
||||
ScriptPath string
|
||||
SessionUUID string
|
||||
ScriptPath string
|
||||
}
|
||||
|
||||
type LuaEngineContract interface {
|
||||
Handle(deps *LuaEngineDeps) *rpc.RPCResponse
|
||||
|
||||
}
|
||||
|
||||
type LuaEngine struct {
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/session"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/session"
|
||||
)
|
||||
|
||||
// serversApiVer is a type alias for string, used to represent API version strings in the GeneralServer.
|
||||
@@ -3,9 +3,9 @@ package gateway
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/session"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/session"
|
||||
)
|
||||
|
||||
// GeneralServerInit structure only for initialization general server.
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@@ -37,7 +37,9 @@ func NewError(code int, message string, data any, id *json.RawMessage) *RPCRespo
|
||||
Error = map[string]any{
|
||||
"code": code,
|
||||
"message": message,
|
||||
"data": data,
|
||||
}
|
||||
if data != nil {
|
||||
Error["data"] = data
|
||||
}
|
||||
|
||||
return &RPCResponse{
|
||||
@@ -80,10 +80,10 @@ func loadDBMod(llog *slog.Logger, sid string) func(*lua.LState) int {
|
||||
|
||||
mt := L.NewTypeMetatable("gosally_db")
|
||||
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{
|
||||
"exec": dbExec,
|
||||
"query": dbQuery,
|
||||
"exec": dbExec,
|
||||
"query": dbQuery,
|
||||
"query_row": dbQueryRow,
|
||||
"close": dbClose,
|
||||
"close": dbClose,
|
||||
}))
|
||||
|
||||
L.SetField(dbMod, "__seed", lua.LString(sid))
|
||||
@@ -215,43 +215,43 @@ func dbExec(L *lua.LState) int {
|
||||
}
|
||||
|
||||
func dbQueryRow(L *lua.LState) int {
|
||||
ud := L.CheckUserData(1)
|
||||
conn, ok := ud.Value.(*DBConnection)
|
||||
if !ok {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString("invalid database connection"))
|
||||
return 2
|
||||
}
|
||||
ud := L.CheckUserData(1)
|
||||
conn, ok := ud.Value.(*DBConnection)
|
||||
if !ok {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString("invalid database connection"))
|
||||
return 2
|
||||
}
|
||||
|
||||
query := L.CheckString(2)
|
||||
query := L.CheckString(2)
|
||||
|
||||
var args []any
|
||||
if L.GetTop() >= 3 {
|
||||
params := L.CheckTable(3)
|
||||
params.ForEach(func(k lua.LValue, v lua.LValue) {
|
||||
args = append(args, ConvertLuaTypesToGolang(v))
|
||||
})
|
||||
}
|
||||
var args []any
|
||||
if L.GetTop() >= 3 {
|
||||
params := L.CheckTable(3)
|
||||
params.ForEach(func(k lua.LValue, v lua.LValue) {
|
||||
args = append(args, ConvertLuaTypesToGolang(v))
|
||||
})
|
||||
}
|
||||
|
||||
if conn.log {
|
||||
conn.logger.Info("DB QueryRow",
|
||||
slog.String("query", query),
|
||||
slog.Any("params", args))
|
||||
}
|
||||
if conn.log {
|
||||
conn.logger.Info("DB QueryRow",
|
||||
slog.String("query", query),
|
||||
slog.Any("params", args))
|
||||
}
|
||||
|
||||
mtx := getDBMutex(conn.dbPath)
|
||||
mtx.RLock()
|
||||
defer mtx.RUnlock()
|
||||
mtx := getDBMutex(conn.dbPath)
|
||||
mtx.RLock()
|
||||
defer mtx.RUnlock()
|
||||
|
||||
db, err := sql.Open("sqlite", conn.dbPath+"?_busy_timeout=5000&_journal_mode=WAL&_sync=NORMAL&_cache_size=-10000")
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
defer db.Close()
|
||||
db, err := sql.Open("sqlite", conn.dbPath+"?_busy_timeout=5000&_journal_mode=WAL&_sync=NORMAL&_cache_size=-10000")
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
row := db.QueryRow(query, args...)
|
||||
row := db.QueryRow(query, args...)
|
||||
|
||||
columns := []string{}
|
||||
stmt, err := db.Prepare(query)
|
||||
@@ -278,36 +278,36 @@ func dbQueryRow(L *lua.LState) int {
|
||||
columns = append(columns, c)
|
||||
}
|
||||
|
||||
colCount := len(columns)
|
||||
values := make([]any, colCount)
|
||||
valuePtrs := make([]any, colCount)
|
||||
for i := range columns {
|
||||
valuePtrs[i] = &values[i]
|
||||
}
|
||||
colCount := len(columns)
|
||||
values := make([]any, colCount)
|
||||
valuePtrs := make([]any, colCount)
|
||||
for i := range columns {
|
||||
valuePtrs[i] = &values[i]
|
||||
}
|
||||
|
||||
err = row.Scan(valuePtrs...)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
L.Push(lua.LNil)
|
||||
return 1
|
||||
}
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(fmt.Sprintf("scan failed: %v", err)))
|
||||
return 2
|
||||
}
|
||||
err = row.Scan(valuePtrs...)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
L.Push(lua.LNil)
|
||||
return 1
|
||||
}
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(fmt.Sprintf("scan failed: %v", err)))
|
||||
return 2
|
||||
}
|
||||
|
||||
rowTable := L.NewTable()
|
||||
for i, col := range columns {
|
||||
val := values[i]
|
||||
if val == nil {
|
||||
L.SetField(rowTable, col, lua.LNil)
|
||||
} else {
|
||||
L.SetField(rowTable, col, ConvertGolangTypesToLua(L, val))
|
||||
}
|
||||
}
|
||||
rowTable := L.NewTable()
|
||||
for i, col := range columns {
|
||||
val := values[i]
|
||||
if val == nil {
|
||||
L.SetField(rowTable, col, lua.LNil)
|
||||
} else {
|
||||
L.SetField(rowTable, col, ConvertGolangTypesToLua(L, val))
|
||||
}
|
||||
}
|
||||
|
||||
L.Push(rowTable)
|
||||
return 1
|
||||
L.Push(rowTable)
|
||||
return 1
|
||||
}
|
||||
|
||||
func dbQuery(L *lua.LState) int {
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
|
||||
)
|
||||
|
||||
func (h *HandlerV1) Handle(_ context.Context, sid string, r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/colors"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
@@ -186,11 +186,6 @@ func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest,
|
||||
L.SetField(scriptDataTable, "result", resTable)
|
||||
L.SetField(outTable, "send", L.NewFunction(func(L *lua.LState) int {
|
||||
res := L.Get(1)
|
||||
if res == lua.LNil {
|
||||
__exit = 0
|
||||
L.RaiseError("__successfull")
|
||||
return 0
|
||||
}
|
||||
|
||||
resFTable := scriptDataTable.RawGetString("result")
|
||||
if resPTable, ok := res.(*lua.LTable); ok {
|
||||
@@ -6,15 +6,17 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
|
||||
)
|
||||
|
||||
var RPCMethodSeparator = "."
|
||||
|
||||
func (h *HandlerV1) resolveMethodPath(method string) (string, error) {
|
||||
if !h.allowedCmd.MatchString(method) {
|
||||
return "", errors.New(rpc.ErrInvalidMethodFormatS)
|
||||
}
|
||||
|
||||
parts := strings.Split(method, ">")
|
||||
parts := strings.Split(method, RPCMethodSeparator)
|
||||
relPath := filepath.Join(parts...) + ".lua"
|
||||
fullPath := filepath.Join(*h.x.Config.Conf.Node.ComDir, relPath)
|
||||
|
||||
@@ -5,10 +5,12 @@ package sv1
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
|
||||
)
|
||||
|
||||
var SV1Version = "v1"
|
||||
|
||||
// HandlerV1InitStruct structure is only for initialization
|
||||
type HandlerV1InitStruct struct {
|
||||
Ver string
|
||||
12
src/internal/server/sv2/handle.go
Normal file
12
src/internal/server/sv2/handle.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package sv2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
|
||||
)
|
||||
|
||||
func (h *Handler) Handle(_ context.Context, sid string, r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||
return nil
|
||||
}
|
||||
43
src/internal/server/sv2/server.go
Normal file
43
src/internal/server/sv2/server.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// SV2 works with binaries, scripts, and anything else that has access to stdin/stdout.
|
||||
// Modules run in a separate process and communicate via I/O.
|
||||
package sv2
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
|
||||
)
|
||||
|
||||
// HandlerV2InitStruct structure is only for initialization
|
||||
type HandlerInitStruct struct {
|
||||
Ver string
|
||||
CS *corestate.CoreState
|
||||
X *app.AppX
|
||||
AllowedCmd *regexp.Regexp
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
cs *corestate.CoreState
|
||||
x *app.AppX
|
||||
|
||||
// allowedCmd and listAllowedCmd are regular expressions used to validate command names.
|
||||
allowedCmd *regexp.Regexp
|
||||
|
||||
ver string
|
||||
}
|
||||
|
||||
func InitServer(o *HandlerInitStruct) *Handler {
|
||||
return &Handler{
|
||||
cs: o.CS,
|
||||
x: o.X,
|
||||
allowedCmd: o.AllowedCmd,
|
||||
ver: o.Ver,
|
||||
}
|
||||
}
|
||||
|
||||
// GetVersion returns the API version of the HandlerV1, which is set during initialization.
|
||||
// This version is used to identify the API version in the request routing.
|
||||
func (h *Handler) GetVersion() string {
|
||||
return h.ver
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/akyaiy/GoSally-mvp/cmd"
|
||||
"github.com/akyaiy/GoSally-mvp/src/cmd"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user