groupware: minor email searching response improvements + started implementing vacation response setting API

This commit is contained in:
Pascal Bleser
2025-08-08 14:45:24 +02:00
parent 4522ac8e89
commit f308b61490
9 changed files with 201 additions and 15 deletions
+19 -4
View File
@@ -23,6 +23,9 @@ const (
type Emails struct {
Emails []Email `json:"emails,omitempty"`
Total int `json:"total,omitzero"`
Limit int `json:"limit,omitzero"`
Offset int `json:"offset,omitzero"`
State string `json:"state,omitempty"`
}
@@ -60,7 +63,7 @@ func (j *Client) GetAllEmails(accountId string, session *Session, ctx context.Co
Filter: &EmailFilterCondition{InMailbox: mailboxId},
Sort: []Sort{{Property: emailSortByReceivedAt, IsAscending: false}},
CollapseThreads: true,
CalculateTotal: false,
CalculateTotal: true,
}
if offset >= 0 {
query.Position = offset
@@ -87,12 +90,24 @@ func (j *Client) GetAllEmails(accountId string, session *Session, ctx context.Co
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Emails, Error) {
var response EmailGetResponse
err = retrieveResponseMatchParameters(body, EmailGet, "1", &response)
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(body, EmailQuery, "0", &queryResponse)
if err != nil {
return Emails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
}
return Emails{Emails: response.List, State: body.SessionState}, nil
var getResponse EmailGetResponse
err = retrieveResponseMatchParameters(body, EmailGet, "1", &getResponse)
if err != nil {
return Emails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
}
return Emails{
Emails: getResponse.List,
State: body.SessionState,
Total: queryResponse.Total,
Limit: queryResponse.Limit,
Offset: queryResponse.Position,
}, nil
})
}
+117
View File
@@ -2,10 +2,15 @@ package jmap
import (
"context"
"time"
"github.com/opencloud-eu/opencloud/pkg/log"
)
const (
vacationResponseId = "singleton"
)
// https://jmap.io/spec-mail.html#vacationresponseget
func (j *Client) GetVacationResponse(accountId string, session *Session, ctx context.Context, logger *log.Logger) (VacationResponseGetResponse, Error) {
aid := session.MailAccountId(accountId)
@@ -20,3 +25,115 @@ func (j *Client) GetVacationResponse(accountId string, session *Session, ctx con
return response, simpleError(err, JmapErrorInvalidJmapResponsePayload)
})
}
type VacationResponseStatusChange struct {
VacationResponse VacationResponse `json:"vacationResponse"`
ResponseState string `json:"state"`
SessionState string `json:"sessionState"`
}
func (j *Client) SetVacationResponseStatus(accountId string, enabled bool, session *Session, ctx context.Context, logger *log.Logger) (VacationResponseStatusChange, Error) {
aid := session.MailAccountId(accountId)
logger = j.logger(aid, "EnableVacationResponse", session, logger)
cmd, err := request(invocation(VacationResponseSet, VacationResponseSetRequest{
AccountId: aid,
Update: map[string]PatchObject{
"u": {
"/isEnabled": enabled,
},
},
}, "0"))
if err != nil {
return VacationResponseStatusChange{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (VacationResponseStatusChange, Error) {
var response VacationResponseSetResponse
err = retrieveResponseMatchParameters(body, VacationResponseSet, "0", &response)
if err != nil {
return VacationResponseStatusChange{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
updated, ok := response.Updated["u"]
if !ok {
// TODO implement error when not updated
}
return VacationResponseStatusChange{
VacationResponse: updated,
ResponseState: response.NewState,
SessionState: response.State,
}, nil
})
}
type VacationResponseBody struct {
// Should a vacation response be sent if a message arrives between the "fromDate" and "toDate"?
IsEnabled bool `json:"isEnabled"`
// If "isEnabled" is true, messages that arrive on or after this date-time (but before the "toDate" if defined) should receive the
// user's vacation response. If null, the vacation response is effective immediately.
FromDate time.Time `json:"fromDate,omitzero"`
// If "isEnabled" is true, messages that arrive before this date-time but on or after the "fromDate" if defined) should receive the
// user's vacation response. If null, the vacation response is effective indefinitely.
ToDate time.Time `json:"toDate,omitzero"`
// The subject that will be used by the message sent in response to messages when the vacation response is enabled.
// If null, an appropriate subject SHOULD be set by the server.
Subject string `json:"subject,omitempty"`
// The plaintext body to send in response to messages when the vacation response is enabled.
// If this is null, the server SHOULD generate a plaintext body part from the "htmlBody" when sending vacation responses
// but MAY choose to send the response as HTML only. If both "textBody" and "htmlBody" are null, an appropriate default
// body SHOULD be generated for responses by the server.
TextBody string `json:"textBody,omitempty"`
// The HTML body to send in response to messages when the vacation response is enabled.
// If this is null, the server MAY choose to generate an HTML body part from the "textBody" when sending vacation responses
// or MAY choose to send the response as plaintext only.
HtmlBody string `json:"htmlBody,omitempty"`
}
type VacationResponseChange struct {
VacationResponse VacationResponse `json:"vacationResponse"`
ResponseState string `json:"state"`
SessionState string `json:"sessionState"`
}
func (j *Client) SetVacationResponse(accountId string, vacation VacationResponseBody, session *Session, ctx context.Context, logger *log.Logger) (VacationResponseChange, Error) {
aid := session.MailAccountId(accountId)
logger = j.logger(aid, "SetVacationResponse", session, logger)
set := VacationResponseSetRequest{
AccountId: aid,
Create: map[string]VacationResponse{
vacationResponseId: {
IsEnabled: vacation.IsEnabled,
FromDate: vacation.FromDate,
ToDate: vacation.ToDate,
Subject: vacation.Subject,
TextBody: vacation.TextBody,
HtmlBody: vacation.HtmlBody,
},
},
}
cmd, err := request(invocation(VacationResponseSet, set, "0"))
if err != nil {
return VacationResponseChange{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (VacationResponseChange, Error) {
var response VacationResponseSetResponse
err = retrieveResponseMatchParameters(body, VacationResponseSet, "0", &response)
if err != nil {
return VacationResponseChange{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
created, ok := response.Created[vacationResponseId]
if !ok {
// TODO handle case where created is missing
}
return VacationResponseChange{
VacationResponse: created,
ResponseState: response.NewState,
SessionState: response.State,
}, nil
})
}
+23
View File
@@ -1616,6 +1616,27 @@ type VacationResponseGetResponse struct {
NotFound []any `json:"notFound,omitempty"`
}
type VacationResponseSetRequest struct {
AccountId string `json:"accountId"`
IfInState string `json:"ifInState,omitempty"`
Create map[string]VacationResponse `json:"create,omitempty"`
Update map[string]PatchObject `json:"update,omitempty"`
Destroy []string `json:"destroy,omitempty"`
}
type VacationResponseSetResponse struct {
AccountId string `json:"accountId"`
OldState string `json:"oldState,omitempty"`
NewState string `json:"newState,omitempty"`
Created map[string]VacationResponse `json:"created,omitempty"`
Updated map[string]VacationResponse `json:"updated,omitempty"`
Destroyed []string `json:"destroyed,omitempty"`
NotCreated map[string]SetError `json:"notCreated,omitempty"`
NotUpdated map[string]SetError `json:"notUpdated,omitempty"`
NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"`
State string `json:"state,omitempty"`
}
// One of these attributes must be set, but not both.
type DataSourceObject struct {
DataAsText string `json:"data:asText,omitempty"`
@@ -1741,6 +1762,7 @@ const (
MailboxChanges Command = "Mailbox/changes"
IdentityGet Command = "Identity/get"
VacationResponseGet Command = "VacationResponse/get"
VacationResponseSet Command = "VacationResponse/set"
SearchSnippetGet Command = "SearchSnippet/get"
)
@@ -1758,5 +1780,6 @@ var CommandResponseTypeMap = map[Command]func() any{
ThreadGet: func() any { return ThreadGetResponse{} },
IdentityGet: func() any { return IdentityGetResponse{} },
VacationResponseGet: func() any { return VacationResponseGetResponse{} },
VacationResponseSet: func() any { return VacationResponseSetResponse{} },
SearchSnippetGet: func() any { return SearchSnippetGetResponse{} },
}