pull/53/head
sunface 4 years ago
parent f5ca3e25ea
commit 686ab3430c

@ -65,7 +65,7 @@ const PostPage = () => {
<Box px="2">
<HStack>
<Heading size="lg" my="6" lineHeight="1.5">{post.title}</Heading>
{post.status === StoryStatus.Forbidden && <Tooltip label="因为文章内容问题,你的文章已经被禁用,如需恢复,请修改内容后,去创作中心申请恢复"><Tag colorScheme="red"></Tag></Tooltip>}
{post.status === StoryStatus.Forbidden && <Tooltip label="因为文章内容问题,你的文章已经被禁用,如需恢复,请修改内容后,发送邮件到官方邮箱"><Tag colorScheme="red"></Tag></Tooltip>}
</HStack>

@ -88,22 +88,22 @@ function PostEditPage() {
<Card width="35%">
<Heading size="xs">
Title
</Heading>
</Heading>
<Input value={tag.title} onChange={(e) => { tag.title = e.target.value; onChange() }} mt="4" variant="flushed" size="sm" placeholder="Tag title..." focusBorderColor="teal.400" />
<Heading size="xs" mt="8">
Name
</Heading>
</Heading>
<Input value={tag.name} onChange={(e) => { tag.name = e.target.value; onChange() }} mt="4" variant="flushed" size="sm" placeholder="Tag name..." focusBorderColor="teal.400" />
<Heading size="xs" mt="8">
</Heading>
</Heading>
<Input value={tag.cover} onChange={(e) => { tag.cover = e.target.value; onChange() }} mt="4" variant="flushed" size="sm" placeholder="图片链接你可以用github当图片存储服务" focusBorderColor="teal.400" />
<Heading size="xs" mt="8">
</Heading>
</Heading>
<Input value={tag.icon} onChange={(e) => { tag.icon = e.target.value; onChange() }} mt="4" variant="flushed" size="sm" placeholder="图片链接" focusBorderColor="teal.400" />
</Card>
</HStack>

@ -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 (
<>
<PageContainer1>
@ -55,13 +82,13 @@ const PostsPage = () => {
</Flex>
{
tags.length === 0 ?
<Empty />
<Empty />
:
<>
<VStack mt="4">
{tags.map(tag =>
<Box width="100%" key={tag.id}>
<TagCard tag={tag} showActions={true} mt="4" onEdit={() => editTag(tag)} onDelete={() => deleteTag(tag.id)} />
<TagCard tag={tag} showActions={true} mt="4" onEdit={() => editTag(tag)} onModerator={() => openModerators(tag)} onDelete={() => deleteTag(tag.id)} />
<Divider mt="5" />
</Box>
)}
@ -72,6 +99,36 @@ const PostsPage = () => {
</Card>
</Box>
</PageContainer1>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>moderators</ModalHeader>
<ModalBody mb="2">
<HStack>
<Input _focus={null} value={tempModerator} onChange={e => setTempModerator(e.currentTarget.value)} placeholder="输入username来添加" />
<Button colorScheme="teal" onClick={addModerator}>Add</Button>
</HStack>
<VStack alignItems="left" mt="4" spacing="4">
{moderators.map((u, i) =>
<Flex alignItems="center" justifyContent="space-between">
<Link href={`/${u.username}`}>
<HStack spacing="4" cursor="pointer">
<Image src={u.avatar} width="42px" height="42px" />
<VStack alignItems="left">
<Heading fontSize="1rem">{u.nickname}</Heading>
<Text fontSize=".9rem">@{u.username}</Text>
</VStack>
</HStack>
</Link>
<Button size="sm" onClick={() => deleteModerator(u.id)}>Delete</Button>
</Flex>
)}
</VStack>
</ModalBody>
</ModalContent>
</Modal>
</>
)
}

@ -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))
}

@ -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)

@ -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),

@ -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)
}
}

@ -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
}

@ -11,12 +11,13 @@ type Props = PropsOf<typeof chakra.div> & {
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 (
<Flex justifyContent="space-between" alignItems="center" className="hover-bg" p="2">
<NextLink href={`${ReserveUrls.Tags}/${tag.name}`}>
@ -31,7 +32,7 @@ export const TagCard= (props:Props) =>{
{showActions ?
<HStack>
<Button size="sm" colorScheme="teal" variant="outline" onClick={onEdit}>Edit</Button>
{/* <Button size="sm" onClick={onDelete} variant="ghost">Delete</Button> */}
<Button size="sm" colorScheme="purple" onClick={onModerator} variant="outline">Moderators</Button>
</HStack> :
<ChakraTag py="1" px="3" colorScheme="cyan"><Count count={unit === 'posts' ? tag.posts : tag.follows} />&nbsp;{unit}</ChakraTag>
}

@ -7,10 +7,11 @@ import userCustomTheme from "theme/user-custom"
type Props = PropsOf<typeof chakra.div> & {
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) => {
<VStack alignItems="left" {...rest}>
{users.map((u,i) =>
<Box borderBottom={showBorder(i) ? `1px solid ${postBorderColor}` : null} key={u.id}>
<UserCard user={u} highlight={highlight}/>
<UserCard user={u} highlight={highlight} displayFollow={displayFollow}/>
</Box>
)}

@ -8,4 +8,5 @@ export interface Tag {
created?: string
posts?: number
follows?: number
moderators?: string
}
Loading…
Cancel
Save