diff --git a/cmd/hspguard/api/api.go b/cmd/hspguard/api/api.go index a86aa02..ad47482 100644 --- a/cmd/hspguard/api/api.go +++ b/cmd/hspguard/api/api.go @@ -46,7 +46,14 @@ func (s *APIServer) Run() error { router.Route("/api/v1", func(r chi.Router) { am := imiddleware.New(s.cfg) - r.Use(imiddleware.WithSkipper(am.Runner, "/api/v1/auth/login", "/api/v1/register", "/api/v1/auth/refresh", "/api/v1/oauth/token")) + r.Use(imiddleware.WithSkipper( + am.Runner, + "/api/v1/auth/login", + "/api/v1/register", + "/api/v1/auth/refresh", + "/api/v1/oauth/token", + "/api/v1/avatar", + )) userHandler := user.NewUserHandler(s.repo, s.storage) userHandler.RegisterRoutes(r) diff --git a/internal/storage/mod.go b/internal/storage/mod.go index ac50219..377e794 100644 --- a/internal/storage/mod.go +++ b/internal/storage/mod.go @@ -34,6 +34,10 @@ func (fs *FileStorage) PutObject(ctx context.Context, bucketName string, objectN return fs.client.PutObject(ctx, bucketName, objectName, reader, size, opts) } +func (fs *FileStorage) GetObject(ctx context.Context, bucketName string, objectName string, opts minio.GetObjectOptions) (*minio.Object, error) { + return fs.client.GetObject(ctx, bucketName, objectName, opts) +} + func (fs *FileStorage) EndpointURL() *url.URL { return fs.client.EndpointURL() } diff --git a/internal/user/routes.go b/internal/user/routes.go index b46c7a4..72b3044 100644 --- a/internal/user/routes.go +++ b/internal/user/routes.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "fmt" + "io" + "log" "net/http" "path/filepath" "strings" @@ -34,6 +36,7 @@ func NewUserHandler(repo *repository.Queries, minio *storage.FileStorage) *UserH func (h *UserHandler) RegisterRoutes(api chi.Router) { api.Post("/register", h.register) api.Put("/avatar", h.uploadAvatar) + api.Get("/avatar/{avatar}", h.getAvatar) } type RegisterParams struct { @@ -93,6 +96,28 @@ func (h *UserHandler) register(w http.ResponseWriter, r *http.Request) { } } +func (h *UserHandler) getAvatar(w http.ResponseWriter, r *http.Request) { + avatarObject := chi.URLParam(r, "avatar") + + object, err := h.minio.GetObject(r.Context(), "guard-storage", avatarObject, minio.GetObjectOptions{}) + if err != nil { + web.Error(w, "avatar not found", http.StatusNotFound) + return + } + defer object.Close() + + stat, err := object.Stat() + if err != nil { + log.Printf("ERR: failed to get object stats: %v\n", err) + web.Error(w, "failed to get avatar", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", stat.ContentType) + w.WriteHeader(http.StatusOK) + io.Copy(w, object) +} + func (h *UserHandler) uploadAvatar(w http.ResponseWriter, r *http.Request) { userId, ok := util.GetRequestUserId(r.Context()) if !ok { @@ -134,11 +159,9 @@ func (h *UserHandler) uploadAvatar(w http.ResponseWriter, r *http.Request) { 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, + String: uploadInfo.Key, Valid: true, }, ID: user.ID, @@ -148,14 +171,14 @@ func (h *UserHandler) uploadAvatar(w http.ResponseWriter, r *http.Request) { } type Response struct { - URL string `json:"url"` + AvatarID 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 { + if err := encoder.Encode(Response{AvatarID: uploadInfo.Key}); err != nil { web.Error(w, "failed to write response", http.StatusInternalServerError) } } diff --git a/web/src/components/Home/Tabs/Home.tsx b/web/src/components/Home/Tabs/Home.tsx index 2ca56fc..437abad 100644 --- a/web/src/components/Home/Tabs/Home.tsx +++ b/web/src/components/Home/Tabs/Home.tsx @@ -1,6 +1,6 @@ import { Button } from "@/components/ui/button"; +import Avatar from "@/feature/Avatar"; import { useAuth } from "@/store/auth"; -import { User } from "lucide-react"; import { type FC } from "react"; const Home: FC = () => { @@ -10,15 +10,7 @@ const Home: FC = () => { return (