diff --git "a/aiprompts/ourdb\\.md" b/aiprompts/ourdb.md similarity index 100% rename from "aiprompts/ourdb\\.md" rename to aiprompts/ourdb.md diff --git a/aiprompts/ourtime.md b/aiprompts/ourtime.md new file mode 120000 index 000000000..f4ff420c1 --- /dev/null +++ b/aiprompts/ourtime.md @@ -0,0 +1 @@ +../crystallib/data/ourtime/readme.md \ No newline at end of file diff --git a/crystallib/data/encoder/readme.md b/crystallib/data/encoder/readme.md index c5e6714df..23a8039e8 100644 --- a/crystallib/data/encoder/readme.md +++ b/crystallib/data/encoder/readme.md @@ -245,22 +245,3 @@ The encoded data follows this format: - u16 count of entries - encoded key-value pairs -### Struct Reflection - -The encoder uses V's compile-time reflection to automatically handle structs: - -```v -pub fn encode[T](obj T) ![]u8 { - mut d := new() - $for field in T.fields { - // Encode each field based on its type - $if field.typ is string { - d.add_string(obj.$(field.name)) - } $else $if field.typ is int { - d.add_int(obj.$(field.name)) - } - // ... other types - } - return d.data -} -``` diff --git a/crystallib/data/ourtime/readme.md b/crystallib/data/ourtime/readme.md index 3ff3d7924..ae134c19e 100644 --- a/crystallib/data/ourtime/readme.md +++ b/crystallib/data/ourtime/readme.md @@ -1,10 +1,186 @@ -# Ourtime +# OurTime Module + +The OurTime module provides a flexible and user-friendly way to work with time in V. It supports both relative and absolute time formats, making it easy to handle various time-related operations. + +## Features + +- Create time objects from various string formats +- Support for relative time expressions +- Support for absolute time formats +- Unix timestamp handling +- Time formatting utilities +- Time warping capabilities + +## Usage + +### Basic Usage ```v import freeflowuniverse.crystallib.data.ourtime +// Create time object for current time +mut t := ourtime.now() + +// Create from string +t2 := ourtime.new('2022-12-05 20:14:35')! + +// Get formatted string +println(t2.str()) // Output: 2022-12-05 20:14 + +// Get unix timestamp +println(t2.unix()) // Output: 1670271275 +``` + +### Time Formats + +#### Relative Time Format + +Relative time expressions use the following period indicators: +- `s`: seconds +- `h`: hours +- `d`: days +- `w`: weeks +- `M`: months +- `Q`: quarters +- `Y`: years + +Examples: +```v +// Create time object with relative time +mut t := ourtime.new('+1w +2d -4h')! // 1 week forward, 2 days forward, 4 hours back + +// Warp existing time object +mut t2 := ourtime.now() +t2.warp('+1h')! // Move 1 hour forward +``` + +#### Absolute Time Format + +Supported date formats: +- `YYYY-MM-DD HH:mm:ss` +- `YYYY-MM-DD HH:mm` +- `YYYY-MM-DD` +- `DD-MM-YYYY` (YYYY must be 4 digits) +- Also supports '/' instead of '-' for dates + +Examples: +```v +// Various absolute time formats +t1 := ourtime.new('2022-12-05 20:14:35')! +t2 := ourtime.new('2022-12-05')! // Sets time to 00:00:00 +t3 := ourtime.new('05-12-2022')! // DD-MM-YYYY format +``` + +### Methods + +#### Creation Methods + +```v +// Create for current time now := ourtime.now() -println(now.key()) +// Create from string +t := ourtime.new('2022-12-05 20:14:35')! + +// Create from unix timestamp +t2 := ourtime.new_from_epoch(1670271275) +``` + +#### Formatting Methods + +```v +mut t := ourtime.now() + +// Get as YYYY-MM-DD HH:mm format +println(t.str()) + +// Get as YYYY-MM-DD format +println(t.day()) + +// Get as formatted key (YYYY_MM_DD_HH_mm_ss) +println(t.key()) + +// Get as markdown formatted string +println(t.md()) +``` + +#### Time Operations + +```v +mut t := ourtime.now() + +// Move time forward or backward +t.warp('+1h')! // 1 hour forward +t.warp('-30m')! // 30 minutes backward +t.warp('+1w +2d -4h')! // Complex time warp + +// Get unix timestamp +unix := t.unix() + +// Get as integer +i := t.int() + +// Check if time is empty (zero) +is_empty := t.empty() +``` + +## Examples + +### Working with Relative Time + +```v +mut t := ourtime.now() + +// Add time periods +t.warp('+1w')! // Add 1 week +t.warp('+2d')! // Add 2 days +t.warp('-4h')! // Subtract 4 hours + +// Complex time warping +t.warp('+1Y -2Q +2M +4h -60s')! // Add 1 year, subtract 2 quarters, add 2 months, add 4 hours, subtract 60 seconds +``` + +### Working with Absolute Time + +```v +// Create time from absolute format +t1 := ourtime.new('2022-12-05 20:14:35')! +println(t1.str()) // 2022-12-05 20:14 + +// Create time from date only +t2 := ourtime.new('2022-12-05')! +println(t2.str()) // 2022-12-05 00:00 + +// Get different formats +println(t1.day()) // 2022-12-05 +println(t1.key()) // 2022_12_05_20_14_35 +``` + +### Time Comparisons and Checks + +```v +mut t := ourtime.now() + +// Check if time is empty +if t.empty() { + t.now() // Set to current time if empty +} + +// Get unix timestamp for comparisons +unix := t.unix() +``` + +## Error Handling + +All time parsing operations that can fail return a Result type, so they should be called with `!` or handled with `or` blocks: + +```v +// Using ! operator (panics on error) +t1 := ourtime.new('2022-12-05')! -``` \ No newline at end of file +// Using or block for error handling +t2 := ourtime.new('invalid-date') or { + println('Error parsing date: ${err}') + ourtime.now() // fallback to current time +} +``` diff --git a/crystallib/servers/rpcsocket/model_agent.v b/crystallib/servers/rpcsocket/model_agent.v new file mode 100644 index 000000000..db1770970 --- /dev/null +++ b/crystallib/servers/rpcsocket/model_agent.v @@ -0,0 +1,59 @@ +module rpcsocket + +import freeflowuniverse.crystallib.data.ourtime +import freeflowuniverse.crystallib.data.encoder + +pub struct Agent { +pub mut: + id u32 @[required] + name string @[required] + description string + ipaddr string + pubkey string + location string + create_date ourtime.OurTime +} + +// encode encodes the Agent struct to binary format +pub fn (a Agent) encode() ![]u8 { + mut e := encoder.new() + + // Add version byte (v1) + e.add_u8(1) + + // Encode all fields + e.add_u32(a.id) + e.add_string(a.name) + e.add_string(a.description) + e.add_string(a.ipaddr) + e.add_string(a.pubkey) + e.add_string(a.location) + e.add_u64(u64(a.create_date.unixt)) + + return e.data +} + +// decode decodes binary data into an Agent struct +pub fn (mut a Agent) decode(data []u8) ! { + if data.len == 0 { + return error('empty data') + } + + mut d := encoder.decoder_new(data) + + // Read and verify version + version := d.get_u8() + if version != 1 { + return error('unsupported version ${version}') + } + + // Decode all fields in same order as encoded + a.id = d.get_u32() + a.name = d.get_string() + a.description = d.get_string() + a.ipaddr = d.get_string() + a.pubkey = d.get_string() + a.location = d.get_string() + unix := d.get_u64() + a.create_date = ourtime.OurTime{unixt: i64(unix)} +} diff --git a/crystallib/servers/rpcsocket/model_executor.v b/crystallib/servers/rpcsocket/model_executor.v new file mode 100644 index 000000000..281fd8e9d --- /dev/null +++ b/crystallib/servers/rpcsocket/model_executor.v @@ -0,0 +1,247 @@ +module rpcsocket + +import freeflowuniverse.crystallib.core.texttools { name_fix } +import freeflowuniverse.crystallib.data.encoder + +pub struct Executor { +pub mut: + id u32 @[required] + name string @[required] + description string + state ExecutorState @[required] + actors map[string]&Actor +} + +pub enum ExecutorState { + init + running + error + halted +} + +pub fn (mut e Executor) add_actor(actor &Actor) ! { + name_fixed := name_fix(actor.name) + if name_fixed in e.actors { + return error('Actor with name ${name_fixed} already exists') + } + e.actors[name_fixed] = actor +} + +pub fn (e Executor) get_actor(name string) !&Actor { + name_fixed := name_fix(name) + if name_fixed !in e.actors { + return error('Actor with name ${name_fixed} not found') + } + return e.actors[name_fixed] or { return error('Actor not found') } +} + +@[heap] +pub struct Actor { +pub mut: + name string @[required] + executor string @[required] // References Executor.name + description string +mut: + actions map[string]&Action +} + +pub fn (mut a Actor) add_action(action &Action) ! { + name_fixed := name_fix(action.name) + if name_fixed in a.actions { + return error('Action with name ${name_fixed} already exists') + } + if action.actor != a.name { + return error('Action ${action.name} references different actor: ${action.actor}') + } + a.actions[name_fixed] = action +} + +pub fn (a Actor) get_action(name string) !&Action { + name_fixed := name_fix(name) + if name_fixed !in a.actions { + return error('Action with name ${name_fixed} not found') + } + return a.actions[name_fixed] or { return error('Action not found') } +} + +@[heap] +pub struct Action { +pub mut: + id u32 @[required] + name string @[required] + actor string @[required] + description string + nrok int + nrfailed int + code string +} + +// encode encodes the Action struct to binary format +pub fn (a Action) encode() ![]u8 { + mut e := encoder.new() + + // Add version byte (v1) + e.add_u8(1) + + // Encode all fields + e.add_u32(a.id) + e.add_string(a.name) + e.add_string(a.actor) + e.add_string(a.description) + e.add_int(a.nrok) + e.add_int(a.nrfailed) + e.add_string(a.code) + + return e.data +} + +// decode decodes binary data into an Action struct +pub fn (mut a Action) decode(data []u8) ! { + if data.len == 0 { + return error('empty data') + } + + mut d := encoder.decoder_new(data) + + // Read and verify version + version := d.get_u8() + if version != 1 { + return error('unsupported version ${version}') + } + + // Decode all fields + a.id = d.get_u32() + a.name = d.get_string() + a.actor = d.get_string() + a.description = d.get_string() + a.nrok = d.get_int() + a.nrfailed = d.get_int() + a.code = d.get_string() +} + +// encode encodes the Actor struct to binary format +pub fn (a Actor) encode() ![]u8 { + mut e := encoder.new() + + // Add version byte (v1) + e.add_u8(1) + + // Encode basic fields + e.add_string(a.name) + e.add_string(a.executor) + e.add_string(a.description) + + // Encode actions map + e.add_u16(u16(a.actions.len)) + for key, action in a.actions { + e.add_string(key) + encoded_action := action.encode()! + e.add_u16(u16(encoded_action.len)) + e.data << encoded_action + } + + return e.data +} + +// decode decodes binary data into an Actor struct +pub fn (mut a Actor) decode(data []u8) ! { + if data.len == 0 { + return error('empty data') + } + + mut d := encoder.decoder_new(data) + + // Read and verify version + version := d.get_u8() + if version != 1 { + return error('unsupported version ${version}') + } + + // Decode basic fields + a.name = d.get_string() + a.executor = d.get_string() + a.description = d.get_string() + + // Decode actions map + actions_len := d.get_u16() + for _ in 0 .. actions_len { + key := d.get_string() + action_data_len := d.get_u16() + mut action_data := []u8{} + for _ in 0 .. action_data_len { + action_data << d.get_u8() + } + + mut action := &Action{ + id: 0 + name: '' + actor: '' + } + action.decode(action_data)! + a.actions[key] = action + } +} + +// encode encodes the Executor struct to binary format +pub fn (e Executor) encode() ![]u8 { + mut enc := encoder.new() + + // Add version byte (v1) + enc.add_u8(1) + + // Encode basic fields + enc.add_u32(e.id) + enc.add_string(e.name) + enc.add_string(e.description) + unsafe { enc.add_u8(u8(e.state)) } + + // Encode actors map + enc.add_u16(u16(e.actors.len)) + for key, actor in e.actors { + enc.add_string(key) + encoded_actor := actor.encode()! + enc.add_u16(u16(encoded_actor.len)) + enc.data << encoded_actor + } + + return enc.data +} + +// decode decodes binary data into an Executor struct +pub fn (mut e Executor) decode(data []u8) ! { + if data.len == 0 { + return error('empty data') + } + + mut d := encoder.decoder_new(data) + + // Read and verify version + version := d.get_u8() + if version != 1 { + return error('unsupported version ${version}') + } + + // Decode basic fields + e.id = d.get_u32() + e.name = d.get_string() + e.description = d.get_string() + unsafe { e.state = ExecutorState(d.get_u8()) } + + // Decode actors map + actors_len := d.get_u16() + for _ in 0 .. actors_len { + key := d.get_string() + actor_data_len := d.get_u16() + mut actor_data := []u8{} + for _ in 0 .. actor_data_len { + actor_data << d.get_u8() + } + + mut actor := &Actor{ + name: '' + executor: '' + } + actor.decode(actor_data)! + e.actors[key] = actor + } +} diff --git a/crystallib/servers/rpcsocket/model_executor_test.v b/crystallib/servers/rpcsocket/model_executor_test.v new file mode 100644 index 000000000..b369aff07 --- /dev/null +++ b/crystallib/servers/rpcsocket/model_executor_test.v @@ -0,0 +1,228 @@ +module rpcsocket + +fn test_executor_creation() { + mut executor := Executor{ + id: 1 + name: 'test_executor' + state: .init + } + assert executor.id == 1 + assert executor.name == 'test_executor' + assert executor.state == .init + assert executor.actors.len == 0 +} + +fn test_executor_add_get_actor() ! { + mut executor := Executor{ + id: 1 + name: 'test_executor' + state: .init + } + + mut actor := &Actor{ + name: 'test_actor' + executor: 'test_executor' + description: 'Test actor description' + } + + executor.add_actor(actor)! + retrieved_actor := executor.get_actor('test_actor')! + + assert retrieved_actor.name == 'test_actor' + assert retrieved_actor.executor == 'test_executor' + assert retrieved_actor.description == 'Test actor description' +} + +fn test_executor_duplicate_actor() ! { + mut executor := Executor{ + id: 1 + name: 'test_executor' + state: .init + } + + mut actor1 := &Actor{ + name: 'test_actor' + executor: 'test_executor' + } + + mut actor2 := &Actor{ + name: 'test_actor' + executor: 'test_executor' + } + + executor.add_actor(actor1)! + if _ := executor.add_actor(actor2) { + assert false, 'Should not allow duplicate actor names' + } +} + +fn test_actor_add_get_action() ! { + mut actor := &Actor{ + name: 'test_actor' + executor: 'test_executor' + } + + mut action := &Action{ + id: 1 + name: 'test_action' + actor: 'test_actor' + description: 'Test action description' + } + + actor.add_action(action)! + retrieved_action := actor.get_action('test_action')! + + assert retrieved_action.id == 1 + assert retrieved_action.name == 'test_action' + assert retrieved_action.actor == 'test_actor' + assert retrieved_action.description == 'Test action description' +} + +fn test_action_encode_decode() ! { + original := Action{ + id: 1 + name: 'test_action' + actor: 'test_actor' + description: 'Test description' + nrok: 5 + nrfailed: 2 + code: 'test code' + } + + encoded := original.encode()! + mut decoded := Action{ + id: 0 + name: '' + actor: '' + } + decoded.decode(encoded)! + + assert decoded.id == original.id + assert decoded.name == original.name + assert decoded.actor == original.actor + assert decoded.description == original.description + assert decoded.nrok == original.nrok + assert decoded.nrfailed == original.nrfailed + assert decoded.code == original.code +} + +fn test_actor_encode_decode() ! { + mut original := Actor{ + name: 'test_actor' + executor: 'test_executor' + description: 'Test description' + } + + mut action := &Action{ + id: 1 + name: 'test_action' + actor: 'test_actor' + description: 'Test action' + } + original.add_action(action)! + + encoded := original.encode()! + mut decoded := Actor{ + name: '' + executor: '' + } + decoded.decode(encoded)! + + assert decoded.name == original.name + assert decoded.executor == original.executor + assert decoded.description == original.description + assert decoded.actions.len == original.actions.len + + decoded_action := decoded.get_action('test_action')! + assert decoded_action.id == action.id + assert decoded_action.name == action.name + assert decoded_action.actor == action.actor +} + +fn test_executor_encode_decode() ! { + mut original := Executor{ + id: 1 + name: 'test_executor' + description: 'Test description' + state: .running + } + + mut actor := &Actor{ + name: 'test_actor' + executor: 'test_executor' + description: 'Test actor' + } + + mut action := &Action{ + id: 1 + name: 'test_action' + actor: 'test_actor' + description: 'Test action' + } + + actor.add_action(action)! + original.add_actor(actor)! + + encoded := original.encode()! + mut decoded := Executor{ + id: 0 + name: '' + state: .init + } + decoded.decode(encoded)! + + assert decoded.id == original.id + assert decoded.name == original.name + assert decoded.description == original.description + assert decoded.state == original.state + assert decoded.actors.len == original.actors.len + + decoded_actor := decoded.get_actor('test_actor')! + assert decoded_actor.name == actor.name + assert decoded_actor.executor == actor.executor + + decoded_action := decoded_actor.get_action('test_action')! + assert decoded_action.id == action.id + assert decoded_action.name == action.name + assert decoded_action.actor == action.actor +} + +fn test_invalid_actor_reference() ! { + mut actor := &Actor{ + name: 'test_actor' + executor: 'test_executor' + } + + mut action := &Action{ + id: 1 + name: 'test_action' + actor: 'different_actor' // Incorrect actor reference + } + + if _ := actor.add_action(action) { + assert false, 'Should not allow action with incorrect actor reference' + } +} + +fn test_get_nonexistent_actor() ! { + mut executor := Executor{ + id: 1 + name: 'test_executor' + state: .init + } + + if _ := executor.get_actor('nonexistent') { + assert false, 'Should not find nonexistent actor' + } +} + +fn test_get_nonexistent_action() ! { + mut actor := &Actor{ + name: 'test_actor' + executor: 'test_executor' + } + + if _ := actor.get_action('nonexistent') { + assert false, 'Should not find nonexistent action' + } +} diff --git a/crystallib/servers/rpcsocket/model_helper.v b/crystallib/servers/rpcsocket/model_helper.v new file mode 100644 index 000000000..16bbf2d70 --- /dev/null +++ b/crystallib/servers/rpcsocket/model_helper.v @@ -0,0 +1,8 @@ +module rpcsocket + +import time + +// Helper function to get current Unix timestamp +pub fn now() i64 { + return time.now().unix() +} diff --git a/crystallib/servers/rpcsocket/model_job.v b/crystallib/servers/rpcsocket/model_job.v new file mode 100644 index 000000000..f421ce2ff --- /dev/null +++ b/crystallib/servers/rpcsocket/model_job.v @@ -0,0 +1,117 @@ +module rpcsocket + +import json +import freeflowuniverse.crystallib.data.ourtime +import freeflowuniverse.crystallib.data.encoder + +pub struct Job { +pub mut: + id u32 @[required] + actor string @[required] + action string @[required] + params string // JSON string + job_type string @[required] + create_date ourtime.OurTime @[required] + schedule_date ourtime.OurTime @[required] + finish_date ourtime.OurTime + locked_until ourtime.OurTime + completed bool + state JobState @[required] + error string + recurring string + deadline ourtime.OurTime + signature string + executor u32 + agent u32 // References Agent.name +} + +// Get params as a JSON object +pub fn (j Job) params_get[T]() !T { + if j.params == '' { + return error('No params set') + } + return json.decode(T, j.params)! +} + +// Set params from a JSON-serializable object +pub fn (mut j Job) params_set[T](params T) ! { + j.params = json.encode(params) +} + +pub enum JobState { + init + running + error + halted + completed + cancelled +} + +// encode encodes the Job struct to binary format +pub fn (j Job) encode() ![]u8 { + mut e := encoder.new() + + // Add version byte (v1) + e.add_u8(1) + + // Encode all fields + e.add_u32(j.id) + e.add_string(j.actor) + e.add_string(j.action) + e.add_string(j.params) + e.add_string(j.job_type) + + // Encode OurTime fields as unix timestamps + e.add_u64(u64(j.create_date.unixt)) + e.add_u64(u64(j.schedule_date.unixt)) + e.add_u64(u64(j.finish_date.unixt)) + e.add_u64(u64(j.locked_until.unixt)) + e.add_u64(u64(j.deadline.unixt)) + + e.add_u8(u8(if j.completed { 1 } else { 0 })) + unsafe { e.add_u8(u8(j.state)) } + e.add_string(j.error) + e.add_string(j.recurring) + e.add_string(j.signature) + e.add_u32(j.executor) + e.add_u32(j.agent) + + return e.data +} + +// decode decodes binary data into a Job struct +pub fn (mut j Job) decode(data []u8) ! { + if data.len == 0 { + return error('empty data') + } + + mut d := encoder.decoder_new(data) + + // Read and verify version + version := d.get_u8() + if version != 1 { + return error('unsupported version ${version}') + } + + // Decode all fields + j.id = d.get_u32() + j.actor = d.get_string() + j.action = d.get_string() + j.params = d.get_string() + j.job_type = d.get_string() + + // Decode OurTime fields from unix timestamps + j.create_date = ourtime.OurTime{unixt: i64(d.get_u64())} + j.schedule_date = ourtime.OurTime{unixt: i64(d.get_u64())} + j.finish_date = ourtime.OurTime{unixt: i64(d.get_u64())} + j.locked_until = ourtime.OurTime{unixt: i64(d.get_u64())} + j.deadline = ourtime.OurTime{unixt: i64(d.get_u64())} + + j.completed = d.get_u8() == 1 + unsafe { j.state = JobState(d.get_u8()) } + j.error = d.get_string() + j.recurring = d.get_string() + j.signature = d.get_string() + j.executor = d.get_u32() + j.agent = d.get_u32() +} diff --git a/crystallib/servers/rpcsocket/model_job_log.v b/crystallib/servers/rpcsocket/model_job_log.v new file mode 100644 index 000000000..9e69c7a1a --- /dev/null +++ b/crystallib/servers/rpcsocket/model_job_log.v @@ -0,0 +1,55 @@ +module rpcsocket + +import freeflowuniverse.crystallib.data.ourtime +import freeflowuniverse.crystallib.data.encoder + +pub struct JobLog { +pub mut: + id u32 @[required] + job string @[required] // References Job ID + log_sequence int @[required] + message string @[required] + category string @[required] + log_time ourtime.OurTime @[required] +} + +// encode encodes the JobLog struct to binary format +pub fn (j JobLog) encode() ![]u8 { + mut e := encoder.new() + + // Add version byte (v1) + e.add_u8(1) + + // Encode all fields + e.add_u32(j.id) + e.add_string(j.job) + e.add_int(j.log_sequence) + e.add_string(j.message) + e.add_string(j.category) + e.add_u64(u64(j.log_time.unixt)) + + return e.data +} + +// decode decodes binary data into a JobLog struct +pub fn (mut j JobLog) decode(data []u8) ! { + if data.len == 0 { + return error('empty data') + } + + mut d := encoder.decoder_new(data) + + // Read and verify version + version := d.get_u8() + if version != 1 { + return error('unsupported version ${version}') + } + + // Decode all fields + j.id = d.get_u32() + j.job = d.get_string() + j.log_sequence = d.get_int() + j.message = d.get_string() + j.category = d.get_string() + j.log_time = ourtime.OurTime{unixt: i64(d.get_u64())} +} diff --git a/crystallib/servers/rpcsocket/model_job_test.v b/crystallib/servers/rpcsocket/model_job_test.v new file mode 100644 index 000000000..891480627 --- /dev/null +++ b/crystallib/servers/rpcsocket/model_job_test.v @@ -0,0 +1,92 @@ +module rpcsocket + +import time +import freeflowuniverse.crystallib.data.ourtime + +// Test struct for params +pub struct TestParams { + key string + value int +} + +fn test_job_encode_decode() ! { + // Create test data + now := time.now() + mut original_job := Job{ + id: 1234 + actor: 'test_actor' + action: 'test_action' + params: '{"key": "value"}' + job_type: 'test_type' + create_date: ourtime.OurTime{unixt: now.unix()} + schedule_date: ourtime.OurTime{unixt: now.unix() + 3600} // 1 hour later + finish_date: ourtime.OurTime{unixt: now.unix() + 7200} // 2 hours later + locked_until: ourtime.OurTime{unixt: now.unix() + 300} // 5 minutes later + completed: true + state: .completed + error: '' + recurring: '0 0 * * *' // daily at midnight + deadline: ourtime.OurTime{unixt: now.unix() + 86400} // 24 hours later + signature: 'test_signature' + executor: 5678 + agent: 9012 + } + + // Encode to binary + encoded := original_job.encode()! + + // Decode back to Job + mut decoded_job := Job{ + id: 0 + actor: '' + action: '' + job_type: '' + create_date: ourtime.OurTime{unixt: 0} + schedule_date: ourtime.OurTime{unixt: 0} + state: .init + } + decoded_job.decode(encoded)! + + // Compare all fields + assert original_job.id == decoded_job.id + assert original_job.actor == decoded_job.actor + assert original_job.action == decoded_job.action + assert original_job.params == decoded_job.params + assert original_job.job_type == decoded_job.job_type + assert original_job.create_date.unixt == decoded_job.create_date.unixt + assert original_job.schedule_date.unixt == decoded_job.schedule_date.unixt + assert original_job.finish_date.unixt == decoded_job.finish_date.unixt + assert original_job.locked_until.unixt == decoded_job.locked_until.unixt + assert original_job.completed == decoded_job.completed + assert original_job.state == decoded_job.state + assert original_job.error == decoded_job.error + assert original_job.recurring == decoded_job.recurring + assert original_job.deadline.unixt == decoded_job.deadline.unixt + assert original_job.signature == decoded_job.signature + assert original_job.executor == decoded_job.executor + assert original_job.agent == decoded_job.agent +} + +fn test_job_params() ! { + mut job := Job{ + id: 1 + actor: 'test' + action: 'test' + job_type: 'test' + create_date: ourtime.OurTime{unixt: time.now().unix()} + schedule_date: ourtime.OurTime{unixt: time.now().unix()} + state: .init + } + + // Test setting params + test_params := TestParams{ + key: 'test_key' + value: 42 + } + job.params_set(test_params)! + + // Test getting params + decoded_params := job.params_get[TestParams]()! + assert decoded_params.key == test_params.key + assert decoded_params.value == test_params.value +} diff --git a/crystallib/servers/rpcsocket/model_signature_request.v b/crystallib/servers/rpcsocket/model_signature_request.v new file mode 100644 index 000000000..af5b4cfec --- /dev/null +++ b/crystallib/servers/rpcsocket/model_signature_request.v @@ -0,0 +1,55 @@ +module rpcsocket + +import freeflowuniverse.crystallib.data.ourtime +import freeflowuniverse.crystallib.data.encoder + +pub struct SignatureRequest { +pub mut: + id u32 @[required] + job u32 @[required] // References Job ID + pubkey string @[required] + signature string + date ourtime.OurTime + verified bool +} + +// encode encodes the SignatureRequest struct to binary format +pub fn (s SignatureRequest) encode() ![]u8 { + mut e := encoder.new() + + // Add version byte (v1) + e.add_u8(1) + + // Encode all fields + e.add_u32(s.id) + e.add_u32(s.job) + e.add_string(s.pubkey) + e.add_string(s.signature) + e.add_u64(u64(s.date.unixt)) + e.add_u8(u8(if s.verified { 1 } else { 0 })) + + return e.data +} + +// decode decodes binary data into a SignatureRequest struct +pub fn (mut s SignatureRequest) decode(data []u8) ! { + if data.len == 0 { + return error('empty data') + } + + mut d := encoder.decoder_new(data) + + // Read and verify version + version := d.get_u8() + if version != 1 { + return error('unsupported version ${version}') + } + + // Decode all fields + s.id = d.get_u32() + s.job = d.get_u32() + s.pubkey = d.get_string() + s.signature = d.get_string() + s.date = ourtime.OurTime{unixt: i64(d.get_u64())} + s.verified = d.get_u8() == 1 +}