commit
9c8d36e380
8 changed files with 480 additions and 3 deletions
|
|
@ -7,6 +7,8 @@ const logger = require('winston')
|
||||||
Gun.log = () => {}
|
Gun.log = () => {}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
require('gun/lib/open')
|
require('gun/lib/open')
|
||||||
|
// @ts-ignore
|
||||||
|
require('gun/lib/load')
|
||||||
const debounce = require('lodash/debounce')
|
const debounce = require('lodash/debounce')
|
||||||
const Encryption = require('../../../utils/encryptionStore')
|
const Encryption = require('../../../utils/encryptionStore')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ export interface GUNNodeBase {
|
||||||
once(this: GUNNode, cb?: Listener): GUNNode
|
once(this: GUNNode, cb?: Listener): GUNNode
|
||||||
|
|
||||||
open(this: GUNNode, cb?: OpenListener): GUNNode
|
open(this: GUNNode, cb?: OpenListener): GUNNode
|
||||||
|
load(this: GUNNode, cb?: OpenListener): GUNNode
|
||||||
|
|
||||||
load(this: GUNNode, cb?: LoadListener): GUNNode
|
load(this: GUNNode, cb?: LoadListener): GUNNode
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
*/
|
*/
|
||||||
const uuidv1 = require('uuid/v1')
|
const uuidv1 = require('uuid/v1')
|
||||||
const logger = require('winston')
|
const logger = require('winston')
|
||||||
const { Constants, Schema } = require('shock-common')
|
const Common = require('shock-common')
|
||||||
|
const { Constants, Schema } = Common
|
||||||
|
|
||||||
const { ErrorCode } = Constants
|
const { ErrorCode } = Constants
|
||||||
|
|
||||||
|
|
@ -1133,7 +1134,21 @@ const setBio = (bio, user) =>
|
||||||
resolve()
|
resolve()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}).then(
|
||||||
|
() =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
user
|
||||||
|
.get(Key.PROFILE)
|
||||||
|
.get(Key.BIO)
|
||||||
|
.put(bio, ack => {
|
||||||
|
if (ack.err) {
|
||||||
|
reject(new Error(ack.err))
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string[]} mnemonicPhrase
|
* @param {string[]} mnemonicPhrase
|
||||||
|
|
@ -1216,7 +1231,228 @@ const setLastSeenApp = () =>
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}).then(
|
||||||
|
() =>
|
||||||
|
new Promise((res, rej) => {
|
||||||
|
require('../Mediator')
|
||||||
|
.getUser()
|
||||||
|
.get(Key.PROFILE)
|
||||||
|
.get(Key.LAST_SEEN_APP)
|
||||||
|
.put(Date.now(), ack => {
|
||||||
|
if (ack.err) {
|
||||||
|
rej(new Error(ack.err))
|
||||||
|
} else {
|
||||||
|
res()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} tags
|
||||||
|
* @param {string} title
|
||||||
|
* @param {Common.Schema.ContentItem[]} content
|
||||||
|
* @returns {Promise<Common.Schema.Post>}
|
||||||
|
*/
|
||||||
|
const createPost = async (tags, title, content) => {
|
||||||
|
if (content.length === 0) {
|
||||||
|
throw new Error(`A post must contain at least one paragraph/image/video`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const numOfPages = await (async () => {
|
||||||
|
const maybeNumOfPages = await Utils.tryAndWait(
|
||||||
|
(_, user) =>
|
||||||
|
user
|
||||||
|
.get(Key.WALL)
|
||||||
|
.get(Key.NUM_OF_PAGES)
|
||||||
|
.then(),
|
||||||
|
v => typeof v !== 'number'
|
||||||
|
)
|
||||||
|
|
||||||
|
return typeof maybeNumOfPages === 'number' ? maybeNumOfPages : 0
|
||||||
|
})()
|
||||||
|
|
||||||
|
let pageIdx = Math.max(0, numOfPages - 1).toString()
|
||||||
|
|
||||||
|
const count = await (async () => {
|
||||||
|
if (numOfPages === 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const maybeCount = await Utils.tryAndWait(
|
||||||
|
(_, user) =>
|
||||||
|
user
|
||||||
|
.get(Key.WALL)
|
||||||
|
.get(Key.PAGES)
|
||||||
|
.get(pageIdx)
|
||||||
|
.get(Key.COUNT)
|
||||||
|
.then(),
|
||||||
|
v => typeof v !== 'number'
|
||||||
|
)
|
||||||
|
|
||||||
|
return typeof maybeCount === 'number' ? maybeCount : 0
|
||||||
|
})()
|
||||||
|
|
||||||
|
const shouldBeNewPage =
|
||||||
|
count >= Common.Constants.Misc.NUM_OF_POSTS_PER_WALL_PAGE
|
||||||
|
|
||||||
|
if (shouldBeNewPage) {
|
||||||
|
pageIdx = Number(pageIdx + 1).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((res, rej) => {
|
||||||
|
require('../Mediator')
|
||||||
|
.getUser()
|
||||||
|
.get(Key.WALL)
|
||||||
|
.get(Key.PAGES)
|
||||||
|
.get(pageIdx)
|
||||||
|
.put(
|
||||||
|
{
|
||||||
|
[Key.COUNT]: shouldBeNewPage ? 1 : count + 1,
|
||||||
|
posts: {
|
||||||
|
unused: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ack => {
|
||||||
|
if (ack.err) {
|
||||||
|
rej(new Error(ack.err))
|
||||||
|
}
|
||||||
|
|
||||||
|
res()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
const postID = await new Promise((res, rej) => {
|
||||||
|
const _n = require('../Mediator')
|
||||||
|
.getUser()
|
||||||
|
.get(Key.WALL)
|
||||||
|
.get(Key.PAGES)
|
||||||
|
.get(pageIdx)
|
||||||
|
.get(Key.POSTS)
|
||||||
|
.set(
|
||||||
|
{
|
||||||
|
date: Date.now(),
|
||||||
|
status: 'publish',
|
||||||
|
tags: tags.join('-'),
|
||||||
|
title
|
||||||
|
},
|
||||||
|
ack => {
|
||||||
|
if (ack.err) {
|
||||||
|
rej(new Error(ack.err))
|
||||||
|
} else {
|
||||||
|
res(_n._.get)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (shouldBeNewPage || numOfPages === 0) {
|
||||||
|
await new Promise(res => {
|
||||||
|
require('../Mediator')
|
||||||
|
.getUser()
|
||||||
|
.get(Key.WALL)
|
||||||
|
.get(Key.NUM_OF_PAGES)
|
||||||
|
.put(numOfPages + 1, ack => {
|
||||||
|
if (ack.err) {
|
||||||
|
throw new Error(ack.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentItems = require('../Mediator')
|
||||||
|
.getUser()
|
||||||
|
.get(Key.WALL)
|
||||||
|
.get(Key.PAGES)
|
||||||
|
.get(pageIdx)
|
||||||
|
.get(Key.POSTS)
|
||||||
|
.get(postID)
|
||||||
|
.get(Key.CONTENT_ITEMS)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
content.map(
|
||||||
|
ci =>
|
||||||
|
new Promise(res => {
|
||||||
|
// @ts-ignore
|
||||||
|
contentItems.set(ci, ack => {
|
||||||
|
if (ack.err) {
|
||||||
|
throw new Error(ack.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
await new Promise(res => {
|
||||||
|
require('../Mediator')
|
||||||
|
.getUser()
|
||||||
|
.get(Key.WALL)
|
||||||
|
.get(Key.PAGES)
|
||||||
|
.get(pageIdx)
|
||||||
|
.get(Key.POSTS)
|
||||||
|
.get(postID)
|
||||||
|
.put(null, ack => {
|
||||||
|
if (ack.err) {
|
||||||
|
throw new Error(ack.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadedPost = await new Promise(res => {
|
||||||
|
require('../Mediator')
|
||||||
|
.getUser()
|
||||||
|
.get(Key.WALL)
|
||||||
|
.get(Key.PAGES)
|
||||||
|
.get(pageIdx)
|
||||||
|
.get(Key.POSTS)
|
||||||
|
.get(postID)
|
||||||
|
.load(data => {
|
||||||
|
res(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/** @type {Common.Schema.User} */
|
||||||
|
const userForPost = await Getters.getMyUser()
|
||||||
|
|
||||||
|
/** @type {Common.Schema.Post} */
|
||||||
|
const completePost = {
|
||||||
|
...loadedPost,
|
||||||
|
author: userForPost,
|
||||||
|
id: postID
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Common.Schema.isPost(completePost)) {
|
||||||
|
throw new Error(
|
||||||
|
`completePost not a Post inside Actions.createPost(): ${JSON.stringify(
|
||||||
|
createPost
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return completePost
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} postId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const deletePost = async postId => {
|
||||||
|
await new Promise(res => {
|
||||||
|
res(postId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} publicKey
|
* @param {string} publicKey
|
||||||
|
|
@ -1285,6 +1521,8 @@ module.exports = {
|
||||||
saveChannelsBackup,
|
saveChannelsBackup,
|
||||||
disconnect,
|
disconnect,
|
||||||
setLastSeenApp,
|
setLastSeenApp,
|
||||||
|
createPost,
|
||||||
|
deletePost,
|
||||||
follow,
|
follow,
|
||||||
unfollow
|
unfollow
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
/**
|
/**
|
||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
const Common = require('shock-common')
|
||||||
|
|
||||||
const Key = require('../key')
|
const Key = require('../key')
|
||||||
const Utils = require('../utils')
|
const Utils = require('../utils')
|
||||||
|
|
||||||
|
const Wall = require('./wall')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} pub
|
* @param {string} pub
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
|
|
@ -39,4 +43,54 @@ exports.userToIncomingID = async pub => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<Common.SchemaTypes.User>}
|
||||||
|
*/
|
||||||
|
const getMyUser = async () => {
|
||||||
|
const oldProfile = await Utils.tryAndWait(
|
||||||
|
(_, user) => new Promise(res => user.get(Key.PROFILE).load(res)),
|
||||||
|
v => typeof v !== 'object'
|
||||||
|
)
|
||||||
|
|
||||||
|
const bio = await Utils.tryAndWait(
|
||||||
|
(_, user) => user.get(Key.BIO).then(),
|
||||||
|
v => typeof v !== 'string'
|
||||||
|
)
|
||||||
|
|
||||||
|
const lastSeenApp = await Utils.tryAndWait(
|
||||||
|
(_, user) => user.get(Key.LAST_SEEN_APP).then(),
|
||||||
|
v => typeof v !== 'number'
|
||||||
|
)
|
||||||
|
|
||||||
|
const lastSeenNode = await Utils.tryAndWait(
|
||||||
|
(_, user) => user.get(Key.LAST_SEEN_NODE).then(),
|
||||||
|
v => typeof v !== 'number'
|
||||||
|
)
|
||||||
|
|
||||||
|
const publicKey = await Utils.tryAndWait(
|
||||||
|
(_, user) => Promise.resolve(user.is && user.is.pub),
|
||||||
|
v => typeof v !== 'string'
|
||||||
|
)
|
||||||
|
|
||||||
|
/** @type {Common.SchemaTypes.User} */
|
||||||
|
const u = {
|
||||||
|
avatar: oldProfile.avatar,
|
||||||
|
// @ts-ignore
|
||||||
|
bio,
|
||||||
|
displayName: oldProfile.displayName,
|
||||||
|
// @ts-ignore
|
||||||
|
lastSeenApp,
|
||||||
|
// @ts-ignore
|
||||||
|
lastSeenNode,
|
||||||
|
// @ts-ignore
|
||||||
|
publicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.getMyUser = getMyUser
|
||||||
module.exports.Follows = require('./follows')
|
module.exports.Follows = require('./follows')
|
||||||
|
|
||||||
|
module.exports.getWallPage = Wall.getWallPage
|
||||||
|
module.exports.getWallTotalPages = Wall.getWallTotalPages
|
||||||
|
|
|
||||||
114
services/gunDB/contact-api/getters/wall.js
Normal file
114
services/gunDB/contact-api/getters/wall.js
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
/**
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
const Common = require('shock-common')
|
||||||
|
const Utils = require('../utils')
|
||||||
|
const Key = require('../key')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<number>}
|
||||||
|
*/
|
||||||
|
const getWallTotalPages = async () => {
|
||||||
|
const totalPages = await Utils.tryAndWait(
|
||||||
|
(_, user) =>
|
||||||
|
user
|
||||||
|
.get(Key.WALL)
|
||||||
|
.get(Key.NUM_OF_PAGES)
|
||||||
|
.then(),
|
||||||
|
v => typeof v !== 'number'
|
||||||
|
)
|
||||||
|
|
||||||
|
return typeof totalPages === 'number' ? totalPages : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} page
|
||||||
|
* @throws {TypeError}
|
||||||
|
* @throws {RangeError}
|
||||||
|
* @returns {Promise<Common.SchemaTypes.WallPage>}
|
||||||
|
*/
|
||||||
|
const getWallPage = async page => {
|
||||||
|
const totalPages = await getWallTotalPages()
|
||||||
|
|
||||||
|
if (page === 0 || totalPages === 0) {
|
||||||
|
return {
|
||||||
|
count: 0,
|
||||||
|
posts: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actualPageIdx = page < 0 ? totalPages + page : page - 1
|
||||||
|
|
||||||
|
if (actualPageIdx > totalPages - 1) {
|
||||||
|
throw new RangeError(`Requested a page out of bounds`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Common.SchemaTypes.WallPage}
|
||||||
|
*/
|
||||||
|
const thePage = await Utils.tryAndWait(
|
||||||
|
(_, user) =>
|
||||||
|
new Promise(res => {
|
||||||
|
user
|
||||||
|
.get(Key.WALL)
|
||||||
|
.get(Key.PAGES)
|
||||||
|
.get(actualPageIdx.toString())
|
||||||
|
// @ts-ignore
|
||||||
|
.load(res)
|
||||||
|
}),
|
||||||
|
maybePage => {
|
||||||
|
if (typeof maybePage !== 'object' || maybePage === null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const clean = {
|
||||||
|
...maybePage
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
for (const [key, post] of Object.entries(clean.posts)) {
|
||||||
|
// delete unsuccessful writes
|
||||||
|
if (post === null) {
|
||||||
|
// @ts-ignore
|
||||||
|
delete clean.posts[key]
|
||||||
|
} else {
|
||||||
|
post.id = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// .load() sometimes doesn't load all data on first call
|
||||||
|
// @ts-ignore
|
||||||
|
if (Object.keys(clean.posts).length === 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return !Common.Schema.isWallPage(clean)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const clean = {
|
||||||
|
...thePage
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, post] of Object.entries(clean.posts)) {
|
||||||
|
// delete unsuccessful writes
|
||||||
|
if (post === null) {
|
||||||
|
delete clean.posts[key]
|
||||||
|
} else {
|
||||||
|
post.id = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Common.Schema.isWallPage(clean)) {
|
||||||
|
throw new Error(
|
||||||
|
`Fetched page not a wall page, instead got: ${JSON.stringify(clean)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clean
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getWallTotalPages,
|
||||||
|
getWallPage
|
||||||
|
}
|
||||||
|
|
@ -37,6 +37,15 @@ const lastSeenNode = user => {
|
||||||
logger.error(`Error inside lastSeenNode job: ${ack.err}`)
|
logger.error(`Error inside lastSeenNode job: ${ack.err}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
user
|
||||||
|
.get(Key.PROFILE)
|
||||||
|
.get(Key.LAST_SEEN_NODE)
|
||||||
|
.put(Date.now(), ack => {
|
||||||
|
if (ack.err) {
|
||||||
|
logger.error(`Error inside lastSeenNode job: ${ack.err}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}, LAST_SEEN_NODE_INTERVAL)
|
}, LAST_SEEN_NODE_INTERVAL)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,4 +43,15 @@ exports.LAST_SEEN_APP = 'lastSeenApp'
|
||||||
|
|
||||||
exports.LAST_SEEN_NODE = 'lastSeenNode'
|
exports.LAST_SEEN_NODE = 'lastSeenNode'
|
||||||
|
|
||||||
|
exports.WALL = 'wall'
|
||||||
|
|
||||||
|
exports.NUM_OF_PAGES = 'numOfPages'
|
||||||
|
|
||||||
|
exports.PAGES = 'pages'
|
||||||
|
|
||||||
|
exports.COUNT = 'count'
|
||||||
|
|
||||||
|
exports.CONTENT_ITEMS = 'contentItems'
|
||||||
exports.FOLLOWS = 'follows'
|
exports.FOLLOWS = 'follows'
|
||||||
|
|
||||||
|
exports.POSTS = 'posts'
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const httpsAgent = require("https");
|
||||||
const responseTime = require("response-time");
|
const responseTime = require("response-time");
|
||||||
const uuid = require("uuid/v4");
|
const uuid = require("uuid/v4");
|
||||||
const Common = require('shock-common')
|
const Common = require('shock-common')
|
||||||
|
const isARealUsableNumber = require('lodash/isFinite')
|
||||||
|
|
||||||
const getListPage = require("../utils/paginate");
|
const getListPage = require("../utils/paginate");
|
||||||
const auth = require("../services/auth/auth");
|
const auth = require("../services/auth/auth");
|
||||||
|
|
@ -1839,7 +1840,54 @@ module.exports = async (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
app.get(`/api/gun/wall`, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { page } = req.query;
|
||||||
|
|
||||||
|
const pageNum = Number(page)
|
||||||
|
|
||||||
|
if (!isARealUsableNumber(pageNum)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
field: 'page',
|
||||||
|
errorMessage: 'Not a number'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalPages = await GunGetters.getWallTotalPages()
|
||||||
|
const fetchedPage = await GunGetters.getWallPage(pageNum)
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
...fetchedPage,
|
||||||
|
totalPages,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(500).json({
|
||||||
|
errorMessage: err.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post(`/api/gun/wall/`, async (req,res) => {
|
||||||
|
try{
|
||||||
|
const {tags,title,contentItems} = req.body
|
||||||
|
return res.status(200).json(await GunActions.createPost(
|
||||||
|
tags,
|
||||||
|
title,
|
||||||
|
contentItems
|
||||||
|
))
|
||||||
|
} catch(e) {
|
||||||
|
return res.status(500).json({
|
||||||
|
errorMessage: (typeof e === 'string' ? e : e.message)
|
||||||
|
|| 'Unknown error.'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.delete(`/api/gun/wall/:postID`, (_, res) => res.status(200).json({
|
||||||
|
ok: 'true'
|
||||||
|
}))
|
||||||
/////////////////////////////////
|
/////////////////////////////////
|
||||||
/**
|
/**
|
||||||
* @template P
|
* @template P
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue