mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
feat: event_stats reactions is now jsonb and updates keys directly
This commit is contained in:
parent
a1078de07b
commit
799774760a
5 changed files with 94 additions and 38 deletions
|
|
@ -30,7 +30,7 @@ interface EventStatsRow {
|
||||||
reposts_count: number;
|
reposts_count: number;
|
||||||
reactions_count: number;
|
reactions_count: number;
|
||||||
quotes_count: number;
|
quotes_count: number;
|
||||||
reactions: string;
|
reactions: { [key: string]: number };
|
||||||
zaps_amount: number;
|
zaps_amount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
41
src/db/migrations/042_event_stats_reactions_to_jsonb.ts
Normal file
41
src/db/migrations/042_event_stats_reactions_to_jsonb.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { Kysely, sql } from 'kysely';
|
||||||
|
|
||||||
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
|
await db.schema
|
||||||
|
.alterTable('event_stats')
|
||||||
|
.alterColumn('reactions', (ac) => ac.setNotNull()).execute();
|
||||||
|
|
||||||
|
await db.schema
|
||||||
|
.alterTable('event_stats')
|
||||||
|
.alterColumn('reactions', (ac) => ac.dropDefault()).execute();
|
||||||
|
|
||||||
|
// Type 'text' cannot be converted automatically to 'jsonb',
|
||||||
|
// so the 'USING' keyword must be used, and there's no way to do this with kysely,
|
||||||
|
// this is why raw SQL is used.
|
||||||
|
await sql`
|
||||||
|
ALTER TABLE event_stats
|
||||||
|
ALTER COLUMN reactions TYPE jsonb USING reactions::jsonb;
|
||||||
|
`.execute(db);
|
||||||
|
|
||||||
|
await db.schema
|
||||||
|
.alterTable('event_stats')
|
||||||
|
.alterColumn('reactions', (ac) => ac.setDefault('{}')).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(db: Kysely<any>): Promise<void> {
|
||||||
|
await db.schema
|
||||||
|
.alterTable('event_stats')
|
||||||
|
.alterColumn('reactions', (ac) => ac.dropNotNull()).execute();
|
||||||
|
|
||||||
|
await db.schema
|
||||||
|
.alterTable('event_stats')
|
||||||
|
.alterColumn('reactions', (ac) => ac.dropDefault()).execute();
|
||||||
|
|
||||||
|
await db.schema
|
||||||
|
.alterTable('event_stats')
|
||||||
|
.alterColumn('reactions', (ac) => ac.setDataType('text')).execute();
|
||||||
|
|
||||||
|
await db.schema
|
||||||
|
.alterTable('event_stats')
|
||||||
|
.alterColumn('reactions', (ac) => ac.setDefault('{}')).execute();
|
||||||
|
}
|
||||||
|
|
@ -91,7 +91,7 @@ export function assembleEvents(
|
||||||
|
|
||||||
const eventStats = stats.events.map((stat) => ({
|
const eventStats = stats.events.map((stat) => ({
|
||||||
...stat,
|
...stat,
|
||||||
reactions: JSON.parse(stat.reactions),
|
reactions: stat.reactions,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
for (const event of a) {
|
for (const event of a) {
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ Deno.test('updateStats with kind 7 increments reactions count', async () => {
|
||||||
|
|
||||||
const stats = await getEventStats(db.kysely, note.id);
|
const stats = await getEventStats(db.kysely, note.id);
|
||||||
|
|
||||||
assertEquals(stats!.reactions, JSON.stringify({ '+': 1, '😂': 1 }));
|
assertEquals(stats!.reactions, { '+': 1, '😂': 1 });
|
||||||
assertEquals(stats!.reactions_count, 2);
|
assertEquals(stats!.reactions_count, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -158,7 +158,7 @@ Deno.test('updateStats with kind 5 decrements reactions count', async () => {
|
||||||
|
|
||||||
const stats = await getEventStats(db.kysely, note.id);
|
const stats = await getEventStats(db.kysely, note.id);
|
||||||
|
|
||||||
assertEquals(stats!.reactions, JSON.stringify({}));
|
assertEquals(stats!.reactions, {});
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test('countAuthorStats counts author stats from the database', async () => {
|
Deno.test('countAuthorStats counts author stats from the database', async () => {
|
||||||
|
|
|
||||||
|
|
@ -107,20 +107,28 @@ async function handleEvent6(kysely: Kysely<DittoTables>, event: NostrEvent, x: n
|
||||||
/** Update stats for kind 7 event. */
|
/** Update stats for kind 7 event. */
|
||||||
async function handleEvent7(kysely: Kysely<DittoTables>, event: NostrEvent, x: number): Promise<void> {
|
async function handleEvent7(kysely: Kysely<DittoTables>, event: NostrEvent, x: number): Promise<void> {
|
||||||
const id = event.tags.findLast(([name]) => name === 'e')?.[1];
|
const id = event.tags.findLast(([name]) => name === 'e')?.[1];
|
||||||
// the '+' and '-' signs are considered emojis
|
// the '+' and '-' signs are considered emojis.
|
||||||
const emoji = event.content;
|
const emoji = event.content;
|
||||||
|
|
||||||
if (id && emoji && (['+', '-'].includes(emoji) || /^\p{RGI_Emoji}$/v.test(emoji))) {
|
if (id && emoji && (['+', '-'].includes(emoji) || /^\p{RGI_Emoji}$/v.test(emoji))) {
|
||||||
await kysely.updateTable('event_stats')
|
const empty = {
|
||||||
.set((eb) => {
|
...getEmpty_event_stats(id),
|
||||||
|
reactions: { [emoji]: x },
|
||||||
|
reactions_count: x,
|
||||||
|
};
|
||||||
|
|
||||||
|
await kysely.insertInto('event_stats')
|
||||||
|
.values(empty)
|
||||||
|
.onConflict((oc) =>
|
||||||
|
oc.column('event_id').doUpdateSet((eb) => {
|
||||||
// Updated reactions.
|
// Updated reactions.
|
||||||
const result = eb.fn('jsonb_set', [
|
const result = eb.fn('jsonb_set', [
|
||||||
sql`${eb.ref('reactions')}::jsonb`,
|
eb.ref('event_stats.reactions'), // Target.
|
||||||
sql<string[]>`ARRAY[${emoji}]`,
|
sql<string[]>`ARRAY[${emoji}]`, // Path.
|
||||||
eb.case()
|
eb.case()
|
||||||
.when(sql`reactions::jsonb -> ${emoji}`, 'is', null)
|
.when(sql`event_stats.reactions -> ${emoji}`, 'is', null)
|
||||||
.then(sql`${x}::jsonb`) // Set the emoji count for the first time.
|
.then(sql`${x}::jsonb`) // New value: Initialize the emoji count for the specific 'emoji'.
|
||||||
.else(eb.fn('to_jsonb', [sql`(reactions::jsonb -> ${emoji})::int + ${x}`])) // Increment or decrement the emoji count.
|
.else(eb.fn('to_jsonb', [sql`(event_stats.reactions -> ${emoji})::int + ${x}`])) // New value: Increment or decrement the emoji count.
|
||||||
.end(),
|
.end(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -129,14 +137,14 @@ async function handleEvent7(kysely: Kysely<DittoTables>, event: NostrEvent, x: n
|
||||||
.when(sql`(${result} -> ${emoji})::int`, '<', 1)
|
.when(sql`(${result} -> ${emoji})::int`, '<', 1)
|
||||||
.then(sql`${result} - ${emoji}`)
|
.then(sql`${result} - ${emoji}`)
|
||||||
.else(result)
|
.else(result)
|
||||||
.end() as ValueExpression<DittoTables, 'event_stats', string>;
|
.end() as ValueExpression<DittoTables, 'event_stats', { [key: string]: number }>;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
reactions: cleanedReactions,
|
reactions: cleanedReactions,
|
||||||
reactions_count: eb('reactions_count', '+', x),
|
reactions_count: eb('event_stats.reactions_count', '+', x),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.where('event_id', '=', id)
|
)
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -243,15 +251,7 @@ export async function updateEventStats(
|
||||||
eventId: string,
|
eventId: string,
|
||||||
fn: (prev: DittoTables['event_stats']) => UpdateObject<DittoTables, 'event_stats'>,
|
fn: (prev: DittoTables['event_stats']) => UpdateObject<DittoTables, 'event_stats'>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const empty: DittoTables['event_stats'] = {
|
const empty = getEmpty_event_stats(eventId);
|
||||||
event_id: eventId,
|
|
||||||
replies_count: 0,
|
|
||||||
reposts_count: 0,
|
|
||||||
reactions_count: 0,
|
|
||||||
quotes_count: 0,
|
|
||||||
zaps_amount: 0,
|
|
||||||
reactions: '{}',
|
|
||||||
};
|
|
||||||
|
|
||||||
const prev = await kysely
|
const prev = await kysely
|
||||||
.selectFrom('event_stats')
|
.selectFrom('event_stats')
|
||||||
|
|
@ -319,3 +319,18 @@ export async function refreshAuthorStats(
|
||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns an empty event_stats object. */
|
||||||
|
function getEmpty_event_stats(id: string) {
|
||||||
|
const empty: DittoTables['event_stats'] = {
|
||||||
|
event_id: id,
|
||||||
|
replies_count: 0,
|
||||||
|
reposts_count: 0,
|
||||||
|
reactions_count: 0,
|
||||||
|
quotes_count: 0,
|
||||||
|
zaps_amount: 0,
|
||||||
|
reactions: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue