diff --git a/pages/[username]/[post_id].tsx b/pages/[username]/[post_id].tsx index 14d88715..5a78367f 100644 --- a/pages/[username]/[post_id].tsx +++ b/pages/[username]/[post_id].tsx @@ -65,7 +65,7 @@ const PostPage = () => { {post.title} - {post.status === StoryStatus.Forbidden && 已禁用} + {post.status === StoryStatus.Forbidden && 已禁用} diff --git a/pages/admin/tag/[id].tsx b/pages/admin/tag/[id].tsx index 9948559f..b77dbb2a 100644 --- a/pages/admin/tag/[id].tsx +++ b/pages/admin/tag/[id].tsx @@ -88,22 +88,22 @@ function PostEditPage() { Title - + { tag.title = e.target.value; onChange() }} mt="4" variant="flushed" size="sm" placeholder="Tag title..." focusBorderColor="teal.400" /> Name - + { tag.name = e.target.value; onChange() }} mt="4" variant="flushed" size="sm" placeholder="Tag name..." focusBorderColor="teal.400" /> 封面 - + { tag.cover = e.target.value; onChange() }} mt="4" variant="flushed" size="sm" placeholder="图片链接,你可以用github当图片存储服务" focusBorderColor="teal.400" /> 图标 - + { tag.icon = e.target.value; onChange() }} mt="4" variant="flushed" size="sm" placeholder="图片链接" focusBorderColor="teal.400" /> diff --git a/pages/admin/tags.tsx b/pages/admin/tags.tsx index 2e22bed4..8be5c351 100644 --- a/pages/admin/tags.tsx +++ b/pages/admin/tags.tsx @@ -1,10 +1,10 @@ -import {Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast } from "@chakra-ui/react" +import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, useDisclosure, Input, HStack } 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} from "src/data/links" +import { adminLinks } from "src/data/links" import { requestApi } from "utils/axios/request" import TagCard from "components/tags/tag-card" import { useRouter } from "next/router" @@ -14,10 +14,17 @@ 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 Users from "components/users/users" +import { UserSimple } from "src/types/user" +import { getUserName } from "utils/user" const PostsPage = () => { const [tags, setTags] = useState([]) + const [moderators, setModerators]: [UserSimple[], any] = useState([]) + const { isOpen, onOpen, onClose } = useDisclosure() + const [tempModerator, setTempModerator] = useState("") + const [tempTag, setTempTag] = useState(null) const router = useRouter() const toast = useToast() const getTags = () => { @@ -32,8 +39,8 @@ const PostsPage = () => { router.push(`${ReserveUrls.Admin}/tag/${tag.name}`) } - const deleteTag= async (id) => { - await requestApi.delete(`/tag/${id}`) + const deleteTag = async (id) => { + await requestApi.delete(`/tag/id/${id}`) getTags() toast({ description: "删除成功", @@ -43,6 +50,26 @@ const PostsPage = () => { }) } + const openModerators = async (tag: Tag) => { + await queryModerators(tag.id) + setTempTag(tag) + onOpen() + } + + const queryModerators = async (id) => { + const res = await requestApi.get(`/tag/moderators/${id}`) + setModerators(res.data) + } + const addModerator = async () => { + await requestApi.post(`/tag/moderator`, { tagID: tempTag.id, username: tempModerator }) + setTempModerator("") + await queryModerators(tempTag.id) + } + + const deleteModerator = async (id) => { + await requestApi.delete(`/tag/moderator/${tempTag.id}/${id}`) + await queryModerators(tempTag.id) + } return ( <> @@ -55,13 +82,13 @@ const PostsPage = () => { { tags.length === 0 ? - + : <> {tags.map(tag => - editTag(tag)} onDelete={() => deleteTag(tag.id)} /> + editTag(tag)} onModerator={() => openModerators(tag)} onDelete={() => deleteTag(tag.id)} /> )} @@ -72,6 +99,36 @@ const PostsPage = () => { + + + + 编辑moderators + + + setTempModerator(e.currentTarget.value)} placeholder="输入username来添加" /> + + + + + {moderators.map((u, i) => + + + + + + + {u.nickname} + @{u.username} + + + + + + )} + + + + ) } diff --git a/server/internal/api/tag.go b/server/internal/api/tag.go index a4f71812..31cd0daa 100644 --- a/server/internal/api/tag.go +++ b/server/internal/api/tag.go @@ -128,3 +128,63 @@ func GetOrgTags(c *gin.Context) { c.JSON(http.StatusOK, common.RespSuccess(res)) } + +func GetTagModerators(c *gin.Context) { + id := c.Param("id") + if id == "" { + c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid)) + return + } + + res, err := tags.GetModerators(id) + if err != nil { + c.JSON(err.Status, common.RespError(err.Message)) + return + } + + c.JSON(http.StatusOK, common.RespSuccess(res)) +} + +type AddModeratorReq struct { + TagID string `json:"tagID"` + Username string `json:"username"` +} + +func AddModerator(c *gin.Context) { + req := &AddModeratorReq{} + c.Bind(&req) + + user := user.CurrentUser(c) + if !user.Role.IsSuperAdmin() { + c.JSON(http.StatusForbidden, common.RespError(e.NoPermission)) + } + + err := tags.AddModerator(req.TagID, req.Username) + if err != nil { + c.JSON(err.Status, common.RespError(err.Message)) + return + } + + c.JSON(http.StatusOK, common.RespSuccess(nil)) +} + +func DeleteModerator(c *gin.Context) { + tagID := c.Param("tagID") + userID := c.Param("userID") + if tagID == "" || userID == "" { + c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid)) + return + } + user := user.CurrentUser(c) + if !user.Role.IsSuperAdmin() { + c.JSON(http.StatusForbidden, common.RespError(e.NoPermission)) + } + + err := tags.DeleteModerator(tagID, userID) + if err != nil { + c.JSON(err.Status, common.RespError(err.Message)) + return + } + + c.JSON(http.StatusOK, common.RespSuccess(nil)) +} diff --git a/server/internal/server.go b/server/internal/server.go index e59fe68c..da88a35b 100644 --- a/server/internal/server.go +++ b/server/internal/server.go @@ -95,12 +95,15 @@ func (s *Server) Start() error { r.POST("/story/forbidden/:id", IsLogin(), api.ForbiddenStory) // tag apis r.POST("/tag", IsLogin(), api.SubmitTag) - r.DELETE("/tag/:id", IsLogin(), api.DeleteTag) + r.DELETE("/tag/id/:id", IsLogin(), api.DeleteTag) r.GET("/tag/all", api.GetTags) r.POST("tag/ids", api.GetTagsByIDs) // 根据对象ID列表获取关联的标签 r.GET("/tag/posts/:id", api.GetTagPosts) r.GET("/tag/info/:name", api.GetTag) r.GET("/tag/user/:userID", api.GetUserTags) // 获取用户博客用到的标签列表 + r.GET("/tag/moderators/:id", api.GetTagModerators) + r.POST("/tag/moderator", IsLogin(), api.AddModerator) + r.DELETE("/tag/moderator/:tagID/:userID", IsLogin(), api.DeleteModerator) // user apis r.GET("/user/all", api.GetUsers) r.POST("/user/ids", api.GetUsersByIDs) diff --git a/server/internal/storage/sql_tables.go b/server/internal/storage/sql_tables.go index 0f6343b9..b2ff14bd 100644 --- a/server/internal/storage/sql_tables.go +++ b/server/internal/storage/sql_tables.go @@ -172,6 +172,15 @@ var sqlTables = map[string]string{ ON tags_using (tag_id,target_type); `, + "tag_moderators": `CREATE TABLE IF NOT EXISTS tag_moderators ( + tag_id VARCHAR(255), + user_id VARCHAR(255), + created DATETIME NOT NULL + ); + CREATE UNIQUE INDEX IF NOT EXISTS tag_md_tid_uid + ON tag_moderators (tag_id,user_id); + `, + "comments": `CREATE TABLE IF NOT EXISTS comments ( id VARCHAR(255) PRIMARY KEY, story_id VARCHAR(255), diff --git a/server/internal/story/posts.go b/server/internal/story/posts.go index 2cffdae0..4699a7ba 100644 --- a/server/internal/story/posts.go +++ b/server/internal/story/posts.go @@ -54,15 +54,15 @@ func UserPosts(tp string, user *models.User, uid string, page int64, perPage int var err error if tp == models.IDTypeUndefined { if perPage == 0 { - rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and status=?", uid, models.StatusPublished) + rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and status!=?", uid, models.StatusDraft) } else { - rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and status=? ORDER BY created DESC LIMIT ?,?", uid, models.StatusPublished, (page-1)*perPage, perPage) + rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and status!=? ORDER BY created DESC LIMIT ?,?", uid, models.StatusDraft, (page-1)*perPage, perPage) } } else { if perPage == 0 { - rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and type=? and status=?", uid, tp, models.StatusPublished) + rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and type=? and status!=?", uid, tp, models.StatusDraft) } else { - rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and type=? and status=? ORDER BY created DESC LIMIT ?,? ", uid, tp, models.StatusPublished, (page-1)*perPage, perPage) + rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and type=? and status!=? ORDER BY created DESC LIMIT ?,? ", uid, tp, models.StatusDraft, (page-1)*perPage, perPage) } } diff --git a/server/internal/tags/tags.go b/server/internal/tags/tags.go index 7250b0fd..e479b471 100644 --- a/server/internal/tags/tags.go +++ b/server/internal/tags/tags.go @@ -250,3 +250,64 @@ func GetUserTags(userID string) ([]*models.Tag, *e.Error) { return tags, nil } + +func GetModerators(id string) ([]*models.UserSimple, *e.Error) { + users := make([]*models.UserSimple, 0) + rows, err := db.Conn.Query("SELECT user_id FROM tag_moderators WHERE tag_id=?", id) + if err != nil { + logger.Warn("get tag moderators error", "error", err) + return nil, e.New(http.StatusInternalServerError, e.Internal) + } + + for rows.Next() { + var uid string + rows.Scan(&uid) + + u := &models.UserSimple{ + ID: uid, + } + + err = u.Query() + if err != nil { + logger.Warn("query user error", "error", err) + continue + } + users = append(users, u) + } + + return users, nil +} + +func AddModerator(tagID, username string) *e.Error { + var userID string + err := db.Conn.QueryRow("SELECT id FROM user WHERE username=?", username).Scan(&userID) + if err != nil { + if err == sql.ErrNoRows { + return e.New(http.StatusNotFound, e.NotFound) + } + logger.Warn("add tag moderator error", "error", err) + return e.New(http.StatusInternalServerError, e.Internal) + } + + _, err = db.Conn.Exec("INSERT INTO tag_moderators (tag_id,user_id,created) VALUES (?,?,?)", tagID, userID, time.Now()) + if err != nil { + if e.IsErrUniqueConstraint(err) { + return e.New(http.StatusConflict, e.AlreadyExist) + } + + logger.Warn("add tag moderator error", "error", err) + return e.New(http.StatusInternalServerError, e.Internal) + } + + return nil +} + +func DeleteModerator(tagID, userID string) *e.Error { + _, err := db.Conn.Exec("DELETE FROM tag_moderators WHERE tag_id=? and user_id=?", tagID, userID) + if err != nil { + logger.Warn("add tag moderator error", "error", err) + return e.New(http.StatusInternalServerError, e.Internal) + } + + return nil +} diff --git a/src/components/tags/tag-card.tsx b/src/components/tags/tag-card.tsx index c91a270e..39a9ed60 100644 --- a/src/components/tags/tag-card.tsx +++ b/src/components/tags/tag-card.tsx @@ -11,12 +11,13 @@ type Props = PropsOf & { tag: Tag showActions?: boolean onEdit?: any + onModerator?: any unit?: string } export const TagCard= (props:Props) =>{ - const {tag,showActions=false,onEdit,unit="posts"} = props + const {tag,showActions=false,onEdit,onModerator,unit="posts"} = props return ( @@ -31,7 +32,7 @@ export const TagCard= (props:Props) =>{ {showActions ? - {/* */} + :  {unit} } diff --git a/src/components/users/users.tsx b/src/components/users/users.tsx index 9ac996aa..5497348b 100644 --- a/src/components/users/users.tsx +++ b/src/components/users/users.tsx @@ -7,10 +7,11 @@ import userCustomTheme from "theme/user-custom" type Props = PropsOf & { users: User[] highlight?: string + displayFollow?: boolean } export const Users = (props: Props) => { - const { users,highlight, ...rest } = props + const { users,highlight,displayFollow=true, ...rest } = props const postBorderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark) const showBorder = i => { if (i < users.length - 1) { @@ -21,7 +22,7 @@ export const Users = (props: Props) => { {users.map((u,i) => - + )} diff --git a/src/types/tag.ts b/src/types/tag.ts index 88ed2c10..54340661 100644 --- a/src/types/tag.ts +++ b/src/types/tag.ts @@ -8,4 +8,5 @@ export interface Tag { created?: string posts?: number follows?: number + moderators?: string } \ No newline at end of file