From f559f546831c970f230cba612fdbedc4941bdc23 Mon Sep 17 00:00:00 2001 From: LandaMm Date: Sat, 24 May 2025 17:17:25 +0200 Subject: [PATCH] feat: upload avatar route --- internal/user/routes.go | 84 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/internal/user/routes.go b/internal/user/routes.go index 44bca33..b46c7a4 100644 --- a/internal/user/routes.go +++ b/internal/user/routes.go @@ -3,26 +3,37 @@ package user import ( "context" "encoding/json" + "fmt" "net/http" + "path/filepath" + "strings" + "time" "gitea.local/admin/hspguard/internal/repository" + "gitea.local/admin/hspguard/internal/storage" "gitea.local/admin/hspguard/internal/util" "gitea.local/admin/hspguard/internal/web" "github.com/go-chi/chi/v5" + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" + "github.com/minio/minio-go/v7" ) type UserHandler struct { - repo *repository.Queries + repo *repository.Queries + minio *storage.FileStorage } -func NewUserHandler(repo *repository.Queries) *UserHandler { +func NewUserHandler(repo *repository.Queries, minio *storage.FileStorage) *UserHandler { return &UserHandler{ - repo: repo, + repo: repo, + minio: minio, } } func (h *UserHandler) RegisterRoutes(api chi.Router) { api.Post("/register", h.register) + api.Put("/avatar", h.uploadAvatar) } type RegisterParams struct { @@ -81,3 +92,70 @@ func (h *UserHandler) register(w http.ResponseWriter, r *http.Request) { web.Error(w, "failed to encode response", http.StatusInternalServerError) } } + +func (h *UserHandler) uploadAvatar(w http.ResponseWriter, r *http.Request) { + userId, ok := util.GetRequestUserId(r.Context()) + if !ok { + web.Error(w, "failed to get user id from auth session", http.StatusInternalServerError) + return + } + + user, err := h.repo.FindUserId(r.Context(), uuid.MustParse(userId)) + if err != nil { + web.Error(w, "user with provided id does not exist", http.StatusUnauthorized) + return + } + + err = r.ParseMultipartForm(10 << 20) + if err != nil { + web.Error(w, "invalid form data", http.StatusBadRequest) + return + } + + file, header, err := r.FormFile("image") + if err != nil { + web.Error(w, "missing image file", http.StatusBadRequest) + return + } + defer file.Close() + + ext := strings.ToLower(filepath.Ext(header.Filename)) + if ext != ".png" && ext != ".jpg" && ext != ".jpeg" && ext != ".webp" { + web.Error(w, "unsupported image format", http.StatusBadRequest) + return + } + objectName := fmt.Sprintf("profile_%s_%d%s", userId, time.Now().UnixNano(), ext) + + uploadInfo, err := h.minio.PutObject(r.Context(), "guard-storage", objectName, file, header.Size, minio.PutObjectOptions{ + ContentType: header.Header.Get("Content-Type"), + }) + if err != nil { + web.Error(w, "failed to upload image", http.StatusInternalServerError) + return + } + + imageURL := fmt.Sprintf("http://%s/%s/%s", h.minio.EndpointURL().Host, "guard-storage", uploadInfo.Key) + + if err := h.repo.UpdateProfilePicture(r.Context(), repository.UpdateProfilePictureParams{ + ProfilePicture: pgtype.Text{ + String: imageURL, + Valid: true, + }, + ID: user.ID, + }); err != nil { + web.Error(w, "failed to update profile picture", http.StatusInternalServerError) + return + } + + type Response struct { + URL string `json:"url"` + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + encoder := json.NewEncoder(w) + + if err := encoder.Encode(Response{URL: imageURL}); err != nil { + web.Error(w, "failed to write response", http.StatusInternalServerError) + } +}