mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-10 16:28:17 +03:00
feat: start actionising on the west coast
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test": "vitest",
|
||||
"test:watch": "vitest",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe } from "vitest";
|
||||
import { api } from "./setup"
|
||||
import { api } from "./setup";
|
||||
import { it } from "vitest";
|
||||
|
||||
describe("GET /activity", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe } from "vitest";
|
||||
import { api } from "./setup"
|
||||
import { api } from "./setup";
|
||||
import { it } from "vitest";
|
||||
|
||||
describe("GET /config/game-support", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe } from "vitest";
|
||||
import { api } from "./setup"
|
||||
import { api } from "./setup";
|
||||
import { it } from "vitest";
|
||||
|
||||
describe("GET /games", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe } from "vitest";
|
||||
import { api } from "./setup"
|
||||
import { api } from "./setup";
|
||||
import { it } from "vitest";
|
||||
|
||||
describe("GET /imports/:importID", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe } from "vitest";
|
||||
import { api } from "./setup"
|
||||
import { api } from "./setup";
|
||||
import { it } from "vitest";
|
||||
|
||||
describe("GET /scores/:scoreID", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe } from "vitest";
|
||||
import { api } from "./setup"
|
||||
import { api } from "./setup";
|
||||
import { it } from "vitest";
|
||||
|
||||
describe("GET /search", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe } from "vitest";
|
||||
import { api } from "./setup"
|
||||
import { api } from "./setup";
|
||||
import { it } from "vitest";
|
||||
|
||||
describe("GET /sessions/:sessionID", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe } from "vitest";
|
||||
import { api } from "./setup"
|
||||
import { api } from "./setup";
|
||||
import { it } from "vitest";
|
||||
|
||||
describe("GET /status", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe } from "vitest";
|
||||
import { api } from "./setup"
|
||||
import { api } from "./setup";
|
||||
import { it } from "vitest";
|
||||
|
||||
describe("GET /users", () => {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import configTachi from "eslint-config-tachi";
|
||||
|
||||
export default [
|
||||
...configTachi.base,
|
||||
configTachi.node,
|
||||
{
|
||||
ignores: ["js/**", "node_modules/**"],
|
||||
},
|
||||
];
|
||||
@@ -36,7 +36,7 @@
|
||||
"dependencies": {
|
||||
"pino": "catalog:",
|
||||
"pino-pretty": "catalog:",
|
||||
"zod": "^4.3.6"
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript/native-preview": "catalog:",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AppendLogCtx, log as baseLogger } from "#log.js";
|
||||
import z, { ZodObject } from "zod";
|
||||
import { type ZodObject } from "zod";
|
||||
|
||||
export type ActionResult = "GOOD" | "BAD" | "THROW";
|
||||
export type ActionResult = "BAD" | "GOOD" | "THROW";
|
||||
|
||||
/**
|
||||
* Secret input and output fields (keys starting with "!") are stripped before logging.
|
||||
@@ -67,10 +67,10 @@ export function MakeActionGuts({
|
||||
kind,
|
||||
fn: actionBodyFn,
|
||||
}: {
|
||||
db: any;
|
||||
appName: string;
|
||||
kind: string;
|
||||
db: any;
|
||||
fn: (taker: ActionTaker | AnonActionTaker, input: object) => Promise<object>;
|
||||
kind: string;
|
||||
}): unknown {
|
||||
return async (taker: ActionTaker | AnonActionTaker, input: Record<string, unknown>) => {
|
||||
const ts_start = new Date();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from "./log";
|
||||
export {
|
||||
AcctInfo,
|
||||
ActionResult,
|
||||
@@ -7,3 +6,4 @@ export {
|
||||
ExpectedErr,
|
||||
MakeActionGuts,
|
||||
} from "./actions";
|
||||
export * from "./log";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { GameGroup, integer, TachiServerCoreConfig } from "tachi-common";
|
||||
|
||||
import { log } from "bliss/log";
|
||||
import { config } from "dotenv";
|
||||
import { p } from "prudence";
|
||||
|
||||
@@ -9,29 +10,6 @@ import { FormatPrError } from "./utils/prudence";
|
||||
// Initialise .env.
|
||||
config();
|
||||
|
||||
// the real log tries to bind to discord, and is dependent on the options
|
||||
// below.
|
||||
const log = console;
|
||||
|
||||
export interface BotConfig {
|
||||
TACHI_SERVER_LOCATION: string;
|
||||
HTTP_SERVER: {
|
||||
URL: string;
|
||||
};
|
||||
OAUTH: {
|
||||
CLIENT_ID: string;
|
||||
CLIENT_SECRET: string;
|
||||
};
|
||||
DISCORD: {
|
||||
ADMIN_USERS: Array<string>;
|
||||
APPROVED_ROLE?: string;
|
||||
GAME_CHANNELS: Partial<Record<GameGroup, string>>;
|
||||
LIMBO_CHANNEL?: string;
|
||||
SERVER_ID: string;
|
||||
TOKEN: string;
|
||||
};
|
||||
}
|
||||
|
||||
function ParseGameChannels(raw: string | undefined): Partial<Record<GameGroup, string>> {
|
||||
if (!raw) {
|
||||
return {};
|
||||
@@ -62,57 +40,17 @@ function ParseGameChannels(raw: string | undefined): Partial<Record<GameGroup, s
|
||||
return parsed as Partial<Record<GameGroup, string>>;
|
||||
}
|
||||
|
||||
function ParseBotConfig(): BotConfig {
|
||||
const err = p(
|
||||
process.env,
|
||||
{
|
||||
TACHI_SERVER_LOCATION: "string",
|
||||
HTTP_SERVER_URL: "string",
|
||||
OAUTH_CLIENT_ID: "string",
|
||||
OAUTH_CLIENT_SECRET: "string",
|
||||
DISCORD_TOKEN: "string",
|
||||
DISCORD_SERVER_ID: "string",
|
||||
DISCORD_GAME_CHANNELS: "*string",
|
||||
DISCORD_ADMIN_USERS: "*string",
|
||||
DISCORD_APPROVED_ROLE: "*string",
|
||||
DISCORD_LIMBO_CHANNEL: "*string",
|
||||
},
|
||||
{},
|
||||
{ allowExcessKeys: true },
|
||||
);
|
||||
|
||||
if (err) {
|
||||
log.error(FormatPrError(err, "Invalid environment. Cannot safely boot."));
|
||||
throw err;
|
||||
}
|
||||
|
||||
const gameChannels = ParseGameChannels(process.env.DISCORD_GAME_CHANNELS);
|
||||
|
||||
const adminUsers = process.env.DISCORD_ADMIN_USERS
|
||||
? process.env.DISCORD_ADMIN_USERS.split(",").filter(Boolean)
|
||||
: [];
|
||||
|
||||
return {
|
||||
TACHI_SERVER_LOCATION: process.env.TACHI_SERVER_LOCATION!,
|
||||
HTTP_SERVER: {
|
||||
URL: process.env.HTTP_SERVER_URL!,
|
||||
},
|
||||
OAUTH: {
|
||||
CLIENT_ID: process.env.OAUTH_CLIENT_ID!,
|
||||
CLIENT_SECRET: process.env.OAUTH_CLIENT_SECRET!,
|
||||
},
|
||||
DISCORD: {
|
||||
TOKEN: process.env.DISCORD_TOKEN!,
|
||||
SERVER_ID: process.env.DISCORD_SERVER_ID!,
|
||||
GAME_CHANNELS: gameChannels,
|
||||
ADMIN_USERS: adminUsers,
|
||||
APPROVED_ROLE: process.env.DISCORD_APPROVED_ROLE,
|
||||
LIMBO_CHANNEL: process.env.DISCORD_LIMBO_CHANNEL,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export interface ProcessEnvironment {
|
||||
TACHI_SERVER_LOCATION: string;
|
||||
HTTP_SERVER_URL: string;
|
||||
OAUTH_CLIENT_ID: string;
|
||||
OAUTH_CLIENT_SECRET: string;
|
||||
DISCORD_TOKEN: string;
|
||||
DISCORD_SERVER_ID: string;
|
||||
DISCORD_GAME_CHANNELS: Partial<Record<GameGroup, string>>;
|
||||
DISCORD_ADMIN_USERS: string[];
|
||||
DISCORD_APPROVED_ROLE: string | undefined;
|
||||
DISCORD_LIMBO_CHANNEL: string | undefined;
|
||||
NODE_ENV: "dev" | "production" | "staging" | "test";
|
||||
POSTGRES_URL: string;
|
||||
PORT: integer;
|
||||
@@ -132,6 +70,17 @@ function ParseEnvVars() {
|
||||
PORT: (self) =>
|
||||
p.isPositiveInteger(Number(self)) === true ||
|
||||
"Should be a string representing a whole integer port.",
|
||||
|
||||
TACHI_SERVER_LOCATION: "string",
|
||||
HTTP_SERVER_URL: "string",
|
||||
OAUTH_CLIENT_ID: "string",
|
||||
OAUTH_CLIENT_SECRET: "string",
|
||||
DISCORD_TOKEN: "string",
|
||||
DISCORD_SERVER_ID: "string",
|
||||
DISCORD_GAME_CHANNELS: "*string",
|
||||
DISCORD_ADMIN_USERS: "*string",
|
||||
DISCORD_APPROVED_ROLE: "*string",
|
||||
DISCORD_LIMBO_CHANNEL: "*string",
|
||||
},
|
||||
{},
|
||||
{ allowExcessKeys: true },
|
||||
@@ -143,14 +92,24 @@ function ParseEnvVars() {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
POSTGRES_URL: process.env.POSTGRES_URL,
|
||||
const Env: ProcessEnvironment = {
|
||||
NODE_ENV: process.env.NODE_ENV as "dev" | "production" | "staging" | "test",
|
||||
POSTGRES_URL: process.env.POSTGRES_URL!,
|
||||
PORT: Number(process.env.PORT),
|
||||
} as ProcessEnvironment;
|
||||
}
|
||||
TACHI_SERVER_LOCATION: process.env.TACHI_SERVER_LOCATION!,
|
||||
HTTP_SERVER_URL: process.env.HTTP_SERVER_URL!,
|
||||
OAUTH_CLIENT_ID: process.env.OAUTH_CLIENT_ID!,
|
||||
OAUTH_CLIENT_SECRET: process.env.OAUTH_CLIENT_SECRET!,
|
||||
DISCORD_TOKEN: process.env.DISCORD_TOKEN!,
|
||||
DISCORD_SERVER_ID: process.env.DISCORD_SERVER_ID!,
|
||||
DISCORD_GAME_CHANNELS: ParseGameChannels(process.env.DISCORD_GAME_CHANNELS),
|
||||
DISCORD_ADMIN_USERS: process.env.DISCORD_ADMIN_USERS?.split(",") ?? [],
|
||||
DISCORD_APPROVED_ROLE: process.env.DISCORD_APPROVED_ROLE,
|
||||
DISCORD_LIMBO_CHANNEL: process.env.DISCORD_LIMBO_CHANNEL,
|
||||
};
|
||||
|
||||
export const BotConfig: BotConfig = ParseBotConfig();
|
||||
return Env;
|
||||
}
|
||||
|
||||
// The Tachi Server exports all of the information about it. This saves us having to
|
||||
// sync more metadata across instances.
|
||||
@@ -160,14 +119,10 @@ async function GetServerConfig() {
|
||||
// This *should* be solved with top-level-await, but good luck actually getting
|
||||
// typescript to output the right stuff here.
|
||||
|
||||
const res = await fetch(`${BotConfig.TACHI_SERVER_LOCATION}/api/v1/config`).then((res) =>
|
||||
res.json(),
|
||||
);
|
||||
const res = await fetch(`${Env.TACHI_SERVER_LOCATION}/api/v1/config`).then((res) => res.json());
|
||||
|
||||
if (!res.success) {
|
||||
log.error(
|
||||
`Failed to fetch server info from ${BotConfig.TACHI_SERVER_LOCATION}. Can't run.`,
|
||||
);
|
||||
log.error(`Failed to fetch server info from ${Env.TACHI_SERVER_LOCATION}. Can't run.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -181,7 +136,7 @@ export const Env = ParseEnvVars();
|
||||
// General warnings for config misuse.
|
||||
// This warns people if their parent server supports games that they aren't acknowledging.
|
||||
for (const game of ServerConfig.GAMES) {
|
||||
if (!Object.prototype.hasOwnProperty.call(BotConfig.DISCORD.GAME_CHANNELS, game)) {
|
||||
if (!Object.prototype.hasOwnProperty.call(Env.DISCORD_GAME_CHANNELS, game)) {
|
||||
log.warn(
|
||||
`${ServerConfig.NAME} declares support for ${game}, but no channel is mapped to it. Set DISCORD_GAME_CHANNELS to include it.`,
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { log } from "#utils/log.js";
|
||||
import { GetLimboChannel } from "#utils/misc";
|
||||
import { Client, type CommandInteraction, Intents, type SelectMenuInteraction } from "discord.js";
|
||||
|
||||
import { BotConfig, Env, ServerConfig } from "./config";
|
||||
import { Env, ServerConfig } from "./config";
|
||||
import { handleIsCommand } from "./interaction-handlers/handle-is-command";
|
||||
import { GetUserAndTokenForDiscordID } from "./query/user-map";
|
||||
import { app } from "./server/server";
|
||||
@@ -25,7 +25,7 @@ export const client = new Client({
|
||||
});
|
||||
|
||||
client.on("guildMemberAdd", async (_member) => {
|
||||
if (BotConfig.DISCORD.APPROVED_ROLE && BotConfig.DISCORD.LIMBO_CHANNEL) {
|
||||
if (Env.DISCORD_APPROVED_ROLE && Env.DISCORD_LIMBO_CHANNEL) {
|
||||
const channel = GetLimboChannel(client);
|
||||
|
||||
await channel.send(
|
||||
@@ -62,7 +62,7 @@ client.on("interactionCreate", async (interaction) => {
|
||||
* If a user tries to do anything without auth, Tell them to authenticate.
|
||||
*/
|
||||
async function RequireUserAuth(interaction: CommandInteraction | SelectMenuInteraction) {
|
||||
const oAuthLink = `${BotConfig.TACHI_SERVER_LOCATION}/oauth/request-auth?clientID=${BotConfig.OAUTH.CLIENT_ID}&context=${interaction.user.id}`;
|
||||
const oAuthLink = `${Env.TACHI_SERVER_LOCATION}/oauth/request-auth?clientID=${Env.OAUTH_CLIENT_ID}&context=${interaction.user.id}`;
|
||||
|
||||
const dmChannel = await interaction.user.createDM();
|
||||
|
||||
@@ -79,7 +79,7 @@ void (async () => {
|
||||
log.info(`Booting Tachi Bot ${VERSION_PRETTY}.`);
|
||||
|
||||
// Login to discord.
|
||||
await client.login(BotConfig.DISCORD.TOKEN);
|
||||
await client.login(Env.DISCORD_TOKEN);
|
||||
|
||||
log.info(`Logged in successfully to ${client.guilds.cache.size} guilds.`);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { RequestHandler } from "express";
|
||||
|
||||
import { log } from "#utils/log";
|
||||
|
||||
import { BotConfig } from "../config";
|
||||
import { Env } from "../config";
|
||||
|
||||
/**
|
||||
* Middleware that checks that a webhook request has Authorization set to
|
||||
@@ -32,7 +32,7 @@ export const ValidateWebhookRequest: RequestHandler = (req, res, next) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (value !== BotConfig.OAUTH.CLIENT_SECRET) {
|
||||
if (value !== Env.OAUTH_CLIENT_SECRET) {
|
||||
log.warn(`Recieved invalid auth value from ${req.ip}. Has the client secret been changed?`);
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
|
||||
@@ -6,7 +6,7 @@ import express, { type Express } from "express";
|
||||
import path from "path";
|
||||
|
||||
import { ACTION_Register } from "../actions/register";
|
||||
import { BotConfig, Env } from "../config";
|
||||
import { Env } from "../config";
|
||||
import { RequestTypes, TachiServerV1Get, TachiServerV1Request } from "../utils/fetch-tachi";
|
||||
import { VERSION_PRETTY } from "../version";
|
||||
import { HandleClassUpdateV1 } from "../webhook-handlers/class-update";
|
||||
@@ -64,10 +64,10 @@ app.get("/oauth/callback", async (req, res) => {
|
||||
null,
|
||||
{
|
||||
code: req.query.code,
|
||||
client_id: BotConfig.OAUTH.CLIENT_ID,
|
||||
client_secret: BotConfig.OAUTH.CLIENT_SECRET,
|
||||
client_id: Env.OAUTH_CLIENT_ID,
|
||||
client_secret: Env.OAUTH_CLIENT_SECRET,
|
||||
grant_type: "authorization_code",
|
||||
redirect_uri: `${BotConfig.HTTP_SERVER.URL}/oauth/callback`,
|
||||
redirect_uri: `${Env.HTTP_SERVER_URL}/oauth/callback`,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SlashCommand } from "./types";
|
||||
|
||||
import { BotConfig, ServerConfig } from "../config";
|
||||
import { Env, ServerConfig } from "../config";
|
||||
import faq from "./commands/faq";
|
||||
import invite from "./commands/invite";
|
||||
import letmein from "./commands/letmein";
|
||||
@@ -16,7 +16,7 @@ export const SLASH_COMMANDS: Map<string, SlashCommand> = new Map(
|
||||
}),
|
||||
);
|
||||
|
||||
if (BotConfig.DISCORD.APPROVED_ROLE) {
|
||||
if (Env.DISCORD_APPROVED_ROLE) {
|
||||
SLASH_COMMANDS.set("letmein", letmein);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import deepmerge from "deepmerge";
|
||||
|
||||
import type { SlashCommand } from "../types";
|
||||
|
||||
import { BotConfig, ServerConfig } from "../../config";
|
||||
import { Env, ServerConfig } from "../../config";
|
||||
import { CreateEmbed } from "../../utils/embeds";
|
||||
|
||||
const NEUTRAL_FAQ_ENTRIES: Record<string, string> = {
|
||||
@@ -117,12 +117,12 @@ const KAMAI_FAQ_ENTRIES: Record<string, string> = {
|
||||
})`,
|
||||
sdvx_1259: `Automation Paradise is song ID 1259. It is not a real song, and it is not supported by ${ServerConfig.NAME}. Related autopara-only songs are not supported, either.`,
|
||||
invites: `To invite your friends, go to your profile and select 'Invites'. From there, you can create an invite code.
|
||||
Your friend can then go to ${BotConfig.TACHI_SERVER_LOCATION} and sign up with the code! You can also invite them to the discord.`,
|
||||
Your friend can then go to ${Env.TACHI_SERVER_LOCATION} and sign up with the code! You can also invite them to the discord.`,
|
||||
};
|
||||
|
||||
const BOKU_FAQ_ENTRIES: Record<string, string> = {
|
||||
usc_hard_mode: `Hard Mode windows are not supported on ${ServerConfig.NAME}. Your scores **will be ignored** if they are played on non-standard windows.`,
|
||||
ir_login: `You **must** put your API Key in the password field for the Bokutachi IR, **NOT your real password!**. See instructions here: ${BotConfig.TACHI_SERVER_LOCATION}/import/beatoraja-ir.
|
||||
ir_login: `You **must** put your API Key in the password field for the Bokutachi IR, **NOT your real password!**. See instructions here: ${Env.TACHI_SERVER_LOCATION}/import/beatoraja-ir.
|
||||
(This is because putting your real password in there is a security nightmare.)`,
|
||||
lr2: `Support for LR2 (the LR2Hook) are **not officially maintained** and therefore **not managed by bokutachi developers**.
|
||||
We urge you to use a more modern BMS client like lr2oraja, unless you have very good reason to stay on LR2.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BotConfig } from "#config";
|
||||
import { Env } from "#config";
|
||||
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||
import { ExpectedErr } from "bliss";
|
||||
import { GuildMember } from "discord.js";
|
||||
@@ -13,7 +13,7 @@ const command: SlashCommand = {
|
||||
.setDescription("Let yourself into the server.")
|
||||
.toJSON(),
|
||||
exec: async (interaction) => {
|
||||
if (!BotConfig.DISCORD.APPROVED_ROLE) {
|
||||
if (!Env.DISCORD_APPROVED_ROLE) {
|
||||
return null; // no-op
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ const command: SlashCommand = {
|
||||
{ ip: null },
|
||||
{
|
||||
discord_user_id: interaction.user.id,
|
||||
role_id: BotConfig.DISCORD.APPROVED_ROLE,
|
||||
role_id: Env.DISCORD_APPROVED_ROLE,
|
||||
"!member": interaction.member,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BotConfig, ServerConfig } from "#config";
|
||||
import { Env, ServerConfig } from "#config";
|
||||
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||
import { ExpectedErr } from "bliss";
|
||||
|
||||
@@ -66,7 +66,7 @@ const command: SlashCommand = {
|
||||
.addField("Errors", result.error_count.toString(), true)
|
||||
.addField(
|
||||
"Your Profile",
|
||||
`${BotConfig.TACHI_SERVER_LOCATION}/u/${result.user_id}/games/${result.game}`,
|
||||
`${Env.TACHI_SERVER_LOCATION}/u/${result.user_id}/games/${result.game}`,
|
||||
);
|
||||
} catch (e) {
|
||||
if (ExpectedErr.is(e)) {
|
||||
|
||||
@@ -4,12 +4,12 @@ import { log } from "#utils/log";
|
||||
import { REST } from "@discordjs/rest";
|
||||
import { Routes } from "discord-api-types/v9";
|
||||
|
||||
import { BotConfig, Env } from "../config";
|
||||
import { Env } from "../config";
|
||||
import { SLASH_COMMANDS } from "./commands";
|
||||
|
||||
const rest = new REST({
|
||||
version: "9",
|
||||
}).setToken(BotConfig.DISCORD.TOKEN);
|
||||
}).setToken(Env.DISCORD_TOKEN);
|
||||
|
||||
/**
|
||||
* Register our slash commands. If in prod, these
|
||||
@@ -34,10 +34,7 @@ export async function RegisterSlashCommands(client: Client): Promise<void> {
|
||||
log.info("Registering guild slash commands.");
|
||||
|
||||
await rest.put(
|
||||
Routes.applicationGuildCommands(
|
||||
client.application!.id,
|
||||
BotConfig.DISCORD.SERVER_ID,
|
||||
),
|
||||
Routes.applicationGuildCommands(client.application!.id, Env.DISCORD_SERVER_ID),
|
||||
{
|
||||
body: commandsArray.map((command) => command.info),
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function createTestAccount(
|
||||
): Promise<TestAccount> {
|
||||
const { id } = await db
|
||||
.insertInto("account")
|
||||
.values({ username })
|
||||
.values({ username, about: "Test user for tests." })
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
UserDocument,
|
||||
} from "tachi-common";
|
||||
|
||||
import { BotConfig } from "#config";
|
||||
import { Env } from "#config";
|
||||
import { log } from "#utils/log";
|
||||
|
||||
import type { ImportDeferred, ImportPollStatus, UGPTStats } from "./return-types";
|
||||
@@ -161,7 +161,7 @@ export async function PerformScoreImport(
|
||||
throw new Error(`Failed to import scores.
|
||||
Your authentication with this service has expired, and a bug on their end prevents us from automatically renewing it.
|
||||
|
||||
Please go to ${BotConfig.TACHI_SERVER_LOCATION}/u/me/integrations/services to un-link and re-link.`);
|
||||
Please go to ${Env.TACHI_SERVER_LOCATION}/u/me/integrations/services to un-link and re-link.`);
|
||||
}
|
||||
|
||||
throw new Error(`Failed to import scores. ${pollRes.description}.`);
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ImportDocument, integer, UserDocument } from "tachi-common";
|
||||
|
||||
import { MessageEmbed } from "discord.js";
|
||||
|
||||
import { BotConfig, ServerConfig } from "../config";
|
||||
import { Env, ServerConfig } from "../config";
|
||||
import { PrependTachiUrl } from "./fetch-tachi";
|
||||
import { FormatDate, Pluralise } from "./misc";
|
||||
|
||||
@@ -30,7 +30,7 @@ export function CreateImportEmbed(importDoc: ImportDocument) {
|
||||
.addField("Errors", importDoc.errors.length.toString(), true)
|
||||
.addField(
|
||||
"Your Profile",
|
||||
`${BotConfig.TACHI_SERVER_LOCATION}/u/${importDoc.userID}/games/${importDoc.game}`,
|
||||
`${Env.TACHI_SERVER_LOCATION}/u/${importDoc.userID}/games/${importDoc.game}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,5 +40,5 @@ export function CreateUserEmbed(userDoc: UserDocument) {
|
||||
.setThumbnail(PrependTachiUrl(`/users/${userDoc.id}/pfp`))
|
||||
.setDescription(userDoc.status ?? "No status...")
|
||||
.addField("Join Date", FormatDate(userDoc.joinDate))
|
||||
.setURL(`${BotConfig.TACHI_SERVER_LOCATION}/u/${userDoc.username}`);
|
||||
.setURL(`${Env.TACHI_SERVER_LOCATION}/u/${userDoc.username}`);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { log } from "#utils/log";
|
||||
import fetch from "node-fetch";
|
||||
import { URLSearchParams } from "url";
|
||||
|
||||
import { BotConfig } from "../config";
|
||||
import { Env } from "../config";
|
||||
import { VERSION_STR } from "../version";
|
||||
|
||||
type ApiResponseBase = {
|
||||
@@ -129,7 +129,7 @@ export function PrependTachiUrl(url: string, version: "1" = "1"): string {
|
||||
url = `/${url}`;
|
||||
}
|
||||
|
||||
return `${BotConfig.TACHI_SERVER_LOCATION}/api/v${version}${url}`;
|
||||
return `${Env.TACHI_SERVER_LOCATION}/api/v${version}${url}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
type Playtype,
|
||||
} from "tachi-common";
|
||||
|
||||
import { BotConfig } from "../config";
|
||||
import { Env } from "../config";
|
||||
|
||||
export function Sleep(ms: number) {
|
||||
return new Promise<void>((resolve) => {
|
||||
@@ -99,7 +99,7 @@ export function FormatClass(
|
||||
* Given a game, return the discord channel it's associated with.
|
||||
*/
|
||||
export function GetGameChannel(client: Client, game: GameGroup) {
|
||||
const gameChannelID = BotConfig.DISCORD.GAME_CHANNELS[game];
|
||||
const gameChannelID = Env.DISCORD_GAME_CHANNELS[game];
|
||||
|
||||
if (!gameChannelID) {
|
||||
throw new Error(
|
||||
@@ -123,7 +123,7 @@ export function GetGameChannel(client: Client, game: GameGroup) {
|
||||
}
|
||||
|
||||
export function GetLimboChannel(client: Client) {
|
||||
const limboCID = BotConfig.DISCORD.LIMBO_CHANNEL;
|
||||
const limboCID = Env.DISCORD_LIMBO_CHANNEL;
|
||||
|
||||
if (!limboCID) {
|
||||
throw new Error(`Attempted to get channel for #limbo, but none was registered.`);
|
||||
@@ -147,12 +147,12 @@ export function GetLimboChannel(client: Client) {
|
||||
*/
|
||||
export function CreateChartLink(chart: ChartDocument, game: GameGroup) {
|
||||
if (chart.isPrimary) {
|
||||
return `${BotConfig.TACHI_SERVER_LOCATION}/games/${game}/${chart.playtype}/songs/${
|
||||
return `${Env.TACHI_SERVER_LOCATION}/games/${game}/${chart.playtype}/songs/${
|
||||
chart.songID
|
||||
}/${encodeURIComponent(chart.difficulty)}`;
|
||||
}
|
||||
|
||||
return `${BotConfig.TACHI_SERVER_LOCATION}/games/${game}/${chart.playtype}/songs/${chart.songID}/${chart.chartID}`;
|
||||
return `${Env.TACHI_SERVER_LOCATION}/games/${game}/${chart.playtype}/songs/${chart.songID}/${chart.chartID}`;
|
||||
}
|
||||
|
||||
export function ConvertInputIntoGenerousRegex(input: string) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
type WebhookEventClassUpdateV1,
|
||||
} from "tachi-common";
|
||||
|
||||
import { BotConfig } from "../config";
|
||||
import { Env } from "../config";
|
||||
import { client } from "../main";
|
||||
import { GetUGPTStats, GetUserInfo } from "../utils/api-requests";
|
||||
import { CreateEmbed } from "../utils/embeds";
|
||||
@@ -62,9 +62,7 @@ export async function HandleClassUpdateV1(
|
||||
.setTitle(
|
||||
`${userDoc.username} just achieved ${newClass} in ${FormatGameGroup(game, playtype)}!`,
|
||||
)
|
||||
.setURL(
|
||||
`${BotConfig.TACHI_SERVER_LOCATION}/users/${userDoc.username}/games/${game}/${playtype}`,
|
||||
)
|
||||
.setURL(`${Env.TACHI_SERVER_LOCATION}/users/${userDoc.username}/games/${game}/${playtype}`)
|
||||
.setThumbnail(PrependTachiUrl(`/users/${userDoc.id}/pfp`));
|
||||
|
||||
if (event.old !== null) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BotConfig } from "#config";
|
||||
import { Env } from "#config";
|
||||
import { PrependTachiUrl } from "#utils/fetch-tachi.js";
|
||||
import { log } from "#utils/log";
|
||||
import {
|
||||
@@ -53,7 +53,7 @@ export async function HandleGoalAchievedV1(
|
||||
)}!`,
|
||||
)
|
||||
.setThumbnail(PrependTachiUrl(`/users/${userDoc.id}/pfp`))
|
||||
.setURL(`${BotConfig.TACHI_SERVER_LOCATION}/u/${userDoc.username}`)
|
||||
.setURL(`${Env.TACHI_SERVER_LOCATION}/u/${userDoc.username}`)
|
||||
.addFields(
|
||||
event.goals.map((e) => {
|
||||
const goal = goalMap.get(e.goalID)!;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BotConfig } from "#config";
|
||||
import { Env } from "#config";
|
||||
import { PrependTachiUrl } from "#utils/fetch-tachi.js";
|
||||
import { log } from "#utils/log";
|
||||
import { GetGameGroupConfig, type integer, type WebhookEventQuestAchievedV1 } from "tachi-common";
|
||||
@@ -33,7 +33,7 @@ export async function HandleQuestAchievedV1(
|
||||
|
||||
const embed = CreateEmbed(userDoc.id)
|
||||
.setThumbnail(PrependTachiUrl(`/users/${userDoc.id}/pfp`))
|
||||
.setURL(`${BotConfig.TACHI_SERVER_LOCATION}/u/${userDoc.username}`)
|
||||
.setURL(`${Env.TACHI_SERVER_LOCATION}/u/${userDoc.username}`)
|
||||
.setTitle(
|
||||
`${userDoc.username} just completed the ${quest.name}${shouldShowPlaytype} quest!`,
|
||||
);
|
||||
|
||||
@@ -16,10 +16,11 @@ export default defineConfig({
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData:
|
||||
process.env.VITE_TCHIC_MODE === "boku"
|
||||
? "$primary: #527acc;"
|
||||
: "$primary: #e61c6e;",
|
||||
additionalData:
|
||||
process.env.VITE_TCHIC_MODE === "boku"
|
||||
? "$primary: #527acc;"
|
||||
: "$primary: #e61c6e;",
|
||||
silenceDeprecations: ["import", "global-builtin", "color-functions", "if-function"],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "tap",
|
||||
"test": "vitest",
|
||||
"build": "tsgo -p tsconfig.build.json",
|
||||
"prepublishOnly": "tsgo -p tsconfig.build.json",
|
||||
"lint": "eslint .",
|
||||
@@ -43,9 +43,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"@types/tap": "catalog:",
|
||||
"eslint-config-tachi": "workspace:*",
|
||||
"tap": "workspace:*",
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"nyc": {
|
||||
|
||||
@@ -8,6 +8,7 @@ export * from "./constants/grade-boundaries";
|
||||
export * as StaticConfig from "./constants/import-types";
|
||||
export * from "./constants/permissions";
|
||||
export * as Schemas from "./lib/schemas";
|
||||
export * from "./lib/zod-schemas";
|
||||
export * from "./seeds/types";
|
||||
export * from "./types";
|
||||
export * from "./utils/util";
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { ALL_PERMISSIONS } from "#constants/permissions.js";
|
||||
import z from "zod";
|
||||
|
||||
export const zodPermission = z.enum(Object.keys(ALL_PERMISSIONS));
|
||||
@@ -15,11 +15,11 @@ import { applyMigrations, getMigrationInfo, revertLastMigration } from "tachi-db
|
||||
const DEFAULT_MIGRATIONS_DIR = "/tachi/db/migrations";
|
||||
|
||||
function getConnectionString(opts: { databaseUrl?: string }): string {
|
||||
const url = opts.databaseUrl ?? process.env.DATABASE_URL;
|
||||
const url = opts.databaseUrl ?? process.env.POSTGRES_URL;
|
||||
|
||||
if (!url) {
|
||||
console.error(
|
||||
"[migrate] No database URL provided. Set DATABASE_URL, or pass --database-url.",
|
||||
"[migrate] No database URL provided. Set POSTGRES_URL, or pass --database-url.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ const program = new Command();
|
||||
program
|
||||
.name("tachi-migrate")
|
||||
.description("Database migration CLI, modelled after sqlx migrate.")
|
||||
.option("--database-url <url>", "Postgres connection string (overrides DATABASE_URL env var)");
|
||||
.option("--database-url <url>", "Postgres connection string (overrides POSTGRES_URL env var)");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// database subcommands
|
||||
@@ -98,7 +98,7 @@ const databaseCmd = program.command("database").description("Create or drop the
|
||||
|
||||
databaseCmd
|
||||
.command("create")
|
||||
.description("Create the database specified by DATABASE_URL.")
|
||||
.description("Create the database specified by POSTGRES_URL.")
|
||||
.action(async () => {
|
||||
const parentOpts = program.opts<{ databaseUrl?: string }>();
|
||||
const connectionString = getConnectionString(parentOpts);
|
||||
@@ -122,7 +122,7 @@ databaseCmd
|
||||
|
||||
databaseCmd
|
||||
.command("drop")
|
||||
.description("Drop the database specified by DATABASE_URL.")
|
||||
.description("Drop the database specified by POSTGRES_URL.")
|
||||
.action(async () => {
|
||||
const parentOpts = program.opts<{ databaseUrl?: string }>();
|
||||
const connectionString = getConnectionString(parentOpts);
|
||||
|
||||
@@ -35,7 +35,6 @@ export { type priv_password_reset_token_token, type default as PrivPasswordReset
|
||||
export { type song_id, type default as SongTable, type Song, type NewSong, type SongUpdate } from './public/Song';
|
||||
export { type account_username_change_row_id, type default as AccountUsernameChangeTable, type AccountUsernameChange, type NewAccountUsernameChange, type AccountUsernameChangeUpdate } from './public/AccountUsernameChange';
|
||||
export { type default as MigrationTable, type Migration, type NewMigration, type MigrationUpdate } from './public/Migration';
|
||||
export { type account_badge_badge, type default as AccountBadgeTable, type AccountBadge, type NewAccountBadge, type AccountBadgeUpdate } from './public/AccountBadge';
|
||||
export { type action_row_id, type default as ActionTable, type Action, type NewAction, type ActionUpdate } from './public/Action';
|
||||
export { type priv_svc_kai_auth_token_service, type default as PrivSvcKaiAuthTokenTable, type PrivSvcKaiAuthToken, type NewPrivSvcKaiAuthToken, type PrivSvcKaiAuthTokenUpdate } from './public/PrivSvcKaiAuthToken';
|
||||
export { type chart_id, type default as ChartTable, type Chart, type NewChart, type ChartUpdate } from './public/Chart';
|
||||
|
||||
@@ -27,9 +27,15 @@ export default interface AccountTable {
|
||||
|
||||
sm_twitch: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
bd_alpha: ColumnType<boolean, boolean | undefined, boolean>;
|
||||
|
||||
bd_beta: ColumnType<boolean, boolean | undefined, boolean>;
|
||||
|
||||
bd_dev_team: ColumnType<boolean, boolean | undefined, boolean>;
|
||||
|
||||
joined: ColumnType<string, string | undefined, string>;
|
||||
|
||||
about: ColumnType<string | null, string | null, string | null>;
|
||||
about: ColumnType<string, string, string>;
|
||||
|
||||
status: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
@@ -40,6 +46,8 @@ export default interface AccountTable {
|
||||
last_seen: ColumnType<string, string | undefined, string>;
|
||||
|
||||
auth_level: ColumnType<AuthLevel, AuthLevel | undefined, AuthLevel>;
|
||||
|
||||
is_supporter: ColumnType<boolean, boolean | undefined, boolean>;
|
||||
}
|
||||
|
||||
export type Account = Selectable<AccountTable>;
|
||||
|
||||
@@ -36,6 +36,12 @@ export default interface PrivApiClientTable {
|
||||
api_key_template: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
api_key_filename: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
webhook_uri: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
redirect_uri: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
is_builtin: ColumnType<boolean, boolean | undefined, boolean>;
|
||||
}
|
||||
|
||||
export type PrivApiClient = Selectable<PrivApiClientTable>;
|
||||
|
||||
@@ -35,7 +35,6 @@ import type { default as PrivPasswordResetTokenTable } from './PrivPasswordReset
|
||||
import type { default as SongTable } from './Song';
|
||||
import type { default as AccountUsernameChangeTable } from './AccountUsernameChange';
|
||||
import type { default as MigrationTable } from './Migration';
|
||||
import type { default as AccountBadgeTable } from './AccountBadge';
|
||||
import type { default as ActionTable } from './Action';
|
||||
import type { default as PrivSvcKaiAuthTokenTable } from './PrivSvcKaiAuthToken';
|
||||
import type { default as ChartTable } from './Chart';
|
||||
@@ -139,8 +138,6 @@ export default interface PublicSchema {
|
||||
|
||||
_migration: MigrationTable;
|
||||
|
||||
account_badge: AccountBadgeTable;
|
||||
|
||||
action: ActionTable;
|
||||
|
||||
priv_svc_kai_auth_token: PrivSvcKaiAuthTokenTable;
|
||||
|
||||
@@ -13,6 +13,9 @@ const base = tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
pluginPerfectionist.configs["recommended-natural"],
|
||||
{
|
||||
ignores: ["**/*.oldtest.ts"],
|
||||
},
|
||||
{
|
||||
name: "zenith/base",
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
js
|
||||
!example/.env
|
||||
js
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
|
||||
NODE_ENV="dev"
|
||||
REDIS_URL="tachi-redis"
|
||||
MONGO_URL="tachi-mongo"
|
||||
PORT=8080
|
||||
LOG_LEVEL="debug"
|
||||
MSGPACKR_NATIVE_ACCELERATION_DISABLED=true
|
||||
|
||||
POSTGRES_URL="postgresql://tachi:tachi@tachi-postgres/tachi_dev"
|
||||
MIGRATIONS_DIR="/tachi/db/migrations"
|
||||
@@ -1,5 +1,4 @@
|
||||
# Test stuffs
|
||||
.env
|
||||
.setup_env
|
||||
failed-tests.txt
|
||||
|
||||
|
||||
@@ -8,9 +8,3 @@ Tachi-Server is the backend for Tachi.
|
||||
- Opens a public API on `/api`.
|
||||
- Supports various IR frameworks under `/ir`
|
||||
- Powers a ridiculously overengineered score import framework.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure you've ran the bootstrap script in `../_scripts`. You can then start the server with
|
||||
`pnpm start` (in this directory) or `pnpm start-server` at the top level.
|
||||
@@ -0,0 +1,10 @@
|
||||
version: v2
|
||||
inputs:
|
||||
- directory: src/proto/myt
|
||||
plugins:
|
||||
- local: protoc-gen-es
|
||||
out: src/proto/generated
|
||||
opt: target=ts
|
||||
- local: protoc-gen-connect-es
|
||||
out: src/proto/generated
|
||||
opt: target=ts
|
||||
@@ -1,6 +0,0 @@
|
||||
NODE_ENV="dev"
|
||||
REDIS_URL="redis"
|
||||
MONGO_URL="mongo"
|
||||
PORT=8080
|
||||
LOG_LEVEL="debug"
|
||||
MSGPACKR_NATIVE_ACCELERATION_DISABLED=true
|
||||
@@ -1,98 +0,0 @@
|
||||
// Config file that the CI deployment uses. This is moved to $pwd/conf.json5 before CI tests are ran.
|
||||
|
||||
{
|
||||
MONGO_DATABASE_NAME: "localdb",
|
||||
CAPTCHA_SECRET_KEY: "unused",
|
||||
SESSION_SECRET: "unused",
|
||||
FLO_API_URL: "https://flo.example.com",
|
||||
EAG_API_URL: "https://eag.example.com",
|
||||
MIN_API_URL: "https://min.example.com",
|
||||
MYT_API_HOST: "myt.example.com",
|
||||
FLO_OAUTH2_INFO: {
|
||||
CLIENT_ID: "DUMMY_CLIENT_ID",
|
||||
CLIENT_SECRET: "DUMMY_CLIENT_SECRET",
|
||||
REDIRECT_URI: "https://example.com",
|
||||
},
|
||||
EAG_OAUTH2_INFO: {
|
||||
CLIENT_ID: "DUMMY_CLIENT_ID",
|
||||
CLIENT_SECRET: "DUMMY_CLIENT_SECRET",
|
||||
REDIRECT_URI: "https://example.com",
|
||||
},
|
||||
ARC_AUTH_TOKEN: "unused",
|
||||
MYT_AUTH_TOKEN: "unused",
|
||||
OUR_URL: "https://127.0.0.1:8080",
|
||||
CLIENT_DEV_SERVER: "http://127.0.0.1:3000",
|
||||
ENABLE_SERVER_HTTPS: true,
|
||||
OPTIONS_ALWAYS_SUCCEEDS: true,
|
||||
CDN_CONFIG: {
|
||||
WEB_LOCATION: "https://127.0.0.1:8080/cdn",
|
||||
SAVE_LOCATION: {
|
||||
TYPE: "LOCAL_FILESYSTEM",
|
||||
LOCATION: "./local-cdn",
|
||||
SERVE_OWN_CDN: true,
|
||||
},
|
||||
},
|
||||
TACHI_CONFIG: {
|
||||
TYPE: "omni",
|
||||
NAME: "Tachi Local Dev",
|
||||
GAMES: [
|
||||
"iidx",
|
||||
"museca",
|
||||
"sdvx",
|
||||
"bms",
|
||||
"chunithm",
|
||||
"usc",
|
||||
"wacca",
|
||||
"popn",
|
||||
"jubeat",
|
||||
"pms",
|
||||
"gitadora",
|
||||
"maimai",
|
||||
"maimaidx",
|
||||
"itg",
|
||||
"ongeki",
|
||||
"ddr"
|
||||
],
|
||||
IMPORT_TYPES: [
|
||||
"file/eamusement-iidx-csv",
|
||||
"file/batch-manual",
|
||||
"file/solid-state-squad",
|
||||
"file/pli-iidx-csv",
|
||||
"file/mypagescraper-records-csv",
|
||||
"file/mypagescraper-player-csv",
|
||||
"ir/direct-manual",
|
||||
"ir/barbatos",
|
||||
"ir/fervidex",
|
||||
"ir/fervidex-static",
|
||||
"ir/beatoraja",
|
||||
"ir/usc",
|
||||
"ir/kshook-sv6c",
|
||||
"ir/lr2hook",
|
||||
"api/eag-iidx",
|
||||
"api/eag-sdvx",
|
||||
"api/flo-iidx",
|
||||
"api/flo-sdvx",
|
||||
"api/min-sdvx",
|
||||
"api/cg-dev-sdvx",
|
||||
"api/cg-dev-popn",
|
||||
"api/cg-dev-museca",
|
||||
"api/cg-dev-jubeat",
|
||||
"api/cg-nag-sdvx",
|
||||
"api/cg-nag-popn",
|
||||
"api/cg-nag-jubeat",
|
||||
"api/cg-nag-museca",
|
||||
"api/cg-gan-sdvx",
|
||||
"api/cg-gan-popn",
|
||||
"api/cg-gan-museca",
|
||||
"api/cg-gan-jubeat",
|
||||
"api/myt-chunithm",
|
||||
"api/myt-maimaidx",
|
||||
"api/myt-ongeki",
|
||||
"api/myt-wacca",
|
||||
],
|
||||
},
|
||||
SEEDS_CONFIG: {
|
||||
TYPE: "LOCAL_FILES",
|
||||
PATH: "../seeds/collections",
|
||||
},
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ext": "ts,json",
|
||||
"ignore": ["src/**/*.spec.ts"],
|
||||
"exec": "ts-node ./src/main.ts"
|
||||
}
|
||||
@@ -6,9 +6,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun --watch src/main.ts",
|
||||
"test": "tap",
|
||||
"snap": "TAP_SNAPSHOT=1 tap",
|
||||
"watchtest": "tap --watch",
|
||||
"test": "vitest",
|
||||
"build": "tsgo -b tsconfig.build.json -v",
|
||||
"typecheck": "tsgo --noEmit",
|
||||
"lint": "eslint ./src",
|
||||
@@ -21,8 +19,8 @@
|
||||
"@types/google-protobuf": "catalog:",
|
||||
"@types/pg": "catalog:",
|
||||
"@types/supertest": "catalog:",
|
||||
"@types/tap": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"@vitest/coverage-v8": "catalog:",
|
||||
"eslint-config-tachi": "workspace:*",
|
||||
"glob": "catalog:",
|
||||
"google-protobuf": "catalog:",
|
||||
@@ -30,9 +28,9 @@
|
||||
"node-mocks-http": "catalog:",
|
||||
"nodemon": "catalog:",
|
||||
"supertest": "catalog:",
|
||||
"tap": "workspace:*",
|
||||
"ts-protoc-gen": "catalog:",
|
||||
"tsconfig-paths": "catalog:"
|
||||
"tsconfig-paths": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "catalog:",
|
||||
@@ -59,6 +57,7 @@
|
||||
"@types/semver": "catalog:",
|
||||
"@valuabletouch/winston-seq": "catalog:",
|
||||
"bcryptjs": "catalog:",
|
||||
"bliss": "workspace:*",
|
||||
"bms-table-loader": "catalog:",
|
||||
"bullmq": "catalog:",
|
||||
"bunyan": "catalog:",
|
||||
@@ -77,6 +76,7 @@
|
||||
"html-entities": "catalog:",
|
||||
"json5": "catalog:",
|
||||
"kysely": "catalog:",
|
||||
"lodash": "^4.17.23",
|
||||
"mkdirp": "catalog:",
|
||||
"mongodb": "catalog:",
|
||||
"monk": "catalog:",
|
||||
@@ -86,7 +86,6 @@
|
||||
"node-html-parser": "catalog:",
|
||||
"nodemailer": "catalog:",
|
||||
"pg": "catalog:",
|
||||
"bliss": "workspace:*",
|
||||
"prudence": "catalog:",
|
||||
"rate-limit-redis": "catalog:",
|
||||
"redis": "catalog:",
|
||||
@@ -115,7 +114,7 @@
|
||||
"src/*/!(test-utils)/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.oldtest.ts",
|
||||
"src/external/mongo/schemas.ts"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { MakeAction } from "#lib/actions/actions.js";
|
||||
import DB from "#services/pg/db.js";
|
||||
import { Random20Hex } from "#utils/misc.js";
|
||||
import { IsUserAdmin } from "#utils/user.js";
|
||||
import { ExpectedErr } from "bliss";
|
||||
|
||||
export const ACTION_InstallBuiltinClient = MakeAction(
|
||||
"INSTALL_BUILTIN_CLIENT",
|
||||
async (
|
||||
taker,
|
||||
{ clientID, name, permissions, apiKeyFilename, apiKeyTemplate, redirectUri, webhookUri },
|
||||
) => {
|
||||
// verify taker is admin
|
||||
if (!(await IsUserAdmin(taker.acct.id))) {
|
||||
throw new ExpectedErr(403, "You are not authorized to perform this action.");
|
||||
}
|
||||
|
||||
// I honestly think this is unused for builtin clients
|
||||
// as they don't need to auth with anyone!
|
||||
const clientSecret = `CS${Random20Hex()}`;
|
||||
|
||||
// create client
|
||||
await DB.insertInto("priv_api_client")
|
||||
.values({
|
||||
client_id: clientID,
|
||||
client_secret: clientSecret,
|
||||
name: name,
|
||||
author: taker.acct.id,
|
||||
pm_customise_profile: permissions.customise_profile,
|
||||
pm_customise_score: permissions.customise_score,
|
||||
pm_customise_session: permissions.customise_session,
|
||||
pm_delete_score: permissions.delete_score,
|
||||
pm_manage_rivals: permissions.manage_rivals,
|
||||
pm_manage_targets: permissions.manage_targets,
|
||||
pm_submit_score: permissions.submit_score,
|
||||
pm_manage_challenges: permissions.manage_challenges,
|
||||
api_key_filename: apiKeyFilename,
|
||||
api_key_template: apiKeyTemplate,
|
||||
webhook_uri: webhookUri,
|
||||
redirect_uri: redirectUri,
|
||||
is_builtin: true,
|
||||
})
|
||||
.onConflict((oc) =>
|
||||
oc.doUpdateSet({
|
||||
client_secret: clientSecret,
|
||||
name: name,
|
||||
author: taker.acct.id,
|
||||
pm_customise_profile: permissions.customise_profile,
|
||||
pm_customise_score: permissions.customise_score,
|
||||
pm_customise_session: permissions.customise_session,
|
||||
pm_delete_score: permissions.delete_score,
|
||||
pm_manage_rivals: permissions.manage_rivals,
|
||||
pm_manage_targets: permissions.manage_targets,
|
||||
pm_submit_score: permissions.submit_score,
|
||||
pm_manage_challenges: permissions.manage_challenges,
|
||||
api_key_filename: apiKeyFilename,
|
||||
api_key_template: apiKeyTemplate,
|
||||
webhook_uri: webhookUri,
|
||||
redirect_uri: redirectUri,
|
||||
is_builtin: true,
|
||||
}),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return {};
|
||||
},
|
||||
);
|
||||
+3
-3
@@ -2,7 +2,7 @@ import type { DeepPartial } from "#utils/types";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestSnapshot } from "#test-utils/single-process-snapshot";
|
||||
@@ -146,8 +146,8 @@ t.test("Arcaea Implementation", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should join best lamp", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestSnapshot } from "#test-utils/single-process-snapshot";
|
||||
@@ -297,10 +297,10 @@ for (const [game, playtype, impl] of [
|
||||
|
||||
t.test("PB Mergers", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
t.beforeEach(() => db.scores.insert(mockScore));
|
||||
t.beforeEach(() => MONGODB_KILL.scores.insert(mockScore));
|
||||
|
||||
t.test("Should join best lamp", async (t) => {
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
@@ -326,7 +326,7 @@ for (const [game, playtype, impl] of [
|
||||
});
|
||||
|
||||
t.test("Should join best BP", async (t) => {
|
||||
await db.scores.insert([
|
||||
await MONGODB_KILL.scores.insert([
|
||||
dmf(mockScore, {
|
||||
scoreID: "whateverBP",
|
||||
scoreData: { optional: { bp: 100 } },
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { CHUNITHMBBKKChart, TestingChunithmScorePB } from "#test-utils/test-data";
|
||||
@@ -83,7 +83,7 @@ t.test("CHUNITHM Implementation", (t) => {
|
||||
const mockPBs = async (ratings: Array<number>) => {
|
||||
await Promise.all(
|
||||
ratings.map((rating, idx) =>
|
||||
db["personal-bests"].insert({
|
||||
MONGODB_KILL["personal-bests"].insert({
|
||||
...TestingChunithmScorePB,
|
||||
chartID: `TEST${idx}`,
|
||||
calculatedData: {
|
||||
@@ -192,8 +192,8 @@ t.test("CHUNITHM Implementation", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should join best lamp", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestNoteLamp",
|
||||
scoreData: {
|
||||
@@ -203,7 +203,7 @@ t.test("CHUNITHM Implementation", (t) => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestClearLamp",
|
||||
scoreData: {
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
ScoreValidator,
|
||||
} from "#game-implementations/types";
|
||||
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { DDRFlare } from "rg-stats";
|
||||
import {
|
||||
type ChartDocument,
|
||||
@@ -157,7 +157,7 @@ const DDR_CALCULATE_FLARE_SKILL: GPTNewProfileCalcs<"ddr:DP" | "ddr:SP"> = async
|
||||
playtype,
|
||||
userID,
|
||||
) => {
|
||||
const sc: Array<PBScoreDocumentWithSong> = await db["personal-bests"].aggregate([
|
||||
const sc: Array<PBScoreDocumentWithSong> = await MONGODB_KILL["personal-bests"].aggregate([
|
||||
{
|
||||
$match: {
|
||||
userID,
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestingGitadoraChart } from "#test-utils/test-data";
|
||||
@@ -170,8 +170,8 @@ for (const [playtype, impl] of [
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should join best lamp", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkFakePBIIDXSP, mkFakeScoreIIDXSP } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestSnapshot } from "#test-utils/single-process-snapshot";
|
||||
@@ -399,7 +399,7 @@ t.test("IIDX Implementation", (t) => {
|
||||
|
||||
for (const gptStr of ["iidx:SP", "iidx:DP"] as const) {
|
||||
t.test("Should join best lamp", async (t) => {
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(
|
||||
mkFakeScoreIIDXSP({
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
@@ -428,7 +428,7 @@ t.test("IIDX Implementation", (t) => {
|
||||
});
|
||||
|
||||
t.test("Should join best BP", async (t) => {
|
||||
await db.scores.insert([
|
||||
await MONGODB_KILL.scores.insert([
|
||||
mkFakeScoreIIDXSP({
|
||||
scoreID: "whateverBP",
|
||||
scoreData: { optional: { bp: 100 } },
|
||||
+5
-5
@@ -2,7 +2,7 @@ import type { ProvidedMetrics, ScoreData } from "tachi-common";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestSnapshot } from "#test-utils/single-process-snapshot";
|
||||
@@ -204,8 +204,8 @@ t.test("Jubeat Implementation", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should join best lamp", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
@@ -234,8 +234,8 @@ t.test("Jubeat Implementation", (t) => {
|
||||
});
|
||||
|
||||
t.test("Should join best score", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestScore",
|
||||
scoreData: {
|
||||
@@ -3,7 +3,7 @@ import type { GPTServerImplementation } from "#game-implementations/types";
|
||||
import { CreatePBMergeFor } from "#game-implementations/utils/pb-merge";
|
||||
import { ProfileSumBestN } from "#game-implementations/utils/profile-calc";
|
||||
import { SessionAvgBest10For } from "#game-implementations/utils/session-calc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { IsNullish } from "#utils/misc";
|
||||
import { p } from "prudence";
|
||||
import { Jubility } from "rg-stats";
|
||||
@@ -27,7 +27,7 @@ async function GetBestJubilityOnSongs(
|
||||
playtype: Playtype,
|
||||
limit: integer,
|
||||
): Promise<Array<PBScoreDocument>> {
|
||||
const r: Array<{ doc: PBScoreDocument }> = await db["personal-bests"].aggregate([
|
||||
const r: Array<{ doc: PBScoreDocument }> = await MONGODB_KILL["personal-bests"].aggregate([
|
||||
{
|
||||
$match: {
|
||||
game,
|
||||
@@ -107,14 +107,14 @@ async function GetBestJubilityOnSongs(
|
||||
const CURRENT_JUBEAT_HOT_VERSION: Versions["jubeat:Single"] = "ave";
|
||||
|
||||
export async function GetPBsForJubility(userID: integer) {
|
||||
const hotSongs = await db.songs.jubeat.find(
|
||||
const hotSongs = await MONGODB_KILL.songs.jubeat.find(
|
||||
{ "data.displayVersion": CURRENT_JUBEAT_HOT_VERSION },
|
||||
{ projection: { id: 1 } },
|
||||
);
|
||||
|
||||
const hotSongIDs = hotSongs.map((e) => e.id);
|
||||
|
||||
const coldSongs = await db.songs.jubeat.find(
|
||||
const coldSongs = await MONGODB_KILL.songs.jubeat.find(
|
||||
{ "data.displayVersion": { $ne: CURRENT_JUBEAT_HOT_VERSION } },
|
||||
{ projection: { id: 1 } },
|
||||
);
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@ import type { integer, ProvidedMetrics, ScoreData } from "tachi-common";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestSnapshot } from "#test-utils/single-process-snapshot";
|
||||
@@ -167,8 +167,8 @@ t.test("maimai Implementation", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should join best lamp", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
+3
-3
@@ -2,7 +2,7 @@ import type { ProvidedMetrics, ScoreData } from "tachi-common";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestingMaimaiDXChart } from "#test-utils/test-data";
|
||||
@@ -134,8 +134,8 @@ t.test("Maimai DX Implementation", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should join best lamp", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestingMusecaChart } from "#test-utils/test-data";
|
||||
@@ -100,8 +100,8 @@ t.test("MUSECA Implementation", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should join best lamp", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
+13
-13
@@ -2,7 +2,7 @@
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc, UpdateChartRanking } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestingOngekiChart, TestingOngekiScorePB } from "#test-utils/test-data";
|
||||
@@ -136,7 +136,7 @@ t.test("ONGEKI Implementation", (t: any) => {
|
||||
? { rating: 0, scoreRating: rating, starRating: 0 }
|
||||
: { rating: 0, scoreRating: 0, starRating: rating };
|
||||
|
||||
return db["personal-bests"].insert({
|
||||
return MONGODB_KILL["personal-bests"].insert({
|
||||
...TestingOngekiScorePB,
|
||||
chartID: `TEST${field}${idx}`,
|
||||
calculatedData: {
|
||||
@@ -282,8 +282,8 @@ t.test("ONGEKI Implementation", (t: any) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should join best lamp", async (t: any) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
@@ -314,8 +314,8 @@ t.test("ONGEKI Implementation", (t: any) => {
|
||||
});
|
||||
|
||||
t.test("Should join platinum score", async (t: any) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestPlatinum",
|
||||
scoreData: {
|
||||
@@ -348,7 +348,7 @@ t.test("ONGEKI Implementation", (t: any) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should tiebreak according to platinum score", async (t: any) => {
|
||||
await db["personal-bests"].insert({
|
||||
await MONGODB_KILL["personal-bests"].insert({
|
||||
...TestingOngekiScorePB,
|
||||
scoreData: {
|
||||
...TestingOngekiScorePB.scoreData,
|
||||
@@ -358,7 +358,7 @@ t.test("ONGEKI Implementation", (t: any) => {
|
||||
userID: 1,
|
||||
});
|
||||
|
||||
await db["personal-bests"].insert({
|
||||
await MONGODB_KILL["personal-bests"].insert({
|
||||
...TestingOngekiScorePB,
|
||||
scoreData: {
|
||||
...TestingOngekiScorePB.scoreData,
|
||||
@@ -368,7 +368,7 @@ t.test("ONGEKI Implementation", (t: any) => {
|
||||
userID: 2,
|
||||
});
|
||||
|
||||
await db["personal-bests"].insert({
|
||||
await MONGODB_KILL["personal-bests"].insert({
|
||||
...TestingOngekiScorePB,
|
||||
scoreData: {
|
||||
...TestingOngekiScorePB.scoreData,
|
||||
@@ -380,7 +380,7 @@ t.test("ONGEKI Implementation", (t: any) => {
|
||||
|
||||
await UpdateChartRanking("ongeki", "Single", TestingOngekiChart.chartID);
|
||||
|
||||
const pbs = (await db["personal-bests"].find({
|
||||
const pbs = (await MONGODB_KILL["personal-bests"].find({
|
||||
chartID: TestingOngekiChart.chartID,
|
||||
})) as Array<PBScoreDocument<"ongeki:Single">>;
|
||||
|
||||
@@ -401,7 +401,7 @@ t.test("ONGEKI Implementation", (t: any) => {
|
||||
});
|
||||
|
||||
t.test("Should not tiebreak if tech scores differ", async (t: any) => {
|
||||
await db["personal-bests"].insert({
|
||||
await MONGODB_KILL["personal-bests"].insert({
|
||||
...TestingOngekiScorePB,
|
||||
scoreData: {
|
||||
...TestingOngekiScorePB.scoreData,
|
||||
@@ -411,7 +411,7 @@ t.test("ONGEKI Implementation", (t: any) => {
|
||||
userID: 1,
|
||||
});
|
||||
|
||||
await db["personal-bests"].insert({
|
||||
await MONGODB_KILL["personal-bests"].insert({
|
||||
...TestingOngekiScorePB,
|
||||
scoreData: {
|
||||
...TestingOngekiScorePB.scoreData,
|
||||
@@ -423,7 +423,7 @@ t.test("ONGEKI Implementation", (t: any) => {
|
||||
|
||||
await UpdateChartRanking("ongeki", "Single", TestingOngekiChart.chartID);
|
||||
|
||||
const [pb1, pb2] = (await db["personal-bests"].find(
|
||||
const [pb1, pb2] = (await MONGODB_KILL["personal-bests"].find(
|
||||
{
|
||||
chartID: TestingOngekiChart.chartID,
|
||||
},
|
||||
+3
-3
@@ -2,7 +2,7 @@ import type { ProvidedMetrics, ScoreData } from "tachi-common";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestingPopnChart } from "#test-utils/test-data";
|
||||
@@ -159,8 +159,8 @@ t.test("Pop'n Implementation", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should join best clear medal", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestClearMedal",
|
||||
scoreData: {
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestSnapshot } from "#test-utils/single-process-snapshot";
|
||||
@@ -172,8 +172,8 @@ t.test("SDVX Implementation", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should join best lamp", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
@@ -197,8 +197,8 @@ t.test("SDVX Implementation", (t) => {
|
||||
});
|
||||
|
||||
t.test("Should join best EX SCore", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestingUSCChart } from "#test-utils/test-data";
|
||||
@@ -166,8 +166,8 @@ for (const [playtype, impl] of [
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should join best lamp", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
+3
-3
@@ -3,7 +3,7 @@ import type { ProvidedMetrics, ScoreData, ScoreDocument } from "tachi-common";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestSnapshot } from "#test-utils/single-process-snapshot";
|
||||
@@ -144,8 +144,8 @@ t.test("WACCA Implementation", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should join best lamp", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
await MONGODB_KILL.scores.insert(mockScore);
|
||||
await MONGODB_KILL.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
} from "tachi-common";
|
||||
import type { ExtractEnumMetricNames } from "tachi-common/types/metrics";
|
||||
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
|
||||
// insane typemagic to get mongodb-safe names for this GPT's metrics.
|
||||
type MetricKeys<GPT extends GPTString> = Exclude<
|
||||
@@ -55,7 +55,7 @@ export function CreatePBMergeFor<GPT extends GPTString>(
|
||||
applicator: (base: PBScoreDocumentNoRank<GPT>, score: ScoreDocument<GPT>) => void,
|
||||
): PBMergeFunction<GPT> {
|
||||
return async (userID, chartID, asOfTimestamp, base) => {
|
||||
const bestScoreFor = (await db.scores.findOne(
|
||||
const bestScoreFor = (await MONGODB_KILL.scores.findOne(
|
||||
HandleAsOf(
|
||||
{
|
||||
userID,
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
ScoreRatingAlgorithms,
|
||||
} from "tachi-common";
|
||||
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
|
||||
/**
|
||||
* Curries a function that returns the sum of N best ratings on `key`.
|
||||
@@ -29,7 +29,7 @@ function CalcN<GPT extends GPTString>(
|
||||
multiplier = 1,
|
||||
) {
|
||||
return async (game: GameGroup, playtype: Playtype, userID: integer) => {
|
||||
const sc = await db["personal-bests"].find(
|
||||
const sc = await MONGODB_KILL["personal-bests"].find(
|
||||
{
|
||||
game,
|
||||
playtype,
|
||||
@@ -100,7 +100,7 @@ export async function GetBestRatingOnSongs(
|
||||
ratingProp: "skill",
|
||||
limit: integer,
|
||||
): Promise<Array<PBScoreDocument>> {
|
||||
const r: Array<{ doc: PBScoreDocument }> = await db["personal-bests"].aggregate([
|
||||
const r: Array<{ doc: PBScoreDocument }> = await MONGODB_KILL["personal-bests"].aggregate([
|
||||
{
|
||||
$match: {
|
||||
game,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import db from "#services/pg/db";
|
||||
import DB from "#services/pg/db";
|
||||
import { type ActionTaker, type AnonActionTaker, MakeActionGuts } from "bliss";
|
||||
import { type ActionSignature } from "bliss/actions";
|
||||
import { zodPermission } from "tachi-common";
|
||||
import { z } from "zod";
|
||||
|
||||
const APP_NAME = "TACHI_SERVER";
|
||||
@@ -13,6 +14,29 @@ export const ActionSignatures = {
|
||||
input: z.object({}),
|
||||
output: z.object({}),
|
||||
},
|
||||
INSTALL_BUILTIN_CLIENT: {
|
||||
input: z.object({
|
||||
clientID: z.string(),
|
||||
name: z.string(),
|
||||
webhookUri: z.url().nullable(),
|
||||
redirectUri: z.url().nullable(),
|
||||
permissions: z
|
||||
.object({
|
||||
customise_profile: z.boolean(),
|
||||
customise_score: z.boolean(),
|
||||
customise_session: z.boolean(),
|
||||
delete_score: z.boolean(),
|
||||
manage_rivals: z.boolean(),
|
||||
manage_targets: z.boolean(),
|
||||
submit_score: z.boolean(),
|
||||
manage_challenges: z.boolean(),
|
||||
})
|
||||
.partial(),
|
||||
apiKeyTemplate: z.string().nullable(),
|
||||
apiKeyFilename: z.string().nullable(),
|
||||
}),
|
||||
output: z.object({}),
|
||||
},
|
||||
} satisfies Record<string, ActionSignature>;
|
||||
|
||||
export const AnonActionSignatures = {
|
||||
@@ -30,7 +54,7 @@ export function MakeAnonAction<A extends AnonActionName>(
|
||||
fn: AnonActionFn<A>,
|
||||
): AnonActionFn<A> {
|
||||
return MakeActionGuts({
|
||||
db,
|
||||
db: DB,
|
||||
appName: APP_NAME,
|
||||
kind,
|
||||
// @ts-expect-error we're being creative with the types here
|
||||
@@ -44,7 +68,7 @@ export function MakeAnonAction<A extends AnonActionName>(
|
||||
*/
|
||||
export function MakeAction<A extends ActionName>(kind: A, fn: ActionFn<A>): ActionFn<A> {
|
||||
return MakeActionGuts({
|
||||
db,
|
||||
db: DB,
|
||||
appName: APP_NAME,
|
||||
kind,
|
||||
// @ts-expect-error we're being creative with the types here
|
||||
|
||||
@@ -16,7 +16,7 @@ import type {
|
||||
UserDocument,
|
||||
} from "tachi-common";
|
||||
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import {
|
||||
GetRecentlyAchievedGoals,
|
||||
GetRecentlyAchievedQuests,
|
||||
@@ -79,7 +79,7 @@ export async function GetRecentActivity(
|
||||
timeStarted: { $lt: startFrom === null ? Infinity : startFrom },
|
||||
};
|
||||
|
||||
const recentSessions = await db.sessions.find(initialSessionQuery, {
|
||||
const recentSessions = await MONGODB_KILL.sessions.find(initialSessionQuery, {
|
||||
sort: {
|
||||
timeStarted: -1,
|
||||
},
|
||||
@@ -108,7 +108,7 @@ export async function GetRecentActivity(
|
||||
{ goals, goalSubs },
|
||||
{ quests, questSubs },
|
||||
] = await Promise.all([
|
||||
db["class-achievements"].find(
|
||||
MONGODB_KILL["class-achievements"].find(
|
||||
{
|
||||
...baseQuery,
|
||||
timeAchieved: timeConstraint,
|
||||
@@ -119,7 +119,7 @@ export async function GetRecentActivity(
|
||||
},
|
||||
},
|
||||
),
|
||||
db.scores.find(
|
||||
MONGODB_KILL.scores.find(
|
||||
{
|
||||
...baseQuery,
|
||||
highlight: true,
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import type { PrudenceError } from "prudence";
|
||||
|
||||
import { ACTION_InstallBuiltinClient } from "#actions/install-builtin-client.js";
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { ServerConfig, TachiConfig } from "#lib/setup/config";
|
||||
import { type TachiAPIClientDocument, UserAuthLevels } from "tachi-common";
|
||||
import _ from "lodash";
|
||||
import { type TachiAPIClientDocument } from "tachi-common";
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import db from "#services/mongo/db";
|
||||
import { DatabaseSchemas } from "#services/mongo/schemas";
|
||||
import { Random20Hex } from "#utils/misc";
|
||||
import { FormatPrError } from "#utils/prudence";
|
||||
import { GetClientByID } from "#utils/queries/api-clients.js";
|
||||
import { GetFirstAdmin } from "#utils/user.js";
|
||||
import fjsh from "fast-json-stable-hash";
|
||||
|
||||
type DefaultClients = Array<Omit<TachiAPIClientDocument, "author" | "clientSecret">>;
|
||||
@@ -307,56 +305,48 @@ export async function LoadDefaultClients() {
|
||||
}
|
||||
|
||||
async function LoadClients(clients: DefaultClients) {
|
||||
const firstAdmin = await db.users.findOne({
|
||||
authLevel: UserAuthLevels.ADMIN,
|
||||
});
|
||||
|
||||
if (!firstAdmin) {
|
||||
log.warn(`There are no admins on this instance of tachi-server. We cannot create default API Clients!
|
||||
Chances are, you're seeing this message because you just bootstrapped Tachi.
|
||||
You'll need to set a user's authLevel to ${UserAuthLevels.ADMIN}.
|
||||
If you have no users, go create an account using the frontend, then run pnpm make-user-admin 1.`);
|
||||
return;
|
||||
}
|
||||
const firstAdmin = await GetFirstAdmin();
|
||||
|
||||
for (const client of clients) {
|
||||
const exists = await db["api-clients"].findOne(
|
||||
const exists = await GetClientByID(client.clientID);
|
||||
|
||||
if (exists) {
|
||||
const existsOmitted = _.omit(exists, "author", "clientSecret");
|
||||
|
||||
// Skip if nothing has changed.
|
||||
if (fjsh.hash(existsOmitted, "sha256") === fjsh.hash(client, "sha256")) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
await ACTION_InstallBuiltinClient(
|
||||
{
|
||||
clientID: client.clientID,
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
clientSecret: 0,
|
||||
author: 0,
|
||||
ip: null,
|
||||
acct: {
|
||||
id: firstAdmin.id,
|
||||
username: firstAdmin.username,
|
||||
},
|
||||
},
|
||||
{
|
||||
clientID: client.clientID,
|
||||
name: client.name,
|
||||
permissions: {
|
||||
customise_profile: false,
|
||||
customise_score: false,
|
||||
customise_session: false,
|
||||
delete_score: false,
|
||||
manage_rivals: false,
|
||||
manage_targets: false,
|
||||
submit_score: false,
|
||||
manage_challenges: false,
|
||||
},
|
||||
apiKeyFilename: client.apiKeyFilename,
|
||||
apiKeyTemplate: client.apiKeyTemplate,
|
||||
redirectUri: client.redirectUri,
|
||||
webhookUri: client.webhookUri,
|
||||
},
|
||||
);
|
||||
|
||||
// Skip if nothing has changed.
|
||||
if (fjsh.hash(exists, "sha256") === fjsh.hash(client, "sha256")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const realClient: TachiAPIClientDocument = {
|
||||
...client,
|
||||
clientSecret: `CS${Random20Hex()}`,
|
||||
author: 1,
|
||||
};
|
||||
|
||||
try {
|
||||
DatabaseSchemas["api-clients"](realClient);
|
||||
} catch (err) {
|
||||
log.error(`Invalid API Client ${client.name}: ${FormatPrError(err as PrudenceError)}.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// No replaceOne support in monk -- have to do this.
|
||||
await db["api-clients"].remove({
|
||||
clientID: client.clientID,
|
||||
});
|
||||
|
||||
await db["api-clients"].insert(realClient);
|
||||
|
||||
log.info(`Loaded/Modified new built-in client ${client.name}.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { type Selection } from "kysely";
|
||||
import { type APIPermissions, type TachiAPIClientDocument } from "tachi-common";
|
||||
import { type Database } from "tachi-db";
|
||||
|
||||
export const SELECT_API_CLIENT = [
|
||||
"priv_api_client.client_id",
|
||||
"priv_api_client.client_secret",
|
||||
"priv_api_client.name",
|
||||
"priv_api_client.author",
|
||||
"priv_api_client.pm_customise_profile",
|
||||
"priv_api_client.pm_customise_score",
|
||||
"priv_api_client.pm_customise_session",
|
||||
"priv_api_client.pm_delete_score",
|
||||
"priv_api_client.pm_manage_rivals",
|
||||
"priv_api_client.pm_manage_targets",
|
||||
"priv_api_client.pm_submit_score",
|
||||
"priv_api_client.pm_manage_challenges",
|
||||
"priv_api_client.api_key_filename",
|
||||
"priv_api_client.api_key_template",
|
||||
"priv_api_client.webhook_uri",
|
||||
"priv_api_client.redirect_uri",
|
||||
] as const;
|
||||
|
||||
export function ToAPIClientDocument(
|
||||
row: Selection<Database, "priv_api_client", (typeof SELECT_API_CLIENT)[number]>,
|
||||
): TachiAPIClientDocument {
|
||||
const requestedPermissions: Array<APIPermissions> = [];
|
||||
if (row.pm_customise_profile) {
|
||||
requestedPermissions.push("customise_profile");
|
||||
}
|
||||
if (row.pm_customise_score) {
|
||||
requestedPermissions.push("customise_score");
|
||||
}
|
||||
if (row.pm_customise_session) {
|
||||
requestedPermissions.push("customise_session");
|
||||
}
|
||||
if (row.pm_delete_score) {
|
||||
requestedPermissions.push("delete_score");
|
||||
}
|
||||
if (row.pm_manage_rivals) {
|
||||
requestedPermissions.push("manage_rivals");
|
||||
}
|
||||
if (row.pm_manage_targets) {
|
||||
requestedPermissions.push("manage_targets");
|
||||
}
|
||||
if (row.pm_submit_score) {
|
||||
requestedPermissions.push("submit_score");
|
||||
}
|
||||
if (row.pm_manage_challenges) {
|
||||
requestedPermissions.push("manage_challenges");
|
||||
}
|
||||
|
||||
return {
|
||||
clientID: row.client_id,
|
||||
clientSecret: row.client_secret,
|
||||
name: row.name,
|
||||
author: row.author,
|
||||
apiKeyFilename: row.api_key_filename,
|
||||
apiKeyTemplate: row.api_key_template,
|
||||
redirectUri: row.redirect_uri,
|
||||
requestedPermissions,
|
||||
webhookUri: row.webhook_uri,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { type Selection } from "kysely";
|
||||
import { type integer, type UserSettingsDocument } from "tachi-common";
|
||||
import { type Database } from "tachi-db";
|
||||
|
||||
export const SELECT_USER_SETTINGS = [
|
||||
"account_settings.user_id",
|
||||
"account_settings.pf_invisible",
|
||||
"account_settings.pf_developer_mode",
|
||||
"account_settings.pf_advanced_mode",
|
||||
"account_settings.pf_contentious_content",
|
||||
"account_settings.pf_deletable_scores",
|
||||
] as const;
|
||||
|
||||
export function ToUserSettingsDocument(
|
||||
following: Array<integer>,
|
||||
row: Selection<Database, "account_settings", (typeof SELECT_USER_SETTINGS)[number]>,
|
||||
): UserSettingsDocument {
|
||||
return {
|
||||
userID: row.user_id,
|
||||
following,
|
||||
preferences: {
|
||||
invisible: row.pf_invisible,
|
||||
developerMode: row.pf_developer_mode,
|
||||
advancedMode: row.pf_advanced_mode,
|
||||
contentiousContent: row.pf_contentious_content,
|
||||
deletableScores: row.pf_deletable_scores,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { AuthLevelToInt } from "#utils/conversion.js";
|
||||
import { ISO8601ToUnixMilliseconds } from "#utils/time.js";
|
||||
import { type Selection } from "kysely";
|
||||
import { type UserBadges, type UserDocument } from "tachi-common";
|
||||
import { type Database } from "tachi-db";
|
||||
|
||||
export const SELECT_USER = [
|
||||
"account.id",
|
||||
"account.bd_alpha",
|
||||
"account.bd_beta",
|
||||
"account.bd_dev_team",
|
||||
"account.about",
|
||||
"account.auth_level",
|
||||
"account.custom_banner_location",
|
||||
"account.custom_pfp_location",
|
||||
"account.joined",
|
||||
"account.last_seen",
|
||||
"account.sm_discord",
|
||||
"account.sm_twitter",
|
||||
"account.sm_github",
|
||||
"account.sm_steam",
|
||||
"account.sm_youtube",
|
||||
"account.sm_twitch",
|
||||
"account.status",
|
||||
"account.username",
|
||||
"account.normalized_username",
|
||||
"account.is_supporter",
|
||||
] as const;
|
||||
|
||||
export function ToUserDocument(
|
||||
row: Selection<Database, "account", (typeof SELECT_USER)[number]>,
|
||||
): UserDocument {
|
||||
const badges: Array<UserBadges> = [];
|
||||
|
||||
if (row.bd_alpha) {
|
||||
badges.push("alpha");
|
||||
}
|
||||
if (row.bd_beta) {
|
||||
badges.push("beta");
|
||||
}
|
||||
if (row.bd_dev_team) {
|
||||
badges.push("dev-team");
|
||||
}
|
||||
|
||||
return {
|
||||
about: row.about,
|
||||
authLevel: AuthLevelToInt(row.auth_level),
|
||||
badges,
|
||||
customBannerLocation: row.custom_banner_location,
|
||||
customPfpLocation: row.custom_pfp_location,
|
||||
id: row.id,
|
||||
joinDate: ISO8601ToUnixMilliseconds(row.joined),
|
||||
lastSeen: ISO8601ToUnixMilliseconds(row.last_seen),
|
||||
socialMedia: {
|
||||
discord: row.sm_discord,
|
||||
twitter: row.sm_twitter,
|
||||
github: row.sm_github,
|
||||
steam: row.sm_steam,
|
||||
youtube: row.sm_youtube,
|
||||
twitch: row.sm_twitch,
|
||||
},
|
||||
status: row.status,
|
||||
username: row.username,
|
||||
usernameLowercase: row.normalized_username,
|
||||
isSupporter: row.is_supporter,
|
||||
};
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import t from "tap";
|
||||
|
||||
@@ -10,7 +10,7 @@ t.test("#CreateGameSettings", (t) => {
|
||||
t.test("Should create a new user's settings.", async (t) => {
|
||||
await CreateGameSettings(1, "bms", "7K");
|
||||
|
||||
const data = await db["game-settings"].findOne({
|
||||
const data = await MONGODB_KILL["game-settings"].findOne({
|
||||
userID: 1,
|
||||
game: "bms",
|
||||
playtype: "7K",
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { GameGroup, integer, Playtype } from "tachi-common";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
|
||||
/**
|
||||
* Create GameSettings for a UGPT (which contains their preferences).
|
||||
*/
|
||||
export async function CreateGameSettings(userID: integer, game: GameGroup, playtype: Playtype) {
|
||||
const exists = await db["game-settings"].findOne({
|
||||
const exists = await MONGODB_KILL["game-settings"].findOne({
|
||||
userID,
|
||||
game,
|
||||
playtype,
|
||||
@@ -32,7 +32,7 @@ export async function CreateGameSettings(userID: integer, game: GameGroup, playt
|
||||
};
|
||||
}
|
||||
|
||||
await db["game-settings"].insert({
|
||||
await MONGODB_KILL["game-settings"].insert({
|
||||
userID,
|
||||
game,
|
||||
playtype,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ChartDocument, integer, Playtypes } from "tachi-common";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { PoyashiBPI } from "rg-stats";
|
||||
|
||||
interface PlaylistEntry {
|
||||
@@ -110,7 +110,7 @@ export const CUSTOM_TACHI_IIDX_PLAYLISTS: Array<TachiIIDXPlaylist> = [
|
||||
bounds.push([cutoffs[i], cutoffs[i + 1]] as [number, number]);
|
||||
}
|
||||
|
||||
const charts = (await db.charts.iidx.find({
|
||||
const charts = (await MONGODB_KILL.charts.iidx.find({
|
||||
"data.kaidenAverage": { $ne: null },
|
||||
playtype,
|
||||
})) as Array<ChartDocument<"iidx:DP" | "iidx:SP">>;
|
||||
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf, mkFakeImport } from "#test-utils/misc";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import { TestingIIDXSPScore } from "#test-utils/test-data";
|
||||
@@ -9,7 +9,7 @@ import { RevertImport } from "./imports";
|
||||
t.test("#RevertImport", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
t.beforeEach(() =>
|
||||
db.scores.insert([
|
||||
MONGODB_KILL.scores.insert([
|
||||
dmf(TestingIIDXSPScore, { scoreID: "score_1" }),
|
||||
dmf(TestingIIDXSPScore, { scoreID: "score_2" }),
|
||||
dmf(TestingIIDXSPScore, { scoreID: "score_3" }),
|
||||
@@ -21,16 +21,16 @@ t.test("#RevertImport", (t) => {
|
||||
scoreIDs: ["score_1", "score_2"],
|
||||
});
|
||||
|
||||
await db.imports.insert(importDoc);
|
||||
await MONGODB_KILL.imports.insert(importDoc);
|
||||
|
||||
await RevertImport(importDoc);
|
||||
|
||||
const dbRes = await db.imports.findOne({ importID: "fake_import" });
|
||||
const dbRes = await MONGODB_KILL.imports.findOne({ importID: "fake_import" });
|
||||
|
||||
t.equal(dbRes, null, "Should have removed the import from the DB.");
|
||||
|
||||
t.resolveMatch(
|
||||
db.scores.findOne({ userID: 1, scoreID: "score_1" }),
|
||||
MONGODB_KILL.scores.findOne({ userID: 1, scoreID: "score_1" }),
|
||||
|
||||
// @ts-expect-error https://github.com/DefinitelyTyped/DefinitelyTyped/pull/60020
|
||||
null,
|
||||
@@ -38,7 +38,7 @@ t.test("#RevertImport", (t) => {
|
||||
);
|
||||
|
||||
t.resolveMatch(
|
||||
db.scores.findOne({ userID: 1, scoreID: "score_2" }),
|
||||
MONGODB_KILL.scores.findOne({ userID: 1, scoreID: "score_2" }),
|
||||
|
||||
// @ts-expect-error see above
|
||||
null,
|
||||
@@ -46,7 +46,7 @@ t.test("#RevertImport", (t) => {
|
||||
);
|
||||
|
||||
t.resolveMatch(
|
||||
db.scores.findOne({ userID: 1, scoreID: "score_3" }),
|
||||
MONGODB_KILL.scores.findOne({ userID: 1, scoreID: "score_3" }),
|
||||
|
||||
// @ts-expect-error see above
|
||||
{ scoreID: "score_3" },
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
UnsetOngoingImportLock,
|
||||
} from "#lib/score-import/framework/import-locks/lock";
|
||||
import { DeleteMultipleScores } from "#lib/score-mutation/delete-scores";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
|
||||
interface OngoingImportError {
|
||||
tag: "ONGOING_IMPORT";
|
||||
@@ -43,7 +43,7 @@ export async function RevertImport(importDoc: ImportDocument): Promise<OngoingIm
|
||||
);
|
||||
|
||||
try {
|
||||
await db.imports.remove({ importID: importDoc.importID });
|
||||
await MONGODB_KILL.imports.remove({ importID: importDoc.importID });
|
||||
|
||||
log.info(`Reverted and deleted import '${importDoc.importID}'.`);
|
||||
} catch (err) {
|
||||
@@ -63,5 +63,5 @@ export async function RevertImport(importDoc: ImportDocument): Promise<OngoingIm
|
||||
* Retrieve the scores inside this import.
|
||||
*/
|
||||
export function GetImportScores(importDoc: ImportDocument) {
|
||||
return db.scores.find({ scoreID: { $in: importDoc.scoreIDs } });
|
||||
return MONGODB_KILL.scores.find({ scoreID: { $in: importDoc.scoreIDs } });
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { PullDatabaseSeeds } from "#lib/seeds/repo";
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { WrapScriptPromise } from "#utils/misc";
|
||||
|
||||
/**
|
||||
@@ -15,7 +15,7 @@ export async function BacksyncBMSPMSSongsAndCharts() {
|
||||
log.info(`Fetching ${game} songs from DB.`);
|
||||
|
||||
// did you know, this code is liable to blow up in my face and OOM one day?
|
||||
let songs = await db.anySongs[game].find({});
|
||||
let songs = await MONGODB_KILL.anySongs[game].find({});
|
||||
|
||||
log.info(`Found ${songs.length} ${game} songs.`);
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function BacksyncBMSPMSSongsAndCharts() {
|
||||
songs = null;
|
||||
|
||||
log.info(`Fetching ${game} charts from DB.`);
|
||||
let charts = await db.anyCharts[game].find({});
|
||||
let charts = await MONGODB_KILL.anyCharts[game].find({});
|
||||
|
||||
log.info(`Found ${charts.length} ${game} charts.`);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { FilterQuery } from "mongodb";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { DeorphanIfInQueue } from "#lib/orphan-queue/orphan-queue";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { InitaliseFolderChartLookup } from "#utils/folder";
|
||||
import { FormatBMSTables, WrapScriptPromise } from "#utils/misc";
|
||||
import { type BMSTableEntry, LoadBMSTable } from "bms-table-loader";
|
||||
@@ -34,7 +34,7 @@ async function HandleTableRemovals(
|
||||
|
||||
// *unless this script crashes, in which case it's
|
||||
// no longer temporary
|
||||
const existingCharts = (await db.charts.bms.find({
|
||||
const existingCharts = (await MONGODB_KILL.charts.bms.find({
|
||||
playtype,
|
||||
"data.tableFolders.table": prefix,
|
||||
})) as unknown as Array<ChartDocument<"bms:7K" | "bms:14K">>;
|
||||
@@ -85,7 +85,7 @@ async function HandleTableRemovals(
|
||||
|
||||
// remove this table info from all of the charts that no longer
|
||||
// exist in the table.
|
||||
await db.charts.bms.update(
|
||||
await MONGODB_KILL.charts.bms.update(
|
||||
{
|
||||
chartID: { $in: toRemove },
|
||||
},
|
||||
@@ -115,7 +115,7 @@ async function ImportTableLevels(
|
||||
.filter((e) => e.checksum.type === "md5")
|
||||
.map((e) => e.checksum.value);
|
||||
|
||||
await db.charts.bms.update(
|
||||
await MONGODB_KILL.charts.bms.update(
|
||||
{
|
||||
"data.hashMD5": { $in: md5s },
|
||||
},
|
||||
@@ -127,7 +127,7 @@ async function ImportTableLevels(
|
||||
{ multi: true },
|
||||
);
|
||||
|
||||
await db.charts.bms.update(
|
||||
await MONGODB_KILL.charts.bms.update(
|
||||
{
|
||||
"data.hashSHA256": { $in: sha256s },
|
||||
},
|
||||
@@ -154,7 +154,8 @@ async function ImportTableLevels(
|
||||
}
|
||||
}
|
||||
|
||||
let chart: ChartDocument<"bms:7K" | "bms:14K"> | null = await db.charts.bms.findOne(query);
|
||||
let chart: ChartDocument<"bms:7K" | "bms:14K"> | null =
|
||||
await MONGODB_KILL.charts.bms.findOne(query);
|
||||
|
||||
if (!chart) {
|
||||
// didn't find it in the DB?
|
||||
@@ -195,7 +196,7 @@ async function ImportTableLevels(
|
||||
return a.level.localeCompare(b.level);
|
||||
});
|
||||
|
||||
await db.charts.bms.update(
|
||||
await MONGODB_KILL.charts.bms.update(
|
||||
{
|
||||
chartID: chart.chartID,
|
||||
},
|
||||
@@ -206,7 +207,7 @@ async function ImportTableLevels(
|
||||
},
|
||||
);
|
||||
|
||||
await db.songs.bms.update(
|
||||
await MONGODB_KILL.songs.bms.update(
|
||||
{
|
||||
id: chart.songID,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { UserGameStats, UserGameStatsSnapshotDocument } from "tachi-common";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { GetMillisecondsSince } from "#utils/misc";
|
||||
import { GetAllRankings } from "#utils/user";
|
||||
|
||||
@@ -16,7 +16,9 @@ let batchWrite: Array<UserGameStatsSnapshotDocument> = [];
|
||||
export async function UGSSnapshot() {
|
||||
const timeStart = process.hrtime.bigint();
|
||||
|
||||
const alreadyExists = await db["game-stats-snapshots"].findOne({ timestamp: currentTime });
|
||||
const alreadyExists = await MONGODB_KILL["game-stats-snapshots"].findOne({
|
||||
timestamp: currentTime,
|
||||
});
|
||||
|
||||
if (alreadyExists) {
|
||||
log.warn(
|
||||
@@ -31,7 +33,7 @@ export async function UGSSnapshot() {
|
||||
log.info(`Snapshotting UserGameStats.`);
|
||||
|
||||
try {
|
||||
await db["game-stats"]
|
||||
await MONGODB_KILL["game-stats"]
|
||||
.find({})
|
||||
|
||||
// @ts-expect-error faulty TS types
|
||||
@@ -41,7 +43,11 @@ export async function UGSSnapshot() {
|
||||
log.debug(`Snapshotting ${ugs.userID} ${ugs.playtype} ${ugs.game}.`);
|
||||
|
||||
const [playcount, rankings] = await Promise.all([
|
||||
db.scores.count({ userID: ugs.userID, playtype: ugs.playtype, game: ugs.game }),
|
||||
MONGODB_KILL.scores.count({
|
||||
userID: ugs.userID,
|
||||
playtype: ugs.playtype,
|
||||
game: ugs.game,
|
||||
}),
|
||||
GetAllRankings(ugs),
|
||||
]);
|
||||
|
||||
@@ -56,7 +62,7 @@ export async function UGSSnapshot() {
|
||||
|
||||
if (batchWrite.length >= 500) {
|
||||
log.debug(`Flushed batch.`);
|
||||
await db["game-stats-snapshots"].insert(batchWrite);
|
||||
await MONGODB_KILL["game-stats-snapshots"].insert(batchWrite);
|
||||
|
||||
batchWrite = [];
|
||||
}
|
||||
@@ -65,7 +71,7 @@ export async function UGSSnapshot() {
|
||||
});
|
||||
|
||||
if (batchWrite.length) {
|
||||
await db["game-stats-snapshots"].insert(batchWrite);
|
||||
await MONGODB_KILL["game-stats-snapshots"].insert(batchWrite);
|
||||
}
|
||||
|
||||
log.info(
|
||||
@@ -84,7 +90,7 @@ export async function UGSSnapshot() {
|
||||
|
||||
log.info(`Removing all snapshots at this timestamp (${currentTime}).`);
|
||||
|
||||
await db["game-stats-snapshots"].remove({ timestamp: currentTime });
|
||||
await MONGODB_KILL["game-stats-snapshots"].remove({ timestamp: currentTime });
|
||||
|
||||
log.info(`Removed.`);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { BacksyncCollection, PullDatabaseSeeds } from "#lib/seeds/repo";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { RecalcAllScores } from "#utils/calculations/recalc-scores";
|
||||
import fetch from "#utils/fetch";
|
||||
import { WrapScriptPromise } from "#utils/misc";
|
||||
@@ -173,7 +173,7 @@ export async function UpdatePoyashiData() {
|
||||
|
||||
await repo.Destroy();
|
||||
|
||||
await BacksyncCollection("charts-iidx", db.charts.iidx, "Update BPI Data");
|
||||
await BacksyncCollection("charts-iidx", MONGODB_KILL.charts.iidx, "Update BPI Data");
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { BacksyncCollection } from "#lib/seeds/repo";
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { RecalcAllScores } from "#utils/calculations/recalc-scores";
|
||||
import fetch from "#utils/fetch";
|
||||
import { WrapScriptPromise } from "#utils/misc";
|
||||
@@ -56,7 +56,7 @@ export async function UpdateDPTiers() {
|
||||
continue;
|
||||
}
|
||||
|
||||
const result = await db.charts.iidx.update(
|
||||
const result = await MONGODB_KILL.charts.iidx.update(
|
||||
{
|
||||
versions: "29",
|
||||
playtype: "DP",
|
||||
@@ -92,7 +92,7 @@ export async function UpdateDPTiers() {
|
||||
|
||||
log.info(`Recalced those scores.`);
|
||||
|
||||
await BacksyncCollection("charts-iidx", db.charts.iidx, "Update DP Tierlist");
|
||||
await BacksyncCollection("charts-iidx", MONGODB_KILL.charts.iidx, "Update DP Tierlist");
|
||||
}
|
||||
|
||||
log.info("Done.");
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { ChartDocument, Difficulties, integer } from "tachi-common";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { BacksyncCollection } from "#lib/seeds/repo";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { RecalcAllScores } from "#utils/calculations/recalc-scores";
|
||||
import { WrapScriptPromise } from "#utils/misc";
|
||||
import { FindChartWithPTDF } from "#utils/queries/charts";
|
||||
@@ -172,7 +172,7 @@ async function FetchSP12Data() {
|
||||
|
||||
updatedChartIDs.push(chart.chartID);
|
||||
|
||||
await db.charts.iidx.update(
|
||||
await MONGODB_KILL.charts.iidx.update(
|
||||
{
|
||||
chartID: chart.chartID,
|
||||
},
|
||||
@@ -203,7 +203,7 @@ async function FetchSP12Data() {
|
||||
|
||||
log.info(`Finished recalcing scores.`);
|
||||
|
||||
await BacksyncCollection("charts-iidx", db.charts.iidx, "Update SP12 Tierlist");
|
||||
await BacksyncCollection("charts-iidx", MONGODB_KILL.charts.iidx, "Update SP12 Tierlist");
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import {
|
||||
FormatGameGroup,
|
||||
type GameGroup,
|
||||
@@ -23,7 +23,7 @@ export async function SendSetRivalNotification(
|
||||
game: GameGroup,
|
||||
playtype: Playtype,
|
||||
) {
|
||||
const alreadyBeenPinged = await db.notifications.findOne({
|
||||
const alreadyBeenPinged = await MONGODB_KILL.notifications.findOne({
|
||||
sentTo: toUserID,
|
||||
"body.type": "RIVALED_BY",
|
||||
"body.content": {
|
||||
@@ -60,13 +60,13 @@ export async function SendSiteAnnouncementNotification(
|
||||
|
||||
if (maybeGame && maybePlaytype) {
|
||||
toUserIDs = (
|
||||
await db["game-stats"].find(
|
||||
await MONGODB_KILL["game-stats"].find(
|
||||
{ game: maybeGame, playtype: maybePlaytype },
|
||||
{ projection: { userID: 1 } },
|
||||
)
|
||||
).map((e) => e.userID);
|
||||
} else {
|
||||
toUserIDs = (await db.users.find({}, { projection: { id: 1 } })).map((e) => e.id);
|
||||
toUserIDs = (await MONGODB_KILL.users.find({}, { projection: { id: 1 } })).map((e) => e.id);
|
||||
}
|
||||
|
||||
return BulkSendNotification(title, toUserIDs, {
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import t from "tap";
|
||||
|
||||
@@ -13,7 +13,7 @@ t.test("#SendNotification", (t) => {
|
||||
content: { questID: "foo", game: "iidx", playtype: "SP" },
|
||||
});
|
||||
|
||||
const dbRes = await db.notifications.findOne({
|
||||
const dbRes = await MONGODB_KILL.notifications.findOne({
|
||||
sentTo: 1,
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ t.test("#BulkSendNotification", (t) => {
|
||||
content: { questID: "foo", game: "iidx", playtype: "SP" },
|
||||
});
|
||||
|
||||
const dbRes = await db.notifications.findOne({
|
||||
const dbRes = await MONGODB_KILL.notifications.findOne({
|
||||
sentTo: 1,
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ t.test("#BulkSendNotification", (t) => {
|
||||
},
|
||||
});
|
||||
|
||||
const dbRes2 = await db.notifications.findOne({
|
||||
const dbRes2 = await MONGODB_KILL.notifications.findOne({
|
||||
sentTo: 2,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { integer, NotificationBody, NotificationDocument } from "tachi-common";
|
||||
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { Random20Hex } from "#utils/misc";
|
||||
|
||||
function ConstructNotificationDoc(
|
||||
@@ -28,7 +28,7 @@ function ConstructNotificationDoc(
|
||||
export function SendNotification(title: string, toUserID: integer, body: NotificationBody) {
|
||||
const notification = ConstructNotificationDoc(title, toUserID, body);
|
||||
|
||||
return db.notifications.insert(notification);
|
||||
return MONGODB_KILL.notifications.insert(notification);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,5 +42,5 @@ export function BulkSendNotification(
|
||||
) {
|
||||
const notifications = toUserIDs.map((e) => ConstructNotificationDoc(title, e, body));
|
||||
|
||||
return db.notifications.insert(notifications);
|
||||
return MONGODB_KILL.notifications.insert(notifications);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
} from "tachi-common";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { GetNextCounterValue } from "#utils/db";
|
||||
import { DedupeArr } from "#utils/misc";
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function HandleOrphanQueue<GPT extends GPTString>(
|
||||
) {
|
||||
log.debug(`Received orphanqueue request for ${chartName}.`);
|
||||
|
||||
const orphanChart = await db["orphan-chart-queue"].findOne(
|
||||
const orphanChart = await MONGODB_KILL["orphan-chart-queue"].findOne(
|
||||
{ gptString, ...orphanMatchCriteria },
|
||||
{
|
||||
projectID: true,
|
||||
@@ -47,7 +47,7 @@ export async function HandleOrphanQueue<GPT extends GPTString>(
|
||||
if (!orphanChart) {
|
||||
log.debug(`Received unknown chart ${chartName}, orphaning.`);
|
||||
|
||||
await db["orphan-chart-queue"].insert({
|
||||
await MONGODB_KILL["orphan-chart-queue"].insert({
|
||||
gptString,
|
||||
chartDoc,
|
||||
songDoc,
|
||||
@@ -77,10 +77,10 @@ export async function HandleOrphanQueue<GPT extends GPTString>(
|
||||
songDoc.id = songID;
|
||||
chartDoc.songID = songID;
|
||||
|
||||
await db.anySongs[game].insert(songDoc);
|
||||
await db.anyCharts[game].insert(chartDoc);
|
||||
await MONGODB_KILL.anySongs[game].insert(songDoc);
|
||||
await MONGODB_KILL.anyCharts[game].insert(chartDoc);
|
||||
|
||||
await db["orphan-chart-queue"].remove({
|
||||
await MONGODB_KILL["orphan-chart-queue"].remove({
|
||||
_id: orphanChart._id,
|
||||
});
|
||||
|
||||
@@ -90,7 +90,7 @@ export async function HandleOrphanQueue<GPT extends GPTString>(
|
||||
// otherwise, update the state of this orphan.
|
||||
|
||||
log.debug(`UserID ${userID} played ${chartName}, which is now at ${playcount} plays.`);
|
||||
await db["orphan-chart-queue"].update(
|
||||
await MONGODB_KILL["orphan-chart-queue"].update(
|
||||
{
|
||||
_id: orphanChart._id,
|
||||
},
|
||||
@@ -115,7 +115,7 @@ export async function DeorphanIfInQueue<GPT extends GPTString>(
|
||||
game: GPTStringToGame[GPT],
|
||||
orphanMatchCriteria: FilterQuery<OrphanChartDocument<GPT>>,
|
||||
): Promise<ChartDocument<GPT> | null> {
|
||||
const orphanChart = await db["orphan-chart-queue"].findOne(
|
||||
const orphanChart = await MONGODB_KILL["orphan-chart-queue"].findOne(
|
||||
{ gptString, ...orphanMatchCriteria },
|
||||
{
|
||||
projectID: true,
|
||||
@@ -136,9 +136,9 @@ export async function DeorphanIfInQueue<GPT extends GPTString>(
|
||||
songDoc.id = songID;
|
||||
chartDoc.songID = songID;
|
||||
|
||||
await db.anySongs[game].insert(songDoc);
|
||||
await db.anyCharts[game].insert(chartDoc);
|
||||
await db["orphan-chart-queue"].remove({
|
||||
await MONGODB_KILL.anySongs[game].insert(songDoc);
|
||||
await MONGODB_KILL.anyCharts[game].insert(chartDoc);
|
||||
await MONGODB_KILL["orphan-chart-queue"].remove({
|
||||
_id: orphanChart._id,
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { SetRivalsFailReasons } from "#lib/constants/err-codes";
|
||||
import { log } from "#lib/log/log.js";
|
||||
import { SendSetRivalNotification } from "#lib/notifications/notification-wrappers";
|
||||
import { ServerConfig } from "#lib/setup/config";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { ArrayDiff } from "#utils/misc";
|
||||
import { GetUsersWithIDs, GetUserWithIDGuaranteed } from "#utils/user";
|
||||
import {
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
* Throws if the user hasn't played the GPT in question.
|
||||
*/
|
||||
export async function GetRivalIDs(userID: integer, game: GameGroup, playtype: Playtype) {
|
||||
const gameSettings = await db["game-settings"].findOne(
|
||||
const gameSettings = await MONGODB_KILL["game-settings"].findOne(
|
||||
{
|
||||
userID,
|
||||
game,
|
||||
@@ -63,7 +63,7 @@ export async function GetEveryonesRivalIDs(
|
||||
game: GameGroup,
|
||||
playtype: Playtype,
|
||||
): Promise<Record<number, Array<number>>> {
|
||||
const allGameSettings = await db["game-settings"].find(
|
||||
const allGameSettings = await MONGODB_KILL["game-settings"].find(
|
||||
{
|
||||
game,
|
||||
playtype,
|
||||
@@ -104,7 +104,7 @@ export async function SetRivals(
|
||||
return SetRivalsFailReasons.RIVALED_SELF;
|
||||
}
|
||||
|
||||
const playedGPTCount = await db["game-settings"].count({
|
||||
const playedGPTCount = await MONGODB_KILL["game-settings"].count({
|
||||
userID: { $in: newRivals },
|
||||
game,
|
||||
playtype,
|
||||
@@ -115,7 +115,7 @@ export async function SetRivals(
|
||||
return SetRivalsFailReasons.RIVALS_HAVENT_PLAYED_GPT;
|
||||
}
|
||||
|
||||
const currentGameSettings = await db["game-settings"].findOne({
|
||||
const currentGameSettings = await MONGODB_KILL["game-settings"].findOne({
|
||||
userID,
|
||||
game,
|
||||
playtype,
|
||||
@@ -145,7 +145,7 @@ export async function SetRivals(
|
||||
newSubs.map((toUserID) => SendSetRivalNotification(toUserID, user, game, playtype)),
|
||||
);
|
||||
|
||||
await db["game-settings"].update(
|
||||
await MONGODB_KILL["game-settings"].update(
|
||||
{
|
||||
userID,
|
||||
game,
|
||||
@@ -206,7 +206,7 @@ export async function RemoveRival(
|
||||
* Get all of the userIDs of people who rival the userID for this GPT.
|
||||
*/
|
||||
export async function GetChallengerIDs(userID: integer, game: GameGroup, playtype: Playtype) {
|
||||
const result = await db["game-settings"].find(
|
||||
const result = await MONGODB_KILL["game-settings"].find(
|
||||
{
|
||||
game,
|
||||
playtype,
|
||||
@@ -252,7 +252,7 @@ export async function UpdatePlayersRivalRankings(
|
||||
const rivalIDs = await GetRivalIDs(userID, game, playtype);
|
||||
|
||||
// get all of this user's chartIDs so we know what to update
|
||||
const userPBs = (await db["personal-bests"].find(
|
||||
const userPBs = (await MONGODB_KILL["personal-bests"].find(
|
||||
{ userID, game, playtype },
|
||||
{ projection: { chartID: 1, [`scoreData.${gptConfig.defaultMetric}`]: 1 } },
|
||||
)) as Array<{ chartID: string; scoreData: { percent: number } }>;
|
||||
@@ -262,7 +262,7 @@ export async function UpdatePlayersRivalRankings(
|
||||
await Promise.all(
|
||||
userPBs.map(async (pb) => {
|
||||
const rivalRank =
|
||||
(await db["personal-bests"].count({
|
||||
(await MONGODB_KILL["personal-bests"].count({
|
||||
chartID: pb.chartID,
|
||||
userID: { $in: rivalIDs },
|
||||
[`scoreData.${gptConfig.defaultMetric}`]: { $gt: pb.scoreData.percent },
|
||||
@@ -285,5 +285,5 @@ export async function UpdatePlayersRivalRankings(
|
||||
return;
|
||||
}
|
||||
|
||||
await db["personal-bests"].bulkWrite(bwrite, { ordered: false });
|
||||
await MONGODB_KILL["personal-bests"].bulkWrite(bwrite, { ordered: false });
|
||||
}
|
||||
|
||||
+23
-18
@@ -1,7 +1,7 @@
|
||||
import type { GoalDocument, GoalSubscriptionDocument } from "tachi-common";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import { dmf } from "#test-utils/misc.js";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import {
|
||||
@@ -51,10 +51,10 @@ t.test("#UpdateGoalsForUser", (t) => {
|
||||
};
|
||||
|
||||
t.test("Should correctly update goals when user achieves goal.", async (t) => {
|
||||
await db.goals.insert(baseGoalDocument);
|
||||
await MONGODB_KILL.goals.insert(baseGoalDocument);
|
||||
|
||||
await db["goal-subs"].insert(baseGoalSubscriptionDocument);
|
||||
await db["personal-bests"].insert(TestingIIDXSPScorePB);
|
||||
await MONGODB_KILL["goal-subs"].insert(baseGoalSubscriptionDocument);
|
||||
await MONGODB_KILL["personal-bests"].insert(TestingIIDXSPScorePB);
|
||||
|
||||
const ugMap = new Map([["FAKE_GOAL_ID", baseGoalSubscriptionDocument]]);
|
||||
|
||||
@@ -80,7 +80,7 @@ t.test("#UpdateGoalsForUser", (t) => {
|
||||
},
|
||||
]);
|
||||
|
||||
const r = await db["goal-subs"].findOne({ goalID: "FAKE_GOAL_ID", userID: 1 });
|
||||
const r = await MONGODB_KILL["goal-subs"].findOne({ goalID: "FAKE_GOAL_ID", userID: 1 });
|
||||
|
||||
t.hasStrict(
|
||||
r,
|
||||
@@ -100,19 +100,21 @@ t.test("#UpdateGoalsForUser", (t) => {
|
||||
t.test("Should correctly update goals when user does not achieve goal.", async (t) => {
|
||||
const goal = dmf(baseGoalDocument, { criteria: { value: 2 } });
|
||||
|
||||
await db.goals.insert(goal);
|
||||
await MONGODB_KILL.goals.insert(goal);
|
||||
|
||||
const goalSub = deepmerge(baseGoalSubscriptionDocument, {
|
||||
outOf: 2,
|
||||
outOfHuman: "2",
|
||||
}) as unknown as GoalSubscriptionDocument;
|
||||
|
||||
await db["goal-subs"].insert(goalSub);
|
||||
await MONGODB_KILL["goal-subs"].insert(goalSub);
|
||||
|
||||
// we dont delete _id here because updategoalsforuser
|
||||
// depends on usergoal _id
|
||||
|
||||
await db["personal-bests"].insert(dmf(TestingIIDXSPScorePB, { scoreData: { score: 1 } }));
|
||||
await MONGODB_KILL["personal-bests"].insert(
|
||||
dmf(TestingIIDXSPScorePB, { scoreData: { score: 1 } }),
|
||||
);
|
||||
|
||||
const ugMap = new Map([["FAKE_GOAL_ID", goalSub]]);
|
||||
|
||||
@@ -138,7 +140,7 @@ t.test("#UpdateGoalsForUser", (t) => {
|
||||
},
|
||||
]);
|
||||
|
||||
const r = await db["goal-subs"].findOne({ goalID: "FAKE_GOAL_ID", userID: 1 });
|
||||
const r = await MONGODB_KILL["goal-subs"].findOne({ goalID: "FAKE_GOAL_ID", userID: 1 });
|
||||
|
||||
t.hasStrict(
|
||||
r,
|
||||
@@ -196,14 +198,14 @@ t.todo("#GetAndUpdateUsersGoals", (t) => {
|
||||
t.test("#ProcessGoal", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
t.beforeEach(async () => {
|
||||
await db.goals.insert(HC511Goal);
|
||||
await MONGODB_KILL.goals.insert(HC511Goal);
|
||||
});
|
||||
|
||||
t.test("Should process the users goal if a score has changed.", async (t) => {
|
||||
await db["goal-subs"].insert(HC511UserGoal);
|
||||
await MONGODB_KILL["goal-subs"].insert(HC511UserGoal);
|
||||
|
||||
// score is EX HARD CLEAR by default.
|
||||
await db["personal-bests"].insert(TestingIIDXSPScorePB);
|
||||
await MONGODB_KILL["personal-bests"].insert(TestingIIDXSPScorePB);
|
||||
|
||||
const res = await ProcessGoal(HC511Goal, HC511UserGoal, 1, log);
|
||||
|
||||
@@ -251,7 +253,7 @@ t.test("#ProcessGoal", (t) => {
|
||||
userID: 1,
|
||||
};
|
||||
|
||||
await db["goal-subs"].insert(achievedGoalSub);
|
||||
await MONGODB_KILL["goal-subs"].insert(achievedGoalSub);
|
||||
|
||||
const res = await ProcessGoal(HC511Goal, achievedGoalSub, 1, log);
|
||||
|
||||
@@ -285,7 +287,7 @@ t.test("#ProcessGoal", (t) => {
|
||||
});
|
||||
|
||||
t.test("Should return undefined if there's no score.", async (t) => {
|
||||
await db["goal-subs"].insert(HC511UserGoal);
|
||||
await MONGODB_KILL["goal-subs"].insert(HC511UserGoal);
|
||||
|
||||
const res = await ProcessGoal(HC511Goal, HC511UserGoal, 1, log);
|
||||
|
||||
@@ -295,17 +297,20 @@ t.test("#ProcessGoal", (t) => {
|
||||
});
|
||||
|
||||
t.test("Should return undefined if the progress has not changed.", async (t) => {
|
||||
await db["goal-subs"].insert(HC511UserGoal);
|
||||
await db["personal-bests"].insert(TestingIIDXSPScorePB);
|
||||
await MONGODB_KILL["goal-subs"].insert(HC511UserGoal);
|
||||
await MONGODB_KILL["personal-bests"].insert(TestingIIDXSPScorePB);
|
||||
|
||||
const firstUpdate = await ProcessGoal(HC511Goal, HC511UserGoal, 1, log);
|
||||
|
||||
// ignore this one
|
||||
t.not(firstUpdate, undefined, "Should NOT return undefined.");
|
||||
|
||||
await db["goal-subs"].bulkWrite([firstUpdate!.bwrite]);
|
||||
await MONGODB_KILL["goal-subs"].bulkWrite([firstUpdate!.bwrite]);
|
||||
|
||||
const goalSub = await db["goal-subs"].findOne({ userID: 1, goalID: HC511Goal.goalID });
|
||||
const goalSub = await MONGODB_KILL["goal-subs"].findOne({
|
||||
userID: 1,
|
||||
goalID: HC511Goal.goalID,
|
||||
});
|
||||
|
||||
const secondUpdate = await ProcessGoal(HC511Goal, goalSub!, 1, log);
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { GameGroup, GoalDocument, GoalSubscriptionDocument, integer } from
|
||||
|
||||
import { EvaluateGoalForUser, GetRelevantGoals } from "#lib/targets/goals";
|
||||
import { EmitWebhookEvent } from "#lib/webhooks/webhooks";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
|
||||
/**
|
||||
* Update a user's progress on all of their set goals.
|
||||
@@ -90,7 +90,7 @@ export async function UpdateGoalsForUser(
|
||||
});
|
||||
}
|
||||
|
||||
await db["goal-subs"].bulkWrite(bulkWrite, { ordered: false });
|
||||
await MONGODB_KILL["goal-subs"].bulkWrite(bulkWrite, { ordered: false });
|
||||
|
||||
return importInfo;
|
||||
}
|
||||
@@ -209,14 +209,14 @@ export async function ProcessGoal(
|
||||
}
|
||||
|
||||
export async function UpdateGoalsInFolder(folderID: string, log: KtLogger) {
|
||||
const goals = await db.goals.find({
|
||||
const goals = await MONGODB_KILL.goals.find({
|
||||
"charts.type": "folder",
|
||||
"charts.data": folderID,
|
||||
});
|
||||
|
||||
log.info(`Updating ${goals.length} goals for ${folderID}`);
|
||||
|
||||
const goalSubs = await db["goal-subs"].find({
|
||||
const goalSubs = await MONGODB_KILL["goal-subs"].find({
|
||||
goalID: { $in: goals.map((e) => e.goalID) },
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { integer } from "tachi-common";
|
||||
|
||||
import { ONE_DAY, ONE_HOUR } from "#lib/constants/time";
|
||||
import { log } from "#lib/log/log.js";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
|
||||
/**
|
||||
* If a user has no ongoing import, enable the import lock and return true.
|
||||
@@ -13,19 +13,19 @@ import db from "#services/mongo/db";
|
||||
* has a lock.
|
||||
*/
|
||||
export async function CheckAndSetOngoingImportLock(userID: integer) {
|
||||
const lockExists = await db["import-locks"].findOne({
|
||||
const lockExists = await MONGODB_KILL["import-locks"].findOne({
|
||||
userID,
|
||||
});
|
||||
|
||||
if (!lockExists) {
|
||||
await db["import-locks"].insert({
|
||||
await MONGODB_KILL["import-locks"].insert({
|
||||
userID,
|
||||
locked: false,
|
||||
lockedAt: null,
|
||||
});
|
||||
} else if (lockExists.locked && lockExists.lockedAt! + ONE_DAY < Date.now()) {
|
||||
log.warn(`Removed import lock for ${userID} as it is ostensibly stuck.`);
|
||||
await db["import-locks"].update(
|
||||
await MONGODB_KILL["import-locks"].update(
|
||||
{
|
||||
userID,
|
||||
},
|
||||
@@ -38,7 +38,7 @@ export async function CheckAndSetOngoingImportLock(userID: integer) {
|
||||
);
|
||||
}
|
||||
|
||||
const lockWasSet = await db["import-locks"].findOneAndUpdate(
|
||||
const lockWasSet = await MONGODB_KILL["import-locks"].findOneAndUpdate(
|
||||
{
|
||||
userID,
|
||||
locked: false,
|
||||
@@ -68,7 +68,7 @@ export async function CheckAndSetOngoingImportLock(userID: integer) {
|
||||
* Disable a users import lock.
|
||||
*/
|
||||
export function UnsetOngoingImportLock(userID: integer) {
|
||||
return db["import-locks"].findOneAndUpdate(
|
||||
return MONGODB_KILL["import-locks"].findOneAndUpdate(
|
||||
{
|
||||
userID,
|
||||
locked: true,
|
||||
|
||||
+19
-11
@@ -1,7 +1,7 @@
|
||||
import type { BatchManualScore } from "tachi-common";
|
||||
|
||||
import { log } from "#lib/log/log.js";
|
||||
import db from "#services/mongo/db";
|
||||
import MONGODB_KILL from "#services/mongo/db";
|
||||
import ResetDBState from "#test-utils/resets";
|
||||
import deepmerge from "deepmerge";
|
||||
import fjsh from "fast-json-stable-hash";
|
||||
@@ -56,7 +56,7 @@ t.test("#OrphanScore", (t) => {
|
||||
"Should be a checksum of the orphan's contents.",
|
||||
);
|
||||
|
||||
const dbCheck = await db["orphan-scores"].findOne({
|
||||
const dbCheck = await MONGODB_KILL["orphan-scores"].findOne({
|
||||
orphanID: res.orphanID,
|
||||
});
|
||||
|
||||
@@ -101,7 +101,7 @@ t.test("#OrphanScore", (t) => {
|
||||
t.equal(res2.success, false);
|
||||
t.equal(res1.orphanID, res2.orphanID);
|
||||
|
||||
const dbCheck = await db["orphan-scores"].count();
|
||||
const dbCheck = await MONGODB_KILL["orphan-scores"].count();
|
||||
|
||||
t.equal(dbCheck, 1);
|
||||
});
|
||||
@@ -163,7 +163,9 @@ t.test("#ReprocessOrphan", (t) => {
|
||||
"Should successfully import the score.",
|
||||
);
|
||||
|
||||
const orphan = await db["orphan-scores"].findOne({ orphanID: orphanDoc.orphanID });
|
||||
const orphan = await MONGODB_KILL["orphan-scores"].findOne({
|
||||
orphanID: orphanDoc.orphanID,
|
||||
});
|
||||
|
||||
t.equal(orphan, null, "Should remove the orphan document.");
|
||||
|
||||
@@ -171,7 +173,7 @@ t.test("#ReprocessOrphan", (t) => {
|
||||
});
|
||||
|
||||
t.test("Should delete the orphan doc and return null if the score is invalid.", async (t) => {
|
||||
await db["orphan-scores"].insert(orphanDoc);
|
||||
await MONGODB_KILL["orphan-scores"].insert(orphanDoc);
|
||||
const res = await ReprocessOrphan(
|
||||
deepmerge(orphanDoc, {
|
||||
data: {
|
||||
@@ -184,7 +186,9 @@ t.test("#ReprocessOrphan", (t) => {
|
||||
|
||||
t.equal(res, null);
|
||||
|
||||
const orphan = await db["orphan-scores"].findOne({ orphanID: orphanDoc.orphanID });
|
||||
const orphan = await MONGODB_KILL["orphan-scores"].findOne({
|
||||
orphanID: orphanDoc.orphanID,
|
||||
});
|
||||
|
||||
t.equal(orphan, null, "Should remove the orphan document.");
|
||||
|
||||
@@ -194,7 +198,7 @@ t.test("#ReprocessOrphan", (t) => {
|
||||
t.test(
|
||||
"Should keep the orphan doc and return false if no parents could be found.",
|
||||
async (t) => {
|
||||
await db["orphan-scores"].insert(orphanDoc);
|
||||
await MONGODB_KILL["orphan-scores"].insert(orphanDoc);
|
||||
const res = await ReprocessOrphan(
|
||||
deepmerge(orphanDoc, {
|
||||
data: {
|
||||
@@ -207,7 +211,9 @@ t.test("#ReprocessOrphan", (t) => {
|
||||
|
||||
t.equal(res, false);
|
||||
|
||||
const orphan = await db["orphan-scores"].findOne({ orphanID: orphanDoc.orphanID });
|
||||
const orphan = await MONGODB_KILL["orphan-scores"].findOne({
|
||||
orphanID: orphanDoc.orphanID,
|
||||
});
|
||||
|
||||
t.not(orphan, null, "Should not remove the orphan document.");
|
||||
|
||||
@@ -218,12 +224,12 @@ t.test("#ReprocessOrphan", (t) => {
|
||||
t.test(
|
||||
"Should keep the orphan doc and return false if an internal failure was triggered.",
|
||||
async (t) => {
|
||||
await db["orphan-scores"].insert(orphanDoc);
|
||||
await MONGODB_KILL["orphan-scores"].insert(orphanDoc);
|
||||
const GAZER_SHA256 = "195fe1be5c3e74fccd04dc426e05f8a9cfa8a1059c339d0a23e99f63661f0b7d";
|
||||
|
||||
// force an internal failure by removing all bms songs and then
|
||||
// leaving a valid bms chart to match with.
|
||||
await db.songs.bms.remove({});
|
||||
await MONGODB_KILL.songs.bms.remove({});
|
||||
|
||||
const res = await ReprocessOrphan(
|
||||
deepmerge(orphanDoc, {
|
||||
@@ -242,7 +248,9 @@ t.test("#ReprocessOrphan", (t) => {
|
||||
|
||||
t.equal(res, false);
|
||||
|
||||
const orphan = await db["orphan-scores"].findOne({ orphanID: orphanDoc.orphanID });
|
||||
const orphan = await MONGODB_KILL["orphan-scores"].findOne({
|
||||
orphanID: orphanDoc.orphanID,
|
||||
});
|
||||
|
||||
t.not(orphan, null, "Should not remove the orphan document.");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user