feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
This commit is contained in:
260
internal/api/handler/auth_handler.go
Normal file
260
internal/api/handler/auth_handler.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// AuthHandler handles authentication requests
|
||||
type AuthHandler struct {
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
// NewAuthHandler creates a new AuthHandler
|
||||
func NewAuthHandler(authService *service.AuthService) *AuthHandler {
|
||||
return &AuthHandler{authService: authService}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
var req struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
registerReq := &service.RegisterRequest{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
Phone: req.Phone,
|
||||
Password: req.Password,
|
||||
Nickname: req.Nickname,
|
||||
}
|
||||
|
||||
userInfo, err := h.authService.Register(c.Request.Context(), registerReq)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, userInfo)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
var req struct {
|
||||
Account string `json:"account"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
loginReq := &service.LoginRequest{
|
||||
Account: req.Account,
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
Phone: req.Phone,
|
||||
Password: req.Password,
|
||||
}
|
||||
|
||||
clientIP := c.ClientIP()
|
||||
resp, err := h.authService.Login(c.Request.Context(), loginReq, clientIP)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Logout(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "logged out"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) RefreshToken(c *gin.Context) {
|
||||
var req struct {
|
||||
RefreshToken string `json:"refresh_token" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.authService.RefreshToken(c.Request.Context(), req.RefreshToken)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) GetUserInfo(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
userInfo, err := h.authService.GetUserInfo(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, userInfo)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) GetCSRFToken(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"csrf_token": "not_implemented"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) GetAuthCapabilities(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"register": true,
|
||||
"login": true,
|
||||
"oauth_login": false,
|
||||
"totp": true,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) OAuthLogin(c *gin.Context) {
|
||||
provider := c.Param("provider")
|
||||
c.JSON(http.StatusOK, gin.H{"provider": provider, "message": "OAuth not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) OAuthCallback(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"error": "OAuth not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) OAuthExchange(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"error": "OAuth not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) GetEnabledOAuthProviders(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"providers": []string{}})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ActivateEmail(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "email activation not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ResendActivationEmail(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "email activation not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) SendEmailCode(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "email code login not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) LoginByEmailCode(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"error": "email code login not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ForgotPassword(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "password reset not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ResetPassword(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "password reset not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ValidateResetToken(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"valid": false})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) BootstrapAdmin(c *gin.Context) {
|
||||
var req struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Email string `json:"email" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
bootstrapReq := &service.BootstrapAdminRequest{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
Password: req.Password,
|
||||
}
|
||||
|
||||
clientIP := c.ClientIP()
|
||||
resp, err := h.authService.BootstrapAdmin(c.Request.Context(), bootstrapReq, clientIP)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, resp)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) SendEmailBindCode(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "email bind not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) BindEmail(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "email bind not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) UnbindEmail(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "email unbind not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) SendPhoneBindCode(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "phone bind not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) BindPhone(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "phone bind not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) UnbindPhone(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "phone unbind not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) GetSocialAccounts(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"accounts": []interface{}{}})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) BindSocialAccount(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "social binding not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) UnbindSocialAccount(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "social unbinding not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) SupportsEmailCodeLogin() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func getUserIDFromContext(c *gin.Context) (int64, bool) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
return 0, false
|
||||
}
|
||||
id, ok := userID.(int64)
|
||||
return id, ok
|
||||
}
|
||||
|
||||
func handleError(c *gin.Context, err error) {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
19
internal/api/handler/avatar_handler.go
Normal file
19
internal/api/handler/avatar_handler.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AvatarHandler handles avatar upload requests
|
||||
type AvatarHandler struct{}
|
||||
|
||||
// NewAvatarHandler creates a new AvatarHandler
|
||||
func NewAvatarHandler() *AvatarHandler {
|
||||
return &AvatarHandler{}
|
||||
}
|
||||
|
||||
func (h *AvatarHandler) UploadAvatar(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "avatar upload not implemented"})
|
||||
}
|
||||
54
internal/api/handler/captcha_handler.go
Normal file
54
internal/api/handler/captcha_handler.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// CaptchaHandler handles captcha requests
|
||||
type CaptchaHandler struct {
|
||||
captchaService *service.CaptchaService
|
||||
}
|
||||
|
||||
// NewCaptchaHandler creates a new CaptchaHandler
|
||||
func NewCaptchaHandler(captchaService *service.CaptchaService) *CaptchaHandler {
|
||||
return &CaptchaHandler{captchaService: captchaService}
|
||||
}
|
||||
|
||||
func (h *CaptchaHandler) GenerateCaptcha(c *gin.Context) {
|
||||
result, err := h.captchaService.Generate(c.Request.Context())
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"captcha_id": result.CaptchaID,
|
||||
"image": result.ImageData,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *CaptchaHandler) GetCaptchaImage(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "captcha image endpoint"})
|
||||
}
|
||||
|
||||
func (h *CaptchaHandler) VerifyCaptcha(c *gin.Context) {
|
||||
var req struct {
|
||||
CaptchaID string `json:"captcha_id" binding:"required"`
|
||||
Answer string `json:"answer" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if h.captchaService.Verify(c.Request.Context(), req.CaptchaID, req.Answer) {
|
||||
c.JSON(http.StatusOK, gin.H{"verified": true})
|
||||
} else {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid captcha"})
|
||||
}
|
||||
}
|
||||
146
internal/api/handler/custom_field_handler.go
Normal file
146
internal/api/handler/custom_field_handler.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// CustomFieldHandler 自定义字段处理器
|
||||
type CustomFieldHandler struct {
|
||||
customFieldService *service.CustomFieldService
|
||||
}
|
||||
|
||||
// NewCustomFieldHandler 创建自定义字段处理器
|
||||
func NewCustomFieldHandler(customFieldService *service.CustomFieldService) *CustomFieldHandler {
|
||||
return &CustomFieldHandler{customFieldService: customFieldService}
|
||||
}
|
||||
|
||||
// CreateField 创建自定义字段
|
||||
func (h *CustomFieldHandler) CreateField(c *gin.Context) {
|
||||
var req service.CreateFieldRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
field, err := h.customFieldService.CreateField(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, field)
|
||||
}
|
||||
|
||||
// UpdateField 更新自定义字段
|
||||
func (h *CustomFieldHandler) UpdateField(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid field id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req service.UpdateFieldRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
field, err := h.customFieldService.UpdateField(c.Request.Context(), id, &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, field)
|
||||
}
|
||||
|
||||
// DeleteField 删除自定义字段
|
||||
func (h *CustomFieldHandler) DeleteField(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid field id"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.customFieldService.DeleteField(c.Request.Context(), id); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "field deleted"})
|
||||
}
|
||||
|
||||
// GetField 获取自定义字段
|
||||
func (h *CustomFieldHandler) GetField(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid field id"})
|
||||
return
|
||||
}
|
||||
|
||||
field, err := h.customFieldService.GetField(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, field)
|
||||
}
|
||||
|
||||
// ListFields 获取所有自定义字段
|
||||
func (h *CustomFieldHandler) ListFields(c *gin.Context) {
|
||||
fields, err := h.customFieldService.ListFields(c.Request.Context())
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"fields": fields})
|
||||
}
|
||||
|
||||
// SetUserFieldValues 设置用户自定义字段值
|
||||
func (h *CustomFieldHandler) SetUserFieldValues(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Values map[string]string `json:"values" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.customFieldService.BatchSetUserFieldValues(c.Request.Context(), userID, req.Values); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "field values set"})
|
||||
}
|
||||
|
||||
// GetUserFieldValues 获取用户自定义字段值
|
||||
func (h *CustomFieldHandler) GetUserFieldValues(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
values, err := h.customFieldService.GetUserFieldValues(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"fields": values})
|
||||
}
|
||||
343
internal/api/handler/device_handler.go
Normal file
343
internal/api/handler/device_handler.go
Normal file
@@ -0,0 +1,343 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/domain"
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// DeviceHandler handles device management requests
|
||||
type DeviceHandler struct {
|
||||
deviceService *service.DeviceService
|
||||
}
|
||||
|
||||
// NewDeviceHandler creates a new DeviceHandler
|
||||
func NewDeviceHandler(deviceService *service.DeviceService) *DeviceHandler {
|
||||
return &DeviceHandler{deviceService: deviceService}
|
||||
}
|
||||
|
||||
func (h *DeviceHandler) CreateDevice(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
var req service.CreateDeviceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
device, err := h.deviceService.CreateDevice(c.Request.Context(), userID, &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, device)
|
||||
}
|
||||
|
||||
func (h *DeviceHandler) GetMyDevices(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
devices, total, err := h.deviceService.GetUserDevices(c.Request.Context(), userID, page, pageSize)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"devices": devices,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *DeviceHandler) GetDevice(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid device id"})
|
||||
return
|
||||
}
|
||||
|
||||
device, err := h.deviceService.GetDevice(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, device)
|
||||
}
|
||||
|
||||
func (h *DeviceHandler) UpdateDevice(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid device id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req service.UpdateDeviceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
device, err := h.deviceService.UpdateDevice(c.Request.Context(), id, &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, device)
|
||||
}
|
||||
|
||||
func (h *DeviceHandler) DeleteDevice(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid device id"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.deviceService.DeleteDevice(c.Request.Context(), id); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "device deleted"})
|
||||
}
|
||||
|
||||
func (h *DeviceHandler) UpdateDeviceStatus(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid device id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var status domain.DeviceStatus
|
||||
switch req.Status {
|
||||
case "active", "1":
|
||||
status = domain.DeviceStatusActive
|
||||
case "inactive", "0":
|
||||
status = domain.DeviceStatusInactive
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid status"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.deviceService.UpdateDeviceStatus(c.Request.Context(), id, status); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "status updated"})
|
||||
}
|
||||
|
||||
func (h *DeviceHandler) GetUserDevices(c *gin.Context) {
|
||||
userIDParam := c.Param("id")
|
||||
userID, err := strconv.ParseInt(userIDParam, 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"})
|
||||
return
|
||||
}
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
devices, total, err := h.deviceService.GetUserDevices(c.Request.Context(), userID, page, pageSize)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"devices": devices,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
// GetAllDevices 获取所有设备列表(管理员)
|
||||
func (h *DeviceHandler) GetAllDevices(c *gin.Context) {
|
||||
var req service.GetAllDevicesRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
devices, total, err := h.deviceService.GetAllDevices(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"devices": devices,
|
||||
"total": total,
|
||||
"page": req.Page,
|
||||
"page_size": req.PageSize,
|
||||
})
|
||||
}
|
||||
|
||||
// TrustDeviceRequest 信任设备请求
|
||||
type TrustDeviceRequest struct {
|
||||
TrustDuration string `json:"trust_duration"` // 信任持续时间,如 "30d" 表示30天
|
||||
}
|
||||
|
||||
// TrustDevice 设置设备为信任设备
|
||||
func (h *DeviceHandler) TrustDevice(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid device id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req TrustDeviceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 解析信任持续时间
|
||||
trustDuration := parseDuration(req.TrustDuration)
|
||||
|
||||
if err := h.deviceService.TrustDevice(c.Request.Context(), id, trustDuration); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "device trusted"})
|
||||
}
|
||||
|
||||
// TrustDeviceByDeviceID 根据设备标识字符串设置设备为信任状态
|
||||
func (h *DeviceHandler) TrustDeviceByDeviceID(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
deviceID := c.Param("deviceId")
|
||||
if deviceID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid device id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req TrustDeviceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 解析信任持续时间
|
||||
trustDuration := parseDuration(req.TrustDuration)
|
||||
|
||||
if err := h.deviceService.TrustDeviceByDeviceID(c.Request.Context(), userID, deviceID, trustDuration); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "device trusted"})
|
||||
}
|
||||
|
||||
// UntrustDevice 取消设备信任状态
|
||||
func (h *DeviceHandler) UntrustDevice(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid device id"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.deviceService.UntrustDevice(c.Request.Context(), id); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "device untrusted"})
|
||||
}
|
||||
|
||||
// GetMyTrustedDevices 获取我的信任设备列表
|
||||
func (h *DeviceHandler) GetMyTrustedDevices(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
devices, err := h.deviceService.GetTrustedDevices(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"devices": devices})
|
||||
}
|
||||
|
||||
// LogoutAllOtherDevices 登出所有其他设备
|
||||
func (h *DeviceHandler) LogoutAllOtherDevices(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
// 从请求中获取当前设备ID
|
||||
currentDeviceIDStr := c.GetHeader("X-Device-ID")
|
||||
currentDeviceID, err := strconv.ParseInt(currentDeviceIDStr, 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid current device id"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.deviceService.LogoutAllOtherDevices(c.Request.Context(), userID, currentDeviceID); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "all other devices logged out"})
|
||||
}
|
||||
|
||||
// parseDuration 解析duration字符串,如 "30d" -> 30天的time.Duration
|
||||
func parseDuration(s string) time.Duration {
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
// 简单实现,支持 d(天)和h(小时)
|
||||
var d int
|
||||
var h int
|
||||
_, _ = d, h
|
||||
switch s[len(s)-1] {
|
||||
case 'd':
|
||||
d = 1
|
||||
_, _ = fmt.Sscanf(s[:len(s)-1], "%d", &d)
|
||||
return time.Duration(d) * 24 * time.Hour
|
||||
case 'h':
|
||||
_, _ = fmt.Sscanf(s[:len(s)-1], "%d", &h)
|
||||
return time.Duration(h) * time.Hour
|
||||
}
|
||||
return 0
|
||||
}
|
||||
31
internal/api/handler/export_handler.go
Normal file
31
internal/api/handler/export_handler.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// ExportHandler handles user export/import requests
|
||||
type ExportHandler struct {
|
||||
exportService *service.ExportService
|
||||
}
|
||||
|
||||
// NewExportHandler creates a new ExportHandler
|
||||
func NewExportHandler(exportService *service.ExportService) *ExportHandler {
|
||||
return &ExportHandler{exportService: exportService}
|
||||
}
|
||||
|
||||
func (h *ExportHandler) ExportUsers(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "user export not implemented"})
|
||||
}
|
||||
|
||||
func (h *ExportHandler) ImportUsers(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "user import not implemented"})
|
||||
}
|
||||
|
||||
func (h *ExportHandler) GetImportTemplate(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"template": "id,username,email,nickname"})
|
||||
}
|
||||
93
internal/api/handler/log_handler.go
Normal file
93
internal/api/handler/log_handler.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// LogHandler handles log requests
|
||||
type LogHandler struct {
|
||||
loginLogService *service.LoginLogService
|
||||
operationLogService *service.OperationLogService
|
||||
}
|
||||
|
||||
// NewLogHandler creates a new LogHandler
|
||||
func NewLogHandler(loginLogService *service.LoginLogService, operationLogService *service.OperationLogService) *LogHandler {
|
||||
return &LogHandler{
|
||||
loginLogService: loginLogService,
|
||||
operationLogService: operationLogService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *LogHandler) GetMyLoginLogs(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
logs, total, err := h.loginLogService.GetMyLoginLogs(c.Request.Context(), userID, page, pageSize)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"logs": logs,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *LogHandler) GetMyOperationLogs(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"logs": []interface{}{}})
|
||||
}
|
||||
|
||||
func (h *LogHandler) GetLoginLogs(c *gin.Context) {
|
||||
var req service.ListLoginLogRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
logs, total, err := h.loginLogService.GetLoginLogs(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"logs": logs,
|
||||
"total": total,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *LogHandler) GetOperationLogs(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"logs": []interface{}{}})
|
||||
}
|
||||
|
||||
func (h *LogHandler) ExportLoginLogs(c *gin.Context) {
|
||||
var req service.ExportLoginLogRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
data, filename, contentType, err := h.loginLogService.ExportLoginLogs(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
||||
c.Data(http.StatusOK, contentType, data)
|
||||
}
|
||||
153
internal/api/handler/password_reset_handler.go
Normal file
153
internal/api/handler/password_reset_handler.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// PasswordResetHandler handles password reset requests
|
||||
type PasswordResetHandler struct {
|
||||
passwordResetService *service.PasswordResetService
|
||||
smsService *service.SMSCodeService
|
||||
}
|
||||
|
||||
// NewPasswordResetHandler creates a new PasswordResetHandler
|
||||
func NewPasswordResetHandler(passwordResetService *service.PasswordResetService) *PasswordResetHandler {
|
||||
return &PasswordResetHandler{passwordResetService: passwordResetService}
|
||||
}
|
||||
|
||||
// NewPasswordResetHandlerWithSMS creates a new PasswordResetHandler with SMS support
|
||||
func NewPasswordResetHandlerWithSMS(passwordResetService *service.PasswordResetService, smsService *service.SMSCodeService) *PasswordResetHandler {
|
||||
return &PasswordResetHandler{
|
||||
passwordResetService: passwordResetService,
|
||||
smsService: smsService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *PasswordResetHandler) ForgotPassword(c *gin.Context) {
|
||||
var req struct {
|
||||
Email string `json:"email" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.passwordResetService.ForgotPassword(c.Request.Context(), req.Email); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "password reset email sent"})
|
||||
}
|
||||
|
||||
func (h *PasswordResetHandler) ValidateResetToken(c *gin.Context) {
|
||||
token := c.Query("token")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "token is required"})
|
||||
return
|
||||
}
|
||||
|
||||
valid, err := h.passwordResetService.ValidateResetToken(c.Request.Context(), token)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"valid": valid})
|
||||
}
|
||||
|
||||
func (h *PasswordResetHandler) ResetPassword(c *gin.Context) {
|
||||
var req struct {
|
||||
Token string `json:"token" binding:"required"`
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.passwordResetService.ResetPassword(c.Request.Context(), req.Token, req.NewPassword); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "password reset successful"})
|
||||
}
|
||||
|
||||
// ForgotPasswordByPhoneRequest 短信密码重置请求
|
||||
type ForgotPasswordByPhoneRequest struct {
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
}
|
||||
|
||||
// ForgotPasswordByPhone 发送短信验证码
|
||||
func (h *PasswordResetHandler) ForgotPasswordByPhone(c *gin.Context) {
|
||||
if h.smsService == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "SMS service not configured"})
|
||||
return
|
||||
}
|
||||
|
||||
var req ForgotPasswordByPhoneRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取验证码(不发送,由调用方通过其他渠道发送)
|
||||
code, err := h.passwordResetService.ForgotPasswordByPhone(c.Request.Context(), req.Phone)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
if code == "" {
|
||||
// 用户不存在,不提示
|
||||
c.JSON(http.StatusOK, gin.H{"message": "verification code sent"})
|
||||
return
|
||||
}
|
||||
|
||||
// 通过SMS服务发送验证码
|
||||
sendReq := &service.SendCodeRequest{
|
||||
Phone: req.Phone,
|
||||
Purpose: "password_reset",
|
||||
}
|
||||
_, err = h.smsService.SendCode(c.Request.Context(), sendReq)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "verification code sent"})
|
||||
}
|
||||
|
||||
// ResetPasswordByPhoneRequest 短信验证码重置密码请求
|
||||
type ResetPasswordByPhoneRequest struct {
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
}
|
||||
|
||||
// ResetPasswordByPhone 通过短信验证码重置密码
|
||||
func (h *PasswordResetHandler) ResetPasswordByPhone(c *gin.Context) {
|
||||
var req ResetPasswordByPhoneRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
err := h.passwordResetService.ResetPasswordByPhone(c.Request.Context(), &service.ResetPasswordByPhoneRequest{
|
||||
Phone: req.Phone,
|
||||
Code: req.Code,
|
||||
NewPassword: req.NewPassword,
|
||||
})
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "password reset successful"})
|
||||
}
|
||||
154
internal/api/handler/permission_handler.go
Normal file
154
internal/api/handler/permission_handler.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/domain"
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// PermissionHandler handles permission management requests
|
||||
type PermissionHandler struct {
|
||||
permissionService *service.PermissionService
|
||||
}
|
||||
|
||||
// NewPermissionHandler creates a new PermissionHandler
|
||||
func NewPermissionHandler(permissionService *service.PermissionService) *PermissionHandler {
|
||||
return &PermissionHandler{permissionService: permissionService}
|
||||
}
|
||||
|
||||
func (h *PermissionHandler) CreatePermission(c *gin.Context) {
|
||||
var req service.CreatePermissionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
perm, err := h.permissionService.CreatePermission(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, perm)
|
||||
}
|
||||
|
||||
func (h *PermissionHandler) ListPermissions(c *gin.Context) {
|
||||
var req service.ListPermissionRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
perms, total, err := h.permissionService.ListPermissions(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"permissions": perms,
|
||||
"total": total,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *PermissionHandler) GetPermission(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid permission id"})
|
||||
return
|
||||
}
|
||||
|
||||
perm, err := h.permissionService.GetPermission(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, perm)
|
||||
}
|
||||
|
||||
func (h *PermissionHandler) UpdatePermission(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid permission id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req service.UpdatePermissionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
perm, err := h.permissionService.UpdatePermission(c.Request.Context(), id, &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, perm)
|
||||
}
|
||||
|
||||
func (h *PermissionHandler) DeletePermission(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid permission id"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.permissionService.DeletePermission(c.Request.Context(), id); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "permission deleted"})
|
||||
}
|
||||
|
||||
func (h *PermissionHandler) UpdatePermissionStatus(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid permission id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var status domain.PermissionStatus
|
||||
switch req.Status {
|
||||
case "enabled", "1":
|
||||
status = domain.PermissionStatusEnabled
|
||||
case "disabled", "0":
|
||||
status = domain.PermissionStatusDisabled
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid status"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.permissionService.UpdatePermissionStatus(c.Request.Context(), id, status); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "status updated"})
|
||||
}
|
||||
|
||||
func (h *PermissionHandler) GetPermissionTree(c *gin.Context) {
|
||||
tree, err := h.permissionService.GetPermissionTree(c.Request.Context())
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"permissions": tree})
|
||||
}
|
||||
186
internal/api/handler/role_handler.go
Normal file
186
internal/api/handler/role_handler.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/domain"
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// RoleHandler handles role management requests
|
||||
type RoleHandler struct {
|
||||
roleService *service.RoleService
|
||||
}
|
||||
|
||||
// NewRoleHandler creates a new RoleHandler
|
||||
func NewRoleHandler(roleService *service.RoleService) *RoleHandler {
|
||||
return &RoleHandler{roleService: roleService}
|
||||
}
|
||||
|
||||
func (h *RoleHandler) CreateRole(c *gin.Context) {
|
||||
var req service.CreateRoleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
role, err := h.roleService.CreateRole(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, role)
|
||||
}
|
||||
|
||||
func (h *RoleHandler) ListRoles(c *gin.Context) {
|
||||
var req service.ListRoleRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
roles, total, err := h.roleService.ListRoles(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"roles": roles,
|
||||
"total": total,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *RoleHandler) GetRole(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role id"})
|
||||
return
|
||||
}
|
||||
|
||||
role, err := h.roleService.GetRole(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, role)
|
||||
}
|
||||
|
||||
func (h *RoleHandler) UpdateRole(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req service.UpdateRoleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
role, err := h.roleService.UpdateRole(c.Request.Context(), id, &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, role)
|
||||
}
|
||||
|
||||
func (h *RoleHandler) DeleteRole(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role id"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.roleService.DeleteRole(c.Request.Context(), id); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "role deleted"})
|
||||
}
|
||||
|
||||
func (h *RoleHandler) UpdateRoleStatus(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var status domain.RoleStatus
|
||||
switch req.Status {
|
||||
case "enabled", "1":
|
||||
status = domain.RoleStatusEnabled
|
||||
case "disabled", "0":
|
||||
status = domain.RoleStatusDisabled
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid status"})
|
||||
return
|
||||
}
|
||||
|
||||
err = h.roleService.UpdateRoleStatus(c.Request.Context(), id, status)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "status updated"})
|
||||
}
|
||||
|
||||
func (h *RoleHandler) GetRolePermissions(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role id"})
|
||||
return
|
||||
}
|
||||
|
||||
perms, err := h.roleService.GetRolePermissions(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"permissions": perms})
|
||||
}
|
||||
|
||||
func (h *RoleHandler) AssignPermissions(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
PermissionIDs []int64 `json:"permission_ids"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
err = h.roleService.AssignPermissions(c.Request.Context(), id, req.PermissionIDs)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "permissions assigned"})
|
||||
}
|
||||
23
internal/api/handler/sms_handler.go
Normal file
23
internal/api/handler/sms_handler.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// SMSHandler handles SMS requests
|
||||
type SMSHandler struct{}
|
||||
|
||||
// NewSMSHandler creates a new SMSHandler
|
||||
func NewSMSHandler() *SMSHandler {
|
||||
return &SMSHandler{}
|
||||
}
|
||||
|
||||
func (h *SMSHandler) SendCode(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "SMS not configured"})
|
||||
}
|
||||
|
||||
func (h *SMSHandler) LoginByCode(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"error": "SMS login not configured"})
|
||||
}
|
||||
236
internal/api/handler/sso_handler.go
Normal file
236
internal/api/handler/sso_handler.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/auth"
|
||||
)
|
||||
|
||||
// SSOHandler SSO 处理程序
|
||||
type SSOHandler struct {
|
||||
ssoManager *auth.SSOManager
|
||||
}
|
||||
|
||||
// NewSSOHandler 创建 SSO 处理程序
|
||||
func NewSSOHandler(ssoManager *auth.SSOManager) *SSOHandler {
|
||||
return &SSOHandler{ssoManager: ssoManager}
|
||||
}
|
||||
|
||||
// AuthorizeRequest 授权请求
|
||||
type AuthorizeRequest struct {
|
||||
ClientID string `form:"client_id" binding:"required"`
|
||||
RedirectURI string `form:"redirect_uri" binding:"required"`
|
||||
ResponseType string `form:"response_type" binding:"required"`
|
||||
Scope string `form:"scope"`
|
||||
State string `form:"state"`
|
||||
}
|
||||
|
||||
// Authorize 处理 SSO 授权请求
|
||||
// GET /api/v1/sso/authorize?client_id=xxx&redirect_uri=xxx&response_type=code&scope=openid&state=xxx
|
||||
func (h *SSOHandler) Authorize(c *gin.Context) {
|
||||
var req AuthorizeRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证 response_type
|
||||
if req.ResponseType != "code" && req.ResponseType != "token" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "unsupported response_type"})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前登录用户(从 auth middleware 设置的 context)
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
username, _ := c.Get("username")
|
||||
|
||||
// 生成授权码或 access token
|
||||
if req.ResponseType == "code" {
|
||||
code, err := h.ssoManager.GenerateAuthorizationCode(
|
||||
req.ClientID,
|
||||
req.RedirectURI,
|
||||
req.Scope,
|
||||
userID.(int64),
|
||||
username.(string),
|
||||
)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate code"})
|
||||
return
|
||||
}
|
||||
|
||||
// 重定向回客户端
|
||||
redirectURL := req.RedirectURI + "?code=" + code
|
||||
if req.State != "" {
|
||||
redirectURL += "&state=" + req.State
|
||||
}
|
||||
c.Redirect(http.StatusFound, redirectURL)
|
||||
} else {
|
||||
// implicit 模式,直接返回 token
|
||||
code, err := h.ssoManager.GenerateAuthorizationCode(
|
||||
req.ClientID,
|
||||
req.RedirectURI,
|
||||
req.Scope,
|
||||
userID.(int64),
|
||||
username.(string),
|
||||
)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate code"})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证授权码获取 session
|
||||
session, err := h.ssoManager.ValidateAuthorizationCode(code)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to validate code"})
|
||||
return
|
||||
}
|
||||
|
||||
token, _ := h.ssoManager.GenerateAccessToken(req.ClientID, session)
|
||||
|
||||
// 重定向回客户端,带 token
|
||||
redirectURL := req.RedirectURI + "#access_token=" + token + "&expires_in=7200"
|
||||
if req.State != "" {
|
||||
redirectURL += "&state=" + req.State
|
||||
}
|
||||
c.Redirect(http.StatusFound, redirectURL)
|
||||
}
|
||||
}
|
||||
|
||||
// TokenRequest Token 请求
|
||||
type TokenRequest struct {
|
||||
GrantType string `form:"grant_type" binding:"required"`
|
||||
Code string `form:"code"`
|
||||
RedirectURI string `form:"redirect_uri"`
|
||||
ClientID string `form:"client_id" binding:"required"`
|
||||
ClientSecret string `form:"client_secret" binding:"required"`
|
||||
}
|
||||
|
||||
// TokenResponse Token 响应
|
||||
type TokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
// Token 处理 Token 请求(授权码模式第二步)
|
||||
// POST /api/v1/sso/token
|
||||
func (h *SSOHandler) Token(c *gin.Context) {
|
||||
var req TokenRequest
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证 grant_type
|
||||
if req.GrantType != "authorization_code" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "unsupported grant_type"})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证授权码
|
||||
session, err := h.ssoManager.ValidateAuthorizationCode(req.Code)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid code"})
|
||||
return
|
||||
}
|
||||
|
||||
// 生成 access token
|
||||
token, expiresAt := h.ssoManager.GenerateAccessToken(req.ClientID, session)
|
||||
|
||||
c.JSON(http.StatusOK, TokenResponse{
|
||||
AccessToken: token,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: int64(time.Until(expiresAt).Seconds()),
|
||||
Scope: session.Scope,
|
||||
})
|
||||
}
|
||||
|
||||
// IntrospectRequest Introspect 请求
|
||||
type IntrospectRequest struct {
|
||||
Token string `form:"token" binding:"required"`
|
||||
ClientID string `form:"client_id"`
|
||||
}
|
||||
|
||||
// IntrospectResponse Introspect 响应
|
||||
type IntrospectResponse struct {
|
||||
Active bool `json:"active"`
|
||||
UserID int64 `json:"user_id,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
ExpiresAt int64 `json:"exp,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
}
|
||||
|
||||
// Introspect 验证 access token
|
||||
// POST /api/v1/sso/introspect
|
||||
func (h *SSOHandler) Introspect(c *gin.Context) {
|
||||
var req IntrospectRequest
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
info, err := h.ssoManager.IntrospectToken(req.Token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, IntrospectResponse{Active: false})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, IntrospectResponse{
|
||||
Active: info.Active,
|
||||
UserID: info.UserID,
|
||||
Username: info.Username,
|
||||
ExpiresAt: info.ExpiresAt.Unix(),
|
||||
Scope: info.Scope,
|
||||
})
|
||||
}
|
||||
|
||||
// RevokeRequest 撤销请求
|
||||
type RevokeRequest struct {
|
||||
Token string `form:"token" binding:"required"`
|
||||
}
|
||||
|
||||
// Revoke 撤销 access token
|
||||
// POST /api/v1/sso/revoke
|
||||
func (h *SSOHandler) Revoke(c *gin.Context) {
|
||||
var req RevokeRequest
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
h.ssoManager.RevokeToken(req.Token)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "token revoked"})
|
||||
}
|
||||
|
||||
// UserInfoResponse 用户信息响应
|
||||
type UserInfoResponse struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// UserInfo 获取当前用户信息(SSO 专用)
|
||||
// GET /api/v1/sso/userinfo
|
||||
func (h *SSOHandler) UserInfo(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
username, _ := c.Get("username")
|
||||
|
||||
c.JSON(http.StatusOK, UserInfoResponse{
|
||||
UserID: userID.(int64),
|
||||
Username: username.(string),
|
||||
})
|
||||
}
|
||||
27
internal/api/handler/stats_handler.go
Normal file
27
internal/api/handler/stats_handler.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// StatsHandler handles statistics requests
|
||||
type StatsHandler struct {
|
||||
statsService *service.StatsService
|
||||
}
|
||||
|
||||
// NewStatsHandler creates a new StatsHandler
|
||||
func NewStatsHandler(statsService *service.StatsService) *StatsHandler {
|
||||
return &StatsHandler{statsService: statsService}
|
||||
}
|
||||
|
||||
func (h *StatsHandler) GetDashboard(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "dashboard stats not implemented"})
|
||||
}
|
||||
|
||||
func (h *StatsHandler) GetUserStats(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "user stats not implemented"})
|
||||
}
|
||||
153
internal/api/handler/theme_handler.go
Normal file
153
internal/api/handler/theme_handler.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// ThemeHandler 主题配置处理器
|
||||
type ThemeHandler struct {
|
||||
themeService *service.ThemeService
|
||||
}
|
||||
|
||||
// NewThemeHandler 创建主题配置处理器
|
||||
func NewThemeHandler(themeService *service.ThemeService) *ThemeHandler {
|
||||
return &ThemeHandler{themeService: themeService}
|
||||
}
|
||||
|
||||
// CreateTheme 创建主题
|
||||
func (h *ThemeHandler) CreateTheme(c *gin.Context) {
|
||||
var req service.CreateThemeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
theme, err := h.themeService.CreateTheme(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, theme)
|
||||
}
|
||||
|
||||
// UpdateTheme 更新主题
|
||||
func (h *ThemeHandler) UpdateTheme(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid theme id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req service.UpdateThemeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
theme, err := h.themeService.UpdateTheme(c.Request.Context(), id, &req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, theme)
|
||||
}
|
||||
|
||||
// DeleteTheme 删除主题
|
||||
func (h *ThemeHandler) DeleteTheme(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid theme id"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.themeService.DeleteTheme(c.Request.Context(), id); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "theme deleted"})
|
||||
}
|
||||
|
||||
// GetTheme 获取主题
|
||||
func (h *ThemeHandler) GetTheme(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid theme id"})
|
||||
return
|
||||
}
|
||||
|
||||
theme, err := h.themeService.GetTheme(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, theme)
|
||||
}
|
||||
|
||||
// ListThemes 获取所有主题
|
||||
func (h *ThemeHandler) ListThemes(c *gin.Context) {
|
||||
themes, err := h.themeService.ListThemes(c.Request.Context())
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"themes": themes})
|
||||
}
|
||||
|
||||
// ListAllThemes 获取所有主题(包括禁用的)
|
||||
func (h *ThemeHandler) ListAllThemes(c *gin.Context) {
|
||||
themes, err := h.themeService.ListAllThemes(c.Request.Context())
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"themes": themes})
|
||||
}
|
||||
|
||||
// GetDefaultTheme 获取默认主题
|
||||
func (h *ThemeHandler) GetDefaultTheme(c *gin.Context) {
|
||||
theme, err := h.themeService.GetDefaultTheme(c.Request.Context())
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, theme)
|
||||
}
|
||||
|
||||
// SetDefaultTheme 设置默认主题
|
||||
func (h *ThemeHandler) SetDefaultTheme(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid theme id"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.themeService.SetDefaultTheme(c.Request.Context(), id); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "default theme set"})
|
||||
}
|
||||
|
||||
// GetActiveTheme 获取当前生效的主题(公开接口)
|
||||
func (h *ThemeHandler) GetActiveTheme(c *gin.Context) {
|
||||
theme, err := h.themeService.GetActiveTheme(c.Request.Context())
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, theme)
|
||||
}
|
||||
132
internal/api/handler/totp_handler.go
Normal file
132
internal/api/handler/totp_handler.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// TOTPHandler handles TOTP 2FA requests
|
||||
type TOTPHandler struct {
|
||||
authService *service.AuthService
|
||||
totpService *service.TOTPService
|
||||
}
|
||||
|
||||
// NewTOTPHandler creates a new TOTPHandler
|
||||
func NewTOTPHandler(authService *service.AuthService, totpService *service.TOTPService) *TOTPHandler {
|
||||
return &TOTPHandler{
|
||||
authService: authService,
|
||||
totpService: totpService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TOTPHandler) GetTOTPStatus(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
enabled, err := h.totpService.GetTOTPStatus(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"enabled": enabled})
|
||||
}
|
||||
|
||||
func (h *TOTPHandler) SetupTOTP(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.totpService.SetupTOTP(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"secret": resp.Secret,
|
||||
"qr_code_base64": resp.QRCodeBase64,
|
||||
"recovery_codes": resp.RecoveryCodes,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *TOTPHandler) EnableTOTP(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Code string `json:"code" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.totpService.EnableTOTP(c.Request.Context(), userID, req.Code); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "TOTP enabled"})
|
||||
}
|
||||
|
||||
func (h *TOTPHandler) DisableTOTP(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Code string `json:"code" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.totpService.DisableTOTP(c.Request.Context(), userID, req.Code); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "TOTP disabled"})
|
||||
}
|
||||
|
||||
func (h *TOTPHandler) VerifyTOTP(c *gin.Context) {
|
||||
userID, ok := getUserIDFromContext(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Code string `json:"code" binding:"required"`
|
||||
DeviceID string `json:"device_id,omitempty"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.authService.VerifyTOTP(c.Request.Context(), userID, req.Code, req.DeviceID); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"verified": true})
|
||||
}
|
||||
261
internal/api/handler/user_handler.go
Normal file
261
internal/api/handler/user_handler.go
Normal file
@@ -0,0 +1,261 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/auth"
|
||||
"github.com/user-management-system/internal/domain"
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// UserHandler handles user management requests
|
||||
type UserHandler struct {
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewUserHandler creates a new UserHandler
|
||||
func NewUserHandler(userService *service.UserService) *UserHandler {
|
||||
return &UserHandler{userService: userService}
|
||||
}
|
||||
|
||||
func (h *UserHandler) CreateUser(c *gin.Context) {
|
||||
var req struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
user := &domain.User{
|
||||
Username: req.Username,
|
||||
Email: domain.StrPtr(req.Email),
|
||||
Nickname: req.Nickname,
|
||||
Status: domain.UserStatusActive,
|
||||
}
|
||||
|
||||
if req.Password != "" {
|
||||
hashed, err := auth.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to hash password"})
|
||||
return
|
||||
}
|
||||
user.Password = hashed
|
||||
}
|
||||
|
||||
if err := h.userService.Create(c.Request.Context(), user); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, toUserResponse(user))
|
||||
}
|
||||
|
||||
func (h *UserHandler) ListUsers(c *gin.Context) {
|
||||
offset, _ := strconv.ParseInt(c.DefaultQuery("offset", "0"), 10, 64)
|
||||
limit, _ := strconv.ParseInt(c.DefaultQuery("limit", "20"), 10, 64)
|
||||
|
||||
users, total, err := h.userService.List(c.Request.Context(), int(offset), int(limit))
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
userResponses := make([]*UserResponse, len(users))
|
||||
for i, u := range users {
|
||||
userResponses[i] = toUserResponse(u)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"users": userResponses,
|
||||
"total": total,
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetUser(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.userService.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, toUserResponse(user))
|
||||
}
|
||||
|
||||
func (h *UserHandler) UpdateUser(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Email *string `json:"email"`
|
||||
Nickname *string `json:"nickname"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.userService.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email != nil {
|
||||
user.Email = req.Email
|
||||
}
|
||||
if req.Nickname != nil {
|
||||
user.Nickname = *req.Nickname
|
||||
}
|
||||
|
||||
if err := h.userService.Update(c.Request.Context(), user); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, toUserResponse(user))
|
||||
}
|
||||
|
||||
func (h *UserHandler) DeleteUser(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.userService.Delete(c.Request.Context(), id); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "user deleted"})
|
||||
}
|
||||
|
||||
func (h *UserHandler) UpdatePassword(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
OldPassword string `json:"old_password" binding:"required"`
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.userService.ChangePassword(c.Request.Context(), id, req.OldPassword, req.NewPassword); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "密码修改成功"})
|
||||
}
|
||||
|
||||
func (h *UserHandler) UpdateUserStatus(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var status domain.UserStatus
|
||||
switch req.Status {
|
||||
case "active", "1":
|
||||
status = domain.UserStatusActive
|
||||
case "inactive", "0":
|
||||
status = domain.UserStatusInactive
|
||||
case "locked", "2":
|
||||
status = domain.UserStatusLocked
|
||||
case "disabled", "3":
|
||||
status = domain.UserStatusDisabled
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid status"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.userService.UpdateStatus(c.Request.Context(), id, status); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "status updated"})
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetUserRoles(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"roles": []interface{}{}})
|
||||
}
|
||||
|
||||
func (h *UserHandler) AssignRoles(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "role assignment not implemented"})
|
||||
}
|
||||
|
||||
func (h *UserHandler) UploadAvatar(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "avatar upload not implemented"})
|
||||
}
|
||||
|
||||
func (h *UserHandler) ListAdmins(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"admins": []interface{}{}})
|
||||
}
|
||||
|
||||
func (h *UserHandler) CreateAdmin(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "admin creation not implemented"})
|
||||
}
|
||||
|
||||
func (h *UserHandler) DeleteAdmin(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "admin deletion not implemented"})
|
||||
}
|
||||
|
||||
type UserResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Nickname string `json:"nickname,omitempty"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func toUserResponse(u *domain.User) *UserResponse {
|
||||
email := ""
|
||||
if u.Email != nil {
|
||||
email = *u.Email
|
||||
}
|
||||
return &UserResponse{
|
||||
ID: u.ID,
|
||||
Username: u.Username,
|
||||
Email: email,
|
||||
Nickname: u.Nickname,
|
||||
Status: strconv.FormatInt(int64(u.Status), 10),
|
||||
}
|
||||
}
|
||||
39
internal/api/handler/webhook_handler.go
Normal file
39
internal/api/handler/webhook_handler.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/user-management-system/internal/service"
|
||||
)
|
||||
|
||||
// WebhookHandler handles webhook requests
|
||||
type WebhookHandler struct {
|
||||
webhookService *service.WebhookService
|
||||
}
|
||||
|
||||
// NewWebhookHandler creates a new WebhookHandler
|
||||
func NewWebhookHandler(webhookService *service.WebhookService) *WebhookHandler {
|
||||
return &WebhookHandler{webhookService: webhookService}
|
||||
}
|
||||
|
||||
func (h *WebhookHandler) CreateWebhook(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "webhook creation not implemented"})
|
||||
}
|
||||
|
||||
func (h *WebhookHandler) ListWebhooks(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"webhooks": []interface{}{}})
|
||||
}
|
||||
|
||||
func (h *WebhookHandler) UpdateWebhook(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "webhook update not implemented"})
|
||||
}
|
||||
|
||||
func (h *WebhookHandler) DeleteWebhook(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "webhook deletion not implemented"})
|
||||
}
|
||||
|
||||
func (h *WebhookHandler) GetWebhookDeliveries(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"deliveries": []interface{}{}})
|
||||
}
|
||||
Reference in New Issue
Block a user