package handlers import ( "fmt" "path/filepath" "github.com/NdoleStudio/httpsms/pkg/repositories" "github.com/NdoleStudio/httpsms/pkg/telemetry" "github.com/gofiber/fiber/v3" "github.com/palantir/stacktrace" ) // AttachmentHandler handles attachment download requests type AttachmentHandler struct { handler logger telemetry.Logger tracer telemetry.Tracer storage repositories.AttachmentRepository } // NewAttachmentHandler creates a new AttachmentHandler func NewAttachmentHandler( logger telemetry.Logger, tracer telemetry.Tracer, storage repositories.AttachmentRepository, ) (h *AttachmentHandler) { return &AttachmentHandler{ logger: logger.WithService(fmt.Sprintf("%T", h)), tracer: tracer, storage: storage, } } // RegisterRoutes registers the routes for the AttachmentHandler (no auth middleware — public endpoint) func (h *AttachmentHandler) RegisterRoutes(router fiber.Router) { router.Get("/v1/attachments/:userID/:messageID/:attachmentIndex/:filename", h.GetAttachment) } // GetAttachment Downloads an attachment // @Summary Download a message attachment // @Description Download an MMS attachment by its path components // @Tags Attachments // @Produce application/octet-stream // @Param userID path string true "User ID" // @Param messageID path string true "Message ID" // @Param attachmentIndex path string true "Attachment index" // @Param filename path string true "Filename with extension" // @Success 200 {file} binary // @Failure 404 {object} responses.NotFound // @Failure 500 {object} responses.InternalServerError // @Router /v1/attachments/{userID}/{messageID}/{attachmentIndex}/{filename} [get] func (h *AttachmentHandler) GetAttachment(c fiber.Ctx) error { ctx, span := h.tracer.StartFromFiberCtx(c) defer span.End() ctxLogger := h.tracer.CtxLogger(h.logger, span) userID := c.Params("userID") messageID := c.Params("messageID") attachmentIndex := c.Params("attachmentIndex") filename := c.Params("filename") path := fmt.Sprintf("attachments/%s/%s/%s/%s", userID, messageID, attachmentIndex, filename) ctxLogger.Info(fmt.Sprintf("downloading attachment from path [%s]", path)) data, err := h.storage.Download(ctx, path) if err != nil { msg := fmt.Sprintf("cannot download attachment from path [%s]", path) ctxLogger.Warn(stacktrace.Propagate(err, msg)) if stacktrace.GetCode(err) == repositories.ErrCodeNotFound { return h.responseNotFound(c, "attachment not found") } return h.responseInternalServerError(c) } ext := filepath.Ext(filename) contentType := repositories.ContentTypeFromExtension(ext) c.Set("Content-Type", contentType) c.Set("Content-Disposition", "attachment") c.Set("X-Content-Type-Options", "nosniff") return c.Send(data) }