mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-02-06 12:21:21 -05:00
groupware: more JMAP operations implementation
This commit is contained in:
404
pkg/jmap/jmap.go
404
pkg/jmap/jmap.go
@@ -2,6 +2,7 @@ package jmap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
@@ -55,14 +56,51 @@ func NewClient(wellKnown SessionClient, api ApiClient) Client {
|
||||
type Session struct {
|
||||
// The name of the user to use to authenticate against Stalwart
|
||||
Username string
|
||||
|
||||
// The base URL to use for JMAP operations towards Stalwart
|
||||
JmapUrl url.URL
|
||||
|
||||
// The upload URL template
|
||||
UploadUrlTemplate string
|
||||
|
||||
// TODO
|
||||
DefaultMailAccountId string
|
||||
|
||||
SessionResponse
|
||||
}
|
||||
|
||||
// Create a new Session from a SessionResponse.
|
||||
func newSession(sessionResponse SessionResponse) (Session, Error) {
|
||||
username := sessionResponse.Username
|
||||
if username == "" {
|
||||
return Session{}, SimpleError{code: JmapErrorInvalidSessionResponse, err: fmt.Errorf("JMAP session response does not provide a username")}
|
||||
}
|
||||
mailAccountId := sessionResponse.PrimaryAccounts.Mail
|
||||
if mailAccountId == "" {
|
||||
return Session{}, SimpleError{code: JmapErrorInvalidSessionResponse, err: fmt.Errorf("JMAP session response does not provide a primary mail account")}
|
||||
}
|
||||
apiStr := sessionResponse.ApiUrl
|
||||
if apiStr == "" {
|
||||
return Session{}, SimpleError{code: JmapErrorInvalidSessionResponse, err: fmt.Errorf("JMAP session response does not provide an API URL")}
|
||||
}
|
||||
apiUrl, err := url.Parse(apiStr)
|
||||
if err != nil {
|
||||
return Session{}, SimpleError{code: JmapErrorInvalidSessionResponse, err: fmt.Errorf("JMAP session response provides an invalid API URL")}
|
||||
}
|
||||
uploadUrl := sessionResponse.UploadUrl
|
||||
if uploadUrl == "" {
|
||||
return Session{}, SimpleError{code: JmapErrorInvalidSessionResponse, err: fmt.Errorf("JMAP session response does not provide an upload URL")}
|
||||
}
|
||||
|
||||
return Session{
|
||||
Username: username,
|
||||
DefaultMailAccountId: mailAccountId,
|
||||
JmapUrl: *apiUrl,
|
||||
UploadUrlTemplate: uploadUrl,
|
||||
SessionResponse: sessionResponse,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Session) MailAccountId(accountId string) string {
|
||||
if accountId != "" && accountId != defaultAccountId {
|
||||
return accountId
|
||||
@@ -71,6 +109,14 @@ func (s *Session) MailAccountId(accountId string) string {
|
||||
return s.DefaultMailAccountId
|
||||
}
|
||||
|
||||
func (s *Session) BlobAccountId(accountId string) string {
|
||||
if accountId != "" && accountId != defaultAccountId {
|
||||
return accountId
|
||||
}
|
||||
// TODO(pbleser-oc) handle case where there is no default blob account
|
||||
return s.PrimaryAccounts.Blob
|
||||
}
|
||||
|
||||
const (
|
||||
logOperation = "operation"
|
||||
logUsername = "username"
|
||||
@@ -81,6 +127,7 @@ const (
|
||||
logLimit = "limit"
|
||||
logApiUrl = "apiurl"
|
||||
logSessionState = "session-state"
|
||||
logSince = "since"
|
||||
|
||||
defaultAccountId = "*"
|
||||
|
||||
@@ -114,32 +161,6 @@ func (j *Client) onSessionOutdated(session *Session) {
|
||||
})
|
||||
}
|
||||
|
||||
// Create a new Session from a WellKnownResponse.
|
||||
func newSession(sessionResponse SessionResponse) (Session, Error) {
|
||||
username := sessionResponse.Username
|
||||
if username == "" {
|
||||
return Session{}, SimpleError{code: JmapErrorInvalidSessionResponse, err: fmt.Errorf("JMAP session response does not provide a username")}
|
||||
}
|
||||
mailAccountId := sessionResponse.PrimaryAccounts.Mail
|
||||
if mailAccountId == "" {
|
||||
return Session{}, SimpleError{code: JmapErrorInvalidSessionResponse, err: fmt.Errorf("JMAP session response does not provide a primary mail account")}
|
||||
}
|
||||
apiStr := sessionResponse.ApiUrl
|
||||
if apiStr == "" {
|
||||
return Session{}, SimpleError{code: JmapErrorInvalidSessionResponse, err: fmt.Errorf("JMAP session response does not provide an API URL")}
|
||||
}
|
||||
apiUrl, err := url.Parse(apiStr)
|
||||
if err != nil {
|
||||
return Session{}, SimpleError{code: JmapErrorInvalidSessionResponse, err: fmt.Errorf("JMAP session response provides an invalid API URL")}
|
||||
}
|
||||
return Session{
|
||||
Username: username,
|
||||
DefaultMailAccountId: mailAccountId,
|
||||
JmapUrl: *apiUrl,
|
||||
SessionResponse: sessionResponse,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Retrieve JMAP well-known data from the Stalwart server and create a Session from that.
|
||||
func (j *Client) FetchSession(username string, logger *log.Logger) (Session, Error) {
|
||||
wk, err := j.wellKnown.GetSession(username, logger)
|
||||
@@ -242,7 +263,7 @@ func (j *Client) SearchMailboxes(accountId string, session *Session, ctx context
|
||||
invocation(MailboxQuery, SimpleMailboxQueryCommand{AccountId: aid, Filter: filter}, "0"),
|
||||
invocation(MailboxGet, MailboxGetRefCommand{
|
||||
AccountId: aid,
|
||||
IdRef: &Ref{Name: MailboxQuery, Path: "/ids/*", ResultOf: "0"},
|
||||
IdRef: &ResultReference{Name: MailboxQuery, Path: "/ids/*", ResultOf: "0"},
|
||||
}, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -310,7 +331,7 @@ func (j *Client) GetAllEmails(accountId string, session *Session, ctx context.Co
|
||||
get := EmailGetRefCommand{
|
||||
AccountId: aid,
|
||||
FetchAllBodyValues: fetchBodies,
|
||||
IdRef: &Ref{Name: EmailQuery, Path: "/ids/*", ResultOf: "0"},
|
||||
IdRef: &ResultReference{Name: EmailQuery, Path: "/ids/*", ResultOf: "0"},
|
||||
}
|
||||
if maxBodyValueBytes >= 0 {
|
||||
get.MaxBodyValueBytes = maxBodyValueBytes
|
||||
@@ -333,3 +354,330 @@ func (j *Client) GetAllEmails(accountId string, session *Session, ctx context.Co
|
||||
return Emails{Emails: response.List, State: body.SessionState}, nil
|
||||
})
|
||||
}
|
||||
|
||||
type EmailsSince struct {
|
||||
Destroyed []string `json:"destroyed,omitzero"`
|
||||
HasMoreChanges bool `json:"hasMoreChanges,omitzero"`
|
||||
NewState string `json:"newState"`
|
||||
Created []Email `json:"created,omitempty"`
|
||||
Updated []Email `json:"updated,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
func (j *Client) GetEmailsInMailboxSince(accountId string, session *Session, ctx context.Context, logger *log.Logger, mailboxId string, since string, fetchBodies bool, maxBodyValueBytes int, maxChanges int) (EmailsSince, Error) {
|
||||
aid := session.MailAccountId(accountId)
|
||||
logger = j.loggerParams(aid, "GetEmailsInMailboxSince", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies).Str(logSince, since)
|
||||
})
|
||||
|
||||
changes := MailboxChangesCommand{
|
||||
AccountId: aid,
|
||||
SinceState: since,
|
||||
}
|
||||
if maxChanges >= 0 {
|
||||
changes.MaxChanges = maxChanges
|
||||
}
|
||||
|
||||
getCreated := EmailGetRefCommand{
|
||||
AccountId: aid,
|
||||
FetchAllBodyValues: fetchBodies,
|
||||
IdRef: &ResultReference{Name: MailboxChanges, Path: "/created", ResultOf: "0"},
|
||||
}
|
||||
if maxBodyValueBytes >= 0 {
|
||||
getCreated.MaxBodyValueBytes = maxBodyValueBytes
|
||||
}
|
||||
getUpdated := EmailGetRefCommand{
|
||||
AccountId: aid,
|
||||
FetchAllBodyValues: fetchBodies,
|
||||
IdRef: &ResultReference{Name: MailboxChanges, Path: "/updated", ResultOf: "0"},
|
||||
}
|
||||
if maxBodyValueBytes >= 0 {
|
||||
getUpdated.MaxBodyValueBytes = maxBodyValueBytes
|
||||
}
|
||||
|
||||
cmd, err := request(
|
||||
invocation(MailboxChanges, changes, "0"),
|
||||
invocation(EmailGet, getCreated, "1"),
|
||||
invocation(EmailGet, getUpdated, "2"),
|
||||
)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailsSince, Error) {
|
||||
var mailboxResponse MailboxChangesResponse
|
||||
err = retrieveResponseMatchParameters(body, MailboxChanges, "0", &mailboxResponse)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
var createdResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, EmailGet, "1", &createdResponse)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
var updatedResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, EmailGet, "2", &updatedResponse)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
return EmailsSince{
|
||||
Destroyed: mailboxResponse.Destroyed,
|
||||
HasMoreChanges: mailboxResponse.HasMoreChanges,
|
||||
NewState: mailboxResponse.NewState,
|
||||
Created: createdResponse.List,
|
||||
Updated: createdResponse.List,
|
||||
State: body.SessionState,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.Context, logger *log.Logger, since string, fetchBodies bool, maxBodyValueBytes int, maxChanges int) (EmailsSince, Error) {
|
||||
aid := session.MailAccountId(accountId)
|
||||
logger = j.loggerParams(aid, "GetEmailsSince", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies).Str(logSince, since)
|
||||
})
|
||||
|
||||
changes := EmailChangesCommand{
|
||||
AccountId: aid,
|
||||
SinceState: since,
|
||||
}
|
||||
if maxChanges >= 0 {
|
||||
changes.MaxChanges = maxChanges
|
||||
}
|
||||
|
||||
getCreated := EmailGetRefCommand{
|
||||
AccountId: aid,
|
||||
FetchAllBodyValues: fetchBodies,
|
||||
IdRef: &ResultReference{Name: EmailChanges, Path: "/created", ResultOf: "0"},
|
||||
}
|
||||
if maxBodyValueBytes >= 0 {
|
||||
getCreated.MaxBodyValueBytes = maxBodyValueBytes
|
||||
}
|
||||
getUpdated := EmailGetRefCommand{
|
||||
AccountId: aid,
|
||||
FetchAllBodyValues: fetchBodies,
|
||||
IdRef: &ResultReference{Name: EmailChanges, Path: "/updated", ResultOf: "0"},
|
||||
}
|
||||
if maxBodyValueBytes >= 0 {
|
||||
getUpdated.MaxBodyValueBytes = maxBodyValueBytes
|
||||
}
|
||||
|
||||
cmd, err := request(
|
||||
invocation(EmailChanges, changes, "0"),
|
||||
invocation(EmailGet, getCreated, "1"),
|
||||
invocation(EmailGet, getUpdated, "2"),
|
||||
)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailsSince, Error) {
|
||||
var changesResponse EmailChangesResponse
|
||||
err = retrieveResponseMatchParameters(body, EmailChanges, "0", &changesResponse)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
var createdResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, EmailGet, "1", &createdResponse)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
var updatedResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, EmailGet, "2", &updatedResponse)
|
||||
if err != nil {
|
||||
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
return EmailsSince{
|
||||
Destroyed: changesResponse.Destroyed,
|
||||
HasMoreChanges: changesResponse.HasMoreChanges,
|
||||
NewState: changesResponse.NewState,
|
||||
Created: createdResponse.List,
|
||||
Updated: createdResponse.List,
|
||||
State: body.SessionState,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) GetBlob(accountId string, session *Session, ctx context.Context, logger *log.Logger, id string) (*Blob, Error) {
|
||||
aid := session.BlobAccountId(accountId)
|
||||
|
||||
cmd, err := request(
|
||||
invocation(BlobUpload, BlobGetCommand{
|
||||
AccountId: aid,
|
||||
Ids: []string{id},
|
||||
Properties: []string{BlobPropertyData, BlobPropertyDigestSha512, BlobPropertySize},
|
||||
}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (*Blob, Error) {
|
||||
var response BlobGetResponse
|
||||
err = retrieveResponseMatchParameters(body, BlobGet, "0", &response)
|
||||
if err != nil {
|
||||
return nil, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
if len(response.List) != 1 {
|
||||
return nil, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
get := response.List[0]
|
||||
return &get, nil
|
||||
})
|
||||
}
|
||||
|
||||
type UploadedBlob struct {
|
||||
Id string `json:"id"`
|
||||
Size int `json:"size"`
|
||||
Type string `json:"type"`
|
||||
Sha512 string `json:"sha:512"`
|
||||
}
|
||||
|
||||
func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Context, logger *log.Logger, data []byte, contentType string) (UploadedBlob, Error) {
|
||||
aid := session.MailAccountId(accountId)
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(data)
|
||||
|
||||
upload := BlobUploadCommand{
|
||||
AccountId: aid,
|
||||
Create: map[string]UploadObject{
|
||||
"0": {
|
||||
Data: []DataSourceObject{{
|
||||
DataAsBase64: encoded,
|
||||
}},
|
||||
Type: contentType,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
getHash := BlobGetRefCommand{
|
||||
AccountId: aid,
|
||||
IdRef: &ResultReference{
|
||||
ResultOf: "0",
|
||||
Name: BlobUpload,
|
||||
Path: "/ids",
|
||||
},
|
||||
Properties: []string{BlobPropertyDigestSha512},
|
||||
}
|
||||
|
||||
cmd, err := request(
|
||||
invocation(BlobUpload, upload, "0"),
|
||||
invocation(BlobGet, getHash, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (UploadedBlob, Error) {
|
||||
var uploadResponse BlobUploadResponse
|
||||
err = retrieveResponseMatchParameters(body, BlobUpload, "0", &uploadResponse)
|
||||
if err != nil {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
var getResponse BlobGetResponse
|
||||
err = retrieveResponseMatchParameters(body, BlobGet, "1", &getResponse)
|
||||
if err != nil {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
if len(uploadResponse.Created) != 1 {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
upload, ok := uploadResponse.Created["0"]
|
||||
if !ok {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
if len(getResponse.List) != 1 {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
get := getResponse.List[0]
|
||||
|
||||
return UploadedBlob{
|
||||
Id: upload.Id,
|
||||
Size: upload.Size,
|
||||
Type: upload.Type,
|
||||
Sha512: get.DigestSha512,
|
||||
}, nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Context, logger *log.Logger, data []byte) (UploadedBlob, Error) {
|
||||
aid := session.MailAccountId(accountId)
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(data)
|
||||
|
||||
upload := BlobUploadCommand{
|
||||
AccountId: aid,
|
||||
Create: map[string]UploadObject{
|
||||
"0": {
|
||||
Data: []DataSourceObject{{
|
||||
DataAsBase64: encoded,
|
||||
}},
|
||||
Type: EmailMimeType,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
getHash := BlobGetRefCommand{
|
||||
AccountId: aid,
|
||||
IdRef: &ResultReference{
|
||||
ResultOf: "0",
|
||||
Name: BlobUpload,
|
||||
Path: "/ids",
|
||||
},
|
||||
Properties: []string{BlobPropertyDigestSha512},
|
||||
}
|
||||
|
||||
cmd, err := request(
|
||||
invocation(BlobUpload, upload, "0"),
|
||||
invocation(BlobGet, getHash, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (UploadedBlob, Error) {
|
||||
var uploadResponse BlobUploadResponse
|
||||
err = retrieveResponseMatchParameters(body, BlobUpload, "0", &uploadResponse)
|
||||
if err != nil {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
var getResponse BlobGetResponse
|
||||
err = retrieveResponseMatchParameters(body, BlobGet, "1", &getResponse)
|
||||
if err != nil {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
if len(uploadResponse.Created) != 1 {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
upload, ok := uploadResponse.Created["0"]
|
||||
if !ok {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
|
||||
if len(getResponse.List) != 1 {
|
||||
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
|
||||
}
|
||||
get := getResponse.List[0]
|
||||
|
||||
return UploadedBlob{
|
||||
Id: upload.Id,
|
||||
Size: upload.Size,
|
||||
Type: upload.Type,
|
||||
Sha512: get.DigestSha512,
|
||||
}, nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -122,11 +122,11 @@ func TestRequests(t *testing.T) {
|
||||
{
|
||||
email := emails.Emails[0]
|
||||
require.Equal("Ornare Senectus Ultrices Elit", email.Subject)
|
||||
require.Equal(false, email.HasAttachments)
|
||||
require.Equal(false, email.HasAttachment)
|
||||
}
|
||||
{
|
||||
email := emails.Emails[1]
|
||||
require.Equal("Lorem Tortor Eros Blandit Adipiscing Scelerisque Fermentum", email.Subject)
|
||||
require.Equal(false, email.HasAttachments)
|
||||
require.Equal(false, email.HasAttachment)
|
||||
}
|
||||
}
|
||||
|
||||
25
services/groupware/pkg/groupware/groupware_api_blob.go
Normal file
25
services/groupware/pkg/groupware/groupware_api_blob.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package groupware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func (g Groupware) GetBlob(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
blobId := chi.URLParam(req.r, UriParamBlobId)
|
||||
if blobId == "" {
|
||||
errorId := req.errorId()
|
||||
msg := fmt.Sprintf("Invalid value for path parameter '%v': empty", UriParamBlobId)
|
||||
return nil, "", apiError(errorId, ErrorInvalidRequestParameter,
|
||||
withDetail(msg),
|
||||
withSource(&ErrorSource{Parameter: UriParamBlobId}),
|
||||
)
|
||||
}
|
||||
|
||||
res, err := g.jmap.GetBlob(req.GetAccountId(), req.session, req.ctx, req.logger, blobId)
|
||||
return res, res.Digest(), req.apiErrorFromJmap(err)
|
||||
})
|
||||
}
|
||||
@@ -106,6 +106,7 @@ func (g Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) {
|
||||
if subscribed != "" {
|
||||
b, err := strconv.ParseBool(subscribed)
|
||||
if err != nil {
|
||||
// TODO proper response object
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -12,45 +12,70 @@ import (
|
||||
|
||||
func (g Groupware) GetAllMessages(w http.ResponseWriter, r *http.Request) {
|
||||
mailboxId := chi.URLParam(r, UriParamMailboxId)
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
if mailboxId == "" {
|
||||
errorId := req.errorId()
|
||||
msg := fmt.Sprintf("Missing required mailbox ID path parameter '%v'", UriParamMailboxId)
|
||||
return nil, "", apiError(errorId, ErrorInvalidRequestParameter,
|
||||
withDetail(msg),
|
||||
withSource(&ErrorSource{Parameter: UriParamMailboxId}),
|
||||
)
|
||||
}
|
||||
page, ok, err := req.parseNumericParam(QueryParamPage, -1)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
logger := req.logger
|
||||
if ok {
|
||||
logger = &log.Logger{Logger: logger.With().Int(QueryParamPage, page).Logger()}
|
||||
}
|
||||
since := r.Header.Get(HeaderSince)
|
||||
|
||||
size, ok, err := req.parseNumericParam(QueryParamSize, -1)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if ok {
|
||||
logger = &log.Logger{Logger: logger.With().Int(QueryParamSize, size).Logger()}
|
||||
}
|
||||
if since != "" {
|
||||
// ... then it's a completely different operation
|
||||
maxChanges := -1
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
if mailboxId == "" {
|
||||
errorId := req.errorId()
|
||||
msg := fmt.Sprintf("Missing required mailbox ID path parameter '%v'", UriParamMailboxId)
|
||||
return nil, "", apiError(errorId, ErrorInvalidRequestParameter,
|
||||
withDetail(msg),
|
||||
withSource(&ErrorSource{Parameter: UriParamMailboxId}),
|
||||
)
|
||||
}
|
||||
logger := &log.Logger{Logger: req.logger.With().Str(HeaderSince, since).Logger()}
|
||||
|
||||
offset := page * size
|
||||
limit := size
|
||||
if limit < 0 {
|
||||
limit = g.defaultEmailLimit
|
||||
}
|
||||
emails, jerr := g.jmap.GetEmailsInMailboxSince(req.GetAccountId(), req.session, req.ctx, logger, mailboxId, since, true, g.maxBodyValueBytes, maxChanges)
|
||||
if jerr != nil {
|
||||
return nil, "", req.apiErrorFromJmap(jerr)
|
||||
}
|
||||
|
||||
emails, jerr := g.jmap.GetAllEmails(req.GetAccountId(), req.session, req.ctx, logger, mailboxId, offset, limit, true, g.maxBodyValueBytes)
|
||||
if jerr != nil {
|
||||
return nil, "", req.apiErrorFromJmap(jerr)
|
||||
}
|
||||
return emails, emails.State, nil
|
||||
})
|
||||
} else {
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
if mailboxId == "" {
|
||||
errorId := req.errorId()
|
||||
msg := fmt.Sprintf("Missing required mailbox ID path parameter '%v'", UriParamMailboxId)
|
||||
return nil, "", apiError(errorId, ErrorInvalidRequestParameter,
|
||||
withDetail(msg),
|
||||
withSource(&ErrorSource{Parameter: UriParamMailboxId}),
|
||||
)
|
||||
}
|
||||
page, ok, err := req.parseNumericParam(QueryParamPage, -1)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
logger := req.logger
|
||||
if ok {
|
||||
logger = &log.Logger{Logger: logger.With().Int(QueryParamPage, page).Logger()}
|
||||
}
|
||||
|
||||
return emails, emails.State, nil
|
||||
})
|
||||
size, ok, err := req.parseNumericParam(QueryParamSize, -1)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if ok {
|
||||
logger = &log.Logger{Logger: logger.With().Int(QueryParamSize, size).Logger()}
|
||||
}
|
||||
|
||||
offset := page * size
|
||||
limit := size
|
||||
if limit < 0 {
|
||||
limit = g.defaultEmailLimit
|
||||
}
|
||||
|
||||
emails, jerr := g.jmap.GetAllEmails(req.GetAccountId(), req.session, req.ctx, logger, mailboxId, offset, limit, true, g.maxBodyValueBytes)
|
||||
if jerr != nil {
|
||||
return nil, "", req.apiErrorFromJmap(jerr)
|
||||
}
|
||||
|
||||
return emails, emails.State, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (g Groupware) GetMessagesById(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -75,3 +100,22 @@ func (g Groupware) GetMessagesById(w http.ResponseWriter, r *http.Request) {
|
||||
return emails, emails.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (g Groupware) GetMessageUpdates(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
since := q.Get(QueryParamSince)
|
||||
if since == "" {
|
||||
since = r.Header.Get("If-None-Match")
|
||||
}
|
||||
maxChanges := -1
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
logger := &log.Logger{Logger: req.logger.With().Str(HeaderSince, since).Logger()}
|
||||
|
||||
emails, jerr := g.jmap.GetEmailsSince(req.GetAccountId(), req.session, req.ctx, logger, since, true, g.maxBodyValueBytes, maxChanges)
|
||||
if jerr != nil {
|
||||
return nil, "", req.apiErrorFromJmap(jerr)
|
||||
}
|
||||
|
||||
return emails, emails.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ const (
|
||||
QueryParamPage = "page"
|
||||
QueryParamSize = "size"
|
||||
UriParamMessagesId = "id"
|
||||
UriParamBlobId = "blobid"
|
||||
QueryParamSince = "since"
|
||||
HeaderSince = "if-none-match"
|
||||
)
|
||||
|
||||
func (g Groupware) Route(r chi.Router) {
|
||||
@@ -21,8 +24,10 @@ func (g Groupware) Route(r chi.Router) {
|
||||
r.Get("/mailboxes/{mailbox}", g.GetMailbox)
|
||||
r.Get("/mailboxes/{mailbox}/messages", g.GetAllMessages)
|
||||
r.Get("/messages/{id}", g.GetMessagesById)
|
||||
r.Get("/messages", g.GetMessageUpdates)
|
||||
r.Get("/identity", g.GetIdentity)
|
||||
r.Get("/vacation", g.GetVacation)
|
||||
r.Get("/blobs/{blobid}", g.GetBlob)
|
||||
})
|
||||
r.NotFound(g.NotFound)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user