This commit is contained in:
2025-09-19 11:52:08 +02:00
parent 1709618f2c
commit dd7946c20c
22 changed files with 263 additions and 965 deletions

View File

@@ -1,17 +1,19 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
import freeflowuniverse.herolib.hero.heromodels.rpc
import freeflowuniverse.herolib.hero.heromodels
import freeflowuniverse.herolib.hero.db
import time
fn main() {
// Start the server in a background thread with authentication disabled for testing
spawn fn () {
rpc.start(
spawn fn () ! {
heromodels.new(reset: true, name: 'test')!
heromodels.server_start(
name: 'test'
port: 8086
auth_enabled: false // Disable auth for testing
cors_enabled: true
reset: true
reset: true
allowed_origins: [
'http://localhost:5173',
]

25
lib/hero/db/decode.v Normal file
View File

@@ -0,0 +1,25 @@
module db
import x.json2
import json
pub fn decode_int(data string) !int {
return json2.decode[int](data) or { return error('Failed to decode int: ${data}') }
}
pub fn decode_u32(data string) !u32 {
return json2.decode[u32](data) or { return error('Failed to decode u32: ${data}') }
}
pub fn decode_bool(data string) !bool {
return json2.decode[bool](data) or { return error('Failed to decode bool: ${data}') }
}
pub fn decode_generic[T](data string) !T {
mut r := json.decode(T, data) or {
println('Failed to decode T: \n***\n${data}\n***\n${err}')
println(T{})
return error('Failed to decode T: ${data}\n${err}')
}
return r
}

View File

@@ -3,6 +3,9 @@ module heromodels
import freeflowuniverse.herolib.data.encoder
import freeflowuniverse.herolib.data.ourtime
import freeflowuniverse.herolib.hero.db
import freeflowuniverse.herolib.schemas.jsonrpc { Response, new_error, new_response, new_response_false, new_response_ok, new_response_true, new_response_int }
import freeflowuniverse.herolib.hero.user { UserRef }
import json
// Calendar represents a collection of events
@[heap]
@@ -135,7 +138,48 @@ pub fn (mut self DBCalendar) get(id u32) !Calendar {
}
pub fn (mut self DBCalendar) list() ![]Calendar {
r:= self.db.list[Calendar]()!.map(self.get(it)!)
r := self.db.list[Calendar]()!.map(self.get(it)!)
println(r)
return r
}
pub fn calendar_handle(mut f ModelsFactory, rpcid int, servercontext map[string]string, userref UserRef, method string, params string) !Response {
match method {
'get' {
id := db.decode_u32(params)!
res := f.calendar.get(id)!
return new_response(rpcid, json.encode(res))
}
'set' {
mut o := db.decode_generic[Calendar](params)!
o=f.calendar.set(o)!
return new_response_int(rpcid,int(o.id))
}
'delete' {
id := db.decode_u32(params)!
f.calendar.delete(id)!
return new_response_ok(rpcid)
}
'exist' {
id := db.decode_u32(params)!
if f.calendar.exist(id)! {
return new_response_true(rpcid)
} else {
return new_response_false(rpcid)
}
}
'list' {
req := jsonrpc.new_request(method, '') // no params
res := f.calendar.list()!
return new_response(req.id, json.encode(res))
}
else {
println('Method not found on calendar: ${method}')
$dbg;
return new_error(rpcid,
code: 32601
message: 'Method ${method} not found on calendar'
)
}
}
}

View File

@@ -1,7 +1,14 @@
module heromodels
import os
import freeflowuniverse.herolib.hero.db
import freeflowuniverse.herolib.hero.user { UserRef }
import freeflowuniverse.herolib.core.redisclient
import freeflowuniverse.herolib.schemas.openrpc { Handler, new_handler }
import freeflowuniverse.herolib.schemas.jsonrpc
import freeflowuniverse.herolib.ui.console
const openrpc_path = os.join_path(os.dir(@FILE), 'openrpc.json')
__global (
rpc_heromodels map[string]&ModelsFactory
@@ -19,11 +26,13 @@ pub mut:
project_issue DBProjectIssue
chat_group DBChatGroup
chat_message DBChatMessage
rpc_handler &Handler
}
@[params]
pub struct NewArgs {
name string @[required]
pub mut:
name string = 'default'
reset bool
redis ?&redisclient.Redis
}
@@ -33,6 +42,7 @@ pub fn new(args NewArgs) !&ModelsFactory {
if args.reset {
mydb.redis.flushdb()!
}
mut h := new_handler(openrpc_path)!
mut f := ModelsFactory{
comments: DBComments{
db: &mydb
@@ -61,7 +71,47 @@ pub fn new(args NewArgs) !&ModelsFactory {
chat_message: DBChatMessage{
db: &mydb
}
rpc_handler: &h
}
// openrpc handler can be used by any server, has even embedded unix sockets and simple http server
f.rpc_handler.register_api_handler('heromodels', group_api_handler)
f.rpc_handler.servercontext['heromodels_instance'] = args.name // pass name to handler
rpc_heromodels[args.name] = &f
return rpc_heromodels[args.name] or { panic('bug') }
}
pub fn get(name string) !&ModelsFactory {
mut f := rpc_heromodels[name] or {
return error('No heromodels factory with name ${name} found')
}
return f
}
pub fn group_api_handler(rpcid int, servercontext map[string]string, actorname string, methodname string, params string) !jsonrpc.Response {
instance := servercontext['heromodels_instance'] or {
return jsonrpc.new_error(rpcid,
code: 32606
message: 'heromodels_instance for modeldb not found on servercontext.'
)
}
user_id := servercontext['user'] or { '' } // can be 0 if no authentication
userref := UserRef{
id: user_id.u32()
}
console.print_debug('heromodels handle: ${rpcid}: ${instance} - ${actorname} - ${methodname} - ${params} - user:${user_id}')
mut f := get(instance)!
match actorname {
'calendar' {
return calendar_handle(mut f, rpcid, servercontext, userref, methodname, params)!
}
else {
return jsonrpc.new_error(rpcid,
code: 32111
data: '${params}'
message: 'Actor ${actorname} not found on heromodels'
)
}
}
}

View File

@@ -0,0 +1,34 @@
module heromodels
import freeflowuniverse.herolib.hero.heroserver
import os
// Start heromodels server using heroserver
@[params]
pub struct ServerArgs {
pub mut:
port int = 8080
host string = 'localhost'
auth_enabled bool = true
cors_enabled bool = true
reset bool
allowed_origins []string = ['*'] // Default allows all origins
name string
}
pub fn server_start(args ServerArgs) ! {
// Create a new heroserver instance
mut server := heroserver.new(
port: args.port
host: args.host
auth_enabled: args.auth_enabled
cors_enabled: args.cors_enabled
allowed_origins: args.allowed_origins
)!
mut f := get(args.name)!
server.register_handler('heromodels', f.rpc_handler)!
// Start the server
server.start()!
}

View File

@@ -1,100 +0,0 @@
module rpc
import freeflowuniverse.herolib.schemas.openrpc { Handler }
import freeflowuniverse.herolib.hero.heroserver
import os
const openrpc_path = os.join_path(os.dir(@FILE), 'openrpc.json')
// Create a new heromodels handler for heroserver
pub fn new_heromodels_handler() !&Handler {
mut handler := openrpc.new_handler(openrpc_path)!
// Register all comment methods
handler.register_procedure_handle('comment_get', comment_get)
handler.register_procedure_handle('comment_set', comment_set)
handler.register_procedure_handle('comment_delete', comment_delete)
handler.register_procedure_handle('comment_list', comment_list)
// Register all calendar methods
handler.register_procedure_handle('calendar_get', calendar_get)
handler.register_procedure_handle('calendar_set', calendar_set)
handler.register_procedure_handle('calendar_delete', calendar_delete)
handler.register_procedure_handle('calendar_list', calendar_list)
// Register all calendar event methods
handler.register_procedure_handle('calendar_event_get', calendar_event_get)
handler.register_procedure_handle('calendar_event_set', calendar_event_set)
handler.register_procedure_handle('calendar_event_delete', calendar_event_delete)
handler.register_procedure_handle('calendar_event_list', calendar_event_list)
// Register all chat group methods
handler.register_procedure_handle('chat_group_get', chat_group_get)
handler.register_procedure_handle('chat_group_set', chat_group_set)
handler.register_procedure_handle('chat_group_delete', chat_group_delete)
handler.register_procedure_handle('chat_group_list', chat_group_list)
// Register all chat message methods
handler.register_procedure_handle('chat_message_get', chat_message_get)
handler.register_procedure_handle('chat_message_set', chat_message_set)
handler.register_procedure_handle('chat_message_delete', chat_message_delete)
handler.register_procedure_handle('chat_message_list', chat_message_list)
// Register all group methods
handler.register_procedure_handle('group_get', group_get)
handler.register_procedure_handle('group_set', group_set)
handler.register_procedure_handle('group_delete', group_delete)
handler.register_procedure_handle('group_list', group_list)
// Register all project issue methods
handler.register_procedure_handle('project_issue_get', project_issue_get)
handler.register_procedure_handle('project_issue_set', project_issue_set)
handler.register_procedure_handle('project_issue_delete', project_issue_delete)
handler.register_procedure_handle('project_issue_list', project_issue_list)
// Register all project methods
handler.register_procedure_handle('project_get', project_get)
handler.register_procedure_handle('project_set', project_set)
handler.register_procedure_handle('project_delete', project_delete)
handler.register_procedure_handle('project_list', project_list)
// Register all user methods
handler.register_procedure_handle('user_get', user_get)
handler.register_procedure_handle('user_set', user_set)
handler.register_procedure_handle('user_delete', user_delete)
handler.register_procedure_handle('user_list', user_list)
return &handler
}
// Start heromodels server using heroserver
@[params]
pub struct ServerArgs {
pub mut:
port int = 8080
host string = 'localhost'
auth_enabled bool = true
cors_enabled bool = true
reset bool
allowed_origins []string = ['*'] // Default allows all origins
name string
}
pub fn start(args ServerArgs) ! {
// Create a new heroserver instance
mut server := heroserver.new(
port: args.port
host: args.host
auth_enabled: args.auth_enabled
cors_enabled: args.cors_enabled
allowed_origins: args.allowed_origins
)!
// Create and register the heromodels handler
handler := new_heromodels_handler()!
handler.params['name'] = args.name
server.register_handler('heromodels', handler)!
// Start the server
server.start()!
}

View File

@@ -1,78 +0,0 @@
module rpc
import json
import freeflowuniverse.herolib.schemas.jsonrpc { Request, Response, new_response_true, new_response_u32 }
import freeflowuniverse.herolib.hero.heromodels
// Calendar-specific argument structures
@[params]
pub struct CalendarGetArgs {
pub mut:
id u32 @[required]
}
@[params]
pub struct CalendarSetArgs {
pub mut:
name string @[required]
description string
color string
timezone string
is_public bool
events []u32
}
@[params]
pub struct CalendarDeleteArgs {
pub mut:
id u32 @[required]
}
pub fn calendar_get(request Request) !Response {
payload := jsonrpc.decode_payload[CalendarGetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
calendar := mydb.calendar.get(payload.id)!
return jsonrpc.new_response(request.id, json.encode(calendar))
}
pub fn calendar_set(request Request) !Response {
payload := jsonrpc.decode_payload[CalendarSetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mut calendar_obj := mydb.calendar.new(
name: payload.name
description: payload.description
color: payload.color
timezone: payload.timezone
is_public: payload.is_public
events: payload.events
)!
calendar_obj = mydb.calendar.set(calendar_obj)!
return new_response_u32(request.id, calendar_obj.id)
}
pub fn calendar_delete(request Request) !Response {
payload := jsonrpc.decode_payload[CalendarDeleteArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mydb.calendar.delete(payload.id)!
// returns
return new_response_true(request.id) // return true as jsonrpc (bool)
}
pub fn calendar_list(request Request) !Response {
mut mydb := heromodels.new()!
calendars := mydb.calendar.list()!
return jsonrpc.new_response(request.id, json.encode(calendars))
}

View File

@@ -1,134 +0,0 @@
module rpc
import json
import freeflowuniverse.herolib.schemas.jsonrpc { Request, Response, new_response_true, new_response_u32 }
import freeflowuniverse.herolib.hero.heromodels
import freeflowuniverse.herolib.hero.db
fn convert_iso_to_ourtime(iso_time string) !string {
if iso_time.trim_space() == '' {
return error('Empty time string')
}
// Remove the 'Z' suffix if present
mut cleaned := iso_time.replace('Z', '')
// Remove milliseconds (.000) if present
if cleaned.contains('.') {
cleaned = cleaned.all_before_last('.')
}
// Replace 'T' with space
cleaned = cleaned.replace('T', ' ')
return cleaned
}
// CalendarEvent-specific argument structures
@[params]
pub struct CalendarEventGetArgs {
pub mut:
id u32 @[required]
}
@[params]
pub struct CalendarEventSetArgs {
pub mut:
name string
description string
title string
start_time string // use ourtime module to go from string to epoch
end_time string // use ourtime module to go from string to epoch
location string
attendees []u32 // IDs of user groups
fs_items []u32 // IDs of linked files or dirs
calendar_id u32 // Associated calendar
status heromodels.EventStatus
is_all_day bool
is_recurring bool
recurrence []heromodels.RecurrenceRule
reminder_mins []int // Minutes before event for reminders
color string // Hex color code
timezone string
securitypolicy u32
tags []string
comments []db.CommentArg
}
@[params]
pub struct CalendarEventDeleteArgs {
pub mut:
id u32 @[required]
}
pub fn calendar_event_get(handlerparams map[string]string, request Request) !Response {
payload := jsonrpc.decode_payload[CalendarEventGetArgs](request.params) or {
return jsonrpc.invalid_params
}
name := handlerparams['name'] or { 'default' }
mut mydb := heromodels.get(name)!
calendar_event := mydb.calendar_event.get(payload.id)!
return jsonrpc.new_response(request.id, json.encode(calendar_event))
}
pub fn calendar_event_set(request Request) !Response {
payload := jsonrpc.decode_payload[CalendarEventSetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new() or { return jsonrpc.internal_error }
start_time_converted := convert_iso_to_ourtime(payload.start_time) or {
return jsonrpc.internal_error
}
end_time_converted := convert_iso_to_ourtime(payload.end_time) or {
return jsonrpc.internal_error
}
mut calendar_event_obj := mydb.calendar_event.new(
name: payload.name
description: payload.description
title: payload.title
start_time: start_time_converted
end_time: end_time_converted
location: payload.location
attendees: payload.attendees
fs_items: payload.fs_items
calendar_id: payload.calendar_id
status: payload.status
is_all_day: payload.is_all_day
is_recurring: payload.is_recurring
recurrence: payload.recurrence
reminder_mins: payload.reminder_mins
color: payload.color
timezone: payload.timezone
securitypolicy: payload.securitypolicy
tags: payload.tags
comments: payload.comments
) or { return jsonrpc.internal_error }
calendar_event_obj = mydb.calendar_event.set(calendar_event_obj) or {
return jsonrpc.internal_error
}
return new_response_u32(request.id, calendar_event_obj.id)
}
pub fn calendar_event_delete(request Request) !Response {
payload := jsonrpc.decode_payload[CalendarEventDeleteArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mydb.calendar_event.delete(payload.id)!
return new_response_true(request.id) // return true as jsonrpc (bool)
}
// List all calendar events
pub fn calendar_event_list(request Request) !Response {
mut mydb := heromodels.new()!
calendar_events := mydb.calendar_event.list()!
return jsonrpc.new_response(request.id, json.encode(calendar_events))
}

View File

@@ -1,83 +0,0 @@
module rpc
import json
import freeflowuniverse.herolib.schemas.jsonrpc { Request, Response, new_response_true, new_response_u32 }
import freeflowuniverse.herolib.hero.heromodels
import freeflowuniverse.herolib.hero.db
// ChatGroup-specific argument structures
@[params]
pub struct ChatGroupGetArgs {
pub mut:
id u32 @[required]
}
@[params]
pub struct ChatGroupSetArgs {
pub mut:
name string
description string
chat_type heromodels.ChatType
last_activity i64
is_archived bool
securitypolicy u32
tags []string
comments []db.CommentArg
}
@[params]
pub struct ChatGroupDeleteArgs {
pub mut:
id u32 @[required]
}
pub fn chat_group_get(request Request) !Response {
payload := jsonrpc.decode_payload[ChatGroupGetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
chat_group := mydb.chat_group.get(payload.id)!
return jsonrpc.new_response(request.id, json.encode(chat_group))
}
pub fn chat_group_set(request Request) !Response {
payload := jsonrpc.decode_payload[ChatGroupSetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mut chat_group_obj := mydb.chat_group.new(
name: payload.name
description: payload.description
chat_type: payload.chat_type
last_activity: payload.last_activity
is_archived: payload.is_archived
securitypolicy: payload.securitypolicy
tags: payload.tags
comments: payload.comments
)!
chat_group_obj=mydb.chat_group.set( chat_group_obj)!
return new_response_u32(request.id, chat_group_obj.id)
}
pub fn chat_group_delete(request Request) !Response {
payload := jsonrpc.decode_payload[ChatGroupDeleteArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mydb.chat_group.delete(payload.id)!
return new_response_true(request.id) // return true as jsonrpc (bool)
}
pub fn chat_group_list(request Request) !Response {
mut mydb := heromodels.new()!
chat_groups := mydb.chat_group.list()!
return jsonrpc.new_response(request.id, json.encode(chat_groups))
}

View File

@@ -1,95 +0,0 @@
module rpc
import json
import freeflowuniverse.herolib.schemas.jsonrpc { Request, Response, new_response_true, new_response_u32 }
import freeflowuniverse.herolib.hero.heromodels
import freeflowuniverse.herolib.hero.db
// ChatMessage-specific argument structures
@[params]
pub struct ChatMessageGetArgs {
pub mut:
id u32 @[required]
}
@[params]
pub struct ChatMessageSetArgs {
pub mut:
name string
description string
content string
chat_group_id u32
sender_id u32
parent_messages []heromodels.MessageLink
fs_files []u32
message_type heromodels.MessageType
status heromodels.MessageStatus
reactions []heromodels.MessageReaction
mentions []u32
securitypolicy u32
tags []string
comments []db.CommentArg
}
@[params]
pub struct ChatMessageDeleteArgs {
pub mut:
id u32 @[required]
}
pub fn chat_message_get(request Request) !Response {
payload := jsonrpc.decode_payload[ChatMessageGetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
chat_message := mydb.chat_message.get(payload.id)!
return jsonrpc.new_response(request.id, json.encode(chat_message))
}
pub fn chat_message_set(request Request) !Response {
payload := jsonrpc.decode_payload[ChatMessageSetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mut chat_message_obj := mydb.chat_message.new(
name: payload.name
description: payload.description
content: payload.content
chat_group_id: payload.chat_group_id
sender_id: payload.sender_id
parent_messages: payload.parent_messages
fs_files: payload.fs_files
message_type: payload.message_type
status: payload.status
reactions: payload.reactions
mentions: payload.mentions
securitypolicy: payload.securitypolicy
tags: payload.tags
comments: payload.comments
)!
chat_message_obj=mydb.chat_message.set( chat_message_obj)!
return new_response_u32(request.id, chat_message_obj.id)
}
pub fn chat_message_delete(request Request) !Response {
payload := jsonrpc.decode_payload[ChatMessageDeleteArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mydb.chat_message.delete(payload.id)!
return new_response_true(request.id) // return true as jsonrpc (bool)
}
pub fn chat_message_list(request Request) !Response {
mut mydb := heromodels.new()!
chat_messages := mydb.chat_message.list()!
return jsonrpc.new_response(request.id, json.encode(chat_messages))
}

View File

@@ -1,93 +0,0 @@
module rpc
import json
import freeflowuniverse.herolib.schemas.jsonrpc { Request, Response, new_response_true, new_response_u32 }
import freeflowuniverse.herolib.hero.heromodels
// Comment-specific argument structures
@[params]
pub struct CommentGetArgs {
pub mut:
id u32 @[required]
}
@[params]
pub struct CommentSetArgs {
pub mut:
comment string @[required]
parent u32
author u32
}
@[params]
pub struct CommentDeleteArgs {
pub mut:
id u32 @[required]
}
pub fn comment_get(request Request) !Response {
payload := jsonrpc.decode_payload[CommentGetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
comment := mydb.comments.get(payload.id) or {
// Return proper JSON-RPC error instead of panicking
return jsonrpc.new_error(request.id, jsonrpc.RPCError{
code: -32000 // Server error
message: 'Comment not found'
data: 'Comment with ID ${payload.id} does not exist'
})
}
return jsonrpc.new_response(request.id, json.encode(comment))
}
pub fn comment_set(request Request) !Response {
payload := jsonrpc.decode_payload[CommentSetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mut comment_obj := mydb.comments.new(
comment: payload.comment
parent: payload.parent
author: payload.author
)!
comment_obj = mydb.comments.set(comment_obj)!
return new_response_u32(request.id, comment_obj.id)
}
pub fn comment_delete(request Request) !Response {
payload := jsonrpc.decode_payload[CommentDeleteArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mydb.comments.delete(payload.id) or {
// Return proper JSON-RPC error instead of panicking
return jsonrpc.new_error(request.id, jsonrpc.RPCError{
code: -32000 // Server error
message: 'Comment not found'
data: 'Comment with ID ${payload.id} does not exist or could not be deleted'
})
}
return new_response_true(request.id) // return true as jsonrpc (bool)
}
pub fn comment_list(request Request) !Response {
mut mydb := heromodels.new()!
comments := mydb.comments.list() or {
// Return proper JSON-RPC error instead of panicking
return jsonrpc.new_error(request.id, jsonrpc.RPCError{
code: -32000 // Server error
message: 'Failed to list comments'
data: 'Error occurred while retrieving comments: ${err}'
})
}
return jsonrpc.new_response(request.id, json.encode(comments))
}

View File

@@ -1,78 +0,0 @@
module rpc
import json
import freeflowuniverse.herolib.schemas.jsonrpc { Request, Response, new_response_true, new_response_u32 }
import freeflowuniverse.herolib.hero.heromodels
// Group-specific argument structures
@[params]
pub struct GroupGetArgs {
pub mut:
id u32 @[required]
}
@[params]
pub struct GroupSetArgs {
pub mut:
name string
description string
members []heromodels.GroupMember
subgroups []u32
parent_group u32
is_public bool
}
@[params]
pub struct GroupDeleteArgs {
pub mut:
id u32 @[required]
}
pub fn group_get(request Request) !Response {
payload := jsonrpc.decode_payload[GroupGetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
group := mydb.group.get(payload.id)!
return jsonrpc.new_response(request.id, json.encode(group))
}
pub fn group_set(request Request) !Response {
payload := jsonrpc.decode_payload[GroupSetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mut group_obj := mydb.group.new(
name: payload.name
description: payload.description
members: payload.members
subgroups: payload.subgroups
parent_group: payload.parent_group
is_public: payload.is_public
)!
group_obj=mydb.group.set( group_obj)!
return new_response_u32(request.id, group_obj.id)
}
pub fn group_delete(request Request) !Response {
payload := jsonrpc.decode_payload[GroupDeleteArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mydb.group.delete(payload.id)!
return new_response_true(request.id) // return true as jsonrpc (bool)
}
pub fn group_list(request Request) !Response {
mut mydb := heromodels.new()!
groups := mydb.group.list()!
return jsonrpc.new_response(request.id, json.encode(groups))
}

View File

@@ -1,91 +0,0 @@
module rpc
import json
import freeflowuniverse.herolib.schemas.jsonrpc { Request, Response, new_response_true, new_response_u32 }
import freeflowuniverse.herolib.hero.heromodels
import freeflowuniverse.herolib.hero.db
// Project-specific argument structures
@[params]
pub struct ProjectGetArgs {
pub mut:
id u32 @[required]
}
@[params]
pub struct ProjectSetArgs {
pub mut:
name string
description string
swimlanes []heromodels.Swimlane
milestones []heromodels.Milestone
issues []string
fs_files []u32
status heromodels.ProjectStatus
start_date string // Use ourtime module to convert to epoch
end_date string // Use ourtime module to convert to epoch
securitypolicy u32
tags []string
comments []db.CommentArg
}
@[params]
pub struct ProjectDeleteArgs {
pub mut:
id u32 @[required]
}
pub fn project_get(request Request) !Response {
payload := jsonrpc.decode_payload[ProjectGetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
project := mydb.project.get(payload.id)!
return jsonrpc.new_response(request.id, json.encode(project))
}
pub fn project_set(request Request) !Response {
payload := jsonrpc.decode_payload[ProjectSetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mut project_obj := mydb.project.new(
name: payload.name
description: payload.description
swimlanes: payload.swimlanes
milestones: payload.milestones
issues: payload.issues
fs_files: payload.fs_files
status: payload.status
start_date: payload.start_date
end_date: payload.end_date
securitypolicy: payload.securitypolicy
tags: payload.tags
comments: payload.comments
)!
project_obj = mydb.project.set(project_obj)!
return new_response_u32(request.id, project_obj.id)
}
pub fn project_delete(request Request) !Response {
payload := jsonrpc.decode_payload[ProjectDeleteArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mydb.project.delete(payload.id)!
return new_response_true(request.id) // return true as jsonrpc (bool)
}
pub fn project_list(request Request) !Response {
mut mydb := heromodels.new()!
projects := mydb.project.list()!
return jsonrpc.new_response(request.id, json.encode(projects))
}

View File

@@ -1,105 +0,0 @@
module rpc
import json
import freeflowuniverse.herolib.schemas.jsonrpc { Request, Response, new_response_true, new_response_u32 }
import freeflowuniverse.herolib.hero.heromodels
import freeflowuniverse.herolib.hero.db
// ProjectIssue-specific argument structures
@[params]
pub struct ProjectIssueGetArgs {
pub mut:
id u32 @[required]
}
@[params]
pub struct ProjectIssueSetArgs {
pub mut:
name string
description string
title string
project_id u32
issue_type heromodels.IssueType
priority heromodels.IssuePriority
status heromodels.IssueStatus
swimlane string
assignees []u32
reporter u32
milestone string
deadline string // Use ourtime module to convert to epoch
estimate int
fs_files []u32
parent_id u32
children []u32
securitypolicy u32
tags []string
comments []db.CommentArg
}
@[params]
pub struct ProjectIssueDeleteArgs {
pub mut:
id u32 @[required]
}
pub fn project_issue_get(request Request) !Response {
payload := jsonrpc.decode_payload[ProjectIssueGetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
project_issue := mydb.project_issue.get(payload.id)!
return jsonrpc.new_response(request.id, json.encode(project_issue))
}
pub fn project_issue_set(request Request) !Response {
payload := jsonrpc.decode_payload[ProjectIssueSetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mut project_issue_obj := mydb.project_issue.new(
name: payload.name
description: payload.description
title: payload.title
project_id: payload.project_id
issue_type: payload.issue_type
priority: payload.priority
status: payload.status
swimlane: payload.swimlane
assignees: payload.assignees
reporter: payload.reporter
milestone: payload.milestone
deadline: payload.deadline
estimate: payload.estimate
fs_files: payload.fs_files
parent_id: payload.parent_id
children: payload.children
securitypolicy: payload.securitypolicy
tags: payload.tags
comments: payload.comments
)!
project_issue_obj=mydb.project_issue.set( project_issue_obj)!
return new_response_u32(request.id, project_issue_obj.id)
}
pub fn project_issue_delete(request Request) !Response {
payload := jsonrpc.decode_payload[ProjectIssueDeleteArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mydb.project_issue.delete(payload.id)!
return new_response_true(request.id) // return true as jsonrpc (bool)
}
pub fn project_issue_list(request Request) !Response {
mut mydb := heromodels.new()!
project_issues := mydb.project_issue.list()!
return jsonrpc.new_response(request.id, json.encode(project_issues))
}

View File

@@ -1,92 +0,0 @@
module rpc
import json
import freeflowuniverse.herolib.schemas.jsonrpc { Request, Response, new_response_true, new_response_u32 }
import freeflowuniverse.herolib.hero.heromodels
// User-specific argument structures
@[params]
pub struct UserGetArgs {
pub mut:
id u32 @[required]
}
@[params]
pub struct UserSetArgs {
pub mut:
name string @[required]
description string
email string
public_key string // for encryption/signing
phone string
address string
avatar_url string
bio string
timezone string
status heromodels.UserStatus
securitypolicy u32
tags u32
comments []u32
}
@[params]
pub struct UserDeleteArgs {
pub mut:
id u32 @[required]
}
pub fn user_get(request Request) !Response {
payload := jsonrpc.decode_payload[UserGetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
user := mydb.user.get(payload.id)!
return jsonrpc.new_response(request.id, json.encode(user))
}
pub fn user_set(request Request) !Response {
payload := jsonrpc.decode_payload[UserSetArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mut user_obj := mydb.user.new(
name: payload.name
description: payload.description
email: payload.email
public_key: payload.public_key
phone: payload.phone
address: payload.address
avatar_url: payload.avatar_url
bio: payload.bio
timezone: payload.timezone
status: payload.status
securitypolicy: payload.securitypolicy
tags: payload.tags
comments: payload.comments
)!
user_obj=mydb.user.set( user_obj)!
return new_response_u32(request.id, user_obj.id)
}
pub fn user_delete(request Request) !Response {
payload := jsonrpc.decode_payload[UserDeleteArgs](request.params) or {
return jsonrpc.invalid_params
}
mut mydb := heromodels.new()!
mydb.user.delete(payload.id)!
return new_response_true(request.id) // return true as jsonrpc (bool)
}
pub fn user_list(request Request) !Response {
mut mydb := heromodels.new()!
users := mydb.user.list()!
return jsonrpc.new_response(request.id, json.encode(users))
}

View File

@@ -4,6 +4,7 @@ import json
import net.http
import veb
import freeflowuniverse.herolib.schemas.jsonrpc
import freeflowuniverse.herolib.ui.console
@['/auth/:action']
pub fn (mut server HeroServer) auth_handler(mut ctx Context, action string) !veb.Result {
@@ -88,10 +89,22 @@ pub fn (mut server HeroServer) api_handler(mut ctx Context, handler_type string)
}
// Parse JSON-RPC request
request := jsonrpc.decode_request(ctx.req.data) or {
mut request := jsonrpc.decode_request(ctx.req.data) or {
return ctx.request_error('Invalid JSON-RPC request: ${err}')
}
if request.method.count('.') == 0 {
return ctx.request_error('Invalid method format, expected actor.method')
} else if request.method.count('.') == 1 {
request.method = '${handler_type.to_lower()}.${request.method}'
} else {
if request.method.count('.') > 1 {
return ctx.request_error('Invalid method format, too many dots. ${ctx.req.method}')
}
}
console.print_debug('Handling request: ${request.method} with params: ${request.params}')
// $dbg;
// Handle the request using the OpenRPC handler
response := handler.handle(request) or { return ctx.server_error('Handler error: ${err}') }

11
lib/hero/user/user.v Normal file
View File

@@ -0,0 +1,11 @@
module user
@[heap]
pub struct UserRef {
pub mut:
id u32
public_key string
name string
email string
roles []string
}

View File

@@ -11,8 +11,9 @@ import x.json2 as json
pub struct Handler {
pub mut:
// A map where keys are method names and values are the corresponding procedure handler functions
procedures map[string]ProcedureHandler
params map[string]string
procedures map[string]ProcedureHandler
procedures_group map[string]ProcedureHandlerGroup
servercontext map[string]string
}
// ProcedureHandler is a function type that processes a JSON-RPC request payload and returns a response.
@@ -23,6 +24,8 @@ pub mut:
// If an error occurs during any of these steps, it should be returned.
pub type ProcedureHandler = fn (request Request) !Response
pub type ProcedureHandlerGroup = fn (rpcid int, servercontext map[string]string, actorname string, methodname string, params string) !Response
// new_handler creates a new JSON-RPC handler with the specified procedure handlers.
//
// Parameters:
@@ -62,13 +65,22 @@ pub fn (mut handler Handler) register_procedure_void[T](method string, function
handler.procedures[procedure.method] = procedure.handle
}
// // register_procedure registers a new procedure handler for the specified method.
// //
// // Parameters:
// // - method: The name of the method to register
// // - procedure: The procedure handler function to register
// pub fn (mut handler Handler) register_procedure(method string, procedure ProcedureHandler) {
// handler.procedures[method] = procedure
// }
// register_procedure registers a new procedure handler for the specified method.
//
// Parameters:
// - method: The name of the method to register
// - procedure: The procedure handler function to register
pub fn (mut handler Handler) register_procedure_handle(method string, procedure ProcedureHandler) {
handler.procedures[method] = procedure
// - procedure: The procedure handler group function to register
pub fn (mut handler Handler) register_api_handler(groupname string, procedure_group ProcedureHandlerGroup) {
handler.procedures_group[groupname] = procedure_group
}
pub struct Procedure[T, U] {
@@ -158,12 +170,46 @@ fn error_to_jsonrpc(err IError) !RPCError {
// Returns:
// - The JSON-RPC response as a string, or an error if processing fails
pub fn (handler Handler) handle(request Request) !Response {
if request.method.contains('.') {
parts := request.method.split('.')
mut groupname := ''
mut actorname := ''
mut methodname := ''
if parts.len == 2 {
groupname = 'default'
actorname = parts[0]
methodname = parts[1]
} else if parts.len == 3 {
groupname = parts[0]
actorname = parts[1]
methodname = parts[2]
} else {
return new_error(request.id, invalid_params)
}
procedure_group := handler.procedures_group[groupname] or {
return new_error(request.id, RPCError{
code: -32602
message: 'Could not find procedure group ${groupname} in function on rpc request.'
data: '${request}'
})
}
return procedure_group(request.id, handler.servercontext, actorname, methodname,
request.params) or {
// Return proper JSON-RPC error instead of panicking
return new_error(request.id, RPCError{
code: -32603
message: 'Error in function on rpc request.'
data: '${request}\n${err}'
})
}
}
procedure_func := handler.procedures[request.method] or {
return new_error(request.id, method_not_found)
}
// Execute the procedure handler with the request payload
return procedure_func(handler.params, request) or {
return procedure_func(request) or {
// Return proper JSON-RPC error instead of panicking
return new_error(request.id, RPCError{
code: -32603

View File

@@ -51,6 +51,7 @@ pub const internal_error = RPCError{
// RPCError represents a JSON-RPC 2.0 error object as defined in the specification.
// Error objects contain a code, message, and optional data field to provide
// more information about the error that occurred.
@[params]
pub struct RPCError {
pub mut:
// Numeric error code. Predefined codes are in the range -32768 to -32000.

View File

@@ -22,8 +22,6 @@ pub mut:
// An identifier established by the client that must be included in the response
// This is used to correlate requests with their corresponding responses
id int @[required]
handler_params map[string]string
}
// new_request creates a new JSON-RPC request with the specified method and parameters.
@@ -52,7 +50,19 @@ pub fn new_request(method string, params string) Request {
// Returns:
// - A Request object or an error if parsing fails
pub fn decode_request(data string) !Request {
return json2.decode[Request](data)!
mut r2 := json2.decode[json2.Any](data)!
mut r3 := r2.as_map()
a := r3['jsonrpc'].str()
b := r3['method'].str()
c := r3['params'].str()
d := r3['id'].int()
mut r4 := Request{
jsonrpc: a
method: b
params: c
id: d
}
return r4
}
// encode serializes the Request object into a JSON string.

View File

@@ -48,6 +48,14 @@ pub fn new_response_true(id int) Response {
}
}
pub fn new_response_ok(id int) Response {
return Response{
jsonrpc: jsonrpc_version
result: ''
id: id
}
}
pub fn new_response_false(id int) Response {
return Response{
jsonrpc: jsonrpc_version
@@ -88,6 +96,9 @@ pub fn new_error_response(id int, error RPCError) Response {
}
}
// decode_response parses a JSON string into a Response object.
// This function handles the complex validation rules for JSON-RPC responses.
//