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 topicIdToTitle: Map<TopicId, string> = new Map();
for (const username of data.users()) {
g.addNode(userNode(serverUrl, username));
}
for (const topic of data.topics()) {
topicIdToTitle.set(topic.id, topic.title);
g.addNode(topicNode(serverUrl, topic));
g.addNode(userNode(serverUrl, topic.authorUsername));
g.addEdge(authorsTopicEdge(serverUrl, topic));
}
for (const post of data.posts()) {
const topicTitle = topicIdToTitle.get(post.topicId) || "[unknown topic]";
g.addNode(postNode(serverUrl, post, topicTitle));
g.addNode(userNode(serverUrl, post.authorUsername));
g.addEdge(authorsPostEdge(serverUrl, post));
g.addEdge(topicContainsPostEdge(serverUrl, post));
let replyToPostIndex = post.replyToPostIndex;

View File

@ -40,6 +40,16 @@ describe("plugins/discourse/createGraph", () => {
posts(): $ReadOnlyArray<Post> {
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 {
const post = this._posts.filter(
(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,
// 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.
@ -45,6 +45,13 @@ export interface DiscourseData {
* Returns undefined if no such post is available.
*/
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);
const tables = [
"CREATE TABLE users (username TEXT PRIMARY KEY)",
dedent`\
CREATE TABLE topics (
id INTEGER PRIMARY KEY,
title TEXT 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`\
@ -147,7 +156,8 @@ export class Mirror implements DiscourseData {
topic_id INTEGER NOT NULL,
index_within_topic INTEGER NOT NULL,
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 {
return this._db
.prepare(
@ -237,6 +254,9 @@ export class Mirror implements DiscourseData {
topicId,
authorUsername,
} = post;
db.prepare("INSERT OR IGNORE INTO users (username) VALUES (?)").run(
authorUsername
);
db.prepare(
dedent`\
REPLACE INTO posts (
@ -275,6 +295,9 @@ export class Mirror implements DiscourseData {
if (topicWithPosts != null) {
const {topic, posts} = topicWithPosts;
const {id, title, timestampMs, authorUsername} = topic;
db.prepare("INSERT OR IGNORE INTO users (username) VALUES (?)").run(
authorUsername
);
this._db
.prepare(
dedent`\

View File

@ -194,6 +194,21 @@ describe("plugins/discourse/mirror", () => {
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", () => {
it("only fetches new topics on `update`", async () => {
const {mirror, fetcher} = example();