Files
EveryPublish/server/internal/api/handlers/member.go
T

191 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handlers
import (
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"everypublish/server/internal/api/response"
"everypublish/server/internal/auth"
"everypublish/server/internal/config"
"everypublish/server/internal/models"
)
// MemberHandler 成员处理器
type MemberHandler struct {
DB *gorm.DB
Cfg *config.Config
}
type inviteReq struct {
Email string `json:"email" binding:"required"`
Role string `json:"role" binding:"required"`
}
type joinReq struct {
Token string `json:"token" binding:"required"`
}
type roleReq struct {
Role string `json:"role" binding:"required"`
}
var memberRoles = map[string]bool{"admin": true, "operator": true, "reviewer": true, "viewer": true}
func isAdminRole(mrole string) bool { return mrole == "owner" || mrole == "admin" }
type memberRow struct {
models.Member
Email string `json:"email"`
Nickname string `json:"nickname"`
}
// List 成员列表(含用户资料)
func (h *MemberHandler) List(c *gin.Context) {
wsid := c.GetUint64("wsid")
var rows []memberRow
h.DB.Table("members").Select("members.*, users.email as email, users.nickname as nickname").
Joins("join users on users.id = members.user_id").
Where("members.workspace_id = ?", wsid).Order("members.id asc").Scan(&rows)
response.OK(c, gin.H{"list": rows})
}
// Invite 生成邀请链接(owner/admin)
func (h *MemberHandler) Invite(c *gin.Context) {
if !isAdminRole(c.GetString("mrole")) {
response.Fail(c, http.StatusForbidden, 1003, "仅管理员可邀请成员")
return
}
var req inviteReq
if err := c.ShouldBindJSON(&req); err != nil {
response.Fail(c, http.StatusBadRequest, 1001, "参数不合法")
return
}
req.Role = strings.ToLower(strings.TrimSpace(req.Role))
if !memberRoles[req.Role] {
response.Fail(c, http.StatusBadRequest, 1001, "角色不合法")
return
}
wsid := c.GetUint64("wsid")
var existing memberRow
err := h.DB.Table("members").Select("members.id").
Joins("join users on users.id = members.user_id").
Where("members.workspace_id = ? and users.email = ?", wsid, strings.ToLower(req.Email)).
Scan(&existing).Error
if err == nil && existing.ID > 0 {
response.Fail(c, http.StatusConflict, 2004, "该邮箱已是成员")
return
}
token, err := auth.IssueInvite(h.Cfg.JWTSecret, wsid, strings.ToLower(req.Email), req.Role)
if err != nil {
response.Fail(c, http.StatusInternalServerError, 5000, "生成邀请失败")
return
}
response.Audit(c, "member.invite", "workspace:"+itoa(wsid), gin.H{"email": req.Email, "role": req.Role})
response.OK(c, gin.H{"token": token, "link": h.Cfg.PublicBaseURL + "/invite?t=" + token})
}
// Join 接受邀请(需登录,邮箱须与邀请一致)
func (h *MemberHandler) Join(c *gin.Context) {
var req joinReq
if err := c.ShouldBindJSON(&req); err != nil {
response.Fail(c, http.StatusBadRequest, 1001, "参数不合法")
return
}
claims, err := auth.ParseInvite(h.Cfg.JWTSecret, req.Token)
if err != nil {
response.Fail(c, http.StatusBadRequest, 2005, "邀请链接无效或已过期")
return
}
var user models.User
if err = h.DB.First(&user, c.GetUint64("uid")).Error; err != nil {
response.Fail(c, http.StatusUnauthorized, 1002, "用户不存在")
return
}
if !strings.EqualFold(user.Email, claims.Email) {
response.Fail(c, http.StatusForbidden, 1003, "邀请链接与当前账号邮箱不匹配")
return
}
member := models.Member{WorkspaceID: claims.WorkspaceID, UserID: user.ID, Role: claims.Role}
err = h.DB.Where("workspace_id = ? and user_id = ?", claims.WorkspaceID, user.ID).First(&models.Member{}).Error
if err == nil {
response.Fail(c, http.StatusConflict, 2004, "你已是该工作空间成员")
return
}
if err = h.DB.Create(&member).Error; err != nil {
response.Fail(c, http.StatusInternalServerError, 5000, "加入失败")
return
}
response.Audit(c, "member.join", "workspace:"+itoa(claims.WorkspaceID), gin.H{"role": claims.Role})
response.OK(c, nil)
}
// UpdateRole 修改成员角色(owner/admin;不可改 owner)
func (h *MemberHandler) UpdateRole(c *gin.Context) {
if !isAdminRole(c.GetString("mrole")) {
response.Fail(c, http.StatusForbidden, 1003, "仅管理员可修改角色")
return
}
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.Fail(c, http.StatusBadRequest, 1001, "参数不合法")
return
}
var req roleReq
if err = c.ShouldBindJSON(&req); err != nil {
response.Fail(c, http.StatusBadRequest, 1001, "参数不合法")
return
}
req.Role = strings.ToLower(strings.TrimSpace(req.Role))
if !memberRoles[req.Role] && req.Role != "owner" {
response.Fail(c, http.StatusBadRequest, 1001, "角色不合法")
return
}
var member models.Member
if err = h.DB.Where("id = ? and workspace_id = ?", id, c.GetUint64("wsid")).First(&member).Error; err != nil {
response.Fail(c, http.StatusNotFound, 1004, "成员不存在")
return
}
if member.Role == "owner" {
response.Fail(c, http.StatusForbidden, 1003, "不可修改 owner 角色")
return
}
if err = h.DB.Model(&member).Update("role", req.Role).Error; err != nil {
response.Fail(c, http.StatusInternalServerError, 5000, "更新失败")
return
}
response.Audit(c, "member.role", "member:"+itoa(id), gin.H{"role": req.Role})
response.OK(c, nil)
}
// Remove 移除成员(owner/admin;不可移除 owner)
func (h *MemberHandler) Remove(c *gin.Context) {
if !isAdminRole(c.GetString("mrole")) {
response.Fail(c, http.StatusForbidden, 1003, "仅管理员可移除成员")
return
}
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.Fail(c, http.StatusBadRequest, 1001, "参数不合法")
return
}
var member models.Member
if err = h.DB.Where("id = ? and workspace_id = ?", id, c.GetUint64("wsid")).First(&member).Error; err != nil {
response.Fail(c, http.StatusNotFound, 1004, "成员不存在")
return
}
if member.Role == "owner" {
response.Fail(c, http.StatusForbidden, 1003, "不可移除 owner")
return
}
if err = h.DB.Delete(&member).Error; err != nil {
response.Fail(c, http.StatusInternalServerError, 5000, "移除失败")
return
}
response.Audit(c, "member.remove", "member:"+itoa(id), nil)
response.OK(c, nil)
}