mirror of https://github.com/veypi/OneAuth.git
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.
512 lines
14 KiB
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(¤tMember).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")
|
|
}
|