Add admin CRUD for UserProfile, AppleSocialAuth, CompletionImage, DocumentImage
Complete admin interface coverage for all database models: - New Go handlers with list, get, update, delete, bulk delete endpoints - New Next.js pages with search, pagination, and bulk operations - Updated sidebar navigation with new menu items 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
233
internal/admin/handlers/apple_social_auth_handler.go
Normal file
233
internal/admin/handlers/apple_social_auth_handler.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
"github.com/treytartt/casera-api/internal/models"
|
||||
)
|
||||
|
||||
// AdminAppleSocialAuthHandler handles admin Apple social auth management endpoints
|
||||
type AdminAppleSocialAuthHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAdminAppleSocialAuthHandler creates a new admin Apple social auth handler
|
||||
func NewAdminAppleSocialAuthHandler(db *gorm.DB) *AdminAppleSocialAuthHandler {
|
||||
return &AdminAppleSocialAuthHandler{db: db}
|
||||
}
|
||||
|
||||
// AppleSocialAuthResponse represents the response for an Apple social auth entry
|
||||
type AppleSocialAuthResponse struct {
|
||||
ID uint `json:"id"`
|
||||
UserID uint `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
UserEmail string `json:"user_email"`
|
||||
AppleID string `json:"apple_id"`
|
||||
Email string `json:"email"`
|
||||
IsPrivateEmail bool `json:"is_private_email"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// UpdateAppleSocialAuthRequest represents the request to update an Apple social auth entry
|
||||
type UpdateAppleSocialAuthRequest struct {
|
||||
Email *string `json:"email"`
|
||||
IsPrivateEmail *bool `json:"is_private_email"`
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/apple-social-auth
|
||||
func (h *AdminAppleSocialAuthHandler) List(c *gin.Context) {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var entries []models.AppleSocialAuth
|
||||
var total int64
|
||||
|
||||
query := h.db.Model(&models.AppleSocialAuth{}).Preload("User")
|
||||
|
||||
// Apply search
|
||||
if filters.Search != "" {
|
||||
search := "%" + filters.Search + "%"
|
||||
query = query.Joins("JOIN auth_user ON auth_user.id = user_applesocialauth.user_id").
|
||||
Where("user_applesocialauth.apple_id ILIKE ? OR user_applesocialauth.email ILIKE ? OR auth_user.username ILIKE ? OR auth_user.email ILIKE ?",
|
||||
search, search, search, search)
|
||||
}
|
||||
|
||||
// Get total count
|
||||
query.Count(&total)
|
||||
|
||||
// Apply sorting
|
||||
sortBy := "created_at"
|
||||
if filters.SortBy != "" {
|
||||
sortBy = filters.SortBy
|
||||
}
|
||||
query = query.Order(sortBy + " " + filters.GetSortDir())
|
||||
|
||||
// Apply pagination
|
||||
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
|
||||
}
|
||||
|
||||
// Build response
|
||||
responses := make([]AppleSocialAuthResponse, len(entries))
|
||||
for i, entry := range entries {
|
||||
responses[i] = h.toResponse(&entry)
|
||||
}
|
||||
|
||||
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) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch Apple social auth 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) {
|
||||
userID, err := strconv.ParseUint(c.Param("user_id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch Apple social auth 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) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch Apple social auth entry"})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateAppleSocialAuthRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email != nil {
|
||||
entry.Email = *req.Email
|
||||
}
|
||||
if req.IsPrivateEmail != nil {
|
||||
entry.IsPrivateEmail = *req.IsPrivateEmail
|
||||
}
|
||||
|
||||
if err := h.db.Save(&entry).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update Apple social auth entry"})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.Preload("User").First(&entry, id)
|
||||
c.JSON(http.StatusOK, h.toResponse(&entry))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/apple-social-auth/:id
|
||||
func (h *AdminAppleSocialAuthHandler) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch Apple social auth entry"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&entry).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete Apple social auth entry"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Apple social auth entry deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/apple-social-auth/bulk
|
||||
func (h *AdminAppleSocialAuthHandler) BulkDelete(c *gin.Context) {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Apple social auth entries deleted successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
// toResponse converts an AppleSocialAuth model to AppleSocialAuthResponse
|
||||
func (h *AdminAppleSocialAuthHandler) toResponse(entry *models.AppleSocialAuth) AppleSocialAuthResponse {
|
||||
response := AppleSocialAuthResponse{
|
||||
ID: entry.ID,
|
||||
UserID: entry.UserID,
|
||||
AppleID: entry.AppleID,
|
||||
Email: entry.Email,
|
||||
IsPrivateEmail: entry.IsPrivateEmail,
|
||||
CreatedAt: entry.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
UpdatedAt: entry.UpdatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
if entry.User.ID != 0 {
|
||||
response.Username = entry.User.Username
|
||||
response.UserEmail = entry.User.Email
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
262
internal/admin/handlers/completion_image_handler.go
Normal file
262
internal/admin/handlers/completion_image_handler.go
Normal file
@@ -0,0 +1,262 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
"github.com/treytartt/casera-api/internal/models"
|
||||
)
|
||||
|
||||
// AdminCompletionImageHandler handles admin task completion image management endpoints
|
||||
type AdminCompletionImageHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAdminCompletionImageHandler creates a new admin completion image handler
|
||||
func NewAdminCompletionImageHandler(db *gorm.DB) *AdminCompletionImageHandler {
|
||||
return &AdminCompletionImageHandler{db: db}
|
||||
}
|
||||
|
||||
// AdminCompletionImageResponse represents the response for a task completion image in admin
|
||||
type AdminCompletionImageResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CompletionID uint `json:"completion_id"`
|
||||
TaskID uint `json:"task_id"`
|
||||
TaskTitle string `json:"task_title"`
|
||||
ImageURL string `json:"image_url"`
|
||||
Caption string `json:"caption"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// CreateCompletionImageRequest represents the request to create a completion image
|
||||
type CreateCompletionImageRequest struct {
|
||||
CompletionID uint `json:"completion_id" binding:"required"`
|
||||
ImageURL string `json:"image_url" binding:"required"`
|
||||
Caption string `json:"caption"`
|
||||
}
|
||||
|
||||
// UpdateCompletionImageRequest represents the request to update a completion image
|
||||
type UpdateCompletionImageRequest struct {
|
||||
ImageURL *string `json:"image_url"`
|
||||
Caption *string `json:"caption"`
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/completion-images
|
||||
func (h *AdminCompletionImageHandler) List(c *gin.Context) {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Optional completion_id filter
|
||||
completionIDStr := c.Query("completion_id")
|
||||
|
||||
var images []models.TaskCompletionImage
|
||||
var total int64
|
||||
|
||||
query := h.db.Model(&models.TaskCompletionImage{})
|
||||
|
||||
// Apply completion_id filter if provided
|
||||
if completionIDStr != "" {
|
||||
completionID, err := strconv.ParseUint(completionIDStr, 10, 32)
|
||||
if err == nil {
|
||||
query = query.Where("completion_id = ?", completionID)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply search
|
||||
if filters.Search != "" {
|
||||
search := "%" + filters.Search + "%"
|
||||
query = query.Where("image_url ILIKE ? OR caption ILIKE ?", search, search)
|
||||
}
|
||||
|
||||
// Get total count
|
||||
query.Count(&total)
|
||||
|
||||
// Apply sorting
|
||||
sortBy := "created_at"
|
||||
if filters.SortBy != "" {
|
||||
sortBy = filters.SortBy
|
||||
}
|
||||
query = query.Order(sortBy + " " + filters.GetSortDir())
|
||||
|
||||
// Apply pagination
|
||||
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
|
||||
}
|
||||
|
||||
// Build response with task info
|
||||
responses := make([]AdminCompletionImageResponse, len(images))
|
||||
for i, image := range images {
|
||||
responses[i] = h.toResponse(&image)
|
||||
}
|
||||
|
||||
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) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion image"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Create handles POST /api/admin/completion-images
|
||||
func (h *AdminCompletionImageHandler) Create(c *gin.Context) {
|
||||
var req CreateCompletionImageRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify completion"})
|
||||
return
|
||||
}
|
||||
|
||||
image := models.TaskCompletionImage{
|
||||
CompletionID: req.CompletionID,
|
||||
ImageURL: req.ImageURL,
|
||||
Caption: req.Caption,
|
||||
}
|
||||
|
||||
if err := h.db.Create(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create completion image"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/completion-images/:id
|
||||
func (h *AdminCompletionImageHandler) Update(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion image"})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateCompletionImageRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if req.ImageURL != nil {
|
||||
image.ImageURL = *req.ImageURL
|
||||
}
|
||||
if req.Caption != nil {
|
||||
image.Caption = *req.Caption
|
||||
}
|
||||
|
||||
if err := h.db.Save(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update completion image"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/completion-images/:id
|
||||
func (h *AdminCompletionImageHandler) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion image"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete completion image"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Completion image deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/completion-images/bulk
|
||||
func (h *AdminCompletionImageHandler) BulkDelete(c *gin.Context) {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Completion images deleted successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
// toResponse converts a TaskCompletionImage model to AdminCompletionImageResponse
|
||||
func (h *AdminCompletionImageHandler) toResponse(image *models.TaskCompletionImage) AdminCompletionImageResponse {
|
||||
response := AdminCompletionImageResponse{
|
||||
ID: image.ID,
|
||||
CompletionID: image.CompletionID,
|
||||
ImageURL: image.ImageURL,
|
||||
Caption: image.Caption,
|
||||
CreatedAt: image.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
UpdatedAt: image.UpdatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
// Get task info via completion
|
||||
var completion models.TaskCompletion
|
||||
if err := h.db.Preload("Task").First(&completion, image.CompletionID).Error; err == nil {
|
||||
response.TaskID = completion.TaskID
|
||||
if completion.Task.ID != 0 {
|
||||
response.TaskTitle = completion.Task.Title
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
264
internal/admin/handlers/document_image_handler.go
Normal file
264
internal/admin/handlers/document_image_handler.go
Normal file
@@ -0,0 +1,264 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
"github.com/treytartt/casera-api/internal/models"
|
||||
)
|
||||
|
||||
// AdminDocumentImageHandler handles admin document image management endpoints
|
||||
type AdminDocumentImageHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAdminDocumentImageHandler creates a new admin document image handler
|
||||
func NewAdminDocumentImageHandler(db *gorm.DB) *AdminDocumentImageHandler {
|
||||
return &AdminDocumentImageHandler{db: db}
|
||||
}
|
||||
|
||||
// DocumentImageResponse represents the response for a document image
|
||||
type DocumentImageResponse struct {
|
||||
ID uint `json:"id"`
|
||||
DocumentID uint `json:"document_id"`
|
||||
DocumentTitle string `json:"document_title"`
|
||||
ResidenceID uint `json:"residence_id"`
|
||||
ResidenceName string `json:"residence_name"`
|
||||
ImageURL string `json:"image_url"`
|
||||
Caption string `json:"caption"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// CreateDocumentImageRequest represents the request to create a document image
|
||||
type CreateDocumentImageRequest struct {
|
||||
DocumentID uint `json:"document_id" binding:"required"`
|
||||
ImageURL string `json:"image_url" binding:"required"`
|
||||
Caption string `json:"caption"`
|
||||
}
|
||||
|
||||
// UpdateDocumentImageRequest represents the request to update a document image
|
||||
type UpdateDocumentImageRequest struct {
|
||||
ImageURL *string `json:"image_url"`
|
||||
Caption *string `json:"caption"`
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/document-images
|
||||
func (h *AdminDocumentImageHandler) List(c *gin.Context) {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Optional document_id filter
|
||||
documentIDStr := c.Query("document_id")
|
||||
|
||||
var images []models.DocumentImage
|
||||
var total int64
|
||||
|
||||
query := h.db.Model(&models.DocumentImage{})
|
||||
|
||||
// Apply document_id filter if provided
|
||||
if documentIDStr != "" {
|
||||
documentID, err := strconv.ParseUint(documentIDStr, 10, 32)
|
||||
if err == nil {
|
||||
query = query.Where("document_id = ?", documentID)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply search
|
||||
if filters.Search != "" {
|
||||
search := "%" + filters.Search + "%"
|
||||
query = query.Where("image_url ILIKE ? OR caption ILIKE ?", search, search)
|
||||
}
|
||||
|
||||
// Get total count
|
||||
query.Count(&total)
|
||||
|
||||
// Apply sorting
|
||||
sortBy := "created_at"
|
||||
if filters.SortBy != "" {
|
||||
sortBy = filters.SortBy
|
||||
}
|
||||
query = query.Order(sortBy + " " + filters.GetSortDir())
|
||||
|
||||
// Apply pagination
|
||||
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
|
||||
}
|
||||
|
||||
// Build response with document info
|
||||
responses := make([]DocumentImageResponse, len(images))
|
||||
for i, image := range images {
|
||||
responses[i] = h.toResponse(&image)
|
||||
}
|
||||
|
||||
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) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch document image"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Create handles POST /api/admin/document-images
|
||||
func (h *AdminDocumentImageHandler) Create(c *gin.Context) {
|
||||
var req CreateDocumentImageRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify document"})
|
||||
return
|
||||
}
|
||||
|
||||
image := models.DocumentImage{
|
||||
DocumentID: req.DocumentID,
|
||||
ImageURL: req.ImageURL,
|
||||
Caption: req.Caption,
|
||||
}
|
||||
|
||||
if err := h.db.Create(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create document image"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/document-images/:id
|
||||
func (h *AdminDocumentImageHandler) Update(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch document image"})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateDocumentImageRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if req.ImageURL != nil {
|
||||
image.ImageURL = *req.ImageURL
|
||||
}
|
||||
if req.Caption != nil {
|
||||
image.Caption = *req.Caption
|
||||
}
|
||||
|
||||
if err := h.db.Save(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update document image"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/document-images/:id
|
||||
func (h *AdminDocumentImageHandler) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch document image"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete document image"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Document image deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/document-images/bulk
|
||||
func (h *AdminDocumentImageHandler) BulkDelete(c *gin.Context) {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Document images deleted successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
// toResponse converts a DocumentImage model to DocumentImageResponse
|
||||
func (h *AdminDocumentImageHandler) toResponse(image *models.DocumentImage) DocumentImageResponse {
|
||||
response := DocumentImageResponse{
|
||||
ID: image.ID,
|
||||
DocumentID: image.DocumentID,
|
||||
ImageURL: image.ImageURL,
|
||||
Caption: image.Caption,
|
||||
CreatedAt: image.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
UpdatedAt: image.UpdatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
// Get document info
|
||||
var document models.Document
|
||||
if err := h.db.Preload("Residence").First(&document, image.DocumentID).Error; err == nil {
|
||||
response.DocumentTitle = document.Title
|
||||
response.ResidenceID = document.ResidenceID
|
||||
if document.Residence.ID != 0 {
|
||||
response.ResidenceName = document.Residence.Name
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
263
internal/admin/handlers/user_profile_handler.go
Normal file
263
internal/admin/handlers/user_profile_handler.go
Normal file
@@ -0,0 +1,263 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
"github.com/treytartt/casera-api/internal/models"
|
||||
)
|
||||
|
||||
// AdminUserProfileHandler handles admin user profile management endpoints
|
||||
type AdminUserProfileHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAdminUserProfileHandler creates a new admin user profile handler
|
||||
func NewAdminUserProfileHandler(db *gorm.DB) *AdminUserProfileHandler {
|
||||
return &AdminUserProfileHandler{db: db}
|
||||
}
|
||||
|
||||
// UserProfileResponse represents the response for a user profile
|
||||
type UserProfileResponse struct {
|
||||
ID uint `json:"id"`
|
||||
UserID uint `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Verified bool `json:"verified"`
|
||||
Bio string `json:"bio"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
DateOfBirth *string `json:"date_of_birth"`
|
||||
ProfilePicture string `json:"profile_picture"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// UpdateUserProfileRequest represents the request to update a user profile
|
||||
type UpdateUserProfileRequest struct {
|
||||
Verified *bool `json:"verified"`
|
||||
Bio *string `json:"bio"`
|
||||
PhoneNumber *string `json:"phone_number"`
|
||||
DateOfBirth *string `json:"date_of_birth"`
|
||||
ProfilePicture *string `json:"profile_picture"`
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/user-profiles
|
||||
func (h *AdminUserProfileHandler) List(c *gin.Context) {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var profiles []models.UserProfile
|
||||
var total int64
|
||||
|
||||
query := h.db.Model(&models.UserProfile{}).Preload("User")
|
||||
|
||||
// Apply search
|
||||
if filters.Search != "" {
|
||||
search := "%" + filters.Search + "%"
|
||||
query = query.Joins("JOIN auth_user ON auth_user.id = user_userprofile.user_id").
|
||||
Where("auth_user.username ILIKE ? OR auth_user.email ILIKE ? OR user_userprofile.phone_number ILIKE ?",
|
||||
search, search, search)
|
||||
}
|
||||
|
||||
// Get total count
|
||||
query.Count(&total)
|
||||
|
||||
// Apply sorting
|
||||
sortBy := "created_at"
|
||||
if filters.SortBy != "" {
|
||||
sortBy = filters.SortBy
|
||||
}
|
||||
query = query.Order(sortBy + " " + filters.GetSortDir())
|
||||
|
||||
// Apply pagination
|
||||
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
|
||||
}
|
||||
|
||||
// Build response
|
||||
responses := make([]UserProfileResponse, len(profiles))
|
||||
for i, profile := range profiles {
|
||||
responses[i] = h.toProfileResponse(&profile)
|
||||
}
|
||||
|
||||
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) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid profile ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user 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) {
|
||||
userID, err := strconv.ParseUint(c.Param("user_id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user profile"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toProfileResponse(&profile))
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/user-profiles/:id
|
||||
func (h *AdminUserProfileHandler) Update(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid profile ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user profile"})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateUserProfileRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Verified != nil {
|
||||
profile.Verified = *req.Verified
|
||||
}
|
||||
if req.Bio != nil {
|
||||
profile.Bio = *req.Bio
|
||||
}
|
||||
if req.PhoneNumber != nil {
|
||||
profile.PhoneNumber = *req.PhoneNumber
|
||||
}
|
||||
if req.ProfilePicture != nil {
|
||||
profile.ProfilePicture = *req.ProfilePicture
|
||||
}
|
||||
if req.DateOfBirth != nil {
|
||||
if *req.DateOfBirth == "" {
|
||||
profile.DateOfBirth = nil
|
||||
} 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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
h.db.Preload("User").First(&profile, id)
|
||||
c.JSON(http.StatusOK, h.toProfileResponse(&profile))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/user-profiles/:id
|
||||
func (h *AdminUserProfileHandler) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid profile ID"})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch user profile"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&profile).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete user profile"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "User profile deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/user-profiles/bulk
|
||||
func (h *AdminUserProfileHandler) BulkDelete(c *gin.Context) {
|
||||
var req dto.BulkDeleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "User profiles deleted successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
// toProfileResponse converts a UserProfile model to UserProfileResponse
|
||||
func (h *AdminUserProfileHandler) toProfileResponse(profile *models.UserProfile) UserProfileResponse {
|
||||
response := UserProfileResponse{
|
||||
ID: profile.ID,
|
||||
UserID: profile.UserID,
|
||||
Verified: profile.Verified,
|
||||
Bio: profile.Bio,
|
||||
PhoneNumber: profile.PhoneNumber,
|
||||
ProfilePicture: profile.ProfilePicture,
|
||||
CreatedAt: profile.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
UpdatedAt: profile.UpdatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
if profile.DateOfBirth != nil {
|
||||
dob := profile.DateOfBirth.Format("2006-01-02")
|
||||
response.DateOfBirth = &dob
|
||||
}
|
||||
|
||||
if profile.User.ID != 0 {
|
||||
response.Username = profile.User.Username
|
||||
response.Email = profile.User.Email
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
@@ -332,6 +332,54 @@ func SetupRoutes(router *gin.Engine, db *gorm.DB, cfg *config.Config, deps *Depe
|
||||
notifPrefs.GET("/user/:user_id", notifPrefsHandler.GetByUser)
|
||||
}
|
||||
|
||||
// User profile management
|
||||
userProfileHandler := handlers.NewAdminUserProfileHandler(db)
|
||||
userProfiles := protected.Group("/user-profiles")
|
||||
{
|
||||
userProfiles.GET("", userProfileHandler.List)
|
||||
userProfiles.DELETE("/bulk", userProfileHandler.BulkDelete)
|
||||
userProfiles.GET("/:id", userProfileHandler.Get)
|
||||
userProfiles.PUT("/:id", userProfileHandler.Update)
|
||||
userProfiles.DELETE("/:id", userProfileHandler.Delete)
|
||||
userProfiles.GET("/user/:user_id", userProfileHandler.GetByUser)
|
||||
}
|
||||
|
||||
// Apple social auth management
|
||||
appleSocialAuthHandler := handlers.NewAdminAppleSocialAuthHandler(db)
|
||||
appleSocialAuth := protected.Group("/apple-social-auth")
|
||||
{
|
||||
appleSocialAuth.GET("", appleSocialAuthHandler.List)
|
||||
appleSocialAuth.DELETE("/bulk", appleSocialAuthHandler.BulkDelete)
|
||||
appleSocialAuth.GET("/:id", appleSocialAuthHandler.Get)
|
||||
appleSocialAuth.PUT("/:id", appleSocialAuthHandler.Update)
|
||||
appleSocialAuth.DELETE("/:id", appleSocialAuthHandler.Delete)
|
||||
appleSocialAuth.GET("/user/:user_id", appleSocialAuthHandler.GetByUser)
|
||||
}
|
||||
|
||||
// Task completion images management
|
||||
completionImageHandler := handlers.NewAdminCompletionImageHandler(db)
|
||||
completionImages := protected.Group("/completion-images")
|
||||
{
|
||||
completionImages.GET("", completionImageHandler.List)
|
||||
completionImages.POST("", completionImageHandler.Create)
|
||||
completionImages.DELETE("/bulk", completionImageHandler.BulkDelete)
|
||||
completionImages.GET("/:id", completionImageHandler.Get)
|
||||
completionImages.PUT("/:id", completionImageHandler.Update)
|
||||
completionImages.DELETE("/:id", completionImageHandler.Delete)
|
||||
}
|
||||
|
||||
// Document images management
|
||||
documentImageHandler := handlers.NewAdminDocumentImageHandler(db)
|
||||
documentImages := protected.Group("/document-images")
|
||||
{
|
||||
documentImages.GET("", documentImageHandler.List)
|
||||
documentImages.POST("", documentImageHandler.Create)
|
||||
documentImages.DELETE("/bulk", documentImageHandler.BulkDelete)
|
||||
documentImages.GET("/:id", documentImageHandler.Get)
|
||||
documentImages.PUT("/:id", documentImageHandler.Update)
|
||||
documentImages.DELETE("/:id", documentImageHandler.Delete)
|
||||
}
|
||||
|
||||
// System settings management
|
||||
settingsHandler := handlers.NewAdminSettingsHandler(db)
|
||||
settings := protected.Group("/settings")
|
||||
|
||||
Reference in New Issue
Block a user