···44import { handleCreate } from './emojiStats.js';
55import logger from './logger.js';
66import { redis } from './redis.js';
77+import fs from 'node:fs';
7889let jetstream: Jetstream;
910let cursor = 0;
···1415}
15161617export const initializeJetstream = async () => {
1717- const result = await redis.get('cursor');
1818- if (result === null) {
1818+ console.log(import.meta.url);
1919+ const cursorOverridePath = new URL('../../../../CURSOR_OVERRIDE.TXT', import.meta.url);
2020+2121+ if (fs.existsSync(cursorOverridePath)) {
2222+ try {
2323+ const overrideCursor = fs.readFileSync(cursorOverridePath, 'utf8').trim();
2424+ cursor = parseInt(overrideCursor, 10);
2525+2626+ if (isNaN(cursor)) {
2727+ throw new Error('Invalid cursor value in CURSOR_OVERRIDE.TXT');
2828+ }
2929+3030+ await redis.set('cursor', cursor.toString());
3131+ logger.info(`Cursor overridden with value: ${cursor} (${epochUsToDateTime(cursor)})`);
3232+3333+ fs.unlinkSync(cursorOverridePath);
3434+ logger.info('CURSOR_OVERRIDE.TXT file deleted after successful override');
3535+ } catch (error) {
3636+ logger.error(`Error processing CURSOR_OVERRIDE.TXT: ${(error as Error).message}`);
3737+ }
3838+ } else {
3939+ const result = await redis.get('cursor');
4040+ if (result === null) {
1941 logger.info('No cursor found, initializing with current epoch in microseconds...');
2042 cursor = Math.floor(Date.now() * 1000);
2143 await redis.set('cursor', cursor.toString());
2244 logger.info(`Initialized new cursor with value: ${cursor} (${epochUsToDateTime(cursor)})`);
4545+ } else {
4646+ logger.info(`Found existing cursor in Redis: ${result} (${epochUsToDateTime(Number(result))})`);
4747+ }
2348 }
2424- logger.info(`Found existing cursor in Redis: ${result} (${epochUsToDateTime(Number(result))})`);
25492650 jetstream = new Jetstream({
2751 wantedCollections: ['app.bsky.feed.post'],
+1-1
packages/backend/src/lib/lua/incrementEmojis.lua
···1212-- Increment per-language emoji counts and global language stats
1313for _, lang in ipairs(langs) do
1414 for _, emoji in ipairs(emojis) do
1515- redis.call('ZINCRBY', lang, 1, emoji) -- langKey being pt, ja, UNKNOWN, etc.
1515+ redis.call('ZINCRBY', lang, 1, emoji) -- langKey being pt, ja, unknown, etc.
1616 redis.call('ZINCRBY', 'languageStats', 1, lang) -- languageStats is the counter for per-language emoji count
1717 end
1818end
+14
packages/backend/src/lib/postgres.ts
···11+import { Kysely, PostgresDialect } from 'kysely';
22+import type { DB } from './schema.js';
33+import pg from 'pg';
44+const { Pool } = pg;
55+66+const db = new Kysely<DB>({
77+ dialect: new PostgresDialect({
88+ pool: new Pool({
99+ connectionString: process.env.DATABASE_URL,
1010+ }),
1111+ }),
1212+});
1313+1414+export { db };
+43
packages/backend/src/lib/schema.d.ts
···11+/**
22+ * This file was generated by kysely-codegen.
33+ * Please do not edit it manually.
44+ */
55+66+import type { ColumnType } from "kysely";
77+88+export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
99+ ? ColumnType<S, I | undefined, U>
1010+ : ColumnType<T, T | undefined, T>;
1111+1212+export type Timestamp = ColumnType<Date, Date | string>;
1313+1414+export interface Emojis {
1515+ created_at: Generated<Timestamp>;
1616+ emoji: string;
1717+ id: Generated<number>;
1818+ lang: string;
1919+ post_id: number;
2020+}
2121+2222+export interface EmojiStats {
2323+ count: Generated<number>;
2424+ created_at: Generated<Timestamp>;
2525+ emoji: string;
2626+ lang: string;
2727+}
2828+2929+export interface Posts {
3030+ cid: string;
3131+ created_at: Generated<Timestamp>;
3232+ did: string;
3333+ has_emojis: Generated<boolean>;
3434+ id: Generated<number>;
3535+ langs: Generated<string[]>;
3636+ rkey: string;
3737+}
3838+3939+export interface DB {
4040+ emoji_stats: EmojiStats;
4141+ emojis: Emojis;
4242+ posts: Posts;
4343+}
···11+import pg from 'pg'
22+const { Client } = pg
33+44+const client = new Client({
55+ connectionString: process.env.DATABASE_URL!,
66+});
77+88+await client.connect();
99+1010+export async function createTables() {
1111+ await client.query(`
1212+ CREATE TABLE IF NOT EXISTS posts (
1313+ id SERIAL PRIMARY KEY,
1414+ cid TEXT NOT NULL, -- 64 characters
1515+ did TEXT NOT NULL, -- 32 characters
1616+ rkey TEXT NOT NULL, -- 13 characters
1717+ has_emojis BOOLEAN NOT NULL DEFAULT FALSE,
1818+ langs TEXT[] NOT NULL DEFAULT '{}',
1919+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT (now() at time zone 'utc')
2020+ );
2121+2222+ CREATE TABLE IF NOT EXISTS emojis (
2323+ id SERIAL PRIMARY KEY,
2424+ post_id INTEGER NOT NULL,
2525+ emoji TEXT NOT NULL,
2626+ lang TEXT NOT NULL, -- 2 or 5 characters
2727+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT (now() at time zone 'utc')
2828+ );
2929+3030+ CREATE TABLE IF NOT EXISTS emoji_stats (
3131+ lang TEXT NOT NULL, -- 2 or 5 characters
3232+ emoji TEXT NOT NULL,
3333+ count INTEGER NOT NULL DEFAULT 0,
3434+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT (now() at time zone 'utc'),
3535+ PRIMARY KEY (lang, emoji)
3636+ );
3737+ `);
3838+}
3939+4040+createTables().catch((e: unknown) => { console.error(e); }).finally(() => void client.end());
4141+