Default profile pic, more logging, a bit of speedup with memory cache of profiles

This commit is contained in:
Rogerio Chaves 2020-04-06 20:41:13 +02:00
parent c9632e6f93
commit 42062a6fc4
No known key found for this signature in database
GPG Key ID: E6AF5440509B1D94
9 changed files with 128 additions and 74 deletions

View File

@ -23,6 +23,7 @@ Client(ssbSecret, ssbConfig, async (err, server) => {
context.profile = await queries.getProfile(server, whoami.id); context.profile = await queries.getProfile(server, whoami.id);
ssbServer = server; ssbServer = server;
console.log("SSB Client ready");
}); });
app.use(bodyParser.json()); app.use(bodyParser.json());
@ -41,6 +42,12 @@ app.use((_req, res, next) => {
return imageHash && `/blob/${encodeURIComponent(imageHash)}`; return imageHash && `/blob/${encodeURIComponent(imageHash)}`;
}; };
res.locals.profileImageUrl = (profile) => {
if (profile.image) {
return res.locals.imageUrl(profile.image);
}
return "/images/no-avatar.png";
};
res.locals.context = context; res.locals.context = context;
next(); next();
}); });
@ -73,11 +80,10 @@ router.get("/profile/:id", async (req, res) => {
return res.redirect("/"); return res.redirect("/");
} }
const profile = await queries.getProfile(ssbServer, id); const [profile, posts, friends] = await Promise.all([
queries.getProfile(ssbServer, id),
const [posts, friends] = await Promise.all([ queries.getPosts(ssbServer, { id }),
queries.getPosts(ssbServer, profile), queries.getFriends(ssbServer, { id }),
queries.getFriends(ssbServer, profile),
]); ]);
res.render("profile", { profile, posts, friends }); res.render("profile", { profile, posts, friends });
@ -139,10 +145,12 @@ router.post("/about", async (req, res) => {
res.redirect("/"); res.redirect("/");
}); });
router.get("/debug", async (_req, res) => { router.get("/debug", async (req, res) => {
const entries = await queries.getAllEntries(ssbServer); const query = req.query || {};
res.render("debug", { entries }); const entries = await queries.getAllEntries(ssbServer, query);
res.render("debug", { entries, query });
}); });
router.get("/search", async (req, res) => { router.get("/search", async (req, res) => {

View File

@ -1,6 +1,8 @@
const { promisify } = require("./utils"); const { promisify } = require("./utils");
const pull = require("pull-stream"); const pull = require("pull-stream");
const cat = require("pull-cat"); const cat = require("pull-cat");
const debug = require("debug")("queries");
const paramap = require("pull-paramap");
const latestOwnerValue = (ssbServer) => ({ key, dest }, cb) => { const latestOwnerValue = (ssbServer) => ({ key, dest }, cb) => {
let value = null; let value = null;
@ -43,57 +45,24 @@ const latestOwnerValue = (ssbServer) => ({ key, dest }, cb) => {
}; };
const mapProfiles = (ssbServer) => (data, callback) => { const mapProfiles = (ssbServer) => (data, callback) => {
let promises = []; const authorPromise = getProfile(ssbServer, data.value.author);
const contactPromise =
data.value.content.type == "contact" &&
getProfile(ssbServer, data.value.content.contact);
promises.push( return Promise.all([authorPromise, contactPromise])
promisify(latestOwnerValue(ssbServer), { .then(([author, contact]) => {
key: "name", data.value.authorProfile = author;
dest: data.value.author, if (contact) {
}) data.value.content.contactProfile = contact;
);
promises.push(
promisify(latestOwnerValue(ssbServer), {
key: "image",
dest: data.value.author,
})
);
if (data.value.content.type == "contact") {
promises.push(
promisify(latestOwnerValue(ssbServer), {
key: "name",
dest: data.value.content.contact,
})
);
promises.push(
promisify(latestOwnerValue(ssbServer), {
key: "image",
dest: data.value.content.contact,
})
);
}
return Promise.all(promises)
.then(([authorName, authorImage, contactName, contactImage]) => {
data.value.authorProfile = {
id: data.value.author,
name: authorName,
image: authorImage,
};
if (contactName) {
data.value.content.contactProfile = {
id: data.value.content.contact,
name: contactName,
image: contactImage,
};
} }
callback(null, data); callback(null, data);
}) })
.catch((err) => callback(err, null)); .catch((err) => callback(err, null));
}; };
const getPosts = (ssbServer, profile) => const getPosts = (ssbServer, profile) =>
debug("Fetching posts") ||
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
pull( pull(
// @ts-ignore // @ts-ignore
@ -130,8 +99,9 @@ const getPosts = (ssbServer, profile) =>
limit: 100, limit: 100,
}), }),
]), ]),
pull.asyncMap(mapProfiles(ssbServer)), paramap(mapProfiles(ssbServer)),
pull.collect((err, msgs) => { pull.collect((err, msgs) => {
debug("Done fetching posts");
const entries = msgs.map((x) => x.value); const entries = msgs.map((x) => x.value);
if (err) return reject(err); if (err) return reject(err);
@ -141,6 +111,7 @@ const getPosts = (ssbServer, profile) =>
}); });
const searchPeople = (ssbServer, search) => const searchPeople = (ssbServer, search) =>
debug("Searching people") ||
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
pull( pull(
ssbServer.query.read({ ssbServer.query.read({
@ -166,6 +137,7 @@ const searchPeople = (ssbServer, search) =>
); );
}), }),
pull.collect((err, msgs) => { pull.collect((err, msgs) => {
debug("Done searching people");
const entries = msgs.map((x) => x.value); const entries = msgs.map((x) => x.value);
if (err) return reject(err); if (err) return reject(err);
@ -175,6 +147,7 @@ const searchPeople = (ssbServer, search) =>
}); });
const getFriends = (ssbServer, profile) => const getFriends = (ssbServer, profile) =>
debug("Fetching friends") ||
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
pull( pull(
ssbServer.query.read({ ssbServer.query.read({
@ -191,10 +164,11 @@ const getFriends = (ssbServer, profile) =>
}, },
}, },
], ],
limit: 500, limit: 20,
}), }),
pull.asyncMap(mapProfiles(ssbServer)), paramap(mapProfiles(ssbServer)),
pull.collect((err, msgs) => { pull.collect((err, msgs) => {
debug("Done fetching friends");
const entries = msgs.map((x) => x.value); const entries = msgs.map((x) => x.value);
if (err) return reject(err); if (err) return reject(err);
@ -203,14 +177,25 @@ const getFriends = (ssbServer, profile) =>
); );
}); });
const getAllEntries = (ssbServer) => const getAllEntries = (ssbServer, query) =>
debug("Fetching All Entries") ||
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
let queries = [];
if (query.author) {
queries.push({ $filter: { value: { author: query.author } } });
}
if (query.type) {
queries.push({ $filter: { value: { content: { type: query.type } } } });
}
pull( pull(
ssbServer.query.read({ ssbServer.query.read({
reverse: true, reverse: true,
limit: 500, limit: 500,
query: queries,
}), }),
pull.collect((err, msgs) => { pull.collect((err, msgs) => {
debug("Done fetching all entries");
const entries = msgs.map((x) => x.value); const entries = msgs.map((x) => x.value);
if (err) return reject(err); if (err) return reject(err);
@ -219,15 +204,32 @@ const getAllEntries = (ssbServer) =>
); );
}); });
let profileCache = {};
const getProfile = async (ssbServer, id) => { const getProfile = async (ssbServer, id) => {
let [name, image] = await Promise.all([ if (profileCache[id]) return profileCache[id];
promisify(latestOwnerValue(ssbServer), { key: "name", dest: id }),
promisify(latestOwnerValue(ssbServer), { key: "image", dest: id }),
]);
return { id, name, image }; let getKey = (key) =>
promisify(latestOwnerValue(ssbServer), { key, dest: id });
let [name, image, description] = await Promise.all([
getKey("name"),
getKey("image"),
getKey("description"),
]).catch((err) => {
console.error("Could not retrieve profile for", id, err);
});
let profile = { id, name, image, description };
profileCache[id] = profile;
return profile;
}; };
setInterval(() => {
debug("Clearing profile cache");
profileCache = {};
}, 5 * 60 * 1000);
module.exports = { module.exports = {
mapProfiles, mapProfiles,
getPosts, getPosts,

View File

@ -4,11 +4,13 @@ const toPull = require("stream-to-pull-stream");
const ident = require("pull-identify-filetype"); const ident = require("pull-identify-filetype");
const mime = require("mime-types"); const mime = require("mime-types");
const URL = require("url"); const URL = require("url");
const debug = require("debug")("server-blobs");
const serveBlobs = (sbot) => { const serveBlobs = (sbot) => {
return (req, res) => { return (req, res) => {
const parsed = URL.parse(req.url, true); const parsed = URL.parse(req.url, true);
const hash = decodeURIComponent(parsed.pathname.replace("/blob/", "")); const hash = decodeURIComponent(parsed.pathname.replace("/blob/", ""));
debug("fetching", hash);
waitFor(hash, function (_, has) { waitFor(hash, function (_, has) {
if (!has) return respond(res, 404, "File not found"); if (!has) return respond(res, 404, "File not found");
@ -22,17 +24,32 @@ const serveBlobs = (sbot) => {
// serve // serve
res.setHeader("Content-Security-Policy", BlobCSP()); res.setHeader("Content-Security-Policy", BlobCSP());
respondSource(res, sbot.blobs.get(hash), false); respondSource(res, sbot.blobs.get(hash), false);
}); });
}; };
function waitFor(hash, cb) { function waitFor(hash, cb) {
let finished = false;
let wrappedCb = (err, result) => {
if (finished) return;
finished = true;
cb(err, result);
};
setTimeout(() => {
debug("timeout for", hash);
wrappedCb(null, false);
}, 1000);
sbot.blobs.has(hash, function (err, has) { sbot.blobs.has(hash, function (err, has) {
if (err) return cb(err); if (err) return wrappedCb(err);
debug("has is ", has, "for", hash);
if (has) { if (has) {
cb(null, has); wrappedCb(null, has);
} else { } else {
sbot.blobs.want(hash, cb); debug("calling want for", hash);
sbot.blobs.want(hash, wrappedCb);
} }
}); });
} }

View File

@ -13,8 +13,11 @@ module.exports.promisify = (method, options = null) => {
}; };
module.exports.asyncRouter = (app) => { module.exports.asyncRouter = (app) => {
let wrapper = (fn) => async (req, res, next) => { const debug = require("debug")("router");
let wrapper = (debugMsg, fn) => async (req, res, next) => {
try { try {
debug(debugMsg);
await fn(req, res); await fn(req, res);
} catch (e) { } catch (e) {
next(e); next(e);
@ -22,10 +25,11 @@ module.exports.asyncRouter = (app) => {
}; };
return { return {
get: (path, fn) => { get: (path, fn) => {
app.get(path, wrapper(fn)); app.get(path, wrapper(`GET ${path}`, fn));
}, },
post: (path, fn) => { post: (path, fn) => {
app.post(path, wrapper(fn)); debug(`POST ${path}`);
app.post(path, wrapper(`POST ${path}`, fn));
}, },
}; };
}; };

View File

@ -4,15 +4,16 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "electron .", "start": "SSB_PORT=8009 EXPRESS_PORT=3000 electron .",
"start:user-2": "SSB_PORT=8009 EXPRESS_PORT=3001 CONFIG_FOLDER=social-user2 electron .", "start:user-2": "SSB_PORT=8010 EXPRESS_PORT=3001 CONFIG_FOLDER=social-user2 electron .",
"start:user-3": "SSB_PORT=8010 EXPRESS_PORT=3002 CONFIG_FOLDER=social-user3 electron .", "start:user-3": "SSB_PORT=8011 EXPRESS_PORT=3002 CONFIG_FOLDER=social-user3 electron .",
"clear": "rm -rf ~/.social; rm -rf ~/.social-user2; rm -rf ~/.social-user3" "clear": "rm -rf ~/.social; rm -rf ~/.social-user2; rm -rf ~/.social-user3"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"chokidar": "^3.3.1", "chokidar": "^3.3.1",
"debug": "^4.1.1",
"ejs": "^3.0.2", "ejs": "^3.0.2",
"express": "^4.17.1", "express": "^4.17.1",
"pull-stream": "^3.6.14", "pull-stream": "^3.6.14",

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -2,6 +2,7 @@ body {
font-family: sans-serif; font-family: sans-serif;
margin: 0; margin: 0;
line-height: 1.3em; line-height: 1.3em;
word-wrap: break-word;
} }
header { header {
padding: 0 30px; padding: 0 30px;
@ -55,15 +56,18 @@ main {
.columns { .columns {
display: flex; display: flex;
align-content: stretch;
} }
h1 { h1 {
text-transform: uppercase; text-transform: uppercase;
font-weight: bold; font-weight: bold;
line-height: 1.3em;
} }
.wall { .wall {
padding: 20px; padding: 20px;
flex-grow: 1;
} }
.post { .post {
@ -90,3 +94,11 @@ h1 {
font-family: Georgia, Times, "Times New Roman", serif; font-family: Georgia, Times, "Times New Roman", serif;
font-size: 18px; font-size: 18px;
} }
.about {
max-width: 300px;
}
.profile-pic {
width: 300px;
}

View File

@ -1,5 +1,13 @@
<%- include('_header') %> <%- include('_header') %>
<p>
<form action="/debug" method="GET">
Author: <input type="text" name="author" value="<%= query.author %>">
Type: <input type="text" name="type" value="<%= query.type %>">
<input type="submit" value="Filter">
</form>
</p>
<% entries.map(entry => { %> <% entries.map(entry => { %>
<code style="width: 80%; word-wrap: break-word;"><%= JSON.stringify(entry) %></code> <code style="width: 80%; word-wrap: break-word;"><%= JSON.stringify(entry) %></code>
<hr /> <hr />

View File

@ -2,10 +2,10 @@
<div class="columns"> <div class="columns">
<div class="about"> <div class="about">
<% if (profile.image) { %> <img class="profile-pic" src="<%= profileImageUrl(profile) %>" />
<img src="<%= imageUrl(profile.image) %>" /> <h1><%= profile.name %></h1>
<% } %>
<h1><%= profile.name %>'s Profile</h1> <%= profile.description %>
<h2>Friends</h2> <h2>Friends</h2>
@ -31,7 +31,9 @@
<% posts.map(entry => { %> <% posts.map(entry => { %>
<div class="post"> <div class="post">
<img src="<%= imageUrl(entry.authorProfile.image) %>" class="post-profile-pic" /> <div>
<img src="<%= profileImageUrl(entry.authorProfile) %>" class="post-profile-pic" />
</div>
<div class="post-content"> <div class="post-content">
<div class="content-header"> <div class="content-header">
<%= entry.authorProfile.name %> <%= entry.authorProfile.name %>