From 78f03f7d82dbf9b68efa4d36104ccc951191b32a Mon Sep 17 00:00:00 2001 From: Pascal Bleser Date: Tue, 4 Nov 2025 21:48:49 +0100 Subject: [PATCH] groupware: some fixes accordingly to the latest JMAP and jscalendarbis RFCs --- pkg/jscalendar/jscalendar_model.go | 78 +++---------------- pkg/jscalendar/jscalendar_model_test.go | 23 ++---- .../pkg/groupware/groupware_mock_calendars.go | 12 +-- .../pkg/groupware/groupware_mock_tasks.go | 6 +- 4 files changed, 21 insertions(+), 98 deletions(-) diff --git a/pkg/jscalendar/jscalendar_model.go b/pkg/jscalendar/jscalendar_model.go index acc6701c32..139fe89c16 100644 --- a/pkg/jscalendar/jscalendar_model.go +++ b/pkg/jscalendar/jscalendar_model.go @@ -17,6 +17,7 @@ type LocalDateTime struct { } */ type LocalDateTime string +type UTCDateTime string type TypeOfRelation string type TypeOfLink string @@ -41,7 +42,6 @@ type Relationship string type Display string type Rel string type LocationTypeOption string -type LocationRelation string type VirtualLocationFeature string type Frequency string type Skip string @@ -301,9 +301,6 @@ const ( LocationTypeOptionWaterFacility = LocationTypeOption("water-facility") LocationTypeOptionYouthCamp = LocationTypeOption("youth-camp") - LocationRelationStart = LocationRelation("start") - LocationRelationEnd = LocationRelation("end") - VirtualLocationFeatureAudio = VirtualLocationFeature("audio") VirtualLocationFeatureChat = VirtualLocationFeature("chat") VirtualLocationFeatureFeed = VirtualLocationFeature("feed") @@ -611,11 +608,6 @@ var ( LocationTypeOptionYouthCamp, } - LocationRelations = []LocationRelation{ - LocationRelationStart, - LocationRelationEnd, - } - Frequencies = []Frequency{ FrequencyYearly, FrequencyMonthly, @@ -645,7 +637,6 @@ var ( "relatedTo", "replyTo", "sentBy", - "timeZones", "uid", } @@ -836,13 +827,6 @@ type Link struct { // server to avoid embedding arbitrarily large data in JSCalendar object instances. Href string `json:"href"` - // This MUST be a valid content-id value according to the definition of Section 2 of [RFC2392]. - // - // The value MUST be unique within this `Link` object but has no meaning beyond that. - // - // It MAY be different from the link id for this `Link` object. - Cid string `json:"cid,omitempty"` - // This is the media type [RFC6838] of the resource, if known. ContentType string `json:"contentType,omitempty"` @@ -897,21 +881,6 @@ type Location struct { // The value for each key in the map MUST be `true`. LocationTypes map[LocationTypeOption]bool `json:"locationTypes,omitempty"` - // This specifies the relation between this location and the time of the JSCalendar object. - // - // This is primarily to allow events representing travel to specify the location of departure (at the - // start of the event) and location of arrival (at the end); this is particularly important if these - // locations are in different time zones, as a client may wish to highlight this information for the user. - // - // This MUST be one of the following values; any value the client or server doesn't understand - // should be treated the same as if this property is omitted: - // !- `start`: The event/task described by this JSCalendar object occurs at this location at the time the event/task starts. - // !- `end`: The event/task described by this JSCalendar object occurs at this location at the time the event/task ends. - RelativeTo LocationRelation `json:"relativeTo,omitempty"` - - // This is a time zone for this location. - TimeZone string `json:"timeZone,omitempty"` - // This is a geo: URI [RFC5870] for the location. Coordinates string `json:"coordinates,omitempty"` @@ -1645,15 +1614,11 @@ type CommonObject struct { ProdId string `json:"prodId,omitempty"` // This is the date and time this object was initially created. - // - // TODO serialize as UTCDateTime - Created time.Time `json:"created,omitzero"` + Created UTCDateTime `json:"created,omitzero"` // This is the date and time the data in this object was last modified (or its creation date/time // if not modified since). - // - // TODO serialize as UTCDateTime - Updated time.Time `json:"updated,omitzero"` + Updated UTCDateTime `json:"updated,omitzero"` // This is a short summary of the object. Title string `json:"title,omitempty"` @@ -1719,35 +1684,6 @@ type CommonObject struct { // [Section 4.3 of CSS Color Module Level 3]: https://www.w3.org/TR/css-color-3/#svg-color // [Section 4.2.1 of CSS Color Module Level 3]: https://www.w3.org/TR/css-color-3/#rgb-color Color string `json:"color,omitempty"` - - // This maps identifiers of custom time zones to their time zone definitions. - // - // The following restrictions apply for each key in the map: - // !- To avoid conflict with names in the IANA Time Zone Database [TZDB], it MUST start with the `/` character. - // !- It MUST be a valid `paramtext` value, as specified in Section 3.1 of [RFC5545]. - // !- At least one other property in the same JSCalendar object MUST reference a time zone using this identifier (i.e., - // orphaned time zones are not allowed). - // - // An identifier need only be unique to this JSCalendar object. - // - // It MAY differ from the tzId property value of the TimeZone object it maps to. - // - // A JSCalendar object may be part of a hierarchy of other JSCalendar objects (say, an `Event` is an entry in a `Group`). - // - // In this case, the set of time zones is the sum of the time zone definitions of this object and its parent objects. - // - // If multiple time zones with the same identifier exist, then the definition closest to the calendar object in relation - // to its parents MUST be used. - // - // (In context of `Event`, a time zone definition in its `timeZones` property has precedence over a definition of the - // same id in the `Group`). - // - // Time zone definitions in any children of the calendar object MUST be ignored. - // - // A `TimeZone` object maps a `VTIMEZONE` component from iCalendar, and the semantics are as defined in [RFC5545]. - // - // A valid time zone MUST define at least one transition rule in the `standard` or `daylight` property. - TimeZones map[string]TimeZone `json:"timeZones,omitempty"` } // TODO @@ -2128,6 +2064,14 @@ type Event struct { // the `Event`'s Location objects (see Section 4.2.5). Duration Duration `json:"duration,omitempty"` + // This identifies the time zone in which this event ends, for cases where the start and time zones of the event differ + // (e.g., a transcontinental flight). + // + // If this property is not set, then the event starts and ends in the same time zone. + // + // This property MUST NOT be set if the timeZone property value is null or not set. + EndTimeZone string `json:"endTimeZone,omitempty"` + // This is the scheduling status (Section 4.4) of an Event. // // If set, it MUST be one of the following values, another value registered in the IANA diff --git a/pkg/jscalendar/jscalendar_model_test.go b/pkg/jscalendar/jscalendar_model_test.go index 1da7ac275d..be00c0d0ce 100644 --- a/pkg/jscalendar/jscalendar_model_test.go +++ b/pkg/jscalendar/jscalendar_model_test.go @@ -74,7 +74,6 @@ func TestLink(t *testing.T) { }`, Link{ Type: LinkType, Href: "https://opencloud.eu.example.com/f72ae875-40be-48a4-84ff-aea9aed3e085.png", - Cid: "c1", ContentType: "image/png", Size: 128912, Rel: RelIcon, @@ -115,14 +114,11 @@ func TestLocation(t *testing.T) { LocationTypeOptionLandmarkAddress: true, LocationTypeOptionIndustrial: true, }, - RelativeTo: LocationRelationStart, - TimeZone: "Europe/Paris", Coordinates: "geo:48.8559324,2.2932441", Links: map[string]Link{ "l1": { Type: LinkType, Href: "https://upload.wikimedia.org/wikipedia/commons/f/fd/Eiffel_blue.PNG", - Cid: "cl1", ContentType: "image/png", Size: 12345, Rel: RelIcon, @@ -340,7 +336,6 @@ func TestParticipant(t *testing.T) { "l1": { Type: LinkType, Href: "https://opa.org/opa.png", - Cid: "c1", ContentType: "image/png", Size: 182912, Rel: RelIcon, @@ -608,11 +603,13 @@ func TestTimeZone(t *testing.T) { } func TestEvent(t *testing.T) { - ts1, err := time.Parse(time.RFC3339, "2025-09-25T18:26:14+02:00") + local1 := "2025-09-25T18:26:14" + ts1, err := time.Parse(time.RFC3339, local1+"+02:00") require.NoError(t, err) ts1 = ts1.UTC() - ts2, err := time.Parse(time.RFC3339, "2025-09-29T15:53:01+02:00") + local2 := "2025-09-29T15:53:01" + ts2, err := time.Parse(time.RFC3339, local2+"+02:00") require.NoError(t, err) ts2 = ts2.UTC() @@ -692,8 +689,8 @@ func TestEvent(t *testing.T) { CommonObject: CommonObject{ Uid: "b422cfec-f7b4-4e04-8ec6-b794007f63f1", ProdId: "OpenCloud 1.0", - Created: ts1, - Updated: ts2, + Created: UTCDateTime(local1), + Updated: UTCDateTime(local2), Title: "End of year party", Description: "It's the party at the end of the year.", DescriptionContentType: "text/plain", @@ -713,12 +710,6 @@ func TestEvent(t *testing.T) { "cat": true, }, Color: "oil", - TimeZones: map[string]TimeZone{ - "cest": { - Type: TimeZoneType, - TzId: "cest", - }, - }, }, RelatedTo: map[string]Relation{ "a": { @@ -738,8 +729,6 @@ func TestEvent(t *testing.T) { LocationTypes: map[LocationTypeOption]bool{ LocationTypeOptionBar: true, }, - RelativeTo: LocationRelationStart, - TimeZone: "cest", Coordinates: "geo:16.7685657,-4.8629852", Links: map[string]Link{ "l1": { diff --git a/services/groupware/pkg/groupware/groupware_mock_calendars.go b/services/groupware/pkg/groupware/groupware_mock_calendars.go index bbe396b9e7..f48439e411 100644 --- a/services/groupware/pkg/groupware/groupware_mock_calendars.go +++ b/services/groupware/pkg/groupware/groupware_mock_calendars.go @@ -80,8 +80,8 @@ var E1 = jmap.CalendarEvent{ CommonObject: jscalendar.CommonObject{ Uid: "9a7ab91a-edca-4988-886f-25e00743430d", ProdId: "Mock 0.0", - Created: mustParseTime("2025-09-29T16:17:18Z"), - Updated: mustParseTime("2025-09-29T16:17:18Z"), + Created: "2025-09-29T16:17:18", + Updated: "2025-09-29T16:17:18", Title: "Meeting of the Minds", Description: "Internal meeting about the grand strategy for the future", DescriptionContentType: "text/plain", @@ -103,12 +103,6 @@ var E1 = jmap.CalendarEvent{ "internal": true, }, Color: "purple", - TimeZones: map[string]jscalendar.TimeZone{ - "airee8ai": { - Type: jscalendar.TimeZoneType, - TzId: "CEST", - }, - }, }, RelatedTo: map[string]jscalendar.Relation{}, Sequence: 0, @@ -121,8 +115,6 @@ var E1 = jmap.CalendarEvent{ LocationTypes: map[jscalendar.LocationTypeOption]bool{ jscalendar.LocationTypeOptionOffice: true, }, - RelativeTo: jscalendar.LocationRelationStart, - TimeZone: "CEST", Coordinates: "geo:52.5334956,13.4079872", Links: map[string]jscalendar.Link{ "eefe2pax": { diff --git a/services/groupware/pkg/groupware/groupware_mock_tasks.go b/services/groupware/pkg/groupware/groupware_mock_tasks.go index 37c3d18791..6c984ecade 100644 --- a/services/groupware/pkg/groupware/groupware_mock_tasks.go +++ b/services/groupware/pkg/groupware/groupware_mock_tasks.go @@ -82,8 +82,8 @@ var T1 = jmap.Task{ CommonObject: jscalendar.CommonObject{ Uid: "7da0d4a2-385c-430f-9022-61db302734d9", ProdId: "Mock 0.0", - Created: mustParseTime("2025-10-01T17:31:49Z"), - Updated: mustParseTime("2025-10-01T17:35:12Z"), + Created: "2025-10-01T17:31:49", + Updated: "2025-10-01T17:35:12", Title: "Crossing the Ring", Description: "We need to cross the Ring the protomolecule opened.", DescriptionContentType: "text/plain", @@ -117,8 +117,6 @@ var T1 = jmap.Task{ LocationTypes: map[jscalendar.LocationTypeOption]bool{ jscalendar.LocationTypeOptionLandmarkAddress: true, }, - RelativeTo: jscalendar.LocationRelationStart, - TimeZone: "UTC", Coordinates: "geo:40.4165583,-3.7063595", Links: map[string]jscalendar.Link{ "jeeshei5": {