diff --git a/backend/internal/controllers/upload.go b/backend/internal/controllers/upload.go new file mode 100644 index 0000000..feb5caf --- /dev/null +++ b/backend/internal/controllers/upload.go @@ -0,0 +1,108 @@ +// Copyright (c) 2025 Nikolai Papin +// +// This file is part of Easywish +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +// the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package controllers + +import ( + "easywish/internal/middleware" + "easywish/internal/models" + "easywish/internal/services" + "easywish/internal/utils/enums" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" + "golang.org/x/time/rate" +) + +type UploadController struct { + log *zap.Logger + us services.UploadService +} + +func NewUploadController(_log *zap.Logger, _us services.UploadService) Controller { + ctrl := UploadController{log: _log, us: _us} + + return &controllerImpl{ + Path: "/upload", + Middleware: []gin.HandlerFunc{ + middleware.RateLimitMiddleware(rate.Every(2*time.Second), 1), + }, + Methods: []ControllerMethod{ + { + HttpMethod: GET, + Path: "/avatar", + Authorization: enums.UserRole, + Middleware: []gin.HandlerFunc{}, + Function: ctrl.getAvatarUploadUrl, + }, + { + HttpMethod: GET, + Path: "/image", + Authorization: enums.UserRole, + Middleware: []gin.HandlerFunc{}, + Function: ctrl.getImageUploadUrl, + }, + }, + } +} + +// XXX: untested +// @Summary Get presigned URL for avatar upload +// @Tags Upload +// @Accept json +// @Produce json +// @Security JWT +// @Success 200 {object} models.PresignedUploadResponse "Presigned URL and form data" +// @Failure 500 "Internal server error" +// @Router /upload/avatar [get] +func (ctrl *UploadController) getAvatarUploadUrl(c *gin.Context) { + url, formData, err := ctrl.us.GetAvatarUrl() + if err != nil { + ctrl.log.Error("Failed to generate avatar upload URL", zap.Error(err)) + c.Status(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, models.PresignedUploadResponse{ + Url: *url, + Fields: *formData, + }) +} + +// XXX: untested +// @Summary Get presigned URL for image upload +// @Tags Upload +// @Accept json +// @Produce json +// @Security JWT +// @Success 200 {object} models.PresignedUploadResponse "Presigned URL and form data" +// @Failure 500 "Internal server error" +// @Router /upload/image [get] +func (ctrl *UploadController) getImageUploadUrl(c *gin.Context) { + url, formData, err := ctrl.us.GetImageUrl() + if err != nil { + c.Status(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, models.PresignedUploadResponse{ + Url: *url, + Fields: *formData, + }) +} diff --git a/backend/internal/middleware/ratelimit.go b/backend/internal/middleware/ratelimit.go new file mode 100644 index 0000000..1e32def --- /dev/null +++ b/backend/internal/middleware/ratelimit.go @@ -0,0 +1,37 @@ +// Copyright (c) 2025 Nikolai Papin +// +// This file is part of Easywish +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +// the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package middleware + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "golang.org/x/time/rate" +) + +func RateLimitMiddleware(r rate.Limit, b int) gin.HandlerFunc { + limiter := rate.NewLimiter(r, b) + + return func(c *gin.Context) { + if !limiter.Allow() { + c.AbortWithStatus(http.StatusTooManyRequests) + return + } + c.Next() + } +} diff --git a/backend/internal/models/upload.go b/backend/internal/models/upload.go new file mode 100644 index 0000000..606f46d --- /dev/null +++ b/backend/internal/models/upload.go @@ -0,0 +1,24 @@ +// Copyright (c) 2025 Nikolai Papin +// +// This file is part of Easywish +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +// the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package models + +type PresignedUploadResponse struct { + Url string `json:"url"` + Fields map[string]string `json:"fields"` +} +