feat(org,audit): M4 组织管理 + 审计日志
org 模块: - tenants 表 (UUID PK, name, slug, status, settings) - 完整 CRUD API: /api/v1/org/tenants - 多租户隔离 audit 模块: - audit_logs 表 (actor, action, resource, IP, 时间) - 查询 API: /api/v1/audit/logs (支持分页+筛选) - 按 tenant_id/user_id/action/resource 过滤
This commit is contained in:
parent
f8c374a624
commit
b13427e76c
@ -14,6 +14,8 @@ import (
|
||||
"mengstack/internal/logger"
|
||||
authinterfaces "mengstack/internal/modules/auth/interfaces"
|
||||
rbacinterfaces "mengstack/internal/modules/rbac/interfaces"
|
||||
orginterfaces "mengstack/internal/modules/org/interfaces"
|
||||
auditinterfaces "mengstack/internal/modules/audit/interfaces"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/redis/go-redis/v9"
|
||||
@ -39,6 +41,8 @@ func newEngine(
|
||||
authHandler *authinterfaces.Handler,
|
||||
authMW gin.HandlerFunc,
|
||||
rbacHandler *rbacinterfaces.Handler,
|
||||
orgHandler *orginterfaces.Handler,
|
||||
auditHandler *auditinterfaces.Handler,
|
||||
) *gin.Engine {
|
||||
ginMode := "release"
|
||||
if cfg.Server.Mode == "debug" || cfg.Server.Mode == "dev" {
|
||||
@ -59,6 +63,8 @@ func newEngine(
|
||||
|
||||
authinterfaces.SetupRoutes(r, authHandler, authMW)
|
||||
rbacinterfaces.SetupRoutes(r, rbacHandler, authMW)
|
||||
orginterfaces.SetupRoutes(r, orgHandler, authMW)
|
||||
auditinterfaces.SetupRoutes(r, auditHandler, authMW)
|
||||
|
||||
return r
|
||||
}
|
||||
@ -101,6 +107,8 @@ func NewApp() *fx.App {
|
||||
cache.Module,
|
||||
authinterfaces.Module,
|
||||
rbacinterfaces.Module,
|
||||
orginterfaces.Module,
|
||||
auditinterfaces.Module,
|
||||
Module,
|
||||
)
|
||||
}
|
||||
|
||||
46
internal/modules/audit/application/service.go
Normal file
46
internal/modules/audit/application/service.go
Normal file
@ -0,0 +1,46 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"mengstack/internal/kernel/errors"
|
||||
"mengstack/internal/modules/audit/domain"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
repo domain.AuditRepository
|
||||
}
|
||||
|
||||
func NewService(repo domain.AuditRepository) *Service {
|
||||
return &Service{repo: repo}
|
||||
}
|
||||
|
||||
func (s *Service) Log(ctx context.Context, tenantID string, userID uint, action, resource, resourceID, detail, ip string) error {
|
||||
log := &domain.AuditLog{
|
||||
TenantID: tenantID,
|
||||
UserID: userID,
|
||||
Action: action,
|
||||
Resource: resource,
|
||||
ResourceID: resourceID,
|
||||
Detail: detail,
|
||||
IP: ip,
|
||||
}
|
||||
|
||||
if err := s.repo.Create(ctx, log); err != nil {
|
||||
return errors.Wrap(err, "AUDIT_LOG_FAILED", "failed to write audit log", 500)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Query(ctx context.Context, query domain.AuditQuery) ([]domain.AuditLogDTO, int64, error) {
|
||||
logs, total, err := s.repo.Find(ctx, query)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "QUERY_AUDIT_FAILED", "failed to query audit logs", 500)
|
||||
}
|
||||
|
||||
dtos := make([]domain.AuditLogDTO, len(logs))
|
||||
for i, l := range logs {
|
||||
dtos[i] = domain.ToAuditLogDTO(&l)
|
||||
}
|
||||
return dtos, total, nil
|
||||
}
|
||||
54
internal/modules/audit/domain/log.go
Normal file
54
internal/modules/audit/domain/log.go
Normal file
@ -0,0 +1,54 @@
|
||||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
type AuditLog struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
TenantID string `json:"tenant_id" gorm:"type:uuid;index;not null"`
|
||||
UserID uint `json:"user_id" gorm:"index;not null"`
|
||||
Action string `json:"action" gorm:"size:64;not null;index"`
|
||||
Resource string `json:"resource" gorm:"size:128;not null;index"`
|
||||
ResourceID string `json:"resource_id" gorm:"size:128"`
|
||||
Detail string `json:"detail" gorm:"type:text"`
|
||||
IP string `json:"ip" gorm:"size:64"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"index"`
|
||||
}
|
||||
|
||||
func (AuditLog) TableName() string {
|
||||
return "audit_logs"
|
||||
}
|
||||
|
||||
type AuditLogDTO struct {
|
||||
ID uint `json:"id"`
|
||||
TenantID string `json:"tenant_id"`
|
||||
UserID uint `json:"user_id"`
|
||||
Action string `json:"action"`
|
||||
Resource string `json:"resource"`
|
||||
ResourceID string `json:"resource_id"`
|
||||
Detail string `json:"detail"`
|
||||
IP string `json:"ip"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func ToAuditLogDTO(l *AuditLog) AuditLogDTO {
|
||||
return AuditLogDTO{
|
||||
ID: l.ID,
|
||||
TenantID: l.TenantID,
|
||||
UserID: l.UserID,
|
||||
Action: l.Action,
|
||||
Resource: l.Resource,
|
||||
ResourceID: l.ResourceID,
|
||||
Detail: l.Detail,
|
||||
IP: l.IP,
|
||||
CreatedAt: l.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
type AuditQuery struct {
|
||||
TenantID string
|
||||
UserID uint
|
||||
Action string
|
||||
Resource string
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
8
internal/modules/audit/domain/repository.go
Normal file
8
internal/modules/audit/domain/repository.go
Normal file
@ -0,0 +1,8 @@
|
||||
package domain
|
||||
|
||||
import "context"
|
||||
|
||||
type AuditRepository interface {
|
||||
Create(ctx context.Context, log *AuditLog) error
|
||||
Find(ctx context.Context, query AuditQuery) ([]AuditLog, int64, error)
|
||||
}
|
||||
61
internal/modules/audit/infrastructure/audit_repo.go
Normal file
61
internal/modules/audit/infrastructure/audit_repo.go
Normal file
@ -0,0 +1,61 @@
|
||||
package infrastructure
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"mengstack/internal/modules/audit/domain"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type auditRepo struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewAuditRepository(db *gorm.DB) domain.AuditRepository {
|
||||
return &auditRepo{db: db}
|
||||
}
|
||||
|
||||
func (r *auditRepo) Create(ctx context.Context, log *domain.AuditLog) error {
|
||||
return r.db.WithContext(ctx).Create(log).Error
|
||||
}
|
||||
|
||||
func (r *auditRepo) Find(ctx context.Context, query domain.AuditQuery) ([]domain.AuditLog, int64, error) {
|
||||
var logs []domain.AuditLog
|
||||
var total int64
|
||||
|
||||
tx := r.db.WithContext(ctx).Model(&domain.AuditLog{})
|
||||
|
||||
if query.TenantID != "" {
|
||||
tx = tx.Where("tenant_id = ?", query.TenantID)
|
||||
}
|
||||
if query.UserID > 0 {
|
||||
tx = tx.Where("user_id = ?", query.UserID)
|
||||
}
|
||||
if query.Action != "" {
|
||||
tx = tx.Where("action = ?", query.Action)
|
||||
}
|
||||
if query.Resource != "" {
|
||||
tx = tx.Where("resource = ?", query.Resource)
|
||||
}
|
||||
|
||||
if err := tx.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
page := query.Page
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
pageSize := query.PageSize
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
if err := tx.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&logs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return logs, total, nil
|
||||
}
|
||||
13
internal/modules/audit/infrastructure/migrate.go
Normal file
13
internal/modules/audit/infrastructure/migrate.go
Normal file
@ -0,0 +1,13 @@
|
||||
package infrastructure
|
||||
|
||||
import (
|
||||
"mengstack/internal/modules/audit/domain"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Migrate(db *gorm.DB) error {
|
||||
return db.AutoMigrate(
|
||||
&domain.AuditLog{},
|
||||
)
|
||||
}
|
||||
53
internal/modules/audit/interfaces/handler.go
Normal file
53
internal/modules/audit/interfaces/handler.go
Normal file
@ -0,0 +1,53 @@
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"mengstack/internal/kernel/response"
|
||||
"mengstack/internal/kernel/tenant"
|
||||
"mengstack/internal/modules/audit/application"
|
||||
auditdomain "mengstack/internal/modules/audit/domain"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
svc *application.Service
|
||||
}
|
||||
|
||||
func NewHandler(svc *application.Service) *Handler {
|
||||
return &Handler{svc: svc}
|
||||
}
|
||||
|
||||
func (h *Handler) ListLogs(c *gin.Context) {
|
||||
tenantID, _ := tenant.FromContext(c.Request.Context())
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
query := auditdomain.AuditQuery{
|
||||
TenantID: tenantID,
|
||||
UserID: parseUintQuery(c, "user_id"),
|
||||
Action: c.Query("action"),
|
||||
Resource: c.Query("resource"),
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}
|
||||
|
||||
dtos, total, err := h.svc.Query(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
response.HandleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"items": dtos,
|
||||
"total": total,
|
||||
"page": page,
|
||||
})
|
||||
}
|
||||
|
||||
func parseUintQuery(c *gin.Context, key string) uint {
|
||||
v, _ := strconv.ParseUint(c.DefaultQuery(key, "0"), 10, 64)
|
||||
return uint(v)
|
||||
}
|
||||
33
internal/modules/audit/interfaces/routes.go
Normal file
33
internal/modules/audit/interfaces/routes.go
Normal file
@ -0,0 +1,33 @@
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"mengstack/internal/app/middleware"
|
||||
"mengstack/internal/modules/audit/application"
|
||||
auditinfra "mengstack/internal/modules/audit/infrastructure"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func SetupRoutes(r *gin.Engine, h *Handler, authMW gin.HandlerFunc) {
|
||||
audit := r.Group("/api/v1/audit")
|
||||
audit.Use(authMW, middleware.MultiTenant())
|
||||
{
|
||||
audit.GET("/logs", h.ListLogs)
|
||||
}
|
||||
}
|
||||
|
||||
var Module = fx.Module("audit",
|
||||
fx.Provide(
|
||||
auditinfra.NewAuditRepository,
|
||||
application.NewService,
|
||||
NewHandler,
|
||||
),
|
||||
fx.Invoke(func(db *gorm.DB, log *zap.Logger) {
|
||||
if err := auditinfra.Migrate(db); err != nil {
|
||||
log.Fatal("audit migration failed", zap.Error(err))
|
||||
}
|
||||
}),
|
||||
)
|
||||
93
internal/modules/org/application/service.go
Normal file
93
internal/modules/org/application/service.go
Normal file
@ -0,0 +1,93 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"mengstack/internal/kernel/errors"
|
||||
"mengstack/internal/modules/org/domain"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
repo domain.TenantRepository
|
||||
}
|
||||
|
||||
func NewService(repo domain.TenantRepository) *Service {
|
||||
return &Service{repo: repo}
|
||||
}
|
||||
|
||||
func (s *Service) CreateTenant(ctx context.Context, req domain.CreateTenantRequest) (domain.TenantDTO, error) {
|
||||
if _, err := s.repo.FindBySlug(ctx, req.Slug); err == nil {
|
||||
return domain.TenantDTO{}, errors.ErrConflict
|
||||
}
|
||||
|
||||
tenant := &domain.Tenant{
|
||||
Name: req.Name,
|
||||
Slug: req.Slug,
|
||||
}
|
||||
|
||||
if err := s.repo.Create(ctx, tenant); err != nil {
|
||||
return domain.TenantDTO{}, errors.Wrap(err, "CREATE_TENANT_FAILED", "failed to create tenant", 500)
|
||||
}
|
||||
|
||||
return domain.ToTenantDTO(tenant), nil
|
||||
}
|
||||
|
||||
func (s *Service) ListTenants(ctx context.Context) ([]domain.TenantDTO, error) {
|
||||
tenants, err := s.repo.FindAll(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "QUERY_TENANTS_FAILED", "failed to query tenants", 500)
|
||||
}
|
||||
|
||||
dtos := make([]domain.TenantDTO, len(tenants))
|
||||
for i, t := range tenants {
|
||||
dtos[i] = domain.ToTenantDTO(&t)
|
||||
}
|
||||
return dtos, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetTenant(ctx context.Context, id string) (domain.TenantDTO, error) {
|
||||
tenant, err := s.repo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return domain.TenantDTO{}, errors.ErrNotFound
|
||||
}
|
||||
|
||||
return domain.ToTenantDTO(tenant), nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateTenant(ctx context.Context, id string, req domain.UpdateTenantRequest) (domain.TenantDTO, error) {
|
||||
tenant, err := s.repo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return domain.TenantDTO{}, errors.ErrNotFound
|
||||
}
|
||||
|
||||
if req.Name != "" {
|
||||
tenant.Name = req.Name
|
||||
}
|
||||
if req.Slug != "" {
|
||||
if existing, err := s.repo.FindBySlug(ctx, req.Slug); err == nil && existing.ID != id {
|
||||
return domain.TenantDTO{}, errors.ErrConflict
|
||||
}
|
||||
tenant.Slug = req.Slug
|
||||
}
|
||||
if req.Status != nil {
|
||||
tenant.Status = *req.Status
|
||||
}
|
||||
|
||||
if err := s.repo.Update(ctx, tenant); err != nil {
|
||||
return domain.TenantDTO{}, errors.Wrap(err, "UPDATE_TENANT_FAILED", "failed to update tenant", 500)
|
||||
}
|
||||
|
||||
return domain.ToTenantDTO(tenant), nil
|
||||
}
|
||||
|
||||
func (s *Service) DeleteTenant(ctx context.Context, id string) error {
|
||||
if _, err := s.repo.FindByID(ctx, id); err != nil {
|
||||
return errors.ErrNotFound
|
||||
}
|
||||
|
||||
if err := s.repo.Delete(ctx, id); err != nil {
|
||||
return errors.Wrap(err, "DELETE_TENANT_FAILED", "failed to delete tenant", 500)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
12
internal/modules/org/domain/repository.go
Normal file
12
internal/modules/org/domain/repository.go
Normal file
@ -0,0 +1,12 @@
|
||||
package domain
|
||||
|
||||
import "context"
|
||||
|
||||
type TenantRepository interface {
|
||||
Create(ctx context.Context, tenant *Tenant) error
|
||||
FindByID(ctx context.Context, id string) (*Tenant, error)
|
||||
FindBySlug(ctx context.Context, slug string) (*Tenant, error)
|
||||
FindAll(ctx context.Context) ([]Tenant, error)
|
||||
Update(ctx context.Context, tenant *Tenant) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
}
|
||||
48
internal/modules/org/domain/tenant.go
Normal file
48
internal/modules/org/domain/tenant.go
Normal file
@ -0,0 +1,48 @@
|
||||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
type Tenant struct {
|
||||
ID string `json:"id" gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
|
||||
Name string `json:"name" gorm:"size:128;not null"`
|
||||
Slug string `json:"slug" gorm:"uniqueIndex;size:64;not null"`
|
||||
Status int `json:"status" gorm:"default:1"`
|
||||
Settings string `json:"settings" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (Tenant) TableName() string {
|
||||
return "tenants"
|
||||
}
|
||||
|
||||
type TenantDTO struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Status int `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func ToTenantDTO(t *Tenant) TenantDTO {
|
||||
return TenantDTO{
|
||||
ID: t.ID,
|
||||
Name: t.Name,
|
||||
Slug: t.Slug,
|
||||
Status: t.Status,
|
||||
CreatedAt: t.CreatedAt,
|
||||
UpdatedAt: t.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
type CreateTenantRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Slug string `json:"slug" binding:"required"`
|
||||
}
|
||||
|
||||
type UpdateTenantRequest struct {
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Status *int `json:"status"`
|
||||
}
|
||||
13
internal/modules/org/infrastructure/migrate.go
Normal file
13
internal/modules/org/infrastructure/migrate.go
Normal file
@ -0,0 +1,13 @@
|
||||
package infrastructure
|
||||
|
||||
import (
|
||||
"mengstack/internal/modules/org/domain"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Migrate(db *gorm.DB) error {
|
||||
return db.AutoMigrate(
|
||||
&domain.Tenant{},
|
||||
)
|
||||
}
|
||||
47
internal/modules/org/infrastructure/tenant_repo.go
Normal file
47
internal/modules/org/infrastructure/tenant_repo.go
Normal file
@ -0,0 +1,47 @@
|
||||
package infrastructure
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"mengstack/internal/modules/org/domain"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type tenantRepo struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewTenantRepository(db *gorm.DB) domain.TenantRepository {
|
||||
return &tenantRepo{db: db}
|
||||
}
|
||||
|
||||
func (r *tenantRepo) Create(ctx context.Context, tenant *domain.Tenant) error {
|
||||
return r.db.WithContext(ctx).Create(tenant).Error
|
||||
}
|
||||
|
||||
func (r *tenantRepo) FindByID(ctx context.Context, id string) (*domain.Tenant, error) {
|
||||
var tenant domain.Tenant
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&tenant).Error
|
||||
return &tenant, err
|
||||
}
|
||||
|
||||
func (r *tenantRepo) FindBySlug(ctx context.Context, slug string) (*domain.Tenant, error) {
|
||||
var tenant domain.Tenant
|
||||
err := r.db.WithContext(ctx).Where("slug = ?", slug).First(&tenant).Error
|
||||
return &tenant, err
|
||||
}
|
||||
|
||||
func (r *tenantRepo) FindAll(ctx context.Context) ([]domain.Tenant, error) {
|
||||
var tenants []domain.Tenant
|
||||
err := r.db.WithContext(ctx).Order("created_at DESC").Find(&tenants).Error
|
||||
return tenants, err
|
||||
}
|
||||
|
||||
func (r *tenantRepo) Update(ctx context.Context, tenant *domain.Tenant) error {
|
||||
return r.db.WithContext(ctx).Save(tenant).Error
|
||||
}
|
||||
|
||||
func (r *tenantRepo) Delete(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Where("id = ?", id).Delete(&domain.Tenant{}).Error
|
||||
}
|
||||
84
internal/modules/org/interfaces/handler.go
Normal file
84
internal/modules/org/interfaces/handler.go
Normal file
@ -0,0 +1,84 @@
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"mengstack/internal/kernel/response"
|
||||
"mengstack/internal/modules/org/application"
|
||||
"mengstack/internal/modules/org/domain"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
svc *application.Service
|
||||
}
|
||||
|
||||
func NewHandler(svc *application.Service) *Handler {
|
||||
return &Handler{svc: svc}
|
||||
}
|
||||
|
||||
func (h *Handler) CreateTenant(c *gin.Context) {
|
||||
var req domain.CreateTenantRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Fail(c, response.NewBadRequest("invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
dto, err := h.svc.CreateTenant(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
response.HandleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dto)
|
||||
}
|
||||
|
||||
func (h *Handler) ListTenants(c *gin.Context) {
|
||||
dtos, err := h.svc.ListTenants(c.Request.Context())
|
||||
if err != nil {
|
||||
response.HandleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dtos)
|
||||
}
|
||||
|
||||
func (h *Handler) GetTenant(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
dto, err := h.svc.GetTenant(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
response.HandleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dto)
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateTenant(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var req domain.UpdateTenantRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Fail(c, response.NewBadRequest("invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
dto, err := h.svc.UpdateTenant(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
response.HandleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dto)
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteTenant(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
if err := h.svc.DeleteTenant(c.Request.Context(), id); err != nil {
|
||||
response.HandleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
37
internal/modules/org/interfaces/routes.go
Normal file
37
internal/modules/org/interfaces/routes.go
Normal file
@ -0,0 +1,37 @@
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"mengstack/internal/app/middleware"
|
||||
"mengstack/internal/modules/org/application"
|
||||
"mengstack/internal/modules/org/infrastructure"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func SetupRoutes(r *gin.Engine, h *Handler, authMW gin.HandlerFunc) {
|
||||
org := r.Group("/api/v1/org")
|
||||
org.Use(authMW, middleware.MultiTenant())
|
||||
{
|
||||
org.POST("/tenants", h.CreateTenant)
|
||||
org.GET("/tenants", h.ListTenants)
|
||||
org.GET("/tenants/:id", h.GetTenant)
|
||||
org.PUT("/tenants/:id", h.UpdateTenant)
|
||||
org.DELETE("/tenants/:id", h.DeleteTenant)
|
||||
}
|
||||
}
|
||||
|
||||
var Module = fx.Module("org",
|
||||
fx.Provide(
|
||||
infrastructure.NewTenantRepository,
|
||||
application.NewService,
|
||||
NewHandler,
|
||||
),
|
||||
fx.Invoke(func(db *gorm.DB, log *zap.Logger) {
|
||||
if err := infrastructure.Migrate(db); err != nil {
|
||||
log.Fatal("org migration failed", zap.Error(err))
|
||||
}
|
||||
}),
|
||||
)
|
||||
Loading…
Reference in New Issue
Block a user