diff --git a/config/connection.go b/config/connection.go index 3fe6ed8..0911c2a 100644 --- a/config/connection.go +++ b/config/connection.go @@ -57,6 +57,7 @@ func InitDatabase() { &domain.MenuAccess{}, &domain.PlatformHandle{}, &domain.Address{}, + &domain.Article{}, ) if err != nil { log.Fatal("Error: Failed to auto migrate domain:", err) diff --git a/domain/article.go b/domain/article.go new file mode 100644 index 0000000..a30cf98 --- /dev/null +++ b/domain/article.go @@ -0,0 +1,14 @@ +package domain + +import "time" + +type Article struct { + ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` + Title string `gorm:"not null" json:"title"` + CoverImage string `gorm:"not null" json:"coverImage"` + Author string `gorm:"not null" json:"author"` + Heading string `gorm:"not null" json:"heading"` + Content string `gorm:"not null" json:"content"` + PublishedAt time.Time `gorm:"not null" json:"publishedAt"` + UpdatedAt time.Time `gorm:"not null" json:"updatedAt"` +} diff --git a/dto/article.go b/dto/article.go new file mode 100644 index 0000000..74815b3 --- /dev/null +++ b/dto/article.go @@ -0,0 +1,35 @@ +package dto + +import "time" + +type ArticleRequest struct { + Title string `json:"title" validate:"required"` + CoverImage string `json:"coverImage" validate:"required"` + Author string `json:"author" validate:"required"` + Heading string `json:"heading" validate:"required"` + Content string `json:"content" validate:"required"` + PublishedAt time.Time `json:"publishedAt" validate:"required"` +} + +type ArticleResponse struct { + ID string `json:"id"` + Title string `json:"title"` + CoverImage string `json:"coverImage"` + Author string `json:"author"` + Heading string `json:"heading"` + Content string `json:"content"` + PublishedAt time.Time `json:"publishedAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type ArticleUpdateRequest struct { + Title string `json:"title" validate:"required"` + CoverImage string `json:"coverImage" validate:"required"` + Author string `json:"author" validate:"required"` + Heading string `json:"heading" validate:"required"` + Content string `json:"content" validate:"required"` +} + +func (ar *ArticleRequest) Validate() error { + return validate.Struct(ar) +} \ No newline at end of file diff --git a/internal/api/routes.go b/internal/api/routes.go index 6a6bcdf..903b384 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -31,4 +31,11 @@ func AppRouter(app *fiber.App) { app.Post("/create-address", middleware.AuthMiddleware, controllers.CreateAddress) app.Put("/address/:id", middleware.AuthMiddleware, controllers.UpdateAddress) app.Delete("/address/:id", middleware.AuthMiddleware, controllers.DeleteAddress) + + // # article + app.Post("/articles", middleware.AuthMiddleware, controllers.CreateArticle) + app.Get("/articles", middleware.AuthMiddleware, controllers.GetArticles) + app.Get("/articles/:id", middleware.AuthMiddleware, controllers.GetArticleByID) + app.Put("/articles/:id", middleware.AuthMiddleware, controllers.UpdateArticle) + app.Delete("/articles/:id", middleware.AuthMiddleware, controllers.DeleteArticle) } diff --git a/internal/controllers/article.go b/internal/controllers/article.go new file mode 100644 index 0000000..4af39aa --- /dev/null +++ b/internal/controllers/article.go @@ -0,0 +1,138 @@ +package controllers + +import ( + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "github.com/pahmiudahgede/senggoldong/dto" + "github.com/pahmiudahgede/senggoldong/internal/services" + "github.com/pahmiudahgede/senggoldong/utils" +) + +func CreateArticle(c *fiber.Ctx) error { + var articleRequest dto.ArticleRequest + if err := c.BodyParser(&articleRequest); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( + fiber.StatusBadRequest, + "Invalid input", + nil, + )) + } + + validate := validator.New() + err := validate.Struct(articleRequest) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( + fiber.StatusBadRequest, + "Validation error", + err.Error(), + )) + } + + createdArticle, err := services.CreateArticle(&articleRequest) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + fiber.StatusInternalServerError, + "Failed to create article", + nil, + )) + } + + return c.Status(fiber.StatusCreated).JSON(utils.FormatResponse( + fiber.StatusCreated, + "Article created successfully", + createdArticle, + )) +} + +func GetArticles(c *fiber.Ctx) error { + articles, err := services.GetArticles() + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + fiber.StatusInternalServerError, + "Failed to fetch articles", + nil, + )) + } + + return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( + fiber.StatusOK, + "Articles fetched successfully", + articles, + )) +} + +func GetArticleByID(c *fiber.Ctx) error { + id := c.Params("id") + + article, err := services.GetArticleByID(id) + if err != nil { + return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse( + fiber.StatusNotFound, + "Article not found", + nil, + )) + } + + return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( + fiber.StatusOK, + "Article fetched successfully", + article, + )) +} + +func UpdateArticle(c *fiber.Ctx) error { + id := c.Params("id") + + var articleUpdateRequest dto.ArticleUpdateRequest + if err := c.BodyParser(&articleUpdateRequest); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( + fiber.StatusBadRequest, + "Invalid input", + nil, + )) + } + + validate := validator.New() + err := validate.Struct(articleUpdateRequest) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( + fiber.StatusBadRequest, + "Validation error", + err.Error(), + )) + } + + updatedArticle, err := services.UpdateArticle(id, &articleUpdateRequest) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + fiber.StatusInternalServerError, + "Failed to update article", + nil, + )) + } + + return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( + fiber.StatusOK, + "Article updated successfully", + updatedArticle, + )) +} + +func DeleteArticle(c *fiber.Ctx) error { + id := c.Params("id") + + err := services.DeleteArticle(id) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + fiber.StatusInternalServerError, + "Failed to delete article", + nil, + )) + } + + return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( + fiber.StatusOK, + "Article deleted successfully", + nil, + )) +} diff --git a/internal/repositories/article.go b/internal/repositories/article.go new file mode 100644 index 0000000..b19d253 --- /dev/null +++ b/internal/repositories/article.go @@ -0,0 +1,57 @@ +package repositories + +import ( + "github.com/pahmiudahgede/senggoldong/config" + "github.com/pahmiudahgede/senggoldong/domain" +) + +func CreateArticle(article *domain.Article) error { + err := config.DB.Create(article).Error + if err != nil { + return err + } + + return nil +} + +func GetArticles() ([]domain.Article, error) { + var articles []domain.Article + err := config.DB.Find(&articles).Error + if err != nil { + return nil, err + } + return articles, nil +} + +func GetArticleByID(id string) (domain.Article, error) { + var article domain.Article + err := config.DB.Where("id = ?", id).First(&article).Error + if err != nil { + return article, err + } + return article, nil +} + +func UpdateArticle(article *domain.Article) error { + + err := config.DB.Save(article).Error + if err != nil { + return err + } + return nil +} + +func DeleteArticle(id string) error { + + var article domain.Article + err := config.DB.Where("id = ?", id).First(&article).Error + if err != nil { + return err + } + + err = config.DB.Delete(&article).Error + if err != nil { + return err + } + return nil +} diff --git a/internal/repositories/role.go b/internal/repositories/role.go index 1c1553d..36fc401 100644 --- a/internal/repositories/role.go +++ b/internal/repositories/role.go @@ -11,7 +11,7 @@ func GetUserRoleByID(id string) (domain.UserRole, error) { var role domain.UserRole err := config.DB.Where("id = ?", id).First(&role).Error if err != nil { - return role, errors.New("UserRole tidak ditemukan") + return role, errors.New("userRole tidak ditemukan") } return role, nil } @@ -20,7 +20,7 @@ func GetAllUserRoles() ([]domain.UserRole, error) { var roles []domain.UserRole err := config.DB.Find(&roles).Error if err != nil { - return nil, errors.New("Gagal mengambil data UserRole") + return nil, errors.New("gagal mengambil data UserRole") } return roles, nil } diff --git a/internal/services/article.go b/internal/services/article.go new file mode 100644 index 0000000..22320a5 --- /dev/null +++ b/internal/services/article.go @@ -0,0 +1,115 @@ +package services + +import ( + "time" + + "github.com/pahmiudahgede/senggoldong/domain" + "github.com/pahmiudahgede/senggoldong/dto" + "github.com/pahmiudahgede/senggoldong/internal/repositories" +) + +func CreateArticle(articleRequest *dto.ArticleRequest) (*dto.ArticleResponse, error) { + article := domain.Article{ + Title: articleRequest.Title, + CoverImage: articleRequest.CoverImage, + Author: articleRequest.Author, + Heading: articleRequest.Heading, + Content: articleRequest.Content, + PublishedAt: articleRequest.PublishedAt, + UpdatedAt: articleRequest.PublishedAt, + } + + err := repositories.CreateArticle(&article) + if err != nil { + return nil, err + } + + return &dto.ArticleResponse{ + ID: article.ID, + Title: article.Title, + CoverImage: article.CoverImage, + Author: article.Author, + Heading: article.Heading, + Content: article.Content, + PublishedAt: article.PublishedAt, + UpdatedAt: article.UpdatedAt, + }, nil +} + +func GetArticles() ([]dto.ArticleResponse, error) { + articles, err := repositories.GetArticles() + if err != nil { + return nil, err + } + var response []dto.ArticleResponse + for _, article := range articles { + response = append(response, dto.ArticleResponse{ + ID: article.ID, + Title: article.Title, + CoverImage: article.CoverImage, + Author: article.Author, + Heading: article.Heading, + Content: article.Content, + PublishedAt: article.PublishedAt, + UpdatedAt: article.UpdatedAt, + }) + } + return response, nil +} + +func GetArticleByID(id string) (dto.ArticleResponse, error) { + article, err := repositories.GetArticleByID(id) + if err != nil { + return dto.ArticleResponse{}, err + } + return dto.ArticleResponse{ + ID: article.ID, + Title: article.Title, + CoverImage: article.CoverImage, + Author: article.Author, + Heading: article.Heading, + Content: article.Content, + PublishedAt: article.PublishedAt, + UpdatedAt: article.UpdatedAt, + }, nil +} + +func UpdateArticle(id string, articleUpdateRequest *dto.ArticleUpdateRequest) (*dto.ArticleResponse, error) { + + article, err := repositories.GetArticleByID(id) + if err != nil { + return nil, err + } + + article.Title = articleUpdateRequest.Title + article.CoverImage = articleUpdateRequest.CoverImage + article.Author = articleUpdateRequest.Author + article.Heading = articleUpdateRequest.Heading + article.Content = articleUpdateRequest.Content + article.UpdatedAt = time.Now() + + err = repositories.UpdateArticle(&article) + if err != nil { + return nil, err + } + + return &dto.ArticleResponse{ + ID: article.ID, + Title: article.Title, + CoverImage: article.CoverImage, + Author: article.Author, + Heading: article.Heading, + Content: article.Content, + PublishedAt: article.PublishedAt, + UpdatedAt: article.UpdatedAt, + }, nil +} + +func DeleteArticle(id string) error { + + err := repositories.DeleteArticle(id) + if err != nil { + return err + } + return nil +}