diff --git a/config.yaml b/config.yaml
index d83207aa..1099fcbb 100644
--- a/config.yaml
+++ b/config.yaml
@@ -11,8 +11,10 @@ server:
#################################### User/Session ##############################
user:
- # github username
+ # username for superadmin, setted only in first run, can't be changed
super_admin_username: "sunface"
+ # email for superadmin
+ super_admin_email: "cto@188.com"
# a session is created when user login to im.dev, this session will be expired after X seconds
session_expire: 259200
diff --git a/layouts/nav/nav.tsx b/layouts/nav/nav.tsx
index aaa3a83f..5a306a90 100644
--- a/layouts/nav/nav.tsx
+++ b/layouts/nav/nav.tsx
@@ -93,11 +93,11 @@ function HeaderContent() {
-
+ /> */}
diff --git a/layouts/nav/vertical-nav.tsx b/layouts/nav/vertical-nav.tsx
new file mode 100644
index 00000000..cf4c33f2
--- /dev/null
+++ b/layouts/nav/vertical-nav.tsx
@@ -0,0 +1,136 @@
+import {
+ chakra,
+ Flex,
+ HStack,
+ IconButton,
+ useColorModeValue,
+ useDisclosure,
+ useUpdateEffect,
+ Box,
+ VStack,
+ useMediaQuery
+ } from "@chakra-ui/react"
+ import siteConfig from "configs/site-config"
+ import { useViewportScroll } from "framer-motion"
+ import NextLink from "next/link"
+ import React from "react"
+ import { FaGithub, FaSearch } from "react-icons/fa"
+ import Logo, { LogoIcon } from "src/components/logo"
+ import { MobileNavButton, MobileNavContent } from "./mobile-nav"
+ import AlgoliaSearch from "src/components/search/algolia-search"
+ import { useRouter } from "next/router"
+ import { ReserveUrls } from "src/data/reserve-urls"
+ import Link from "next/link"
+ import DarkMode from "components/dark-mode"
+ import AccountMenu from "components/account-menu"
+
+ const navLinks = [{
+ title: '主页',
+ url: '/',
+ },
+ {
+ title: '标签',
+ url: ReserveUrls.Tags,
+ },
+ {
+ title: '学习资料',
+ url: ReserveUrls.Courses,
+ },
+ ]
+
+
+ function HeaderContent() {
+ const router = useRouter()
+ const { asPath } = router
+ const mobileNav = useDisclosure()
+
+
+ const mobileNavBtnRef = React.useRef()
+ const [isLargerThan768] = useMediaQuery("(min-width: 768px)")
+ useUpdateEffect(() => {
+ mobileNavBtnRef.current?.focus()
+ }, [mobileNav.isOpen])
+
+
+ return (
+ <>
+
+
+
+
+ {isLargerThan768 ? : }
+
+
+
+
+ {navLinks.map(link => {link.title})}
+
+
+
+
+ }
+ />
+
+
+ }
+ />
+
+
+
+ {/* */}
+
+
+
+ >
+ )
+ }
+
+ function VerticalNav(props) {
+ const ref = React.useRef()
+
+ return (
+
+
+
+
+
+ )
+ }
+
+ export default VerticalNav
+
+
\ No newline at end of file
diff --git a/layouts/sidebar/sidebar.tsx b/layouts/sidebar/sidebar.tsx
index 910e00a3..786c4f7e 100644
--- a/layouts/sidebar/sidebar.tsx
+++ b/layouts/sidebar/sidebar.tsx
@@ -1,4 +1,4 @@
-import { Box, Stack } from "@chakra-ui/react"
+import { Box, Heading, Stack, VStack } from "@chakra-ui/react"
import Card from "components/card"
import { useRouter } from "next/router"
import * as React from "react"
@@ -22,11 +22,13 @@ export function SidebarContent(props) {
)
}
-const Sidebar = ({ routes, ...props }) => {
+const Sidebar = ({ routes,title, ...props }) => {
const { pathname } = useRouter()
const ref = React.useRef(null)
return (
+
+ {title}
{
sx={{
overscrollBehavior: "contain",
}}
- top="6.5rem"
+ top="8.5rem"
pr="3"
pb="6"
pl="3"
@@ -44,11 +46,12 @@ const Sidebar = ({ routes, ...props }) => {
overflowY="auto"
className="sidebar-content"
flexShrink={0}
- display={{ base: "none", md: "block" }}
+ // display={{ base: "none", md: "block" }}
>
+
)
}
diff --git a/pages/[username]/index.tsx b/pages/[username]/index.tsx
index b1451add..fb818169 100644
--- a/pages/[username]/index.tsx
+++ b/pages/[username]/index.tsx
@@ -1,23 +1,58 @@
-import { chakra } from "@chakra-ui/react"
+import { Box, chakra, Flex, HStack, VStack ,Image, Heading, Text, Button, useColorModeValue} from "@chakra-ui/react"
+import Card from "components/card"
import Container from "components/container"
import SEO from "components/seo"
import siteConfig from "configs/site-config"
+import useSession from "hooks/use-session"
import Nav from "layouts/nav/nav"
+import VerticalNav from "layouts/nav/vertical-nav"
import PageContainer from "layouts/page-container"
import { useRouter } from "next/router"
-import React from "react"
+import React, { useEffect, useState } from "react"
+import { FaEdit, FaPlus } from "react-icons/fa"
+import { User } from "src/types/session"
+import { requestApi } from "utils/axios/request"
const UserPage = () => {
const router = useRouter()
+ const username = router.query.username
+ const session = useSession()
+ const [user,setUser]:[User,any] = useState(null)
+ const borderColor = useColorModeValue('white','transparent')
+ useEffect(() => {
+ if (username) {
+ requestApi.get(`/user/info/${username}`).then(res => setUser(res.data))
+ }
+ },[username])
return (
<>
-
- {router.query.username}'s home
-
+
+
+
+ {
+ user &&
+
+
+
+
+
+
+ {user.nickname}
+ {user.tagline}
+ {session?.user.id === user.id ?
+
+ :} colorScheme="teal" mt="6">Follow}
+
+
+
+
+ }
+
+
>
)}
diff --git a/pages/admin/tags.tsx b/pages/admin/tags.tsx
index 7f68f2da..161cd1ac 100644
--- a/pages/admin/tags.tsx
+++ b/pages/admin/tags.tsx
@@ -46,7 +46,7 @@ const PostsPage = () => {
<>
-
+
标签列表({tags.length})
diff --git a/pages/editor/posts.tsx b/pages/editor/posts.tsx
index 63c2211a..41803599 100644
--- a/pages/editor/posts.tsx
+++ b/pages/editor/posts.tsx
@@ -99,7 +99,7 @@ const PostsPage = () => {
<>
-
+
文章列表({posts.length})
diff --git a/pages/settings/profile.tsx b/pages/settings/profile.tsx
new file mode 100644
index 00000000..f006238f
--- /dev/null
+++ b/pages/settings/profile.tsx
@@ -0,0 +1,298 @@
+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, settingLinks } from "src/data/links"
+import { requestApi } from "utils/axios/request"
+import TagCard from "components/posts/tag-edit-card"
+import { Post } from "src/types/posts"
+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 { Field, Form, Formik } from "formik"
+import useSession from "hooks/use-session"
+import { config } from "utils/config"
+var validator = require('validator');
+
+const UserProfilePage = () => {
+ const [user, setUser] = useState(null)
+ 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/public/edit.svg b/public/edit.svg
new file mode 100644
index 00000000..af05be6a
--- /dev/null
+++ b/public/edit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/server/internal/api/comment.go b/server/internal/api/comment.go
index 2de82a6a..89861132 100644
--- a/server/internal/api/comment.go
+++ b/server/internal/api/comment.go
@@ -5,8 +5,8 @@ import (
"strings"
"github.com/gin-gonic/gin"
- "github.com/imdotdev/im.dev/server/internal/session"
"github.com/imdotdev/im.dev/server/internal/story"
+ "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"
@@ -32,7 +32,7 @@ func SubmitComment(c *gin.Context) {
var err *e.Error
if comment.ID == "" { //add comment
- user := session.CurrentUser(c)
+ user := user.CurrentUser(c)
comment.CreatorID = user.ID
comment.ID = utils.GenStoryID(models.StoryComment)
err = story.AddComment(comment)
@@ -56,7 +56,7 @@ func GetStoryComments(c *gin.Context) {
return
}
- user := session.CurrentUser(c)
+ user := user.CurrentUser(c)
for _, comment := range comments {
if user != nil {
comment.Liked = story.GetLiked(comment.ID, user.ID)
@@ -87,7 +87,7 @@ func DeleteComment(c *gin.Context) {
return
}
- user := session.CurrentUser(c)
+ user := user.CurrentUser(c)
canDel := false
if user.Role.IsAdmin() {
canDel = true
diff --git a/server/internal/api/editor.go b/server/internal/api/editor.go
index db1cb313..fc76cb28 100644
--- a/server/internal/api/editor.go
+++ b/server/internal/api/editor.go
@@ -4,14 +4,14 @@ import (
"net/http"
"github.com/gin-gonic/gin"
- "github.com/imdotdev/im.dev/server/internal/session"
"github.com/imdotdev/im.dev/server/internal/story"
+ "github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/common"
"github.com/imdotdev/im.dev/server/pkg/e"
)
func GetEditorPosts(c *gin.Context) {
- user := session.CurrentUser(c)
+ user := user.CurrentUser(c)
ars, err := story.UserPosts(int64(user.ID))
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
@@ -28,7 +28,7 @@ func GetEditorPost(c *gin.Context) {
return
}
- user := session.CurrentUser(c)
+ user := user.CurrentUser(c)
creator, err := story.GetPostCreator(id)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
diff --git a/server/internal/api/story.go b/server/internal/api/story.go
index 05beaa68..6014e353 100644
--- a/server/internal/api/story.go
+++ b/server/internal/api/story.go
@@ -4,8 +4,8 @@ import (
"net/http"
"github.com/gin-gonic/gin"
- "github.com/imdotdev/im.dev/server/internal/session"
"github.com/imdotdev/im.dev/server/internal/story"
+ "github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/common"
"github.com/imdotdev/im.dev/server/pkg/e"
)
@@ -27,7 +27,7 @@ func DeletePost(c *gin.Context) {
return
}
- user := session.CurrentUser(c)
+ user := user.CurrentUser(c)
creator, err := story.GetPostCreator(id)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
@@ -56,7 +56,7 @@ func GetPost(c *gin.Context) {
return
}
- user := session.CurrentUser(c)
+ user := user.CurrentUser(c)
if user == nil {
ar.Liked = false
} else {
@@ -68,7 +68,7 @@ func GetPost(c *gin.Context) {
}
func LikeStory(c *gin.Context) {
- user := session.CurrentUser(c)
+ user := user.CurrentUser(c)
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
diff --git a/server/internal/api/tags.go b/server/internal/api/tags.go
index c8219d47..c872249e 100644
--- a/server/internal/api/tags.go
+++ b/server/internal/api/tags.go
@@ -5,8 +5,8 @@ import (
"strconv"
"github.com/gin-gonic/gin"
- "github.com/imdotdev/im.dev/server/internal/session"
"github.com/imdotdev/im.dev/server/internal/tags"
+ "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"
@@ -34,7 +34,7 @@ func GetTags(c *gin.Context) {
}
func SubmitTag(c *gin.Context) {
- user := session.CurrentUser(c)
+ user := user.CurrentUser(c)
if !user.Role.IsAdmin() {
c.JSON(http.StatusForbidden, common.RespError(e.NoEditorPermission))
return
@@ -64,7 +64,7 @@ func DeleteTag(c *gin.Context) {
return
}
- user := session.CurrentUser(c)
+ user := user.CurrentUser(c)
if !user.Role.IsAdmin() {
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
}
diff --git a/server/internal/api/users.go b/server/internal/api/users.go
index 02353956..953782b6 100644
--- a/server/internal/api/users.go
+++ b/server/internal/api/users.go
@@ -4,13 +4,15 @@ import (
"net/http"
"github.com/gin-gonic/gin"
- "github.com/imdotdev/im.dev/server/internal/session"
+ "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 GetUsers(c *gin.Context) {
query := c.Query("query")
- users, err := session.GetUsers(query)
+ users, err := user.GetUsers(query)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
@@ -18,3 +20,51 @@ func GetUsers(c *gin.Context) {
c.JSON(http.StatusOK, common.RespSuccess(users))
}
+
+func GetUserSelf(c *gin.Context) {
+ u := user.CurrentUser(c)
+
+ userDetail, err := user.GetUserDetail(u.ID, "")
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(userDetail))
+}
+
+func GetUser(c *gin.Context) {
+ username := c.Param("username")
+
+ userDetail, err := user.GetUserDetail(0, username)
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(userDetail))
+}
+
+func UpdateUser(c *gin.Context) {
+ u := &models.User{}
+ c.Bind(&u)
+
+ currentUser := user.CurrentUser(c)
+ if currentUser.ID != u.ID {
+ c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
+ return
+ }
+
+ err := user.UpdateUser(u)
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(nil))
+}
+
+func GetSession(c *gin.Context) {
+ sess := user.GetSession(c)
+ c.JSON(http.StatusOK, common.RespSuccess(sess))
+}
diff --git a/server/internal/server.go b/server/internal/server.go
index f80942e7..20adcc40 100644
--- a/server/internal/server.go
+++ b/server/internal/server.go
@@ -6,8 +6,8 @@ import (
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/api"
"github.com/imdotdev/im.dev/server/internal/cache"
- "github.com/imdotdev/im.dev/server/internal/session"
"github.com/imdotdev/im.dev/server/internal/storage"
+ "github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/common"
"github.com/imdotdev/im.dev/server/pkg/config"
"github.com/imdotdev/im.dev/server/pkg/e"
@@ -44,8 +44,8 @@ func (s *Server) Start() error {
r := router.Group("/api")
{
- r.POST("/login", session.Login)
- r.POST("/logout", session.Logout)
+ r.POST("/login", user.Login)
+ r.POST("/logout", user.Logout)
r.GET("/uiconfig", GetUIConfig)
}
@@ -69,6 +69,10 @@ func (s *Server) Start() error {
r.GET("/tag/:name", api.GetTag)
r.GET("/users", api.GetUsers)
+ r.GET("/user/self", IsLogin(), api.GetUserSelf)
+ r.GET("/user/info/:username", api.GetUser)
+ r.POST("/user/update", IsLogin(), api.UpdateUser)
+ r.GET("/session", IsLogin(), api.GetSession)
err := router.Run(config.Data.Server.Addr)
if err != nil {
logger.Crit("start backend server error", "error", err)
@@ -107,7 +111,7 @@ func Cors() gin.HandlerFunc {
// Auth is a gin middleware for user auth
func IsLogin() gin.HandlerFunc {
return func(c *gin.Context) {
- user := session.CurrentUser(c)
+ user := user.CurrentUser(c)
if user == nil {
c.JSON(http.StatusUnauthorized, common.RespError(e.NeedLogin))
c.Abort()
diff --git a/server/internal/session/users.go b/server/internal/session/users.go
deleted file mode 100644
index 77169b61..00000000
--- a/server/internal/session/users.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package session
-
-import (
- "strings"
-
- "github.com/imdotdev/im.dev/server/internal/cache"
- "github.com/imdotdev/im.dev/server/pkg/e"
- "github.com/imdotdev/im.dev/server/pkg/models"
-)
-
-func GetUsers(q string) ([]*models.User, *e.Error) {
- allUsers := cache.Users
-
- users := make([]*models.User, 0)
- for _, u := range allUsers {
- if strings.HasPrefix(strings.ToLower(u.Nickname), strings.ToLower(q)) {
- users = append(users, u)
- continue
- }
-
- if strings.HasPrefix(strings.ToLower(u.Username), strings.ToLower(q)) {
- users = append(users, u)
- continue
- }
- }
-
- return users, nil
-}
diff --git a/server/internal/storage/init.go b/server/internal/storage/init.go
index 2624fb3f..6ca1f5ae 100644
--- a/server/internal/storage/init.go
+++ b/server/internal/storage/init.go
@@ -56,8 +56,8 @@ func initTables() error {
}
now := time.Now()
- _, err := db.Conn.Exec(`INSERT INTO user (id,username,role,nickname,avatar,created,updated) VALUES (?,?,?,?,?,?,?)`,
- 1, config.Data.User.SuperAdminUsername, models.ROLE_SUPER_ADMIN, "", "", now, now)
+ _, err := db.Conn.Exec(`INSERT INTO user (id,username,email,role,nickname,avatar,created,updated) VALUES (?,?,?,?,?,?,?,?)`,
+ 1, config.Data.User.SuperAdminUsername, config.Data.User.SuperAdminEmail, models.ROLE_SUPER_ADMIN, "", "", now, now)
if err != nil {
log.RootLogger.Crit("init super admin error", "error:", err)
return err
diff --git a/server/internal/storage/sql_tables.go b/server/internal/storage/sql_tables.go
index 7fb131d9..279699d2 100644
--- a/server/internal/storage/sql_tables.go
+++ b/server/internal/storage/sql_tables.go
@@ -6,7 +6,7 @@ var sqlTables = map[string]string{
username VARCHAR(255) NOT NULL UNIQUE,
nickname VARCHAR(255) DEFAULT '',
avatar VARCHAR(255) DEFAULT '',
- email VARCHAR(255) UNIQUE DEFAULT '',
+ email VARCHAR(255) UNIQUE NOT NULL,
role VARCHAR(20) NOT NULL,
last_seen_at DATETIME DEFAULT CURRENT_DATETIME,
@@ -22,6 +22,26 @@ var sqlTables = map[string]string{
CREATE INDEX IF NOT EXISTS user_email
ON user (email);`,
+ "user_profile": `CREATE TABLE IF NOT EXISTS user_profile (
+ id INTEGER PRIMARY KEY,
+
+ tagline VARCHAR(255),
+ cover VARCHAR(255),
+ location VARCHAR(255),
+ avail_for TEXT,
+ about TEXT,
+
+ website VARCHAR(255),
+ twitter VARCHAR(255),
+ github VARCHAR(255),
+ zhihu VARCHAR(255),
+ weibo VARCHAR(255),
+ facebook VARCHAR(255),
+ stackoverflow VARCHAR(255),
+
+ updated DATETIME
+ );`,
+
"sessions": `CREATE TABLE IF NOT EXISTS sessions (
sid VARCHAR(255) primary key,
user_id INTEGER
diff --git a/server/internal/story/comment.go b/server/internal/story/comment.go
index d81c50f8..2f31716d 100644
--- a/server/internal/story/comment.go
+++ b/server/internal/story/comment.go
@@ -89,7 +89,14 @@ func GetComment(id string) (*models.Comment, *e.Error) {
}
func DeleteComment(id string) *e.Error {
- _, err := db.Conn.Exec("DELETE FROM comments WHERE id=?", id)
+ // delete children replies
+ _, err := db.Conn.Exec("DELETE FROM comments WHERE target_id=?", id)
+ if err != nil {
+ logger.Warn("delete comment replies error", "error", err)
+ return e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ _, err = db.Conn.Exec("DELETE FROM comments WHERE id=?", id)
if err != nil {
logger.Warn("delete comment error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
diff --git a/server/internal/story/post.go b/server/internal/story/post.go
index b4a105c2..2b8e9d1f 100644
--- a/server/internal/story/post.go
+++ b/server/internal/story/post.go
@@ -11,8 +11,8 @@ import (
"github.com/asaskevich/govalidator"
"github.com/gin-gonic/gin"
- "github.com/imdotdev/im.dev/server/internal/session"
"github.com/imdotdev/im.dev/server/internal/tags"
+ "github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/config"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
@@ -50,7 +50,7 @@ func UserPosts(uid int64) (models.Posts, *e.Error) {
}
func SubmitPost(c *gin.Context) (map[string]string, *e.Error) {
- user := session.CurrentUser(c)
+ user := user.CurrentUser(c)
post := &models.Post{}
err := c.Bind(&post)
diff --git a/server/internal/ui_config.go b/server/internal/ui_config.go
index 87476482..b2b645c5 100644
--- a/server/internal/ui_config.go
+++ b/server/internal/ui_config.go
@@ -9,8 +9,9 @@ import (
)
type UIConfig struct {
- Posts *PostsConfig `json:"posts"`
- User *UserConfig `json:"user"`
+ CommonMaxLen int `json:"commonMaxlen"`
+ Posts *PostsConfig `json:"posts"`
+ User *UserConfig `json:"user"`
}
type PostsConfig struct {
@@ -28,6 +29,7 @@ type UserConfig struct {
// 在后台页面配置,存储到mysql中
func GetUIConfig(c *gin.Context) {
conf := &UIConfig{
+ CommonMaxLen: 255,
Posts: &PostsConfig{
TitleMaxLen: config.Data.Posts.TitleMaxLen,
BriefMaxLen: config.Data.Posts.BriefMaxLen,
diff --git a/server/internal/session/session.go b/server/internal/user/session.go
similarity index 87%
rename from server/internal/session/session.go
rename to server/internal/user/session.go
index 26cd6048..d7f6d0cb 100644
--- a/server/internal/session/session.go
+++ b/server/internal/user/session.go
@@ -1,4 +1,4 @@
-package session
+package user
import (
"database/sql"
@@ -111,6 +111,26 @@ func CurrentUser(c *gin.Context) *models.User {
return sess.User
}
+func GetSession(c *gin.Context) *Session {
+ token := getToken(c)
+ createTime, _ := strconv.ParseInt(token, 10, 64)
+ if createTime != 0 {
+ // check whether token is expired
+ if (time.Now().Unix() - createTime/1e9) > config.Data.User.SessionExpire {
+ deleteSession(token)
+ return nil
+ }
+ }
+
+ sess := loadSession(token)
+ if sess == nil {
+ // 用户未登陆或者session失效
+ return nil
+ }
+
+ return sess
+}
+
func loadSession(sid string) *Session {
var userid int64
q := `SELECT user_id FROM sessions WHERE sid=?`
diff --git a/server/internal/user/users.go b/server/internal/user/users.go
new file mode 100644
index 00000000..ab5501b0
--- /dev/null
+++ b/server/internal/user/users.go
@@ -0,0 +1,86 @@
+package user
+
+import (
+ "database/sql"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/imdotdev/im.dev/server/internal/cache"
+ "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(q string) ([]*models.User, *e.Error) {
+ allUsers := cache.Users
+
+ users := make([]*models.User, 0)
+ for _, u := range allUsers {
+ if strings.HasPrefix(strings.ToLower(u.Nickname), strings.ToLower(q)) {
+ users = append(users, u)
+ continue
+ }
+
+ if strings.HasPrefix(strings.ToLower(u.Username), strings.ToLower(q)) {
+ users = append(users, u)
+ continue
+ }
+ }
+
+ return users, nil
+}
+
+func GetUserDetail(id int64, username string) (*models.User, *e.Error) {
+ user := &models.User{}
+ err := user.Query(id, username, "")
+ if err != nil {
+ logger.Warn("query user error", "error", err)
+ return nil, e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ err = db.Conn.QueryRow("SELECT tagline,cover,location,avail_for,about,website,twitter,github,zhihu,weibo,facebook,stackoverflow from user_profile WHERE id=?", user.ID).Scan(
+ &user.Tagline, &user.Cover, &user.Location, &user.AvailFor, &user.About, &user.Website, &user.Twitter,
+ &user.Github, &user.Zhihu, &user.Weibo, &user.Facebook, &user.Stackoverflow,
+ )
+ if err != nil && err != sql.ErrNoRows {
+ logger.Warn("query user profile error", "error", err)
+ return nil, e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ return user, nil
+}
+
+func UpdateUser(u *models.User) *e.Error {
+ _, err := db.Conn.Exec("UPDATE user SET nickname=?,avatar=?,email=?,updated=? WHERE id=?", u.Nickname, u.Avatar, u.Email, time.Now(), u.ID)
+ if err != nil {
+ if e.IsErrUniqueConstraint(err) {
+ return e.New(http.StatusConflict, "email已经存在")
+ }
+ logger.Warn("update user error", "error", err)
+ return e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ var nid int64
+ err = db.Conn.QueryRow("SELECT id FROM user_profile WHERE id=?", u.ID).Scan(&nid)
+ if err != nil && err != sql.ErrNoRows {
+ logger.Warn("update user profile error", "error", err)
+ return e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ now := time.Now()
+ if err == sql.ErrNoRows {
+ _, err = db.Conn.Exec("INSERT INTO user_profile (id,tagline,cover,location,avail_for,about,website,twitter,github,zhihu,weibo,facebook,stackoverflow,updated) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
+ u.ID, u.Tagline, u.Cover, u.Location, u.AvailFor, u.About, u.Website, u.Twitter, u.Github, u.Zhihu, u.Weibo, u.Facebook, u.Stackoverflow, now)
+ } else {
+ _, err = db.Conn.Exec("UPDATE user_profile SET tagline=?,cover=?,location=?,avail_for=?,about=?,website=?,twitter=?,github=?,zhihu=?,weibo=?,facebook=?,stackoverflow=?,updated=? WHERE id=?",
+ u.Tagline, u.Cover, u.Location, u.AvailFor, u.About, u.Website, u.Twitter, u.Github, u.Zhihu, u.Weibo, u.Facebook, u.Stackoverflow, now, u.ID)
+ }
+
+ if err != nil {
+ logger.Warn("update user profile error", "error", err)
+ return e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ return nil
+}
diff --git a/server/pkg/config/config.go b/server/pkg/config/config.go
index 2f1d552f..07584186 100644
--- a/server/pkg/config/config.go
+++ b/server/pkg/config/config.go
@@ -19,6 +19,7 @@ type Config struct {
User struct {
SuperAdminUsername string `yaml:"super_admin_username"`
+ SuperAdminEmail string `yaml:"super_admin_email"`
SessionExpire int64 `yaml:"session_expire"`
}
diff --git a/server/pkg/models/user.go b/server/pkg/models/user.go
index ce8233bf..72f9b25c 100644
--- a/server/pkg/models/user.go
+++ b/server/pkg/models/user.go
@@ -7,12 +7,28 @@ import (
)
type User struct {
- ID int64 `json:"id"`
- Username string `json:"username"`
- Nickname string `json:"nickname"`
- Avatar string `json:"avatar"`
- Email string `json:"email"`
- Role RoleType `json:"role"`
+ ID int64 `json:"id"`
+ Username string `json:"username"`
+ Nickname string `json:"nickname"`
+ Avatar string `json:"avatar"`
+ Email string `json:"email"`
+ Role RoleType `json:"role"`
+
+ Tagline string `json:"tagline"`
+ Cover string `json:"cover"`
+ Location string `json:"location"`
+ AvailFor string `json:"availFor"`
+ About string `json:"about"`
+ Skills string `json:"skills"`
+
+ Website string `json:"website"`
+ Twitter string `json:"twitter"`
+ Github string `json:"github"`
+ Zhihu string `json:"zhihu"`
+ Weibo string `json:"weibo"`
+ Facebook string `json:"facebook"`
+ Stackoverflow string `json:"stackoverflow"`
+
LastSeenAt time.Time `json:"lastSeenAt,omitempty"`
Created time.Time `json:"created"`
}
diff --git a/src/components/account-menu.tsx b/src/components/account-menu.tsx
index 2720cdb3..0899aef6 100644
--- a/src/components/account-menu.tsx
+++ b/src/components/account-menu.tsx
@@ -49,15 +49,17 @@ export const AccountMenu = () => {
ml={{ base: "0", md: "2" }}
/>
- }>
- Sunface
-
+
+ }>
+ {session.user.nickname}
+
+
{isEditor(session.user.role) && } >创作中心}
{isAdmin(session.user.role) && } >管理员}
}>书签收藏
- }>偏好设置
+ }>偏好设置
:
diff --git a/src/components/dark-mode.tsx b/src/components/dark-mode.tsx
index 0d85a7aa..38621998 100644
--- a/src/components/dark-mode.tsx
+++ b/src/components/dark-mode.tsx
@@ -2,7 +2,7 @@ import React from "react"
import { IconButton, useColorMode, useColorModeValue } from "@chakra-ui/react"
import { FaMoon, FaSun } from "react-icons/fa"
-export const DarkMode = () => {
+export const DarkMode = (props) => {
const { toggleColorMode: toggleMode } = useColorMode()
const text = useColorModeValue("dark", "light")
const SwitchIcon = useColorModeValue(FaMoon, FaSun)
@@ -10,7 +10,7 @@ export const DarkMode = () => {
return (
,
disabled: false
-}
-]
+}]
+
+
+export const settingLinks: Route[] = [{
+ title: '用户设置',
+ path: `${ReserveUrls.Settings}/profile`,
+ icon: ,
+ disabled: false
+}]
\ No newline at end of file
diff --git a/src/hooks/use-session.ts b/src/hooks/use-session.ts
index 04a78c0f..d7532b69 100644
--- a/src/hooks/use-session.ts
+++ b/src/hooks/use-session.ts
@@ -1,5 +1,6 @@
import { useEffect, useState } from "react"
import { Session } from "src/types/session"
+import { requestApi } from "utils/axios/request"
import events from "utils/events"
import storage from "utils/localStorage"
@@ -9,6 +10,10 @@ function useSession(): Session{
const sess = storage.get('session')
if (sess) {
setSession(sess)
+ // 页面重新进入时,跟服务器端进行信息同步
+ requestApi.get(`/session`).then(res => {
+ setSession(res.data)
+ })
}
events.on('set-session',storeSession)
diff --git a/src/types/session.ts b/src/types/session.ts
index cd0a2d1c..000d3bc5 100644
--- a/src/types/session.ts
+++ b/src/types/session.ts
@@ -1,23 +1,43 @@
+import { Tag } from './tag'
export interface Session {
- token: string
- createTime: string
- user : User
+ token: string
+ createTime: string
+ user: User
}
export interface User {
- id :number
- username: string
- nickname: string
- avatar: string
- role?: string
- email?: string
+ // basic info
+ id: number
+ username: string
+ nickname: string
+ avatar: string
+ role?: string
+ email?: string
+
+ // about user
+ tagline?: string
+ cover?: string
+ location?: string
+ availFor?: string
+ about?: string
+ skills?: Tag[]
+
+ // social links
+ website?: string
+ twitter?: string
+ github?: string
+ zhihu?: string
+ weibo?: string
+ facebook?: string
+ stackoverflow?: string
+
lastSeenAt?: string
created?: string
}
export interface UserSimple {
- id :number
- username: string
- nickname: string
- avatar: string
+ id: number
+ username: string
+ nickname: string
+ avatar: string
}
\ No newline at end of file
diff --git a/src/utils/config.ts b/src/utils/config.ts
index 699abf85..67fa8924 100644
--- a/src/utils/config.ts
+++ b/src/utils/config.ts
@@ -1,6 +1,7 @@
import { requestApi } from "./axios/request"
export let config = {
+ commonMaxlen: 255,
posts: {
titleMaxLen: 128,
briefMaxLen: 128,