This commit is contained in:
2025-09-18 08:07:45 +02:00
parent 62a64e9fd0
commit f1294b26cb
7 changed files with 475 additions and 20 deletions

View File

@@ -29,6 +29,10 @@ pub fn (mut self DB) set[T](obj_ T) !T {
for comment in obj.comments {
e.add_u32(comment)
}
e.add_u16(u16(obj.messages.len))
for message in obj.messages {
e.add_u32(message)
}
obj.dump(mut e)!
self.redis.hset(self.db_name[T](), obj.id.str(), e.data.bytestr())!
@@ -60,6 +64,9 @@ pub fn (mut self DB) get_data[T](id u32) !(T, []u8) {
for _ in 0 .. e.get_u16()! {
base.comments << e.get_u32()!
}
for _ in 0 .. e.get_u16()! {
base.messages << e.get_u32()!
}
return base, e.data
}

View File

@@ -19,13 +19,14 @@ pub mut:
status EventStatus
is_all_day bool
is_recurring bool
recurrence []RecurrenceRule // normally empty
recurrence []RecurrenceRule
reminder_mins []int // Minutes before event for reminders
color string // Hex color code
timezone string
priority EventPriority
public bool
locations []EventLocation
is_template bool //not to be shown as real event, serves as placeholder e.g. for planning
}

View File

@@ -9,9 +9,12 @@ pub struct Comment {
db.Base
pub mut:
// id u32
subject string
comment string
parent u32 // id of parent comment if any, 0 means none
author u32 // links to user
to []u32 // if comment/message has been sent to someone specifically
cc []u32 // like to but then for cc
}
//////////TO BE GENERATED BY AI////////////////////////////////

View File

@@ -1,3 +1,6 @@
## notes around how to do a calendly feature
- make an agenda for the planning and call it as such, this has the timeboxes available for planning
- make an agenda for the planning and call it as such, this has the timeboxes available for planning
- create template for calendar_event
- create planning item and link to this template
- select the rules which work for recurrence

View File

