Files
VRCX/src/services/database/mutualGraph.js

198 lines
7.4 KiB
JavaScript

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 metaTable = `${dbVars.userPrefix}_mutual_graph_meta`;
const pairs = entries instanceof Map ? entries : new Map();
await sqliteService.executeNonQuery('BEGIN');
try {
await sqliteService.executeNonQuery(
`DELETE FROM ${linkTable} WHERE friend_id NOT IN (SELECT friend_id FROM ${metaTable} WHERE opted_out = 1)`
);
await sqliteService.executeNonQuery(
`DELETE FROM ${friendTable} WHERE friend_id NOT IN (SELECT friend_id FROM ${metaTable} WHERE opted_out = 1)`
);
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;
}
},
async updateMutualsForFriend(friendId, mutualIds) {
if (!dbVars.userPrefix || !friendId) {
return;
}
const friendTable = `${dbVars.userPrefix}_mutual_graph_friends`;
const linkTable = `${dbVars.userPrefix}_mutual_graph_links`;
const safeFriendId = friendId.replace(/'/g, "''");
await sqliteService.executeNonQuery(
`INSERT OR REPLACE INTO ${friendTable} (friend_id) VALUES ('${safeFriendId}')`
);
await sqliteService.executeNonQuery(
`DELETE FROM ${linkTable} WHERE friend_id='${safeFriendId}'`
);
let edgeValues = '';
for (const mutual of mutualIds) {
if (!mutual) {
continue;
}
const safeMutualId = String(mutual).replace(/'/g, "''");
edgeValues += `('${safeFriendId}', '${safeMutualId}'),`;
}
if (edgeValues) {
edgeValues = edgeValues.slice(0, -1);
await sqliteService.executeNonQuery(
`INSERT OR REPLACE INTO ${linkTable} (friend_id, mutual_id) VALUES ${edgeValues}`
);
}
},
async getMutualCountForAllUsers() {
const mutualCountMap = new Map();
if (!dbVars.userPrefix) {
return mutualCountMap;
}
const linkTable = `${dbVars.userPrefix}_mutual_graph_links`;
await sqliteService.execute((dbRow) => {
const mutualId = dbRow[0];
const count = dbRow[1];
if (mutualId) {
mutualCountMap.set(mutualId, count);
}
}, `SELECT mutual_id, COUNT(*) FROM ${linkTable} GROUP BY mutual_id`);
return mutualCountMap;
},
async upsertMutualGraphMeta(friendId, { lastFetchedAt, optedOut }) {
if (!dbVars.userPrefix || !friendId) {
return;
}
const metaTable = `${dbVars.userPrefix}_mutual_graph_meta`;
const escapedId = friendId.replace(/'/g, "''");
const time = (lastFetchedAt || new Date().toISOString()).replace(
/'/g,
"''"
);
const optedOutInt = optedOut ? 1 : 0;
await sqliteService.executeNonQuery(
`INSERT OR REPLACE INTO ${metaTable} (friend_id, last_fetched_at, opted_out) VALUES ('${escapedId}', '${time}', ${optedOutInt})`
);
},
async bulkUpsertMutualGraphMeta(entries) {
if (!dbVars.userPrefix || !entries || entries.size === 0) {
return;
}
const metaTable = `${dbVars.userPrefix}_mutual_graph_meta`;
let values = '';
const now = new Date().toISOString();
entries.forEach(({ optedOut }, friendId) => {
if (!friendId) return;
const escapedId = friendId.replace(/'/g, "''");
const optedOutInt = optedOut ? 1 : 0;
values += `('${escapedId}', '${now}', ${optedOutInt}),`;
});
if (values) {
values = values.slice(0, -1);
await sqliteService.executeNonQuery(
`INSERT OR REPLACE INTO ${metaTable} (friend_id, last_fetched_at, opted_out) VALUES ${values}`
);
}
},
async getMutualGraphMeta() {
const metaMap = new Map();
if (!dbVars.userPrefix) {
return metaMap;
}
const metaTable = `${dbVars.userPrefix}_mutual_graph_meta`;
await sqliteService.execute((dbRow) => {
const friendId = dbRow[0];
if (friendId) {
metaMap.set(friendId, {
lastFetchedAt: dbRow[1] || null,
optedOut: dbRow[2] === 1
});
}
}, `SELECT friend_id, last_fetched_at, opted_out FROM ${metaTable}`);
return metaMap;
}
};
export { mutualGraph };