mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-04-06 16:44:00 -04:00
groupware: add addressbook and calendar creation APIs
* add Groupware APIs for creating and deleting addressbooks * add Groupware APIs for creating and deleting calendars * add JMAP APIs for creating and deleting addressbooks, calendars * add JMAP APIs to retrieve Principals * fix API tagging * move addressbook JMAP APIs into its own file * move addressbook Groupware APIs into its own file
This commit is contained in:
118
pkg/jmap/api_addressbook.go
Normal file
118
pkg/jmap/api_addressbook.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package jmap
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
var NS_ADDRESSBOOKS = ns(JmapContacts)
|
||||
|
||||
type AddressBooksResponse struct {
|
||||
AddressBooks []AddressBook `json:"addressbooks"`
|
||||
NotFound []string `json:"notFound,omitempty"`
|
||||
}
|
||||
|
||||
func (j *Client) GetAddressbooks(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (AddressBooksResponse, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetAddressbooks", session, logger)
|
||||
|
||||
cmd, err := j.request(session, logger, NS_ADDRESSBOOKS,
|
||||
invocation(CommandAddressBookGet, AddressBookGetCommand{AccountId: accountId, Ids: ids}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return AddressBooksResponse{}, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (AddressBooksResponse, State, Error) {
|
||||
var response AddressBookGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandAddressBookGet, "0", &response)
|
||||
if err != nil {
|
||||
return AddressBooksResponse{}, response.State, err
|
||||
}
|
||||
return AddressBooksResponse{
|
||||
AddressBooks: response.List,
|
||||
NotFound: response.NotFound,
|
||||
}, response.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
type AddressBookChanges struct {
|
||||
HasMoreChanges bool `json:"hasMoreChanges"`
|
||||
OldState State `json:"oldState,omitempty"`
|
||||
NewState State `json:"newState"`
|
||||
Created []AddressBook `json:"created,omitempty"`
|
||||
Updated []AddressBook `json:"updated,omitempty"`
|
||||
Destroyed []string `json:"destroyed,omitempty"`
|
||||
}
|
||||
|
||||
// Retrieve Address Book changes since a given state.
|
||||
// @apidoc addressbook,changes
|
||||
func (j *Client) GetAddressbookChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, maxChanges uint) (AddressBookChanges, SessionState, State, Language, Error) {
|
||||
return changesTemplate(j, "GetAddressbookChanges", NS_ADDRESSBOOKS,
|
||||
CommandAddressBookChanges, CommandAddressBookGet,
|
||||
func() AddressBookChangesCommand {
|
||||
return AddressBookChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
|
||||
},
|
||||
func(path string, rof string) AddressBookGetRefCommand {
|
||||
return AddressBookGetRefCommand{
|
||||
AccountId: accountId,
|
||||
IdsRef: &ResultReference{
|
||||
Name: CommandAddressBookChanges,
|
||||
Path: path,
|
||||
ResultOf: rof,
|
||||
},
|
||||
}
|
||||
},
|
||||
func(resp AddressBookChangesResponse) (State, State, bool, []string) {
|
||||
return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
|
||||
},
|
||||
func(resp AddressBookGetResponse) []AddressBook { return resp.List },
|
||||
func(oldState, newState State, hasMoreChanges bool, created, updated []AddressBook, destroyed []string) AddressBookChanges {
|
||||
return AddressBookChanges{
|
||||
OldState: oldState,
|
||||
NewState: newState,
|
||||
HasMoreChanges: hasMoreChanges,
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
Destroyed: destroyed,
|
||||
}
|
||||
},
|
||||
func(resp AddressBookGetResponse) State { return resp.State },
|
||||
session, ctx, logger, acceptLanguage,
|
||||
)
|
||||
}
|
||||
|
||||
func (j *Client) CreateAddressBook(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create AddressBookChange) (*AddressBook, SessionState, State, Language, Error) {
|
||||
return createTemplate(j, "CreateAddressBook", NS_ADDRESSBOOKS, AddressBookType, CommandAddressBookSet, CommandAddressBookGet,
|
||||
func(accountId string, create map[string]AddressBookChange) AddressBookSetCommand {
|
||||
return AddressBookSetCommand{AccountId: accountId, Create: create}
|
||||
},
|
||||
func(accountId string, ref string) AddressBookGetCommand {
|
||||
return AddressBookGetCommand{AccountId: accountId, Ids: []string{ref}}
|
||||
},
|
||||
func(resp AddressBookSetResponse) map[string]*AddressBook {
|
||||
return resp.Created
|
||||
},
|
||||
func(resp AddressBookSetResponse) map[string]SetError {
|
||||
return resp.NotCreated
|
||||
},
|
||||
func(resp AddressBookGetResponse) []AddressBook {
|
||||
return resp.List
|
||||
},
|
||||
func(resp AddressBookSetResponse) State {
|
||||
return resp.NewState
|
||||
},
|
||||
accountId, session, ctx, logger, acceptLanguage, create,
|
||||
)
|
||||
}
|
||||
|
||||
func (j *Client) DeleteAddressBook(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
|
||||
return deleteTemplate(j, "DeleteAddressBook", NS_ADDRESSBOOKS, CommandAddressBookSet,
|
||||
func(accountId string, destroy []string) AddressBookSetCommand {
|
||||
return AddressBookSetCommand{AccountId: accountId, Destroy: destroy}
|
||||
},
|
||||
func(resp AddressBookSetResponse) map[string]SetError { return resp.NotDestroyed },
|
||||
func(resp AddressBookSetResponse) State { return resp.NewState },
|
||||
accountId, destroy, session, ctx, logger, acceptLanguage,
|
||||
)
|
||||
}
|
||||
@@ -161,6 +161,8 @@ type CalendarEventChanges struct {
|
||||
Destroyed []string `json:"destroyed,omitempty"`
|
||||
}
|
||||
|
||||
// Retrieve the changes in Calendar Events since a given State.
|
||||
// @api:tags event,changes
|
||||
func (j *Client) GetCalendarEventChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
|
||||
acceptLanguage string, sinceState State, maxChanges uint) (CalendarEventChanges, SessionState, State, Language, Error) {
|
||||
return changesTemplate(j, "GetCalendarEventChanges", NS_CALENDARS,
|
||||
@@ -229,3 +231,38 @@ func (j *Client) DeleteCalendarEvent(accountId string, destroy []string, session
|
||||
func(resp CalendarEventSetResponse) State { return resp.NewState },
|
||||
accountId, destroy, session, ctx, logger, acceptLanguage)
|
||||
}
|
||||
|
||||
func (j *Client) CreateCalendar(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create CalendarChange) (*Calendar, SessionState, State, Language, Error) {
|
||||
return createTemplate(j, "CreateCalendar", NS_CALENDARS, CalendarType, CommandAddressBookSet, CommandAddressBookGet,
|
||||
func(accountId string, create map[string]CalendarChange) CalendarSetCommand {
|
||||
return CalendarSetCommand{AccountId: accountId, Create: create}
|
||||
},
|
||||
func(accountId string, ref string) CalendarGetCommand {
|
||||
return CalendarGetCommand{AccountId: accountId, Ids: []string{ref}}
|
||||
},
|
||||
func(resp CalendarSetResponse) map[string]*Calendar {
|
||||
return resp.Created
|
||||
},
|
||||
func(resp CalendarSetResponse) map[string]SetError {
|
||||
return resp.NotCreated
|
||||
},
|
||||
func(resp CalendarGetResponse) []Calendar {
|
||||
return resp.List
|
||||
},
|
||||
func(resp CalendarSetResponse) State {
|
||||
return resp.NewState
|
||||
},
|
||||
accountId, session, ctx, logger, acceptLanguage, create,
|
||||
)
|
||||
}
|
||||
|
||||
func (j *Client) DeleteCalendar(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
|
||||
return deleteTemplate(j, "DeleteCalendar", NS_ADDRESSBOOKS, CommandAddressBookSet,
|
||||
func(accountId string, destroy []string) CalendarSetCommand {
|
||||
return CalendarSetCommand{AccountId: accountId, Destroy: destroy}
|
||||
},
|
||||
func(resp CalendarSetResponse) map[string]SetError { return resp.NotDestroyed },
|
||||
func(resp CalendarSetResponse) State { return resp.NewState },
|
||||
accountId, destroy, session, ctx, logger, acceptLanguage,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ func (s StateMap) MarshalZerologObject(e *zerolog.Event) {
|
||||
// if s.Quotas != nil { e.Str("quotas", string(*s.Quotas)) }
|
||||
}
|
||||
|
||||
// Retrieve the changes in any type of objects at once since a given State.
|
||||
// @api:tags changes
|
||||
func (j *Client) GetChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, stateMap StateMap, maxChanges uint) (Changes, SessionState, State, Language, Error) { //NOSONAR
|
||||
logger = log.From(j.logger("GetChanges", session, logger).With().Object("state", stateMap).Uint("maxChanges", maxChanges))
|
||||
|
||||
|
||||
@@ -11,80 +11,6 @@ import (
|
||||
|
||||
var NS_CONTACTS = ns(JmapContacts)
|
||||
|
||||
type AddressBooksResponse struct {
|
||||
AddressBooks []AddressBook `json:"addressbooks"`
|
||||
NotFound []string `json:"notFound,omitempty"`
|
||||
}
|
||||
|
||||
func (j *Client) GetAddressbooks(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (AddressBooksResponse, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetAddressbooks", session, logger)
|
||||
|
||||
cmd, err := j.request(session, logger, NS_CONTACTS,
|
||||
invocation(CommandAddressBookGet, AddressBookGetCommand{AccountId: accountId, Ids: ids}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return AddressBooksResponse{}, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (AddressBooksResponse, State, Error) {
|
||||
var response AddressBookGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandAddressBookGet, "0", &response)
|
||||
if err != nil {
|
||||
return AddressBooksResponse{}, response.State, err
|
||||
}
|
||||
return AddressBooksResponse{
|
||||
AddressBooks: response.List,
|
||||
NotFound: response.NotFound,
|
||||
}, response.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
type AddressBookChanges struct {
|
||||
HasMoreChanges bool `json:"hasMoreChanges"`
|
||||
OldState State `json:"oldState,omitempty"`
|
||||
NewState State `json:"newState"`
|
||||
Created []AddressBook `json:"created,omitempty"`
|
||||
Updated []AddressBook `json:"updated,omitempty"`
|
||||
Destroyed []string `json:"destroyed,omitempty"`
|
||||
}
|
||||
|
||||
// Retrieve Address Book changes since a given state.
|
||||
// @apidoc addressbook,changes
|
||||
func (j *Client) GetAddressbookChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, maxChanges uint) (AddressBookChanges, SessionState, State, Language, Error) {
|
||||
return changesTemplate(j, "GetAddressbookChanges", NS_CONTACTS,
|
||||
CommandAddressBookChanges, CommandAddressBookGet,
|
||||
func() AddressBookChangesCommand {
|
||||
return AddressBookChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
|
||||
},
|
||||
func(path string, rof string) AddressBookGetRefCommand {
|
||||
return AddressBookGetRefCommand{
|
||||
AccountId: accountId,
|
||||
IdsRef: &ResultReference{
|
||||
Name: CommandAddressBookChanges,
|
||||
Path: path,
|
||||
ResultOf: rof,
|
||||
},
|
||||
}
|
||||
},
|
||||
func(resp AddressBookChangesResponse) (State, State, bool, []string) {
|
||||
return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
|
||||
},
|
||||
func(resp AddressBookGetResponse) []AddressBook { return resp.List },
|
||||
func(oldState, newState State, hasMoreChanges bool, created, updated []AddressBook, destroyed []string) AddressBookChanges {
|
||||
return AddressBookChanges{
|
||||
OldState: oldState,
|
||||
NewState: newState,
|
||||
HasMoreChanges: hasMoreChanges,
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
Destroyed: destroyed,
|
||||
}
|
||||
},
|
||||
func(resp AddressBookGetResponse) State { return resp.State },
|
||||
session, ctx, logger, acceptLanguage,
|
||||
)
|
||||
}
|
||||
|
||||
func (j *Client) GetContactCardsById(accountId string, session *Session, ctx context.Context, logger *log.Logger,
|
||||
acceptLanguage string, contactIds []string) (map[string]jscontact.ContactCard, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetContactCardsById", session, logger)
|
||||
@@ -132,6 +58,8 @@ type ContactCardChanges struct {
|
||||
Destroyed []string `json:"destroyed,omitempty"`
|
||||
}
|
||||
|
||||
// Retrieve the changes in Contact Cards since a given State.
|
||||
// @api:tags contact,changes
|
||||
func (j *Client) GetContactCardChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
|
||||
acceptLanguage string, sinceState State, maxChanges uint) (ContactCardChanges, SessionState, State, Language, Error) {
|
||||
return changesTemplate(j, "GetContactCardChanges", NS_CONTACTS,
|
||||
|
||||
@@ -204,7 +204,8 @@ type EmailChanges struct {
|
||||
Destroyed []string `json:"destroyed,omitempty"`
|
||||
}
|
||||
|
||||
// Get all the Emails that have been created, updated or deleted since a given state.
|
||||
// Retrieve the changes in Emails since a given State.
|
||||
// @api:tags email,changes
|
||||
func (j *Client) GetEmailChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (EmailChanges, SessionState, State, Language, Error) { //NOSONAR
|
||||
logger = j.loggerParams("GetEmailChanges", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies).Str(logSinceState, string(sinceState))
|
||||
@@ -1080,6 +1081,8 @@ type EmailSubmissionChanges struct {
|
||||
Destroyed []string `json:"destroyed,omitempty"`
|
||||
}
|
||||
|
||||
// Retrieve the changes in Email Submissions since a given State.
|
||||
// @api:tags email,changes
|
||||
func (j *Client) GetEmailSubmissionChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
|
||||
acceptLanguage string, sinceState State, maxChanges uint) (EmailSubmissionChanges, SessionState, State, Language, Error) {
|
||||
return changesTemplate(j, "GetEmailSubmissionChanges", NS_MAIL_SUBMISSION,
|
||||
|
||||
@@ -180,6 +180,8 @@ type IdentityChanges struct {
|
||||
Destroyed []string `json:"destroyed,omitempty"`
|
||||
}
|
||||
|
||||
// Retrieve the changes in Email Identities since a given State.
|
||||
// @api:tags email,changes
|
||||
func (j *Client) GetIdentityChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
|
||||
acceptLanguage string, sinceState State, maxChanges uint) (IdentityChanges, SessionState, State, Language, Error) {
|
||||
return changesTemplate(j, "GetIdentityChanges", NS_IDENTITY,
|
||||
|
||||
@@ -13,7 +13,7 @@ var NS_MAILBOX = ns(JmapMail)
|
||||
|
||||
type MailboxesResponse struct {
|
||||
Mailboxes []Mailbox `json:"mailboxes"`
|
||||
NotFound []any `json:"notFound"`
|
||||
NotFound []string `json:"notFound"`
|
||||
}
|
||||
|
||||
func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (MailboxesResponse, SessionState, State, Language, Error) {
|
||||
@@ -172,6 +172,7 @@ func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx conte
|
||||
}
|
||||
|
||||
// Retrieve Mailbox changes of multiple Accounts.
|
||||
// @api:tags email,changes
|
||||
func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceStateMap map[string]State, maxChanges uint) (map[string]MailboxChanges, SessionState, State, Language, Error) { //NOSONAR
|
||||
return changesTemplateN(j, "GetMailboxChangesForMultipleAccounts", NS_MAILBOX,
|
||||
accountIds, sinceStateMap, CommandMailboxChanges, CommandMailboxGet,
|
||||
|
||||
@@ -20,6 +20,8 @@ type Objects struct {
|
||||
EmailSubmissions *EmailSubmissionGetResponse `json:"submissions,omitempty"`
|
||||
}
|
||||
|
||||
// Retrieve objects of all types by their identifiers in a single batch.
|
||||
// @api:tags changes
|
||||
func (j *Client) GetObjects(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, //NOSONAR
|
||||
mailboxIds []string, emailIds []string,
|
||||
addressbookIds []string, contactIds []string,
|
||||
|
||||
37
pkg/jmap/api_principal.go
Normal file
37
pkg/jmap/api_principal.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package jmap
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
var NS_PRINCIPALS = ns(JmapPrincipals)
|
||||
|
||||
type PrincipalsResponse struct {
|
||||
Principals []Principal `json:"principals"`
|
||||
NotFound []string `json:"notFound,omitempty"`
|
||||
}
|
||||
|
||||
func (j *Client) GetPrincipals(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (PrincipalsResponse, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetPrincipals", session, logger)
|
||||
|
||||
cmd, err := j.request(session, logger, NS_PRINCIPALS,
|
||||
invocation(CommandPrincipalGet, PrincipalGetCommand{AccountId: accountId, Ids: ids}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return PrincipalsResponse{}, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (PrincipalsResponse, State, Error) {
|
||||
var response PrincipalGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandPrincipalGet, "0", &response)
|
||||
if err != nil {
|
||||
return PrincipalsResponse{}, response.State, err
|
||||
}
|
||||
return PrincipalsResponse{
|
||||
Principals: response.List,
|
||||
NotFound: response.NotFound,
|
||||
}, response.State, nil
|
||||
})
|
||||
}
|
||||
@@ -29,6 +29,8 @@ type QuotaChanges struct {
|
||||
Destroyed []string `json:"destroyed,omitempty"`
|
||||
}
|
||||
|
||||
// Retrieve the changes in Quotas since a given State.
|
||||
// @api:tags quota,changes
|
||||
func (j *Client) GetQuotaChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
|
||||
acceptLanguage string, sinceState State, maxChanges uint) (QuotaChanges, SessionState, State, Language, Error) {
|
||||
return changesTemplate(j, "GetQuotaChanges", NS_QUOTA,
|
||||
|
||||
@@ -26,6 +26,55 @@ const (
|
||||
EnableMediaWithBlobId = false
|
||||
)
|
||||
|
||||
type AddressBooksBoxes struct {
|
||||
sharedReadOnly bool
|
||||
sharedReadWrite bool
|
||||
sharedDelete bool
|
||||
sortOrdered bool
|
||||
}
|
||||
|
||||
func TestAddressBooks(t *testing.T) {
|
||||
if skip(t) {
|
||||
return
|
||||
}
|
||||
|
||||
require := require.New(t)
|
||||
|
||||
s, err := newStalwartTest(t, withDirectoryQueries(true))
|
||||
require.NoError(err)
|
||||
defer s.Close()
|
||||
|
||||
user := pickUser()
|
||||
session := s.Session(user.name)
|
||||
|
||||
principalIds := []string{}
|
||||
{
|
||||
principals, _, _, _, err := s.client.GetPrincipals(session.PrimaryAccounts.Mail, session, s.ctx, s.logger, "", []string{})
|
||||
require.NoError(err)
|
||||
require.NotEmpty(principals.Principals)
|
||||
principalIds = structs.Map(principals.Principals, func(p Principal) string { return p.Id })
|
||||
}
|
||||
|
||||
num := uint(5 + rand.Intn(30))
|
||||
{
|
||||
accountId := ""
|
||||
a, boxes, abooks, err := s.fillAddressBook(t, num, session, user, principalIds)
|
||||
require.NoError(err)
|
||||
require.NotEmpty(a)
|
||||
require.Len(abooks, int(num))
|
||||
accountId = a
|
||||
|
||||
ids := structs.Map(abooks, func(a AddressBook) string { return a.Id })
|
||||
{
|
||||
errMap, _, _, _, err := s.client.DeleteAddressBook(accountId, ids, session, s.ctx, s.logger, "")
|
||||
require.NoError(err)
|
||||
require.Empty(errMap)
|
||||
}
|
||||
|
||||
allBoxesAreTicked(t, boxes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContacts(t *testing.T) {
|
||||
if skip(t) {
|
||||
return
|
||||
@@ -97,6 +146,73 @@ type ContactsBoxes struct {
|
||||
|
||||
var streetNumberRegex = regexp.MustCompile(`^(\d+)\s+(.+)$`)
|
||||
|
||||
func (s *StalwartTest) fillAddressBook(
|
||||
t *testing.T,
|
||||
count uint,
|
||||
session *Session,
|
||||
_ User,
|
||||
principalIds []string,
|
||||
) (string, AddressBooksBoxes, []AddressBook, error) {
|
||||
require := require.New(t)
|
||||
|
||||
accountId := session.PrimaryAccounts.Contacts
|
||||
require.NotEmpty(accountId, "no primary account for contacts in session")
|
||||
|
||||
boxes := AddressBooksBoxes{}
|
||||
created := []AddressBook{}
|
||||
|
||||
printer := func(s string) { log.Println(s) }
|
||||
|
||||
for i := range count {
|
||||
name := gofakeit.Company()
|
||||
description := gofakeit.SentenceSimple()
|
||||
subscribed := gofakeit.Bool()
|
||||
abook := AddressBookChange{
|
||||
Name: &name,
|
||||
Description: &description,
|
||||
IsSubscribed: &subscribed,
|
||||
}
|
||||
if i%2 == 0 {
|
||||
abook.SortOrder = posUIntPtr(gofakeit.Uint())
|
||||
boxes.sortOrdered = true
|
||||
}
|
||||
var sharing *AddressBookRights = nil
|
||||
switch i % 4 {
|
||||
default:
|
||||
// no sharing
|
||||
case 1:
|
||||
sharing = &AddressBookRights{MayRead: true, MayWrite: true, MayAdmin: false, MayDelete: false}
|
||||
boxes.sharedReadWrite = true
|
||||
case 2:
|
||||
sharing = &AddressBookRights{MayRead: true, MayWrite: false, MayAdmin: false, MayDelete: false}
|
||||
boxes.sharedReadOnly = true
|
||||
case 3:
|
||||
sharing = &AddressBookRights{MayRead: true, MayWrite: true, MayAdmin: false, MayDelete: true}
|
||||
boxes.sharedDelete = true
|
||||
}
|
||||
if sharing != nil {
|
||||
numPrincipals := 1 + rand.Intn(len(principalIds)-1)
|
||||
m := make(map[string]AddressBookRights, numPrincipals)
|
||||
for _, p := range pickRandomN(numPrincipals, principalIds...) {
|
||||
m[p] = *sharing
|
||||
}
|
||||
abook.ShareWith = m
|
||||
}
|
||||
|
||||
a, sessionState, state, _, err := s.client.CreateAddressBook(accountId, session, s.ctx, s.logger, "", abook)
|
||||
if err != nil {
|
||||
return accountId, boxes, created, err
|
||||
}
|
||||
require.NotEmpty(sessionState)
|
||||
require.NotEmpty(state)
|
||||
require.NotNil(a)
|
||||
created = append(created, *a)
|
||||
|
||||
printer(fmt.Sprintf("📔 created %*s/%v id=%v", int(math.Log10(float64(count))+1), strconv.Itoa(int(i+1)), count, a.Id))
|
||||
}
|
||||
return accountId, boxes, created, nil
|
||||
}
|
||||
|
||||
func (s *StalwartTest) fillContacts( //NOSONAR
|
||||
t *testing.T,
|
||||
count uint,
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"text/template"
|
||||
@@ -120,7 +121,7 @@ tracer.log.level = "trace"
|
||||
tracer.log.lossy = false
|
||||
tracer.log.multiline = false
|
||||
tracer.log.type = "stdout"
|
||||
sharing.allow-directory-query = false
|
||||
sharing.allow-directory-query = {{.dirquery}}
|
||||
auth.dkim.sign = false
|
||||
auth.dkim.verify = "disable"
|
||||
auth.spf.verify.ehlo = "disable"
|
||||
@@ -134,11 +135,11 @@ auth.iprev.verify = "disable"
|
||||
|
||||
func skip(t *testing.T) bool {
|
||||
if os.Getenv("CI") == "woodpecker" {
|
||||
t.Skip("Skipping tests because CI==wookpecker")
|
||||
t.Skip("Skipping tests because CI==woodpecker")
|
||||
return true
|
||||
}
|
||||
if os.Getenv("CI_SYSTEM_NAME") == "woodpecker" {
|
||||
t.Skip("Skipping tests because CI_SYSTEM_NAME==wookpecker")
|
||||
t.Skip("Skipping tests because CI_SYSTEM_NAME==woodpecker")
|
||||
return true
|
||||
}
|
||||
if os.Getenv("USE_TESTCONTAINERS") == "false" {
|
||||
@@ -206,7 +207,13 @@ func (lc *stalwartTestLogConsumer) Accept(l testcontainers.Log) {
|
||||
fmt.Print("STALWART: " + string(l.Content))
|
||||
}
|
||||
|
||||
func newStalwartTest(t *testing.T) (*StalwartTest, error) { //NOSONAR
|
||||
func withDirectoryQueries(allowDirectoryQueries bool) func(map[string]any) {
|
||||
return func(m map[string]any) {
|
||||
m["dirquery"] = strconv.FormatBool(allowDirectoryQueries)
|
||||
}
|
||||
}
|
||||
|
||||
func newStalwartTest(t *testing.T, options ...func(map[string]any)) (*StalwartTest, error) { //NOSONAR
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
var _ context.CancelFunc = cancel // ignore context leak warning: it is passed in the struct and called in Close()
|
||||
|
||||
@@ -235,14 +242,20 @@ func newStalwartTest(t *testing.T) (*StalwartTest, error) { //NOSONAR
|
||||
|
||||
hostname := "localhost"
|
||||
|
||||
configBuf := bytes.NewBufferString("")
|
||||
template.Must(template.New("").Parse(configTemplate)).Execute(configBuf, map[string]any{
|
||||
settings := map[string]any{
|
||||
"hostname": hostname,
|
||||
"masterusername": masterUsername,
|
||||
"masterpassword": masterPasswordHash,
|
||||
"httpPort": httpPort,
|
||||
"imapsPort": imapsPort,
|
||||
})
|
||||
"dirquery": "false",
|
||||
}
|
||||
for _, option := range options {
|
||||
option(settings)
|
||||
}
|
||||
|
||||
configBuf := bytes.NewBufferString("")
|
||||
template.Must(template.New("").Parse(configTemplate)).Execute(configBuf, settings)
|
||||
config := configBuf.String()
|
||||
configReader := strings.NewReader(config)
|
||||
|
||||
@@ -1128,7 +1141,10 @@ func pickUser() User {
|
||||
}
|
||||
|
||||
func pickRandoms[T any](s ...T) []T {
|
||||
n := rand.Intn(len(s))
|
||||
return pickRandomN[T](rand.Intn(len(s)), s...)
|
||||
}
|
||||
|
||||
func pickRandomN[T any](n int, s ...T) []T {
|
||||
if n == 0 {
|
||||
return []T{}
|
||||
}
|
||||
@@ -1165,17 +1181,18 @@ func pickLocale() string {
|
||||
func allBoxesAreTicked[S any](t *testing.T, s S, exceptions ...string) {
|
||||
v := reflect.ValueOf(s)
|
||||
typ := v.Type()
|
||||
tname := typ.Name()
|
||||
for i := range v.NumField() {
|
||||
name := typ.Field(i).Name
|
||||
if slices.Contains(exceptions, name) {
|
||||
log.Printf("(/) %s\n", name)
|
||||
log.Printf("%s[🍒] %s\n", tname, name)
|
||||
continue
|
||||
}
|
||||
value := v.Field(i).Bool()
|
||||
if value {
|
||||
log.Printf("(X) %s\n", name)
|
||||
log.Printf("%s[✅] %s\n", tname, name)
|
||||
} else {
|
||||
log.Printf("( ) %s\n", name)
|
||||
log.Printf("%s[❌] %s\n", tname, name)
|
||||
}
|
||||
require.True(t, value, "should be true: %v", name)
|
||||
}
|
||||
|
||||
@@ -2861,7 +2861,7 @@ type MailboxGetResponse struct {
|
||||
|
||||
// This array contains the ids passed to the method for records that do not exist.
|
||||
// The array is empty if all requested ids were found or if the ids argument passed in was either null or an empty array.
|
||||
NotFound []any `json:"notFound"`
|
||||
NotFound []string `json:"notFound"`
|
||||
}
|
||||
|
||||
type MailboxChangesCommand struct {
|
||||
@@ -4037,6 +4037,141 @@ type Calendar struct {
|
||||
MyRights *CalendarRights `json:"myRights,omitempty"`
|
||||
}
|
||||
|
||||
type CalendarChange struct {
|
||||
// The user-visible name of the calendar.
|
||||
//
|
||||
// This may be any UTF-8 string of at least 1 character in length and maximum 255 octets in size.
|
||||
Name string `json:"name"`
|
||||
|
||||
// An optional longer-form description of the calendar, to provide context in shared environments
|
||||
// where users need more than just the name.
|
||||
Description string `json:"description,omitempty"`
|
||||
|
||||
// A color to be used when displaying events associated with the calendar.
|
||||
//
|
||||
// If not null, the value MUST be a case-insensitive color name taken from the set of names
|
||||
// defined in Section 4.3 of CSS Color Module Level 3 COLORS, or an RGB value in hexadecimal
|
||||
// notation, as defined in Section 4.2.1 of CSS Color Module Level 3.
|
||||
//
|
||||
// The color SHOULD have sufficient contrast to be used as text on a white background.
|
||||
Color string `json:"color,omitempty"`
|
||||
|
||||
// Defines the sort order of calendars when presented in the client’s UI, so it is consistent
|
||||
// between devices.
|
||||
//
|
||||
// The number MUST be an integer in the range 0 <= sortOrder < 2^31.
|
||||
//
|
||||
// A calendar with a lower order should be displayed before a calendar with a higher order in any
|
||||
// list of calendars in the client’s UI.
|
||||
//
|
||||
// Calendars with equal order SHOULD be sorted in alphabetical order by name.
|
||||
//
|
||||
// The sorting should take into account locale-specific character order convention.
|
||||
SortOrder uint `json:"sortOrder,omitzero"`
|
||||
|
||||
// True if the user has indicated they wish to see this Calendar in their client.
|
||||
//
|
||||
// This SHOULD default to `false` for Calendars in shared accounts the user has access to and `true`
|
||||
// for any new Calendars created by the user themself.
|
||||
//
|
||||
// If false, the calendar SHOULD only be displayed when the user explicitly requests it or to offer
|
||||
// it for the user to subscribe to.
|
||||
//
|
||||
// For example, a company may have a large number of shared calendars which all employees have
|
||||
// permission to access, but you would only subscribe to the ones you care about and want to be able
|
||||
// to have normally accessible.
|
||||
IsSubscribed bool `json:"isSubscribed"`
|
||||
|
||||
// Should the calendar’s events be displayed to the user at the moment?
|
||||
//
|
||||
// Clients MUST ignore this property if `isSubscribed` is false.
|
||||
//
|
||||
// If an event is in multiple calendars, it should be displayed if `isVisible` is `true`
|
||||
// for any of those calendars.
|
||||
IsVisible bool `json:"isVisible" default:"true" doc:"opt"`
|
||||
|
||||
// This SHOULD be true for exactly one calendar in any account, and MUST NOT be true for more
|
||||
// than one calendar within an account (server-set).
|
||||
//
|
||||
// The default calendar should be used by clients whenever they need to choose a calendar
|
||||
// for the user within this account, and they do not have any other information on which to make
|
||||
// a choice.
|
||||
//
|
||||
// For example, if the user creates a new event, the client may automatically set the event as
|
||||
// belonging to the default calendar from the user’s primary account.
|
||||
IsDefault bool `json:"isDefault,omitzero"`
|
||||
|
||||
// Should the calendar’s 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 user’s own account, and “none” for calendars shared with the user.
|
||||
IncludeInAvailability IncludeInAvailability `json:"includeInAvailability,omitempty"`
|
||||
|
||||
// A map of alert ids to Alert objects (see [@!RFC8984], Section 4.5.2) to apply for events
|
||||
// where `showWithoutTime` is `false` and `useDefaultAlerts` is `true`.
|
||||
//
|
||||
// Ids MUST be unique across all default alerts in the account, including those in other
|
||||
// calendars; a UUID is recommended.
|
||||
//
|
||||
// The "trigger" MUST NOT be an `AbsoluteTrigger`, as this would fire for every event at the same
|
||||
// time and so does not make sense for a default alert.
|
||||
//
|
||||
// If omitted on creation, the default is server dependent.
|
||||
//
|
||||
// For example, servers may choose to always default to null, or may copy the alerts from the default calendar.
|
||||
DefaultAlertsWithTime map[string]jscalendar.Alert `json:"defaultAlertsWithTime,omitempty"`
|
||||
|
||||
// A map of alert ids to Alert objects (see [@!RFC8984], Section 4.5.2) to apply for events where
|
||||
// `showWithoutTime` is `true` and `useDefaultAlerts` is `true`.
|
||||
//
|
||||
// Ids MUST be unique across all default alerts in the account, including those in other
|
||||
// calendars; a UUID is recommended.
|
||||
//
|
||||
// The "trigger" MUST NOT be an `AbsoluteTrigger`, as this would fire for every event at the
|
||||
// same time and so does not make sense for a default alert.
|
||||
//
|
||||
// If omitted on creation, the default is server dependent.
|
||||
//
|
||||
// For example, servers may choose to always default to null, or may copy the alerts from the default calendar.
|
||||
DefaultAlertsWithoutTime map[string]jscalendar.Alert `json:"defaultAlertsWithoutTime,omitempty"`
|
||||
|
||||
// The time zone to use for events without a time zone when the server needs to resolve them into
|
||||
// absolute time, e.g., for alerts or availability calculation.
|
||||
//
|
||||
// The value MUST be a time zone id from the IANA Time Zone Database TZDB.
|
||||
//
|
||||
// If null, the `timeZone` of the account’s associated `Principal` will be used.
|
||||
//
|
||||
// Clients SHOULD use this as the default for new events in this calendar if set.
|
||||
TimeZone string `json:"timeZone,omitempty"`
|
||||
|
||||
// A map of `Principal` id to rights for principals this calendar is shared with.
|
||||
//
|
||||
// The principal to which this calendar belongs MUST NOT be in this set.
|
||||
//
|
||||
// This is null if the calendar is not shared with anyone.
|
||||
//
|
||||
// May be modified only if the user has the `mayAdmin` right.
|
||||
//
|
||||
// The account id for the principals may be found in the `urn:ietf:params:jmap:principals:owner`
|
||||
// capability of the `Account` to which the calendar belongs.
|
||||
ShareWith map[string]CalendarRights `json:"shareWith,omitempty"`
|
||||
|
||||
// The set of access rights the user has in relation to this Calendar.
|
||||
//
|
||||
// If any event is in multiple calendars, the user has the following rights:
|
||||
// * The user may fetch the event if they have the mayReadItems right on any calendar the event is in.
|
||||
// * The user may remove an event from a calendar (by modifying the event’s “calendarIds” property) if the user
|
||||
// has the appropriate permission for that calendar.
|
||||
// * The user may make other changes to the event if they have the right to do so in all calendars to which the
|
||||
// event belongs.
|
||||
MyRights *CalendarRights `json:"myRights,omitempty"`
|
||||
}
|
||||
|
||||
// A CalendarEvent object contains information about an event, or recurring series of events,
|
||||
// that takes place at a particular time.
|
||||
//
|
||||
@@ -5150,6 +5285,122 @@ type AddressBookGetResponse struct {
|
||||
NotFound []string `json:"notFound,omitempty"`
|
||||
}
|
||||
|
||||
type AddressBookChange struct {
|
||||
// The user-visible name of the AddressBook.
|
||||
//
|
||||
// This may be any UTF-8 string of at least 1 character in length and maximum 255 octets in size.
|
||||
Name *string `json:"name"`
|
||||
|
||||
// An optional longer-form description of the AddressBook, to provide context in shared environments
|
||||
// where users need more than just the name.
|
||||
Description *string `json:"description,omitempty"`
|
||||
|
||||
// Defines the sort order of AddressBooks when presented in the client’s UI, so it is consistent between devices.
|
||||
//
|
||||
// The number MUST be an integer in the range 0 <= sortOrder < 2^31.
|
||||
//
|
||||
// An AddressBook with a lower order should be displayed before a AddressBook with a higher order in any list
|
||||
// of AddressBooks in the client’s UI.
|
||||
//
|
||||
// AddressBooks with equal order SHOULD be sorted in alphabetical order by name.
|
||||
//
|
||||
// The sorting should take into account locale-specific character order convention.
|
||||
SortOrder *uint `json:"sortOrder,omitzero" default:"0" doc:"opt"`
|
||||
|
||||
// True if the user has indicated they wish to see this AddressBook in their client.
|
||||
//
|
||||
// This SHOULD default to false for AddressBooks in shared accounts the user has access to and true for any
|
||||
// new AddressBooks created by the user themself.
|
||||
//
|
||||
// If false, the AddressBook and its contents SHOULD only be displayed when the user explicitly requests it
|
||||
// or to offer it for the user to subscribe to.
|
||||
IsSubscribed *bool `json:"isSubscribed"`
|
||||
|
||||
// A map of Principal id to rights for principals this AddressBook is shared with.
|
||||
//
|
||||
// The principal to which this AddressBook belongs MUST NOT be in this set.
|
||||
//
|
||||
// This is null if the AddressBook is not shared with anyone.
|
||||
//
|
||||
// May be modified only if the user has the mayAdmin right.
|
||||
//
|
||||
// The account id for the principals may be found in the urn:ietf:params:jmap:principals:owner capability
|
||||
// of the Account to which the AddressBook belongs.
|
||||
ShareWith map[string]AddressBookRights `json:"shareWith,omitempty"`
|
||||
}
|
||||
|
||||
func (a AddressBookChange) AsPatch() PatchObject {
|
||||
p := PatchObject{}
|
||||
if a.Name != nil {
|
||||
p["name"] = *a.Name
|
||||
}
|
||||
if a.Description != nil {
|
||||
p["description"] = *a.Description
|
||||
}
|
||||
if a.IsSubscribed != nil {
|
||||
p["isSubscribed"] = *a.IsSubscribed
|
||||
}
|
||||
if a.ShareWith != nil {
|
||||
p["shareWith"] = a.ShareWith
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
type AddressBookSetCommand struct {
|
||||
AccountId string `json:"accountId"`
|
||||
IfInState string `json:"ifInState,omitempty"`
|
||||
Create map[string]AddressBookChange `json:"create,omitempty"`
|
||||
Update map[string]PatchObject `json:"update,omitempty"`
|
||||
Destroy []string `json:"destroy,omitempty"`
|
||||
}
|
||||
|
||||
type AddressBookSetResponse struct {
|
||||
// The id of the account used for the call.
|
||||
AccountId string `json:"accountId"`
|
||||
|
||||
// The state string that would have been returned by AddressBook/get before making the
|
||||
// requested changes, or null if the server doesn’t know what the previous state
|
||||
// string was.
|
||||
OldState State `json:"oldState,omitempty"`
|
||||
|
||||
// The state string that will now be returned by Email/get.
|
||||
NewState State `json:"newState"`
|
||||
|
||||
// A map of the creation id to an object containing any properties of the created Email object
|
||||
// that were not sent by the client.
|
||||
//
|
||||
// This includes all server-set properties (such as the id in most object types) and any properties
|
||||
// that were omitted by the client and thus set to a default by the server.
|
||||
//
|
||||
// This argument is null if no ContactCard objects were successfully created.
|
||||
Created map[string]*AddressBook `json:"created,omitempty"`
|
||||
|
||||
// The keys in this map are the ids of all AddressBooks that were successfully updated.
|
||||
//
|
||||
// The value for each id is an AddressBook object containing any property that changed in a way not
|
||||
// explicitly requested by the PatchObject sent to the server, or null if none.
|
||||
//
|
||||
// This lets the client know of any changes to server-set or computed properties.
|
||||
//
|
||||
// This argument is null if no ContactCard objects were successfully updated.
|
||||
Updated map[string]*AddressBook `json:"updated,omitempty"`
|
||||
|
||||
// A list of ContactCard ids for records that were successfully destroyed, or null if none.
|
||||
Destroyed []string `json:"destroyed,omitempty"`
|
||||
|
||||
// A map of the creation id to a SetError object for each record that failed to be created,
|
||||
// or null if all successful.
|
||||
NotCreated map[string]SetError `json:"notCreated,omitempty"`
|
||||
|
||||
// A map of the ContactCard id to a SetError object for each record that failed to be updated,
|
||||
// or null if all successful.
|
||||
NotUpdated map[string]SetError `json:"notUpdated,omitempty"`
|
||||
|
||||
// A map of the ContactCard id to a SetError object for each record that failed to be destroyed,
|
||||
// or null if all successful.
|
||||
NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"`
|
||||
}
|
||||
|
||||
type AddressBookChangesCommand struct {
|
||||
// The id of the account to use.
|
||||
AccountId string `json:"accountId"`
|
||||
@@ -5547,7 +5798,7 @@ type ContactCardGetResponse struct {
|
||||
// This array contains the ids passed to the method for records that do not exist.
|
||||
//
|
||||
// The array is empty if all requested ids were found or if the ids argument passed in was either null or an empty array.
|
||||
NotFound []any `json:"notFound"`
|
||||
NotFound []string `json:"notFound"`
|
||||
}
|
||||
|
||||
type ContactCardChangesCommand struct {
|
||||
@@ -5739,6 +5990,61 @@ type CalendarGetResponse struct {
|
||||
NotFound []string `json:"notFound,omitempty"`
|
||||
}
|
||||
|
||||
type CalendarSetCommand struct {
|
||||
AccountId string `json:"accountId"`
|
||||
IfInState string `json:"ifInState,omitempty"`
|
||||
Create map[string]CalendarChange `json:"create,omitempty"`
|
||||
Update map[string]PatchObject `json:"update,omitempty"`
|
||||
Destroy []string `json:"destroy,omitempty"`
|
||||
}
|
||||
|
||||
type CalendarSetResponse struct {
|
||||
// The id of the account used for the call.
|
||||
AccountId string `json:"accountId"`
|
||||
|
||||
// The state string that would have been returned by Calendar/get before making the
|
||||
// requested changes, or null if the server doesn’t know what the previous state
|
||||
// string was.
|
||||
OldState State `json:"oldState,omitempty"`
|
||||
|
||||
// The state string that will now be returned by Email/get.
|
||||
NewState State `json:"newState"`
|
||||
|
||||
// A map of the creation id to an object containing any properties of the created Email object
|
||||
// that were not sent by the client.
|
||||
//
|
||||
// This includes all server-set properties (such as the id in most object types) and any properties
|
||||
// that were omitted by the client and thus set to a default by the server.
|
||||
//
|
||||
// This argument is null if no Calendar objects were successfully created.
|
||||
Created map[string]*Calendar `json:"created,omitempty"`
|
||||
|
||||
// The keys in this map are the ids of all Calendars that were successfully updated.
|
||||
//
|
||||
// The value for each id is an Calendar object containing any property that changed in a way not
|
||||
// explicitly requested by the PatchObject sent to the server, or null if none.
|
||||
//
|
||||
// This lets the client know of any changes to server-set or computed properties.
|
||||
//
|
||||
// This argument is null if no Calendar objects were successfully updated.
|
||||
Updated map[string]*Calendar `json:"updated,omitempty"`
|
||||
|
||||
// A list of Calendar ids for records that were successfully destroyed, or null if none.
|
||||
Destroyed []string `json:"destroyed,omitempty"`
|
||||
|
||||
// A map of the creation id to a SetError object for each record that failed to be created,
|
||||
// or null if all successful.
|
||||
NotCreated map[string]SetError `json:"notCreated,omitempty"`
|
||||
|
||||
// A map of the Calendar id to a SetError object for each record that failed to be updated,
|
||||
// or null if all successful.
|
||||
NotUpdated map[string]SetError `json:"notUpdated,omitempty"`
|
||||
|
||||
// A map of the Calendar id to a SetError object for each record that failed to be destroyed,
|
||||
// or null if all successful.
|
||||
NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"`
|
||||
}
|
||||
|
||||
type CalendarChangesCommand struct {
|
||||
// The id of the account to use.
|
||||
AccountId string `json:"accountId"`
|
||||
@@ -6087,7 +6393,7 @@ type CalendarEventGetResponse struct {
|
||||
// This array contains the ids passed to the method for records that do not exist.
|
||||
//
|
||||
// The array is empty if all requested ids were found or if the ids argument passed in was either null or an empty array.
|
||||
NotFound []any `json:"notFound"`
|
||||
NotFound []string `json:"notFound"`
|
||||
}
|
||||
|
||||
type CalendarEventChangesCommand struct {
|
||||
@@ -6239,6 +6545,139 @@ type CalendarEventSetResponse struct {
|
||||
NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"`
|
||||
}
|
||||
|
||||
type PrincipalGetCommand struct {
|
||||
AccountId string `json:"accountId"`
|
||||
Ids []string `json:"ids,omitempty"`
|
||||
}
|
||||
|
||||
type PrincipalGetRefCommand struct {
|
||||
AccountId string `json:"accountId"`
|
||||
IdsRef *ResultReference `json:"#ids,omitempty"`
|
||||
}
|
||||
|
||||
type PrincipalGetResponse struct {
|
||||
// The id of the account used for the call.
|
||||
AccountId string `json:"accountId"`
|
||||
|
||||
// A (preferably short) string representing the state on the server for all the data of this type in the account
|
||||
// (not just the objects returned in this call).
|
||||
// If the data changes, this string MUST change.
|
||||
// If the Principal data is unchanged, servers SHOULD return the same state string on subsequent requests for this data type.
|
||||
// When a client receives a response with a different state string to a previous call, it MUST either throw away all currently
|
||||
// cached objects for the type or call Principal/changes to get the exact changes.
|
||||
State State `json:"state"`
|
||||
|
||||
// An array of the Principal objects requested.
|
||||
// This is the empty array if no objects were found or if the ids argument passed in was also an empty array.
|
||||
// The results MAY be in a different order to the ids in the request arguments.
|
||||
// If an identical id is included more than once in the request, the server MUST only include it once in either
|
||||
// the list or the notFound argument of the response.
|
||||
List []Principal `json:"list"`
|
||||
|
||||
// This array contains the ids passed to the method for records that do not exist.
|
||||
// The array is empty if all requested ids were found or if the ids argument passed in was either null or an empty array.
|
||||
NotFound []string `json:"notFound"`
|
||||
}
|
||||
|
||||
type PrincipalFilterElement interface {
|
||||
_isAPrincipalFilterElement() // marker method
|
||||
}
|
||||
|
||||
type PrincipalFilterCondition struct {
|
||||
// A list of Account ids.
|
||||
// The Principal matches if any of the ids in this list are keys in the Principal's "accounts" property (i.e., if any of the Account ids belong to the Principal).
|
||||
AccountIds []string `json:"accountIds,omitempty"`
|
||||
// The email property of the Principal contains the given string.
|
||||
Email string `json:"email,omitempty"`
|
||||
// The name property of the Principal contains the given string.
|
||||
Name string `json:"name,omitempty"`
|
||||
// The name, email, or description property of the Principal contains the given string.
|
||||
Text string `json:"text,omitempty"`
|
||||
// The type must be exactly as given to match the condition.
|
||||
Type PrincipalTypeOption `json:"type,omitempty"`
|
||||
// The timeZone must be exactly as given to match the condition.
|
||||
TimeZone string `json:"timeZone,omitempty"`
|
||||
}
|
||||
|
||||
func (c PrincipalFilterCondition) _isAPrincipalFilterElement() { //NOSONAR
|
||||
// marker interface method, does not need to do anything
|
||||
}
|
||||
|
||||
var _ PrincipalFilterElement = &PrincipalFilterCondition{}
|
||||
|
||||
type PrincipalFilterOperator struct {
|
||||
Operator FilterOperatorTerm `json:"operator"`
|
||||
Conditions []PrincipalFilterElement `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
func (c PrincipalFilterOperator) _isAPrincipalFilterElement() { //NOSONAR
|
||||
// marker interface method, does not need to do anything
|
||||
}
|
||||
|
||||
var _ PrincipalFilterElement = &PrincipalFilterOperator{}
|
||||
|
||||
type PrincipalComparator struct {
|
||||
Property string `json:"property"`
|
||||
IsAscending bool `json:"isAscending,omitempty"`
|
||||
Limit int `json:"limit,omitzero"`
|
||||
CalculateTotal bool `json:"calculateTotal,omitempty"`
|
||||
}
|
||||
|
||||
type PrincipalQueryCommand struct {
|
||||
AccountId string `json:"accountId"`
|
||||
Filter PrincipalFilterElement `json:"filter,omitempty"`
|
||||
Sort []PrincipalComparator `json:"sort,omitempty"`
|
||||
SortAsTree bool `json:"sortAsTree,omitempty"`
|
||||
FilterAsTree bool `json:"filterAsTree,omitempty"`
|
||||
}
|
||||
|
||||
type PrincipalQueryResponse struct {
|
||||
// The id of the account used for the call.
|
||||
AccountId string `json:"accountId"`
|
||||
|
||||
// A string encoding the current state of the query on the server.
|
||||
//
|
||||
// This string MUST change if the results of the query (i.e., the matching ids and their sort order) have changed.
|
||||
// The queryState string MAY change if something has changed on the server, which means the results may have
|
||||
// changed but the server doesn’t know for sure.
|
||||
//
|
||||
// The queryState string only represents the ordered list of ids that match the particular query (including its
|
||||
// sort/filter). There is no requirement for it to change if a property on an object matching the query changes
|
||||
// but the query results are unaffected (indeed, it is more efficient if the queryState string does not change
|
||||
// in this case). The queryState string only has meaning when compared to future responses to a query with the
|
||||
// same type/sort/filter or when used with /queryChanges to fetch changes.
|
||||
//
|
||||
// Should a client receive back a response with a different queryState string to a previous call, it MUST either
|
||||
// throw away the currently cached query and fetch it again (note, this does not require fetching the records
|
||||
// again, just the list of ids) or call Mailbox/queryChanges to get the difference.
|
||||
QueryState State `json:"queryState"`
|
||||
|
||||
// This is true if the server supports calling Mailbox/queryChanges with these filter/sort parameters.
|
||||
//
|
||||
// Note, this does not guarantee that the Mailbox/queryChanges call will succeed, as it may only be possible for
|
||||
// a limited time afterwards due to server internal implementation details.
|
||||
CanCalculateChanges bool `json:"canCalculateChanges"`
|
||||
|
||||
// The zero-based index of the first result in the ids array within the complete list of query results.
|
||||
Position int `json:"position"`
|
||||
|
||||
// The list of ids for each Mailbox in the query results, starting at the index given by the position argument
|
||||
// of this response and continuing until it hits the end of the results or reaches the limit number of ids.
|
||||
//
|
||||
// If position is >= total, this MUST be the empty list.
|
||||
Ids []string `json:"ids"`
|
||||
|
||||
// The total number of Mailbox in the results (given the filter) (only if requested).
|
||||
//
|
||||
// This argument MUST be omitted if the calculateTotal request argument is not true.
|
||||
Total int `json:"total,omitzero"`
|
||||
|
||||
// The limit enforced by the server on the maximum number of results to return (if set by the server).
|
||||
//
|
||||
// This is only returned if the server set a limit or used a different limit than that given in the request.
|
||||
Limit int `json:"limit,omitzero"`
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description,omitempty"`
|
||||
@@ -6270,6 +6709,7 @@ const (
|
||||
CommandQuotaGet Command = "Quota/get"
|
||||
CommandQuotaChanges Command = "Quota/changes"
|
||||
CommandAddressBookGet Command = "AddressBook/get"
|
||||
CommandAddressBookSet Command = "AddressBook/set"
|
||||
CommandAddressBookChanges Command = "AddressBook/changes"
|
||||
CommandContactCardQuery Command = "ContactCard/query"
|
||||
CommandContactCardGet Command = "ContactCard/get"
|
||||
@@ -6277,11 +6717,14 @@ const (
|
||||
CommandContactCardSet Command = "ContactCard/set"
|
||||
CommandCalendarEventParse Command = "CalendarEvent/parse"
|
||||
CommandCalendarGet Command = "Calendar/get"
|
||||
CommandCalendarSet Command = "Calendar/set"
|
||||
CommandCalendarChanges Command = "Calendar/changes"
|
||||
CommandCalendarEventQuery Command = "CalendarEvent/query"
|
||||
CommandCalendarEventGet Command = "CalendarEvent/get"
|
||||
CommandCalendarEventSet Command = "CalendarEvent/set"
|
||||
CommandCalendarEventChanges Command = "CalendarEvent/changes"
|
||||
CommandPrincipalGet Command = "Principal/get"
|
||||
CommandPrincipalQuery Command = "Principal/query"
|
||||
)
|
||||
|
||||
var CommandResponseTypeMap = map[Command]func() any{
|
||||
@@ -6309,6 +6752,7 @@ var CommandResponseTypeMap = map[Command]func() any{
|
||||
CommandQuotaGet: func() any { return QuotaGetResponse{} },
|
||||
CommandQuotaChanges: func() any { return QuotaChangesResponse{} },
|
||||
CommandAddressBookGet: func() any { return AddressBookGetResponse{} },
|
||||
CommandAddressBookSet: func() any { return AddressBookSetResponse{} },
|
||||
CommandAddressBookChanges: func() any { return AddressBookChangesResponse{} },
|
||||
CommandContactCardQuery: func() any { return ContactCardQueryResponse{} },
|
||||
CommandContactCardGet: func() any { return ContactCardGetResponse{} },
|
||||
@@ -6316,9 +6760,12 @@ var CommandResponseTypeMap = map[Command]func() any{
|
||||
CommandContactCardSet: func() any { return ContactCardSetResponse{} },
|
||||
CommandCalendarEventParse: func() any { return CalendarEventParseResponse{} },
|
||||
CommandCalendarGet: func() any { return CalendarGetResponse{} },
|
||||
CommandCalendarSet: func() any { return CalendarSetResponse{} },
|
||||
CommandCalendarChanges: func() any { return CalendarChangesResponse{} },
|
||||
CommandCalendarEventQuery: func() any { return CalendarEventQueryResponse{} },
|
||||
CommandCalendarEventGet: func() any { return CalendarEventGetResponse{} },
|
||||
CommandCalendarEventSet: func() any { return CalendarEventSetResponse{} },
|
||||
CommandCalendarEventChanges: func() any { return CalendarEventChangesResponse{} },
|
||||
CommandPrincipalGet: func() any { return PrincipalGetResponse{} },
|
||||
CommandPrincipalQuery: func() any { return PrincipalQueryResponse{} },
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jscalendar"
|
||||
"github.com/opencloud-eu/opencloud/pkg/jscontact"
|
||||
c "github.com/opencloud-eu/opencloud/pkg/jscontact"
|
||||
)
|
||||
@@ -491,6 +492,17 @@ func (e Exemplar) Quotas() []Quota {
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) QuotaGetResponse() QuotaGetResponse {
|
||||
return QuotaGetResponse{
|
||||
AccountId: e.AccountId,
|
||||
State: "oroomoh1",
|
||||
NotFound: []string{"aab2n", "aab8f"},
|
||||
List: []Quota{
|
||||
e.Quota(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) Identity() Identity {
|
||||
return Identity{
|
||||
Id: e.IdentityId,
|
||||
@@ -530,6 +542,15 @@ func (e Exemplar) Identity_req() Identity { //NOSONAR
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) IdentityGetResponse() IdentityGetResponse {
|
||||
return IdentityGetResponse{
|
||||
AccountId: e.AccountId,
|
||||
State: "geechae0",
|
||||
NotFound: []string{"eea2"},
|
||||
List: e.Identities(),
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) Thread() Thread {
|
||||
return Thread{
|
||||
Id: e.ThreadId,
|
||||
@@ -697,6 +718,15 @@ func (e Exemplar) Mailboxes() []Mailbox {
|
||||
return []Mailbox{a, b, c, d, f, g}
|
||||
}
|
||||
|
||||
func (e Exemplar) MailboxGetResponse() MailboxGetResponse {
|
||||
return MailboxGetResponse{
|
||||
AccountId: e.AccountId,
|
||||
State: "aesh2ahj",
|
||||
List: e.Mailboxes(),
|
||||
NotFound: []string{"ah"},
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) MailboxChanges() MailboxChanges {
|
||||
a, _, _ := e.MailboxInbox()
|
||||
return MailboxChanges{
|
||||
@@ -757,10 +787,10 @@ func (e Exemplar) Email() Email {
|
||||
},
|
||||
},
|
||||
TextBody: []EmailBodyPart{
|
||||
{PartId: "1", BlobId: "ckyxndo0fxob1jnm3z2lroex131oj7eo2ezo1djhlfgtsu7jgucfeaiasibnebdw", Size: 115, Type: "text/plain", Charset: "utf-8"},
|
||||
{PartId: "1", BlobId: "ckyxndo0fxob1jnm3z2lroex131oj7eo2ezo1djhlfgtsu7jgucfeaiasibnebdw", Size: 115, Type: "text/plain", Charset: "utf-8"}, //NOSONAR
|
||||
},
|
||||
HtmlBody: []EmailBodyPart{
|
||||
{PartId: "2", BlobId: "ckyxndo0fxob1jnm3z2lroex131oj7eo2ezo1djhlfgtsu7jgucfeaiasibnsbvjae", Size: 163, Type: "text/html", Charset: "utf-8"},
|
||||
{PartId: "2", BlobId: "ckyxndo0fxob1jnm3z2lroex131oj7eo2ezo1djhlfgtsu7jgucfeaiasibnsbvjae", Size: 163, Type: "text/html", Charset: "utf-8"}, //NOSONAR
|
||||
},
|
||||
Preview: "The Canterbury was destroyed while investigating a false distress call from the Scopuli.",
|
||||
}
|
||||
@@ -786,6 +816,49 @@ func (e Exemplar) Emails() Emails {
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) EmailGetResponse() EmailGetResponse {
|
||||
return EmailGetResponse{
|
||||
AccountId: e.AccountId,
|
||||
State: "aesh2ahj",
|
||||
NotFound: []string{"ahx"},
|
||||
List: e.Emails().Emails,
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) EmailSubmission() EmailSubmission {
|
||||
sendAt, err := time.Parse(time.RFC3339, "2026-04-08T14:00:00.000Z")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return EmailSubmission{
|
||||
Id: "cea1ae",
|
||||
IdentityId: e.IdentityId,
|
||||
EmailId: e.EmailId,
|
||||
ThreadId: e.ThreadId,
|
||||
Envelope: &Envelope{
|
||||
MailFrom: Address{
|
||||
Email: "camina@opa.org.example.com",
|
||||
},
|
||||
RcptTo: []Address{
|
||||
{Email: "crissy@earth.gov.example.com"},
|
||||
},
|
||||
},
|
||||
SendAt: sendAt,
|
||||
UndoStatus: UndoStatusPending,
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) EmailSubmissionGetResponse() EmailSubmissionGetResponse {
|
||||
return EmailSubmissionGetResponse{
|
||||
AccountId: e.AccountId,
|
||||
State: "eiph2pha",
|
||||
NotFound: []string{"zfa92bn"},
|
||||
List: []EmailSubmission{
|
||||
e.EmailSubmission(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) VacationResponse() VacationResponse {
|
||||
from, _ := time.Parse(time.RFC3339, "20260101T00:00:00.000Z")
|
||||
to, _ := time.Parse(time.RFC3339, "20260114T23:59:59.999Z")
|
||||
@@ -1011,6 +1084,16 @@ func (e Exemplar) CalendarsResponse() CalendarsResponse {
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) CalendarGetResponse() CalendarGetResponse {
|
||||
a := e.Calendar()
|
||||
return CalendarGetResponse{
|
||||
AccountId: e.AccountId,
|
||||
State: "aesh2ahj",
|
||||
List: []Calendar{a},
|
||||
NotFound: []string{"eehn", "eehz"},
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) Link() c.Link {
|
||||
return c.Link{
|
||||
Type: c.LinkType,
|
||||
@@ -1786,6 +1869,178 @@ func (e Exemplar) ContactCard() c.ContactCard {
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) ContactCardGetResponse() ContactCardGetResponse {
|
||||
a := e.ContactCard()
|
||||
b, _, _ := e.DesignContactCard()
|
||||
return ContactCardGetResponse{
|
||||
AccountId: e.AccountId,
|
||||
State: "ewohl8ie",
|
||||
NotFound: []string{"eeaa2"},
|
||||
List: []c.ContactCard{a, b},
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) CalendarEvent() CalendarEvent {
|
||||
cal := e.Calendar()
|
||||
return CalendarEvent{
|
||||
Id: "aeZaik2faash",
|
||||
CalendarIds: map[string]bool{cal.Id: true},
|
||||
IsDraft: false,
|
||||
IsOrigin: true,
|
||||
Event: jscalendar.Event{
|
||||
Type: jscalendar.EventType,
|
||||
Object: jscalendar.Object{
|
||||
CommonObject: jscalendar.CommonObject{
|
||||
Uid: "dda22c7e-7674-4811-ae2e-2cc1ac605f5c",
|
||||
ProdId: "Groupware//1.0",
|
||||
Created: "2026-04-01T15:29:12.912Z",
|
||||
Updated: "2026-04-01T15:35:44.091Z",
|
||||
Title: "James Holden's Intronisation Ceremony",
|
||||
Description: "James Holden will be confirmed as the President of the Transport Union, in room 2201 on station TSL-5.",
|
||||
DescriptionContentType: "text/plain",
|
||||
Links: map[string]jscalendar.Link{
|
||||
"aig1oh": jscalendar.Link{
|
||||
Type: jscalendar.LinkType,
|
||||
Href: "https://expanse.fandom.com/wiki/TSL-5",
|
||||
ContentType: "text/html",
|
||||
Display: "TSL-5",
|
||||
Title: "TSL-5",
|
||||
},
|
||||
},
|
||||
Locale: "en-US",
|
||||
Keywords: map[string]bool{"union": true},
|
||||
Categories: map[string]bool{
|
||||
"meeting": true,
|
||||
},
|
||||
Color: "#ff0000",
|
||||
},
|
||||
ShowWithoutTime: false,
|
||||
Locations: map[string]jscalendar.Location{
|
||||
"eigha6": jscalendar.Location{
|
||||
Type: jscalendar.LocationType,
|
||||
Name: "Room 2201",
|
||||
LocationTypes: map[jscalendar.LocationTypeOption]bool{
|
||||
jscalendar.LocationTypeOptionOffice: true,
|
||||
},
|
||||
Coordinates: "geo:40.7495,-73.9681",
|
||||
Links: map[string]jscalendar.Link{
|
||||
"ohb6qu": jscalendar.Link{
|
||||
Type: jscalendar.LinkType,
|
||||
Href: "https://nss.org/what-is-l5/",
|
||||
ContentType: "text/html",
|
||||
Display: "Lagrange Point 5",
|
||||
Title: "Lagrange Point 5",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Sequence: 0,
|
||||
MainLocationId: "eigha6",
|
||||
VirtualLocations: map[string]jscalendar.VirtualLocation{
|
||||
"eec4ei": jscalendar.VirtualLocation{
|
||||
Type: jscalendar.VirtualLocationType,
|
||||
Name: "OpenTalk",
|
||||
Uri: "https://earth.gov.example.com/opentalk/l5/2022",
|
||||
Features: map[jscalendar.VirtualLocationFeature]bool{
|
||||
jscalendar.VirtualLocationFeatureVideo: true,
|
||||
jscalendar.VirtualLocationFeatureScreen: true,
|
||||
jscalendar.VirtualLocationFeatureAudio: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Priority: 1,
|
||||
FreeBusyStatus: jscalendar.FreeBusyStatusBusy,
|
||||
Privacy: jscalendar.PrivacyPublic,
|
||||
SentBy: "avasarala@earth.gov.example.com",
|
||||
Participants: map[string]jscalendar.Participant{
|
||||
"xaku3f": jscalendar.Participant{
|
||||
Type: jscalendar.ParticipantType,
|
||||
Name: "Christjen Avasarala",
|
||||
Email: "crissy@earth.gov.example.com",
|
||||
Kind: jscalendar.ParticipantKindIndividual,
|
||||
Roles: map[jscalendar.Role]bool{
|
||||
jscalendar.RoleRequired: true,
|
||||
jscalendar.RoleChair: true,
|
||||
jscalendar.RoleOwner: true,
|
||||
},
|
||||
ParticipationStatus: jscalendar.ParticipationStatusAccepted,
|
||||
},
|
||||
"chao1a": jscalendar.Participant{
|
||||
Type: jscalendar.ParticipantType,
|
||||
Name: "Camina Drummer",
|
||||
Email: "camina@opa.org.example.com",
|
||||
Kind: jscalendar.ParticipantKindIndividual,
|
||||
Roles: map[jscalendar.Role]bool{
|
||||
jscalendar.RoleRequired: true,
|
||||
},
|
||||
ParticipationStatus: jscalendar.ParticipationStatusAccepted,
|
||||
ParticipationComment: "I'll definitely be there",
|
||||
ExpectReply: true,
|
||||
InvitedBy: "xaku3f",
|
||||
},
|
||||
"ees0oo": jscalendar.Participant{
|
||||
Type: jscalendar.ParticipantType,
|
||||
Name: "James Holden",
|
||||
Email: "james.holden@rocinante.space",
|
||||
Kind: jscalendar.ParticipantKindIndividual,
|
||||
Roles: map[jscalendar.Role]bool{
|
||||
jscalendar.RoleRequired: true,
|
||||
},
|
||||
ParticipationStatus: jscalendar.ParticipationStatusAccepted,
|
||||
ExpectReply: true,
|
||||
InvitedBy: "xaku3f",
|
||||
},
|
||||
},
|
||||
Alerts: map[string]jscalendar.Alert{
|
||||
"kus9fa": jscalendar.Alert{
|
||||
Type: jscalendar.AlertType,
|
||||
Action: jscalendar.AlertActionDisplay,
|
||||
Trigger: jscalendar.OffsetTrigger{
|
||||
Type: jscalendar.OffsetTriggerType,
|
||||
Offset: "-PT1H",
|
||||
RelativeTo: jscalendar.RelativeToStart,
|
||||
},
|
||||
},
|
||||
"lohve9": jscalendar.Alert{
|
||||
Type: jscalendar.AlertType,
|
||||
Action: jscalendar.AlertActionDisplay,
|
||||
Trigger: jscalendar.OffsetTrigger{
|
||||
Type: jscalendar.OffsetTriggerType,
|
||||
Offset: "-PT10M",
|
||||
RelativeTo: jscalendar.RelativeToStart,
|
||||
},
|
||||
},
|
||||
},
|
||||
MayInviteOthers: true,
|
||||
HideAttendees: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) CalendarEventGetResponse() CalendarEventGetResponse {
|
||||
ev := e.CalendarEvent()
|
||||
return CalendarEventGetResponse{
|
||||
AccountId: e.AccountId,
|
||||
State: "zah1ooj0",
|
||||
NotFound: []string{"eea9"},
|
||||
List: []CalendarEvent{
|
||||
ev,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) AddressBookChanges() AddressBookChanges {
|
||||
a := e.AddressBook()
|
||||
return AddressBookChanges{
|
||||
OldState: "eebees6o",
|
||||
NewState: "gae1iey0",
|
||||
HasMoreChanges: true,
|
||||
Created: []AddressBook{a},
|
||||
Destroyed: []string{"l9fn"},
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) ContactCardChanges() (ContactCardChanges, string, string) {
|
||||
c := e.ContactCard()
|
||||
return ContactCardChanges{
|
||||
@@ -1818,7 +2073,7 @@ func (e Exemplar) EmailChanges() EmailChanges {
|
||||
}
|
||||
}
|
||||
|
||||
func (e Exemplar) Changes() Changes {
|
||||
func (e Exemplar) Changes() (Changes, string, string) {
|
||||
return Changes{
|
||||
MaxChanges: 3,
|
||||
Mailboxes: &MailboxChangesResponse{
|
||||
@@ -1862,5 +2117,28 @@ func (e Exemplar) Changes() Changes {
|
||||
HasMoreChanges: true,
|
||||
Created: []string{"fq", "fr", "fs"},
|
||||
},
|
||||
}, "A set of changes to objects", "changes"
|
||||
}
|
||||
|
||||
func (e Exemplar) Objects() Objects {
|
||||
mailboxes := e.MailboxGetResponse()
|
||||
emails := e.EmailGetResponse()
|
||||
calendars := e.CalendarGetResponse()
|
||||
events := e.CalendarEventGetResponse()
|
||||
addressbooks := e.AddressBookGetResponse()
|
||||
contacts := e.ContactCardGetResponse()
|
||||
quotas := e.QuotaGetResponse()
|
||||
identities := e.IdentityGetResponse()
|
||||
emailSubmissions := e.EmailSubmissionGetResponse()
|
||||
return Objects{
|
||||
Mailboxes: &mailboxes,
|
||||
Emails: &emails,
|
||||
Calendars: &calendars,
|
||||
Events: &events,
|
||||
Addressbooks: &addressbooks,
|
||||
Contacts: &contacts,
|
||||
Quotas: "as,
|
||||
Identities: &identities,
|
||||
EmailSubmissions: &emailSubmissions,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,19 +76,19 @@ func getTemplateN[GETREQ any, GETRESP any, ITEM any, RESP any]( //NOSONAR
|
||||
})
|
||||
}
|
||||
|
||||
func createTemplate[T any, SETREQ any, GETREQ any, SETRESP any, GETRESP any]( //NOSONAR
|
||||
func createTemplate[T any, C any, SETREQ any, GETREQ any, SETRESP any, GETRESP any]( //NOSONAR
|
||||
client *Client, name string, using []JmapNamespace, t ObjectType,
|
||||
setCommand Command, getCommand Command,
|
||||
setCommandFactory func(string, map[string]T) SETREQ,
|
||||
setCommandFactory func(string, map[string]C) SETREQ,
|
||||
getCommandFactory func(string, string) GETREQ,
|
||||
createdMapper func(SETRESP) map[string]*T,
|
||||
notCreatedMapper func(SETRESP) map[string]SetError,
|
||||
listMapper func(GETRESP) []T,
|
||||
stateMapper func(SETRESP) State,
|
||||
accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create T) (*T, SessionState, State, Language, Error) {
|
||||
accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create C) (*T, SessionState, State, Language, Error) {
|
||||
logger = client.logger(name, session, logger)
|
||||
|
||||
createMap := map[string]T{"c": create}
|
||||
createMap := map[string]C{"c": create}
|
||||
cmd, err := client.request(session, logger, using,
|
||||
invocation(setCommand, setCommandFactory(accountId, createMap), "0"),
|
||||
invocation(getCommand, getCommandFactory(accountId, "#c"), "1"),
|
||||
|
||||
@@ -58,6 +58,9 @@ tags:
|
||||
- name: vacation
|
||||
x-displayName: Vacation Responses
|
||||
description: APIs about vacation responses
|
||||
- name: blob
|
||||
x-displayName: BLOBs
|
||||
description: APIs about binary large objects
|
||||
- name: changes
|
||||
x-displayName: Changes
|
||||
description: APIs for retrieving changes to objects
|
||||
@@ -89,6 +92,9 @@ x-tagGroups:
|
||||
- name: Quotas
|
||||
tags:
|
||||
- quota
|
||||
- name: Blobs
|
||||
tags:
|
||||
- blob
|
||||
- name: changes
|
||||
tags:
|
||||
- changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@redocly/cli": "^2.25.2",
|
||||
"@redocly/cli": "^2.25.3",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"cheerio": "^1.2.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
|
||||
155
services/groupware/pkg/groupware/api_addressbooks.go
Normal file
155
services/groupware/pkg/groupware/api_addressbooks.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package groupware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
// Get all addressbooks of an account.
|
||||
func (g *Groupware) GetAddressbooks(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, req.logger, req.language(), nil)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
var body jmap.AddressBooksResponse = addressbooks
|
||||
return req.respond(accountId, body, sessionState, AddressBookResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
|
||||
// Get an addressbook of an account by its identifier.
|
||||
func (g *Groupware) GetAddressbook(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
addressBookId, err := req.PathParam(UriParamAddressBookId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
|
||||
|
||||
logger := log.From(l)
|
||||
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, logger, req.language(), []string{addressBookId})
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
if len(addressbooks.NotFound) > 0 {
|
||||
return req.notFound(accountId, sessionState, AddressBookResponseObjectType, state)
|
||||
} else {
|
||||
return req.respond(accountId, addressbooks.AddressBooks[0], sessionState, AddressBookResponseObjectType, state)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Get the changes to Address Books since a certain State.
|
||||
// @api:tags addressbook,changes
|
||||
func (g *Groupware) GetAddressBookChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamMaxChanges, maxChanges)
|
||||
}
|
||||
|
||||
sinceState := jmap.State(req.OptHeaderParamDoc(HeaderParamSince, "Optionally specifies the state identifier from which on to list addressbook changes"))
|
||||
if sinceState != "" {
|
||||
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetAddressbookChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, changes, sessionState, AddressBookResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) CreateAddressBook(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
var create jmap.AddressBookChange
|
||||
err := req.bodydoc(&create, "The address book to create")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateAddressBook(accountId, req.session, req.ctx, logger, req.language(), create)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, created, sessionState, ContactResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) DeleteAddressBook(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
addressBookId, err := req.PathParam(UriParamAddressBookId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
deleted, sessionState, state, lang, jerr := g.jmap.DeleteAddressBook(accountId, []string{addressBookId}, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
for _, e := range deleted {
|
||||
desc := e.Description
|
||||
if desc != "" {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteAddressBook,
|
||||
withDetail(e.Description),
|
||||
))
|
||||
} else {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteAddressBook,
|
||||
))
|
||||
}
|
||||
}
|
||||
return req.noContent(accountId, sessionState, AddressBookResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
@@ -67,17 +67,18 @@ func (g *Groupware) UploadBlob(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// Download a BLOB by its identifier.
|
||||
func (g *Groupware) DownloadBlob(w http.ResponseWriter, r *http.Request) {
|
||||
g.stream(w, r, func(req Request, w http.ResponseWriter) *Error {
|
||||
blobId, err := req.PathParam(UriParamBlobId)
|
||||
blobId, err := req.PathParam(UriParamBlobId) // the unique identifier of the blob to download
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := req.PathParam(UriParamBlobName)
|
||||
name, err := req.PathParam(UriParamBlobName) // the filename of the blob to download, which is then used in the response and may be arbitrary if unknown
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
typ, _ := req.getStringParam(QueryParamBlobType, "")
|
||||
typ, _ := req.getStringParam(QueryParamBlobType, "") // optionally, the Content-Type of the blob, which is then used in the response
|
||||
|
||||
accountId, gwerr := req.GetAccountIdForBlob()
|
||||
if gwerr != nil {
|
||||
|
||||
@@ -2,7 +2,6 @@ package groupware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
@@ -55,8 +54,8 @@ func (g *Groupware) GetCalendarById(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// Get the changes that occured in a given mailbox since a certain state.
|
||||
// @api:tags calendars,changes
|
||||
// Get the changes to Calendars since a certain State.
|
||||
// @api:tags calendar,changes
|
||||
func (g *Groupware) GetCalendarChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
@@ -90,131 +89,47 @@ func (g *Groupware) GetCalendarChanges(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// Get all the events in a calendar of an account by its identifier.
|
||||
func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
func (g *Groupware) CreateCalendar(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
calendarId, err := req.PathParam(UriParamCalendarId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(UriParamCalendarId, log.SafeString(calendarId))
|
||||
|
||||
offset, ok, err := req.parseUIntParam(QueryParamOffset, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamOffset, offset)
|
||||
}
|
||||
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.contactLimit)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
}
|
||||
|
||||
filter := jmap.CalendarEventFilterCondition{
|
||||
InCalendar: calendarId,
|
||||
}
|
||||
sortBy := []jmap.CalendarEventComparator{{Property: jmap.CalendarEventPropertyUpdated, IsAscending: false}}
|
||||
|
||||
logger := log.From(l)
|
||||
eventsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryCalendarEvents(single(accountId), req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
if events, ok := eventsByAccountId[accountId]; ok {
|
||||
return req.respond(accountId, events, sessionState, EventResponseObjectType, state)
|
||||
} else {
|
||||
return req.notFound(accountId, sessionState, EventResponseObjectType, state)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Get changes to Contacts since a given State
|
||||
// @api:tags event,changes
|
||||
func (g *Groupware) GetEventChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
var maxChanges uint = 0
|
||||
if v, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0); err != nil {
|
||||
return req.error(accountId, err)
|
||||
} else if ok {
|
||||
maxChanges = v
|
||||
l = l.Uint(QueryParamMaxChanges, v)
|
||||
}
|
||||
|
||||
sinceState := jmap.State(req.OptHeaderParamDoc(HeaderParamSince, "Specifies the state identifier from which on to list event changes"))
|
||||
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
|
||||
|
||||
logger := log.From(l)
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetCalendarEventChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
var body jmap.CalendarEventChanges = changes
|
||||
|
||||
return req.respond(accountId, body, sessionState, ContactResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) CreateCalendarEvent(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
var create jmap.CalendarEvent
|
||||
err := req.body(&create)
|
||||
var create jmap.CalendarChange
|
||||
err := req.bodydoc(&create, "The calendar to create")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateCalendarEvent(accountId, req.session, req.ctx, logger, req.language(), create)
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateCalendar(accountId, req.session, req.ctx, logger, req.language(), create)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, created, sessionState, EventResponseObjectType, state)
|
||||
return req.respond(accountId, created, sessionState, ContactResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) DeleteCalendarEvent(w http.ResponseWriter, r *http.Request) {
|
||||
func (g *Groupware) DeleteCalendar(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
eventId, err := req.PathParam(UriParamEventId)
|
||||
calendarId, err := req.PathParam(UriParamCalendarId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(UriParamEventId, log.SafeString(eventId))
|
||||
l.Str(UriParamCalendarId, log.SafeString(calendarId))
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
deleted, sessionState, state, lang, jerr := g.jmap.DeleteCalendarEvent(accountId, []string{eventId}, req.session, req.ctx, logger, req.language())
|
||||
deleted, sessionState, state, lang, jerr := g.jmap.DeleteCalendar(accountId, []string{calendarId}, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
@@ -222,42 +137,18 @@ func (g *Groupware) DeleteCalendarEvent(w http.ResponseWriter, r *http.Request)
|
||||
for _, e := range deleted {
|
||||
desc := e.Description
|
||||
if desc != "" {
|
||||
return req.errorS(accountId, apiError(
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteContact,
|
||||
ErrorFailedToDeleteCalendar,
|
||||
withDetail(e.Description),
|
||||
), sessionState)
|
||||
))
|
||||
} else {
|
||||
return req.errorS(accountId, apiError(
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteContact,
|
||||
), sessionState)
|
||||
ErrorFailedToDeleteCalendar,
|
||||
))
|
||||
}
|
||||
}
|
||||
return req.noContent(accountId, sessionState, EventResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) ParseIcalBlob(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForBlob()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
blobId, err := req.PathParam(UriParamBlobId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
blobIds := strings.Split(blobId, ",")
|
||||
l := req.logger.With().Array(UriParamBlobId, log.SafeStringArray(blobIds))
|
||||
logger := log.From(l)
|
||||
|
||||
resp, sessionState, state, lang, jerr := g.jmap.ParseICalendarBlob(accountId, req.session, req.ctx, logger, req.language(), blobIds)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, resp, sessionState, EventResponseObjectType, state)
|
||||
return req.noContent(accountId, sessionState, CalendarResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
// object type separately.
|
||||
//
|
||||
// This is done through individual query parameter, as follows:
|
||||
//
|
||||
// `?emails=rafrag&contacts=rbsxqeay&events=n`
|
||||
//
|
||||
// Additionally, the `maxchanges` query parameter may be used to limit the number of changes
|
||||
@@ -24,6 +25,8 @@ import (
|
||||
// The response then includes the new state after that maximum number if changes,
|
||||
// as well as a `hasMoreChanges` boolean flag which can be used to paginate the retrieval of
|
||||
// changes and the objects associated with the identifiers.
|
||||
//
|
||||
// @api:tags changes
|
||||
func (g *Groupware) GetChanges(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
@@ -34,7 +37,7 @@ func (g *Groupware) GetChanges(w http.ResponseWriter, r *http.Request) { //NOSON
|
||||
l = l.Str(logAccountId, accountId)
|
||||
|
||||
var maxChanges uint = 0
|
||||
if v, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0); err != nil {
|
||||
if v, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0); err != nil { // The maximum amount of changes to emit for each type of object.
|
||||
return req.error(accountId, err)
|
||||
} else if ok {
|
||||
maxChanges = v
|
||||
@@ -43,33 +46,33 @@ func (g *Groupware) GetChanges(w http.ResponseWriter, r *http.Request) { //NOSON
|
||||
|
||||
sinceState := jmap.StateMap{}
|
||||
{
|
||||
if state, ok := req.getStringParam(QueryParamMailboxes, ""); ok {
|
||||
if state, ok := req.getStringParam(QueryParamMailboxes, ""); ok { // The state of Mailboxes from which to determine changes.
|
||||
sinceState.Mailboxes = ptr(toState(state))
|
||||
}
|
||||
if state, ok := req.getStringParam(QueryParamEmails, ""); ok {
|
||||
if state, ok := req.getStringParam(QueryParamEmails, ""); ok { // The state of Emails from which to determine changes.
|
||||
sinceState.Emails = ptr(toState(state))
|
||||
}
|
||||
if state, ok := req.getStringParam(QueryParamAddressbooks, ""); ok {
|
||||
if state, ok := req.getStringParam(QueryParamAddressbooks, ""); ok { // The state of Address Books from which to determine changes.
|
||||
sinceState.Addressbooks = ptr(toState(state))
|
||||
}
|
||||
if state, ok := req.getStringParam(QueryParamContacts, ""); ok {
|
||||
if state, ok := req.getStringParam(QueryParamContacts, ""); ok { // The state of Contact Cards from which to determine changes.
|
||||
sinceState.Contacts = ptr(toState(state))
|
||||
}
|
||||
if state, ok := req.getStringParam(QueryParamCalendars, ""); ok {
|
||||
if state, ok := req.getStringParam(QueryParamCalendars, ""); ok { // The state of Calendars from which to determine changes.
|
||||
sinceState.Calendars = ptr(toState(state))
|
||||
}
|
||||
if state, ok := req.getStringParam(QueryParamEvents, ""); ok {
|
||||
if state, ok := req.getStringParam(QueryParamEvents, ""); ok { // The state of Calendar Events from which to determine changes.
|
||||
sinceState.Events = ptr(toState(state))
|
||||
}
|
||||
if state, ok := req.getStringParam(QueryParamIdentities, ""); ok {
|
||||
if state, ok := req.getStringParam(QueryParamIdentities, ""); ok { // The state of Identities from which to determine changes.
|
||||
sinceState.Identities = ptr(toState(state))
|
||||
}
|
||||
if state, ok := req.getStringParam(QueryParamEmailSubmissions, ""); ok {
|
||||
if state, ok := req.getStringParam(QueryParamEmailSubmissions, ""); ok { // The state of Email Submissions from which to determine changes.
|
||||
sinceState.EmailSubmissions = ptr(toState(state))
|
||||
}
|
||||
//if state, ok := req.getStringParam(QueryParamQuotas, ""); ok { sinceState.Quotas = ptr(toState(state)) }
|
||||
if sinceState.IsZero() {
|
||||
return req.noop(accountId)
|
||||
return req.noop(accountId) // No content response if no object IDs were requested.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,89 +41,6 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// Get all addressbooks of an account.
|
||||
func (g *Groupware) GetAddressbooks(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, req.logger, req.language(), nil)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
var body jmap.AddressBooksResponse = addressbooks
|
||||
return req.respond(accountId, body, sessionState, AddressBookResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
|
||||
// Get an addressbook of an account by its identifier.
|
||||
func (g *Groupware) GetAddressbook(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
addressBookId, err := req.PathParam(UriParamAddressBookId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
|
||||
|
||||
logger := log.From(l)
|
||||
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, logger, req.language(), []string{addressBookId})
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
if len(addressbooks.NotFound) > 0 {
|
||||
return req.notFound(accountId, sessionState, AddressBookResponseObjectType, state)
|
||||
} else {
|
||||
return req.respond(accountId, addressbooks.AddressBooks[0], sessionState, AddressBookResponseObjectType, state)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Get the changes that occured in a given mailbox since a certain state.
|
||||
// @api:tags mailbox,changes
|
||||
func (g *Groupware) GetAddressBookChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamMaxChanges, maxChanges)
|
||||
}
|
||||
|
||||
sinceState := jmap.State(req.OptHeaderParamDoc(HeaderParamSince, "Optionally specifies the state identifier from which on to list addressbook changes"))
|
||||
if sinceState != "" {
|
||||
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetAddressbookChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
return req.respond(accountId, changes, sessionState, AddressBookResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
|
||||
// Get all the contacts in an addressbook of an account by its identifier.
|
||||
func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/services/groupware/pkg/metrics"
|
||||
)
|
||||
|
||||
// Get the changes that occured in a given mailbox since a certain state.
|
||||
// @api:tags mailbox,changes
|
||||
// Get the changes tp Emails since a certain State.
|
||||
// @api:tags email,changes
|
||||
func (g *Groupware) GetEmailChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
@@ -230,6 +230,9 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
|
||||
}
|
||||
}
|
||||
|
||||
// Get the attachments of an email by its identifier.
|
||||
//
|
||||
// @api:tags email
|
||||
func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
contextAppender := func(l zerolog.Context) zerolog.Context { return l }
|
||||
q := r.URL.Query()
|
||||
@@ -941,6 +944,9 @@ func (e emailKeywordUpdates) IsEmpty() bool {
|
||||
return len(e.Add) == 0 && len(e.Remove) == 0
|
||||
}
|
||||
|
||||
// Update the keywords of an email by its identifier.
|
||||
//
|
||||
// @api:tags email
|
||||
func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
@@ -1000,6 +1006,8 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
// Add keywords to an email by its unique identifier.
|
||||
//
|
||||
// @api:tags email
|
||||
func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
@@ -1060,6 +1068,8 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { /
|
||||
}
|
||||
|
||||
// Remove keywords of an email by its unique identifier.
|
||||
//
|
||||
// @api:tags email
|
||||
func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
|
||||
184
services/groupware/pkg/groupware/api_events.go
Normal file
184
services/groupware/pkg/groupware/api_events.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package groupware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
// Get all the events in a calendar of an account by its identifier.
|
||||
func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
calendarId, err := req.PathParam(UriParamCalendarId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l = l.Str(UriParamCalendarId, log.SafeString(calendarId))
|
||||
|
||||
offset, ok, err := req.parseUIntParam(QueryParamOffset, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamOffset, offset)
|
||||
}
|
||||
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.contactLimit)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
}
|
||||
|
||||
filter := jmap.CalendarEventFilterCondition{
|
||||
InCalendar: calendarId,
|
||||
}
|
||||
sortBy := []jmap.CalendarEventComparator{{Property: jmap.CalendarEventPropertyUpdated, IsAscending: false}}
|
||||
|
||||
logger := log.From(l)
|
||||
eventsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryCalendarEvents(single(accountId), req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
if events, ok := eventsByAccountId[accountId]; ok {
|
||||
return req.respond(accountId, events, sessionState, EventResponseObjectType, state)
|
||||
} else {
|
||||
return req.notFound(accountId, sessionState, EventResponseObjectType, state)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Get changes to Contacts since a given State
|
||||
// @api:tags event,changes
|
||||
func (g *Groupware) GetEventChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
var maxChanges uint = 0
|
||||
if v, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0); err != nil {
|
||||
return req.error(accountId, err)
|
||||
} else if ok {
|
||||
maxChanges = v
|
||||
l = l.Uint(QueryParamMaxChanges, v)
|
||||
}
|
||||
|
||||
sinceState := jmap.State(req.OptHeaderParamDoc(HeaderParamSince, "Specifies the state identifier from which on to list event changes"))
|
||||
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
|
||||
|
||||
logger := log.From(l)
|
||||
changes, sessionState, state, lang, jerr := g.jmap.GetCalendarEventChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
var body jmap.CalendarEventChanges = changes
|
||||
|
||||
return req.respond(accountId, body, sessionState, ContactResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) CreateCalendarEvent(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
var create jmap.CalendarEvent
|
||||
err := req.body(&create)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
created, sessionState, state, lang, jerr := g.jmap.CreateCalendarEvent(accountId, req.session, req.ctx, logger, req.language(), create)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, created, sessionState, EventResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) DeleteCalendarEvent(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needCalendarWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
l := req.logger.With().Str(accountId, log.SafeString(accountId))
|
||||
|
||||
eventId, err := req.PathParam(UriParamEventId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
l.Str(UriParamEventId, log.SafeString(eventId))
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
deleted, sessionState, state, lang, jerr := g.jmap.DeleteCalendarEvent(accountId, []string{eventId}, req.session, req.ctx, logger, req.language())
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
|
||||
for _, e := range deleted {
|
||||
desc := e.Description
|
||||
if desc != "" {
|
||||
return req.errorS(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteContact,
|
||||
withDetail(e.Description),
|
||||
), sessionState)
|
||||
} else {
|
||||
return req.errorS(accountId, apiError(
|
||||
req.errorId(),
|
||||
ErrorFailedToDeleteContact,
|
||||
), sessionState)
|
||||
}
|
||||
}
|
||||
return req.noContent(accountId, sessionState, EventResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
|
||||
// Parse a blob that contains an iCal file and return it as JSCalendar.
|
||||
//
|
||||
// @api:tags calendar,blob
|
||||
func (g *Groupware) ParseIcalBlob(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForBlob()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
blobId, err := req.PathParam(UriParamBlobId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
}
|
||||
|
||||
blobIds := strings.Split(blobId, ",")
|
||||
l := req.logger.With().Array(UriParamBlobId, log.SafeStringArray(blobIds))
|
||||
logger := log.From(l)
|
||||
|
||||
resp, sessionState, state, lang, jerr := g.jmap.ParseICalendarBlob(accountId, req.session, req.ctx, logger, req.language(), blobIds)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, sessionState, lang)
|
||||
}
|
||||
return req.respond(accountId, resp, sessionState, EventResponseObjectType, state)
|
||||
})
|
||||
}
|
||||
@@ -176,7 +176,7 @@ func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *htt
|
||||
})
|
||||
}
|
||||
|
||||
// Get the changes that occured in a given mailbox since a certain state.
|
||||
// Get the changes tp Mailboxes since a certain State.
|
||||
// @api:tags mailbox,changes
|
||||
func (g *Groupware) GetMailboxChanges(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
|
||||
@@ -36,6 +36,8 @@ type ObjectsRequest struct {
|
||||
// The response then includes the new state after that maximum number if changes,
|
||||
// as well as a `hasMoreChanges` boolean flag which can be used to paginate the retrieval of
|
||||
// changes and the objects associated with the identifiers.
|
||||
//
|
||||
// @api:tags mailbox,email,addressbook,contact,calendar,event,quota,identity
|
||||
func (g *Groupware) GetObjects(w http.ResponseWriter, r *http.Request) { //NOSONAR
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
|
||||
@@ -200,6 +200,8 @@ const (
|
||||
ErrorCodeFailedToDeleteEmail = "DELEML"
|
||||
ErrorCodeFailedToDeleteSomeIdentities = "DELSID"
|
||||
ErrorCodeFailedToSanitizeEmail = "FSANEM"
|
||||
ErrorCodeFailedToDeleteAddressBook = "DELABK"
|
||||
ErrorCodeFailedToDeleteCalendar = "DELCAL"
|
||||
ErrorCodeFailedToDeleteContact = "DELCNT"
|
||||
ErrorCodeNoMailboxWithDraftRole = "NMBXDR"
|
||||
ErrorCodeNoMailboxWithSentRole = "NMBXSE"
|
||||
@@ -443,12 +445,24 @@ var (
|
||||
Title: "Failed to sanitize an email",
|
||||
Detail: "Email content sanitization failed.",
|
||||
}
|
||||
ErrorFailedToDeleteAddressBook = GroupwareError{
|
||||
Status: http.StatusInternalServerError,
|
||||
Code: ErrorCodeFailedToDeleteAddressBook,
|
||||
Title: "Failed to delete address books",
|
||||
Detail: "One or more address books could not be deleted.",
|
||||
}
|
||||
ErrorFailedToDeleteContact = GroupwareError{
|
||||
Status: http.StatusInternalServerError,
|
||||
Code: ErrorCodeFailedToDeleteContact,
|
||||
Title: "Failed to delete contacts",
|
||||
Detail: "One or more contacts could not be deleted.",
|
||||
}
|
||||
ErrorFailedToDeleteCalendar = GroupwareError{
|
||||
Status: http.StatusInternalServerError,
|
||||
Code: ErrorCodeFailedToDeleteCalendar,
|
||||
Title: "Failed to delete calendar",
|
||||
Detail: "One or more calendars could not be deleted.",
|
||||
}
|
||||
ErrorNoMailboxWithDraftRole = GroupwareError{
|
||||
Status: http.StatusExpectationFailed,
|
||||
Code: ErrorCodeNoMailboxWithDraftRole,
|
||||
|
||||
@@ -187,3 +187,17 @@ func (e Exemplar) DeletedMailboxes() ([]string, string, string, string) {
|
||||
j := jmap.ExemplarInstance
|
||||
return []string{j.MailboxProjectId, j.MailboxJunkId}, "Identifiers of the Mailboxes that have successfully been deleted", "", "deletedmailboxes"
|
||||
}
|
||||
|
||||
func (e Exemplar) ObjectsRequest() ObjectsRequest {
|
||||
return ObjectsRequest{
|
||||
Mailboxes: []string{"ahh9ye", "ahbei8"},
|
||||
Emails: []string{"koo6ka", "fa1ees", "zaish0", "iek2fo"},
|
||||
Addressbooks: []string{"ungu0a"},
|
||||
Contacts: []string{"oo8ahv", "lexue6", "mohth3"},
|
||||
Calendars: []string{"aa8aqu", "detho5"},
|
||||
Events: []string{"oo8thu", "mu9sha", "aim1sh", "sair6a"},
|
||||
Quotas: []string{"vei4ai"},
|
||||
Identities: []string{"iuj4ae", "mahv9y"},
|
||||
EmailSubmissions: []string{"eidoo6", "aakie7", "uh7ous"},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,22 +148,28 @@ func (g *Groupware) Route(r chi.Router) {
|
||||
})
|
||||
r.Route("/addressbooks", func(r chi.Router) {
|
||||
r.Get("/", g.GetAddressbooks)
|
||||
r.Post("/", g.CreateAddressBook)
|
||||
r.Route("/{addressbookid}", func(r chi.Router) {
|
||||
r.Get("/", g.GetAddressbook)
|
||||
r.Get("/contacts", g.GetContactsInAddressbook) //NOSONAR
|
||||
r.Delete("/", g.DeleteAddressBook)
|
||||
})
|
||||
})
|
||||
r.Route("/contacts", func(r chi.Router) {
|
||||
r.Get("/", g.GetAllContacts)
|
||||
r.Post("/", g.CreateContact)
|
||||
r.Delete("/{contactid}", g.DeleteContact)
|
||||
r.Get("/{contactid}", g.GetContactById)
|
||||
r.Route("/{contactid}", func(r chi.Router) {
|
||||
r.Get("/", g.GetContactById)
|
||||
r.Delete("/", g.DeleteContact)
|
||||
})
|
||||
})
|
||||
r.Route("/calendars", func(r chi.Router) {
|
||||
r.Get("/", g.GetCalendars)
|
||||
r.Post("/", g.CreateCalendar)
|
||||
r.Route("/{calendarid}", func(r chi.Router) {
|
||||
r.Get("/", g.GetCalendarById)
|
||||
r.Get("/events", g.GetEventsInCalendar) //NOSONAR
|
||||
r.Delete("/", g.DeleteCalendar)
|
||||
})
|
||||
})
|
||||
r.Route("/events", func(r chi.Router) {
|
||||
|
||||
36
services/groupware/pnpm-lock.yaml
generated
36
services/groupware/pnpm-lock.yaml
generated
@@ -9,8 +9,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@redocly/cli':
|
||||
specifier: ^2.25.2
|
||||
version: 2.25.2(@opentelemetry/api@1.9.1)(core-js@3.45.1)
|
||||
specifier: ^2.25.3
|
||||
version: 2.25.3(@opentelemetry/api@1.9.1)(core-js@3.45.1)
|
||||
'@types/js-yaml':
|
||||
specifier: ^4.0.9
|
||||
version: 4.0.9
|
||||
@@ -194,8 +194,8 @@ packages:
|
||||
'@redocly/cli-otel@0.1.2':
|
||||
resolution: {integrity: sha512-Bg7BoO5t1x3lVK+KhA5aGPmeXpQmdf6WtTYHhelKJCsQ+tRMiJoFAQoKHoBHAoNxXrhlS3K9lKFLHGmtxsFQfA==}
|
||||
|
||||
'@redocly/cli@2.25.2':
|
||||
resolution: {integrity: sha512-kn1SiHDss3t+Ami37T6ZH5ov1fiEXF1y488bUOUgrh0pEK8VOq8+HlPbdte/cH0K+dWPhuLyKNACd+KhMQPjCw==}
|
||||
'@redocly/cli@2.25.3':
|
||||
resolution: {integrity: sha512-02wjApwJwGD+kGWRoiFVY0Hq960ydMAMHrK3AJH2LMiYNYcrzAr1FSbA3OSylvg2gx3w/r1r710B+iMz3KJKbw==}
|
||||
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
@@ -209,12 +209,12 @@ packages:
|
||||
resolution: {integrity: sha512-V09ayfnb5GyysmvARbt+voFZAjGcf7hSYxOYxSkCc4fbH/DTfq5YWoec8cflvmHHqyIFbqvmGKmYFzqhr9zxDg==}
|
||||
engines: {node: '>=18.17.0', npm: '>=9.5.0'}
|
||||
|
||||
'@redocly/openapi-core@2.25.2':
|
||||
resolution: {integrity: sha512-HIvxgwxQct/IdRJjjqu4g8BLpCik6I3zxp8JFJpRtmY1TSIZAOZjJwlkoh4uQcy/nCP+psSMgQvzjVGml3k6+w==}
|
||||
'@redocly/openapi-core@2.25.3':
|
||||
resolution: {integrity: sha512-GIu3Mdym5IDIPCvXTzMZ6TQw/+7sKd52PdysxNVe7zBk22ExSGnVE9UAk9BaLOzXT77PJWDUwaimBdJoPpxHMA==}
|
||||
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
|
||||
|
||||
'@redocly/respect-core@2.25.2':
|
||||
resolution: {integrity: sha512-GpvmjY2x8u4pAGNts7slexuKDzDWHNUB4gey9/rSqvC8IaqY49vkvMuRodIBwCsqXhn2rpkJbar1UK3rAOuy7g==}
|
||||
'@redocly/respect-core@2.25.3':
|
||||
resolution: {integrity: sha512-07m80JYdp7J7kH4D1Vqdpa2ZBFCv3QAwCoh2w9H3OjuT/rXQkBSkJQm1n70fzO/HuUf4azzULdp2XnsIpxP2qw==}
|
||||
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
|
||||
|
||||
'@tsconfig/node10@1.0.12':
|
||||
@@ -556,8 +556,8 @@ packages:
|
||||
engines: {node: '>= 12'}
|
||||
hasBin: true
|
||||
|
||||
minimatch@10.2.4:
|
||||
resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
|
||||
minimatch@10.2.5:
|
||||
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
minimatch@5.1.9:
|
||||
@@ -1135,15 +1135,15 @@ snapshots:
|
||||
dependencies:
|
||||
ulid: 2.4.0
|
||||
|
||||
'@redocly/cli@2.25.2(@opentelemetry/api@1.9.1)(core-js@3.45.1)':
|
||||
'@redocly/cli@2.25.3(@opentelemetry/api@1.9.1)(core-js@3.45.1)':
|
||||
dependencies:
|
||||
'@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.1)
|
||||
'@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1)
|
||||
'@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.1)
|
||||
'@opentelemetry/semantic-conventions': 1.34.0
|
||||
'@redocly/cli-otel': 0.1.2
|
||||
'@redocly/openapi-core': 2.25.2
|
||||
'@redocly/respect-core': 2.25.2
|
||||
'@redocly/openapi-core': 2.25.3
|
||||
'@redocly/respect-core': 2.25.3
|
||||
abort-controller: 3.0.0
|
||||
ajv: '@redocly/ajv@8.18.0'
|
||||
ajv-formats: 3.0.1(@redocly/ajv@8.18.0)
|
||||
@@ -1195,7 +1195,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@redocly/openapi-core@2.25.2':
|
||||
'@redocly/openapi-core@2.25.3':
|
||||
dependencies:
|
||||
'@redocly/ajv': 8.18.0
|
||||
'@redocly/config': 0.45.0
|
||||
@@ -1208,12 +1208,12 @@ snapshots:
|
||||
pluralize: 8.0.0
|
||||
yaml-ast-parser: 0.0.43
|
||||
|
||||
'@redocly/respect-core@2.25.2':
|
||||
'@redocly/respect-core@2.25.3':
|
||||
dependencies:
|
||||
'@faker-js/faker': 7.6.0
|
||||
'@noble/hashes': 1.8.0
|
||||
'@redocly/ajv': 8.18.0
|
||||
'@redocly/openapi-core': 2.25.2
|
||||
'@redocly/openapi-core': 2.25.3
|
||||
ajv: '@redocly/ajv@8.18.0'
|
||||
better-ajv-errors: 1.2.0(@redocly/ajv@8.18.0)
|
||||
colorette: 2.0.20
|
||||
@@ -1446,7 +1446,7 @@ snapshots:
|
||||
|
||||
glob@13.0.6:
|
||||
dependencies:
|
||||
minimatch: 10.2.4
|
||||
minimatch: 10.2.5
|
||||
minipass: 7.1.3
|
||||
path-scurry: 2.0.2
|
||||
|
||||
@@ -1527,7 +1527,7 @@ snapshots:
|
||||
|
||||
marked@4.3.0: {}
|
||||
|
||||
minimatch@10.2.4:
|
||||
minimatch@10.2.5:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.5
|
||||
|
||||
|
||||
Reference in New Issue
Block a user