mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-18 16:01:18 +00:00
bc435747ea
Add richer backend models, handlers, services, and repository queries for admin campaigns, discounts, outreach, organizations, limit requests, and provisioning surfaces.
1027 lines
26 KiB
Go
1027 lines
26 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/warmbly/warmbly/internal/api/middleware"
|
|
"github.com/warmbly/warmbly/internal/errx"
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
)
|
|
|
|
// User Management Handlers
|
|
|
|
// AdminSearchUsers searches for users with pagination
|
|
func (h *Handler) AdminSearchUsers(c *gin.Context) {
|
|
var search models.AdminUserSearch
|
|
if err := c.ShouldBindQuery(&search); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid query parameters"))
|
|
return
|
|
}
|
|
|
|
result, xerr := h.AdminService.SearchUsers(c.Request.Context(), &search)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// AdminGetUser gets a user's details
|
|
func (h *Handler) AdminGetUser(c *gin.Context) {
|
|
userID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid user ID"))
|
|
return
|
|
}
|
|
|
|
user, xerr := h.AdminService.GetUserDetail(c.Request.Context(), userID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, user)
|
|
}
|
|
|
|
// AdminGetUserPreview gets a full preview of a user's account
|
|
func (h *Handler) AdminGetUserPreview(c *gin.Context) {
|
|
userID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid user ID"))
|
|
return
|
|
}
|
|
|
|
preview, xerr := h.AdminService.GetUserPreview(c.Request.Context(), userID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, preview)
|
|
}
|
|
|
|
// AdminBanUser bans a user
|
|
func (h *Handler) AdminBanUser(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
userID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid user ID"))
|
|
return
|
|
}
|
|
|
|
var req models.BanUserRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
xerr := h.AdminService.BanUser(c.Request.Context(), *adminID, userID, req.Reason, req.Scope, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "user banned successfully"})
|
|
}
|
|
|
|
// AdminUnbanUser unbans a user
|
|
func (h *Handler) AdminUnbanUser(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
userID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid user ID"))
|
|
return
|
|
}
|
|
|
|
var req models.UnbanUserRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
xerr := h.AdminService.UnbanUser(c.Request.Context(), *adminID, userID, req.Reason, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "user unbanned successfully"})
|
|
}
|
|
|
|
// AdminGetUserBans gets the ban history for a user
|
|
func (h *Handler) AdminGetUserBans(c *gin.Context) {
|
|
userID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid user ID"))
|
|
return
|
|
}
|
|
|
|
bans, xerr := h.AdminService.GetUserBans(c.Request.Context(), userID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"bans": bans})
|
|
}
|
|
|
|
// AdminGetUserCampaigns gets a user's campaigns
|
|
func (h *Handler) AdminGetUserCampaigns(c *gin.Context) {
|
|
userID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid user ID"))
|
|
return
|
|
}
|
|
|
|
cursor := parseCursor(c.Query("cursor"))
|
|
limit := parseLimit(c.Query("limit"), 50)
|
|
|
|
result, xerr := h.AdminService.GetUserCampaigns(c.Request.Context(), userID, cursor, limit)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// AdminGetUserEmails gets a user's email accounts
|
|
func (h *Handler) AdminGetUserEmails(c *gin.Context) {
|
|
userID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid user ID"))
|
|
return
|
|
}
|
|
|
|
cursor := parseCursor(c.Query("cursor"))
|
|
limit := parseLimit(c.Query("limit"), 50)
|
|
|
|
emails, pagination, xerr := h.AdminService.GetUserEmails(c.Request.Context(), userID, cursor, limit)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"data": emails, "pagination": pagination})
|
|
}
|
|
|
|
// AdminGetUserRateLimits gets a user's rate limits
|
|
func (h *Handler) AdminGetUserRateLimits(c *gin.Context) {
|
|
userID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid user ID"))
|
|
return
|
|
}
|
|
|
|
limits, xerr := h.AdminService.GetUserRateLimits(c.Request.Context(), userID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, limits)
|
|
}
|
|
|
|
// AdminUpdateUserRateLimits updates a user's rate limits
|
|
func (h *Handler) AdminUpdateUserRateLimits(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
userID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid user ID"))
|
|
return
|
|
}
|
|
|
|
var req models.UpdateUserRateLimitsRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
xerr := h.AdminService.UpdateUserRateLimits(c.Request.Context(), *adminID, userID, &req, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "rate limits updated successfully"})
|
|
}
|
|
|
|
// Worker Management Handlers
|
|
|
|
// AdminListWorkers lists all workers
|
|
func (h *Handler) AdminListWorkers(c *gin.Context) {
|
|
cursor := parseCursor(c.Query("cursor"))
|
|
limit := parseLimit(c.Query("limit"), 50)
|
|
|
|
result, xerr := h.AdminService.ListWorkers(c.Request.Context(), cursor, limit)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// AdminGetWorker gets a worker's details
|
|
func (h *Handler) AdminGetWorker(c *gin.Context) {
|
|
workerID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid worker ID"))
|
|
return
|
|
}
|
|
|
|
worker, xerr := h.AdminService.GetWorkerDetail(c.Request.Context(), workerID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, worker)
|
|
}
|
|
|
|
// AdminUpdateWorker updates a worker
|
|
func (h *Handler) AdminUpdateWorker(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
workerID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid worker ID"))
|
|
return
|
|
}
|
|
|
|
var req models.AdminUpdateWorker
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
xerr := h.AdminService.UpdateWorker(c.Request.Context(), *adminID, workerID, &req, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "worker updated successfully"})
|
|
}
|
|
|
|
// AdminGetWorkerEmails gets emails connected to a worker
|
|
func (h *Handler) AdminGetWorkerEmails(c *gin.Context) {
|
|
workerID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid worker ID"))
|
|
return
|
|
}
|
|
|
|
cursor := parseCursor(c.Query("cursor"))
|
|
limit := parseLimit(c.Query("limit"), 50)
|
|
|
|
emails, pagination, xerr := h.AdminService.GetWorkerEmails(c.Request.Context(), workerID, cursor, limit)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"data": emails, "pagination": pagination})
|
|
}
|
|
|
|
// AdminGetWorkerStats gets statistics for a worker
|
|
func (h *Handler) AdminGetWorkerStats(c *gin.Context) {
|
|
workerID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid worker ID"))
|
|
return
|
|
}
|
|
|
|
stats, xerr := h.AdminService.GetWorkerStats(c.Request.Context(), workerID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, stats)
|
|
}
|
|
|
|
// AdminReassignEmails reassigns emails to a new worker
|
|
func (h *Handler) AdminReassignEmails(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
workerID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid worker ID"))
|
|
return
|
|
}
|
|
|
|
var req models.ReassignEmailsRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
// Use the workerID from the URL as the target
|
|
xerr := h.AdminService.ReassignEmails(c.Request.Context(), *adminID, req.EmailIDs, workerID, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "emails reassigned successfully"})
|
|
}
|
|
|
|
// Warmup Management Handlers
|
|
|
|
// AdminListWarmupPools lists all warmup pools
|
|
func (h *Handler) AdminListWarmupPools(c *gin.Context) {
|
|
pools, xerr := h.AdminService.ListWarmupPools(c.Request.Context())
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"pools": pools})
|
|
}
|
|
|
|
// AdminGetWarmupHealthSummary returns an aggregate health overview of all warmup pools
|
|
func (h *Handler) AdminGetWarmupHealthSummary(c *gin.Context) {
|
|
if h.WarmupService == nil {
|
|
errx.JSON(c, errx.New(errx.Internal, "warmup service not available"))
|
|
return
|
|
}
|
|
summary, xerr := h.WarmupService.GetPoolHealthSummary(c.Request.Context())
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, summary)
|
|
}
|
|
|
|
// AdminGetPoolParticipants gets participants in a warmup pool
|
|
func (h *Handler) AdminGetPoolParticipants(c *gin.Context) {
|
|
poolType := c.Param("type")
|
|
cursor := parseCursor(c.Query("cursor"))
|
|
limit := parseLimit(c.Query("limit"), 50)
|
|
|
|
result, xerr := h.AdminService.GetPoolParticipants(c.Request.Context(), poolType, cursor, limit)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// AdminListBlockedAccounts lists blocked warmup accounts
|
|
func (h *Handler) AdminListBlockedAccounts(c *gin.Context) {
|
|
cursor := parseCursor(c.Query("cursor"))
|
|
limit := parseLimit(c.Query("limit"), 50)
|
|
|
|
result, xerr := h.AdminService.ListBlockedAccounts(c.Request.Context(), cursor, limit)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// AdminBlockAccount blocks an account from warmup
|
|
func (h *Handler) AdminBlockAccount(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
accountID, err := uuid.Parse(c.Param("accountId"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid account ID"))
|
|
return
|
|
}
|
|
|
|
var req models.BlockAccountRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
xerr := h.AdminService.BlockAccount(c.Request.Context(), *adminID, accountID, req.Reason, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "account blocked successfully"})
|
|
}
|
|
|
|
// AdminUnblockAccount unblocks an account from warmup
|
|
func (h *Handler) AdminUnblockAccount(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
accountID, err := uuid.Parse(c.Param("accountId"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid account ID"))
|
|
return
|
|
}
|
|
|
|
xerr := h.AdminService.UnblockAccount(c.Request.Context(), *adminID, accountID, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "account unblocked successfully"})
|
|
}
|
|
|
|
// Appeal Handlers
|
|
|
|
// AdminListAppeals lists warmup appeals
|
|
func (h *Handler) AdminListAppeals(c *gin.Context) {
|
|
status := c.Query("status")
|
|
cursor := parseCursor(c.Query("cursor"))
|
|
limit := parseLimit(c.Query("limit"), 50)
|
|
|
|
result, xerr := h.AdminService.ListAppeals(c.Request.Context(), status, cursor, limit)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// AdminGetAppeal gets a specific appeal
|
|
func (h *Handler) AdminGetAppeal(c *gin.Context) {
|
|
appealID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid appeal ID"))
|
|
return
|
|
}
|
|
|
|
appeal, xerr := h.AdminService.GetAppeal(c.Request.Context(), appealID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, appeal)
|
|
}
|
|
|
|
// AdminApproveAppeal approves a warmup appeal
|
|
func (h *Handler) AdminApproveAppeal(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
appealID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid appeal ID"))
|
|
return
|
|
}
|
|
|
|
var req models.ReviewAppealRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.ErrInvalid)
|
|
return
|
|
}
|
|
|
|
xerr := h.AdminService.ReviewAppeal(c.Request.Context(), *adminID, appealID, true, req.Notes, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "appeal approved successfully"})
|
|
}
|
|
|
|
// AdminRejectAppeal rejects a warmup appeal
|
|
func (h *Handler) AdminRejectAppeal(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
appealID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid appeal ID"))
|
|
return
|
|
}
|
|
|
|
var req models.ReviewAppealRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.ErrInvalid)
|
|
return
|
|
}
|
|
|
|
xerr := h.AdminService.ReviewAppeal(c.Request.Context(), *adminID, appealID, false, req.Notes, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "appeal rejected"})
|
|
}
|
|
|
|
// Campaign Management Handlers
|
|
|
|
// AdminSearchCampaigns searches for campaigns
|
|
func (h *Handler) AdminSearchCampaigns(c *gin.Context) {
|
|
var search models.AdminCampaignSearch
|
|
if err := c.ShouldBindQuery(&search); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid query parameters"))
|
|
return
|
|
}
|
|
|
|
result, xerr := h.AdminService.SearchCampaigns(c.Request.Context(), &search)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// AdminGetCampaign gets a campaign's details
|
|
func (h *Handler) AdminGetCampaign(c *gin.Context) {
|
|
campaignID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid campaign ID"))
|
|
return
|
|
}
|
|
|
|
campaign, xerr := h.AdminService.GetCampaignDetail(c.Request.Context(), campaignID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, campaign)
|
|
}
|
|
|
|
// AdminStopCampaign force-stops a campaign
|
|
func (h *Handler) AdminStopCampaign(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
campaignID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid campaign ID"))
|
|
return
|
|
}
|
|
|
|
xerr := h.AdminService.StopCampaign(c.Request.Context(), *adminID, campaignID, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "campaign stopped successfully"})
|
|
}
|
|
|
|
// Analytics Handlers
|
|
|
|
// AdminGetPlatformOverview gets platform overview statistics
|
|
func (h *Handler) AdminGetPlatformOverview(c *gin.Context) {
|
|
overview, xerr := h.AdminService.GetPlatformOverview(c.Request.Context())
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, overview)
|
|
}
|
|
|
|
// AdminGetAnalyticsTrends gets analytics trend data
|
|
func (h *Handler) AdminGetAnalyticsTrends(c *gin.Context) {
|
|
trends, xerr := h.AdminService.GetAnalyticsTrends(c.Request.Context())
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, trends)
|
|
}
|
|
|
|
// AdminGetDailyEmailStats gets daily email statistics
|
|
func (h *Handler) AdminGetDailyEmailStats(c *gin.Context) {
|
|
startDate, endDate := parseDateRange(c)
|
|
|
|
stats, xerr := h.AdminService.GetDailyEmailStats(c.Request.Context(), startDate, endDate)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"stats": stats})
|
|
}
|
|
|
|
// AdminGetHourlyEmailStats gets hourly email statistics
|
|
func (h *Handler) AdminGetHourlyEmailStats(c *gin.Context) {
|
|
dateStr := c.Query("date")
|
|
date := time.Now()
|
|
if dateStr != "" {
|
|
if parsed, err := time.Parse("2006-01-02", dateStr); err == nil {
|
|
date = parsed
|
|
}
|
|
}
|
|
|
|
stats, xerr := h.AdminService.GetHourlyEmailStats(c.Request.Context(), date)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"stats": stats})
|
|
}
|
|
|
|
// AdminGetWorkerLoadStats gets worker load statistics
|
|
func (h *Handler) AdminGetWorkerLoadStats(c *gin.Context) {
|
|
stats, xerr := h.AdminService.GetWorkerLoadStats(c.Request.Context())
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"stats": stats})
|
|
}
|
|
|
|
// AdminGetEmailDistribution gets email distribution across workers
|
|
func (h *Handler) AdminGetEmailDistribution(c *gin.Context) {
|
|
dist, xerr := h.AdminService.GetEmailDistribution(c.Request.Context())
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"distribution": dist})
|
|
}
|
|
|
|
// AdminGetUserGrowthStats gets user growth statistics
|
|
func (h *Handler) AdminGetUserGrowthStats(c *gin.Context) {
|
|
startDate, endDate := parseDateRange(c)
|
|
|
|
stats, xerr := h.AdminService.GetUserGrowthStats(c.Request.Context(), startDate, endDate)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"stats": stats})
|
|
}
|
|
|
|
// Plan Management Handlers
|
|
|
|
// AdminListPlans lists the plan catalog with the shared faceted query params;
|
|
// returns the standard {data, pagination} envelope.
|
|
func (h *Handler) AdminListPlans(c *gin.Context) {
|
|
var search models.AdminPlanSearch
|
|
if err := c.ShouldBindQuery(&search); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid query parameters"))
|
|
return
|
|
}
|
|
// duration is durations.title (free text, no DB enum) — validate app-side.
|
|
if search.Duration != "" && search.Duration != "month" && search.Duration != "year" {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid duration"))
|
|
return
|
|
}
|
|
|
|
result, xerr := h.AdminService.SearchPlansForAdmin(c.Request.Context(), &search)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// AdminCreatePlan creates a new plan
|
|
func (h *Handler) AdminCreatePlan(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
var req models.CreatePlanRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
plan, xerr := h.AdminService.CreatePlan(c.Request.Context(), *adminID, &req, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, plan)
|
|
}
|
|
|
|
// AdminGetPlan gets a plan by ID
|
|
func (h *Handler) AdminGetPlan(c *gin.Context) {
|
|
planID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid plan ID"))
|
|
return
|
|
}
|
|
|
|
plan, xerr := h.AdminService.GetPlan(c.Request.Context(), planID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, plan)
|
|
}
|
|
|
|
// AdminUpdatePlan updates a plan
|
|
func (h *Handler) AdminUpdatePlan(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
planID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid plan ID"))
|
|
return
|
|
}
|
|
|
|
var req models.UpdatePlanRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
plan, xerr := h.AdminService.UpdatePlan(c.Request.Context(), *adminID, planID, &req, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, plan)
|
|
}
|
|
|
|
// AdminDeletePlan deletes a plan
|
|
func (h *Handler) AdminDeletePlan(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
planID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid plan ID"))
|
|
return
|
|
}
|
|
|
|
xerr := h.AdminService.DeletePlan(c.Request.Context(), *adminID, planID, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "plan deleted successfully"})
|
|
}
|
|
|
|
// Enterprise Inquiry Handlers
|
|
|
|
// AdminListEnterpriseInquiries lists enterprise inquiries with the shared
|
|
// faceted query params; returns the standard {data, pagination} envelope.
|
|
func (h *Handler) AdminListEnterpriseInquiries(c *gin.Context) {
|
|
var search models.AdminEnterpriseInquirySearch
|
|
if err := c.ShouldBindQuery(&search); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid query parameters"))
|
|
return
|
|
}
|
|
|
|
result, xerr := h.AdminService.ListEnterpriseInquiries(c.Request.Context(), &search)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// AdminGetEnterpriseInquiry gets a specific enterprise inquiry
|
|
func (h *Handler) AdminGetEnterpriseInquiry(c *gin.Context) {
|
|
inquiryID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid inquiry ID"))
|
|
return
|
|
}
|
|
|
|
inquiry, xerr := h.AdminService.GetEnterpriseInquiry(c.Request.Context(), inquiryID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, inquiry)
|
|
}
|
|
|
|
// AdminUpdateEnterpriseInquiry updates an enterprise inquiry
|
|
func (h *Handler) AdminUpdateEnterpriseInquiry(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
inquiryID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid inquiry ID"))
|
|
return
|
|
}
|
|
|
|
var req models.UpdateEnterpriseInquiryRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
xerr := h.AdminService.UpdateEnterpriseInquiry(c.Request.Context(), *adminID, inquiryID, &req, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "inquiry updated successfully"})
|
|
}
|
|
|
|
// Admin Management Handlers
|
|
|
|
// AdminListAdmins lists all admin users
|
|
func (h *Handler) AdminListAdmins(c *gin.Context) {
|
|
cursor := parseCursor(c.Query("cursor"))
|
|
limit := parseLimit(c.Query("limit"), 50)
|
|
|
|
result, xerr := h.AdminService.ListAdmins(c.Request.Context(), cursor, limit)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// AdminGrantPermissions grants admin permissions to a user
|
|
func (h *Handler) AdminGrantPermissions(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
userID, err := uuid.Parse(c.Param("userId"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid user ID"))
|
|
return
|
|
}
|
|
|
|
var req models.GrantAdminRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
xerr := h.AdminService.GrantAdminPermissions(c.Request.Context(), *adminID, userID, req.Permissions, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "admin permissions granted successfully"})
|
|
}
|
|
|
|
// AdminRevokePermissions revokes admin permissions from a user
|
|
func (h *Handler) AdminRevokePermissions(c *gin.Context) {
|
|
adminID := middleware.GetAdminUserID(c)
|
|
if adminID == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
|
|
userID, err := uuid.Parse(c.Param("userId"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid user ID"))
|
|
return
|
|
}
|
|
|
|
xerr := h.AdminService.RevokeAdminPermissions(c.Request.Context(), *adminID, userID, c.ClientIP(), c.GetHeader("User-Agent"))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "admin permissions revoked successfully"})
|
|
}
|
|
|
|
// AdminSearchAuditLogs searches audit logs
|
|
func (h *Handler) AdminSearchAuditLogs(c *gin.Context) {
|
|
var search models.AdminAuditLogSearch
|
|
if err := c.ShouldBindQuery(&search); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid query parameters"))
|
|
return
|
|
}
|
|
|
|
result, xerr := h.AdminService.SearchAuditLogs(c.Request.Context(), &search)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// AdminGetPermissionList returns the list of available admin permissions
|
|
func (h *Handler) AdminGetPermissionList(c *gin.Context) {
|
|
permissions := models.GetAllPermissionInfos()
|
|
c.JSON(http.StatusOK, gin.H{"permissions": permissions})
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func parseCursor(s string) *uuid.UUID {
|
|
if s == "" {
|
|
return nil
|
|
}
|
|
id, err := uuid.Parse(s)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return &id
|
|
}
|
|
|
|
func parseLimit(s string, defaultLimit int) int {
|
|
if s == "" {
|
|
return defaultLimit
|
|
}
|
|
limit, err := strconv.Atoi(s)
|
|
if err != nil || limit <= 0 {
|
|
return defaultLimit
|
|
}
|
|
if limit > 100 {
|
|
return 100
|
|
}
|
|
return limit
|
|
}
|
|
|
|
func parseDateRange(c *gin.Context) (time.Time, time.Time) {
|
|
now := time.Now()
|
|
endDate := now
|
|
startDate := now.AddDate(0, 0, -30) // Default to last 30 days
|
|
|
|
if s := c.Query("start_date"); s != "" {
|
|
if t, err := time.Parse("2006-01-02", s); err == nil {
|
|
startDate = t
|
|
}
|
|
}
|
|
if s := c.Query("end_date"); s != "" {
|
|
if t, err := time.Parse("2006-01-02", s); err == nil {
|
|
endDate = t
|
|
}
|
|
}
|
|
|
|
return startDate, endDate
|
|
}
|