diff --git a/pages/editor/series.tsx b/pages/editor/series.tsx
index 52bea20f..dd98e990 100644
--- a/pages/editor/series.tsx
+++ b/pages/editor/series.tsx
@@ -25,6 +25,21 @@ var validator = require('validator');
const newSeries: Story = { title: '', brief: '', cover: '', type: IDType.Series }
const SeriesPage = () => {
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+}
+export default SeriesPage
+
+
+export const SeriesEditor = ({orgID=""}) => {
const [currentSeries, setCurrentSeries]: [Story, any] = useState(null)
const [series, setSeries] = useState([])
const [posts, setPosts] = useState([])
@@ -34,12 +49,26 @@ const SeriesPage = () => {
const borderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark)
- const getSeries = () => {
- requestApi.get(`/story/posts/editor?type=${IDType.Series}`).then((res) => setSeries(res.data)).catch(_ => setPosts([]))
+ const getSeries = async () => {
+ let res
+ if (orgID) {
+ res = await requestApi.get(`/story/posts/org/${orgID}?type=${IDType.Series}`)
+ } else {
+ res = await requestApi.get(`/story/posts/editor?type=${IDType.Series}`)
+ }
+
+ setSeries(res.data)
}
- const getPosts = () => {
- requestApi.get(`/story/posts/editor?type=${IDType.Post}`).then((res) => setPosts(res.data)).catch(_ => setPosts([]))
+ const getPosts = async () => {
+ let res
+ if (orgID) {
+ res = await requestApi.get(`/story/posts/org/${orgID}?type=${IDType.Post}`)
+ } else {
+ res = await requestApi.get(`/story/posts/editor?type=${IDType.Post}`)
+ }
+
+ setPosts(res.data)
}
useEffect(() => {
@@ -79,9 +108,13 @@ const SeriesPage = () => {
const submitSeries = async (values, _) => {
// 这里必须按照顺序同步提交
- await requestApi.post(`/story`, values)
- await requestApi.post(`/story/series/post/${values.id}`, seriesPosts)
+ if (orgID) {
+ await requestApi.post(`/story`, {...values,ownerID: orgID})
+ } else {
+ await requestApi.post(`/story`, values)
+ }
+ await requestApi.post(`/story/series/post/${values.id}`, seriesPosts)
toast({
description: "提交成功",
@@ -147,15 +180,16 @@ const SeriesPage = () => {
}
const onPinPost = async id => {
- await requestApi.post(`/story/pin/${id}`)
+ if (orgID) {
+ await requestApi.post(`/org/pin/story/${id}`)
+ } else {
+ await requestApi.post(`/story/pin/${id}`)
+ }
+
getSeries()
}
return (
- <>
-
-
-
{currentSeries ?
<>
@@ -293,10 +327,5 @@ const SeriesPage = () => {
}
>}
-
-
- >
)
-}
-export default SeriesPage
-
+}
\ No newline at end of file
diff --git a/pages/settings/navbar.tsx b/pages/settings/navbar.tsx
index b0028e2f..830195d3 100644
--- a/pages/settings/navbar.tsx
+++ b/pages/settings/navbar.tsx
@@ -13,7 +13,22 @@ import { IDType } from "src/types/id"
import { Story } from "src/types/story"
const UserNavbarPage = () => {
- const [navbars, setNavbars]:[Navbar[],any] = useState([])
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+}
+export default UserNavbarPage
+
+
+export const NavbarEditor = ({ orgID = "" }) => {
+ const [navbars, setNavbars]: [Navbar[], any] = useState([])
const [series, setSeries]: [Story[], any] = useState([])
const [currentNavbar, setCurrentNavbar]: [Navbar, any] = useState(null)
const { isOpen, onOpen, onClose } = useDisclosure()
@@ -24,12 +39,18 @@ const UserNavbarPage = () => {
}, [])
const getNavbars = async () => {
- const res = await requestApi.get("/user/navbars/0")
+ const res = await requestApi.get(`/user/navbars/${orgID ? orgID : 0}`)
setNavbars(res.data)
}
const getSeries = async () => {
- const res = await requestApi.get(`/story/posts/editor?type=${IDType.Series}`)
+ let res
+ if (orgID) {
+ res = await requestApi.get(`/story/posts/org/${orgID}?type=${IDType.Series}`)
+ } else {
+ res = await requestApi.get(`/story/posts/editor?type=${IDType.Series}`)
+ }
+
setSeries(res.data)
}
@@ -41,7 +62,7 @@ const UserNavbarPage = () => {
duration: 2000,
isClosable: true,
})
- return
+ return
}
if (currentNavbar.label.length > config.user.navbarMaxLen) {
@@ -51,11 +72,15 @@ const UserNavbarPage = () => {
duration: 2000,
isClosable: true,
})
- return
+ return
}
+ if (orgID) {
+ await requestApi.post(`/org/navbar/${orgID}`,currentNavbar)
+ } else {
+ await requestApi.post(`/user/navbar`, currentNavbar)
+ }
- await requestApi.post(`/user/navbar`, currentNavbar)
setCurrentNavbar(null)
onClose()
getNavbars()
@@ -77,7 +102,7 @@ const UserNavbarPage = () => {
}
const onNvTypeChange = v => {
- const tp = parseInt(v);
+ const tp = parseInt(v);
currentNavbar.type = tp
if (tp === NavbarType.Link) {
currentNavbar.value = ""
@@ -97,50 +122,44 @@ const UserNavbarPage = () => {
}
const onDeleteNavbar = async id => {
- requestApi.delete(`/user/navbar/${id}`)
+ await requestApi.delete(`/${orgID?'org':'user'}/navbar/${id}`)
getNavbars()
}
return (
<>
-
-
-
-
-
- 菜单设置
-
-
-
-
-
- Label |
- Type |
- Value |
- Weight |
- |
-
-
-
- {
- navbars.map((nv,i) =>
- {nv.label} |
- {nv.type === NavbarType.Link ? "link" : "series"} |
- {nv.type === NavbarType.Link ? nv.value : getSeriesTitle(nv.value)} |
- {nv.weight} |
-
- onEditNavbar(nv)}/>
- onDeleteNavbar(nv.id)} />
- |
-
)
- }
-
-
-
-
-
-
-
+
+
+ 菜单设置
+
+
+
+
+
+ Label |
+ Type |
+ Value |
+ Weight |
+ |
+
+
+
+ {
+ navbars.map((nv, i) =>
+ {nv.label} |
+ {nv.type === NavbarType.Link ? "link" : "series"} |
+ {nv.type === NavbarType.Link ? nv.value : getSeriesTitle(nv.value)} |
+ {nv.weight} |
+
+ onEditNavbar(nv)} />
+ onDeleteNavbar(nv.id)} />
+ |
+
)
+ }
+
+
+
+
{currentNavbar &&
@@ -185,6 +204,4 @@ const UserNavbarPage = () => {
>
)
-}
-export default UserNavbarPage
-
+}
\ No newline at end of file
diff --git a/pages/settings/org/navbar/[org_id].tsx b/pages/settings/org/navbar/[org_id].tsx
new file mode 100644
index 00000000..e9715aaf
--- /dev/null
+++ b/pages/settings/org/navbar/[org_id].tsx
@@ -0,0 +1,35 @@
+import { Box } from "@chakra-ui/react"
+import PageContainer from "layouts/page-container"
+import PageContainer1 from "layouts/page-container1"
+import Sidebar from "layouts/sidebar/sidebar"
+import { useRouter } from "next/router"
+import { NavbarEditor } from "pages/settings/navbar"
+import { useEffect, useState } from "react"
+import { orgSettingLinks } from "src/data/links"
+import { User } from "src/types/user"
+import { requestApi } from "utils/axios/request"
+
+const UserNavbarPage = () => {
+ const [org, setOrg]:[User,any] = useState(null)
+ const router = useRouter()
+ const orgID = router.query.org_id
+ useEffect(() => {
+ if (orgID) {
+ requestApi.get(`/user/info/${router.query.org_id}`).then(res => setOrg(res.data))
+ }
+
+ }, [orgID])
+
+
+ return (
+ <>
+
+
+
+ {orgID && }
+
+
+ >
+ )
+}
+export default UserNavbarPage
\ No newline at end of file
diff --git a/pages/settings/org/series/[org_id].tsx b/pages/settings/org/series/[org_id].tsx
new file mode 100644
index 00000000..6eadabf4
--- /dev/null
+++ b/pages/settings/org/series/[org_id].tsx
@@ -0,0 +1,48 @@
+import { Text, Box, VStack, Divider, useToast, Heading, Alert, Tag, Button, HStack, Modal, ModalOverlay, ModalContent, ModalBody, Select, useDisclosure, Flex } 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 { orgSettingLinks } from "src/data/links"
+import { requestApi } from "utils/axios/request"
+import { useRouter } from "next/router"
+import { User } from "src/types/user"
+import UserCard from "components/users/user-card"
+import { config } from "configs/config"
+import OrgMember from "components/users/org-member"
+import { Role } from "src/types/role"
+import { cloneDeep } from "lodash"
+import { Story } from "src/types/story"
+import Empty from "components/empty"
+import ManageStories from "components/story/manage-stories"
+import { IDType } from "src/types/id"
+import { SeriesEditor } from "pages/editor/series"
+
+
+const OrgPostsPage = () => {
+ const [org, setOrg]:[User,any] = useState(null)
+ const router = useRouter()
+ const orgID = router.query.org_id
+ useEffect(() => {
+ if (orgID) {
+ requestApi.get(`/user/info/${router.query.org_id}`).then(res => setOrg(res.data))
+ }
+
+ }, [orgID])
+
+
+
+ return (
+ <>
+
+
+
+ {orgID && }
+
+
+ >
+ )
+}
+export default OrgPostsPage
+
diff --git a/server/internal/api/api.go b/server/internal/api/api.go
index 4ec29f76..ba8045cc 100644
--- a/server/internal/api/api.go
+++ b/server/internal/api/api.go
@@ -1,7 +1,34 @@
package api
-import "github.com/imdotdev/im.dev/server/pkg/log"
+import (
+ "github.com/imdotdev/im.dev/server/internal/org"
+ "github.com/imdotdev/im.dev/server/internal/story"
+ "github.com/imdotdev/im.dev/server/pkg/log"
+ "github.com/imdotdev/im.dev/server/pkg/models"
+)
var logger = log.RootLogger.New("logger", "api")
/* 鉴权、数据合法性验证都在api模块进行处理 */
+
+func isStoryCreator(userID string, storyID string) bool {
+ if models.GetIDType(storyID) == models.IDTypeSeries {
+ // 如果是series,需要判断它属于组织还是个人,两者的权限验证不同
+ story, _ := story.GetStory(storyID, "")
+ if story.OwnerID != "" {
+ if !org.IsOrgAdmin(userID, story.OwnerID) {
+ return false
+ }
+ } else {
+ if !models.IsStoryCreator(userID, storyID) {
+ return false
+ }
+ }
+ } else {
+ if !models.IsStoryCreator(userID, storyID) {
+ return false
+ }
+ }
+
+ return true
+}
diff --git a/server/internal/api/org.go b/server/internal/api/org.go
index b1db00bf..a7995209 100644
--- a/server/internal/api/org.go
+++ b/server/internal/api/org.go
@@ -372,3 +372,53 @@ func PinOrgStory(c *gin.Context) {
c.JSON(http.StatusOK, common.RespSuccess(nil))
}
+
+func SubmitOrgNavbar(c *gin.Context) {
+ orgID := c.Param("orgID")
+ u := user.CurrentUser(c)
+ if !org.IsOrgAdmin(u.ID, orgID) {
+ c.JSON(http.StatusForbidden, common.RespError(e.NoAdminPermission))
+ return
+ }
+
+ nav := &models.Navbar{}
+ err := c.Bind(&nav)
+ if err != nil || !models.ValidNavbarType(nav.Type) {
+ c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
+ return
+ }
+
+ nav.UserID = orgID
+ err1 := user.SubmitNavbar(nav)
+ if err != nil {
+ c.JSON(err1.Status, common.RespError(err1.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(nil))
+}
+
+func DeleteOrgNavbar(c *gin.Context) {
+ id := c.Param("id")
+
+ u := user.CurrentUser(c)
+
+ nav, err := user.GetNavbar(id)
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ if !org.IsOrgAdmin(u.ID, nav.UserID) {
+ c.JSON(http.StatusForbidden, common.RespError(e.NoAdminPermission))
+ return
+ }
+
+ err = user.DeleteNavbar(nav.UserID, id)
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(nil))
+}
diff --git a/server/internal/api/story.go b/server/internal/api/story.go
index 8644c246..1753d85f 100644
--- a/server/internal/api/story.go
+++ b/server/internal/api/story.go
@@ -41,7 +41,7 @@ func DeletePost(c *gin.Context) {
}
u := user.CurrentUser(c)
- if !models.IsStoryCreator(u.ID, id) {
+ if !isStoryCreator(u.ID, id) {
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
return
}
@@ -105,10 +105,11 @@ func SubmitSeriesPost(c *gin.Context) {
}
u := user.CurrentUser(c)
- if !models.IsStoryCreator(u.ID, seriesID) {
+ if !isStoryCreator(u.ID, seriesID) {
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
return
}
+
posts := make([]*models.SeriesPost, 0)
err0 := c.Bind(&posts)
if err0 != nil {
@@ -157,11 +158,10 @@ func DeleteSeriesPost(c *gin.Context) {
}
u := user.CurrentUser(c)
- if !models.IsStoryCreator(u.ID, id) {
+ if !isStoryCreator(u.ID, id) {
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
return
}
-
err := story.DeleteSeriesPost(id)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
@@ -207,8 +207,9 @@ func PinStory(c *gin.Context) {
storyID := c.Param("storyID")
u := user.CurrentUser(c)
- if !models.IsStoryCreator(u.ID, storyID) {
+ if !isStoryCreator(u.ID, storyID) {
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
+ return
}
err := story.PinStory(storyID, u.ID)
diff --git a/server/internal/server.go b/server/internal/server.go
index 796f73c8..689231fd 100644
--- a/server/internal/server.go
+++ b/server/internal/server.go
@@ -117,6 +117,9 @@ func (s *Server) Start() error {
r.POST("/org/leave/:orgID", IsLogin(), api.LeaveOrg)
r.DELETE("/org/post/:orgID/:postID", api.DeleteOrgPost)
r.POST("/org/pin/story/:id", IsLogin(), api.PinOrgStory)
+ r.POST("/org/navbar/:orgID", IsLogin(), api.SubmitOrgNavbar)
+ r.DELETE("/org/navbar/:id", IsLogin(), api.DeleteOrgNavbar)
+
// admin apis
r.POST("/admin/user", IsLogin(), api.AdminSubmitUser)
r.GET("/admin/user/all", IsLogin(), api.AdminGetUsers)
diff --git a/server/internal/story/post.go b/server/internal/story/post.go
index 3e2fc7ac..72a198db 100644
--- a/server/internal/story/post.go
+++ b/server/internal/story/post.go
@@ -71,10 +71,19 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) {
}
}
+ post.CreatorID = user.ID
// check user is in org exist
if post.OwnerID != "" {
- if !org.UserInOrg(user.ID, post.OwnerID) {
- return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
+ if models.GetIDType(post.ID) == models.IDTypeSeries {
+ // 组织的series所有权和创作权都归组织所有
+ post.CreatorID = post.OwnerID
+ if !org.IsOrgAdmin(user.ID, post.OwnerID) {
+ return nil, e.New(http.StatusForbidden, e.NoAdminPermission)
+ }
+ } else {
+ if !org.UserInOrg(user.ID, post.OwnerID) {
+ return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
+ }
}
}
@@ -92,7 +101,7 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) {
//create
_, 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)
+ post.ID, post.Type, post.CreatorID, 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)
@@ -100,7 +109,7 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) {
} else {
// 只有创建者自己才能更新内容
creator, _ := GetPostCreator(post.ID)
- if creator != user.ID {
+ if creator != post.CreatorID {
return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
}
diff --git a/server/internal/user/navbar.go b/server/internal/user/navbar.go
index 4436e666..d3959126 100644
--- a/server/internal/user/navbar.go
+++ b/server/internal/user/navbar.go
@@ -1,6 +1,7 @@
package user
import (
+ "database/sql"
"net/http"
"sort"
@@ -54,3 +55,19 @@ func DeleteNavbar(userID string, id string) *e.Error {
return nil
}
+
+func GetNavbar(id string) (*models.Navbar, *e.Error) {
+ nav := &models.Navbar{}
+ err := db.Conn.QueryRow("SELECT user_id,label,type,value,weight FROM user_navbar WHERE id=?", id).Scan(
+ &nav.UserID, &nav.Label, &nav.Type, &nav.Value, &nav.Weight,
+ )
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return nil, e.New(http.StatusNotFound, e.NotFound)
+ }
+ logger.Warn("select navbar error", "error", err)
+ return nil, e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ return nav, nil
+}
diff --git a/src/components/story/manage-stories.tsx b/src/components/story/manage-stories.tsx
index 69ad3733..fe30eaa2 100644
--- a/src/components/story/manage-stories.tsx
+++ b/src/components/story/manage-stories.tsx
@@ -38,7 +38,7 @@ export const ManageStories = (props: Props) => {
)}
- {showFooter &&
没有更多文章了}
+ {showFooter && 没有更多文章了}
>
)
}
diff --git a/src/data/links.tsx b/src/data/links.tsx
index 2780d15a..1a93545e 100644
--- a/src/data/links.tsx
+++ b/src/data/links.tsx
@@ -119,6 +119,18 @@ export function orgSettingLinks(orgID) {
icon: getSvgIcon("post"),
disabled: false
},
+ {
+ title: '系列管理',
+ path: `${ReserveUrls.Settings}/org/series/${orgID}`,
+ icon: getSvgIcon("series"),
+ disabled: false
+ },
+ {
+ title: '菜单管理',
+ path: `${ReserveUrls.Settings}/org/navbar/${orgID}`,
+ icon: getSvgIcon("navbar"),
+ disabled: false
+ },
]
}