first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:40:14 +03:00
commit e04ba85564
129 changed files with 17541 additions and 0 deletions

745
app/blogs/handlers/blog.go Normal file
View File

@@ -0,0 +1,745 @@
package handlers
import (
"net/http"
"strconv"
"time"
blogModels "ginimageApi/app/blogs/models"
"ginimageApi/configs"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type createPostRequest struct {
Title string `json:"title" binding:"required,min=3"`
Content string `json:"content"`
Keywords string `json:"keywords"`
Image string `json:"image"`
Video string `json:"video"`
IsActive *bool `json:"is_active"`
IsFront *bool `json:"is_front"`
}
type updatePostRequest struct {
Title string `json:"title"`
Content string `json:"content"`
Keywords string `json:"keywords"`
Image string `json:"image"`
Video string `json:"video"`
IsActive *bool `json:"is_active"`
IsFront *bool `json:"is_front"`
}
type BlogErrorResponse struct {
Error string `json:"error"`
}
type BlogPostResponse struct {
ID uint64 `json:"id"`
Title string `json:"title"`
Slug string `json:"slug"`
Content string `json:"content"`
Keywords string `json:"keywords"`
Image string `json:"image"`
Video string `json:"video"`
IsActive bool `json:"is_active"`
IsFront bool `json:"is_front"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type BlogListResponse struct {
Count int `json:"count"`
Items []BlogPostResponse `json:"items"`
}
type BlogCategoryResponse struct {
ID uint64 `json:"id"`
Title string `json:"title"`
Slug string `json:"slug"`
Keywords string `json:"keywords"`
Desc string `json:"description"`
Image string `json:"image"`
IsActive bool `json:"is_active"`
Order int `json:"order"`
}
type BlogTagResponse struct {
ID uint64 `json:"id"`
Tag string `json:"tag"`
Slug string `json:"slug"`
IsActive bool `json:"is_active"`
}
type BlogCategoryListResponse struct {
Count int `json:"count"`
Items []BlogCategoryResponse `json:"items"`
}
type BlogTagListResponse struct {
Count int `json:"count"`
Items []BlogTagResponse `json:"items"`
}
type createCategoryRequest struct {
Title string `json:"title" binding:"required,min=2"`
Keywords string `json:"keywords"`
Desc string `json:"description"`
Image string `json:"image"`
Order int `json:"order"`
IsActive *bool `json:"is_active"`
ParentID *uint64 `json:"parent_id"`
}
type updateCategoryRequest struct {
Title string `json:"title"`
Keywords string `json:"keywords"`
Desc string `json:"description"`
Image string `json:"image"`
Order *int `json:"order"`
IsActive *bool `json:"is_active"`
ParentID *uint64 `json:"parent_id"`
}
type createTagRequest struct {
Tag string `json:"tag" binding:"required,min=2"`
IsActive *bool `json:"is_active"`
}
type updateTagRequest struct {
Tag string `json:"tag"`
IsActive *bool `json:"is_active"`
}
func toBlogPostResponse(p blogModels.Post) BlogPostResponse {
return BlogPostResponse{
ID: p.ID,
Title: p.Title,
Slug: p.Slug,
Content: p.Content,
Keywords: p.Keywords,
Image: p.Image,
Video: p.Video,
IsActive: p.IsActive,
IsFront: p.IsFront,
CreatedAt: p.CreatedAt.Format(time.RFC3339),
UpdatedAt: p.UpdatedAt.Format(time.RFC3339),
}
}
func toBlogCategoryResponse(c blogModels.Category) BlogCategoryResponse {
return BlogCategoryResponse{
ID: c.ID,
Title: c.Title,
Slug: c.Slug,
Keywords: c.Keywords,
Desc: c.Desc,
Image: c.Image,
IsActive: c.IsActive,
Order: c.Order,
}
}
func toBlogTagResponse(t blogModels.Tag) BlogTagResponse {
return BlogTagResponse{
ID: t.ID,
Tag: t.Tag,
Slug: t.Slug,
IsActive: t.IsActive,
}
}
func userIDFromContext(c *gin.Context) (uint64, bool) {
v, ok := c.Get("user_id")
if !ok {
return 0, false
}
switch id := v.(type) {
case uint:
return uint64(id), true
case int:
if id < 0 {
return 0, false
}
return uint64(id), true
case string:
parsed, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return 0, false
}
return parsed, true
default:
return 0, false
}
}
// ListPosts godoc
// @Summary Public blog post listesini getirir
// @Tags blogs
// @Produce json
// @Success 200 {object} BlogListResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs [get]
func ListPosts(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
var posts []blogModels.Post
if err := configs.DB.Where("is_active = ?", true).Order("id desc").Find(&posts).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "postlar listelenemedi"})
return
}
items := make([]BlogPostResponse, 0, len(posts))
for _, p := range posts {
items = append(items, toBlogPostResponse(p))
}
c.JSON(http.StatusOK, BlogListResponse{Count: len(items), Items: items})
}
// GetPost godoc
// @Summary Public tekil blog postu getirir
// @Tags blogs
// @Produce json
// @Param slug path string true "Post slug"
// @Success 200 {object} BlogPostResponse
// @Failure 404 {object} BlogErrorResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs/{slug} [get]
func GetPost(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
slug := c.Param("slug")
var post blogModels.Post
err := configs.DB.Where("slug = ? AND is_active = ?", slug, true).First(&post).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "post bulunamadi"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "post getirilemedi"})
return
}
c.JSON(http.StatusOK, toBlogPostResponse(post))
}
// CreatePost godoc
// @Summary Admin blog post olusturur
// @Tags blogs
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body createPostRequest true "Post bilgileri"
// @Success 201 {object} BlogPostResponse
// @Failure 400 {object} BlogErrorResponse
// @Failure 401 {object} BlogErrorResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs [post]
func CreatePost(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
userID, ok := userIDFromContext(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "kullanici bulunamadi"})
return
}
var req createPostRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
post := blogModels.Post{
Title: req.Title,
Content: req.Content,
Keywords: req.Keywords,
Image: req.Image,
Video: req.Video,
UserID: &userID,
IsActive: true,
IsFront: true,
}
if req.IsActive != nil {
post.IsActive = *req.IsActive
}
if req.IsFront != nil {
post.IsFront = *req.IsFront
}
if err := configs.DB.Create(&post).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "post olusturulamadi"})
return
}
c.JSON(http.StatusCreated, toBlogPostResponse(post))
}
// UpdatePost godoc
// @Summary Admin blog post gunceller
// @Tags blogs
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Post ID"
// @Param request body updatePostRequest true "Guncellenecek alanlar"
// @Success 200 {object} BlogPostResponse
// @Failure 400 {object} BlogErrorResponse
// @Failure 404 {object} BlogErrorResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs/{id} [put]
func UpdatePost(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "gecersiz post id"})
return
}
var post blogModels.Post
if err := configs.DB.First(&post, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "post bulunamadi"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "post bulunamadi"})
return
}
var req updatePostRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.Title != "" {
post.Title = req.Title
}
if req.Content != "" {
post.Content = req.Content
}
if req.Keywords != "" {
post.Keywords = req.Keywords
}
if req.Image != "" {
post.Image = req.Image
}
if req.Video != "" {
post.Video = req.Video
}
if req.IsActive != nil {
post.IsActive = *req.IsActive
}
if req.IsFront != nil {
post.IsFront = *req.IsFront
}
if err := configs.DB.Save(&post).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "post guncellenemedi"})
return
}
c.JSON(http.StatusOK, toBlogPostResponse(post))
}
// DeletePost godoc
// @Summary Admin blog post siler
// @Tags blogs
// @Security BearerAuth
// @Param id path int true "Post ID"
// @Success 204
// @Failure 400 {object} BlogErrorResponse
// @Failure 404 {object} BlogErrorResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs/{id} [delete]
func DeletePost(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "gecersiz post id"})
return
}
res := configs.DB.Delete(&blogModels.Post{}, id)
if res.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "post silinemedi"})
return
}
if res.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "post bulunamadi"})
return
}
c.Status(http.StatusNoContent)
}
// ListCategories godoc
// @Summary Public kategori listesini getirir
// @Tags blogs
// @Produce json
// @Success 200 {object} BlogCategoryListResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs/categories [get]
func ListCategories(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
var categories []blogModels.Category
if err := configs.DB.Where("is_active = ?", true).Order("`order` asc, id desc").Find(&categories).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "kategoriler listelenemedi"})
return
}
items := make([]BlogCategoryResponse, 0, len(categories))
for _, item := range categories {
items = append(items, toBlogCategoryResponse(item))
}
c.JSON(http.StatusOK, BlogCategoryListResponse{Count: len(items), Items: items})
}
// GetCategory godoc
// @Summary Public tekil kategori getirir
// @Tags blogs
// @Produce json
// @Param slug path string true "Kategori slug"
// @Success 200 {object} BlogCategoryResponse
// @Failure 404 {object} BlogErrorResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs/categories/{slug} [get]
func GetCategory(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
var category blogModels.Category
err := configs.DB.Where("slug = ? AND is_active = ?", c.Param("slug"), true).First(&category).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "kategori bulunamadi"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "kategori getirilemedi"})
return
}
c.JSON(http.StatusOK, toBlogCategoryResponse(category))
}
// CreateCategory godoc
// @Summary Admin kategori olusturur
// @Tags blogs
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body createCategoryRequest true "Kategori bilgileri"
// @Success 201 {object} BlogCategoryResponse
// @Failure 400 {object} BlogErrorResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs/categories [post]
func CreateCategory(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
var req createCategoryRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
category := blogModels.Category{
Title: req.Title,
Keywords: req.Keywords,
Desc: req.Desc,
Image: req.Image,
Order: req.Order,
ParentID: req.ParentID,
IsActive: true,
}
if req.IsActive != nil {
category.IsActive = *req.IsActive
}
if category.Order == 0 {
category.Order = 1
}
if err := configs.DB.Create(&category).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "kategori olusturulamadi"})
return
}
c.JSON(http.StatusCreated, toBlogCategoryResponse(category))
}
// UpdateCategory godoc
// @Summary Admin kategori gunceller
// @Tags blogs
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Kategori ID"
// @Param request body updateCategoryRequest true "Guncellenecek alanlar"
// @Success 200 {object} BlogCategoryResponse
// @Failure 400 {object} BlogErrorResponse
// @Failure 404 {object} BlogErrorResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs/categories/{id} [put]
func UpdateCategory(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "gecersiz kategori id"})
return
}
var category blogModels.Category
if err := configs.DB.First(&category, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "kategori bulunamadi"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "kategori bulunamadi"})
return
}
var req updateCategoryRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.Title != "" {
category.Title = req.Title
}
if req.Keywords != "" {
category.Keywords = req.Keywords
}
if req.Desc != "" {
category.Desc = req.Desc
}
if req.Image != "" {
category.Image = req.Image
}
if req.Order != nil {
category.Order = *req.Order
}
if req.IsActive != nil {
category.IsActive = *req.IsActive
}
if req.ParentID != nil {
category.ParentID = req.ParentID
}
if err := configs.DB.Save(&category).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "kategori guncellenemedi"})
return
}
c.JSON(http.StatusOK, toBlogCategoryResponse(category))
}
// DeleteCategory godoc
// @Summary Admin kategori siler
// @Tags blogs
// @Security BearerAuth
// @Param id path int true "Kategori ID"
// @Success 204
// @Failure 400 {object} BlogErrorResponse
// @Failure 404 {object} BlogErrorResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs/categories/{id} [delete]
func DeleteCategory(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "gecersiz kategori id"})
return
}
res := configs.DB.Delete(&blogModels.Category{}, id)
if res.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "kategori silinemedi"})
return
}
if res.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "kategori bulunamadi"})
return
}
c.Status(http.StatusNoContent)
}
// ListTags godoc
// @Summary Public tag listesini getirir
// @Tags blogs
// @Produce json
// @Success 200 {object} BlogTagListResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs/tags [get]
func ListTags(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
var tags []blogModels.Tag
if err := configs.DB.Where("is_active = ?", true).Order("id desc").Find(&tags).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "tagler listelenemedi"})
return
}
items := make([]BlogTagResponse, 0, len(tags))
for _, item := range tags {
items = append(items, toBlogTagResponse(item))
}
c.JSON(http.StatusOK, BlogTagListResponse{Count: len(items), Items: items})
}
// GetTag godoc
// @Summary Public tekil tag getirir
// @Tags blogs
// @Produce json
// @Param slug path string true "Tag slug"
// @Success 200 {object} BlogTagResponse
// @Failure 404 {object} BlogErrorResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs/tags/{slug} [get]
func GetTag(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
var tag blogModels.Tag
err := configs.DB.Where("slug = ? AND is_active = ?", c.Param("slug"), true).First(&tag).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "tag bulunamadi"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "tag getirilemedi"})
return
}
c.JSON(http.StatusOK, toBlogTagResponse(tag))
}
// CreateTag godoc
// @Summary Admin tag olusturur
// @Tags blogs
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body createTagRequest true "Tag bilgileri"
// @Success 201 {object} BlogTagResponse
// @Failure 400 {object} BlogErrorResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs/tags [post]
func CreateTag(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
var req createTagRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
tag := blogModels.Tag{Tag: req.Tag, IsActive: true}
if req.IsActive != nil {
tag.IsActive = *req.IsActive
}
if err := configs.DB.Create(&tag).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "tag olusturulamadi"})
return
}
c.JSON(http.StatusCreated, toBlogTagResponse(tag))
}
// UpdateTag godoc
// @Summary Admin tag gunceller
// @Tags blogs
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Tag ID"
// @Param request body updateTagRequest true "Guncellenecek alanlar"
// @Success 200 {object} BlogTagResponse
// @Failure 400 {object} BlogErrorResponse
// @Failure 404 {object} BlogErrorResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs/tags/{id} [put]
func UpdateTag(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "gecersiz tag id"})
return
}
var tag blogModels.Tag
if err := configs.DB.First(&tag, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "tag bulunamadi"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "tag bulunamadi"})
return
}
var req updateTagRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.Tag != "" {
tag.Tag = req.Tag
}
if req.IsActive != nil {
tag.IsActive = *req.IsActive
}
if err := configs.DB.Save(&tag).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "tag guncellenemedi"})
return
}
c.JSON(http.StatusOK, toBlogTagResponse(tag))
}
// DeleteTag godoc
// @Summary Admin tag siler
// @Tags blogs
// @Security BearerAuth
// @Param id path int true "Tag ID"
// @Success 204
// @Failure 400 {object} BlogErrorResponse
// @Failure 404 {object} BlogErrorResponse
// @Failure 500 {object} BlogErrorResponse
// @Router /api/v1/blogs/tags/{id} [delete]
func DeleteTag(c *gin.Context) {
if configs.DB == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db baglantisi yok"})
return
}
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "gecersiz tag id"})
return
}
res := configs.DB.Delete(&blogModels.Tag{}, id)
if res.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "tag silinemedi"})
return
}
if res.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "tag bulunamadi"})
return
}
c.Status(http.StatusNoContent)
}