Skip to content

Commit

Permalink
implement followers and following getters logic and testing in api b0…
Browse files Browse the repository at this point in the history
  • Loading branch information
Eden23 committed Aug 20, 2024
1 parent d05ee80 commit f76a8f7
Show file tree
Hide file tree
Showing 15 changed files with 338 additions and 10 deletions.
2 changes: 1 addition & 1 deletion staff/marti-herms/project/V-HUB/app/src/home/Footer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function Footer({ makeReviewVisibility, onSearchGame, onAddGame,
{role === 'dev' && <NavigationButton onClick={onAddGame}>Add Game</NavigationButton>}
<NavigationButton onClick={onSearchGame}>Search</NavigationButton>
</>} />
<Route path='/profile/:userId' element={<NavigationButton onClick={onHome}>HOME</NavigationButton>} />
<Route path='/profile/:userId/*' element={<NavigationButton onClick={onHome}>HOME</NavigationButton>} />
<Route path='/games/register' element={<NavigationButton onClick={onHome}>HOME</NavigationButton>} />
<Route path='/search' element={<NavigationButton onClick={onHome}>HOME</NavigationButton>} />
<Route path='/games/:gameId' element={<>
Expand Down
10 changes: 6 additions & 4 deletions staff/marti-herms/project/V-HUB/app/src/home/Library.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import logic from '../../logic'

import useContext from '../context.js'
import GameBanner from './GameBanner.jsx'
import Button from '../library/Button.jsx'

import extractPayloadFromToken from '../../util/extractPayloadFromToken.js'

export default function Library({ onGameClick, user }) {
const { alert } = useContext()

const { userId, role } = extractPayloadFromToken(sessionStorage.token)
const { sub: userId, role } = extractPayloadFromToken(sessionStorage.token)

const [games, setGames] = useState([])
const [libraryVisibility, setLibraryVisibility] = useState(false)
Expand Down Expand Up @@ -90,12 +92,12 @@ export default function Library({ onGameClick, user }) {

return <div>
{((!user && role) || user.role === 'dev') && <>
<button className='w-full h-[45px] bg-black text-white border border-solid border-slate-700' onClick={handleDevGames}>Games</button>
<Button className='w-full h-[45px] bg-black text-white border border-solid border-slate-700' onClick={handleDevGames}>Games</Button>
{devGamesVisibility && games.map(game => <GameBanner key={game.id} game={game} onInteraction={devGames} onGameClick={onGameClick} collectionType={'devGames'} />)}
</>}
<button className='w-full h-[45px] bg-black text-white border border-solid border-slate-700' onClick={handleLibrary}>Library</button>
<Button className='w-full h-[45px] bg-black text-white border border-solid border-slate-700' onClick={handleLibrary}>Library</Button>
{libraryVisibility && games.map(game => <GameBanner key={game.id} game={game} onInteraction={libraryGames} onGameClick={onGameClick} collectionType={'library'} />)}
<button className='w-full h-[45px] bg-black text-white border border-solid border-slate-700' onClick={handleFavs}>Favs</button>
<Button className='w-full h-[45px] bg-black text-white border border-solid border-slate-700' onClick={handleFavs}>Favs</Button>
{favsVisibility && games.map(game => <GameBanner key={game.id} game={game} onInteraction={favsGames} onGameClick={onGameClick} collectionType={'favs'} />)}
</div>
}
17 changes: 16 additions & 1 deletion staff/marti-herms/project/V-HUB/app/src/home/Profile.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
import { useParams } from 'react-router-dom'
import { useParams, useNavigate, useLocation } from 'react-router-dom'

import logic from '../../logic'

Expand All @@ -20,6 +20,9 @@ import defaultAvatar from '../../images/defaultAvatar.svg'
export default function Profile({ refreshStamp, onChange, onGameClick }) {
const { alert } = useContext()

const navigate = useNavigate()
const location = useLocation()

const { userId } = useParams()

const { sub: loggedInUserId } = extractPayloadFromToken(sessionStorage.token)
Expand Down Expand Up @@ -119,6 +122,14 @@ export default function Profile({ refreshStamp, onChange, onGameClick }) {
setEditAvatarVisibility(false)
}

const handleFollowers = () => {
navigate(location.pathname + '/followers')
}

const handleFollowing = () => {
navigate(location.pathname + '/following')
}

return <>
{user && <Container className='flex flex-row justify-center items-center my-4'>
<Avatar url={user.avatar || defaultAvatar} className='w-20 h-20' />
Expand Down Expand Up @@ -153,5 +164,9 @@ export default function Profile({ refreshStamp, onChange, onGameClick }) {
{user && <Container>
<Library onGameClick={onGameClick} user={user} />
</Container>}
{user && <Container>
<Button className='bg-white w-9/12 h-10 px-3 py-1 text-2xl text-black rounded-md border border-solid border-black shadow-md shadow-black' onClick={handleFollowing}>Following</Button>
<Button className='bg-white w-9/12 h-10 px-3 py-1 text-2xl text-black rounded-md border border-solid border-black shadow-md shadow-black' onClick={handleFollowers}>Followers</Button>
</Container>}
</>
}
15 changes: 15 additions & 0 deletions staff/marti-herms/project/V-HUB/app/src/home/UserList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useParams } from 'react-router-dom'

import useContext from '../context'

import Container from '../library/Container'

export default function UserList({ userList }) {
const { alert } = useContext()

const { userId } = useParams()

return <Container>

</Container>
}
2 changes: 2 additions & 0 deletions staff/marti-herms/project/V-HUB/app/src/home/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export default function Home({ onLogout }) {
<Routes>
<Route path='/' element={<Library onGameClick={handleGame} />} />
<Route path='/profile/:userId' element={<Profile refreshStamp={refreshStamp} onChange={handleSearchUser} onGameClick={handleGame} />} />
<Route path='/profile/:userId/following' element={<p>Hello</p>} />
<Route path='/profile/:userId/followers' element={<p>Hello</p>} />
<Route path='/games/register' element={<AddGame onAddGame={handleAddGame} />} />
<Route path='/search' element={<><Search onChange={handleRefresh} /> <SearchResults refreshStamp={refreshStamp} onGameClick={handleGame} onUserClick={handleSearchUser} /></>} />
<Route path='/games/:gameId' element={<Game makeReviewVisibility={makeReviewVisibility} onCancel={handleCancelReview} />} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export default function Button({ children, ...nextProps }) {
return <button className={'border border-solid border-slate-500 bg-white px-2 rounded active:bg-slate-500'} {...nextProps}>{children}</button>
export default function NavigationButton({ children, ...nextProps }) {
return <button className={'border border-solid border-slate-500 bg-white px-2 rounded active:bg-slate-500 w-[95px]'} {...nextProps}>{children}</button>
}
4 changes: 4 additions & 0 deletions staff/marti-herms/project/V-HUB/core/data/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const user = new Schema({
following: {
type: [ObjectId],
ref: 'User'
},
followers: {
type: [ObjectId],
ref: 'User'
}
})

Expand Down
39 changes: 39 additions & 0 deletions staff/marti-herms/project/V-HUB/core/logic/getUserFollowers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { User } from '../data/models.js'

import { validate, errors } from 'com'

const { NotFoundError, SystemError } = errors

export default (userId, targetUserId) => {
validate.string(userId, 'userId')
validate.string(targetUserId, 'targetUserId')

return User.findById(userId).lean()
.catch(error => { throw new SystemError(error.message) })
.then(user => {
if (!user)
throw new NotFoundError('user not found')

return User.findById(targetUserId).lean()
.catch(error => { throw new SystemError(error.message) })
.then(user => {
if (!user) throw new NotFoundError('targetUser not found')

return User.find({ _id: { $in: user.followers } })
.catch(error => { throw new SystemError(error.message) })
.then(users => {
const promises = users.map(user => {
user.id = user._id.toString()

delete user._id

delete user.password

return user
})

return Promise.all(promises)
})
})
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import 'dotenv/config'
import getUserFollowers from './getUserFollowers.js'
import toggleFollowUser from './toggleFollowUser.js'
import mongoose from 'mongoose'

import { expect } from 'chai'
import { User } from '../data/models.js'

import { errors } from 'com'

const { NotFoundError, ValidationError } = errors

describe('getUserFollowers', () => {
before(() => mongoose.connect(process.env.MONGODB_URI))

beforeEach(() => User.deleteMany())

it('succeeds on existing user returning all followers', () => {
return User.create({ username: 'monoloco', email: '[email protected]', password: '123123123' })
.then(user =>
User.create({ username: 'eden', email: '[email protected]', password: '123123123' })
.then(_user =>
toggleFollowUser(user.id, _user.id)
.then(() => getUserFollowers(_user.id, _user.id))
.then(users => {
expect(users).to.be.an('array')
expect(users[0].id).to.equal(user.id)
})
)
)
})

it('succeeds on existing user and returning empty array ', () => {
return User.create({ username: 'monoloco', email: '[email protected]', password: '123123123' })
.then(user =>
getUserFollowers(user.id, user.id)
.then(users => {
expect(users).to.be.an('array')
expect(users.length).to.equal(0)
})
)
})

it('fails on non-existing user', () => {
let _error

return getUserFollowers('66ba007f874aa7b84ec54491', '66ba007f874aa7b84ec54491')
.catch(error => _error = error)
.finally(() => {
expect(_error).to.be.instanceOf(NotFoundError)
expect(_error.message).to.equal('user not found')
})
})

it('fails on non-existing targetUser', () => {
let _error

return User.create({ name: 'Mono', surname: 'Loco', email: '[email protected]', username: 'monoloco', password: '123123123' })
.then(user => getUserFollowers(user.id, '66ba007f874aa7b84ec54491'))
.catch(error => _error = error)
.finally(() => {
expect(_error).to.be.instanceOf(NotFoundError)
expect(_error.message).to.equal('targetUser not found')
})
})

it('fails on non-string userId', () => {
let error

try {
getUserFollowers(123, '66ba007f874aa7b84ec54491')
} catch (_error) {
error = _error
} finally {
expect(error).to.be.instanceOf(ValidationError)
expect(error.message).to.equal('userId is not a string')
}
})

it('fails on non-string targetUserId', () => {
let error

try {
getUserFollowers('66ba007f874aa7b84ec54491', 123)
} catch (_error) {
error = _error
} finally {
expect(error).to.be.instanceOf(ValidationError)
expect(error.message).to.equal('targetUserId is not a string')
}
})

afterEach(() => User.deleteMany())

after(() => mongoose.disconnect())
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'dotenv/config'
import mongoose from 'mongoose'

import getUserFollowers from './getUserFollowers.js'

mongoose.connect(process.env.MONGODB_URI)
.then(() => getUserFollowers('66acb2b1730b0f09da259589', '66acb2b1730b0f09da259589'))
.then(games => console.log(games))
.catch(error => console.error(error))
.finally(() => mongoose.disconnect())
39 changes: 39 additions & 0 deletions staff/marti-herms/project/V-HUB/core/logic/getUserFollowing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { User } from '../data/models.js'

import { validate, errors } from 'com'

const { NotFoundError, SystemError } = errors

export default (userId, targetUserId) => {
validate.string(userId, 'userId')
validate.string(targetUserId, 'targetUserId')

return User.findById(userId).lean()
.catch(error => { throw new SystemError(error.message) })
.then(user => {
if (!user)
throw new NotFoundError('user not found')

return User.findById(targetUserId).lean()
.catch(error => { throw new SystemError(error.message) })
.then(user => {
if (!user) throw new NotFoundError('targetUser not found')

return User.find({ _id: { $in: user.following } })
.catch(error => { throw new SystemError(error.message) })
.then(users => {
const promises = users.map(user => {
user.id = user._id.toString()

delete user._id

delete user.password

return user
})

return Promise.all(promises)
})
})
})
}
Loading

0 comments on commit f76a8f7

Please sign in to comment.