Skip to content

Commit c21245b

Browse files
AchoArnoldCopilot
andcommitted
feat: add AttachmentHandler for downloading attachments
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4cea63e commit c21245b

1 file changed

Lines changed: 82 additions & 0 deletions

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package handlers
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
7+
"github.com/NdoleStudio/httpsms/pkg/repositories"
8+
"github.com/NdoleStudio/httpsms/pkg/telemetry"
9+
"github.com/gofiber/fiber/v2"
10+
"github.com/palantir/stacktrace"
11+
)
12+
13+
// AttachmentHandler handles attachment download requests
14+
type AttachmentHandler struct {
15+
handler
16+
logger telemetry.Logger
17+
tracer telemetry.Tracer
18+
storage repositories.AttachmentStorage
19+
}
20+
21+
// NewAttachmentHandler creates a new AttachmentHandler
22+
func NewAttachmentHandler(
23+
logger telemetry.Logger,
24+
tracer telemetry.Tracer,
25+
storage repositories.AttachmentStorage,
26+
) (h *AttachmentHandler) {
27+
return &AttachmentHandler{
28+
logger: logger.WithService(fmt.Sprintf("%T", h)),
29+
tracer: tracer,
30+
storage: storage,
31+
}
32+
}
33+
34+
// RegisterRoutes registers the routes for the AttachmentHandler (no auth middleware — public endpoint)
35+
func (h *AttachmentHandler) RegisterRoutes(router fiber.Router) {
36+
router.Get("/v1/attachments/:userID/:messageID/:attachmentIndex/:filename", h.GetAttachment)
37+
}
38+
39+
// GetAttachment downloads an attachment
40+
// @Summary Download a message attachment
41+
// @Description Download an MMS attachment by its path components
42+
// @Tags Attachments
43+
// @Produce octet-stream
44+
// @Param userID path string true "User ID"
45+
// @Param messageID path string true "Message ID"
46+
// @Param attachmentIndex path string true "Attachment index"
47+
// @Param filename path string true "Filename with extension"
48+
// @Success 200 {file} binary
49+
// @Failure 404 {object} responses.NotFoundResponse
50+
// @Failure 500 {object} responses.InternalServerError
51+
// @Router /attachments/{userID}/{messageID}/{attachmentIndex}/{filename} [get]
52+
func (h *AttachmentHandler) GetAttachment(c *fiber.Ctx) error {
53+
ctx, span := h.tracer.StartFromFiberCtx(c)
54+
defer span.End()
55+
56+
ctxLogger := h.tracer.CtxLogger(h.logger, span)
57+
58+
userID := c.Params("userID")
59+
messageID := c.Params("messageID")
60+
attachmentIndex := c.Params("attachmentIndex")
61+
filename := c.Params("filename")
62+
63+
path := fmt.Sprintf("attachments/%s/%s/%s/%s", userID, messageID, attachmentIndex, filename)
64+
65+
ctxLogger.Info(fmt.Sprintf("downloading attachment from path [%s]", path))
66+
67+
data, err := h.storage.Download(ctx, path)
68+
if err != nil {
69+
msg := fmt.Sprintf("cannot download attachment from path [%s]", path)
70+
ctxLogger.Warn(stacktrace.Propagate(err, msg))
71+
return h.responseNotFound(c, "attachment not found")
72+
}
73+
74+
ext := filepath.Ext(filename)
75+
contentType := repositories.ContentTypeFromExtension(ext)
76+
77+
c.Set("Content-Type", contentType)
78+
c.Set("Content-Disposition", "attachment")
79+
c.Set("X-Content-Type-Options", "nosniff")
80+
81+
return c.Send(data)
82+
}

0 commit comments

Comments
 (0)