From c2a9de6671f0550ebafb026f1720d63f8f68d707 Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Wed, 2 Jul 2025 19:41:29 +0300 Subject: [PATCH] fix: company filter --- db/migrations/000001_fortune.up.sql | 4 +- db/query/branch.sql | 3 +- db/query/company.sql | 20 +- gen/db/branch.sql.go | 3 +- gen/db/company.sql.go | 48 +++- gen/db/models.go | 30 +- gen/db/wallet.sql.go | 11 +- internal/domain/company.go | 7 + internal/repository/branch.go | 2 +- internal/repository/company.go | 17 +- internal/repository/notification.go | 2 +- internal/services/company/port.go | 2 +- internal/services/company/service.go | 4 +- .../web_server/handlers/company_handler.go | 40 ++- internal/web_server/handlers/customer.go | 270 ++++++++++++++++++ internal/web_server/routes.go | 4 + 16 files changed, 431 insertions(+), 36 deletions(-) create mode 100644 internal/web_server/handlers/customer.go diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index a44ca00..1391f8a 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -285,7 +285,9 @@ CREATE TABLE companies ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, admin_id BIGINT NOT NULL, - wallet_id BIGINT NOT NULL + wallet_id BIGINT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE leagues ( id BIGINT PRIMARY KEY, diff --git a/db/query/branch.sql b/db/query/branch.sql index 34f22eb..4c38e0c 100644 --- a/db/query/branch.sql +++ b/db/query/branch.sql @@ -87,7 +87,8 @@ SET name = COALESCE(sqlc.narg(name), name), branch_manager_id = COALESCE(sqlc.narg(branch_manager_id), branch_manager_id), company_id = COALESCE(sqlc.narg(company_id), company_id), is_self_owned = COALESCE(sqlc.narg(is_self_owned), is_self_owned), - is_active = COALESCE(sqlc.narg(is_active), is_active) + is_active = COALESCE(sqlc.narg(is_active), is_active), + updated_at = CURRENT_TIMESTAMP WHERE id = $1 RETURNING *; -- name: DeleteBranch :exec diff --git a/db/query/company.sql b/db/query/company.sql index 3315132..78779a8 100644 --- a/db/query/company.sql +++ b/db/query/company.sql @@ -8,7 +8,22 @@ VALUES ($1, $2, $3) RETURNING *; -- name: GetAllCompanies :many SELECT * -FROM companies_details; +FROM companies_details +WHERE ( + name ILIKE '%' || sqlc.narg('query') || '%' + OR admin_first_name ILIKE '%' || sqlc.narg('query') || '%' + OR admin_last_name ILIKE '%' || sqlc.narg('query') || '%' + OR admin_phone_number ILIKE '%' || sqlc.narg('query') || '%' + OR sqlc.narg('query') IS NULL + ) + AND ( + created_at > sqlc.narg('created_before') + OR sqlc.narg('created_before') IS NULL + ) + AND ( + created_at < sqlc.narg('created_after') + OR sqlc.narg('created_after') IS NULL + ); -- name: GetCompanyByID :one SELECT * FROM companies_details @@ -20,7 +35,8 @@ WHERE name ILIKE '%' || $1 || '%'; -- name: UpdateCompany :one UPDATE companies SET name = COALESCE(sqlc.narg(name), name), - admin_id = COALESCE(sqlc.narg(admin_id), admin_id) + admin_id = COALESCE(sqlc.narg(admin_id), admin_id), + updated_at = CURRENT_TIMESTAMP WHERE id = $1 RETURNING *; -- name: DeleteCompany :exec diff --git a/gen/db/branch.sql.go b/gen/db/branch.sql.go index 71e5257..09eafd2 100644 --- a/gen/db/branch.sql.go +++ b/gen/db/branch.sql.go @@ -490,7 +490,8 @@ SET name = COALESCE($2, name), branch_manager_id = COALESCE($4, branch_manager_id), company_id = COALESCE($5, company_id), is_self_owned = COALESCE($6, is_self_owned), - is_active = COALESCE($7, is_active) + is_active = COALESCE($7, is_active), + updated_at = CURRENT_TIMESTAMP WHERE id = $1 RETURNING id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at ` diff --git a/gen/db/company.sql.go b/gen/db/company.sql.go index 449c8fd..2cb5371 100644 --- a/gen/db/company.sql.go +++ b/gen/db/company.sql.go @@ -18,7 +18,7 @@ INSERT INTO companies ( wallet_id ) VALUES ($1, $2, $3) -RETURNING id, name, admin_id, wallet_id +RETURNING id, name, admin_id, wallet_id, created_at, updated_at ` type CreateCompanyParams struct { @@ -35,6 +35,8 @@ func (q *Queries) CreateCompany(ctx context.Context, arg CreateCompanyParams) (C &i.Name, &i.AdminID, &i.WalletID, + &i.CreatedAt, + &i.UpdatedAt, ) return i, err } @@ -50,12 +52,33 @@ func (q *Queries) DeleteCompany(ctx context.Context, id int64) error { } const GetAllCompanies = `-- name: GetAllCompanies :many -SELECT id, name, admin_id, wallet_id, balance, is_active, admin_first_name, admin_last_name, admin_phone_number +SELECT id, name, admin_id, wallet_id, created_at, updated_at, balance, is_active, admin_first_name, admin_last_name, admin_phone_number FROM companies_details +WHERE ( + name ILIKE '%' || $1 || '%' + OR admin_first_name ILIKE '%' || $1 || '%' + OR admin_last_name ILIKE '%' || $1 || '%' + OR admin_phone_number ILIKE '%' || $1 || '%' + OR $1 IS NULL + ) + AND ( + created_at > $2 + OR $2 IS NULL + ) + AND ( + created_at < $3 + OR $3 IS NULL + ) ` -func (q *Queries) GetAllCompanies(ctx context.Context) ([]CompaniesDetail, error) { - rows, err := q.db.Query(ctx, GetAllCompanies) +type GetAllCompaniesParams struct { + Query pgtype.Text `json:"query"` + CreatedBefore pgtype.Timestamp `json:"created_before"` + CreatedAfter pgtype.Timestamp `json:"created_after"` +} + +func (q *Queries) GetAllCompanies(ctx context.Context, arg GetAllCompaniesParams) ([]CompaniesDetail, error) { + rows, err := q.db.Query(ctx, GetAllCompanies, arg.Query, arg.CreatedBefore, arg.CreatedAfter) if err != nil { return nil, err } @@ -68,6 +91,8 @@ func (q *Queries) GetAllCompanies(ctx context.Context) ([]CompaniesDetail, error &i.Name, &i.AdminID, &i.WalletID, + &i.CreatedAt, + &i.UpdatedAt, &i.Balance, &i.IsActive, &i.AdminFirstName, @@ -85,7 +110,7 @@ func (q *Queries) GetAllCompanies(ctx context.Context) ([]CompaniesDetail, error } const GetCompanyByID = `-- name: GetCompanyByID :one -SELECT id, name, admin_id, wallet_id, balance, is_active, admin_first_name, admin_last_name, admin_phone_number +SELECT id, name, admin_id, wallet_id, created_at, updated_at, balance, is_active, admin_first_name, admin_last_name, admin_phone_number FROM companies_details WHERE id = $1 ` @@ -98,6 +123,8 @@ func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (CompaniesDetail &i.Name, &i.AdminID, &i.WalletID, + &i.CreatedAt, + &i.UpdatedAt, &i.Balance, &i.IsActive, &i.AdminFirstName, @@ -108,7 +135,7 @@ func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (CompaniesDetail } const SearchCompanyByName = `-- name: SearchCompanyByName :many -SELECT id, name, admin_id, wallet_id, balance, is_active, admin_first_name, admin_last_name, admin_phone_number +SELECT id, name, admin_id, wallet_id, created_at, updated_at, balance, is_active, admin_first_name, admin_last_name, admin_phone_number FROM companies_details WHERE name ILIKE '%' || $1 || '%' ` @@ -127,6 +154,8 @@ func (q *Queries) SearchCompanyByName(ctx context.Context, dollar_1 pgtype.Text) &i.Name, &i.AdminID, &i.WalletID, + &i.CreatedAt, + &i.UpdatedAt, &i.Balance, &i.IsActive, &i.AdminFirstName, @@ -146,9 +175,10 @@ func (q *Queries) SearchCompanyByName(ctx context.Context, dollar_1 pgtype.Text) const UpdateCompany = `-- name: UpdateCompany :one UPDATE companies SET name = COALESCE($2, name), - admin_id = COALESCE($3, admin_id) + admin_id = COALESCE($3, admin_id), + updated_at = CURRENT_TIMESTAMP WHERE id = $1 -RETURNING id, name, admin_id, wallet_id +RETURNING id, name, admin_id, wallet_id, created_at, updated_at ` type UpdateCompanyParams struct { @@ -165,6 +195,8 @@ func (q *Queries) UpdateCompany(ctx context.Context, arg UpdateCompanyParams) (C &i.Name, &i.AdminID, &i.WalletID, + &i.CreatedAt, + &i.UpdatedAt, ) return i, err } diff --git a/gen/db/models.go b/gen/db/models.go index f5be831..c50ec94 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -179,22 +179,26 @@ type BranchOperation struct { } type CompaniesDetail struct { - ID int64 `json:"id"` - Name string `json:"name"` - AdminID int64 `json:"admin_id"` - WalletID int64 `json:"wallet_id"` - Balance int64 `json:"balance"` - IsActive bool `json:"is_active"` - AdminFirstName string `json:"admin_first_name"` - AdminLastName string `json:"admin_last_name"` - AdminPhoneNumber pgtype.Text `json:"admin_phone_number"` + ID int64 `json:"id"` + Name string `json:"name"` + AdminID int64 `json:"admin_id"` + WalletID int64 `json:"wallet_id"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + Balance int64 `json:"balance"` + IsActive bool `json:"is_active"` + AdminFirstName string `json:"admin_first_name"` + AdminLastName string `json:"admin_last_name"` + AdminPhoneNumber pgtype.Text `json:"admin_phone_number"` } type Company struct { - ID int64 `json:"id"` - Name string `json:"name"` - AdminID int64 `json:"admin_id"` - WalletID int64 `json:"wallet_id"` + ID int64 `json:"id"` + Name string `json:"name"` + AdminID int64 `json:"admin_id"` + WalletID int64 `json:"wallet_id"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` } type CustomerWallet struct { diff --git a/gen/db/wallet.sql.go b/gen/db/wallet.sql.go index 3a49ebf..118f4ee 100644 --- a/gen/db/wallet.sql.go +++ b/gen/db/wallet.sql.go @@ -253,9 +253,16 @@ WHERE wallet_id = $1 LIMIT 1 ` -func (q *Queries) GetCompanyByWalletID(ctx context.Context, walletID int64) (Company, error) { +type GetCompanyByWalletIDRow struct { + ID int64 `json:"id"` + Name string `json:"name"` + AdminID int64 `json:"admin_id"` + WalletID int64 `json:"wallet_id"` +} + +func (q *Queries) GetCompanyByWalletID(ctx context.Context, walletID int64) (GetCompanyByWalletIDRow, error) { row := q.db.QueryRow(ctx, GetCompanyByWalletID, walletID) - var i Company + var i GetCompanyByWalletIDRow err := row.Scan( &i.ID, &i.Name, diff --git a/internal/domain/company.go b/internal/domain/company.go index f0a6420..dbb609e 100644 --- a/internal/domain/company.go +++ b/internal/domain/company.go @@ -10,6 +10,13 @@ type Company struct { WalletID int64 } +type CompanyFilter struct { + IsActive ValidBool + Query ValidString + CreatedBefore ValidTime + CreatedAfter ValidTime +} + type GetCompany struct { ID int64 Name string diff --git a/internal/repository/branch.go b/internal/repository/branch.go index e9491bb..b816e05 100644 --- a/internal/repository/branch.go +++ b/internal/repository/branch.go @@ -400,7 +400,7 @@ func (s *Store) GetBranchDetails(ctx context.Context, filter domain.ReportFilter // In internal/repository/branch.go func (s *Store) GetAllCompaniesBranch(ctx context.Context) ([]domain.Company, error) { - dbCompanies, err := s.queries.GetAllCompanies(ctx) + dbCompanies, err := s.queries.GetAllCompanies(ctx, dbgen.GetAllCompaniesParams{}) if err != nil { return nil, fmt.Errorf("failed to get all companies: %w", err) } diff --git a/internal/repository/company.go b/internal/repository/company.go index 8fd8432..ddd0404 100644 --- a/internal/repository/company.go +++ b/internal/repository/company.go @@ -70,8 +70,21 @@ func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany) return convertDBCompany(dbCompany), nil } -func (s *Store) GetAllCompanies(ctx context.Context) ([]domain.GetCompany, error) { - dbCompanies, err := s.queries.GetAllCompanies(ctx) +func (s *Store) GetAllCompanies(ctx context.Context, filter domain.CompanyFilter) ([]domain.GetCompany, error) { + dbCompanies, err := s.queries.GetAllCompanies(ctx, dbgen.GetAllCompaniesParams{ + Query: pgtype.Text{ + String: filter.Query.Value, + Valid: filter.Query.Valid, + }, + CreatedBefore: pgtype.Timestamp{ + Time: filter.CreatedBefore.Value, + Valid: filter.CreatedBefore.Valid, + }, + CreatedAfter: pgtype.Timestamp{ + Time: filter.CreatedAfter.Value, + Valid: filter.CreatedAfter.Valid, + }, + }) if err != nil { return nil, err } diff --git a/internal/repository/notification.go b/internal/repository/notification.go index c279377..d874c47 100644 --- a/internal/repository/notification.go +++ b/internal/repository/notification.go @@ -108,7 +108,7 @@ func (r *Repository) ListNotifications(ctx context.Context, recipientID int64, l return nil, err } - var result []domain.Notification + var result []domain.Notification = make([]domain.Notification, 0, len(dbNotifications)) for _, dbNotif := range dbNotifications { domainNotif := r.mapDBToDomain(&dbNotif) result = append(result, *domainNotif) diff --git a/internal/services/company/port.go b/internal/services/company/port.go index a6d14da..992f69f 100644 --- a/internal/services/company/port.go +++ b/internal/services/company/port.go @@ -8,7 +8,7 @@ import ( type CompanyStore interface { CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) - GetAllCompanies(ctx context.Context) ([]domain.GetCompany, error) + GetAllCompanies(ctx context.Context, filter domain.CompanyFilter) ([]domain.GetCompany, error) SearchCompanyByName(ctx context.Context, name string) ([]domain.GetCompany, error) GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany, error) UpdateCompany(ctx context.Context, company domain.UpdateCompany) (domain.Company, error) diff --git a/internal/services/company/service.go b/internal/services/company/service.go index 6379356..ce82da8 100644 --- a/internal/services/company/service.go +++ b/internal/services/company/service.go @@ -19,8 +19,8 @@ func NewService(companyStore CompanyStore) *Service { func (s *Service) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) { return s.companyStore.CreateCompany(ctx, company) } -func (s *Service) GetAllCompanies(ctx context.Context) ([]domain.GetCompany, error) { - return s.companyStore.GetAllCompanies(ctx) +func (s *Service) GetAllCompanies(ctx context.Context, filter domain.CompanyFilter) ([]domain.GetCompany, error) { + return s.companyStore.GetAllCompanies(ctx, filter) } func (s *Service) GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany, error) { diff --git a/internal/web_server/handlers/company_handler.go b/internal/web_server/handlers/company_handler.go index e4d01c2..0501fbc 100644 --- a/internal/web_server/handlers/company_handler.go +++ b/internal/web_server/handlers/company_handler.go @@ -2,6 +2,7 @@ package handlers import ( "strconv" + "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" @@ -137,8 +138,45 @@ func (h *Handler) CreateCompany(c *fiber.Ctx) error { // @Failure 500 {object} response.APIResponse // @Router /company [get] func (h *Handler) GetAllCompanies(c *fiber.Ctx) error { + searchQuery := c.Query("query") + searchString := domain.ValidString{ + Value: searchQuery, + Valid: searchQuery != "", + } - companies, err := h.companySvc.GetAllCompanies(c.Context()) + createdBeforeQuery := c.Query("created_before") + var createdBefore domain.ValidTime + if createdBeforeQuery != "" { + createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) + if err != nil { + h.logger.Error("invalid start_time format", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) + } + createdBefore = domain.ValidTime{ + Value: createdBeforeParsed, + Valid: true, + } + } + + createdAfterQuery := c.Query("created_after") + var createdAfter domain.ValidTime + if createdAfterQuery != "" { + createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) + if err != nil { + h.logger.Error("invalid start_time format", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) + } + createdAfter = domain.ValidTime{ + Value: createdAfterParsed, + Valid: true, + } + } + + companies, err := h.companySvc.GetAllCompanies(c.Context(), domain.CompanyFilter{ + Query: searchString, + CreatedBefore: createdBefore, + CreatedAfter: createdAfter, + }) if err != nil { h.logger.Error("Failed to get companies", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get companies", err, nil) diff --git a/internal/web_server/handlers/customer.go b/internal/web_server/handlers/customer.go new file mode 100644 index 0000000..d340531 --- /dev/null +++ b/internal/web_server/handlers/customer.go @@ -0,0 +1,270 @@ +package handlers + +import ( + "strconv" + "time" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + "github.com/gofiber/fiber/v2" +) + + +type CustomersRes 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 domain.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"` + LastLogin time.Time `json:"last_login"` + SuspendedAt time.Time `json:"suspended_at"` + Suspended bool `json:"suspended"` +} +// GetAllCustomers godoc +// @Summary Get all Customers +// @Description Get all Customers +// @Tags customer +// @Accept json +// @Produce json +// @Param page query int false "Page number" +// @Param page_size query int false "Page size" +// @Success 200 {object} CustomersRes +// @Failure 400 {object} response.APIResponse +// @Failure 401 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /customer [get] +func (h *Handler) GetAllCustomers(c *fiber.Ctx) error { + role := c.Locals("role").(domain.Role) + companyId := c.Locals("company_id").(domain.ValidInt64) + + // Checking to make sure that admin user has a company id in the token + if role != domain.RoleSuperAdmin && !companyId.Valid { + return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID") + } + + searchQuery := c.Query("query") + searchString := domain.ValidString{ + Value: searchQuery, + Valid: searchQuery != "", + } + + createdBeforeQuery := c.Query("created_before") + var createdBefore domain.ValidTime + if createdBeforeQuery != "" { + createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) + if err != nil { + h.logger.Error("invalid start_time format", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) + } + createdBefore = domain.ValidTime{ + Value: createdBeforeParsed, + Valid: true, + } + } + + createdAfterQuery := c.Query("created_after") + var createdAfter domain.ValidTime + if createdAfterQuery != "" { + createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) + if err != nil { + h.logger.Error("invalid start_time format", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) + } + createdAfter = domain.ValidTime{ + Value: createdAfterParsed, + Valid: true, + } + } + + filter := domain.UserFilter{ + Role: string(domain.RoleCustomer), + CompanyID: companyId, + Page: domain.ValidInt{ + Value: c.QueryInt("page", 1) - 1, + Valid: true, + }, + PageSize: domain.ValidInt{ + Value: c.QueryInt("page_size", 10), + Valid: true, + }, + Query: searchString, + CreatedBefore: createdBefore, + CreatedAfter: createdAfter, + } + valErrs, ok := h.validator.Validate(c, filter) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + customers, total, err := h.userSvc.GetAllUsers(c.Context(), filter) + if err != nil { + h.logger.Error("GetAllCustomers failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get Customers", err, nil) + } + + var result []CustomersRes = make([]CustomersRes, len(customers)) + for index, customer := range customers { + lastLogin, err := h.authSvc.GetLastLogin(c.Context(), customer.ID) + if err != nil { + if err == authentication.ErrRefreshTokenNotFound { + lastLogin = &customer.CreatedAt + } else { + h.logger.Error("Failed to get user last login", "userID", customer.ID, "error", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login") + } + } + result[index] = CustomersRes{ + ID: customer.ID, + FirstName: customer.FirstName, + LastName: customer.LastName, + Email: customer.Email, + PhoneNumber: customer.PhoneNumber, + Role: customer.Role, + EmailVerified: customer.EmailVerified, + PhoneVerified: customer.PhoneVerified, + CreatedAt: customer.CreatedAt, + UpdatedAt: customer.UpdatedAt, + SuspendedAt: customer.SuspendedAt, + Suspended: customer.Suspended, + LastLogin: *lastLogin, + } + } + + return response.WritePaginatedJSON(c, fiber.StatusOK, "Customers retrieved successfully", result, nil, filter.Page.Value, int(total)) + +} + +// GetCustomerByID godoc +// @Summary Get customer by id +// @Description Get a single customer by id +// @Tags customer +// @Accept json +// @Produce json +// @Param id path int true "User ID" +// @Success 200 {object} CustomersRes +// @Failure 400 {object} response.APIResponse +// @Failure 401 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /customer/{id} [get] +func (h *Handler) GetCustomerByID(c *fiber.Ctx) error { + userIDstr := c.Params("id") + userID, err := strconv.ParseInt(userIDstr, 10, 64) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid customers ID") + } + + user, err := h.userSvc.GetUserByID(c.Context(), userID) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get customers") + } + + 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 := CustomersRes{ + 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 updateCustomerReq 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"` +} + +// UpdateCustomers godoc +// @Summary Update Customers +// @Description Update Customers +// @Tags customer +// @Accept json +// @Produce json +// @Param Customers body updateCustomerReq true "Update Customers" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 401 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /customer/{id} [put] +func (h *Handler) UpdateCustomer(c *fiber.Ctx) error { + + var req updateCustomerReq + + if err := c.BodyParser(&req); err != nil { + h.logger.Error("UpdateCustomers 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) + } + CustomersIdStr := c.Params("id") + CustomersId, err := strconv.ParseInt(CustomersIdStr, 10, 64) + if err != nil { + h.logger.Error("UpdateCustomers failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Customers ID", nil, nil) + } + + // var companyID domain.ValidInt64 + // role := c.Locals("role").(domain.Role) + // if req.CompanyID != nil { + // if role != domain.RoleSuperAdmin { + // h.logger.Error("UpdateCustomers 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: CustomersId, + 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, + }, + }, + ) + if err != nil { + h.logger.Error("UpdateCustomers failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update Customers", nil, nil) + } + return response.WriteJSON(c, fiber.StatusOK, "Customers updated successfully", nil, nil) + +} diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 11172c1..2bf2c5a 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -123,6 +123,10 @@ func (a *App) initAppRoutes() { a.fiber.Post("/cashiers", a.authMiddleware, h.CreateCashier) a.fiber.Put("/cashiers/:id", a.authMiddleware, h.UpdateCashier) + a.fiber.Get("/customer", a.authMiddleware, a.SuperAdminOnly, h.GetAllCustomers) + a.fiber.Get("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.GetCustomerByID) + a.fiber.Put("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateCustomer) + 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)