commit
1821988184
6 changed files with 161 additions and 17 deletions
|
|
@ -6,6 +6,8 @@ const isFinite = require('lodash/isFinite')
|
||||||
const shuffle = require('lodash/shuffle')
|
const shuffle = require('lodash/shuffle')
|
||||||
const R = require('ramda')
|
const R = require('ramda')
|
||||||
|
|
||||||
|
const { asyncFilter } = require('../../../../utils')
|
||||||
|
|
||||||
const Follows = require('./follows')
|
const Follows = require('./follows')
|
||||||
const Wall = require('./wall')
|
const Wall = require('./wall')
|
||||||
|
|
||||||
|
|
@ -14,7 +16,7 @@ const Wall = require('./wall')
|
||||||
* @param {number} pageRequested
|
* @param {number} pageRequested
|
||||||
* @returns {[ number , number ]}
|
* @returns {[ number , number ]}
|
||||||
*/
|
*/
|
||||||
const calculateFeedPage = (numberOfPublicKeyGroups, pageRequested) => {
|
const calculateWallRequest = (numberOfPublicKeyGroups, pageRequested) => {
|
||||||
// thanks to sebassdc
|
// thanks to sebassdc
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
@ -56,12 +58,18 @@ const getFeedPage = async page => {
|
||||||
|
|
||||||
const pagedPublicKeys = R.splitEvery(10, shuffle(subbedPublicKeys))
|
const pagedPublicKeys = R.splitEvery(10, shuffle(subbedPublicKeys))
|
||||||
|
|
||||||
const [publicKeyGroupIdx, pageToRequest] = calculateFeedPage(
|
const [publicKeyGroupIdx, pageToRequest] = calculateWallRequest(
|
||||||
pagedPublicKeys.length,
|
pagedPublicKeys.length,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
|
|
||||||
const publicKeys = pagedPublicKeys[publicKeyGroupIdx]
|
const publicKeysRaw = pagedPublicKeys[publicKeyGroupIdx]
|
||||||
|
const publicKeys = await asyncFilter(
|
||||||
|
publicKeysRaw,
|
||||||
|
// reject public keys for which the page to request would result in an out
|
||||||
|
// of bounds error
|
||||||
|
async pk => pageToRequest <= (await Wall.getWallTotalPages(pk))
|
||||||
|
)
|
||||||
|
|
||||||
const fetchedPages = await Promise.all(
|
const fetchedPages = await Promise.all(
|
||||||
publicKeys.map(pk => Wall.getWallPage(pageToRequest, pk))
|
publicKeys.map(pk => Wall.getWallPage(pageToRequest, pk))
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,11 @@ exports.currentFollows = async () => {
|
||||||
* @type {Record<string, Common.Schema.Follow>}
|
* @type {Record<string, Common.Schema.Follow>}
|
||||||
*/
|
*/
|
||||||
const raw = await Utils.tryAndWait(
|
const raw = await Utils.tryAndWait(
|
||||||
// @ts-ignore
|
(_, user) =>
|
||||||
(_, user) => new Promise(res => user.get(Key.FOLLOWS).load(res)),
|
new Promise(res =>
|
||||||
|
// @ts-expect-error
|
||||||
|
user.get(Key.FOLLOWS).load(res)
|
||||||
|
),
|
||||||
v => {
|
v => {
|
||||||
if (typeof v !== 'object' || v === null) {
|
if (typeof v !== 'object' || v === null) {
|
||||||
return true
|
return true
|
||||||
|
|
@ -33,7 +36,10 @@ exports.currentFollows = async () => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
// sometimes it returns empty sub objects
|
||||||
|
return Object.values(v)
|
||||||
|
.filter(Common.Schema.isObj)
|
||||||
|
.some(obj => size(obj) === 0)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const Common = require('shock-common')
|
||||||
const isARealUsableNumber = require('lodash/isFinite')
|
const isARealUsableNumber = require('lodash/isFinite')
|
||||||
const Big = require('big.js')
|
const Big = require('big.js')
|
||||||
const size = require('lodash/size')
|
const size = require('lodash/size')
|
||||||
|
const { range, flatten } = require('ramda')
|
||||||
|
|
||||||
const getListPage = require('../utils/paginate')
|
const getListPage = require('../utils/paginate')
|
||||||
const auth = require('../services/auth/auth')
|
const auth = require('../services/auth/auth')
|
||||||
|
|
@ -2055,7 +2056,20 @@ module.exports = async (
|
||||||
*/
|
*/
|
||||||
const apiGunFeedGet = async (req, res) => {
|
const apiGunFeedGet = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const MAX_PAGES_TO_FETCH_FOR_TRY_UNTIL = 4
|
||||||
|
|
||||||
const { page: pageStr } = req.query
|
const { page: pageStr } = req.query
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to a "before" query param in cursor based pagination. We call
|
||||||
|
* it "try" because it is likely that this item lies beyond
|
||||||
|
* MAX_PAGES_TO_FETCH_FOR_TRY_UNTIL in which case we gracefully just send
|
||||||
|
* 2 pages and 205 response.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
const try_until = req.query.try_until
|
||||||
|
|
||||||
|
if (pageStr) {
|
||||||
const page = Number(pageStr)
|
const page = Number(pageStr)
|
||||||
|
|
||||||
if (!isARealUsableNumber(page)) {
|
if (!isARealUsableNumber(page)) {
|
||||||
|
|
@ -2075,6 +2089,40 @@ module.exports = async (
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
posts: await GunGetters.getFeedPage(page)
|
posts: await GunGetters.getFeedPage(page)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try_until) {
|
||||||
|
const pages = range(1, MAX_PAGES_TO_FETCH_FOR_TRY_UNTIL)
|
||||||
|
const promises = pages.map(p => GunGetters.getFeedPage(p))
|
||||||
|
|
||||||
|
let results = await Promise.all(promises)
|
||||||
|
|
||||||
|
const idxIfFound = results.findIndex(pp =>
|
||||||
|
pp.some(p => p.id === try_until)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (idxIfFound > -1) {
|
||||||
|
results = results.slice(0, idxIfFound + 1)
|
||||||
|
|
||||||
|
const posts = flatten(results)
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
posts
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// we couldn't find the posts leading up to the requested post
|
||||||
|
// (try_until) Let's just return the ones we found with together with a
|
||||||
|
// 205 code (client should refresh UI)
|
||||||
|
|
||||||
|
return res.status(205).json({
|
||||||
|
posts: results[0] || []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(400).json({
|
||||||
|
errorMessage: `Must provide at least a page or a try_until query param.`
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
errorMessage: err.message || 'Unknown error inside /api/gun/follows/'
|
errorMessage: err.message || 'Unknown error inside /api/gun/follows/'
|
||||||
|
|
|
||||||
24
utils/helpers.js
Normal file
24
utils/helpers.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
/**
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {(value: T) => Promise<boolean>} AsyncFilterCallback
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T[]} arr
|
||||||
|
* @param {AsyncFilterCallback<T>} cb
|
||||||
|
* @returns {Promise<T[]>}
|
||||||
|
*/
|
||||||
|
const asyncFilter = async (arr, cb) => {
|
||||||
|
const results = await Promise.all(arr.map(item => cb(item)))
|
||||||
|
|
||||||
|
return arr.filter((_, i) => results[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
asyncFilter
|
||||||
|
}
|
||||||
49
utils/helpers.spec.js
Normal file
49
utils/helpers.spec.js
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
/**
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { asyncFilter } = require('./helpers')
|
||||||
|
|
||||||
|
const numbers = [1, 2, 3, 4]
|
||||||
|
const odds = [1, 3]
|
||||||
|
const evens = [2, 4]
|
||||||
|
|
||||||
|
describe('asyncFilter()', () => {
|
||||||
|
it('returns an empty array when given one', async () => {
|
||||||
|
expect.hasAssertions()
|
||||||
|
const result = await asyncFilter([], () => true)
|
||||||
|
|
||||||
|
expect(result).toStrictEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects', async () => {
|
||||||
|
expect.hasAssertions()
|
||||||
|
|
||||||
|
const result = await asyncFilter(numbers, () => false)
|
||||||
|
|
||||||
|
expect(result).toStrictEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects via calling with the correct value', async () => {
|
||||||
|
expect.hasAssertions()
|
||||||
|
|
||||||
|
const result = await asyncFilter(numbers, v => v % 2 !== 0)
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(odds)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('filters via calling with the correct value', async () => {
|
||||||
|
expect.hasAssertions()
|
||||||
|
|
||||||
|
const result = await asyncFilter(numbers, v => v % 2 === 0)
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(evens)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles promises', async () => {
|
||||||
|
expect.hasAssertions()
|
||||||
|
|
||||||
|
const result = await asyncFilter(numbers, v => Promise.resolve(v % 2 === 0))
|
||||||
|
expect(result).toStrictEqual(evens)
|
||||||
|
})
|
||||||
|
})
|
||||||
9
utils/index.js
Normal file
9
utils/index.js
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { asyncFilter } = require('./helpers')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
asyncFilter
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue