Compare commits

...

3 Commits

Author SHA1 Message Date
Amir Tavakolian 8aad672795 add update role feature 2026-05-21 09:11:25 +03:30
Amir Tavakolian 73b900d24e refactor 2026-05-21 09:11:16 +03:30
Amir Tavakolian 7a54387722 add store role feature 2026-05-21 09:11:03 +03:30
11 changed files with 358 additions and 0 deletions

73
authorizationapp/app.go Normal file
View File

@ -0,0 +1,73 @@
package authorizationapp
import (
"git.gocasts.ir/ebhomengo/niki/authorizationapp/delivery/http"
"git.gocasts.ir/ebhomengo/niki/domain/authorization/repository"
"git.gocasts.ir/ebhomengo/niki/domain/authorization/service"
cfgloader "git.gocasts.ir/ebhomengo/niki/pkg/cfg_loader"
mySql "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
"git.gocasts.ir/ebhomengo/niki/pkg/httpserver"
"git.gocasts.ir/ebhomengo/niki/pkg/path"
"log"
"os"
"path/filepath"
)
type Config struct {
HTTPServer httpserver.Config `koanf:"http_server"`
MySQLDB mySql.Config `koanf:"mariadb"`
}
type Application struct {
RoleRepo *repository.RoleRepo
AuthorizationService service.Authorization
RoleHandler http.RoleHandler
RoleServer http.RoleServer
}
func (a Application) Setup() Application {
appConfig := a.getYamlConfigPath()
db := mySql.New(appConfig.MySQLDB)
roleRepo := repository.New(db)
authorizationService := service.NewAuthorization(roleRepo)
roleHandler := http.NewRoleHandler(authorizationService)
server := http.NewRoleServer(appConfig.HTTPServer, roleHandler)
return Application{
RoleRepo: roleRepo,
AuthorizationService: authorizationService,
RoleHandler: roleHandler,
RoleServer: server,
}
}
func (a Application) Start() {
// todo implement role service start
}
func (a Application) getYamlConfigPath() Config {
var appConfig Config
projectRoot, err := path.PathProjectRoot()
if err != nil {
log.Fatalf("Error finding project root: %v", err)
}
defaultConfig := filepath.Join(projectRoot, "deploy", "authorization", "config.yml")
_, err = os.Stat(defaultConfig)
if err != nil {
log.Fatal(err.Error())
}
options := cfgloader.Option{
Prefix: "AUTHORIZATION_",
Delimiter: ".",
Separator: "__",
YamlFilePath: defaultConfig,
CallbackEnv: nil,
}
err = cfgloader.Load(options, &appConfig)
if err != nil {
log.Fatalf("failed loading authorization config: %v", err)
}
return appConfig
}

View File

@ -0,0 +1,50 @@
package http
import (
"fmt"
"git.gocasts.ir/ebhomengo/niki/domain/authorization/service"
"github.com/gin-gonic/gin"
"net/http"
)
type RoleHandler struct {
service service.Authorization
}
func NewRoleHandler(service service.Authorization) RoleHandler {
return RoleHandler{service: service}
}
func (r RoleHandler) Store(c *gin.Context) {
var request service.StoreRoleRequest
err := c.ShouldBindJSON(&request)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
role, err := r.service.Store(c, request)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("role %s created successfully", role.Title_fa)})
}
func (r RoleHandler) Update(c *gin.Context) {
var request service.UpdateRoleRequest
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
role, err := r.service.Update(c, request)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("role %s updated successfully", role.Title_fa)})
}

View File

@ -0,0 +1,18 @@
package http
import (
"git.gocasts.ir/ebhomengo/niki/pkg/httpserver"
)
type RoleServer struct {
cfg httpserver.Config
handler RoleHandler
}
func NewRoleServer(cfg httpserver.Config, handler RoleHandler) RoleServer {
return RoleServer{cfg: cfg, handler: handler}
}
func (s RoleServer) Start() {
}

View File

@ -0,0 +1 @@
database:

View File

@ -0,0 +1,13 @@
package entity
import (
"git.gocasts.ir/ebhomengo/niki/types"
"time"
)
type Role struct {
ID types.ID `json:"id"`
Title string `json:"title"`
Title_fa string `json:"title_Fa"`
CreatedAt time.Time `json:"created_at"`
}

View File

