This commit is contained in:
2025-07-30 22:12:19 +02:00
parent bfe3110894
commit bfaa2446cd
51 changed files with 1008 additions and 793 deletions

9
.kilocode/mcp.json Normal file
View File

@@ -0,0 +1,9 @@
{
"chat.mcp.discovery.enabled": true,
"mcpServers": {
"VSCode": {
"type": "internal",
"tools": ["chat_send", "apply_edits", "read_file", "write_file", "get_file_tree"]
}
}
}

View File

View File

@@ -1,39 +0,0 @@
generate specs for /Users/despiegk/code/github/freeflowuniverse/herolib/lib/circles/actions
use mcp
get the output of it un actions/specs.v
then use these specs.v
to generate play command instructions see @3_heroscript_vlang.md
this play command gets heroscript in and will then call the methods for actions as are ONLY in @lib/circles/actions/db
so the play only calls the methods in @lib/circles/actions/db
# put the play commands in
/Users/despiegk/code/github/freeflowuniverse/herolib/lib/circles/actions/play
do one file in the module per action
each method is an action
put them all on one Struct called Player
in this Player we have a method per action
Player has a property called actor: which is the name of the actor as is used in the heroscript
Player has also a output called return format which is enum for heroscript or json
input of the method - action is a params object
on player there is a method play which takes the text as input or playbook
if text then playbook is created
then we walk over all actions
all the ones starting with actions in this case are given to the right method

View File

@@ -0,0 +1,224 @@
module hero_db
import json
import freeflowuniverse.herolib.clients.postgresql_client
import freeflowuniverse.herolib.core.texttools
import time
// Generic database interface for Hero root objects
pub struct HeroDB[T] {
db_client &postgresql_client.PostgresClient
table_name string
}
// Initialize a new HeroDB instance for a specific type
pub fn new[T](client &postgresql_client.PostgresClient) HeroDB[T] {
mut table_name := '${texttools.snake_case(T.name)}s'
// Map dirname from module path
module_path := T.name.split('.')
if module_path.len >= 2 {
dirname := texttools.snake_case(module_path[module_path.len - 2])
table_name = '${dirname}_${texttools.snake_case(T.name)}'
}
return HeroDB[T]{
db_client: client
table_name: table_name
}
}
// Ensure table exists with proper schema
pub fn (mut self HeroDB[T]) ensure_table() ! {
// Get index fields from struct reflection
index_fields := self.get_index_fields()
// Build index column definitions
mut index_cols := []string{}
for field in index_fields {
index_cols << '${field} varchar(255)'
}
// Create table with JSON storage
create_sql := '
CREATE TABLE IF NOT EXISTS ${self.table_name} (
id serial PRIMARY KEY,
${index_cols.join(', ')},
data jsonb NOT NULL,
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp DEFAULT CURRENT_TIMESTAMP
)
'
self.db_client.exec(create_sql)!
// Create indexes on index fields
for field in index_fields {
index_sql := 'CREATE INDEX IF NOT EXISTS idx_${self.table_name}_${field} ON ${self.table_name}(${field})'
self.db_client.exec(index_sql)!
}
}
// Get index fields marked with @[index] from struct
fn (self HeroDB[T]) get_index_fields() []string {
mut fields := []string{}
$for field in T.fields {
$if field.attributes.len > 0 {
$for attr in field.attributes {
$if attr == 'index' {
fields << texttools.snake_case(field.name)
}
}
}
}
return fields
}
// Save object to database
pub fn (mut self HeroDB[T]) save(obj &T) ! {
// Get index values from object
index_data := self.extract_index_values(obj)
// Serialize to JSON
json_data := json.encode_pretty(obj)
// Check if object already exists
mut query := 'SELECT id FROM ${self.table_name} WHERE '
mut params := []string{}
// Build WHERE clause for unique lookup
for key, value in index_data {
params << '${key} = \'${value}\''
}
query += params.join(' AND ')
existing := self.db_client.exec(query)!
if existing.len > 0 {
// Update existing record
id := existing[0].vals[0].int()
update_sql := '
UPDATE ${self.table_name}
SET data = \$1, updated_at = CURRENT_TIMESTAMP
WHERE id = \$2
'
self.db_client.exec(update_sql.replace('\$1', "'${json_data}'").replace('\$2', id.str()))!
} else {
// Insert new record
mut columns := []string{}
mut values := []string{}
// Add index columns
for key, value in index_data {
columns << key
values << "'${value}'"
}
// Add JSON data
columns << 'data'
values << "'${json_data}'"
insert_sql := '
INSERT INTO ${self.table_name} (${columns.join(', ')})
VALUES (${values.join(', ')})
'
self.db_client.exec(insert_sql)!
}
}
// Get object by index values
pub fn (mut self HeroDB[T]) get_by_index(index_values map[string]string) !&T {
mut query := 'SELECT data FROM ${self.table_name} WHERE '
mut params := []string{}
for key, value in index_values {
params << '${key} = \'${value}\''
}
query += params.join(' AND ')
rows := self.db_client.exec(query)!
if rows.len == 0 {
return error('${T.name} not found with index values: ${index_values}')
}
json_data := rows[0].vals[0].str()
mut obj := json.decode(T, json_data) or {
return error('Failed to decode JSON: ${err}')
}
return &obj
}
// Get all objects
pub fn (mut self HeroDB[T]) get_all() ![]&T {
query := 'SELECT data FROM ${self.table_name} ORDER BY id DESC'
rows := self.db_client.exec(query)!
mut results := []&T{}
for row in rows {
json_data := row.vals[0].str()
obj := json.decode(T, json_data) or {
continue // Skip invalid JSON
}
results << &obj
}
return results
}
// Search by index field
pub fn (mut self HeroDB[T]) search_by_index(field_name string, value string) ![]&T {
query := 'SELECT data FROM ${self.table_name} WHERE ${field_name} = \'${value}\' ORDER BY id DESC'
rows := self.db_client.exec(query)!
mut results := []&T{}
for row in rows {
json_data := row.vals[0].str()
obj := json.decode(T, json_data) or {
continue
}
results << &obj
}
return results
}
// Delete by index values
pub fn (mut self HeroDB[T]) delete_by_index(index_values map[string]string) ! {
mut query := 'DELETE FROM ${self.table_name} WHERE '
mut params := []string{}
for key, value in index_values {
params << '${key} = \'${value}\''
}
query += params.join(' AND ')
self.db_client.exec(query)!
}
// Helper to extract index values from object
fn (self HeroDB[T]) extract_index_values(obj &T) map[string]string {
mut index_data := map[string]string{}
$for field in T.fields {
$if field.attributes.len > 0 {
$for attr in field.attributes {
$if attr == 'index' {
field_name := texttools.snake_case(field.name)
$if field.typ is string {
value := obj.$(field.name).str()
index_data[field_name] = value
} $else $if field.typ is u32 || field.typ is u64 {
value := obj.$(field.name).str()
index_data[field_name] = value
} $else {
// Convert other types to string
value := obj.$(field.name).str()
index_data[field_name] = value
}
}
}
}
}
return index_data
}

