525 lines
14 KiB
Go
525 lines
14 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"tss-rocks-be/internal/config"
|
|
"tss-rocks-be/internal/service"
|
|
"tss-rocks-be/internal/types"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
type Handler struct {
|
|
cfg *config.Config
|
|
service service.Service
|
|
}
|
|
|
|
func NewHandler(cfg *config.Config, service service.Service) *Handler {
|
|
return &Handler{
|
|
cfg: cfg,
|
|
service: service,
|
|
}
|
|
}
|
|
|
|
// RegisterRoutes registers all the routes
|
|
func (h *Handler) RegisterRoutes(r *gin.Engine) {
|
|
api := r.Group("/api/v1")
|
|
{
|
|
// Auth routes
|
|
auth := api.Group("/auth")
|
|
{
|
|
auth.POST("/register", h.Register)
|
|
auth.POST("/login", h.Login)
|
|
}
|
|
|
|
// User routes
|
|
users := api.Group("/users")
|
|
{
|
|
users.GET("", h.ListUsers)
|
|
users.POST("", h.CreateUser)
|
|
users.GET("/:id", h.GetUser)
|
|
users.PUT("/:id", h.UpdateUser)
|
|
users.DELETE("/:id", h.DeleteUser)
|
|
users.GET("/me", h.GetCurrentUser)
|
|
users.PUT("/me", h.UpdateCurrentUser)
|
|
}
|
|
|
|
// Category routes
|
|
categories := api.Group("/categories")
|
|
{
|
|
categories.GET("", h.ListCategories)
|
|
categories.GET("/:slug", h.GetCategory)
|
|
categories.POST("", h.CreateCategory)
|
|
categories.POST("/:id/contents", h.AddCategoryContent)
|
|
}
|
|
|
|
// Post routes
|
|
posts := api.Group("/posts")
|
|
{
|
|
posts.GET("", h.ListPosts)
|
|
posts.GET("/:slug", h.GetPost)
|
|
posts.POST("", h.CreatePost)
|
|
posts.POST("/:id/contents", h.AddPostContent)
|
|
}
|
|
|
|
// Contributor routes
|
|
contributors := api.Group("/contributors")
|
|
{
|
|
contributors.GET("", h.ListContributors)
|
|
contributors.GET("/:id", h.GetContributor)
|
|
contributors.POST("", h.CreateContributor)
|
|
contributors.POST("/:id/social-links", h.AddContributorSocialLink)
|
|
}
|
|
|
|
// Daily routes
|
|
dailies := api.Group("/dailies")
|
|
{
|
|
dailies.GET("", h.ListDailies)
|
|
dailies.GET("/:id", h.GetDaily)
|
|
dailies.POST("", h.CreateDaily)
|
|
dailies.POST("/:id/contents", h.AddDailyContent)
|
|
}
|
|
|
|
// Media routes
|
|
media := api.Group("/media")
|
|
{
|
|
media.GET("", h.ListMedia)
|
|
media.POST("", h.UploadMedia)
|
|
media.GET("/:id", h.GetMedia)
|
|
media.DELETE("/:id", h.DeleteMedia)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Category handlers
|
|
func (h *Handler) ListCategories(c *gin.Context) {
|
|
langCode := c.Query("lang")
|
|
if langCode == "" {
|
|
langCode = "en" // Default to English
|
|
}
|
|
|
|
categories, err := h.service.ListCategories(c.Request.Context(), langCode)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to list categories")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list categories"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, categories)
|
|
}
|
|
|
|
func (h *Handler) GetCategory(c *gin.Context) {
|
|
langCode := c.Query("lang")
|
|
if langCode == "" {
|
|
langCode = "en" // Default to English
|
|
}
|
|
|
|
slug := c.Param("slug")
|
|
category, err := h.service.GetCategoryBySlug(c.Request.Context(), langCode, slug)
|
|
if err != nil {
|
|
if err == types.ErrNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Category not found"})
|
|
return
|
|
}
|
|
log.Error().Err(err).Msg("Failed to get category")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get category"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, category)
|
|
}
|
|
|
|
func (h *Handler) CreateCategory(c *gin.Context) {
|
|
category, err := h.service.CreateCategory(c.Request.Context())
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to create category")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create category"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, category)
|
|
}
|
|
|
|
type AddCategoryContentRequest struct {
|
|
LanguageCode string `json:"language_code" binding:"required"`
|
|
Name string `json:"name" binding:"required"`
|
|
Description *string `json:"description"`
|
|
Slug string `json:"slug" binding:"required"`
|
|
}
|
|
|
|
func (h *Handler) AddCategoryContent(c *gin.Context) {
|
|
var req AddCategoryContentRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
categoryID, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid category ID"})
|
|
return
|
|
}
|
|
|
|
var description string
|
|
if req.Description != nil {
|
|
description = *req.Description
|
|
}
|
|
|
|
content, err := h.service.AddCategoryContent(c.Request.Context(), categoryID, req.LanguageCode, req.Name, description, req.Slug)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to add category content")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to add category content"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, content)
|
|
}
|
|
|
|
// Post handlers
|
|
func (h *Handler) ListPosts(c *gin.Context) {
|
|
langCode := c.Query("lang")
|
|
if langCode == "" {
|
|
langCode = "en" // Default to English
|
|
}
|
|
|
|
var categoryID *int
|
|
if catIDStr := c.Query("category_id"); catIDStr != "" {
|
|
if id, err := strconv.Atoi(catIDStr); err == nil {
|
|
categoryID = &id
|
|
}
|
|
}
|
|
|
|
limit := 10 // Default limit
|
|
if limitStr := c.Query("limit"); limitStr != "" {
|
|
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
|
|
limit = l
|
|
}
|
|
}
|
|
|
|
offset := 0 // Default offset
|
|
if offsetStr := c.Query("offset"); offsetStr != "" {
|
|
if o, err := strconv.Atoi(offsetStr); err == nil && o >= 0 {
|
|
offset = o
|
|
}
|
|
}
|
|
|
|
posts, err := h.service.ListPosts(c.Request.Context(), langCode, categoryID, limit, offset)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to list posts")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list posts"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, posts)
|
|
}
|
|
|
|
func (h *Handler) GetPost(c *gin.Context) {
|
|
langCode := c.Query("lang")
|
|
if langCode == "" {
|
|
langCode = "en" // Default to English
|
|
}
|
|
|
|
slug := c.Param("slug")
|
|
post, err := h.service.GetPostBySlug(c.Request.Context(), langCode, slug)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to get post")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get post"})
|
|
return
|
|
}
|
|
|
|
// Convert to a map to control the fields
|
|
response := gin.H{
|
|
"id": post.ID,
|
|
"status": post.Status,
|
|
"slug": post.Slug,
|
|
"edges": gin.H{
|
|
"contents": []gin.H{},
|
|
},
|
|
}
|
|
|
|
contents := make([]gin.H, 0, len(post.Edges.Contents))
|
|
for _, content := range post.Edges.Contents {
|
|
contents = append(contents, gin.H{
|
|
"language_code": content.LanguageCode,
|
|
"title": content.Title,
|
|
"content_markdown": content.ContentMarkdown,
|
|
"summary": content.Summary,
|
|
})
|
|
}
|
|
response["edges"].(gin.H)["contents"] = contents
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (h *Handler) CreatePost(c *gin.Context) {
|
|
post, err := h.service.CreatePost(c.Request.Context(), "draft") // Default to draft status
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to create post")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create post"})
|
|
return
|
|
}
|
|
|
|
// Convert to a map to control the fields
|
|
response := gin.H{
|
|
"id": post.ID,
|
|
"status": post.Status,
|
|
"edges": gin.H{
|
|
"contents": []interface{}{},
|
|
},
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, response)
|
|
}
|
|
|
|
type AddPostContentRequest struct {
|
|
LanguageCode string `json:"language_code" binding:"required"`
|
|
Title string `json:"title" binding:"required"`
|
|
ContentMarkdown string `json:"content_markdown" binding:"required"`
|
|
Summary string `json:"summary" binding:"required"`
|
|
MetaKeywords string `json:"meta_keywords"`
|
|
MetaDescription string `json:"meta_description"`
|
|
}
|
|
|
|
func (h *Handler) AddPostContent(c *gin.Context) {
|
|
var req AddPostContentRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
postID, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid post ID"})
|
|
return
|
|
}
|
|
|
|
content, err := h.service.AddPostContent(c.Request.Context(), postID, req.LanguageCode, req.Title, req.ContentMarkdown, req.Summary, req.MetaKeywords, req.MetaDescription)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to add post content")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to add post content"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"title": content.Title,
|
|
"content_markdown": content.ContentMarkdown,
|
|
"language_code": content.LanguageCode,
|
|
"summary": content.Summary,
|
|
"meta_keywords": content.MetaKeywords,
|
|
"meta_description": content.MetaDescription,
|
|
"edges": gin.H{},
|
|
})
|
|
}
|
|
|
|
// Contributor handlers
|
|
func (h *Handler) ListContributors(c *gin.Context) {
|
|
contributors, err := h.service.ListContributors(c.Request.Context())
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to list contributors")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list contributors"})
|
|
return
|
|
}
|
|
|
|
response := make([]gin.H, len(contributors))
|
|
for i, contributor := range contributors {
|
|
socialLinks := make([]gin.H, len(contributor.Edges.SocialLinks))
|
|
for j, link := range contributor.Edges.SocialLinks {
|
|
socialLinks[j] = gin.H{
|
|
"type": link.Type,
|
|
"value": link.Value,
|
|
"edges": gin.H{},
|
|
}
|
|
}
|
|
|
|
response[i] = gin.H{
|
|
"id": contributor.ID,
|
|
"name": contributor.Name,
|
|
"created_at": contributor.CreatedAt,
|
|
"updated_at": contributor.UpdatedAt,
|
|
"edges": gin.H{
|
|
"social_links": socialLinks,
|
|
},
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (h *Handler) GetContributor(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid contributor ID"})
|
|
return
|
|
}
|
|
|
|
contributor, err := h.service.GetContributorByID(c.Request.Context(), id)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to get contributor")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get contributor"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, contributor)
|
|
}
|
|
|
|
type CreateContributorRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
AvatarURL *string `json:"avatar_url"`
|
|
Bio *string `json:"bio"`
|
|
}
|
|
|
|
func (h *Handler) CreateContributor(c *gin.Context) {
|
|
var req CreateContributorRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
contributor, err := h.service.CreateContributor(c.Request.Context(), req.Name, req.AvatarURL, req.Bio)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to create contributor")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create contributor"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, contributor)
|
|
}
|
|
|
|
type AddContributorSocialLinkRequest struct {
|
|
Type string `json:"type" binding:"required"`
|
|
Name *string `json:"name"`
|
|
Value string `json:"value" binding:"required"`
|
|
}
|
|
|
|
func (h *Handler) AddContributorSocialLink(c *gin.Context) {
|
|
var req AddContributorSocialLinkRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
contributorID, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid contributor ID"})
|
|
return
|
|
}
|
|
|
|
name := ""
|
|
if req.Name != nil {
|
|
name = *req.Name
|
|
}
|
|
link, err := h.service.AddContributorSocialLink(c.Request.Context(), contributorID, req.Type, name, req.Value)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to add contributor social link")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to add contributor social link"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, link)
|
|
}
|
|
|
|
// Daily handlers
|
|
func (h *Handler) ListDailies(c *gin.Context) {
|
|
langCode := c.Query("lang")
|
|
if langCode == "" {
|
|
langCode = "en" // Default to English
|
|
}
|
|
|
|
var categoryID *int
|
|
if catIDStr := c.Query("category_id"); catIDStr != "" {
|
|
if id, err := strconv.Atoi(catIDStr); err == nil {
|
|
categoryID = &id
|
|
}
|
|
}
|
|
|
|
limit := 10 // Default limit
|
|
if limitStr := c.Query("limit"); limitStr != "" {
|
|
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
|
|
limit = l
|
|
}
|
|
}
|
|
|
|
offset := 0 // Default offset
|
|
if offsetStr := c.Query("offset"); offsetStr != "" {
|
|
if o, err := strconv.Atoi(offsetStr); err == nil && o >= 0 {
|
|
offset = o
|
|
}
|
|
}
|
|
|
|
dailies, err := h.service.ListDailies(c.Request.Context(), langCode, categoryID, limit, offset)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to list dailies")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list dailies"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, dailies)
|
|
}
|
|
|
|
func (h *Handler) GetDaily(c *gin.Context) {
|
|
id := c.Param("id")
|
|
daily, err := h.service.GetDailyByID(c.Request.Context(), id)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to get daily")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get daily"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, daily)
|
|
}
|
|
|
|
type CreateDailyRequest struct {
|
|
ID string `json:"id" binding:"required"`
|
|
CategoryID int `json:"category_id" binding:"required"`
|
|
ImageURL string `json:"image_url" binding:"required"`
|
|
}
|
|
|
|
func (h *Handler) CreateDaily(c *gin.Context) {
|
|
var req CreateDailyRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
daily, err := h.service.CreateDaily(c.Request.Context(), req.ID, req.CategoryID, req.ImageURL)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to create daily")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create daily"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, daily)
|
|
}
|
|
|
|
type AddDailyContentRequest struct {
|
|
LanguageCode string `json:"language_code" binding:"required"`
|
|
Quote string `json:"quote" binding:"required"`
|
|
}
|
|
|
|
func (h *Handler) AddDailyContent(c *gin.Context) {
|
|
var req AddDailyContentRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
dailyID := c.Param("id")
|
|
content, err := h.service.AddDailyContent(c.Request.Context(), dailyID, req.LanguageCode, req.Quote)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to add daily content")
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to add daily content"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, content)
|
|
}
|
|
|
|
// Helper functions
|
|
func stringPtr(s *string) string {
|
|
if s == nil {
|
|
return ""
|
|
}
|
|
return *s
|
|
}
|