Add CMS Management Functionality
- Introduced a new CMS management feature, including the ability to create, retrieve, update, search, and delete CMS entries. - Implemented the CMS entity, repository, service, and handler layers following Clean Architecture principles. - Added API endpoints for CMS operations in Swagger documentation, ensuring comprehensive API specifications. - Enhanced error handling and validation for CMS data, improving robustness and user experience. - Updated the main application to initialize the CMS repository and service, integrating them into the existing system.
This commit is contained in:
+21
-5
@@ -53,6 +53,12 @@ package main
|
|||||||
// @tag.name Admin-Notification
|
// @tag.name Admin-Notification
|
||||||
// @tag.description Administrative notification management operations for web panel including CRUD operations, bulk notifications, queue management, and comprehensive filtering with pagination
|
// @tag.description Administrative notification management operations for web panel including CRUD operations, bulk notifications, queue management, and comprehensive filtering with pagination
|
||||||
|
|
||||||
|
// @tag.name Admin-Contacts
|
||||||
|
// @tag.description Administrative contact management operations for web panel including CRUD operations, search, statistics, and comprehensive filtering with pagination
|
||||||
|
|
||||||
|
// @tag.name Admin-CMS
|
||||||
|
// @tag.description Administrative CMS management operations for web panel including CRUD operations, search, statistics, and comprehensive filtering with pagination
|
||||||
|
|
||||||
// @tag.name Authorization
|
// @tag.name Authorization
|
||||||
// @tag.description Customer authentication and authorization operations for mobile application including login, logout, token refresh, and profile access
|
// @tag.description Customer authentication and authorization operations for mobile application including login, logout, token refresh, and profile access
|
||||||
|
|
||||||
@@ -77,12 +83,19 @@ package main
|
|||||||
// @tag.name Notification
|
// @tag.name Notification
|
||||||
// @tag.description Public notification management operations for mobile application including notification listing and detailed notification information
|
// @tag.description Public notification management operations for mobile application including notification listing and detailed notification information
|
||||||
|
|
||||||
|
// @tag.name Contacts
|
||||||
|
// @tag.description Public contact management operations for mobile application including contact submission and contact listing
|
||||||
|
|
||||||
|
// @tag.name CMS
|
||||||
|
// @tag.description Public CMS management operations for mobile application including CMS listing and detailed CMS information
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
"tm/internal/assets"
|
"tm/internal/assets"
|
||||||
|
"tm/internal/cms"
|
||||||
"tm/internal/company"
|
"tm/internal/company"
|
||||||
"tm/internal/company_category"
|
"tm/internal/company_category"
|
||||||
"tm/internal/contact"
|
"tm/internal/contact"
|
||||||
@@ -144,8 +157,9 @@ func main() {
|
|||||||
tenderApprovalRepository := tender_approval.NewRepository(mongoManager, logger)
|
tenderApprovalRepository := tender_approval.NewRepository(mongoManager, logger)
|
||||||
inquiryRepository := inquiry.NewRepository(mongoManager, logger)
|
inquiryRepository := inquiry.NewRepository(mongoManager, logger)
|
||||||
contactRepository := contact.NewContactRepository(mongoManager, logger)
|
contactRepository := contact.NewContactRepository(mongoManager, logger)
|
||||||
|
cmsRepository := cms.NewRepository(mongoManager, logger)
|
||||||
logger.Info("Repositories initialized successfully", map[string]interface{}{
|
logger.Info("Repositories initialized successfully", map[string]interface{}{
|
||||||
"repositories": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval", "inquiry", "contact"},
|
"repositories": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval", "inquiry", "contact", "cms"},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Initialize validation services
|
// Initialize validation services
|
||||||
@@ -164,8 +178,9 @@ func main() {
|
|||||||
flagService := assets.NewService(logger, conf.Assets.FlagsPath)
|
flagService := assets.NewService(logger, conf.Assets.FlagsPath)
|
||||||
notificationService := notification.NewService(notificationSDK, userService, customerService, logger)
|
notificationService := notification.NewService(notificationSDK, userService, customerService, logger)
|
||||||
contactService := contact.NewService(contactRepository, logger)
|
contactService := contact.NewService(contactRepository, logger)
|
||||||
|
cmsService := cms.NewService(cmsRepository, logger)
|
||||||
logger.Info("Services initialized successfully", map[string]interface{}{
|
logger.Info("Services initialized successfully", map[string]interface{}{
|
||||||
"services": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval", "inquiry", "flag", "notification", "contact"},
|
"services": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval", "inquiry", "flag", "notification", "contact", "cms"},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Initialize handlers with services
|
// Initialize handlers with services
|
||||||
@@ -180,16 +195,17 @@ func main() {
|
|||||||
flagHandler := assets.NewHandler(flagService, logger)
|
flagHandler := assets.NewHandler(flagService, logger)
|
||||||
notificationHandler := notification.NewHandler(notificationService)
|
notificationHandler := notification.NewHandler(notificationService)
|
||||||
contactHandler := contact.NewHandler(contactService)
|
contactHandler := contact.NewHandler(contactService)
|
||||||
|
cmsHandler := cms.NewHandler(cmsService)
|
||||||
logger.Info("Handlers initialized successfully", map[string]interface{}{
|
logger.Info("Handlers initialized successfully", map[string]interface{}{
|
||||||
"handlers": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval", "inquiry", "flag", "notification", "contact"},
|
"handlers": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval", "inquiry", "flag", "notification", "contact", "cms"},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Initialize HTTP server
|
// Initialize HTTP server
|
||||||
e := bootstrap.InitHTTPServer(conf, logger)
|
e := bootstrap.InitHTTPServer(conf, logger)
|
||||||
|
|
||||||
// Register routes
|
// Register routes
|
||||||
router.RegisterAdminRoutes(e, userHandler, companyHandler, customerHandler, tenderHandler, feedbackHandler, tenderApprovalHandler, inquiryHandler, categoryHandler, flagHandler, notificationHandler, contactHandler)
|
router.RegisterAdminRoutes(e, userHandler, companyHandler, customerHandler, tenderHandler, feedbackHandler, tenderApprovalHandler, inquiryHandler, categoryHandler, flagHandler, notificationHandler, contactHandler, cmsHandler)
|
||||||
router.RegisterPublicRoutes(e, customerHandler, tenderHandler, feedbackHandler, tenderApprovalHandler, companyHandler, inquiryHandler, categoryHandler, flagHandler, notificationHandler, contactHandler)
|
router.RegisterPublicRoutes(e, customerHandler, tenderHandler, feedbackHandler, tenderApprovalHandler, companyHandler, inquiryHandler, categoryHandler, flagHandler, notificationHandler, contactHandler, cmsHandler)
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
serverAddr := fmt.Sprintf("%s:%d", conf.Server.Host, conf.Server.Port)
|
serverAddr := fmt.Sprintf("%s:%d", conf.Server.Host, conf.Server.Port)
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"tm/internal/assets"
|
"tm/internal/assets"
|
||||||
|
"tm/internal/cms"
|
||||||
"tm/internal/company"
|
"tm/internal/company"
|
||||||
"tm/internal/company_category"
|
"tm/internal/company_category"
|
||||||
|
"tm/internal/contact"
|
||||||
"tm/internal/customer"
|
"tm/internal/customer"
|
||||||
"tm/internal/feedback"
|
"tm/internal/feedback"
|
||||||
"tm/internal/inquiry"
|
"tm/internal/inquiry"
|
||||||
@@ -15,7 +17,7 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler *company.Handler, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler, tenderApprovalHandler *tender_approval.Handler, inquiryHandler *inquiry.Handler, categoryHandler *company_category.Handler, flagHandler *assets.Handler, notificationHandler *notification.NotificationHandler) {
|
func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler *company.Handler, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler, tenderApprovalHandler *tender_approval.Handler, inquiryHandler *inquiry.Handler, categoryHandler *company_category.Handler, flagHandler *assets.Handler, notificationHandler *notification.NotificationHandler, contactHandler *contact.Handler, cmsHandler *cms.Handler) {
|
||||||
adminV1 := e.Group("/admin/v1")
|
adminV1 := e.Group("/admin/v1")
|
||||||
|
|
||||||
// Admin Users Routes
|
// Admin Users Routes
|
||||||
@@ -141,9 +143,31 @@ func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler
|
|||||||
notificationsGP.GET("/mark-seen/:id", notificationHandler.MarkSeen)
|
notificationsGP.GET("/mark-seen/:id", notificationHandler.MarkSeen)
|
||||||
notificationsGP.GET("/my", notificationHandler.AdminNotifications)
|
notificationsGP.GET("/my", notificationHandler.AdminNotifications)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Admin Contact Routes
|
||||||
|
contactsGP := adminV1.Group("/contacts")
|
||||||
|
{
|
||||||
|
contactsGP.Use(userHandler.AuthMiddleware())
|
||||||
|
contactsGP.GET("", contactHandler.Search)
|
||||||
|
contactsGP.GET("/stats", contactHandler.GetStats)
|
||||||
|
contactsGP.GET("/:id", contactHandler.GetByID)
|
||||||
|
contactsGP.PUT("/:id/status", contactHandler.UpdateStatus)
|
||||||
|
contactsGP.DELETE("/:id", contactHandler.Delete)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterPublicRoutes(e *echo.Echo, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler, tenderApprovalHandler *tender_approval.Handler, companyHandler *company.Handler, inquiryHandler *inquiry.Handler, categoryHandler *company_category.Handler, flagHandler *assets.Handler, notificationHandler *notification.NotificationHandler) {
|
// Admin CMS Routes
|
||||||
|
cmsGP := adminV1.Group("/cms")
|
||||||
|
{
|
||||||
|
// cmsGP.Use(userHandler.AuthMiddleware())
|
||||||
|
cmsGP.POST("", cmsHandler.Create)
|
||||||
|
cmsGP.GET("", cmsHandler.Search)
|
||||||
|
cmsGP.GET("/:id", cmsHandler.GetByID)
|
||||||
|
cmsGP.PUT("/:id", cmsHandler.Update)
|
||||||
|
cmsGP.DELETE("/:id", cmsHandler.Delete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterPublicRoutes(e *echo.Echo, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler, tenderApprovalHandler *tender_approval.Handler, companyHandler *company.Handler, inquiryHandler *inquiry.Handler, categoryHandler *company_category.Handler, flagHandler *assets.Handler, notificationHandler *notification.NotificationHandler, contactHandler *contact.Handler, cmsHandler *cms.Handler) {
|
||||||
v1 := e.Group("/api/v1")
|
v1 := e.Group("/api/v1")
|
||||||
|
|
||||||
customerGP := v1.Group("/profile")
|
customerGP := v1.Group("/profile")
|
||||||
@@ -221,4 +245,16 @@ func RegisterPublicRoutes(e *echo.Echo, customerHandler *customer.Handler, tende
|
|||||||
notificationsGP.GET("/mark", notificationHandler.PublicAllMarkSeen)
|
notificationsGP.GET("/mark", notificationHandler.PublicAllMarkSeen)
|
||||||
notificationsGP.GET("/mark/:id", notificationHandler.PublicMarkSeen)
|
notificationsGP.GET("/mark/:id", notificationHandler.PublicMarkSeen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Public Contact Routes
|
||||||
|
contactsPublicGP := v1.Group("/contacts")
|
||||||
|
{
|
||||||
|
contactsPublicGP.POST("", contactHandler.Create)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public CMS Routes
|
||||||
|
cmsGP := v1.Group("/cms")
|
||||||
|
{
|
||||||
|
cmsGP.GET("/:key", cmsHandler.GetByKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package cms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tm/pkg/mongo"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CMS represents the content management system for the landing page
|
||||||
|
type CMS struct {
|
||||||
|
mongo.Model `bson:",inline"`
|
||||||
|
Key string `bson:"key" json:"key" validate:"required"`
|
||||||
|
Hero heroSection `bson:"hero" json:"hero"`
|
||||||
|
Chart chartSection `bson:"chart" json:"chart"`
|
||||||
|
Features cardSection `bson:"features" json:"features"`
|
||||||
|
Challenges cardSection `bson:"challenges" json:"challenges"`
|
||||||
|
Advantages cardSection `bson:"advantages" json:"advantages"`
|
||||||
|
Contact contactSection `bson:"contact" json:"contact"`
|
||||||
|
Footer footerSection `bson:"footer" json:"footer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetID sets the CMS ID (implements IDSetter interface)
|
||||||
|
func (c *CMS) SetID(id string) {
|
||||||
|
c.ID, _ = bson.ObjectIDFromHex(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetID returns the CMS ID (implements IDGetter interface)
|
||||||
|
func (c *CMS) GetID() string {
|
||||||
|
return c.ID.Hex()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCreatedAt sets the created timestamp (implements Timestamp interface)
|
||||||
|
func (c *CMS) SetCreatedAt(timestamp int64) {
|
||||||
|
c.CreatedAt = timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpdatedAt sets the updated timestamp (implements Timestamp interface)
|
||||||
|
func (c *CMS) SetUpdatedAt(timestamp int64) {
|
||||||
|
c.UpdatedAt = timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCreatedAt returns the created timestamp (implements Timestamp interface)
|
||||||
|
func (c *CMS) GetCreatedAt() int64 {
|
||||||
|
return c.CreatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUpdatedAt returns the updated timestamp (implements Timestamp interface)
|
||||||
|
func (c *CMS) GetUpdatedAt() int64 {
|
||||||
|
return c.UpdatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
// heroSection represents the hero section of the landing page
|
||||||
|
type heroSection struct {
|
||||||
|
Title string `bson:"title"`
|
||||||
|
Description string `bson:"description"`
|
||||||
|
ButtonText string `bson:"button_text"`
|
||||||
|
ButtonLink string `bson:"button_link"`
|
||||||
|
GifFile string `bson:"gif_file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// chartSection represents the chart section of the landing page
|
||||||
|
type chartSection struct {
|
||||||
|
Title string `bson:"title" json:"title"`
|
||||||
|
MissedAmount string `bson:"missed_amount" json:"missedAmount"`
|
||||||
|
Data []chartPoint `bson:"data" json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type chartPoint struct {
|
||||||
|
Key string `bson:"key" json:"key"`
|
||||||
|
Value int `bson:"value" json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// cardSection represents the card section of the landing page
|
||||||
|
type cardSection struct {
|
||||||
|
Title string `bson:"title"`
|
||||||
|
Description string `bson:"description"`
|
||||||
|
Cards []card `bson:"cards"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// card represents a card in the card section
|
||||||
|
type card struct {
|
||||||
|
Icon string `bson:"icon"`
|
||||||
|
Title string `bson:"title"`
|
||||||
|
Description string `bson:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// contactSection represents the contact section of the landing page
|
||||||
|
type contactSection struct {
|
||||||
|
Title string `bson:"title"`
|
||||||
|
Description string `bson:"description"`
|
||||||
|
SubmitButtonText string `bson:"submit_button_text"`
|
||||||
|
Fields []formField `bson:"fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// formField represents a form field in the contact section
|
||||||
|
type formField struct {
|
||||||
|
Name string `bson:"name"`
|
||||||
|
Label string `bson:"label"`
|
||||||
|
Placeholder string `bson:"placeholder"`
|
||||||
|
Required bool `bson:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// footerSection represents the footer section of the landing page
|
||||||
|
type footerSection struct {
|
||||||
|
Email string `bson:"email"`
|
||||||
|
Phone string `bson:"phone"`
|
||||||
|
Location string `bson:"location"`
|
||||||
|
Tagline string `bson:"tagline"`
|
||||||
|
Copyright string `bson:"copyright"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package cms
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// Custom errors for cms domain
|
||||||
|
var (
|
||||||
|
ErrCMSNotFound = errors.New("cms not found")
|
||||||
|
ErrCMSKeyRequired = errors.New("cms key is required")
|
||||||
|
ErrCMSKeyExists = errors.New("cms with this key already exists")
|
||||||
|
ErrInvalidCMSData = errors.New("invalid cms data")
|
||||||
|
ErrUnauthorizedAccess = errors.New("unauthorized access to cms")
|
||||||
|
ErrInvalidKeyFormat = errors.New("invalid cms key format")
|
||||||
|
)
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package cms
|
||||||
|
|
||||||
|
import "tm/pkg/response"
|
||||||
|
|
||||||
|
// CMSForm represents the form for creating/updating the CMS
|
||||||
|
type CMSForm struct {
|
||||||
|
Key string `json:"key" valid:"required,length(2|100)" example:"SW-001"`
|
||||||
|
Hero heroSectionForm `json:"hero" valid:"optional"`
|
||||||
|
Chart chartSectionForm `json:"chart" valid:"optional"`
|
||||||
|
Features cardSectionForm `json:"features" valid:"optional"`
|
||||||
|
Challenges cardSectionForm `json:"challenges" valid:"optional"`
|
||||||
|
Advantages cardSectionForm `json:"advantages" valid:"optional"`
|
||||||
|
Contact contactSectionForm `json:"contact" valid:"optional"`
|
||||||
|
Footer footerSectionForm `json:"footer" valid:"optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// heroSectionForm represents the form for creating/updating the hero section
|
||||||
|
type heroSectionForm struct {
|
||||||
|
Title string `json:"title" valid:"optional,length(2|100)" example:"Hero Title"`
|
||||||
|
Description string `json:"description" valid:"optional,length(2|1000)" example:"Hero Description"`
|
||||||
|
ButtonText string `json:"button_text" valid:"optional,length(2|100)" example:"Button Text"`
|
||||||
|
ButtonLink string `json:"button_link" valid:"optional,url" example:"https://example.com"`
|
||||||
|
GifFile string `json:"gif_file" valid:"optional,url" example:"https://example.com/gif.gif"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// chartSectionForm represents the form for creating/updating the chart section
|
||||||
|
type chartSectionForm struct {
|
||||||
|
Title string `json:"title" valid:"optional,length(2|100)" example:"Chart Title"`
|
||||||
|
MissedAmount string `json:"missed_amount" valid:"optional,length(2|100)" example:"Missed Amount"`
|
||||||
|
Data []chartPointForm `json:"data" valid:"optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// chartPointForm represents the form for creating/updating a chart point
|
||||||
|
type chartPointForm struct {
|
||||||
|
Key string `json:"key" valid:"optional,length(2|100)" example:"Key"`
|
||||||
|
Value int `json:"value" valid:"optional,range(0|1000000)" example:"1000000"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// cardSectionForm represents the form for creating/updating the card section
|
||||||
|
type cardSectionForm struct {
|
||||||
|
Title string `json:"title" valid:"optional,length(2|100)" example:"Card Title"`
|
||||||
|
Description string `json:"description" valid:"optional,length(2|1000)" example:"Card Description"`
|
||||||
|
Cards []cardForm `json:"cards" valid:"optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// cardForm represents the form for creating/updating a card
|
||||||
|
type cardForm struct {
|
||||||
|
Icon string `json:"icon" valid:"optional,url" example:"https://example.com/icon.png"`
|
||||||
|
Title string `json:"title" valid:"optional,length(2|100)" example:"Card Title"`
|
||||||
|
Description string `json:"description" valid:"optional,length(2|1000)" example:"Card Description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// contactSectionForm represents the form for creating/updating the contact section
|
||||||
|
type contactSectionForm struct {
|
||||||
|
Title string `json:"title" valid:"optional,length(2|100)" example:"Contact Title"`
|
||||||
|
Description string `json:"description" valid:"optional,length(2|1000)" example:"Contact Description"`
|
||||||
|
SubmitButtonText string `json:"submit_button_text" valid:"optional,length(2|100)" example:"Submit Button Text"`
|
||||||
|
Fields []formFieldForm `json:"fields" valid:"optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// formFieldForm represents the form for creating/updating a form field
|
||||||
|
type formFieldForm struct {
|
||||||
|
Name string `json:"name" valid:"optional,length(2|100)" example:"Name"`
|
||||||
|
Label string `json:"label" valid:"optional,length(2|100)" example:"Label"`
|
||||||
|
Placeholder string `json:"placeholder" valid:"optional,length(2|100)" example:"Placeholder"`
|
||||||
|
Required bool `json:"required" valid:"optional" example:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// footerSectionForm represents the form for creating/updating the footer section
|
||||||
|
type footerSectionForm struct {
|
||||||
|
Email string `json:"email" valid:"optional,email" example:"info@example.com"`
|
||||||
|
Phone string `json:"phone" valid:"optional,length(10|20)" example:"+1234567890"`
|
||||||
|
Location string `json:"location" valid:"optional,length(2|100)" example:"Location"`
|
||||||
|
Tagline string `json:"tagline" valid:"optional,length(2|100)" example:"Tagline"`
|
||||||
|
Copyright string `json:"copyright" valid:"optional,length(2|100)" example:"Copyright"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchCMSForm represents the form for searching CMS entries
|
||||||
|
type SearchCMSForm struct {
|
||||||
|
Search *string `query:"q" valid:"optional"`
|
||||||
|
Key *string `query:"key" valid:"optional,length(2|100)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CMSResponse represents the CMS data sent in API responses
|
||||||
|
type CMSResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
Hero heroSection `json:"hero"`
|
||||||
|
Chart chartSection `json:"chart"`
|
||||||
|
Features cardSection `json:"features"`
|
||||||
|
Challenges cardSection `json:"challenges"`
|
||||||
|
Advantages cardSection `json:"advantages"`
|
||||||
|
Contact contactSection `json:"contact"`
|
||||||
|
Footer footerSection `json:"footer"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CMSListResponse represents the response for listing CMS entries
|
||||||
|
type CMSListResponse struct {
|
||||||
|
CMS []*CMSResponse `json:"cms"`
|
||||||
|
Meta *response.Meta `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToResponse converts CMS to CMSResponse
|
||||||
|
func (c *CMS) ToResponse() *CMSResponse {
|
||||||
|
return &CMSResponse{
|
||||||
|
ID: c.GetID(),
|
||||||
|
Key: c.Key,
|
||||||
|
Hero: c.Hero,
|
||||||
|
Chart: c.Chart,
|
||||||
|
Features: c.Features,
|
||||||
|
Challenges: c.Challenges,
|
||||||
|
Advantages: c.Advantages,
|
||||||
|
Contact: c.Contact,
|
||||||
|
Footer: c.Footer,
|
||||||
|
CreatedAt: c.GetCreatedAt(),
|
||||||
|
UpdatedAt: c.GetUpdatedAt(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
package cms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tm/pkg/response"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler handles HTTP requests for CMS operations
|
||||||
|
type Handler struct {
|
||||||
|
service Service
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler creates a new CMS handler
|
||||||
|
func NewHandler(service Service) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
service: service,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByKey handles retrieving CMS content by key (public API)
|
||||||
|
// @Summary Get CMS content by key
|
||||||
|
// @Description Retrieve CMS content for public display by its key
|
||||||
|
// @Tags CMS
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param key path string true "CMS key"
|
||||||
|
// @Success 200 {object} response.APIResponse{data=CMSResponse}
|
||||||
|
// @Failure 404 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /api/v1/cms/{key} [get]
|
||||||
|
func (h *Handler) GetByKey(c echo.Context) error {
|
||||||
|
key := c.Param("key")
|
||||||
|
|
||||||
|
cms, err := h.service.GetByKey(c.Request().Context(), key)
|
||||||
|
if err != nil {
|
||||||
|
return response.NotFound(c, "CMS content not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Success(c, cms, "CMS content retrieved successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create handles creating a new CMS entry (admin only)
|
||||||
|
// @Summary Create a new CMS entry
|
||||||
|
// @Description Create a new CMS entry for content management (admin only)
|
||||||
|
// @Tags Admin-CMS
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param cms body CMSForm true "CMS information"
|
||||||
|
// @Success 201 {object} response.APIResponse{data=CMSResponse}
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 409 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /admin/v1/cms [post]
|
||||||
|
func (h *Handler) Create(c echo.Context) error {
|
||||||
|
form, err := response.Parse[CMSForm](c)
|
||||||
|
if err != nil {
|
||||||
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create CMS
|
||||||
|
cms, err := h.service.Create(c.Request().Context(), form)
|
||||||
|
if err != nil {
|
||||||
|
if err == ErrCMSKeyExists {
|
||||||
|
return response.Conflict(c, "CMS with this key already exists")
|
||||||
|
}
|
||||||
|
return response.InternalServerError(c, "Failed to create CMS")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Created(c, cms, "CMS entry created successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID handles retrieving a CMS entry by ID (admin only)
|
||||||
|
// @Summary Get CMS by ID
|
||||||
|
// @Description Retrieve a CMS entry by its ID for editing (admin only)
|
||||||
|
// @Tags Admin-CMS
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "CMS ID"
|
||||||
|
// @Success 200 {object} response.APIResponse{data=CMSResponse}
|
||||||
|
// @Failure 404 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /admin/v1/cms/{id} [get]
|
||||||
|
func (h *Handler) GetByID(c echo.Context) error {
|
||||||
|
id := c.Param("id")
|
||||||
|
|
||||||
|
cms, err := h.service.GetByID(c.Request().Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "cms not found" {
|
||||||
|
return response.NotFound(c, "CMS entry not found")
|
||||||
|
}
|
||||||
|
return response.InternalServerError(c, "Failed to get CMS entry")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Success(c, cms, "CMS entry retrieved successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update handles updating a CMS entry (admin only)
|
||||||
|
// @Summary Update CMS entry
|
||||||
|
// @Description Update an existing CMS entry by its ID (admin only)
|
||||||
|
// @Tags Admin-CMS
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "CMS ID"
|
||||||
|
// @Param cms body CMSForm true "Updated CMS information"
|
||||||
|
// @Success 200 {object} response.APIResponse{data=CMSResponse}
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 404 {object} response.APIResponse
|
||||||
|
// @Failure 409 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /admin/v1/cms/{id} [put]
|
||||||
|
func (h *Handler) Update(c echo.Context) error {
|
||||||
|
id := c.Param("id")
|
||||||
|
|
||||||
|
form, err := response.Parse[CMSForm](c)
|
||||||
|
if err != nil {
|
||||||
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update CMS
|
||||||
|
cms, err := h.service.Update(c.Request().Context(), id, form)
|
||||||
|
if err != nil {
|
||||||
|
if err == ErrCMSKeyExists {
|
||||||
|
return response.Conflict(c, "CMS with this key already exists")
|
||||||
|
}
|
||||||
|
if err.Error() == "cms not found" {
|
||||||
|
return response.NotFound(c, "CMS entry not found")
|
||||||
|
}
|
||||||
|
return response.InternalServerError(c, "Failed to update CMS entry")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Success(c, cms, "CMS entry updated successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete handles deleting a CMS entry (admin only)
|
||||||
|
// @Summary Delete CMS entry
|
||||||
|
// @Description Delete a CMS entry by its ID (admin only)
|
||||||
|
// @Tags Admin-CMS
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "CMS ID"
|
||||||
|
// @Success 200 {object} response.APIResponse
|
||||||
|
// @Failure 404 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /admin/v1/cms/{id} [delete]
|
||||||
|
func (h *Handler) Delete(c echo.Context) error {
|
||||||
|
id := c.Param("id")
|
||||||
|
|
||||||
|
err := h.service.Delete(c.Request().Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "cms not found" {
|
||||||
|
return response.NotFound(c, "CMS entry not found")
|
||||||
|
}
|
||||||
|
return response.InternalServerError(c, "Failed to delete CMS entry")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Success(c, nil, "CMS entry deleted successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search handles searching CMS entries (admin only)
|
||||||
|
// @Summary Search CMS entries
|
||||||
|
// @Description Search CMS entries with filters and pagination (admin only)
|
||||||
|
// @Tags Admin-CMS
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param q query string false "Search query"
|
||||||
|
// @Param key query string false "CMS key filter"
|
||||||
|
// @Param limit query integer false "Number of CMS entries per page (1-100)" minimum(1) maximum(100) default(20)
|
||||||
|
// @Param offset query integer false "Number of CMS entries to skip for pagination" minimum(0) default(0)
|
||||||
|
// @Param sort_by query string false "Field to sort by" Enums(key, created_at, updated_at) default(created_at)
|
||||||
|
// @Param sort_order query string false "Sort order" Enums(asc, desc) default(desc)
|
||||||
|
// @Success 200 {object} response.APIResponse{data=CMSListResponse}
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /admin/v1/cms [get]
|
||||||
|
func (h *Handler) Search(c echo.Context) error {
|
||||||
|
form, err := response.Parse[SearchCMSForm](c)
|
||||||
|
if err != nil {
|
||||||
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
pagination := response.NewPagination(c)
|
||||||
|
|
||||||
|
result, err := h.service.Search(c.Request().Context(), form, pagination)
|
||||||
|
if err != nil {
|
||||||
|
return response.InternalServerError(c, "Failed to search CMS entries")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.SuccessWithMeta(c, result, result.Meta, "CMS entries retrieved successfully")
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
package cms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
"tm/pkg/logger"
|
||||||
|
orm "tm/pkg/mongo"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repository defines methods for CMS data access
|
||||||
|
type Repository interface {
|
||||||
|
Create(ctx context.Context, cms *CMS) error
|
||||||
|
Update(ctx context.Context, cms *CMS) error
|
||||||
|
Delete(ctx context.Context, id string) error
|
||||||
|
GetByID(ctx context.Context, id string) (*CMS, error)
|
||||||
|
GetByKey(ctx context.Context, key string) (*CMS, error)
|
||||||
|
Search(ctx context.Context, search *SearchCMSForm, pagination orm.Pagination) (*orm.PaginatedResult[CMS], error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cmsRepository implements the Repository interface using the MongoDB ORM
|
||||||
|
type cmsRepository struct {
|
||||||
|
ormRepo orm.Repository[CMS]
|
||||||
|
logger logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRepository creates a new CMS repository
|
||||||
|
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
||||||
|
// Create indexes using the ORM's index management
|
||||||
|
indexes := []orm.Index{
|
||||||
|
*orm.CreateTextIndex("cms_search_idx", "key"),
|
||||||
|
*orm.NewIndex("key_idx", bson.D{{Key: "key", Value: 1}}),
|
||||||
|
*orm.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create indexes
|
||||||
|
err := mongoManager.CreateIndexes("cms", indexes)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Failed to create CMS indexes", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create ORM repository
|
||||||
|
ormRepo := orm.NewRepository[CMS](mongoManager.GetCollection("cms"), logger)
|
||||||
|
|
||||||
|
return &cmsRepository{
|
||||||
|
ormRepo: ormRepo,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a new CMS entry
|
||||||
|
func (r *cmsRepository) Create(ctx context.Context, cms *CMS) error {
|
||||||
|
// Set created timestamp using Unix timestamp
|
||||||
|
now := time.Now().Unix()
|
||||||
|
cms.SetCreatedAt(now)
|
||||||
|
|
||||||
|
// Use ORM to create CMS
|
||||||
|
err := r.ormRepo.Create(ctx, cms)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("Failed to create CMS", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
"key": cms.Key,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logger.Info("CMS created successfully", map[string]interface{}{
|
||||||
|
"id": cms.GetID(),
|
||||||
|
"key": cms.Key,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates a CMS entry
|
||||||
|
func (r *cmsRepository) Update(ctx context.Context, cms *CMS) error {
|
||||||
|
// Set updated timestamp using Unix timestamp
|
||||||
|
cms.SetUpdatedAt(time.Now().Unix())
|
||||||
|
|
||||||
|
// Use ORM to update CMS
|
||||||
|
err := r.ormRepo.Update(ctx, cms)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("Failed to update CMS", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
"id": cms.GetID(),
|
||||||
|
"key": cms.Key,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logger.Info("CMS updated successfully", map[string]interface{}{
|
||||||
|
"id": cms.GetID(),
|
||||||
|
"key": cms.Key,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID retrieves a CMS entry by ID
|
||||||
|
func (r *cmsRepository) GetByID(ctx context.Context, id string) (*CMS, error) {
|
||||||
|
cms, err := r.ormRepo.FindByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||||
|
return nil, ErrCMSNotFound
|
||||||
|
}
|
||||||
|
r.logger.Error("Failed to get CMS by ID", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
"id": id,
|
||||||
|
})
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByKey retrieves a CMS entry by key
|
||||||
|
func (r *cmsRepository) GetByKey(ctx context.Context, key string) (*CMS, error) {
|
||||||
|
cms, err := r.ormRepo.FindOne(ctx, bson.M{"key": key})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||||
|
return nil, ErrCMSNotFound
|
||||||
|
}
|
||||||
|
r.logger.Error("Failed to get CMS by key", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
"key": key,
|
||||||
|
})
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes a CMS entry
|
||||||
|
func (r *cmsRepository) Delete(ctx context.Context, id string) error {
|
||||||
|
// Get CMS first to log the key
|
||||||
|
cms, err := r.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("Failed to delete CMS", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
"id": id,
|
||||||
|
})
|
||||||
|
if errors.Is(err, ErrCMSNotFound) {
|
||||||
|
return ErrCMSNotFound
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete from database
|
||||||
|
err = r.ormRepo.Delete(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("Failed to delete CMS", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
"id": id,
|
||||||
|
"key": cms.Key,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logger.Info("CMS deleted successfully", map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"key": cms.Key,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search searches CMS entries with filters and pagination
|
||||||
|
func (r *cmsRepository) Search(ctx context.Context, search *SearchCMSForm, pagination orm.Pagination) (*orm.PaginatedResult[CMS], error) {
|
||||||
|
filter := bson.M{}
|
||||||
|
|
||||||
|
if search.Search != nil && *search.Search != "" {
|
||||||
|
filter["$or"] = bson.A{
|
||||||
|
bson.M{"key": bson.M{"$regex": search.Search, "$options": "i"}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if search.Key != nil && *search.Key != "" {
|
||||||
|
filter["key"] = bson.M{"$regex": search.Key, "$options": "i"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use ORM to find CMS entries
|
||||||
|
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("Failed to search CMS entries", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,328 @@
|
|||||||
|
package cms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"tm/pkg/logger"
|
||||||
|
orm "tm/pkg/mongo"
|
||||||
|
"tm/pkg/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service defines the interface for CMS business operations
|
||||||
|
type Service interface {
|
||||||
|
// Create creates a new CMS entry
|
||||||
|
Create(ctx context.Context, form *CMSForm) (*CMSResponse, error)
|
||||||
|
|
||||||
|
// GetByID retrieves a CMS entry by ID
|
||||||
|
GetByID(ctx context.Context, id string) (*CMSResponse, error)
|
||||||
|
|
||||||
|
// GetByKey retrieves a CMS entry by key
|
||||||
|
GetByKey(ctx context.Context, key string) (*CMSResponse, error)
|
||||||
|
|
||||||
|
// Update updates a CMS entry
|
||||||
|
Update(ctx context.Context, id string, form *CMSForm) (*CMSResponse, error)
|
||||||
|
|
||||||
|
// Delete deletes a CMS entry by ID
|
||||||
|
Delete(ctx context.Context, id string) error
|
||||||
|
|
||||||
|
// Search searches CMS entries with filters and pagination
|
||||||
|
Search(ctx context.Context, form *SearchCMSForm, pagination *response.Pagination) (*CMSListResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cmsService implements Service interface
|
||||||
|
type cmsService struct {
|
||||||
|
repo Repository
|
||||||
|
logger logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new CMS service
|
||||||
|
func NewService(repo Repository, logger logger.Logger) Service {
|
||||||
|
return &cmsService{
|
||||||
|
repo: repo,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a new CMS entry
|
||||||
|
func (s *cmsService) Create(ctx context.Context, form *CMSForm) (*CMSResponse, error) {
|
||||||
|
s.logger.Info("Creating new CMS entry", map[string]interface{}{
|
||||||
|
"key": form.Key,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check if CMS with this key already exists
|
||||||
|
existing, err := s.repo.GetByKey(ctx, form.Key)
|
||||||
|
if err == nil && existing != nil {
|
||||||
|
s.logger.Warn("CMS with this key already exists", map[string]interface{}{
|
||||||
|
"key": form.Key,
|
||||||
|
})
|
||||||
|
return nil, ErrCMSKeyExists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create CMS entity
|
||||||
|
cms := &CMS{
|
||||||
|
Key: form.Key,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy form data to entity
|
||||||
|
s.copyFormToEntity(form, cms)
|
||||||
|
|
||||||
|
// Save to database
|
||||||
|
if err := s.repo.Create(ctx, cms); err != nil {
|
||||||
|
s.logger.Error("Failed to create CMS", map[string]interface{}{
|
||||||
|
"key": form.Key,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, fmt.Errorf("failed to create CMS: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("CMS entry created successfully", map[string]interface{}{
|
||||||
|
"id": cms.GetID(),
|
||||||
|
"key": form.Key,
|
||||||
|
})
|
||||||
|
|
||||||
|
return cms.ToResponse(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID retrieves a CMS entry by ID
|
||||||
|
func (s *cmsService) GetByID(ctx context.Context, id string) (*CMSResponse, error) {
|
||||||
|
s.logger.Debug("Retrieving CMS by ID", map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
})
|
||||||
|
|
||||||
|
cms, err := s.repo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get CMS by ID", map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, fmt.Errorf("failed to get CMS: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cms.ToResponse(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByKey retrieves a CMS entry by key
|
||||||
|
func (s *cmsService) GetByKey(ctx context.Context, key string) (*CMSResponse, error) {
|
||||||
|
s.logger.Debug("Retrieving CMS by key", map[string]interface{}{
|
||||||
|
"key": key,
|
||||||
|
})
|
||||||
|
|
||||||
|
cms, err := s.repo.GetByKey(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get CMS by key", map[string]interface{}{
|
||||||
|
"key": key,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, fmt.Errorf("failed to get CMS: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cms.ToResponse(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates a CMS entry
|
||||||
|
func (s *cmsService) Update(ctx context.Context, id string, form *CMSForm) (*CMSResponse, error) {
|
||||||
|
s.logger.Info("Updating CMS entry", map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"key": form.Key,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get existing CMS
|
||||||
|
cms, err := s.repo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get existing CMS for update", map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, fmt.Errorf("failed to get CMS for update: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if key is being changed and if new key already exists
|
||||||
|
if cms.Key != form.Key {
|
||||||
|
existing, err := s.repo.GetByKey(ctx, form.Key)
|
||||||
|
if err == nil && existing != nil && existing.GetID() != id {
|
||||||
|
s.logger.Warn("CMS with this key already exists", map[string]interface{}{
|
||||||
|
"key": form.Key,
|
||||||
|
})
|
||||||
|
return nil, ErrCMSKeyExists
|
||||||
|
}
|
||||||
|
cms.Key = form.Key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy form data to entity
|
||||||
|
s.copyFormToEntity(form, cms)
|
||||||
|
|
||||||
|
// Update in database
|
||||||
|
if err := s.repo.Update(ctx, cms); err != nil {
|
||||||
|
s.logger.Error("Failed to update CMS", map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"key": form.Key,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, fmt.Errorf("failed to update CMS: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("CMS entry updated successfully", map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"key": form.Key,
|
||||||
|
})
|
||||||
|
|
||||||
|
return cms.ToResponse(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes a CMS entry by ID
|
||||||
|
func (s *cmsService) Delete(ctx context.Context, id string) error {
|
||||||
|
s.logger.Info("Deleting CMS entry", map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := s.repo.Delete(ctx, id); err != nil {
|
||||||
|
s.logger.Error("Failed to delete CMS", map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return fmt.Errorf("failed to delete CMS: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("CMS entry deleted successfully", map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search searches CMS entries with filters and pagination
|
||||||
|
func (s *cmsService) Search(ctx context.Context, form *SearchCMSForm, pagination *response.Pagination) (*CMSListResponse, error) {
|
||||||
|
s.logger.Debug("Searching CMS entries", map[string]interface{}{
|
||||||
|
"filters": form,
|
||||||
|
"pagination": pagination,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Convert response.Pagination to orm.Pagination
|
||||||
|
ormPagination := orm.Pagination{
|
||||||
|
Limit: pagination.Limit,
|
||||||
|
Skip: pagination.Offset,
|
||||||
|
SortField: pagination.SortBy,
|
||||||
|
SortOrder: 1, // Default ascending
|
||||||
|
}
|
||||||
|
if pagination.SortOrder == "desc" {
|
||||||
|
ormPagination.SortOrder = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := s.repo.Search(ctx, form, ormPagination)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to search CMS entries", map[string]interface{}{
|
||||||
|
"filters": form,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, fmt.Errorf("failed to search CMS entries: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to response format
|
||||||
|
cmsResponses := make([]*CMSResponse, len(result.Items))
|
||||||
|
for i, cms := range result.Items {
|
||||||
|
cmsResponses[i] = cms.ToResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("CMS search completed", map[string]interface{}{
|
||||||
|
"total_count": result.TotalCount,
|
||||||
|
"items_count": len(result.Items),
|
||||||
|
})
|
||||||
|
|
||||||
|
return &CMSListResponse{
|
||||||
|
CMS: cmsResponses,
|
||||||
|
Meta: pagination.Response(result.TotalCount),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyFormToEntity copies data from form to entity
|
||||||
|
func (s *cmsService) copyFormToEntity(form *CMSForm, cms *CMS) {
|
||||||
|
// Hero section
|
||||||
|
if form.Hero.Title != "" {
|
||||||
|
cms.Hero.Title = form.Hero.Title
|
||||||
|
}
|
||||||
|
if form.Hero.Description != "" {
|
||||||
|
cms.Hero.Description = form.Hero.Description
|
||||||
|
}
|
||||||
|
if form.Hero.ButtonText != "" {
|
||||||
|
cms.Hero.ButtonText = form.Hero.ButtonText
|
||||||
|
}
|
||||||
|
if form.Hero.ButtonLink != "" {
|
||||||
|
cms.Hero.ButtonLink = form.Hero.ButtonLink
|
||||||
|
}
|
||||||
|
if form.Hero.GifFile != "" {
|
||||||
|
cms.Hero.GifFile = form.Hero.GifFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chart section
|
||||||
|
if form.Chart.Title != "" {
|
||||||
|
cms.Chart.Title = form.Chart.Title
|
||||||
|
}
|
||||||
|
if form.Chart.MissedAmount != "" {
|
||||||
|
cms.Chart.MissedAmount = form.Chart.MissedAmount
|
||||||
|
}
|
||||||
|
if len(form.Chart.Data) > 0 {
|
||||||
|
cms.Chart.Data = make([]chartPoint, len(form.Chart.Data))
|
||||||
|
for i, point := range form.Chart.Data {
|
||||||
|
cms.Chart.Data[i] = chartPoint(point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Features section
|
||||||
|
s.copyCardSectionForm(&form.Features, &cms.Features)
|
||||||
|
|
||||||
|
// Challenges section
|
||||||
|
s.copyCardSectionForm(&form.Challenges, &cms.Challenges)
|
||||||
|
|
||||||
|
// Advantages section
|
||||||
|
s.copyCardSectionForm(&form.Advantages, &cms.Advantages)
|
||||||
|
|
||||||
|
// Contact section
|
||||||
|
if form.Contact.Title != "" {
|
||||||
|
cms.Contact.Title = form.Contact.Title
|
||||||
|
}
|
||||||
|
if form.Contact.Description != "" {
|
||||||
|
cms.Contact.Description = form.Contact.Description
|
||||||
|
}
|
||||||
|
if form.Contact.SubmitButtonText != "" {
|
||||||
|
cms.Contact.SubmitButtonText = form.Contact.SubmitButtonText
|
||||||
|
}
|
||||||
|
if len(form.Contact.Fields) > 0 {
|
||||||
|
cms.Contact.Fields = make([]formField, len(form.Contact.Fields))
|
||||||
|
for i, field := range form.Contact.Fields {
|
||||||
|
cms.Contact.Fields[i] = formField(field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Footer section
|
||||||
|
if form.Footer.Email != "" {
|
||||||
|
cms.Footer.Email = form.Footer.Email
|
||||||
|
}
|
||||||
|
if form.Footer.Phone != "" {
|
||||||
|
cms.Footer.Phone = form.Footer.Phone
|
||||||
|
}
|
||||||
|
if form.Footer.Location != "" {
|
||||||
|
cms.Footer.Location = form.Footer.Location
|
||||||
|
}
|
||||||
|
if form.Footer.Tagline != "" {
|
||||||
|
cms.Footer.Tagline = form.Footer.Tagline
|
||||||
|
}
|
||||||
|
if form.Footer.Copyright != "" {
|
||||||
|
cms.Footer.Copyright = form.Footer.Copyright
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyCardSectionForm copies card section form data to entity
|
||||||
|
func (s *cmsService) copyCardSectionForm(formSection *cardSectionForm, entitySection *cardSection) {
|
||||||
|
if formSection.Title != "" {
|
||||||
|
entitySection.Title = formSection.Title
|
||||||
|
}
|
||||||
|
if formSection.Description != "" {
|
||||||
|
entitySection.Description = formSection.Description
|
||||||
|
}
|
||||||
|
if len(formSection.Cards) > 0 {
|
||||||
|
entitySection.Cards = make([]card, len(formSection.Cards))
|
||||||
|
for i, cardForm := range formSection.Cards {
|
||||||
|
entitySection.Cards[i] = card(cardForm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ func NewHandler(service Service) *Handler {
|
|||||||
// Create handles creating a new contact message
|
// Create handles creating a new contact message
|
||||||
// @Summary Create a new contact message
|
// @Summary Create a new contact message
|
||||||
// @Description Create a new contact message from a user
|
// @Description Create a new contact message from a user
|
||||||
// @Tags contacts
|
// @Tags Contacts
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param contact body CreateContactForm true "Contact information"
|
// @Param contact body CreateContactForm true "Contact information"
|
||||||
@@ -52,7 +52,7 @@ func (h *Handler) Create(c echo.Context) error {
|
|||||||
// GetByID handles retrieving a contact by ID (admin only)
|
// GetByID handles retrieving a contact by ID (admin only)
|
||||||
// @Summary Get contact by ID
|
// @Summary Get contact by ID
|
||||||
// @Description Retrieve a contact message by its ID (admin only)
|
// @Description Retrieve a contact message by its ID (admin only)
|
||||||
// @Tags contacts
|
// @Tags Admin-Contacts
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Contact ID"
|
// @Param id path string true "Contact ID"
|
||||||
@@ -78,7 +78,7 @@ func (h *Handler) GetByID(c echo.Context) error {
|
|||||||
// Search handles searching contacts with filters (admin only)
|
// Search handles searching contacts with filters (admin only)
|
||||||
// @Summary Search contacts
|
// @Summary Search contacts
|
||||||
// @Description Search contacts with filters and pagination (admin only)
|
// @Description Search contacts with filters and pagination (admin only)
|
||||||
// @Tags contacts
|
// @Tags Admin-Contacts
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param q query string false "Search query"
|
// @Param q query string false "Search query"
|
||||||
@@ -112,7 +112,7 @@ func (h *Handler) Search(c echo.Context) error {
|
|||||||
// UpdateStatus handles updating contact status (admin only)
|
// UpdateStatus handles updating contact status (admin only)
|
||||||
// @Summary Update contact status
|
// @Summary Update contact status
|
||||||
// @Description Update the status of a contact message (admin only)
|
// @Description Update the status of a contact message (admin only)
|
||||||
// @Tags contacts
|
// @Tags Admin-Contacts
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Contact ID"
|
// @Param id path string true "Contact ID"
|
||||||
@@ -152,7 +152,7 @@ func (h *Handler) UpdateStatus(c echo.Context) error {
|
|||||||
// GetStats handles retrieving contact statistics (admin only)
|
// GetStats handles retrieving contact statistics (admin only)
|
||||||
// @Summary Get contact statistics
|
// @Summary Get contact statistics
|
||||||
// @Description Get contact statistics by status (admin only)
|
// @Description Get contact statistics by status (admin only)
|
||||||
// @Tags contacts
|
// @Tags Admin-Contacts
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} response.APIResponse{data=ContactStats}
|
// @Success 200 {object} response.APIResponse{data=ContactStats}
|
||||||
@@ -170,7 +170,7 @@ func (h *Handler) GetStats(c echo.Context) error {
|
|||||||
// Delete handles deleting a contact (admin only)
|
// Delete handles deleting a contact (admin only)
|
||||||
// @Summary Delete contact
|
// @Summary Delete contact
|
||||||
// @Description Delete a contact message by ID (admin only)
|
// @Description Delete a contact message by ID (admin only)
|
||||||
// @Tags contacts
|
// @Tags Admin-Contacts
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Contact ID"
|
// @Param id path string true "Contact ID"
|
||||||
|
|||||||
Reference in New Issue
Block a user