diff --git a/pages/[username]/index.tsx b/pages/[username]/index.tsx
index 1f65dcb0..6f05cb39 100644
--- a/pages/[username]/index.tsx
+++ b/pages/[username]/index.tsx
@@ -51,7 +51,7 @@ const UserPage = () => {
getTags(res.data.id)
getNavbars(res.data.id)
- const res1 = await requestApi.get(`/user/posts/${res.data.id}`)
+ const res1 = res.data.type === IDType.User ? await requestApi.get(`/user/posts/${res.data.id}`) : await requestApi.get(`/story/posts/org/${res.data.id}?type=0`)
setPosts(res1.data)
setRawPosts(res1.data)
}
@@ -251,7 +251,7 @@ const UserPage = () => {
:
-
+
}
diff --git a/pages/admin/users.tsx b/pages/admin/users.tsx
new file mode 100644
index 00000000..3af75651
--- /dev/null
+++ b/pages/admin/users.tsx
@@ -0,0 +1,160 @@
+import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, Table, Thead, Tr, Th, Tbody, Td, IconButton, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, useDisclosure, FormControl, FormLabel, Input, FormErrorMessage, Select} from "@chakra-ui/react"
+import Card from "components/card"
+import Sidebar from "layouts/sidebar/sidebar"
+import React, { useEffect, useState } from "react"
+import { adminLinks } from "src/data/links"
+import { requestApi } from "utils/axios/request"
+import TagCard from "components/tags/tag-card"
+import { useRouter } from "next/router"
+import Link from "next/link"
+import { ReserveUrls } from "src/data/reserve-urls"
+import { Tag } from "src/types/tag"
+import { route } from "next/dist/next-server/server/router"
+import PageContainer1 from "layouts/page-container1"
+import Empty from "components/empty"
+import { User } from "src/types/user"
+import moment from 'moment'
+import { getSvgIcon } from "components/svg-icon"
+import { Field, Form, Formik } from "formik"
+import { validateEmail, validateNickname, validateUsername } from "utils/user"
+import { Role } from "src/types/role"
+
+const PostsPage = () => {
+ const { isOpen, onOpen, onClose } = useDisclosure()
+ const [currentUser,setCurrentUser]:[User,any] = useState(null)
+ const [users, setUsers]: [User[], any] = useState([])
+ const router = useRouter()
+ const toast = useToast()
+ const getUsers = async () => {
+ const res = await requestApi.get(`/admin/user/all`)
+ setUsers(res.data)
+ }
+
+ useEffect(() => {
+ getUsers()
+ }, [])
+
+ const onEditUser = user => {
+ if (!user) {
+ // add user
+ setCurrentUser({role:Role.NORMAL})
+ } else {
+ // edit user
+ setCurrentUser(user)
+ }
+ onOpen()
+ }
+
+ const submitUser = async values => {
+ await requestApi.post("/admin/user",values)
+ getUsers()
+ onClose()
+ }
+
+ return (
+ <>
+
+
+
+
+
+ 用户列表({users.length})
+
+
+
+
+
+ 用户名 |
+ 邮箱 |
+ 角色 |
+ 加入时间 |
+ |
+
+
+
+ {
+ users.map((user, i) =>
+ {user.username} |
+ {user.email} |
+ {user.role} |
+ {moment(user.created).fromNow()} |
+
+ onEditUser(user)} />
+ {/* onDeleteUser(user)} /> */}
+ |
+
)
+ }
+
+
+
+
+
+
+
+
+
+ {currentUser &&
+ {currentUser.id ? '编辑用户' : '新建用户'}
+
+
+ {(props) => (
+
+ )}
+
+
+ }
+
+ >
+ )
+}
+export default PostsPage
+
diff --git a/pages/settings/orgs.tsx b/pages/settings/orgs.tsx
index b80a6f53..8518cbb7 100644
--- a/pages/settings/orgs.tsx
+++ b/pages/settings/orgs.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, Table, Thead, Tr, Th, Tbody, Td, IconButton, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, NumberInput, NumberInputField, NumberInputStepper, NumberIncrementStepper, NumberDecrementStepper, useColorModeValue, StackDivider } from "@chakra-ui/react"
+import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, HStack, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, useColorModeValue, StackDivider } from "@chakra-ui/react"
import Card from "components/card"
import PageContainer from "layouts/page-container"
import Sidebar from "layouts/sidebar/sidebar"
@@ -8,7 +8,7 @@ import { requestApi } from "utils/axios/request"
import { Org } from "src/types/org"
import { Field, Form, Formik } from "formik"
import { config } from "configs/config"
-import { isUsernameChar, usernameInvalidTips } from "utils/user"
+import { isUsernameChar, usernameInvalidTips, validateNickname, validateUsername } from "utils/user"
import { isAdmin } from "utils/role"
import userCustomTheme from "theme/user-custom"
import { useRouter } from "next/router"
@@ -41,41 +41,7 @@ const UserOrgsPage = () => {
onOpen()
}
- const validateUsername = async value => {
- let error
- if (!value?.trim()) {
- return "不能为空"
- }
-
- if (value?.length > config.user.usernameMaxLen) {
- return `长度不能超过${config.user.usernameMaxLen}`
- }
-
- for (const c of value) {
- if (!isUsernameChar(c)) {
- return usernameInvalidTips
- }
- }
-
- const res = await requestApi.get(`/username/exist/${value}`)
- if (res.data) {
- return `The name '${value}' is already taken.`
- }
- return error
- }
-
- function validateNickname(value) {
- let error
- if (!value?.trim()) {
- error = "不能为空"
- }
-
- if (value?.length > config.user.usernameMaxLen) {
- error = `长度不能超过${config.user.usernameMaxLen}`
- }
-
- return error
- }
+
return (
diff --git a/pages/settings/profile.tsx b/pages/settings/profile.tsx
index 28946a74..2fa9bd45 100644
--- a/pages/settings/profile.tsx
+++ b/pages/settings/profile.tsx
@@ -10,7 +10,8 @@ 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');
+import { validateNickname ,validateEmail,validateUrl} from "utils/user"
+
const UserProfilePage = () => {
const [user, setUser] = useState(null)
@@ -33,57 +34,8 @@ const UserProfilePage = () => {
})
}
- 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
diff --git a/server/internal/admin/admin.go b/server/internal/admin/admin.go
new file mode 100644
index 00000000..f218f2ba
--- /dev/null
+++ b/server/internal/admin/admin.go
@@ -0,0 +1,27 @@
+package admin
+
+import (
+ "net/http"
+
+ "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
+ "github.com/imdotdev/im.dev/server/pkg/db"
+ "github.com/imdotdev/im.dev/server/pkg/e"
+ "github.com/imdotdev/im.dev/server/pkg/models"
+)
+
+func GetUsers() ([]*models.User, *e.Error) {
+ users := make([]*models.User, 0)
+ rows, err := db.Conn.Query("SELECT id,username,email,role,created FROM user WHERE type=?", models.IDTypeUser)
+ if err != nil {
+ logger.Warn("get users error", "error", err)
+ return nil, e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ for rows.Next() {
+ user := &models.User{}
+ rows.Scan(&user.ID, &user.Username, &user.Email, &user.Role, &user.Created)
+ users = append(users, user)
+ }
+
+ return users, nil
+}
diff --git a/server/internal/api/admin.go b/server/internal/api/admin.go
new file mode 100644
index 00000000..6cf4953d
--- /dev/null
+++ b/server/internal/api/admin.go
@@ -0,0 +1,53 @@
+package api
+
+import (
+ "net/http"
+
+ "github.com/asaskevich/govalidator"
+ "github.com/gin-gonic/gin"
+ "github.com/imdotdev/im.dev/server/internal/admin"
+ "github.com/imdotdev/im.dev/server/internal/user"
+ "github.com/imdotdev/im.dev/server/pkg/common"
+ "github.com/imdotdev/im.dev/server/pkg/e"
+ "github.com/imdotdev/im.dev/server/pkg/models"
+)
+
+func AdminSubmitUser(c *gin.Context) {
+ currentUser := user.CurrentUser(c)
+ if !currentUser.Role.IsAdmin() {
+ c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
+ return
+ }
+
+ u := &models.User{}
+ c.Bind(&u)
+
+ if u.Username == "" || u.Email == "" || !govalidator.IsEmail(u.Email) || !u.Role.IsValid() {
+ c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
+ return
+ }
+
+ err := user.SubmitUser(u)
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(nil))
+}
+
+func AdminGetUsers(c *gin.Context) {
+ currentUser := user.CurrentUser(c)
+ if !currentUser.Role.IsAdmin() {
+ c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
+ return
+ }
+
+ users, err := admin.GetUsers()
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(users))
+}
diff --git a/server/internal/api/posts.go b/server/internal/api/posts.go
index d3c4b0a3..5b1fe841 100644
--- a/server/internal/api/posts.go
+++ b/server/internal/api/posts.go
@@ -27,6 +27,24 @@ func GetEditorPosts(c *gin.Context) {
c.JSON(http.StatusOK, common.RespSuccess(ars))
}
+func GetOrgPosts(c *gin.Context) {
+ orgID := c.Param("id")
+ tp := c.Query("type")
+ if tp != models.IDTypeUndefined && !models.ValidStoryIDType(tp) {
+ c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
+ return
+ }
+
+ user := user.CurrentUser(c)
+ ars, err := story.OrgPosts(tp, user, orgID)
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(ars))
+}
+
func GetEditorDrafts(c *gin.Context) {
user := user.CurrentUser(c)
ars, err := story.UserDrafts(nil, user.ID)
diff --git a/server/internal/api/user.go b/server/internal/api/user.go
index a255458d..fffe2589 100644
--- a/server/internal/api/user.go
+++ b/server/internal/api/user.go
@@ -143,7 +143,7 @@ func DeleteUserNavbar(c *gin.Context) {
c.JSON(http.StatusOK, common.RespSuccess(nil))
}
-func NameExist(c *gin.Context) {
+func UserNameExist(c *gin.Context) {
name := c.Param("name")
if strings.TrimSpace(name) == "" {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
@@ -158,3 +158,19 @@ func NameExist(c *gin.Context) {
c.JSON(http.StatusOK, common.RespSuccess(exist))
}
+
+func UserEmailExist(c *gin.Context) {
+ email := c.Param("email")
+ if strings.TrimSpace(email) == "" {
+ c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
+ return
+ }
+
+ exist, err := user.EmailExist(email)
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(exist))
+}
diff --git a/server/internal/cache/cache.go b/server/internal/cache/cache.go
index 9f8a704e..63e5b7f6 100644
--- a/server/internal/cache/cache.go
+++ b/server/internal/cache/cache.go
@@ -16,7 +16,7 @@ func Init() {
time.Sleep(2 * time.Second)
for {
// load users
- rows, err := db.Conn.Query(`SELECT id,username,role,nickname,avatar,last_seen_at,created FROM user`)
+ rows, err := db.Conn.Query(`SELECT id,type,username,role,nickname,avatar,last_seen_at,created FROM user`)
if err != nil {
logger.Error("load users error", "error", err)
time.Sleep(60 * time.Second)
@@ -27,7 +27,7 @@ func Init() {
usersMap := make(map[string]*models.User)
for rows.Next() {
user := &models.User{}
- err := rows.Scan(&user.ID, &user.Username, &user.Role, &user.Nickname, &user.Avatar, &user.LastSeenAt, &user.Created)
+ err := rows.Scan(&user.ID, &user.Type, &user.Username, &user.Role, &user.Nickname, &user.Avatar, &user.LastSeenAt, &user.Created)
if err != nil {
logger.Warn("scan user error", "error", err)
continue
diff --git a/server/internal/interaction/follow.go b/server/internal/interaction/follow.go
index 05a1ea42..c1a47902 100644
--- a/server/internal/interaction/follow.go
+++ b/server/internal/interaction/follow.go
@@ -135,7 +135,7 @@ func GetFollowers(targetID, targetType string) ([]*models.User, *e.Error) {
u, ok := models.UsersMapCache[id]
if ok {
users = append(users, u)
- u.Followed = GetFollowed(u.ID, targetID)
+ u.Followed = GetFollowed(targetID, u.ID)
u.Follows = GetFollows(u.ID)
}
}
diff --git a/server/internal/server.go b/server/internal/server.go
index 3ed124ca..86ec4767 100644
--- a/server/internal/server.go
+++ b/server/internal/server.go
@@ -51,6 +51,7 @@ func (s *Server) Start() error {
r.POST("/story/comment", IsLogin(), api.SubmitComment)
r.DELETE("/story/comment/:id", IsLogin(), api.DeleteStoryComment)
r.GET("/story/posts/editor", IsLogin(), api.GetEditorPosts)
+ r.GET("/story/posts/org/:id", IsLogin(), api.GetOrgPosts)
r.GET("/story/posts/drafts", IsLogin(), api.GetEditorDrafts)
r.GET("/story/posts/home/:filter", api.GetHomePosts)
r.POST("/story", IsLogin(), api.SubmitStory)
@@ -87,6 +88,9 @@ func (s *Server) Start() error {
r.POST("/user/navbar", IsLogin(), api.SubmitUserNavbar)
r.GET("/user/navbars/:userID", api.GetUserNavbars)
r.DELETE("/user/navbar/:id", IsLogin(), api.DeleteUserNavbar)
+ r.GET("/user/name/exist/:name", api.UserNameExist)
+ r.GET("/user/email/exist/:email", api.UserEmailExist)
+
// interaction apis
r.POST("/interaction/like/:id", IsLogin(), api.Like)
r.POST("/interaction/follow/:id", IsLogin(), api.Follow)
@@ -104,12 +108,17 @@ 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)
+
+ // admin apis
+ r.POST("/admin/user", IsLogin(), api.AdminSubmitUser)
+ r.GET("/admin/user/all", IsLogin(), api.AdminGetUsers)
+
// other apis
r.GET("/config", GetConfig)
r.GET("/navbars", GetNavbars)
r.POST("/navbar", IsLogin(), SubmitNavbar)
r.DELETE("/navbar/:id", IsLogin(), DeleteNavbar)
- r.GET("/username/exist/:name", api.NameExist)
+
err := router.Run(config.Data.Server.Addr)
if err != nil {
logger.Crit("start backend server error", "error", err)
diff --git a/server/internal/storage/init.go b/server/internal/storage/init.go
index 2ff86ffc..e30c447b 100644
--- a/server/internal/storage/init.go
+++ b/server/internal/storage/init.go
@@ -41,9 +41,9 @@ func Init() error {
}
var navbars = []*models.Navbar{
- &models.Navbar{Label: "主页", Value: "/", Weight: 0},
+ &models.Navbar{Label: "主页", Value: "/", Weight: 2},
&models.Navbar{Label: "标签", Value: "/tags", Weight: 1},
- &models.Navbar{Label: "Search", Value: "/search/posts", Weight: 2},
+ &models.Navbar{Label: "Search", Value: "/search/posts", Weight: 0},
}
func initTables() error {
diff --git a/server/internal/story/posts.go b/server/internal/story/posts.go
index e411c799..16e22818 100644
--- a/server/internal/story/posts.go
+++ b/server/internal/story/posts.go
@@ -64,6 +64,40 @@ func UserPosts(tp string, user *models.User, uid string) (models.Stories, *e.Err
return newPosts, nil
}
+func OrgPosts(tp string, user *models.User, orgID string) (models.Stories, *e.Error) {
+ var rows *sql.Rows
+ var err error
+ if tp == models.IDTypeUndefined {
+ rows, err = db.Conn.Query(PostQueryPrefix+"where owner=? and status=?", orgID, models.StatusPublished)
+ } else {
+ rows, err = db.Conn.Query(PostQueryPrefix+"where owner=? and type=? and status=?", orgID, tp, models.StatusPublished)
+ }
+
+ if err != nil && err != sql.ErrNoRows {
+ logger.Warn("get user posts error", "error", err)
+ return nil, e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ posts := GetPosts(user, rows)
+
+ sort.Sort(posts)
+
+ pinned := make([]*models.Story, 0)
+ unpinned := make([]*models.Story, 0)
+
+ for _, post := range posts {
+ post.Pinned = GetPinned(post.ID, user.ID)
+ if post.Pinned {
+ pinned = append(pinned, post)
+ } else {
+ unpinned = append(unpinned, post)
+ }
+ }
+
+ newPosts := append(pinned, unpinned...)
+ return newPosts, nil
+}
+
func UserDrafts(user *models.User, uid string) (models.Stories, *e.Error) {
rows, err := db.Conn.Query(PostQueryPrefix+"where creator=? and status=?", uid, models.StatusDraft)
if err != nil && err != sql.ErrNoRows {
diff --git a/server/internal/user/user.go b/server/internal/user/user.go
index f3305a36..457eb979 100644
--- a/server/internal/user/user.go
+++ b/server/internal/user/user.go
@@ -12,11 +12,16 @@ import (
"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 GetUsers(q string) ([]*models.User, *e.Error) {
users := make([]*models.User, 0)
for _, u := range models.UsersCache {
+ if u.Type != models.IDTypeUser {
+ continue
+ }
+
if strings.HasPrefix(strings.ToLower(u.Nickname), strings.ToLower(q)) {
users = append(users, u)
continue
@@ -142,3 +147,52 @@ func NameExist(name string) (bool, *e.Error) {
return true, nil
}
+
+func EmailExist(email string) (bool, *e.Error) {
+ var ne string
+ err := db.Conn.QueryRow("SELECT email FROM user WHERE email=?", email).Scan(&ne)
+ if err != nil && err != sql.ErrNoRows {
+ logger.Warn("check email exist error", "error", err)
+ return false, e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ if err == sql.ErrNoRows {
+ return false, nil
+ }
+
+ return true, nil
+}
+
+func SubmitUser(user *models.User) *e.Error {
+ if user.Nickname == "" {
+ user.Nickname = "New user"
+ }
+
+ var err error
+ now := time.Now()
+ if user.ID == "" {
+ // create user
+ emailExist, err0 := EmailExist(user.Email)
+ if err0 != nil {
+ return e.New(err0.Status, err0.Message)
+ }
+
+ if emailExist {
+ return e.New(http.StatusConflict, "邮箱地址已存在")
+ }
+
+ user.ID = utils.GenID(models.IDTypeUser)
+ _, err = db.Conn.Exec("INSERT INTO user (id,type,email,username,nickname,role,created,updated) VALUES (?,?,?,?,?,?,?,?)",
+ user.ID, models.IDTypeUser, user.Email, user.Username, user.Nickname, user.Role, now, now)
+ } else {
+ // update user
+ _, err = db.Conn.Exec("UPDATE user SET role=?,updated=? WHERE id=?", user.Role, now, user.ID)
+ }
+
+ if err != nil {
+ logger.Warn("submit user error", "error", err)
+ return e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ return nil
+}
diff --git a/src/components/story/stories.tsx b/src/components/story/stories.tsx
index 125a4297..ba3b24ec 100644
--- a/src/components/story/stories.tsx
+++ b/src/components/story/stories.tsx
@@ -12,11 +12,12 @@ interface Props {
showPinned?: boolean
type?: string
highlight?: string
+ showOrg?:boolean
}
export const Stroies = (props: Props) => {
- const { stories,card=StoryCard,showFooter=true,type="classic",showPinned = false} = props
+ const { stories,card=StoryCard,showFooter=true,type="classic",showPinned = false,showOrg=true} = props
const borderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark)
const Card = card
const showBorder = i => {
@@ -35,7 +36,7 @@ export const Stroies = (props: Props) => {
{stories.map((story,i) =>
-
+
)}
{showFooter &&
没有更多文章了}
diff --git a/src/components/story/story-author.tsx b/src/components/story/story-author.tsx
index 7976516f..72f21b55 100644
--- a/src/components/story/story-author.tsx
+++ b/src/components/story/story-author.tsx
@@ -13,15 +13,16 @@ type Props = PropsOf & {
size?: 'lg' | 'md'
story : Story
showFooter?: boolean
+ showOrg?: boolean
}
-export const StoryAuthor= ({story,showFooter=true,size='lg'}:Props) =>{
+export const StoryAuthor= ({story,showFooter=true,size='lg',showOrg=true}:Props) =>{
const router = useRouter()
return (
router.push(`/${story.creator.username}`)} cursor="pointer"/>
- {story.ownerId ?
+ {(showOrg && story.ownerId!=='') ?
{story.creator.nickname}
for
diff --git a/src/components/story/story-card.tsx b/src/components/story/story-card.tsx
index 6d242de0..4d89d5d4 100644
--- a/src/components/story/story-card.tsx
+++ b/src/components/story/story-card.tsx
@@ -16,6 +16,7 @@ interface Props {
story: Story
type?: string
highlight?: string
+ showOrg?: boolean
}
@@ -24,10 +25,9 @@ export const StoryCard = (props: Props) => {
const [isLargeScreen] = useMediaQuery("(min-width: 768px)")
const Layout = isLargeScreen ? HStack : VStack
-
return (
-
+
diff --git a/src/data/links.tsx b/src/data/links.tsx
index 20baaaef..aa0ff539 100644
--- a/src/data/links.tsx
+++ b/src/data/links.tsx
@@ -70,6 +70,12 @@ export const adminLinks: Route[] = [{
icon: getSvgIcon("navbar"),
disabled: false
},
+{
+ title: '用户管理',
+ path: `${ReserveUrls.Admin}/users`,
+ icon: getSvgIcon("user"),
+ disabled: false
+},
]
diff --git a/src/utils/user.ts b/src/utils/user.ts
index 13badcc0..2686049d 100644
--- a/src/utils/user.ts
+++ b/src/utils/user.ts
@@ -1,4 +1,8 @@
+import { config } from 'configs/config'
import {User} from 'src/types/user'
+import { requestApi } from './axios/request'
+var validator = require('validator');
+
export function getUserName(user:User) {
return user.nickname === "" ? user.username : user.nickname
}
@@ -11,4 +15,84 @@ export function isUsernameChar(c) {
return false
}
-export const usernameInvalidTips = "May only contain alphanumeric characters or single hyphens, and cannot begin or end with a hyphen."
\ No newline at end of file
+export const usernameInvalidTips = "May only contain alphanumeric characters or single hyphens, and cannot begin or end with a hyphen."
+
+
+export const validateUsername = async value => {
+ let error
+ if (!value?.trim()) {
+ return "不能为空"
+ }
+
+ if (value?.length > config.user.usernameMaxLen) {
+ return `长度不能超过${config.user.usernameMaxLen}`
+ }
+
+ for (const c of value) {
+ if (!isUsernameChar(c)) {
+ return usernameInvalidTips
+ }
+ }
+
+ const res = await requestApi.get(`/user/name/exist/${value}`)
+ if (res.data) {
+ return `The name '${value}' is already taken.`
+ }
+ return error
+}
+
+export function validateNickname(value) {
+ let error
+ if (!value?.trim()) {
+ error = "不能为空"
+ }
+
+ if (value?.length > config.user.usernameMaxLen) {
+ error = `长度不能超过${config.user.usernameMaxLen}`
+ }
+
+ return error
+}
+
+export async function validateEmail(value) {
+ let email = value?.trim()
+ if (!email) {
+ return "邮箱不能为空"
+ }
+
+ if (email?.length > config.user.usernameMaxLen) {
+ return `长度不能超过${config.user.usernameMaxLen}`
+ }
+
+ if (email) {
+ if (!validator.isEmail(email)) {
+ return "Email格式不合法"
+ }
+ }
+
+ const res = await requestApi.get(`/user/email/exist/${value}`)
+ if (res.data) {
+ return `The email '${value}' is already taken.`
+ }
+}
+
+
+export 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
+}
\ No newline at end of file