diff --git a/src/plugins/discourse/referenceDetector.js b/src/plugins/discourse/referenceDetector.js new file mode 100644 index 0000000..214d333 --- /dev/null +++ b/src/plugins/discourse/referenceDetector.js @@ -0,0 +1,64 @@ +// @flow + +import type {NodeAddressT} from "../../core/graph"; +import type {ReferenceDetector, URL} from "../../core/references"; +import type {ReadRepository} from "./mirrorRepository"; +import {topicAddress, userAddress, postAddress} from "./address"; +import {linksToReferences} from "./references"; + +/** + * Discourse ReferenceDetector detector that relies on database lookups. + */ +export class DiscourseReferenceDetector implements ReferenceDetector { + data: ReadRepository; + + constructor(data: ReadRepository) { + this.data = data; + } + + addressFromUrl(url: URL): ?NodeAddressT { + const [reference] = linksToReferences([url]); + if (!reference) { + return null; + } + + switch (reference.type) { + case "TOPIC": { + // Just validating the topic exists. + if (this.data.topicById(reference.topicId)) { + return topicAddress(reference.serverUrl, reference.topicId); + } + break; + } + + case "POST": { + // For posts, we need to convert from topicId + index to a post ID. + // We're using it to validate the topic and post index exist as well. + const postId = this.data.findPostInTopic( + reference.topicId, + reference.postIndex + ); + if (postId) { + return postAddress(reference.serverUrl, postId); + } + break; + } + + case "USER": { + // Look up the username to validate it exists, and make sure we use + // the result. As this should correct our capitalization. See #1479. + const username = this.data.findUsername(reference.username); + if (username) { + return userAddress(reference.serverUrl, username); + } + break; + } + + default: { + throw new Error( + `Unexpected reference type: ${(reference.type: empty)}` + ); + } + } + } +} diff --git a/src/plugins/discourse/referenceDetector.test.js b/src/plugins/discourse/referenceDetector.test.js new file mode 100644 index 0000000..8176de4 --- /dev/null +++ b/src/plugins/discourse/referenceDetector.test.js @@ -0,0 +1,112 @@ +// @flow + +import Database from "better-sqlite3"; +import type {Topic, Post} from "./fetch"; +import {SqliteMirrorRepository} from "./mirrorRepository"; +import {DiscourseReferenceDetector} from "./referenceDetector"; +import {type NodeAddressT, NodeAddress} from "../../core/graph"; + +const TEST_URL = "https://example.com"; + +const emptyRepository = (): SqliteMirrorRepository => { + const db = new Database(":memory:"); + return new SqliteMirrorRepository(db, TEST_URL); +}; + +const maybeToParts = (a: ?NodeAddressT) => { + return a ? NodeAddress.toParts(a) : a; +}; + +describe("plugins/discourse/referenceDetector", () => { + describe("DiscourseReferenceDetector", () => { + it("should detect user reference", () => { + // Given + const repo = emptyRepository(); + const detector = new DiscourseReferenceDetector(repo); + const username = "PascalFan1988"; + repo.addUser(username); + + // When + const result = detector.addressFromUrl(`${TEST_URL}/u/pascalfan1988`); + + // Then + expect(maybeToParts(result)).toMatchInlineSnapshot(` + Array [ + "sourcecred", + "discourse", + "user", + "https://example.com", + "PascalFan1988", + ] + `); + }); + + it("should detect topic reference", () => { + // Given + const repo = emptyRepository(); + const detector = new DiscourseReferenceDetector(repo); + const topic: Topic = { + id: 123, + categoryId: 1, + title: "Sample topic", + timestampMs: 456789, + bumpedMs: 456999, + authorUsername: "credbot", + }; + repo.addTopic(topic); + + // When + const result = detector.addressFromUrl(`${TEST_URL}/t/random-slug/123`); + + // Then + expect(maybeToParts(result)).toMatchInlineSnapshot(` + Array [ + "sourcecred", + "discourse", + "topic", + "https://example.com", + "123", + ] + `); + }); + + it("should detect post reference", () => { + // Given + const repo = emptyRepository(); + const detector = new DiscourseReferenceDetector(repo); + const topic: Topic = { + id: 123, + categoryId: 1, + title: "Sample topic", + timestampMs: 456789, + bumpedMs: 456999, + authorUsername: "credbot", + }; + const p1: Post = { + id: 100, + topicId: 123, + indexWithinTopic: 1, + replyToPostIndex: null, + timestampMs: 456789, + authorUsername: "credbot", + cooked: "

Valid post

", + }; + repo.addTopic(topic); + repo.addPost(p1); + + // When + const result = detector.addressFromUrl(`${TEST_URL}/t/random-slug/123/1`); + + // Then + expect(maybeToParts(result)).toMatchInlineSnapshot(` + Array [ + "sourcecred", + "discourse", + "post", + "https://example.com", + "100", + ] + `); + }); + }); +});