groupware: more mock data, added missing JMAP types

This commit is contained in:
Pascal Bleser
2025-10-01 12:13:08 +02:00
parent eeccb56d19
commit 2c6ff6cd9e
8 changed files with 668 additions and 79 deletions

View File

@@ -7,11 +7,64 @@ import (
"github.com/opencloud-eu/opencloud/pkg/jscalendar"
)
// https://www.iana.org/assignments/jmap/jmap.xml#jmap-data-types
type ObjectType string
// TODO
type UTCDate struct {
type UTCDateTime struct {
time.Time
}
// TODO
type LocalDate struct {
time.Time
}
// Should the calendars events be used as part of availability calculation?
//
// This MUST be one of:
// !- `all“: all events are considered.
// !- `attending“: events the user is a confirmed or tentative participant of are considered.
// !- `none“: all events are ignored (but may be considered if also in another calendar).
//
// This should default to “all” for the calendars in the users own account, and “none” for calendars shared with the user.
type IncludeInAvailability string
type TypeOfCalendarAlert string
// `CalendarEventNotification` type.
//
// This MUST be one of
// !- `created`
// !- `updated`
// !- `destroyed`
type CalendarEventNotificationTypeOption string
// `Principal` type.
//
// This MUST be one of the following values:
// !- `individual`: This represents a single person.
// !- `group`: This represents a group of people.
// !- `resource`: This represents some resource, e.g. a projector.
// !- `location`: This represents a location.
// !- `other`: This represents some other undefined principal.
type PrincipalTypeOption string
// Algorithms in this list MUST be present in the ["HTTP Digest Algorithm Values" registry]
// defined by [RFC3230]; however, in JMAP, they must be lowercased, e.g., "md5" rather than
// "MD5".
//
// Clients SHOULD prefer algorithms listed earlier in this list.
//
// ["HTTP Digest Algorithm Values" registry]: https://www.iana.org/assignments/http-dig-alg/http-dig-alg.xhtml
type HttpDigestAlgorithm string
// The ResourceType data type is used to act as a unit of measure for the quota usage.
type ResourceType string
// The Scope data type is used to represent the entities the quota applies to.
type Scope string
const (
JmapCore = "urn:ietf:params:jmap:core"
JmapMail = "urn:ietf:params:jmap:mail"
@@ -24,6 +77,30 @@ const (
JmapBlob = "urn:ietf:params:jmap:blob"
JmapQuota = "urn:ietf:params:jmap:quota"
JmapWebsocket = "urn:ietf:params:jmap:websocket"
JmapPrincipals = "urn:ietf:params:jmap:principals"
JmapPrincipalsOwner = "urn:ietf:params:jmap:principals:owner"
CoreType = ObjectType("Core")
PushSubscriptionType = ObjectType("PushSubscription")
MailboxType = ObjectType("Mailbox")
ThreadType = ObjectType("Thread")
EmailType = ObjectType("Email")
EmailDeliveryType = ObjectType("EmailDelivery")
SearchSnippetType = ObjectType("SearchSnippet")
IdentityType = ObjectType("Identity")
EmailSubmissionType = ObjectType("EmailSubmission")
VacationResponseType = ObjectType("VacationResponse")
MDNType = ObjectType("MDN")
QuotaType = ObjectType("Quota")
SieveScriptType = ObjectType("SieveScript")
PrincipalType = ObjectType("PrincipalType")
ShareNotificationType = ObjectType("ShareNotification")
AddressBookType = ObjectType("AddressBook")
ContactCardType = ObjectType("ContactCard")
CalendarType = ObjectType("Calendar")
CalendarEventType = ObjectType("CalendarEvent")
CalendarEventNotificationType = ObjectType("CalendarEventNotification")
ParticipantIdentityType = ObjectType("ParticipantIdentity")
JmapKeywordPrefix = "$"
JmapKeywordSeen = "$seen"
@@ -47,9 +124,71 @@ const (
JmapMailboxRoleSent = "sent"
//JmapMailboxRoleSubscribed = "subscribed"
JmapMailboxRoleTrash = "trash"
CalendarAlertType = TypeOfCalendarAlert("CalendarAlert")
CalendarEventNotificationTypeOptionCreated = CalendarEventNotificationTypeOption("created")
CalendarEventNotificationTypeOptionUpdated = CalendarEventNotificationTypeOption("updated")
CalendarEventNotificationTypeOptionDestroyed = CalendarEventNotificationTypeOption("destroyed")
PrincipalTypeOptionIndividual = PrincipalTypeOption("individual")
PrincipalTypeOptionGroup = PrincipalTypeOption("group")
PrincipalTypeOptionResource = PrincipalTypeOption("resource")
PrincipalTypeOptionLocation = PrincipalTypeOption("location")
PrincipalTypeOptionOther = PrincipalTypeOption("other")
HttpDigestAlgorithmAdler32 = HttpDigestAlgorithm("adler32")
HttpDigestAlgorithmCrc32c = HttpDigestAlgorithm("crc32c")
HttpDigestAlgorithmMd5 = HttpDigestAlgorithm("md5")
HttpDigestAlgorithmSha = HttpDigestAlgorithm("sha")
HttpDigestAlgorithmSha256 = HttpDigestAlgorithm("sha-256")
HttpDigestAlgorithmSha512 = HttpDigestAlgorithm("sha-512")
HttpDigestAlgorithmUnixSum = HttpDigestAlgorithm("unixsum")
HttpDigestAlgorithmUnixcksum = HttpDigestAlgorithm("unixcksum")
// The quota is measured in a number of data type objects.
//
// For example, a quota can have a limit of 50 `Mail` objects.
ResourceTypeCount = ResourceType("count")
// The quota is measured in size (in octets).
//
// For example, a quota can have a limit of 25000 octets.
ResourceTypeOctets = ResourceType("octets")
// The quota information applies to just the client's account.
ScopeAccount = Scope("account")
// The quota information applies to all accounts sharing this domain.
ScopeDomain = Scope("domain")
// The quota information applies to all accounts belonging to the server.
ScopeGlobal = Scope("global")
)
var (
ObjectTypes = []ObjectType{
CoreType,
PushSubscriptionType,
MailboxType,
ThreadType,
EmailType,
EmailDeliveryType,
SearchSnippetType,
IdentityType,
EmailSubmissionType,
VacationResponseType,
MDNType,
QuotaType,
SieveScriptType,
PrincipalType,
ShareNotificationType,
AddressBookType,
ContactCardType,
CalendarType,
CalendarEventType,
CalendarEventNotificationType,
ParticipantIdentityType,
}
JmapMailboxRoles = []string{
JmapMailboxRoleInbox,
JmapMailboxRoleSent,
@@ -57,17 +196,43 @@ var (
JmapMailboxRoleJunk,
JmapMailboxRoleTrash,
}
)
// Should the calendars events be used as part of availability calculation?
//
// This MUST be one of:
// !- `all“: all events are considered.
// !- `attending“: events the user is a confirmed or tentative participant of are considered.
// !- `none“: all events are ignored (but may be considered if also in another calendar).
//
// This should default to “all” for the calendars in the users own account, and “none” for calendars shared with the user.
type IncludeInAvailability string
CalendarEventNotificationOptionTypes = []CalendarEventNotificationTypeOption{
CalendarEventNotificationTypeOptionCreated,
CalendarEventNotificationTypeOptionUpdated,
CalendarEventNotificationTypeOptionDestroyed,
}
PrincipalTypeOptions = []PrincipalTypeOption{
PrincipalTypeOptionIndividual,
PrincipalTypeOptionGroup,
PrincipalTypeOptionResource,
PrincipalTypeOptionLocation,
PrincipalTypeOptionOther,
}
HttpDigestAlgorithms = []HttpDigestAlgorithm{
HttpDigestAlgorithmAdler32,
HttpDigestAlgorithmCrc32c,
HttpDigestAlgorithmMd5,
HttpDigestAlgorithmSha,
HttpDigestAlgorithmSha256,
HttpDigestAlgorithmSha512,
HttpDigestAlgorithmUnixSum,
HttpDigestAlgorithmUnixcksum,
}
ResourceTypes = []ResourceType{
ResourceTypeCount,
ResourceTypeOctets,
}
Scopes = []Scope{
ScopeAccount,
ScopeDomain,
ScopeGlobal,
}
)
const (
IncludeInAvailabilityAll = IncludeInAvailability("all")
@@ -263,7 +428,7 @@ type SessionBlobAccountCapabilities struct {
// Clients SHOULD prefer algorithms listed earlier in this list.
//
// ["HTTP Digest Algorithm Values" registry]: https://www.iana.org/assignments/http-dig-alg/http-dig-alg.xhtml
SupportedDigestAlgorithms []string `json:"supportedDigestAlgorithms"`
SupportedDigestAlgorithms []HttpDigestAlgorithm `json:"supportedDigestAlgorithms"`
}
type SessionQuotaAccountCapabilities struct {
@@ -280,6 +445,20 @@ type SessionContactsAccountCapabilities struct {
MayCreateAddressBook bool `json:"mayCreateAddressBook"`
}
type SessionPrincipalsAccountCapabilities struct {
// The id of the principal in this account that corresponds to the user fetching this object, if any.
CurrentUserPrincipalId string `json:"currentUserPrincipalId,omitempty"`
}
type SessionPrincipalsOwnerAccountCapabilities struct {
// The id of an account with the `urn:ietf:params:jmap:principals` capability that contains the
// corresponding `Principal` object.
AccountIdForPrincipal string `json:"accountIdForPrincipal,omitempty"`
// The id of the `Principal` that owns this account.
PrincipalId string `json:"principalId,omitempty"`
}
type SessionAccountCapabilities struct {
Mail SessionMailAccountCapabilities `json:"urn:ietf:params:jmap:mail"`
Submission SessionSubmissionAccountCapabilities `json:"urn:ietf:params:jmap:submission"`
@@ -288,9 +467,11 @@ type SessionAccountCapabilities struct {
Blob SessionBlobAccountCapabilities `json:"urn:ietf:params:jmap:blob"`
Quota SessionQuotaAccountCapabilities `json:"urn:ietf:params:jmap:quota"`
Contacts SessionContactsAccountCapabilities `json:"urn:ietf:params:jmap:contacts"`
Principals *SessionPrincipalsAccountCapabilities `json:"urn:ietf:params:jmap:principals,omitempty"`
PrincipalsOwner *SessionPrincipalsOwnerAccountCapabilities `json:"urn:ietf:params:jmap:principals:owner,omitempty"`
}
type SessionAccount struct {
type Account struct {
// A user-friendly string to show when presenting content from this account, e.g., the email address representing the owner of the account.
Name string `json:"name,omitempty"`
// This is true if the account belongs to the authenticated user rather than a group account or a personal account of another user that has been shared with them.
@@ -357,6 +538,9 @@ type SessionWebsocketCapabilities struct {
type SessionContactsCapabilities struct {
}
type SessionPrincipalCapabilities struct {
}
type SessionCapabilities struct {
Core SessionCoreCapabilities `json:"urn:ietf:params:jmap:core"`
Mail SessionMailCapabilities `json:"urn:ietf:params:jmap:mail"`
@@ -366,7 +550,8 @@ type SessionCapabilities struct {
Blob SessionBlobCapabilities `json:"urn:ietf:params:jmap:blob"`
Quota SessionQuotaCapabilities `json:"urn:ietf:params:jmap:quota"`
Websocket SessionWebsocketCapabilities `json:"urn:ietf:params:jmap:websocket"`
Contacts SessionContactsCapabilities `json:"urn:ietf:params:jmap:contacts"`
Contacts *SessionContactsCapabilities `json:"urn:ietf:params:jmap:contacts"`
Principals *SessionPrincipalCapabilities `json:"urn:ietf:params:jmap:principals"`
}
type SessionPrimaryAccounts struct {
@@ -387,7 +572,7 @@ type State string
type SessionResponse struct {
Capabilities SessionCapabilities `json:"capabilities"`
Accounts map[string]SessionAccount `json:"accounts,omitempty"`
Accounts map[string]Account `json:"accounts,omitempty"`
// A map of capability URIs (as found in accountCapabilities) to the account id that is considered to be the users main or default
// account for data pertaining to that capability.
@@ -2010,15 +2195,6 @@ type EmailSubmissionSetResponse struct {
// TODO(pbleser-oc) add updated and destroyed when they are needed
}
type ObjectType string
const (
VacationResponseType ObjectType = "VacationResponse"
EmailType ObjectType = "Email"
EmailDeliveryType ObjectType = "EmailDelivery"
MailboxType ObjectType = "Mailbox"
)
type Command string
type Invocation struct {
@@ -3160,23 +3336,347 @@ type CalendarEvent struct {
// `recurrenceId` within a particular account.
Id string `json:"id"`
baseEventId string
// This is only defined if the `id` property is a synthetic id, generated by the
// server to represent a particular instance of a recurring event (immutable; server-set).
//
// This property gives the id of the "real" `CalendarEvent` this was generated from.
BaseEventId string `json:"baseEventId,omitempty"`
calendarIds map[string]bool
// The set of Calendar ids this event belongs to.
//
// An event MUST belong to one or more Calendars at all times (until it is destroyed).
//
// The set is represented as an object, with each key being a Calendar id.
//
// The value for each key in the object MUST be `true`.
CalendarIds map[string]bool `json:"calendarIds,omitempty"`
isDraft bool
// If true, this event is to be considered a draft.
//
// The server will not send any scheduling messages to participants or send push notifications
// for alerts.
//
// This may only be set to `true` upon creation.
//
// Once set to `false`, the value cannot be updated to `true`.
//
// This property MUST NOT appear in `recurrenceOverrides`.
IsDraft bool `json:"isDraft,omitzero"`
isOrigin bool
// Is this the authoritative source for this event (i.e., does it control scheduling for
// this event; the event has not been added as a result of an invitation from another calendar system)?
//
// This is true if, and only if:
// !- the events `replyTo` property is null; or
// !- the account will receive messages sent to at least one of the methods specified in the `replyTo` property of the event.
IsOrigin bool `json:"isOrigin,omitzero"`
utcStart UTCDate
// For simple clients that do not implement time zone support.
//
// Clients should only use this if also asking the server to expand recurrences, as you cannot accurately
// expand a recurrence without the original time zone.
//
// This property is calculated at fetch time by the server.
//
// Time zones are political and they can and do change at any time.
//
// Fetching exactly the same property again may return a different results if the time zone data has been updated on the server.
//
// Time zone data changes are not considered `updates` to the event.
//
// If set, the server will convert the UTC date to the event's current time zone and store the local time.
//
// This property is not included in `CalendarEvent/get` responses by default and must be requested explicitly.
//
// Floating events (events without a time zone) will be interpreted as per the time zone given as a `CalendarEvent/get` argument.
//
// Note that it is not possible to accurately calculate the expansion of recurrence rules or recurrence overrides with the
// `utcStart` property rather than the local start time. Even simple recurrences such as "repeat weekly" may cross a
// daylight-savings boundary and end up at a different UTC time. Clients that wish to use "utcStart" are RECOMMENDED to
// request the server expand recurrences.
UtcStart UTCDateTime `json:"utcStart,omitzero"`
utcEnd UTCDate
// TODO https://jmap.io/spec-calendars.html#calendar-events
// The server calculates the end time in UTC from the start/timeZone/duration properties of the event.
//
// This property is not included by default and must be requested explicitly.
//
// Like `utcStart`, it is calculated at fetch time if requested and may change due to time zone data changes.
//
// Floating events will be interpreted as per the time zone given as a `CalendarEvent/get` argument.
UtcEnd UTCDateTime `json:"utcEnd,omitzero"`
jscalendar.Event
}
// A ParticipantIdentity stores information about a URI that represents the user within that account in an events participants.
type ParticipantIdentity struct {
// The id of the ParticipantIdentity (immutable; server-set).
Id string `json:"id"`
// The display name of the participant to use when adding this participant to an event, e.g. "Joe Bloggs".
//
// default:
Name string `json:"name,omitempty"`
// The URI that represents this participant for scheduling.
//
// This URI MAY also be the URI for one of the sendTo methods.
ScheduleId string `json:"scheduleId"`
// Represents methods by which the participant may receive invitations and updates to an event.
//
// The keys in the property value are the available methods and MUST only contain ASCII alphanumeric
// characters (`A-Za-z0-9`).
//
// The value is a URI for the method specified in the key.
SendTo map[string]string `json:"sendTo,omitempty"`
// This SHOULD be true for exactly one participant identity in any account, and MUST NOT be true for more
// than one participant identity within an account (server-set).
//
// The default identity should be used by clients whenever they need to choose an identity for the user
// within this account, and they do not have any other information on which to make a choice.
//
// For example, if creating a scheduled event in this account, the default identity may be automatically
// added as an owner. (But the client may ignore this if, for example, it has its own feature to allow
// users to choose which identity to use based on the invitees.)
IsDefault bool `json:"isDefault,omitzero"`
}
type CalendarAlert struct {
// This MUST be the string `CalendarAlert`.
Type TypeOfCalendarAlert `json:"@type,omitempty"`
// The account id for the calendar in which the alert triggered.
AccountId string `json:"accountId"`
// The CalendarEvent id for the alert that triggered.
//
// Note, for a recurring event this is the id of the base event, never a synthetic id for a particular instance.
CalendarEventId string `json:"calendarEventId"`
// The uid property of the CalendarEvent for the alert that triggered.
Uid string `json:"uid"`
// The `recurrenceId` for the instance of the event for which this alert is being
// triggered, or null if the event is not recurring.
RecurrenceId LocalDate `json:"recurrenceId,omitzero"`
// The id for the alert that triggered.
AlertId string `json:"alertId"`
}
type Person struct {
// The name of the person who made the change.
Name string `json:"name"`
// The email of the person who made the change, or null if no email is available.
Email string `json:"email,omitempty"`
// The id of the `Principal` corresponding to the person who made the change, if any.
//
// This will be null if the change was due to receving an iTIP message.
PrincipalId string `json:"principalId,omitempty"`
// The `scheduleId` URI of the person who made the change, if any.
//
// This will normally be set if the change was made due to receving an iTIP message.
ScheduleId string `json:"scheduleId,omitempty"`
}
type CalendarEventNotification struct {
// The id of the `CalendarEventNotification`.
Id string `json:"id"`
// The time this notification was created.
Created UTCDateTime `json:"created,omitzero"`
// Who made the change.
ChangedBy *Person `json:"person,omitempty"`
// Comment sent along with the change by the user that made it.
//
// (e.g. `COMMENT` property in an iTIP message), if any.
Comment string `json:"comment,omitempty"`
// `CalendarEventNotification` type.
//
// This MUST be one of
// !- `created`
// !- `updated`
// !- `destroyed`
Type CalendarEventNotificationTypeOption `json:"type"`
// The id of the CalendarEvent that this notification is about.
//
// If the change only affects a single instance of a recurring event, the server MAY set the
// `event` and `event`atch properties for just that instance; the `calendarEventId` MUST
// still be for the base event.
CalendarEventId string `json:"calendarEventId"`
// Is this event a draft? (created/updated only)
IsDraft bool `json:"isDraft,omitzero"`
// The data before the change (if updated or destroyed),
// or the data after creation (if created).
Event *jscalendar.Event `json:"event,omitempty"`
// A patch encoding the change between the data in the event property,
// and the data after the update (updated only).
EventPatch PatchObject `json:"eventPatch,omitempty"`
}
// A Principal represents an individual, group, location (e.g. a room), resource (e.g. a projector) or other entity
// in a collaborative environment.
//
// Sharing in JMAP is generally configured by assigning rights to certain data within an account to other principals,
// for example a user may assign permission to read their calendar to a principal representing another user, or their team.
//
// In a shared environment such as a workplace, a user may have access to a large number of principals.
//
// In most systems the user will have access to a single `Account` containing `Principal` objects, but they may
// have access to multiple if, for example, aggregating data from different places.
type Principal struct {
// The id of the principal.
Id string `json:"id"`
// `Principal` type.
//
// This MUST be one of the following values:
// !- `individual`: This represents a single person.
// !- `group`: This represents a group of people.
// !- `resource`: This represents some resource, e.g. a projector.
// !- `location`: This represents a location.
// !- `other`: This represents some other undefined principal.
Type PrincipalTypeOption `json:"type"`
// The name of the principal, e.g. `"Jane Doe"`, or `"Room 4B"`.
Name string `json:"name"`
// A longer description of the principal, for example details about the
// facilities of a resource, or null if no description available.
Description string `json:"description,omitempty"`
// An email address for the principal, or null if no email is available.
Email string `json:"email,omitempty"`
// The time zone for this principal, if known.
//
// If not null, the value MUST be a time zone id from the IANA Time Zone Database TZDB.
TimeZone string `json:"timeZone,omitempty"`
// A map of JMAP capability URIs to domain specific information about the principal in relation
// to that capability, as defined in the document that registered the capability.
Capabilities map[string]any `json:"capabilities,omitempty"`
// A map of account id to `Account` object for each JMAP Account containing data for
// this principal that the user has access to, or null if none.
Accounts map[string]Account `json:"accounts,omitempty"`
}
// TODO https://jmap.io/spec-sharing.html#object-properties
type ShareNotification struct {
}
type Shareable struct {
// Has the user indicated they wish to see this data?
//
// The initial value for this when data is shared by another user is implementation dependent,
// although data types may give advice on appropriate defaults.
IsSubscribed bool `json:"isSubscribed,omitzero"`
// The set of permissions the user currently has.
//
// Appropriate permissions are domain specific and must be defined per data type.
MyRights map[string]bool `json:"myRights,omitempty"`
// A map of principal id to rights to give that principal, or null if not shared with anyone.
//
// The account id for the principal id can be found in the capabilities of the `Account` this object is in.
//
// Users with appropriate permission may set this property to modify who the data is shared with.
//
// The principal that owns the account this data is in MUST NOT be in the set of sharees; their rights are implicit.
ShareWith map[string]map[string]bool `json:"shareWith,omitempty"`
}
// The Quota is an object that displays the limit set to an account usage.
//
// It then shows as well the current usage in regard to that limit.
type Quota struct {
// The unique identifier for this object.
Id string `json:"id"`
// The resource type of the quota.
ResourceType ResourceType `json:"resourceType"`
// The current usage of the defined quota, using the `resourceType` defined as unit of measure.
//
// Computation of this value is handled by the server.
Used uint `json:"used"`
// The hard limit set by this quota, using the `resourceType` defined as unit of measure.
//
// Objects in scope may not be created or updated if this limit is reached.
HardLimit uint `json:"hardLimit"`
// The Scope data type is used to represent the entities the quota applies to.
//
// It is defined as a "String" with values from the following set:
// !- `account`: The quota information applies to just the client's account.
// !- `domain`: The quota information applies to all accounts sharing this domain.
// !- `global`: The quota information applies to all accounts belonging to the server.
Scope Scope `json:"scope"`
// The name of the quota.
//
// Useful for managing quotas and using queries for searching.
Name string `json:"name"`
// A list of all the type names as defined in the "JMAP Types Names" registry
// (e.g., `Email`, `Calendar`, etc.) to which this quota applies.
//
// This allows the quotas to be assigned to distinct or shared data types.
//
// The server MUST filter out any types for which the client did not request the associated capability
// in the `using` section of the request.
//
// Further, the server MUST NOT return Quota objects for which there are no types recognized by the client.
Types []ObjectType `json:"types,omitempty"`
// The warn limit set by this quota, using the `resourceType` defined as unit of measure.
//
// It can be used to send a warning to an entity about to reach the hard limit soon, but with no
// action taken yet.
//
// If set, it SHOULD be lower than the `softLimit` (if present and different from null) and the `hardLimit`.
WarnLimit uint `json:"warnLimit,omitzero"`
// The soft limit set by this quota, using the `resourceType` defined as unit of measure.
//
// It can be used to still allow some operations but refuse some others.
//
// What is allowed or not is up to the server.
//
// For example, it could be used for blocking outgoing events of an entity (sending emails, creating
// calendar events, etc.) while still receiving incoming events (receiving emails, receiving calendars
// events, etc.).
//
// If set, it SHOULD be higher than the `warnLimit` (if present and different from null) but lower
// than the `hardLimit`.
SoftLimit uint `json:"softLimit,omitzero"`
// Arbitrary, free, human-readable description of this quota.
//
// It might be used to explain where the different limits come from and explain the entities and data
// types this quota applies to.
//
// The description MUST be encoded in UTF-8 [RFC3629] as described in [RFC8620], Section 1.5, and
// selected based on an `Accept-Language` header in the request (as defined in [RFC9110], Section 12.5.4)
// or out-of-band information about the user's language or locale.
Description string `json:"description,omitempty"`
}
type ErrorResponse struct {
Type string `json:"type"`
Description string `json:"description,omitempty"`

View File

@@ -37,7 +37,6 @@ type SignedDuration string // TODO
type Relationship string
type Display string
type Rel string
type Method string
type LocationTypeOption string
type LocationRelation string
type VirtualLocationFeature string
@@ -225,17 +224,6 @@ const (
RelWorkingCopy = Rel("working-copy")
RelWorkingCopyOf = Rel("working-copy-of")
MethodPublish = Method("publish")
MethodRequest = Method("request")
MethodReply = Method("reply")
MethodAdd = Method("add")
MethodCancel = Method("cancel")
MethodRefresh = Method("refresh")
MethodCounter = Method("counter")
MethodDeclineCounter = Method("declinecounter")
// mlr --csv --headerless-csv-output cut -f Token ./location-type-registry-1.csv |sort|perl -ne 'chomp; print "LocationTypeOption".ucfirst($_)." = LocationTypeOption(\"".$_."\")\n"'
LocationTypeOptionAircraft = LocationTypeOption("aircraft")
LocationTypeOptionAirport = LocationTypeOption("airport")
LocationTypeOptionArena = LocationTypeOption("arena")
@@ -537,17 +525,6 @@ var (
RelWorkingCopyOf,
}
Methods = []Method{
MethodPublish,
MethodRequest,
MethodReply,
MethodAdd,
MethodCancel,
MethodRefresh,
MethodCounter,
MethodDeclineCounter,
}
LocationTypeOptions = []LocationTypeOption{
LocationTypeOptionAircraft,
LocationTypeOptionAirport,
@@ -1781,10 +1758,11 @@ type Object struct {
// to know which version of the object a scheduling message relates to.
Sequence uint `json:"sequence,omitzero"`
// This is the iTIP [RFC5546] method, in lowercase.
//
// This MUST only be present if the JSCalendar object represents an iTIP scheduling message.
Method Method `json:"method,omitempty"`
/*
// CalendarEvent objects MUST NOT have a “method” property as this is only used when representing iTIP
// [@!RFC5546] scheduling messages, not events in a data store.
Method Method `json:"method,omitempty"`
*/
// This indicates that the time is not important to display to the user when rendering this calendar object.
//
@@ -2232,3 +2210,5 @@ func (g *Group) UnmarshalJSON(b []byte) error {
type tmp Group
return json.Unmarshal(b, (*tmp)(g))
}
// mlr --csv --headerless-csv-output cut -f Token ./location-type-registry-1.csv |sort|perl -ne 'chomp; print "LocationTypeOption".ucfirst($_)." = LocationTypeOption(\"".$_."\")\n"'

View File

@@ -727,7 +727,6 @@ func TestEvent(t *testing.T) {
},
},
Sequence: 3,
Method: MethodRefresh,
ShowWithoutTime: true,
Locations: map[string]Location{
"loc1": {

View File

@@ -9,7 +9,6 @@ examples:
emailReceivedAt: '2025-09-23T10:58:03Z'
emailSentAt: '2025-09-23T12:58:03+02:00'
blobId: 'cfz7vkmhcfwl1gfln02hga2fb3xwsqirirousda0rs1soeosla2p1aiaahcqjwaf'
attachmentName: 'Alloy_Yellow_Scale.pdf'
attachmentType: 'application/pdf'
attachmentSize: 192128
attachmentDisposition: 'attachment'

View File

@@ -13,7 +13,7 @@ import (
type SwaggerGetAccountResponse struct {
// in: body
Body struct {
*jmap.SessionAccount
*jmap.Account
}
}
@@ -40,7 +40,7 @@ func (g *Groupware) GetAccount(w http.ResponseWriter, r *http.Request) {
// swagger:response GetAccountsResponse200
type SwaggerGetAccountsResponse struct {
// in: body
Body map[string]jmap.SessionAccount
Body map[string]jmap.Account
}
// swagger:route GET /groupware/accounts account accounts

View File

@@ -20,7 +20,7 @@ var C1 = jmap.Calendar{
Type: jscalendar.AlertType,
Trigger: jscalendar.AbsoluteTrigger{
Type: jscalendar.AbsoluteTriggerType,
When: MustParse("2025-09-30T20:34:12Z"),
When: mustParseTime("2025-09-30T20:34:12Z"),
},
},
},
@@ -63,17 +63,25 @@ var AllCalendars = []jmap.Calendar{C1}
var E1 = jmap.CalendarEvent{
Id: "ovei9oqu",
CalendarIds: map[string]bool{
C1.Id: true,
},
BaseEventId: "ahtah9qu",
IsDraft: true,
IsOrigin: true,
UtcStart: jmap.UTCDateTime{Time: mustParseTime("2025-10-01T00:00:00Z")},
UtcEnd: jmap.UTCDateTime{Time: mustParseTime("2025-10-07T00:00:00Z")},
Event: jscalendar.Event{
Type: jscalendar.EventType,
Start: jscalendar.LocalDateTime{Time: MustParse("2025-09-30T12:00:00Z")},
Start: jscalendar.LocalDateTime{Time: mustParseTime("2025-09-30T12:00:00Z")},
Duration: "PT30M",
Status: jscalendar.StatusConfirmed,
Object: jscalendar.Object{
CommonObject: jscalendar.CommonObject{
Uid: "9a7ab91a-edca-4988-886f-25e00743430d",
ProdId: "Mock 0.0",
Created: MustParse("2025-09-29T16:17:18Z"),
Updated: MustParse("2025-09-29T16:17:18Z"),
Created: mustParseTime("2025-09-29T16:17:18Z"),
Updated: mustParseTime("2025-09-29T16:17:18Z"),
Title: "Meeting of the Minds",
Description: "Internal meeting about the grand strategy for the future",
DescriptionContentType: "text/plain",
@@ -104,7 +112,6 @@ var E1 = jmap.CalendarEvent{
},
RelatedTo: map[string]jscalendar.Relation{},
Sequence: 0,
Method: jscalendar.MethodAdd,
ShowWithoutTime: false,
Locations: map[string]jscalendar.Location{
"ux1uokie": {
@@ -139,7 +146,111 @@ var E1 = jmap.CalendarEvent{
},
},
},
// TODO more properties, a lot more properties
RecurrenceRules: []jscalendar.RecurrenceRule{
{
Type: jscalendar.RecurrenceRuleType,
Frequency: jscalendar.FrequencyWeekly,
Interval: 1,
Rscale: jscalendar.RscaleIso8601,
Skip: jscalendar.SkipOmit,
FirstDayOfWeek: jscalendar.DayOfWeekMonday,
Count: 4,
},
},
FreeBusyStatus: jscalendar.FreeBusyStatusBusy,
Privacy: jscalendar.PrivacyPublic,
ReplyTo: map[jscalendar.ReplyMethod]string{
jscalendar.ReplyMethodImip: "mailto:organizer@example.com",
},
SentBy: "organizer@example.com",
Participants: map[string]jscalendar.Participant{
"eegh7uph": {
Type: jscalendar.ParticipantType,
Name: "Anderson Dawes",
Email: "adawes@opa.org",
Description: "Called the meeting",
SendTo: map[jscalendar.SendToMethod]string{
jscalendar.SendToMethodImip: "mailto:adawes@opa.org",
},
Kind: jscalendar.ParticipantKindIndividual,
Roles: map[jscalendar.Role]bool{
jscalendar.RoleAttendee: true,
jscalendar.RoleChair: true,
jscalendar.RoleOwner: true,
},
LocationId: "ux1uokie",
Language: "en-GB",
ParticipationStatus: jscalendar.ParticipationStatusAccepted,
ParticipationComment: "I'll be there for sure",
ExpectReply: true,
ScheduleAgent: jscalendar.ScheduleAgentServer,
ScheduleSequence: 1,
ScheduleStatus: []string{"1.0"},
ScheduleUpdated: mustParseTime("2025-10-01T11:59:12Z"),
SentBy: "adawes@opa.org",
InvitedBy: "eegh7uph",
Links: map[string]jscalendar.Link{
"ieni5eiw": {
Type: jscalendar.LinkType,
Href: "https://static.wikia.nocookie.net/expanse/images/1/1e/OPA_leader.png/revision/latest?cb=20250121103410",
ContentType: "image/png",
Rel: jscalendar.RelIcon,
Size: 192812,
Display: jscalendar.DisplayBadge,
Title: "Anderson Dawes' photo",
},
},
ScheduleId: "mailto:adawes@opa.org",
},
"xeikie9p": {
Type: jscalendar.ParticipantType,
Name: "Klaes Ashford",
Email: "ashford@opa.org",
Description: "As the first officer on the Behemoth",
SendTo: map[jscalendar.SendToMethod]string{
jscalendar.SendToMethodImip: "mailto:ashford@opa.org",
jscalendar.SendToMethodOther: "https://behemoth.example.com/ping/@ashford",
},
Kind: jscalendar.ParticipantKindIndividual,
Roles: map[jscalendar.Role]bool{
jscalendar.RoleAttendee: true,
},
LocationId: "em4eal0o",
Language: "en-GB",
ParticipationStatus: jscalendar.ParticipationStatusNeedsAction,
ExpectReply: true,
ScheduleAgent: jscalendar.ScheduleAgentServer,
ScheduleSequence: 0,
SentBy: "adawes@opa.org",
InvitedBy: "eegh7uph",
Links: map[string]jscalendar.Link{
"oifooj6g": {
Type: jscalendar.LinkType,
Href: "https://static.wikia.nocookie.net/expanse/images/0/02/Klaes_Ashford_-_Expanse_season_4_promotional_2.png/revision/latest?cb=20191206012007",
ContentType: "image/png",
Rel: jscalendar.RelIcon,
Size: 201291,
Display: jscalendar.DisplayBadge,
Title: "Ashford on Medina Station",
},
},
ScheduleId: "mailto:ashford@opa.org",
},
},
Alerts: map[string]jscalendar.Alert{
"ahqu4xi0": {
Type: jscalendar.AlertType,
Trigger: jscalendar.OffsetTrigger{
Type: jscalendar.OffsetTriggerType,
Offset: "PT-5M",
RelativeTo: jscalendar.RelativeToStart,
},
},
},
TimeZone: "UTC",
MayInviteSelf: true,
MayInviteOthers: true,
HideAttendees: false,
},
},
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/opencloud-eu/opencloud/pkg/jscontact"
)
func MustParse(text string) time.Time {
func mustParseTime(text string) time.Time {
t, err := time.Parse(time.RFC3339, text)
if err != nil {
panic(err)
@@ -53,8 +53,8 @@ var CaminaDrummerContact = jscontact.ContactCard{
A2.Id: true,
},
Version: jscontact.JSContactVersion_1_0,
Created: MustParse("2025-09-30T11:00:12Z").UTC(),
Updated: MustParse("2025-09-30T11:00:12Z").UTC(),
Created: mustParseTime("2025-09-30T11:00:12Z").UTC(),
Updated: mustParseTime("2025-09-30T11:00:12Z").UTC(),
Kind: jscontact.ContactCardKindIndividual,
Language: "en-GB",
ProdId: "Mock 0.0",
@@ -330,7 +330,7 @@ var CaminaDrummerContact = jscontact.ContactCard{
Notes: map[string]jscontact.Note{
"n1": {
Type: jscontact.NoteType,
Created: MustParse("2025-09-30T11:00:12Z").UTC(),
Created: mustParseTime("2025-09-30T11:00:12Z").UTC(),
Author: &jscontact.Author{
Type: jscontact.AuthorType,
Name: "expanse.fandom.com",
@@ -348,8 +348,8 @@ var AndersonDawesContact = jscontact.ContactCard{
A1.Id: true,
},
Version: jscontact.JSContactVersion_1_0,
Created: MustParse("2025-09-30T11:00:12Z").UTC(),
Updated: MustParse("2025-09-30T11:00:12Z").UTC(),
Created: mustParseTime("2025-09-30T11:00:12Z").UTC(),
Updated: mustParseTime("2025-09-30T11:00:12Z").UTC(),
Kind: jscontact.ContactCardKindIndividual,
Language: "en-GB",
ProdId: "Mock 0.0",
@@ -544,7 +544,7 @@ var AndersonDawesContact = jscontact.ContactCard{
Kind: jscontact.AnniversaryKindBirth,
Date: jscontact.Timestamp{
Type: jscontact.TimestampType,
Utc: MustParse("1961-08-24T00:00:00Z"),
Utc: mustParseTime("1961-08-24T00:00:00Z"),
},
},
},

View File

@@ -105,17 +105,17 @@ func (r Request) GetAccountIdForSubmission() (string, *Error) {
return r.getAccountId(r.session.PrimaryAccounts.Blob, errNoPrimaryAccountForSubmission)
}
func (r Request) GetAccountForMail() (jmap.SessionAccount, *Error) {
func (r Request) GetAccountForMail() (jmap.Account, *Error) {
accountId, err := r.GetAccountIdForMail()
if err != nil {
return jmap.SessionAccount{}, err
return jmap.Account{}, err
}
account, ok := r.session.Accounts[accountId]
if !ok {
r.logger.Debug().Msgf("failed to find account '%v'", accountId)
// TODO metric for inexistent accounts
return jmap.SessionAccount{}, apiError(r.errorId(), ErrorNonExistingAccount,
return jmap.Account{}, apiError(r.errorId(), ErrorNonExistingAccount,
withDetail(fmt.Sprintf("The account '%v' does not exist", log.SafeString(accountId))),
withSource(&ErrorSource{Parameter: UriParamAccountId}),
)