Skip to content

Commit

Permalink
Fix up scripts with batched iterator, add toggleAdmin (#170)
Browse files Browse the repository at this point in the history
  • Loading branch information
ekzhang authored Dec 22, 2024
1 parent 543e085 commit 9aec3b9
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 43 deletions.
14 changes: 6 additions & 8 deletions scripts/src/calcStats.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import admin from "firebase-admin";
import { getDatabase } from "firebase-admin/database";

const batchSize = 1000;

Expand All @@ -14,7 +14,7 @@ const batchSize = 1000;
*/
export async function calcStats() {
console.log("Loading users...");
const users = await admin.database().ref("users").orderByKey().get();
const users = await getDatabase().ref("users").orderByKey().get();
const userIds = Object.keys(users.val());
console.log("Done loading users!");

Expand All @@ -24,8 +24,7 @@ export async function calcStats() {

await Promise.all(
userIds.slice(i, i + batchSize).map(async (userId) => {
const games = await admin
.database()
const games = await getDatabase()
.ref(`userGames/${userId}`)
.once("value");

Expand All @@ -45,14 +44,13 @@ export async function calcStats() {

await Promise.all(
Object.keys(games.val()).map(async (gameId) => {
const game = await admin.database().ref(`games/${gameId}`).get();
const game = await getDatabase().ref(`games/${gameId}`).get();
if (game.child("status").val() !== "done") {
if (process.env.VERBOSE)
console.log(`[${userId}] Skipping ongoing game ${gameId}`);
return;
}
const gameData = await admin
.database()
const gameData = await getDatabase()
.ref(`gameData/${gameId}`)
.get();

Expand Down Expand Up @@ -116,7 +114,7 @@ export async function calcStats() {
updates[`${mode}/${variant}`] = stats[mode][variant];
}
}
await admin.database().ref(`userStats/${userId}`).update(updates);
await getDatabase().ref(`userStats/${userId}`).update(updates);
}),
);

Expand Down
10 changes: 4 additions & 6 deletions scripts/src/fixGames.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import assert from "assert";
import admin from "firebase-admin";
import { getDatabase } from "firebase-admin/database";

/** Fix games that have `endedAt === 0` due to a site migration bug in v3.0.0. */
export async function fixGames() {
const badGames = await admin
.database()
const badGames = await getDatabase()
.ref("games")
.orderByChild("endedAt")
.equalTo(0)
Expand All @@ -14,16 +13,15 @@ export async function fixGames() {
console.log(`Fixing game ${gameId}...`);
console.log({ [gameId]: game });
assert.strictEqual(game.status, "done");
const events = await admin
.database()
const events = await getDatabase()
.ref(`gameData/${gameId}/events`)
.once("value");
let lastTime = 0;
events.forEach((event) => {
lastTime = event.val().time;
});
console.log(`Setting endedAt = ${lastTime}...`);
await admin.database().ref(`games/${gameId}/endedAt`).set(lastTime);
await getDatabase().ref(`games/${gameId}/endedAt`).set(lastTime);
console.log("Done.\n");
}

Expand Down
20 changes: 14 additions & 6 deletions scripts/src/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import admin from "firebase-admin";
import { cert, initializeApp } from "firebase-admin/app";
import inquirer from "inquirer";
import process from "node:process";

import { calcStats } from "./calcStats.js";
import { fixGames } from "./fixGames.js";
import { listAdmins, listPatrons } from "./listUsers.js";
import { sanitizeNames } from "./sanitizeNames.js";
import { listAdmins, listPatrons, toggleAdmin } from "./users.js";

// Add scripts as functions to this array
const scripts = [listAdmins, listPatrons, sanitizeNames, fixGames, calcStats];
const scripts = [
listAdmins,
listPatrons,
toggleAdmin,
sanitizeNames,
fixGames,
calcStats,
];

admin.initializeApp({
credential: admin.credential.cert("./credential.json"),
initializeApp({
credential: cert("./credential.json"),
databaseURL: "https://setwithfriends.firebaseio.com",
});

Expand All @@ -30,4 +38,4 @@ async function main() {
}
}

main().then(() => require("process").exit());
main().then(() => process.exit());
21 changes: 0 additions & 21 deletions scripts/src/listUsers.js

This file was deleted.

4 changes: 2 additions & 2 deletions scripts/src/sanitizeNames.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import admin from "firebase-admin";
import { getDatabase } from "firebase-admin/database";

function sanitize(name) {
return (
Expand All @@ -9,7 +9,7 @@ function sanitize(name) {
}

export async function sanitizeNames() {
const users = await admin.database().ref("users").orderByKey().once("value");
const users = await getDatabase().ref("users").orderByKey().once("value");

const updates = [];

Expand Down
61 changes: 61 additions & 0 deletions scripts/src/users.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { getDatabase } from "firebase-admin/database";
import inquirer from "inquirer";

import { databaseIterator } from "./utils.js";

function displayUser(user) {
const name = user.child("name").val();
const lastOnline = new Date(
user.child("lastOnline").val(),
).toLocaleDateString();
return `${name} (last online: ${lastOnline})`;
}

export async function listAdmins() {
for await (const [userId, user] of databaseIterator("users")) {
if (user.child("admin").val()) {
console.log(userId, displayUser(user));
}
}
}

export async function listPatrons() {
for await (const [userId, user] of databaseIterator("users")) {
if (user.child("patron").val()) {
console.log(userId, displayUser(user));
}
}
}

export async function toggleAdmin() {
const { userId } = await inquirer.prompt([
{ type: "input", name: "userId", message: "Enter the user ID:" },
]);

const user = await getDatabase().ref(`users/${userId}`).get();
console.log(displayUser(user));

if (user.child("admin").val()) {
const { confirm } = await inquirer.prompt([
{
type: "confirm",
name: "confirm",
message: "User is admin, do you want to remove admin status?",
},
]);
if (confirm) {
await getDatabase().ref(`users/${userId}/admin`).remove();
}
} else {
const { confirm } = await inquirer.prompt([
{
type: "confirm",
name: "confirm",
message: "User is not admin, do you want to grant admin status?",
},
]);
if (confirm) {
await getDatabase().ref(`users/${userId}/admin`).set(true);
}
}
}
37 changes: 37 additions & 0 deletions scripts/src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { getDatabase } from "firebase-admin/database";

/**
* Iterate through a reference in the database, returning the children ordered
* by key in batches.
*
* @param {string} path The path to the reference to iterate through.
* @param {number} batchSize The number of children to fetch in each batch.
* @returns {AsyncGenerator<[string, import("firebase-admin/database").DataSnapshot]>}
*/
export async function* databaseIterator(path, batchSize = 1000) {
let lastKey = null;
while (true) {
const snap = lastKey
? await getDatabase()
.ref(path)
.orderByKey()
.startAfter(lastKey)
.limitToFirst(batchSize)
.get()
: await getDatabase()
.ref(path)
.orderByKey()
.limitToFirst(batchSize)
.get();
if (!snap.exists()) return;

const childKeys = [];
snap.forEach((child) => {
childKeys.push(child.key);
lastKey = child.key;
});
for (const key of childKeys) {
yield [key, snap.child(key)];
}
}
}

0 comments on commit 9aec3b9

Please sign in to comment.