add following

pull/50/head
sunface 5 years ago
parent 00d9d1e56e
commit e0c35fae98

@ -12,99 +12,32 @@ import {
Text
} from "@chakra-ui/react"
import { useViewportScroll } from "framer-motion"
import React from "react"
import React, { useEffect, useState } from "react"
import { SearchIcon } from "@chakra-ui/icons"
import DarkMode from "components/dark-mode"
import AccountMenu from "components/user-menu"
import { FaGithub, FaTwitter, FaUserPlus } from "react-icons/fa"
import Follow from "components/interaction/follow"
import { requestApi } from "utils/axios/request"
import { Post } from "src/types/posts"
function HeaderContent() {
const mobileNav = useDisclosure()
const mobileNavBtnRef = React.useRef<HTMLButtonElement>()
useUpdateEffect(() => {
mobileNavBtnRef.current?.focus()
}, [mobileNav.isOpen])
return (
<>
<Flex w="100%" h="100%" align="center" justify="space-between" px={{ base: "4", md: "6" }}>
<HStack spacing="2">
<Heading size="md">Sunface</Heading>
<Button colorScheme="teal" variant="outline" leftIcon={<FaUserPlus />}>Follow</Button>
</HStack>
<HStack
color={useColorModeValue("gray.500", "gray.400")}
spacing={[1,1,2,2]}
>
<IconButton
size="md"
fontSize="lg"
variant="ghost"
color="current"
_focus={null}
onClick={() => alert('search in this blog')}
icon={<SearchIcon />}
aria-label="search in this blog"
/>
<DarkMode />
<AccountMenu />
</HStack>
</Flex>
<Flex w="100%" align="center" justify="space-between" px={{ base: "6", md: "10" }} mt="2">
<HStack spacing="4">
<Text fontSize="1.1rem" fontWeight="600">Home</Text>
<Text fontSize="1.1rem">Badges</Text>
</HStack>
<HStack
color={useColorModeValue("gray.500", "gray.400")}
spacing="2"
>
<IconButton
size="md"
fontSize="1.2rem"
aria-label="go to github"
variant="ghost"
color="current"
_focus={null}
icon={<FaGithub />}
/>
<IconButton
size="md"
fontSize="1.2rem"
aria-label="go to twitter"
variant="ghost"
color="current"
_focus={null}
icon={<FaTwitter />}
/>
</HStack>
</Flex>
<Divider mt="2"/>
</>
)
interface Props {
post: Post
}
function PostNav(props) {
const ref = React.useRef<HTMLHeadingElement>()
const [y, setY] = React.useState(0)
const { height = 0 } = ref.current?.getBoundingClientRect() ?? {}
function PostNav(props:Props) {
const {post} = props
const [followed, setFollowed] = useState(null)
const { scrollY } = useViewportScroll()
React.useEffect(() => {
return scrollY.onChange(() => setY(scrollY.get()))
}, [scrollY])
useEffect(() => {
if (post) {
requestApi.get(`/interaction/followed/${post.id}`).then(res => setFollowed(res.data))
}
}, [])
return (
<chakra.header
ref={ref}
shadow={y > height ? "sm" : undefined}
transition="box-shadow 0.2s"
top="0"
zIndex="3"
@ -112,10 +45,64 @@ function PostNav(props) {
right="0"
width="full"
bg={useColorModeValue('white', 'gray.800')}
{...props}
>
<chakra.div height="4.5rem" mx="auto" maxW="1200px">
<HeaderContent />
<Flex w="100%" h="100%" align="center" justify="space-between" px={{ base: "4", md: "6" }}>
<HStack spacing="2">
<Heading size="md">Sunface</Heading>
{followed !== null && <Follow targetID={post?.id} followed={followed} />}
</HStack>
<HStack
color={useColorModeValue("gray.500", "gray.400")}
spacing={[1, 1, 2, 2]}
>
<IconButton
size="md"
fontSize="lg"
variant="ghost"
color="current"
_focus={null}
onClick={() => alert('search in this blog')}
icon={<SearchIcon />}
aria-label="search in this blog"
/>
<DarkMode />
<AccountMenu />
</HStack>
</Flex>
<Flex w="100%" align="center" justify="space-between" px={{ base: "6", md: "10" }} mt="2">
<HStack spacing="4">
<Text fontSize="1.1rem" fontWeight="600">Home</Text>
<Text fontSize="1.1rem">Badges</Text>
</HStack>
<HStack
color={useColorModeValue("gray.500", "gray.400")}
spacing="2"
>
<IconButton
size="md"
fontSize="1.2rem"
aria-label="go to github"
variant="ghost"
color="current"
_focus={null}
icon={<FaGithub />}
/>
<IconButton
size="md"
fontSize="1.2rem"
aria-label="go to twitter"
variant="ghost"
color="current"
_focus={null}
icon={<FaTwitter />}
/>
</HStack>
</Flex>
<Divider mt="2" />
</chakra.div>
</chakra.header>
)

@ -64,33 +64,32 @@ const PostPage = () => {
title={siteConfig.seo.title}
description={siteConfig.seo.description}
/>
<PageContainer nav={<PostNav />} mt="2rem">
{post &&
<>
<HStack alignItems="top" spacing={[0, 0, 14, 14]}>
<Box width={["100%", "100%", "75%", "75%"]} height="fit-content" pl={[0, 0, "0%", "10%"]}>
<Image src={post.cover} />
<Box px="2">
<Heading size="xl" my="6" lineHeight="1.5">{post.title}</Heading>
{post && <PageContainer nav={<PostNav post={post} />} mt="2rem">
<>
<HStack alignItems="top" spacing={[0, 0, 14, 14]}>
<Box width={["100%", "100%", "75%", "75%"]} height="fit-content" pl={[0, 0, "0%", "10%"]}>
<Image src={post.cover} />
<Box px="2">
<Heading size="xl" my="6" lineHeight="1.5">{post.title}</Heading>
<Divider my="4" />
<PostAuthor post={post} />
<Divider my="4" />
<Divider my="4" />
<PostAuthor post={post} />
<Divider my="4" />
<MarkdownRender md={post.md} py="2" mt="6" />
</Box>
<HStack ml="2" spacing="3" mt="4">{post.rawTags.map(tag => <TagTextCard key={tag.id} tag={tag} />)}</HStack>
<Box mt="6" p="2"><Comments storyID={post.id} comments={comments} onChange={() => getComments(post.id)} /></Box>
</Box>
<Box pt="16">
<PostSidebar post={post}/>
<MarkdownRender md={post.md} py="2" mt="6" />
</Box>
</HStack>
<HStack ml="2" spacing="3" mt="4">{post.rawTags.map(tag => <TagTextCard key={tag.id} tag={tag} />)}</HStack>
<Box mt="6" p="2"><Comments storyID={post.id} comments={comments} onChange={() => getComments(post.id)} /></Box>
</Box>
<Box pt="16">
<PostSidebar post={post} />
</Box>
</HStack>
</>
}
</>
</PageContainer>
}
</>
)
}

@ -12,7 +12,7 @@ import { useRouter } from "next/router"
import React, { useEffect, useState } from "react"
import { FaComment, FaCommentAlt, FaDove, FaEdit, FaFacebook, FaFile, FaGithub, FaHeart, FaPlus, FaRegStar, FaStackOverflow, FaStar, FaTwitter, FaWeibo, FaZhihu } from "react-icons/fa"
import { ReserveUrls } from "src/data/reserve-urls"
import { User } from "src/types/session"
import { User } from "src/types/user"
import { requestApi } from "utils/axios/request"
import moment from 'moment'
import { Post } from "src/types/posts"

@ -2,13 +2,15 @@ import { trackPageview } from "analytics/track-event"
import { DefaultSeo } from "next-seo"
import Head from "next/head"
import Router from "next/router"
import React from "react"
import React, { useEffect } from "react"
import { ChakraProvider } from "@chakra-ui/react"
import theme from "theme"
import FontFace from "src/components/font-face"
import { getSeo } from "utils/seo"
import GAScript from "analytics/ga-script"
import {initUIConfig} from 'configs/config'
import { requestApi } from "utils/axios/request"
import events from "utils/events"
Router.events.on("routeChangeComplete", (url) => {
trackPageview(url)
@ -17,7 +19,16 @@ Router.events.on("routeChangeComplete", (url) => {
const App = ({ Component, pageProps }) => {
const seo = getSeo({ omitOpenGraphImage: false })
initUIConfig()
useEffect(() => {
requestApi.get(`/user/session`).then(res => {
events.emit('set-session', res.data)
})
initUIConfig()
}, [])
return (
<>
<Head>

@ -29,7 +29,7 @@ import Empty from "components/empty"
const BookmarksPage = () => {
const [filter, setFilter]:[Tag,any] = useState({id:-1})
const [filter, setFilter]:[Tag,any] = useState({id:"-1"})
const [tags, setTags]: [Tag[], any] = useState([])
const [rawPosts,setRawPosts]: [Post[],any] = useState([])
const [posts,setPosts]: [Post[],any] = useState([])
@ -59,7 +59,7 @@ import Empty from "components/empty"
}
const filterPosts = () => {
if (filter.id === -1) {
if (filter.id === "-1") {
setPosts(rawPosts)
return
}

@ -17,12 +17,22 @@ import { Post } from "src/types/posts"
import { Tag } from "src/types/tag"
import { requestApi } from "utils/axios/request"
import { isAdmin } from "utils/role"
import Follow from "components/interaction/follow"
const UserPage = () => {
const router = useRouter()
const [posts, setPosts]: [Post[], any] = useState([])
const [tag, setTag]: [Tag, any] = useState({})
const [tag, setTag]: [Tag, any] = useState(null)
const [followed, setFollowed] = useState(null)
useEffect(() => {
if (tag) {
requestApi.get(`/interaction/followed/${tag.id}`).then(res => setFollowed(res.data))
}
}, [tag])
const initData = async () => {
const res = await requestApi.get(`/tag/info/${router.query.name}`)
setTag(res.data)
@ -38,7 +48,6 @@ const UserPage = () => {
}, [router.query.name])
const session = useSession()
return (
<>
<SEO
@ -46,7 +55,7 @@ const UserPage = () => {
description={siteConfig.seo.description}
/>
<PageContainer1>
{tag.name &&
{tag && tag.name &&
<HStack alignItems="top" spacing="4" p="2">
<VStack width={["100%","100%","70%","70%"]} alignItems="left" spacing="2">
<Card p="0">
@ -58,7 +67,7 @@ const UserPage = () => {
<Text layerStyle="textSecondary" fontWeight="500" fontSize="1.2rem" mt="1" ml="1">#{tag.name}</Text>
</Box>
<Box>
<Button colorScheme="teal">Follow</Button>
{followed !== null && <Follow followed={followed} targetID={tag.id}/>}
{isAdmin(session?.user.role) && <Button ml="2" onClick={() => router.push(`${ReserveUrls.Admin}/tag/${tag.name}`)}>Edit</Button>}
</Box>
</Flex>

@ -5,6 +5,7 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/interaction"
"github.com/imdotdev/im.dev/server/internal/story"
"github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/common"
@ -24,7 +25,7 @@ func SubmitComment(c *gin.Context) {
}
// check story exist
exist := story.Exist(comment.TargetID)
exist := models.IdExist(comment.TargetID)
if !exist {
c.JSON(http.StatusNotFound, common.RespError(e.NotFound))
return
@ -59,7 +60,7 @@ func GetStoryComments(c *gin.Context) {
user := user.CurrentUser(c)
for _, comment := range comments {
if user != nil {
comment.Liked = story.GetLiked(comment.ID, user.ID)
comment.Liked = interaction.GetLiked(comment.ID, user.ID)
}
replies, err := story.GetComments(comment.ID)
@ -70,7 +71,7 @@ func GetStoryComments(c *gin.Context) {
comment.Replies = replies
for _, reply := range replies {
if user != nil {
reply.Liked = story.GetLiked(reply.ID, user.ID)
reply.Liked = interaction.GetLiked(reply.ID, user.ID)
}
}
}

@ -0,0 +1,61 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/interaction"
"github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/common"
"github.com/imdotdev/im.dev/server/pkg/e"
)
func Follow(c *gin.Context) {
user := user.CurrentUser(c)
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
err := interaction.Follow(id, user.ID)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(nil))
}
func Followed(c *gin.Context) {
user := user.CurrentUser(c)
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
followed := false
if user != nil {
followed = interaction.GetFollowed(id, user.ID)
}
c.JSON(http.StatusOK, common.RespSuccess(followed))
}
func Like(c *gin.Context) {
user := user.CurrentUser(c)
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
err := interaction.Like(id, user.ID)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(nil))
}

@ -4,6 +4,7 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/interaction"
"github.com/imdotdev/im.dev/server/internal/story"
"github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/common"
@ -58,30 +59,13 @@ func GetStoryPost(c *gin.Context) {
}
if user != nil {
ar.Liked = story.GetLiked(ar.ID, user.ID)
ar.Liked = interaction.GetLiked(ar.ID, user.ID)
ar.Bookmarked, _ = story.Bookmarked(user.ID, ar.ID)
}
c.JSON(http.StatusOK, common.RespSuccess(ar))
}
func LikeStory(c *gin.Context) {
user := user.CurrentUser(c)
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
err := story.Like(id, user.ID)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(nil))
}
func Bookmark(c *gin.Context) {
storyID := c.Param("storyID")

@ -0,0 +1,93 @@
package interaction
import (
"database/sql"
"net/http"
"time"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/models"
)
func Follow(targetID string, userId string) *e.Error {
exist := models.IdExist(targetID)
if !exist {
return e.New(http.StatusNotFound, e.NotFound)
}
followed := GetFollowed(targetID, userId)
var count int
err := db.Conn.QueryRow("SELECT count FROM follows_count WHERE target_id=?", targetID).Scan(&count)
if err != nil && err != sql.ErrNoRows {
logger.Warn("query follows count error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
exist = !(err == sql.ErrNoRows)
tx, err := db.Conn.Begin()
if err != nil {
logger.Warn("start sql transaction error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
if followed {
// 已经喜欢过该篇文章,更改为不喜欢
_, err := tx.Exec("DELETE FROM follows WHERE user_id=? and target_id=?", userId, targetID)
if err != nil {
return e.New(http.StatusInternalServerError, e.Internal)
}
count = count - 1
} else {
_, err := tx.Exec("INSERT INTO follows (user_id,target_id,target_type,created) VALUES (?,?,?,?)", userId, targetID, models.GetIDType(targetID), time.Now())
if err != nil {
logger.Warn("add follows error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
count = count + 1
}
var err0 error
if !exist {
_, err0 = tx.Exec("INSERT INTO follows_count (target_id,count) VALUES (?,?)", targetID, count)
} else {
_, err0 = tx.Exec("UPDATE follows_count SET count=? WHERE target_id=?", count, targetID)
}
if err0 != nil {
logger.Warn("add follows_count error", "error", err0)
tx.Rollback()
return e.New(http.StatusInternalServerError, e.Internal)
}
tx.Commit()
return nil
}
func GetFollowed(targetID string, userID string) bool {
followed := false
var nid string
err := db.Conn.QueryRow("SELECT target_id FROM follows WHERE user_id=? and target_id=?", userID, targetID).Scan(&nid)
if err != nil && err != sql.ErrNoRows {
logger.Warn("query folloed error", "error", err)
return false
}
if nid == targetID {
followed = true
}
return followed
}
func GetFollows(targetID string) int {
var follows int
err := db.Conn.QueryRow("SELECT count FROM follows_count WHERE target_id=?", targetID).Scan(&follows)
if err != nil && err != sql.ErrNoRows {
logger.Warn("get follow count error", "error", err)
}
return follows
}

@ -0,0 +1,5 @@
package interaction
import "github.com/imdotdev/im.dev/server/pkg/log"
var logger = log.RootLogger.New("logger", "api")

@ -1,4 +1,4 @@
package story
package interaction
import (
"database/sql"
@ -7,10 +7,11 @@ import (
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/models"
)
func Like(storyID string, userId string) *e.Error {
exist := Exist(storyID)
exist := models.IdExist(storyID)
if !exist {
return e.New(http.StatusNotFound, e.NotFound)
}
@ -40,7 +41,7 @@ func Like(storyID string, userId string) *e.Error {
}
count = count - 1
} else {
_, err := tx.Exec("INSERT INTO likes (story_id,user_id,created) VALUES (?,?,?)", storyID, userId, time.Now())
_, err := tx.Exec("INSERT INTO likes (story_id,user_id,story_type,created) VALUES (?,?,?,?)", storyID, userId, models.GetIDType(storyID), time.Now())
if err != nil {
logger.Warn("add like error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)

@ -46,7 +46,6 @@ func (s *Server) Start() error {
//story apis
r.GET("/story/post/:id", api.GetStoryPost)
r.POST("/story/like/:id", IsLogin(), api.LikeStory)
r.GET("/story/comments/:id", api.GetStoryComments)
r.POST("/story/comment", IsLogin(), api.SubmitComment)
r.DELETE("/story/comment/:id", IsLogin(), api.DeleteStoryComment)
@ -70,10 +69,14 @@ func (s *Server) Start() error {
r.GET("/user/info/:username", api.GetUser)
r.POST("/user/update", IsLogin(), api.UpdateUser)
r.GET("/user/posts/:userID", api.GetUserPosts)
r.GET("/user/session", IsLogin(), api.GetSession)
r.GET("/user/session", api.GetSession)
r.POST("/user/login", user.Login)
r.POST("/user/logout", user.Logout)
// interaction apis
r.POST("/interaction/like/:id", IsLogin(), api.Like)
r.POST("/interaction/follow/:id", IsLogin(), api.Follow)
r.GET("/interaction/followed/:id", api.Followed)
// other apis
r.GET("/config", GetConfig)

@ -57,7 +57,7 @@ func initTables() error {
now := time.Now()
_, err := db.Conn.Exec(`INSERT INTO user (id,username,email,role,nickname,avatar,created,updated) VALUES (?,?,?,?,?,?,?,?)`,
1, config.Data.User.SuperAdminUsername, config.Data.User.SuperAdminEmail, models.ROLE_SUPER_ADMIN, "", "", now, now)
utils.GenID(models.IDTypeUser), config.Data.User.SuperAdminUsername, config.Data.User.SuperAdminEmail, models.ROLE_SUPER_ADMIN, "", "", now, now)
if err != nil {
log.RootLogger.Crit("init super admin error", "error:", err)
return err

@ -69,6 +69,7 @@ var sqlTables = map[string]string{
"likes": `CREATE TABLE IF NOT EXISTS likes (
story_id VARCHAR(255),
story_type VARCHAR(1),
user_id VARCHAR(255),
created DATETIME NOT NULL
);
@ -87,6 +88,7 @@ var sqlTables = map[string]string{
"follows": `CREATE TABLE IF NOT EXISTS follows (
user_id VARCHAR(255),
target_id VARCHAR(255),
target_type VARCHAR(1),
created DATETIME NOT NULL
);
CREATE INDEX IF NOT EXISTS follows_userid

@ -6,10 +6,11 @@ import (
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/models"
)
func Bookmark(userID string, storyID string) *e.Error {
storyExist := Exist(storyID)
storyExist := models.IdExist(storyID)
if !storyExist {
return e.New(http.StatusNotFound, e.NotFound)
}

@ -7,6 +7,7 @@ import (
"sort"
"time"
"github.com/imdotdev/im.dev/server/internal/interaction"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/models"
@ -94,7 +95,7 @@ func GetComments(storyID string) (models.Comments, *e.Error) {
c.Creator = &models.UserSimple{ID: c.CreatorID}
err = c.Creator.Query()
c.Likes = GetLikes(c.ID)
c.Likes = interaction.GetLikes(c.ID)
comments = append(comments, c)
}
@ -118,7 +119,7 @@ func GetComment(id string) (*models.Comment, *e.Error) {
md, _ := utils.Uncompress(rawMd)
c.Md = string(md)
c.Likes = GetLikes(c.ID)
c.Likes = interaction.GetLikes(c.ID)
return c, nil
}
@ -175,21 +176,6 @@ func DeleteComment(id string) *e.Error {
return nil
}
func commentExist(id string) bool {
var nid string
err := db.Conn.QueryRow("SELECT id FROM comments WHERE id=?", id).Scan(&nid)
if err != nil && err != sql.ErrNoRows {
logger.Warn("query comment error", "error", err)
return false
}
if nid == id {
return true
}
return false
}
func GetCommentCount(storyID string) int {
count := 0
err := db.Conn.QueryRow("SELECT count from comments_count WHERE story_id=?", storyID).Scan(&count)

@ -10,6 +10,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/tags"
"github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/config"
@ -158,7 +159,7 @@ func GetPost(id string, slug string) (*models.Post, *e.Error) {
ar.Tags = t
ar.RawTags = rawTags
ar.Likes = GetLikes(ar.ID)
ar.Likes = interaction.GetLikes(ar.ID)
return ar, nil
}
@ -176,21 +177,6 @@ func GetPostCreator(id string) (string, *e.Error) {
return uid, nil
}
func postExist(id string) bool {
var nid string
err := db.Conn.QueryRow("SELECT id from posts WHERE id=?", id).Scan(&nid)
if err != nil {
logger.Warn("query post error", "error", err)
return false
}
if nid != id {
return false
}
return true
}
//slug有三个规则
// 1. 长度不能超过127
// 2. 每次title更新都要重新生成slug

@ -7,6 +7,7 @@ import (
"sort"
"strings"
"github.com/imdotdev/im.dev/server/internal/interaction"
"github.com/imdotdev/im.dev/server/internal/tags"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
@ -123,12 +124,11 @@ func getPosts(user *models.User, rows *sql.Rows) models.Posts {
// 获取当前登录用户的like
if user != nil {
ar.Liked = GetLiked(ar.ID, user.ID)
ar.Liked = interaction.GetLiked(ar.ID, user.ID)
// 获取当前登录用户的bookmark
ar.Bookmarked, _ = Bookmarked(user.ID, ar.ID)
}
ar.Likes = GetLikes(ar.ID)
// 获取当前登录用户的bookmark
ar.Bookmarked, _ = Bookmarked(user.ID, ar.ID)
ar.Likes = interaction.GetLikes(ar.ID)
posts = append(posts, ar)
}

@ -2,18 +2,6 @@ package story
import (
"github.com/imdotdev/im.dev/server/pkg/log"
"github.com/imdotdev/im.dev/server/pkg/models"
)
var logger = log.RootLogger.New("logger", "story")
func Exist(id string) bool {
switch id[:1] {
case models.IDTypePost:
return postExist(id)
case models.IDTypeComment:
return commentExist(id)
default:
return false
}
}

@ -15,7 +15,7 @@ func New(status int, msg string) *Error {
const (
DB = "数据库异常"
Internal = "服务器内部错误"
NeedLogin = "你需要登录才能访问该页面"
NeedLogin = "你需要登录"
NoEditorPermission = "只有编辑角色才能执行此操作"
ParamInvalid = "请求参数不正确"
NotFound = "目标不存在"

@ -1,6 +1,14 @@
package models
import (
"fmt"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
"github.com/imdotdev/im.dev/server/pkg/db"
)
const (
IDUndefined = "0"
IDTypePost = "1"
IDTypeComment = "2"
IDTypeUser = "3"
@ -26,6 +34,30 @@ func GetIdTypeTable(id string) string {
case IDTypeTag:
return "tags"
default:
return "unknown"
return IDUndefined
}
}
func IdExist(id string) bool {
if id == "" {
return false
}
tbl := GetIdTypeTable(id)
if tbl == IDUndefined {
return false
}
var nid string
err := db.Conn.QueryRow(fmt.Sprintf("SELECT id from %s WHERE id=?", tbl), id).Scan(&nid)
if err != nil {
logger.Warn("query post error", "error", err)
return false
}
if nid != id {
return false
}
return true
}

@ -5,9 +5,9 @@ import Card from "components/card"
import { getUserName } from "utils/user"
import moment from 'moment'
import { MarkdownRender } from "components/markdown-editor/render"
import Like from "components/story/like"
import Like from "components/interaction/like"
import { FaRegEdit, FaRegFlag, FaRegTrashAlt, FaReply, FaTrash } from "react-icons/fa"
import { User } from "src/types/session"
import { User } from "src/types/user"
import CommentEditor from "./editor"
import { requestApi } from "utils/axios/request"
import Reply from "./reply"

@ -8,7 +8,7 @@ import EditModeSelect from "components/edit-mode-select"
import { EditMode } from "src/types/editor"
import { MarkdownRender } from "components/markdown-editor/render"
import { Comment } from "src/types/comments"
import { User } from "src/types/session"
import { User } from "src/types/user"
interface Props {
user: User

@ -5,9 +5,9 @@ import Card from "components/card"
import { getUserName } from "utils/user"
import moment from 'moment'
import { MarkdownRender } from "components/markdown-editor/render"
import Like from "components/story/like"
import Like from "components/interaction/like"
import { FaRegEdit, FaRegFlag, FaRegTrashAlt, FaReply, FaTrash } from "react-icons/fa"
import { User } from "src/types/session"
import { User } from "src/types/user"
import CommentEditor from "./editor"
import { requestApi } from "utils/axios/request"
import Link from "next/link"

@ -0,0 +1,30 @@
import { Button } from "@chakra-ui/react";
import { useEffect, useState } from "react";
import { FaCheck, FaPlus, FaUserPlus } from "react-icons/fa";
import { requestApi } from "utils/axios/request";
interface Props {
targetID: string
followed: boolean
fontSize?: string
}
const Follow = (props: Props) => {
const [followed, setFollowed] = useState(props.followed)
const follow = async () => {
await requestApi.post(`/interaction/follow/${props.targetID}`)
setFollowed(!followed)
}
return (
<>
{followed ?
<Button colorScheme="teal" onClick={follow} _focus={null} leftIcon={<FaCheck />}>Following</Button>
:
<Button colorScheme="teal" variant="outline" leftIcon={<FaPlus />} onClick={follow} _focus={null}>Follow</Button>
}
</>
)
}
export default Follow

@ -18,7 +18,7 @@ const Like = (props: Props) => {
const [liked,setLiked] = useState(props.liked)
const [count,setCount] = useState(props.count)
const like = async () => {
await requestApi.post(`/story/like/${props.storyID}`)
await requestApi.post(`/interaction/like/${props.storyID}`)
if (liked) {

@ -8,7 +8,7 @@ import CaretStyles from 'theme/caret.styles'
import { isUsernameChar } from 'utils/user';
import { requestApi } from 'utils/axios/request';
import Card from 'components/card';
import { User } from 'src/types/session';
import { User } from 'src/types/user';
import userCustomTheme from 'theme/user-custom';
import { cloneDeep } from 'lodash';

@ -3,7 +3,7 @@ import { Box, chakra, Flex, Heading, HStack, Image, Text, useMediaQuery, VStack
import { Post } from "src/types/posts"
import PostAuthor from "./post-author"
import Link from "next/link"
import Like from "./like"
import Like from "../interaction/like"
import { FaHeart, FaRegHeart } from "react-icons/fa"
import Bookmark from "./bookmark"
import { getSvgIcon } from "components/svg-icon"

@ -2,7 +2,7 @@ import React from "react"
import { Box, BoxProps, useColorModeValue, VStack } from "@chakra-ui/react"
import { Post } from "src/types/posts"
import useSession from "hooks/use-session"
import Like from "./like"
import Like from "../interaction/like"
import Bookmark from "./bookmark"
import SvgButton from "components/svg-button"
import { useRouter } from "next/router"

@ -3,7 +3,7 @@ import { Box, chakra, Flex, Heading, HStack, Image, Text, useMediaQuery, VStack
import { Post } from "src/types/posts"
import PostAuthor from "./post-author"
import Link from "next/link"
import UnicornLike from "./like"
import UnicornLike from "../interaction/like"
import { FaHeart, FaRegBookmark, FaRegComment, FaRegHeart } from "react-icons/fa"
import SvgButton from "components/svg-button"
import Bookmark from "./bookmark"

@ -10,7 +10,7 @@ import {
Button
} from "@chakra-ui/react"
import useSession from "hooks/use-session"
import { Session } from "src/types/session"
import { Session } from "src/types/user"
import { useRouter } from "next/router"
import storage from "utils/localStorage"
import { ReserveUrls } from "src/data/reserve-urls"

@ -1,5 +1,5 @@
import { useEffect, useState } from "react"
import { Session } from "src/types/session"
import { Session } from "src/types/user"
import { requestApi } from "utils/axios/request"
import events from "utils/events"
import storage from "utils/localStorage"
@ -10,10 +10,6 @@ function useSession(): Session{
const sess = storage.get('session')
if (sess) {
setSession(sess)
// 页面重新进入时,跟服务器端进行信息同步
requestApi.get(`/user/session`).then(res => {
setSession(res.data)
})
}
events.on('set-session',storeSession)

@ -1,4 +1,4 @@
import { UserSimple } from "./session";
import { UserSimple } from "./user";
export interface Comment {
id: string

@ -1,4 +1,4 @@
import { UserSimple} from './session'
import { UserSimple} from './user'
import { Tag } from './tag';
export enum PostFilter {
@ -18,7 +18,7 @@ export interface Post {
cover?: string
brief?: string
created?: string
tags?: number[]
tags?: string[]
rawTags?: Tag[]
likes? : number
liked? : boolean

@ -1,5 +1,5 @@
export interface Tag {
id?: number
id?: string
name?: string
title?: string
md?: string

@ -21,7 +21,7 @@ export interface User {
availFor?: string
about?: string
rawSkills?: Tag[]
skills?: number[]
skills?: string[]
// social links
website?: string

@ -1,4 +1,4 @@
import {User} from 'src/types/session'
import {User} from 'src/types/user'
export function getUserName(user:User) {
return user.nickname === "" ? user.username : user.nickname
}

Loading…
Cancel
Save