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);
ssbServer = server;
console.log("SSB Client ready");
});
app.use(bodyParser.json());
@ -41,6 +42,12 @@ app.use((_req, res, next) => {
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;
next();
});
@ -73,11 +80,10 @@ router.get("/profile/:id", async (req, res) => {
return res.redirect("/");
}
const profile = await queries.getProfile(ssbServer, id);
const [posts, friends] = await Promise.all([
queries.getPosts(ssbServer, profile),
queries.getFriends(ssbServer, profile),
const [profile, posts, friends] = await Promise.all([
queries.getProfile(ssbServer, id),
queries.getPosts(ssbServer, { id }),
queries.getFriends(ssbServer, { id }),
]);
res.render("profile", { profile, posts, friends });
@ -139,10 +145,12 @@ router.post("/about", async (req, res) => {
res.redirect("/");
});
router.get("/debug", async (_req, res) => {
const entries = await queries.getAllEntries(ssbServer);
router.get("/debug", async (req, res) => {
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) => {

View File

@ -1,6 +1,8 @@
const { promisify } = require("./utils");
const pull = require("pull-stream");
const cat = require("pull-cat");
const debug = require("debug")("queries");
const paramap = require("pull-paramap");
const latestOwnerValue = (ssbServer) => ({ key, dest }, cb) => {
let value = null;
@ -43,57 +45,24 @@ const latestOwnerValue = (ssbServer) => ({ key, dest }, cb) => {
};
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(
promisify(latestOwnerValue(ssbServer), {
key: "name",
dest: data.value.author,
})
);
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,
};
return Promise.all([authorPromise, contactPromise])
.then(([author, contact]) => {
data.value.authorProfile = author;
if (contact) {
data.value.content.contactProfile = contact;
}
callback(null, data);
})
.catch((err) => callback(err, null));
};
const getPosts = (ssbServer, profile) =>
debug("Fetching posts") ||
new Promise((resolve, reject) => {
pull(
// @ts-ignore
@ -130,8 +99,9 @@ const getPosts = (ssbServer, profile) =>
limit: 100,
}),
]),
pull.asyncMap(mapProfiles(ssbServer)),
paramap(mapProfiles(ssbServer)),
pull.collect((err, msgs) => {
debug("Done fetching posts");
const entries = msgs.map((x) => x.value);
if (err) return reject(err);
@ -141,6 +111,7 @@ const getPosts = (ssbServer, profile) =>
});
const searchPeople = (ssbServer, search) =>
debug("Searching people") ||
new Promise((resolve, reject) => {
pull(
ssbServer.query.read({
@ -166,6 +137,7 @@ const searchPeople = (ssbServer, search) =>
);
}),
pull.collect((err, msgs) => {
debug("Done searching people");
const entries = msgs.map((x) => x.value);
if (err) return reject(err);
@ -175,6 +147,7 @@ const searchPeople = (ssbServer, search) =>
});
const getFriends = (ssbServer, profile) =>
debug("Fetching friends") ||
new Promise((resolve, reject) => {
pull(
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) => {
debug("Done fetching friends");
const entries = msgs.map((x) => x.value);
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) => {
let queries = [];
if (query.author) {
queries.push({ $filter: { value: { author: query.author } } });
}
if (query.type) {
queries.push({ $filter: { value: { content: { type: query.type } } } });
}
pull(
ssbServer.query.read({
reverse: true,
limit: 500,
query: queries,
}),
pull.collect((err, msgs) => {
debug("Done fetching all entries");
const entries = msgs.map((x) => x.value);
if (err) return reject(err);
@ -219,15 +204,32 @@ const getAllEntries = (ssbServer) =>
);
});
let profileCache = {};
const getProfile = async (ssbServer, id) => {
let [name, image] = await Promise.all([
promisify(latestOwnerValue(ssbServer), { key: "name", dest: id }),
promisify(latestOwnerValue(ssbServer), { key: "image", dest: id }),
]);
if (profileCache[id]) return profileCache[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 = {
mapProfiles,
getPosts,

View File

@ -4,11 +4,13 @@ const toPull = require("stream-to-pull-stream");
const ident = require("pull-identify-filetype");
const mime = require("mime-types");
const URL = require("url");
const debug = require("debug")("server-blobs");
const serveBlobs = (sbot) => {
return (req, res) => {
const parsed = URL.parse(req.url, true);
const hash = decodeURIComponent(parsed.pathname.replace("/blob/", ""));
debug("fetching", hash);
waitFor(hash, function (_, has) {
if (!has) return respond(res, 404, "File not found");
@ -22,17 +24,32 @@ const serveBlobs = (sbot) => {
// serve
res.setHeader("Content-Security-Policy", BlobCSP());
respondSource(res, sbot.blobs.get(hash), false);
});
};
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) {
if (err) return cb(err);
if (err) return wrappedCb(err);
debug("has is ", has, "for", hash);
if (has) {
cb(null, has);
wrappedCb(null, has);
} 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) => {
let wrapper = (fn) => async (req, res, next) => {
const debug = require("debug")("router");
let wrapper = (debugMsg, fn) => async (req, res, next) => {
try {
debug(debugMsg);
await fn(req, res);
} catch (e) {
next(e);
@ -22,10 +25,11 @@ module.exports.asyncRouter = (app) => {
};
return {
get: (path, fn) => {
app.get(path, wrapper(fn));
app.get(path, wrapper(`GET ${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": "",
"main": "index.js",
"scripts": {
"start": "electron .",
"start:user-2": "SSB_PORT=8009 EXPRESS_PORT=3001 CONFIG_FOLDER=social-user2 electron .",
"start:user-3": "SSB_PORT=8010 EXPRESS_PORT=3002 CONFIG_FOLDER=social-user3 electron .",
"start": "SSB_PORT=8009 EXPRESS_PORT=3000 electron .",
"start:user-2": "SSB_PORT=8010 EXPRESS_PORT=3001 CONFIG_FOLDER=social-user2 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"
},
"author": "",
"license": "ISC",
"dependencies": {
"chokidar": "^3.3.1",
"debug": "^4.1.1",
"ejs": "^3.0.2",
"express": "^4.17.1",
"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;
margin: 0;
line-height: 1.3em;
word-wrap: break-word;
}
header {
padding: 0 30px;
@ -55,15 +56,18 @@ main {
.columns {
display: flex;
align-content: stretch;
}
h1 {
text-transform: uppercase;
font-weight: bold;
line-height: 1.3em;
}
.wall {
padding: 20px;
flex-grow: 1;
}
.post {
@ -90,3 +94,11 @@ h1 {
font-family: Georgia, Times, "Times New Roman", serif;
font-size: 18px;
}
.about {
max-width: 300px;
}
.profile-pic {
width: 300px;
}

View File

@ -1,5 +1,13 @@
<%- 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 => { %>
<code style="width: 80%; word-wrap: break-word;"><%= JSON.stringify(entry) %></code>
<hr />

View File

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