diff --git a/db/data/001_initial_seed_data.sql b/db/data/001_initial_seed_data.sql index 159a658..95a21d8 100644 --- a/db/data/001_initial_seed_data.sql +++ b/db/data/001_initial_seed_data.sql @@ -350,4 +350,30 @@ SET name = EXCLUDED.name, profit_percent = EXCLUDED.profit_percent, is_active = EXCLUDED.is_active, created_at = EXCLUDED.created_at, - updated_at = EXCLUDED.updated_at; \ No newline at end of file + updated_at = EXCLUDED.updated_at; +-- Bonus +INSERT INTO user_bonuses ( + id, + name, + description, + type, + user_id, + reward_amount, + expires_at + ) +VALUES ( + 1, + 'Welcome Bonus', + 'Awarded for deposit number (1 / 3)', + 'welcome_bonus', + 1, + 1000, + now() + INTERVAL '1 day' + ) ON CONFLICT (id) DO +UPDATE +SET name = EXCLUDED.name, + description = EXCLUDED.description, + type = EXCLUDED.type, + user_id = EXCLUDED.user_id, + reward_amount = EXCLUDED.reward_amount, + expires_at = EXCLUDED.expires_at; \ No newline at end of file diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 20dfd81..f8f509d 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -454,7 +454,7 @@ CREATE TABLE IF NOT EXISTS company_settings ( PRIMARY KEY (company_id, key) ); CREATE TABLE user_bonuses ( - id BIGINT NOT NULL, + id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL, type TEXT NOT NULL, @@ -462,7 +462,7 @@ CREATE TABLE user_bonuses ( reward_amount BIGINT NOT NULL, is_claimed BOOLEAN NOT NULL DEFAULT false, expires_at TIMESTAMP NOT NULL, - claimed_at TIMESTAMP NOT NULL, + claimed_at TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); diff --git a/db/query/settings.sql b/db/query/settings.sql index cda4e87..e51c1ff 100644 --- a/db/query/settings.sql +++ b/db/query/settings.sql @@ -27,7 +27,9 @@ SELECT * FROM company_settings WHERE key = $1; -- name: GetOverrideSettings :many -SELECT gs.*, +SELECT gs.key, + gs.created_at, + gs.updated_at, COALESCE(cs.value, gs.value) AS value FROM global_settings gs LEFT JOIN company_settings cs ON cs.key = gs.key diff --git a/gen/db/settings.sql.go b/gen/db/settings.sql.go index f67fecc..96ea916 100644 --- a/gen/db/settings.sql.go +++ b/gen/db/settings.sql.go @@ -181,7 +181,9 @@ func (q *Queries) GetGlobalSettings(ctx context.Context) ([]GlobalSetting, error } const GetOverrideSettings = `-- name: GetOverrideSettings :many -SELECT gs.key, gs.value, gs.created_at, gs.updated_at, +SELECT gs.key, + gs.created_at, + gs.updated_at, COALESCE(cs.value, gs.value) AS value FROM global_settings gs LEFT JOIN company_settings cs ON cs.key = gs.key @@ -190,10 +192,9 @@ FROM global_settings gs type GetOverrideSettingsRow struct { Key string `json:"key"` - Value string `json:"value"` CreatedAt pgtype.Timestamp `json:"created_at"` UpdatedAt pgtype.Timestamp `json:"updated_at"` - Value_2 string `json:"value_2"` + Value string `json:"value"` } func (q *Queries) GetOverrideSettings(ctx context.Context, companyID int64) ([]GetOverrideSettingsRow, error) { @@ -207,10 +208,9 @@ func (q *Queries) GetOverrideSettings(ctx context.Context, companyID int64) ([]G var i GetOverrideSettingsRow if err := rows.Scan( &i.Key, - &i.Value, &i.CreatedAt, &i.UpdatedAt, - &i.Value_2, + &i.Value, ); err != nil { return nil, err } diff --git a/internal/domain/raffle.go b/internal/domain/raffle.go index 7366b0d..0767b5d 100644 --- a/internal/domain/raffle.go +++ b/internal/domain/raffle.go @@ -22,6 +22,16 @@ type RaffleStanding struct { TicketCount int64 } +type RaffleStandingRes struct { + UserID int64 `json:"user_id"` + RaffleID int32 `json:"raffle_id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + PhoneNumber string `json:"phone_number"` + Email string `json:"email"` + TicketCount int64 `json:"ticket_count"` +} + type RaffleWinnerParams struct { RaffleID int32 UserID int32 diff --git a/internal/services/bonus/service.go b/internal/services/bonus/service.go index 047fb7c..3daaf71 100644 --- a/internal/services/bonus/service.go +++ b/internal/services/bonus/service.go @@ -72,6 +72,7 @@ func (s *Service) CreateWelcomeBonus(ctx context.Context, amount domain.Currency Name: "Welcome Bonus", Description: fmt.Sprintf("Awarded for deposit number (%v / %v)", stats.TotalDeposits, settingsList.WelcomeBonusCount), UserID: userID, + Type: domain.WelcomeBonus, RewardAmount: domain.Currency(newBalance), ExpiresAt: time.Now().Add(time.Duration(settingsList.WelcomeBonusExpire) * 24 * time.Hour), }) diff --git a/internal/services/user/direct.go b/internal/services/user/direct.go index bbad0b6..9f1a40f 100644 --- a/internal/services/user/direct.go +++ b/internal/services/user/direct.go @@ -19,6 +19,7 @@ func (s *Service) CreateUser(ctx context.Context, User domain.CreateUserReq, is_ // User.BranchID = branchId // User.Role = string(domain.RoleBranchManager) // } + hashedPassword, err := hashPassword(User.Password) if err != nil { return domain.User{}, err diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index 3c39dfc..1cf19c6 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -26,71 +26,71 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S spec string task func() }{ - // { - // spec: "0 0 * * * *", // Every 1 hour - // task: func() { - // mongoLogger.Info("Began fetching upcoming events cron task") - // if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { - // mongoLogger.Error("Failed to fetch upcoming events", - // zap.Error(err), - // ) - // } else { - // mongoLogger.Info("Completed fetching upcoming events without errors") - // } - // }, - // }, - // { - // spec: "0 0 * * * *", // Every 1 hour (since its takes that long to fetch all the events) - // task: func() { - // mongoLogger.Info("Began fetching non live odds cron task") - // if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { - // mongoLogger.Error("Failed to fetch non live odds", - // zap.Error(err), - // ) - // } else { - // mongoLogger.Info("Completed fetching non live odds without errors") - // } - // }, - // }, - // { - // spec: "0 */5 * * * *", // Every 5 Minutes - // task: func() { - // mongoLogger.Info("Began update all expired events status cron task") - // if _, err := resultService.CheckAndUpdateExpiredEvents(context.Background()); err != nil { - // mongoLogger.Error("Failed to update expired events status", - // zap.Error(err), - // ) - // } else { - // mongoLogger.Info("Completed expired events without errors") - // } - // }, - // }, - // { - // spec: "0 */15 * * * *", // Every 15 Minutes - // task: func() { - // mongoLogger.Info("Began fetching results for upcoming events cron task") - // if err := resultService.FetchAndProcessResults(context.Background()); err != nil { - // mongoLogger.Error("Failed to process result", - // zap.Error(err), - // ) - // } else { - // mongoLogger.Info("Completed processing all event result outcomes without errors") - // } - // }, - // }, - // { - // spec: "0 0 0 * * *", // Every Day - // task: func() { - // mongoLogger.Info("Began Send daily result notification cron task") - // if err := resultService.CheckAndSendResultNotifications(context.Background(), time.Now().Add(-24*time.Hour)); err != nil { - // mongoLogger.Error("Failed to process result", - // zap.Error(err), - // ) - // } else { - // mongoLogger.Info("Completed sending daily result notification without errors") - // } - // }, - // }, + { + spec: "0 0 * * * *", // Every 1 hour + task: func() { + mongoLogger.Info("Began fetching upcoming events cron task") + if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { + mongoLogger.Error("Failed to fetch upcoming events", + zap.Error(err), + ) + } else { + mongoLogger.Info("Completed fetching upcoming events without errors") + } + }, + }, + { + spec: "0 0 * * * *", // Every 1 hour (since its takes that long to fetch all the events) + task: func() { + mongoLogger.Info("Began fetching non live odds cron task") + if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { + mongoLogger.Error("Failed to fetch non live odds", + zap.Error(err), + ) + } else { + mongoLogger.Info("Completed fetching non live odds without errors") + } + }, + }, + { + spec: "0 */5 * * * *", // Every 5 Minutes + task: func() { + mongoLogger.Info("Began update all expired events status cron task") + if _, err := resultService.CheckAndUpdateExpiredEvents(context.Background()); err != nil { + mongoLogger.Error("Failed to update expired events status", + zap.Error(err), + ) + } else { + mongoLogger.Info("Completed expired events without errors") + } + }, + }, + { + spec: "0 */15 * * * *", // Every 15 Minutes + task: func() { + mongoLogger.Info("Began fetching results for upcoming events cron task") + if err := resultService.FetchAndProcessResults(context.Background()); err != nil { + mongoLogger.Error("Failed to process result", + zap.Error(err), + ) + } else { + mongoLogger.Info("Completed processing all event result outcomes without errors") + } + }, + }, + { + spec: "0 0 0 * * *", // Every Day + task: func() { + mongoLogger.Info("Began Send daily result notification cron task") + if err := resultService.CheckAndSendResultNotifications(context.Background(), time.Now().Add(-24*time.Hour)); err != nil { + mongoLogger.Error("Failed to process result", + zap.Error(err), + ) + } else { + mongoLogger.Info("Completed sending daily result notification without errors") + } + }, + }, } for _, job := range schedule { diff --git a/internal/web_server/handlers/raffle_handler.go b/internal/web_server/handlers/raffle_handler.go index 6f64a9b..6dc9041 100644 --- a/internal/web_server/handlers/raffle_handler.go +++ b/internal/web_server/handlers/raffle_handler.go @@ -103,6 +103,26 @@ func (h *Handler) GetRafflesOfCompany(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusOK, "Company Raffles fetched successfully", companyRaffles, nil) } +func (h *Handler) GetTenantRaffles(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } + + companyRaffles, err := h.raffleSvc.GetRafflesOfCompany(c.Context(), int32(companyID.Value)) + if err != nil { + h.mongoLoggerSvc.Error("Failed to fetch company raffle", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch company raffle") + } + + return response.WriteJSON(c, fiber.StatusOK, "Company Raffles fetched successfully", companyRaffles, nil) +} + func (h *Handler) GetRaffleStanding(c *fiber.Ctx) error { raffleIDStr := c.Params("id") limitStr := c.Params("limit") @@ -122,9 +142,10 @@ func (h *Handler) GetRaffleStanding(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch raffle standing") } - maskedRaffleStanding := []domain.RaffleStanding{} + maskedRaffleStanding := make([]domain.RaffleStandingRes, 0, len(raffleStanding)) + for _, standing := range raffleStanding { - maskedStanding := domain.RaffleStanding{ + maskedStanding := domain.RaffleStandingRes{ UserID: standing.UserID, RaffleID: standing.RaffleID, FirstName: standing.FirstName, diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index f94cf4b..94924e7 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -60,7 +60,7 @@ func (a *App) initAppRoutes() { a.fiber.Get("/", func(c *fiber.Ctx) error { return c.JSON(fiber.Map{ "message": "Welcome to the FortuneBet API", - "version": "1.0.dev14", + "version": "1.0.dev15", }) }) @@ -198,10 +198,11 @@ func (a *App) initAppRoutes() { // groupV1.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings) // Raffle Routes + tenant.Get("/raffle/list", h.GetTenantRaffles) + a.fiber.Get("/raffle/standing/:id/:limit", h.GetRaffleStanding) //This needs to be accessible by non-login user a.fiber.Post("/raffle/create", a.authMiddleware, h.CreateRaffle) a.fiber.Get("/raffle/delete/:id", a.authMiddleware, h.DeleteRaffle) a.fiber.Get("/raffle/company/:id", a.authMiddleware, h.GetRafflesOfCompany) - a.fiber.Get("/raffle/standing/:id/:limit", a.authMiddleware, h.GetRaffleStanding) a.fiber.Get("raffle/winners/:id/:limit", a.authMiddleware, h.GetRaffleWinners) a.fiber.Post("/raffle-ticket/create", a.authMiddleware, h.CreateRaffleTicket) a.fiber.Get("/raffle-ticket/:id", a.authMiddleware, h.GetUserRaffleTickets) @@ -209,9 +210,9 @@ func (a *App) initAppRoutes() { a.fiber.Get("/raffle-ticket/unsuspend/:id", a.authMiddleware, h.UnSuspendRaffleTicket) // Bonus Routes - groupV1.Get("/bonus", a.authMiddleware, h.GetBonusesByUserID) - groupV1.Get("/bonus/stats", a.authMiddleware, h.GetBonusStats) - groupV1.Post("/bonus/claim/:id", a.authMiddleware, h.ClaimBonus) + tenant.Get("/bonus", a.authMiddleware, h.GetBonusesByUserID) + tenant.Get("/bonus/stats", a.authMiddleware, h.GetBonusStats) + tenant.Post("/bonus/claim/:id", a.authMiddleware, h.ClaimBonus) // groupV1.Post("/bonus/create", a.authMiddleware, h.CreateBonusMultiplier) // groupV1.Put("/bonus/update", a.authMiddleware, h.UpdateBonusMultiplier) @@ -228,7 +229,7 @@ func (a *App) initAppRoutes() { groupV1.Get("/customer", a.authMiddleware, a.SuperAdminOnly, h.GetAllCustomers) groupV1.Get("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.GetCustomerByID) groupV1.Put("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateCustomer) - tenant.Get("/customer/:id/bets", a.authMiddleware, h.GetCustomerBets) + groupV1.Get("/customer/:id/bets", a.authMiddleware, h.GetCustomerBets) groupV1.Get("/admin", a.authMiddleware, h.GetAllAdmins) groupV1.Get("/admin/:id", a.authMiddleware, h.GetAdminByID) @@ -446,8 +447,8 @@ func (a *App) initAppRoutes() { groupV1.Get("/settings/:key", a.authMiddleware, a.SuperAdminOnly, h.GetGlobalSettingByKey) groupV1.Put("/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalSettingList) - tenant.Post("/settings", a.authMiddleware, h.SaveCompanySettingList) tenant.Get("/settings", a.authMiddleware, h.GetCompanySettingList) + tenant.Put("/settings", a.authMiddleware, h.SaveCompanySettingList) tenant.Delete("/settings/:key", a.authMiddleware, h.DeleteCompanySetting) tenant.Delete("/settings", a.authMiddleware, h.DeleteAllCompanySetting)