feat(permissions): Implement permissions management with CRUD operations and routing

This commit is contained in:
Habib Fatkhul Rohman 2025-10-28 21:26:58 +07:00
parent 2020a906fc
commit 62a188fcc6
8 changed files with 472 additions and 0 deletions

View File

@ -11,6 +11,7 @@ import (
"github.com/Caknoooo/go-gin-clean-starter/modules/client" "github.com/Caknoooo/go-gin-clean-starter/modules/client"
maintenancegroup "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group" maintenancegroup "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group"
"github.com/Caknoooo/go-gin-clean-starter/modules/menu" "github.com/Caknoooo/go-gin-clean-starter/modules/menu"
"github.com/Caknoooo/go-gin-clean-starter/modules/permissions"
"github.com/Caknoooo/go-gin-clean-starter/modules/role" "github.com/Caknoooo/go-gin-clean-starter/modules/role"
swaggerFiles "github.com/swaggo/files" swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger" ginSwagger "github.com/swaggo/gin-swagger"
@ -129,6 +130,7 @@ func main() {
menu.RegisterRoutes(server, injector) menu.RegisterRoutes(server, injector)
maintenancegroup.RegisterRoutes(server, injector) maintenancegroup.RegisterRoutes(server, injector)
client.RegisterRoutes(server, injector) client.RegisterRoutes(server, injector)
permissions.RegisterRoutes(server, injector)
// register swagger route // register swagger route
server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

View File

@ -0,0 +1,133 @@
package controller
import (
"net/http"
"github.com/Caknoooo/go-gin-clean-starter/modules/permissions/dto"
"github.com/Caknoooo/go-gin-clean-starter/modules/permissions/service"
"github.com/Caknoooo/go-gin-clean-starter/pkg/utils"
"github.com/gin-gonic/gin"
)
type PermissionsController interface {
CreatePermission(ctx *gin.Context)
GetPermissionByID(ctx *gin.Context)
GetPermissionByName(ctx *gin.Context)
GetAllPermissions(ctx *gin.Context)
UpdatePermission(ctx *gin.Context)
DeletePermission(ctx *gin.Context)
}
type permissionsController struct {
service service.PermissionsService
}
func (c *permissionsController) CreatePermission(ctx *gin.Context) {
var req dto.PermissionsCreateRequest
if err := ctx.ShouldBind(&req); err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil)
ctx.AbortWithStatusJSON(http.StatusBadRequest, res)
return
}
res, err := c.service.Create(ctx.Request.Context(), req)
if err != nil {
resp := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_PERMISSIONS, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, resp)
return
}
resp := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_CREATE_PERMISSIONS, res)
ctx.JSON(http.StatusOK, resp)
}
func (c *permissionsController) GetPermissionByID(ctx *gin.Context) {
id := ctx.Param("id")
if id == "" {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_PERMISSIONS, "id is required", nil)
ctx.JSON(http.StatusBadRequest, res)
return
}
res, err := c.service.GetById(ctx.Request.Context(), id)
if err != nil {
resErr := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_PERMISSIONS, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, resErr)
return
}
resp := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_PERMISSIONS, res)
ctx.JSON(http.StatusOK, resp)
}
func (c *permissionsController) GetPermissionByName(ctx *gin.Context) {
name := ctx.Query("name")
if name == "" {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_PERMISSIONS, "name query is required", nil)
ctx.JSON(http.StatusBadRequest, res)
return
}
res, err := c.service.GetByName(ctx.Request.Context(), name)
if err != nil {
resErr := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_PERMISSIONS, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, resErr)
return
}
resp := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_PERMISSIONS, res)
ctx.JSON(http.StatusOK, resp)
}
func (c *permissionsController) GetAllPermissions(ctx *gin.Context) {
res, err := c.service.GetAll(ctx.Request.Context())
if err != nil {
resp := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_PERMISSIONS, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, resp)
return
}
resp := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_PERMISSIONS, res)
ctx.JSON(http.StatusOK, resp)
}
func (c *permissionsController) UpdatePermission(ctx *gin.Context) {
var req dto.PermissionsUpdateRequest
if err := ctx.ShouldBind(&req); err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil)
ctx.AbortWithStatusJSON(http.StatusBadRequest, res)
return
}
id := ctx.Param("id")
result, err := c.service.Update(ctx.Request.Context(), id, req)
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_PERMISSIONS, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, res)
return
}
resp := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_PERMISSIONS, result)
ctx.JSON(http.StatusOK, resp)
}
func (c *permissionsController) DeletePermission(ctx *gin.Context) {
id := ctx.Param("id")
if id == "" {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_PERMISSIONS, "id is required", nil)
ctx.JSON(http.StatusBadRequest, res)
return
}
if err := c.service.Delete(ctx.Request.Context(), id); err != nil {
resp := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_PERMISSIONS, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, resp)
return
}
resp := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_PERMISSIONS, nil)
ctx.JSON(http.StatusOK, resp)
}
func NewPermissionsController(s service.PermissionsService) PermissionsController {
return &permissionsController{
service: s,
}
}
// func NewPermissionsController(i *do.Injector, s service.PermissionsService) PermissionsController {
// db := do.MustInvokeNamed[*gorm.DB](i, constants.DB)
// return &permissionsController{
// service: s,
// db: db,
// }
// }

