From e0c35fae982bb64113c993cc40646ee8d87bf3b0 Mon Sep 17 00:00:00 2001 From: sunface Date: Fri, 5 Mar 2021 14:21:34 +0800 Subject: [PATCH] add following --- layouts/nav/post-nav.tsx | 153 ++++++++---------- pages/[username]/[post_id].tsx | 43 +++-- pages/[username]/index.tsx | 2 +- pages/_app.tsx | 15 +- pages/bookmarks.tsx | 4 +- pages/tags/[name].tsx | 17 +- server/internal/api/comment.go | 7 +- server/internal/api/interaction.go | 61 +++++++ server/internal/api/story.go | 20 +-- server/internal/interaction/follow.go | 93 +++++++++++ server/internal/interaction/interaction.go | 5 + .../internal/{story => interaction}/like.go | 7 +- server/internal/server.go | 7 +- server/internal/storage/init.go | 2 +- server/internal/storage/sql_tables.go | 2 + server/internal/story/bookmark.go | 3 +- server/internal/story/comment.go | 20 +-- server/internal/story/post.go | 18 +-- server/internal/story/posts.go | 10 +- server/internal/story/story.go | 12 -- server/pkg/e/err_code.go | 2 +- server/pkg/models/id_type.go | 34 +++- src/components/comments/comment.tsx | 4 +- src/components/comments/editor.tsx | 2 +- src/components/comments/reply.tsx | 4 +- src/components/interaction/follow.tsx | 30 ++++ .../{story => interaction}/like.tsx | 2 +- src/components/markdown-editor/editor.tsx | 2 +- src/components/story/post-card.tsx | 2 +- src/components/story/post-sidebar.tsx | 2 +- src/components/story/simple-post-card.tsx | 2 +- src/components/user-menu.tsx | 2 +- src/hooks/use-session.ts | 6 +- src/types/comments.ts | 2 +- src/types/posts.ts | 4 +- src/types/tag.ts | 2 +- src/types/{session.ts => user.ts} | 2 +- src/utils/user.ts | 2 +- 38 files changed, 391 insertions(+), 216 deletions(-) create mode 100644 server/internal/api/interaction.go create mode 100644 server/internal/interaction/follow.go create mode 100644 server/internal/interaction/interaction.go rename server/internal/{story => interaction}/like.go (89%) create mode 100644 src/components/interaction/follow.tsx rename src/components/{story => interaction}/like.tsx (94%) rename src/types/{session.ts => user.ts} (96%) diff --git a/layouts/nav/post-nav.tsx b/layouts/nav/post-nav.tsx index 32b8b943..d60113bd 100644 --- a/layouts/nav/post-nav.tsx +++ b/layouts/nav/post-nav.tsx @@ -12,99 +12,32 @@ import { Text } from "@chakra-ui/react" import { useViewportScroll } from "framer-motion" -import React from "react" +import React, { useEffect, useState } from "react" import { SearchIcon } from "@chakra-ui/icons" import DarkMode from "components/dark-mode" import AccountMenu from "components/user-menu" import { FaGithub, FaTwitter, FaUserPlus } from "react-icons/fa" +import Follow from "components/interaction/follow" +import { requestApi } from "utils/axios/request" +import { Post } from "src/types/posts" - -function HeaderContent() { - const mobileNav = useDisclosure() - - const mobileNavBtnRef = React.useRef() - - useUpdateEffect(() => { - mobileNavBtnRef.current?.focus() - }, [mobileNav.isOpen]) - - return ( - <> - - - Sunface的博客 - - - - - - alert('search in this blog')} - icon={} - aria-label="search in this blog" - /> - - - - - - - Home - Badges - - - - } - /> - } - /> - - - - - ) +interface Props { + post: Post } -function PostNav(props) { - const ref = React.useRef() - const [y, setY] = React.useState(0) - const { height = 0 } = ref.current?.getBoundingClientRect() ?? {} +function PostNav(props:Props) { + const {post} = props + const [followed, setFollowed] = useState(null) - const { scrollY } = useViewportScroll() - React.useEffect(() => { - return scrollY.onChange(() => setY(scrollY.get())) - }, [scrollY]) + useEffect(() => { + if (post) { + requestApi.get(`/interaction/followed/${post.id}`).then(res => setFollowed(res.data)) + } + }, []) return ( height ? "sm" : undefined} transition="box-shadow 0.2s" top="0" zIndex="3" @@ -112,10 +45,64 @@ function PostNav(props) { right="0" width="full" bg={useColorModeValue('white', 'gray.800')} - {...props} > - + + + Sunface的博客 + {followed !== null && } + + + + + alert('search in this blog')} + icon={} + aria-label="search in this blog" + /> + + + + + + + Home + Badges + + + + } + /> + } + /> + + + ) diff --git a/pages/[username]/[post_id].tsx b/pages/[username]/[post_id].tsx index e6a49634..f38bd080 100644 --- a/pages/[username]/[post_id].tsx +++ b/pages/[username]/[post_id].tsx @@ -64,33 +64,32 @@ const PostPage = () => { title={siteConfig.seo.title} description={siteConfig.seo.description} /> - } mt="2rem"> - {post && - <> - - - - - {post.title} + {post && } mt="2rem"> + <> + + + + + {post.title} - - - + + + - - - {post.rawTags.map(tag => )} - - getComments(post.id)} /> - - - + - + {post.rawTags.map(tag => )} + + getComments(post.id)} /> + + + + + - - } + + } ) } diff --git a/pages/[username]/index.tsx b/pages/[username]/index.tsx index 7d3f1ba4..c8fd43bf 100644 --- a/pages/[username]/index.tsx +++ b/pages/[username]/index.tsx @@ -12,7 +12,7 @@ import { useRouter } from "next/router" import React, { useEffect, useState } from "react" import { FaComment, FaCommentAlt, FaDove, FaEdit, FaFacebook, FaFile, FaGithub, FaHeart, FaPlus, FaRegStar, FaStackOverflow, FaStar, FaTwitter, FaWeibo, FaZhihu } from "react-icons/fa" import { ReserveUrls } from "src/data/reserve-urls" -import { User } from "src/types/session" +import { User } from "src/types/user" import { requestApi } from "utils/axios/request" import moment from 'moment' import { Post } from "src/types/posts" diff --git a/pages/_app.tsx b/pages/_app.tsx index e127d5a0..7537a227 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -2,13 +2,15 @@ import { trackPageview } from "analytics/track-event" import { DefaultSeo } from "next-seo" import Head from "next/head" import Router from "next/router" -import React from "react" +import React, { useEffect } from "react" import { ChakraProvider } from "@chakra-ui/react" import theme from "theme" import FontFace from "src/components/font-face" import { getSeo } from "utils/seo" import GAScript from "analytics/ga-script" import {initUIConfig} from 'configs/config' +import { requestApi } from "utils/axios/request" +import events from "utils/events" Router.events.on("routeChangeComplete", (url) => { trackPageview(url) @@ -17,7 +19,16 @@ Router.events.on("routeChangeComplete", (url) => { const App = ({ Component, pageProps }) => { const seo = getSeo({ omitOpenGraphImage: false }) - initUIConfig() + useEffect(() => { + requestApi.get(`/user/session`).then(res => { + events.emit('set-session', res.data) + }) + + initUIConfig() + }, []) + + + return ( <> diff --git a/pages/bookmarks.tsx b/pages/bookmarks.tsx index 1d3eeb30..8034b0b2 100644 --- a/pages/bookmarks.tsx +++ b/pages/bookmarks.tsx @@ -29,7 +29,7 @@ import Empty from "components/empty" const BookmarksPage = () => { - const [filter, setFilter]:[Tag,any] = useState({id:-1}) + const [filter, setFilter]:[Tag,any] = useState({id:"-1"}) const [tags, setTags]: [Tag[], any] = useState([]) const [rawPosts,setRawPosts]: [Post[],any] = useState([]) const [posts,setPosts]: [Post[],any] = useState([]) @@ -59,7 +59,7 @@ import Empty from "components/empty" } const filterPosts = () => { - if (filter.id === -1) { + if (filter.id === "-1") { setPosts(rawPosts) return } diff --git a/pages/tags/[name].tsx b/pages/tags/[name].tsx index 8ceed261..0a9eec0b 100644 --- a/pages/tags/[name].tsx +++ b/pages/tags/[name].tsx @@ -17,12 +17,22 @@ import { Post } from "src/types/posts" import { Tag } from "src/types/tag" import { requestApi } from "utils/axios/request" import { isAdmin } from "utils/role" +import Follow from "components/interaction/follow" const UserPage = () => { const router = useRouter() const [posts, setPosts]: [Post[], any] = useState([]) - const [tag, setTag]: [Tag, any] = useState({}) + const [tag, setTag]: [Tag, any] = useState(null) + + const [followed, setFollowed] = useState(null) + useEffect(() => { + if (tag) { + requestApi.get(`/interaction/followed/${tag.id}`).then(res => setFollowed(res.data)) + } + }, [tag]) + + const initData = async () => { const res = await requestApi.get(`/tag/info/${router.query.name}`) setTag(res.data) @@ -38,7 +48,6 @@ const UserPage = () => { }, [router.query.name]) const session = useSession() - return ( <> { description={siteConfig.seo.description} /> - {tag.name && + {tag && tag.name && @@ -58,7 +67,7 @@ const UserPage = () => { #{tag.name} - + {followed !== null && } {isAdmin(session?.user.role) && } diff --git a/server/internal/api/comment.go b/server/internal/api/comment.go index d3c27461..35e0297d 100644 --- a/server/internal/api/comment.go +++ b/server/internal/api/comment.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/imdotdev/im.dev/server/internal/interaction" "github.com/imdotdev/im.dev/server/internal/story" "github.com/imdotdev/im.dev/server/internal/user" "github.com/imdotdev/im.dev/server/pkg/common" @@ -24,7 +25,7 @@ func SubmitComment(c *gin.Context) { } // check story exist - exist := story.Exist(comment.TargetID) + exist := models.IdExist(comment.TargetID) if !exist { c.JSON(http.StatusNotFound, common.RespError(e.NotFound)) return @@ -59,7 +60,7 @@ func GetStoryComments(c *gin.Context) { user := user.CurrentUser(c) for _, comment := range comments { if user != nil { - comment.Liked = story.GetLiked(comment.ID, user.ID) + comment.Liked = interaction.GetLiked(comment.ID, user.ID) } replies, err := story.GetComments(comment.ID) @@ -70,7 +71,7 @@ func GetStoryComments(c *gin.Context) { comment.Replies = replies for _, reply := range replies { if user != nil { - reply.Liked = story.GetLiked(reply.ID, user.ID) + reply.Liked = interaction.GetLiked(reply.ID, user.ID) } } } diff --git a/server/internal/api/interaction.go b/server/internal/api/interaction.go new file mode 100644 index 00000000..562c933b --- /dev/null +++ b/server/internal/api/interaction.go @@ -0,0 +1,61 @@ +package api + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/imdotdev/im.dev/server/internal/interaction" + "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 Follow(c *gin.Context) { + user := user.CurrentUser(c) + id := c.Param("id") + if id == "" { + c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid)) + return + } + + err := interaction.Follow(id, user.ID) + if err != nil { + c.JSON(err.Status, common.RespError(err.Message)) + return + } + + c.JSON(http.StatusOK, common.RespSuccess(nil)) +} + +func Followed(c *gin.Context) { + user := user.CurrentUser(c) + id := c.Param("id") + if id == "" { + c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid)) + return + } + + followed := false + if user != nil { + followed = interaction.GetFollowed(id, user.ID) + } + + c.JSON(http.StatusOK, common.RespSuccess(followed)) +} + +func Like(c *gin.Context) { + user := user.CurrentUser(c) + id := c.Param("id") + if id == "" { + c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid)) + return + } + + err := interaction.Like(id, user.ID) + if err != nil { + c.JSON(err.Status, common.RespError(err.Message)) + return + } + + c.JSON(http.StatusOK, common.RespSuccess(nil)) +} diff --git a/server/internal/api/story.go b/server/internal/api/story.go index 2800a512..0981409d 100644 --- a/server/internal/api/story.go +++ b/server/internal/api/story.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/imdotdev/im.dev/server/internal/interaction" "github.com/imdotdev/im.dev/server/internal/story" "github.com/imdotdev/im.dev/server/internal/user" "github.com/imdotdev/im.dev/server/pkg/common" @@ -58,30 +59,13 @@ func GetStoryPost(c *gin.Context) { } if user != nil { - ar.Liked = story.GetLiked(ar.ID, user.ID) + ar.Liked = interaction.GetLiked(ar.ID, user.ID) ar.Bookmarked, _ = story.Bookmarked(user.ID, ar.ID) } c.JSON(http.StatusOK, common.RespSuccess(ar)) } -func LikeStory(c *gin.Context) { - user := user.CurrentUser(c) - id := c.Param("id") - if id == "" { - c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid)) - return - } - - err := story.Like(id, user.ID) - if err != nil { - c.JSON(err.Status, common.RespError(err.Message)) - return - } - - c.JSON(http.StatusOK, common.RespSuccess(nil)) -} - func Bookmark(c *gin.Context) { storyID := c.Param("storyID") diff --git a/server/internal/interaction/follow.go b/server/internal/interaction/follow.go new file mode 100644 index 00000000..3ffbf77e --- /dev/null +++ b/server/internal/interaction/follow.go @@ -0,0 +1,93 @@ +package interaction + +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" +) + +func Follow(targetID string, userId string) *e.Error { + exist := models.IdExist(targetID) + if !exist { + return e.New(http.StatusNotFound, e.NotFound) + } + + followed := GetFollowed(targetID, userId) + + var count int + err := db.Conn.QueryRow("SELECT count FROM follows_count WHERE target_id=?", targetID).Scan(&count) + if err != nil && err != sql.ErrNoRows { + logger.Warn("query follows count error", "error", err) + return e.New(http.StatusInternalServerError, e.Internal) + } + exist = !(err == sql.ErrNoRows) + + tx, err := db.Conn.Begin() + if err != nil { + logger.Warn("start sql transaction error", "error", err) + return e.New(http.StatusInternalServerError, e.Internal) + } + + if followed { + // 已经喜欢过该篇文章,更改为不喜欢 + _, err := tx.Exec("DELETE FROM follows WHERE user_id=? and target_id=?", userId, targetID) + if err != nil { + return e.New(http.StatusInternalServerError, e.Internal) + } + count = count - 1 + } else { + _, err := tx.Exec("INSERT INTO follows (user_id,target_id,target_type,created) VALUES (?,?,?,?)", userId, targetID, models.GetIDType(targetID), time.Now()) + if err != nil { + logger.Warn("add follows error", "error", err) + return e.New(http.StatusInternalServerError, e.Internal) + } + count = count + 1 + } + + var err0 error + if !exist { + _, err0 = tx.Exec("INSERT INTO follows_count (target_id,count) VALUES (?,?)", targetID, count) + } else { + _, err0 = tx.Exec("UPDATE follows_count SET count=? WHERE target_id=?", count, targetID) + } + + if err0 != nil { + logger.Warn("add follows_count error", "error", err0) + tx.Rollback() + return e.New(http.StatusInternalServerError, e.Internal) + } + + tx.Commit() + + return nil +} + +func GetFollowed(targetID string, userID string) bool { + followed := false + var nid string + err := db.Conn.QueryRow("SELECT target_id FROM follows WHERE user_id=? and target_id=?", userID, targetID).Scan(&nid) + if err != nil && err != sql.ErrNoRows { + logger.Warn("query folloed error", "error", err) + return false + } + + if nid == targetID { + followed = true + } + + return followed +} + +func GetFollows(targetID string) int { + var follows int + err := db.Conn.QueryRow("SELECT count FROM follows_count WHERE target_id=?", targetID).Scan(&follows) + if err != nil && err != sql.ErrNoRows { + logger.Warn("get follow count error", "error", err) + } + + return follows +} diff --git a/server/internal/interaction/interaction.go b/server/internal/interaction/interaction.go new file mode 100644 index 00000000..948ef4f9 --- /dev/null +++ b/server/internal/interaction/interaction.go @@ -0,0 +1,5 @@ +package interaction + +import "github.com/imdotdev/im.dev/server/pkg/log" + +var logger = log.RootLogger.New("logger", "api") diff --git a/server/internal/story/like.go b/server/internal/interaction/like.go similarity index 89% rename from server/internal/story/like.go rename to server/internal/interaction/like.go index 66f53f5d..e769a8fe 100644 --- a/server/internal/story/like.go +++ b/server/internal/interaction/like.go @@ -1,4 +1,4 @@ -package story +package interaction import ( "database/sql" @@ -7,10 +7,11 @@ 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" ) func Like(storyID string, userId string) *e.Error { - exist := Exist(storyID) + exist := models.IdExist(storyID) if !exist { return e.New(http.StatusNotFound, e.NotFound) } @@ -40,7 +41,7 @@ func Like(storyID string, userId string) *e.Error { } count = count - 1 } else { - _, err := tx.Exec("INSERT INTO likes (story_id,user_id,created) VALUES (?,?,?)", storyID, userId, time.Now()) + _, err := tx.Exec("INSERT INTO likes (story_id,user_id,story_type,created) VALUES (?,?,?,?)", storyID, userId, models.GetIDType(storyID), time.Now()) if err != nil { logger.Warn("add like error", "error", err) return e.New(http.StatusInternalServerError, e.Internal) diff --git a/server/internal/server.go b/server/internal/server.go index fc5f3cbd..43b5bc76 100644 --- a/server/internal/server.go +++ b/server/internal/server.go @@ -46,7 +46,6 @@ func (s *Server) Start() error { //story apis r.GET("/story/post/:id", api.GetStoryPost) - r.POST("/story/like/:id", IsLogin(), api.LikeStory) r.GET("/story/comments/:id", api.GetStoryComments) r.POST("/story/comment", IsLogin(), api.SubmitComment) r.DELETE("/story/comment/:id", IsLogin(), api.DeleteStoryComment) @@ -70,10 +69,14 @@ func (s *Server) Start() error { r.GET("/user/info/:username", api.GetUser) r.POST("/user/update", IsLogin(), api.UpdateUser) r.GET("/user/posts/:userID", api.GetUserPosts) - r.GET("/user/session", IsLogin(), api.GetSession) + r.GET("/user/session", api.GetSession) r.POST("/user/login", user.Login) r.POST("/user/logout", user.Logout) + // interaction apis + r.POST("/interaction/like/:id", IsLogin(), api.Like) + r.POST("/interaction/follow/:id", IsLogin(), api.Follow) + r.GET("/interaction/followed/:id", api.Followed) // other apis r.GET("/config", GetConfig) diff --git a/server/internal/storage/init.go b/server/internal/storage/init.go index 6ca1f5ae..c9f60219 100644 --- a/server/internal/storage/init.go +++ b/server/internal/storage/init.go @@ -57,7 +57,7 @@ func initTables() error { now := time.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) + utils.GenID(models.IDTypeUser), 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 6ee2bc32..fda323cb 100644 --- a/server/internal/storage/sql_tables.go +++ b/server/internal/storage/sql_tables.go @@ -69,6 +69,7 @@ var sqlTables = map[string]string{ "likes": `CREATE TABLE IF NOT EXISTS likes ( story_id VARCHAR(255), + story_type VARCHAR(1), user_id VARCHAR(255), created DATETIME NOT NULL ); @@ -87,6 +88,7 @@ var sqlTables = map[string]string{ "follows": `CREATE TABLE IF NOT EXISTS follows ( user_id VARCHAR(255), target_id VARCHAR(255), + target_type VARCHAR(1), created DATETIME NOT NULL ); CREATE INDEX IF NOT EXISTS follows_userid diff --git a/server/internal/story/bookmark.go b/server/internal/story/bookmark.go index 67ef29ca..3cca0959 100644 --- a/server/internal/story/bookmark.go +++ b/server/internal/story/bookmark.go @@ -6,10 +6,11 @@ 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" ) func Bookmark(userID string, storyID string) *e.Error { - storyExist := Exist(storyID) + storyExist := models.IdExist(storyID) if !storyExist { return e.New(http.StatusNotFound, e.NotFound) } diff --git a/server/internal/story/comment.go b/server/internal/story/comment.go index ad554f5f..4f36a8f7 100644 --- a/server/internal/story/comment.go +++ b/server/internal/story/comment.go @@ -7,6 +7,7 @@ import ( "sort" "time" + "github.com/imdotdev/im.dev/server/internal/interaction" "github.com/imdotdev/im.dev/server/pkg/db" "github.com/imdotdev/im.dev/server/pkg/e" "github.com/imdotdev/im.dev/server/pkg/models" @@ -94,7 +95,7 @@ func GetComments(storyID string) (models.Comments, *e.Error) { c.Creator = &models.UserSimple{ID: c.CreatorID} err = c.Creator.Query() - c.Likes = GetLikes(c.ID) + c.Likes = interaction.GetLikes(c.ID) comments = append(comments, c) } @@ -118,7 +119,7 @@ func GetComment(id string) (*models.Comment, *e.Error) { md, _ := utils.Uncompress(rawMd) c.Md = string(md) - c.Likes = GetLikes(c.ID) + c.Likes = interaction.GetLikes(c.ID) return c, nil } @@ -175,21 +176,6 @@ func DeleteComment(id string) *e.Error { return nil } -func commentExist(id string) bool { - var nid string - err := db.Conn.QueryRow("SELECT id FROM comments WHERE id=?", id).Scan(&nid) - if err != nil && err != sql.ErrNoRows { - logger.Warn("query comment error", "error", err) - return false - } - - if nid == id { - return true - } - - return false -} - func GetCommentCount(storyID string) int { count := 0 err := db.Conn.QueryRow("SELECT count from comments_count WHERE story_id=?", storyID).Scan(&count) diff --git a/server/internal/story/post.go b/server/internal/story/post.go index b14930f9..ab52a5bd 100644 --- a/server/internal/story/post.go +++ b/server/internal/story/post.go @@ -10,6 +10,7 @@ import ( "github.com/asaskevich/govalidator" "github.com/gin-gonic/gin" + "github.com/imdotdev/im.dev/server/internal/interaction" "github.com/imdotdev/im.dev/server/internal/tags" "github.com/imdotdev/im.dev/server/internal/user" "github.com/imdotdev/im.dev/server/pkg/config" @@ -158,7 +159,7 @@ func GetPost(id string, slug string) (*models.Post, *e.Error) { ar.Tags = t ar.RawTags = rawTags - ar.Likes = GetLikes(ar.ID) + ar.Likes = interaction.GetLikes(ar.ID) return ar, nil } @@ -176,21 +177,6 @@ func GetPostCreator(id string) (string, *e.Error) { return uid, nil } -func postExist(id string) bool { - var nid string - err := db.Conn.QueryRow("SELECT id from posts WHERE id=?", id).Scan(&nid) - if err != nil { - logger.Warn("query post error", "error", err) - return false - } - - if nid != id { - return false - } - - return true -} - //slug有三个规则 // 1. 长度不能超过127 // 2. 每次title更新,都要重新生成slug diff --git a/server/internal/story/posts.go b/server/internal/story/posts.go index 83ae6669..105eebcf 100644 --- a/server/internal/story/posts.go +++ b/server/internal/story/posts.go @@ -7,6 +7,7 @@ import ( "sort" "strings" + "github.com/imdotdev/im.dev/server/internal/interaction" "github.com/imdotdev/im.dev/server/internal/tags" "github.com/imdotdev/im.dev/server/pkg/db" "github.com/imdotdev/im.dev/server/pkg/e" @@ -123,12 +124,11 @@ func getPosts(user *models.User, rows *sql.Rows) models.Posts { // 获取当前登录用户的like if user != nil { - ar.Liked = GetLiked(ar.ID, user.ID) + ar.Liked = interaction.GetLiked(ar.ID, user.ID) + // 获取当前登录用户的bookmark + ar.Bookmarked, _ = Bookmarked(user.ID, ar.ID) } - ar.Likes = GetLikes(ar.ID) - - // 获取当前登录用户的bookmark - ar.Bookmarked, _ = Bookmarked(user.ID, ar.ID) + ar.Likes = interaction.GetLikes(ar.ID) posts = append(posts, ar) } diff --git a/server/internal/story/story.go b/server/internal/story/story.go index 8bcecc74..721cbeb1 100644 --- a/server/internal/story/story.go +++ b/server/internal/story/story.go @@ -2,18 +2,6 @@ package story import ( "github.com/imdotdev/im.dev/server/pkg/log" - "github.com/imdotdev/im.dev/server/pkg/models" ) var logger = log.RootLogger.New("logger", "story") - -func Exist(id string) bool { - switch id[:1] { - case models.IDTypePost: - return postExist(id) - case models.IDTypeComment: - return commentExist(id) - default: - return false - } -} diff --git a/server/pkg/e/err_code.go b/server/pkg/e/err_code.go index e7145f15..a72b497b 100644 --- a/server/pkg/e/err_code.go +++ b/server/pkg/e/err_code.go @@ -15,7 +15,7 @@ func New(status int, msg string) *Error { const ( DB = "数据库异常" Internal = "服务器内部错误" - NeedLogin = "你需要登录才能访问该页面" + NeedLogin = "你需要登录" NoEditorPermission = "只有编辑角色才能执行此操作" ParamInvalid = "请求参数不正确" NotFound = "目标不存在" diff --git a/server/pkg/models/id_type.go b/server/pkg/models/id_type.go index 4a109361..be85b4a3 100644 --- a/server/pkg/models/id_type.go +++ b/server/pkg/models/id_type.go @@ -1,6 +1,14 @@ package models +import ( + "fmt" + + "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" + "github.com/imdotdev/im.dev/server/pkg/db" +) + const ( + IDUndefined = "0" IDTypePost = "1" IDTypeComment = "2" IDTypeUser = "3" @@ -26,6 +34,30 @@ func GetIdTypeTable(id string) string { case IDTypeTag: return "tags" default: - return "unknown" + return IDUndefined + } +} + +func IdExist(id string) bool { + if id == "" { + return false } + + tbl := GetIdTypeTable(id) + if tbl == IDUndefined { + return false + } + + var nid string + err := db.Conn.QueryRow(fmt.Sprintf("SELECT id from %s WHERE id=?", tbl), id).Scan(&nid) + if err != nil { + logger.Warn("query post error", "error", err) + return false + } + + if nid != id { + return false + } + + return true } diff --git a/src/components/comments/comment.tsx b/src/components/comments/comment.tsx index b6441ba4..36460547 100644 --- a/src/components/comments/comment.tsx +++ b/src/components/comments/comment.tsx @@ -5,9 +5,9 @@ import Card from "components/card" import { getUserName } from "utils/user" import moment from 'moment' import { MarkdownRender } from "components/markdown-editor/render" -import Like from "components/story/like" +import Like from "components/interaction/like" import { FaRegEdit, FaRegFlag, FaRegTrashAlt, FaReply, FaTrash } from "react-icons/fa" -import { User } from "src/types/session" +import { User } from "src/types/user" import CommentEditor from "./editor" import { requestApi } from "utils/axios/request" import Reply from "./reply" diff --git a/src/components/comments/editor.tsx b/src/components/comments/editor.tsx index 41844ed4..622f95ed 100644 --- a/src/components/comments/editor.tsx +++ b/src/components/comments/editor.tsx @@ -8,7 +8,7 @@ import EditModeSelect from "components/edit-mode-select" import { EditMode } from "src/types/editor" import { MarkdownRender } from "components/markdown-editor/render" import { Comment } from "src/types/comments" -import { User } from "src/types/session" +import { User } from "src/types/user" interface Props { user: User diff --git a/src/components/comments/reply.tsx b/src/components/comments/reply.tsx index e0afb2e7..4f92bbae 100644 --- a/src/components/comments/reply.tsx +++ b/src/components/comments/reply.tsx @@ -5,9 +5,9 @@ import Card from "components/card" import { getUserName } from "utils/user" import moment from 'moment' import { MarkdownRender } from "components/markdown-editor/render" -import Like from "components/story/like" +import Like from "components/interaction/like" import { FaRegEdit, FaRegFlag, FaRegTrashAlt, FaReply, FaTrash } from "react-icons/fa" -import { User } from "src/types/session" +import { User } from "src/types/user" import CommentEditor from "./editor" import { requestApi } from "utils/axios/request" import Link from "next/link" diff --git a/src/components/interaction/follow.tsx b/src/components/interaction/follow.tsx new file mode 100644 index 00000000..db7724e5 --- /dev/null +++ b/src/components/interaction/follow.tsx @@ -0,0 +1,30 @@ +import { Button } from "@chakra-ui/react"; +import { useEffect, useState } from "react"; +import { FaCheck, FaPlus, FaUserPlus } from "react-icons/fa"; +import { requestApi } from "utils/axios/request"; + +interface Props { + targetID: string + followed: boolean + fontSize?: string +} +const Follow = (props: Props) => { + const [followed, setFollowed] = useState(props.followed) + + const follow = async () => { + await requestApi.post(`/interaction/follow/${props.targetID}`) + setFollowed(!followed) + } + + return ( + <> + {followed ? + + : + + } + + ) +} + +export default Follow \ No newline at end of file diff --git a/src/components/story/like.tsx b/src/components/interaction/like.tsx similarity index 94% rename from src/components/story/like.tsx rename to src/components/interaction/like.tsx index 145dc297..4014ec24 100644 --- a/src/components/story/like.tsx +++ b/src/components/interaction/like.tsx @@ -18,7 +18,7 @@ const Like = (props: Props) => { const [liked,setLiked] = useState(props.liked) const [count,setCount] = useState(props.count) const like = async () => { - await requestApi.post(`/story/like/${props.storyID}`) + await requestApi.post(`/interaction/like/${props.storyID}`) if (liked) { diff --git a/src/components/markdown-editor/editor.tsx b/src/components/markdown-editor/editor.tsx index e9d19f44..617af95a 100644 --- a/src/components/markdown-editor/editor.tsx +++ b/src/components/markdown-editor/editor.tsx @@ -8,7 +8,7 @@ import CaretStyles from 'theme/caret.styles' import { isUsernameChar } from 'utils/user'; import { requestApi } from 'utils/axios/request'; import Card from 'components/card'; -import { User } from 'src/types/session'; +import { User } from 'src/types/user'; import userCustomTheme from 'theme/user-custom'; import { cloneDeep } from 'lodash'; diff --git a/src/components/story/post-card.tsx b/src/components/story/post-card.tsx index 980808f7..6fe996d1 100644 --- a/src/components/story/post-card.tsx +++ b/src/components/story/post-card.tsx @@ -3,7 +3,7 @@ import { Box, chakra, Flex, Heading, HStack, Image, Text, useMediaQuery, VStack import { Post } from "src/types/posts" import PostAuthor from "./post-author" import Link from "next/link" -import Like from "./like" +import Like from "../interaction/like" import { FaHeart, FaRegHeart } from "react-icons/fa" import Bookmark from "./bookmark" import { getSvgIcon } from "components/svg-icon" diff --git a/src/components/story/post-sidebar.tsx b/src/components/story/post-sidebar.tsx index 83960e92..9d6b50ee 100644 --- a/src/components/story/post-sidebar.tsx +++ b/src/components/story/post-sidebar.tsx @@ -2,7 +2,7 @@ import React from "react" import { Box, BoxProps, useColorModeValue, VStack } from "@chakra-ui/react" import { Post } from "src/types/posts" import useSession from "hooks/use-session" -import Like from "./like" +import Like from "../interaction/like" import Bookmark from "./bookmark" import SvgButton from "components/svg-button" import { useRouter } from "next/router" diff --git a/src/components/story/simple-post-card.tsx b/src/components/story/simple-post-card.tsx index 86db0cba..ee297282 100644 --- a/src/components/story/simple-post-card.tsx +++ b/src/components/story/simple-post-card.tsx @@ -3,7 +3,7 @@ import { Box, chakra, Flex, Heading, HStack, Image, Text, useMediaQuery, VStack import { Post } from "src/types/posts" import PostAuthor from "./post-author" import Link from "next/link" -import UnicornLike from "./like" +import UnicornLike from "../interaction/like" import { FaHeart, FaRegBookmark, FaRegComment, FaRegHeart } from "react-icons/fa" import SvgButton from "components/svg-button" import Bookmark from "./bookmark" diff --git a/src/components/user-menu.tsx b/src/components/user-menu.tsx index aeb0fcdf..185e2bf8 100644 --- a/src/components/user-menu.tsx +++ b/src/components/user-menu.tsx @@ -10,7 +10,7 @@ import { Button } from "@chakra-ui/react" import useSession from "hooks/use-session" -import { Session } from "src/types/session" +import { Session } from "src/types/user" import { useRouter } from "next/router" import storage from "utils/localStorage" import { ReserveUrls } from "src/data/reserve-urls" diff --git a/src/hooks/use-session.ts b/src/hooks/use-session.ts index 44cea46a..371acc52 100644 --- a/src/hooks/use-session.ts +++ b/src/hooks/use-session.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from "react" -import { Session } from "src/types/session" +import { Session } from "src/types/user" import { requestApi } from "utils/axios/request" import events from "utils/events" import storage from "utils/localStorage" @@ -10,10 +10,6 @@ function useSession(): Session{ const sess = storage.get('session') if (sess) { setSession(sess) - // 页面重新进入时,跟服务器端进行信息同步 - requestApi.get(`/user/session`).then(res => { - setSession(res.data) - }) } events.on('set-session',storeSession) diff --git a/src/types/comments.ts b/src/types/comments.ts index 737f938b..1bb738f1 100644 --- a/src/types/comments.ts +++ b/src/types/comments.ts @@ -1,4 +1,4 @@ -import { UserSimple } from "./session"; +import { UserSimple } from "./user"; export interface Comment { id: string diff --git a/src/types/posts.ts b/src/types/posts.ts index 15872510..35f3ce59 100644 --- a/src/types/posts.ts +++ b/src/types/posts.ts @@ -1,4 +1,4 @@ -import { UserSimple} from './session' +import { UserSimple} from './user' import { Tag } from './tag'; export enum PostFilter { @@ -18,7 +18,7 @@ export interface Post { cover?: string brief?: string created?: string - tags?: number[] + tags?: string[] rawTags?: Tag[] likes? : number liked? : boolean diff --git a/src/types/tag.ts b/src/types/tag.ts index 2140425f..8d92e820 100644 --- a/src/types/tag.ts +++ b/src/types/tag.ts @@ -1,5 +1,5 @@ export interface Tag { - id?: number + id?: string name?: string title?: string md?: string diff --git a/src/types/session.ts b/src/types/user.ts similarity index 96% rename from src/types/session.ts rename to src/types/user.ts index 480210fb..4a7ffec2 100644 --- a/src/types/session.ts +++ b/src/types/user.ts @@ -21,7 +21,7 @@ export interface User { availFor?: string about?: string rawSkills?: Tag[] - skills?: number[] + skills?: string[] // social links website?: string diff --git a/src/utils/user.ts b/src/utils/user.ts index 38fe602f..88142f51 100644 --- a/src/utils/user.ts +++ b/src/utils/user.ts @@ -1,4 +1,4 @@ -import {User} from 'src/types/session' +import {User} from 'src/types/user' export function getUserName(user:User) { return user.nickname === "" ? user.username : user.nickname }