Initiatives: add queries to mirror to implement DiscourseQueries (#1490)
We defined a DiscourseQueries interface, intended as a subset of the Discourse plugin's MirrorRepository methods. This subset is used by the Initiatives plugin to source Iniaitive data. We're now adding the new methods it needed to the MirrorRepository.
This commit is contained in:
parent
adec1b0b5d
commit
5cf0fba634
|
@ -83,6 +83,12 @@ describe("plugins/discourse/createGraph", () => {
|
||||||
findUsername() {
|
findUsername() {
|
||||||
throw new Error("Method findUsername should be unused by createGraph");
|
throw new Error("Method findUsername should be unused by createGraph");
|
||||||
}
|
}
|
||||||
|
topicById() {
|
||||||
|
throw new Error("Method topicById should be unused by createGraph");
|
||||||
|
}
|
||||||
|
postsInTopic() {
|
||||||
|
throw new Error("Method postsInTopic should be unused by createGraph");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function example() {
|
function example() {
|
||||||
|
|
|
@ -67,6 +67,19 @@ export interface ReadRepository {
|
||||||
* Note: input username is case-insensitive.
|
* Note: input username is case-insensitive.
|
||||||
*/
|
*/
|
||||||
findUsername(username: string): ?string;
|
findUsername(username: string): ?string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a Topic by ID.
|
||||||
|
*/
|
||||||
|
topicById(id: TopicId): ?Topic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a number of Posts in a given Topic. Starting with the first post,
|
||||||
|
* ordered by `indexWithinTopic`.
|
||||||
|
*
|
||||||
|
* numberOfPosts: the maximum number of posts to get (may return fewer).
|
||||||
|
*/
|
||||||
|
postsInTopic(topicId: TopicId, numberOfPosts: number): $ReadOnlyArray<Post>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SyncHeads = {|
|
export type SyncHeads = {|
|
||||||
|
@ -278,6 +291,36 @@ export class SqliteMirrorRepository
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
topicById(id: TopicId): ?Topic {
|
||||||
|
const res = this._db
|
||||||
|
.prepare(
|
||||||
|
dedent`\
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
category_id,
|
||||||
|
title,
|
||||||
|
timestamp_ms,
|
||||||
|
bumped_ms,
|
||||||
|
author_username
|
||||||
|
FROM topics
|
||||||
|
WHERE id = :id`
|
||||||
|
)
|
||||||
|
.get({id});
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: res.id,
|
||||||
|
categoryId: res.category_id,
|
||||||
|
title: res.title,
|
||||||
|
timestampMs: res.timestamp_ms,
|
||||||
|
bumpedMs: res.bumped_ms,
|
||||||
|
authorUsername: res.author_username,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
posts(): $ReadOnlyArray<Post> {
|
posts(): $ReadOnlyArray<Post> {
|
||||||
return this._db
|
return this._db
|
||||||
.prepare(
|
.prepare(
|
||||||
|
@ -304,6 +347,35 @@ export class SqliteMirrorRepository
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postsInTopic(topicId: TopicId, numberOfPosts: number): $ReadOnlyArray<Post> {
|
||||||
|
return this._db
|
||||||
|
.prepare(
|
||||||
|
dedent`\
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
timestamp_ms,
|
||||||
|
author_username,
|
||||||
|
topic_id,
|
||||||
|
index_within_topic,
|
||||||
|
reply_to_post_index,
|
||||||
|
cooked
|
||||||
|
FROM posts
|
||||||
|
WHERE topic_id = :topic_id
|
||||||
|
ORDER BY index_within_topic ASC
|
||||||
|
LIMIT :max`
|
||||||
|
)
|
||||||
|
.all({topic_id: topicId, max: numberOfPosts})
|
||||||
|
.map((x) => ({
|
||||||
|
id: x.id,
|
||||||
|
timestampMs: x.timestamp_ms,
|
||||||
|
authorUsername: x.author_username,
|
||||||
|
topicId: x.topic_id,
|
||||||
|
indexWithinTopic: x.index_within_topic,
|
||||||
|
replyToPostIndex: x.reply_to_post_index,
|
||||||
|
cooked: x.cooked,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
users(): $ReadOnlyArray<string> {
|
users(): $ReadOnlyArray<string> {
|
||||||
return this._db
|
return this._db
|
||||||
.prepare("SELECT username FROM users")
|
.prepare("SELECT username FROM users")
|
||||||
|
|
|
@ -302,4 +302,96 @@ describe("plugins/discourse/mirrorRepository", () => {
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
}).toThrow("FOREIGN KEY constraint failed");
|
}).toThrow("FOREIGN KEY constraint failed");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("topicById gets the matching topics", () => {
|
||||||
|
// Given
|
||||||
|
const db = new Database(":memory:");
|
||||||
|
const url = "http://example.com";
|
||||||
|
const repository = new SqliteMirrorRepository(db, url);
|
||||||
|
const topic1: Topic = {
|
||||||
|
id: 123,
|
||||||
|
categoryId: 42,
|
||||||
|
title: "Sample topic 1",
|
||||||
|
timestampMs: 456789,
|
||||||
|
bumpedMs: 456999,
|
||||||
|
authorUsername: "credbot",
|
||||||
|
};
|
||||||
|
const topic2: Topic = {
|
||||||
|
id: 456,
|
||||||
|
categoryId: 42,
|
||||||
|
title: "Sample topic 2",
|
||||||
|
timestampMs: 456789,
|
||||||
|
bumpedMs: 456999,
|
||||||
|
authorUsername: "credbot",
|
||||||
|
};
|
||||||
|
|
||||||
|
// When
|
||||||
|
repository.addTopic(topic1);
|
||||||
|
repository.addTopic(topic2);
|
||||||
|
const actualT1 = repository.topicById(topic1.id);
|
||||||
|
const actualT2 = repository.topicById(topic2.id);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(actualT1).toEqual(topic1);
|
||||||
|
expect(actualT2).toEqual(topic2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("postsInTopic gets the number of posts requested", () => {
|
||||||
|
// Given
|
||||||
|
const db = new Database(":memory:");
|
||||||
|
const url = "http://example.com";
|
||||||
|
const repository = new SqliteMirrorRepository(db, url);
|
||||||
|
const topic: Topic = {
|
||||||
|
id: 123,
|
||||||
|
categoryId: 1,
|
||||||
|
title: "Sample topic",
|
||||||
|
timestampMs: 456789,
|
||||||
|
bumpedMs: 456999,
|
||||||
|
authorUsername: "credbot",
|
||||||
|
};
|
||||||
|
const p1: Post = {
|
||||||
|
id: 100,
|
||||||
|
topicId: 123,
|
||||||
|
indexWithinTopic: 0,
|
||||||
|
replyToPostIndex: null,
|
||||||
|
timestampMs: 456789,
|
||||||
|
authorUsername: "credbot",
|
||||||
|
cooked: "<p>Valid post</p>",
|
||||||
|
};
|
||||||
|
const p2: Post = {
|
||||||
|
// Deliberately scramble id, order of `indexWithinTopic` should matter.
|
||||||
|
id: 121,
|
||||||
|
topicId: 123,
|
||||||
|
indexWithinTopic: 1,
|
||||||
|
replyToPostIndex: null,
|
||||||
|
timestampMs: 456888,
|
||||||
|
authorUsername: "credbot",
|
||||||
|
cooked: "<p>Follow up 1</p>",
|
||||||
|
};
|
||||||
|
const p3: Post = {
|
||||||
|
id: 102,
|
||||||
|
topicId: 123,
|
||||||
|
indexWithinTopic: 2,
|
||||||
|
replyToPostIndex: null,
|
||||||
|
timestampMs: 456999,
|
||||||
|
authorUsername: "credbot",
|
||||||
|
cooked: "<p>Follow up 2</p>",
|
||||||
|
};
|
||||||
|
|
||||||
|
// When
|
||||||
|
repository.addTopic(topic);
|
||||||
|
// Deliberately scramble the adding order, order of `indexWithinTopic` should matter.
|
||||||
|
repository.addPost(p3);
|
||||||
|
repository.addPost(p1);
|
||||||
|
repository.addPost(p2);
|
||||||
|
const posts0 = repository.postsInTopic(topic.id, 0);
|
||||||
|
const posts2 = repository.postsInTopic(topic.id, 2);
|
||||||
|
const posts5 = repository.postsInTopic(topic.id, 5);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
// Note: these are in order, starting from the opening post.
|
||||||
|
expect(posts0).toEqual([]);
|
||||||
|
expect(posts2).toEqual([p1, p2]);
|
||||||
|
expect(posts5).toEqual([p1, p2, p3]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
type DiscourseQueries,
|
type DiscourseQueries,
|
||||||
} from "./discourse";
|
} from "./discourse";
|
||||||
import {type Initiative} from "./initiative";
|
import {type Initiative} from "./initiative";
|
||||||
|
import type {ReadRepository} from "../discourse/mirrorRepository";
|
||||||
import type {Topic, Post, CategoryId, TopicId} from "../discourse/fetch";
|
import type {Topic, Post, CategoryId, TopicId} from "../discourse/fetch";
|
||||||
import {NodeAddress} from "../../core/graph";
|
import {NodeAddress} from "../../core/graph";
|
||||||
import dedent from "../../util/dedent";
|
import dedent from "../../util/dedent";
|
||||||
|
@ -156,6 +157,14 @@ describe("plugins/initiatives/discourse", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("DiscourseQueries", () => {
|
||||||
|
it("should be a subset of the ReadRepository interface", () => {
|
||||||
|
const _unused_toDiscourseQueries = (
|
||||||
|
x: ReadRepository
|
||||||
|
): DiscourseQueries => x;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("DiscourseInitiativeRepository", () => {
|
describe("DiscourseInitiativeRepository", () => {
|
||||||
it("uses topicsInCategories to find initiative topics", () => {
|
it("uses topicsInCategories to find initiative topics", () => {
|
||||||
// Given
|
// Given
|
||||||
|
|
Loading…
Reference in New Issue