mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-02-02 18:32:05 -05:00
refactored the Session object, refactored the services/groupware directory, and started Swagger documentation implementation
This commit is contained in:
@@ -47,9 +47,14 @@ func NewClient(wellKnown SessionClient, api ApiClient) Client {
|
||||
// instead of being part of the Session, since the username is always part of the request (typically in
|
||||
// the authentication token payload.)
|
||||
type Session struct {
|
||||
Username string // The name of the user to use to authenticate against Stalwart
|
||||
AccountId string // The identifier of the account to use when performing JMAP operations with Stalwart
|
||||
JmapUrl url.URL // The base URL to use for JMAP operations towards Stalwart
|
||||
// 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
|
||||
// TODO
|
||||
MailAccountId string
|
||||
|
||||
SessionResponse
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -75,7 +80,7 @@ const (
|
||||
// Create a new log.Logger that is decorated with fields containing information about the Session.
|
||||
func (s Session) DecorateLogger(l log.Logger) log.Logger {
|
||||
return log.Logger{
|
||||
Logger: l.With().Str(logUsername, s.Username).Str(logAccountId, s.AccountId).Logger(),
|
||||
Logger: l.With().Str(logUsername, s.Username).Str(logAccountId, s.MailAccountId).Logger(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,8 +90,8 @@ func NewSession(sessionResponse SessionResponse) (Session, Error) {
|
||||
if username == "" {
|
||||
return Session{}, SimpleError{code: JmapErrorInvalidSessionResponse, err: fmt.Errorf("JMAP session response does not provide a username")}
|
||||
}
|
||||
accountId := sessionResponse.PrimaryAccounts[JmapMail]
|
||||
if accountId == "" {
|
||||
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
|
||||
@@ -98,9 +103,10 @@ func NewSession(sessionResponse SessionResponse) (Session, Error) {
|
||||
return Session{}, SimpleError{code: JmapErrorInvalidSessionResponse, err: fmt.Errorf("JMAP session response provides an invalid API URL")}
|
||||
}
|
||||
return Session{
|
||||
Username: username,
|
||||
AccountId: accountId,
|
||||
JmapUrl: *apiUrl,
|
||||
Username: username,
|
||||
MailAccountId: mailAccountId,
|
||||
JmapUrl: *apiUrl,
|
||||
SessionResponse: sessionResponse,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -114,18 +120,18 @@ func (j *Client) FetchSession(username string, logger *log.Logger) (Session, Err
|
||||
}
|
||||
|
||||
func (j *Client) logger(operation string, session *Session, logger *log.Logger) *log.Logger {
|
||||
return &log.Logger{Logger: logger.With().Str(logOperation, operation).Str(logUsername, session.Username).Str(logAccountId, session.AccountId).Logger()}
|
||||
return &log.Logger{Logger: logger.With().Str(logOperation, operation).Str(logUsername, session.Username).Str(logAccountId, session.MailAccountId).Logger()}
|
||||
}
|
||||
|
||||
func (j *Client) loggerParams(operation string, session *Session, logger *log.Logger, params func(zerolog.Context) zerolog.Context) *log.Logger {
|
||||
base := logger.With().Str(logOperation, operation).Str(logUsername, session.Username).Str(logAccountId, session.AccountId)
|
||||
base := logger.With().Str(logOperation, operation).Str(logUsername, session.Username).Str(logAccountId, session.MailAccountId)
|
||||
return &log.Logger{Logger: params(base).Logger()}
|
||||
}
|
||||
|
||||
// https://jmap.io/spec-mail.html#identityget
|
||||
func (j *Client) GetIdentity(session *Session, ctx context.Context, logger *log.Logger) (IdentityGetResponse, Error) {
|
||||
logger = j.logger("GetIdentity", session, logger)
|
||||
cmd, err := request(invocation(IdentityGet, IdentityGetCommand{AccountId: session.AccountId}, "0"))
|
||||
cmd, err := request(invocation(IdentityGet, IdentityGetCommand{AccountId: session.MailAccountId}, "0"))
|
||||
if err != nil {
|
||||
return IdentityGetResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
}
|
||||
@@ -139,7 +145,7 @@ func (j *Client) GetIdentity(session *Session, ctx context.Context, logger *log.
|
||||
// https://jmap.io/spec-mail.html#vacationresponseget
|
||||
func (j *Client) GetVacationResponse(session *Session, ctx context.Context, logger *log.Logger) (VacationResponseGetResponse, Error) {
|
||||
logger = j.logger("GetVacationResponse", session, logger)
|
||||
cmd, err := request(invocation(VacationResponseGet, VacationResponseGetCommand{AccountId: session.AccountId}, "0"))
|
||||
cmd, err := request(invocation(VacationResponseGet, VacationResponseGetCommand{AccountId: session.MailAccountId}, "0"))
|
||||
if err != nil {
|
||||
return VacationResponseGetResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
}
|
||||
@@ -153,7 +159,7 @@ func (j *Client) GetVacationResponse(session *Session, ctx context.Context, logg
|
||||
// https://jmap.io/spec-mail.html#mailboxget
|
||||
func (j *Client) GetMailbox(session *Session, ctx context.Context, logger *log.Logger, ids []string) (MailboxGetResponse, Error) {
|
||||
logger = j.logger("GetMailbox", session, logger)
|
||||
cmd, err := request(invocation(MailboxGet, MailboxGetCommand{AccountId: session.AccountId, Ids: ids}, "0"))
|
||||
cmd, err := request(invocation(MailboxGet, MailboxGetCommand{AccountId: session.MailAccountId, Ids: ids}, "0"))
|
||||
if err != nil {
|
||||
return MailboxGetResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
}
|
||||
@@ -171,7 +177,7 @@ func (j *Client) GetAllMailboxes(session *Session, ctx context.Context, logger *
|
||||
// https://jmap.io/spec-mail.html#mailboxquery
|
||||
func (j *Client) QueryMailbox(session *Session, ctx context.Context, logger *log.Logger, filter MailboxFilterCondition) (MailboxQueryResponse, Error) {
|
||||
logger = j.logger("QueryMailbox", session, logger)
|
||||
cmd, err := request(invocation(MailboxQuery, SimpleMailboxQueryCommand{AccountId: session.AccountId, Filter: filter}, "0"))
|
||||
cmd, err := request(invocation(MailboxQuery, SimpleMailboxQueryCommand{AccountId: session.MailAccountId, Filter: filter}, "0"))
|
||||
if err != nil {
|
||||
return MailboxQueryResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
|
||||
}
|
||||
@@ -191,9 +197,9 @@ func (j *Client) SearchMailboxes(session *Session, ctx context.Context, logger *
|
||||
logger = j.logger("SearchMailboxes", session, logger)
|
||||
|
||||
cmd, err := request(
|
||||
invocation(MailboxQuery, SimpleMailboxQueryCommand{AccountId: session.AccountId, Filter: filter}, "0"),
|
||||
invocation(MailboxQuery, SimpleMailboxQueryCommand{AccountId: session.MailAccountId, Filter: filter}, "0"),
|
||||
invocation(MailboxGet, MailboxGetRefCommand{
|
||||
AccountId: session.AccountId,
|
||||
AccountId: session.MailAccountId,
|
||||
IdRef: &Ref{Name: MailboxQuery, Path: "/ids/*", ResultOf: "0"},
|
||||
}, "1"),
|
||||
)
|
||||
@@ -222,7 +228,7 @@ func (j *Client) GetEmails(session *Session, ctx context.Context, logger *log.Lo
|
||||
})
|
||||
|
||||
query := EmailQueryCommand{
|
||||
AccountId: session.AccountId,
|
||||
AccountId: session.MailAccountId,
|
||||
Filter: &MessageFilter{InMailbox: mailboxId},
|
||||
Sort: []Sort{{Property: emailSortByReceivedAt, IsAscending: false}},
|
||||
CollapseThreads: true,
|
||||
@@ -236,7 +242,7 @@ func (j *Client) GetEmails(session *Session, ctx context.Context, logger *log.Lo
|
||||
}
|
||||
|
||||
get := EmailGetRefCommand{
|
||||
AccountId: session.AccountId,
|
||||
AccountId: session.MailAccountId,
|
||||
FetchAllBodyValues: fetchBodies,
|
||||
IdRef: &Ref{Name: EmailQuery, Path: "/ids/*", ResultOf: "0"},
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ const (
|
||||
JmapSubmission = "urn:ietf:params:jmap:submission"
|
||||
JmapVacationResponse = "urn:ietf:params:jmap:vacationresponse"
|
||||
JmapCalendars = "urn:ietf:params:jmap:calendars"
|
||||
JmapSieve = "urn:ietf:params:jmap:sieve"
|
||||
JmapBlob = "urn:ietf:params:jmap:blob"
|
||||
JmapQuota = "urn:ietf:params:jmap:quota"
|
||||
JmapWebsocket = "urn:ietf:params:jmap:websocket"
|
||||
|
||||
JmapKeywordPrefix = "$"
|
||||
JmapKeywordSeen = "$seen"
|
||||
JmapKeywordDraft = "$draft"
|
||||
@@ -23,17 +28,119 @@ const (
|
||||
JmapKeywordMdnSent = "$mdnsent"
|
||||
)
|
||||
|
||||
type SessionMailAccountCapabilities struct {
|
||||
MaxMailboxesPerEmail int `json:"maxMailboxesPerEmail"`
|
||||
MaxMailboxDepth int `json:"maxMailboxDepth"`
|
||||
MaxSizeMailboxName int `json:"maxSizeMailboxName"`
|
||||
MaxSizeAttachmentsPerEmail int `json:"maxSizeAttachmentsPerEmail"`
|
||||
EmailQuerySortOptions []string `json:"emailQuerySortOptions"`
|
||||
MayCreateTopLevelMailbox bool `json:"mayCreateTopLevelMailbox"`
|
||||
}
|
||||
|
||||
type SessionSubmissionAccountCapabilities struct {
|
||||
MaxDelayedSend int `json:"maxDelayedSend"`
|
||||
SubmissionExtensions map[string][]string `json:"submissionExtensions"`
|
||||
}
|
||||
|
||||
type SessionVacationResponseAccountCapabilities struct {
|
||||
}
|
||||
|
||||
type SessionSieveAccountCapabilities struct {
|
||||
MaxSizeScriptName int `json:"maxSizeScriptName"`
|
||||
MaxSizeScript int `json:"maxSizeScript"`
|
||||
MaxNumberScripts int `json:"maxNumberScripts"`
|
||||
MaxNumberRedirects int `json:"maxNumberRedirects"`
|
||||
SieveExtensions []string `json:"sieveExtensions"`
|
||||
NotificationMethods []string `json:"notificationMethods"`
|
||||
ExternalLists any `json:"externalLists"` // ?
|
||||
}
|
||||
|
||||
type SessionBlobAccountCapabilities struct {
|
||||
MaxSizeBlobSet int `json:"maxSizeBlobSet"`
|
||||
MaxDataSources int `json:"maxDataSources"`
|
||||
SupportedTypeNames []string `json:"supportedTypeNames"`
|
||||
SupportedDigestAlgorithms []string `json:"supportedDigestAlgorithms"`
|
||||
}
|
||||
|
||||
type SessionQuotaAccountCapabilities struct {
|
||||
}
|
||||
|
||||
type SessionAccountCapabilities struct {
|
||||
Mail SessionMailAccountCapabilities `json:"urn:ietf:params:jmap:mail"`
|
||||
Submission SessionSubmissionAccountCapabilities `json:"urn:ietf:params:jmap:submission"`
|
||||
VacationResponse SessionVacationResponseAccountCapabilities `json:"urn:ietf:params:jmap:vacationresponse"`
|
||||
Sieve SessionSieveAccountCapabilities `json:"urn:ietf:params:jmap:sieve"`
|
||||
Blob SessionBlobAccountCapabilities `json:"urn:ietf:params:jmap:blob"`
|
||||
Quota SessionQuotaAccountCapabilities `json:"urn:ietf:params:jmap:quota"`
|
||||
}
|
||||
|
||||
type SessionAccount struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
IsPersonal bool `json:"isPersonal"`
|
||||
IsReadOnly bool `json:"isReadOnly"`
|
||||
AccountCapabilities map[string]any `json:"accountCapabilities,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
IsPersonal bool `json:"isPersonal"`
|
||||
IsReadOnly bool `json:"isReadOnly"`
|
||||
AccountCapabilities SessionAccountCapabilities `json:"accountCapabilities,omitempty"`
|
||||
}
|
||||
|
||||
type SessionCoreCapabilities struct {
|
||||
MaxSizeUpload int `json:"maxSizeUpload"`
|
||||
MaxConcurrentUpload int `json:"maxConcurrentUpload"`
|
||||
MaxSizeRequest int `json:"maxSizeRequest"`
|
||||
MaxConcurrentRequests int `json:"maxConcurrentRequests"`
|
||||
MaxCallsInRequest int `json:"maxCallsInRequest"`
|
||||
MaxObjectsInGet int `json:"maxObjectsInGet"`
|
||||
MaxObjectsInSet int `json:"maxObjectsInSet"`
|
||||
CollationAlgorithms []string `json:"collationAlgorithms"`
|
||||
}
|
||||
|
||||
type SessionMailCapabilities struct {
|
||||
}
|
||||
|
||||
type SessionSubmissionCapabilities struct {
|
||||
}
|
||||
|
||||
type SessionVacationResponseCapabilities struct {
|
||||
}
|
||||
|
||||
type SessionSieveCapabilities struct {
|
||||
}
|
||||
|
||||
type SessionBlobCapabilities struct {
|
||||
}
|
||||
|
||||
type SessionQuotaCapabilities struct {
|
||||
}
|
||||
|
||||
type SessionWebsocketCapabilities struct {
|
||||
Url string `json:"url"`
|
||||
SupportsPush bool `json:"supportsPush"`
|
||||
}
|
||||
|
||||
type SessionCapabilities struct {
|
||||
Core SessionCoreCapabilities `json:"urn:ietf:params:jmap:core"`
|
||||
Mail SessionMailCapabilities `json:"urn:ietf:params:jmap:mail"`
|
||||
Submission SessionSubmissionCapabilities `json:"urn:ietf:params:jmap:submission"`
|
||||
VacationResponse SessionVacationResponseCapabilities `json:"urn:ietf:params:jmap:vacationresponse"`
|
||||
Sieve SessionSieveCapabilities `json:"urn:ietf:params:jmap:sieve"`
|
||||
Blob SessionBlobCapabilities `json:"urn:ietf:params:jmap:blob"`
|
||||
Quota SessionQuotaCapabilities `json:"urn:ietf:params:jmap:quota"`
|
||||
Websocket SessionWebsocketCapabilities `json:"urn:ietf:params:jmap:websocket"`
|
||||
}
|
||||
|
||||
type SessionPrimaryAccounts struct {
|
||||
Core string `json:"urn:ietf:params:jmap:core"`
|
||||
Mail string `json:"urn:ietf:params:jmap:mail"`
|
||||
Submission string `json:"urn:ietf:params:jmap:submission"`
|
||||
VacationResponse string `json:"urn:ietf:params:jmap:vacationresponse"`
|
||||
Sieve string `json:"urn:ietf:params:jmap:sieve"`
|
||||
Blob string `json:"urn:ietf:params:jmap:blob"`
|
||||
Quota string `json:"urn:ietf:params:jmap:quota"`
|
||||
Websocket string `json:"urn:ietf:params:jmap:websocket"`
|
||||
}
|
||||
|
||||
type SessionResponse struct {
|
||||
Capabilities map[string]any `json:"capabilities,omitempty"`
|
||||
Capabilities SessionCapabilities `json:"capabilities,omitempty"`
|
||||
Accounts map[string]SessionAccount `json:"accounts,omitempty"`
|
||||
PrimaryAccounts map[string]string `json:"primaryAccounts,omitempty"`
|
||||
PrimaryAccounts SessionPrimaryAccounts `json:"primaryAccounts,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
ApiUrl string `json:"apiUrl,omitempty"`
|
||||
DownloadUrl string `json:"downloadUrl,omitempty"`
|
||||
@@ -326,21 +433,45 @@ type VacationResponseGetCommand struct {
|
||||
AccountId string `json:"accountId"`
|
||||
}
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc8621#section-8
|
||||
type VacationResponse struct {
|
||||
Id string `json:"id"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
FromDate time.Time `json:"fromDate,omitzero"`
|
||||
ToDate time.Time `json:"toDate,omitzero"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
TextBody string `json:"textBody,omitempty"`
|
||||
HtmlBody string `json:"htmlBody,omitempty"`
|
||||
// The id of the object.
|
||||
// There is only ever one VacationResponse object, and its id is "singleton"
|
||||
Id string `json:"id"`
|
||||
// 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 VacationResponseGetResponse struct {
|
||||
AccountId string `json:"accountId"`
|
||||
State string `json:"state,omitempty"`
|
||||
List []VacationResponse `json:"list,omitempty"`
|
||||
NotFound []any `json:"notFound,omitempty"`
|
||||
// The identifier of the account this response pertains to.
|
||||
AccountId string `json:"accountId"`
|
||||
// A 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 data is unchanged, servers SHOULD return the same state string
|
||||
// on subsequent requests for this data type.
|
||||
State string `json:"state,omitempty"`
|
||||
// An array of VacationResponse objects.
|
||||
List []VacationResponse `json:"list,omitempty"`
|
||||
// Contains identifiers of requested objects that were not found.
|
||||
NotFound []any `json:"notFound,omitempty"`
|
||||
}
|
||||
|
||||
var CommandResponseTypeMap = map[Command]func() any{
|
||||
|
||||
@@ -28,10 +28,20 @@ func (t *TestJmapWellKnownClient) Close() error {
|
||||
}
|
||||
|
||||
func (t *TestJmapWellKnownClient) GetSession(username string, logger *log.Logger) (SessionResponse, Error) {
|
||||
pa := generateRandomString(2 + seededRand.Intn(10))
|
||||
return SessionResponse{
|
||||
Username: generateRandomString(8),
|
||||
ApiUrl: "test://",
|
||||
PrimaryAccounts: map[string]string{JmapMail: generateRandomString(2 + seededRand.Intn(10))},
|
||||
Username: generateRandomString(8),
|
||||
ApiUrl: "test://",
|
||||
PrimaryAccounts: SessionPrimaryAccounts{
|
||||
Core: pa,
|
||||
Mail: pa,
|
||||
Submission: pa,
|
||||
VacationResponse: pa,
|
||||
Sieve: pa,
|
||||
Blob: pa,
|
||||
Quota: pa,
|
||||
Websocket: pa,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -99,7 +109,7 @@ func TestRequests(t *testing.T) {
|
||||
jmapUrl, err := url.Parse("http://localhost/jmap")
|
||||
require.NoError(err)
|
||||
|
||||
session := Session{AccountId: "123", Username: "user123", JmapUrl: *jmapUrl}
|
||||
session := Session{MailAccountId: "123", Username: "user123", JmapUrl: *jmapUrl}
|
||||
|
||||
folders, err := client.GetAllMailboxes(&session, ctx, &logger)
|
||||
require.NoError(err)
|
||||
|
||||
@@ -32,6 +32,10 @@ func command[T any](api ApiClient,
|
||||
return zero, SimpleError{code: JmapErrorDecodingResponseBody, err: err}
|
||||
}
|
||||
|
||||
if data.SessionState != session.State {
|
||||
// TODO(pbleser-oc) handle session renewal
|
||||
}
|
||||
|
||||
return mapper(&data)
|
||||
}
|
||||
|
||||
|
||||
1
services/groupware/.gitignore
vendored
Normal file
1
services/groupware/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/swagger.yml
|
||||
@@ -9,3 +9,16 @@ include ../../.make/default.mk
|
||||
include ../../.make/go.mk
|
||||
include ../../.make/release.mk
|
||||
include ../../.make/docs.mk
|
||||
|
||||
.PHONY: apidoc
|
||||
apidoc: swagger.yml
|
||||
|
||||
.PHONY: swagger.yml
|
||||
swagger.yml:
|
||||
swagger generate spec -c groupware -o ./swagger.yml
|
||||
|
||||
APIDOC_PORT=9999
|
||||
|
||||
.PHONY: serve-apidoc
|
||||
serve-apidoc: swagger.yml
|
||||
swagger serve --no-open --port=$(APIDOC_PORT) --host=127.0.0.1 --flavor=redoc $<
|
||||
|
||||
43
services/groupware/pkg/groupware/groupware_api.go
Normal file
43
services/groupware/pkg/groupware/groupware_api.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package groupware
|
||||
|
||||
const (
|
||||
Version = "1.0.0"
|
||||
)
|
||||
|
||||
const (
|
||||
CapMail_1 = "mail:1"
|
||||
)
|
||||
|
||||
var Capabilities = []string{
|
||||
CapMail_1,
|
||||
}
|
||||
|
||||
// When the request contains invalid parameters.
|
||||
// swagger:response ErrorResponse400
|
||||
type SwaggerErrorResponse400 struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
*ErrorResponse
|
||||
}
|
||||
}
|
||||
|
||||
// When the requested object does not exist.
|
||||
// swagger:response ErrorResponse404
|
||||
type SwaggerErrorResponse404 struct {
|
||||
}
|
||||
|
||||
// When the server was unable to complete the request.
|
||||
// swagger:response ErrorResponse500
|
||||
type SwaggerErrorResponse500 struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
*ErrorResponse
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters vacation mailboxes
|
||||
type SwaggerAccountParams struct {
|
||||
// The identifier of the account.
|
||||
// in: path
|
||||
Account string `json:"account"`
|
||||
}
|
||||
15
services/groupware/pkg/groupware/groupware_api_account.go
Normal file
15
services/groupware/pkg/groupware/groupware_api_account.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package groupware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (g Groupware) GetAccount(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
account, ok := req.GetAccount()
|
||||
if !ok {
|
||||
return nil, "", nil
|
||||
}
|
||||
return account, req.session.State, nil
|
||||
})
|
||||
}
|
||||
12
services/groupware/pkg/groupware/groupware_api_identity.go
Normal file
12
services/groupware/pkg/groupware/groupware_api_identity.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package groupware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (g Groupware) GetIdentity(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
res, err := g.jmap.GetIdentity(req.session, req.ctx, req.logger)
|
||||
return res, res.State, apiErrorFromJmap(err)
|
||||
})
|
||||
}
|
||||
111
services/groupware/pkg/groupware/groupware_api_index.go
Normal file
111
services/groupware/pkg/groupware/groupware_api_index.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package groupware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type IndexLimits struct {
|
||||
MaxSizeUpload int `json:"maxSizeUpload"`
|
||||
MaxConcurrentUpload int `json:"maxConcurrentUpload"`
|
||||
MaxSizeRequest int `json:"maxSizeRequest"`
|
||||
MaxConcurrentRequests int `json:"maxConcurrentRequests"`
|
||||
}
|
||||
|
||||
type IndexAccountMailCapabilities struct {
|
||||
MaxMailboxDepth int `json:"maxMailboxDepth"`
|
||||
MaxSizeMailboxName int `json:"maxSizeMailboxName"`
|
||||
MaxSizeAttachmentsPerEmail int `json:"maxSizeAttachmentsPerEmail"`
|
||||
MayCreateTopLevelMailbox bool `json:"mayCreateTopLevelMailbox"`
|
||||
MaxDelayedSend int `json:"maxDelayedSend"`
|
||||
}
|
||||
|
||||
type IndexAccountSieveCapabilities struct {
|
||||
MaxSizeScriptName int `json:"maxSizeScriptName"`
|
||||
MaxSizeScript int `json:"maxSizeScript"`
|
||||
MaxNumberScripts int `json:"maxNumberScripts"`
|
||||
MaxNumberRedirects int `json:"maxNumberRedirects"`
|
||||
}
|
||||
|
||||
type IndexAccountCapabilities struct {
|
||||
Mail IndexAccountMailCapabilities `json:"mail"`
|
||||
Sieve IndexAccountSieveCapabilities `json:"sieve"`
|
||||
}
|
||||
|
||||
type IndexAccount struct {
|
||||
Name string `json:"name"`
|
||||
IsPersonal bool `json:"isPersonal"`
|
||||
IsReadOnly bool `json:"isReadOnly"`
|
||||
Capabilities IndexAccountCapabilities `json:"capabilities"`
|
||||
}
|
||||
|
||||
type IndexPrimaryAccounts struct {
|
||||
Mail string `json:"mail"`
|
||||
Submission string `json:"submission"`
|
||||
}
|
||||
|
||||
type IndexResponse struct {
|
||||
Version string `json:"version"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
Limits IndexLimits `json:"limits"`
|
||||
Accounts map[string]IndexAccount `json:"accounts"`
|
||||
PrimaryAccounts IndexPrimaryAccounts `json:"primaryAccounts"`
|
||||
}
|
||||
|
||||
// When the request suceeds.
|
||||
// swagger:response IndexResponse
|
||||
type SwaggerIndexResponse struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
*IndexResponse
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:route GET / index
|
||||
// Get initial bootup information
|
||||
//
|
||||
// responses:
|
||||
//
|
||||
// 200: IndexResponse
|
||||
func (g Groupware) Index(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
accounts := make(map[string]IndexAccount, len(req.session.Accounts))
|
||||
for i, a := range req.session.Accounts {
|
||||
accounts[i] = IndexAccount{
|
||||
Name: a.Name,
|
||||
IsPersonal: a.IsPersonal,
|
||||
IsReadOnly: a.IsReadOnly,
|
||||
Capabilities: IndexAccountCapabilities{
|
||||
Mail: IndexAccountMailCapabilities{
|
||||
MaxMailboxDepth: a.AccountCapabilities.Mail.MaxMailboxDepth,
|
||||
MaxSizeMailboxName: a.AccountCapabilities.Mail.MaxSizeMailboxName,
|
||||
MaxSizeAttachmentsPerEmail: a.AccountCapabilities.Mail.MaxSizeAttachmentsPerEmail,
|
||||
MayCreateTopLevelMailbox: a.AccountCapabilities.Mail.MayCreateTopLevelMailbox,
|
||||
MaxDelayedSend: a.AccountCapabilities.Submission.MaxDelayedSend,
|
||||
},
|
||||
Sieve: IndexAccountSieveCapabilities{
|
||||
MaxSizeScriptName: a.AccountCapabilities.Sieve.MaxSizeScript,
|
||||
MaxSizeScript: a.AccountCapabilities.Sieve.MaxSizeScript,
|
||||
MaxNumberScripts: a.AccountCapabilities.Sieve.MaxNumberScripts,
|
||||
MaxNumberRedirects: a.AccountCapabilities.Sieve.MaxNumberRedirects,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return IndexResponse{
|
||||
Version: Version,
|
||||
Capabilities: Capabilities,
|
||||
Limits: IndexLimits{
|
||||
MaxSizeUpload: req.session.Capabilities.Core.MaxSizeUpload,
|
||||
MaxConcurrentUpload: req.session.Capabilities.Core.MaxConcurrentUpload,
|
||||
MaxSizeRequest: req.session.Capabilities.Core.MaxSizeRequest,
|
||||
MaxConcurrentRequests: req.session.Capabilities.Core.MaxConcurrentRequests,
|
||||
},
|
||||
Accounts: accounts,
|
||||
PrimaryAccounts: IndexPrimaryAccounts{
|
||||
Mail: req.session.PrimaryAccounts.Mail,
|
||||
Submission: req.session.PrimaryAccounts.Submission,
|
||||
},
|
||||
}, req.session.State, nil
|
||||
})
|
||||
}
|
||||
@@ -10,43 +10,28 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
func (g Groupware) Route(r chi.Router) {
|
||||
r.Get("/", g.Index)
|
||||
r.Get("/mailboxes", g.GetMailboxes) // ?name=&role=&subcribed=
|
||||
r.Get("/mailbox/{id}", g.GetMailboxById)
|
||||
r.Get("/{mailbox}/messages", g.GetMessages)
|
||||
r.Get("/identity", g.GetIdentity)
|
||||
r.Get("/vacation", g.GetVacation)
|
||||
}
|
||||
|
||||
type IndexResponse struct {
|
||||
AccountId string
|
||||
}
|
||||
|
||||
func (IndexResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g Groupware) Index(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) (any, string, *ApiError) {
|
||||
return IndexResponse{AccountId: req.session.AccountId}, "", nil
|
||||
})
|
||||
}
|
||||
|
||||
func (g Groupware) GetIdentity(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) (any, string, *ApiError) {
|
||||
res, err := g.jmap.GetIdentity(req.session, req.ctx, req.logger)
|
||||
return res, res.State, apiErrorFromJmap(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (g Groupware) GetVacation(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) (any, string, *ApiError) {
|
||||
res, err := g.jmap.GetVacationResponse(req.session, req.ctx, req.logger)
|
||||
return res, res.State, apiErrorFromJmap(err)
|
||||
})
|
||||
// When the request succeeds.
|
||||
// swagger:response MailboxResponse200
|
||||
type SwaggerGetMailboxById200 struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
*jmap.Mailbox
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:route GET /accounts/{account}/mailboxes/{id} mailboxes_by_id
|
||||
// Get a specific mailbox by its identifier.
|
||||
//
|
||||
// A Mailbox represents a named set of Emails.
|
||||
// This is the primary mechanism for organising Emails within an account.
|
||||
// It is analogous to a folder or a label in other systems.
|
||||
//
|
||||
// responses:
|
||||
//
|
||||
// 200: MailboxResponse200
|
||||
// 400: ErrorResponse400
|
||||
// 404: ErrorResponse404
|
||||
// 500: ErrorResponse500
|
||||
func (g Groupware) GetMailboxById(w http.ResponseWriter, r *http.Request) {
|
||||
mailboxId := chi.URLParam(r, "mailbox")
|
||||
if mailboxId == "" {
|
||||
@@ -54,7 +39,7 @@ func (g Groupware) GetMailboxById(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
g.respond(w, r, func(req Request) (any, string, *ApiError) {
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
res, err := g.jmap.GetMailbox(req.session, req.ctx, req.logger, []string{mailboxId})
|
||||
if err != nil {
|
||||
return res, "", apiErrorFromJmap(err)
|
||||
@@ -68,6 +53,41 @@ func (g Groupware) GetMailboxById(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters mailboxes
|
||||
type SwaggerMailboxesParams struct {
|
||||
// The name of the mailbox, with substring matching.
|
||||
// in: query
|
||||
Name string `json:"name,omitempty"`
|
||||
// The role of the mailbox.
|
||||
// in: query
|
||||
Role string `json:"role,omitempty"`
|
||||
// Whether the mailbox is subscribed by the user or not.
|
||||
// When omitted, the subscribed and unsubscribed mailboxes are returned.
|
||||
// in: query
|
||||
Subscribed bool `json:"subscribed,omitempty"`
|
||||
}
|
||||
|
||||
// When the request succeeds.
|
||||
// swagger:response MailboxesResponse200
|
||||
type SwaggerMailboxesResponse200 struct {
|
||||
// in: body
|
||||
Body []jmap.Mailbox
|
||||
}
|
||||
|
||||
// swagger:route GET /accounts/{account}/mailboxes mailboxes
|
||||
// Get the list of all the mailboxes of an account.
|
||||
//
|
||||
// A Mailbox represents a named set of Emails.
|
||||
// This is the primary mechanism for organising Emails within an account.
|
||||
// It is analogous to a folder or a label in other systems.
|
||||
//
|
||||
// When none of the query parameters are specified, all the mailboxes are returned.
|
||||
//
|
||||
// responses:
|
||||
//
|
||||
// 200: MailboxesResponse200
|
||||
// 400: ErrorResponse400
|
||||
// 500: ErrorResponse500
|
||||
func (g Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
var filter jmap.MailboxFilterCondition
|
||||
@@ -94,7 +114,7 @@ func (g Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) {
|
||||
hasCriteria = true
|
||||
}
|
||||
|
||||
g.respond(w, r, func(req Request) (any, string, *ApiError) {
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
if hasCriteria {
|
||||
mailboxes, err := g.jmap.SearchMailboxes(req.session, req.ctx, req.logger, filter)
|
||||
if err != nil {
|
||||
@@ -113,7 +133,7 @@ func (g Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (g Groupware) GetMessages(w http.ResponseWriter, r *http.Request) {
|
||||
mailboxId := chi.URLParam(r, "mailbox")
|
||||
g.respond(w, r, func(req Request) (any, string, *ApiError) {
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
page, ok, _ := ParseNumericParam(r, "page", -1)
|
||||
logger := req.logger
|
||||
if ok {
|
||||
36
services/groupware/pkg/groupware/groupware_api_vacation.go
Normal file
36
services/groupware/pkg/groupware/groupware_api_vacation.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package groupware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
)
|
||||
|
||||
// When the request succeeds.
|
||||
// swagger:response VacationResponse200
|
||||
type SwaggerVacationResponse200 struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
*jmap.VacationResponseGetResponse
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:route GET /accounts/{account}/vacation vacation
|
||||
// Get vacation notice information.
|
||||
//
|
||||
// A vacation response sends an automatic reply when a message is delivered to the mail store, informing the original
|
||||
// sender that their message may not be read for some time.
|
||||
//
|
||||
// The VacationResponse object represents the state of vacation-response-related settings for an account.
|
||||
//
|
||||
// responses:
|
||||
//
|
||||
// 200: VacationResponse200
|
||||
// 400: ErrorResponse400
|
||||
// 500: ErrorResponse500
|
||||
func (g Groupware) GetVacation(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) (any, string, *Error) {
|
||||
res, err := g.jmap.GetVacationResponse(req.session, req.ctx, req.logger)
|
||||
return res, res.State, apiErrorFromJmap(err)
|
||||
})
|
||||
}
|
||||
26
services/groupware/pkg/groupware/groupware_docs.go
Normal file
26
services/groupware/pkg/groupware/groupware_docs.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// OpenCloud Groupware API
|
||||
//
|
||||
// Documentation for the OpenCloud Groupware API
|
||||
//
|
||||
// Schemes: https
|
||||
// BasePath: /groupware
|
||||
// Version: 1.0.0
|
||||
// Host:
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Security:
|
||||
// - bearer
|
||||
//
|
||||
// SecurityDefinitions:
|
||||
// bearer:
|
||||
// type: http
|
||||
// scheme: bearer
|
||||
// bearerFormat: JWT
|
||||
//
|
||||
// swagger:meta
|
||||
package groupware
|
||||
@@ -10,38 +10,71 @@ import (
|
||||
)
|
||||
|
||||
type Link struct {
|
||||
Href string `json:"href"`
|
||||
Rel string `json:"rel,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Meta map[string]any `json:"meta,omitempty"`
|
||||
// A string whose value is a URI-reference [RFC3986 Section 4.1] pointing to the link’s target.
|
||||
Href string `json:"href"`
|
||||
// A string indicating the link’s relation type. The string MUST be a valid link relation type.
|
||||
// required: false
|
||||
Rel string `json:"rel,omitempty"`
|
||||
// A string which serves as a label for the destination of a link such that it can be used as a human-readable identifier (e.g., a menu entry).
|
||||
// required: false
|
||||
Title string `json:"title,omitempty"`
|
||||
// A string indicating the media type of the link’s target.
|
||||
// required: false
|
||||
Type string `json:"type,omitempty"`
|
||||
// A meta object containing non-standard meta-information about the link.
|
||||
// required: false
|
||||
Meta map[string]any `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
type ErrorLinks struct {
|
||||
// A link that leads to further details about this particular occurrence of the problem.
|
||||
// When dereferenced, this URI SHOULD return a human-readable description of the error.
|
||||
// This is either a string containing an URL, or a Link object.
|
||||
About any `json:"about,omitempty"`
|
||||
Type any `json:"type"` // either a string containing an URL, or a Link object
|
||||
// A link that identifies the type of error that this particular error is an instance of.
|
||||
// This URI SHOULD be dereferenceable to a human-readable explanation of the general error.
|
||||
// This is either a string containing an URL, or a Link object.
|
||||
Type any `json:"type"`
|
||||
}
|
||||
|
||||
type ErrorSource struct {
|
||||
Pointer string `json:"pointer,omitempty"` // a JSON Pointer [RFC6901] to the value in the request document that caused the error
|
||||
Parameter string `json:"parameter,omitempty"` // a string indicating which URI query parameter caused the error
|
||||
Header string `json:"header,omitempty"` // a string indicating the name of a single request header which caused the error
|
||||
// A JSON Pointer [RFC6901] to the value in the request document that caused the error
|
||||
// (e.g. "/data" for a primary data object, or "/data/attributes/title" for a specific attribute).
|
||||
// This MUST point to a value in the request document that exists; if it doesn’t, the client SHOULD simply ignore the pointer.
|
||||
Pointer string `json:"pointer,omitempty"`
|
||||
// A string indicating which URI query parameter caused the error.
|
||||
Parameter string `json:"parameter,omitempty"`
|
||||
// A string indicating the name of a single request header which caused the error.
|
||||
Header string `json:"header,omitempty"`
|
||||
}
|
||||
|
||||
type ApiError struct {
|
||||
Id string `json:"id"` // a unique identifier for this particular occurrence of the problem
|
||||
Links *ErrorLinks `json:"links,omitempty"`
|
||||
NumStatus int `json:"-"`
|
||||
Status string `json:"status"` // the HTTP status code applicable to this problem, expressed as a string value
|
||||
Code string `json:"code"` // an application-specific error code, expressed as a string value
|
||||
Title string `json:"title,omitempty"` // a short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem
|
||||
Detail string `json:"detail,omitempty"` // a human-readable explanation specific to this occurrence of the problem
|
||||
Source *ErrorSource `json:"source,omitempty"` // an object containing references to the primary source of the error
|
||||
Meta map[string]any `json:"meta,omitempty"` // a meta object containing non-standard meta-information about the error
|
||||
// [Error](https://jsonapi.org/format/#error-objects)
|
||||
type Error struct {
|
||||
// A unique identifier for this particular occurrence of the problem
|
||||
Id string `json:"id"`
|
||||
// Further detail links about the error.
|
||||
// required: false
|
||||
Links *ErrorLinks `json:"links,omitempty"`
|
||||
// swagger:ignore
|
||||
NumStatus int `json:"-"`
|
||||
// The HTTP status code applicable to this problem, expressed as a string value.
|
||||
Status string `json:"status"`
|
||||
// An application-specific error code, expressed as a string value.
|
||||
Code string `json:"code"`
|
||||
// A short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem.
|
||||
Title string `json:"title,omitempty"`
|
||||
// A human-readable explanation specific to this occurrence of the problem.
|
||||
Detail string `json:"detail,omitempty"`
|
||||
// An object containing references to the primary source of the error.
|
||||
Source *ErrorSource `json:"source,omitempty"`
|
||||
// A meta object containing non-standard meta-information about the error.
|
||||
Meta map[string]any `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
// swagger:response ErrorResponse
|
||||
type ErrorResponse struct {
|
||||
Errors []ApiError `json:"errors"`
|
||||
// List of error objects
|
||||
Errors []Error `json:"errors"`
|
||||
}
|
||||
|
||||
var _ render.Renderer = ErrorResponse{}
|
||||
@@ -197,14 +230,14 @@ var (
|
||||
)
|
||||
|
||||
type ErrorOpt interface {
|
||||
apply(error *ApiError)
|
||||
apply(error *Error)
|
||||
}
|
||||
|
||||
type ErrorLinksOpt struct {
|
||||
links *ErrorLinks
|
||||
}
|
||||
|
||||
func (o ErrorLinksOpt) apply(error *ApiError) {
|
||||
func (o ErrorLinksOpt) apply(error *Error) {
|
||||
error.Links = o.links
|
||||
}
|
||||
|
||||
@@ -212,7 +245,7 @@ type SourceLinksOpt struct {
|
||||
source *ErrorSource
|
||||
}
|
||||
|
||||
func (o SourceLinksOpt) apply(error *ApiError) {
|
||||
func (o SourceLinksOpt) apply(error *Error) {
|
||||
error.Source = o.source
|
||||
}
|
||||
|
||||
@@ -220,7 +253,7 @@ type MetaLinksOpt struct {
|
||||
meta map[string]any
|
||||
}
|
||||
|
||||
func (o MetaLinksOpt) apply(error *ApiError) {
|
||||
func (o MetaLinksOpt) apply(error *Error) {
|
||||
error.Meta = o.meta
|
||||
}
|
||||
|
||||
@@ -228,7 +261,7 @@ type CodeOpt struct {
|
||||
code string
|
||||
}
|
||||
|
||||
func (o CodeOpt) apply(error *ApiError) {
|
||||
func (o CodeOpt) apply(error *Error) {
|
||||
error.Code = o.code
|
||||
}
|
||||
|
||||
@@ -237,13 +270,13 @@ type TitleOpt struct {
|
||||
detail string
|
||||
}
|
||||
|
||||
func (o TitleOpt) apply(error *ApiError) {
|
||||
func (o TitleOpt) apply(error *Error) {
|
||||
error.Title = o.title
|
||||
error.Detail = o.detail
|
||||
}
|
||||
|
||||
func errorResponse(id string, error GroupwareError, options ...ErrorOpt) ErrorResponse {
|
||||
err := ApiError{
|
||||
err := Error{
|
||||
Id: id,
|
||||
NumStatus: error.Status,
|
||||
Status: strconv.Itoa(error.Status),
|
||||
@@ -257,12 +290,12 @@ func errorResponse(id string, error GroupwareError, options ...ErrorOpt) ErrorRe
|
||||
}
|
||||
|
||||
return ErrorResponse{
|
||||
Errors: []ApiError{err},
|
||||
Errors: []Error{err},
|
||||
}
|
||||
}
|
||||
|
||||
func apiError(id string, error GroupwareError, options ...ErrorOpt) ApiError {
|
||||
err := ApiError{
|
||||
func apiError(id string, error GroupwareError, options ...ErrorOpt) Error {
|
||||
err := Error{
|
||||
Id: id,
|
||||
NumStatus: error.Status,
|
||||
Status: strconv.Itoa(error.Status),
|
||||
@@ -278,7 +311,7 @@ func apiError(id string, error GroupwareError, options ...ErrorOpt) ApiError {
|
||||
return err
|
||||
}
|
||||
|
||||
func apiErrorFromJmap(error jmap.Error) *ApiError {
|
||||
func apiErrorFromJmap(error jmap.Error) *Error {
|
||||
if error == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -290,6 +323,6 @@ func apiErrorFromJmap(error jmap.Error) *ApiError {
|
||||
return &api
|
||||
}
|
||||
|
||||
func errorResponses(errors ...ApiError) ErrorResponse {
|
||||
func errorResponses(errors ...Error) ErrorResponse {
|
||||
return ErrorResponse{Errors: errors}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,21 @@ type Request struct {
|
||||
session *jmap.Session
|
||||
}
|
||||
|
||||
func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func(r Request) (any, string, *ApiError)) {
|
||||
func (r Request) GetAccountId() string {
|
||||
return chi.URLParam(r.r, "account")
|
||||
}
|
||||
|
||||
func (r Request) GetAccount() (jmap.SessionAccount, bool) {
|
||||
accountId := r.GetAccountId()
|
||||
account, ok := r.session.Accounts[accountId]
|
||||
if !ok {
|
||||
r.logger.Debug().Msgf("failed to find account '%v'", accountId)
|
||||
return jmap.SessionAccount{}, false
|
||||
}
|
||||
return account, true
|
||||
}
|
||||
|
||||
func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func(r Request) (any, string, *Error)) {
|
||||
ctx := r.Context()
|
||||
logger := g.logger.SubloggerWithRequestID(ctx)
|
||||
session, ok, err := g.session(r, ctx, &logger)
|
||||
@@ -187,6 +201,7 @@ func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func(
|
||||
logger.Warn().Interface("error", apierr).Msgf("API error: %v", apierr)
|
||||
w.Header().Add("Content-Type", ContentTypeJsonApi)
|
||||
render.Status(r, apierr.NumStatus)
|
||||
w.WriteHeader(apierr.NumStatus)
|
||||
render.Render(w, r, errorResponses(*apierr))
|
||||
return
|
||||
}
|
||||
@@ -195,7 +210,9 @@ func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func(
|
||||
w.Header().Add("ETag", state)
|
||||
}
|
||||
if response == nil {
|
||||
logger.Debug().Msgf("respond: response is nil, 404")
|
||||
render.Status(r, http.StatusNotFound)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
} else {
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, response)
|
||||
17
services/groupware/pkg/groupware/groupware_route.go
Normal file
17
services/groupware/pkg/groupware/groupware_route.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package groupware
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func (g Groupware) Route(r chi.Router) {
|
||||
r.Get("/", g.Index)
|
||||
r.Route("/accounts/{account}", func(r chi.Router) {
|
||||
r.Get("/", g.GetAccount)
|
||||
r.Get("/mailboxes", g.GetMailboxes) // ?name=&role=&subcribed=
|
||||
r.Get("/mailboxes/{id}", g.GetMailboxById)
|
||||
r.Get("/{mailbox}/messages", g.GetMessages)
|
||||
r.Get("/identity", g.GetIdentity)
|
||||
r.Get("/vacation", g.GetVacation)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user