You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/internal/api/org/handler.go

512 lines
14 KiB
Go

package org
import (
"fmt"
"strings"
"time"
"github.com/veypi/vbase/internal/api/middleware"
"github.com/veypi/vbase/internal/model"
"github.com/veypi/vigo"
"gorm.io/gorm"
)
// ListRequest 组织列表请求
type ListRequest struct {
Page int `json:"page" src:"query" default:"1" desc:"页码"`
PageSize int `json:"page_size" src:"query" default:"10" desc:"每页数量"`
Keyword string `json:"keyword" src:"query" desc:"搜索关键词"`
}
// ListResponse 组织列表响应
type ListResponse struct {
Items []OrgInfo `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalPages int `json:"total_pages"`
}
// OrgInfo 组织信息
type OrgInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
OwnerID string `json:"owner_id"`
ParentID string `json:"parent_id,omitempty"`
Path string `json:"path"`
Level int `json:"level"`
LeaderID string `json:"leader_id,omitempty"`
Description string `json:"description"`
Logo string `json:"logo"`
Status int `json:"status"`
MaxMembers int `json:"max_members"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
// 当前用户在该组织的信息
MyRoles []string `json:"my_roles,omitempty"`
MyStatus int `json:"my_status,omitempty"`
}
// List 获取当前用户的组织列表
func List(x *vigo.X, req *ListRequest) (*ListResponse, error) {
userID := middleware.CurrentUser(x)
if userID == "" {
return nil, vigo.ErrNotAuthorized
}
if req.Page < 1 {
req.Page = 1
}
if req.PageSize < 1 || req.PageSize > 100 {
req.PageSize = 10
}
// 查询用户所属的组织ID
var memberOrgs []model.OrgMember
if err := model.DB.Where("user_id = ? AND status = ?", userID, model.MemberStatusActive).Find(&memberOrgs).Error; err != nil {
return nil, vigo.ErrInternalServer.WithError(err)
}
if len(memberOrgs) == 0 {
return &ListResponse{
Items: []OrgInfo{},
Total: 0,
Page: req.Page,
PageSize: req.PageSize,
TotalPages: 0,
}, nil
}
orgIDs := make([]string, 0, len(memberOrgs))
memberMap := make(map[string]*model.OrgMember)
for _, m := range memberOrgs {
orgIDs = append(orgIDs, m.OrgID)
memberMap[m.OrgID] = &m
}
// 查询组织详情
var total int64
query := model.DB.Model(&model.Org{}).Where("id IN ?", orgIDs)
if req.Keyword != "" {
query = query.Where("name LIKE ? OR code LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
if err := query.Count(&total).Error; err != nil {
return nil, vigo.ErrInternalServer.WithError(err)
}
var orgs []model.Org
offset := (req.Page - 1) * req.PageSize
if err := query.Offset(offset).Limit(req.PageSize).Order("created_at DESC").Find(&orgs).Error; err != nil {
return nil, vigo.ErrInternalServer.WithError(err)
}
items := make([]OrgInfo, 0, len(orgs))
for _, o := range orgs {
info := toOrgInfo(&o)
if m, ok := memberMap[o.ID]; ok {
info.MyRoles = parseRoles(m.RoleIDs)
info.MyStatus = m.Status
}
items = append(items, info)
}
totalPages := int((total + int64(req.PageSize) - 1) / int64(req.PageSize))
return &ListResponse{
Items: items,
Total: total,
Page: req.Page,
PageSize: req.PageSize,
TotalPages: totalPages,
}, nil
}
// CreateRequest 创建组织请求
type CreateRequest struct {
Name string `json:"name" src:"json" desc:"组织名称"`
Code string `json:"code" src:"json" desc:"组织编码"`
ParentID *string `json:"parent_id" src:"json" desc:"父组织ID"`
Description *string `json:"description" src:"json" desc:"描述"`
Logo *string `json:"logo" src:"json" desc:"Logo"`
MaxMembers *int `json:"max_members" src:"json" desc:"最大成员数"`
}
// Create 创建组织
func Create(x *vigo.X, req *CreateRequest) (*OrgInfo, error) {
userID := middleware.CurrentUser(x)
if userID == "" {
return nil, vigo.ErrNotAuthorized
}
// 检查编码是否已存在
var count int64
model.DB.Model(&model.Org{}).Where("code = ?", req.Code).Count(&count)
if count > 0 {
return nil, vigo.ErrArgInvalid.WithString("organization code already exists")
}
// 构建组织路径
path := "/" + req.Code
level := 0
if req.ParentID != nil && *req.ParentID != "" {
var parent model.Org
if err := model.DB.First(&parent, "id = ?", *req.ParentID).Error; err != nil {
return nil, vigo.ErrArgInvalid.WithString("parent organization not found")
}
path = parent.Path + "/" + req.Code
level = parent.Level + 1
}
org := &model.Org{
Name: req.Name,
Code: req.Code,
OwnerID: userID,
Path: path,
Level: level,
Status: model.OrgStatusActive,
MaxMembers: 100,
}
if req.ParentID != nil {
org.ParentID = req.ParentID
}
if req.Description != nil {
org.Description = *req.Description
}
if req.Logo != nil {
org.Logo = *req.Logo
}
if req.MaxMembers != nil {
org.MaxMembers = *req.MaxMembers
}
err := model.DB.Transaction(func(tx *gorm.DB) error {
// 创建组织
if err := tx.Create(org).Error; err != nil {
return err
}
// 创建成员关系(所有者)
member := &model.OrgMember{
OrgID: org.ID,
UserID: userID,
Status: model.MemberStatusActive,
JoinedAt: time.Now().Format("2006-01-02 15:04:05"),
}
return tx.Create(member).Error
})
if err != nil {
return nil, vigo.ErrInternalServer.WithError(err)
}
info := toOrgInfo(org)
return &info, nil
}
// GetRequest 获取组织请求
type GetRequest struct {
ID string `json:"id" src:"path@org_id" desc:"组织ID"`
}
// Get 获取组织详情
func Get(x *vigo.X, req *GetRequest) (*OrgInfo, error) {
userID := middleware.CurrentUser(x)
if userID == "" {
return nil, vigo.ErrNotAuthorized
}
var org model.Org
if err := model.DB.First(&org, "id = ?", req.ID).Error; err != nil {
return nil, vigo.ErrNotFound
}
// 检查用户是否是组织成员
var member model.OrgMember
if err := model.DB.Where("org_id = ? AND user_id = ?", req.ID, userID).First(&member).Error; err != nil {
return nil, vigo.ErrForbidden.WithString("you are not a member of this organization")
}
info := toOrgInfo(&org)
info.MyRoles = parseRoles(member.RoleIDs)
info.MyStatus = member.Status
return &info, nil
}
// UpdateRequest 更新组织请求
type UpdateRequest struct {
ID string `json:"id" src:"path@org_id" desc:"组织ID"`
Name *string `json:"name" src:"json" desc:"组织名称"`
Description *string `json:"description" src:"json" desc:"描述"`
Logo *string `json:"logo" src:"json" desc:"Logo"`
LeaderID *string `json:"leader_id" src:"json" desc:"负责人ID"`
MaxMembers *int `json:"max_members" src:"json" desc:"最大成员数"`
Status *int `json:"status" src:"json" desc:"状态"`
}
// Update 更新组织
func Update(x *vigo.X, req *UpdateRequest) (*OrgInfo, error) {
userID := middleware.CurrentUser(x)
if userID == "" {
return nil, vigo.ErrNotAuthorized
}
var org model.Org
if err := model.DB.First(&org, "id = ?", req.ID).Error; err != nil {
return nil, vigo.ErrNotFound
}
// 检查权限(只有所有者可以修改)
if org.OwnerID != userID {
return nil, vigo.ErrForbidden.WithString("only organization owner can update")
}
updates := make(map[string]interface{})
if req.Name != nil {
updates["name"] = *req.Name
}
if req.Description != nil {
updates["description"] = *req.Description
}
if req.Logo != nil {
updates["logo"] = *req.Logo
}
if req.LeaderID != nil {
updates["leader_id"] = *req.LeaderID
}
if req.MaxMembers != nil {
updates["max_members"] = *req.MaxMembers
}
if req.Status != nil {
updates["status"] = *req.Status
}
if len(updates) > 0 {
if err := model.DB.Model(&org).Updates(updates).Error; err != nil {
return nil, vigo.ErrInternalServer.WithError(err)
}
}
info := toOrgInfo(&org)
return &info, nil
}
// DeleteRequest 删除组织请求
type DeleteRequest struct {
ID string `json:"id" src:"path@org_id" desc:"组织ID"`
}
// Delete 删除组织
func Delete(x *vigo.X, req *DeleteRequest) error {
userID := middleware.CurrentUser(x)
if userID == "" {
return vigo.ErrNotAuthorized
}
var org model.Org
if err := model.DB.First(&org, "id = ?", req.ID).Error; err != nil {
return vigo.ErrNotFound
}
// 检查权限(只有所有者可以删除)
if org.OwnerID != userID {
return vigo.ErrForbidden.WithString("only organization owner can delete")
}
// 检查是否有子组织
var childCount int64
model.DB.Model(&model.Org{}).Where("parent_id = ?", req.ID).Count(&childCount)
if childCount > 0 {
return vigo.ErrArgInvalid.WithString("cannot delete organization with sub-organizations")
}
// 软删除
if err := model.DB.Delete(&org).Error; err != nil {
return vigo.ErrInternalServer.WithError(err)
}
return nil
}
// TreeResponse 组织树响应
type TreeResponse struct {
OrgInfo
Children []TreeResponse `json:"children,omitempty"`
}
// Tree 获取组织树
func Tree(x *vigo.X) ([]TreeResponse, error) {
userID := middleware.CurrentUser(x)
if userID == "" {
return nil, vigo.ErrNotAuthorized
}
// 获取用户所属的所有组织ID
var memberOrgs []model.OrgMember
if err := model.DB.Where("user_id = ? AND status = ?", userID, model.MemberStatusActive).Find(&memberOrgs).Error; err != nil {
return nil, vigo.ErrInternalServer.WithError(err)
}
if len(memberOrgs) == 0 {
return []TreeResponse{}, nil
}
orgIDs := make([]string, 0, len(memberOrgs))
for _, m := range memberOrgs {
orgIDs = append(orgIDs, m.OrgID)
}
// 获取所有组织
var orgs []model.Org
if err := model.DB.Where("id IN ?", orgIDs).Order("path").Find(&orgs).Error; err != nil {
return nil, vigo.ErrInternalServer.WithError(err)
}
// 构建树
return buildOrgTree(orgs, ""), nil
}
func buildOrgTree(orgs []model.Org, parentID string) []TreeResponse {
var result []TreeResponse
for _, o := range orgs {
if (parentID == "" && (o.ParentID == nil || *o.ParentID == "")) ||
(o.ParentID != nil && *o.ParentID == parentID) {
node := TreeResponse{
OrgInfo: toOrgInfo(&o),
}
node.Children = buildOrgTree(orgs, o.ID)
result = append(result, node)
}
}
return result
}
// helper functions
func toOrgInfo(o *model.Org) OrgInfo {
info := OrgInfo{
ID: o.ID,
Name: o.Name,
Code: o.Code,
OwnerID: o.OwnerID,
Path: o.Path,
Level: o.Level,
Description: o.Description,
Logo: o.Logo,
Status: o.Status,
MaxMembers: o.MaxMembers,
CreatedAt: o.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: o.UpdatedAt.Format("2006-01-02 15:04:05"),
}
if o.ParentID != nil {
info.ParentID = *o.ParentID
}
if o.LeaderID != nil {
info.LeaderID = *o.LeaderID
}
return info
}
func parseRoles(roleIDs string) []string {
if roleIDs == "" {
return []string{}
}
return strings.Split(roleIDs, ",")
}
func formatRoleID(roleIDs []string) string {
return strings.Join(roleIDs, ",")
}
// ListMembersRequest 成员列表请求
type ListMembersRequest struct {
OrgID string `json:"org_id" src:"path@org_id" desc:"组织ID"`
Page int `json:"page" src:"query" default:"1" desc:"页码"`
PageSize int `json:"page_size" src:"query" default:"10" desc:"每页数量"`
Status *int `json:"status" src:"query" desc:"状态筛选"`
}
// MemberInfo 成员信息
type MemberInfo struct {
ID string `json:"id"`
OrgID string `json:"org_id"`
UserID string `json:"user_id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Email string `json:"email"`
Roles []string `json:"roles"`
Position string `json:"position"`
Department string `json:"department"`
JoinedAt string `json:"joined_at"`
Status int `json:"status"`
}
// ListMembers 获取组织成员列表
func ListMembers(x *vigo.X, req *ListMembersRequest) (*ListResponse, error) {
userID := middleware.CurrentUser(x)
if userID == "" {
return nil, vigo.ErrNotAuthorized
}
if req.Page < 1 {
req.Page = 1
}
if req.PageSize < 1 || req.PageSize > 100 {
req.PageSize = 10
}
// 检查用户是否是组织成员
var currentMember model.OrgMember
if err := model.DB.Where("org_id = ? AND user_id = ?", req.OrgID, userID).First(&currentMember).Error; err != nil {
return nil, vigo.ErrForbidden.WithString("you are not a member of this organization")
}
var total int64
query := model.DB.Model(&model.OrgMember{}).Where("org_id = ?", req.OrgID)
if req.Status != nil {
query = query.Where("status = ?", *req.Status)
}
if err := query.Count(&total).Error; err != nil {
return nil, vigo.ErrInternalServer.WithError(err)
}
var members []model.OrgMember
offset := (req.Page - 1) * req.PageSize
if err := query.Offset(offset).Limit(req.PageSize).Find(&members).Error; err != nil {
return nil, vigo.ErrInternalServer.WithError(err)
}
items := make([]OrgInfo, 0) // 这里需要修改返回类型
_ = items
// 获取用户信息
memberInfos := make([]MemberInfo, 0, len(members))
for _, m := range members {
var user model.User
if err := model.DB.First(&user, "id = ?", m.UserID).Error; err != nil {
continue
}
memberInfos = append(memberInfos, MemberInfo{
ID: m.ID,
OrgID: m.OrgID,
UserID: m.UserID,
Username: user.Username,
Nickname: user.Nickname,
Avatar: user.Avatar,
Email: user.Email,
Roles: parseRoles(m.RoleIDs),
Position: m.Position,
Department: m.Department,
JoinedAt: m.JoinedAt,
Status: m.Status,
})
}
_ = memberInfos
return nil, fmt.Errorf("not fully implemented")
}