diff --git a/config.yaml b/config.yaml
index 60549ecb..8c5cf39c 100644
--- a/config.yaml
+++ b/config.yaml
@@ -19,6 +19,9 @@ user:
# a session is created when user login to im.dev, this session will be expired after X seconds
session_expire: 259200
+ui:
+ # base ui domain
+ domain: "http://localhost:4004"
#################################### Paths ##############################
# Path to where im.dev can store temp files, sessions, and the sqlite3 db (if that is used)
diff --git a/configs/config.ts b/configs/config.ts
index e6ef4e5b..ffdbeb14 100644
--- a/configs/config.ts
+++ b/configs/config.ts
@@ -2,6 +2,7 @@ import { requestApi } from "../src/utils/axios/request"
export let config = {
appName: "im.dev",
+ uiDomain: "http://localhost:4004",
commonMaxlen: 255,
posts: {
titleMaxLen: 128,
diff --git a/layouts/nav/nav.tsx b/layouts/nav/nav.tsx
index 5e5bbca6..0440ef69 100644
--- a/layouts/nav/nav.tsx
+++ b/layouts/nav/nav.tsx
@@ -92,7 +92,7 @@ function HeaderContent() {
variant="ghost"
color="current"
_focus={null}
- display={{ base: "none", md: "block" }}
+ display={{ base: "none", md: "flex" }}
icon={}
/>
diff --git a/pages/login.tsx b/pages/login.tsx
index f4f922b4..4bef5aa7 100644
--- a/pages/login.tsx
+++ b/pages/login.tsx
@@ -1,4 +1,4 @@
-import React from "react"
+import React, { useState } from "react"
import {
Text,
Box,
@@ -7,20 +7,34 @@ import {
Image,
useColorModeValue,
Link,
- Center
+ Center,
+ Flex,
+ IconButton,
+ HStack,
+ Modal,
+ ModalOverlay,
+ ModalContent,
+ ModalBody,
+ useDisclosure,
+ Input,
+ useToast
} from "@chakra-ui/react"
import Logo from "components/logo"
-import { FaGithub } from "react-icons/fa"
+import { FaEnvelope, FaGithub } from "react-icons/fa"
import { requestApi } from "utils/axios/request"
import { saveToken } from "utils/axios/getToken"
import storage from "utils/localStorage"
import { useRouter } from "next/router"
+import { validateEmail } from "utils/user"
const LoginPage = () => {
+ const { isOpen, onOpen, onClose } = useDisclosure()
+ const toast = useToast()
const router = useRouter()
- const login = async () => {
- const res = await requestApi.post("/user/login")
+ const [email,setEmail] = useState('')
+ const login = async (email:string) => {
+ const res = await requestApi.post("/user/login",{email: email})
saveToken(res.data.token)
storage.set('session', res.data)
const oldPage = storage.get('current-page')
@@ -32,10 +46,25 @@ const LoginPage = () => {
}
}
+ const onEmailLogin = async () => {
+ const err = await validateEmail(email,false)
+ if (err) {
+ toast({
+ description: err,
+ status: "error",
+ duration: 2000,
+ isClosable: true,
+ })
+ return
+ }
+
+ login(email)
+ }
+
return (
-
+
欢迎加入im.dev,一起打造全世界最好的开发者社区
@@ -53,11 +82,26 @@ const LoginPage = () => {
-
+
+
+ OR
+ } onClick={onOpen}/>
+
如果继续,则表示你同意im.dev的服务条款和隐私政策
{/* */}
-
+
+
+
+
+
+ Sign in using a secure link
+ setEmail(e.currentTarget.value)} placeholder="enter your email address" _focus={null}>
+
+
+
+
+
)
}
diff --git a/pages/settings/org/members.tsx b/pages/settings/org/members.tsx
deleted file mode 100644
index a14a3caf..00000000
--- a/pages/settings/org/members.tsx
+++ /dev/null
@@ -1,301 +0,0 @@
-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 Card from "components/card"
-import Nav from "layouts/nav/nav"
-import PageContainer from "layouts/page-container"
-import Sidebar from "layouts/sidebar/sidebar"
-import React, { useEffect, useState } from "react"
-import { adminLinks, orgSettingLinks, settingLinks } from "src/data/links"
-import { requestApi } from "utils/axios/request"
-import { useRouter } from "next/router"
-import { Field, Form, Formik } from "formik"
-import { config } from "configs/config"
-import Tags from "components/tags/tags"
-var validator = require('validator');
-
-const UserProfilePage = () => {
- const [user, setUser] = useState(null)
- const [skills, setSkills] = useState([])
- const [isLargerThan1280] = useMediaQuery("(min-width: 768px)")
- useEffect(() => {
- requestApi.get("/user/self").then(res => setUser(res.data))
- }, [])
- const router = useRouter()
- const toast = useToast()
-
- const submitUser = async (values, _) => {
- await requestApi.post(`/user/update`, values)
- setUser(values)
- toast({
- description: "更新成功",
- status: "success",
- duration: 2000,
- isClosable: true,
- })
- }
-
- function validateNickname(value) {
- let error
- if (!value?.trim()) {
- error = "昵称不能为空"
- }
-
- if (value?.length > config.user.nicknameMaxLen) {
- error = `长度不能超过${config.user.nicknameMaxLen}`
- }
-
- return error
- }
-
- function validateEmail(value) {
- let email = value?.trim()
- let error
-
- if (email?.length > config.user.usernameMaxLen) {
- error = `长度不能超过${config.user.usernameMaxLen}`
- return error
- }
-
- if (email) {
- if (!validator.isEmail(email)) {
- error = "Email格式不合法"
- return error
- }
- }
- return error
- }
-
-
- function validateUrl(value, canBeEmpty = true) {
- let url = value?.trim()
- let error
- if (!canBeEmpty) {
- if (!url) {
- error = "url不能为空"
- return error
- }
- }
-
- if (url) {
- if (!validator.isURL(value)) {
- error = "URL格式不合法"
- return error
- }
- }
-
- return error
- }
-
- function validateLen(value) {
- let error
- if (value?.length > config.commonMaxlen) {
- error = `长度不能超过${config.commonMaxlen}`
- }
-
- return error
- }
-
- const Layout = isLargerThan1280 ? HStack : VStack
- return (
- <>
-
-
-
- {user &&
-
- {(props) => (
-
- )}
-
- }
-
-
- >
- )
-}
-export default UserProfilePage
-
diff --git a/pages/settings/org/members/[org_id].tsx b/pages/settings/org/members/[org_id].tsx
new file mode 100644
index 00000000..3fbc23f3
--- /dev/null
+++ b/pages/settings/org/members/[org_id].tsx
@@ -0,0 +1,119 @@
+import { Text, Box, VStack, Divider, useToast, Heading, Alert, Tag, Button, HStack, Modal, ModalOverlay, ModalContent, ModalBody, Select, useDisclosure } from "@chakra-ui/react"
+import Card from "components/card"
+import Nav from "layouts/nav/nav"
+import PageContainer from "layouts/page-container"
+import Sidebar from "layouts/sidebar/sidebar"
+import React, { useEffect, useState } from "react"
+import { orgSettingLinks } from "src/data/links"
+import { requestApi } from "utils/axios/request"
+import { useRouter } from "next/router"
+import { User } from "src/types/user"
+import UserCard from "components/users/user-card"
+import { config } from "configs/config"
+import OrgMember from "components/users/org-member"
+import { Role } from "src/types/role"
+import { cloneDeep } from "lodash"
+
+
+const UserProfilePage = () => {
+ const { isOpen, onOpen, onClose } = useDisclosure()
+ const [currentMember,setCurrentMember]:[User,any] = useState(null)
+ const [org, setOrg]:[User,any] = useState(null)
+ const [users, setUsers]: [User[], any] = useState(null)
+ const [secret, setSecret] = useState('')
+ const router = useRouter()
+ useEffect(() => {
+ if (router.query.org_id) {
+ getMembers()
+ requestApi.get(`/org/secret/${router.query.org_id}`).then(res => setSecret(res.data))
+ requestApi.get(`/user/info/${router.query.org_id}`).then(res => setOrg(res.data))
+ }
+
+ }, [router.query.org_id])
+
+ const getMembers = async () => {
+ const res = await requestApi.get(`/org/members/${router.query.org_id}`)
+ setUsers(res.data)
+ }
+ const toast = useToast()
+
+ const generateSecret = async () => {
+ const res = await requestApi.post(`/org/secret/${router.query.org_id}`)
+ toast({
+ description: "生成secret成功",
+ status: "success",
+ duration: 2000,
+ isClosable: true,
+ })
+ setSecret(res.data)
+ }
+
+ const onEdit = (member) => {
+ setCurrentMember(member)
+ onOpen()
+ }
+
+ const onChangeRole = e => {
+ const member = cloneDeep(currentMember)
+ member.role = e.currentTarget.value;
+ setCurrentMember(member)
+ }
+
+ const onSumitMember = async () => {
+ await requestApi.post(`/org/member/role`,{orgID:org.id, memberID: currentMember.id, role: currentMember.role})
+ setCurrentMember(null)
+ onClose()
+ getMembers()
+ }
+
+ return (
+ <>
+
+
+
+
+
+ Grow the org
+ Invite teammates by sending them the secret and the following instructions:
+
+ 1. Sign in
+ 2. Navigate to {config.uiDomain}/settings/orgs
+ 3. Paste the secret code below and click Join Organization
+ {secret}
+
+
+
+ You should rotate this regularly!
+
+
+
+
+ {users &&
+
+ {
+ users.map(u => )
+ }
+ }
+
+
+
+
+
+
+
+ {currentMember &&
+
+ Change role
+
+
+
+ }
+
+ >
+ )
+}
+export default UserProfilePage
+
diff --git a/pages/settings/org/profile.tsx b/pages/settings/org/profile/[org_id].tsx
similarity index 98%
rename from pages/settings/org/profile.tsx
rename to pages/settings/org/profile/[org_id].tsx
index 6f399602..7fc4574e 100644
--- a/pages/settings/org/profile.tsx
+++ b/pages/settings/org/profile/[org_id].tsx
@@ -14,15 +14,14 @@ var validator = require('validator');
const UserProfilePage = () => {
const [user, setUser] = useState(null)
- const [skills, setSkills] = useState([])
const [isLargerThan1280] = useMediaQuery("(min-width: 768px)")
const router = useRouter()
useEffect(() => {
- if (router.query.id) {
- requestApi.get(`/user/info/${router.query.id}`).then(res => setUser(res.data))
+ if (router.query.org_id) {
+ requestApi.get(`/user/info/${router.query.org_id}`).then(res => setUser(res.data))
}
- }, [router.query.id])
+ }, [router.query.org_id])
const toast = useToast()
@@ -103,7 +102,7 @@ const UserProfilePage = () => {
<>
-
+
{user &&
{
const [orgs, setOrgs]:[Org[],any] = useState([])
const { isOpen, onOpen, onClose } = useDisclosure()
+ const { isOpen:isOpen1, onOpen:onOpen1, onClose:onClose1 } = useDisclosure()
const router = useRouter()
const stackBorderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark)
+ const [secret,setSecret] = useState('')
useEffect(() => {
getOrgs()
@@ -41,9 +44,15 @@ const UserOrgsPage = () => {
onOpen()
}
-
-
+ const onJoinOrg = () => {
+ onOpen1()
+ }
+ const joinOrg = async () => {
+ await requestApi.post(`/org/join`,{secret: secret})
+ onClose1()
+ getOrgs()
+ }
return (
<>
@@ -51,8 +60,11 @@ const UserOrgsPage = () => {
- 组织管理
-
+ 组织列表
+
+
+
+
} alignItems="left">
@@ -66,7 +78,7 @@ const UserOrgsPage = () => {
-
+ {isAdmin(o.role) && }
)
}
@@ -123,6 +135,18 @@ const UserOrgsPage = () => {
}
+
+
+
+ {
+
+ Secret code
+ Provided to you by an org admin
+ setSecret(e.currentTarget.value)}/>
+
+
+ }
+
>
)
}
diff --git a/pages/settings/profile.tsx b/pages/settings/profile.tsx
index 2fa9bd45..8d56a3d9 100644
--- a/pages/settings/profile.tsx
+++ b/pages/settings/profile.tsx
@@ -74,7 +74,7 @@ const UserProfilePage = () => {
)}
-
+ validateEmail(v,false)}>
{({ field, form }) => (
邮箱地址
diff --git a/server/internal/api/org.go b/server/internal/api/org.go
index 50a0655b..a4dfae8c 100644
--- a/server/internal/api/org.go
+++ b/server/internal/api/org.go
@@ -7,6 +7,7 @@ import (
"github.com/imdotdev/im.dev/server/internal/org"
"github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/common"
+ "github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/models"
)
@@ -88,3 +89,123 @@ func GetOrgMembers(c *gin.Context) {
c.JSON(http.StatusOK, common.RespSuccess(users))
}
+
+func GenOrgSecret(c *gin.Context) {
+ orgID := c.Param("id")
+ currentUser := user.CurrentUser(c)
+ if !org.IsOrgAdmin(currentUser.ID, orgID) {
+ c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
+ return
+ }
+
+ secret, err := user.GenSecret(orgID)
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(secret))
+}
+
+func GetOrgSecret(c *gin.Context) {
+ orgID := c.Param("id")
+ currentUser := user.CurrentUser(c)
+ if !org.IsOrgAdmin(currentUser.ID, orgID) {
+ c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
+ return
+ }
+
+ secret, err := user.GetSecret(orgID)
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(secret))
+}
+
+type JoinOrgReq struct {
+ Secret string `json:"secret"`
+}
+
+func JoinOrg(c *gin.Context) {
+ req := &JoinOrgReq{}
+ c.Bind(&req)
+
+ u := user.CurrentUser(c)
+ err := org.Join(req.Secret, u.ID)
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(nil))
+}
+
+type UpdateMemberReq struct {
+ OrgID string `json:"orgID"`
+ MemberID string `json:"memberID"`
+ Role models.RoleType `json:"role"`
+}
+
+func UpdateOrgMember(c *gin.Context) {
+ req := &UpdateMemberReq{}
+ c.Bind(&req)
+
+ if !req.Role.IsValid() {
+ c.JSON(http.StatusBadRequest, common.RespError(e.BadRequest))
+ return
+ }
+
+ if req.Role == models.ROLE_SUPER_ADMIN || req.Role == models.ROLE_EDITOR {
+ c.JSON(http.StatusBadRequest, common.RespError(e.BadRequest))
+ return
+ }
+ u := user.CurrentUser(c)
+ var role models.RoleType
+ err := db.Conn.QueryRow("SELECT role FROM org_member WHERE org_id=? and user_id=?", req.OrgID, u.ID).Scan(&role)
+ if err != nil {
+ logger.Warn("select role error", "error", err)
+ c.JSON(http.StatusBadRequest, common.RespError(e.BadRequest))
+ return
+ }
+
+ // 修改角色要求至少是管理员
+ if !role.IsAdmin() {
+ c.JSON(http.StatusForbidden, common.RespError("你需要成为组织的管理员"))
+ return
+ }
+
+ // 目标角色修改为管理员,要求当前操作用户必须是超级管理员
+ if req.Role == models.ROLE_ADMIN {
+ if role != models.ROLE_SUPER_ADMIN {
+ c.JSON(http.StatusForbidden, common.RespError("你需要成为组织的超级管理员"))
+ return
+ }
+ }
+
+ // 若目标角色之前是管理员,那么必须是超级管理员才能对其修改
+ targetRole, err := org.GetMemberRole(req.OrgID, req.MemberID)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, common.RespError(e.BadRequest))
+ return
+ }
+ if targetRole == models.ROLE_SUPER_ADMIN {
+ c.JSON(http.StatusForbidden, common.RespError("超级管理员不能被修改"))
+ return
+ }
+ if targetRole == models.ROLE_ADMIN {
+ if role != models.ROLE_SUPER_ADMIN {
+ c.JSON(http.StatusForbidden, common.RespError("你需要成为组织的超级管理员"))
+ return
+ }
+ }
+
+ err0 := org.UpdateMember(req.OrgID, req.MemberID, req.Role)
+ if err0 != nil {
+ c.JSON(err0.Status, common.RespError(err0.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(nil))
+}
diff --git a/server/internal/api/user.go b/server/internal/api/user.go
index fffe2589..c829e36f 100644
--- a/server/internal/api/user.go
+++ b/server/internal/api/user.go
@@ -51,7 +51,7 @@ func GetUserSelf(c *gin.Context) {
func GetUser(c *gin.Context) {
username := c.Param("username")
- userDetail, err := user.GetUserDetail("", username)
+ userDetail, err := user.GetUserDetail(username, username)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
diff --git a/server/internal/config.go b/server/internal/config.go
index 1fd6ba57..73d70700 100644
--- a/server/internal/config.go
+++ b/server/internal/config.go
@@ -10,6 +10,7 @@ import (
type Config struct {
AppName string `json:"appName"`
+ UIDomain string `json:"uiDomain"`
CommonMaxLen int `json:"commonMaxlen"`
Posts *PostsConfig `json:"posts"`
User *UserConfig `json:"user"`
@@ -32,6 +33,7 @@ type UserConfig struct {
func GetConfig(c *gin.Context) {
conf := &Config{
AppName: config.Data.Common.AppName,
+ UIDomain: config.Data.UI.Domain,
CommonMaxLen: 255,
Posts: &PostsConfig{
TitleMaxLen: config.Data.Posts.TitleMaxLen,
diff --git a/server/internal/org/org.go b/server/internal/org/org.go
index f977268b..4336ca07 100644
--- a/server/internal/org/org.go
+++ b/server/internal/org/org.go
@@ -54,7 +54,7 @@ func Create(o *models.User, userID string) *e.Error {
return e.New(http.StatusInternalServerError, e.Internal)
}
- _, err = tx.Exec("INSERT INTO org_member (org_id,user_id,role,created) VALUES (?,?,?,?)", o.ID, userID, models.ROLE_ADMIN, now)
+ _, err = tx.Exec("INSERT INTO org_member (org_id,user_id,role,created) VALUES (?,?,?,?)", o.ID, userID, models.ROLE_SUPER_ADMIN, now)
if err != nil {
logger.Warn("add org member error", "error", err)
tx.Rollback()
@@ -67,7 +67,7 @@ func Create(o *models.User, userID string) *e.Error {
}
func GetMembers(user *models.User, orgID string) ([]*models.User, *e.Error) {
- rows, err := db.Conn.Query("SELECT user_id from org_member where org_id=?", orgID)
+ rows, err := db.Conn.Query("SELECT user_id,role from org_member where org_id=?", orgID)
if err != nil {
logger.Warn("get org members error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
@@ -75,16 +75,17 @@ func GetMembers(user *models.User, orgID string) ([]*models.User, *e.Error) {
users := make([]*models.User, 0)
for rows.Next() {
- var id string
- rows.Scan(&id)
+ var id, role string
+ rows.Scan(&id, &role)
u, ok := models.UsersMapCache[id]
if ok {
- users = append(users, u)
if user != nil {
u.Followed = interaction.GetFollowed(u.ID, user.ID)
u.Follows = interaction.GetFollows(u.ID)
}
+ u.Role = models.RoleType(role)
+ users = append(users, u)
}
}
@@ -126,3 +127,47 @@ func UserInOrg(userID string, orgID string) bool {
return true
}
+
+func Join(secret string, userID string) *e.Error {
+ var orgID string
+ err := db.Conn.QueryRow("SELECT user_id FROM user_secret WHERE secret=?", secret).Scan(&orgID)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return e.New(http.StatusNotFound, "无效的secret")
+ }
+ logger.Warn("join org error", "error", err)
+ return e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ if !models.IdExist(orgID) {
+ return e.New(http.StatusNotFound, "组织不存在")
+ }
+
+ _, err = db.Conn.Exec("INSERT INTO org_member (org_id,user_id,role,created) VALUES (?,?,?,?)", orgID, userID, models.ROLE_NORMAL, time.Now())
+ if err != nil {
+ logger.Warn("join org error", "error", err)
+ return e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ return nil
+}
+
+func UpdateMember(orgID, memberID string, role models.RoleType) *e.Error {
+ _, err := db.Conn.Exec("UPDATE org_member SET role=? WHERE org_id=? and user_id=?", role, orgID, memberID)
+ if err != nil {
+ logger.Warn("update org member error", "error", err)
+ return e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ return nil
+}
+
+func GetMemberRole(orgID string, memberID string) (models.RoleType, error) {
+ var role models.RoleType
+ err := db.Conn.QueryRow("SELECT role FROM org_member WHERE org_id=? and user_id=?", orgID, memberID).Scan(&role)
+ if err != nil {
+ return role, err
+ }
+
+ return role, nil
+}
diff --git a/server/internal/server.go b/server/internal/server.go
index 86ec4767..a55cccb6 100644
--- a/server/internal/server.go
+++ b/server/internal/server.go
@@ -108,7 +108,10 @@ func (s *Server) Start() error {
r.POST("/org/update", IsLogin(), api.UpdateOrg)
r.GET("/org/byUserID/:userID", api.GetOrgByUserID)
r.GET("/org/members/:id", api.GetOrgMembers)
-
+ r.POST("/org/secret/:id", IsLogin(), api.GenOrgSecret)
+ r.GET("/org/secret/:id", IsLogin(), api.GetOrgSecret)
+ r.POST("/org/join", IsLogin(), api.JoinOrg)
+ r.POST("/org/member/role", IsLogin(), api.UpdateOrgMember)
// admin apis
r.POST("/admin/user", IsLogin(), api.AdminSubmitUser)
r.GET("/admin/user/all", IsLogin(), api.AdminGetUsers)
diff --git a/server/internal/storage/sql_tables.go b/server/internal/storage/sql_tables.go
index 2a048188..1b094fe4 100644
--- a/server/internal/storage/sql_tables.go
+++ b/server/internal/storage/sql_tables.go
@@ -43,6 +43,15 @@ var sqlTables = map[string]string{
updated DATETIME
);`,
+ "user_secret": `CREATE TABLE IF NOT EXISTS user_secret (
+ user_id VARCHAR(255) primary key,
+ secret VARCHAR(255),
+ created DATETIME
+ );
+ CREATE UNIQUE INDEX IF NOT EXISTS us_secret
+ ON user_secret (secret);
+ `,
+
"sessions": `CREATE TABLE IF NOT EXISTS sessions (
sid VARCHAR(255) primary key,
user_id VARCHAR(255)
diff --git a/server/internal/user/secret.go b/server/internal/user/secret.go
new file mode 100644
index 00000000..df63016d
--- /dev/null
+++ b/server/internal/user/secret.go
@@ -0,0 +1,52 @@
+package user
+
+import (
+ "database/sql"
+ "net/http"
+ "time"
+
+ "github.com/imdotdev/im.dev/server/pkg/db"
+ "github.com/imdotdev/im.dev/server/pkg/e"
+ "github.com/imdotdev/im.dev/server/pkg/models"
+ "github.com/imdotdev/im.dev/server/pkg/utils"
+)
+
+func GenSecret(userID string) (string, *e.Error) {
+ secret := utils.GenID(models.IDTypeSecret)
+ tx, err := db.Conn.Begin()
+ if err != nil {
+ logger.Warn("start transaction error", "error", err)
+ return "", e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ _, err = tx.Exec("DELETE FROM user_secret WHERE user_id=?", userID)
+ if err != nil {
+ logger.Warn("delete secret error", "error", err)
+ return "", e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ _, err = tx.Exec("INSERT INTO user_secret (user_id,secret,created) VALUES (?,?,?)", userID, secret, time.Now())
+ if err != nil {
+ logger.Warn("insert secret error", "error", err)
+ tx.Rollback()
+ return "", e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ tx.Commit()
+ return secret, nil
+}
+
+func GetSecret(userID string) (string, *e.Error) {
+ var secret string
+ err := db.Conn.QueryRow("SELECT secret from user_secret WHERE user_id=?", userID).Scan(&secret)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return "", e.New(http.StatusNotFound, "找不到对应的secret,请重新生成")
+ }
+
+ logger.Warn("select secret error", "error", err)
+ return "", e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ return secret, nil
+}
diff --git a/server/internal/user/session.go b/server/internal/user/session.go
index 4c2eed1d..279f766c 100644
--- a/server/internal/user/session.go
+++ b/server/internal/user/session.go
@@ -24,10 +24,11 @@ type Session struct {
func Login(c *gin.Context) {
user := &models.User{}
- err := user.Query("", config.Data.User.SuperAdminUsername, "")
+ c.Bind(&user)
+ err := user.Query("", "", user.Email)
if err != nil {
if err == sql.ErrNoRows {
- c.String(http.StatusNotFound, "")
+ c.JSON(http.StatusNotFound, common.RespError("邮箱不存在"))
return
}
logger.Error("login error", "error", err)
diff --git a/server/pkg/config/config.go b/server/pkg/config/config.go
index f2e13020..3f498db1 100644
--- a/server/pkg/config/config.go
+++ b/server/pkg/config/config.go
@@ -29,6 +29,10 @@ type Config struct {
BaseUrl string `yaml:"base_url"`
}
+ UI struct {
+ Domain string
+ }
+
Paths struct {
Data string
Logs string
diff --git a/server/pkg/models/id_type.go b/server/pkg/models/id_type.go
index ddae6ba8..7883caaa 100644
--- a/server/pkg/models/id_type.go
+++ b/server/pkg/models/id_type.go
@@ -16,6 +16,7 @@ const (
IDTypeSeries = "5"
IDTypeBook = "6"
IDTypeOrg = "7"
+ IDTypeSecret = "s"
)
func GetIDType(id string) string {
diff --git a/src/components/users/org-member.tsx b/src/components/users/org-member.tsx
new file mode 100644
index 00000000..b6872c25
--- /dev/null
+++ b/src/components/users/org-member.tsx
@@ -0,0 +1,53 @@
+import React from "react"
+import {chakra, Heading, Image, Text, HStack,Button, Flex,PropsOf,Box, Avatar, VStack, propNames, Tag} from "@chakra-ui/react"
+import moment from 'moment'
+import { FaGithub } from "react-icons/fa"
+import { useRouter } from "next/router"
+import { User } from "src/types/user"
+import { getUserName } from "utils/user"
+import Follow from "components/interaction/follow"
+import Highlighter from 'react-highlight-words';
+import Count from "components/count"
+
+type Props = PropsOf & {
+ user : User
+ highlight?: string
+ onEdit: any
+}
+
+export const OrgMember= ({user,highlight,onEdit}:Props) =>{
+ const router = useRouter()
+ return (
+
+
+ router.push(`/${user.username}`)} cursor="pointer"/>
+
+
+ router.push(`/${user.username}`)} cursor="pointer">
+
+ {user.role}
+
+
+
+ @
+
+
+
+
+ followers
+
+
+
+
+ )
+}
+
+export default OrgMember
diff --git a/src/components/users/user-card.tsx b/src/components/users/user-card.tsx
index f05b54d7..c18651ea 100644
--- a/src/components/users/user-card.tsx
+++ b/src/components/users/user-card.tsx
@@ -1,5 +1,5 @@
import React from "react"
-import {chakra, Heading, Image, Text, HStack,Button, Flex,PropsOf,Box, Avatar, VStack, propNames} from "@chakra-ui/react"
+import {chakra, Heading, Image, Text, HStack,Button, Flex,PropsOf,Box, Avatar, VStack, propNames, Tag} from "@chakra-ui/react"
import moment from 'moment'
import { FaGithub } from "react-icons/fa"
import { useRouter } from "next/router"
@@ -12,14 +12,16 @@ import Count from "components/count"
type Props = PropsOf & {
user : User
highlight?: string
+ displayFollow?: boolean
+ displayRole?:boolean
}
-export const UserCard= ({user,highlight}:Props) =>{
+export const UserCard= ({user,highlight,displayFollow=true,displayRole=false}:Props) =>{
const router = useRouter()
return (
- router.push(`/${user.username}`)} cursor="pointer"/>
+ router.push(`/${user.username}`)} cursor="pointer"/>
router.push(`/${user.username}`)} cursor="pointer">
@@ -36,7 +38,7 @@ export const UserCard= ({user,highlight}:Props) =>{
searchWords={[highlight]}
/>
- {user.tagline &&
+ {user.tagline &&
{
+ {displayRole && {user.role}}
followers
-
+ {displayFollow && }
diff --git a/src/data/links.tsx b/src/data/links.tsx
index aa0ff539..955ee877 100644
--- a/src/data/links.tsx
+++ b/src/data/links.tsx
@@ -92,26 +92,30 @@ export const settingLinks: Route[] = [{
disabled: false
},
{
- title: '组织管理',
+ title: '组织列表',
path: `${ReserveUrls.Settings}/orgs`,
icon: ,
disabled: false
},
]
-export const orgSettingLinks: Route[] = [{
- title: '组织信息',
- path: `${ReserveUrls.Settings}/org/profile`,
- icon: ,
- disabled: false
-},
-{
- title: '成员管理',
- path: `${ReserveUrls.Settings}/org/members`,
- icon: ,
- disabled: false
-},
-]
+
+export function orgSettingLinks(orgID) {
+ return [{
+ title: '组织信息',
+ path: `${ReserveUrls.Settings}/org/profile/${orgID}`,
+ icon: ,
+ disabled: false
+ },
+ {
+ title: '成员管理',
+ path: `${ReserveUrls.Settings}/org/members/${orgID}`,
+ icon: ,
+ disabled: false
+ },
+ ]
+}
+
diff --git a/src/utils/user.ts b/src/utils/user.ts
index 2686049d..14852968 100644
--- a/src/utils/user.ts
+++ b/src/utils/user.ts
@@ -54,7 +54,7 @@ export function validateNickname(value) {
return error
}
-export async function validateEmail(value) {
+export async function validateEmail(value,checkExist=true) {
let email = value?.trim()
if (!email) {
return "邮箱不能为空"
@@ -70,9 +70,11 @@ export async function validateEmail(value) {
}
}
- const res = await requestApi.get(`/user/email/exist/${value}`)
- if (res.data) {
- return `The email '${value}' is already taken.`
+ if (checkExist) {
+ const res = await requestApi.get(`/user/email/exist/${value}`)
+ if (res.data) {
+ return `The email '${value}' is already taken.`
+ }
}
}