View File

@ -0,0 +1,48 @@
package dto
import (
"errors"
)
const (
MESSAGE_FAILED_CREATE_PERMISSIONS = "failed create permissions"
MESSAGE_SUCCESS_CREATE_PERMISSIONS = "success create permissions"
MESSAGE_FAILED_GET_PERMISSIONS = "failed get permissions"
MESSAGE_SUCCESS_GET_PERMISSIONS = "success get permissions"
MESSAGE_FAILED_UPDATE_PERMISSIONS = "failed update permissions"
MESSAGE_FAILED_PROSES_REQUEST = "failed proses request"
MESSAGE_SUCCESS_UPDATE_PERMISSIONS = "success update permissions"
MESSAGE_FAILED_DELETE_PERMISSIONS = "failed delete permissions"
MESSAGE_SUCCESS_DELETE_PERMISSIONS = "success delete permissions"
MESSAGE_FAILED_GET_DATA_FROM_BODY = "failed get data from body"
)
var (
ErrCreatePermissions = errors.New("failed to create permissions")
ErrGetPermissionsById = errors.New("failed to get permissions by id")
ErrUpdatePermissions = errors.New("failed to update permissions")
ErrDeletePermissions = errors.New("failed to delete permissions")
)
type (
PermissionsCreateRequest struct {
Name string `json:"name" binding:"required"`
MenuID string `json:"menu_id" binding:"required,uuid"`
}
PermissionsUpdateRequest struct {
Name string `json:"name" binding:"omitempty"`
MenuID string `json:"menu_id" binding:"omitempty,uuid"`
}
PermissionsResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Menu Menu `json:"menu"`
}
Menu struct {
ID string `json:"id"`
Name string `json:"name"`
}
)

View File

@ -0,0 +1,49 @@
package query
import (
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
"github.com/Caknoooo/go-gin-clean-starter/modules/permissions/dto"
"github.com/Caknoooo/go-pagination"
"github.com/google/uuid"
"gorm.io/gorm"
)
type M_Permissions struct {
ID uuid.UUID `json:"id" gorm:"column:id"`
Name string `json:"name" gorm:"column:name"`
MenuID uuid.UUID `json:"menu_id" gorm:"column:menu_id"`
Menu dto.Menu `json:"menu" gorm:"foreignKey:MenuID;references:ID"`
}
type PermissionsFilter struct {
pagination.BaseFilter
Name string `form:"name"`
MenuID string `form:"menu_id"`
}
func (f *PermissionsFilter) ApplyFilters(q *gorm.DB) *gorm.DB {
if f.Name != "" {
q = q.Where("name ILIKE ?", "%"+f.Name+"%")
}
if f.MenuID != "" {
q = q.Where("menu_id = ?", f.MenuID)
}
q = q.Model(entities.M_Permissions{}).Preload("Menu")
return q
}
func (f *PermissionsFilter) GetTableName() string {
return "m_permissions"
}
func (f *PermissionsFilter) GetSearchFields() []string {
return []string{"name"}
}
func (f *PermissionsFilter) GetDefaultSort() string {
return "id asc"
}
func (f *PermissionsFilter) GetPagination() pagination.PaginationRequest {
return f.Pagination
}

