diff --git a/package.json b/package.json index 52b8992c..a1b337c1 100644 --- a/package.json +++ b/package.json @@ -78,9 +78,12 @@ "typescript": "^3.6.3" }, "lint-staged": { - "*.{js,ts}": [ + "*.js": [ "prettier --check", "eslint" + ], + "*.ts": [ + "prettier --check" ] }, "husky": { diff --git a/services/gunDB/contact-api/SimpleGUN.ts b/services/gunDB/contact-api/SimpleGUN.ts index 25f9e8ba..0929b854 100644 --- a/services/gunDB/contact-api/SimpleGUN.ts +++ b/services/gunDB/contact-api/SimpleGUN.ts @@ -38,6 +38,9 @@ export interface Soul { export type OpenListenerData = Primitive | null | OpenListenerDataObj export type OpenListener = (data: OpenListenerData, key: string) => void +export type LoadListenerData = OpenListenerData +export type LoadListener = (data: LoadListenerData, key: string) => void + export interface GUNNodeBase { _: Soul @@ -48,6 +51,8 @@ export interface GUNNodeBase { open(this: GUNNode, cb?: OpenListener): GUNNode + load(this: GUNNode, cb?: LoadListener): GUNNode + off(): void user(): UserGUNNode user(epub: string): GUNNode diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index b4fe5bed..54bd41fe 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -1156,7 +1156,7 @@ const saveSeedBackup = async (mnemonicPhrase, user, SEA) => { return new Promise((res, rej) => { user.get(Key.SEED_BACKUP).put(encryptedSeed, ack => { if (ack.err) { - rej(ack.err) + rej(new Error(ack.err)) } else { res() } @@ -1179,7 +1179,7 @@ const saveChannelsBackup = async (backups, user, SEA) => { return new Promise((res, rej) => { user.get(Key.CHANNELS_BACKUP).put(encryptBackups, ack => { if (ack.err) { - rej(ack.err) + rej(new Error(ack.err)) } else { res() } @@ -1218,6 +1218,54 @@ const setLastSeenApp = () => }) }) +/** + * @param {string} publicKey + * @param {boolean} isPrivate Will overwrite previous private status. + * @returns {Promise} + */ +const follow = (publicKey, isPrivate) => { + /** @type {import('shock-common').Schema.Follow} */ + const newFollow = { + private: isPrivate, + status: 'ok', + user: publicKey + } + + return new Promise((res, rej) => { + require('../Mediator') + .getUser() + .get(Key.FOLLOWS) + .get(publicKey) + // @ts-ignore + .put(newFollow, ack => { + if (ack.err) { + rej(new Error(ack.err)) + } else { + res() + } + }) + }) +} + +/** + * @param {string} publicKey + * @returns {Promise} + */ +const unfollow = publicKey => + new Promise((res, rej) => { + require('../Mediator') + .getUser() + .get(Key.FOLLOWS) + .get(publicKey) + .put(null, ack => { + if (ack.err) { + rej(new Error(ack.err)) + } else { + res() + } + }) + }) + module.exports = { __createOutgoingFeed, acceptRequest, @@ -1236,5 +1284,7 @@ module.exports = { saveSeedBackup, saveChannelsBackup, disconnect, - setLastSeenApp + setLastSeenApp, + follow, + unfollow } diff --git a/services/gunDB/contact-api/getters.js b/services/gunDB/contact-api/getters.js deleted file mode 100644 index cc1104f7..00000000 --- a/services/gunDB/contact-api/getters.js +++ /dev/null @@ -1,29 +0,0 @@ -const Key = require('./key') -const Utils = require('./utils') - -/** - * @param {string} pub - * @returns {Promise} - */ -exports.currentOrderAddress = async (pub) => { - const currAddr = await Utils.tryAndWait((gun) => gun.user(pub).get(Key.CURRENT_ORDER_ADDRESS).then()) - - if (typeof currAddr !== 'string') { - throw new TypeError('Expected user.currentOrderAddress to be an string') - } - - return currAddr -} - -/** - * @param {string} pub - * @returns {Promise} - */ -exports.userToIncomingID = async (pub) => { - const incomingID = await require('../Mediator').getUser().get(Key.USER_TO_INCOMING).get(pub).then() - - if (typeof incomingID === 'string') return incomingID - - return null - -} \ No newline at end of file diff --git a/services/gunDB/contact-api/getters/follows.js b/services/gunDB/contact-api/getters/follows.js new file mode 100644 index 00000000..b74080fa --- /dev/null +++ b/services/gunDB/contact-api/getters/follows.js @@ -0,0 +1,50 @@ +const Common = require('shock-common') +const Logger = require('winston') + +const Key = require('../key') + +/** + * @typedef {Common.Schema.Follow} Follow + */ + +/** + * @returns {Promise>} + */ +exports.currentFollows = () => { + const user = require('../../Mediator').getUser() + + return new Promise(res => { + user.get(Key.FOLLOWS).load(data => { + if (typeof data !== 'object' || data === null) { + Logger.warn( + `GunDb -> getters -> currentFollows() -> Current follows data as fetched from gun is not an object but ${typeof data}. This can happen if Gun lost its cache and it's still retrieving data from the network.` + ) + res({}) + return + } + + const rejected = Object.entries(data).filter( + ([_, follow]) => !Common.Schema.isFollow(follow) + ) + + rejected.forEach(([key, item]) => { + // expected when unfollowed + if (item !== null) { + Logger.warn( + `GunDb -> getters -> currentFollows() -> Item not conforming to schema found inside follows data. Key: ${key}, item: ${JSON.stringify( + item + )}.` + ) + } + }) + + const passed = Object.entries(data).filter(([_, follow]) => + Common.Schema.isFollow(follow) + ) + + const p = /** @type {unknown} */ (passed) + + res(/** @type {Record} */ (p)) + }) + }) +} \ No newline at end of file diff --git a/services/gunDB/contact-api/getters/index.js b/services/gunDB/contact-api/getters/index.js new file mode 100644 index 00000000..9b33cb92 --- /dev/null +++ b/services/gunDB/contact-api/getters/index.js @@ -0,0 +1,42 @@ +/** + * @format + */ +const Key = require('../key') +const Utils = require('../utils') + +/** + * @param {string} pub + * @returns {Promise} + */ +exports.currentOrderAddress = async pub => { + const currAddr = await Utils.tryAndWait(gun => + gun + .user(pub) + .get(Key.CURRENT_ORDER_ADDRESS) + .then() + ) + + if (typeof currAddr !== 'string') { + throw new TypeError('Expected user.currentOrderAddress to be an string') + } + + return currAddr +} + +/** + * @param {string} pub + * @returns {Promise} + */ +exports.userToIncomingID = async pub => { + const incomingID = await require('../../Mediator') + .getUser() + .get(Key.USER_TO_INCOMING) + .get(pub) + .then() + + if (typeof incomingID === 'string') return incomingID + + return null +} + +module.exports.Follows = require('./follows') diff --git a/services/gunDB/contact-api/key.js b/services/gunDB/contact-api/key.js index be14b788..837f38e9 100644 --- a/services/gunDB/contact-api/key.js +++ b/services/gunDB/contact-api/key.js @@ -42,3 +42,5 @@ exports.CHANNELS_BACKUP = 'channelsBackup' exports.LAST_SEEN_APP = 'lastSeenApp' exports.LAST_SEEN_NODE = 'lastSeenNode' + +exports.FOLLOWS = 'follows' diff --git a/src/routes.js b/src/routes.js index 594f226e..9dd57c6e 100644 --- a/src/routes.js +++ b/src/routes.js @@ -13,7 +13,6 @@ const responseTime = require("response-time"); const uuid = require("uuid/v4"); const Common = require('shock-common') - const getListPage = require("../utils/paginate"); const auth = require("../services/auth/auth"); const FS = require("../utils/fs"); @@ -22,6 +21,7 @@ const LightningServices = require("../utils/lightningServices"); const GunDB = require("../services/gunDB/Mediator"); const { unprotectedRoutes, nonEncryptedRoutes } = require("../utils/protectedRoutes"); const GunActions = require("../services/gunDB/contact-api/actions") +const GunGetters = require('../services/gunDB/contact-api/getters') const DEFAULT_MAX_NUM_ROUTES_TO_QUERY = 10; const SESSION_ID = uuid(); @@ -33,6 +33,8 @@ module.exports = async ( mySocketsEvents, { serverPort, CA, CA_KEY, usetls } ) => { + const {timeout5} = require('../services/gunDB/contact-api/utils') + const Http = Axios.create({ httpsAgent: new httpsAgent.Agent({ ca: await FS.readFile(CA) @@ -433,6 +435,67 @@ module.exports = async ( GunActions.saveChannelsBackup(JSON.stringify(channelBackups),user,SEA) }); + + const feedObj = { + feed: [ + { + id:'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba', + paragraphs:[ + "SOme text and stuff 12" + ], + profilePic:"", + username:"bobni", + media:[ + { + type:'VIDEO', + ratio_x: 1024, + ratio_y: 436, + magnetUri:'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent', + }, + ] + }, + { + id:'3ac68afc-c605-48d3-a4f8-fbd91aa97f63', + paragraphs:[ + "SOme text and stuff" + ], + profilePic:"", + username:"bobni", + media:[ + { + type:'VIDEO', + ratio_x: 1920, + ratio_y: 804, + magnetUri:'magnet:?xt=urn:btih:c9e15763f722f23e98a29decdfae341b98d53056&dn=Cosmos+Laundromat&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fcosmos-laundromat.torrent', + }, + ] + }, + { + id:'58694a0f-3da1-471f-bd96-145571e29d72', + paragraphs:[ + "SOme text and stuff" + ], + profilePic:"", + username:"bobni", + media:[ + { + type:'VIDEO', + ratio_x: 1920, + ratio_y: 1080, + magnetUri:'magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c&dn=Big+Buck+Bunny&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fbig-buck-bunny.torrent', + }, + ] + } + ] + } + user.get("FEED_POC").put(JSON.stringify(feedObj), ack => { + if (ack.err) { + //rej(new Error(ack.err)) + logger.log(ack.err) + } else { + logger.log(ack.err) + } + }) //register to listen for channel backups const onNewChannelBackup = () => { @@ -1617,8 +1680,7 @@ module.exports = async ( const GunEvent = Common.Constants.Event const Key = require('../services/gunDB/contact-api/key') - const { timeout5 } = require('../services/gunDB/contact-api/utils') - + app.get("/api/gun/lndchanbackups", async (req,res) => { try{ const user = require('../services/gunDB/Mediator').getUser() @@ -1633,6 +1695,18 @@ module.exports = async ( res.json({ok:"err"}) } }) + app.get("/api/gun/feedpoc", async (req,res) =>{ + try{ + logger.warn("FEED POC") + const user = require('../services/gunDB/Mediator').getUser() + const feedObj = await timeout5(user.get("FEED_POC").then()) + logger.warn(feedObj) + + res.json({data:feedObj}) + } catch (err) { + //res.json({ok:"err"}) + } + }) const Events = require('../services/gunDB/contact-api/events') @@ -1757,6 +1831,88 @@ module.exports = async ( } }) + ///////////////////////////////// + /** + * @template P + * @typedef {import('express-serve-static-core').RequestHandler

} RequestHandler + */ + + + const ap = /** @type {Application} */ (app); + + /** + * @typedef {object} FollowsRouteParams + * @prop {(string|undefined)=} publicKey + */ + + + /** + * @type {RequestHandler} + */ + const apiGunFollowsGet = (_, res) => { + try { + // const { publicKey } = req.params; + // const currFollows = await GunGetters.currentFollows() + + return res.status(200).json({}) + } catch (err) { + return res.status(500).json({ + errorMessage: err.message || 'Unknown ERR at GET /api/follows' + }) + } + } + + + /** + * @type {RequestHandler} + */ + const apiGunFollowsPut = (req, res) => { + try { + const { publicKey } = req.params; + if (!publicKey) { + throw new Error(`Missing publicKey route param.`) + } + + // await GunActions.follow(req.params.publicKey, false) + + // 201 would be extraneous here. Implement it inside app.put + return res.status(200).json({ + ok: true + }) + } catch (err) { + return res.status(500).json({ + errorMessage: err.message || 'Unknown error inside /api/gun/follows/' + }) + } + } + + /** + * @type {RequestHandler} + */ + const apiGunFollowsDelete = (req, res) => { + try { + const { publicKey } = req.params; + if (!publicKey) { + throw new Error(`Missing publicKey route param.`) + } + + // await GunActions.unfollow(req.params.publicKey) + + return res.status(200).json({ + ok: true + }) + } catch (err) { + return res.status(500).json({ + errorMessage: err.message || 'Unknown error inside /api/gun/follows/' + }) + } + } + + ap.get('/api/gun/follows/', apiGunFollowsGet) + ap.get('/api/gun/follows/:publicKey', apiGunFollowsGet) + ap.put(`/api/gun/follows/:publicKey`,apiGunFollowsPut) + ap.delete(`/api/gun/follows/:publicKey`, apiGunFollowsDelete) + /** * Return app so that it can be used by express. */ diff --git a/yarn.lock b/yarn.lock index 6221484f..82894a49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5877,9 +5877,9 @@ shellwords@^0.1.1: integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== shock-common@0.x.x: - version "0.2.0" - resolved "https://registry.yarnpkg.com/shock-common/-/shock-common-0.2.0.tgz#b942d4b8730890aaf192eb873957c235333eddca" - integrity sha512-K9UysxR4LnArhljK3kRNdRM2yeI52YxPe0dZ62SNvsHO5oJgE6wDV0qGy9UZYS5FoAprF5QRzzADxgqmGWin+Q== + version "0.3.0" + resolved "https://registry.yarnpkg.com/shock-common/-/shock-common-0.3.0.tgz#2e8a9c2cd1eda300a8461ea9648d250237f22f78" + integrity sha512-K0xOKkrOAvD4rlWVQD5WXASiKyfatEX2cj114LC8Wddi3wXpex5yLNdWBXNGFpL6Vc5Wzhza/sEHs4p+Q3xBmw== dependencies: lodash "^4.17.15" normalizr "^3.6.0"