Compare commits

31 Commits

Author SHA1 Message Date
d4413c433f fmt 2025-11-02 11:04:12 +02:00
d9a4bb7871 fix fmt 2025-11-02 10:17:49 +02:00
8e017af3ed change sv1 version 2025-11-01 10:22:14 +02:00
ef6023330d 2025-10-22 15:05:12 +03:00
5474b22fc8 Update README.md 2025-10-12 13:11:55 +03:00
6cd678d9f1 Update README.md 2025-10-12 13:10:10 +03:00
856d3b418c Update README.md 2025-10-12 13:06:37 +03:00
5734ca7a67 update readme.md 2025-10-12 12:56:30 +03:00
608c5aed4a Update README.md 2025-10-12 12:46:07 +03:00
Aleksey
d4d04115f3 Update README.md 2025-10-12 11:10:21 +03:00
Aleksey
4b916f4fc9 Update README.md 2025-10-12 10:46:41 +03:00
54cc496c39 fix noname node message 2025-10-12 01:57:52 +03:00
f7b0014a37 fix bug 2025-10-12 01:56:11 +03:00
54eb5eec6a add wiki to gitignore 2025-10-11 23:20:14 +03:00
6cc24a1e7f update readme 2025-10-11 21:07:51 +03:00
ea41c435dd update readme 2025-10-11 21:04:59 +03:00
d24e1a94ae 2025-10-11 21:01:17 +03:00
846dc06601 update makefile 2025-10-11 16:31:35 +03:00
740fbbff78 fix version message 2025-10-11 16:25:17 +03:00
40be3c8d09 add sv1 version 2025-10-11 16:24:58 +03:00
9c140abc6d make a table 2025-10-11 12:49:53 +03:00
e90233aec4 complete part of readme 2025-10-11 12:44:48 +03:00
df1ef57769 fix bugs 2025-10-10 22:58:31 +03:00
4c840c40bb some changes 2025-10-10 22:54:29 +03:00
57f35e8f33 move go files to src/ 2025-10-10 22:46:24 +03:00
f0c591f325 delete old files 2025-10-10 22:28:55 +03:00
36ee320c45 comment upx 2025-10-10 22:22:34 +03:00
ee6fd205d5 fix the use of empty fields in the response 2025-10-10 22:22:15 +03:00
bed0471cc4 remove some messages 2025-10-10 22:03:21 +03:00
e3812a18a6 require bcrypt only if needed 2025-10-10 20:35:19 +03:00
b7d939d5d7 optimise db exec 2025-10-10 20:23:24 +03:00
71 changed files with 410 additions and 746 deletions

6
.gitignore vendored
View File

@@ -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

View File

@@ -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
View File

@@ -1,51 +1,132 @@
# Go Sally MVP (Minimum/Minimal Viable Product)
[![Status](https://img.shields.io/badge/status-MVP-orange.svg)]()
[![Go Version](https://img.shields.io/badge/Go-1.24.6-informational)](https://go.dev/)
[![Lua Version](https://img.shields.io/badge/Lua-5.1-informational)](https://www.lua.org/manual/5.1/)
[![Go Reference](https://pkg.go.dev/badge/github.com/akyaiy/GoSally-mvp.svg)](https://pkg.go.dev/github.com/akyaiy/GoSally-mvp)
[![License](https://img.shields.io/badge/license-BSD--3--Clause-blue)](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.
[![Last Commit](https://img.shields.io/github/last-commit/akyaiy/GoSally-mvp)]()
[![Commits per month](https://img.shields.io/github/commit-activity/m/akyaiy/GoSally-mvp)]()
[![Docs](https://img.shields.io/badge/docs-wiki-blue)](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
View 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
View 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

View File

@@ -37,31 +37,6 @@ 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 among active users
local existing, err = db:query([[
SELECT 1
FROM units
WHERE (email = ? OR username = ?)
AND entry_status != 'deleted'
AND deleted_at IS NULL
LIMIT 1
]], {
params.email,
params.username
})
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_EXISTS.code, errors.UNIT_EXISTS.message)
end
-- Second db query: insert new unit
local ctx, err = db:exec(
"INSERT INTO units (user_id, username, email, password) VALUES (?, ?, ?, ?)",
{
@@ -71,18 +46,23 @@ local ctx, err = db:exec(
hashPass,
}
)
if err ~= nil then
log.error("Insert failed: "..tostring(err))
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))
close_db()
session.response.send_error(errors.DB_INSERT_FAILED.code, errors.DB_INSERT_FAILED.message)
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
close_db()
session.response.send({message = "Unit created successfully", unit_id = unitID})
session.response.send({unit_id = unitID})

View File

@@ -74,4 +74,4 @@ if err ~= nil then
end
close_db()
session.response.send({message = "Unit deleted successfully", unit_id = params.user_id})
session.response.send()

View File

@@ -6,7 +6,6 @@
local log = require("internal.log")
local db = require("internal.database.sqlite").connect("db/unit.db", { log = true })
local session = require("internal.session")
local crypt = require("internal.crypt.bcrypt")
local common = require("com/Unit/_common")
local errors = require("com/Unit/_errors")
@@ -55,6 +54,7 @@ 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 .. " = ?")
@@ -99,8 +99,4 @@ end
close_db()
session.response.send({
message = "User updated successfully",
fields_updated = #set_clauses - 1, -- excluding updated_at
fields = params.fields
})
session.response.send()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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")

View File

@@ -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.

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

View File

@@ -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)

View File

@@ -1,7 +1,7 @@
package cmd
import (
"github.com/akyaiy/GoSally-mvp/hooks"
"github.com/akyaiy/GoSally-mvp/src/hooks"
"github.com/spf13/cobra"
)

View File

@@ -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)
},

View File

@@ -1,4 +1,4 @@
module github.com/akyaiy/GoSally-mvp
module github.com/akyaiy/GoSally-mvp/src
go 1.24.4

View File

@@ -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=

View File

@@ -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)

View File

@@ -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"
@@ -65,13 +66,20 @@ func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
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{

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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"
)

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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"
)

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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.

View File

@@ -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"
)

View File

@@ -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{

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -6,7 +6,7 @@ import (
"path/filepath"
"strings"
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
)
var RPCMethodSeparator = "."

View File

@@ -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

View 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
}

View 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
}

View File

@@ -2,7 +2,7 @@
package main
import (
"github.com/akyaiy/GoSally-mvp/cmd"
"github.com/akyaiy/GoSally-mvp/src/cmd"
_ "modernc.org/sqlite"
)