mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-02-06 12:21:21 -05:00
groupware: improved attachment APIs
* feat(groupware): add /accounts/{}/emails/{}/attachments
* feat(groupware): add
/accounts/{}/emails/{}/attachments?partId=&name=&blobId=
This commit is contained in:
@@ -15,7 +15,7 @@ const (
|
||||
DefaultBlobDownloadType = "application/octet-stream"
|
||||
)
|
||||
|
||||
func (g *Groupware) GetBlob(w http.ResponseWriter, r *http.Request) {
|
||||
func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
blobId := chi.URLParam(req.r, UriParamBlobId)
|
||||
if blobId == "" {
|
||||
@@ -28,15 +28,20 @@ func (g *Groupware) GetBlob(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
logger := log.From(req.logger.With().Str(logAccountId, accountId))
|
||||
|
||||
res, _, jerr := g.jmap.GetBlob(accountId, req.session, req.ctx, logger, blobId)
|
||||
res, sessionState, jerr := g.jmap.GetBlobMetadata(accountId, req.session, req.ctx, logger, blobId)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
blob := res.Blob
|
||||
if blob == nil {
|
||||
return notFoundResponse("")
|
||||
return notFoundResponse(sessionState)
|
||||
}
|
||||
digest := blob.Digest()
|
||||
if digest != "" {
|
||||
return etagResponse(res, sessionState, jmap.State(digest))
|
||||
} else {
|
||||
return response(res, sessionState)
|
||||
}
|
||||
return etagOnlyResponse(res, jmap.State(blob.Digest()))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,14 @@ package groupware
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
@@ -159,6 +162,164 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
type attachmentPicker interface {
|
||||
pick(parts []jmap.EmailBodyPart) *jmap.EmailBodyPart
|
||||
}
|
||||
|
||||
type partIdAttachmentPicker struct {
|
||||
partId string
|
||||
}
|
||||
|
||||
var _ attachmentPicker = partIdAttachmentPicker{}
|
||||
|
||||
func (p partIdAttachmentPicker) pick(parts []jmap.EmailBodyPart) *jmap.EmailBodyPart {
|
||||
for _, part := range parts {
|
||||
if part.PartId == p.partId {
|
||||
return &part
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type nameAttachmentPicker struct {
|
||||
name string
|
||||
}
|
||||
|
||||
var _ attachmentPicker = nameAttachmentPicker{}
|
||||
|
||||
func (p nameAttachmentPicker) pick(parts []jmap.EmailBodyPart) *jmap.EmailBodyPart {
|
||||
for _, part := range parts {
|
||||
if part.Name == p.name {
|
||||
return &part
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type blobIdAttachmentPicker struct {
|
||||
blobId string
|
||||
}
|
||||
|
||||
var _ attachmentPicker = blobIdAttachmentPicker{}
|
||||
|
||||
func (p blobIdAttachmentPicker) pick(parts []jmap.EmailBodyPart) *jmap.EmailBodyPart {
|
||||
for _, part := range parts {
|
||||
if part.BlobId == p.blobId {
|
||||
return &part
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, UriParamEmailId)
|
||||
|
||||
contextAppender := func(l zerolog.Context) zerolog.Context { return l }
|
||||
q := r.URL.Query()
|
||||
var picker attachmentPicker = nil
|
||||
if q.Has(QueryParamPartId) {
|
||||
str := q.Get(QueryParamPartId)
|
||||
picker = partIdAttachmentPicker{partId: str}
|
||||
contextAppender = func(l zerolog.Context) zerolog.Context { return l.Str(QueryParamPartId, log.SafeString(str)) }
|
||||
}
|
||||
if q.Has(QueryParamAttachmentName) {
|
||||
str := q.Get(QueryParamAttachmentName)
|
||||
picker = nameAttachmentPicker{name: str}
|
||||
contextAppender = func(l zerolog.Context) zerolog.Context { return l.Str(QueryParamAttachmentName, log.SafeString(str)) }
|
||||
}
|
||||
if q.Has(QueryParamAttachmentBlobId) {
|
||||
str := q.Get(QueryParamAttachmentBlobId)
|
||||
picker = blobIdAttachmentPicker{blobId: str}
|
||||
contextAppender = func(l zerolog.Context) zerolog.Context { return l.Str(QueryParamAttachmentBlobId, log.SafeString(str)) }
|
||||
}
|
||||
|
||||
if picker == nil {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
l := req.logger.With().Str(logAccountId, accountId)
|
||||
logger := log.From(l)
|
||||
emails, sessionState, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, []string{id}, false, 0)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
if len(emails.Emails) < 1 {
|
||||
return notFoundResponse(sessionState)
|
||||
}
|
||||
email := emails.Emails[0]
|
||||
return etagResponse(email.Attachments, sessionState, emails.State)
|
||||
})
|
||||
} else {
|
||||
g.stream(w, r, func(req Request, w http.ResponseWriter) *Error {
|
||||
mailAccountId, gwerr := req.GetAccountIdForMail()
|
||||
if gwerr != nil {
|
||||
return gwerr
|
||||
}
|
||||
blobAccountId, gwerr := req.GetAccountIdForBlob()
|
||||
if gwerr != nil {
|
||||
return gwerr
|
||||
}
|
||||
|
||||
l := req.logger.With().Str(logAccountId, mailAccountId).Str(logBlobAccountId, log.SafeString(blobAccountId))
|
||||
l = contextAppender(l)
|
||||
logger := log.From(l)
|
||||
|
||||
emails, _, jerr := g.jmap.GetEmails(mailAccountId, req.session, req.ctx, logger, []string{id}, false, 0)
|
||||
if jerr != nil {
|
||||
return req.apiErrorFromJmap(req.observeJmapError(jerr))
|
||||
}
|
||||
if len(emails.Emails) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
email := emails.Emails[0]
|
||||
attachment := picker.pick(email.Attachments)
|
||||
if attachment == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
blob, jerr := g.jmap.DownloadBlobStream(blobAccountId, attachment.BlobId, attachment.Name, attachment.Type, req.session, req.ctx, logger)
|
||||
if blob != nil && blob.Body != nil {
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}(blob.Body)
|
||||
}
|
||||
if jerr != nil {
|
||||
return req.apiErrorFromJmap(jerr)
|
||||
}
|
||||
if blob == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return nil
|
||||
}
|
||||
|
||||
if blob.Type != "" {
|
||||
w.Header().Add("Content-Type", blob.Type)
|
||||
}
|
||||
if blob.CacheControl != "" {
|
||||
w.Header().Add("Cache-Control", blob.CacheControl)
|
||||
}
|
||||
if blob.ContentDisposition != "" {
|
||||
w.Header().Add("Content-Disposition", blob.ContentDisposition)
|
||||
}
|
||||
if blob.Size >= 0 {
|
||||
w.Header().Add("Content-Size", strconv.Itoa(blob.Size))
|
||||
}
|
||||
|
||||
_, err := io.Copy(w, blob.Body)
|
||||
if err != nil {
|
||||
return req.observedParameterError(ErrorStreamingResponse)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since string) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With().Str(QueryParamSince, since)
|
||||
|
||||
@@ -32,6 +32,7 @@ const (
|
||||
logUserId = "user-id"
|
||||
logSessionState = "session-state"
|
||||
logAccountId = "account-id"
|
||||
logBlobAccountId = "blob-account-id" // if the blob accountId is needed as well
|
||||
logErrorId = "error-id"
|
||||
logErrorCode = "code"
|
||||
logErrorStatus = "status"
|
||||
@@ -624,6 +625,8 @@ func (g *Groupware) stream(w http.ResponseWriter, r *http.Request, handler func(
|
||||
decoratedLogger := decorateLogger(logger, session)
|
||||
|
||||
req := Request{
|
||||
g: g,
|
||||
user: user,
|
||||
r: r,
|
||||
ctx: ctx,
|
||||
logger: decoratedLogger,
|
||||
|
||||
@@ -41,6 +41,9 @@ const (
|
||||
QueryParamOffset = "offset"
|
||||
QueryParamLimit = "limit"
|
||||
QueryParamDays = "days"
|
||||
QueryParamPartId = "partId"
|
||||
QueryParamAttachmentName = "name"
|
||||
QueryParamAttachmentBlobId = "blobId"
|
||||
HeaderSince = "if-none-match"
|
||||
)
|
||||
|
||||
@@ -75,9 +78,10 @@ func (g *Groupware) Route(r chi.Router) {
|
||||
r.Patch("/{emailid}", g.UpdateEmail)
|
||||
r.Delete("/{emailid}", g.DeleteEmail)
|
||||
Report(r, "/{emailid}", g.RelatedToEmail)
|
||||
r.Get("/{emailid}/attachments", g.GetEmailAttachments) // ?partId=&name=?&blobId=?
|
||||
})
|
||||
r.Route("/blobs", func(r chi.Router) {
|
||||
r.Get("/{blobid}", g.GetBlob)
|
||||
r.Get("/{blobid}", g.GetBlobMeta)
|
||||
r.Get("/{blobid}/{blobname}", g.DownloadBlob) // ?type=
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user