Merge branch 'development' into development_decartive
This commit is contained in:
119
lib/schemas/openrpcserver/comment.v
Normal file
119
lib/schemas/openrpcserver/comment.v
Normal file
@@ -0,0 +1,119 @@
|
||||
module openrpcserver
|
||||
|
||||
import freeflowuniverse.herolib.data.encoder
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
|
||||
|
||||
@[heap]
|
||||
pub struct Comment {
|
||||
pub mut:
|
||||
id u32
|
||||
comment string
|
||||
parent u32 //id of parent comment if any, 0 means none
|
||||
updated_at i64
|
||||
author u32 //links to user
|
||||
}
|
||||
|
||||
pub fn (self Comment) type_name() string {
|
||||
return 'comments'
|
||||
}
|
||||
|
||||
pub fn (self Comment) load(data []u8) !Comment {
|
||||
return comment_load(data)!
|
||||
}
|
||||
|
||||
pub fn (self Comment) dump() ![]u8{
|
||||
// Create a new encoder
|
||||
mut e := encoder.new()
|
||||
e.add_u8(1)
|
||||
e.add_u32(self.id)
|
||||
e.add_string(self.comment)
|
||||
e.add_u32(self.parent)
|
||||
e.add_i64(self.updated_at)
|
||||
e.add_u32(self.author)
|
||||
return e.data
|
||||
}
|
||||
|
||||
|
||||
pub fn comment_load(data []u8) !Comment{
|
||||
// Create a new decoder
|
||||
mut e := encoder.decoder_new(data)
|
||||
version := e.get_u8()!
|
||||
if version != 1 {
|
||||
panic("wrong version in comment load")
|
||||
}
|
||||
mut comment := Comment{}
|
||||
comment.id = e.get_u32()!
|
||||
comment.comment = e.get_string()!
|
||||
comment.parent = e.get_u32()!
|
||||
comment.updated_at = e.get_i64()!
|
||||
comment.author = e.get_u32()!
|
||||
return comment
|
||||
}
|
||||
|
||||
|
||||
pub struct CommentArg {
|
||||
pub mut:
|
||||
comment string
|
||||
parent u32
|
||||
author u32
|
||||
}
|
||||
|
||||
pub fn comment_multiset(args []CommentArg) ![]u32 {
|
||||
return comments2ids(args)!
|
||||
}
|
||||
|
||||
pub fn comments2ids(args []CommentArg) ![]u32 {
|
||||
return args.map(comment2id(it.comment)!)
|
||||
}
|
||||
|
||||
pub fn comment2id(comment string) !u32 {
|
||||
comment_fixed := comment.to_lower_ascii().trim_space()
|
||||
mut redis := redisclient.core_get()!
|
||||
return if comment_fixed.len > 0{
|
||||
hash := md5.hexhash(comment_fixed)
|
||||
comment_found := redis.hget("db:comments", hash)!
|
||||
if comment_found == ""{
|
||||
id := u32(redis.incr("db:comments:id")!)
|
||||
redis.hset("db:comments", hash, id.str())!
|
||||
redis.hset("db:comments", id.str(), comment_fixed)!
|
||||
id
|
||||
}else{
|
||||
comment_found.u32()
|
||||
}
|
||||
} else { 0 }
|
||||
}
|
||||
|
||||
|
||||
//get new comment, not from the DB
|
||||
pub fn comment_new(args CommentArg) !Comment{
|
||||
mut o := Comment {
|
||||
comment: args.comment
|
||||
parent: args.parent
|
||||
updated_at: ourtime.now().unix()
|
||||
author: args.author
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
pub fn comment_multiset(args []CommentArg) ![]u32{
|
||||
mut ids := []u32{}
|
||||
for comment in args {
|
||||
ids << comment_set(comment)!
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
pub fn comment_set(args CommentArg) !u32{
|
||||
mut o := comment_new(args)!
|
||||
// Use openrpcserver set function which now returns the ID
|
||||
return openrpcserver.set[Comment](mut o)!
|
||||
}
|
||||
|
||||
pub fn comment_exist(id u32) !bool{
|
||||
return openrpcserver.exists[Comment](id)!
|
||||
}
|
||||
|
||||
pub fn comment_get(id u32) !Comment{
|
||||
return openrpcserver.get[Comment](id)!
|
||||
}
|
||||
57
lib/schemas/openrpcserver/core_methods.v
Normal file
57
lib/schemas/openrpcserver/core_methods.v
Normal file
@@ -0,0 +1,57 @@
|
||||
module openrpcserver
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
|
||||
pub fn set[T](mut obj T) !u32 {
|
||||
name := T{}.type_name()
|
||||
mut redis := redisclient.core_get()!
|
||||
|
||||
// Generate ID if not set
|
||||
if obj.id == 0 {
|
||||
myid := redis.incr("db:${name}:id")!
|
||||
obj.id = u32(myid)
|
||||
}
|
||||
|
||||
data := obj.dump()!
|
||||
redis.hset("db:${name}",obj.id.str(),data.bytestr())!
|
||||
return obj.id
|
||||
}
|
||||
|
||||
pub fn get[T](id u32) !T {
|
||||
name := T{}.type_name()
|
||||
mut redis := redisclient.core_get()!
|
||||
data := redis.hget("db:${name}",id.str())!
|
||||
if data.len > 0 {
|
||||
return T{}.load(data.bytes())!
|
||||
} else {
|
||||
return error("Can't find ${name} with id: ${id}")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exists[T](id u32) !bool {
|
||||
name := T{}.type_name()
|
||||
mut redis := redisclient.core_get()!
|
||||
return redis.hexists("db:${name}",id.str())!
|
||||
}
|
||||
|
||||
pub fn delete[T](id u32) ! {
|
||||
name := T{}.type_name()
|
||||
mut redis := redisclient.core_get()!
|
||||
redis.hdel("db:${name}", id.str())!
|
||||
}
|
||||
|
||||
pub fn list[T]() ![]T {
|
||||
name := T{}.type_name()
|
||||
mut redis := redisclient.core_get()!
|
||||
all_data := redis.hgetall("db:${name}")!
|
||||
mut result := []T{}
|
||||
for _, data in all_data {
|
||||
result << T{}.load(data.bytes())!
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
//make it easy to get a base object
|
||||
pub fn new_from_base[T](args BaseArgs) !Base {
|
||||
return T { Base: new_base(args)! }
|
||||
}
|
||||
93
lib/schemas/openrpcserver/core_models.v
Normal file
93
lib/schemas/openrpcserver/core_models.v
Normal file
@@ -0,0 +1,93 @@
|
||||
module openrpcserver
|
||||
|
||||
import crypto.md5
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
|
||||
// Group represents a collection of users with roles and permissions
|
||||
@[heap]
|
||||
pub struct Base {
|
||||
pub mut:
|
||||
id u32
|
||||
name string
|
||||
description string
|
||||
created_at i64
|
||||
updated_at i64
|
||||
securitypolicy u32
|
||||
tags u32 //when we set/get we always do as []string but this can then be sorted and md5ed this gies the unique id of tags
|
||||
comments []u32
|
||||
}
|
||||
|
||||
@[heap]
|
||||
pub struct SecurityPolicy {
|
||||
pub mut:
|
||||
id u32
|
||||
read []u32 //links to users & groups
|
||||
write []u32 //links to users & groups
|
||||
delete []u32 //links to users & groups
|
||||
public bool
|
||||
md5 string //this sorts read, write and delete u32 + hash, then do md5 hash, this allows to go from a random read/write/delete/public config to a hash
|
||||
}
|
||||
|
||||
|
||||
@[heap]
|
||||
pub struct Tags {
|
||||
pub mut:
|
||||
id u32
|
||||
names []string //unique per id
|
||||
md5 string //of sorted names, to make easy to find unique id, each name lowercased and made ascii
|
||||
}
|
||||
|
||||
|
||||
/////////////////
|
||||
|
||||
@[params]
|
||||
pub struct BaseArgs {
|
||||
pub mut:
|
||||
id ?u32
|
||||
name string
|
||||
description string
|
||||
securitypolicy ?u32
|
||||
tags []string
|
||||
comments []CommentArg
|
||||
}
|
||||
|
||||
//make it easy to get a base object
|
||||
pub fn new_base(args BaseArgs) !Base {
|
||||
mut redis := redisclient.core_get()!
|
||||
|
||||
commentids:=comment_multiset(args.comments)!
|
||||
tags:=tags2id(args.tags)!
|
||||
|
||||
return Base {
|
||||
id: args.id or { 0 }
|
||||
name: args.name
|
||||
description: args.description
|
||||
created_at: ourtime.now().unix()
|
||||
updated_at: ourtime.now().unix()
|
||||
securitypolicy: args.securitypolicy or { 0 }
|
||||
tags: tags
|
||||
comments: commentids
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tags2id(tags []string) !u32 {
|
||||
mut redis := redisclient.core_get()!
|
||||
return if tags.len>0{
|
||||
mut tags_fixed := tags.map(it.to_lower_ascii().trim_space()).filter(it != "")
|
||||
tags_fixed.sort_ignore_case()
|
||||
hash :=md5.hexhash(tags_fixed.join(","))
|
||||
tags_found := redis.hget("db:tags", hash)!
|
||||
return if tags_found == ""{
|
||||
id := u32(redis.incr("db:tags:id")!)
|
||||
redis.hset("db:tags", hash, id.str())!
|
||||
redis.hset("db:tags", id.str(), tags_fixed.join(","))!
|
||||
id
|
||||
}else{
|
||||
tags_found.u32()
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
172
lib/schemas/openrpcserver/openrpc_server.v
Normal file
172
lib/schemas/openrpcserver/openrpc_server.v
Normal file
@@ -0,0 +1,172 @@
|
||||
module openrpcserver
|
||||
|
||||
import json
|
||||
import x.json2
|
||||
import net.unix
|
||||
import os
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
//THIS IS DEFAULT NEEDED FOR EACH OPENRPC SERVER WE MAKE
|
||||
|
||||
pub struct JsonRpcRequest {
|
||||
pub:
|
||||
jsonrpc string = '2.0'
|
||||
method string
|
||||
params string
|
||||
id string
|
||||
}
|
||||
|
||||
// JSON-RPC 2.0 response structure
|
||||
pub struct JsonRpcResponse {
|
||||
pub:
|
||||
jsonrpc string = '2.0'
|
||||
result string
|
||||
error ?JsonRpcError
|
||||
id string
|
||||
}
|
||||
|
||||
// JSON-RPC 2.0 error structure
|
||||
pub struct JsonRpcError {
|
||||
pub:
|
||||
code int
|
||||
message string
|
||||
data string
|
||||
}
|
||||
|
||||
|
||||
pub struct RPCServer {
|
||||
pub mut:
|
||||
listener &unix.StreamListener
|
||||
socket_path string
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct RPCServerArgs {
|
||||
pub mut:
|
||||
socket_path string = '/tmp/heromodels'
|
||||
}
|
||||
|
||||
// Temporary struct for parsing incoming JSON-RPC requests using json2
|
||||
struct JsonRpcRequestRaw {
|
||||
jsonrpc string
|
||||
method string
|
||||
params json2.Any
|
||||
id json2.Any
|
||||
}
|
||||
|
||||
pub fn new_rpc_server(args RPCServerArgs) !&RPCServer {
|
||||
// Remove existing socket file if it exists
|
||||
if os.exists(args.socket_path) {
|
||||
os.rm(args.socket_path)!
|
||||
}
|
||||
|
||||
listener := unix.listen_stream(args.socket_path, unix.ListenOptions{})!
|
||||
|
||||
return &RPCServer{
|
||||
listener: listener
|
||||
socket_path: args.socket_path
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut server RPCServer) start() ! {
|
||||
console.print_header('Starting HeroModels OpenRPC Server on ${server.socket_path}')
|
||||
|
||||
for {
|
||||
mut conn := server.listener.accept()!
|
||||
spawn server.handle_connection(mut conn)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut server RPCServer) close() ! {
|
||||
server.listener.close()!
|
||||
if os.exists(server.socket_path) {
|
||||
os.rm(server.socket_path)!
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut server RPCServer) handle_connection(mut conn unix.StreamConn) {
|
||||
defer {
|
||||
conn.close() or { console.print_stderr('Error closing connection: ${err}') }
|
||||
}
|
||||
|
||||
for {
|
||||
// Read JSON-RPC request
|
||||
mut buffer := []u8{len: 4096}
|
||||
bytes_read := conn.read(mut buffer) or {
|
||||
console.print_debug('Connection closed or error reading: ${err}')
|
||||
break
|
||||
}
|
||||
|
||||
if bytes_read == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
request_data := buffer[..bytes_read].bytestr()
|
||||
console.print_debug('Received request: ${request_data}')
|
||||
|
||||
// Process the JSON-RPC request
|
||||
response := server.process_request(request_data) or {
|
||||
server.create_error_response(-32603, 'Internal error: ${err}', 'null')
|
||||
}
|
||||
|
||||
// Send response
|
||||
conn.write_string(response) or {
|
||||
console.print_stderr('Error writing response: ${err}')
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut server RPCServer) process_request(request_data string) !string {
|
||||
// Parse JSON-RPC request using json2 to handle Any types
|
||||
request := json2.decode[JsonRpcRequestRaw](request_data)!
|
||||
// Convert params to string representation
|
||||
params_str := request.params.json_str()
|
||||
// Convert id to string
|
||||
id_str := request.id.json_str()
|
||||
r := request.method.trim_space().to_lower()
|
||||
// Route to appropriate method
|
||||
result := server.process(r, params_str)!
|
||||
return server.create_success_response(result, id_str)
|
||||
}
|
||||
|
||||
// Default process method - should be overridden by implementations
|
||||
pub fn (mut server RPCServer) process(method string, params_str string) !string {
|
||||
return match method {
|
||||
'rpc.discover' {
|
||||
server.discover()!
|
||||
}
|
||||
else {
|
||||
server.create_error_response(-32601, 'Method not found', method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut server RPCServer) create_success_response(result string, id string) string {
|
||||
response := JsonRpcResponse{
|
||||
jsonrpc: '2.0'
|
||||
result: result
|
||||
id: id
|
||||
}
|
||||
return json.encode(response)
|
||||
}
|
||||
|
||||
fn (mut server RPCServer) create_error_response(code int, message string, id string) string {
|
||||
error := JsonRpcError{
|
||||
code: code
|
||||
message: message
|
||||
data: 'null'
|
||||
}
|
||||
response := JsonRpcResponse{
|
||||
jsonrpc: '2.0'
|
||||
error: error
|
||||
id: id
|
||||
}
|
||||
return json.encode(response)
|
||||
}
|
||||
|
||||
// discover returns the OpenRPC specification for the service
|
||||
pub fn (mut server RPCServer) discover() !string {
|
||||
// Return a basic OpenRPC spec - should be overridden by implementations
|
||||
return '{"openrpc": "1.2.6", "info": {"title": "OpenRPC Server", "version": "1.0.0"}, "methods": []}'
|
||||
}
|
||||
Reference in New Issue
Block a user