mirror of https://github.com/sunface/rust-course
parent
f66164e834
commit
4a148aa80c
@ -1,50 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/imdotdev/im.dev/server/internal/story"
|
|
||||||
"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 GetEditorPosts(c *gin.Context) {
|
|
||||||
user := user.CurrentUser(c)
|
|
||||||
ars, err := story.UserPosts(int64(user.ID))
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(err.Status, common.RespError(err.Message))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, common.RespSuccess(ars))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEditorPost(c *gin.Context) {
|
|
||||||
id := c.Param("id")
|
|
||||||
if id == "" {
|
|
||||||
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user := user.CurrentUser(c)
|
|
||||||
creator, err := story.GetPostCreator(id)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(err.Status, common.RespError(err.Message))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.ID != creator {
|
|
||||||
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ar, err := story.GetPost(id, "")
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(err.Status, common.RespError(err.Message))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, common.RespSuccess(ar))
|
|
||||||
}
|
|
@ -0,0 +1,60 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/imdotdev/im.dev/server/internal/story"
|
||||||
|
"github.com/imdotdev/im.dev/server/internal/user"
|
||||||
|
"github.com/imdotdev/im.dev/server/pkg/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetEditorPosts(c *gin.Context) {
|
||||||
|
user := user.CurrentUser(c)
|
||||||
|
ars, err := story.UserPosts(user, int64(user.ID))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(err.Status, common.RespError(err.Message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, common.RespSuccess(ars))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserPosts(c *gin.Context) {
|
||||||
|
userID, _ := strconv.ParseInt(c.Param("userID"), 10, 64)
|
||||||
|
|
||||||
|
user := user.CurrentUser(c)
|
||||||
|
|
||||||
|
posts, err := story.UserPosts(user, userID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(err.Status, common.RespError(err.Message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, common.RespSuccess(posts))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTagPosts(c *gin.Context) {
|
||||||
|
tagID, _ := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
user := user.CurrentUser(c)
|
||||||
|
posts, err := story.TagPosts(user, tagID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(err.Status, common.RespError(err.Message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, common.RespSuccess(posts))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHomePosts(c *gin.Context) {
|
||||||
|
filter := c.Param("filter")
|
||||||
|
user := user.CurrentUser(c)
|
||||||
|
posts, err := story.HomePosts(user, filter)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(err.Status, common.RespError(err.Message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, common.RespSuccess(posts))
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package story
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/imdotdev/im.dev/server/pkg/db"
|
||||||
|
"github.com/imdotdev/im.dev/server/pkg/e"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Bookmark(userID int64, storyID string) *e.Error {
|
||||||
|
storyExist := Exist(storyID)
|
||||||
|
if !storyExist {
|
||||||
|
return e.New(http.StatusNotFound, e.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
bookmarked, err := Bookmarked(userID, storyID)
|
||||||
|
if err != nil {
|
||||||
|
return e.New(http.StatusInternalServerError, e.Internal)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bookmarked {
|
||||||
|
_, err = db.Conn.Exec("insert into bookmarks (user_id,story_id) values (?,?)", userID, storyID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("add bookmark error", "error", err)
|
||||||
|
return e.New(http.StatusInternalServerError, e.Internal)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err = db.Conn.Exec("delete from bookmarks where user_id=? and story_id=?", userID, storyID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("delete bookmark error", "error", err)
|
||||||
|
return e.New(http.StatusInternalServerError, e.Internal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bookmarked(userID int64, storyID string) (bool, error) {
|
||||||
|
var nid string
|
||||||
|
err := db.Conn.QueryRow("select story_id from bookmarks where user_id=? and story_id=?", userID, storyID).Scan(&nid)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
logger.Warn("get bookmarked error", "error", err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
package story
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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 HomePosts(user *models.User, filter string) (models.Posts, *e.Error) {
|
||||||
|
|
||||||
|
rows, err := db.Conn.Query("select id,slug,title,url,cover,brief,likes,views,creator,created,updated from posts")
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
logger.Warn("get user posts error", "error", err)
|
||||||
|
return nil, e.New(http.StatusInternalServerError, e.Internal)
|
||||||
|
}
|
||||||
|
|
||||||
|
posts := getPosts(user, rows)
|
||||||
|
sort.Sort(posts)
|
||||||
|
|
||||||
|
return posts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserPosts(user *models.User, uid int64) (models.Posts, *e.Error) {
|
||||||
|
rows, err := db.Conn.Query("select id,slug,title,url,cover,brief,likes,views,creator,created,updated from posts where creator=?", uid)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
logger.Warn("get user posts error", "error", err)
|
||||||
|
return nil, e.New(http.StatusInternalServerError, e.Internal)
|
||||||
|
}
|
||||||
|
|
||||||
|
posts := getPosts(user, rows)
|
||||||
|
|
||||||
|
sort.Sort(posts)
|
||||||
|
return posts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TagPosts(user *models.User, tagID int64) (models.Posts, *e.Error) {
|
||||||
|
// get post ids
|
||||||
|
rows, err := db.Conn.Query("select post_id from tag_post where tag_id=?", tagID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("get user posts error", "error", err)
|
||||||
|
return nil, e.New(http.StatusInternalServerError, e.Internal)
|
||||||
|
}
|
||||||
|
|
||||||
|
postIDs := make([]string, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var id string
|
||||||
|
rows.Scan(&id)
|
||||||
|
postIDs = append(postIDs, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
ids := strings.Join(postIDs, "','")
|
||||||
|
|
||||||
|
q := fmt.Sprintf("select id,slug,title,url,cover,brief,likes,views,creator,created,updated from posts where id in ('%s')", ids)
|
||||||
|
rows, err = db.Conn.Query(q)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
logger.Warn("get user posts error", "error", err)
|
||||||
|
return nil, e.New(http.StatusInternalServerError, e.Internal)
|
||||||
|
}
|
||||||
|
|
||||||
|
posts := getPosts(user, rows)
|
||||||
|
|
||||||
|
sort.Sort(posts)
|
||||||
|
return posts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPosts(user *models.User, rows *sql.Rows) models.Posts {
|
||||||
|
posts := make(models.Posts, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
ar := &models.Post{}
|
||||||
|
err := rows.Scan(&ar.ID, &ar.Slug, &ar.Title, &ar.URL, &ar.Cover, &ar.Brief, &ar.Likes, &ar.Views, &ar.CreatorID, &ar.Created, &ar.Updated)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("scan post error", "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取作者信息
|
||||||
|
creator := &models.UserSimple{ID: ar.CreatorID}
|
||||||
|
creator.Query()
|
||||||
|
ar.Creator = creator
|
||||||
|
|
||||||
|
// 获取评论信息
|
||||||
|
ar.Comments = GetCommentCount(ar.ID)
|
||||||
|
|
||||||
|
// 获取当前登录用户的like
|
||||||
|
if user != nil {
|
||||||
|
ar.Liked = GetLiked(ar.ID, user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前登录用户的bookmark
|
||||||
|
ar.Bookmarked, _ = Bookmarked(user.ID, ar.ID)
|
||||||
|
posts = append(posts, ar)
|
||||||
|
}
|
||||||
|
|
||||||
|
return posts
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { chakra } from "@chakra-ui/react"
|
||||||
|
var shortNumber = require('short-number');
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Count = (props: Props) => {
|
||||||
|
return (
|
||||||
|
<chakra.span title={props.count.toString()}>{shortNumber(props.count)}</chakra.span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Count
|
@ -0,0 +1,35 @@
|
|||||||
|
import { chakra, HStack, IconButton, Image, Tooltip, useColorMode, useColorModeValue } from "@chakra-ui/react";
|
||||||
|
import SvgButton from "components/svg-button";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { FaHeart, FaRegHeart } from "react-icons/fa";
|
||||||
|
import { requestApi } from "utils/axios/request";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
storyID: string
|
||||||
|
bookmarked: boolean
|
||||||
|
height?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Bookmark = (props: Props) => {
|
||||||
|
const {storyID, height="1.4rem"} = props
|
||||||
|
|
||||||
|
const [bookmarked,setBookmarked] = useState(props.bookmarked)
|
||||||
|
const bookmark = async () => {
|
||||||
|
await requestApi.post(`/bookmark/${storyID}`)
|
||||||
|
setBookmarked(!bookmarked)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SvgButton
|
||||||
|
aria-label="bookmark"
|
||||||
|
variant="ghost"
|
||||||
|
layerStyle="textSecondary"
|
||||||
|
_focus={null}
|
||||||
|
icon={bookmarked ?"bookmarked" :"bookmark"}
|
||||||
|
onClick={bookmark}
|
||||||
|
height={height}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Bookmark
|
@ -1,39 +0,0 @@
|
|||||||
import { chakra, HStack, IconButton, Image, Tooltip, useColorMode, useColorModeValue } from "@chakra-ui/react";
|
|
||||||
import { FaHeart, FaRegHeart } from "react-icons/fa";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
count: number
|
|
||||||
onClick: any
|
|
||||||
liked: boolean
|
|
||||||
}
|
|
||||||
const UnicornLike = (props: Props) => {
|
|
||||||
const label = "I like it"
|
|
||||||
return (
|
|
||||||
<HStack alignItems="center">
|
|
||||||
<Tooltip label={label} size="sm">
|
|
||||||
{props.liked? <IconButton
|
|
||||||
aria-label="go to github"
|
|
||||||
variant="ghost"
|
|
||||||
_focus={null}
|
|
||||||
color="red.400"
|
|
||||||
icon={<FaHeart />}
|
|
||||||
onClick={props.onClick}
|
|
||||||
fontSize="20px"
|
|
||||||
/> :
|
|
||||||
<IconButton
|
|
||||||
aria-label="go to github"
|
|
||||||
variant="ghost"
|
|
||||||
_focus={null}
|
|
||||||
color="gray.500"
|
|
||||||
icon={<FaRegHeart />}
|
|
||||||
onClick={props.onClick}
|
|
||||||
fontSize="20px"
|
|
||||||
/>}
|
|
||||||
|
|
||||||
</Tooltip>
|
|
||||||
<chakra.span layerStyle="textSecondary" fontWeight="600">{props.count}</chakra.span>
|
|
||||||
</HStack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UnicornLike
|
|
@ -0,0 +1,61 @@
|
|||||||
|
import { chakra, HStack, IconButton, Tooltip} from "@chakra-ui/react";
|
||||||
|
import Count from "components/count";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { FaHeart, FaRegHeart } from "react-icons/fa";
|
||||||
|
import { requestApi } from "utils/axios/request";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
storyID: string
|
||||||
|
count: number
|
||||||
|
liked: boolean
|
||||||
|
fontSize?: string
|
||||||
|
spacing?: string
|
||||||
|
}
|
||||||
|
const Like = (props: Props) => {
|
||||||
|
const {fontSize="20px",spacing="0"} = props
|
||||||
|
const label = "I like it"
|
||||||
|
|
||||||
|
const [liked,setLiked] = useState(props.liked)
|
||||||
|
const [count,setCount] = useState(props.count)
|
||||||
|
const like = async () => {
|
||||||
|
await requestApi.post(`/story/like/${props.storyID}`)
|
||||||
|
|
||||||
|
|
||||||
|
if (liked) {
|
||||||
|
setCount(count-1)
|
||||||
|
} else {
|
||||||
|
setCount(count+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
setLiked(!liked)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack alignItems="center" spacing={spacing}>
|
||||||
|
<Tooltip label={label} size="sm">
|
||||||
|
{liked? <IconButton
|
||||||
|
aria-label="go to github"
|
||||||
|
variant="ghost"
|
||||||
|
_focus={null}
|
||||||
|
color="red.400"
|
||||||
|
icon={<FaHeart />}
|
||||||
|
onClick={like}
|
||||||
|
fontSize={fontSize}
|
||||||
|
/> :
|
||||||
|
<IconButton
|
||||||
|
aria-label="go to github"
|
||||||
|
variant="ghost"
|
||||||
|
_focus={null}
|
||||||
|
color="gray.500"
|
||||||
|
icon={<FaRegHeart />}
|
||||||
|
onClick={like}
|
||||||
|
fontSize={fontSize}
|
||||||
|
/>}
|
||||||
|
|
||||||
|
</Tooltip>
|
||||||
|
<chakra.span layerStyle="textSecondary" fontWeight="600"><Count count={count}/></chakra.span>
|
||||||
|
</HStack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Like
|
@ -0,0 +1,57 @@
|
|||||||
|
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 Bookmark from "./bookmark"
|
||||||
|
import SvgButton from "components/svg-button"
|
||||||
|
import { useRouter } from "next/router"
|
||||||
|
import { ReserveUrls } from "src/data/reserve-urls"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
post: Post
|
||||||
|
vertical?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PostSidebar = (props: Props) => {
|
||||||
|
const {post,vertical = true} = props
|
||||||
|
const session = useSession()
|
||||||
|
const router = useRouter()
|
||||||
|
return (
|
||||||
|
<VStack alignItems="left" pos="fixed" display={{ base: "none", md: 'flex' }} width={["100%", "100%", "15%", "15%"]}>
|
||||||
|
<Box>
|
||||||
|
<Like count={post.likes} storyID={post.id} liked={post.liked} fontSize="24px" />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Box mt="6">
|
||||||
|
<Bookmark height="1.7rem" storyID={post.id} bookmarked={post.bookmarked} />
|
||||||
|
</Box>
|
||||||
|
<Box mt="4">
|
||||||
|
<SvgButton
|
||||||
|
aria-label="go to github"
|
||||||
|
variant="ghost"
|
||||||
|
layerStyle="textSecondary"
|
||||||
|
_focus={null}
|
||||||
|
fontWeight="300"
|
||||||
|
icon="share"
|
||||||
|
onClick={() => location.href = "#comments"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{post.creatorId === session?.user.id && <Box mt="4">
|
||||||
|
<SvgButton
|
||||||
|
aria-label="go to github"
|
||||||
|
variant="ghost"
|
||||||
|
layerStyle="textSecondary"
|
||||||
|
_focus={null}
|
||||||
|
fontWeight="300"
|
||||||
|
onClick={() => router.push(`${ReserveUrls.Editor}/post/${post.id}`)}
|
||||||
|
icon="edit"
|
||||||
|
/>
|
||||||
|
</Box>}
|
||||||
|
</Box>
|
||||||
|
</VStack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PostSidebar
|
Loading…
Reference in new issue