From 39b01f4cca4355b71bb20c62eb5fc33c6e48595f Mon Sep 17 00:00:00 2001 From: sunface Date: Wed, 17 Mar 2021 10:21:45 +0800 Subject: [PATCH] udpate --- layouts/nav/editor-nav.tsx | 28 +++++++++++++++++++-- pages/editor/series.tsx | 35 ++++++++++++++++++--------- server/internal/org/org.go | 15 ++++++++++++ server/internal/storage/sql_tables.go | 3 +++ server/internal/story/post.go | 25 ++++++++++++++----- server/internal/story/posts.go | 10 ++++++-- server/pkg/models/story.go | 2 ++ src/components/story/story-author.tsx | 10 ++++++-- src/types/story.ts | 2 ++ 9 files changed, 107 insertions(+), 23 deletions(-) diff --git a/layouts/nav/editor-nav.tsx b/layouts/nav/editor-nav.tsx index 16c3eca5..8b1caaae 100644 --- a/layouts/nav/editor-nav.tsx +++ b/layouts/nav/editor-nav.tsx @@ -17,7 +17,8 @@ import { TagLabel, TagCloseButton, Spinner, - Text + Text, + Select } from "@chakra-ui/react" import { useViewportScroll } from "framer-motion" import NextLink from "next/link" @@ -29,6 +30,7 @@ import EditModeSelect from "components/edit-mode-select" import Tags from "components/tags/tags" import { Story } from "src/types/story" import { FaCloud } from "react-icons/fa" +import { requestApi } from "utils/axios/request" @@ -44,10 +46,21 @@ interface Props { function HeaderContent(props: Props) { const { isOpen, onOpen, onClose } = useDisclosure() - + const [orgs,setOrgs] = useState([{id:"", nickname:"Personal"}]) + const onTagsChange = ids => { props.ar.tags = ids } + + useEffect(() => { + getOrgs() + },[]) + + const getOrgs = async() => { + const res = await requestApi.get(`/org/byUserID/0`) + res.data.unshift({id:"", nickname:"Personal"}) + setOrgs(res.data) + } return ( <> @@ -95,6 +108,17 @@ function HeaderContent(props: Props) { + + 发布到 + + + + + 封面图片 diff --git a/pages/editor/series.tsx b/pages/editor/series.tsx index 665378d7..ad6699a9 100644 --- a/pages/editor/series.tsx +++ b/pages/editor/series.tsx @@ -1,4 +1,4 @@ -import { Text, Box, Heading, Image, HStack, Center, Button, Flex, FormControl, FormLabel, Input, FormErrorMessage, VStack, Textarea, Divider, useToast, Stack, StackDivider, useColorModeValue, Table, Thead, Tr, Th, Tbody, Td, CloseButton, Editable, EditablePreview, EditableInput } from "@chakra-ui/react" +import { Text, Box, Heading, Image, HStack, Center, Button, Flex, FormControl, FormLabel, Input, FormErrorMessage, VStack, Textarea, Divider, useToast, Stack, StackDivider, useColorModeValue, Table, Thead, Tr, Th, Tbody, Td, CloseButton, Editable, EditablePreview, EditableInput, Select } from "@chakra-ui/react" import Card from "components/card" import Sidebar from "layouts/sidebar/sidebar" import React, { useEffect, useState } from "react" @@ -24,7 +24,7 @@ var validator = require('validator'); const newSeries: Story = { title: '', brief: '', cover: '', type: IDType.Series } const PostsPage = () => { - const [currentSeries, setCurrentSeries]:[Story,any] = useState(null) + const [currentSeries, setCurrentSeries]: [Story, any] = useState(null) const [series, setSeries] = useState([]) const [posts, setPosts] = useState([]) const [seriesPosts, setSeriesPosts] = useState([]) @@ -80,7 +80,7 @@ const PostsPage = () => { // 这里必须按照顺序同步提交 await requestApi.post(`/story`, values) await requestApi.post(`/story/series/post/${values.id}`, seriesPosts) - + toast({ description: "提交成功", @@ -124,17 +124,17 @@ const PostsPage = () => { setSeriesPosts(sposts) } - const onPriorityChange = (e,s) => { + const onPriorityChange = (e, s) => { if (e.currentTarget.value) { - const i = parseInt(e.currentTarget.value) + const i = parseInt(e.currentTarget.value) if (i) { s.priority = i } } else { s.priority = 0 } - - const sposts = cloneDeep(seriesPosts) + + const sposts = cloneDeep(seriesPosts) setSeriesPosts(sposts) } @@ -174,6 +174,19 @@ const PostsPage = () => { )} + {/* + {({ field, form }) => ( + + 发布于 + + {form.errors.ownerId} + + )} + */} {({ field, form }) => ( @@ -196,7 +209,7 @@ const PostsPage = () => { {({ field, form }) => ( 标签 - currentSeries.tags = ids}/> + currentSeries.tags = ids} /> )} @@ -229,8 +242,8 @@ const PostsPage = () => { {post.title} - - onPriorityChange(e,s)}/> + + onPriorityChange(e, s)} /> onPostDelete(s.id)} _focus={null} /> @@ -277,7 +290,7 @@ const PostsPage = () => { {series.map(post => - editSeries(post)} onDelete={() => onDeleteSeries(post.id)} showSource={false} onPin={() => onPinPost(post.id)}/> + editSeries(post)} onDelete={() => onDeleteSeries(post.id)} showSource={false} onPin={() => onPinPost(post.id)} /> )} diff --git a/server/internal/org/org.go b/server/internal/org/org.go index 7994bab7..f977268b 100644 --- a/server/internal/org/org.go +++ b/server/internal/org/org.go @@ -111,3 +111,18 @@ func IsOrgAdmin(userID string, orgID string) bool { return role.IsAdmin() } + +func UserInOrg(userID string, orgID string) bool { + var role models.RoleType + err := db.Conn.QueryRow("SELECT role FROM org_member WHERE org_id=? and user_id=?", orgID, userID).Scan(&role) + if err != nil && err != sql.ErrNoRows { + logger.Warn("check is org admin error", "error", err) + return false + } + + if err == sql.ErrNoRows { + return false + } + + return true +} diff --git a/server/internal/storage/sql_tables.go b/server/internal/storage/sql_tables.go index 837a5b6f..2a048188 100644 --- a/server/internal/storage/sql_tables.go +++ b/server/internal/storage/sql_tables.go @@ -66,6 +66,7 @@ var sqlTables = map[string]string{ id VARCHAR(255) PRIMARY KEY, type VARCHAR(1) NOT NULL, creator VARCHAR(255) NOT NULL, + owner VARCHAR(255) DEFAULT '', slug VARCHAR(64) DEFAULT '', title VARCHAR(255) DEFAULT '', md TEXT DEFAULT '', @@ -80,6 +81,8 @@ var sqlTables = map[string]string{ ON story (type); CREATE INDEX IF NOT EXISTS story_creator ON story (creator); + CREATE INDEX IF NOT EXISTS story_owner + ON story (owner); CREATE INDEX IF NOT EXISTS story_created ON story (created); `, diff --git a/server/internal/story/post.go b/server/internal/story/post.go index bc7d05af..3e2fc7ac 100644 --- a/server/internal/story/post.go +++ b/server/internal/story/post.go @@ -11,6 +11,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/org" "github.com/imdotdev/im.dev/server/internal/tags" "github.com/imdotdev/im.dev/server/internal/user" "github.com/imdotdev/im.dev/server/pkg/config" @@ -70,6 +71,13 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) { } } + // check user is in org exist + if post.OwnerID != "" { + if !org.UserInOrg(user.ID, post.OwnerID) { + return nil, e.New(http.StatusForbidden, e.NoEditorPermission) + } + } + now := time.Now() md := utils.Compress(post.Md) @@ -83,8 +91,8 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) { } //create - _, err := db.Conn.Exec("INSERT INTO story (id,type,creator,slug, title, md, url, cover, brief,status, created, updated) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", - post.ID, post.Type, user.ID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, models.StatusPublished, now, now) + _, err := db.Conn.Exec("INSERT INTO story (id,type,creator,owner,slug, title, md, url, cover, brief,status, created, updated) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)", + post.ID, post.Type, user.ID, post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, models.StatusPublished, now, now) if err != nil { logger.Warn("submit post error", "error", err) return nil, e.New(http.StatusInternalServerError, e.Internal) @@ -96,8 +104,8 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) { return nil, e.New(http.StatusForbidden, e.NoEditorPermission) } - _, err = db.Conn.Exec("UPDATE story SET slug=?, title=?, md=?, url=?, cover=?, brief=?, updated=? WHERE id=?", - post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, post.ID) + _, err = db.Conn.Exec("UPDATE story SET owner=?, slug=?, title=?, md=?, url=?, cover=?, brief=?, updated=? WHERE id=?", + post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, post.ID) if err != nil { logger.Warn("upate post error", "error", err) return nil, e.New(http.StatusInternalServerError, e.Internal) @@ -205,8 +213,8 @@ func DeletePost(id string) *e.Error { func GetStory(id string, slug string) (*models.Story, *e.Error) { ar := &models.Story{} var rawmd []byte - err := db.Conn.QueryRow("select id,type,slug,title,md,url,cover,brief,creator,status,created,updated from story where id=?", id).Scan( - &ar.ID, &ar.Type, &ar.Slug, &ar.Title, &rawmd, &ar.URL, &ar.Cover, &ar.Brief, &ar.CreatorID, &ar.Status, &ar.Created, &ar.Updated, + err := db.Conn.QueryRow("select id,type,slug,title,md,url,cover,brief,creator,owner,status,created,updated from story where id=?", id).Scan( + &ar.ID, &ar.Type, &ar.Slug, &ar.Title, &rawmd, &ar.URL, &ar.Cover, &ar.Brief, &ar.CreatorID, &ar.OwnerID, &ar.Status, &ar.Created, &ar.Updated, ) if err != nil { if err == sql.ErrNoRows { @@ -218,8 +226,13 @@ func GetStory(id string, slug string) (*models.Story, *e.Error) { md, _ := utils.Uncompress(rawmd) ar.Md = string(md) + ar.Creator = &models.UserSimple{ID: ar.CreatorID} err = ar.Creator.Query() + if ar.OwnerID != "" { + ar.Owner = &models.UserSimple{ID: ar.OwnerID} + err = ar.Owner.Query() + } // get tags t, rawTags, err := tags.GetTargetTags(ar.ID) diff --git a/server/internal/story/posts.go b/server/internal/story/posts.go index 10897110..e411c799 100644 --- a/server/internal/story/posts.go +++ b/server/internal/story/posts.go @@ -14,7 +14,7 @@ import ( "github.com/imdotdev/im.dev/server/pkg/models" ) -const PostQueryPrefix = "select id,type,slug,title,url,cover,brief,creator,created,updated from story " +const PostQueryPrefix = "select id,type,slug,title,url,cover,brief,creator,owner,created,updated from story " func HomePosts(user *models.User, filter string) (models.Stories, *e.Error) { @@ -144,7 +144,7 @@ func GetPosts(user *models.User, rows *sql.Rows) models.Stories { posts := make(models.Stories, 0) for rows.Next() { ar := &models.Story{} - err := rows.Scan(&ar.ID, &ar.Type, &ar.Slug, &ar.Title, &ar.URL, &ar.Cover, &ar.Brief, &ar.CreatorID, &ar.Created, &ar.Updated) + err := rows.Scan(&ar.ID, &ar.Type, &ar.Slug, &ar.Title, &ar.URL, &ar.Cover, &ar.Brief, &ar.CreatorID, &ar.OwnerID, &ar.Created, &ar.Updated) if err != nil { logger.Warn("scan post error", "error", err) continue @@ -155,6 +155,12 @@ func GetPosts(user *models.User, rows *sql.Rows) models.Stories { creator.Query() ar.Creator = creator + if ar.OwnerID != "" { + owner := &models.UserSimple{ID: ar.OwnerID} + owner.Query() + ar.Owner = owner + } + // 获取评论信息 ar.Comments = GetCommentCount(ar.ID) diff --git a/server/pkg/models/story.go b/server/pkg/models/story.go index 69549fe6..a6420a24 100644 --- a/server/pkg/models/story.go +++ b/server/pkg/models/story.go @@ -17,6 +17,8 @@ type Story struct { Type string `json:"type"` Creator *UserSimple `json:"creator"` CreatorID string `json:"creatorId"` + Owner *UserSimple `json:"owner"` + OwnerID string `json:"ownerId"` Title string `json:"title"` Slug string `json:"slug"` Md string `json:"md"` diff --git a/src/components/story/story-author.tsx b/src/components/story/story-author.tsx index 60e7ca9c..7976516f 100644 --- a/src/components/story/story-author.tsx +++ b/src/components/story/story-author.tsx @@ -21,8 +21,14 @@ export const StoryAuthor= ({story,showFooter=true,size='lg'}:Props) =>{ router.push(`/${story.creator.username}`)} cursor="pointer"/> - router.push(`/${story.creator.username}`)} cursor="pointer">{story.creator.nickname === "" ? story.creator.username : story.creator.nickname} - 发布于{moment(story.created).fromNow()} + {story.ownerId ? + + {story.creator.nickname} + for + {story.owner.nickname} + : + router.push(`/${story.creator.username}`)} cursor="pointer">{story.creator.nickname === "" ? story.creator.username : story.creator.nickname}} + {moment(story.created).fromNow()} {showFooter && 4 min read } diff --git a/src/types/story.ts b/src/types/story.ts index 543f88d2..a90e6ac4 100644 --- a/src/types/story.ts +++ b/src/types/story.ts @@ -14,6 +14,8 @@ export interface Story { slug?: string creator?: UserSimple creatorId?: string + owner?: UserSimple + ownerId?:string title?: string md?: string url?: string