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