refact: optimize code
This commit is contained in:
parent
fc1298e894
commit
1f3c027cbd
|
@ -13,7 +13,7 @@ type LoginDTO struct {
|
|||
|
||||
type UserResponseWithToken struct {
|
||||
UserID string `json:"user_id"`
|
||||
RoleName string `json:"loginas"`
|
||||
RoleName string `json:"role_name"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
|
@ -49,39 +49,23 @@ func (l *LoginDTO) Validate() (map[string][]string, bool) {
|
|||
func (r *RegisterDTO) Validate() (map[string][]string, bool) {
|
||||
errors := make(map[string][]string)
|
||||
|
||||
if strings.TrimSpace(r.Username) == "" {
|
||||
errors["username"] = append(errors["username"], "Username is required")
|
||||
}
|
||||
if strings.TrimSpace(r.Name) == "" {
|
||||
errors["name"] = append(errors["name"], "Name is required")
|
||||
}
|
||||
r.validateRequiredFields(errors)
|
||||
|
||||
if strings.TrimSpace(r.Phone) == "" {
|
||||
errors["phone"] = append(errors["phone"], "Phone number is required")
|
||||
} else if !IsValidPhoneNumber(r.Phone) {
|
||||
if r.Phone != "" && !IsValidPhoneNumber(r.Phone) {
|
||||
errors["phone"] = append(errors["phone"], "Invalid phone number format. Use +62 followed by 9-13 digits")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(r.Email) == "" {
|
||||
errors["email"] = append(errors["email"], "Email is required")
|
||||
} else if !IsValidEmail(r.Email) {
|
||||
if r.Email != "" && !IsValidEmail(r.Email) {
|
||||
errors["email"] = append(errors["email"], "Invalid email format")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(r.Password) == "" {
|
||||
errors["password"] = append(errors["password"], "Password is required")
|
||||
} else if !IsValidPassword(r.Password) {
|
||||
if r.Password != "" && !IsValidPassword(r.Password) {
|
||||
errors["password"] = append(errors["password"], "Password must be at least 8 characters long and contain at least one number")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(r.ConfirmPassword) == "" {
|
||||
errors["confirm_password"] = append(errors["confirm_password"], "Confirm password is required")
|
||||
} else if r.Password != r.ConfirmPassword {
|
||||
if r.ConfirmPassword != "" && r.Password != r.ConfirmPassword {
|
||||
errors["confirm_password"] = append(errors["confirm_password"], "Password and confirm password do not match")
|
||||
}
|
||||
if strings.TrimSpace(r.RoleID) == "" {
|
||||
errors["roleId"] = append(errors["roleId"], "RoleID is required")
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return errors, false
|
||||
|
@ -90,6 +74,31 @@ func (r *RegisterDTO) Validate() (map[string][]string, bool) {
|
|||
return nil, true
|
||||
}
|
||||
|
||||
func (r *RegisterDTO) validateRequiredFields(errors map[string][]string) {
|
||||
|
||||
if strings.TrimSpace(r.Username) == "" {
|
||||
errors["username"] = append(errors["username"], "Username is required")
|
||||
}
|
||||
if strings.TrimSpace(r.Name) == "" {
|
||||
errors["name"] = append(errors["name"], "Name is required")
|
||||
}
|
||||
if strings.TrimSpace(r.Phone) == "" {
|
||||
errors["phone"] = append(errors["phone"], "Phone number is required")
|
||||
}
|
||||
if strings.TrimSpace(r.Email) == "" {
|
||||
errors["email"] = append(errors["email"], "Email is required")
|
||||
}
|
||||
if strings.TrimSpace(r.Password) == "" {
|
||||
errors["password"] = append(errors["password"], "Password is required")
|
||||
}
|
||||
if strings.TrimSpace(r.ConfirmPassword) == "" {
|
||||
errors["confirm_password"] = append(errors["confirm_password"], "Confirm password is required")
|
||||
}
|
||||
if strings.TrimSpace(r.RoleID) == "" {
|
||||
errors["roleId"] = append(errors["roleId"], "RoleID is required")
|
||||
}
|
||||
}
|
||||
|
||||
func IsValidPhoneNumber(phone string) bool {
|
||||
|
||||
re := regexp.MustCompile(`^\+62\d{9,13}$`)
|
||||
|
|
|
@ -18,8 +18,8 @@ func NewArticleHandler(articleService services.ArticleService) *ArticleHandler {
|
|||
}
|
||||
|
||||
func (h *ArticleHandler) CreateArticle(c *fiber.Ctx) error {
|
||||
|
||||
var request dto.RequestArticleDTO
|
||||
|
||||
if err := c.BodyParser(&request); err != nil {
|
||||
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
|
||||
}
|
||||
|
@ -45,29 +45,37 @@ func (h *ArticleHandler) CreateArticle(c *fiber.Ctx) error {
|
|||
func (h *ArticleHandler) GetAllArticles(c *fiber.Ctx) error {
|
||||
|
||||
page, err := strconv.Atoi(c.Query("page", "0"))
|
||||
if err != nil {
|
||||
if err != nil || page < 1 {
|
||||
page = 0
|
||||
}
|
||||
|
||||
limit, err := strconv.Atoi(c.Query("limit", "0"))
|
||||
if err != nil {
|
||||
if err != nil || limit < 1 {
|
||||
limit = 0
|
||||
}
|
||||
|
||||
article, totalArticle, err := h.ArticleService.GetAllArticles(page, limit)
|
||||
var articles []dto.ArticleResponseDTO
|
||||
var totalArticles int
|
||||
|
||||
if page == 0 && limit == 0 {
|
||||
|
||||
articles, totalArticles, err = h.ArticleService.GetAllArticles(0, 0)
|
||||
if err != nil {
|
||||
return utils.GenericErrorResponse(c, fiber.StatusInternalServerError, "Failed to fetch articles")
|
||||
}
|
||||
|
||||
return utils.NonPaginatedResponse(c, articles, totalArticles, "Articles fetched successfully")
|
||||
}
|
||||
|
||||
articles, totalArticles, err = h.ArticleService.GetAllArticles(page, limit)
|
||||
if err != nil {
|
||||
return utils.GenericErrorResponse(c, fiber.StatusInternalServerError, "Failed to fetch article")
|
||||
return utils.GenericErrorResponse(c, fiber.StatusInternalServerError, "Failed to fetch articles")
|
||||
}
|
||||
|
||||
if page > 0 && limit > 0 {
|
||||
return utils.PaginatedResponse(c, article, page, limit, totalArticle, "Article fetched successfully")
|
||||
}
|
||||
|
||||
return utils.NonPaginatedResponse(c, article, totalArticle, "Article fetched successfully")
|
||||
|
||||
return utils.PaginatedResponse(c, articles, page, limit, totalArticles, "Articles fetched successfully")
|
||||
}
|
||||
|
||||
func (h *ArticleHandler) GetArticleByID(c *fiber.Ctx) error {
|
||||
|
||||
id := c.Params("article_id")
|
||||
if id == "" {
|
||||
return utils.GenericErrorResponse(c, fiber.StatusBadRequest, "Article ID is required")
|
||||
|
@ -80,3 +88,33 @@ func (h *ArticleHandler) GetArticleByID(c *fiber.Ctx) error {
|
|||
|
||||
return utils.SuccessResponse(c, article, "Article fetched successfully")
|
||||
}
|
||||
|
||||
func (h *ArticleHandler) UpdateArticle(c *fiber.Ctx) error {
|
||||
id := c.Params("article_id")
|
||||
if id == "" {
|
||||
return utils.GenericErrorResponse(c, fiber.StatusBadRequest, "Article ID is required")
|
||||
}
|
||||
|
||||
var request dto.RequestArticleDTO
|
||||
|
||||
if err := c.BodyParser(&request); err != nil {
|
||||
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
|
||||
}
|
||||
|
||||
errors, valid := request.Validate()
|
||||
if !valid {
|
||||
return utils.ValidationErrorResponse(c, errors)
|
||||
}
|
||||
|
||||
coverImage, err := c.FormFile("coverImage")
|
||||
if err != nil && err.Error() != "no such file" {
|
||||
return utils.GenericErrorResponse(c, fiber.StatusBadRequest, "Cover image is required")
|
||||
}
|
||||
|
||||
articleResponse, err := h.ArticleService.UpdateArticle(id, request, coverImage)
|
||||
if err != nil {
|
||||
return utils.GenericErrorResponse(c, fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return utils.SuccessResponse(c, articleResponse, "Article updated successfully")
|
||||
}
|
||||
|
|
|
@ -37,9 +37,10 @@ func (h *UserHandler) Login(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
func (h *UserHandler) Register(c *fiber.Ctx) error {
|
||||
|
||||
var registerDTO dto.RegisterDTO
|
||||
if err := c.BodyParser(®isterDTO); err != nil {
|
||||
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
|
||||
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid request body"}})
|
||||
}
|
||||
|
||||
errors, valid := registerDTO.Validate()
|
||||
|
@ -47,26 +48,11 @@ func (h *UserHandler) Register(c *fiber.Ctx) error {
|
|||
return utils.ValidationErrorResponse(c, errors)
|
||||
}
|
||||
|
||||
user, err := h.UserService.Register(registerDTO)
|
||||
userResponse, err := h.UserService.Register(registerDTO)
|
||||
if err != nil {
|
||||
return utils.GenericErrorResponse(c, fiber.StatusConflict, err.Error())
|
||||
}
|
||||
|
||||
createdAt, _ := utils.FormatDateToIndonesianFormat(user.CreatedAt)
|
||||
updatedAt, _ := utils.FormatDateToIndonesianFormat(user.UpdatedAt)
|
||||
|
||||
userResponse := dto.UserResponseDTO{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Name: user.Name,
|
||||
Phone: user.Phone,
|
||||
Email: user.Email,
|
||||
EmailVerified: user.EmailVerified,
|
||||
RoleName: user.Role.RoleName,
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
}
|
||||
|
||||
return utils.CreateResponse(c, userResponse, "Registration successful")
|
||||
}
|
||||
|
||||
|
@ -82,5 +68,5 @@ func (h *UserHandler) Logout(c *fiber.Ctx) error {
|
|||
return utils.InternalServerErrorResponse(c, "Error logging out")
|
||||
}
|
||||
|
||||
return utils.NonPaginatedResponse(c, nil, 0, "Logout successful")
|
||||
return utils.SuccessResponse(c, nil, "Logout successful")
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ type ArticleRepository interface {
|
|||
CreateArticle(article *model.Article) error
|
||||
FindArticleByID(id string) (*model.Article, error)
|
||||
FindAllArticles(page, limit int) ([]model.Article, int, error)
|
||||
UpdateArticle(id string, article *model.Article) error
|
||||
}
|
||||
|
||||
type articleRepository struct {
|
||||
|
@ -20,11 +21,7 @@ func NewArticleRepository(db *gorm.DB) ArticleRepository {
|
|||
}
|
||||
|
||||
func (r *articleRepository) CreateArticle(article *model.Article) error {
|
||||
err := r.DB.Create(article).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return r.DB.Create(article).Error
|
||||
}
|
||||
|
||||
func (r *articleRepository) FindArticleByID(id string) (*model.Article, error) {
|
||||
|
@ -51,6 +48,7 @@ func (r *articleRepository) FindAllArticles(page, limit int) ([]model.Article, i
|
|||
return nil, 0, err
|
||||
}
|
||||
} else {
|
||||
|
||||
err := r.DB.Find(&articles).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
@ -59,3 +57,7 @@ func (r *articleRepository) FindAllArticles(page, limit int) ([]model.Article, i
|
|||
|
||||
return articles, int(total), nil
|
||||
}
|
||||
|
||||
func (r *articleRepository) UpdateArticle(id string, article *model.Article) error {
|
||||
return r.DB.Save(article).Error
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package repositories
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pahmiudahgede/senggoldong/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
@ -29,6 +31,9 @@ func (r *userRepository) FindByIdentifierAndRole(identifier, roleID string) (*mo
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user.Role == nil {
|
||||
return nil, fmt.Errorf("role not found for this user")
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ type ArticleService interface {
|
|||
CreateArticle(request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error)
|
||||
GetAllArticles(page, limit int) ([]dto.ArticleResponseDTO, int, error)
|
||||
GetArticleByID(id string) (*dto.ArticleResponseDTO, error)
|
||||
UpdateArticle(id string, request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error)
|
||||
}
|
||||
|
||||
type articleService struct {
|
||||
|
@ -31,11 +32,8 @@ func NewArticleService(articleRepo repositories.ArticleRepository) ArticleServic
|
|||
func (s *articleService) CreateArticle(request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error) {
|
||||
|
||||
coverImageDir := "./public/uploads/articles"
|
||||
if _, err := os.Stat(coverImageDir); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(coverImageDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create directory for cover image: %v", err)
|
||||
}
|
||||
if err := os.MkdirAll(coverImageDir, os.ModePerm); err != nil {
|
||||
return nil, fmt.Errorf("failed to create directory for cover image: %v", err)
|
||||
}
|
||||
|
||||
extension := filepath.Ext(coverImage.Filename)
|
||||
|
@ -58,8 +56,7 @@ func (s *articleService) CreateArticle(request dto.RequestArticleDTO, coverImage
|
|||
}
|
||||
defer dst.Close()
|
||||
|
||||
_, err = dst.ReadFrom(src)
|
||||
if err != nil {
|
||||
if _, err := dst.ReadFrom(src); err != nil {
|
||||
return nil, fmt.Errorf("failed to save cover image: %v", err)
|
||||
}
|
||||
|
||||
|
@ -71,8 +68,7 @@ func (s *articleService) CreateArticle(request dto.RequestArticleDTO, coverImage
|
|||
Content: request.Content,
|
||||
}
|
||||
|
||||
err = s.ArticleRepo.CreateArticle(&article)
|
||||
if err != nil {
|
||||
if err := s.ArticleRepo.CreateArticle(&article); err != nil {
|
||||
return nil, fmt.Errorf("failed to create article: %v", err)
|
||||
}
|
||||
|
||||
|
@ -94,11 +90,49 @@ func (s *articleService) CreateArticle(request dto.RequestArticleDTO, coverImage
|
|||
cacheData := map[string]interface{}{
|
||||
"data": articleResponseDTO,
|
||||
}
|
||||
err = utils.SetJSONData(cacheKey, cacheData, time.Hour*24)
|
||||
if err != nil {
|
||||
|
||||
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
|
||||
|
||||
fmt.Printf("Error caching article to Redis: %v\n", err)
|
||||
}
|
||||
|
||||
articlesCacheKey := "articles:all"
|
||||
if err := utils.DeleteData(articlesCacheKey); err != nil {
|
||||
|
||||
fmt.Printf("Error deleting articles cache: %v\n", err)
|
||||
}
|
||||
|
||||
articles, _, err := s.ArticleRepo.FindAllArticles(0, 0)
|
||||
if err == nil {
|
||||
var articleDTOs []dto.ArticleResponseDTO
|
||||
for _, a := range articles {
|
||||
createdAt, _ := utils.FormatDateToIndonesianFormat(a.PublishedAt)
|
||||
updatedAt, _ := utils.FormatDateToIndonesianFormat(a.UpdatedAt)
|
||||
|
||||
articleDTOs = append(articleDTOs, dto.ArticleResponseDTO{
|
||||
ID: a.ID,
|
||||
Title: a.Title,
|
||||
CoverImage: a.CoverImage,
|
||||
Author: a.Author,
|
||||
Heading: a.Heading,
|
||||
Content: a.Content,
|
||||
PublishedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
cacheData = map[string]interface{}{
|
||||
"data": articleDTOs,
|
||||
}
|
||||
if err := utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24); err != nil {
|
||||
|
||||
fmt.Printf("Error caching all articles to Redis: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
|
||||
fmt.Printf("Error fetching all articles: %v\n", err)
|
||||
}
|
||||
|
||||
return articleResponseDTO, nil
|
||||
}
|
||||
|
||||
|
@ -109,10 +143,12 @@ func (s *articleService) GetAllArticles(page, limit int) ([]dto.ArticleResponseD
|
|||
cachedData, err := utils.GetJSONData(cacheKey)
|
||||
if err == nil && cachedData != nil {
|
||||
var articles []dto.ArticleResponseDTO
|
||||
|
||||
if data, ok := cachedData["data"].([]interface{}); ok {
|
||||
for _, item := range data {
|
||||
articleData, ok := item.(map[string]interface{})
|
||||
if ok {
|
||||
|
||||
articles = append(articles, dto.ArticleResponseDTO{
|
||||
ID: articleData["article_id"].(string),
|
||||
Title: articleData["title"].(string),
|
||||
|
@ -125,7 +161,12 @@ func (s *articleService) GetAllArticles(page, limit int) ([]dto.ArticleResponseD
|
|||
})
|
||||
}
|
||||
}
|
||||
return articles, len(articles), nil
|
||||
|
||||
total, ok := cachedData["total"].(float64)
|
||||
if !ok {
|
||||
return nil, 0, fmt.Errorf("invalid total count in cache")
|
||||
}
|
||||
return articles, int(total), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,7 +193,8 @@ func (s *articleService) GetAllArticles(page, limit int) ([]dto.ArticleResponseD
|
|||
}
|
||||
|
||||
cacheData := map[string]interface{}{
|
||||
"data": articleDTOs,
|
||||
"data": articleDTOs,
|
||||
"total": total,
|
||||
}
|
||||
err = utils.SetJSONData(cacheKey, cacheData, time.Hour*24)
|
||||
if err != nil {
|
||||
|
@ -169,6 +211,7 @@ func (s *articleService) GetArticleByID(id string) (*dto.ArticleResponseDTO, err
|
|||
if err == nil && cachedData != nil {
|
||||
articleData, ok := cachedData["data"].(map[string]interface{})
|
||||
if ok {
|
||||
|
||||
article := dto.ArticleResponseDTO{
|
||||
ID: articleData["article_id"].(string),
|
||||
Title: articleData["title"].(string),
|
||||
|
@ -207,8 +250,135 @@ func (s *articleService) GetArticleByID(id string) (*dto.ArticleResponseDTO, err
|
|||
}
|
||||
err = utils.SetJSONData(cacheKey, cacheData, time.Hour*24)
|
||||
if err != nil {
|
||||
|
||||
fmt.Printf("Error caching article to Redis: %v\n", err)
|
||||
}
|
||||
|
||||
return articleResponseDTO, nil
|
||||
}
|
||||
|
||||
func (s *articleService) UpdateArticle(id string, request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error) {
|
||||
article, err := s.ArticleRepo.FindArticleByID(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("article not found: %v", err)
|
||||
}
|
||||
|
||||
article.Title = request.Title
|
||||
article.Heading = request.Heading
|
||||
article.Content = request.Content
|
||||
article.Author = request.Author
|
||||
|
||||
var coverImagePath string
|
||||
if coverImage != nil {
|
||||
coverImageDir := "./public/uploads/articles"
|
||||
if _, err := os.Stat(coverImageDir); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(coverImageDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create directory for cover image: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
extension := filepath.Ext(coverImage.Filename)
|
||||
if extension != ".jpg" && extension != ".jpeg" && extension != ".png" {
|
||||
return nil, fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed")
|
||||
}
|
||||
|
||||
coverImageFileName := fmt.Sprintf("%s_cover%s", uuid.New().String(), extension)
|
||||
coverImagePath = filepath.Join(coverImageDir, coverImageFileName)
|
||||
|
||||
src, err := coverImage.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open uploaded file: %v", err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.Create(coverImagePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create cover image file: %v", err)
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
_, err = dst.ReadFrom(src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to save cover image: %v", err)
|
||||
}
|
||||
|
||||
article.CoverImage = coverImagePath
|
||||
}
|
||||
|
||||
err = s.ArticleRepo.UpdateArticle(id, article)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update article: %v", err)
|
||||
}
|
||||
|
||||
updatedArticle, err := s.ArticleRepo.FindArticleByID(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch updated article: %v", err)
|
||||
}
|
||||
|
||||
createdAt, _ := utils.FormatDateToIndonesianFormat(updatedArticle.PublishedAt)
|
||||
updatedAt, _ := utils.FormatDateToIndonesianFormat(updatedArticle.UpdatedAt)
|
||||
|
||||
articleResponseDTO := &dto.ArticleResponseDTO{
|
||||
ID: updatedArticle.ID,
|
||||
Title: updatedArticle.Title,
|
||||
CoverImage: updatedArticle.CoverImage,
|
||||
Author: updatedArticle.Author,
|
||||
Heading: updatedArticle.Heading,
|
||||
Content: updatedArticle.Content,
|
||||
PublishedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
}
|
||||
|
||||
articleCacheKey := fmt.Sprintf("article:%s", updatedArticle.ID)
|
||||
err = utils.DeleteData(articleCacheKey)
|
||||
if err != nil {
|
||||
fmt.Printf("Error deleting old cache for article: %v\n", err)
|
||||
}
|
||||
|
||||
cacheData := map[string]interface{}{
|
||||
"data": articleResponseDTO,
|
||||
}
|
||||
err = utils.SetJSONData(articleCacheKey, cacheData, time.Hour*24)
|
||||
if err != nil {
|
||||
fmt.Printf("Error caching updated article to Redis: %v\n", err)
|
||||
}
|
||||
|
||||
articlesCacheKey := "articles:all"
|
||||
err = utils.DeleteData(articlesCacheKey)
|
||||
if err != nil {
|
||||
fmt.Printf("Error deleting articles cache: %v\n", err)
|
||||
}
|
||||
|
||||
articles, _, err := s.ArticleRepo.FindAllArticles(0, 0)
|
||||
if err != nil {
|
||||
fmt.Printf("Error fetching all articles: %v\n", err)
|
||||
} else {
|
||||
var articleDTOs []dto.ArticleResponseDTO
|
||||
for _, a := range articles {
|
||||
createdAt, _ := utils.FormatDateToIndonesianFormat(a.PublishedAt)
|
||||
updatedAt, _ := utils.FormatDateToIndonesianFormat(a.UpdatedAt)
|
||||
|
||||
articleDTOs = append(articleDTOs, dto.ArticleResponseDTO{
|
||||
ID: a.ID,
|
||||
Title: a.Title,
|
||||
CoverImage: a.CoverImage,
|
||||
Author: a.Author,
|
||||
Heading: a.Heading,
|
||||
Content: a.Content,
|
||||
PublishedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
cacheData = map[string]interface{}{
|
||||
"data": articleDTOs,
|
||||
}
|
||||
err = utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24)
|
||||
if err != nil {
|
||||
fmt.Printf("Error caching updated articles to Redis: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
return articleResponseDTO, nil
|
||||
}
|
||||
|
|
|
@ -13,9 +13,22 @@ import (
|
|||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrUsernameTaken = "username is already taken"
|
||||
ErrPhoneTaken = "phone number is already used for this role"
|
||||
ErrEmailTaken = "email is already used for this role"
|
||||
ErrInvalidRoleID = "invalid roleId"
|
||||
ErrPasswordMismatch = "password and confirm password do not match"
|
||||
ErrRoleIDRequired = "roleId is required"
|
||||
ErrFailedToHashPassword = "failed to hash password"
|
||||
ErrFailedToCreateUser = "failed to create user"
|
||||
ErrIncorrectPassword = "incorrect password"
|
||||
ErrAccountNotFound = "account not found"
|
||||
)
|
||||
|
||||
type UserService interface {
|
||||
Login(credentials dto.LoginDTO) (*dto.UserResponseWithToken, error)
|
||||
Register(user dto.RegisterDTO) (*model.User, error)
|
||||
Register(user dto.RegisterDTO) (*dto.UserResponseDTO, error)
|
||||
}
|
||||
|
||||
type userService struct {
|
||||
|
@ -29,18 +42,17 @@ func NewUserService(userRepo repositories.UserRepository, roleRepo repositories.
|
|||
}
|
||||
|
||||
func (s *userService) Login(credentials dto.LoginDTO) (*dto.UserResponseWithToken, error) {
|
||||
|
||||
if credentials.RoleID == "" {
|
||||
return nil, errors.New("roleId is required")
|
||||
return nil, errors.New(ErrRoleIDRequired)
|
||||
}
|
||||
|
||||
user, err := s.UserRepo.FindByIdentifierAndRole(credentials.Identifier, credentials.RoleID)
|
||||
if err != nil {
|
||||
return nil, errors.New("akun dengan role tersebut belum terdaftar")
|
||||
return nil, errors.New(ErrAccountNotFound)
|
||||
}
|
||||
|
||||
if !CheckPasswordHash(credentials.Password, user.Password) {
|
||||
return nil, errors.New("password yang anda masukkan salah")
|
||||
return nil, errors.New(ErrIncorrectPassword)
|
||||
}
|
||||
|
||||
token, err := s.generateJWT(user)
|
||||
|
@ -89,39 +101,36 @@ func CheckPasswordHash(password, hashedPassword string) bool {
|
|||
return err == nil
|
||||
}
|
||||
|
||||
func (s *userService) Register(user dto.RegisterDTO) (*model.User, error) {
|
||||
func (s *userService) Register(user dto.RegisterDTO) (*dto.UserResponseDTO, error) {
|
||||
|
||||
if user.Password != user.ConfirmPassword {
|
||||
return nil, fmt.Errorf("password and confirm password do not match")
|
||||
return nil, fmt.Errorf("%s: %v", ErrPasswordMismatch, nil)
|
||||
}
|
||||
|
||||
if user.RoleID == "" {
|
||||
return nil, fmt.Errorf("roleId is required")
|
||||
return nil, fmt.Errorf("%s: %v", ErrRoleIDRequired, nil)
|
||||
}
|
||||
|
||||
role, err := s.RoleRepo.FindByID(user.RoleID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid roleId")
|
||||
return nil, fmt.Errorf("%s: %v", ErrInvalidRoleID, err)
|
||||
}
|
||||
|
||||
existingUser, _ := s.UserRepo.FindByUsername(user.Username)
|
||||
if existingUser != nil {
|
||||
return nil, fmt.Errorf("username is already taken")
|
||||
if existingUser, _ := s.UserRepo.FindByUsername(user.Username); existingUser != nil {
|
||||
return nil, fmt.Errorf("%s: %v", ErrUsernameTaken, nil)
|
||||
}
|
||||
|
||||
existingPhone, _ := s.UserRepo.FindByPhoneAndRole(user.Phone, user.RoleID)
|
||||
if existingPhone != nil {
|
||||
return nil, fmt.Errorf("phone number is already used for this role")
|
||||
if existingPhone, _ := s.UserRepo.FindByPhoneAndRole(user.Phone, user.RoleID); existingPhone != nil {
|
||||
return nil, fmt.Errorf("%s: %v", ErrPhoneTaken, nil)
|
||||
}
|
||||
|
||||
existingEmail, _ := s.UserRepo.FindByEmailAndRole(user.Email, user.RoleID)
|
||||
if existingEmail != nil {
|
||||
return nil, fmt.Errorf("email is already used for this role")
|
||||
if existingEmail, _ := s.UserRepo.FindByEmailAndRole(user.Email, user.RoleID); existingEmail != nil {
|
||||
return nil, fmt.Errorf("%s: %v", ErrEmailTaken, nil)
|
||||
}
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to hash password: %v", err)
|
||||
return nil, fmt.Errorf("%s: %v", ErrFailedToHashPassword, err)
|
||||
}
|
||||
|
||||
newUser := model.User{
|
||||
|
@ -135,10 +144,28 @@ func (s *userService) Register(user dto.RegisterDTO) (*model.User, error) {
|
|||
|
||||
err = s.UserRepo.Create(&newUser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create user: %v", err)
|
||||
return nil, fmt.Errorf("%s: %v", ErrFailedToCreateUser, err)
|
||||
}
|
||||
|
||||
newUser.Role = *role
|
||||
userResponse := s.prepareUserResponse(newUser, role)
|
||||
|
||||
return &newUser, nil
|
||||
return userResponse, nil
|
||||
}
|
||||
|
||||
func (s *userService) prepareUserResponse(user model.User, role *model.Role) *dto.UserResponseDTO {
|
||||
|
||||
createdAt, _ := utils.FormatDateToIndonesianFormat(user.CreatedAt)
|
||||
updatedAt, _ := utils.FormatDateToIndonesianFormat(user.UpdatedAt)
|
||||
|
||||
return &dto.UserResponseDTO{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Name: user.Name,
|
||||
Phone: user.Phone,
|
||||
Email: user.Email,
|
||||
EmailVerified: user.EmailVerified,
|
||||
RoleName: role.RoleName,
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ type User struct {
|
|||
EmailVerified bool `gorm:"default:false" json:"emailVerified"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
RoleID string `gorm:"not null" json:"roleId"`
|
||||
Role Role `gorm:"foreignKey:RoleID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"role"`
|
||||
Role *Role `gorm:"foreignKey:RoleID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"role"`
|
||||
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
|
||||
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ func ArticleRouter(api fiber.Router) {
|
|||
articleAPI := api.Group("/article-rijik")
|
||||
|
||||
articleAPI.Post("/create-article", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator), articleHandler.CreateArticle)
|
||||
articleAPI.Get("/view-article", middleware.AuthMiddleware, articleHandler.GetAllArticles)
|
||||
articleAPI.Get("/view-article/:article_id", middleware.AuthMiddleware, articleHandler.GetArticleByID)
|
||||
articleAPI.Get("/view-article", articleHandler.GetAllArticles)
|
||||
articleAPI.Get("/view-article/:article_id", articleHandler.GetArticleByID)
|
||||
articleAPI.Put("/update-article/:article_id", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator), articleHandler.UpdateArticle)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package utils
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
|
@ -15,6 +16,7 @@ var ctx = context.Background()
|
|||
const defaultExpiration = 1 * time.Hour
|
||||
|
||||
func SetData[T any](key string, value T, expiration time.Duration) error {
|
||||
|
||||
if expiration == 0 {
|
||||
expiration = defaultExpiration
|
||||
}
|
||||
|
@ -26,7 +28,7 @@ func SetData[T any](key string, value T, expiration time.Duration) error {
|
|||
|
||||
err = config.RedisClient.Set(ctx, key, jsonData, expiration).Err()
|
||||
if err != nil {
|
||||
return logAndReturnError("Error setting data in Redis", err)
|
||||
return logAndReturnError(fmt.Sprintf("Error setting data in Redis with key: %s", key), err)
|
||||
}
|
||||
|
||||
log.Printf("Data stored in Redis with key: %s", key)
|
||||
|
@ -40,7 +42,7 @@ func GetData(key string) (string, error) {
|
|||
return "", nil
|
||||
} else if err != nil {
|
||||
|
||||
return "", logAndReturnError("Error retrieving data from Redis", err)
|
||||
return "", logAndReturnError(fmt.Sprintf("Error retrieving data from Redis with key: %s", key), err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
@ -48,7 +50,7 @@ func GetData(key string) (string, error) {
|
|||
func DeleteData(key string) error {
|
||||
err := config.RedisClient.Del(ctx, key).Err()
|
||||
if err != nil {
|
||||
return logAndReturnError("Error deleting data from Redis", err)
|
||||
return logAndReturnError(fmt.Sprintf("Error deleting data from Redis with key: %s", key), err)
|
||||
}
|
||||
log.Printf("Data deleted from Redis with key: %s", key)
|
||||
return nil
|
||||
|
@ -57,7 +59,7 @@ func DeleteData(key string) error {
|
|||
func CheckKeyExists(key string) (bool, error) {
|
||||
val, err := config.RedisClient.Exists(ctx, key).Result()
|
||||
if err != nil {
|
||||
return false, logAndReturnError("Error checking if key exists in Redis", err)
|
||||
return false, logAndReturnError(fmt.Sprintf("Error checking if key exists in Redis with key: %s", key), err)
|
||||
}
|
||||
return val > 0, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue