Migrate from Gin to Echo framework and add comprehensive integration tests
Major changes: - Migrate all handlers from Gin to Echo framework - Add new apperrors, echohelpers, and validator packages - Update middleware for Echo compatibility - Add ArchivedHandler to task categorization chain (archived tasks go to cancelled_tasks column) - Add 6 new integration tests: - RecurringTaskLifecycle: NextDueDate advancement for weekly/monthly tasks - MultiUserSharing: Complex sharing with user removal - TaskStateTransitions: All state transitions and kanban column changes - DateBoundaryEdgeCases: Threshold boundary testing - CascadeOperations: Residence deletion cascade effects - MultiUserOperations: Shared residence collaboration - Add single-purpose repository functions for kanban columns (GetOverdueTasks, GetDueSoonTasks, etc.) - Fix RemoveUser route param mismatch (userId -> user_id) - Fix determineExpectedColumn helper to correctly prioritize in_progress over overdue 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,11 +2,11 @@ package dto
|
||||
|
||||
// PaginationParams holds pagination query parameters
|
||||
type PaginationParams struct {
|
||||
Page int `form:"page" binding:"omitempty,min=1"`
|
||||
PerPage int `form:"per_page" binding:"omitempty,min=1,max=10000"`
|
||||
Page int `form:"page" validate:"omitempty,min=1"`
|
||||
PerPage int `form:"per_page" validate:"omitempty,min=1,max=10000"`
|
||||
Search string `form:"search"`
|
||||
SortBy string `form:"sort_by"`
|
||||
SortDir string `form:"sort_dir" binding:"omitempty,oneof=asc desc"`
|
||||
SortDir string `form:"sort_dir" validate:"omitempty,oneof=asc desc"`
|
||||
}
|
||||
|
||||
// GetPage returns the page number with default
|
||||
@@ -52,12 +52,12 @@ type UserFilters struct {
|
||||
|
||||
// CreateUserRequest for creating a new user
|
||||
type CreateUserRequest struct {
|
||||
Username string `json:"username" binding:"required,min=3,max=150"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required,min=8"`
|
||||
FirstName string `json:"first_name" binding:"max=150"`
|
||||
LastName string `json:"last_name" binding:"max=150"`
|
||||
PhoneNumber string `json:"phone_number" binding:"max=20"`
|
||||
Username string `json:"username" validate:"required,min=3,max=150"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Password string `json:"password" validate:"required,min=8"`
|
||||
FirstName string `json:"first_name" validate:"max=150"`
|
||||
LastName string `json:"last_name" validate:"max=150"`
|
||||
PhoneNumber string `json:"phone_number" validate:"max=20"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
IsStaff *bool `json:"is_staff"`
|
||||
IsSuperuser *bool `json:"is_superuser"`
|
||||
@@ -65,12 +65,12 @@ type CreateUserRequest struct {
|
||||
|
||||
// UpdateUserRequest for updating a user
|
||||
type UpdateUserRequest struct {
|
||||
Username *string `json:"username" binding:"omitempty,min=3,max=150"`
|
||||
Email *string `json:"email" binding:"omitempty,email"`
|
||||
Password *string `json:"password" binding:"omitempty,min=8"`
|
||||
FirstName *string `json:"first_name" binding:"omitempty,max=150"`
|
||||
LastName *string `json:"last_name" binding:"omitempty,max=150"`
|
||||
PhoneNumber *string `json:"phone_number" binding:"omitempty,max=20"`
|
||||
Username *string `json:"username" validate:"omitempty,min=3,max=150"`
|
||||
Email *string `json:"email" validate:"omitempty,email"`
|
||||
Password *string `json:"password" validate:"omitempty,min=8"`
|
||||
FirstName *string `json:"first_name" validate:"omitempty,max=150"`
|
||||
LastName *string `json:"last_name" validate:"omitempty,max=150"`
|
||||
PhoneNumber *string `json:"phone_number" validate:"omitempty,max=20"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
IsStaff *bool `json:"is_staff"`
|
||||
IsSuperuser *bool `json:"is_superuser"`
|
||||
@@ -79,7 +79,7 @@ type UpdateUserRequest struct {
|
||||
|
||||
// BulkDeleteRequest for bulk delete operations
|
||||
type BulkDeleteRequest struct {
|
||||
IDs []uint `json:"ids" binding:"required,min=1"`
|
||||
IDs []uint `json:"ids" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
// ResidenceFilters holds residence-specific filter parameters
|
||||
@@ -92,14 +92,14 @@ type ResidenceFilters struct {
|
||||
// UpdateResidenceRequest for updating a residence
|
||||
type UpdateResidenceRequest struct {
|
||||
OwnerID *uint `json:"owner_id"`
|
||||
Name *string `json:"name" binding:"omitempty,max=200"`
|
||||
Name *string `json:"name" validate:"omitempty,max=200"`
|
||||
PropertyTypeID *uint `json:"property_type_id"`
|
||||
StreetAddress *string `json:"street_address" binding:"omitempty,max=255"`
|
||||
ApartmentUnit *string `json:"apartment_unit" binding:"omitempty,max=50"`
|
||||
City *string `json:"city" binding:"omitempty,max=100"`
|
||||
StateProvince *string `json:"state_province" binding:"omitempty,max=100"`
|
||||
PostalCode *string `json:"postal_code" binding:"omitempty,max=20"`
|
||||
Country *string `json:"country" binding:"omitempty,max=100"`
|
||||
StreetAddress *string `json:"street_address" validate:"omitempty,max=255"`
|
||||
ApartmentUnit *string `json:"apartment_unit" validate:"omitempty,max=50"`
|
||||
City *string `json:"city" validate:"omitempty,max=100"`
|
||||
StateProvince *string `json:"state_province" validate:"omitempty,max=100"`
|
||||
PostalCode *string `json:"postal_code" validate:"omitempty,max=20"`
|
||||
Country *string `json:"country" validate:"omitempty,max=100"`
|
||||
Bedrooms *int `json:"bedrooms"`
|
||||
Bathrooms *float64 `json:"bathrooms"`
|
||||
SquareFootage *int `json:"square_footage"`
|
||||
@@ -128,7 +128,7 @@ type UpdateTaskRequest struct {
|
||||
ResidenceID *uint `json:"residence_id"`
|
||||
CreatedByID *uint `json:"created_by_id"`
|
||||
AssignedToID *uint `json:"assigned_to_id"`
|
||||
Title *string `json:"title" binding:"omitempty,max=200"`
|
||||
Title *string `json:"title" validate:"omitempty,max=200"`
|
||||
Description *string `json:"description"`
|
||||
CategoryID *uint `json:"category_id"`
|
||||
PriorityID *uint `json:"priority_id"`
|
||||
@@ -156,16 +156,16 @@ type ContractorFilters struct {
|
||||
type UpdateContractorRequest struct {
|
||||
ResidenceID *uint `json:"residence_id"`
|
||||
CreatedByID *uint `json:"created_by_id"`
|
||||
Name *string `json:"name" binding:"omitempty,max=200"`
|
||||
Company *string `json:"company" binding:"omitempty,max=200"`
|
||||
Phone *string `json:"phone" binding:"omitempty,max=20"`
|
||||
Email *string `json:"email" binding:"omitempty,email"`
|
||||
Website *string `json:"website" binding:"omitempty,max=200"`
|
||||
Name *string `json:"name" validate:"omitempty,max=200"`
|
||||
Company *string `json:"company" validate:"omitempty,max=200"`
|
||||
Phone *string `json:"phone" validate:"omitempty,max=20"`
|
||||
Email *string `json:"email" validate:"omitempty,email"`
|
||||
Website *string `json:"website" validate:"omitempty,max=200"`
|
||||
Notes *string `json:"notes"`
|
||||
StreetAddress *string `json:"street_address" binding:"omitempty,max=255"`
|
||||
City *string `json:"city" binding:"omitempty,max=100"`
|
||||
StateProvince *string `json:"state_province" binding:"omitempty,max=100"`
|
||||
PostalCode *string `json:"postal_code" binding:"omitempty,max=20"`
|
||||
StreetAddress *string `json:"street_address" validate:"omitempty,max=255"`
|
||||
City *string `json:"city" validate:"omitempty,max=100"`
|
||||
StateProvince *string `json:"state_province" validate:"omitempty,max=100"`
|
||||
PostalCode *string `json:"postal_code" validate:"omitempty,max=20"`
|
||||
Rating *float64 `json:"rating"`
|
||||
IsFavorite *bool `json:"is_favorite"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
@@ -184,24 +184,24 @@ type DocumentFilters struct {
|
||||
type UpdateDocumentRequest struct {
|
||||
ResidenceID *uint `json:"residence_id"`
|
||||
CreatedByID *uint `json:"created_by_id"`
|
||||
Title *string `json:"title" binding:"omitempty,max=200"`
|
||||
Title *string `json:"title" validate:"omitempty,max=200"`
|
||||
Description *string `json:"description"`
|
||||
DocumentType *string `json:"document_type"`
|
||||
FileURL *string `json:"file_url" binding:"omitempty,max=500"`
|
||||
FileName *string `json:"file_name" binding:"omitempty,max=255"`
|
||||
FileURL *string `json:"file_url" validate:"omitempty,max=500"`
|
||||
FileName *string `json:"file_name" validate:"omitempty,max=255"`
|
||||
FileSize *int64 `json:"file_size"`
|
||||
MimeType *string `json:"mime_type" binding:"omitempty,max=100"`
|
||||
MimeType *string `json:"mime_type" validate:"omitempty,max=100"`
|
||||
PurchaseDate *string `json:"purchase_date"`
|
||||
ExpiryDate *string `json:"expiry_date"`
|
||||
PurchasePrice *float64 `json:"purchase_price"`
|
||||
Vendor *string `json:"vendor" binding:"omitempty,max=200"`
|
||||
SerialNumber *string `json:"serial_number" binding:"omitempty,max=100"`
|
||||
ModelNumber *string `json:"model_number" binding:"omitempty,max=100"`
|
||||
Provider *string `json:"provider" binding:"omitempty,max=200"`
|
||||
ProviderContact *string `json:"provider_contact" binding:"omitempty,max=200"`
|
||||
ClaimPhone *string `json:"claim_phone" binding:"omitempty,max=50"`
|
||||
ClaimEmail *string `json:"claim_email" binding:"omitempty,email"`
|
||||
ClaimWebsite *string `json:"claim_website" binding:"omitempty,max=500"`
|
||||
Vendor *string `json:"vendor" validate:"omitempty,max=200"`
|
||||
SerialNumber *string `json:"serial_number" validate:"omitempty,max=100"`
|
||||
ModelNumber *string `json:"model_number" validate:"omitempty,max=100"`
|
||||
Provider *string `json:"provider" validate:"omitempty,max=200"`
|
||||
ProviderContact *string `json:"provider_contact" validate:"omitempty,max=200"`
|
||||
ClaimPhone *string `json:"claim_phone" validate:"omitempty,max=50"`
|
||||
ClaimEmail *string `json:"claim_email" validate:"omitempty,email"`
|
||||
ClaimWebsite *string `json:"claim_website" validate:"omitempty,max=500"`
|
||||
Notes *string `json:"notes"`
|
||||
TaskID *uint `json:"task_id"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
@@ -218,8 +218,8 @@ type NotificationFilters struct {
|
||||
|
||||
// UpdateNotificationRequest for updating a notification
|
||||
type UpdateNotificationRequest struct {
|
||||
Title *string `json:"title" binding:"omitempty,max=200"`
|
||||
Body *string `json:"body" binding:"omitempty,max=1000"`
|
||||
Title *string `json:"title" validate:"omitempty,max=200"`
|
||||
Body *string `json:"body" validate:"omitempty,max=1000"`
|
||||
Read *bool `json:"read"`
|
||||
}
|
||||
|
||||
@@ -235,10 +235,10 @@ type SubscriptionFilters struct {
|
||||
|
||||
// UpdateSubscriptionRequest for updating a subscription
|
||||
type UpdateSubscriptionRequest struct {
|
||||
Tier *string `json:"tier" binding:"omitempty,oneof=free premium pro"`
|
||||
Tier *string `json:"tier" validate:"omitempty,oneof=free premium pro"`
|
||||
AutoRenew *bool `json:"auto_renew"`
|
||||
IsFree *bool `json:"is_free"`
|
||||
Platform *string `json:"platform" binding:"omitempty,max=20"`
|
||||
Platform *string `json:"platform" validate:"omitempty,max=20"`
|
||||
SubscribedAt *string `json:"subscribed_at"`
|
||||
ExpiresAt *string `json:"expires_at"`
|
||||
CancelledAt *string `json:"cancelled_at"`
|
||||
@@ -246,15 +246,15 @@ type UpdateSubscriptionRequest struct {
|
||||
|
||||
// CreateResidenceRequest for creating a new residence
|
||||
type CreateResidenceRequest struct {
|
||||
OwnerID uint `json:"owner_id" binding:"required"`
|
||||
Name string `json:"name" binding:"required,max=200"`
|
||||
OwnerID uint `json:"owner_id" validate:"required"`
|
||||
Name string `json:"name" validate:"required,max=200"`
|
||||
PropertyTypeID *uint `json:"property_type_id"`
|
||||
StreetAddress string `json:"street_address" binding:"max=255"`
|
||||
ApartmentUnit string `json:"apartment_unit" binding:"max=50"`
|
||||
City string `json:"city" binding:"max=100"`
|
||||
StateProvince string `json:"state_province" binding:"max=100"`
|
||||
PostalCode string `json:"postal_code" binding:"max=20"`
|
||||
Country string `json:"country" binding:"max=100"`
|
||||
StreetAddress string `json:"street_address" validate:"max=255"`
|
||||
ApartmentUnit string `json:"apartment_unit" validate:"max=50"`
|
||||
City string `json:"city" validate:"max=100"`
|
||||
StateProvince string `json:"state_province" validate:"max=100"`
|
||||
PostalCode string `json:"postal_code" validate:"max=20"`
|
||||
Country string `json:"country" validate:"max=100"`
|
||||
Bedrooms *int `json:"bedrooms"`
|
||||
Bathrooms *float64 `json:"bathrooms"`
|
||||
SquareFootage *int `json:"square_footage"`
|
||||
@@ -265,9 +265,9 @@ type CreateResidenceRequest struct {
|
||||
|
||||
// CreateTaskRequest for creating a new task
|
||||
type CreateTaskRequest struct {
|
||||
ResidenceID uint `json:"residence_id" binding:"required"`
|
||||
CreatedByID uint `json:"created_by_id" binding:"required"`
|
||||
Title string `json:"title" binding:"required,max=200"`
|
||||
ResidenceID uint `json:"residence_id" validate:"required"`
|
||||
CreatedByID uint `json:"created_by_id" validate:"required"`
|
||||
Title string `json:"title" validate:"required,max=200"`
|
||||
Description string `json:"description"`
|
||||
CategoryID *uint `json:"category_id"`
|
||||
PriorityID *uint `json:"priority_id"`
|
||||
@@ -282,51 +282,51 @@ type CreateTaskRequest struct {
|
||||
// CreateContractorRequest for creating a new contractor
|
||||
type CreateContractorRequest struct {
|
||||
ResidenceID *uint `json:"residence_id"`
|
||||
CreatedByID uint `json:"created_by_id" binding:"required"`
|
||||
Name string `json:"name" binding:"required,max=200"`
|
||||
Company string `json:"company" binding:"max=200"`
|
||||
Phone string `json:"phone" binding:"max=20"`
|
||||
Email string `json:"email" binding:"omitempty,email"`
|
||||
Website string `json:"website" binding:"max=200"`
|
||||
CreatedByID uint `json:"created_by_id" validate:"required"`
|
||||
Name string `json:"name" validate:"required,max=200"`
|
||||
Company string `json:"company" validate:"max=200"`
|
||||
Phone string `json:"phone" validate:"max=20"`
|
||||
Email string `json:"email" validate:"omitempty,email"`
|
||||
Website string `json:"website" validate:"max=200"`
|
||||
Notes string `json:"notes"`
|
||||
StreetAddress string `json:"street_address" binding:"max=255"`
|
||||
City string `json:"city" binding:"max=100"`
|
||||
StateProvince string `json:"state_province" binding:"max=100"`
|
||||
PostalCode string `json:"postal_code" binding:"max=20"`
|
||||
StreetAddress string `json:"street_address" validate:"max=255"`
|
||||
City string `json:"city" validate:"max=100"`
|
||||
StateProvince string `json:"state_province" validate:"max=100"`
|
||||
PostalCode string `json:"postal_code" validate:"max=20"`
|
||||
IsFavorite bool `json:"is_favorite"`
|
||||
SpecialtyIDs []uint `json:"specialty_ids"`
|
||||
}
|
||||
|
||||
// CreateDocumentRequest for creating a new document
|
||||
type CreateDocumentRequest struct {
|
||||
ResidenceID uint `json:"residence_id" binding:"required"`
|
||||
CreatedByID uint `json:"created_by_id" binding:"required"`
|
||||
Title string `json:"title" binding:"required,max=200"`
|
||||
ResidenceID uint `json:"residence_id" validate:"required"`
|
||||
CreatedByID uint `json:"created_by_id" validate:"required"`
|
||||
Title string `json:"title" validate:"required,max=200"`
|
||||
Description string `json:"description"`
|
||||
DocumentType string `json:"document_type" binding:"omitempty,oneof=general warranty receipt contract insurance manual"`
|
||||
FileURL string `json:"file_url" binding:"max=500"`
|
||||
FileName string `json:"file_name" binding:"max=255"`
|
||||
DocumentType string `json:"document_type" validate:"omitempty,oneof=general warranty receipt contract insurance manual"`
|
||||
FileURL string `json:"file_url" validate:"max=500"`
|
||||
FileName string `json:"file_name" validate:"max=255"`
|
||||
FileSize *int64 `json:"file_size"`
|
||||
MimeType string `json:"mime_type" binding:"max=100"`
|
||||
MimeType string `json:"mime_type" validate:"max=100"`
|
||||
PurchaseDate *string `json:"purchase_date"`
|
||||
ExpiryDate *string `json:"expiry_date"`
|
||||
PurchasePrice *float64 `json:"purchase_price"`
|
||||
Vendor string `json:"vendor" binding:"max=200"`
|
||||
SerialNumber string `json:"serial_number" binding:"max=100"`
|
||||
ModelNumber string `json:"model_number" binding:"max=100"`
|
||||
Vendor string `json:"vendor" validate:"max=200"`
|
||||
SerialNumber string `json:"serial_number" validate:"max=100"`
|
||||
ModelNumber string `json:"model_number" validate:"max=100"`
|
||||
TaskID *uint `json:"task_id"`
|
||||
}
|
||||
|
||||
// SendTestNotificationRequest for sending a test push notification
|
||||
type SendTestNotificationRequest struct {
|
||||
UserID uint `json:"user_id" binding:"required"`
|
||||
Title string `json:"title" binding:"required,max=200"`
|
||||
Body string `json:"body" binding:"required,max=500"`
|
||||
UserID uint `json:"user_id" validate:"required"`
|
||||
Title string `json:"title" validate:"required,max=200"`
|
||||
Body string `json:"body" validate:"required,max=500"`
|
||||
}
|
||||
|
||||
// SendTestEmailRequest for sending a test email
|
||||
type SendTestEmailRequest struct {
|
||||
UserID uint `json:"user_id" binding:"required"`
|
||||
Subject string `json:"subject" binding:"required,max=200"`
|
||||
Body string `json:"body" binding:"required"`
|
||||
UserID uint `json:"user_id" validate:"required"`
|
||||
Subject string `json:"subject" validate:"required,max=200"`
|
||||
Body string `json:"body" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -52,11 +52,10 @@ type UpdateAdminUserRequest struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/admin-users
|
||||
func (h *AdminUserManagementHandler) List(c *gin.Context) {
|
||||
func (h *AdminUserManagementHandler) List(c echo.Context) error {
|
||||
var filters AdminUserFilters
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var adminUsers []models.AdminUser
|
||||
@@ -92,8 +91,7 @@ func (h *AdminUserManagementHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&adminUsers).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch admin users"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch admin users"})
|
||||
}
|
||||
|
||||
responses := make([]AdminUserResponse, len(adminUsers))
|
||||
@@ -101,56 +99,49 @@ func (h *AdminUserManagementHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toAdminUserResponse(&u)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/admin-users/:id
|
||||
func (h *AdminUserManagementHandler) Get(c *gin.Context) {
|
||||
func (h *AdminUserManagementHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid admin user ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid admin user ID"})
|
||||
}
|
||||
|
||||
var adminUser models.AdminUser
|
||||
if err := h.db.First(&adminUser, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Admin user not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Admin user not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch admin user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch admin user"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toAdminUserResponse(&adminUser))
|
||||
return c.JSON(http.StatusOK, h.toAdminUserResponse(&adminUser))
|
||||
}
|
||||
|
||||
// Create handles POST /api/admin/admin-users
|
||||
func (h *AdminUserManagementHandler) Create(c *gin.Context) {
|
||||
func (h *AdminUserManagementHandler) Create(c echo.Context) error {
|
||||
// Only super admins can create admin users
|
||||
currentAdmin, exists := c.Get(middleware.AdminUserKey)
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
currentAdmin := c.Get(middleware.AdminUserKey)
|
||||
if currentAdmin == nil {
|
||||
return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "Unauthorized"})
|
||||
}
|
||||
admin := currentAdmin.(*models.AdminUser)
|
||||
if !admin.IsSuperAdmin() {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Only super admins can create admin users"})
|
||||
return
|
||||
return c.JSON(http.StatusForbidden, map[string]interface{}{"error": "Only super admins can create admin users"})
|
||||
}
|
||||
|
||||
var req CreateAdminUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Check if email already exists
|
||||
var existingCount int64
|
||||
h.db.Model(&models.AdminUser{}).Where("email = ?", req.Email).Count(&existingCount)
|
||||
if existingCount > 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Email already exists"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Email already exists"})
|
||||
}
|
||||
|
||||
adminUser := models.AdminUser{
|
||||
@@ -169,54 +160,46 @@ func (h *AdminUserManagementHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := adminUser.SetPassword(req.Password); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to hash password"})
|
||||
}
|
||||
|
||||
if err := h.db.Create(&adminUser).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create admin user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create admin user"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, h.toAdminUserResponse(&adminUser))
|
||||
return c.JSON(http.StatusCreated, h.toAdminUserResponse(&adminUser))
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/admin-users/:id
|
||||
func (h *AdminUserManagementHandler) Update(c *gin.Context) {
|
||||
func (h *AdminUserManagementHandler) Update(c echo.Context) error {
|
||||
// Only super admins can update admin users
|
||||
currentAdmin, exists := c.Get(middleware.AdminUserKey)
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
currentAdmin := c.Get(middleware.AdminUserKey)
|
||||
if currentAdmin == nil {
|
||||
return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "Unauthorized"})
|
||||
}
|
||||
admin := currentAdmin.(*models.AdminUser)
|
||||
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid admin user ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid admin user ID"})
|
||||
}
|
||||
|
||||
// Allow users to update themselves, but only super admins can update others
|
||||
if uint(id) != admin.ID && !admin.IsSuperAdmin() {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Only super admins can update other admin users"})
|
||||
return
|
||||
return c.JSON(http.StatusForbidden, map[string]interface{}{"error": "Only super admins can update other admin users"})
|
||||
}
|
||||
|
||||
var adminUser models.AdminUser
|
||||
if err := h.db.First(&adminUser, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Admin user not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Admin user not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch admin user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch admin user"})
|
||||
}
|
||||
|
||||
var req UpdateAdminUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if req.Email != nil {
|
||||
@@ -224,8 +207,7 @@ func (h *AdminUserManagementHandler) Update(c *gin.Context) {
|
||||
var existingCount int64
|
||||
h.db.Model(&models.AdminUser{}).Where("email = ? AND id != ?", *req.Email, id).Count(&existingCount)
|
||||
if existingCount > 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Email already exists"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Email already exists"})
|
||||
}
|
||||
adminUser.Email = *req.Email
|
||||
}
|
||||
@@ -242,68 +224,58 @@ func (h *AdminUserManagementHandler) Update(c *gin.Context) {
|
||||
if req.IsActive != nil && admin.IsSuperAdmin() {
|
||||
// Prevent disabling yourself
|
||||
if uint(id) == admin.ID && !*req.IsActive {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot deactivate your own account"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Cannot deactivate your own account"})
|
||||
}
|
||||
adminUser.IsActive = *req.IsActive
|
||||
}
|
||||
if req.Password != nil {
|
||||
if err := adminUser.SetPassword(*req.Password); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to hash password"})
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.db.Save(&adminUser).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update admin user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update admin user"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toAdminUserResponse(&adminUser))
|
||||
return c.JSON(http.StatusOK, h.toAdminUserResponse(&adminUser))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/admin-users/:id
|
||||
func (h *AdminUserManagementHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminUserManagementHandler) Delete(c echo.Context) error {
|
||||
// Only super admins can delete admin users
|
||||
currentAdmin, exists := c.Get(middleware.AdminUserKey)
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
currentAdmin := c.Get(middleware.AdminUserKey)
|
||||
if currentAdmin == nil {
|
||||
return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "Unauthorized"})
|
||||
}
|
||||
admin := currentAdmin.(*models.AdminUser)
|
||||
if !admin.IsSuperAdmin() {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Only super admins can delete admin users"})
|
||||
return
|
||||
return c.JSON(http.StatusForbidden, map[string]interface{}{"error": "Only super admins can delete admin users"})
|
||||
}
|
||||
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid admin user ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid admin user ID"})
|
||||
}
|
||||
|
||||
// Prevent self-deletion
|
||||
if uint(id) == admin.ID {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot delete your own account"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Cannot delete your own account"})
|
||||
}
|
||||
|
||||
var adminUser models.AdminUser
|
||||
if err := h.db.First(&adminUser, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Admin user not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Admin user not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch admin user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch admin user"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&adminUser).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete admin user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete admin user"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Admin user deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Admin user deleted successfully"})
|
||||
}
|
||||
|
||||
func (h *AdminUserManagementHandler) toAdminUserResponse(u *models.AdminUser) AdminUserResponse {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -41,11 +41,10 @@ type UpdateAppleSocialAuthRequest struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/apple-social-auth
|
||||
func (h *AdminAppleSocialAuthHandler) List(c *gin.Context) {
|
||||
func (h *AdminAppleSocialAuthHandler) List(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var entries []models.AppleSocialAuth
|
||||
@@ -75,8 +74,7 @@ func (h *AdminAppleSocialAuthHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&entries).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch Apple social auth entries"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch Apple social auth entries"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -85,73 +83,63 @@ func (h *AdminAppleSocialAuthHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toResponse(&entry)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/apple-social-auth/:id
|
||||
func (h *AdminAppleSocialAuthHandler) Get(c *gin.Context) {
|
||||
func (h *AdminAppleSocialAuthHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var entry models.AppleSocialAuth
|
||||
if err := h.db.Preload("User").First(&entry, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Apple social auth entry not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Apple social auth entry not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch Apple social auth entry"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch Apple social auth entry"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&entry))
|
||||
return c.JSON(http.StatusOK, h.toResponse(&entry))
|
||||
}
|
||||
|
||||
// GetByUser handles GET /api/admin/apple-social-auth/user/:user_id
|
||||
func (h *AdminAppleSocialAuthHandler) GetByUser(c *gin.Context) {
|
||||
func (h *AdminAppleSocialAuthHandler) GetByUser(c echo.Context) error {
|
||||
userID, err := strconv.ParseUint(c.Param("user_id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid user ID"})
|
||||
}
|
||||
|
||||
var entry models.AppleSocialAuth
|
||||
if err := h.db.Preload("User").Where("user_id = ?", userID).First(&entry).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Apple social auth entry not found for user"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Apple social auth entry not found for user"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch Apple social auth entry"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch Apple social auth entry"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&entry))
|
||||
return c.JSON(http.StatusOK, h.toResponse(&entry))
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/apple-social-auth/:id
|
||||
func (h *AdminAppleSocialAuthHandler) Update(c *gin.Context) {
|
||||
func (h *AdminAppleSocialAuthHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var entry models.AppleSocialAuth
|
||||
if err := h.db.First(&entry, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Apple social auth entry not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Apple social auth entry not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch Apple social auth entry"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch Apple social auth entry"})
|
||||
}
|
||||
|
||||
var req UpdateAppleSocialAuthRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if req.Email != nil {
|
||||
@@ -162,54 +150,47 @@ func (h *AdminAppleSocialAuthHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&entry).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update Apple social auth entry"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update Apple social auth entry"})
|
||||
}
|
||||
|
||||
h.db.Preload("User").First(&entry, id)
|
||||
c.JSON(http.StatusOK, h.toResponse(&entry))
|
||||
return c.JSON(http.StatusOK, h.toResponse(&entry))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/apple-social-auth/:id
|
||||
func (h *AdminAppleSocialAuthHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminAppleSocialAuthHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var entry models.AppleSocialAuth
|
||||
if err := h.db.First(&entry, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Apple social auth entry not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Apple social auth entry not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch Apple social auth entry"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch Apple social auth entry"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&entry).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete Apple social auth entry"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete Apple social auth entry"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Apple social auth entry deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Apple social auth entry deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/apple-social-auth/bulk
|
||||
func (h *AdminAppleSocialAuthHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminAppleSocialAuthHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if err := h.db.Where("id IN ?", req.IDs).Delete(&models.AppleSocialAuth{}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete Apple social auth entries"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete Apple social auth entries"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Apple social auth entries deleted successfully", "count": len(req.IDs)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Apple social auth entries deleted successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
// toResponse converts an AppleSocialAuth model to AppleSocialAuthResponse
|
||||
|
||||
@@ -3,7 +3,7 @@ package handlers
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/config"
|
||||
"github.com/treytartt/casera-api/internal/middleware"
|
||||
@@ -68,37 +68,32 @@ func NewAdminUserResponse(admin *models.AdminUser) AdminUserResponse {
|
||||
}
|
||||
|
||||
// Login handles POST /api/admin/auth/login
|
||||
func (h *AdminAuthHandler) Login(c *gin.Context) {
|
||||
func (h *AdminAuthHandler) Login(c echo.Context) error {
|
||||
var req LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: " + err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid request: " + err.Error()})
|
||||
}
|
||||
|
||||
// Find admin by email
|
||||
admin, err := h.adminRepo.FindByEmail(req.Email)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
|
||||
return
|
||||
return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "Invalid email or password"})
|
||||
}
|
||||
|
||||
// Check password
|
||||
if !admin.CheckPassword(req.Password) {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
|
||||
return
|
||||
return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "Invalid email or password"})
|
||||
}
|
||||
|
||||
// Check if admin is active
|
||||
if !admin.IsActive {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Account is disabled"})
|
||||
return
|
||||
return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "Account is disabled"})
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
token, err := middleware.GenerateAdminToken(admin, h.cfg)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to generate token"})
|
||||
}
|
||||
|
||||
// Update last login
|
||||
@@ -107,7 +102,7 @@ func (h *AdminAuthHandler) Login(c *gin.Context) {
|
||||
// Refresh admin data after updating last login
|
||||
admin, _ = h.adminRepo.FindByID(admin.ID)
|
||||
|
||||
c.JSON(http.StatusOK, LoginResponse{
|
||||
return c.JSON(http.StatusOK, LoginResponse{
|
||||
Token: token,
|
||||
Admin: NewAdminUserResponse(admin),
|
||||
})
|
||||
@@ -115,26 +110,25 @@ func (h *AdminAuthHandler) Login(c *gin.Context) {
|
||||
|
||||
// Logout handles POST /api/admin/auth/logout
|
||||
// Note: JWT tokens are stateless, so logout is handled client-side by removing the token
|
||||
func (h *AdminAuthHandler) Logout(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Logged out successfully"})
|
||||
func (h *AdminAuthHandler) Logout(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Logged out successfully"})
|
||||
}
|
||||
|
||||
// Me handles GET /api/admin/auth/me
|
||||
func (h *AdminAuthHandler) Me(c *gin.Context) {
|
||||
admin := c.MustGet(middleware.AdminUserKey).(*models.AdminUser)
|
||||
c.JSON(http.StatusOK, NewAdminUserResponse(admin))
|
||||
func (h *AdminAuthHandler) Me(c echo.Context) error {
|
||||
admin := c.Get(middleware.AdminUserKey).(*models.AdminUser)
|
||||
return c.JSON(http.StatusOK, NewAdminUserResponse(admin))
|
||||
}
|
||||
|
||||
// RefreshToken handles POST /api/admin/auth/refresh
|
||||
func (h *AdminAuthHandler) RefreshToken(c *gin.Context) {
|
||||
admin := c.MustGet(middleware.AdminUserKey).(*models.AdminUser)
|
||||
func (h *AdminAuthHandler) RefreshToken(c echo.Context) error {
|
||||
admin := c.Get(middleware.AdminUserKey).(*models.AdminUser)
|
||||
|
||||
// Generate new token
|
||||
token, err := middleware.GenerateAdminToken(admin, h.cfg)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to generate token"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"token": token})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"token": token})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -31,11 +31,10 @@ type AuthTokenResponse struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/auth-tokens
|
||||
func (h *AdminAuthTokenHandler) List(c *gin.Context) {
|
||||
func (h *AdminAuthTokenHandler) List(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var tokens []models.AuthToken
|
||||
@@ -67,8 +66,7 @@ func (h *AdminAuthTokenHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&tokens).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch auth tokens"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch auth tokens"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -83,25 +81,22 @@ func (h *AdminAuthTokenHandler) List(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/auth-tokens/:id (id is actually user_id)
|
||||
func (h *AdminAuthTokenHandler) Get(c *gin.Context) {
|
||||
func (h *AdminAuthTokenHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid user ID"})
|
||||
}
|
||||
|
||||
var token models.AuthToken
|
||||
if err := h.db.Preload("User").Where("user_id = ?", id).First(&token).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Auth token not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Auth token not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch auth token"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch auth token"})
|
||||
}
|
||||
|
||||
response := AuthTokenResponse{
|
||||
@@ -112,44 +107,39 @@ func (h *AdminAuthTokenHandler) Get(c *gin.Context) {
|
||||
Created: token.Created.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/auth-tokens/:id (revoke token)
|
||||
func (h *AdminAuthTokenHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminAuthTokenHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid user ID"})
|
||||
}
|
||||
|
||||
result := h.db.Where("user_id = ?", id).Delete(&models.AuthToken{})
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to revoke token"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to revoke token"})
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Auth token not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Auth token not found"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Auth token revoked successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Auth token revoked successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/auth-tokens/bulk
|
||||
func (h *AdminAuthTokenHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminAuthTokenHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
result := h.db.Where("user_id IN ?", req.IDs).Delete(&models.AuthToken{})
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to revoke tokens"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to revoke tokens"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Auth tokens revoked successfully", "count": result.RowsAffected})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Auth tokens revoked successfully", "count": result.RowsAffected})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/shopspring/decimal"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -55,11 +55,10 @@ type CompletionFilters struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/completions
|
||||
func (h *AdminCompletionHandler) List(c *gin.Context) {
|
||||
func (h *AdminCompletionHandler) List(c echo.Context) error {
|
||||
var filters CompletionFilters
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var completions []models.TaskCompletion
|
||||
@@ -112,8 +111,7 @@ func (h *AdminCompletionHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&completions).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completions"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch completions"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -122,71 +120,62 @@ func (h *AdminCompletionHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toCompletionResponse(&completion)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/completions/:id
|
||||
func (h *AdminCompletionHandler) Get(c *gin.Context) {
|
||||
func (h *AdminCompletionHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid completion ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid completion ID"})
|
||||
}
|
||||
|
||||
var completion models.TaskCompletion
|
||||
if err := h.db.Preload("Task").Preload("Task.Residence").Preload("CompletedBy").Preload("Images").First(&completion, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Completion not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Completion not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch completion"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toCompletionResponse(&completion))
|
||||
return c.JSON(http.StatusOK, h.toCompletionResponse(&completion))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/completions/:id
|
||||
func (h *AdminCompletionHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminCompletionHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid completion ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid completion ID"})
|
||||
}
|
||||
|
||||
var completion models.TaskCompletion
|
||||
if err := h.db.First(&completion, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Completion not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Completion not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch completion"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&completion).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete completion"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete completion"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Completion deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Completion deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/completions/bulk
|
||||
func (h *AdminCompletionHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminCompletionHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
result := h.db.Where("id IN ?", req.IDs).Delete(&models.TaskCompletion{})
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete completions"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete completions"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Completions deleted successfully", "count": result.RowsAffected})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Completions deleted successfully", "count": result.RowsAffected})
|
||||
}
|
||||
|
||||
// UpdateCompletionRequest represents the request to update a completion
|
||||
@@ -196,27 +185,23 @@ type UpdateCompletionRequest struct {
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/completions/:id
|
||||
func (h *AdminCompletionHandler) Update(c *gin.Context) {
|
||||
func (h *AdminCompletionHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid completion ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid completion ID"})
|
||||
}
|
||||
|
||||
var completion models.TaskCompletion
|
||||
if err := h.db.First(&completion, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Completion not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Completion not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch completion"})
|
||||
}
|
||||
|
||||
var req UpdateCompletionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if req.Notes != nil {
|
||||
@@ -234,12 +219,11 @@ func (h *AdminCompletionHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&completion).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update completion"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update completion"})
|
||||
}
|
||||
|
||||
h.db.Preload("Task").Preload("Task.Residence").Preload("CompletedBy").Preload("Images").First(&completion, id)
|
||||
c.JSON(http.StatusOK, h.toCompletionResponse(&completion))
|
||||
return c.JSON(http.StatusOK, h.toCompletionResponse(&completion))
|
||||
}
|
||||
|
||||
// toCompletionResponse converts a TaskCompletion model to CompletionResponse
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -47,15 +47,14 @@ type UpdateCompletionImageRequest struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/completion-images
|
||||
func (h *AdminCompletionImageHandler) List(c *gin.Context) {
|
||||
func (h *AdminCompletionImageHandler) List(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Optional completion_id filter
|
||||
completionIDStr := c.Query("completion_id")
|
||||
completionIDStr := c.QueryParam("completion_id")
|
||||
|
||||
var images []models.TaskCompletionImage
|
||||
var total int64
|
||||
@@ -90,8 +89,7 @@ func (h *AdminCompletionImageHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&images).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion images"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch completion images"})
|
||||
}
|
||||
|
||||
// Build response with task info
|
||||
@@ -100,47 +98,41 @@ func (h *AdminCompletionImageHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toResponse(&image)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/completion-images/:id
|
||||
func (h *AdminCompletionImageHandler) Get(c *gin.Context) {
|
||||
func (h *AdminCompletionImageHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid image ID"})
|
||||
}
|
||||
|
||||
var image models.TaskCompletionImage
|
||||
if err := h.db.First(&image, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Completion image not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Completion image not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch completion image"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
return c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Create handles POST /api/admin/completion-images
|
||||
func (h *AdminCompletionImageHandler) Create(c *gin.Context) {
|
||||
func (h *AdminCompletionImageHandler) Create(c echo.Context) error {
|
||||
var req CreateCompletionImageRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Verify completion exists
|
||||
var completion models.TaskCompletion
|
||||
if err := h.db.First(&completion, req.CompletionID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Task completion not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Task completion not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify completion"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to verify completion"})
|
||||
}
|
||||
|
||||
image := models.TaskCompletionImage{
|
||||
@@ -150,35 +142,30 @@ func (h *AdminCompletionImageHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create completion image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create completion image"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, h.toResponse(&image))
|
||||
return c.JSON(http.StatusCreated, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/completion-images/:id
|
||||
func (h *AdminCompletionImageHandler) Update(c *gin.Context) {
|
||||
func (h *AdminCompletionImageHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid image ID"})
|
||||
}
|
||||
|
||||
var image models.TaskCompletionImage
|
||||
if err := h.db.First(&image, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Completion image not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Completion image not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch completion image"})
|
||||
}
|
||||
|
||||
var req UpdateCompletionImageRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if req.ImageURL != nil {
|
||||
@@ -189,53 +176,46 @@ func (h *AdminCompletionImageHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update completion image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update completion image"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
return c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/completion-images/:id
|
||||
func (h *AdminCompletionImageHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminCompletionImageHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid image ID"})
|
||||
}
|
||||
|
||||
var image models.TaskCompletionImage
|
||||
if err := h.db.First(&image, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Completion image not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Completion image not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch completion image"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete completion image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete completion image"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Completion image deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Completion image deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/completion-images/bulk
|
||||
func (h *AdminCompletionImageHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminCompletionImageHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if err := h.db.Where("id IN ?", req.IDs).Delete(&models.TaskCompletionImage{}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete completion images"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete completion images"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Completion images deleted successfully", "count": len(req.IDs)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Completion images deleted successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
// toResponse converts a TaskCompletionImage model to AdminCompletionImageResponse
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -34,11 +34,10 @@ type ConfirmationCodeResponse struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/confirmation-codes
|
||||
func (h *AdminConfirmationCodeHandler) List(c *gin.Context) {
|
||||
func (h *AdminConfirmationCodeHandler) List(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var codes []models.ConfirmationCode
|
||||
@@ -70,8 +69,7 @@ func (h *AdminConfirmationCodeHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&codes).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch confirmation codes"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch confirmation codes"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -89,25 +87,22 @@ func (h *AdminConfirmationCodeHandler) List(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/confirmation-codes/:id
|
||||
func (h *AdminConfirmationCodeHandler) Get(c *gin.Context) {
|
||||
func (h *AdminConfirmationCodeHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var code models.ConfirmationCode
|
||||
if err := h.db.Preload("User").First(&code, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Confirmation code not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Confirmation code not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch confirmation code"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch confirmation code"})
|
||||
}
|
||||
|
||||
response := ConfirmationCodeResponse{
|
||||
@@ -121,44 +116,39 @@ func (h *AdminConfirmationCodeHandler) Get(c *gin.Context) {
|
||||
CreatedAt: code.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/confirmation-codes/:id
|
||||
func (h *AdminConfirmationCodeHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminConfirmationCodeHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
result := h.db.Delete(&models.ConfirmationCode{}, id)
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete confirmation code"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete confirmation code"})
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Confirmation code not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Confirmation code not found"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Confirmation code deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Confirmation code deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/confirmation-codes/bulk
|
||||
func (h *AdminConfirmationCodeHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminConfirmationCodeHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
result := h.db.Where("id IN ?", req.IDs).Delete(&models.ConfirmationCode{})
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete confirmation codes"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete confirmation codes"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Confirmation codes deleted successfully", "count": result.RowsAffected})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Confirmation codes deleted successfully", "count": result.RowsAffected})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -22,11 +22,10 @@ func NewAdminContractorHandler(db *gorm.DB) *AdminContractorHandler {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/contractors
|
||||
func (h *AdminContractorHandler) List(c *gin.Context) {
|
||||
func (h *AdminContractorHandler) List(c echo.Context) error {
|
||||
var filters dto.ContractorFilters
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var contractors []models.Contractor
|
||||
@@ -71,8 +70,7 @@ func (h *AdminContractorHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&contractors).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch contractors"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch contractors"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -81,15 +79,14 @@ func (h *AdminContractorHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toContractorResponse(&contractor)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/contractors/:id
|
||||
func (h *AdminContractorHandler) Get(c *gin.Context) {
|
||||
func (h *AdminContractorHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid contractor ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid contractor ID"})
|
||||
}
|
||||
|
||||
var contractor models.Contractor
|
||||
@@ -99,11 +96,9 @@ func (h *AdminContractorHandler) Get(c *gin.Context) {
|
||||
Preload("Specialties").
|
||||
First(&contractor, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Contractor not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Contractor not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch contractor"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch contractor"})
|
||||
}
|
||||
|
||||
response := dto.ContractorDetailResponse{
|
||||
@@ -115,39 +110,34 @@ func (h *AdminContractorHandler) Get(c *gin.Context) {
|
||||
h.db.Model(&models.Task{}).Where("contractor_id = ?", contractor.ID).Count(&taskCount)
|
||||
response.TaskCount = int(taskCount)
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/contractors/:id
|
||||
func (h *AdminContractorHandler) Update(c *gin.Context) {
|
||||
func (h *AdminContractorHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid contractor ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid contractor ID"})
|
||||
}
|
||||
|
||||
var contractor models.Contractor
|
||||
if err := h.db.First(&contractor, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Contractor not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Contractor not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch contractor"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch contractor"})
|
||||
}
|
||||
|
||||
var req dto.UpdateContractorRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Verify residence if changing
|
||||
if req.ResidenceID != nil {
|
||||
var residence models.Residence
|
||||
if err := h.db.First(&residence, *req.ResidenceID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Residence not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Residence not found"})
|
||||
}
|
||||
contractor.ResidenceID = req.ResidenceID
|
||||
}
|
||||
@@ -155,8 +145,7 @@ func (h *AdminContractorHandler) Update(c *gin.Context) {
|
||||
if req.CreatedByID != nil {
|
||||
var user models.User
|
||||
if err := h.db.First(&user, *req.CreatedByID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Created by user not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Created by user not found"})
|
||||
}
|
||||
contractor.CreatedByID = *req.CreatedByID
|
||||
}
|
||||
@@ -213,36 +202,32 @@ func (h *AdminContractorHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&contractor).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update contractor"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update contractor"})
|
||||
}
|
||||
|
||||
h.db.Preload("Residence").Preload("CreatedBy").Preload("Specialties").First(&contractor, id)
|
||||
c.JSON(http.StatusOK, h.toContractorResponse(&contractor))
|
||||
return c.JSON(http.StatusOK, h.toContractorResponse(&contractor))
|
||||
}
|
||||
|
||||
// Create handles POST /api/admin/contractors
|
||||
func (h *AdminContractorHandler) Create(c *gin.Context) {
|
||||
func (h *AdminContractorHandler) Create(c echo.Context) error {
|
||||
var req dto.CreateContractorRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Verify residence exists if provided
|
||||
if req.ResidenceID != nil {
|
||||
var residence models.Residence
|
||||
if err := h.db.First(&residence, *req.ResidenceID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Residence not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Residence not found"})
|
||||
}
|
||||
}
|
||||
|
||||
// Verify created_by user exists
|
||||
var creator models.User
|
||||
if err := h.db.First(&creator, req.CreatedByID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Creator user not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Creator user not found"})
|
||||
}
|
||||
|
||||
contractor := models.Contractor{
|
||||
@@ -263,8 +248,7 @@ func (h *AdminContractorHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&contractor).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create contractor"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create contractor"})
|
||||
}
|
||||
|
||||
// Add specialties if provided
|
||||
@@ -275,52 +259,46 @@ func (h *AdminContractorHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
h.db.Preload("Residence").Preload("CreatedBy").Preload("Specialties").First(&contractor, contractor.ID)
|
||||
c.JSON(http.StatusCreated, h.toContractorResponse(&contractor))
|
||||
return c.JSON(http.StatusCreated, h.toContractorResponse(&contractor))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/contractors/:id
|
||||
func (h *AdminContractorHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminContractorHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid contractor ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid contractor ID"})
|
||||
}
|
||||
|
||||
var contractor models.Contractor
|
||||
if err := h.db.First(&contractor, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Contractor not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Contractor not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch contractor"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch contractor"})
|
||||
}
|
||||
|
||||
// Soft delete
|
||||
contractor.IsActive = false
|
||||
if err := h.db.Save(&contractor).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete contractor"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete contractor"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Contractor deactivated successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Contractor deactivated successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/contractors/bulk
|
||||
func (h *AdminContractorHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminContractorHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Soft delete - deactivate all
|
||||
if err := h.db.Model(&models.Contractor{}).Where("id IN ?", req.IDs).Update("is_active", false).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete contractors"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete contractors"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Contractors deactivated successfully", "count": len(req.IDs)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Contractors deactivated successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
func (h *AdminContractorHandler) toContractorResponse(contractor *models.Contractor) dto.ContractorResponse {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/models"
|
||||
@@ -93,7 +93,7 @@ type SubscriptionStats struct {
|
||||
}
|
||||
|
||||
// GetStats handles GET /api/admin/dashboard/stats
|
||||
func (h *AdminDashboardHandler) GetStats(c *gin.Context) {
|
||||
func (h *AdminDashboardHandler) GetStats(c echo.Context) error {
|
||||
stats := DashboardStats{}
|
||||
now := time.Now()
|
||||
thirtyDaysAgo := now.AddDate(0, 0, -30)
|
||||
@@ -164,5 +164,5 @@ func (h *AdminDashboardHandler) GetStats(c *gin.Context) {
|
||||
h.db.Model(&models.UserSubscription{}).Where("tier = ?", "premium").Count(&stats.Subscriptions.Premium)
|
||||
h.db.Model(&models.UserSubscription{}).Where("tier = ?", "pro").Count(&stats.Subscriptions.Pro)
|
||||
|
||||
c.JSON(http.StatusOK, stats)
|
||||
return c.JSON(http.StatusOK, stats)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -47,11 +47,10 @@ type GCMDeviceResponse struct {
|
||||
}
|
||||
|
||||
// ListAPNS handles GET /api/admin/devices/apns
|
||||
func (h *AdminDeviceHandler) ListAPNS(c *gin.Context) {
|
||||
func (h *AdminDeviceHandler) ListAPNS(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var devices []models.APNSDevice
|
||||
@@ -79,8 +78,7 @@ func (h *AdminDeviceHandler) ListAPNS(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&devices).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch devices"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch devices"})
|
||||
}
|
||||
|
||||
responses := make([]APNSDeviceResponse, len(devices))
|
||||
@@ -101,15 +99,14 @@ func (h *AdminDeviceHandler) ListAPNS(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// ListGCM handles GET /api/admin/devices/gcm
|
||||
func (h *AdminDeviceHandler) ListGCM(c *gin.Context) {
|
||||
func (h *AdminDeviceHandler) ListGCM(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var devices []models.GCMDevice
|
||||
@@ -136,8 +133,7 @@ func (h *AdminDeviceHandler) ListGCM(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&devices).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch devices"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch devices"})
|
||||
}
|
||||
|
||||
responses := make([]GCMDeviceResponse, len(devices))
|
||||
@@ -159,159 +155,139 @@ func (h *AdminDeviceHandler) ListGCM(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// UpdateAPNS handles PUT /api/admin/devices/apns/:id
|
||||
func (h *AdminDeviceHandler) UpdateAPNS(c *gin.Context) {
|
||||
func (h *AdminDeviceHandler) UpdateAPNS(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var device models.APNSDevice
|
||||
if err := h.db.First(&device, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Device not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Device not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch device"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch device"})
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
device.Active = req.Active
|
||||
if err := h.db.Save(&device).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update device"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update device"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Device updated successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Device updated successfully"})
|
||||
}
|
||||
|
||||
// UpdateGCM handles PUT /api/admin/devices/gcm/:id
|
||||
func (h *AdminDeviceHandler) UpdateGCM(c *gin.Context) {
|
||||
func (h *AdminDeviceHandler) UpdateGCM(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var device models.GCMDevice
|
||||
if err := h.db.First(&device, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Device not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Device not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch device"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch device"})
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
device.Active = req.Active
|
||||
if err := h.db.Save(&device).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update device"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update device"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Device updated successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Device updated successfully"})
|
||||
}
|
||||
|
||||
// DeleteAPNS handles DELETE /api/admin/devices/apns/:id
|
||||
func (h *AdminDeviceHandler) DeleteAPNS(c *gin.Context) {
|
||||
func (h *AdminDeviceHandler) DeleteAPNS(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
result := h.db.Delete(&models.APNSDevice{}, id)
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete device"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete device"})
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Device not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Device not found"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Device deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Device deleted successfully"})
|
||||
}
|
||||
|
||||
// DeleteGCM handles DELETE /api/admin/devices/gcm/:id
|
||||
func (h *AdminDeviceHandler) DeleteGCM(c *gin.Context) {
|
||||
func (h *AdminDeviceHandler) DeleteGCM(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
result := h.db.Delete(&models.GCMDevice{}, id)
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete device"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete device"})
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Device not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Device not found"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Device deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Device deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDeleteAPNS handles DELETE /api/admin/devices/apns/bulk
|
||||
func (h *AdminDeviceHandler) BulkDeleteAPNS(c *gin.Context) {
|
||||
func (h *AdminDeviceHandler) BulkDeleteAPNS(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
result := h.db.Where("id IN ?", req.IDs).Delete(&models.APNSDevice{})
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete devices"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete devices"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Devices deleted successfully", "count": result.RowsAffected})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Devices deleted successfully", "count": result.RowsAffected})
|
||||
}
|
||||
|
||||
// BulkDeleteGCM handles DELETE /api/admin/devices/gcm/bulk
|
||||
func (h *AdminDeviceHandler) BulkDeleteGCM(c *gin.Context) {
|
||||
func (h *AdminDeviceHandler) BulkDeleteGCM(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
result := h.db.Where("id IN ?", req.IDs).Delete(&models.GCMDevice{})
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete devices"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete devices"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Devices deleted successfully", "count": result.RowsAffected})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Devices deleted successfully", "count": result.RowsAffected})
|
||||
}
|
||||
|
||||
// GetStats handles GET /api/admin/devices/stats
|
||||
func (h *AdminDeviceHandler) GetStats(c *gin.Context) {
|
||||
func (h *AdminDeviceHandler) GetStats(c echo.Context) error {
|
||||
var apnsTotal, apnsActive, gcmTotal, gcmActive int64
|
||||
|
||||
h.db.Model(&models.APNSDevice{}).Count(&apnsTotal)
|
||||
@@ -319,12 +295,12 @@ func (h *AdminDeviceHandler) GetStats(c *gin.Context) {
|
||||
h.db.Model(&models.GCMDevice{}).Count(&gcmTotal)
|
||||
h.db.Model(&models.GCMDevice{}).Where("active = ?", true).Count(&gcmActive)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"apns": gin.H{
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"apns": map[string]interface{}{
|
||||
"total": apnsTotal,
|
||||
"active": apnsActive,
|
||||
},
|
||||
"gcm": gin.H{
|
||||
"gcm": map[string]interface{}{
|
||||
"total": gcmTotal,
|
||||
"active": gcmActive,
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/shopspring/decimal"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -24,11 +24,10 @@ func NewAdminDocumentHandler(db *gorm.DB) *AdminDocumentHandler {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/documents
|
||||
func (h *AdminDocumentHandler) List(c *gin.Context) {
|
||||
func (h *AdminDocumentHandler) List(c echo.Context) error {
|
||||
var filters dto.DocumentFilters
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var documents []models.Document
|
||||
@@ -73,8 +72,7 @@ func (h *AdminDocumentHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&documents).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch documents"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch documents"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -83,15 +81,14 @@ func (h *AdminDocumentHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toDocumentResponse(&doc)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/documents/:id
|
||||
func (h *AdminDocumentHandler) Get(c *gin.Context) {
|
||||
func (h *AdminDocumentHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid document ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid document ID"})
|
||||
}
|
||||
|
||||
var document models.Document
|
||||
@@ -102,11 +99,9 @@ func (h *AdminDocumentHandler) Get(c *gin.Context) {
|
||||
Preload("Images").
|
||||
First(&document, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Document not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Document not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch document"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch document"})
|
||||
}
|
||||
|
||||
response := dto.DocumentDetailResponse{
|
||||
@@ -117,39 +112,34 @@ func (h *AdminDocumentHandler) Get(c *gin.Context) {
|
||||
response.TaskTitle = &document.Task.Title
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/documents/:id
|
||||
func (h *AdminDocumentHandler) Update(c *gin.Context) {
|
||||
func (h *AdminDocumentHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid document ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid document ID"})
|
||||
}
|
||||
|
||||
var document models.Document
|
||||
if err := h.db.First(&document, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Document not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Document not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch document"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch document"})
|
||||
}
|
||||
|
||||
var req dto.UpdateDocumentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Verify residence if changing
|
||||
if req.ResidenceID != nil {
|
||||
var residence models.Residence
|
||||
if err := h.db.First(&residence, *req.ResidenceID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Residence not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Residence not found"})
|
||||
}
|
||||
document.ResidenceID = *req.ResidenceID
|
||||
}
|
||||
@@ -157,8 +147,7 @@ func (h *AdminDocumentHandler) Update(c *gin.Context) {
|
||||
if req.CreatedByID != nil {
|
||||
var user models.User
|
||||
if err := h.db.First(&user, *req.CreatedByID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Created by user not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Created by user not found"})
|
||||
}
|
||||
document.CreatedByID = *req.CreatedByID
|
||||
}
|
||||
@@ -232,34 +221,30 @@ func (h *AdminDocumentHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&document).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update document"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update document"})
|
||||
}
|
||||
|
||||
h.db.Preload("Residence").Preload("CreatedBy").Preload("Images").First(&document, id)
|
||||
c.JSON(http.StatusOK, h.toDocumentResponse(&document))
|
||||
return c.JSON(http.StatusOK, h.toDocumentResponse(&document))
|
||||
}
|
||||
|
||||
// Create handles POST /api/admin/documents
|
||||
func (h *AdminDocumentHandler) Create(c *gin.Context) {
|
||||
func (h *AdminDocumentHandler) Create(c echo.Context) error {
|
||||
var req dto.CreateDocumentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Verify residence exists
|
||||
var residence models.Residence
|
||||
if err := h.db.First(&residence, req.ResidenceID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Residence not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Residence not found"})
|
||||
}
|
||||
|
||||
// Verify created_by user exists
|
||||
var creator models.User
|
||||
if err := h.db.First(&creator, req.CreatedByID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Creator user not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Creator user not found"})
|
||||
}
|
||||
|
||||
documentType := models.DocumentTypeGeneral
|
||||
@@ -302,57 +287,50 @@ func (h *AdminDocumentHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&document).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create document"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create document"})
|
||||
}
|
||||
|
||||
h.db.Preload("Residence").Preload("CreatedBy").Preload("Images").First(&document, document.ID)
|
||||
c.JSON(http.StatusCreated, h.toDocumentResponse(&document))
|
||||
return c.JSON(http.StatusCreated, h.toDocumentResponse(&document))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/documents/:id
|
||||
func (h *AdminDocumentHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminDocumentHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid document ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid document ID"})
|
||||
}
|
||||
|
||||
var document models.Document
|
||||
if err := h.db.First(&document, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Document not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Document not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch document"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch document"})
|
||||
}
|
||||
|
||||
// Soft delete
|
||||
document.IsActive = false
|
||||
if err := h.db.Save(&document).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete document"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete document"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Document deactivated successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Document deactivated successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/documents/bulk
|
||||
func (h *AdminDocumentHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminDocumentHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Soft delete - deactivate all
|
||||
if err := h.db.Model(&models.Document{}).Where("id IN ?", req.IDs).Update("is_active", false).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete documents"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete documents"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Documents deactivated successfully", "count": len(req.IDs)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Documents deactivated successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
func (h *AdminDocumentHandler) toDocumentResponse(doc *models.Document) dto.DocumentResponse {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -48,15 +48,14 @@ type UpdateDocumentImageRequest struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/document-images
|
||||
func (h *AdminDocumentImageHandler) List(c *gin.Context) {
|
||||
func (h *AdminDocumentImageHandler) List(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Optional document_id filter
|
||||
documentIDStr := c.Query("document_id")
|
||||
documentIDStr := c.QueryParam("document_id")
|
||||
|
||||
var images []models.DocumentImage
|
||||
var total int64
|
||||
@@ -91,8 +90,7 @@ func (h *AdminDocumentImageHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&images).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch document images"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch document images"})
|
||||
}
|
||||
|
||||
// Build response with document info
|
||||
@@ -101,47 +99,41 @@ func (h *AdminDocumentImageHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toResponse(&image)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/document-images/:id
|
||||
func (h *AdminDocumentImageHandler) Get(c *gin.Context) {
|
||||
func (h *AdminDocumentImageHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid image ID"})
|
||||
}
|
||||
|
||||
var image models.DocumentImage
|
||||
if err := h.db.First(&image, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Document image not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Document image not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch document image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch document image"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
return c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Create handles POST /api/admin/document-images
|
||||
func (h *AdminDocumentImageHandler) Create(c *gin.Context) {
|
||||
func (h *AdminDocumentImageHandler) Create(c echo.Context) error {
|
||||
var req CreateDocumentImageRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Verify document exists
|
||||
var document models.Document
|
||||
if err := h.db.First(&document, req.DocumentID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Document not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Document not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify document"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to verify document"})
|
||||
}
|
||||
|
||||
image := models.DocumentImage{
|
||||
@@ -151,35 +143,30 @@ func (h *AdminDocumentImageHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create document image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create document image"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, h.toResponse(&image))
|
||||
return c.JSON(http.StatusCreated, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/document-images/:id
|
||||
func (h *AdminDocumentImageHandler) Update(c *gin.Context) {
|
||||
func (h *AdminDocumentImageHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid image ID"})
|
||||
}
|
||||
|
||||
var image models.DocumentImage
|
||||
if err := h.db.First(&image, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Document image not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Document image not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch document image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch document image"})
|
||||
}
|
||||
|
||||
var req UpdateDocumentImageRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if req.ImageURL != nil {
|
||||
@@ -190,53 +177,46 @@ func (h *AdminDocumentImageHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update document image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update document image"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
return c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/document-images/:id
|
||||
func (h *AdminDocumentImageHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminDocumentImageHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid image ID"})
|
||||
}
|
||||
|
||||
var image models.DocumentImage
|
||||
if err := h.db.First(&image, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Document image not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Document image not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch document image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch document image"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete document image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete document image"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Document image deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Document image deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/document-images/bulk
|
||||
func (h *AdminDocumentImageHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminDocumentImageHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if err := h.db.Where("id IN ?", req.IDs).Delete(&models.DocumentImage{}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete document images"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete document images"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Document images deleted successfully", "count": len(req.IDs)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Document images deleted successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
// toResponse converts a DocumentImage model to DocumentImageResponse
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -34,11 +34,10 @@ type FeatureBenefitResponse struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/feature-benefits
|
||||
func (h *AdminFeatureBenefitHandler) List(c *gin.Context) {
|
||||
func (h *AdminFeatureBenefitHandler) List(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var benefits []models.FeatureBenefit
|
||||
@@ -61,8 +60,7 @@ func (h *AdminFeatureBenefitHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&benefits).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch feature benefits"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch feature benefits"})
|
||||
}
|
||||
|
||||
responses := make([]FeatureBenefitResponse, len(benefits))
|
||||
@@ -79,25 +77,22 @@ func (h *AdminFeatureBenefitHandler) List(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/feature-benefits/:id
|
||||
func (h *AdminFeatureBenefitHandler) Get(c *gin.Context) {
|
||||
func (h *AdminFeatureBenefitHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var benefit models.FeatureBenefit
|
||||
if err := h.db.First(&benefit, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Feature benefit not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Feature benefit not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch feature benefit"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch feature benefit"})
|
||||
}
|
||||
|
||||
response := FeatureBenefitResponse{
|
||||
@@ -111,11 +106,11 @@ func (h *AdminFeatureBenefitHandler) Get(c *gin.Context) {
|
||||
UpdatedAt: benefit.UpdatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Create handles POST /api/admin/feature-benefits
|
||||
func (h *AdminFeatureBenefitHandler) Create(c *gin.Context) {
|
||||
func (h *AdminFeatureBenefitHandler) Create(c echo.Context) error {
|
||||
var req struct {
|
||||
FeatureName string `json:"feature_name" binding:"required"`
|
||||
FreeTierText string `json:"free_tier_text" binding:"required"`
|
||||
@@ -124,9 +119,8 @@ func (h *AdminFeatureBenefitHandler) Create(c *gin.Context) {
|
||||
IsActive *bool `json:"is_active"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
benefit := models.FeatureBenefit{
|
||||
@@ -142,11 +136,10 @@ func (h *AdminFeatureBenefitHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&benefit).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create feature benefit"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create feature benefit"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, FeatureBenefitResponse{
|
||||
return c.JSON(http.StatusCreated, FeatureBenefitResponse{
|
||||
ID: benefit.ID,
|
||||
FeatureName: benefit.FeatureName,
|
||||
FreeTierText: benefit.FreeTierText,
|
||||
@@ -159,21 +152,18 @@ func (h *AdminFeatureBenefitHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/feature-benefits/:id
|
||||
func (h *AdminFeatureBenefitHandler) Update(c *gin.Context) {
|
||||
func (h *AdminFeatureBenefitHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var benefit models.FeatureBenefit
|
||||
if err := h.db.First(&benefit, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Feature benefit not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Feature benefit not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch feature benefit"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch feature benefit"})
|
||||
}
|
||||
|
||||
var req struct {
|
||||
@@ -184,9 +174,8 @@ func (h *AdminFeatureBenefitHandler) Update(c *gin.Context) {
|
||||
IsActive *bool `json:"is_active"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if req.FeatureName != nil {
|
||||
@@ -206,11 +195,10 @@ func (h *AdminFeatureBenefitHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&benefit).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update feature benefit"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update feature benefit"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, FeatureBenefitResponse{
|
||||
return c.JSON(http.StatusOK, FeatureBenefitResponse{
|
||||
ID: benefit.ID,
|
||||
FeatureName: benefit.FeatureName,
|
||||
FreeTierText: benefit.FreeTierText,
|
||||
@@ -223,23 +211,20 @@ func (h *AdminFeatureBenefitHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/feature-benefits/:id
|
||||
func (h *AdminFeatureBenefitHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminFeatureBenefitHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
result := h.db.Delete(&models.FeatureBenefit{}, id)
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete feature benefit"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete feature benefit"})
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Feature benefit not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Feature benefit not found"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Feature benefit deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Feature benefit deleted successfully"})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/models"
|
||||
@@ -28,7 +28,7 @@ type LimitationsSettingsResponse struct {
|
||||
}
|
||||
|
||||
// GetSettings handles GET /api/admin/limitations/settings
|
||||
func (h *AdminLimitationsHandler) GetSettings(c *gin.Context) {
|
||||
func (h *AdminLimitationsHandler) GetSettings(c echo.Context) error {
|
||||
var settings models.SubscriptionSettings
|
||||
if err := h.db.First(&settings, 1).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
@@ -36,12 +36,11 @@ func (h *AdminLimitationsHandler) GetSettings(c *gin.Context) {
|
||||
settings = models.SubscriptionSettings{ID: 1, EnableLimitations: false}
|
||||
h.db.Create(&settings)
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch settings"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch settings"})
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, LimitationsSettingsResponse{
|
||||
return c.JSON(http.StatusOK, LimitationsSettingsResponse{
|
||||
EnableLimitations: settings.EnableLimitations,
|
||||
})
|
||||
}
|
||||
@@ -52,11 +51,10 @@ type UpdateLimitationsSettingsRequest struct {
|
||||
}
|
||||
|
||||
// UpdateSettings handles PUT /api/admin/limitations/settings
|
||||
func (h *AdminLimitationsHandler) UpdateSettings(c *gin.Context) {
|
||||
func (h *AdminLimitationsHandler) UpdateSettings(c echo.Context) error {
|
||||
var req UpdateLimitationsSettingsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var settings models.SubscriptionSettings
|
||||
@@ -64,8 +62,7 @@ func (h *AdminLimitationsHandler) UpdateSettings(c *gin.Context) {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
settings = models.SubscriptionSettings{ID: 1}
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch settings"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch settings"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,11 +71,10 @@ func (h *AdminLimitationsHandler) UpdateSettings(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&settings).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update settings"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update settings"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, LimitationsSettingsResponse{
|
||||
return c.JSON(http.StatusOK, LimitationsSettingsResponse{
|
||||
EnableLimitations: settings.EnableLimitations,
|
||||
})
|
||||
}
|
||||
@@ -111,11 +107,10 @@ func toTierLimitsResponse(t *models.TierLimits) TierLimitsResponse {
|
||||
}
|
||||
|
||||
// ListTierLimits handles GET /api/admin/limitations/tier-limits
|
||||
func (h *AdminLimitationsHandler) ListTierLimits(c *gin.Context) {
|
||||
func (h *AdminLimitationsHandler) ListTierLimits(c echo.Context) error {
|
||||
var limits []models.TierLimits
|
||||
if err := h.db.Order("tier").Find(&limits).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch tier limits"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch tier limits"})
|
||||
}
|
||||
|
||||
// If no limits exist, create defaults
|
||||
@@ -132,18 +127,17 @@ func (h *AdminLimitationsHandler) ListTierLimits(c *gin.Context) {
|
||||
responses[i] = toTierLimitsResponse(&l)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"data": responses,
|
||||
"total": len(responses),
|
||||
})
|
||||
}
|
||||
|
||||
// GetTierLimits handles GET /api/admin/limitations/tier-limits/:tier
|
||||
func (h *AdminLimitationsHandler) GetTierLimits(c *gin.Context) {
|
||||
func (h *AdminLimitationsHandler) GetTierLimits(c echo.Context) error {
|
||||
tier := c.Param("tier")
|
||||
if tier != "free" && tier != "pro" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid tier. Must be 'free' or 'pro'"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid tier. Must be 'free' or 'pro'"})
|
||||
}
|
||||
|
||||
var limits models.TierLimits
|
||||
@@ -157,12 +151,11 @@ func (h *AdminLimitationsHandler) GetTierLimits(c *gin.Context) {
|
||||
}
|
||||
h.db.Create(&limits)
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch tier limits"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch tier limits"})
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, toTierLimitsResponse(&limits))
|
||||
return c.JSON(http.StatusOK, toTierLimitsResponse(&limits))
|
||||
}
|
||||
|
||||
// UpdateTierLimitsRequest represents the update request for tier limits
|
||||
@@ -174,17 +167,15 @@ type UpdateTierLimitsRequest struct {
|
||||
}
|
||||
|
||||
// UpdateTierLimits handles PUT /api/admin/limitations/tier-limits/:tier
|
||||
func (h *AdminLimitationsHandler) UpdateTierLimits(c *gin.Context) {
|
||||
func (h *AdminLimitationsHandler) UpdateTierLimits(c echo.Context) error {
|
||||
tier := c.Param("tier")
|
||||
if tier != "free" && tier != "pro" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid tier. Must be 'free' or 'pro'"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid tier. Must be 'free' or 'pro'"})
|
||||
}
|
||||
|
||||
var req UpdateTierLimitsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var limits models.TierLimits
|
||||
@@ -193,8 +184,7 @@ func (h *AdminLimitationsHandler) UpdateTierLimits(c *gin.Context) {
|
||||
// Create new entry
|
||||
limits = models.TierLimits{Tier: models.SubscriptionTier(tier)}
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch tier limits"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch tier limits"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,11 +197,10 @@ func (h *AdminLimitationsHandler) UpdateTierLimits(c *gin.Context) {
|
||||
limits.DocumentsLimit = req.DocumentsLimit
|
||||
|
||||
if err := h.db.Save(&limits).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update tier limits"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update tier limits"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, toTierLimitsResponse(&limits))
|
||||
return c.JSON(http.StatusOK, toTierLimitsResponse(&limits))
|
||||
}
|
||||
|
||||
// === Upgrade Triggers ===
|
||||
@@ -253,7 +242,7 @@ var availableTriggerKeys = []string{
|
||||
}
|
||||
|
||||
// GetAvailableTriggerKeys handles GET /api/admin/limitations/upgrade-triggers/keys
|
||||
func (h *AdminLimitationsHandler) GetAvailableTriggerKeys(c *gin.Context) {
|
||||
func (h *AdminLimitationsHandler) GetAvailableTriggerKeys(c echo.Context) error {
|
||||
type KeyOption struct {
|
||||
Key string `json:"key"`
|
||||
Label string `json:"label"`
|
||||
@@ -267,15 +256,14 @@ func (h *AdminLimitationsHandler) GetAvailableTriggerKeys(c *gin.Context) {
|
||||
{Key: "view_documents", Label: "View Documents & Warranties"},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, keys)
|
||||
return c.JSON(http.StatusOK, keys)
|
||||
}
|
||||
|
||||
// ListUpgradeTriggers handles GET /api/admin/limitations/upgrade-triggers
|
||||
func (h *AdminLimitationsHandler) ListUpgradeTriggers(c *gin.Context) {
|
||||
func (h *AdminLimitationsHandler) ListUpgradeTriggers(c echo.Context) error {
|
||||
var triggers []models.UpgradeTrigger
|
||||
if err := h.db.Order("trigger_key").Find(&triggers).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch upgrade triggers"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch upgrade triggers"})
|
||||
}
|
||||
|
||||
responses := make([]UpgradeTriggerResponse, len(triggers))
|
||||
@@ -283,31 +271,28 @@ func (h *AdminLimitationsHandler) ListUpgradeTriggers(c *gin.Context) {
|
||||
responses[i] = toUpgradeTriggerResponse(&t)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"data": responses,
|
||||
"total": len(responses),
|
||||
})
|
||||
}
|
||||
|
||||
// GetUpgradeTrigger handles GET /api/admin/limitations/upgrade-triggers/:id
|
||||
func (h *AdminLimitationsHandler) GetUpgradeTrigger(c *gin.Context) {
|
||||
func (h *AdminLimitationsHandler) GetUpgradeTrigger(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var trigger models.UpgradeTrigger
|
||||
if err := h.db.First(&trigger, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Upgrade trigger not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Upgrade trigger not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch upgrade trigger"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch upgrade trigger"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, toUpgradeTriggerResponse(&trigger))
|
||||
return c.JSON(http.StatusOK, toUpgradeTriggerResponse(&trigger))
|
||||
}
|
||||
|
||||
// CreateUpgradeTriggerRequest represents the create request
|
||||
@@ -321,11 +306,10 @@ type CreateUpgradeTriggerRequest struct {
|
||||
}
|
||||
|
||||
// CreateUpgradeTrigger handles POST /api/admin/limitations/upgrade-triggers
|
||||
func (h *AdminLimitationsHandler) CreateUpgradeTrigger(c *gin.Context) {
|
||||
func (h *AdminLimitationsHandler) CreateUpgradeTrigger(c echo.Context) error {
|
||||
var req CreateUpgradeTriggerRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Validate trigger key
|
||||
@@ -337,15 +321,13 @@ func (h *AdminLimitationsHandler) CreateUpgradeTrigger(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
if !validKey {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid trigger_key"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid trigger_key"})
|
||||
}
|
||||
|
||||
// Check if trigger key already exists
|
||||
var existing models.UpgradeTrigger
|
||||
if err := h.db.Where("trigger_key = ?", req.TriggerKey).First(&existing).Error; err == nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Trigger key already exists"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Trigger key already exists"})
|
||||
}
|
||||
|
||||
trigger := models.UpgradeTrigger{
|
||||
@@ -365,11 +347,10 @@ func (h *AdminLimitationsHandler) CreateUpgradeTrigger(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&trigger).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create upgrade trigger"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create upgrade trigger"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, toUpgradeTriggerResponse(&trigger))
|
||||
return c.JSON(http.StatusCreated, toUpgradeTriggerResponse(&trigger))
|
||||
}
|
||||
|
||||
// UpdateUpgradeTriggerRequest represents the update request
|
||||
@@ -383,27 +364,23 @@ type UpdateUpgradeTriggerRequest struct {
|
||||
}
|
||||
|
||||
// UpdateUpgradeTrigger handles PUT /api/admin/limitations/upgrade-triggers/:id
|
||||
func (h *AdminLimitationsHandler) UpdateUpgradeTrigger(c *gin.Context) {
|
||||
func (h *AdminLimitationsHandler) UpdateUpgradeTrigger(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var trigger models.UpgradeTrigger
|
||||
if err := h.db.First(&trigger, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Upgrade trigger not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Upgrade trigger not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch upgrade trigger"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch upgrade trigger"})
|
||||
}
|
||||
|
||||
var req UpdateUpgradeTriggerRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if req.TriggerKey != nil {
|
||||
@@ -416,15 +393,13 @@ func (h *AdminLimitationsHandler) UpdateUpgradeTrigger(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
if !validKey {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid trigger_key"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid trigger_key"})
|
||||
}
|
||||
// Check if key is already used by another trigger
|
||||
if *req.TriggerKey != trigger.TriggerKey {
|
||||
var existing models.UpgradeTrigger
|
||||
if err := h.db.Where("trigger_key = ?", *req.TriggerKey).First(&existing).Error; err == nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Trigger key already exists"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Trigger key already exists"})
|
||||
}
|
||||
}
|
||||
trigger.TriggerKey = *req.TriggerKey
|
||||
@@ -446,35 +421,30 @@ func (h *AdminLimitationsHandler) UpdateUpgradeTrigger(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&trigger).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update upgrade trigger"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update upgrade trigger"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, toUpgradeTriggerResponse(&trigger))
|
||||
return c.JSON(http.StatusOK, toUpgradeTriggerResponse(&trigger))
|
||||
}
|
||||
|
||||
// DeleteUpgradeTrigger handles DELETE /api/admin/limitations/upgrade-triggers/:id
|
||||
func (h *AdminLimitationsHandler) DeleteUpgradeTrigger(c *gin.Context) {
|
||||
func (h *AdminLimitationsHandler) DeleteUpgradeTrigger(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var trigger models.UpgradeTrigger
|
||||
if err := h.db.First(&trigger, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Upgrade trigger not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Upgrade trigger not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch upgrade trigger"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch upgrade trigger"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&trigger).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete upgrade trigger"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete upgrade trigger"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Upgrade trigger deleted"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Upgrade trigger deleted"})
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -28,18 +28,15 @@ func NewAdminLookupHandler(db *gorm.DB) *AdminLookupHandler {
|
||||
func (h *AdminLookupHandler) refreshCategoriesCache(ctx context.Context) {
|
||||
cache := services.GetCache()
|
||||
if cache == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var categories []models.TaskCategory
|
||||
if err := h.db.Order("display_order ASC, name ASC").Find(&categories).Error; err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to fetch categories for cache refresh")
|
||||
return
|
||||
}
|
||||
|
||||
if err := cache.CacheCategories(ctx, categories); err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to cache categories")
|
||||
return
|
||||
}
|
||||
log.Debug().Int("count", len(categories)).Msg("Refreshed categories cache")
|
||||
|
||||
@@ -51,18 +48,15 @@ func (h *AdminLookupHandler) refreshCategoriesCache(ctx context.Context) {
|
||||
func (h *AdminLookupHandler) refreshPrioritiesCache(ctx context.Context) {
|
||||
cache := services.GetCache()
|
||||
if cache == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var priorities []models.TaskPriority
|
||||
if err := h.db.Order("display_order ASC, level ASC").Find(&priorities).Error; err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to fetch priorities for cache refresh")
|
||||
return
|
||||
}
|
||||
|
||||
if err := cache.CachePriorities(ctx, priorities); err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to cache priorities")
|
||||
return
|
||||
}
|
||||
log.Debug().Int("count", len(priorities)).Msg("Refreshed priorities cache")
|
||||
|
||||
@@ -74,18 +68,15 @@ func (h *AdminLookupHandler) refreshPrioritiesCache(ctx context.Context) {
|
||||
func (h *AdminLookupHandler) refreshFrequenciesCache(ctx context.Context) {
|
||||
cache := services.GetCache()
|
||||
if cache == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var frequencies []models.TaskFrequency
|
||||
if err := h.db.Order("display_order ASC, name ASC").Find(&frequencies).Error; err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to fetch frequencies for cache refresh")
|
||||
return
|
||||
}
|
||||
|
||||
if err := cache.CacheFrequencies(ctx, frequencies); err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to cache frequencies")
|
||||
return
|
||||
}
|
||||
log.Debug().Int("count", len(frequencies)).Msg("Refreshed frequencies cache")
|
||||
|
||||
@@ -97,18 +88,15 @@ func (h *AdminLookupHandler) refreshFrequenciesCache(ctx context.Context) {
|
||||
func (h *AdminLookupHandler) refreshResidenceTypesCache(ctx context.Context) {
|
||||
cache := services.GetCache()
|
||||
if cache == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var types []models.ResidenceType
|
||||
if err := h.db.Order("name ASC").Find(&types).Error; err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to fetch residence types for cache refresh")
|
||||
return
|
||||
}
|
||||
|
||||
if err := cache.CacheResidenceTypes(ctx, types); err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to cache residence types")
|
||||
return
|
||||
}
|
||||
log.Debug().Int("count", len(types)).Msg("Refreshed residence types cache")
|
||||
|
||||
@@ -120,18 +108,15 @@ func (h *AdminLookupHandler) refreshResidenceTypesCache(ctx context.Context) {
|
||||
func (h *AdminLookupHandler) refreshSpecialtiesCache(ctx context.Context) {
|
||||
cache := services.GetCache()
|
||||
if cache == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var specialties []models.ContractorSpecialty
|
||||
if err := h.db.Order("display_order ASC, name ASC").Find(&specialties).Error; err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to fetch specialties for cache refresh")
|
||||
return
|
||||
}
|
||||
|
||||
if err := cache.CacheSpecialties(ctx, specialties); err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to cache specialties")
|
||||
return
|
||||
}
|
||||
log.Debug().Int("count", len(specialties)).Msg("Refreshed specialties cache")
|
||||
|
||||
@@ -144,12 +129,10 @@ func (h *AdminLookupHandler) refreshSpecialtiesCache(ctx context.Context) {
|
||||
func (h *AdminLookupHandler) invalidateSeededDataCache(ctx context.Context) {
|
||||
cache := services.GetCache()
|
||||
if cache == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := cache.InvalidateSeededData(ctx); err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to invalidate seeded data cache")
|
||||
return
|
||||
}
|
||||
log.Debug().Msg("Invalidated seeded data cache")
|
||||
}
|
||||
@@ -173,11 +156,10 @@ type CreateUpdateCategoryRequest struct {
|
||||
DisplayOrder *int `json:"display_order"`
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) ListCategories(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) ListCategories(c echo.Context) error {
|
||||
var categories []models.TaskCategory
|
||||
if err := h.db.Order("display_order ASC, name ASC").Find(&categories).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch categories"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch categories"})
|
||||
}
|
||||
|
||||
responses := make([]TaskCategoryResponse, len(categories))
|
||||
@@ -192,14 +174,13 @@ func (h *AdminLookupHandler) ListCategories(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": responses, "total": len(responses)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"data": responses, "total": len(responses)})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) CreateCategory(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) CreateCategory(c echo.Context) error {
|
||||
var req CreateUpdateCategoryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
category := models.TaskCategory{
|
||||
@@ -213,14 +194,13 @@ func (h *AdminLookupHandler) CreateCategory(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&category).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create category"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create category"})
|
||||
}
|
||||
|
||||
// Refresh cache after creating
|
||||
h.refreshCategoriesCache(c.Request.Context())
|
||||
h.refreshCategoriesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusCreated, TaskCategoryResponse{
|
||||
return c.JSON(http.StatusCreated, TaskCategoryResponse{
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
Description: category.Description,
|
||||
@@ -230,27 +210,23 @@ func (h *AdminLookupHandler) CreateCategory(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) UpdateCategory(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) UpdateCategory(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid category ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid category ID"})
|
||||
}
|
||||
|
||||
var category models.TaskCategory
|
||||
if err := h.db.First(&category, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Category not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Category not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch category"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch category"})
|
||||
}
|
||||
|
||||
var req CreateUpdateCategoryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
category.Name = req.Name
|
||||
@@ -262,14 +238,13 @@ func (h *AdminLookupHandler) UpdateCategory(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&category).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update category"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update category"})
|
||||
}
|
||||
|
||||
// Refresh cache after updating
|
||||
h.refreshCategoriesCache(c.Request.Context())
|
||||
h.refreshCategoriesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusOK, TaskCategoryResponse{
|
||||
return c.JSON(http.StatusOK, TaskCategoryResponse{
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
Description: category.Description,
|
||||
@@ -279,30 +254,27 @@ func (h *AdminLookupHandler) UpdateCategory(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) DeleteCategory(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) DeleteCategory(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid category ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid category ID"})
|
||||
}
|
||||
|
||||
// Check if category is in use
|
||||
var count int64
|
||||
h.db.Model(&models.Task{}).Where("category_id = ?", id).Count(&count)
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot delete category that is in use by tasks"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Cannot delete category that is in use by tasks"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&models.TaskCategory{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete category"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete category"})
|
||||
}
|
||||
|
||||
// Refresh cache after deleting
|
||||
h.refreshCategoriesCache(c.Request.Context())
|
||||
h.refreshCategoriesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Category deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Category deleted successfully"})
|
||||
}
|
||||
|
||||
// ========== Task Priorities ==========
|
||||
@@ -322,11 +294,10 @@ type CreateUpdatePriorityRequest struct {
|
||||
DisplayOrder *int `json:"display_order"`
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) ListPriorities(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) ListPriorities(c echo.Context) error {
|
||||
var priorities []models.TaskPriority
|
||||
if err := h.db.Order("display_order ASC, level ASC").Find(&priorities).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch priorities"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch priorities"})
|
||||
}
|
||||
|
||||
responses := make([]TaskPriorityResponse, len(priorities))
|
||||
@@ -340,14 +311,13 @@ func (h *AdminLookupHandler) ListPriorities(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": responses, "total": len(responses)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"data": responses, "total": len(responses)})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) CreatePriority(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) CreatePriority(c echo.Context) error {
|
||||
var req CreateUpdatePriorityRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
priority := models.TaskPriority{
|
||||
@@ -360,14 +330,13 @@ func (h *AdminLookupHandler) CreatePriority(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&priority).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create priority"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create priority"})
|
||||
}
|
||||
|
||||
// Refresh cache after creating
|
||||
h.refreshPrioritiesCache(c.Request.Context())
|
||||
h.refreshPrioritiesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusCreated, TaskPriorityResponse{
|
||||
return c.JSON(http.StatusCreated, TaskPriorityResponse{
|
||||
ID: priority.ID,
|
||||
Name: priority.Name,
|
||||
Level: priority.Level,
|
||||
@@ -376,27 +345,23 @@ func (h *AdminLookupHandler) CreatePriority(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) UpdatePriority(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) UpdatePriority(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid priority ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid priority ID"})
|
||||
}
|
||||
|
||||
var priority models.TaskPriority
|
||||
if err := h.db.First(&priority, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Priority not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Priority not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch priority"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch priority"})
|
||||
}
|
||||
|
||||
var req CreateUpdatePriorityRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
priority.Name = req.Name
|
||||
@@ -407,14 +372,13 @@ func (h *AdminLookupHandler) UpdatePriority(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&priority).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update priority"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update priority"})
|
||||
}
|
||||
|
||||
// Refresh cache after updating
|
||||
h.refreshPrioritiesCache(c.Request.Context())
|
||||
h.refreshPrioritiesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusOK, TaskPriorityResponse{
|
||||
return c.JSON(http.StatusOK, TaskPriorityResponse{
|
||||
ID: priority.ID,
|
||||
Name: priority.Name,
|
||||
Level: priority.Level,
|
||||
@@ -423,29 +387,26 @@ func (h *AdminLookupHandler) UpdatePriority(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) DeletePriority(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) DeletePriority(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid priority ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid priority ID"})
|
||||
}
|
||||
|
||||
var count int64
|
||||
h.db.Model(&models.Task{}).Where("priority_id = ?", id).Count(&count)
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot delete priority that is in use by tasks"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Cannot delete priority that is in use by tasks"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&models.TaskPriority{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete priority"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete priority"})
|
||||
}
|
||||
|
||||
// Refresh cache after deleting
|
||||
h.refreshPrioritiesCache(c.Request.Context())
|
||||
h.refreshPrioritiesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Priority deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Priority deleted successfully"})
|
||||
}
|
||||
|
||||
// ========== Task Frequencies ==========
|
||||
@@ -463,11 +424,10 @@ type CreateUpdateFrequencyRequest struct {
|
||||
DisplayOrder *int `json:"display_order"`
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) ListFrequencies(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) ListFrequencies(c echo.Context) error {
|
||||
var frequencies []models.TaskFrequency
|
||||
if err := h.db.Order("display_order ASC, name ASC").Find(&frequencies).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch frequencies"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch frequencies"})
|
||||
}
|
||||
|
||||
responses := make([]TaskFrequencyResponse, len(frequencies))
|
||||
@@ -480,14 +440,13 @@ func (h *AdminLookupHandler) ListFrequencies(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": responses, "total": len(responses)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"data": responses, "total": len(responses)})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) CreateFrequency(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) CreateFrequency(c echo.Context) error {
|
||||
var req CreateUpdateFrequencyRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
frequency := models.TaskFrequency{
|
||||
@@ -499,14 +458,13 @@ func (h *AdminLookupHandler) CreateFrequency(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&frequency).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create frequency"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create frequency"})
|
||||
}
|
||||
|
||||
// Refresh cache after creating
|
||||
h.refreshFrequenciesCache(c.Request.Context())
|
||||
h.refreshFrequenciesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusCreated, TaskFrequencyResponse{
|
||||
return c.JSON(http.StatusCreated, TaskFrequencyResponse{
|
||||
ID: frequency.ID,
|
||||
Name: frequency.Name,
|
||||
Days: frequency.Days,
|
||||
@@ -514,27 +472,23 @@ func (h *AdminLookupHandler) CreateFrequency(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) UpdateFrequency(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) UpdateFrequency(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid frequency ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid frequency ID"})
|
||||
}
|
||||
|
||||
var frequency models.TaskFrequency
|
||||
if err := h.db.First(&frequency, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Frequency not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Frequency not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch frequency"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch frequency"})
|
||||
}
|
||||
|
||||
var req CreateUpdateFrequencyRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
frequency.Name = req.Name
|
||||
@@ -544,14 +498,13 @@ func (h *AdminLookupHandler) UpdateFrequency(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&frequency).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update frequency"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update frequency"})
|
||||
}
|
||||
|
||||
// Refresh cache after updating
|
||||
h.refreshFrequenciesCache(c.Request.Context())
|
||||
h.refreshFrequenciesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusOK, TaskFrequencyResponse{
|
||||
return c.JSON(http.StatusOK, TaskFrequencyResponse{
|
||||
ID: frequency.ID,
|
||||
Name: frequency.Name,
|
||||
Days: frequency.Days,
|
||||
@@ -559,29 +512,26 @@ func (h *AdminLookupHandler) UpdateFrequency(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) DeleteFrequency(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) DeleteFrequency(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid frequency ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid frequency ID"})
|
||||
}
|
||||
|
||||
var count int64
|
||||
h.db.Model(&models.Task{}).Where("frequency_id = ?", id).Count(&count)
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot delete frequency that is in use by tasks"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Cannot delete frequency that is in use by tasks"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&models.TaskFrequency{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete frequency"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete frequency"})
|
||||
}
|
||||
|
||||
// Refresh cache after deleting
|
||||
h.refreshFrequenciesCache(c.Request.Context())
|
||||
h.refreshFrequenciesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Frequency deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Frequency deleted successfully"})
|
||||
}
|
||||
|
||||
// ========== Residence Types ==========
|
||||
@@ -595,11 +545,10 @@ type CreateUpdateResidenceTypeRequest struct {
|
||||
Name string `json:"name" binding:"required,max=20"`
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) ListResidenceTypes(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) ListResidenceTypes(c echo.Context) error {
|
||||
var types []models.ResidenceType
|
||||
if err := h.db.Order("name ASC").Find(&types).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch residence types"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch residence types"})
|
||||
}
|
||||
|
||||
responses := make([]ResidenceTypeResponse, len(types))
|
||||
@@ -610,92 +559,82 @@ func (h *AdminLookupHandler) ListResidenceTypes(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": responses, "total": len(responses)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"data": responses, "total": len(responses)})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) CreateResidenceType(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) CreateResidenceType(c echo.Context) error {
|
||||
var req CreateUpdateResidenceTypeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
residenceType := models.ResidenceType{Name: req.Name}
|
||||
if err := h.db.Create(&residenceType).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create residence type"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create residence type"})
|
||||
}
|
||||
|
||||
// Refresh cache after creating
|
||||
h.refreshResidenceTypesCache(c.Request.Context())
|
||||
h.refreshResidenceTypesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusCreated, ResidenceTypeResponse{
|
||||
return c.JSON(http.StatusCreated, ResidenceTypeResponse{
|
||||
ID: residenceType.ID,
|
||||
Name: residenceType.Name,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) UpdateResidenceType(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) UpdateResidenceType(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid residence type ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid residence type ID"})
|
||||
}
|
||||
|
||||
var residenceType models.ResidenceType
|
||||
if err := h.db.First(&residenceType, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Residence type not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Residence type not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch residence type"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch residence type"})
|
||||
}
|
||||
|
||||
var req CreateUpdateResidenceTypeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
residenceType.Name = req.Name
|
||||
if err := h.db.Save(&residenceType).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update residence type"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update residence type"})
|
||||
}
|
||||
|
||||
// Refresh cache after updating
|
||||
h.refreshResidenceTypesCache(c.Request.Context())
|
||||
h.refreshResidenceTypesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusOK, ResidenceTypeResponse{
|
||||
return c.JSON(http.StatusOK, ResidenceTypeResponse{
|
||||
ID: residenceType.ID,
|
||||
Name: residenceType.Name,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) DeleteResidenceType(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) DeleteResidenceType(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid residence type ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid residence type ID"})
|
||||
}
|
||||
|
||||
var count int64
|
||||
h.db.Model(&models.Residence{}).Where("property_type_id = ?", id).Count(&count)
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot delete residence type that is in use"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Cannot delete residence type that is in use"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&models.ResidenceType{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete residence type"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete residence type"})
|
||||
}
|
||||
|
||||
// Refresh cache after deleting
|
||||
h.refreshResidenceTypesCache(c.Request.Context())
|
||||
h.refreshResidenceTypesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Residence type deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Residence type deleted successfully"})
|
||||
}
|
||||
|
||||
// ========== Contractor Specialties ==========
|
||||
@@ -715,11 +654,10 @@ type CreateUpdateSpecialtyRequest struct {
|
||||
DisplayOrder *int `json:"display_order"`
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) ListSpecialties(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) ListSpecialties(c echo.Context) error {
|
||||
var specialties []models.ContractorSpecialty
|
||||
if err := h.db.Order("display_order ASC, name ASC").Find(&specialties).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch specialties"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch specialties"})
|
||||
}
|
||||
|
||||
responses := make([]ContractorSpecialtyResponse, len(specialties))
|
||||
@@ -733,14 +671,13 @@ func (h *AdminLookupHandler) ListSpecialties(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": responses, "total": len(responses)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"data": responses, "total": len(responses)})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) CreateSpecialty(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) CreateSpecialty(c echo.Context) error {
|
||||
var req CreateUpdateSpecialtyRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
specialty := models.ContractorSpecialty{
|
||||
@@ -753,14 +690,13 @@ func (h *AdminLookupHandler) CreateSpecialty(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&specialty).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create specialty"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create specialty"})
|
||||
}
|
||||
|
||||
// Refresh cache after creating
|
||||
h.refreshSpecialtiesCache(c.Request.Context())
|
||||
h.refreshSpecialtiesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusCreated, ContractorSpecialtyResponse{
|
||||
return c.JSON(http.StatusCreated, ContractorSpecialtyResponse{
|
||||
ID: specialty.ID,
|
||||
Name: specialty.Name,
|
||||
Description: specialty.Description,
|
||||
@@ -769,27 +705,23 @@ func (h *AdminLookupHandler) CreateSpecialty(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) UpdateSpecialty(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) UpdateSpecialty(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid specialty ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid specialty ID"})
|
||||
}
|
||||
|
||||
var specialty models.ContractorSpecialty
|
||||
if err := h.db.First(&specialty, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Specialty not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Specialty not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch specialty"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch specialty"})
|
||||
}
|
||||
|
||||
var req CreateUpdateSpecialtyRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
specialty.Name = req.Name
|
||||
@@ -800,14 +732,13 @@ func (h *AdminLookupHandler) UpdateSpecialty(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&specialty).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update specialty"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update specialty"})
|
||||
}
|
||||
|
||||
// Refresh cache after updating
|
||||
h.refreshSpecialtiesCache(c.Request.Context())
|
||||
h.refreshSpecialtiesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusOK, ContractorSpecialtyResponse{
|
||||
return c.JSON(http.StatusOK, ContractorSpecialtyResponse{
|
||||
ID: specialty.ID,
|
||||
Name: specialty.Name,
|
||||
Description: specialty.Description,
|
||||
@@ -816,30 +747,27 @@ func (h *AdminLookupHandler) UpdateSpecialty(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminLookupHandler) DeleteSpecialty(c *gin.Context) {
|
||||
func (h *AdminLookupHandler) DeleteSpecialty(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid specialty ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid specialty ID"})
|
||||
}
|
||||
|
||||
// Check if in use via many-to-many relationship
|
||||
var count int64
|
||||
h.db.Table("task_contractor_specialties").Where("contractorspecialty_id = ?", id).Count(&count)
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot delete specialty that is in use by contractors"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Cannot delete specialty that is in use by contractors"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&models.ContractorSpecialty{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete specialty"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete specialty"})
|
||||
}
|
||||
|
||||
// Refresh cache after deleting
|
||||
h.refreshSpecialtiesCache(c.Request.Context())
|
||||
h.refreshSpecialtiesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Specialty deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Specialty deleted successfully"})
|
||||
}
|
||||
|
||||
// Ensure dto import is used
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -32,11 +32,10 @@ func NewAdminNotificationHandler(db *gorm.DB, emailService *services.EmailServic
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/notifications
|
||||
func (h *AdminNotificationHandler) List(c *gin.Context) {
|
||||
func (h *AdminNotificationHandler) List(c echo.Context) error {
|
||||
var filters dto.NotificationFilters
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var notifications []models.Notification
|
||||
@@ -79,8 +78,7 @@ func (h *AdminNotificationHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(¬ifications).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch notifications"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch notifications"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -89,15 +87,14 @@ func (h *AdminNotificationHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toNotificationResponse(¬if)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/notifications/:id
|
||||
func (h *AdminNotificationHandler) Get(c *gin.Context) {
|
||||
func (h *AdminNotificationHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid notification ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid notification ID"})
|
||||
}
|
||||
|
||||
var notification models.Notification
|
||||
@@ -105,65 +102,55 @@ func (h *AdminNotificationHandler) Get(c *gin.Context) {
|
||||
Preload("User").
|
||||
First(¬ification, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Notification not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Notification not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch notification"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch notification"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toNotificationDetailResponse(¬ification))
|
||||
return c.JSON(http.StatusOK, h.toNotificationDetailResponse(¬ification))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/notifications/:id
|
||||
func (h *AdminNotificationHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminNotificationHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid notification ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid notification ID"})
|
||||
}
|
||||
|
||||
var notification models.Notification
|
||||
if err := h.db.First(¬ification, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Notification not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Notification not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch notification"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch notification"})
|
||||
}
|
||||
|
||||
// Hard delete notifications
|
||||
if err := h.db.Delete(¬ification).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete notification"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete notification"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Notification deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Notification deleted successfully"})
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/notifications/:id
|
||||
func (h *AdminNotificationHandler) Update(c *gin.Context) {
|
||||
func (h *AdminNotificationHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid notification ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid notification ID"})
|
||||
}
|
||||
|
||||
var notification models.Notification
|
||||
if err := h.db.First(¬ification, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Notification not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Notification not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch notification"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch notification"})
|
||||
}
|
||||
|
||||
var req dto.UpdateNotificationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
@@ -183,16 +170,15 @@ func (h *AdminNotificationHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(¬ification).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update notification"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update notification"})
|
||||
}
|
||||
|
||||
h.db.Preload("User").First(¬ification, id)
|
||||
c.JSON(http.StatusOK, h.toNotificationResponse(¬ification))
|
||||
return c.JSON(http.StatusOK, h.toNotificationResponse(¬ification))
|
||||
}
|
||||
|
||||
// GetStats handles GET /api/admin/notifications/stats
|
||||
func (h *AdminNotificationHandler) GetStats(c *gin.Context) {
|
||||
func (h *AdminNotificationHandler) GetStats(c echo.Context) error {
|
||||
var total, sent, read, pending int64
|
||||
|
||||
h.db.Model(&models.Notification{}).Count(&total)
|
||||
@@ -200,7 +186,7 @@ func (h *AdminNotificationHandler) GetStats(c *gin.Context) {
|
||||
h.db.Model(&models.Notification{}).Where("read = ?", true).Count(&read)
|
||||
h.db.Model(&models.Notification{}).Where("sent = ?", false).Count(&pending)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"total": total,
|
||||
"sent": sent,
|
||||
"read": read,
|
||||
@@ -245,22 +231,19 @@ func (h *AdminNotificationHandler) toNotificationDetailResponse(notif *models.No
|
||||
}
|
||||
|
||||
// SendTestNotification handles POST /api/admin/notifications/send-test
|
||||
func (h *AdminNotificationHandler) SendTestNotification(c *gin.Context) {
|
||||
func (h *AdminNotificationHandler) SendTestNotification(c echo.Context) error {
|
||||
var req dto.SendTestNotificationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Verify user exists
|
||||
var user models.User
|
||||
if err := h.db.First(&user, req.UserID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user"})
|
||||
}
|
||||
|
||||
// Get user's device tokens
|
||||
@@ -271,8 +254,7 @@ func (h *AdminNotificationHandler) SendTestNotification(c *gin.Context) {
|
||||
h.db.Where("user_id = ? AND active = ?", req.UserID, true).Find(&androidDevices)
|
||||
|
||||
if len(iosDevices) == 0 && len(androidDevices) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "User has no registered devices"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "User has no registered devices"})
|
||||
}
|
||||
|
||||
// Create notification record
|
||||
@@ -287,8 +269,7 @@ func (h *AdminNotificationHandler) SendTestNotification(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(¬ification).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create notification record"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create notification record"})
|
||||
}
|
||||
|
||||
// Collect tokens
|
||||
@@ -316,15 +297,13 @@ func (h *AdminNotificationHandler) SendTestNotification(c *gin.Context) {
|
||||
h.db.Model(¬ification).Updates(map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"error": "Failed to send push notification",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Push notification service not configured"})
|
||||
return
|
||||
return c.JSON(http.StatusServiceUnavailable, map[string]interface{}{"error": "Push notification service not configured"})
|
||||
}
|
||||
|
||||
// Mark as sent
|
||||
@@ -333,10 +312,10 @@ func (h *AdminNotificationHandler) SendTestNotification(c *gin.Context) {
|
||||
"sent_at": now,
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"message": "Test notification sent successfully",
|
||||
"notification_id": notification.ID,
|
||||
"devices": gin.H{
|
||||
"devices": map[string]interface{}{
|
||||
"ios": len(iosTokens),
|
||||
"android": len(androidTokens),
|
||||
},
|
||||
@@ -344,33 +323,28 @@ func (h *AdminNotificationHandler) SendTestNotification(c *gin.Context) {
|
||||
}
|
||||
|
||||
// SendTestEmail handles POST /api/admin/emails/send-test
|
||||
func (h *AdminNotificationHandler) SendTestEmail(c *gin.Context) {
|
||||
func (h *AdminNotificationHandler) SendTestEmail(c echo.Context) error {
|
||||
var req dto.SendTestEmailRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Verify user exists
|
||||
var user models.User
|
||||
if err := h.db.First(&user, req.UserID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user"})
|
||||
}
|
||||
|
||||
if user.Email == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "User has no email address"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "User has no email address"})
|
||||
}
|
||||
|
||||
// Send email
|
||||
if h.emailService == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Email service not configured"})
|
||||
return
|
||||
return c.JSON(http.StatusServiceUnavailable, map[string]interface{}{"error": "Email service not configured"})
|
||||
}
|
||||
|
||||
// Create HTML body with basic styling
|
||||
@@ -390,61 +364,54 @@ func (h *AdminNotificationHandler) SendTestEmail(c *gin.Context) {
|
||||
|
||||
err := h.emailService.SendEmail(user.Email, req.Subject, htmlBody, req.Body)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"error": "Failed to send email",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"message": "Test email sent successfully",
|
||||
"to": user.Email,
|
||||
})
|
||||
}
|
||||
|
||||
// SendPostVerificationEmail handles POST /api/admin/emails/send-post-verification
|
||||
func (h *AdminNotificationHandler) SendPostVerificationEmail(c *gin.Context) {
|
||||
func (h *AdminNotificationHandler) SendPostVerificationEmail(c echo.Context) error {
|
||||
var req struct {
|
||||
UserID uint `json:"user_id" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "user_id is required"})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "user_id is required"})
|
||||
}
|
||||
|
||||
// Verify user exists
|
||||
var user models.User
|
||||
if err := h.db.First(&user, req.UserID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user"})
|
||||
}
|
||||
|
||||
if user.Email == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "User has no email address"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "User has no email address"})
|
||||
}
|
||||
|
||||
// Send email
|
||||
if h.emailService == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Email service not configured"})
|
||||
return
|
||||
return c.JSON(http.StatusServiceUnavailable, map[string]interface{}{"error": "Email service not configured"})
|
||||
}
|
||||
|
||||
err := h.emailService.SendPostVerificationEmail(user.Email, user.FirstName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"error": "Failed to send email",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"message": "Post-verification email sent successfully",
|
||||
"to": user.Email,
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -49,11 +49,10 @@ type NotificationPrefResponse struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/notification-prefs
|
||||
func (h *AdminNotificationPrefsHandler) List(c *gin.Context) {
|
||||
func (h *AdminNotificationPrefsHandler) List(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var prefs []models.NotificationPreference
|
||||
@@ -85,8 +84,7 @@ func (h *AdminNotificationPrefsHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&prefs).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch notification preferences"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch notification preferences"})
|
||||
}
|
||||
|
||||
// Get user info for each preference
|
||||
@@ -130,31 +128,28 @@ func (h *AdminNotificationPrefsHandler) List(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/notification-prefs/:id
|
||||
func (h *AdminNotificationPrefsHandler) Get(c *gin.Context) {
|
||||
func (h *AdminNotificationPrefsHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var pref models.NotificationPreference
|
||||
if err := h.db.First(&pref, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Notification preference not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Notification preference not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch notification preference"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch notification preference"})
|
||||
}
|
||||
|
||||
var user models.User
|
||||
h.db.First(&user, pref.UserID)
|
||||
|
||||
c.JSON(http.StatusOK, NotificationPrefResponse{
|
||||
return c.JSON(http.StatusOK, NotificationPrefResponse{
|
||||
ID: pref.ID,
|
||||
UserID: pref.UserID,
|
||||
Username: user.Username,
|
||||
@@ -197,27 +192,23 @@ type UpdateNotificationPrefRequest struct {
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/notification-prefs/:id
|
||||
func (h *AdminNotificationPrefsHandler) Update(c *gin.Context) {
|
||||
func (h *AdminNotificationPrefsHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var pref models.NotificationPreference
|
||||
if err := h.db.First(&pref, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Notification preference not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Notification preference not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch notification preference"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch notification preference"})
|
||||
}
|
||||
|
||||
var req UpdateNotificationPrefRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Apply updates
|
||||
@@ -261,14 +252,13 @@ func (h *AdminNotificationPrefsHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&pref).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update notification preference"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update notification preference"})
|
||||
}
|
||||
|
||||
var user models.User
|
||||
h.db.First(&user, pref.UserID)
|
||||
|
||||
c.JSON(http.StatusOK, NotificationPrefResponse{
|
||||
return c.JSON(http.StatusOK, NotificationPrefResponse{
|
||||
ID: pref.ID,
|
||||
UserID: pref.UserID,
|
||||
Username: user.Username,
|
||||
@@ -291,48 +281,42 @@ func (h *AdminNotificationPrefsHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/notification-prefs/:id
|
||||
func (h *AdminNotificationPrefsHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminNotificationPrefsHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
result := h.db.Delete(&models.NotificationPreference{}, id)
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete notification preference"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete notification preference"})
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Notification preference not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Notification preference not found"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Notification preference deleted"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Notification preference deleted"})
|
||||
}
|
||||
|
||||
// GetByUser handles GET /api/admin/notification-prefs/user/:user_id
|
||||
func (h *AdminNotificationPrefsHandler) GetByUser(c *gin.Context) {
|
||||
func (h *AdminNotificationPrefsHandler) GetByUser(c echo.Context) error {
|
||||
userID, err := strconv.ParseUint(c.Param("user_id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid user ID"})
|
||||
}
|
||||
|
||||
var pref models.NotificationPreference
|
||||
if err := h.db.Where("user_id = ?", userID).First(&pref).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Notification preference not found for this user"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Notification preference not found for this user"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch notification preference"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch notification preference"})
|
||||
}
|
||||
|
||||
var user models.User
|
||||
h.db.First(&user, pref.UserID)
|
||||
|
||||
c.JSON(http.StatusOK, NotificationPrefResponse{
|
||||
return c.JSON(http.StatusOK, NotificationPrefResponse{
|
||||
ID: pref.ID,
|
||||
UserID: pref.UserID,
|
||||
Username: user.Username,
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/models"
|
||||
@@ -62,12 +62,21 @@ type OnboardingStatsResponse struct {
|
||||
|
||||
// List returns paginated list of onboarding emails
|
||||
// GET /api/admin/onboarding-emails
|
||||
func (h *AdminOnboardingHandler) List(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
emailType := c.Query("email_type")
|
||||
userID, _ := strconv.Atoi(c.Query("user_id"))
|
||||
opened := c.Query("opened")
|
||||
func (h *AdminOnboardingHandler) List(c echo.Context) error {
|
||||
pageStr := c.QueryParam("page")
|
||||
if pageStr == "" {
|
||||
pageStr = "1"
|
||||
}
|
||||
page, _ := strconv.Atoi(pageStr)
|
||||
|
||||
pageSizeStr := c.QueryParam("page_size")
|
||||
if pageSizeStr == "" {
|
||||
pageSizeStr = "20"
|
||||
}
|
||||
pageSize, _ := strconv.Atoi(pageSizeStr)
|
||||
emailType := c.QueryParam("email_type")
|
||||
userID, _ := strconv.Atoi(c.QueryParam("user_id"))
|
||||
opened := c.QueryParam("opened")
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
@@ -96,15 +105,13 @@ func (h *AdminOnboardingHandler) List(c *gin.Context) {
|
||||
// Count total
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to count emails"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to count emails"})
|
||||
}
|
||||
|
||||
// Get paginated results
|
||||
var emails []models.OnboardingEmail
|
||||
if err := query.Order("sent_at DESC").Offset(offset).Limit(pageSize).Find(&emails).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch emails"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch emails"})
|
||||
}
|
||||
|
||||
// Transform to response
|
||||
@@ -118,7 +125,7 @@ func (h *AdminOnboardingHandler) List(c *gin.Context) {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, OnboardingEmailListResponse{
|
||||
return c.JSON(http.StatusOK, OnboardingEmailListResponse{
|
||||
Data: data,
|
||||
Total: total,
|
||||
Page: page,
|
||||
@@ -129,7 +136,7 @@ func (h *AdminOnboardingHandler) List(c *gin.Context) {
|
||||
|
||||
// GetStats returns onboarding email statistics
|
||||
// GET /api/admin/onboarding-emails/stats
|
||||
func (h *AdminOnboardingHandler) GetStats(c *gin.Context) {
|
||||
func (h *AdminOnboardingHandler) GetStats(c echo.Context) error {
|
||||
var stats OnboardingStatsResponse
|
||||
|
||||
// No residence email stats
|
||||
@@ -162,22 +169,20 @@ func (h *AdminOnboardingHandler) GetStats(c *gin.Context) {
|
||||
stats.OverallRate = float64(stats.TotalOpened) / float64(stats.TotalSent) * 100
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, stats)
|
||||
return c.JSON(http.StatusOK, stats)
|
||||
}
|
||||
|
||||
// GetByUser returns onboarding emails for a specific user
|
||||
// GET /api/admin/onboarding-emails/user/:user_id
|
||||
func (h *AdminOnboardingHandler) GetByUser(c *gin.Context) {
|
||||
func (h *AdminOnboardingHandler) GetByUser(c echo.Context) error {
|
||||
userID, err := strconv.ParseUint(c.Param("user_id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid user ID"})
|
||||
}
|
||||
|
||||
var emails []models.OnboardingEmail
|
||||
if err := h.db.Where("user_id = ?", userID).Order("sent_at DESC").Find(&emails).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch emails"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch emails"})
|
||||
}
|
||||
|
||||
// Transform to response
|
||||
@@ -186,7 +191,7 @@ func (h *AdminOnboardingHandler) GetByUser(c *gin.Context) {
|
||||
data[i] = transformOnboardingEmail(email)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"data": data,
|
||||
"user_id": userID,
|
||||
"count": len(data),
|
||||
@@ -195,71 +200,62 @@ func (h *AdminOnboardingHandler) GetByUser(c *gin.Context) {
|
||||
|
||||
// Get returns a single onboarding email by ID
|
||||
// GET /api/admin/onboarding-emails/:id
|
||||
func (h *AdminOnboardingHandler) Get(c *gin.Context) {
|
||||
func (h *AdminOnboardingHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var email models.OnboardingEmail
|
||||
if err := h.db.Preload("User").First(&email, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Email not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Email not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch email"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch email"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, transformOnboardingEmail(email))
|
||||
return c.JSON(http.StatusOK, transformOnboardingEmail(email))
|
||||
}
|
||||
|
||||
// Delete removes an onboarding email record
|
||||
// DELETE /api/admin/onboarding-emails/:id
|
||||
func (h *AdminOnboardingHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminOnboardingHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
result := h.db.Delete(&models.OnboardingEmail{}, id)
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete email"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete email"})
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Email not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Email not found"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Email record deleted"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Email record deleted"})
|
||||
}
|
||||
|
||||
// BulkDelete removes multiple onboarding email records
|
||||
// DELETE /api/admin/onboarding-emails/bulk
|
||||
func (h *AdminOnboardingHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminOnboardingHandler) BulkDelete(c echo.Context) error {
|
||||
var req struct {
|
||||
IDs []uint `json:"ids" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid request"})
|
||||
}
|
||||
|
||||
if len(req.IDs) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "No IDs provided"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "No IDs provided"})
|
||||
}
|
||||
|
||||
result := h.db.Delete(&models.OnboardingEmail{}, req.IDs)
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete emails"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete emails"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"message": "Emails deleted",
|
||||
"count": result.RowsAffected,
|
||||
})
|
||||
@@ -273,16 +269,14 @@ type SendOnboardingEmailRequest struct {
|
||||
|
||||
// Send sends an onboarding email to a specific user
|
||||
// POST /api/admin/onboarding-emails/send
|
||||
func (h *AdminOnboardingHandler) Send(c *gin.Context) {
|
||||
func (h *AdminOnboardingHandler) Send(c echo.Context) error {
|
||||
if h.onboardingService == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Onboarding email service not configured"})
|
||||
return
|
||||
return c.JSON(http.StatusServiceUnavailable, map[string]interface{}{"error": "Onboarding email service not configured"})
|
||||
}
|
||||
|
||||
var req SendOnboardingEmailRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: user_id and email_type are required"})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid request: user_id and email_type are required"})
|
||||
}
|
||||
|
||||
// Validate email type
|
||||
@@ -293,28 +287,24 @@ func (h *AdminOnboardingHandler) Send(c *gin.Context) {
|
||||
case "no_tasks":
|
||||
emailType = models.OnboardingEmailNoTasks
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid email_type. Must be 'no_residence' or 'no_tasks'"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid email_type. Must be 'no_residence' or 'no_tasks'"})
|
||||
}
|
||||
|
||||
// Get user email for response
|
||||
var user models.User
|
||||
if err := h.db.First(&user, req.UserID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user"})
|
||||
}
|
||||
|
||||
// Send the email
|
||||
if err := h.onboardingService.SendOnboardingEmailToUser(req.UserID, emailType); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"message": "Onboarding email sent successfully",
|
||||
"user_id": req.UserID,
|
||||
"email": user.Email,
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -36,11 +36,10 @@ type PasswordResetCodeResponse struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/password-reset-codes
|
||||
func (h *AdminPasswordResetCodeHandler) List(c *gin.Context) {
|
||||
func (h *AdminPasswordResetCodeHandler) List(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var codes []models.PasswordResetCode
|
||||
@@ -72,8 +71,7 @@ func (h *AdminPasswordResetCodeHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&codes).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch password reset codes"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch password reset codes"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -93,25 +91,22 @@ func (h *AdminPasswordResetCodeHandler) List(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/password-reset-codes/:id
|
||||
func (h *AdminPasswordResetCodeHandler) Get(c *gin.Context) {
|
||||
func (h *AdminPasswordResetCodeHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var code models.PasswordResetCode
|
||||
if err := h.db.Preload("User").First(&code, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Password reset code not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Password reset code not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch password reset code"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch password reset code"})
|
||||
}
|
||||
|
||||
response := PasswordResetCodeResponse{
|
||||
@@ -127,44 +122,39 @@ func (h *AdminPasswordResetCodeHandler) Get(c *gin.Context) {
|
||||
CreatedAt: code.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/password-reset-codes/:id
|
||||
func (h *AdminPasswordResetCodeHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminPasswordResetCodeHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
result := h.db.Delete(&models.PasswordResetCode{}, id)
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete password reset code"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete password reset code"})
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Password reset code not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Password reset code not found"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Password reset code deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Password reset code deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/password-reset-codes/bulk
|
||||
func (h *AdminPasswordResetCodeHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminPasswordResetCodeHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
result := h.db.Where("id IN ?", req.IDs).Delete(&models.PasswordResetCode{})
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete password reset codes"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete password reset codes"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Password reset codes deleted successfully", "count": result.RowsAffected})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Password reset codes deleted successfully", "count": result.RowsAffected})
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -38,11 +38,10 @@ type PromotionResponse struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/promotions
|
||||
func (h *AdminPromotionHandler) List(c *gin.Context) {
|
||||
func (h *AdminPromotionHandler) List(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var promotions []models.Promotion
|
||||
@@ -65,8 +64,7 @@ func (h *AdminPromotionHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&promotions).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch promotions"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch promotions"})
|
||||
}
|
||||
|
||||
responses := make([]PromotionResponse, len(promotions))
|
||||
@@ -86,25 +84,22 @@ func (h *AdminPromotionHandler) List(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/promotions/:id
|
||||
func (h *AdminPromotionHandler) Get(c *gin.Context) {
|
||||
func (h *AdminPromotionHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var promotion models.Promotion
|
||||
if err := h.db.First(&promotion, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Promotion not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Promotion not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch promotion"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch promotion"})
|
||||
}
|
||||
|
||||
response := PromotionResponse{
|
||||
@@ -121,11 +116,11 @@ func (h *AdminPromotionHandler) Get(c *gin.Context) {
|
||||
UpdatedAt: promotion.UpdatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Create handles POST /api/admin/promotions
|
||||
func (h *AdminPromotionHandler) Create(c *gin.Context) {
|
||||
func (h *AdminPromotionHandler) Create(c echo.Context) error {
|
||||
var req struct {
|
||||
PromotionID string `json:"promotion_id" binding:"required"`
|
||||
Title string `json:"title" binding:"required"`
|
||||
@@ -137,17 +132,15 @@ func (h *AdminPromotionHandler) Create(c *gin.Context) {
|
||||
IsActive *bool `json:"is_active"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
startDate, err := time.Parse("2006-01-02T15:04:05Z", req.StartDate)
|
||||
if err != nil {
|
||||
startDate, err = time.Parse("2006-01-02", req.StartDate)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid start_date format"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid start_date format"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,8 +148,7 @@ func (h *AdminPromotionHandler) Create(c *gin.Context) {
|
||||
if err != nil {
|
||||
endDate, err = time.Parse("2006-01-02", req.EndDate)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid end_date format"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid end_date format"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,11 +173,10 @@ func (h *AdminPromotionHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&promotion).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create promotion"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create promotion"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, PromotionResponse{
|
||||
return c.JSON(http.StatusCreated, PromotionResponse{
|
||||
ID: promotion.ID,
|
||||
PromotionID: promotion.PromotionID,
|
||||
Title: promotion.Title,
|
||||
@@ -201,21 +192,18 @@ func (h *AdminPromotionHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/promotions/:id
|
||||
func (h *AdminPromotionHandler) Update(c *gin.Context) {
|
||||
func (h *AdminPromotionHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var promotion models.Promotion
|
||||
if err := h.db.First(&promotion, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Promotion not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Promotion not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch promotion"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch promotion"})
|
||||
}
|
||||
|
||||
var req struct {
|
||||
@@ -229,9 +217,8 @@ func (h *AdminPromotionHandler) Update(c *gin.Context) {
|
||||
IsActive *bool `json:"is_active"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if req.PromotionID != nil {
|
||||
@@ -251,8 +238,7 @@ func (h *AdminPromotionHandler) Update(c *gin.Context) {
|
||||
if err != nil {
|
||||
startDate, err = time.Parse("2006-01-02", *req.StartDate)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid start_date format"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid start_date format"})
|
||||
}
|
||||
}
|
||||
promotion.StartDate = startDate
|
||||
@@ -262,8 +248,7 @@ func (h *AdminPromotionHandler) Update(c *gin.Context) {
|
||||
if err != nil {
|
||||
endDate, err = time.Parse("2006-01-02", *req.EndDate)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid end_date format"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid end_date format"})
|
||||
}
|
||||
}
|
||||
promotion.EndDate = endDate
|
||||
@@ -280,11 +265,10 @@ func (h *AdminPromotionHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&promotion).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update promotion"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update promotion"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, PromotionResponse{
|
||||
return c.JSON(http.StatusOK, PromotionResponse{
|
||||
ID: promotion.ID,
|
||||
PromotionID: promotion.PromotionID,
|
||||
Title: promotion.Title,
|
||||
@@ -300,23 +284,20 @@ func (h *AdminPromotionHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/promotions/:id
|
||||
func (h *AdminPromotionHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminPromotionHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
result := h.db.Delete(&models.Promotion{}, id)
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete promotion"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete promotion"})
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Promotion not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Promotion not found"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Promotion deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Promotion deleted successfully"})
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/shopspring/decimal"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -24,11 +24,10 @@ func NewAdminResidenceHandler(db *gorm.DB) *AdminResidenceHandler {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/residences
|
||||
func (h *AdminResidenceHandler) List(c *gin.Context) {
|
||||
func (h *AdminResidenceHandler) List(c echo.Context) error {
|
||||
var filters dto.ResidenceFilters
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var residences []models.Residence
|
||||
@@ -70,8 +69,7 @@ func (h *AdminResidenceHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&residences).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch residences"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch residences"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -80,25 +78,22 @@ func (h *AdminResidenceHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toResidenceResponse(&res)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/residences/:id
|
||||
func (h *AdminResidenceHandler) Get(c *gin.Context) {
|
||||
func (h *AdminResidenceHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid residence ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid residence ID"})
|
||||
}
|
||||
|
||||
var residence models.Residence
|
||||
if err := h.db.Preload("Owner").Preload("PropertyType").Preload("Users").First(&residence, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Residence not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Residence not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch residence"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch residence"})
|
||||
}
|
||||
|
||||
response := dto.ResidenceDetailResponse{
|
||||
@@ -128,39 +123,34 @@ func (h *AdminResidenceHandler) Get(c *gin.Context) {
|
||||
response.TaskCount = int(taskCount)
|
||||
response.DocumentCount = int(documentCount)
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/residences/:id
|
||||
func (h *AdminResidenceHandler) Update(c *gin.Context) {
|
||||
func (h *AdminResidenceHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid residence ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid residence ID"})
|
||||
}
|
||||
|
||||
var residence models.Residence
|
||||
if err := h.db.First(&residence, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Residence not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Residence not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch residence"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch residence"})
|
||||
}
|
||||
|
||||
var req dto.UpdateResidenceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if req.OwnerID != nil {
|
||||
// Verify owner exists
|
||||
var owner models.User
|
||||
if err := h.db.First(&owner, *req.OwnerID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Owner not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Owner not found"})
|
||||
}
|
||||
residence.OwnerID = *req.OwnerID
|
||||
}
|
||||
@@ -225,27 +215,24 @@ func (h *AdminResidenceHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&residence).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update residence"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update residence"})
|
||||
}
|
||||
|
||||
h.db.Preload("Owner").Preload("PropertyType").First(&residence, id)
|
||||
c.JSON(http.StatusOK, h.toResidenceResponse(&residence))
|
||||
return c.JSON(http.StatusOK, h.toResidenceResponse(&residence))
|
||||
}
|
||||
|
||||
// Create handles POST /api/admin/residences
|
||||
func (h *AdminResidenceHandler) Create(c *gin.Context) {
|
||||
func (h *AdminResidenceHandler) Create(c echo.Context) error {
|
||||
var req dto.CreateResidenceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Verify owner exists
|
||||
var owner models.User
|
||||
if err := h.db.First(&owner, req.OwnerID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Owner not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Owner not found"})
|
||||
}
|
||||
|
||||
residence := models.Residence{
|
||||
@@ -278,57 +265,50 @@ func (h *AdminResidenceHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&residence).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create residence"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create residence"})
|
||||
}
|
||||
|
||||
h.db.Preload("Owner").Preload("PropertyType").First(&residence, residence.ID)
|
||||
c.JSON(http.StatusCreated, h.toResidenceResponse(&residence))
|
||||
return c.JSON(http.StatusCreated, h.toResidenceResponse(&residence))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/residences/:id
|
||||
func (h *AdminResidenceHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminResidenceHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid residence ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid residence ID"})
|
||||
}
|
||||
|
||||
var residence models.Residence
|
||||
if err := h.db.First(&residence, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Residence not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Residence not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch residence"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch residence"})
|
||||
}
|
||||
|
||||
// Soft delete
|
||||
residence.IsActive = false
|
||||
if err := h.db.Save(&residence).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete residence"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete residence"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Residence deactivated successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Residence deactivated successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/residences/bulk
|
||||
func (h *AdminResidenceHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminResidenceHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Soft delete - deactivate all
|
||||
if err := h.db.Model(&models.Residence{}).Where("id IN ?", req.IDs).Update("is_active", false).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete residences"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete residences"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Residences deactivated successfully", "count": len(req.IDs)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Residences deactivated successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
func (h *AdminResidenceHandler) toResidenceResponse(res *models.Residence) dto.ResidenceResponse {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -33,7 +33,7 @@ type SettingsResponse struct {
|
||||
}
|
||||
|
||||
// GetSettings handles GET /api/admin/settings
|
||||
func (h *AdminSettingsHandler) GetSettings(c *gin.Context) {
|
||||
func (h *AdminSettingsHandler) GetSettings(c echo.Context) error {
|
||||
var settings models.SubscriptionSettings
|
||||
if err := h.db.First(&settings, 1).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
@@ -41,12 +41,11 @@ func (h *AdminSettingsHandler) GetSettings(c *gin.Context) {
|
||||
settings = models.SubscriptionSettings{ID: 1, EnableLimitations: false, EnableMonitoring: true}
|
||||
h.db.Create(&settings)
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch settings"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch settings"})
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, SettingsResponse{
|
||||
return c.JSON(http.StatusOK, SettingsResponse{
|
||||
EnableLimitations: settings.EnableLimitations,
|
||||
EnableMonitoring: settings.EnableMonitoring,
|
||||
})
|
||||
@@ -59,11 +58,10 @@ type UpdateSettingsRequest struct {
|
||||
}
|
||||
|
||||
// UpdateSettings handles PUT /api/admin/settings
|
||||
func (h *AdminSettingsHandler) UpdateSettings(c *gin.Context) {
|
||||
func (h *AdminSettingsHandler) UpdateSettings(c echo.Context) error {
|
||||
var req UpdateSettingsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var settings models.SubscriptionSettings
|
||||
@@ -71,8 +69,7 @@ func (h *AdminSettingsHandler) UpdateSettings(c *gin.Context) {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
settings = models.SubscriptionSettings{ID: 1, EnableMonitoring: true}
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch settings"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch settings"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,11 +82,10 @@ func (h *AdminSettingsHandler) UpdateSettings(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&settings).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update settings"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update settings"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, SettingsResponse{
|
||||
return c.JSON(http.StatusOK, SettingsResponse{
|
||||
EnableLimitations: settings.EnableLimitations,
|
||||
EnableMonitoring: settings.EnableMonitoring,
|
||||
})
|
||||
@@ -97,31 +93,29 @@ func (h *AdminSettingsHandler) UpdateSettings(c *gin.Context) {
|
||||
|
||||
// SeedLookups handles POST /api/admin/settings/seed-lookups
|
||||
// Seeds both lookup tables AND task templates, then caches all lookups in Redis
|
||||
func (h *AdminSettingsHandler) SeedLookups(c *gin.Context) {
|
||||
func (h *AdminSettingsHandler) SeedLookups(c echo.Context) error {
|
||||
// First seed lookup tables
|
||||
if err := h.runSeedFile("001_lookups.sql"); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to seed lookups: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to seed lookups: " + err.Error()})
|
||||
}
|
||||
|
||||
// Then seed task templates
|
||||
if err := h.runSeedFile("003_task_templates.sql"); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to seed task templates: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to seed task templates: " + err.Error()})
|
||||
}
|
||||
|
||||
// Cache all lookups in Redis
|
||||
cached, cacheErr := h.cacheAllLookups(c.Request.Context())
|
||||
cached, cacheErr := h.cacheAllLookups(c.Request().Context())
|
||||
if cacheErr != nil {
|
||||
log.Warn().Err(cacheErr).Msg("Failed to cache lookups in Redis, but seed was successful")
|
||||
}
|
||||
|
||||
response := gin.H{
|
||||
response := map[string]interface{}{
|
||||
"message": "Lookup data and task templates seeded successfully",
|
||||
"redis_cached": cached,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// cacheAllLookups fetches all lookup data from the database and caches it in Redis
|
||||
@@ -326,23 +320,21 @@ func parseTags(tags string) []string {
|
||||
}
|
||||
|
||||
// SeedTestData handles POST /api/admin/settings/seed-test-data
|
||||
func (h *AdminSettingsHandler) SeedTestData(c *gin.Context) {
|
||||
func (h *AdminSettingsHandler) SeedTestData(c echo.Context) error {
|
||||
if err := h.runSeedFile("002_test_data.sql"); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to seed test data: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to seed test data: " + err.Error()})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Test data seeded successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Test data seeded successfully"})
|
||||
}
|
||||
|
||||
// SeedTaskTemplates handles POST /api/admin/settings/seed-task-templates
|
||||
func (h *AdminSettingsHandler) SeedTaskTemplates(c *gin.Context) {
|
||||
func (h *AdminSettingsHandler) SeedTaskTemplates(c echo.Context) error {
|
||||
if err := h.runSeedFile("003_task_templates.sql"); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to seed task templates: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to seed task templates: " + err.Error()})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Task templates seeded successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Task templates seeded successfully"})
|
||||
}
|
||||
|
||||
// runSeedFile executes a seed SQL file
|
||||
@@ -473,14 +465,13 @@ type ClearStuckJobsResponse struct {
|
||||
|
||||
// ClearStuckJobs handles POST /api/admin/settings/clear-stuck-jobs
|
||||
// This clears stuck/failed asynq worker jobs from Redis
|
||||
func (h *AdminSettingsHandler) ClearStuckJobs(c *gin.Context) {
|
||||
func (h *AdminSettingsHandler) ClearStuckJobs(c echo.Context) error {
|
||||
cache := services.GetCache()
|
||||
if cache == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Redis cache not available"})
|
||||
return
|
||||
return c.JSON(http.StatusServiceUnavailable, map[string]interface{}{"error": "Redis cache not available"})
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
ctx := c.Request().Context()
|
||||
client := cache.Client()
|
||||
|
||||
var deletedKeys []string
|
||||
@@ -526,7 +517,7 @@ func (h *AdminSettingsHandler) ClearStuckJobs(c *gin.Context) {
|
||||
|
||||
log.Info().Int("count", len(deletedKeys)).Strs("keys", deletedKeys).Msg("Cleared stuck Redis jobs")
|
||||
|
||||
c.JSON(http.StatusOK, ClearStuckJobsResponse{
|
||||
return c.JSON(http.StatusOK, ClearStuckJobsResponse{
|
||||
Message: "Stuck jobs cleared successfully",
|
||||
KeysDeleted: len(deletedKeys),
|
||||
DeletedKeys: deletedKeys,
|
||||
@@ -535,12 +526,11 @@ func (h *AdminSettingsHandler) ClearStuckJobs(c *gin.Context) {
|
||||
|
||||
// ClearAllData handles POST /api/admin/settings/clear-all-data
|
||||
// This clears all data except super admin accounts and lookup tables
|
||||
func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) {
|
||||
func (h *AdminSettingsHandler) ClearAllData(c echo.Context) error {
|
||||
// Start a transaction
|
||||
tx := h.db.Begin()
|
||||
if tx.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to start transaction"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to start transaction"})
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@@ -555,8 +545,7 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) {
|
||||
Where("is_superuser = ?", true).
|
||||
Pluck("id", &preservedUserIDs).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get superuser IDs"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to get superuser IDs"})
|
||||
}
|
||||
|
||||
// Count users that will be deleted
|
||||
@@ -565,8 +554,7 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) {
|
||||
Where("is_superuser = ?", false).
|
||||
Count(&usersToDelete).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to count users"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to count users"})
|
||||
}
|
||||
|
||||
// Delete in order to respect foreign key constraints
|
||||
@@ -575,110 +563,94 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) {
|
||||
// 1. Delete task completion images
|
||||
if err := tx.Exec("DELETE FROM task_taskcompletionimage").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete task completion images: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete task completion images: " + err.Error()})
|
||||
}
|
||||
|
||||
// 2. Delete task completions
|
||||
if err := tx.Exec("DELETE FROM task_taskcompletion").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete task completions: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete task completions: " + err.Error()})
|
||||
}
|
||||
|
||||
// 3. Delete notifications (must be before tasks since notifications have task_id FK)
|
||||
if len(preservedUserIDs) > 0 {
|
||||
if err := tx.Exec("DELETE FROM notifications_notification WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete notifications: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete notifications: " + err.Error()})
|
||||
}
|
||||
} else {
|
||||
if err := tx.Exec("DELETE FROM notifications_notification").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete notifications: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete notifications: " + err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Delete document images
|
||||
if err := tx.Exec("DELETE FROM task_documentimage").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete document images: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete document images: " + err.Error()})
|
||||
}
|
||||
|
||||
// 5. Delete documents
|
||||
if err := tx.Exec("DELETE FROM task_document").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete documents: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete documents: " + err.Error()})
|
||||
}
|
||||
|
||||
// 6. Delete tasks (must be before contractors since tasks reference contractors)
|
||||
if err := tx.Exec("DELETE FROM task_task").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete tasks: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete tasks: " + err.Error()})
|
||||
}
|
||||
|
||||
// 7. Delete contractor specialties (many-to-many)
|
||||
if err := tx.Exec("DELETE FROM task_contractor_specialties").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete contractor specialties: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete contractor specialties: " + err.Error()})
|
||||
}
|
||||
|
||||
// 8. Delete contractors
|
||||
if err := tx.Exec("DELETE FROM task_contractor").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete contractors: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete contractors: " + err.Error()})
|
||||
}
|
||||
|
||||
// 9. Delete residence_users (many-to-many for shared residences)
|
||||
if err := tx.Exec("DELETE FROM residence_residence_users").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete residence users: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete residence users: " + err.Error()})
|
||||
}
|
||||
|
||||
// 10. Delete residence share codes (must be before residences since share codes have residence_id FK)
|
||||
if err := tx.Exec("DELETE FROM residence_residencesharecode").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete residence share codes: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete residence share codes: " + err.Error()})
|
||||
}
|
||||
|
||||
// 11. Delete residences
|
||||
if err := tx.Exec("DELETE FROM residence_residence").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete residences: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete residences: " + err.Error()})
|
||||
}
|
||||
|
||||
// 12. Delete push devices for non-superusers (both APNS and GCM)
|
||||
if len(preservedUserIDs) > 0 {
|
||||
if err := tx.Exec("DELETE FROM push_notifications_apnsdevice WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete APNS devices: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete APNS devices: " + err.Error()})
|
||||
}
|
||||
if err := tx.Exec("DELETE FROM push_notifications_gcmdevice WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete GCM devices: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete GCM devices: " + err.Error()})
|
||||
}
|
||||
} else {
|
||||
if err := tx.Exec("DELETE FROM push_notifications_apnsdevice").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete APNS devices: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete APNS devices: " + err.Error()})
|
||||
}
|
||||
if err := tx.Exec("DELETE FROM push_notifications_gcmdevice").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete GCM devices: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete GCM devices: " + err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,14 +658,12 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) {
|
||||
if len(preservedUserIDs) > 0 {
|
||||
if err := tx.Exec("DELETE FROM notifications_notificationpreference WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete notification preferences: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete notification preferences: " + err.Error()})
|
||||
}
|
||||
} else {
|
||||
if err := tx.Exec("DELETE FROM notifications_notificationpreference").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete notification preferences: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete notification preferences: " + err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,14 +671,12 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) {
|
||||
if len(preservedUserIDs) > 0 {
|
||||
if err := tx.Exec("DELETE FROM subscription_usersubscription WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete subscriptions: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete subscriptions: " + err.Error()})
|
||||
}
|
||||
} else {
|
||||
if err := tx.Exec("DELETE FROM subscription_usersubscription").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete subscriptions: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete subscriptions: " + err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -716,14 +684,12 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) {
|
||||
if len(preservedUserIDs) > 0 {
|
||||
if err := tx.Exec("DELETE FROM user_passwordresetcode WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete password reset codes: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete password reset codes: " + err.Error()})
|
||||
}
|
||||
} else {
|
||||
if err := tx.Exec("DELETE FROM user_passwordresetcode").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete password reset codes: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete password reset codes: " + err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -731,14 +697,12 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) {
|
||||
if len(preservedUserIDs) > 0 {
|
||||
if err := tx.Exec("DELETE FROM user_confirmationcode WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete confirmation codes: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete confirmation codes: " + err.Error()})
|
||||
}
|
||||
} else {
|
||||
if err := tx.Exec("DELETE FROM user_confirmationcode").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete confirmation codes: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete confirmation codes: " + err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -746,14 +710,12 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) {
|
||||
if len(preservedUserIDs) > 0 {
|
||||
if err := tx.Exec("DELETE FROM user_authtoken WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete auth tokens: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete auth tokens: " + err.Error()})
|
||||
}
|
||||
} else {
|
||||
if err := tx.Exec("DELETE FROM user_authtoken").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete auth tokens: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete auth tokens: " + err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -761,14 +723,12 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) {
|
||||
if len(preservedUserIDs) > 0 {
|
||||
if err := tx.Exec("DELETE FROM user_applesocialauth WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete Apple social auth: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete Apple social auth: " + err.Error()})
|
||||
}
|
||||
} else {
|
||||
if err := tx.Exec("DELETE FROM user_applesocialauth").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete Apple social auth: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete Apple social auth: " + err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,14 +736,12 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) {
|
||||
if len(preservedUserIDs) > 0 {
|
||||
if err := tx.Exec("DELETE FROM user_userprofile WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete user profiles: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete user profiles: " + err.Error()})
|
||||
}
|
||||
} else {
|
||||
if err := tx.Exec("DELETE FROM user_userprofile").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete user profiles: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete user profiles: " + err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -791,17 +749,15 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) {
|
||||
// Always filter by is_superuser to be safe, regardless of preservedUserIDs
|
||||
if err := tx.Exec("DELETE FROM auth_user WHERE is_superuser = false").Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete users: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete users: " + err.Error()})
|
||||
}
|
||||
|
||||
// Commit the transaction
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to commit transaction: " + err.Error()})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to commit transaction: " + err.Error()})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, ClearAllDataResponse{
|
||||
return c.JSON(http.StatusOK, ClearAllDataResponse{
|
||||
Message: "All data cleared successfully (superadmin accounts preserved)",
|
||||
UsersDeleted: usersToDelete,
|
||||
PreservedUsers: int64(len(preservedUserIDs)),
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -35,11 +35,10 @@ type ShareCodeResponse struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/share-codes
|
||||
func (h *AdminShareCodeHandler) List(c *gin.Context) {
|
||||
func (h *AdminShareCodeHandler) List(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var codes []models.ResidenceShareCode
|
||||
@@ -74,8 +73,7 @@ func (h *AdminShareCodeHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&codes).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch share codes"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch share codes"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -100,25 +98,22 @@ func (h *AdminShareCodeHandler) List(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/share-codes/:id
|
||||
func (h *AdminShareCodeHandler) Get(c *gin.Context) {
|
||||
func (h *AdminShareCodeHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var code models.ResidenceShareCode
|
||||
if err := h.db.Preload("Residence").Preload("CreatedBy").First(&code, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Share code not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Share code not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch share code"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch share code"})
|
||||
}
|
||||
|
||||
var expiresAt *string
|
||||
@@ -139,40 +134,35 @@ func (h *AdminShareCodeHandler) Get(c *gin.Context) {
|
||||
CreatedAt: code.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/share-codes/:id
|
||||
func (h *AdminShareCodeHandler) Update(c *gin.Context) {
|
||||
func (h *AdminShareCodeHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
var code models.ResidenceShareCode
|
||||
if err := h.db.First(&code, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Share code not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Share code not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch share code"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch share code"})
|
||||
}
|
||||
|
||||
var req struct {
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
code.IsActive = req.IsActive
|
||||
|
||||
if err := h.db.Save(&code).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update share code"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update share code"})
|
||||
}
|
||||
|
||||
// Reload with relations
|
||||
@@ -196,44 +186,39 @@ func (h *AdminShareCodeHandler) Update(c *gin.Context) {
|
||||
CreatedAt: code.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/share-codes/:id
|
||||
func (h *AdminShareCodeHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminShareCodeHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid ID"})
|
||||
}
|
||||
|
||||
result := h.db.Delete(&models.ResidenceShareCode{}, id)
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete share code"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete share code"})
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Share code not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Share code not found"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Share code deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Share code deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/share-codes/bulk
|
||||
func (h *AdminShareCodeHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminShareCodeHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
result := h.db.Where("id IN ?", req.IDs).Delete(&models.ResidenceShareCode{})
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete share codes"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete share codes"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Share codes deleted successfully", "count": result.RowsAffected})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Share codes deleted successfully", "count": result.RowsAffected})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -22,11 +22,10 @@ func NewAdminSubscriptionHandler(db *gorm.DB) *AdminSubscriptionHandler {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/subscriptions
|
||||
func (h *AdminSubscriptionHandler) List(c *gin.Context) {
|
||||
func (h *AdminSubscriptionHandler) List(c echo.Context) error {
|
||||
var filters dto.SubscriptionFilters
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var subscriptions []models.UserSubscription
|
||||
@@ -77,8 +76,7 @@ func (h *AdminSubscriptionHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&subscriptions).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch subscriptions"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch subscriptions"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -87,15 +85,14 @@ func (h *AdminSubscriptionHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toSubscriptionResponse(&sub)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/subscriptions/:id
|
||||
func (h *AdminSubscriptionHandler) Get(c *gin.Context) {
|
||||
func (h *AdminSubscriptionHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid subscription ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid subscription ID"})
|
||||
}
|
||||
|
||||
var subscription models.UserSubscription
|
||||
@@ -103,38 +100,32 @@ func (h *AdminSubscriptionHandler) Get(c *gin.Context) {
|
||||
Preload("User").
|
||||
First(&subscription, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Subscription not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Subscription not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch subscription"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch subscription"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toSubscriptionDetailResponse(&subscription))
|
||||
return c.JSON(http.StatusOK, h.toSubscriptionDetailResponse(&subscription))
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/subscriptions/:id
|
||||
func (h *AdminSubscriptionHandler) Update(c *gin.Context) {
|
||||
func (h *AdminSubscriptionHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid subscription ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid subscription ID"})
|
||||
}
|
||||
|
||||
var subscription models.UserSubscription
|
||||
if err := h.db.First(&subscription, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Subscription not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Subscription not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch subscription"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch subscription"})
|
||||
}
|
||||
|
||||
var req dto.UpdateSubscriptionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if req.Tier != nil {
|
||||
@@ -148,20 +139,18 @@ func (h *AdminSubscriptionHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&subscription).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update subscription"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update subscription"})
|
||||
}
|
||||
|
||||
h.db.Preload("User").First(&subscription, id)
|
||||
c.JSON(http.StatusOK, h.toSubscriptionResponse(&subscription))
|
||||
return c.JSON(http.StatusOK, h.toSubscriptionResponse(&subscription))
|
||||
}
|
||||
|
||||
// GetByUser handles GET /api/admin/subscriptions/user/:user_id
|
||||
func (h *AdminSubscriptionHandler) GetByUser(c *gin.Context) {
|
||||
func (h *AdminSubscriptionHandler) GetByUser(c echo.Context) error {
|
||||
userID, err := strconv.ParseUint(c.Param("user_id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid user ID"})
|
||||
}
|
||||
|
||||
var subscription models.UserSubscription
|
||||
@@ -180,22 +169,20 @@ func (h *AdminSubscriptionHandler) GetByUser(c *gin.Context) {
|
||||
IsFree: false,
|
||||
}
|
||||
if err := h.db.Create(&subscription).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create subscription"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create subscription"})
|
||||
}
|
||||
// Reload with user
|
||||
h.db.Preload("User").First(&subscription, subscription.ID)
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch subscription"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch subscription"})
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toSubscriptionResponse(&subscription))
|
||||
return c.JSON(http.StatusOK, h.toSubscriptionResponse(&subscription))
|
||||
}
|
||||
|
||||
// GetStats handles GET /api/admin/subscriptions/stats
|
||||
func (h *AdminSubscriptionHandler) GetStats(c *gin.Context) {
|
||||
func (h *AdminSubscriptionHandler) GetStats(c echo.Context) error {
|
||||
var total, free, premium, pro int64
|
||||
|
||||
h.db.Model(&models.UserSubscription{}).Count(&total)
|
||||
@@ -203,7 +190,7 @@ func (h *AdminSubscriptionHandler) GetStats(c *gin.Context) {
|
||||
h.db.Model(&models.UserSubscription{}).Where("tier = ?", "premium").Count(&premium)
|
||||
h.db.Model(&models.UserSubscription{}).Where("tier = ?", "pro").Count(&pro)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"total": total,
|
||||
"free": free,
|
||||
"premium": premium,
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/shopspring/decimal"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -24,11 +24,10 @@ func NewAdminTaskHandler(db *gorm.DB) *AdminTaskHandler {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/tasks
|
||||
func (h *AdminTaskHandler) List(c *gin.Context) {
|
||||
func (h *AdminTaskHandler) List(c echo.Context) error {
|
||||
var filters dto.TaskFilters
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var tasks []models.Task
|
||||
@@ -80,8 +79,7 @@ func (h *AdminTaskHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&tasks).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch tasks"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch tasks"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -90,15 +88,14 @@ func (h *AdminTaskHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toTaskResponse(&task)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/tasks/:id
|
||||
func (h *AdminTaskHandler) Get(c *gin.Context) {
|
||||
func (h *AdminTaskHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid task ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid task ID"})
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
@@ -112,11 +109,9 @@ func (h *AdminTaskHandler) Get(c *gin.Context) {
|
||||
Preload("Completions").
|
||||
First(&task, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Task not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch task"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch task"})
|
||||
}
|
||||
|
||||
response := dto.TaskDetailResponse{
|
||||
@@ -133,55 +128,48 @@ func (h *AdminTaskHandler) Get(c *gin.Context) {
|
||||
|
||||
response.CompletionCount = len(task.Completions)
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/tasks/:id
|
||||
func (h *AdminTaskHandler) Update(c *gin.Context) {
|
||||
func (h *AdminTaskHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid task ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid task ID"})
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
if err := h.db.First(&task, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Task not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch task"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch task"})
|
||||
}
|
||||
|
||||
var req dto.UpdateTaskRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Verify residence if changing
|
||||
if req.ResidenceID != nil {
|
||||
var residence models.Residence
|
||||
if err := h.db.First(&residence, *req.ResidenceID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Residence not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Residence not found"})
|
||||
}
|
||||
}
|
||||
// Verify created_by if changing
|
||||
if req.CreatedByID != nil {
|
||||
var user models.User
|
||||
if err := h.db.First(&user, *req.CreatedByID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Created by user not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Created by user not found"})
|
||||
}
|
||||
}
|
||||
// Verify assigned_to if changing
|
||||
if req.AssignedToID != nil {
|
||||
var user models.User
|
||||
if err := h.db.First(&user, *req.AssignedToID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Assigned to user not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Assigned to user not found"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,35 +235,31 @@ func (h *AdminTaskHandler) Update(c *gin.Context) {
|
||||
|
||||
// Use Updates with map to only update specified fields
|
||||
if err := h.db.Model(&task).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update task"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update task"})
|
||||
}
|
||||
|
||||
// Reload with preloads for response
|
||||
h.db.Preload("Residence").Preload("CreatedBy").Preload("Category").Preload("Priority").First(&task, id)
|
||||
c.JSON(http.StatusOK, h.toTaskResponse(&task))
|
||||
return c.JSON(http.StatusOK, h.toTaskResponse(&task))
|
||||
}
|
||||
|
||||
// Create handles POST /api/admin/tasks
|
||||
func (h *AdminTaskHandler) Create(c *gin.Context) {
|
||||
func (h *AdminTaskHandler) Create(c echo.Context) error {
|
||||
var req dto.CreateTaskRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Verify residence exists
|
||||
var residence models.Residence
|
||||
if err := h.db.First(&residence, req.ResidenceID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Residence not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Residence not found"})
|
||||
}
|
||||
|
||||
// Verify created_by user exists
|
||||
var creator models.User
|
||||
if err := h.db.First(&creator, req.CreatedByID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Creator user not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Creator user not found"})
|
||||
}
|
||||
|
||||
task := models.Task{
|
||||
@@ -305,49 +289,43 @@ func (h *AdminTaskHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&task).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create task"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create task"})
|
||||
}
|
||||
|
||||
h.db.Preload("Residence").Preload("CreatedBy").Preload("Category").Preload("Priority").First(&task, task.ID)
|
||||
c.JSON(http.StatusCreated, h.toTaskResponse(&task))
|
||||
return c.JSON(http.StatusCreated, h.toTaskResponse(&task))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/tasks/:id
|
||||
func (h *AdminTaskHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminTaskHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid task ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid task ID"})
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
if err := h.db.First(&task, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Task not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch task"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch task"})
|
||||
}
|
||||
|
||||
// Soft delete - archive and cancel
|
||||
task.IsArchived = true
|
||||
task.IsCancelled = true
|
||||
if err := h.db.Omit("Residence", "CreatedBy", "AssignedTo", "Category", "Priority", "Frequency", "ParentTask", "Completions").Save(&task).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete task"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete task"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Task archived successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Task archived successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/tasks/bulk
|
||||
func (h *AdminTaskHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminTaskHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Soft delete - archive and cancel all
|
||||
@@ -355,11 +333,10 @@ func (h *AdminTaskHandler) BulkDelete(c *gin.Context) {
|
||||
"is_archived": true,
|
||||
"is_cancelled": true,
|
||||
}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete tasks"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete tasks"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Tasks archived successfully", "count": len(req.IDs)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Tasks archived successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
func (h *AdminTaskHandler) toTaskResponse(task *models.Task) dto.TaskResponse {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -28,7 +28,6 @@ func NewAdminTaskTemplateHandler(db *gorm.DB) *AdminTaskTemplateHandler {
|
||||
func (h *AdminTaskTemplateHandler) refreshTaskTemplatesCache(ctx context.Context) {
|
||||
cache := services.GetCache()
|
||||
if cache == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var templates []models.TaskTemplate
|
||||
@@ -37,19 +36,16 @@ func (h *AdminTaskTemplateHandler) refreshTaskTemplatesCache(ctx context.Context
|
||||
Order("display_order ASC, title ASC").
|
||||
Find(&templates).Error; err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to fetch task templates for cache refresh")
|
||||
return
|
||||
}
|
||||
|
||||
if err := cache.CacheTaskTemplates(ctx, templates); err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to cache task templates")
|
||||
return
|
||||
}
|
||||
log.Debug().Int("count", len(templates)).Msg("Refreshed task templates cache")
|
||||
|
||||
// Invalidate unified seeded data cache
|
||||
if err := cache.InvalidateSeededData(ctx); err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to invalidate seeded data cache")
|
||||
return
|
||||
}
|
||||
log.Debug().Msg("Invalidated seeded data cache")
|
||||
}
|
||||
@@ -84,35 +80,34 @@ type CreateUpdateTaskTemplateRequest struct {
|
||||
}
|
||||
|
||||
// ListTemplates handles GET /admin/api/task-templates/
|
||||
func (h *AdminTaskTemplateHandler) ListTemplates(c *gin.Context) {
|
||||
func (h *AdminTaskTemplateHandler) ListTemplates(c echo.Context) error {
|
||||
var templates []models.TaskTemplate
|
||||
query := h.db.Preload("Category").Preload("Frequency").Order("display_order ASC, title ASC")
|
||||
|
||||
// Optional filter by active status
|
||||
if activeParam := c.Query("is_active"); activeParam != "" {
|
||||
if activeParam := c.QueryParam("is_active"); activeParam != "" {
|
||||
isActive := activeParam == "true"
|
||||
query = query.Where("is_active = ?", isActive)
|
||||
}
|
||||
|
||||
// Optional filter by category
|
||||
if categoryID := c.Query("category_id"); categoryID != "" {
|
||||
if categoryID := c.QueryParam("category_id"); categoryID != "" {
|
||||
query = query.Where("category_id = ?", categoryID)
|
||||
}
|
||||
|
||||
// Optional filter by frequency
|
||||
if frequencyID := c.Query("frequency_id"); frequencyID != "" {
|
||||
if frequencyID := c.QueryParam("frequency_id"); frequencyID != "" {
|
||||
query = query.Where("frequency_id = ?", frequencyID)
|
||||
}
|
||||
|
||||
// Optional search
|
||||
if search := c.Query("search"); search != "" {
|
||||
if search := c.QueryParam("search"); search != "" {
|
||||
searchTerm := "%" + strings.ToLower(search) + "%"
|
||||
query = query.Where("LOWER(title) LIKE ? OR LOWER(tags) LIKE ?", searchTerm, searchTerm)
|
||||
}
|
||||
|
||||
if err := query.Find(&templates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch templates"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch templates"})
|
||||
}
|
||||
|
||||
responses := make([]TaskTemplateResponse, len(templates))
|
||||
@@ -120,36 +115,32 @@ func (h *AdminTaskTemplateHandler) ListTemplates(c *gin.Context) {
|
||||
responses[i] = h.toResponse(&t)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": responses, "total": len(responses)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"data": responses, "total": len(responses)})
|
||||
}
|
||||
|
||||
// GetTemplate handles GET /admin/api/task-templates/:id/
|
||||
func (h *AdminTaskTemplateHandler) GetTemplate(c *gin.Context) {
|
||||
func (h *AdminTaskTemplateHandler) GetTemplate(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid template ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid template ID"})
|
||||
}
|
||||
|
||||
var template models.TaskTemplate
|
||||
if err := h.db.Preload("Category").Preload("Frequency").First(&template, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Template not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Template not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch template"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch template"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&template))
|
||||
return c.JSON(http.StatusOK, h.toResponse(&template))
|
||||
}
|
||||
|
||||
// CreateTemplate handles POST /admin/api/task-templates/
|
||||
func (h *AdminTaskTemplateHandler) CreateTemplate(c *gin.Context) {
|
||||
func (h *AdminTaskTemplateHandler) CreateTemplate(c echo.Context) error {
|
||||
var req CreateUpdateTaskTemplateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
template := models.TaskTemplate{
|
||||
@@ -171,41 +162,36 @@ func (h *AdminTaskTemplateHandler) CreateTemplate(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&template).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create template"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create template"})
|
||||
}
|
||||
|
||||
// Reload with preloads
|
||||
h.db.Preload("Category").Preload("Frequency").First(&template, template.ID)
|
||||
|
||||
// Refresh cache after creating
|
||||
h.refreshTaskTemplatesCache(c.Request.Context())
|
||||
h.refreshTaskTemplatesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusCreated, h.toResponse(&template))
|
||||
return c.JSON(http.StatusCreated, h.toResponse(&template))
|
||||
}
|
||||
|
||||
// UpdateTemplate handles PUT /admin/api/task-templates/:id/
|
||||
func (h *AdminTaskTemplateHandler) UpdateTemplate(c *gin.Context) {
|
||||
func (h *AdminTaskTemplateHandler) UpdateTemplate(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid template ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid template ID"})
|
||||
}
|
||||
|
||||
var template models.TaskTemplate
|
||||
if err := h.db.First(&template, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Template not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Template not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch template"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch template"})
|
||||
}
|
||||
|
||||
var req CreateUpdateTaskTemplateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
template.Title = req.Title
|
||||
@@ -224,79 +210,71 @@ func (h *AdminTaskTemplateHandler) UpdateTemplate(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&template).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update template"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update template"})
|
||||
}
|
||||
|
||||
// Reload with preloads
|
||||
h.db.Preload("Category").Preload("Frequency").First(&template, template.ID)
|
||||
|
||||
// Refresh cache after updating
|
||||
h.refreshTaskTemplatesCache(c.Request.Context())
|
||||
h.refreshTaskTemplatesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&template))
|
||||
return c.JSON(http.StatusOK, h.toResponse(&template))
|
||||
}
|
||||
|
||||
// DeleteTemplate handles DELETE /admin/api/task-templates/:id/
|
||||
func (h *AdminTaskTemplateHandler) DeleteTemplate(c *gin.Context) {
|
||||
func (h *AdminTaskTemplateHandler) DeleteTemplate(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid template ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid template ID"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&models.TaskTemplate{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete template"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete template"})
|
||||
}
|
||||
|
||||
// Refresh cache after deleting
|
||||
h.refreshTaskTemplatesCache(c.Request.Context())
|
||||
h.refreshTaskTemplatesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Template deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Template deleted successfully"})
|
||||
}
|
||||
|
||||
// ToggleActive handles POST /admin/api/task-templates/:id/toggle-active/
|
||||
func (h *AdminTaskTemplateHandler) ToggleActive(c *gin.Context) {
|
||||
func (h *AdminTaskTemplateHandler) ToggleActive(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid template ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid template ID"})
|
||||
}
|
||||
|
||||
var template models.TaskTemplate
|
||||
if err := h.db.First(&template, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Template not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Template not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch template"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch template"})
|
||||
}
|
||||
|
||||
template.IsActive = !template.IsActive
|
||||
if err := h.db.Save(&template).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update template"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update template"})
|
||||
}
|
||||
|
||||
// Reload with preloads
|
||||
h.db.Preload("Category").Preload("Frequency").First(&template, template.ID)
|
||||
|
||||
// Refresh cache after toggling active status
|
||||
h.refreshTaskTemplatesCache(c.Request.Context())
|
||||
h.refreshTaskTemplatesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&template))
|
||||
return c.JSON(http.StatusOK, h.toResponse(&template))
|
||||
}
|
||||
|
||||
// BulkCreate handles POST /admin/api/task-templates/bulk/
|
||||
func (h *AdminTaskTemplateHandler) BulkCreate(c *gin.Context) {
|
||||
func (h *AdminTaskTemplateHandler) BulkCreate(c echo.Context) error {
|
||||
var req struct {
|
||||
Templates []CreateUpdateTaskTemplateRequest `json:"templates" binding:"required,dive"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
templates := make([]models.TaskTemplate, len(req.Templates))
|
||||
@@ -320,14 +298,13 @@ func (h *AdminTaskTemplateHandler) BulkCreate(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&templates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create templates"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create templates"})
|
||||
}
|
||||
|
||||
// Refresh cache after bulk creating
|
||||
h.refreshTaskTemplatesCache(c.Request.Context())
|
||||
h.refreshTaskTemplatesCache(c.Request().Context())
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"message": "Templates created successfully", "count": len(templates)})
|
||||
return c.JSON(http.StatusCreated, map[string]interface{}{"message": "Templates created successfully", "count": len(templates)})
|
||||
}
|
||||
|
||||
// Helper to convert model to response
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -22,11 +22,10 @@ func NewAdminUserHandler(db *gorm.DB) *AdminUserHandler {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/users
|
||||
func (h *AdminUserHandler) List(c *gin.Context) {
|
||||
func (h *AdminUserHandler) List(c echo.Context) error {
|
||||
var filters dto.UserFilters
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var users []models.User
|
||||
@@ -68,8 +67,7 @@ func (h *AdminUserHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&users).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch users"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch users"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -78,25 +76,22 @@ func (h *AdminUserHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toUserResponse(&user)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/users/:id
|
||||
func (h *AdminUserHandler) Get(c *gin.Context) {
|
||||
func (h *AdminUserHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid user ID"})
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := h.db.Preload("Profile").Preload("OwnedResidences").First(&user, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user"})
|
||||
}
|
||||
|
||||
// Build detailed response
|
||||
@@ -114,30 +109,27 @@ func (h *AdminUserHandler) Get(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Create handles POST /api/admin/users
|
||||
func (h *AdminUserHandler) Create(c *gin.Context) {
|
||||
func (h *AdminUserHandler) Create(c echo.Context) error {
|
||||
var req dto.CreateUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Check if username exists
|
||||
var count int64
|
||||
h.db.Model(&models.User{}).Where("username = ?", req.Username).Count(&count)
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "Username already exists"})
|
||||
return
|
||||
return c.JSON(http.StatusConflict, map[string]interface{}{"error": "Username already exists"})
|
||||
}
|
||||
|
||||
// Check if email exists
|
||||
h.db.Model(&models.User{}).Where("email = ?", req.Email).Count(&count)
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "Email already exists"})
|
||||
return
|
||||
return c.JSON(http.StatusConflict, map[string]interface{}{"error": "Email already exists"})
|
||||
}
|
||||
|
||||
user := models.User{
|
||||
@@ -160,13 +152,11 @@ func (h *AdminUserHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := user.SetPassword(req.Password); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to hash password"})
|
||||
}
|
||||
|
||||
if err := h.db.Create(&user).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create user"})
|
||||
}
|
||||
|
||||
// Create profile with phone number
|
||||
@@ -178,31 +168,27 @@ func (h *AdminUserHandler) Create(c *gin.Context) {
|
||||
|
||||
// Reload with profile
|
||||
h.db.Preload("Profile").First(&user, user.ID)
|
||||
c.JSON(http.StatusCreated, h.toUserResponse(&user))
|
||||
return c.JSON(http.StatusCreated, h.toUserResponse(&user))
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/users/:id
|
||||
func (h *AdminUserHandler) Update(c *gin.Context) {
|
||||
func (h *AdminUserHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid user ID"})
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := h.db.First(&user, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user"})
|
||||
}
|
||||
|
||||
var req dto.UpdateUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Check username uniqueness if changing
|
||||
@@ -210,8 +196,7 @@ func (h *AdminUserHandler) Update(c *gin.Context) {
|
||||
var count int64
|
||||
h.db.Model(&models.User{}).Where("username = ? AND id != ?", *req.Username, id).Count(&count)
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "Username already exists"})
|
||||
return
|
||||
return c.JSON(http.StatusConflict, map[string]interface{}{"error": "Username already exists"})
|
||||
}
|
||||
user.Username = *req.Username
|
||||
}
|
||||
@@ -221,8 +206,7 @@ func (h *AdminUserHandler) Update(c *gin.Context) {
|
||||
var count int64
|
||||
h.db.Model(&models.User{}).Where("email = ? AND id != ?", *req.Email, id).Count(&count)
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "Email already exists"})
|
||||
return
|
||||
return c.JSON(http.StatusConflict, map[string]interface{}{"error": "Email already exists"})
|
||||
}
|
||||
user.Email = *req.Email
|
||||
}
|
||||
@@ -244,14 +228,12 @@ func (h *AdminUserHandler) Update(c *gin.Context) {
|
||||
}
|
||||
if req.Password != nil {
|
||||
if err := user.SetPassword(*req.Password); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to hash password"})
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.db.Save(&user).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update user"})
|
||||
}
|
||||
|
||||
// Update profile fields if provided
|
||||
@@ -279,52 +261,46 @@ func (h *AdminUserHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
h.db.Preload("Profile").First(&user, id)
|
||||
c.JSON(http.StatusOK, h.toUserResponse(&user))
|
||||
return c.JSON(http.StatusOK, h.toUserResponse(&user))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/users/:id
|
||||
func (h *AdminUserHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminUserHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid user ID"})
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := h.db.First(&user, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user"})
|
||||
}
|
||||
|
||||
// Soft delete - just deactivate
|
||||
user.IsActive = false
|
||||
if err := h.db.Save(&user).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete user"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete user"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "User deactivated successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "User deactivated successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/users/bulk
|
||||
func (h *AdminUserHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminUserHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Soft delete - deactivate all
|
||||
if err := h.db.Model(&models.User{}).Where("id IN ?", req.IDs).Update("is_active", false).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete users"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete users"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Users deactivated successfully", "count": len(req.IDs)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Users deactivated successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
// toUserResponse converts a User model to UserResponse DTO
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -47,11 +47,10 @@ type UpdateUserProfileRequest struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/user-profiles
|
||||
func (h *AdminUserProfileHandler) List(c *gin.Context) {
|
||||
func (h *AdminUserProfileHandler) List(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
var profiles []models.UserProfile
|
||||
@@ -81,8 +80,7 @@ func (h *AdminUserProfileHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&profiles).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user profiles"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user profiles"})
|
||||
}
|
||||
|
||||
// Build response
|
||||
@@ -91,73 +89,63 @@ func (h *AdminUserProfileHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toProfileResponse(&profile)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/user-profiles/:id
|
||||
func (h *AdminUserProfileHandler) Get(c *gin.Context) {
|
||||
func (h *AdminUserProfileHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid profile ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid profile ID"})
|
||||
}
|
||||
|
||||
var profile models.UserProfile
|
||||
if err := h.db.Preload("User").First(&profile, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User profile not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User profile not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user profile"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user profile"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toProfileResponse(&profile))
|
||||
return c.JSON(http.StatusOK, h.toProfileResponse(&profile))
|
||||
}
|
||||
|
||||
// GetByUser handles GET /api/admin/user-profiles/user/:user_id
|
||||
func (h *AdminUserProfileHandler) GetByUser(c *gin.Context) {
|
||||
func (h *AdminUserProfileHandler) GetByUser(c echo.Context) error {
|
||||
userID, err := strconv.ParseUint(c.Param("user_id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid user ID"})
|
||||
}
|
||||
|
||||
var profile models.UserProfile
|
||||
if err := h.db.Preload("User").Where("user_id = ?", userID).First(&profile).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User profile not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User profile not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user profile"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user profile"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toProfileResponse(&profile))
|
||||
return c.JSON(http.StatusOK, h.toProfileResponse(&profile))
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/user-profiles/:id
|
||||
func (h *AdminUserProfileHandler) Update(c *gin.Context) {
|
||||
func (h *AdminUserProfileHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid profile ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid profile ID"})
|
||||
}
|
||||
|
||||
var profile models.UserProfile
|
||||
if err := h.db.First(&profile, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User profile not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User profile not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user profile"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user profile"})
|
||||
}
|
||||
|
||||
var req UpdateUserProfileRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if req.Verified != nil {
|
||||
@@ -178,62 +166,54 @@ func (h *AdminUserProfileHandler) Update(c *gin.Context) {
|
||||
} else {
|
||||
dob, err := time.Parse("2006-01-02", *req.DateOfBirth)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid date format for date_of_birth, use YYYY-MM-DD"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid date format for date_of_birth, use YYYY-MM-DD"})
|
||||
}
|
||||
profile.DateOfBirth = &dob
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.db.Save(&profile).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update user profile"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update user profile"})
|
||||
}
|
||||
|
||||
h.db.Preload("User").First(&profile, id)
|
||||
c.JSON(http.StatusOK, h.toProfileResponse(&profile))
|
||||
return c.JSON(http.StatusOK, h.toProfileResponse(&profile))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/user-profiles/:id
|
||||
func (h *AdminUserProfileHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminUserProfileHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid profile ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid profile ID"})
|
||||
}
|
||||
|
||||
var profile models.UserProfile
|
||||
if err := h.db.First(&profile, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User profile not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "User profile not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user profile"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch user profile"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&profile).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete user profile"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete user profile"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "User profile deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "User profile deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/user-profiles/bulk
|
||||
func (h *AdminUserProfileHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminUserProfileHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if err := h.db.Where("id IN ?", req.IDs).Delete(&models.UserProfile{}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete user profiles"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete user profiles"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "User profiles deleted successfully", "count": len(req.IDs)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "User profiles deleted successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
// toProfileResponse converts a UserProfile model to UserProfileResponse
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/handlers"
|
||||
@@ -27,7 +27,7 @@ type Dependencies struct {
|
||||
}
|
||||
|
||||
// SetupRoutes configures all admin routes
|
||||
func SetupRoutes(router *gin.Engine, db *gorm.DB, cfg *config.Config, deps *Dependencies) {
|
||||
func SetupRoutes(router *echo.Echo, db *gorm.DB, cfg *config.Config, deps *Dependencies) {
|
||||
// Create repositories
|
||||
adminRepo := repositories.NewAdminRepository(db)
|
||||
|
||||
@@ -445,7 +445,7 @@ func SetupRoutes(router *gin.Engine, db *gorm.DB, cfg *config.Config, deps *Depe
|
||||
}
|
||||
|
||||
// setupAdminProxy configures reverse proxy to the Next.js admin panel
|
||||
func setupAdminProxy(router *gin.Engine) {
|
||||
func setupAdminProxy(router *echo.Echo) {
|
||||
// Get admin panel URL from env, default to localhost:3001
|
||||
// Note: In production (Dokku), Next.js runs on internal port 3001
|
||||
adminURL := os.Getenv("ADMIN_PANEL_URL")
|
||||
@@ -461,17 +461,19 @@ func setupAdminProxy(router *gin.Engine) {
|
||||
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||
|
||||
// Handle all /admin/* requests
|
||||
router.Any("/admin/*filepath", func(c *gin.Context) {
|
||||
proxy.ServeHTTP(c.Writer, c.Request)
|
||||
router.Any("/admin/*", func(c echo.Context) error {
|
||||
proxy.ServeHTTP(c.Response(), c.Request())
|
||||
return nil
|
||||
})
|
||||
|
||||
// Also handle /admin without trailing path
|
||||
router.Any("/admin", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusMovedPermanently, "/admin/")
|
||||
router.Any("/admin", func(c echo.Context) error {
|
||||
return c.Redirect(http.StatusMovedPermanently, "/admin/")
|
||||
})
|
||||
|
||||
// Proxy Next.js static assets
|
||||
router.Any("/_next/*filepath", func(c *gin.Context) {
|
||||
proxy.ServeHTTP(c.Writer, c.Request)
|
||||
router.Any("/_next/*", func(c echo.Context) error {
|
||||
proxy.ServeHTTP(c.Response(), c.Request())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user