/Users/despiegk/code/github/incubaid/herolib ├── .github │ └── workflows ├── .zed ├── aiprompts │ ├── .openhands │ ├── bizmodel │ ├── documentor │ ├── docusaurus │ ├── herolib_advanced │ ├── herolib_core │ ├── instructions_archive │ │ ├── models_from_v │ │ └── processing │ ├── v_advanced │ ├── v_core │ │ ├── array │ │ ├── benchmark │ │ ├── builtin │ │ ├── crypto │ │ ├── encoding │ │ ├── io │ │ ├── json │ │ ├── json2 │ │ ├── maps │ │ ├── net │ │ ├── orm │ │ ├── regex │ │ ├── string │ │ ├── time │ │ ├── toml │ │ └── veb │ └── v_veb_webserver ├── cli ├── docker │ ├── herolib │ │ └── scripts │ └── postgresql ├── examples │ ├── aiexamples │ ├── biztools │ │ ├── _archive │ │ ├── bizmodel_docusaurus │ │ │ └── archive │ │ │ └── img │ │ └── examples │ │ └── full │ ├── builder │ │ └── remote_executor │ ├── clients │ ├── core │ │ ├── base │ │ ├── db │ │ ├── logger │ │ ├── openapi │ │ │ └── gitea │ │ ├── openrpc │ │ │ └── examples │ │ │ ├── openrpc_client │ │ │ ├── openrpc_docs │ │ │ └── petstore_client │ │ └── pathlib │ │ └── examples │ │ ├── list │ │ ├── md5 │ │ ├── scanner │ │ └── sha256 │ ├── data │ │ ├── location │ │ ├── ourdb_syncer │ │ ├── params │ │ │ ├── args │ │ │ │ └── data │ │ │ └── paramsfilter │ │ └── resp │ ├── develop │ │ ├── codewalker │ │ ├── gittools │ │ ├── heroprompt │ │ ├── ipapi │ │ ├── juggler │ │ │ └── hero │ │ │ └── playbook │ │ ├── luadns │ │ ├── openai │ │ ├── runpod │ │ ├── vastai │ │ └── wireguard │ ├── hero │ │ ├── db │ │ ├── generation │ │ │ ├── blank_generation │ │ │ └── openapi_generation │ │ │ └── example_actor │ │ │ └── specs │ │ ├── herofs │ │ ├── heromodels │ │ └── openapi │ │ └── data │ ├── installers │ │ ├── db │ │ ├── infra │ │ ├── lang │ │ ├── net │ │ ├── sysadmintools │ │ ├── threefold │ │ └── virt │ ├── installers_remote │ ├── jobs │ ├── lang │ │ └── python │ ├── mcp │ │ ├── http_demo │ │ ├── http_server │ │ ├── inspector │ │ └── simple_http │ ├── osal │ │ ├── coredns │ │ ├── download │ │ ├── ping │ │ ├── process │ │ │ ├── process_bash │ │ │ └── process_python │ │ ├── rsync │ │ ├── sandbox │ │ │ └── examples │ │ ├── sshagent │ │ ├── tmux │ │ │ └── heroscripts │ │ ├── ubuntu │ │ └── zinit │ │ ├── rpc │ │ └── simple │ ├── schemas │ │ ├── example │ │ │ └── testdata │ │ ├── openapi │ │ │ └── codegen │ │ └── openrpc │ ├── sshagent │ ├── threefold │ │ ├── grid │ │ │ ├── deploy │ │ │ └── utils │ │ ├── gridproxy │ │ ├── holochain │ │ ├── incatokens │ │ │ └── data │ │ ├── solana │ │ └── tfgrid3deployer │ │ ├── gw_over_wireguard │ │ ├── heroscript │ │ ├── hetzner │ │ ├── open_webui_gw │ │ └── vm_gw_caddy │ ├── tools │ │ └── imagemagick │ │ └── .backup │ ├── ui │ │ ├── console │ │ │ ├── console2 │ │ │ └── flow1 │ │ └── telegram │ ├── vfs │ │ └── vfs_db │ ├── virt │ │ ├── daguserver │ │ ├── docker │ │ │ └── ai_web_ui │ │ ├── heropods │ │ ├── hetzner │ │ ├── lima │ │ ├── podman │ │ └── windows │ ├── web │ │ ├── doctree │ │ │ └── content │ │ └── markdown_renderer │ └── webdav ├── lib │ ├── ai │ │ ├── escalayer │ │ ├── mcp │ │ │ ├── baobab │ │ │ ├── cmd │ │ │ ├── mcpgen │ │ │ │ ├── schemas │ │ │ │ └── templates │ │ │ ├── pugconvert │ │ │ │ ├── cmd │ │ │ │ ├── logic │ │ │ │ │ └── templates │ │ │ │ └── mcp │ │ │ ├── rhai │ │ │ │ ├── cmd │ │ │ │ ├── example │ │ │ │ ├── logic │ │ │ │ │ ├── prompts │ │ │ │ │ └── templates │ │ │ │ └── mcp │ │ │ ├── rust │ │ │ └── vcode │ │ │ ├── cmd │ │ │ ├── logic │ │ │ └── mcp │ │ └── utils │ ├── biz │ │ ├── bizmodel │ │ │ ├── docu │ │ │ ├── exampledata │ │ │ └── templates │ │ ├── investortool │ │ │ └── simulator │ │ │ └── templates │ │ ├── planner │ │ │ ├── examples │ │ │ └── models │ │ └── spreadsheet │ │ └── docu │ ├── builder │ ├── clients │ │ ├── giteaclient │ │ ├── ipapi │ │ ├── jina │ │ │ └── py_specs │ │ ├── livekit │ │ ├── mailclient │ │ ├── meilisearch │ │ ├── mycelium │ │ ├── mycelium_rpc │ │ ├── openai │ │ │ ├── audio │ │ │ ├── embeddings │ │ │ ├── files │ │ │ ├── finetune │ │ │ ├── images │ │ │ └── moderation │ │ ├── postgresql_client │ │ ├── qdrant │ │ ├── rclone │ │ ├── runpod │ │ ├── sendgrid │ │ ├── traefik │ │ ├── vastai │ │ ├── wireguard │ │ ├── zerodb_client │ │ └── zinit │ ├── conversiontools │ │ ├── docsorter │ │ │ └── pythonscripts │ │ ├── imagemagick │ │ ├── pdftotext │ │ └── text_extractor │ ├── core │ │ ├── base │ │ ├── code │ │ │ └── templates │ │ │ ├── comment │ │ │ ├── function │ │ │ ├── interface │ │ │ └── struct │ │ ├── generator │ │ │ └── generic │ │ │ └── templates │ │ ├── herocmds │ │ ├── httpconnection │ │ ├── logger │ │ ├── openrpc_remove │ │ │ ├── examples │ │ │ └── specs │ │ ├── pathlib │ │ ├── playbook │ │ ├── playcmds │ │ ├── playmacros │ │ ├── redisclient │ │ ├── rootpath │ │ ├── smartid │ │ ├── texttools │ │ │ └── regext │ │ │ └── testdata │ │ └── vexecutor │ ├── crypt │ │ ├── aes_symmetric │ │ ├── crpgp │ │ ├── ed25519 │ │ ├── keychain │ │ ├── keysafe │ │ ├── openssl │ │ ├── pgp │ │ └── secrets │ ├── data │ │ ├── cache │ │ ├── countries │ │ │ └── data │ │ ├── currency │ │ ├── dbfs │ │ ├── dedupestor │ │ │ └── dedupe_ourdb │ │ ├── doctree │ │ │ ├── collection │ │ │ │ ├── data │ │ │ │ ├── template │ │ │ │ └── testdata │ │ │ │ └── export_test │ │ │ │ ├── export_expected │ │ │ │ │ └── src │ │ │ │ │ └── col1 │ │ │ │ │ └── img │ │ │ │ └── mytree │ │ │ │ └── dir1 │ │ │ │ └── dir2 │ │ │ ├── pointer │ │ │ └── testdata │ │ │ ├── actions │ │ │ │ └── functionality │ │ │ ├── export_test │ │ │ │ ├── export_expected │ │ │ │ │ ├── col1 │ │ │ │ │ │ └── img │ │ │ │ │ └── col2 │ │ │ │ └── mytree │ │ │ │ ├── dir1 │ │ │ │ │ └── dir2 │ │ │ │ └── dir3 │ │ │ ├── process_defs_test │ │ │ │ ├── col1 │ │ │ │ └── col2 │ │ │ ├── process_includes_test │ │ │ │ ├── col1 │ │ │ │ └── col2 │ │ │ ├── rpc │ │ │ └── tree_test │ │ │ ├── fruits │ │ │ │ └── berries │ │ │ │ └── img │ │ │ └── vegetables │ │ │ └── cruciferous │ │ ├── encoder │ │ ├── encoderhero │ │ ├── flist │ │ ├── gid │ │ ├── graphdb │ │ ├── ipaddress │ │ ├── location │ │ ├── markdown │ │ │ ├── elements │ │ │ ├── parsers │ │ │ ├── testdata │ │ │ └── tools │ │ ├── markdownparser2 │ │ ├── markdownrenderer │ │ ├── mnemonic │ │ ├── models │ │ │ └── hr │ │ ├── ourdb │ │ ├── ourdb_syncer │ │ │ ├── http │ │ │ └── streamer │ │ ├── ourjson │ │ ├── ourtime │ │ ├── paramsparser │ │ ├── radixtree │ │ ├── resp │ │ ├── serializers │ │ ├── tst │ │ ├── verasure │ │ └── vstor │ ├── dav │ │ └── webdav │ │ ├── bin │ │ ├── specs │ │ └── templates │ ├── develop │ │ ├── codewalker │ │ ├── gittools │ │ │ └── tests │ │ ├── heroprompt │ │ │ └── templates │ │ ├── luadns │ │ ├── performance │ │ │ └── cmd │ │ ├── sourcetree │ │ ├── vscode │ │ └── vscode_extensions │ │ └── ourdb │ │ └── templates │ ├── hero │ │ ├── db │ │ ├── herocluster │ │ │ └── example │ │ ├── herofs │ │ ├── herohandlers │ │ ├── heromodels │ │ └── heromodels copy │ │ └── examples │ ├── installers │ │ ├── base │ │ │ └── templates │ │ ├── db │ │ │ ├── cometbft │ │ │ │ └── templates │ │ │ ├── meilisearch_installer │ │ │ ├── postgresql │ │ │ │ └── templates │ │ │ ├── qdrant_installer │ │ │ │ └── templates │ │ │ ├── zerodb │ │ │ └── zerofs │ │ ├── develapps │ │ │ ├── chrome │ │ │ └── vscode │ │ ├── infra │ │ │ ├── coredns │ │ │ │ └── templates │ │ │ ├── gitea │ │ │ │ └── templates │ │ │ ├── livekit │ │ │ │ └── templates │ │ │ └── zinit_installer │ │ ├── lang │ │ │ ├── golang │ │ │ ├── herolib │ │ │ ├── nodejs │ │ │ ├── python │ │ │ ├── rust │ │ │ └── vlang │ │ ├── net │ │ │ ├── mycelium_installer │ │ │ ├── wireguard_installer │ │ │ └── yggdrasil │ │ ├── sysadmintools │ │ │ ├── actrunner │ │ │ │ └── templates │ │ │ ├── b2 │ │ │ ├── fungistor │ │ │ ├── garage_s3 │ │ │ │ └── templates │ │ │ ├── grafana │ │ │ ├── prometheus │ │ │ │ └── templates │ │ │ ├── rclone │ │ │ │ └── templates │ │ │ ├── restic │ │ │ └── s3 │ │ ├── threefold │ │ │ ├── griddriver │ │ │ └── tfrobot │ │ ├── ulist │ │ ├── virt │ │ │ ├── cloudhypervisor │ │ │ ├── docker │ │ │ ├── herorunner │ │ │ ├── lima │ │ │ │ └── templates │ │ │ ├── pacman │ │ │ │ └── templates │ │ │ ├── podman │ │ │ ├── qemu │ │ │ └── youki │ │ └── web │ │ ├── bun │ │ ├── imagemagick │ │ ├── lighttpd │ │ │ └── templates │ │ ├── tailwind │ │ ├── tailwind4 │ │ ├── traefik │ │ │ └── templates │ │ └── zola │ ├── lang │ │ ├── python │ │ │ └── templates │ │ └── rust │ ├── mcp │ │ ├── baobab │ │ ├── cmd │ │ ├── mcpgen │ │ │ ├── schemas │ │ │ └── templates │ │ ├── pugconvert │ │ │ ├── cmd │ │ │ ├── logic │ │ │ │ └── templates │ │ │ └── mcp │ │ ├── rhai │ │ │ ├── cmd │ │ │ ├── example │ │ │ ├── logic │ │ │ │ ├── prompts │ │ │ │ └── templates │ │ │ └── mcp │ │ ├── transport │ │ └── vcode │ │ ├── cmd │ │ ├── logic │ │ └── mcp │ ├── osal │ │ ├── core │ │ ├── coredns │ │ ├── hostsfile │ │ ├── linux │ │ │ └── templates │ │ ├── netns │ │ ├── notifier │ │ ├── osinstaller │ │ ├── rsync │ │ │ └── templates │ │ ├── screen │ │ ├── sshagent │ │ ├── startupmanager │ │ ├── systemd │ │ │ └── templates │ │ ├── tmux │ │ │ └── bin │ │ ├── traefik │ │ │ └── specs │ │ ├── tun │ │ ├── ubuntu │ │ └── ufw │ ├── schemas │ │ ├── jsonrpc │ │ │ ├── reflection │ │ │ └── testdata │ │ │ ├── testmodule │ │ │ └── testserver │ │ ├── jsonschema │ │ │ ├── codegen │ │ │ │ └── templates │ │ │ └── testdata │ │ ├── openapi │ │ │ ├── codegen │ │ │ ├── templates │ │ │ └── testdata │ │ └── openrpc │ │ ├── codegen │ │ │ ├── templates │ │ │ └── testdata │ │ ├── server │ │ └── testdata │ │ └── petstore_client │ ├── security │ │ ├── authentication │ │ │ └── templates │ │ └── jwt │ ├── threefold │ │ ├── grid3 │ │ │ ├── deploy_tosort │ │ │ ├── deployer │ │ │ ├── deployer2_sort │ │ │ ├── griddriver │ │ │ ├── gridproxy │ │ │ │ └── model │ │ │ ├── models │ │ │ ├── rmb │ │ │ ├── tfrobot │ │ │ │ └── templates │ │ │ ├── tokens │ │ │ └── zerohub │ │ ├── grid4 │ │ │ ├── datamodel │ │ │ ├── datamodelsimulator │ │ │ ├── farmingsimulator │ │ │ │ └── templates │ │ │ └── gridsimulator │ │ │ └── manual │ │ ├── incatokens │ │ │ └── templates │ │ └── models │ │ ├── business │ │ ├── core │ │ ├── finance │ │ ├── flow │ │ ├── identity │ │ ├── legal │ │ ├── library │ │ ├── location │ │ └── payment │ ├── ui │ │ ├── console │ │ ├── generic │ │ ├── logger │ │ ├── telegram │ │ │ └── client │ │ ├── template │ │ └── uimodel │ ├── vfs │ │ ├── vfs_calendar │ │ ├── vfs_contacts │ │ ├── vfs_db │ │ ├── vfs_local │ │ ├── vfs_mail │ │ └── vfs_nested │ ├── virt │ │ ├── cloudhypervisor │ │ ├── crun │ │ ├── docker │ │ ├── heropods │ │ ├── herorun │ │ ├── herorun2 │ │ ├── hetznermanager │ │ ├── lima │ │ │ ├── raw │ │ │ └── templates │ │ ├── podman │ │ └── qemu │ │ └── templates │ └── web │ ├── doctreeclient │ ├── docusaurus │ │ └── example │ ├── echarts │ ├── site │ │ └── example │ └── ui │ ├── static │ │ ├── css │ │ └── js │ └── templates │ └── admin ├── libarchive │ ├── baobab │ │ ├── actor │ │ ├── generator │ │ │ ├── _archive │ │ │ ├── templates │ │ │ └── testdata │ │ ├── osis │ │ ├── specification │ │ └── stage │ │ └── interfaces │ ├── buildah │ ├── daguserver │ │ └── templates │ ├── dify │ │ └── templates │ ├── examples │ │ └── baobab │ │ ├── generator │ │ │ ├── basic │ │ │ ├── geomind_poc │ │ │ └── openapi_e2e │ │ └── specification │ ├── installers │ │ └── web │ │ └── caddy2 │ │ └── templates │ ├── rhai │ │ ├── prompts │ │ ├── templates │ │ └── testdata │ ├── starlight │ │ └── templates │ └── zinit │ └── zinit ├── manual │ ├── best_practices │ │ ├── osal │ │ └── scripts │ ├── core │ │ └── concepts │ ├── documentation │ └── playcmds ├── research │ ├── globals │ └── openrpc ├── tests │ └── data └── vscodeplugin └── heroscrypt-syntax └── syntaxes File: /Users/despiegk/code/github/incubaid/herolib/lib/hero/db/ai_instructions.md ```md # HeroDB Model Creation Instructions for AI ## Overview This document provides clear instructions for AI agents to create new HeroDB models similar to `comment.v`. These models are used to store structured data in Redis using the HeroDB system. ## Key Concepts - Each model represents a data type stored in Redis hash sets - Models must implement serialization/deserialization using the `encoder` module - Models inherit from the `Base` struct which provides common fields - The database uses a factory pattern for model access ## File Structure Create a new file in `lib/hero/heromodels/` with the model name (e.g., `calendar.v`). ## Required Components ### 1. Model Struct Definition Define your model struct with the following pattern: ```v @[heap] pub struct Calendar { db.Base // Inherit from Base struct pub mut: // Add your specific fields here title string start_time i64 end_time i64 location string attendees []string } ``` ### 2. Type Name Method Implement a method to return the model's type name: ```v pub fn (self Calendar) type_name() string { return 'calendar' } ``` ### 3. Serialization (dump) Method Implement the `dump` method to serialize your struct's fields using the encoder: ```v pub fn (self Calendar) dump(mut e &encoder.Encoder) ! { e.add_string(self.title) e.add_i64(self.start_time) e.add_i64(self.end_time) e.add_string(self.location) e.add_list_string(self.attendees) } ``` ### 4. Deserialization (load) Method Implement the `load` method to deserialize your struct's fields: ```v fn (mut self DBCalendar) load(mut o Calendar, mut e &encoder.Decoder) ! { o.title = e.get_string()! o.start_time = e.get_i64()! o.end_time = e.get_i64()! o.location = e.get_string()! o.attendees = e.get_list_string()! } ``` ### 5. Model Arguments Struct Define a struct for creating new instances of your model: ```v @[params] pub struct CalendarArg { pub mut: title string @[required] start_time i64 end_time i64 location string attendees []string } ``` ### 6. Database Wrapper Struct Create a database wrapper struct for your model: ```v pub struct DBCalendar { pub mut: db &db.DB @[skip; str: skip] } ``` ### 7. Factory Integration Add your model to the ModelsFactory struct in `factory.v`: ```v pub struct ModelsFactory { pub mut: comments DBCalendar // ... other models } ``` And initialize it in the `new()` function: ```v pub fn new() !ModelsFactory { mut mydb := db.new()! return ModelsFactory{ comments: DBCalendar{ db: &mydb } // ... initialize other models } } ``` ## Encoder Methods Reference Use these methods for serialization/deserialization: ### Encoder (Serialization) - `e.add_bool(val bool)` - `e.add_u8(val u8)` - `e.add_u16(val u16)` - `e.add_u32(val u32)` - `e.add_u64(val u64)` - `e.add_i8(val i8)` - `e.add_i16(val i16)` - `e.add_i32(val i32)` - `e.add_i64(val i64)` - `e.add_f32(val f32)` - `e.add_f64(val f64)` - `e.add_string(val string)` - `e.add_list_bool(val []bool)` - `e.add_list_u8(val []u8)` - `e.add_list_u16(val []u16)` - `e.add_list_u32(val []u32)` - `e.add_list_u64(val []u64)` - `e.add_list_i8(val []i8)` - `e.add_list_i16(val []i16)` - `e.add_list_i32(val []i32)` - `e.add_list_i64(val []i64)` - `e.add_list_f32(val []f32)` - `e.add_list_f64(val []f64)` - `e.add_list_string(val []string)` ### Decoder (Deserialization) - `e.get_bool()!` - `e.get_u8()!` - `e.get_u16()!` - `e.get_u32()!` - `e.get_u64()!` - `e.get_i8()!` - `e.get_i16()!` - `e.get_i32()!` - `e.get_i64()!` - `e.get_f32()!` - `e.get_f64()!` - `e.get_string()!` - `e.get_list_bool()!` - `e.get_list_u8()!` - `e.get_list_u16()!` - `e.get_list_u32()!` - `e.get_list_u64()!` - `e.get_list_i8()!` - `e.get_list_i16()!` - `e.get_list_i32()!` - `e.get_list_i64()!` - `e.get_list_f32()!` - `e.get_list_f64()!` - `e.get_list_string()!` ## CRUD Methods Implementation ### Create New Instance ```v pub fn (mut self DBCalendar) new(args CalendarArg) !Calendar { mut o := Calendar{ title: args.title start_time: args.start_time end_time: args.end_time location: args.location attendees: args.attendees updated_at: ourtime.now().unix() } return o } ``` ### Save to Database ```v pub fn (mut self DBCalendar) set(o Calendar) !u32 { return self.db.set[Calendar](o)! } ``` ### Retrieve from Database ```v pub fn (mut self DBCalendar) get(id u32) !Calendar { mut o, data := self.db.get_data[Calendar](id)! mut e_decoder := encoder.decoder_new(data) self.load(mut o, mut e_decoder)! return o } ``` ### Delete from Database ```v pub fn (mut self DBCalendar) delete(id u32) ! { self.db.delete[Calendar](id)! } ``` ### Check Existence ```v pub fn (mut self DBCalendar) exist(id u32) !bool { return self.db.exists[Calendar](id)! } ``` ### List All Objects ```v pub fn (mut self DBCalendar) list() ![]Calendar { return self.db.list[Calendar]()!.map(self.get(it)!) } ``` ## Example Usage Script Create a `.vsh` script in `examples/hero/heromodels/` to demonstrate usage: ```v #!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run import incubaid.herolib.core.redisclient import incubaid.herolib.hero.heromodels mut mydb := heromodels.new()! // Create a new object mut o := mydb.calendar.new( title: 'Meeting' start_time: 1672531200 end_time: 1672534800 location: 'Conference Room' attendees: ['john@example.com', 'jane@example.com'] )! // Save to database oid := mydb.calendar.set(o)! println('Created object with ID: ${oid}') // Retrieve from database mut o2 := mydb.calendar.get(oid)! println('Retrieved object: ${o2}') // List all objects mut objects := mydb.calendar.list()! println('All objects: ${objects}') ``` ## Best Practices 1. Always inherit from `db.Base` struct 2. Implement all required methods (`type_name`, `dump`, `load`) 3. Use the encoder methods for consistent serialization 4. Handle errors appropriately with `!` or `or` blocks 5. Keep field ordering consistent between `dump` and `load` methods 6. Use snake_case for field names 7. Add `@[required]` attribute to mandatory fields in argument structs 8. Initialize timestamps using `ourtime.now().unix()` ## Implementation Steps Summary 1. Create model struct inheriting from `db.Base` 2. Implement `type_name()` method 3. Implement `dump()` method using encoder 4. Implement `load()` method using decoder 5. Create argument struct with `@[params]` attribute 6. Create database wrapper struct 7. Add model to `ModelsFactory` in `factory.v` 8. Implement CRUD methods 9. Create example usage script 10. Test the implementation with the example script ``` File: /Users/despiegk/code/github/incubaid/herolib/lib/hero/db/core_methods.v ```v module db import incubaid.herolib.data.ourtime import incubaid.herolib.data.encoder pub fn (mut self DB) set[T](obj_ T) !u32 { // Get the next ID mut obj := obj_ if obj.id == 0 { obj.id = self.new_id()! } mut t := ourtime.now().unix() if obj.created_at == 0 { obj.created_at = t } obj.updated_at = t // 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 mut e := encoder.new() e.add_u8(1) e.add_u32(obj.id) e.add_string(obj.name) e.add_string(obj.description) e.add_i64(obj.created_at) e.add_i64(obj.updated_at) e.add_u32(obj.securitypolicy) e.add_u32(obj.tags) e.add_u16(u16(obj.comments.len)) for comment in obj.comments { e.add_u32(comment) } // println('set: before dump, e.data.len: ${e.data.len}') obj.dump(mut e)! // println('set: after dump, e.data.len: ${e.data.len}') self.redis.hset(self.db_name[T](), obj.id.str(), e.data.bytestr())! return obj.id } // return the data, cannot return the object as we do not know the type pub fn (mut self DB) get_data[T](id u32) !(T, []u8) { data := self.redis.hget(self.db_name[T](), id.str())! if data.len == 0 { return error('herodb:${self.db_name[T]()} not found for ${id}') } // println('get_data: data.len: ${data.len}') mut e := encoder.decoder_new(data.bytes()) version := e.get_u8()! if version != 1 { panic('wrong version in base load') } mut base := T{} base.id = e.get_u32()! base.name = e.get_string()! base.description = e.get_string()! base.created_at = e.get_i64()! base.updated_at = e.get_i64()! base.securitypolicy = e.get_u32()! base.tags = e.get_u32()! for _ in 0 .. e.get_u16()! { base.comments << e.get_u32()! } return base, e.data } pub fn (mut self DB) exists[T](id u32) !bool { return self.redis.hexists(self.db_name[T](), id.str())! } pub fn (mut self DB) delete[T](id u32) ! { self.redis.hdel(self.db_name[T](), id.str())! } pub fn (mut self DB) list[T]() ![]u32 { ids := self.redis.hkeys(self.db_name[T]())! return ids.map(it.u32()) } // make it easy to get a base object pub fn (mut self DB) new_from_base[T](args BaseArgs) !Base { return T{ Base: new_base(args)! } } fn (mut self DB) db_name[T]() string { // get the name of the type T mut name := T.name.to_lower_ascii().split('.').last() // println("db_name rediskey: '${name}'") return 'db:${name}' } pub fn (mut self DB) new_id() !u32 { return u32(self.redis.incr('db:id')!) } ``` File: /Users/despiegk/code/github/incubaid/herolib/lib/hero/db/core_models.v ```v module db import crypto.md5 import incubaid.herolib.core.redisclient import incubaid.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 } ``` File: /Users/despiegk/code/github/incubaid/herolib/lib/hero/db/helpers_comments.v ```v module db import crypto.md5 @[params] pub struct CommentArg { pub mut: comment string parent u32 author u32 } pub fn (mut self DB) comments_get(args []CommentArg) ![]u32 { return args.map(self.comment_get(it.comment)!) } pub fn (mut self DB) comment_get(comment string) !u32 { comment_fixed := comment.to_lower_ascii().trim_space() return if comment_fixed.len > 0 { hash := md5.hexhash(comment_fixed) comment_found := self.redis.hget('db:comments', hash)! if comment_found == '' { id := self.new_id()! self.redis.hset('db:comments', hash, id.str())! self.redis.hset('db:comments', id.str(), comment_fixed)! id } else { comment_found.u32() } } else { 0 } } ``` File: /Users/despiegk/code/github/incubaid/herolib/lib/hero/db/helpers_tags.v ```v module db import crypto.md5 pub fn (mut self DB) tags_get(tags []string) !u32 { 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 := self.redis.hget('db:tags', hash)! return if tags_found == '' { println('tags_get: new tags: ${tags_fixed.join(",")}') id := self.new_id()! self.redis.hset('db:tags', hash, id.str())! self.redis.hset('db:tags', id.str(), tags_fixed.join(','))! id } else { tags_found.u32() } } else { 0 } } ``` File: /Users/despiegk/code/github/incubaid/herolib/lib/hero/heromodels/calendar_event.v ```v module heromodels import incubaid.herolib.data.encoder import incubaid.herolib.data.ourtime import incubaid.herolib.hero.db // CalendarEvent represents a single event in a calendar @[heap] pub struct CalendarEvent { db.Base pub mut: title string start_time i64 // Unix timestamp end_time i64 // Unix timestamp location string attendees []u32 // IDs of user groups fs_items []u32 // IDs of linked files or dirs calendar_id u32 // Associated calendar status EventStatus is_all_day bool is_recurring bool recurrence []RecurrenceRule // normally empty reminder_mins []int // Minutes before event for reminders color string // Hex color code timezone string } pub struct Attendee { pub mut: user_id u32 status AttendanceStatus role AttendeeRole } pub enum AttendanceStatus { no_response accepted declined tentative } pub enum AttendeeRole { required optional organizer } pub enum EventStatus { draft published cancelled completed } pub struct RecurrenceRule { pub mut: frequency RecurrenceFreq interval int // Every N frequencies until i64 // End date (Unix timestamp) count int // Number of occurrences by_weekday []int // Days of week (0=Sunday) by_monthday []int // Days of month } pub enum RecurrenceFreq { none daily weekly monthly yearly } pub struct DBCalendarEvent { pub mut: db &db.DB @[skip; str: skip] } pub fn (self CalendarEvent) type_name() string { return 'calendar_event' } pub fn (self CalendarEvent) dump(mut e &encoder.Encoder) ! { e.add_string(self.title) e.add_i64(self.start_time) e.add_i64(self.end_time) e.add_string(self.location) e.add_list_u32(self.attendees) e.add_list_u32(self.fs_items) e.add_u32(self.calendar_id) e.add_u8(u8(self.status)) e.add_bool(self.is_all_day) e.add_bool(self.is_recurring) // Encode recurrence array e.add_u16(u16(self.recurrence.len)) for rule in self.recurrence { e.add_u8(u8(rule.frequency)) e.add_int(rule.interval) e.add_i64(rule.until) e.add_int(rule.count) e.add_list_int(rule.by_weekday) e.add_list_int(rule.by_monthday) } e.add_list_int(self.reminder_mins) e.add_string(self.color) e.add_string(self.timezone) } fn (mut self DBCalendarEvent) load(mut o CalendarEvent, mut e &encoder.Decoder) ! { o.title = e.get_string()! o.start_time = e.get_i64()! o.end_time = e.get_i64()! o.location = e.get_string()! o.attendees = e.get_list_u32()! o.fs_items = e.get_list_u32()! o.calendar_id = e.get_u32()! o.status = unsafe { EventStatus(e.get_u8()!) } //TODO: is there no better way? o.is_all_day = e.get_bool()! o.is_recurring = e.get_bool()! // Decode recurrence array recurrence_len := e.get_u16()! mut recurrence := []RecurrenceRule{} for _ in 0 .. recurrence_len { frequency := unsafe { RecurrenceFreq(e.get_u8()!) } interval := e.get_int()! until := e.get_i64()! count := e.get_int()! by_weekday := e.get_list_int()! by_monthday := e.get_list_int()! recurrence << RecurrenceRule{ frequency: frequency interval: interval until: until count: count by_weekday: by_weekday by_monthday: by_monthday } } o.recurrence = recurrence o.reminder_mins = e.get_list_int()! o.color = e.get_string()! o.timezone = e.get_string()! } @[params] pub struct CalendarEventArg { 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 EventStatus is_all_day bool is_recurring bool recurrence []RecurrenceRule reminder_mins []int // Minutes before event for reminders color string // Hex color code timezone string securitypolicy u32 tags []string comments []db.CommentArg } // get new calendar event, not from the DB pub fn (mut self DBCalendarEvent) new(args CalendarEventArg) !CalendarEvent { mut o := CalendarEvent{ title: args.title location: args.location attendees: args.attendees fs_items: args.fs_items calendar_id: args.calendar_id status: args.status is_all_day: args.is_all_day is_recurring: args.is_recurring recurrence: args.recurrence reminder_mins: args.reminder_mins color: args.color timezone: args.timezone } // Set base fields o.name = args.name o.description = args.description o.securitypolicy = args.securitypolicy o.tags = self.db.tags_get(args.tags)! o.comments = self.db.comments_get(args.comments)! o.updated_at = ourtime.now().unix() // Convert string times to Unix timestamps mut start_time_obj := ourtime.new(args.start_time)! o.start_time = start_time_obj.unix() mut end_time_obj := ourtime.new(args.end_time)! o.end_time = end_time_obj.unix() return o } pub fn (mut self DBCalendarEvent) set(o CalendarEvent) !u32 { // Use db set function which now returns the ID return self.db.set[CalendarEvent](o)! } pub fn (mut self DBCalendarEvent) delete(id u32) ! { self.db.delete[CalendarEvent](id)! } pub fn (mut self DBCalendarEvent) exist(id u32) !bool { return self.db.exists[CalendarEvent](id)! } pub fn (mut self DBCalendarEvent) get(id u32) !CalendarEvent { mut o, data := self.db.get_data[CalendarEvent](id)! mut e_decoder := encoder.decoder_new(data) self.load(mut o, mut e_decoder)! return o } pub fn (mut self DBCalendarEvent) list() ![]CalendarEvent { return self.db.list[CalendarEvent]()!.map(self.get(it)!) } ``` make the crud and example for all files in lib/hero/herofs think about which additional hsets we need to make it efficient check the implementation do the implementation