From 8f6488a70d2a18fce2473da5baebe3aeb3bfffc2 Mon Sep 17 00:00:00 2001 From: Sosokker Date: Fri, 14 Feb 2025 08:03:35 +0700 Subject: [PATCH] feat: add api to fetch user profile data --- backend/internal/api/api.go | 1 + backend/internal/api/user.go | 62 ++++++++++++++++++++ backend/internal/domain/user.go | 1 + backend/internal/repository/postgres_user.go | 16 +++++ backend/internal/utilities/jwt.go | 24 +++++++- 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 backend/internal/api/user.go diff --git a/backend/internal/api/api.go b/backend/internal/api/api.go index a66a918..cc64d33 100644 --- a/backend/internal/api/api.go +++ b/backend/internal/api/api.go @@ -82,6 +82,7 @@ func (a *api) Routes() *chi.Mux { api.UseMiddleware(m.AuthMiddleware(api)) a.registerHelloRoutes(r, api) a.registerFarmRoutes(r, api) + a.registerUserRoutes(r, api) }) return router diff --git a/backend/internal/api/user.go b/backend/internal/api/user.go new file mode 100644 index 0000000..f289582 --- /dev/null +++ b/backend/internal/api/user.go @@ -0,0 +1,62 @@ +package api + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/danielgtaylor/huma/v2" + "github.com/forfarm/backend/internal/domain" + "github.com/forfarm/backend/internal/utilities" + "github.com/go-chi/chi/v5" +) + +func (a *api) registerUserRoutes(_ chi.Router, api huma.API) { + tags := []string{"user"} + prefix := "/user" + + huma.Register(api, huma.Operation{ + OperationID: "getSelfData", + Method: http.MethodGet, + Path: prefix + "/me", + Tags: tags, + }, a.getSelfData) +} + +type getSelfDataInput struct { + Authorization string `header:"Authorization" required:"true" example:"Bearer token"` +} + +type getSelfDataOutput struct { + Body struct { + User domain.User `json:"user"` + } +} + +func (a *api) getSelfData(ctx context.Context, input *getSelfDataInput) (*getSelfDataOutput, error) { + resp := &getSelfDataOutput{} + + authHeader := input.Authorization + if authHeader == "" { + return nil, fmt.Errorf("no authorization header provided") + } + + authToken := strings.TrimPrefix(authHeader, "Bearer ") + if authToken == "" { + return nil, fmt.Errorf("no token provided") + } + + uuid, err := utilities.ExtractUUIDFromToken(authToken) + if err != nil { + return nil, err + } + + user, err := a.userRepo.GetByUUID(ctx, uuid) + if err != nil { + return nil, err + } + + resp.Body.User = user + return resp, nil +} diff --git a/backend/internal/domain/user.go b/backend/internal/domain/user.go index f87937a..b87fc0b 100644 --- a/backend/internal/domain/user.go +++ b/backend/internal/domain/user.go @@ -51,6 +51,7 @@ func (u *User) Validate() error { type UserRepository interface { GetByID(context.Context, int64) (User, error) + GetByUUID(context.Context, string) (User, error) GetByUsername(context.Context, string) (User, error) GetByEmail(context.Context, string) (User, error) CreateOrUpdate(context.Context, *User) error diff --git a/backend/internal/repository/postgres_user.go b/backend/internal/repository/postgres_user.go index 67a00f5..dd94576 100644 --- a/backend/internal/repository/postgres_user.go +++ b/backend/internal/repository/postgres_user.go @@ -60,6 +60,22 @@ func (p *postgresUserRepository) GetByID(ctx context.Context, id int64) (domain. return users[0], nil } +func (p *postgresUserRepository) GetByUUID(ctx context.Context, uuid string) (domain.User, error) { + query := ` + SELECT id, uuid, username, password, email, created_at, updated_at, is_active + FROM users + WHERE uuid = $1` + + users, err := p.fetch(ctx, query, uuid) + if err != nil { + return domain.User{}, err + } + if len(users) == 0 { + return domain.User{}, domain.ErrNotFound + } + return users[0], nil +} + func (p *postgresUserRepository) GetByUsername(ctx context.Context, username string) (domain.User, error) { query := ` SELECT id, uuid, username, password, email, created_at, updated_at, is_active diff --git a/backend/internal/utilities/jwt.go b/backend/internal/utilities/jwt.go index 5b406b3..f5cdc8f 100644 --- a/backend/internal/utilities/jwt.go +++ b/backend/internal/utilities/jwt.go @@ -8,7 +8,6 @@ import ( "github.com/golang-jwt/jwt/v5" ) -// TODO: Change later var deafultSecretKey = []byte(config.JWT_SECRET_KEY) func CreateJwtToken(uuid string) (string, error) { @@ -52,3 +51,26 @@ func VerifyJwtToken(tokenString string, customKey ...[]byte) error { return nil } + +// ExtractUUIDFromToken decodes the JWT token using the default secret key, +// and returns the uuid claim contained within the token. +func ExtractUUIDFromToken(tokenString string) (string, error) { + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, jwt.ErrSignatureInvalid + } + return deafultSecretKey, nil + }) + if err != nil { + return "", err + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + if uuid, ok := claims["uuid"].(string); ok { + return uuid, nil + } + return "", errors.New("uuid not found in token") + } + + return "", errors.New("invalid token claims") +}