diff --git a/pages/settings/org/profile/[org_id].tsx b/pages/settings/org/profile/[org_id].tsx index 7fc4574e..08984494 100644 --- a/pages/settings/org/profile/[org_id].tsx +++ b/pages/settings/org/profile/[org_id].tsx @@ -1,4 +1,4 @@ -import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, HStack, Wrap, useMediaQuery, Avatar, Textarea, } from "@chakra-ui/react" +import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, HStack, Wrap, useMediaQuery, Avatar, Textarea, Modal, ModalOverlay, ModalContent, ModalBody, useDisclosure, Alert, AlertIcon, } from "@chakra-ui/react" import Card from "components/card" import Nav from "layouts/nav/nav" import PageContainer from "layouts/page-container" @@ -13,12 +13,15 @@ import Tags from "components/tags/tags" var validator = require('validator'); const UserProfilePage = () => { - const [user, setUser] = useState(null) + const { isOpen, onOpen, onClose } = useDisclosure() + const [org, setOrg] = useState(null) + const [newOnwer, setNewOnwer] = useState('') + const [isLargerThan1280] = useMediaQuery("(min-width: 768px)") const router = useRouter() useEffect(() => { if (router.query.org_id) { - requestApi.get(`/user/info/${router.query.org_id}`).then(res => setUser(res.data)) + requestApi.get(`/user/info/${router.query.org_id}`).then(res => setOrg(res.data)) } }, [router.query.org_id]) @@ -27,7 +30,7 @@ const UserProfilePage = () => { const submitUser = async (values, _) => { await requestApi.post(`/org/update`, values) - setUser(values) + setOrg(values) toast({ description: "更新成功", status: "success", @@ -97,15 +100,26 @@ const UserProfilePage = () => { return error } + const transferOrg = async () => { + await requestApi.post(`/org/transfer`,{orgID: router.query.org_id, ownerID: newOnwer}) + toast({ + description: "转移成功", + status: "success", + duration: 2000, + isClosable: true, + }) + router.push('/settings/orgs') + } + const Layout = isLargerThan1280 ? HStack : VStack return ( <> - - {user && + + {org && {(props) => ( @@ -115,10 +129,15 @@ const UserProfilePage = () => { 基本信息 + + Username + + + {({ field, form }) => ( - 用户昵称 + 组织名称 {form.errors.nickname} @@ -141,7 +160,7 @@ const UserProfilePage = () => { 头像设置 {form.errors.avatar} - {user.avatar && } + {org.avatar && } )} @@ -152,7 +171,7 @@ const UserProfilePage = () => { 封面图片 {form.errors.cover} - {user.cover && } + {org.cover && } )} @@ -192,7 +211,7 @@ const UserProfilePage = () => { {({ field, form }) => ( 组织技术栈 - form.values.skills = v} size="lg"/> + form.values.skills = v} size="lg" /> )} @@ -277,10 +296,18 @@ const UserProfilePage = () => { variant="outline" type="submit" _focus={null} - + > 更新信息 - + + )} @@ -288,6 +315,23 @@ const UserProfilePage = () => { } + + + + { + + 转移组织 + 你必须是超级管理员才能完成此操作 + setNewOnwer(e.currentTarget.value)} /> + + + 注意:该操作无法逆转,一旦转移,你将变为普通成员 + + + + + } + ) } diff --git a/pages/settings/profile.tsx b/pages/settings/profile.tsx index 8d56a3d9..bf6ceee9 100644 --- a/pages/settings/profile.tsx +++ b/pages/settings/profile.tsx @@ -64,6 +64,16 @@ const UserProfilePage = () => { 基本信息 + + ID + + + + + Username + + + {({ field, form }) => ( diff --git a/server/internal/api/org.go b/server/internal/api/org.go index a4dfae8c..0eb7a4e7 100644 --- a/server/internal/api/org.go +++ b/server/internal/api/org.go @@ -110,7 +110,7 @@ func GenOrgSecret(c *gin.Context) { func GetOrgSecret(c *gin.Context) { orgID := c.Param("id") currentUser := user.CurrentUser(c) - if !org.IsOrgAdmin(currentUser.ID, orgID) { + if !org.UserInOrg(currentUser.ID, orgID) { c.JSON(http.StatusForbidden, common.RespError(e.NoPermission)) return } @@ -209,3 +209,40 @@ func UpdateOrgMember(c *gin.Context) { c.JSON(http.StatusOK, common.RespSuccess(nil)) } + +type TransferReq struct { + OrgID string `json:"orgID"` + OwnerID string `json:"ownerID"` +} + +func TransferOrg(c *gin.Context) { + req := &TransferReq{} + c.Bind(&req) + + // 检查当前用户是否是组织的超级管理员 + currentUser := user.CurrentUser(c) + currentRole, err := org.GetMemberRole(req.OrgID, currentUser.ID) + if err != nil { + c.JSON(http.StatusBadRequest, common.RespError(e.BadRequest)) + return + } + + if currentRole != models.ROLE_SUPER_ADMIN { + c.JSON(http.StatusForbidden, common.RespError("只有超级管理员才能转移组织")) + return + } + + // 检查目标用户是否是组织成员 + if !org.UserInOrg(req.OwnerID, req.OrgID) { + c.JSON(http.StatusForbidden, common.RespError("目标用户不是组织成员")) + return + } + + err0 := org.Transfer(req.OrgID, currentUser.ID, req.OwnerID) + if err0 != nil { + c.JSON(err0.Status, common.RespError(err0.Message)) + return + } + + c.JSON(http.StatusOK, common.RespSuccess(nil)) +} diff --git a/server/internal/org/org.go b/server/internal/org/org.go index 4336ca07..71561ceb 100644 --- a/server/internal/org/org.go +++ b/server/internal/org/org.go @@ -171,3 +171,28 @@ func GetMemberRole(orgID string, memberID string) (models.RoleType, error) { return role, nil } + +func Transfer(orgID, currentOwner, newOwner string) *e.Error { + tx, err := db.Conn.Begin() + if err != nil { + logger.Warn("start sql transaction error", "error", err) + return e.New(http.StatusInternalServerError, e.Internal) + } + + _, err = tx.Exec("UPDATE org_member SET role=? WHERE org_id=? and user_id=?", models.ROLE_SUPER_ADMIN, orgID, newOwner) + if err != nil { + logger.Warn("transfer org error", "error", err) + return e.New(http.StatusInternalServerError, e.Internal) + } + + _, err = tx.Exec("UPDATE org_member SET role=? WHERE org_id=? and user_id=?", models.ROLE_NORMAL, orgID, currentOwner) + if err != nil { + logger.Warn("transfer org error", "error", err) + tx.Rollback() + return e.New(http.StatusInternalServerError, e.Internal) + } + + tx.Commit() + + return nil +} diff --git a/server/internal/server.go b/server/internal/server.go index a55cccb6..a0e589ab 100644 --- a/server/internal/server.go +++ b/server/internal/server.go @@ -112,6 +112,7 @@ func (s *Server) Start() error { r.GET("/org/secret/:id", IsLogin(), api.GetOrgSecret) r.POST("/org/join", IsLogin(), api.JoinOrg) r.POST("/org/member/role", IsLogin(), api.UpdateOrgMember) + r.POST("/org/transfer", api.TransferOrg) // admin apis r.POST("/admin/user", IsLogin(), api.AdminSubmitUser) r.GET("/admin/user/all", IsLogin(), api.AdminGetUsers) diff --git a/src/data/links.tsx b/src/data/links.tsx index 955ee877..0678d26c 100644 --- a/src/data/links.tsx +++ b/src/data/links.tsx @@ -102,7 +102,7 @@ export const settingLinks: Route[] = [{ export function orgSettingLinks(orgID) { return [{ - title: '组织信息', + title: '组织设置', path: `${ReserveUrls.Settings}/org/profile/${orgID}`, icon: , disabled: false