View File

@@ -0,0 +1,36 @@
```v
// Example usage:
// Initialize database client
mut db_client := postgresql_client.get(name: "default")!
// Create HeroDB for Circle type
mut circle_db := hero_db.new[circle.Circle](db_client)!
circle_db.ensure_table()!
// Create and save a circle
mut my_circle := circle.Circle{
name: "Tech Community"
description: "A community for tech enthusiasts"
domain: "tech.example.com"
config: circle.CircleConfig{
max_members: 1000
allow_guests: true
auto_approve: false
theme: "modern"
}
status: circle.CircleStatus.active
}
circle_db.save(&my_circle)!
// Retrieve the circle
retrieved_circle := circle_db.get_by_index({
"domain": "tech.example.com"
})!
// Search circles by status
active_circles := circle_db.search_by_index("status", "active")!
```

View File

@@ -3,7 +3,6 @@ module gov
import freeflowuniverse.herolib.hero.models.core
// CommitteeMember represents a member of a committee
pub struct CommitteeMember {
pub struct CommitteeMember {
core.Base
pub mut:

View File

@@ -2,7 +2,6 @@ module governance
import freeflowuniverse.herolib.hero.models.core
pub struct GovernanceActivity {
core.Base
pub mut:

View File

@@ -45,7 +45,6 @@ pub mut:
signature_date u64 // Unix timestamp when contract was signed
version string // Version identifier for contract revisions
parent_contract_id ?u32 // Optional reference to parent contract for amendments @[index]

View File

@@ -16,9 +16,7 @@ pub struct TocEntry {
// Book represents a Book library item (collection of markdown pages with TOC)
pub struct Book {
core.Base
// Title of the book
core.Base // Title of the book
title string @[index]
// Optional description of the book

View File

@@ -4,9 +4,7 @@ import freeflowuniverse.herolib.hero.models.core
// Collection represents a collection of library items
pub struct Collection {
core.Base
// Title of the collection
core.Base // Title of the collection
title string @[index]
// Optional description of the collection

View File

@@ -4,9 +4,7 @@ import freeflowuniverse.herolib.hero.models.core
// Image represents an Image library item
pub struct Image {
core.Base
// Title of the image
core.Base // Title of the image
title string @[index]
// Optional description of the image

View File

@@ -4,9 +4,7 @@ import freeflowuniverse.herolib.hero.models.core
// Markdown represents a Markdown document library item
pub struct Markdown {
core.Base
// Title of the document
core.Base // Title of the document
title string @[index]
// Optional description of the document

View File

@@ -4,9 +4,7 @@ import freeflowuniverse.herolib.hero.models.core
// Pdf represents a PDF document library item
pub struct Pdf {
core.Base
// Title of the PDF
core.Base // Title of the PDF
title string @[index]
// Optional description of the PDF

View File

@@ -16,9 +16,7 @@ pub struct Slide {
// Slideshow represents a Slideshow library item (collection of images for slideshow)
pub struct Slideshow {
core.Base
// Title of the slideshow
core.Base // Title of the slideshow
title string @[index]
// Optional description of the slideshow