/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