mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-13 15:49:38 -06:00
groupware: minor email searching response improvements + started implementing vacation response setting API
This commit is contained in:
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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{} },
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package groupware
|
||||
|
||||
const (
|
||||
Version = "1.0.0"
|
||||
Version = "0.0.1"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (g Groupware) GetIdentity(w http.ResponseWriter, r *http.Request) {
|
||||
func (g Groupware) GetIdentities(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
res, err := g.jmap.GetIdentity(req.GetAccountId(), req.session, req.ctx, req.logger)
|
||||
if err != nil {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
func (g Groupware) GetAllMessages(w http.ResponseWriter, r *http.Request) {
|
||||
func (g Groupware) GetAllMessagesInMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
mailboxId := chi.URLParam(r, UriParamMailboxId)
|
||||
since := r.Header.Get(HeaderSince)
|
||||
|
||||
@@ -280,6 +280,14 @@ func (g Groupware) searchMessages(w http.ResponseWriter, r *http.Request) {
|
||||
return errResp
|
||||
}
|
||||
|
||||
var empty jmap.EmailFilterElement
|
||||
|
||||
if filter == empty {
|
||||
errorId := req.errorId()
|
||||
msg := "Invalid search request has no criteria"
|
||||
return errorResponse(apiError(errorId, ErrorInvalidUserRequest, withDetail(msg)))
|
||||
}
|
||||
|
||||
fetchEmails, ok, err := req.parseBoolParam(QueryParamSearchFetchEmails, false)
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
|
||||
@@ -37,3 +37,19 @@ func (g Groupware) GetVacation(w http.ResponseWriter, r *http.Request) {
|
||||
return response(res, res.State)
|
||||
})
|
||||
}
|
||||
|
||||
func (g Groupware) SetVacation(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
var body jmap.VacationResponseBody
|
||||
err := req.body(&body)
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
|
||||
res, jerr := g.jmap.SetVacationResponse(req.GetAccountId(), body, req.session, req.ctx, req.logger)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
return response(res, res.SessionState)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ func groupwareErrorFromJmap(j jmap.Error) *GroupwareError {
|
||||
case jmap.JmapErrorAuthenticationFailed:
|
||||
return &ErrorForbidden
|
||||
case jmap.JmapErrorInvalidHttpRequest:
|
||||
return &ErrorInvalidRequest
|
||||
return &ErrorInvalidBackendRequest
|
||||
case jmap.JmapErrorServerResponse:
|
||||
return &ErrorServerResponse
|
||||
case jmap.JmapErrorReadingResponseBody:
|
||||
@@ -148,7 +148,7 @@ const (
|
||||
ErrorCodeInvalidAuthentication = "AUTINV"
|
||||
ErrorCodeMissingAuthentication = "AUTMIS"
|
||||
ErrorCodeForbiddenGeneric = "AUTFOR"
|
||||
ErrorCodeInvalidRequest = "INVREQ"
|
||||
ErrorCodeInvalidBackendRequest = "INVREQ"
|
||||
ErrorCodeServerResponse = "SRVRSP"
|
||||
ErrorCodeStreamingResponse = "SRVRST"
|
||||
ErrorCodeServerReadingResponse = "SRVRRE"
|
||||
@@ -162,6 +162,7 @@ const (
|
||||
ErrorCodeInvalidRequestParameter = "INVPAR"
|
||||
ErrorCodeNonExistingAccount = "INVACC"
|
||||
ErrorCodeApiInconsistency = "APIINC"
|
||||
ErrorCodeInvalidUserRequest = "INVURQ"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -189,9 +190,9 @@ var (
|
||||
Title: "Invalid Authentication",
|
||||
Detail: "Authentication credentials were provided but are either invalid or not authorized to perform the request operation.",
|
||||
}
|
||||
ErrorInvalidRequest = GroupwareError{
|
||||
ErrorInvalidBackendRequest = GroupwareError{
|
||||
Status: http.StatusInternalServerError,
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Code: ErrorCodeInvalidBackendRequest,
|
||||
Title: "Invalid Request",
|
||||
Detail: "The request that was meant to be sent to the mail server is invalid, which might be caused by configuration issues.",
|
||||
}
|
||||
@@ -261,6 +262,12 @@ var (
|
||||
Title: "Invalid Request Parameter",
|
||||
Detail: "At least one of the parameters in the request is invalid.",
|
||||
}
|
||||
ErrorInvalidUserRequest = GroupwareError{
|
||||
Status: http.StatusBadRequest,
|
||||
Code: ErrorCodeInvalidUserRequest,
|
||||
Title: "Invalid Request",
|
||||
Detail: "The request is invalid.",
|
||||
}
|
||||
ErrorNonExistingAccount = GroupwareError{
|
||||
Status: http.StatusBadRequest,
|
||||
Code: ErrorCodeNonExistingAccount,
|
||||
|
||||
@@ -39,19 +39,19 @@ func (g Groupware) Route(r chi.Router) {
|
||||
r.Get("/accounts", g.GetAccounts)
|
||||
r.Route("/accounts/{accountid}", func(r chi.Router) {
|
||||
r.Get("/", g.GetAccount)
|
||||
r.Get("/identity", g.GetIdentity)
|
||||
r.Get("/identities", g.GetIdentities)
|
||||
r.Get("/vacation", g.GetVacation)
|
||||
r.Post("/vacation", g.SetVacation)
|
||||
r.Route("/mailboxes", func(r chi.Router) {
|
||||
r.Get("/", g.GetMailboxes) // ?name=&role=&subcribed=
|
||||
r.Get("/{mailbox}", g.GetMailbox)
|
||||
r.Get("/{mailbox}/messages", g.GetAllMessages)
|
||||
r.Get("/{mailbox}/messages", g.GetAllMessagesInMailbox)
|
||||
})
|
||||
r.Route("/messages", func(r chi.Router) {
|
||||
r.Get("/", g.GetMessages) // ?fetchemails=true&fetchbodies=true&text=&subject=&body=&keyword=&keyword=&...
|
||||
r.Post("/", g.CreateMessage)
|
||||
r.Get("/{messageid}", g.GetMessagesById)
|
||||
r.Patch("/{messageid}", g.UpdateMessage) // or PUT?
|
||||
r.Put("/{messageid}", g.UpdateMessage) // or PATCH?
|
||||
r.Put("/{messageid}", g.UpdateMessage) // or PATCH?
|
||||
r.Delete("/{messageId}", g.DeleteMessage)
|
||||
})
|
||||
r.Route("/blobs", func(r chi.Router) {
|
||||
|
||||
Reference in New Issue
Block a user