View File

@ -0,0 +1,90 @@
package repository
import (
"context"
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
"gorm.io/gorm"
)
type (
PermissionsRepository interface {
Create(ctx context.Context, tx *gorm.DB, permission entities.M_Permissions) (entities.M_Permissions, error)
GetById(ctx context.Context, tx *gorm.DB, id string) (entities.M_Permissions, error)
GetByName(ctx context.Context, tx *gorm.DB, name string) (entities.M_Permissions, error)
Update(ctx context.Context, tx *gorm.DB, permission entities.M_Permissions) (entities.M_Permissions, error)
Delete(ctx context.Context, tx *gorm.DB, id string) error
GetAll(ctx context.Context, tx *gorm.DB) ([]entities.M_Permissions, error)
}
permissionsRepository struct {
db *gorm.DB
}
)
func (r *permissionsRepository) Create(ctx context.Context, tx *gorm.DB, permission entities.M_Permissions) (entities.M_Permissions, error) {
if tx == nil {
tx = r.db
}
if err := tx.WithContext(ctx).Create(&permission).Error; err != nil {
return entities.M_Permissions{}, err
}
return permission, nil
}
func (r *permissionsRepository) GetById(ctx context.Context, tx *gorm.DB, id string) (entities.M_Permissions, error) {
if tx == nil {
tx = r.db
}
var permission entities.M_Permissions
if err := tx.WithContext(ctx).Preload("Menu").First(&permission, "id = ?", id).Error; err != nil {
return entities.M_Permissions{}, err
}
return permission, nil
}
func (r *permissionsRepository) GetByName(ctx context.Context, tx *gorm.DB, name string) (entities.M_Permissions, error) {
if tx == nil {
tx = r.db
}
var permission entities.M_Permissions
if err := tx.WithContext(ctx).Where("name = ?", name).First(&permission).Error; err != nil {
return entities.M_Permissions{}, err
}
return permission, nil
}
func (r *permissionsRepository) Update(ctx context.Context, tx *gorm.DB, permission entities.M_Permissions) (entities.M_Permissions, error) {
if tx == nil {
tx = r.db
}
if err := tx.WithContext(ctx).Updates(&permission).Error; err != nil {
return entities.M_Permissions{}, err
}
return permission, nil
}
func (r *permissionsRepository) Delete(ctx context.Context, tx *gorm.DB, id string) error {
if tx == nil {
tx = r.db
}
if err := tx.WithContext(ctx).Where("id = ?", id).Delete(&entities.M_Permissions{}).Error; err != nil {
return err
}
return nil
}
func (r *permissionsRepository) GetAll(ctx context.Context, tx *gorm.DB) ([]entities.M_Permissions, error) {
if tx == nil {
tx = r.db
}
var permissions []entities.M_Permissions
if err := tx.WithContext(ctx).Preload("Menu").Find(&permissions).Error; err != nil {
return nil, err
}
return permissions, nil
}
func NewPermissionsRepository(db *gorm.DB) PermissionsRepository {
return &permissionsRepository{db: db}
}

View File

