diff --git a/application/common.repository.go b/application/common.repository.go index 1a61040..e76fa8e 100644 --- a/application/common.repository.go +++ b/application/common.repository.go @@ -4,23 +4,27 @@ import ( "bistleague-be/model/config" "bistleague-be/services/repository/auth" "bistleague-be/services/repository/hello" + "bistleague-be/services/repository/profile" "bistleague-be/services/repository/team" ) type CommonRepository struct { - helloRepo *hello.Repository - authRepo *auth.Repository - teamRepo *team.Repository + helloRepo *hello.Repository + authRepo *auth.Repository + teamRepo *team.Repository + profileRepo *profile.Repository } func NewCommonRepository(cfg *config.Config, rsc *CommonResource) (*CommonRepository, error) { helloRepo := hello.New(cfg) authRepo := auth.New(cfg, rsc.Db, rsc.QBuilder) teamRepo := team.New(cfg, rsc.Db, rsc.QBuilder) + profileRepo := profile.New(cfg, rsc.Db) commonRepo := CommonRepository{ - helloRepo: helloRepo, - authRepo: authRepo, - teamRepo: teamRepo, + helloRepo: helloRepo, + authRepo: authRepo, + teamRepo: teamRepo, + profileRepo: profileRepo, } return &commonRepo, nil } diff --git a/application/common.usecase.go b/application/common.usecase.go index b4cc032..3eb7362 100644 --- a/application/common.usecase.go +++ b/application/common.usecase.go @@ -4,23 +4,27 @@ import ( "bistleague-be/model/config" "bistleague-be/services/usecase/auth" "bistleague-be/services/usecase/hello" + "bistleague-be/services/usecase/profile" "bistleague-be/services/usecase/team" ) type CommonUsecase struct { - HelloUC *hello.Usecase - AuthUC *auth.Usecase - TeamUC *team.Usecase + HelloUC *hello.Usecase + AuthUC *auth.Usecase + TeamUC *team.Usecase + ProfileUC *profile.Usecase } func NewCommonUsecase(cfg *config.Config, commonRepo *CommonRepository) (*CommonUsecase, error) { helloUC := hello.New(cfg, commonRepo.helloRepo) authUC := auth.New(cfg, commonRepo.authRepo) teamUC := team.New(cfg, commonRepo.teamRepo) + profileUC := profile.New(cfg, commonRepo.profileRepo) commonUC := CommonUsecase{ - HelloUC: helloUC, - AuthUC: authUC, - TeamUC: teamUC, + HelloUC: helloUC, + AuthUC: authUC, + TeamUC: teamUC, + ProfileUC: profileUC, } return &commonUC, nil } diff --git a/application/rest/app.go b/application/rest/app.go index 432a8a7..8701ca1 100644 --- a/application/rest/app.go +++ b/application/rest/app.go @@ -49,7 +49,7 @@ func applicationDelegate(cfg *config.Config) (*fiber.App, error) { Message: "Hello, Hacker!", }) }) - //hello router + //hello route helloRoute := hello.New(cfg, usecase.HelloUC) helloRoute.Register(app) @@ -58,7 +58,7 @@ func applicationDelegate(cfg *config.Config) (*fiber.App, error) { authRoute.RegisterRoute(app) //profile route - profileRoute := profile.New(cfg) + profileRoute := profile.New(cfg, usecase.ProfileUC, resource.Vld) profileRoute.Register(app) //team route diff --git a/files/seed.sql b/files/seed.sql index 629a1b1..c5887fc 100644 --- a/files/seed.sql +++ b/files/seed.sql @@ -4,6 +4,8 @@ CREATE TABLE IF NOT EXISTS users ( email VARCHAR(75) unique not null, password VARCHAR(155) not null, full_name VARCHAR(155) not null, + user_age int8 default 0, + phone_number VARCHAR(155), institution VARCHAR(155), major VARCHAR(55), entry_year int2 default 0, @@ -28,5 +30,6 @@ CREATE TABLE IF NOT EXISTS teams( CREATE TABLE IF NOT EXISTS teams_code( team_id uuid PRIMARY KEY, code VARCHAR(55) UNIQUE, + team_member_mails TEXT[], used int8 default 2 ) \ No newline at end of file diff --git a/model/dto/dto.user.go b/model/dto/dto.user.go index b367fbb..046f145 100644 --- a/model/dto/dto.user.go +++ b/model/dto/dto.user.go @@ -1,11 +1,25 @@ package dto -type UserResponseDTO struct { +type UpdateUserProfileRequestDTO struct { + Email string `json:"email" validate:"required,email"` + FullName string `json:"full_name" validate:"required"` + Age uint64 `json:"age" validate:"required"` + PhoneNumber string `json:"phone_number" validate:"required"` + Institution string `json:"institution" validate:"required"` + Major string `json:"major" validate:"required"` + EntryYear uint32 `json:"entry_year" validate:"required"` + LinkedInURL string `json:"linkedin_url" validate:"required,url"` + LineID string `json:"line_id" validate:"required"` +} + +type UserProfileResponseDTO struct { UID string `json:"uid"` TeamID string `json:"team_id"` Email string `json:"email"` FullName string `json:"full_name"` + Age uint64 `json:"age"` Username string `json:"username"` + PhoneNumber string `json:"phone_number"` Institution string `json:"institution"` Major string `json:"major"` EntryYear uint32 `json:"entry_year"` diff --git a/model/entity/entity.user.go b/model/entity/entity.user.go index d2d6523..968a02d 100644 --- a/model/entity/entity.user.go +++ b/model/entity/entity.user.go @@ -12,6 +12,8 @@ type UserEntity struct { Email string `db:"email"` Username string `db:"username"` FullName string `db:"full_name"` + Age uint64 `db:"user_age"` + PhoneNumber sql.NullString `db:"phone_number"` Institution sql.NullString `db:"institution"` Major sql.NullString `db:"major"` EntryYear uint32 `db:"entry_year"` diff --git a/services/repository/profile/profile.repository.go b/services/repository/profile/profile.repository.go new file mode 100644 index 0000000..a87e9ae --- /dev/null +++ b/services/repository/profile/profile.repository.go @@ -0,0 +1,40 @@ +package profile + +import ( + "bistleague-be/model/config" + "bistleague-be/model/entity" + "context" + "github.com/jmoiron/sqlx" +) + +type Repository struct { + cfg *config.Config + db *sqlx.DB +} + +func New(cfg *config.Config, db *sqlx.DB) *Repository { + return &Repository{ + cfg: cfg, + db: db, + } +} + +func (r *Repository) GetUserProfile(ctx context.Context, userID string) (*entity.UserEntity, error) { + q := `SELECT + uid, team_id, email, full_name, user_age,username, phone_number, institution, major, entry_year, linkedin_url, line_id + FROM users WHERE uid = $1 LIMIT 1` + resp := entity.UserEntity{} + err := r.db.GetContext(ctx, &resp, q, userID) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (r *Repository) UpdateUserProfile(ctx context.Context, req entity.UserEntity) error { + q := `UPDATE users +SET email = $2, full_name = $3, user_age = $4, phone_number = $5, institution =$6, + major =$7, entry_year =$8, linkedin_url =$9, line_id = $10 WHERE uid = $1` + _, err := r.db.ExecContext(ctx, q, req.UID, req.Email, req.FullName, req.Age, req.PhoneNumber.String, req.Institution.String, req.Major.String, req.EntryYear, req.LinkedInURL.String, req.LineID.String) + return err +} diff --git a/services/router/rest/auth/auth.router.go b/services/router/rest/auth/auth.router.go index 7bbabd5..eacd44c 100644 --- a/services/router/rest/auth/auth.router.go +++ b/services/router/rest/auth/auth.router.go @@ -3,16 +3,12 @@ package auth import ( "bistleague-be/model/config" "bistleague-be/model/dto" - "bistleague-be/model/entity" "bistleague-be/services/middleware/guard" "bistleague-be/services/usecase/auth" - "bistleague-be/services/utils" "fmt" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" - "github.com/golang-jwt/jwt/v5" "net/http" - "time" ) type Router struct { @@ -31,7 +27,6 @@ func New(cfg *config.Config, usecase *auth.Usecase, vld *validator.Validate) *Ro func (r *Router) RegisterRoute(app *fiber.App) { app.Post("/login", guard.DefaultGuard(r.SignInUser)) - app.Get("/token", guard.DefaultGuard(r.CreateSign)) app.Post("/register", guard.DefaultGuard(r.SignUpUser)) } @@ -40,31 +35,6 @@ type AuthRequest struct { Password string `json:"password"` } -type TokenAuth struct { - Token string `json:"token"` - ReturnSecureToken bool `json:"returnSecureToken"` -} - -func (r *Router) CreateSign(g *guard.GuardContext) error { - claims := entity.CustomClaim{ - TeamID: "s08d8d", - UserID: "skss", - RegisteredClaims: jwt.RegisteredClaims{ - Issuer: "rest", - Subject: "", - ExpiresAt: jwt.NewNumericDate(time.Now()), - IssuedAt: jwt.NewNumericDate(time.Now()), - }, - } - token, err := utils.CreateJWTToken(r.cfg.Secret.JWTSecret, claims) - if err != nil { - return g.ReturnError(500, err.Error()) - } - return g.ReturnSuccess(map[string]interface{}{ - "token": token, - }) -} - func (r *Router) SignInUser(g *guard.GuardContext) error { req := dto.SignInUserRequestDTO{} err := g.FiberCtx.BodyParser(&req) diff --git a/services/router/rest/profile/profile.router.go b/services/router/rest/profile/profile.router.go index 1b63879..d0d4d03 100644 --- a/services/router/rest/profile/profile.router.go +++ b/services/router/rest/profile/profile.router.go @@ -2,31 +2,64 @@ package profile import ( "bistleague-be/model/config" + "bistleague-be/model/dto" "bistleague-be/services/middleware/guard" + "bistleague-be/services/usecase/profile" + "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" + "log" + "net/http" ) type Router struct { cfg *config.Config + uc *profile.Usecase + vld *validator.Validate } -func New(cfg *config.Config) *Router { +func New(cfg *config.Config, uc *profile.Usecase, vld *validator.Validate) *Router { return &Router{ cfg: cfg, + uc: uc, + vld: vld, } } func (r *Router) Register(app *fiber.App) { g := app.Group("/profile") - g.Get("", guard.AuthGuard(r.cfg, r.GetUserProfile)...) + g.Get("/:uid", guard.DefaultGuard(r.GetUserProfile)) g.Put("", guard.AuthGuard(r.cfg, r.UpdateUserProfile)...) } -func (r *Router) GetUserProfile(g *guard.AuthGuardContext) error { +func (r *Router) GetUserProfile(g *guard.GuardContext) error { uid := g.FiberCtx.Params("uid") - return g.ReturnSuccess(uid) + if uid == "" { + return g.ReturnError(http.StatusBadRequest, "user id is not provided") + } + resp, err := r.uc.GetUserProfile(g.FiberCtx.Context(), uid) + if err != nil { + return g.ReturnError(http.StatusNotFound, "cannot find user information") + } + return g.ReturnSuccess(resp) } func (r *Router) UpdateUserProfile(g *guard.AuthGuardContext) error { - return g.ReturnSuccess(nil) + req := dto.UpdateUserProfileRequestDTO{} + err := g.FiberCtx.BodyParser(&req) + if err != nil { + return g.ReturnError(http.StatusBadRequest, "cannot find json body") + } + err = r.vld.StructCtx(g.FiberCtx.Context(), &req) + if err != nil { + return g.ReturnError(http.StatusBadRequest, err.Error()) + } + err = r.uc.UpdateUserProfile(g.FiberCtx.Context(), req, g.Claims.UserID) + if err != nil { + log.Println(err) + return g.ReturnError(http.StatusBadRequest, "cannot update user profile") + } + return g.FiberCtx.JSON(dto.NoBodyDTOResponseWrapper{ + Status: http.StatusAccepted, + Message: "user profile has been updated", + }) } diff --git a/services/usecase/profile/profile.usecase.go b/services/usecase/profile/profile.usecase.go new file mode 100644 index 0000000..79675ea --- /dev/null +++ b/services/usecase/profile/profile.usecase.go @@ -0,0 +1,69 @@ +package profile + +import ( + "bistleague-be/model/config" + "bistleague-be/model/dto" + "bistleague-be/model/entity" + "bistleague-be/services/repository/profile" + "context" + "database/sql" +) + +type Usecase struct { + cfg *config.Config + repo *profile.Repository +} + +func New(cfg *config.Config, repo *profile.Repository) *Usecase { + return &Usecase{ + cfg: cfg, + repo: repo, + } +} + +func (u *Usecase) GetUserProfile(ctx context.Context, userID string) (*dto.UserProfileResponseDTO, error) { + resp, err := u.repo.GetUserProfile(ctx, userID) + if err != nil { + return nil, err + } + return &dto.UserProfileResponseDTO{ + UID: userID, + TeamID: resp.TeamID.String, + Email: resp.Email, + FullName: resp.FullName, + Username: resp.Username, + Age: resp.Age, + PhoneNumber: resp.PhoneNumber.String, + Institution: resp.Institution.String, + Major: resp.Major.String, + EntryYear: resp.EntryYear, + LinkedInURL: resp.LinkedInURL.String, + LineID: resp.LineID.String, + }, nil +} + +func (u *Usecase) UpdateUserProfile(ctx context.Context, req dto.UpdateUserProfileRequestDTO, userID string) error { + ety := entity.UserEntity{ + UID: userID, + Email: req.Email, + FullName: req.FullName, + Age: req.Age, + PhoneNumber: sql.NullString{ + String: req.PhoneNumber, + }, + Institution: sql.NullString{ + String: req.Institution, + }, + Major: sql.NullString{ + String: req.Major, + }, + EntryYear: req.EntryYear, + LinkedInURL: sql.NullString{ + String: req.LinkedInURL, + }, + LineID: sql.NullString{ + String: req.LineID, + }, + } + return u.repo.UpdateUserProfile(ctx, ety) +}