191 lines
6.1 KiB
Go
191 lines
6.1 KiB
Go
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)
|
||
}
|