@ -0,0 +1,24 @@
package permissions
import (
"github.com/Caknoooo/go-gin-clean-starter/middlewares"
"github.com/Caknoooo/go-gin-clean-starter/modules/auth/service"
"github.com/Caknoooo/go-gin-clean-starter/modules/permissions/controller"
"github.com/Caknoooo/go-gin-clean-starter/pkg/constants"
"github.com/gin-gonic/gin"
"github.com/samber/do"
)
func RegisterRoutes(server *gin.Engine, injector *do.Injector) {
permissionsController := do.MustInvoke[controller.PermissionsController](injector)
jwtService := do.MustInvokeNamed[service.JWTService](injector, constants.JWTService)
permissionsRoutes := server.Group("/api/v1/permissions")
{
permissionsRoutes.GET("", middlewares.Authenticate(jwtService), permissionsController.GetAllPermissions)
permissionsRoutes.POST("", middlewares.Authenticate(jwtService), permissionsController.CreatePermission)
permissionsRoutes.GET("/:id", middlewares.Authenticate(jwtService), permissionsController.GetPermissionByID)
permissionsRoutes.PUT("/:id", middlewares.Authenticate(jwtService), permissionsController.UpdatePermission)
permissionsRoutes.DELETE("/:id", middlewares.Authenticate(jwtService), permissionsController.DeletePermission)
}
}

View File

@ -0,0 +1,115 @@
package service
import (
"context"
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
"github.com/Caknoooo/go-gin-clean-starter/modules/permissions/dto"
"github.com/Caknoooo/go-gin-clean-starter/modules/permissions/repository"
"github.com/google/uuid"
"gorm.io/gorm"
)
type PermissionsService interface {
Create(ctx context.Context, req dto.PermissionsCreateRequest) (dto.PermissionsResponse, error)
GetById(ctx context.Context, id string) (dto.PermissionsResponse, error)
GetByName(ctx context.Context, name string) (dto.PermissionsResponse, error)
Update(ctx context.Context, id string, req dto.PermissionsUpdateRequest) (dto.PermissionsResponse, error)
Delete(ctx context.Context, id string) error
GetAll(ctx context.Context) ([]dto.PermissionsResponse, error)
}
type permissionsService struct {
repo repository.PermissionsRepository
db *gorm.DB
}
func (s *permissionsService) Create(ctx context.Context, req dto.PermissionsCreateRequest) (dto.PermissionsResponse, error) {
menuID, err := uuid.Parse(req.MenuID)
if err != nil {
return dto.PermissionsResponse{}, err
}
entity := entities.M_Permissions{
Name: req.Name,
MenuID: menuID,
}
created, err := s.repo.Create(ctx, s.db, entity)
if err != nil {
return dto.PermissionsResponse{}, err
}
// Ambil ulang dengan preload agar relasi Menu terisi
createdWithMenu, err := s.repo.GetById(ctx, s.db, created.ID.String())
if err != nil {
return dto.PermissionsResponse{}, err
}
return toPermissionsResponse(createdWithMenu), nil
}
func (s *permissionsService) GetById(ctx context.Context, id string) (dto.PermissionsResponse, error) {
entity, err := s.repo.GetById(ctx, s.db, id)
if err != nil {
return dto.PermissionsResponse{}, err
}
return toPermissionsResponse(entity), nil
}
func (s *permissionsService) GetByName(ctx context.Context, name string) (dto.PermissionsResponse, error) {
entity, err := s.repo.GetByName(ctx, s.db, name)
if err != nil {
return dto.PermissionsResponse{}, err
}
return toPermissionsResponse(entity), nil
}
func (s *permissionsService) Update(ctx context.Context, id string, req dto.PermissionsUpdateRequest) (dto.PermissionsResponse, error) {
existing, err := s.repo.GetById(ctx, s.db, id)
if err != nil {
return dto.PermissionsResponse{}, dto.ErrGetPermissionsById
}
if req.Name != "" {
existing.Name = req.Name
}
if req.MenuID != "" {
menuID, err := uuid.Parse(req.MenuID)
if err != nil {
return dto.PermissionsResponse{}, err
}
existing.MenuID = menuID
}
updated, err := s.repo.Update(ctx, s.db, existing)
if err != nil {
return dto.PermissionsResponse{}, err
}
return toPermissionsResponse(updated), nil
}
func (s *permissionsService) Delete(ctx context.Context, id string) error {
return s.repo.Delete(ctx, s.db, id)
}
func (s *permissionsService) GetAll(ctx context.Context) ([]dto.PermissionsResponse, error) {
entities, err := s.repo.GetAll(ctx, s.db)
if err != nil {
return nil, err
}
var res []dto.PermissionsResponse
for _, e := range entities {
res = append(res, toPermissionsResponse(e))
}
return res, nil
}
func NewPermissionsService(repo repository.PermissionsRepository, db *gorm.DB) PermissionsService {
return &permissionsService{repo: repo, db: db}
}
func toPermissionsResponse(e entities.M_Permissions) dto.PermissionsResponse {
return dto.PermissionsResponse{
ID: e.ID.String(),
Name: e.Name,
Menu: dto.Menu{
ID: e.Menu.ID.String(),
Name: e.Menu.Name,
},
}
}

