11package models
22
3- import "strings"
3+ import (
4+ "fmt"
5+ "strings"
6+ )
7+
8+ const (
9+ StatusDraft = "draft"
10+ StatusPublished = "published"
11+ StatusClosed = "closed"
12+ )
13+
14+ const (
15+ VisibilityPublic = "public"
16+ VisibilityPrivate = "private"
17+ )
18+
19+ const (
20+ RoleMember = "member"
21+ RoleOfficer = "officer"
22+ RoleAdmin = "admin"
23+ )
424
525type AnswerDetails struct {
626 MaxChars int `bson:"max_chars,omitempty" json:"max_chars,omitempty"`
@@ -20,18 +40,20 @@ type FormQuestion struct {
2040}
2141
2242type Event struct {
23- ID string `bson:"_id" json:"id"`
24- Name string `bson:"name" json:"name"`
25- Date string `bson:"date" json:"date"`
26- EndDate string `bson:"end_date,omitempty" json:"end_date,omitempty"`
27- Time string `bson:"time" json:"time"`
28- Location string `bson:"location" json:"location"`
29- Description string `bson:"description" json:"description"`
30- Admins []string `bson:"admins" json:"admins"`
31- RegistrationForm []FormQuestion `bson:"registration_form" json:"registration_form"`
32- MaxAttendees int `bson:"max_attendees" json:"max_attendees"`
33- CreatedAt string `bson:"created_at" json:"created_at"`
34- Status string `bson:"status" json:"status"` // draft, published, closed
43+ ID string `bson:"_id" json:"id"`
44+ Name string `bson:"name" json:"name"`
45+ Date string `bson:"date" json:"date"`
46+ EndDate string `bson:"end_date,omitempty" json:"end_date,omitempty"`
47+ Time string `bson:"time" json:"time"`
48+ Location string `bson:"location" json:"location"`
49+ Description string `bson:"description" json:"description"`
50+ Admins []string `bson:"admins" json:"admins"`
51+ RegistrationForm []FormQuestion `bson:"registration_form" json:"registration_form"`
52+ MaxAttendees int `bson:"max_attendees" json:"max_attendees"`
53+ CreatedAt string `bson:"created_at" json:"created_at"`
54+ Status string `bson:"status" json:"status"`
55+ Visibility string `bson:"visibility" json:"visibility"`
56+ MinimumVisibleRole string `bson:"minimum_visible_role,omitempty" json:"minimum_visible_role,omitempty"`
3557}
3658
3759type RegistrationFormValidationError struct {
@@ -43,6 +65,113 @@ func (e *RegistrationFormValidationError) Error() string {
4365 return e .Message
4466}
4567
68+ // ApplyDefaults sets safe defaults for newly created events.
69+ func (e * Event ) ApplyDefaults () {
70+ if strings .TrimSpace (e .Status ) == "" {
71+ e .Status = StatusDraft
72+ }
73+
74+ if strings .TrimSpace (e .Visibility ) == "" {
75+ e .Visibility = VisibilityPublic
76+ }
77+
78+ e .normalize ()
79+ }
80+
81+ // normalize enforces internal consistency for dependent fields.
82+ func (e * Event ) normalize () {
83+ e .Status = strings .TrimSpace (e .Status )
84+ e .Visibility = strings .TrimSpace (e .Visibility )
85+ e .MinimumVisibleRole = strings .TrimSpace (e .MinimumVisibleRole )
86+
87+ if e .Visibility == VisibilityPublic {
88+ e .MinimumVisibleRole = ""
89+ }
90+ }
91+
92+ // Validate validates the event as a whole.
93+ // Use this for creates and for PATCH after applying incoming fields onto an existing event.
94+ func (e * Event ) Validate () error {
95+ e .normalize ()
96+
97+ if strings .TrimSpace (e .Name ) == "" {
98+ return fmt .Errorf ("name is required" )
99+ }
100+ if strings .TrimSpace (e .Date ) == "" {
101+ return fmt .Errorf ("date is required" )
102+ }
103+ if strings .TrimSpace (e .Time ) == "" {
104+ return fmt .Errorf ("time is required" )
105+ }
106+ if strings .TrimSpace (e .Location ) == "" {
107+ return fmt .Errorf ("location is required" )
108+ }
109+
110+ if err := ValidateStatus (e .Status ); err != nil {
111+ return err
112+ }
113+ if err := ValidateVisibility (e .Visibility ); err != nil {
114+ return err
115+ }
116+ if err := ValidateMinimumVisibleRole (e .Visibility , e .MinimumVisibleRole ); err != nil {
117+ return err
118+ }
119+ if e .MaxAttendees < 0 {
120+ return fmt .Errorf ("max_attendees cannot be negative" )
121+ }
122+
123+ return nil
124+ }
125+
126+ func ValidateStatus (status string ) error {
127+ switch strings .TrimSpace (status ) {
128+ case StatusDraft , StatusPublished , StatusClosed :
129+ return nil
130+ default :
131+ return fmt .Errorf ("invalid status: must be one of draft, published, closed" )
132+ }
133+ }
134+
135+ func ValidateVisibility (visibility string ) error {
136+ switch strings .TrimSpace (visibility ) {
137+ case VisibilityPublic , VisibilityPrivate :
138+ return nil
139+ default :
140+ return fmt .Errorf ("invalid visibility: must be one of public, private" )
141+ }
142+ }
143+
144+ func ValidateRole (role string ) error {
145+ switch strings .TrimSpace (role ) {
146+ case RoleMember , RoleOfficer , RoleAdmin :
147+ return nil
148+ default :
149+ return fmt .Errorf ("invalid minimum_visible_role: must be one of member, officer, admin" )
150+ }
151+ }
152+
153+ func ValidateMinimumVisibleRole (visibility string , role string ) error {
154+ visibility = strings .TrimSpace (visibility )
155+ role = strings .TrimSpace (role )
156+
157+ switch visibility {
158+ case VisibilityPublic :
159+ if role != "" {
160+ return fmt .Errorf ("minimum_visible_role must be empty when visibility is public" )
161+ }
162+ return nil
163+
164+ case VisibilityPrivate :
165+ if role == "" {
166+ return fmt .Errorf ("minimum_visible_role is required when visibility is private" )
167+ }
168+ return ValidateRole (role )
169+
170+ default :
171+ return fmt .Errorf ("invalid visibility: must be one of public, private" )
172+ }
173+ }
174+
46175// IsAdmin checks if the given userID is in the event's Admins list.
47176func (e * Event ) IsAdmin (userID string ) bool {
48177 for _ , admin := range e .Admins {
@@ -94,3 +223,89 @@ func SanitizeUpdateFields(fields map[string]interface{}) {
94223 delete (fields , "_id" )
95224 delete (fields , "created_at" )
96225}
226+
227+ // ApplyPatch applies supported PATCH fields onto the event.
228+ // It performs type validation for incoming map values.
229+ // After ApplyPatch, call Validate() on the event.
230+ func (e * Event ) ApplyPatch (fields map [string ]interface {}) error {
231+ for key , value := range fields {
232+ switch key {
233+ case "name" :
234+ s , ok := value .(string )
235+ if ! ok {
236+ return fmt .Errorf ("name must be a string" )
237+ }
238+ e .Name = s
239+
240+ case "date" :
241+ s , ok := value .(string )
242+ if ! ok {
243+ return fmt .Errorf ("date must be a string" )
244+ }
245+ e .Date = s
246+
247+ case "end_date" :
248+ s , ok := value .(string )
249+ if ! ok {
250+ return fmt .Errorf ("end_date must be a string" )
251+ }
252+ e .EndDate = s
253+
254+ case "time" :
255+ s , ok := value .(string )
256+ if ! ok {
257+ return fmt .Errorf ("time must be a string" )
258+ }
259+ e .Time = s
260+
261+ case "location" :
262+ s , ok := value .(string )
263+ if ! ok {
264+ return fmt .Errorf ("location must be a string" )
265+ }
266+ e .Location = s
267+
268+ case "description" :
269+ s , ok := value .(string )
270+ if ! ok {
271+ return fmt .Errorf ("description must be a string" )
272+ }
273+ e .Description = s
274+
275+ case "status" :
276+ s , ok := value .(string )
277+ if ! ok {
278+ return fmt .Errorf ("status must be a string" )
279+ }
280+ e .Status = s
281+
282+ case "visibility" :
283+ s , ok := value .(string )
284+ if ! ok {
285+ return fmt .Errorf ("visibility must be a string" )
286+ }
287+ e .Visibility = s
288+
289+ case "minimum_visible_role" :
290+ s , ok := value .(string )
291+ if ! ok {
292+ return fmt .Errorf ("minimum_visible_role must be a string" )
293+ }
294+ e .MinimumVisibleRole = s
295+
296+ case "max_attendees" :
297+ n , ok := value .(float64 )
298+ if ! ok {
299+ return fmt .Errorf ("max_attendees must be a number" )
300+ }
301+ e .MaxAttendees = int (n )
302+
303+ case "admins" , "registration_form" :
304+ // supported by persistence model, but not yet patchable here
305+ return fmt .Errorf ("%s cannot be updated through this endpoint yet" , key )
306+ }
307+ }
308+
309+ e .normalize ()
310+ return nil
311+ }
0 commit comments