refact: optimize code

This commit is contained in:
pahmiudahgede 2025-02-12 19:14:05 +07:00
parent fc1298e894
commit 1f3c027cbd
10 changed files with 340 additions and 100 deletions

View File

@ -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}$`)

View File

@ -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")
}

View File

@ -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(&registerDTO); 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")
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -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
}