pull/52/head
sunface 4 years ago
parent fe2090db57
commit 39b01f4cca

@ -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,11 +46,22 @@ 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 (
<>
<Flex w="100%" h="100%" align="center" justify="space-between" px={{ base: "2", md: "6" }}>
@ -95,6 +108,17 @@ function HeaderContent(props: Props) {
<Divider mt="5" mb="5"/>
<Card>
<Heading size="xs">
</Heading>
<Select value={props.ar.ownerId??""} onChange={(e) => {props.ar.ownerId = e.target.value; props.onChange()}} variant="unstyled" mt="3">
{
orgs.map(o => <option value={o.id}>{o.nickname}</option>)
}
</Select>
</Card>
<Card mt="4">
<Heading size="xs">
</Heading>

@ -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([])
@ -124,7 +124,7 @@ const PostsPage = () => {
setSeriesPosts(sposts)
}
const onPriorityChange = (e,s) => {
const onPriorityChange = (e, s) => {
if (e.currentTarget.value) {
const i = parseInt(e.currentTarget.value)
if (i) {
@ -174,6 +174,19 @@ const PostsPage = () => {
</FormControl>
)}
</Field>
{/* <Field name="ownerId">
{({ field, form }) => (
<FormControl isInvalid={form.errors.ownerId && form.touched.ownerId} >
<FormLabel></FormLabel>
<Select {...field} variant="unstyled" mt="3">
{
orgs.map(o => <option value={o.id}>{o.nickname}</option>)
}
</Select>
<FormErrorMessage>{form.errors.ownerId}</FormErrorMessage>
</FormControl>
)}
</Field> */}
<Field name="cover" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.cover && form.touched.cover}>
@ -196,7 +209,7 @@ const PostsPage = () => {
{({ field, form }) => (
<FormControl>
<FormLabel></FormLabel>
<Tags tags={currentSeries.tags} onChange={(ids) => currentSeries.tags = ids}/>
<Tags tags={currentSeries.tags} onChange={(ids) => currentSeries.tags = ids} />
</FormControl>
)}
</Field>
@ -229,8 +242,8 @@ const PostsPage = () => {
<Td>{post.title}</Td>
<Td>
<Editable value={s.priority}>
<EditablePreview minWidth="100px"/>
<EditableInput onChange={(e) => onPriorityChange(e,s)}/>
<EditablePreview minWidth="100px" />
<EditableInput onChange={(e) => onPriorityChange(e, s)} />
</Editable>
</Td>
<Td width="50px"><CloseButton size="sm" onClick={() => onPostDelete(s.id)} _focus={null} /></Td>
@ -277,7 +290,7 @@ const PostsPage = () => {
<VStack mt="4">
{series.map(post =>
<Box width="100%" key={post.id}>
<TextStoryCard story={post} showActions={true} mt="4" onEdit={() => editSeries(post)} onDelete={() => onDeleteSeries(post.id)} showSource={false} onPin={() => onPinPost(post.id)}/>
<TextStoryCard story={post} showActions={true} mt="4" onEdit={() => editSeries(post)} onDelete={() => onDeleteSeries(post.id)} showSource={false} onPin={() => onPinPost(post.id)} />
<Divider mt="5" />
</Box>
)}

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

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

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

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

@ -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"`

@ -21,8 +21,14 @@ export const StoryAuthor= ({story,showFooter=true,size='lg'}:Props) =>{
<HStack spacing="4">
<Avatar src={story.creator.avatar} size={size} onClick={() => router.push(`/${story.creator.username}`)} cursor="pointer"/>
<VStack alignItems="left" spacing="1">
<Heading size="sm" onClick={() => router.push(`/${story.creator.username}`)} cursor="pointer">{story.creator.nickname === "" ? story.creator.username : story.creator.nickname}</Heading>
<Text layerStyle="textSecondary" fontSize={size==='lg' ? ".9rem" : ".8rem"}><chakra.span fontWeight="600" ml="1">{moment(story.created).fromNow()}</chakra.span></Text>
{story.ownerId ?
<HStack spacing={size==='lg'?2:1}>
<Link href={`/${story.creator.username}`}><Text cursor="pointer">{story.creator.nickname}</Text></Link>
<Text layerStyle="textSecondary">for</Text>
<Link href={`/${story.owner.username}`}><Text cursor="pointer">{story.owner.nickname}</Text></Link>
</HStack> :
<Heading size="sm" onClick={() => router.push(`/${story.creator.username}`)} cursor="pointer">{story.creator.nickname === "" ? story.creator.username : story.creator.nickname}</Heading>}
<Text layerStyle="textSecondary" fontSize={size==='lg' ? ".9rem" : ".8rem"}><chakra.span fontWeight="600">{moment(story.created).fromNow()}</chakra.span></Text>
{showFooter && <HStack layerStyle="textSecondary" fontSize=".9rem" spacing="3">
<FaGithub /> <chakra.span>4 min read</chakra.span>
</HStack>}

@ -14,6 +14,8 @@ export interface Story {
slug?: string
creator?: UserSimple
creatorId?: string
owner?: UserSimple
ownerId?:string
title?: string
md?: string
url?: string

Loading…
Cancel
Save