mirror: allow fetching all usernames (#1293)

This is a minor change to the Discourse mirror so that it supports a
query to get all users from the server. It will be convenient for a
followon change which makes `update` search for every user's likes.

I also modified createGraph so that it uses the new method, which
results in code that is cleaner and slightly more efficient.

Test plan: Unit tests updated.
This commit is contained in:
Dandelion Mané 2019-08-18 16:27:11 +02:00 committed by GitHub
parent f3ae0a8415
commit e7b1fbd681
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 5 deletions

View File

@ -130,17 +130,19 @@ export function createGraph(serverUrl: string, data: DiscourseData): Graph {
const g = new Graph(); const g = new Graph();
const topicIdToTitle: Map<TopicId, string> = new Map(); const topicIdToTitle: Map<TopicId, string> = new Map();
for (const username of data.users()) {
g.addNode(userNode(serverUrl, username));
}
for (const topic of data.topics()) { for (const topic of data.topics()) {
topicIdToTitle.set(topic.id, topic.title); topicIdToTitle.set(topic.id, topic.title);
g.addNode(topicNode(serverUrl, topic)); g.addNode(topicNode(serverUrl, topic));
g.addNode(userNode(serverUrl, topic.authorUsername));
g.addEdge(authorsTopicEdge(serverUrl, topic)); g.addEdge(authorsTopicEdge(serverUrl, topic));
} }
for (const post of data.posts()) { for (const post of data.posts()) {
const topicTitle = topicIdToTitle.get(post.topicId) || "[unknown topic]"; const topicTitle = topicIdToTitle.get(post.topicId) || "[unknown topic]";
g.addNode(postNode(serverUrl, post, topicTitle)); g.addNode(postNode(serverUrl, post, topicTitle));
g.addNode(userNode(serverUrl, post.authorUsername));
g.addEdge(authorsPostEdge(serverUrl, post)); g.addEdge(authorsPostEdge(serverUrl, post));
g.addEdge(topicContainsPostEdge(serverUrl, post)); g.addEdge(topicContainsPostEdge(serverUrl, post));
let replyToPostIndex = post.replyToPostIndex; let replyToPostIndex = post.replyToPostIndex;

View File

@ -40,6 +40,16 @@ describe("plugins/discourse/createGraph", () => {
posts(): $ReadOnlyArray<Post> { posts(): $ReadOnlyArray<Post> {
return this._posts; return this._posts;
} }
users(): $ReadOnlyArray<string> {
const users = new Set();
for (const {authorUsername} of this.posts()) {
users.add(authorUsername);
}
for (const {authorUsername} of this.topics()) {
users.add(authorUsername);
}
return Array.from(users);
}
findPostInTopic(topicId: TopicId, indexWithinTopic: number): ?PostId { findPostInTopic(topicId: TopicId, indexWithinTopic: number): ?PostId {
const post = this._posts.filter( const post = this._posts.filter(
(p) => p.topicId === topicId && p.indexWithinTopic === indexWithinTopic (p) => p.topicId === topicId && p.indexWithinTopic === indexWithinTopic

View File

@ -13,7 +13,7 @@ import {
// The version should be bumped any time the database schema is changed, // The version should be bumped any time the database schema is changed,
// so that the cache will be properly invalidated. // so that the cache will be properly invalidated.
const VERSION = "discourse_mirror_v1"; const VERSION = "discourse_mirror_v2";
/** /**
* An interface for retrieving all of the Discourse data at once. * An interface for retrieving all of the Discourse data at once.
@ -45,6 +45,13 @@ export interface DiscourseData {
* Returns undefined if no such post is available. * Returns undefined if no such post is available.
*/ */
findPostInTopic(topicId: TopicId, indexWithinTopic: number): ?PostId; findPostInTopic(topicId: TopicId, indexWithinTopic: number): ?PostId;
/**
* Get usernames for all users.
*
* The order is unspecified.
*/
users(): $ReadOnlyArray<string>;
} }
/** /**
@ -131,12 +138,14 @@ export class Mirror implements DiscourseData {
db.prepare("INSERT INTO meta (zero, config) VALUES (0, ?)").run(config); db.prepare("INSERT INTO meta (zero, config) VALUES (0, ?)").run(config);
const tables = [ const tables = [
"CREATE TABLE users (username TEXT PRIMARY KEY)",
dedent`\ dedent`\
CREATE TABLE topics ( CREATE TABLE topics (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
title TEXT NOT NULL, title TEXT NOT NULL,
timestamp_ms INTEGER NOT NULL, timestamp_ms INTEGER NOT NULL,
author_username TEXT NOT NULL author_username TEXT NOT NULL,
FOREIGN KEY(author_username) REFERENCES users(username)
) )
`, `,
dedent`\ dedent`\
@ -147,7 +156,8 @@ export class Mirror implements DiscourseData {
topic_id INTEGER NOT NULL, topic_id INTEGER NOT NULL,
index_within_topic INTEGER NOT NULL, index_within_topic INTEGER NOT NULL,
reply_to_post_index INTEGER, reply_to_post_index INTEGER,
FOREIGN KEY(topic_id) REFERENCES topics(id) FOREIGN KEY(topic_id) REFERENCES topics(id),
FOREIGN KEY(author_username) REFERENCES users(username)
) )
`, `,
]; ];
@ -200,6 +210,13 @@ export class Mirror implements DiscourseData {
})); }));
} }
users(): $ReadOnlyArray<string> {
return this._db
.prepare("SELECT username FROM users")
.pluck()
.all();
}
findPostInTopic(topicId: TopicId, indexWithinTopic: number): ?PostId { findPostInTopic(topicId: TopicId, indexWithinTopic: number): ?PostId {
return this._db return this._db
.prepare( .prepare(
@ -237,6 +254,9 @@ export class Mirror implements DiscourseData {
topicId, topicId,
authorUsername, authorUsername,
} = post; } = post;
db.prepare("INSERT OR IGNORE INTO users (username) VALUES (?)").run(
authorUsername
);
db.prepare( db.prepare(
dedent`\ dedent`\
REPLACE INTO posts ( REPLACE INTO posts (
@ -275,6 +295,9 @@ export class Mirror implements DiscourseData {
if (topicWithPosts != null) { if (topicWithPosts != null) {
const {topic, posts} = topicWithPosts; const {topic, posts} = topicWithPosts;
const {id, title, timestampMs, authorUsername} = topic; const {id, title, timestampMs, authorUsername} = topic;
db.prepare("INSERT OR IGNORE INTO users (username) VALUES (?)").run(
authorUsername
);
this._db this._db
.prepare( .prepare(
dedent`\ dedent`\

View File

@ -194,6 +194,21 @@ describe("plugins/discourse/mirror", () => {
expect(mirror.posts()).toEqual(posts); expect(mirror.posts()).toEqual(posts);
}); });
it("provides usernames for all active users", async () => {
const {mirror, fetcher} = example();
fetcher.addPost(2, null, "alpha");
fetcher.addPost(3, null, "beta");
fetcher.addPost(3, 1, "alpha");
await mirror.update();
// credbot appears because it is the nominal author of all topics
expect(
mirror
.users()
.slice()
.sort()
).toEqual(["alpha", "beta", "credbot"]);
});
describe("update semantics", () => { describe("update semantics", () => {
it("only fetches new topics on `update`", async () => { it("only fetches new topics on `update`", async () => {
const {mirror, fetcher} = example(); const {mirror, fetcher} = example();