add following/follower

pull/51/head
sunface 4 years ago
parent f6fc6ddc84
commit 6bb93c579e

@ -1,4 +1,4 @@
import { Box, chakra, Flex, HStack, VStack, Image, Heading, Text, Button, useColorModeValue, Divider, Wrap, Avatar, Center } from "@chakra-ui/react" import { Box, chakra, Flex, HStack, VStack, Image, Heading, Text, Button, useColorModeValue, Divider, Wrap, Avatar, Center, useDisclosure, Modal, ModalOverlay, ModalContent, ModalCloseButton, ModalBody, StackDivider, Stack, ModalHeader } from "@chakra-ui/react"
import Card from "components/card" import Card from "components/card"
import Container from "components/container" import Container from "components/container"
import SEO from "components/seo" import SEO from "components/seo"
@ -7,7 +7,7 @@ import useSession from "hooks/use-session"
import PageContainer1 from "layouts/page-container1" import PageContainer1 from "layouts/page-container1"
import { useRouter } from "next/router" import { useRouter } from "next/router"
import React, { useEffect, useState } from "react" import React, { useEffect, useState } from "react"
import {FaFacebook, FaFile, FaGithub, FaHeart, FaPlus, FaRegStar, FaStackOverflow, FaStar, FaTwitter, FaWeibo, FaZhihu } from "react-icons/fa" import { FaFacebook, FaFile, FaGithub, FaHeart, FaPlus, FaRegStar, FaStackOverflow, FaStar, FaTwitter, FaWeibo, FaZhihu } from "react-icons/fa"
import { ReserveUrls } from "src/data/reserve-urls" import { ReserveUrls } from "src/data/reserve-urls"
import { User } from "src/types/user" import { User } from "src/types/user"
import { requestApi } from "utils/axios/request" import { requestApi } from "utils/axios/request"
@ -18,18 +18,23 @@ import Link from "next/link"
import Empty from "components/empty" import Empty from "components/empty"
import Count from "components/count" import Count from "components/count"
import { Tag } from "src/types/tag" import { Tag } from "src/types/tag"
import { IDType } from "src/types/id"
import UserCard from "components/users/user-card"
import userCustomTheme from "theme/user-custom"
const UserPage = () => { const UserPage = () => {
const { isOpen, onOpen, onClose } = useDisclosure()
const router = useRouter() const router = useRouter()
const username = router.query.username const username = router.query.username
const session = useSession() const session = useSession()
const [user, setUser]: [User, any] = useState(null) const [user, setUser]: [User, any] = useState(null)
const [rawPosts, setRawPosts]: [Story[], any] = useState([]) const [rawPosts, setRawPosts]: [Story[], any] = useState([])
const [posts, setPosts]: [Story[], any] = useState([]) const [posts, setPosts]: [Story[], any] = useState([])
const [tags,setTags]:[Tag[],any] = useState([]) const [tags, setTags]: [Tag[], any] = useState([])
const [tagFilter,setTagFilter]:[Tag,any] = useState(null) const [tagFilter, setTagFilter]: [Tag, any] = useState(null)
const [followers, setFollowers]: [User[], any] = useState([])
const borderColor = useColorModeValue('white', 'transparent') const borderColor = useColorModeValue('white', 'transparent')
const stackBorderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark)
useEffect(() => { useEffect(() => {
if (username) { if (username) {
initData(username) initData(username)
@ -62,7 +67,7 @@ const UserPage = () => {
setTagFilter(tag) setTagFilter(tag)
const p = [] const p = []
rawPosts.forEach(post => { rawPosts.forEach(post => {
for (let i=0;i<post.rawTags.length;i++) { for (let i = 0; i < post.rawTags.length; i++) {
if (post.rawTags[i].id === tag.id) { if (post.rawTags[i].id === tag.id) {
p.push(post) p.push(post)
break break
@ -73,6 +78,28 @@ const UserPage = () => {
setPosts(p) setPosts(p)
} }
const viewFollowers = async tp => {
let res
if (tp === 1) {
// followings
const res0 = await requestApi.get(`/interaction/following/${user.id}?type=${IDType.User}`)
const ids = []
for (const f of res0.data) {
ids.push(f.id)
}
res = await requestApi.post(`/user/ids`, ids)
} else {
// followers
res = await requestApi.get(`/interaction/followers/${user.id}?type=${IDType.User}`)
}
setFollowers(res.data)
if (res.data.length > 0) {
onOpen()
}
}
return ( return (
<> <>
<SEO <SEO
@ -90,10 +117,12 @@ const UserPage = () => {
<Heading size="lg">{user.nickname}</Heading> <Heading size="lg">{user.nickname}</Heading>
{user.tagline && <Text layerStyle="textSecondary" fontWeight="450" fontSize="1.2rem" ml="1" mt="2">{user.tagline}</Text>} {user.tagline && <Text layerStyle="textSecondary" fontWeight="450" fontSize="1.2rem" ml="1" mt="2">{user.tagline}</Text>}
<Flex layerStyle="textSecondary" spacing="2" pt="1" alignItems="center"> <Flex layerStyle="textSecondary" spacing="2" pt="1" alignItems="center">
<chakra.span><FaHeart /></chakra.span><chakra.span ml="1">Followers <chakra.a fontWeight="600"><Count count={user.follows}/></chakra.a></chakra.span> {/* <chakra.span><FaHeart /></chakra.span> */}
<chakra.span ml="5"><FaStar /></chakra.span><chakra.span ml="1">Following <chakra.a fontWeight="600"><Count count={0}/></chakra.a></chakra.span> <chakra.span cursor="pointer" onClick={() => viewFollowers(0)}>Followers <chakra.a fontWeight="600"><Count count={user.follows} /></chakra.a></chakra.span>
{/* <chakra.span ml="5"><FaStar /></chakra.span> */}
<chakra.span ml="3" cursor="pointer" onClick={() => viewFollowers(1)}>Following <chakra.a fontWeight="600"><Count count={user.followings??0} /></chakra.a></chakra.span>
</Flex> </Flex>
<Box pt="3" position="absolute" right="15px" top="60px">{session?.user.id === user.id ? <Button onClick={() => router.push(`${ReserveUrls.Settings}/profile`)} variant="outline" leftIcon={<svg height="1.3rem" fill="currentColor" viewBox="0 0 512 512"><path d="M493.255 56.236l-37.49-37.49c-24.993-24.993-65.515-24.994-90.51 0L12.838 371.162.151 485.346c-1.698 15.286 11.22 28.203 26.504 26.504l114.184-12.687 352.417-352.417c24.992-24.994 24.992-65.517-.001-90.51zm-95.196 140.45L174 420.745V386h-48v-48H91.255l224.059-224.059 82.745 82.745zM126.147 468.598l-58.995 6.555-30.305-30.305 6.555-58.995L63.255 366H98v48h48v34.745l-19.853 19.853zm344.48-344.48l-49.941 49.941-82.745-82.745 49.941-49.941c12.505-12.505 32.748-12.507 45.255 0l37.49 37.49c12.506 12.506 12.507 32.747 0 45.255z"></path></svg>}><chakra.span display={{base:"none",md:"block"}}>Edit Profile</chakra.span></Button> <Box pt="3" position="absolute" right="15px" top="60px">{session?.user.id === user.id ? <Button onClick={() => router.push(`${ReserveUrls.Settings}/profile`)} variant="outline" leftIcon={<svg height="1.3rem" fill="currentColor" viewBox="0 0 512 512"><path d="M493.255 56.236l-37.49-37.49c-24.993-24.993-65.515-24.994-90.51 0L12.838 371.162.151 485.346c-1.698 15.286 11.22 28.203 26.504 26.504l114.184-12.687 352.417-352.417c24.992-24.994 24.992-65.517-.001-90.51zm-95.196 140.45L174 420.745V386h-48v-48H91.255l224.059-224.059 82.745 82.745zM126.147 468.598l-58.995 6.555-30.305-30.305 6.555-58.995L63.255 366H98v48h48v34.745l-19.853 19.853zm344.48-344.48l-49.941 49.941-82.745-82.745 49.941-49.941c12.505-12.505 32.748-12.507 45.255 0l37.49 37.49c12.506 12.506 12.507 32.747 0 45.255z"></path></svg>}><chakra.span display={{ base: "none", md: "block" }}>Edit Profile</chakra.span></Button>
: <Button colorScheme="teal">Follow</Button>}</Box> : <Button colorScheme="teal">Follow</Button>}</Box>
</VStack> </VStack>
</Card> </Card>
@ -120,7 +149,7 @@ const UserPage = () => {
<chakra.span fontWeight="500" ml="2">{moment(user.created).fromNow()}</chakra.span> <chakra.span fontWeight="500" ml="2">{moment(user.created).fromNow()}</chakra.span>
</HStack> </HStack>
<HStack layerStyle="textSecondary" fontSize="1.4rem" mt={(user.github || user.twitter || user.facebook || user.stackoverflow || user.weibo || user.zhihu) ? 4 : 0 } spacing="5"> <HStack layerStyle="textSecondary" fontSize="1.4rem" mt={(user.github || user.twitter || user.facebook || user.stackoverflow || user.weibo || user.zhihu) ? 4 : 0} spacing="5">
{user.github && <chakra.a href={user.github} target="_blank"><FaGithub /></chakra.a>} {user.github && <chakra.a href={user.github} target="_blank"><FaGithub /></chakra.a>}
{user.twitter && <chakra.a href={user.twitter} target="_blank"><FaTwitter /></chakra.a>} {user.twitter && <chakra.a href={user.twitter} target="_blank"><FaTwitter /></chakra.a>}
{user.facebook && <chakra.a href={user.facebook} target="_blank"><FaFacebook /></chakra.a>} {user.facebook && <chakra.a href={user.facebook} target="_blank"><FaFacebook /></chakra.a>}
@ -135,7 +164,7 @@ const UserPage = () => {
<Text mt="2">{user.availFor}</Text> <Text mt="2">{user.availFor}</Text>
</Box>} </Box>}
</Card> </Card>
{user.rawSkills.length > 0 && <Card> {user.rawSkills?.length > 0 && <Card>
<Heading size="sm" layerStyle="textSecondary" fontWeight="500"></Heading> <Heading size="sm" layerStyle="textSecondary" fontWeight="500"></Heading>
<Wrap mt="4" p="1"> <Wrap mt="4" p="1">
{ {
@ -166,14 +195,14 @@ const UserPage = () => {
<Box width={["100%","100%","70%","70%"]}> <Box width={["100%", "100%", "70%", "70%"]}>
{ posts.length === 0 ? {posts.length === 0 ?
<Card width="100%" height="fit-content"> <Card width="100%" height="fit-content">
<Empty /> <Empty />
</Card> </Card>
: :
<Card width="100%" height="fit-content" p="0" px="3"> <Card width="100%" height="fit-content" p="0" px="3">
<Stories stories={posts} showFooter={tagFilter === null}/> <Stories stories={posts} showFooter={tagFilter === null} />
</Card> </Card>
} }
</Box> </Box>
@ -182,6 +211,19 @@ const UserPage = () => {
</HStack> </HStack>
</Box> </Box>
} }
<Modal isOpen={isOpen} onClose={onClose} size="xl" isCentered>
<ModalOverlay />
<ModalContent mt="0">
<ModalBody maxHeight="calc(100vh - 200px)" overflowY="scroll" >
<VStack alignItems="left" divider={<StackDivider borderColor={stackBorderColor} />} >
{followers.map(user =>
<UserCard user={user} />
)}
</VStack>
</ModalBody>
</ModalContent>
</Modal>
</PageContainer1> </PageContainer1>
</> </>
) )

@ -1,79 +0,0 @@
import {Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast } 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 {adminLinks, followLinks} from "src/data/links"
import { requestApi } from "utils/axios/request"
import TagCard from "components/tags/tag-card"
import { useRouter } from "next/router"
import Link from "next/link"
import { ReserveUrls } from "src/data/reserve-urls"
import { Tag } from "src/types/tag"
import { route } from "next/dist/next-server/server/router"
import PageContainer1 from "layouts/page-container1"
import Empty from "components/empty"
const FollowersPage = () => {
const [tags, setTags] = useState([])
const router = useRouter()
const toast = useToast()
const getTags = () => {
requestApi.get(`/tag/all`).then((res) => setTags(res.data)).catch(_ => setTags([]))
}
useEffect(() => {
getTags()
}, [])
const editTag = (tag: Tag) => {
router.push(`${ReserveUrls.Admin}/tag/${tag.name}`)
}
const deleteTag= async (id) => {
await requestApi.delete(`/tag/${id}`)
getTags()
toast({
description: "删除成功",
status: "success",
duration: 2000,
isClosable: true,
})
}
return (
<>
<PageContainer1>
<Box display="flex">
<Sidebar routes={followLinks} title="我的关注" />
<Card ml="4" p="6" width="100%">
<Flex alignItems="center" justify="space-between">
<Heading size="md">({tags.length})</Heading>
<Button colorScheme="teal" size="sm" _focus={null}><Link href={`${ReserveUrls.Admin}/tag/new`}></Link></Button>
</Flex>
{
tags.length === 0 ?
<Empty />
:
<>
<VStack mt="4">
{tags.map(tag =>
<Box width="100%" key={tag.id}>
<TagCard tag={tag} showActions={true} mt="4" onEdit={() => editTag(tag)} onDelete={() => deleteTag(tag.id)} />
<Divider mt="5" />
</Box>
)}
</VStack>
<Center><Text layerStyle="textSecondary" fontSize="sm" mt="5"></Text></Center>
</>
}
</Card>
</Box>
</PageContainer1>
</>
)
}
export default FollowersPage

@ -1,79 +0,0 @@
import {Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast } 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 {adminLinks, followLinks} from "src/data/links"
import { requestApi } from "utils/axios/request"
import TagCard from "components/tags/tag-card"
import { useRouter } from "next/router"
import Link from "next/link"
import { ReserveUrls } from "src/data/reserve-urls"
import { Tag } from "src/types/tag"
import { route } from "next/dist/next-server/server/router"
import PageContainer1 from "layouts/page-container1"
import Empty from "components/empty"
const UsersPage = () => {
const [tags, setTags] = useState([])
const router = useRouter()
const toast = useToast()
const getTags = () => {
requestApi.get(`/tag/all`).then((res) => setTags(res.data)).catch(_ => setTags([]))
}
useEffect(() => {
getTags()
}, [])
const editTag = (tag: Tag) => {
router.push(`${ReserveUrls.Admin}/tag/${tag.name}`)
}
const deleteTag= async (id) => {
await requestApi.delete(`/tag/${id}`)
getTags()
toast({
description: "删除成功",
status: "success",
duration: 2000,
isClosable: true,
})
}
return (
<>
<PageContainer1>
<Box display="flex">
<Sidebar routes={followLinks} title="我的关注" />
<Card ml="4" p="6" width="100%">
<Flex alignItems="center" justify="space-between">
<Heading size="md">({tags.length})</Heading>
<Button colorScheme="teal" size="sm" _focus={null}><Link href={`${ReserveUrls.Admin}/tag/new`}></Link></Button>
</Flex>
{
tags.length === 0 ?
<Empty />
:
<>
<VStack mt="4">
{tags.map(tag =>
<Box width="100%" key={tag.id}>
<TagCard tag={tag} showActions={true} mt="4" onEdit={() => editTag(tag)} onDelete={() => deleteTag(tag.id)} />
<Divider mt="5" />
</Box>
)}
</VStack>
<Center><Text layerStyle="textSecondary" fontSize="sm" mt="5"></Text></Center>
</>
}
</Card>
</Box>
</PageContainer1>
</>
)
}
export default UsersPage

@ -0,0 +1,52 @@
import {Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, Wrap, WrapItem, useColorModeValue, StackDivider } 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 {adminLinks, interactionLinks} from "src/data/links"
import { requestApi } from "utils/axios/request"
import PageContainer1 from "layouts/page-container1"
import Empty from "components/empty"
import { IDType } from "src/types/id"
import UserCard from "components/users/user-card"
import userCustomTheme from "theme/user-custom"
const FollowersPage = () => {
const [users, setUsers] = useState([])
const borderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark)
const getUsers = async () => {
const res = await requestApi.get(`/interaction/followers/0?type=${IDType.User}`)
setUsers(res.data)
}
useEffect(() => {
getUsers()
}, [])
return (
<>
<PageContainer1>
<Box display="flex">
<Sidebar routes={interactionLinks} title="我的关注" />
<Card ml="4" p="6" width="100%">
{
users.length === 0 ? <Empty /> :
<VStack alignItems="left" divider={<StackDivider borderColor={borderColor} />} >
{users.map(user =>
<UserCard user={user} />
)}
</VStack>
}
</Card>
</Box>
</PageContainer1>
</>
)
}
export default FollowersPage

@ -2,7 +2,7 @@ import { Text, Box, Heading, Image, Divider, useToast, HStack, Slider, SliderTra
import Card from "components/card" import Card from "components/card"
import Sidebar from "layouts/sidebar/sidebar" import Sidebar from "layouts/sidebar/sidebar"
import React, { useEffect, useState } from "react" import React, { useEffect, useState } from "react"
import { followLinks } from "src/data/links" import {interactionLinks} from "src/data/links"
import { requestApi } from "utils/axios/request" import { requestApi } from "utils/axios/request"
import { useRouter } from "next/router" import { useRouter } from "next/router"
import PageContainer1 from "layouts/page-container1" import PageContainer1 from "layouts/page-container1"
@ -42,7 +42,7 @@ const TagsPage = () => {
<> <>
<PageContainer1> <PageContainer1>
<Box display="flex"> <Box display="flex">
<Sidebar routes={followLinks} title="我的关注" /> <Sidebar routes={interactionLinks} title="我的关注" />
<Card ml="4" p="6" width="100%"> <Card ml="4" p="6" width="100%">
<Text fontSize=".95rem" fontWeight="600">Adjust tag weight to modify your home feed. Higher values mean more appearances.</Text> <Text fontSize=".95rem" fontWeight="600">Adjust tag weight to modify your home feed. Higher values mean more appearances.</Text>
<Divider my="6" /> <Divider my="6" />

@ -0,0 +1,59 @@
import {Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, Wrap, WrapItem, useColorModeValue, StackDivider } 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 {interactionLinks} from "src/data/links"
import { requestApi } from "utils/axios/request"
import PageContainer1 from "layouts/page-container1"
import Empty from "components/empty"
import { IDType } from "src/types/id"
import UserCard from "components/users/user-card"
import userCustomTheme from "theme/user-custom"
const UsersPage = () => {
const [users, setUsers] = useState([])
const borderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark)
const getUsers = async () => {
const res = await requestApi.get(`/interaction/following/0?type=${IDType.User}`)
const ids = []
for (const f of res.data) {
ids.push(f.id)
}
const res1 = await requestApi.post(`/user/ids`, ids)
setUsers(res1.data)
}
useEffect(() => {
getUsers()
}, [])
return (
<>
<PageContainer1>
<Box display="flex">
<Sidebar routes={interactionLinks} title="我的关注" />
<Card ml="4" p="6" width="100%">
{
users.length === 0 ? <Empty /> :
<VStack alignItems="left" divider={<StackDivider borderColor={borderColor} />} >
{users.map(user =>
<UserCard user={user} />
)}
</VStack>
}
</Card>
</Box>
</PageContainer1>
</>
)
}
export default UsersPage

@ -74,13 +74,35 @@ func GetFollowing(c *gin.Context) {
userID = u.ID userID = u.ID
} }
tags, err := interaction.GetFollowing(userID, targetType) following, err := interaction.GetFollowing(userID, targetType)
if err != nil { if err != nil {
c.JSON(err.Status, common.RespError(err.Message)) c.JSON(err.Status, common.RespError(err.Message))
return return
} }
c.JSON(http.StatusOK, common.RespSuccess(tags)) c.JSON(http.StatusOK, common.RespSuccess(following))
}
func GetFollowers(c *gin.Context) {
userID := c.Param("userID")
targetType := c.Query("type")
if userID == "" || !models.ValidFollowIDType(targetType) {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
if userID == "0" {
u := user.CurrentUser(c)
userID = u.ID
}
followers, err := interaction.GetFollowers(userID, targetType)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(followers))
} }
func SetFollowingWeight(c *gin.Context) { func SetFollowingWeight(c *gin.Context) {

@ -21,6 +21,19 @@ func GetUsers(c *gin.Context) {
c.JSON(http.StatusOK, common.RespSuccess(users)) c.JSON(http.StatusOK, common.RespSuccess(users))
} }
func GetUsersByIDs(c *gin.Context) {
ids := make([]string, 0)
err := c.Bind(&ids)
if err != nil {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
us := user.GetUsersByIDs(ids)
c.JSON(http.StatusOK, common.RespSuccess(us))
}
func GetUserSelf(c *gin.Context) { func GetUserSelf(c *gin.Context) {
u := user.CurrentUser(c) u := user.CurrentUser(c)

@ -10,13 +10,12 @@ import (
) )
var logger = log.RootLogger.New("logger", "cache") var logger = log.RootLogger.New("logger", "cache")
var Users []*models.User
func Init() { func Init() {
time.Sleep(10 * time.Second) time.Sleep(2 * time.Second)
for { for {
// load users // load users
rows, err := db.Conn.Query(`SELECT id,username,role,nickname,email,avatar,last_seen_at,created FROM user`) rows, err := db.Conn.Query(`SELECT id,username,role,nickname,avatar,last_seen_at,created FROM user`)
if err != nil { if err != nil {
logger.Error("load users error", "error", err) logger.Error("load users error", "error", err)
time.Sleep(60 * time.Second) time.Sleep(60 * time.Second)
@ -24,9 +23,10 @@ func Init() {
} }
var users []*models.User var users []*models.User
usersMap := make(map[string]*models.User)
for rows.Next() { for rows.Next() {
user := &models.User{} user := &models.User{}
err := rows.Scan(&user.ID, &user.Username, &user.Role, &user.Nickname, &user.Email, &user.Avatar, &user.LastSeenAt, &user.Created) err := rows.Scan(&user.ID, &user.Username, &user.Role, &user.Nickname, &user.Avatar, &user.LastSeenAt, &user.Created)
if err != nil { if err != nil {
logger.Warn("scan user error", "error", err) logger.Warn("scan user error", "error", err)
continue continue
@ -43,9 +43,11 @@ func Init() {
user.Follows = interaction.GetFollows(user.ID) user.Follows = interaction.GetFollows(user.ID)
users = append(users, user) users = append(users, user)
usersMap[user.ID] = user
} }
Users = users models.UsersCache = users
models.UsersMapCache = usersMap
time.Sleep(60 * time.Second) time.Sleep(60 * time.Second)
} }

@ -93,6 +93,16 @@ func GetFollows(targetID string) int {
return follows return follows
} }
func GetFollowings(userID string, targetType string) int {
var followings int
err := db.Conn.QueryRow("SELECT count(*) FROM follows WHERE user_id=? and target_type=?", userID, targetType).Scan(&followings)
if err != nil && err != sql.ErrNoRows {
logger.Warn("get following count error", "error", err)
}
return followings
}
func GetFollowing(userID, targetType string) ([]*models.Following, *e.Error) { func GetFollowing(userID, targetType string) ([]*models.Following, *e.Error) {
rows, err := db.Conn.Query("SELECT target_id,weight from follows where user_id=? and target_type=?", userID, targetType) rows, err := db.Conn.Query("SELECT target_id,weight from follows where user_id=? and target_type=?", userID, targetType)
if err != nil { if err != nil {
@ -111,6 +121,28 @@ func GetFollowing(userID, targetType string) ([]*models.Following, *e.Error) {
return following, nil return following, nil
} }
func GetFollowers(targetID, targetType string) ([]*models.User, *e.Error) {
rows, err := db.Conn.Query("SELECT user_id from follows where target_id=? and target_type=? ORDER BY created DESC", targetID, targetType)
if err != nil {
logger.Warn("get followers error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
users := make([]*models.User, 0)
for rows.Next() {
var id string
rows.Scan(&id)
u, ok := models.UsersMapCache[id]
if ok {
users = append(users, u)
u.Followed = GetFollowed(u.ID, targetID)
}
}
return users, nil
}
func SetFolloingWeight(userID string, f *models.Following) *e.Error { func SetFolloingWeight(userID string, f *models.Following) *e.Error {
_, err := db.Conn.Exec("UPDATE follows SET weight=? WHERE user_id=? and target_id=?", f.Weight, userID, f.ID) _, err := db.Conn.Exec("UPDATE follows SET weight=? WHERE user_id=? and target_id=?", f.Weight, userID, f.ID)
if err != nil { if err != nil {

@ -4,7 +4,6 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/imdotdev/im.dev/server/internal/cache"
"github.com/imdotdev/im.dev/server/internal/interaction" "github.com/imdotdev/im.dev/server/internal/interaction"
"github.com/imdotdev/im.dev/server/internal/story" "github.com/imdotdev/im.dev/server/internal/story"
"github.com/imdotdev/im.dev/server/pkg/db" "github.com/imdotdev/im.dev/server/pkg/db"
@ -39,10 +38,8 @@ func Posts(user *models.User, filter, query string) []*models.Story {
} }
func Users(user *models.User, filter, query string) []*models.User { func Users(user *models.User, filter, query string) []*models.User {
allUsers := cache.Users
users := make(models.Users, 0) users := make(models.Users, 0)
for _, u := range allUsers { for _, u := range models.UsersCache {
if strings.Contains(strings.ToLower(u.Nickname), strings.ToLower(query)) { if strings.Contains(strings.ToLower(u.Nickname), strings.ToLower(query)) {
users = append(users, u) users = append(users, u)
continue continue

@ -75,6 +75,7 @@ func (s *Server) Start() error {
r.GET("/tag/user/:userID", api.GetUserTags) r.GET("/tag/user/:userID", api.GetUserTags)
// user apis // user apis
r.GET("/user/all", api.GetUsers) r.GET("/user/all", api.GetUsers)
r.POST("/user/ids", api.GetUsersByIDs)
r.GET("/user/self", IsLogin(), api.GetUserSelf) r.GET("/user/self", IsLogin(), api.GetUserSelf)
r.GET("/user/info/:username", api.GetUser) r.GET("/user/info/:username", api.GetUser)
r.POST("/user/update", IsLogin(), api.UpdateUser) r.POST("/user/update", IsLogin(), api.UpdateUser)
@ -89,6 +90,7 @@ func (s *Server) Start() error {
r.POST("/interaction/following/weight", IsLogin(), api.SetFollowingWeight) r.POST("/interaction/following/weight", IsLogin(), api.SetFollowingWeight)
r.GET("/interaction/followed/:id", api.Followed) r.GET("/interaction/followed/:id", api.Followed)
r.GET("/interaction/following/:userID", api.GetFollowing) r.GET("/interaction/following/:userID", api.GetFollowing)
r.GET("/interaction/followers/:userID", api.GetFollowers)
// search apis // search apis
r.GET("/search/posts/:filter", api.SearchPosts) r.GET("/search/posts/:filter", api.SearchPosts)

@ -6,7 +6,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/imdotdev/im.dev/server/internal/cache"
"github.com/imdotdev/im.dev/server/internal/interaction" "github.com/imdotdev/im.dev/server/internal/interaction"
"github.com/imdotdev/im.dev/server/internal/tags" "github.com/imdotdev/im.dev/server/internal/tags"
"github.com/imdotdev/im.dev/server/pkg/db" "github.com/imdotdev/im.dev/server/pkg/db"
@ -15,10 +14,8 @@ import (
) )
func GetUsers(q string) ([]*models.User, *e.Error) { func GetUsers(q string) ([]*models.User, *e.Error) {
allUsers := cache.Users
users := make([]*models.User, 0) users := make([]*models.User, 0)
for _, u := range allUsers { for _, u := range models.UsersCache {
if strings.HasPrefix(strings.ToLower(u.Nickname), strings.ToLower(q)) { if strings.HasPrefix(strings.ToLower(u.Nickname), strings.ToLower(q)) {
users = append(users, u) users = append(users, u)
continue continue
@ -33,6 +30,19 @@ func GetUsers(q string) ([]*models.User, *e.Error) {
return users, nil return users, nil
} }
func GetUsersByIDs(ids []string) []*models.User {
users := make([]*models.User, 0)
for _, id := range ids {
u, ok := models.UsersMapCache[id]
if ok {
users = append(users, u)
}
u.Followed = true
}
return users
}
func GetUserDetail(id string, username string) (*models.User, *e.Error) { func GetUserDetail(id string, username string) (*models.User, *e.Error) {
user := &models.User{} user := &models.User{}
err := user.Query(id, username, "") err := user.Query(id, username, "")
@ -65,6 +75,8 @@ func GetUserDetail(id string, username string) (*models.User, *e.Error) {
user.Follows = interaction.GetFollows(user.ID) user.Follows = interaction.GetFollows(user.ID)
user.Followings = interaction.GetFollowings(user.ID, models.IDTypeUser)
return user, nil return user, nil
} }

@ -4,7 +4,7 @@ package common
var ReserverURLs = []string{ var ReserverURLs = []string{
"/tags", "/tags",
"/courses", "/courses",
"/follow", "/interaction",
"/editor", "/editor",
"/admin", "/admin",
"/bookmarks", "/bookmarks",

@ -0,0 +1,4 @@
package models
var UsersCache []*User
var UsersMapCache map[string]*User

@ -11,27 +11,28 @@ type User struct {
Username string `json:"username"` Username string `json:"username"`
Nickname string `json:"nickname"` Nickname string `json:"nickname"`
Avatar string `json:"avatar"` Avatar string `json:"avatar"`
Email string `json:"email"` Email string `json:"email,omitempty"`
Role RoleType `json:"role"` Role RoleType `json:"role,omitempty"`
Tagline string `json:"tagline"` Tagline string `json:"tagline,omitempty"`
Cover string `json:"cover"` Cover string `json:"cover,omitempty"`
Location string `json:"location"` Location string `json:"location,omitempty"`
AvailFor string `json:"availFor"` AvailFor string `json:"availFor,omitempty"`
About string `json:"about"` About string `json:"about,omitempty"`
RawSkills []*Tag `json:"rawSkills"` RawSkills []*Tag `json:"rawSkills,omitempty"`
Skills []string `json:"skills"` Skills []string `json:"skills,omitempty"`
Website string `json:"website"` Website string `json:"website,omitempty"`
Twitter string `json:"twitter"` Twitter string `json:"twitter,omitempty"`
Github string `json:"github"` Github string `json:"github,omitempty"`
Zhihu string `json:"zhihu"` Zhihu string `json:"zhihu,omitempty"`
Weibo string `json:"weibo"` Weibo string `json:"weibo,omitempty"`
Facebook string `json:"facebook"` Facebook string `json:"facebook,omitempty"`
Stackoverflow string `json:"stackoverflow"` Stackoverflow string `json:"stackoverflow,omitempty"`
Follows int `json:"follows"` Follows int `json:"follows,omitempty"`
Followed bool `json:"followed"` Followings int `json:"followings,omitempty"`
Followed bool `json:"followed,omitempty"`
LastSeenAt time.Time `json:"lastSeenAt,omitempty"` LastSeenAt time.Time `json:"lastSeenAt,omitempty"`
Created time.Time `json:"created"` Created time.Time `json:"created"`

@ -14,6 +14,7 @@ export const Card = ({shadowed, ...rest}: BoxProps&Props) => {
borderWidth="1px" borderWidth="1px"
p={[2,2,4,4]} p={[2,2,4,4]}
boxShadow={shadowed? "0 1px 1px 0 rgb(0 0 0 / 5%)" : null} boxShadow={shadowed? "0 1px 1px 0 rgb(0 0 0 / 5%)" : null}
height="fit-content"
{...rest} {...rest}
/> />
) )

@ -57,7 +57,7 @@ export const UserMenu = () => {
<MenuDivider /> <MenuDivider />
{isEditor(session.user.role) && <Link href={`${ReserveUrls.Editor}/posts`}><MenuItem icon={<FaEdit fontSize="16" />} ></MenuItem></Link>} {isEditor(session.user.role) && <Link href={`${ReserveUrls.Editor}/posts`}><MenuItem icon={<FaEdit fontSize="16" />} ></MenuItem></Link>}
<Link href={`${ReserveUrls.Bookmarks}`}><MenuItem icon={<FaBookmark fontSize="16" />}></MenuItem></Link> <Link href={`${ReserveUrls.Bookmarks}`}><MenuItem icon={<FaBookmark fontSize="16" />}></MenuItem></Link>
<Link href={`${ReserveUrls.Follow}/tags`}><MenuItem icon={<FaHeart fontSize="16" />}></MenuItem></Link> <Link href={`${ReserveUrls.Interaction}/following-tags`}><MenuItem icon={<FaHeart fontSize="16" />}></MenuItem></Link>
{isAdmin(session.user.role) && <Link href={`${ReserveUrls.Admin}/tags`}><MenuItem icon={<FaStar fontSize="16" />} ></MenuItem></Link>} {isAdmin(session.user.role) && <Link href={`${ReserveUrls.Admin}/tags`}><MenuItem icon={<FaStar fontSize="16" />} ></MenuItem></Link>}
<MenuDivider /> <MenuDivider />
<Link href={`${ReserveUrls.Settings}/profile`}><MenuItem icon={<FaRegSun fontSize="16" />}></MenuItem></Link> <Link href={`${ReserveUrls.Settings}/profile`}><MenuItem icon={<FaRegSun fontSize="16" />}></MenuItem></Link>

@ -7,6 +7,7 @@ import { User } from "src/types/user"
import { getUserName } from "utils/user" import { getUserName } from "utils/user"
import Follow from "components/interaction/follow" import Follow from "components/interaction/follow"
import Highlighter from 'react-highlight-words'; import Highlighter from 'react-highlight-words';
import Count from "components/count"
type Props = PropsOf<typeof chakra.div> & { type Props = PropsOf<typeof chakra.div> & {
user : User user : User
@ -45,7 +46,7 @@ export const UserCard= ({user,highlight}:Props) =>{
</VStack> </VStack>
</HStack> </HStack>
<HStack> <HStack>
<Text fontWeight="600" fontSize=".9rem">{user.follows} followers</Text> <Text fontWeight="600" fontSize=".9rem"><Count count={user.follows??0}/> followers</Text>
<Follow followed={user.followed} targetID={user.id} size="sm"/> <Follow followed={user.followed} targetID={user.id} size="sm"/>
</HStack> </HStack>

@ -24,20 +24,20 @@ export const editorLinks: Route[] = [{
} }
] ]
export const followLinks: any[] = [ export const interactionLinks: any[] = [
{ {
title: 'Following tags', title: 'Following tags',
path: `${ReserveUrls.Follow}/tags`, path: `${ReserveUrls.Interaction}/following-tags`,
disabled: false disabled: false
}, },
{ {
title: 'Following users', title: 'Following users',
path: `${ReserveUrls.Follow}/users`, path: `${ReserveUrls.Interaction}/following-users`,
disabled: false disabled: false
}, },
{ {
title: 'Followers', title: 'Followers',
path: `${ReserveUrls.Follow}/followers`, path: `${ReserveUrls.Interaction}/followers`,
disabled: false disabled: false
}, },
] ]

@ -2,7 +2,7 @@
export enum ReserveUrls { export enum ReserveUrls {
Tags = "/tags", Tags = "/tags",
Courses = "/courses", Courses = "/courses",
Follow = "/follow", Interaction = "/interaction",
Editor = "/editor", Editor = "/editor",
Admin = "/admin", Admin = "/admin",
Bookmarks = "/bookmarks", Bookmarks = "/bookmarks",

@ -33,6 +33,7 @@ export interface User {
stackoverflow?: string stackoverflow?: string
follows?: number follows?: number
followings?: number
followed?: boolean followed?: boolean
lastSeenAt?: string lastSeenAt?: string

Loading…
Cancel
Save