View File

@ -20,6 +20,10 @@ import (
roleRepo "github.com/Caknoooo/go-gin-clean-starter/modules/role/repository" roleRepo "github.com/Caknoooo/go-gin-clean-starter/modules/role/repository"
roleService "github.com/Caknoooo/go-gin-clean-starter/modules/role/service" roleService "github.com/Caknoooo/go-gin-clean-starter/modules/role/service"
permissionsController "github.com/Caknoooo/go-gin-clean-starter/modules/permissions/controller"
permissionsRepo "github.com/Caknoooo/go-gin-clean-starter/modules/permissions/repository"
permissionsService "github.com/Caknoooo/go-gin-clean-starter/modules/permissions/service"
maintGroupController "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/controller" maintGroupController "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/controller"
maintGroupRepo "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/repository" maintGroupRepo "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/repository"
maintGroupRepoRole "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/repository" maintGroupRepoRole "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/repository"
@ -67,6 +71,7 @@ func RegisterDependencies(injector *do.Injector) {
maintenanceGroupRepository := maintGroupRepo.NewMaintGroupRepository(db) maintenanceGroupRepository := maintGroupRepo.NewMaintGroupRepository(db)
maintenanceGroupRoleRepository := maintGroupRepoRole.NewMaintGroupRoleRepository(db) maintenanceGroupRoleRepository := maintGroupRepoRole.NewMaintGroupRoleRepository(db)
maintenanceGroupRoleUserRepository := maintGroupRepoRoleUser.NewMaintGroupRoleUserRepository(db) maintenanceGroupRoleUserRepository := maintGroupRepoRoleUser.NewMaintGroupRoleUserRepository(db)
permissionsRepository := permissionsRepo.NewPermissionsRepository(db)
// Service // Service
userServ := userService.NewUserService(userRepository, refreshTokenRepository, jwtService, db) userServ := userService.NewUserService(userRepository, refreshTokenRepository, jwtService, db)
@ -75,6 +80,7 @@ func RegisterDependencies(injector *do.Injector) {
menuSvc := menuService.NewMenuService(menuRepository, jwtService, db) menuSvc := menuService.NewMenuService(menuRepository, jwtService, db)
maintenanceGroupServ := maintGroupService.NewMaintenanceGroupService(maintenanceGroupRepository, maintenanceGroupRoleRepository, maintenanceGroupRoleUserRepository, db) maintenanceGroupServ := maintGroupService.NewMaintenanceGroupService(maintenanceGroupRepository, maintenanceGroupRoleRepository, maintenanceGroupRoleUserRepository, db)
clientServ := clientService.NewClientService(clientRepository, db) clientServ := clientService.NewClientService(clientRepository, db)
permissionsServ := permissionsService.NewPermissionsService(permissionsRepository, db)
// Controller // Controller
do.Provide( do.Provide(
@ -117,4 +123,9 @@ func RegisterDependencies(injector *do.Injector) {
return maintGroupController.NewMaintenanceGroupController(i, maintenanceGroupServ), nil return maintGroupController.NewMaintenanceGroupController(i, maintenanceGroupServ), nil
}, },
) )
do.Provide(
injector, func(i *do.Injector) (permissionsController.PermissionsController, error) {
return permissionsController.NewPermissionsController(permissionsServ), nil
},
)
} }