module heromodels import incubaid.herolib.data.encoder import incubaid.herolib.data.ourtime import incubaid.herolib.hero.db import incubaid.herolib.schemas.jsonrpc { Response, new_error, new_response, new_response_false, new_response_int, new_response_true } import incubaid.herolib.hero.user { UserRef } import json // 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_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 []PlanningRecurrenceRule // will automatically schedule, uses calendar_id as template invite_rules []PlanningRecurrenceRule // times in which people can invite themselves attendees_required []u32 attendees_optional []u32 // if we want to specify upfront } pub struct PlanningRecurrenceRule { pub mut: until u64 // End date (Unix timestamp) by_weekday []u8 // Days of week (0=Sunday) by_monthday []u8 // Days of month hour_from u8 // Start hour (0-23) hour_to u8 // End hour (0-23) duration int // Duration in minutes priority u8 // Priority level (0-10) } pub fn (self PlanningRecurrenceRule) 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 PlanningRecurrenceRule) 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: db &db.DB @[skip; str: skip] } @[params] pub struct PlanningListArg { pub mut: is_public bool calendar_template_id u32 registration_desk_id u32 limit int = 100 // Default limit is 100 } pub fn (self Planning) type_name() string { return 'planning' } // return example rpc call and result for each methodname pub fn (self Planning) description(methodname string) string { match methodname { 'set' { return 'Create or update a planning. Returns the ID of the planning.' } 'get' { return 'Retrieve a planning by ID. Returns the planning object.' } 'delete' { return 'Delete a planning by ID. Returns true if successful.' } 'exist' { return 'Check if a planning exists by ID. Returns true or false.' } 'list' { return 'List all plannings. Returns an array of planning objects.' } else { return 'This is generic method for the root object, TODO fill in, ...' } } } // return example rpc call and result for each methodname pub fn (self Planning) example(methodname string) (string, string) { match methodname { 'set' { return '{"planning": {"name": "My Planning", "description": "A personal planning", "color": "#FF0000", "timezone": "UTC", "is_public": true, "calendar_template_id": 1, "registration_desk_id": 10, "autoschedule_rules": [{"until": 1893456000, "by_weekday": [1, 3, 5], "by_monthday": [], "hour_from": 9, "hour_to": 17, "duration": 30, "priority": 5}], "invite_rules": [{"until": 0, "by_weekday": [], "by_monthday": [1, 15], "hour_from": 10, "hour_to": 12, "duration": 60, "priority": 8}], "attendees_required": [100, 101], "attendees_optional": [200]}}', '1' } 'get' { return '{"id": 1}', '{"name": "My Planning", "description": "A personal planning", "color": "#FF0000", "timezone": "UTC", "is_public": true, "calendar_template_id": 1, "registration_desk_id": 10, "autoschedule_rules": [{"until": 1893456000, "by_weekday": [1, 3, 5], "by_monthday": [], "hour_from": 9, "hour_to": 17, "duration": 30, "priority": 5}], "invite_rules": [{"until": 0, "by_weekday": [], "by_monthday": [1, 15], "hour_from": 10, "hour_to": 12, "duration": 60, "priority": 8}], "attendees_required": [100, 101], "attendees_optional": [200]}' } 'delete' { return '{"id": 1}', 'true' } 'exist' { return '{"id": 1}', 'true' } 'list' { return '{}', '[{"name": "My Planning", "description": "A personal planning", "color": "#FF0000", "timezone": "UTC", "is_public": true, "calendar_template_id": 1, "registration_desk_id": 10, "autoschedule_rules": [{"until": 1893456000, "by_weekday": [1, 3, 5], "by_monthday": [], "hour_from": 9, "hour_to": 17, "duration": 30, "priority": 5}], "invite_rules": [{"until": 0, "by_weekday": [], "by_monthday": [1, 15], "hour_from": 10, "hour_to": 12, "duration": 60, "priority": 8}], "attendees_required": [100, 101], "attendees_optional": [200]}]' } else { return '{}', '{}' } } } 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_template_id) e.add_u32(self.registration_desk_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)! } // Encode attendees_required array e.add_list_u32(self.attendees_required) // Encode attendees_optional array e.add_list_u32(self.attendees_optional) } 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_template_id = e.get_u32()! o.registration_desk_id = e.get_u32()! // Decode autoschedule_rules array autoschedule_rules_len := e.get_u16()! mut autoschedule_rules := []PlanningRecurrenceRule{} for _ in 0 .. autoschedule_rules_len { mut rule := PlanningRecurrenceRule{} 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 := []PlanningRecurrenceRule{} for _ in 0 .. invite_rules_len { mut rule := PlanningRecurrenceRule{} rule.load(mut e)! invite_rules << rule } o.invite_rules = invite_rules // Decode attendees_required array o.attendees_required = e.get_list_u32()! // Decode attendees_optional array o.attendees_optional = e.get_list_u32()! } @[params] pub struct PlanningArg { pub mut: id u32 name string description string color string timezone string is_public bool calendar_template_id u32 registration_desk_id u32 autoschedule_rules []PlanningRecurrenceRule invite_rules []PlanningRecurrenceRule attendees_required []u32 attendees_optional []u32 securitypolicy u32 tags []string messages []db.MessageArg } // get new calendar, not from the DB pub fn (mut self DBPlanning) new(args PlanningArg) !Planning { mut o := Planning{ color: args.color timezone: args.timezone is_public: args.is_public calendar_template_id: args.calendar_template_id registration_desk_id: args.registration_desk_id autoschedule_rules: args.autoschedule_rules invite_rules: args.invite_rules attendees_required: args.attendees_required attendees_optional: args.attendees_optional } // Set base fields o.name = args.name o.description = args.description o.securitypolicy = args.securitypolicy o.tags = self.db.tags_get(args.tags)! o.messages = self.db.messages_get(args.messages)! o.updated_at = ourtime.now().unix() return o } pub fn (mut self DBPlanning) set(o Planning) !Planning { // Use db set function which returns the object with assigned ID return self.db.set[Planning](o)! } pub fn (mut self DBPlanning) delete(id u32) !bool { // Check if the item exists before trying to delete if !self.db.exists[Planning](id)! { return false } self.db.delete[Planning](id)! return true } pub fn (mut self DBPlanning) exist(id u32) !bool { return self.db.exists[Planning](id)! } pub fn (mut self DBPlanning) get(id u32) !Planning { mut o, data := self.db.get_data[Planning](id)! mut e_decoder := encoder.decoder_new(data) self.load(mut o, mut e_decoder)! return o } pub fn (mut self DBPlanning) list(args PlanningListArg) ![]Planning { // Get all plannings from the database all_plannings := self.db.list[Planning]()!.map(self.get(it)!) // Apply filters mut filtered_plannings := []Planning{} for planning in all_plannings { // Filter by is_public if provided if args.is_public && !planning.is_public { continue } // Filter by calendar_template_id if provided if args.calendar_template_id != 0 && planning.calendar_template_id != args.calendar_template_id { continue } // Filter by registration_desk_id if provided if args.registration_desk_id != 0 && planning.registration_desk_id != args.registration_desk_id { continue } filtered_plannings << planning } // Limit results to 100 or the specified limit mut limit := args.limit if limit > 100 { limit = 100 } if filtered_plannings.len > limit { return filtered_plannings[..limit] } return filtered_plannings } pub fn planning_handle(mut f ModelsFactory, rpcid int, servercontext map[string]string, userref UserRef, method string, params string) !Response { match method { 'get' { id := db.decode_u32(params)! res := f.planning.get(id)! return new_response(rpcid, json.encode(res)) } 'set' { mut args := db.decode_generic[PlanningArg](params)! mut o := f.planning.new(args)! if args.id != 0 { o.id = args.id } o = f.planning.set(o)! return new_response_int(rpcid, int(o.id)) } 'delete' { id := db.decode_u32(params)! deleted := f.planning.delete(id)! if deleted { return new_response_true(rpcid) } else { return new_error(rpcid, code: 404 message: 'Planning with ID ${id} not found' ) } } 'exist' { id := db.decode_u32(params)! if f.planning.exist(id)! { return new_response_true(rpcid) } else { return new_response_false(rpcid) } } 'list' { args := db.decode_generic[PlanningListArg](params)! res := f.planning.list(args)! return new_response(rpcid, json.encode(res)) } else { return new_error(rpcid, code: 32601 message: 'Method ${method} not found on planning' ) } } }