Discourse: adds bumpedMsForTopic and topicsInCategories queries (#1458)

bumpedMsForTopic
For the given topic ID, retrieves the bumpedMs value.
Returns null, when the topic wasn't found.
Used by the new update code as a fallback value when making API
calls that don't contain the bumpedMs field.

topicsInCategories
Finds the TopicIds of topics that have one of the categoryIds as
it's category.
Useful to find out which topics a set of categories contains.
For example to implement the `recheckTopicsInCategories` mirror
option, or to locate topics for the initiative plugin.
This commit is contained in:
Robin van Boven 2019-11-26 11:02:27 +01:00 committed by GitHub
parent 564fd89b1e
commit 7deb0a3205
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 161 additions and 1 deletions

View File

@ -75,6 +75,11 @@ describe("plugins/discourse/createGraph", () => {
maxTopicId: this._topics.reduce((max, t) => Math.max(t.id, max), 0),
};
}
topicsInCategories() {
throw new Error(
"Method topicsInCategories should be unused for createGraph"
);
}
}
function example() {

View File

@ -3,7 +3,14 @@
import type {Database} from "better-sqlite3";
import stringify from "json-stable-stringify";
import dedent from "../../util/dedent";
import type {TopicId, PostId, Topic, Post, LikeAction} from "./fetch";
import type {
CategoryId,
TopicId,
PostId,
Topic,
Post,
LikeAction,
} from "./fetch";
// The version should be bumped any time the database schema is changed,
// so that the cache will be properly invalidated.
@ -45,6 +52,13 @@ export interface ReadRepository {
* Gets all of the like actions in the history.
*/
likes(): $ReadOnlyArray<LikeAction>;
/**
* Finds the TopicIds of topics that have one of the categoryIds as it's category.
*/
topicsInCategories(
categoryIds: $ReadOnlyArray<CategoryId>
): $ReadOnlyArray<TopicId>;
}
export type MaxIds = {|
@ -63,6 +77,12 @@ export interface MirrorRepository extends ReadRepository {
addTopic(topic: Topic): AddResult;
addPost(post: Post): AddResult;
addLike(like: LikeAction): AddResult;
/**
* For the given topic ID, retrieves the bumpedMs value.
* Returns null, when the topic wasn't found.
*/
bumpedMsForTopic(id: TopicId): number | null;
}
function toAddResult({
@ -293,6 +313,27 @@ export class SqliteMirrorRepository
return toAddResult(res);
}
topicsInCategories(
categoryIds: $ReadOnlyArray<CategoryId>
): $ReadOnlyArray<TopicId> {
return this._db
.prepare(
dedent`\
SELECT id FROM topics
WHERE category_id IN (${categoryIds.map((_) => "?").join(",")})
`
)
.all(...categoryIds)
.map((t) => t.id);
}
bumpedMsForTopic(id: TopicId): number | null {
const res = this._db
.prepare(`SELECT bumped_ms FROM topics WHERE id = :id`)
.get({id});
return res != null ? res.bumped_ms : null;
}
addPost(post: Post): AddResult {
this.addUser(post.authorUsername);
const res = this._db

View File

@ -4,6 +4,7 @@ import Database from "better-sqlite3";
import fs from "fs";
import tmp from "tmp";
import {SqliteMirrorRepository} from "./mirrorRepository";
import type {Topic} from "./fetch";
describe("plugins/discourse/mirrorRepository", () => {
it("rejects a different server url without changing the database", () => {
@ -24,4 +25,117 @@ describe("plugins/discourse/mirrorRepository", () => {
expect(() => new SqliteMirrorRepository(db, url1)).not.toThrow();
expect(fs.readFileSync(filename).toJSON()).toEqual(data);
});
it("bumpedMsForTopic finds an existing topic's bumpedMs", () => {
// 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",
};
// When
repository.addTopic(topic);
const bumpedMs = repository.bumpedMsForTopic(topic.id);
// Then
expect(bumpedMs).toEqual(topic.bumpedMs);
});
it("bumpedMsForTopic returns null when missing topic", () => {
// Given
const db = new Database(":memory:");
const url = "http://example.com";
const repository = new SqliteMirrorRepository(db, url);
// When
const bumpedMs = repository.bumpedMsForTopic(123);
// Then
expect(bumpedMs).toEqual(null);
});
it("topicsInCategories finds an an existing topic by categoryId", () => {
// Given
const db = new Database(":memory:");
const url = "http://example.com";
const repository = new SqliteMirrorRepository(db, url);
const topic: Topic = {
id: 123,
categoryId: 42,
title: "Sample topic",
timestampMs: 456789,
bumpedMs: 456999,
authorUsername: "credbot",
};
// When
repository.addTopic(topic);
const topicIds = repository.topicsInCategories([topic.categoryId]);
// Then
expect(topicIds).toEqual([topic.id]);
});
it("topicsInCategories with several categoryIds returns all 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: 16,
title: "Sample topic 2",
timestampMs: 456789,
bumpedMs: 456999,
authorUsername: "credbot",
};
// When
repository.addTopic(topic1);
repository.addTopic(topic2);
const topicIds = repository.topicsInCategories([
topic1.categoryId,
topic2.categoryId,
]);
// Then
expect(topicIds).toEqual([topic1.id, topic2.id]);
});
it("topicsInCategories without categoryIds gives no topicIds", () => {
// Given
const db = new Database(":memory:");
const url = "http://example.com";
const repository = new SqliteMirrorRepository(db, url);
const topic: Topic = {
id: 123,
categoryId: 42,
title: "Sample topic",
timestampMs: 456789,
bumpedMs: 456999,
authorUsername: "credbot",
};
// When
repository.addTopic(topic);
const topicIds = repository.topicsInCategories([]);
// Then
expect(topicIds).toEqual([]);
});
});