From f6fc6ddc84fd0d1035fe3e5332451c5b0974addf Mon Sep 17 00:00:00 2001 From: sunface Date: Thu, 11 Mar 2021 12:25:14 +0800 Subject: [PATCH] update --- layouts/sidebar/sidebar-link.tsx | 2 +- pages/[username]/index.tsx | 2 +- pages/admin/tags.tsx | 10 +-- pages/editor/post/[id].tsx | 2 +- pages/follow/followers.tsx | 79 ++++++++++++++++++++++ pages/follow/tags.tsx | 97 +++++++++++++++++++++++++++ pages/follow/users.tsx | 79 ++++++++++++++++++++++ server/internal/api/interaction.go | 36 ++++++++++ server/internal/api/tag.go | 17 +++++ server/internal/interaction/follow.go | 29 ++++++++ server/internal/server.go | 3 + server/internal/storage/sql_tables.go | 1 + server/internal/tags/tags.go | 14 ++++ server/pkg/common/reserve_urls.go | 1 + server/pkg/models/id_type.go | 8 +++ server/pkg/models/interaction.go | 14 ++++ src/components/card.tsx | 10 ++- src/components/tags/tag-card.tsx | 2 +- src/components/user-menu.tsx | 5 +- src/data/links.tsx | 18 +++++ src/data/reserve-urls.ts | 1 + 21 files changed, 413 insertions(+), 17 deletions(-) create mode 100644 pages/follow/followers.tsx create mode 100644 pages/follow/tags.tsx create mode 100644 pages/follow/users.tsx create mode 100644 server/pkg/models/interaction.go diff --git a/layouts/sidebar/sidebar-link.tsx b/layouts/sidebar/sidebar-link.tsx index 7fceb910..64a11a7b 100644 --- a/layouts/sidebar/sidebar-link.tsx +++ b/layouts/sidebar/sidebar-link.tsx @@ -28,7 +28,7 @@ const StyledLink = React.forwardRef(function StyledLink( fontWeight: "600", }} {...rest} - >{icon} {children} + >{icon && {icon} }{children} ) }) diff --git a/pages/[username]/index.tsx b/pages/[username]/index.tsx index 2fbe9a62..ed8fe251 100644 --- a/pages/[username]/index.tsx +++ b/pages/[username]/index.tsx @@ -82,7 +82,7 @@ const UserPage = () => { { user && - + diff --git a/pages/admin/tags.tsx b/pages/admin/tags.tsx index 688652c4..2e22bed4 100644 --- a/pages/admin/tags.tsx +++ b/pages/admin/tags.tsx @@ -13,6 +13,7 @@ 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" const PostsPage = () => { @@ -54,14 +55,7 @@ const PostsPage = () => { { tags.length === 0 ? - <> -
- -
-
- 你还没创建任何标签 -
- + : <> diff --git a/pages/editor/post/[id].tsx b/pages/editor/post/[id].tsx index a72e7529..7df4cc06 100644 --- a/pages/editor/post/[id].tsx +++ b/pages/editor/post/[id].tsx @@ -104,7 +104,7 @@ function PostEditPage() { } const publish = async () => { - if (ar.tags?.length === 0) { + if (!ar.tags || ar.tags?.length === 0) { toast({ description: "请设置文章标签", status: "error", diff --git a/pages/follow/followers.tsx b/pages/follow/followers.tsx new file mode 100644 index 00000000..e420d346 --- /dev/null +++ b/pages/follow/followers.tsx @@ -0,0 +1,79 @@ +import {Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast } 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, followLinks} 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" + + +const FollowersPage = () => { + const [tags, setTags] = useState([]) + const router = useRouter() + const toast = useToast() + const getTags = () => { + requestApi.get(`/tag/all`).then((res) => setTags(res.data)).catch(_ => setTags([])) + } + + useEffect(() => { + getTags() + }, []) + + const editTag = (tag: Tag) => { + router.push(`${ReserveUrls.Admin}/tag/${tag.name}`) + } + + const deleteTag= async (id) => { + await requestApi.delete(`/tag/${id}`) + getTags() + toast({ + description: "删除成功", + status: "success", + duration: 2000, + isClosable: true, + }) + } + + return ( + <> + + + + + + 标签列表({tags.length}) + + + { + tags.length === 0 ? + + : + <> + + {tags.map(tag => + + editTag(tag)} onDelete={() => deleteTag(tag.id)} /> + + + )} + +
没有更多标签了
+ + } +
+
+
+ + ) +} +export default FollowersPage + diff --git a/pages/follow/tags.tsx b/pages/follow/tags.tsx new file mode 100644 index 00000000..e539998f --- /dev/null +++ b/pages/follow/tags.tsx @@ -0,0 +1,97 @@ +import { Text, Box, Heading, Image, Divider, useToast, HStack, Slider, SliderTrack, SliderFilledTrack, SliderThumb, Wrap, WrapItem } from "@chakra-ui/react" +import Card from "components/card" +import Sidebar from "layouts/sidebar/sidebar" +import React, { useEffect, useState } from "react" +import { followLinks } from "src/data/links" +import { requestApi } from "utils/axios/request" +import { useRouter } from "next/router" +import PageContainer1 from "layouts/page-container1" +import { IDType } from "src/types/id" +import Empty from "components/empty" + + +const TagsPage = () => { + const [tags, setTags] = useState([]) + const [following, setFollowing] = useState([]) + useEffect(() => { + getFollowing() + }, []) + const getFollowing = async () => { + const res = await requestApi.get(`/interaction/following/0?type=${IDType.Tag}`) + const ids = [] + for (const f of res.data) { + ids.push(f.id) + } + setFollowing(res.data) + + const res1 = await requestApi.post(`/tag/ids`, ids) + setTags(res1.data) + } + + const getTagWeight = tag => { + for (const f of following) { + if (f.id === tag.id) { + return f.weight + } + } + + return 0 + } + + return ( + <> + + + + + Adjust tag weight to modify your home feed. Higher values mean more appearances. + + { + tags.length === 0 ? + + : + + + {tags.map(tag => + + )} + + + + } + + + + + ) +} +export default TagsPage + + +function FollowingTag(props) { + const [weight, setWeight] = React.useState(props.weight) + const onWeightChange = async w => { + await requestApi.post(`/interaction/following/weight`, { id: props.tag.id, weight: weight }) + setWeight(w) + } + + return ( + + + + + {props.tag.title} + #{props.tag.name} + + + + setWeight(w)} onChangeEnd={onWeightChange}> + + + + + + + + ) +} \ No newline at end of file diff --git a/pages/follow/users.tsx b/pages/follow/users.tsx new file mode 100644 index 00000000..0bcc39da --- /dev/null +++ b/pages/follow/users.tsx @@ -0,0 +1,79 @@ +import {Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast } 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, followLinks} 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" + + +const UsersPage = () => { + const [tags, setTags] = useState([]) + const router = useRouter() + const toast = useToast() + const getTags = () => { + requestApi.get(`/tag/all`).then((res) => setTags(res.data)).catch(_ => setTags([])) + } + + useEffect(() => { + getTags() + }, []) + + const editTag = (tag: Tag) => { + router.push(`${ReserveUrls.Admin}/tag/${tag.name}`) + } + + const deleteTag= async (id) => { + await requestApi.delete(`/tag/${id}`) + getTags() + toast({ + description: "删除成功", + status: "success", + duration: 2000, + isClosable: true, + }) + } + + return ( + <> + + + + + + 标签列表({tags.length}) + + + { + tags.length === 0 ? + + : + <> + + {tags.map(tag => + + editTag(tag)} onDelete={() => deleteTag(tag.id)} /> + + + )} + +
没有更多标签了
+ + } +
+
+
+ + ) +} +export default UsersPage + diff --git a/server/internal/api/interaction.go b/server/internal/api/interaction.go index 562c933b..713d8d9a 100644 --- a/server/internal/api/interaction.go +++ b/server/internal/api/interaction.go @@ -8,6 +8,7 @@ import ( "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 Follow(c *gin.Context) { @@ -59,3 +60,38 @@ func Like(c *gin.Context) { c.JSON(http.StatusOK, common.RespSuccess(nil)) } + +func GetFollowing(c *gin.Context) { + userID := c.Param("userID") + targetType := c.Query("type") + if userID == "" || !models.ValidFollowIDType(targetType) { + c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid)) + return + } + + if userID == "0" { + u := user.CurrentUser(c) + userID = u.ID + } + + tags, err := interaction.GetFollowing(userID, targetType) + if err != nil { + c.JSON(err.Status, common.RespError(err.Message)) + return + } + + c.JSON(http.StatusOK, common.RespSuccess(tags)) +} + +func SetFollowingWeight(c *gin.Context) { + f := &models.Following{} + c.Bind(&f) + u := user.CurrentUser(c) + err := interaction.SetFolloingWeight(u.ID, f) + 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/tag.go b/server/internal/api/tag.go index 084fb094..8dccb7b8 100644 --- a/server/internal/api/tag.go +++ b/server/internal/api/tag.go @@ -33,6 +33,23 @@ func GetTags(c *gin.Context) { c.JSON(http.StatusOK, common.RespSuccess(res)) } +func GetTagsByIDs(c *gin.Context) { + ids := make([]string, 0) + err := c.Bind(&ids) + if err != nil { + c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid)) + return + } + + ts, err0 := tags.GetTagsByIDs(ids) + if err != nil { + c.JSON(err0.Status, common.RespError(err0.Message)) + return + } + + c.JSON(http.StatusOK, common.RespSuccess(ts)) +} + func SubmitTag(c *gin.Context) { user := user.CurrentUser(c) if !user.Role.IsAdmin() { diff --git a/server/internal/interaction/follow.go b/server/internal/interaction/follow.go index 3ffbf77e..507fdd88 100644 --- a/server/internal/interaction/follow.go +++ b/server/internal/interaction/follow.go @@ -3,6 +3,7 @@ package interaction import ( "database/sql" "net/http" + "sort" "time" "github.com/imdotdev/im.dev/server/pkg/db" @@ -91,3 +92,31 @@ func GetFollows(targetID string) int { return follows } + +func GetFollowing(userID, targetType string) ([]*models.Following, *e.Error) { + rows, err := db.Conn.Query("SELECT target_id,weight from follows where user_id=? and target_type=?", userID, targetType) + if err != nil { + logger.Warn("get following error", "error", err) + return nil, e.New(http.StatusInternalServerError, e.Internal) + } + + following := make(models.Followings, 0) + for rows.Next() { + f := &models.Following{} + rows.Scan(&f.ID, &f.Weight) + following = append(following, f) + } + + sort.Sort(following) + return following, nil +} + +func SetFolloingWeight(userID string, f *models.Following) *e.Error { + _, err := db.Conn.Exec("UPDATE follows SET weight=? WHERE user_id=? and target_id=?", f.Weight, userID, f.ID) + if err != nil { + logger.Warn("set following weight error", "error", err) + return e.New(http.StatusInternalServerError, e.Internal) + } + + return nil +} diff --git a/server/internal/server.go b/server/internal/server.go index c29f1b2e..bebb0ce3 100644 --- a/server/internal/server.go +++ b/server/internal/server.go @@ -69,6 +69,7 @@ func (s *Server) Start() error { r.POST("/tag", IsLogin(), api.SubmitTag) r.DELETE("/tag/:id", IsLogin(), api.DeleteTag) r.GET("/tag/all", api.GetTags) + r.POST("tag/ids", api.GetTagsByIDs) r.GET("/tag/posts/:id", api.GetTagPosts) r.GET("/tag/info/:name", api.GetTag) r.GET("/tag/user/:userID", api.GetUserTags) @@ -85,7 +86,9 @@ func (s *Server) Start() error { // interaction apis r.POST("/interaction/like/:id", IsLogin(), api.Like) r.POST("/interaction/follow/:id", IsLogin(), api.Follow) + r.POST("/interaction/following/weight", IsLogin(), api.SetFollowingWeight) r.GET("/interaction/followed/:id", api.Followed) + r.GET("/interaction/following/:userID", api.GetFollowing) // search apis r.GET("/search/posts/:filter", api.SearchPosts) diff --git a/server/internal/storage/sql_tables.go b/server/internal/storage/sql_tables.go index 80fe0765..eb6b6a98 100644 --- a/server/internal/storage/sql_tables.go +++ b/server/internal/storage/sql_tables.go @@ -92,6 +92,7 @@ var sqlTables = map[string]string{ user_id VARCHAR(255), target_id VARCHAR(255), target_type VARCHAR(1), + weight TINYINT DEFAULT 1, created DATETIME NOT NULL ); CREATE INDEX IF NOT EXISTS follows_userid diff --git a/server/internal/tags/tags.go b/server/internal/tags/tags.go index 3450898e..8a154641 100644 --- a/server/internal/tags/tags.go +++ b/server/internal/tags/tags.go @@ -103,6 +103,20 @@ func GetTags() (models.Tags, *e.Error) { return tags, nil } +func GetTagsByIDs(ids []string) ([]*models.Tag, *e.Error) { + tags := make([]*models.Tag, 0, len(ids)) + for _, id := range ids { + tag, err := GetSimpleTag(id, "") + if err != nil { + logger.Warn("get tag error", "error", err) + continue + } + tags = append(tags, tag) + } + + return tags, nil +} + func DeleteTag(id int64) *e.Error { _, err := db.Conn.Exec("DELETE FROM tags WHERE id=?", id) if err != nil { diff --git a/server/pkg/common/reserve_urls.go b/server/pkg/common/reserve_urls.go index 7e1e523e..4f9b5cb0 100644 --- a/server/pkg/common/reserve_urls.go +++ b/server/pkg/common/reserve_urls.go @@ -4,6 +4,7 @@ package common var ReserverURLs = []string{ "/tags", "/courses", + "/follow", "/editor", "/admin", "/bookmarks", diff --git a/server/pkg/models/id_type.go b/server/pkg/models/id_type.go index ae91554d..9a62947b 100644 --- a/server/pkg/models/id_type.go +++ b/server/pkg/models/id_type.go @@ -75,3 +75,11 @@ func ValidStoryIDType(tp string) bool { return false } + +func ValidFollowIDType(tp string) bool { + if tp == IDTypeUser || tp == IDTypeTag { + return true + } + + return false +} diff --git a/server/pkg/models/interaction.go b/server/pkg/models/interaction.go new file mode 100644 index 00000000..93182405 --- /dev/null +++ b/server/pkg/models/interaction.go @@ -0,0 +1,14 @@ +package models + +type Following struct { + ID string `json:"id"` + Weight int `json:"weight"` +} + +type Followings []*Following + +func (s Followings) Len() int { return len(s) } +func (s Followings) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s Followings) Less(i, j int) bool { + return s[i].Weight > s[j].Weight +} diff --git a/src/components/card.tsx b/src/components/card.tsx index 01151004..396f65e4 100644 --- a/src/components/card.tsx +++ b/src/components/card.tsx @@ -1,7 +1,11 @@ import React from "react" import { Box, BoxProps, useColorModeValue } from "@chakra-ui/react" -export const Card = (props: BoxProps) => { +interface Props { + shadowed?: boolean +} + +export const Card = ({shadowed, ...rest}: BoxProps&Props) => { const bg = useColorModeValue("white", "gray.780") return ( { borderRadius=".5rem" borderWidth="1px" p={[2,2,4,4]} - // boxShadow="0 1px 1px 0 rgb(0 0 0 / 5%)" - {...props} + boxShadow={shadowed? "0 1px 1px 0 rgb(0 0 0 / 5%)" : null} + {...rest} /> ) } diff --git a/src/components/tags/tag-card.tsx b/src/components/tags/tag-card.tsx index d83a1c0b..e70b4558 100644 --- a/src/components/tags/tag-card.tsx +++ b/src/components/tags/tag-card.tsx @@ -30,7 +30,7 @@ export const TagCard= (props:Props) =>{ {showActions ? - + {/* */} :  posts } diff --git a/src/components/user-menu.tsx b/src/components/user-menu.tsx index 0a63b77d..95e0c35d 100644 --- a/src/components/user-menu.tsx +++ b/src/components/user-menu.tsx @@ -14,7 +14,7 @@ import { Session } from "src/types/user" import { useRouter } from "next/router" import storage from "utils/localStorage" import { ReserveUrls } from "src/data/reserve-urls" -import { FaRegSun, FaUserAlt ,FaBookmark, FaSignOutAlt,FaEdit,FaStar} from "react-icons/fa" +import { FaRegSun, FaUserAlt ,FaBookmark, FaSignOutAlt,FaEdit,FaStar, FaHeart} from "react-icons/fa" import { isAdmin, isEditor } from "utils/role" import { logout } from "utils/session" import Link from "next/link" @@ -56,8 +56,9 @@ export const UserMenu = () => { {isEditor(session.user.role) && } >创作中心} - {isAdmin(session.user.role) && } >管理员} }>书签收藏 + }>我的关注 + {isAdmin(session.user.role) && } >管理员} }>偏好设置 logout()} icon={}>账号登出 diff --git a/src/data/links.tsx b/src/data/links.tsx index 86e324d5..2e2d082b 100644 --- a/src/data/links.tsx +++ b/src/data/links.tsx @@ -24,6 +24,24 @@ export const editorLinks: Route[] = [{ } ] +export const followLinks: any[] = [ + { + title: 'Following tags', + path: `${ReserveUrls.Follow}/tags`, + disabled: false + }, + { + title: 'Following users', + path: `${ReserveUrls.Follow}/users`, + disabled: false + }, + { + title: 'Followers', + path: `${ReserveUrls.Follow}/followers`, + disabled: false + }, +] + export const searchLinks: any[] = [{ title: '文章', path: `${ReserveUrls.Search}/posts`, diff --git a/src/data/reserve-urls.ts b/src/data/reserve-urls.ts index 43acd2db..8f721029 100644 --- a/src/data/reserve-urls.ts +++ b/src/data/reserve-urls.ts @@ -2,6 +2,7 @@ export enum ReserveUrls { Tags = "/tags", Courses = "/courses", + Follow = "/follow", Editor = "/editor", Admin = "/admin", Bookmarks = "/bookmarks",