@ -0,0 +1,13 @@
-- +migrate Up
CREATE TABLE IF NOT EXISTS `roles` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`title_fa` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_roles_title` (`title`),
UNIQUE KEY `idx_roles_title_fa` (`title_fa`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- +migrate Down
DROP TABLE IF EXISTS `roles`;

View File

@ -0,0 +1,64 @@
package repository
import (
"context"
"git.gocasts.ir/ebhomengo/niki/domain/authorization/service"
"git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
"git.gocasts.ir/ebhomengo/niki/types"
)
type RoleRepo struct {
conn *mysql.DB
}
func New(conn *mysql.DB) *RoleRepo {
return &RoleRepo{conn: conn}
}
func (m RoleRepo) Store(ctx context.Context, req service.StoreRoleRequest) (types.ID, error) {
const op = "domain.authorization.repository.role.store"
result, err := m.conn.Conn().ExecContext(ctx, "INSERT INTO roles VALUES (`?,?`)", req.Title, req.TitleFa)
if err != nil {
return 0, richerror.New(op).WithErr(err)
}
roleID, err := result.LastInsertId()
if err != nil {
return 0, richerror.New(op).WithErr(err)
}
return types.ID(roleID), nil
}
func (m RoleRepo) Update(ctx context.Context, req service.UpdateRoleRequest) (types.ID, error) {
const op = "domain.authorization.repository.role.update"
result, err := m.conn.Conn().ExecContext(ctx,
"UPDATE roles SET title = ?, title_fa = ? WHERE id = ?",
req.Title, req.TitleFa, req.ID)
if err != nil {
return 0, richerror.New(op).WithErr(err)
}
_, err = result.RowsAffected()
if err != nil {
return 0, richerror.New(op).WithErr(err)
}
return types.ID(req.ID), nil
}
func (m RoleRepo) IsRoleExistsByID(ctx context.Context, id types.ID) error {
const op = "domain.authorization.repository.role.is_exists_by_id"
var exists bool
err := m.conn.Conn().QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM roles WHERE id = ?)", id).Scan(&exists)
if err != nil {
return richerror.New(op).WithErr(err)
}
return nil
}

View File

@ -0,0 +1,52 @@
package service
import (
"context"
"git.gocasts.ir/ebhomengo/niki/domain/authorization/entity"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
)
type Authorization struct {
roleRepo RoleRepo
}
func NewAuthorization(roleRepo RoleRepo) Authorization {
return Authorization{roleRepo: roleRepo}
}
func (a Authorization) Store(ctx context.Context, req StoreRoleRequest) (entity.Role, error) {
const op = "authorizationservice.Store"
if err := a.validateStoreRole(req); err != nil {
return entity.Role{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
}
role, err := a.roleRepo.Store(ctx, req)
if err != nil {
return entity.Role{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
}
return entity.Role{
ID: role,
Title_fa: req.TitleFa,
Title: req.Title,
}, nil
}
func (a Authorization) Update(ctx context.Context, req UpdateRoleRequest) (entity.Role, error) {
const op = "authorizationservice.Update"
if err := a.validateUpdateRole(ctx, req); err != nil {
return entity.Role{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
}
role, err := a.roleRepo.Update(ctx, req)
if err != nil {
return entity.Role{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
}
return entity.Role{
ID: role,
Title_fa: req.TitleFa,
Title: req.Title,
}, nil
}

View File

@ -0,0 +1,17 @@
package service
import "git.gocasts.ir/ebhomengo/niki/types"
type RoleRequest struct {
Title string `json:"title"`
TitleFa string `json:"title_fa"`
}
type StoreRoleRequest struct {
RoleRequest
}
type UpdateRoleRequest struct {
ID types.ID `json:"id"`
RoleRequest
}

View File

@ -0,0 +1,12 @@
package service
import (
"context"
"git.gocasts.ir/ebhomengo/niki/types"
)
type RoleRepo interface {
Store(ctx context.Context, req StoreRoleRequest) (types.ID, error)
Update(ctx context.Context, req UpdateRoleRequest) (types.ID, error)
IsRoleExistsByID(ctx context.Context, id types.ID) error
}

View File

@ -0,0 +1,45 @@
package service
import (
"context"
"github.com/go-ozzo/ozzo-validation/v4"
"regexp"
)
func (a Authorization) validateStoreRole(req StoreRoleRequest) error {
return validation.ValidateStruct(&req,
validation.Field(&req.Title,
validation.Required,
validation.Length(4, 0),
validation.Match(regexp.MustCompile(`^[a-zA-Z\s]+$`))),
validation.Field(&req.TitleFa,
validation.Required,
validation.Length(4, 0),
validation.Match(regexp.MustCompile(`^[\x{0600}-\x{06FF}\s]+$`))),
)
}
func (a Authorization) validateUpdateRole(ctx context.Context, req UpdateRoleRequest) error {
return validation.ValidateStruct(&req,
validation.Field(&req.ID,
validation.Required,
validation.By(func(value interface{}) error {
err := a.roleRepo.IsRoleExistsByID(ctx, req.ID)
if err != nil {
return err
}
return nil
}),
),
validation.Field(&req.Title,
validation.Required,
validation.Length(4, 0),
validation.Match(regexp.MustCompile(`^[a-zA-Z\s]+$`))),
validation.Field(&req.TitleFa,
validation.Required,
validation.Length(4, 0),
validation.Match(regexp.MustCompile(`^[\x{0600}-\x{06FF}\s]+$`))),
)
}