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