feat: mutual friend graph (#1491)

This commit is contained in:
pa
2025-11-18 23:30:00 +09:00
committed by Natsumi
parent 0bc9980cae
commit 424edb04e0
12 changed files with 1073 additions and 35 deletions

View File

@@ -5,6 +5,7 @@ import { friendLogHistory } from './database/friendLogHistory.js';
import { gameLog } from './database/gameLog.js';
import { memos } from './database/memos.js';
import { moderation } from './database/moderation.js';
import { mutualGraph } from './database/mutualGraph.js';
import { notifications } from './database/notifications.js';
import { tableAlter } from './database/tableAlter.js';
import { tableFixes } from './database/tableFixes.js';
@@ -32,6 +33,7 @@ const database = {
...tableAlter,
...tableFixes,
...tableSize,
...mutualGraph,
setMaxTableSize(limit) {
dbVars.maxTableSize = limit;
@@ -77,6 +79,12 @@ const database = {
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS ${dbVars.userPrefix}_notes (user_id TEXT PRIMARY KEY, display_name TEXT, note TEXT, created_at TEXT)`
);
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS ${dbVars.userPrefix}_mutual_graph_friends (friend_id TEXT PRIMARY KEY)`
);
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS ${dbVars.userPrefix}_mutual_graph_links (friend_id TEXT NOT NULL, mutual_id TEXT NOT NULL, PRIMARY KEY(friend_id, mutual_id))`
);
},
async initTables() {

View File

@@ -0,0 +1,92 @@
import { dbVars } from '../database';
import sqliteService from '../sqlite.js';
const mutualGraph = {
async getMutualGraphSnapshot() {
const snapshot = new Map();
if (!dbVars.userPrefix) {
return snapshot;
}
const friendTable = `${dbVars.userPrefix}_mutual_graph_friends`;
const linkTable = `${dbVars.userPrefix}_mutual_graph_links`;
await sqliteService.execute((dbRow) => {
const friendId = dbRow[0];
if (friendId && !snapshot.has(friendId)) {
snapshot.set(friendId, []);
}
}, `SELECT friend_id FROM ${friendTable}`);
await sqliteService.execute((dbRow) => {
const friendId = dbRow[0];
const mutualId = dbRow[1];
if (!friendId || !mutualId) {
return;
}
let list = snapshot.get(friendId);
if (!list) {
list = [];
snapshot.set(friendId, list);
}
list.push(mutualId);
}, `SELECT friend_id, mutual_id FROM ${linkTable}`);
return snapshot;
},
async saveMutualGraphSnapshot(entries) {
if (!dbVars.userPrefix) {
return;
}
const friendTable = `${dbVars.userPrefix}_mutual_graph_friends`;
const linkTable = `${dbVars.userPrefix}_mutual_graph_links`;
const pairs = entries instanceof Map ? entries : new Map();
await sqliteService.executeNonQuery('BEGIN');
try {
await sqliteService.executeNonQuery(`DELETE FROM ${friendTable}`);
await sqliteService.executeNonQuery(`DELETE FROM ${linkTable}`);
if (pairs.size === 0) {
await sqliteService.executeNonQuery('COMMIT');
return;
}
let friendValues = '';
let edgeValues = '';
pairs.forEach((mutualIds, friendId) => {
if (!friendId) {
return;
}
const safeFriendId = friendId.replace(/'/g, "''");
friendValues += `('${safeFriendId}'),`;
let collection = [];
if (Array.isArray(mutualIds)) {
collection = mutualIds;
} else if (mutualIds instanceof Set) {
collection = Array.from(mutualIds);
}
for (const mutual of collection) {
if (!mutual) {
continue;
}
const safeMutualId = String(mutual).replace(/'/g, "''");
edgeValues += `('${safeFriendId}', '${safeMutualId}'),`;
}
});
if (friendValues) {
friendValues = friendValues.slice(0, -1);
await sqliteService.executeNonQuery(
`INSERT OR REPLACE INTO ${friendTable} (friend_id) VALUES ${friendValues}`
);
}
if (edgeValues) {
edgeValues = edgeValues.slice(0, -1);
await sqliteService.executeNonQuery(
`INSERT OR REPLACE INTO ${linkTable} (friend_id, mutual_id) VALUES ${edgeValues}`
);
}
await sqliteService.executeNonQuery('COMMIT');
} catch (err) {
await sqliteService.executeNonQuery('ROLLBACK');
throw err;
}
}
};
export { mutualGraph };