From 16768ad9243a7bf763952979dfd1b3f0dd078ba5 Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Fri, 23 May 2025 12:00:04 +0300 Subject: [PATCH] fix: Update integration issues --- db/query/branch.sql | 9 - db/query/cashier.sql | 15 ++ db/query/user.sql | 8 +- gen/db/branch.sql.go | 87 --------- gen/db/cashier.sql.go | 173 ++++++++++++++++++ gen/db/user.sql.go | 24 +-- internal/domain/user.go | 17 ++ internal/repository/user.go | 40 +++- internal/services/bet/service.go | 3 + internal/services/user/direct.go | 6 +- internal/services/user/port.go | 3 +- internal/services/user/service.go | 1 - internal/web_server/handlers/admin.go | 152 ++++++++++++++- .../web_server/handlers/branch_handler.go | 3 +- internal/web_server/handlers/cashier.go | 118 ++++++++++-- .../web_server/handlers/company_handler.go | 2 +- internal/web_server/handlers/manager.go | 110 ++++++++++- internal/web_server/handlers/user.go | 6 +- internal/web_server/routes.go | 4 + 19 files changed, 619 insertions(+), 162 deletions(-) create mode 100644 db/query/cashier.sql create mode 100644 gen/db/cashier.sql.go diff --git a/db/query/branch.sql b/db/query/branch.sql index 422a612..bb01b26 100644 --- a/db/query/branch.sql +++ b/db/query/branch.sql @@ -55,15 +55,6 @@ SELECT branches.* FROM branch_cashiers JOIN branches ON branch_cashiers.branch_id = branches.id WHERE branch_cashiers.user_id = $1; --- name: GetCashiersByBranch :many -SELECT users.* -FROM branch_cashiers - JOIN users ON branch_cashiers.user_id = users.id -WHERE branch_cashiers.branch_id = $1; --- name: GetAllCashiers :many -SELECT users.* -FROM branch_cashiers - JOIN users ON branch_cashiers.user_id = users.id; -- name: UpdateBranch :one UPDATE branches SET name = COALESCE(sqlc.narg(name), name), diff --git a/db/query/cashier.sql b/db/query/cashier.sql new file mode 100644 index 0000000..dcb8dfb --- /dev/null +++ b/db/query/cashier.sql @@ -0,0 +1,15 @@ +-- name: GetCashiersByBranch :many +SELECT users.* +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = users.id +WHERE branch_cashiers.branch_id = $1; +-- name: GetAllCashiers :many +SELECT users.*, + branch_id +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = users.id; +-- name: GetCashierByID :one +SELECT users.*, + branch_id +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = $1; \ No newline at end of file diff --git a/db/query/user.sql b/db/query/user.sql index 84cfe4a..3341656 100644 --- a/db/query/user.sql +++ b/db/query/user.sql @@ -100,11 +100,9 @@ WHERE first_name ILIKE '%' || $1 || '%' UPDATE users SET first_name = $1, last_name = $2, - email = $3, - phone_number = $4, - role = $5, - updated_at = $6 -WHERE id = $7; + suspended = $3, + updated_at = CURRENT_TIMESTAMP +WHERE id = $4; -- name: UpdateUserCompany :exec UPDATE users SET company_id = $1 diff --git a/gen/db/branch.sql.go b/gen/db/branch.sql.go index cf16465..93e9b2b 100644 --- a/gen/db/branch.sql.go +++ b/gen/db/branch.sql.go @@ -190,49 +190,6 @@ func (q *Queries) GetAllBranches(ctx context.Context) ([]BranchDetail, error) { return items, nil } -const GetAllCashiers = `-- name: GetAllCashiers :many -SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by -FROM branch_cashiers - JOIN users ON branch_cashiers.user_id = users.id -` - -func (q *Queries) GetAllCashiers(ctx context.Context) ([]User, error) { - rows, err := q.db.Query(ctx, GetAllCashiers) - if err != nil { - return nil, err - } - defer rows.Close() - var items []User - for rows.Next() { - var i User - if err := rows.Scan( - &i.ID, - &i.FirstName, - &i.LastName, - &i.Email, - &i.PhoneNumber, - &i.Role, - &i.Password, - &i.EmailVerified, - &i.PhoneVerified, - &i.CreatedAt, - &i.UpdatedAt, - &i.CompanyID, - &i.SuspendedAt, - &i.Suspended, - &i.ReferralCode, - &i.ReferredBy, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const GetAllSupportedOperations = `-- name: GetAllSupportedOperations :many SELECT id, name, description FROM supported_operations @@ -430,50 +387,6 @@ func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]Ge return items, nil } -const GetCashiersByBranch = `-- name: GetCashiersByBranch :many -SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by -FROM branch_cashiers - JOIN users ON branch_cashiers.user_id = users.id -WHERE branch_cashiers.branch_id = $1 -` - -func (q *Queries) GetCashiersByBranch(ctx context.Context, branchID int64) ([]User, error) { - rows, err := q.db.Query(ctx, GetCashiersByBranch, branchID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []User - for rows.Next() { - var i User - if err := rows.Scan( - &i.ID, - &i.FirstName, - &i.LastName, - &i.Email, - &i.PhoneNumber, - &i.Role, - &i.Password, - &i.EmailVerified, - &i.PhoneVerified, - &i.CreatedAt, - &i.UpdatedAt, - &i.CompanyID, - &i.SuspendedAt, - &i.Suspended, - &i.ReferralCode, - &i.ReferredBy, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const SearchBranchByName = `-- name: SearchBranchByName :many SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number FROM branch_details diff --git a/gen/db/cashier.sql.go b/gen/db/cashier.sql.go new file mode 100644 index 0000000..d0f6768 --- /dev/null +++ b/gen/db/cashier.sql.go @@ -0,0 +1,173 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: cashier.sql + +package dbgen + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const GetAllCashiers = `-- name: GetAllCashiers :many +SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by, + branch_id +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = users.id +` + +type GetAllCashiersRow struct { + ID int64 `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email pgtype.Text `json:"email"` + PhoneNumber pgtype.Text `json:"phone_number"` + Role string `json:"role"` + Password []byte `json:"password"` + EmailVerified bool `json:"email_verified"` + PhoneVerified bool `json:"phone_verified"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + CompanyID pgtype.Int8 `json:"company_id"` + SuspendedAt pgtype.Timestamptz `json:"suspended_at"` + Suspended bool `json:"suspended"` + ReferralCode pgtype.Text `json:"referral_code"` + ReferredBy pgtype.Text `json:"referred_by"` + BranchID int64 `json:"branch_id"` +} + +func (q *Queries) GetAllCashiers(ctx context.Context) ([]GetAllCashiersRow, error) { + rows, err := q.db.Query(ctx, GetAllCashiers) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetAllCashiersRow + for rows.Next() { + var i GetAllCashiersRow + if err := rows.Scan( + &i.ID, + &i.FirstName, + &i.LastName, + &i.Email, + &i.PhoneNumber, + &i.Role, + &i.Password, + &i.EmailVerified, + &i.PhoneVerified, + &i.CreatedAt, + &i.UpdatedAt, + &i.CompanyID, + &i.SuspendedAt, + &i.Suspended, + &i.ReferralCode, + &i.ReferredBy, + &i.BranchID, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetCashierByID = `-- name: GetCashierByID :one +SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by, + branch_id +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = $1 +` + +type GetCashierByIDRow struct { + ID int64 `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email pgtype.Text `json:"email"` + PhoneNumber pgtype.Text `json:"phone_number"` + Role string `json:"role"` + Password []byte `json:"password"` + EmailVerified bool `json:"email_verified"` + PhoneVerified bool `json:"phone_verified"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + CompanyID pgtype.Int8 `json:"company_id"` + SuspendedAt pgtype.Timestamptz `json:"suspended_at"` + Suspended bool `json:"suspended"` + ReferralCode pgtype.Text `json:"referral_code"` + ReferredBy pgtype.Text `json:"referred_by"` + BranchID int64 `json:"branch_id"` +} + +func (q *Queries) GetCashierByID(ctx context.Context, userID int64) (GetCashierByIDRow, error) { + row := q.db.QueryRow(ctx, GetCashierByID, userID) + var i GetCashierByIDRow + err := row.Scan( + &i.ID, + &i.FirstName, + &i.LastName, + &i.Email, + &i.PhoneNumber, + &i.Role, + &i.Password, + &i.EmailVerified, + &i.PhoneVerified, + &i.CreatedAt, + &i.UpdatedAt, + &i.CompanyID, + &i.SuspendedAt, + &i.Suspended, + &i.ReferralCode, + &i.ReferredBy, + &i.BranchID, + ) + return i, err +} + +const GetCashiersByBranch = `-- name: GetCashiersByBranch :many +SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = users.id +WHERE branch_cashiers.branch_id = $1 +` + +func (q *Queries) GetCashiersByBranch(ctx context.Context, branchID int64) ([]User, error) { + rows, err := q.db.Query(ctx, GetCashiersByBranch, branchID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []User + for rows.Next() { + var i User + if err := rows.Scan( + &i.ID, + &i.FirstName, + &i.LastName, + &i.Email, + &i.PhoneNumber, + &i.Role, + &i.Password, + &i.EmailVerified, + &i.PhoneVerified, + &i.CreatedAt, + &i.UpdatedAt, + &i.CompanyID, + &i.SuspendedAt, + &i.Suspended, + &i.ReferralCode, + &i.ReferredBy, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/gen/db/user.sql.go b/gen/db/user.sql.go index f66aff0..dd2f985 100644 --- a/gen/db/user.sql.go +++ b/gen/db/user.sql.go @@ -532,31 +532,23 @@ const UpdateUser = `-- name: UpdateUser :exec UPDATE users SET first_name = $1, last_name = $2, - email = $3, - phone_number = $4, - role = $5, - updated_at = $6 -WHERE id = $7 + suspended = $3, + updated_at = CURRENT_TIMESTAMP +WHERE id = $4 ` type UpdateUserParams struct { - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Email pgtype.Text `json:"email"` - PhoneNumber pgtype.Text `json:"phone_number"` - Role string `json:"role"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` - ID int64 `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Suspended bool `json:"suspended"` + ID int64 `json:"id"` } func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error { _, err := q.db.Exec(ctx, UpdateUser, arg.FirstName, arg.LastName, - arg.Email, - arg.PhoneNumber, - arg.Role, - arg.UpdatedAt, + arg.Suspended, arg.ID, ) return err diff --git a/internal/domain/user.go b/internal/domain/user.go index 1cd27d6..bdafe6c 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -62,9 +62,26 @@ type UpdateUserReq struct { FirstName ValidString LastName ValidString Suspended ValidBool + CompanyID ValidInt64 } type UpdateUserReferalCode struct { UserID int64 Code string } + +type GetCashier struct { + ID int64 `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email string `json:"email"` + PhoneNumber string `json:"phone_number"` + Role Role `json:"role"` + EmailVerified bool `json:"email_verified"` + PhoneVerified bool `json:"phone_verified"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + SuspendedAt time.Time `json:"suspended_at"` + Suspended bool `json:"suspended"` + BranchID int64 `json:"branch_id"` +} diff --git a/internal/repository/user.go b/internal/repository/user.go index 88a320b..df82a40 100644 --- a/internal/repository/user.go +++ b/internal/repository/user.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "fmt" "time" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" @@ -129,14 +130,14 @@ func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.U return userList, totalCount, nil } -func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.User, error) { +func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.GetCashier, error) { users, err := s.queries.GetAllCashiers(ctx) if err != nil { return nil, err } - userList := make([]domain.User, len(users)) + userList := make([]domain.GetCashier, len(users)) for i, user := range users { - userList[i] = domain.User{ + userList[i] = domain.GetCashier{ ID: user.ID, FirstName: user.FirstName, LastName: user.LastName, @@ -154,6 +155,28 @@ func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.User, error) { return userList, nil } +func (s *Store) GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error) { + user, err := s.queries.GetCashierByID(ctx, cashierID) + if err != nil { + return domain.GetCashier{}, err + } + return domain.GetCashier{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email.String, + PhoneNumber: user.PhoneNumber.String, + Role: domain.Role(user.Role), + EmailVerified: user.EmailVerified, + PhoneVerified: user.PhoneVerified, + CreatedAt: user.CreatedAt.Time, + UpdatedAt: user.UpdatedAt.Time, + SuspendedAt: user.SuspendedAt.Time, + Suspended: user.Suspended, + BranchID: user.BranchID, + }, nil +} + func (s *Store) GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error) { users, err := s.queries.GetCashiersByBranch(ctx, branchID) if err != nil { @@ -210,13 +233,12 @@ func (s *Store) SearchUserByNameOrPhone(ctx context.Context, searchString string func (s *Store) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error { err := s.queries.UpdateUser(ctx, dbgen.UpdateUserParams{ - // ID: user.ID, - // FirstName: user.FirstName, - // LastName: user.LastName, - // Email: user.Email, - // PhoneNumber: user.PhoneNumber, - + ID: user.UserId, + FirstName: user.FirstName.Value, + LastName: user.LastName.Value, + Suspended: user.Suspended.Value, }) + fmt.Printf("Updating User %v with values %v", user.UserId, user) if err != nil { return err } diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index a644021..6e5b2d5 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -219,6 +219,9 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID } newBet.IsShopBet = true case domain.RoleCustomer: + // Get User Wallet + + return domain.CreateBetRes{}, fmt.Errorf("Not yet implemented") default: return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type") diff --git a/internal/services/user/direct.go b/internal/services/user/direct.go index 04b8a65..c61cd01 100644 --- a/internal/services/user/direct.go +++ b/internal/services/user/direct.go @@ -71,6 +71,10 @@ func (s *Service) GetCashiersByBranch(ctx context.Context, branchID int64) ([]do return s.userStore.GetCashiersByBranch(ctx, branchID) } -func (s *Service) GetAllCashiers(ctx context.Context) ([]domain.User, error) { +func (s *Service) GetAllCashiers(ctx context.Context) ([]domain.GetCashier, error) { return s.userStore.GetAllCashiers(ctx) } + +func (s *Service) GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error) { + return s.userStore.GetCashierByID(ctx, cashierID) +} diff --git a/internal/services/user/port.go b/internal/services/user/port.go index c7d1bfb..f6adec0 100644 --- a/internal/services/user/port.go +++ b/internal/services/user/port.go @@ -11,7 +11,8 @@ type UserStore interface { CreateUserWithoutOtp(ctx context.Context, user domain.User, is_company bool) (domain.User, error) GetUserByID(ctx context.Context, id int64) (domain.User, error) GetAllUsers(ctx context.Context, filter Filter) ([]domain.User, int64, error) - GetAllCashiers(ctx context.Context) ([]domain.User, error) + GetAllCashiers(ctx context.Context) ([]domain.GetCashier, error) + GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error) GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error UpdateUserCompany(ctx context.Context, id int64, companyID int64) error diff --git a/internal/services/user/service.go b/internal/services/user/service.go index 17a7820..cfa93fd 100644 --- a/internal/services/user/service.go +++ b/internal/services/user/service.go @@ -13,7 +13,6 @@ type Service struct { otpStore OtpStore smsGateway SmsGateway emailGateway EmailGateway - } func NewService( diff --git a/internal/web_server/handlers/admin.go b/internal/web_server/handlers/admin.go index 3b5bbb6..795a61f 100644 --- a/internal/web_server/handlers/admin.go +++ b/internal/web_server/handlers/admin.go @@ -2,6 +2,7 @@ package handlers import ( "log/slog" + "strconv" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" @@ -129,7 +130,7 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error { Role: string(domain.RoleAdmin), CompanyID: domain.ValidInt64{ Value: int64(c.QueryInt("company_id")), - Valid: true, + Valid: false, }, Page: domain.ValidInt{ Value: c.QueryInt("page", 1) - 1, @@ -179,5 +180,154 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error { } return response.WritePaginatedJSON(c, fiber.StatusOK, "Admins retrieved successfully", result, nil, filter.Page.Value, int(total)) +} + +// GetAdminByID godoc +// @Summary Get admin by id +// @Description Get a single admin by id +// @Tags admin +// @Accept json +// @Produce json +// @Param id path int true "User ID" +// @Success 200 {object} AdminRes +// @Failure 400 {object} response.APIResponse +// @Failure 401 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /admin/{id} [get] +func (h *Handler) GetAdminByID(c *fiber.Ctx) error { + // branchId := int64(12) //c.Locals("branch_id").(int64) + // filter := user.Filter{ + // Role: string(domain.RoleUser), + // BranchId: user.ValidBranchId{ + // Value: branchId, + // Valid: true, + // }, + // Page: c.QueryInt("page", 1), + // PageSize: c.QueryInt("page_size", 10), + // } + // valErrs, ok := validator.Validate(c, filter) + // if !ok { + // return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + // } + + userIDstr := c.Params("id") + userID, err := strconv.ParseInt(userIDstr, 10, 64) + if err != nil { + h.logger.Error("failed to fetch user using UserID", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid admin ID", nil, nil) + } + + user, err := h.userSvc.GetUserByID(c.Context(), userID) + if err != nil { + h.logger.Error("Get User By ID failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get admin", nil, nil) + } + + lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID) + if err != nil { + if err != authentication.ErrRefreshTokenNotFound { + h.logger.Error("Failed to get user last login", "userID", user.ID, "error", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login") + } + + lastLogin = &user.CreatedAt + } + + res := AdminRes{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email, + PhoneNumber: user.PhoneNumber, + Role: user.Role, + EmailVerified: user.EmailVerified, + PhoneVerified: user.PhoneVerified, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + SuspendedAt: user.SuspendedAt, + Suspended: user.Suspended, + LastLogin: *lastLogin, + } + + return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil) +} + +type updateAdminReq struct { + FirstName string `json:"first_name" example:"John"` + LastName string `json:"last_name" example:"Doe"` + Suspended bool `json:"suspended" example:"false"` + CompanyID *int64 `json:"company_id,omitempty" example:"1"` +} + +// UpdateAdmin godoc +// @Summary Update Admin +// @Description Update Admin +// @Tags admin +// @Accept json +// @Produce json +// @Param admin body updateAdminReq true "Update Admin" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 401 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /admin/{id} [put] +func (h *Handler) UpdateAdmin(c *fiber.Ctx) error { + var req updateAdminReq + if err := c.BodyParser(&req); err != nil { + h.logger.Error("UpdateAdmin failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) + } + + valErrs, ok := h.validator.Validate(c, req) + + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + AdminIDStr := c.Params("id") + AdminID, err := strconv.ParseInt(AdminIDStr, 10, 64) + if err != nil { + h.logger.Error("UpdateAdmin failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Admin ID", nil, nil) + } + var companyID domain.ValidInt64 + if req.CompanyID != nil { + companyID = domain.ValidInt64{ + Value: *req.CompanyID, + Valid: true, + } + } + err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{ + UserId: AdminID, + FirstName: domain.ValidString{ + Value: req.FirstName, + Valid: req.FirstName != "", + }, + LastName: domain.ValidString{ + Value: req.LastName, + Valid: req.LastName != "", + }, + Suspended: domain.ValidBool{ + Value: req.Suspended, + Valid: true, + }, + CompanyID: companyID, + }, + ) + if err != nil { + h.logger.Error("UpdateAdmin failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update admin", nil, nil) + } + if req.CompanyID != nil { + _, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{ + ID: *req.CompanyID, + AdminID: &AdminID, + }) + if err != nil { + h.logger.Error("CreateAdmin failed to update company", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update company", nil, nil) + } + } + + return response.WriteJSON(c, fiber.StatusOK, "Managers updated successfully", nil, nil) } diff --git a/internal/web_server/handlers/branch_handler.go b/internal/web_server/handlers/branch_handler.go index 8f090ee..395ba19 100644 --- a/internal/web_server/handlers/branch_handler.go +++ b/internal/web_server/handlers/branch_handler.go @@ -142,7 +142,8 @@ func (h *Handler) CreateBranch(c *fiber.Ctx) error { checkedCompanyID = *req.CompanyID } else { IsSelfOwned = false - checkedCompanyID = companyID.Value //the company id is always valid when its not a super admin + checkedCompanyID = companyID.Value + //TODO:check that the company id is always valid when its not a super admin } // Create Branch Wallet diff --git a/internal/web_server/handlers/cashier.go b/internal/web_server/handlers/cashier.go index 8a99d3b..4fdebfc 100644 --- a/internal/web_server/handlers/cashier.go +++ b/internal/web_server/handlers/cashier.go @@ -7,6 +7,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/gofiber/fiber/v2" ) @@ -87,6 +88,7 @@ type GetCashierRes struct { SuspendedAt time.Time `json:"suspended_at"` Suspended bool `json:"suspended"` LastLogin time.Time `json:"last_login"` + BranchID int64 `json:"branch_id"` } // GetAllCashiers godoc @@ -103,22 +105,31 @@ type GetCashierRes struct { // @Failure 500 {object} response.APIResponse // @Router /cashiers [get] func (h *Handler) GetAllCashiers(c *fiber.Ctx) error { - // branchId := int64(12) //c.Locals("branch_id").(int64) - // filter := user.Filter{ - // Role: string(domain.RoleCashier), - // BranchId: user.ValidBranchId{ - // Value: branchId, - // Valid: true, - // }, - // Page: c.QueryInt("page", 1), - // PageSize: c.QueryInt("page_size", 10), - // } - // valErrs, ok := validator.Validate(c, filter) - // if !ok { - // return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - // } + role := c.Locals("role").(domain.Role) + companyId := c.Locals("company_id").(domain.ValidInt64) - cashiers, err := h.userSvc.GetAllCashiers(c.Context()) + if role != domain.RoleSuperAdmin && !companyId.Valid { + return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID") + } + filter := user.Filter{ + Role: string(domain.RoleCashier), + CompanyID: companyId, + Page: domain.ValidInt{ + Value: c.QueryInt("page", 1) - 1, + Valid: true, + }, + PageSize: domain.ValidInt{ + Value: c.QueryInt("page_size", 10), + Valid: true, + }, + } + + valErrs, ok := h.validator.Validate(c, filter) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + cashiers, total, err := h.userSvc.GetAllUsers(c.Context(), filter) if err != nil { h.logger.Error("GetAllCashiers failed", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil) @@ -154,11 +165,80 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error { }) } - return response.WriteJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", result, nil) + return response.WritePaginatedJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", result, nil, filter.Page.Value, int(total)) } -type updateUserReq struct { +// GetCashierByID godoc +// @Summary Get cashier by id +// @Description Get a single cashier by id +// @Tags cashier +// @Accept json +// @Produce json +// @Param id path int true "User ID" +// @Success 200 {object} UserProfileRes +// @Failure 400 {object} response.APIResponse +// @Failure 401 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /cashier/{id} [get] +func (h *Handler) GetCashierByID(c *fiber.Ctx) error { + // branchId := int64(12) //c.Locals("branch_id").(int64) + // filter := user.Filter{ + // Role: string(domain.RoleUser), + // BranchId: user.ValidBranchId{ + // Value: branchId, + // Valid: true, + // }, + // Page: c.QueryInt("page", 1), + // PageSize: c.QueryInt("page_size", 10), + // } + // valErrs, ok := validator.Validate(c, filter) + // if !ok { + // return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + // } + + stringID := c.Params("id") + cashierID, err := strconv.ParseInt(stringID, 10, 64) + if err != nil { + h.logger.Error("failed to fetch user using UserID", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil) + } + + user, err := h.userSvc.GetCashierByID(c.Context(), cashierID) + if err != nil { + h.logger.Error("Get User By ID failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil) + } + + lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID) + if err != nil { + if err != authentication.ErrRefreshTokenNotFound { + h.logger.Error("Failed to get user last login", "cashierID", user.ID, "error", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login") + } + lastLogin = &user.CreatedAt + } + + res := GetCashierRes{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email, + PhoneNumber: user.PhoneNumber, + Role: user.Role, + EmailVerified: user.EmailVerified, + PhoneVerified: user.PhoneVerified, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + SuspendedAt: user.SuspendedAt, + Suspended: user.Suspended, + LastLogin: *lastLogin, + BranchID: user.BranchID, + } + return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil) +} + +type updateCashierReq struct { FirstName string `json:"first_name" example:"John"` LastName string `json:"last_name" example:"Doe"` Suspended bool `json:"suspended" example:"false"` @@ -171,7 +251,7 @@ type updateUserReq struct { // @Accept json // @Produce json // @Param id path int true "Cashier ID" -// @Param cashier body updateUserReq true "Update cashier" +// @Param cashier body updateCashierReq true "Update cashier" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse @@ -184,7 +264,7 @@ func (h *Handler) UpdateCashier(c *fiber.Ctx) error { h.logger.Error("UpdateCashier failed", "error", err) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil) } - var req updateUserReq + var req updateCashierReq if err := c.BodyParser(&req); err != nil { h.logger.Error("UpdateCashier failed", "error", err) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) diff --git a/internal/web_server/handlers/company_handler.go b/internal/web_server/handlers/company_handler.go index 46b8a7d..2555cdd 100644 --- a/internal/web_server/handlers/company_handler.go +++ b/internal/web_server/handlers/company_handler.go @@ -241,7 +241,7 @@ func (h *Handler) UpdateCompany(c *fiber.Ctx) error { var req UpdateCompanyReq if err := c.BodyParser(&req); err != nil { - h.logger.Error("CreateCompanyReq failed", "error", err) + h.logger.Error("UpdateCompanyReq failed", "error", err) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := h.validator.Validate(c, req) diff --git a/internal/web_server/handlers/manager.go b/internal/web_server/handlers/manager.go index 0c3a980..6d35089 100644 --- a/internal/web_server/handlers/manager.go +++ b/internal/web_server/handlers/manager.go @@ -109,12 +109,15 @@ type ManagersRes struct { // @Failure 500 {object} response.APIResponse // @Router /managers [get] func (h *Handler) GetAllManagers(c *fiber.Ctx) error { + role := c.Locals("role").(domain.Role) + companyId := c.Locals("company_id").(domain.ValidInt64) + + if role != domain.RoleSuperAdmin && !companyId.Valid { + return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID") + } filter := user.Filter{ - Role: string(domain.RoleBranchManager), - CompanyID: domain.ValidInt64{ - Value: int64(c.QueryInt("company_id")), - Valid: true, - }, + Role: string(domain.RoleBranchManager), + CompanyID: companyId, Page: domain.ValidInt{ Value: c.QueryInt("page", 1) - 1, Valid: true, @@ -166,20 +169,97 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error { } +// GetManagerByID godoc +// @Summary Get manager by id +// @Description Get a single manager by id +// @Tags manager +// @Accept json +// @Produce json +// @Param id path int true "User ID" +// @Success 200 {object} ManagerRes +// @Failure 400 {object} response.APIResponse +// @Failure 401 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /managers/{id} [get] +func (h *Handler) GetManagerByID(c *fiber.Ctx) error { + // branchId := int64(12) //c.Locals("branch_id").(int64) + // filter := user.Filter{ + // Role: string(domain.RoleUser), + // BranchId: user.ValidBranchId{ + // Value: branchId, + // Valid: true, + // }, + // Page: c.QueryInt("page", 1), + // PageSize: c.QueryInt("page_size", 10), + // } + // valErrs, ok := validator.Validate(c, filter) + // if !ok { + // return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + // } + + userIDstr := c.Params("id") + userID, err := strconv.ParseInt(userIDstr, 10, 64) + if err != nil { + h.logger.Error("failed to fetch user using UserID", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid managers ID", nil, nil) + } + + user, err := h.userSvc.GetUserByID(c.Context(), userID) + if err != nil { + h.logger.Error("Get User By ID failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get managers", nil, nil) + } + + lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID) + if err != nil { + if err != authentication.ErrRefreshTokenNotFound { + h.logger.Error("Failed to get user last login", "userID", user.ID, "error", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login") + } + + lastLogin = &user.CreatedAt + } + + res := ManagersRes{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email, + PhoneNumber: user.PhoneNumber, + Role: user.Role, + EmailVerified: user.EmailVerified, + PhoneVerified: user.PhoneVerified, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + SuspendedAt: user.SuspendedAt, + Suspended: user.Suspended, + LastLogin: *lastLogin, + } + + return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil) +} + +type updateManagerReq struct { + FirstName string `json:"first_name" example:"John"` + LastName string `json:"last_name" example:"Doe"` + Suspended bool `json:"suspended" example:"false"` + CompanyID *int64 `json:"company_id,omitempty" example:"1"` +} + // UpdateManagers godoc // @Summary Update Managers // @Description Update Managers -// @Tags Managers +// @Tags manager // @Accept json // @Produce json -// @Param Managers body updateUserReq true "Update Managers" +// @Param Managers body updateManagerReq true "Update Managers" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /managers/{id} [put] func (h *Handler) UpdateManagers(c *fiber.Ctx) error { - var req updateUserReq + var req updateManagerReq if err := c.BodyParser(&req); err != nil { h.logger.Error("UpdateManagers failed", "error", err) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) @@ -196,6 +276,19 @@ func (h *Handler) UpdateManagers(c *fiber.Ctx) error { h.logger.Error("UpdateManagers failed", "error", err) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Managers ID", nil, nil) } + var companyID domain.ValidInt64 + role := c.Locals("role").(domain.Role) + if req.CompanyID != nil { + if role != domain.RoleSuperAdmin { + h.logger.Error("UpdateManagers failed", "error", err) + return response.WriteJSON(c, fiber.StatusUnauthorized, "This user role cannot modify company ID", nil, nil) + } + companyID = domain.ValidInt64{ + Value: *req.CompanyID, + Valid: true, + } + } + err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{ UserId: ManagersId, FirstName: domain.ValidString{ @@ -210,6 +303,7 @@ func (h *Handler) UpdateManagers(c *fiber.Ctx) error { Value: req.Suspended, Valid: true, }, + CompanyID: companyID, }, ) if err != nil { diff --git a/internal/web_server/handlers/user.go b/internal/web_server/handlers/user.go index 55de2af..8e280fa 100644 --- a/internal/web_server/handlers/user.go +++ b/internal/web_server/handlers/user.go @@ -474,13 +474,13 @@ func (h *Handler) GetUserByID(c *fiber.Ctx) error { userIDstr := c.Params("id") userID, err := strconv.ParseInt(userIDstr, 10, 64) if err != nil { - h.logger.Error("UpdateCashier failed", "error", err) + h.logger.Error("failed to fetch user using UserID", "error", err) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil) } user, err := h.userSvc.GetUserByID(c.Context(), userID) if err != nil { - h.logger.Error("GetAllCashiers failed", "error", err) + h.logger.Error("Get User By ID failed", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil) } @@ -510,7 +510,7 @@ func (h *Handler) GetUserByID(c *fiber.Ctx) error { LastLogin: *lastLogin, } - return response.WriteJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", res, nil) + return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil) } diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 06611c9..51a9d1c 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -90,13 +90,17 @@ func (a *App) initAppRoutes() { a.fiber.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings) a.fiber.Get("/cashiers", a.authMiddleware, h.GetAllCashiers) + a.fiber.Get("/cashiers/:id", a.authMiddleware, h.GetCashierByID) a.fiber.Post("/cashiers", a.authMiddleware, h.CreateCashier) a.fiber.Put("/cashiers/:id", a.authMiddleware, h.UpdateCashier) a.fiber.Get("/admin", a.authMiddleware, h.GetAllAdmins) + a.fiber.Get("/admin/:id", a.authMiddleware, h.GetAdminByID) a.fiber.Post("/admin", a.authMiddleware, h.CreateAdmin) + a.fiber.Put("/admin/:id", a.authMiddleware, h.UpdateAdmin) a.fiber.Get("/managers", a.authMiddleware, h.GetAllManagers) + a.fiber.Get("/managers/:id", a.authMiddleware, h.GetManagerByID) a.fiber.Post("/managers", a.authMiddleware, h.CreateManager) a.fiber.Put("/managers/:id", a.authMiddleware, h.UpdateManagers) a.fiber.Get("/manager/:id/branch", a.authMiddleware, h.GetBranchByManagerID)