Skip to content

Commit

Permalink
Emoji reactions v1
Browse files Browse the repository at this point in the history
  • Loading branch information
TimDaub committed Jan 31, 2025
1 parent 353fcdf commit 61ed231
Show file tree
Hide file tree
Showing 5 changed files with 428 additions and 162 deletions.
78 changes: 77 additions & 1 deletion src/cache.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,42 @@ export function initializeNotifications() {
`);
}

export function initializeReactions() {
const tableExists = db
.prepare(
"SELECT name FROM sqlite_master WHERE type='table' AND name='reactions'",
)
.get();

if (tableExists) {
log(
"Aborting cache.initializeReactions early because table already exists",
);
return;
}

log("Creating reactions table");
db.exec(`
CREATE TABLE reactions (
id TEXT PRIMARY KEY NOT NULL,
comment_id TEXT NOT NULL,
timestamp INTEGER NOT NULL,
title TEXT NOT NULL,
signer TEXT NOT NULL,
identity TEXT NOT NULL,
FOREIGN KEY(comment_id) REFERENCES comments(id)
);
CREATE INDEX IF NOT EXISTS idx_reactions_comment_id ON reactions(comment_id);
CREATE INDEX IF NOT EXISTS idx_reactions_timestamp ON reactions(timestamp);
CREATE INDEX IF NOT EXISTS idx_reactions_identity ON reactions(identity);
`);
}

export function isReactionComment(title) {
const trimmedTitle = title.trim();
return /^\p{Emoji}+$/u.test(trimmedTitle);
}

export async function getRecommendations(candidates, fingerprint, identity) {
if (identity) {
const commentedStories = db
Expand Down Expand Up @@ -704,15 +740,33 @@ export function getSubmission(index, href, identityFilter, hrefs) {
.all(submission.id)
.map((comment) => {
const [, index] = comment.id.split("0x");
const originalCommentId = comment.id;
delete comment.id;

const submissionId = comment.submission_id;
delete comment.submission_id;

const reactions = db
.prepare(
`
SELECT title as emoji, COUNT(*) as count, GROUP_CONCAT(identity) as reactors
FROM reactions
WHERE comment_id = ?
GROUP BY title
ORDER BY count DESC, timestamp ASC
`,
)
.all(originalCommentId);

return {
...comment,
submissionId,
index,
type: "comment",
reactions: reactions.map((reaction) => ({
emoji: reaction.emoji,
count: reaction.count,
reactors: reaction.reactors.split(","),
})),
};
});

Expand Down Expand Up @@ -815,6 +869,28 @@ export function insertMessage(message) {
);
}
} else if (type === "comment") {
if (isReactionComment(title)) {
// Insert reaction
const insertReaction = db.prepare(
`INSERT INTO reactions (id, comment_id, timestamp, title, signer, identity) VALUES (?, ?, ?, ?, ?, ?)`,
);
try {
insertReaction.run(
`kiwi:0x${index}`,
href,
timestamp,
title.trim(),
signer,
identity,
);
} catch (err) {
log(
`Failing to insert reaction "${title}", error: "${err.toString()}"`,
);
}
return;
}

// Insert comment
const insertComment = db.prepare(
`INSERT INTO comments (id, submission_id, timestamp, title, signer, identity) VALUES (?, ?, ?, ?, ?, ?)`,
Expand Down
1 change: 1 addition & 0 deletions src/http.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,7 @@ export async function launch(trie, libp2p) {
try {
submission = getSubmission(index);
} catch (e) {
log(`/api/v1/stories: Error in getSubmission: ${err.stack}`);
const code = 404;
const httpMessage = "Not Found";
const details = "Couldn't find the submission";
Expand Down
1 change: 1 addition & 0 deletions src/launch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ await Promise.allSettled([

cache.initialize([...upvotes, ...comments]);
cache.initializeNotifications();
cache.initializeReactions();

store
.cache(upvotes, comments)
Expand Down
27 changes: 25 additions & 2 deletions src/store.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { EIP712_MESSAGE } from "./constants.mjs";
import { elog } from "./utils.mjs";
import * as messages from "./topics/messages.mjs";
import { newWalk } from "./WalkController.mjs";
import { insertMessage } from "./cache.mjs";
import { insertMessage, isReactionComment } from "./cache.mjs";
import { triggerNotification } from "./subscriptions.mjs";

const maxReaders = 500;
Expand All @@ -41,6 +41,7 @@ const piscina = new Piscina({
});

export const upvotes = new Set();
export const reactions = new Set();
export const commentCounts = new Map();

// TODO: This function is badly named, it should be renamed to
Expand All @@ -60,14 +61,27 @@ export function passes(marker) {
}
return !exists;
}

export function passesReaction(marker) {
const exists = reactions.has(marker);
if (!exists) {
reactions.add(marker);
}
return !exists;
}

export async function cache(upvotes, comments) {
log("Caching upvote ids of upvotes, this can take a minute...");
for (const { identity, href, type } of upvotes) {
const marker = upvoteID(identity, href, type);
passes(marker);
}
for (const { href } of comments) {
for (const { href, title, identity } of comments) {
addComment(href);
if (isReactionComment(title)) {
const reactionMarker = upvoteID(identity, href, "reaction");
passesReaction(reactionMarker);
}
}
}

Expand Down Expand Up @@ -312,6 +326,15 @@ async function atomicPut(trie, message, identity, accounts, delegations) {
log(err);
throw new Error(err);
}
if (isReactionComment(message.title)) {
const reactionMarker = upvoteID(identity, message.href, "reaction");
const legit = await passesReaction(reactionMarker);
if (!legit) {
const err = `Reaction with marker "${reactionMarker}" doesn't pass legitimacy criteria (duplicate). It was probably submitted and accepted before.`;
log(err);
throw new Error(err);
}
}
}

try {
Expand Down
Loading

0 comments on commit 61ed231

Please sign in to comment.