@@ -4,36 +4,56 @@ import freeflowuniverse.herolib.data.encoder
import freeflowuniverse.herolib.data.ourtime
import freeflowuniverse.herolib.hero.db
// Planning represents a collection of events
// Planning, how do people or teams want to plan their time
//acls can be used to define who can change this planning
@[heap]
pub struct Planning {
db.Base
pub mut:
color string // Hex color code
timezone string
is_public bool
calendar_id u32 //link to calendarid which is relevant for this planning, the calendar has
color string // Hex color code
timezone string
is_public bool
calendar_template_id u32 // link to calendarid which is relevant for this planning, this calendar event will be a template
registration_desk_id u32 //to arrange how we let people register, and track registrations
autoschedule_rules []RecurrenceRule // will automatically schedule, uses calendar_id as template
invite_rules []RecurrenceRule // times in which people can invite themselves
attendees_required []u32
attendees_optional []u32 //if we want to specify upfront
}
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
// cron string // in linux cron format, if cron used then other ones below not used
until u64 // End date (Unix timestamp)
by_weekday []u8 // Days of week (0=Sunday)
by_monthday []u8 // Days of month
hour_from u8 // starts at midnight e.g. 10
hour_to u8 // e.g. 12 means between 10 and 12 (noon)
duration int // in minutes e.g. 30, means half hour
priority u8 // to tell user what has our preference, higher nr is better, max 10
}
pub enum RecurrenceFreq {
none
daily
weekly
monthly
yearly
pub fn (self RecurrenceRule) dump(mut e encoder.Encoder) ! {
e.add_u64(self.until)
e.add_list_u8(self.by_weekday)
e.add_list_u8(self.by_monthday)
e.add_u8(self.hour_from)
e.add_u8(self.hour_to)
e.add_int(self.duration)
e.add_u8(self.priority)
}
pub fn (mut self RecurrenceRule) load(mut e encoder.Decoder) ! {
self.until = e.get_u64()!
self.by_weekday = e.get_list_u8()!
self.by_monthday = e.get_list_u8()!
self.hour_from = e.get_u8()!
self.hour_to = e.get_u8()!
self.duration = e.get_int()!
self.priority = e.get_u8()!
}
pub struct DBPlanning {
pub mut:
@@ -103,12 +123,46 @@ pub fn (self Planning) dump(mut e encoder.Encoder) ! {
e.add_string(self.color)
e.add_string(self.timezone)
e.add_bool(self.is_public)
e.add_u32(self.calendar_id)
// Encode autoschedule_rules array
e.add_u16(u16(self.autoschedule_rules.len))
for rule in self.autoschedule_rules {
rule.dump(mut e)!
}
// Encode invite_rules array
e.add_u16(u16(self.invite_rules.len))
for rule in self.invite_rules {
rule.dump(mut e)!
}
}
fn (mut self DBPlanning) load(mut o Planning, mut e encoder.Decoder) ! {
o.color = e.get_string()!
o.timezone = e.get_string()!
o.is_public = e.get_bool()!
o.calendar_id = e.get_u32()!
// Decode autoschedule_rules array
autoschedule_rules_len := e.get_u16()!
mut autoschedule_rules := []RecurrenceRule{}
for _ in 0 .. autoschedule_rules_len {
mut rule := RecurrenceRule{}
rule.load(mut e)!
autoschedule_rules << rule
}
o.autoschedule_rules = autoschedule_rules
// Decode invite_rules array
invite_rules_len := e.get_u16()!
mut invite_rules := []RecurrenceRule{}
for _ in 0 .. invite_rules_len {
mut rule := RecurrenceRule{}
rule.load(mut e)!
invite_rules << rule
}
o.invite_rules = invite_rules
}
@[params]

View File

@@ -0,0 +1,386 @@
module heromodels
import freeflowuniverse.herolib.hero.db
import freeflowuniverse.herolib.data.ourtime
fn test_planning_new() ! {
// Initialize DBPlanning for testing
mut mydb := db.new_test()!
mut db_planning := DBPlanning{
db: &mydb
}
// Test creating a new planning
mut args := PlanningArg{
name: 'test_planning'
description: 'Test planning for unit testing'
color: '#FF0000'
timezone: 'UTC'
is_public: true
events: []u32{}
}
planning := db_planning.new(args)!
assert planning.name == 'test_planning'
assert planning.description == 'Test planning for unit testing'
assert planning.color == '#FF0000'
assert planning.timezone == 'UTC'
assert planning.is_public == true
assert planning.calendar_id == 0
assert planning.autoschedule_rules.len == 0
assert planning.invite_rules.len == 0
assert planning.updated_at > 0
println(' Planning new test passed!')
}
fn test_planning_crud_operations() ! {
// Initialize DBPlanning for testing
mut mydb := db.new_test()!
mut db_planning := DBPlanning{
db: &mydb
}
// Create a new planning
mut args := PlanningArg{
name: 'crud_test_planning'
description: 'Test planning for CRUD operations'
color: '#00FF00'
timezone: 'EST'
is_public: false
events: []u32{}
}
mut planning := db_planning.new(args)!
// Add some data for calendar_id, autoschedule_rules, and invite_rules
planning.calendar_id = 1
// Create some recurrence rules
mut rule1 := RecurrenceRule{
until: 1893456000 // 2030-01-01
by_weekday: [1, 3, 5] // Monday, Wednesday, Friday
by_monthday: []u8{}
hour_from: 9
hour_to: 17
duration: 30
priority: 5
}
mut rule2 := RecurrenceRule{
until: 0
by_weekday: []u8{}
by_monthday: [1, 15] // 1st and 15th of each month
hour_from: 10
hour_to: 12
duration: 60
priority: 8
}
planning.autoschedule_rules = [rule1]
planning.invite_rules = [rule2]
// Test set operation
planning = db_planning.set(planning)!
original_id := planning.id
// Test get operation
retrieved_planning := db_planning.get(original_id)!
assert retrieved_planning.name == 'crud_test_planning'
assert retrieved_planning.description == 'Test planning for CRUD operations'
assert retrieved_planning.color == '#00FF00'
assert retrieved_planning.timezone == 'EST'
assert retrieved_planning.is_public == false
assert retrieved_planning.calendar_id == 1
assert retrieved_planning.id == original_id
// Verify autoschedule_rules
assert retrieved_planning.autoschedule_rules.len == 1
assert retrieved_planning.autoschedule_rules[0].until == 1893456000
assert retrieved_planning.autoschedule_rules[0].by_weekday == [1, 3, 5]
assert retrieved_planning.autoschedule_rules[0].by_monthday.len == 0
assert retrieved_planning.autoschedule_rules[0].hour_from == 9
assert retrieved_planning.autoschedule_rules[0].hour_to == 17
assert retrieved_planning.autoschedule_rules[0].duration == 30
assert retrieved_planning.autoschedule_rules[0].priority == 5
// Verify invite_rules
assert retrieved_planning.invite_rules.len == 1
assert retrieved_planning.invite_rules[0].until == 0
assert retrieved_planning.invite_rules[0].by_weekday.len == 0
assert retrieved_planning.invite_rules[0].by_monthday == [1, 15]
assert retrieved_planning.invite_rules[0].hour_from == 10
assert retrieved_planning.invite_rules[0].hour_to == 12
assert retrieved_planning.invite_rules[0].duration == 60
assert retrieved_planning.invite_rules[0].priority == 8
// Test exist operation
exists := db_planning.exist(original_id)!
assert exists == true
// Test update
mut updated_args := PlanningArg{
name: 'updated_planning'
description: 'Updated test planning'
color: '#0000FF'
timezone: 'PST'
is_public: true
events: []u32{}
}
mut updated_planning := db_planning.new(updated_args)!
updated_planning.id = original_id
updated_planning.calendar_id = 2
// Update rules
mut updated_rule1 := RecurrenceRule{
until: 1924992000 // 2031-01-01
by_weekday: [2, 4] // Tuesday, Thursday
by_monthday: []u8{}
hour_from: 8
hour_to: 16
duration: 45
priority: 7
}
mut updated_rule2 := RecurrenceRule{
until: 1956528000 // 2032-01-01
by_weekday: []u8{}
by_monthday: [5, 20] // 5th and 20th of each month
hour_from: 11
hour_to: 13
duration: 90
priority: 3
}
updated_planning.autoschedule_rules = [updated_rule1]
updated_planning.invite_rules = [updated_rule2]
updated_planning = db_planning.set(updated_planning)!
// Verify update
final_planning := db_planning.get(original_id)!
assert final_planning.name == 'updated_planning'
assert final_planning.description == 'Updated test planning'
assert final_planning.color == '#0000FF'
assert final_planning.timezone == 'PST'
assert final_planning.is_public == true
assert final_planning.calendar_id == 2
// Verify updated autoschedule_rules
assert final_planning.autoschedule_rules.len == 1
assert final_planning.autoschedule_rules[0].until == 1924992000
assert final_planning.autoschedule_rules[0].by_weekday == [2, 4]
assert final_planning.autoschedule_rules[0].by_monthday.len == 0
assert final_planning.autoschedule_rules[0].hour_from == 8
assert final_planning.autoschedule_rules[0].hour_to == 16
assert final_planning.autoschedule_rules[0].duration == 45
assert final_planning.autoschedule_rules[0].priority == 7
// Verify updated invite_rules
assert final_planning.invite_rules.len == 1
assert final_planning.invite_rules[0].until == 1956528000
assert final_planning.invite_rules[0].by_weekday.len == 0
assert final_planning.invite_rules[0].by_monthday == [5, 20]
assert final_planning.invite_rules[0].hour_from == 11
assert final_planning.invite_rules[0].hour_to == 13
assert final_planning.invite_rules[0].duration == 90
assert final_planning.invite_rules[0].priority == 3
// Test delete operation
db_planning.delete(original_id)!
// Verify deletion
exists_after_delete := db_planning.exist(original_id)!
assert exists_after_delete == false
println(' Planning CRUD operations test passed!')
}
fn test_planning_recurrence_rules_encoding_decoding() ! {
// Initialize DBPlanning for testing
mut mydb := db.new_test()!
mut db_planning := DBPlanning{
db: &mydb
}
// Create a new planning with recurrence rules
mut args := PlanningArg{
name: 'recurrence_test_planning'
description: 'Test planning for recurrence rules encoding/decoding'
color: '#FFFF00'
timezone: 'UTC'
is_public: true
events: []u32{}
}
mut planning := db_planning.new(args)!
planning.calendar_id = 1
// Add complex recurrence rules
mut rule1 := RecurrenceRule{
until: 1893456000 // 2030-01-01
by_weekday: [0, 1, 2, 3, 4, 5, 6] // All days of week
by_monthday: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31] // All days of month
hour_from: 0
hour_to: 23
duration: 15
priority: 10
}
mut rule2 := RecurrenceRule{
until: 1924992000 // 2031-01-01
by_weekday: []u8{}
by_monthday: []u8{}
hour_from: 12
hour_to: 13
duration: 120
priority: 1
}
planning.autoschedule_rules = [rule1, rule2]
planning.invite_rules = [rule1]
// Save the planning
planning = db_planning.set(planning)!
planning_id := planning.id
// Retrieve and verify all fields were properly encoded/decoded
retrieved_planning := db_planning.get(planning_id)!
assert retrieved_planning.autoschedule_rules.len == 2
assert retrieved_planning.invite_rules.len == 1
// Verify first autoschedule rule details
assert retrieved_planning.autoschedule_rules[0].until == 1893456000
assert retrieved_planning.autoschedule_rules[0].by_weekday == [0, 1, 2, 3, 4, 5, 6]
assert retrieved_planning.autoschedule_rules[0].by_monthday.len == 31
assert retrieved_planning.autoschedule_rules[0].hour_from == 0
assert retrieved_planning.autoschedule_rules[0].hour_to == 23
assert retrieved_planning.autoschedule_rules[0].duration == 15
assert retrieved_planning.autoschedule_rules[0].priority == 10
// Verify second autoschedule rule details
assert retrieved_planning.autoschedule_rules[1].until == 1924992000
assert retrieved_planning.autoschedule_rules[1].by_weekday.len == 0
assert retrieved_planning.autoschedule_rules[1].by_monthday.len == 0
assert retrieved_planning.autoschedule_rules[1].hour_from == 12
assert retrieved_planning.autoschedule_rules[1].hour_to == 13
assert retrieved_planning.autoschedule_rules[1].duration == 120
assert retrieved_planning.autoschedule_rules[1].priority == 1
// Verify invite rule details
assert retrieved_planning.invite_rules[0].until == 1893456000
assert retrieved_planning.invite_rules[0].by_weekday == [0, 1, 2, 3, 4, 5, 6]
assert retrieved_planning.invite_rules[0].by_monthday.len == 31
assert retrieved_planning.invite_rules[0].hour_from == 0
assert retrieved_planning.invite_rules[0].hour_to == 23
assert retrieved_planning.invite_rules[0].duration == 15
assert retrieved_planning.invite_rules[0].priority == 10
println(' Planning recurrence rules encoding/decoding test passed!')
}
fn test_planning_type_name() ! {
// Initialize DBPlanning for testing
mut mydb := db.new_test()!
mut db_planning := DBPlanning{
db: &mydb
}
// Create a new planning
mut args := PlanningArg{
name: 'type_test_planning'
description: 'Test planning for type name'
color: '#FF00FF'
timezone: 'UTC'
is_public: true
events: []u32{}
}
planning := db_planning.new(args)!
// Test type_name method
type_name := planning.type_name()
assert type_name == 'calendar'
println(' Planning type_name test passed!')
}
fn test_planning_description() ! {
// Initialize DBPlanning for testing
mut mydb := db.new_test()!
mut db_planning := DBPlanning{
db: &mydb
}
// Create a new planning
mut args := PlanningArg{
name: 'description_test_planning'
description: 'Test planning for description'
color: '#00FFFF'
timezone: 'UTC'
is_public: true
events: []u32{}
}
planning := db_planning.new(args)!
// Test description method for each methodname
assert planning.description('set') == 'Create or update a calendar. Returns the ID of the calendar.'
assert planning.description('get') == 'Retrieve a calendar by ID. Returns the calendar object.'
assert planning.description('delete') == 'Delete a calendar by ID. Returns true if successful.'
assert planning.description('exist') == 'Check if a calendar exists by ID. Returns true or false.'
assert planning.description('list') == 'List all calendars. Returns an array of calendar objects.'
assert planning.description('unknown') == 'This is generic method for the root object, TODO fill in, ...'
println(' Planning description test passed!')
}
fn test_planning_example() ! {
// Initialize DBPlanning for testing
mut mydb := db.new_test()!
mut db_planning := DBPlanning{
db: &mydb
}
// Create a new planning
mut args := PlanningArg{
name: 'example_test_planning'
description: 'Test planning for example'
color: '#AAAAAA'
timezone: 'UTC'
is_public: true
events: []u32{}
}
planning := db_planning.new(args)!
// Test example method for each methodname
set_call, set_result := planning.example('set')
assert set_call == '{"calendar": {"name": "My Planning", "description": "A personal calendar", "color": "#FF0000", "timezone": "UTC", "is_public": true, "events": []}}'
assert set_result == '1'
get_call, get_result := planning.example('get')
assert get_call == '{"id": 1}'
assert get_result == '{"name": "My Planning", "description": "A personal calendar", "color": "#FF0000", "timezone": "UTC", "is_public": true, "events": []}'
delete_call, delete_result := planning.example('delete')
assert delete_call == '{"id": 1}'
assert delete_result == 'true'
exist_call, exist_result := planning.example('exist')
assert exist_call == '{"id": 1}'
assert exist_result == 'true'
list_call, list_result := planning.example('list')
assert list_call == '{}'
assert list_result == '[{"name": "My Planning", "description": "A personal calendar", "color": "#FF0000", "timezone": "UTC", "is_public": true, "events": []}]'
unknown_call, unknown_result := planning.example('unknown')
assert unknown_call == '{}'
assert unknown_result == '{}'
println(' Planning example test passed!')
}

View File

@@ -14,6 +14,7 @@ pub mut:
fs_items []RegistrationFileAttachment // link to docs
white_list []u32 // users who can enter, if 1 specified then people need to be in this list
white_list_accepted []u32 // if in this list automatically accepted
required_list []u32 // users who must be part of the event
black_list []u32 // users not allowed
start_time u64 // time when users can start registration
end_time u64 // time when registration desk stops