mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-01 15:48:23 -05:00
Migrate Prettier/Eslint/Stylelint setup to Biome (#1772)
* Initial * CSS lint * Test CI * Add 1v1, 2v2, and 3v3 Tags (#1771) * Initial * CSS lint * Test CI * Rename step --------- Co-authored-by: xi <104683822+ximk@users.noreply.github.com>
This commit is contained in:
@@ -28,116 +28,116 @@ const cleanUpStm = sql.prepare(/*sql*/ `
|
||||
`);
|
||||
|
||||
export const cleanUp = () => {
|
||||
removeOldLikesStm.run();
|
||||
removeOldGroupStm.run();
|
||||
cleanUpStm.run();
|
||||
removeOldLikesStm.run();
|
||||
removeOldGroupStm.run();
|
||||
cleanUpStm.run();
|
||||
};
|
||||
|
||||
export function migrate(args: { newUserId: number; oldUserId: number }) {
|
||||
return db.transaction().execute(async (trx) => {
|
||||
const deletedUser = await trx
|
||||
.deleteFrom("User")
|
||||
.where("User.id", "=", args.newUserId)
|
||||
.returning("discordId")
|
||||
.executeTakeFirstOrThrow();
|
||||
return db.transaction().execute(async (trx) => {
|
||||
const deletedUser = await trx
|
||||
.deleteFrom("User")
|
||||
.where("User.id", "=", args.newUserId)
|
||||
.returning("discordId")
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
await trx
|
||||
.updateTable("User")
|
||||
.set({ discordId: deletedUser.discordId })
|
||||
.where("User.id", "=", args.oldUserId)
|
||||
.execute();
|
||||
});
|
||||
await trx
|
||||
.updateTable("User")
|
||||
.set({ discordId: deletedUser.discordId })
|
||||
.where("User.id", "=", args.oldUserId)
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
|
||||
export function replacePlusTiers(
|
||||
plusTiers: Array<{ userId: number; tier: number }>,
|
||||
plusTiers: Array<{ userId: number; tier: number }>,
|
||||
) {
|
||||
invariant(plusTiers.length > 0, "plusTiers must not be empty");
|
||||
invariant(plusTiers.length > 0, "plusTiers must not be empty");
|
||||
|
||||
return db.transaction().execute(async (trx) => {
|
||||
await trx.deleteFrom("PlusTier").execute();
|
||||
await trx.insertInto("PlusTier").values(plusTiers).execute();
|
||||
});
|
||||
return db.transaction().execute(async (trx) => {
|
||||
await trx.deleteFrom("PlusTier").execute();
|
||||
await trx.insertInto("PlusTier").values(plusTiers).execute();
|
||||
});
|
||||
}
|
||||
|
||||
export function allPlusTiersFromLatestVoting() {
|
||||
return db
|
||||
.selectFrom("FreshPlusTier")
|
||||
.select(["FreshPlusTier.userId", "FreshPlusTier.tier"])
|
||||
.where("FreshPlusTier.tier", "is not", null)
|
||||
.execute() as Promise<{ userId: number; tier: number }[]>;
|
||||
return db
|
||||
.selectFrom("FreshPlusTier")
|
||||
.select(["FreshPlusTier.userId", "FreshPlusTier.tier"])
|
||||
.where("FreshPlusTier.tier", "is not", null)
|
||||
.execute() as Promise<{ userId: number; tier: number }[]>;
|
||||
}
|
||||
|
||||
export function makeVideoAdderByUserId(userId: number) {
|
||||
return db
|
||||
.updateTable("User")
|
||||
.set({ isVideoAdder: 1 })
|
||||
.where("User.id", "=", userId)
|
||||
.execute();
|
||||
return db
|
||||
.updateTable("User")
|
||||
.set({ isVideoAdder: 1 })
|
||||
.where("User.id", "=", userId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export async function linkUserAndPlayer({
|
||||
userId,
|
||||
playerId,
|
||||
userId,
|
||||
playerId,
|
||||
}: {
|
||||
userId: number;
|
||||
playerId: number;
|
||||
userId: number;
|
||||
playerId: number;
|
||||
}) {
|
||||
await db
|
||||
.updateTable("SplatoonPlayer")
|
||||
.set({ userId: null })
|
||||
.where("SplatoonPlayer.userId", "=", userId)
|
||||
.execute();
|
||||
await db
|
||||
.updateTable("SplatoonPlayer")
|
||||
.set({ userId: null })
|
||||
.where("SplatoonPlayer.userId", "=", userId)
|
||||
.execute();
|
||||
|
||||
await db
|
||||
.updateTable("SplatoonPlayer")
|
||||
.set({ userId })
|
||||
.where("SplatoonPlayer.id", "=", playerId)
|
||||
.execute();
|
||||
await db
|
||||
.updateTable("SplatoonPlayer")
|
||||
.set({ userId })
|
||||
.where("SplatoonPlayer.id", "=", playerId)
|
||||
.execute();
|
||||
|
||||
syncXPBadges();
|
||||
syncXPBadges();
|
||||
}
|
||||
|
||||
export function forcePatron(args: {
|
||||
id: number;
|
||||
patronTier: Tables["User"]["patronTier"];
|
||||
patronSince: Date;
|
||||
patronTill: Date;
|
||||
id: number;
|
||||
patronTier: Tables["User"]["patronTier"];
|
||||
patronSince: Date;
|
||||
patronTill: Date;
|
||||
}) {
|
||||
return db
|
||||
.updateTable("User")
|
||||
.set({
|
||||
patronTier: args.patronTier,
|
||||
patronSince: dateToDatabaseTimestamp(args.patronSince),
|
||||
patronTill: dateToDatabaseTimestamp(args.patronTill),
|
||||
})
|
||||
.where("User.id", "=", args.id)
|
||||
.execute();
|
||||
return db
|
||||
.updateTable("User")
|
||||
.set({
|
||||
patronTier: args.patronTier,
|
||||
patronSince: dateToDatabaseTimestamp(args.patronSince),
|
||||
patronTill: dateToDatabaseTimestamp(args.patronTill),
|
||||
})
|
||||
.where("User.id", "=", args.id)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export function banUser({
|
||||
userId,
|
||||
banned,
|
||||
bannedReason,
|
||||
userId,
|
||||
banned,
|
||||
bannedReason,
|
||||
}: {
|
||||
userId: number;
|
||||
banned: 1 | Date;
|
||||
bannedReason: string | null;
|
||||
userId: number;
|
||||
banned: 1 | Date;
|
||||
bannedReason: string | null;
|
||||
}) {
|
||||
return db
|
||||
.updateTable("User")
|
||||
.set({
|
||||
banned: banned === 1 ? banned : dateToDatabaseTimestamp(banned),
|
||||
bannedReason,
|
||||
})
|
||||
.where("User.id", "=", userId)
|
||||
.execute();
|
||||
return db
|
||||
.updateTable("User")
|
||||
.set({
|
||||
banned: banned === 1 ? banned : dateToDatabaseTimestamp(banned),
|
||||
bannedReason,
|
||||
})
|
||||
.where("User.id", "=", userId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export function unbanUser(userId: number) {
|
||||
return db
|
||||
.updateTable("User")
|
||||
.set({ banned: 0 })
|
||||
.where("User.id", "=", userId)
|
||||
.execute();
|
||||
return db
|
||||
.updateTable("User")
|
||||
.set({ banned: 0 })
|
||||
.where("User.id", "=", userId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
@@ -3,162 +3,162 @@ import { z } from "zod";
|
||||
import * as AdminRepository from "~/features/admin/AdminRepository.server";
|
||||
import { makeArtist } from "~/features/art/queries/makeArtist.server";
|
||||
import { requireUserId } from "~/features/auth/core/user.server";
|
||||
import { refreshBannedCache } from "~/features/ban/core/banned.server";
|
||||
import { isAdmin, isMod } from "~/permissions";
|
||||
import { logger } from "~/utils/logger";
|
||||
import { parseRequestFormData, validate } from "~/utils/remix";
|
||||
import { assertUnreachable } from "~/utils/types";
|
||||
import { _action, actualNumber } from "~/utils/zod";
|
||||
import { plusTiersFromVotingAndLeaderboard } from "../core/plus-tier.server";
|
||||
import { refreshBannedCache } from "~/features/ban/core/banned.server";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
const data = await parseRequestFormData({
|
||||
request,
|
||||
schema: adminActionSchema,
|
||||
});
|
||||
const user = await requireUserId(request);
|
||||
const data = await parseRequestFormData({
|
||||
request,
|
||||
schema: adminActionSchema,
|
||||
});
|
||||
const user = await requireUserId(request);
|
||||
|
||||
switch (data._action) {
|
||||
case "MIGRATE": {
|
||||
validate(isAdmin(user), "Admin needed", 401);
|
||||
switch (data._action) {
|
||||
case "MIGRATE": {
|
||||
validate(isAdmin(user), "Admin needed", 401);
|
||||
|
||||
await AdminRepository.migrate({
|
||||
oldUserId: data["old-user"],
|
||||
newUserId: data["new-user"],
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "REFRESH": {
|
||||
validate(isAdmin(user));
|
||||
await AdminRepository.migrate({
|
||||
oldUserId: data["old-user"],
|
||||
newUserId: data["new-user"],
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "REFRESH": {
|
||||
validate(isAdmin(user));
|
||||
|
||||
await AdminRepository.replacePlusTiers(
|
||||
await plusTiersFromVotingAndLeaderboard(),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "FORCE_PATRON": {
|
||||
validate(isAdmin(user), "Admin needed", 401);
|
||||
await AdminRepository.replacePlusTiers(
|
||||
await plusTiersFromVotingAndLeaderboard(),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "FORCE_PATRON": {
|
||||
validate(isAdmin(user), "Admin needed", 401);
|
||||
|
||||
await AdminRepository.forcePatron({
|
||||
id: data["user"],
|
||||
patronSince: new Date(),
|
||||
patronTier: data.patronTier,
|
||||
patronTill: new Date(data.patronTill),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "CLEAN_UP": {
|
||||
validate(isAdmin(user), "Admin needed", 401);
|
||||
await AdminRepository.forcePatron({
|
||||
id: data.user,
|
||||
patronSince: new Date(),
|
||||
patronTier: data.patronTier,
|
||||
patronTill: new Date(data.patronTill),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "CLEAN_UP": {
|
||||
validate(isAdmin(user), "Admin needed", 401);
|
||||
|
||||
// on purpose sync
|
||||
AdminRepository.cleanUp();
|
||||
break;
|
||||
}
|
||||
case "ARTIST": {
|
||||
validate(isMod(user), "Mod needed", 401);
|
||||
// on purpose sync
|
||||
AdminRepository.cleanUp();
|
||||
break;
|
||||
}
|
||||
case "ARTIST": {
|
||||
validate(isMod(user), "Mod needed", 401);
|
||||
|
||||
makeArtist(data["user"]);
|
||||
break;
|
||||
}
|
||||
case "VIDEO_ADDER": {
|
||||
validate(isMod(user), "Mod needed", 401);
|
||||
makeArtist(data.user);
|
||||
break;
|
||||
}
|
||||
case "VIDEO_ADDER": {
|
||||
validate(isMod(user), "Mod needed", 401);
|
||||
|
||||
await AdminRepository.makeVideoAdderByUserId(data["user"]);
|
||||
break;
|
||||
}
|
||||
case "LINK_PLAYER": {
|
||||
validate(isMod(user), "Mod needed", 401);
|
||||
await AdminRepository.makeVideoAdderByUserId(data.user);
|
||||
break;
|
||||
}
|
||||
case "LINK_PLAYER": {
|
||||
validate(isMod(user), "Mod needed", 401);
|
||||
|
||||
await AdminRepository.linkUserAndPlayer({
|
||||
userId: data["user"],
|
||||
playerId: data.playerId,
|
||||
});
|
||||
await AdminRepository.linkUserAndPlayer({
|
||||
userId: data.user,
|
||||
playerId: data.playerId,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case "BAN_USER": {
|
||||
validate(isMod(user), "Mod needed", 401);
|
||||
break;
|
||||
}
|
||||
case "BAN_USER": {
|
||||
validate(isMod(user), "Mod needed", 401);
|
||||
|
||||
await AdminRepository.banUser({
|
||||
bannedReason: data.reason ?? null,
|
||||
userId: data["user"],
|
||||
banned: data.duration ? new Date(data.duration) : 1,
|
||||
});
|
||||
await AdminRepository.banUser({
|
||||
bannedReason: data.reason ?? null,
|
||||
userId: data.user,
|
||||
banned: data.duration ? new Date(data.duration) : 1,
|
||||
});
|
||||
|
||||
refreshBannedCache();
|
||||
refreshBannedCache();
|
||||
|
||||
logger.info("Banned user", {
|
||||
userId: data["user"],
|
||||
byUserId: user.id,
|
||||
reason: data.reason,
|
||||
duration: data.duration
|
||||
? new Date(data.duration).toLocaleString()
|
||||
: undefined,
|
||||
});
|
||||
logger.info("Banned user", {
|
||||
userId: data.user,
|
||||
byUserId: user.id,
|
||||
reason: data.reason,
|
||||
duration: data.duration
|
||||
? new Date(data.duration).toLocaleString()
|
||||
: undefined,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case "UNBAN_USER": {
|
||||
validate(isMod(user), "Mod needed", 401);
|
||||
break;
|
||||
}
|
||||
case "UNBAN_USER": {
|
||||
validate(isMod(user), "Mod needed", 401);
|
||||
|
||||
await AdminRepository.unbanUser(data["user"]);
|
||||
await AdminRepository.unbanUser(data.user);
|
||||
|
||||
refreshBannedCache();
|
||||
refreshBannedCache();
|
||||
|
||||
logger.info("Unbanned user", {
|
||||
userId: data["user"],
|
||||
byUserId: user.id,
|
||||
});
|
||||
logger.info("Unbanned user", {
|
||||
userId: data.user,
|
||||
byUserId: user.id,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertUnreachable(data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertUnreachable(data);
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
return { ok: true };
|
||||
};
|
||||
|
||||
export const adminActionSchema = z.union([
|
||||
z.object({
|
||||
_action: _action("MIGRATE"),
|
||||
"old-user": z.preprocess(actualNumber, z.number().positive()),
|
||||
"new-user": z.preprocess(actualNumber, z.number().positive()),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("REFRESH"),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("CLEAN_UP"),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("FORCE_PATRON"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
patronTier: z.preprocess(actualNumber, z.number()),
|
||||
patronTill: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("VIDEO_ADDER"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("ARTIST"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("LINK_PLAYER"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
playerId: z.preprocess(actualNumber, z.number().positive()),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("BAN_USER"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
reason: z.string().nullish(),
|
||||
duration: z.string().nullish(),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("UNBAN_USER"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("MIGRATE"),
|
||||
"old-user": z.preprocess(actualNumber, z.number().positive()),
|
||||
"new-user": z.preprocess(actualNumber, z.number().positive()),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("REFRESH"),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("CLEAN_UP"),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("FORCE_PATRON"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
patronTier: z.preprocess(actualNumber, z.number()),
|
||||
patronTill: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("VIDEO_ADDER"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("ARTIST"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("LINK_PLAYER"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
playerId: z.preprocess(actualNumber, z.number().positive()),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("BAN_USER"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
reason: z.string().nullish(),
|
||||
duration: z.string().nullish(),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("UNBAN_USER"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
import invariant from "~/utils/invariant";
|
||||
import * as AdminRepository from "~/features/admin/AdminRepository.server";
|
||||
import { addPendingPlusTiers } from "~/features/leaderboards/core/leaderboards.server";
|
||||
import { userSPLeaderboard } from "~/features/leaderboards/queries/userSPLeaderboard.server";
|
||||
import { previousSeason } from "~/features/mmr/season";
|
||||
import * as AdminRepository from "~/features/admin/AdminRepository.server";
|
||||
import invariant from "~/utils/invariant";
|
||||
|
||||
export async function plusTiersFromVotingAndLeaderboard() {
|
||||
const newMembersFromLeaderboard = fromLeaderboard();
|
||||
const newMembersFromVoting =
|
||||
await AdminRepository.allPlusTiersFromLatestVoting();
|
||||
const newMembersFromLeaderboard = fromLeaderboard();
|
||||
const newMembersFromVoting =
|
||||
await AdminRepository.allPlusTiersFromLatestVoting();
|
||||
|
||||
return [
|
||||
...newMembersFromLeaderboard,
|
||||
// filter to ensure that user gets their highest tier
|
||||
...newMembersFromVoting.filter(
|
||||
(member) =>
|
||||
!newMembersFromLeaderboard.some(
|
||||
(leaderboardMember) => leaderboardMember.userId === member.userId,
|
||||
),
|
||||
),
|
||||
];
|
||||
return [
|
||||
...newMembersFromLeaderboard,
|
||||
// filter to ensure that user gets their highest tier
|
||||
...newMembersFromVoting.filter(
|
||||
(member) =>
|
||||
!newMembersFromLeaderboard.some(
|
||||
(leaderboardMember) => leaderboardMember.userId === member.userId,
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
function fromLeaderboard() {
|
||||
const now = new Date();
|
||||
const lastCompletedSeason = previousSeason(now);
|
||||
invariant(lastCompletedSeason, "No previous season found");
|
||||
const now = new Date();
|
||||
const lastCompletedSeason = previousSeason(now);
|
||||
invariant(lastCompletedSeason, "No previous season found");
|
||||
|
||||
// there has been voting after this season ended, the results no longer apply
|
||||
if (now.getMonth() !== lastCompletedSeason.ends.getMonth()) return [];
|
||||
// there has been voting after this season ended, the results no longer apply
|
||||
if (now.getMonth() !== lastCompletedSeason.ends.getMonth()) return [];
|
||||
|
||||
const leaderboard = addPendingPlusTiers(
|
||||
userSPLeaderboard(lastCompletedSeason.nth),
|
||||
);
|
||||
const leaderboard = addPendingPlusTiers(
|
||||
userSPLeaderboard(lastCompletedSeason.nth),
|
||||
);
|
||||
|
||||
return leaderboard.flatMap((entry) => {
|
||||
if (!entry.pendingPlusTier) return [];
|
||||
return leaderboard.flatMap((entry) => {
|
||||
if (!entry.pendingPlusTier) return [];
|
||||
|
||||
return {
|
||||
userId: entry.id,
|
||||
tier: entry.pendingPlusTier,
|
||||
};
|
||||
});
|
||||
return {
|
||||
userId: entry.id,
|
||||
tier: entry.pendingPlusTier,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ import { getUserId, isImpersonating } from "~/features/auth/core/user.server";
|
||||
import { isMod } from "~/permissions";
|
||||
|
||||
export const loader: LoaderFunction = async ({ request }) => {
|
||||
const user = await getUserId(request);
|
||||
const user = await getUserId(request);
|
||||
|
||||
if (process.env.NODE_ENV === "production" && !isMod(user)) {
|
||||
throw redirect("/");
|
||||
}
|
||||
if (process.env.NODE_ENV === "production" && !isMod(user)) {
|
||||
throw redirect("/");
|
||||
}
|
||||
|
||||
return {
|
||||
isImpersonating: await isImpersonating(request),
|
||||
};
|
||||
return {
|
||||
isImpersonating: await isImpersonating(request),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,260 +1,260 @@
|
||||
import MockDate from "mockdate";
|
||||
import { suite } from "uvu";
|
||||
import * as assert from "uvu/assert";
|
||||
import * as Test from "~/utils/Test";
|
||||
import { action } from "./admin";
|
||||
import { db } from "~/db/sql";
|
||||
import MockDate from "mockdate";
|
||||
import * as PlusVotingRepository from "~/features/plus-voting/PlusVotingRepository.server";
|
||||
import * as Test from "~/utils/Test";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import type { adminActionSchema } from "../actions/admin.server";
|
||||
import { action } from "./admin";
|
||||
|
||||
const PlusVoting = suite("Plus voting");
|
||||
|
||||
const adminAction = Test.wrappedAction<typeof adminActionSchema>({ action });
|
||||
|
||||
const voteArgs = ({
|
||||
score,
|
||||
votedId,
|
||||
authorId = 1,
|
||||
month = 6,
|
||||
year = 2021,
|
||||
score,
|
||||
votedId,
|
||||
authorId = 1,
|
||||
month = 6,
|
||||
year = 2021,
|
||||
}: {
|
||||
score: number;
|
||||
votedId: number;
|
||||
authorId?: number;
|
||||
month?: number;
|
||||
year?: number;
|
||||
score: number;
|
||||
votedId: number;
|
||||
authorId?: number;
|
||||
month?: number;
|
||||
year?: number;
|
||||
}) => ({
|
||||
score,
|
||||
votedId,
|
||||
authorId,
|
||||
month,
|
||||
tier: 1,
|
||||
validAfter: dateToDatabaseTimestamp(new Date("2021-12-11T00:00:00.000Z")),
|
||||
year,
|
||||
score,
|
||||
votedId,
|
||||
authorId,
|
||||
month,
|
||||
tier: 1,
|
||||
validAfter: dateToDatabaseTimestamp(new Date("2021-12-11T00:00:00.000Z")),
|
||||
year,
|
||||
});
|
||||
|
||||
const countPlusTierMembers = (tier = 1) =>
|
||||
db
|
||||
.selectFrom("PlusTier")
|
||||
.where("PlusTier.tier", "=", tier)
|
||||
.select(({ fn }) => fn.count<number>("PlusTier.tier").as("count"))
|
||||
.executeTakeFirstOrThrow()
|
||||
.then((row) => row.count);
|
||||
db
|
||||
.selectFrom("PlusTier")
|
||||
.where("PlusTier.tier", "=", tier)
|
||||
.select(({ fn }) => fn.count<number>("PlusTier.tier").as("count"))
|
||||
.executeTakeFirstOrThrow()
|
||||
.then((row) => row.count);
|
||||
|
||||
const createLeaderboard = (userIds: number[]) =>
|
||||
db
|
||||
.insertInto("Skill")
|
||||
.values(
|
||||
userIds.map((userId, i) => ({
|
||||
matchesCount: 10,
|
||||
mu: 25,
|
||||
sigma: 8.333333333333334,
|
||||
ordinal: 0.5 - i * 0.001,
|
||||
userId,
|
||||
season: 1,
|
||||
})),
|
||||
)
|
||||
.execute();
|
||||
db
|
||||
.insertInto("Skill")
|
||||
.values(
|
||||
userIds.map((userId, i) => ({
|
||||
matchesCount: 10,
|
||||
mu: 25,
|
||||
sigma: 8.333333333333334,
|
||||
ordinal: 0.5 - i * 0.001,
|
||||
userId,
|
||||
season: 1,
|
||||
})),
|
||||
)
|
||||
.execute();
|
||||
|
||||
PlusVoting.after.each(() => {
|
||||
MockDate.reset();
|
||||
Test.database.reset();
|
||||
MockDate.reset();
|
||||
Test.database.reset();
|
||||
});
|
||||
|
||||
PlusVoting("gives correct amount of plus tiers", async () => {
|
||||
MockDate.set(new Date("2023-12-12T00:00:00.000Z"));
|
||||
MockDate.set(new Date("2023-12-12T00:00:00.000Z"));
|
||||
|
||||
await Test.database.insertUsers(10);
|
||||
await PlusVotingRepository.upsertMany(
|
||||
Array.from({ length: 10 }).map((_, i) => {
|
||||
const id = i + 1;
|
||||
await Test.database.insertUsers(10);
|
||||
await PlusVotingRepository.upsertMany(
|
||||
Array.from({ length: 10 }).map((_, i) => {
|
||||
const id = i + 1;
|
||||
|
||||
return voteArgs({
|
||||
score: id <= 5 ? -1 : 1,
|
||||
votedId: id,
|
||||
});
|
||||
}),
|
||||
);
|
||||
return voteArgs({
|
||||
score: id <= 5 ? -1 : 1,
|
||||
votedId: id,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
|
||||
assert.equal(await countPlusTierMembers(), 5);
|
||||
assert.equal(await countPlusTierMembers(), 5);
|
||||
});
|
||||
|
||||
PlusVoting("60% is the criteria to pass voting", async () => {
|
||||
MockDate.set(new Date("2023-12-12T00:00:00.000Z"));
|
||||
MockDate.set(new Date("2023-12-12T00:00:00.000Z"));
|
||||
|
||||
await Test.database.insertUsers(10);
|
||||
await Test.database.insertUsers(10);
|
||||
|
||||
// 50%
|
||||
await PlusVotingRepository.upsertMany(
|
||||
Array.from({ length: 10 }).map((_, i) => {
|
||||
return voteArgs({
|
||||
authorId: i + 1,
|
||||
score: i < 5 ? -1 : 1,
|
||||
votedId: 1,
|
||||
});
|
||||
}),
|
||||
);
|
||||
// 60%
|
||||
await PlusVotingRepository.upsertMany(
|
||||
Array.from({ length: 10 }).map((_, i) => {
|
||||
return voteArgs({
|
||||
authorId: i + 1,
|
||||
score: i < 4 ? -1 : 1,
|
||||
votedId: 2,
|
||||
});
|
||||
}),
|
||||
);
|
||||
// 50%
|
||||
await PlusVotingRepository.upsertMany(
|
||||
Array.from({ length: 10 }).map((_, i) => {
|
||||
return voteArgs({
|
||||
authorId: i + 1,
|
||||
score: i < 5 ? -1 : 1,
|
||||
votedId: 1,
|
||||
});
|
||||
}),
|
||||
);
|
||||
// 60%
|
||||
await PlusVotingRepository.upsertMany(
|
||||
Array.from({ length: 10 }).map((_, i) => {
|
||||
return voteArgs({
|
||||
authorId: i + 1,
|
||||
score: i < 4 ? -1 : 1,
|
||||
votedId: 2,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
|
||||
const rows = await db
|
||||
.selectFrom("PlusTier")
|
||||
.select(["PlusTier.tier", "PlusTier.userId"])
|
||||
.where("PlusTier.tier", "=", 1)
|
||||
.execute();
|
||||
const rows = await db
|
||||
.selectFrom("PlusTier")
|
||||
.select(["PlusTier.tier", "PlusTier.userId"])
|
||||
.where("PlusTier.tier", "=", 1)
|
||||
.execute();
|
||||
|
||||
assert.equal(rows.length, 1);
|
||||
assert.equal(rows[0].userId, 2);
|
||||
assert.equal(rows.length, 1);
|
||||
assert.equal(rows[0].userId, 2);
|
||||
});
|
||||
|
||||
PlusVoting(
|
||||
"combines leaderboard and voting results (after season over)",
|
||||
async () => {
|
||||
MockDate.set(new Date("2023-11-29T00:00:00.000Z"));
|
||||
"combines leaderboard and voting results (after season over)",
|
||||
async () => {
|
||||
MockDate.set(new Date("2023-11-29T00:00:00.000Z"));
|
||||
|
||||
await Test.database.insertUsers(2);
|
||||
await PlusVotingRepository.upsertMany([
|
||||
voteArgs({
|
||||
score: 1,
|
||||
votedId: 1,
|
||||
}),
|
||||
]);
|
||||
await createLeaderboard([2]);
|
||||
await Test.database.insertUsers(2);
|
||||
await PlusVotingRepository.upsertMany([
|
||||
voteArgs({
|
||||
score: 1,
|
||||
votedId: 1,
|
||||
}),
|
||||
]);
|
||||
await createLeaderboard([2]);
|
||||
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
|
||||
assert.equal(await countPlusTierMembers(), 2);
|
||||
},
|
||||
assert.equal(await countPlusTierMembers(), 2);
|
||||
},
|
||||
);
|
||||
|
||||
PlusVoting(
|
||||
"skips users from leaderboard with the skip flag for the season",
|
||||
async () => {
|
||||
MockDate.set(new Date("2023-11-29T00:00:00.000Z"));
|
||||
"skips users from leaderboard with the skip flag for the season",
|
||||
async () => {
|
||||
MockDate.set(new Date("2023-11-29T00:00:00.000Z"));
|
||||
|
||||
await Test.database.insertUsers(11);
|
||||
await createLeaderboard(Array.from({ length: 11 }).map((_, i) => i + 1));
|
||||
await Test.database.insertUsers(11);
|
||||
await createLeaderboard(Array.from({ length: 11 }).map((_, i) => i + 1));
|
||||
|
||||
await db
|
||||
.updateTable("User")
|
||||
.set({ plusSkippedForSeasonNth: 1 })
|
||||
.where("User.id", "=", 1)
|
||||
.execute();
|
||||
await db
|
||||
.updateTable("User")
|
||||
.set({ plusSkippedForSeasonNth: 1 })
|
||||
.where("User.id", "=", 1)
|
||||
.execute();
|
||||
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
|
||||
assert.equal(await countPlusTierMembers(1), 10);
|
||||
assert.equal(await countPlusTierMembers(2), 0);
|
||||
},
|
||||
assert.equal(await countPlusTierMembers(1), 10);
|
||||
assert.equal(await countPlusTierMembers(2), 0);
|
||||
},
|
||||
);
|
||||
|
||||
PlusVoting("plus server skip flag ignored if for past season", async () => {
|
||||
MockDate.set(new Date("2023-11-29T00:00:00.000Z"));
|
||||
MockDate.set(new Date("2023-11-29T00:00:00.000Z"));
|
||||
|
||||
await Test.database.insertUsers(11);
|
||||
await createLeaderboard(Array.from({ length: 11 }).map((_, i) => i + 1));
|
||||
await Test.database.insertUsers(11);
|
||||
await createLeaderboard(Array.from({ length: 11 }).map((_, i) => i + 1));
|
||||
|
||||
await db
|
||||
.updateTable("User")
|
||||
.set({ plusSkippedForSeasonNth: 0 })
|
||||
.where("User.id", "=", 1)
|
||||
.execute();
|
||||
await db
|
||||
.updateTable("User")
|
||||
.set({ plusSkippedForSeasonNth: 0 })
|
||||
.where("User.id", "=", 1)
|
||||
.execute();
|
||||
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
|
||||
assert.equal(await countPlusTierMembers(1), 10);
|
||||
assert.equal(await countPlusTierMembers(2), 1);
|
||||
assert.equal(await countPlusTierMembers(1), 10);
|
||||
assert.equal(await countPlusTierMembers(2), 1);
|
||||
});
|
||||
|
||||
PlusVoting("ignores leaderboard while season is ongoing", async () => {
|
||||
MockDate.set(new Date("2024-02-15T00:00:00.000Z"));
|
||||
MockDate.set(new Date("2024-02-15T00:00:00.000Z"));
|
||||
|
||||
await Test.database.insertUsers(2);
|
||||
await PlusVotingRepository.upsertMany([
|
||||
voteArgs({
|
||||
score: 1,
|
||||
votedId: 1,
|
||||
}),
|
||||
]);
|
||||
await createLeaderboard([2]);
|
||||
await Test.database.insertUsers(2);
|
||||
await PlusVotingRepository.upsertMany([
|
||||
voteArgs({
|
||||
score: 1,
|
||||
votedId: 1,
|
||||
}),
|
||||
]);
|
||||
await createLeaderboard([2]);
|
||||
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
|
||||
assert.equal(await countPlusTierMembers(), 1);
|
||||
assert.equal(await countPlusTierMembers(2), 0);
|
||||
assert.equal(await countPlusTierMembers(), 1);
|
||||
assert.equal(await countPlusTierMembers(2), 0);
|
||||
});
|
||||
|
||||
PlusVoting("leaderboard gives members to all tiers", async () => {
|
||||
MockDate.set(new Date("2023-11-20T00:00:00.000Z"));
|
||||
MockDate.set(new Date("2023-11-20T00:00:00.000Z"));
|
||||
|
||||
await Test.database.insertUsers(60);
|
||||
await createLeaderboard(Array.from({ length: 60 }, (_, i) => i + 1));
|
||||
await Test.database.insertUsers(60);
|
||||
await createLeaderboard(Array.from({ length: 60 }, (_, i) => i + 1));
|
||||
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
|
||||
assert.ok((await countPlusTierMembers()) > 0);
|
||||
assert.ok((await countPlusTierMembers(2)) > 0);
|
||||
assert.ok((await countPlusTierMembers(3)) > 0);
|
||||
assert.ok((await countPlusTierMembers()) > 0);
|
||||
assert.ok((await countPlusTierMembers(2)) > 0);
|
||||
assert.ok((await countPlusTierMembers(3)) > 0);
|
||||
});
|
||||
|
||||
PlusVoting(
|
||||
"gives membership if failed voting and is on the leaderboard",
|
||||
async () => {
|
||||
MockDate.set(new Date("2023-11-29T00:00:00.000Z"));
|
||||
"gives membership if failed voting and is on the leaderboard",
|
||||
async () => {
|
||||
MockDate.set(new Date("2023-11-29T00:00:00.000Z"));
|
||||
|
||||
await Test.database.insertUsers(1);
|
||||
await PlusVotingRepository.upsertMany([
|
||||
voteArgs({
|
||||
score: -1,
|
||||
votedId: 1,
|
||||
}),
|
||||
]);
|
||||
await createLeaderboard([1]);
|
||||
await Test.database.insertUsers(1);
|
||||
await PlusVotingRepository.upsertMany([
|
||||
voteArgs({
|
||||
score: -1,
|
||||
votedId: 1,
|
||||
}),
|
||||
]);
|
||||
await createLeaderboard([1]);
|
||||
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
|
||||
assert.equal(await countPlusTierMembers(1), 1);
|
||||
},
|
||||
assert.equal(await countPlusTierMembers(1), 1);
|
||||
},
|
||||
);
|
||||
|
||||
PlusVoting("members who fails voting drops one tier", async () => {
|
||||
MockDate.set(new Date("2024-02-15T00:00:00.000Z"));
|
||||
MockDate.set(new Date("2024-02-15T00:00:00.000Z"));
|
||||
|
||||
await Test.database.insertUsers(1);
|
||||
await PlusVotingRepository.upsertMany([
|
||||
voteArgs({
|
||||
score: 1,
|
||||
votedId: 1,
|
||||
month: 11,
|
||||
year: 2023,
|
||||
}),
|
||||
]);
|
||||
await Test.database.insertUsers(1);
|
||||
await PlusVotingRepository.upsertMany([
|
||||
voteArgs({
|
||||
score: 1,
|
||||
votedId: 1,
|
||||
month: 11,
|
||||
year: 2023,
|
||||
}),
|
||||
]);
|
||||
|
||||
await PlusVotingRepository.upsertMany([
|
||||
voteArgs({
|
||||
score: -1,
|
||||
votedId: 1,
|
||||
month: 2,
|
||||
year: 2024,
|
||||
}),
|
||||
]);
|
||||
await PlusVotingRepository.upsertMany([
|
||||
voteArgs({
|
||||
score: -1,
|
||||
votedId: 1,
|
||||
month: 2,
|
||||
year: 2024,
|
||||
}),
|
||||
]);
|
||||
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
await adminAction({ _action: "REFRESH" }, { user: "admin" });
|
||||
|
||||
assert.equal(await countPlusTierMembers(2), 1);
|
||||
assert.equal(await countPlusTierMembers(2), 1);
|
||||
});
|
||||
|
||||
PlusVoting.run();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { MetaFunction } from "@remix-run/node";
|
||||
import {
|
||||
Form,
|
||||
useFetcher,
|
||||
useLoaderData,
|
||||
useNavigation,
|
||||
Form,
|
||||
useFetcher,
|
||||
useLoaderData,
|
||||
useNavigation,
|
||||
} from "@remix-run/react";
|
||||
import * as React from "react";
|
||||
import { Button } from "~/components/Button";
|
||||
@@ -13,7 +13,7 @@ import { SubmitButton } from "~/components/SubmitButton";
|
||||
import { UserSearch } from "~/components/UserSearch";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import { isAdmin, isMod } from "~/permissions";
|
||||
import { type SendouRouteHandle } from "~/utils/remix";
|
||||
import type { SendouRouteHandle } from "~/utils/remix";
|
||||
import { makeTitle } from "~/utils/strings";
|
||||
import { SEED_URL, STOP_IMPERSONATING_URL, impersonateUrl } from "~/utils/urls";
|
||||
|
||||
@@ -22,311 +22,311 @@ import { loader } from "../loaders/admin.server";
|
||||
export { action, loader };
|
||||
|
||||
export const meta: MetaFunction = () => {
|
||||
return [{ title: makeTitle("Admin page") }];
|
||||
return [{ title: makeTitle("Admin page") }];
|
||||
};
|
||||
|
||||
export const handle: SendouRouteHandle = {
|
||||
navItemName: "admin",
|
||||
navItemName: "admin",
|
||||
};
|
||||
|
||||
export default function AdminPage() {
|
||||
const user = useUser();
|
||||
const user = useUser();
|
||||
|
||||
return (
|
||||
<Main className="stack lg">
|
||||
{process.env.NODE_ENV !== "production" && <Seed />}
|
||||
return (
|
||||
<Main className="stack lg">
|
||||
{process.env.NODE_ENV !== "production" && <Seed />}
|
||||
|
||||
{isMod(user) ? <LinkPlayer /> : null}
|
||||
{isMod(user) ? <GiveArtist /> : null}
|
||||
{isMod(user) ? <GiveVideoAdder /> : null}
|
||||
{isMod(user) ? <LinkPlayer /> : null}
|
||||
{isMod(user) ? <GiveArtist /> : null}
|
||||
{isMod(user) ? <GiveVideoAdder /> : null}
|
||||
|
||||
{process.env.NODE_ENV !== "production" || isAdmin(user) ? (
|
||||
<Impersonate />
|
||||
) : null}
|
||||
{isAdmin(user) ? <MigrateUser /> : null}
|
||||
{isAdmin(user) ? <ForcePatron /> : null}
|
||||
{isMod(user) ? <BanUser /> : null}
|
||||
{isMod(user) ? <UnbanUser /> : null}
|
||||
{isAdmin(user) ? <RefreshPlusTiers /> : null}
|
||||
{isAdmin(user) ? <CleanUp /> : null}
|
||||
</Main>
|
||||
);
|
||||
{process.env.NODE_ENV !== "production" || isAdmin(user) ? (
|
||||
<Impersonate />
|
||||
) : null}
|
||||
{isAdmin(user) ? <MigrateUser /> : null}
|
||||
{isAdmin(user) ? <ForcePatron /> : null}
|
||||
{isMod(user) ? <BanUser /> : null}
|
||||
{isMod(user) ? <UnbanUser /> : null}
|
||||
{isAdmin(user) ? <RefreshPlusTiers /> : null}
|
||||
{isAdmin(user) ? <CleanUp /> : null}
|
||||
</Main>
|
||||
);
|
||||
}
|
||||
|
||||
function Impersonate() {
|
||||
const [userId, setUserId] = React.useState<number>();
|
||||
const { isImpersonating } = useLoaderData<typeof loader>();
|
||||
const [userId, setUserId] = React.useState<number>();
|
||||
const { isImpersonating } = useLoaderData<typeof loader>();
|
||||
|
||||
return (
|
||||
<Form
|
||||
method="post"
|
||||
action={impersonateUrl(userId ?? 0)}
|
||||
className="stack md"
|
||||
reloadDocument
|
||||
>
|
||||
<h2>Impersonate user</h2>
|
||||
<div>
|
||||
<label>User to log in as</label>
|
||||
<UserSearch
|
||||
inputName="user"
|
||||
onChange={(newUser) => setUserId(newUser.id)}
|
||||
/>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<Button type="submit" disabled={!userId}>
|
||||
Go
|
||||
</Button>
|
||||
{isImpersonating ? (
|
||||
<Button type="submit" formAction={STOP_IMPERSONATING_URL}>
|
||||
Stop impersonating
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
return (
|
||||
<Form
|
||||
method="post"
|
||||
action={impersonateUrl(userId ?? 0)}
|
||||
className="stack md"
|
||||
reloadDocument
|
||||
>
|
||||
<h2>Impersonate user</h2>
|
||||
<div>
|
||||
<label>User to log in as</label>
|
||||
<UserSearch
|
||||
inputName="user"
|
||||
onChange={(newUser) => setUserId(newUser.id)}
|
||||
/>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<Button type="submit" disabled={!userId}>
|
||||
Go
|
||||
</Button>
|
||||
{isImpersonating ? (
|
||||
<Button type="submit" formAction={STOP_IMPERSONATING_URL}>
|
||||
Stop impersonating
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
function MigrateUser() {
|
||||
const [oldUserId, setOldUserId] = React.useState<number>();
|
||||
const [newUserId, setNewUserId] = React.useState<number>();
|
||||
const navigation = useNavigation();
|
||||
const fetcher = useFetcher();
|
||||
const [oldUserId, setOldUserId] = React.useState<number>();
|
||||
const [newUserId, setNewUserId] = React.useState<number>();
|
||||
const navigation = useNavigation();
|
||||
const fetcher = useFetcher();
|
||||
|
||||
const submitButtonText =
|
||||
navigation.state === "submitting"
|
||||
? "Migrating..."
|
||||
: navigation.state === "loading"
|
||||
? "Migrated!"
|
||||
: "Migrate";
|
||||
const submitButtonText =
|
||||
navigation.state === "submitting"
|
||||
? "Migrating..."
|
||||
: navigation.state === "loading"
|
||||
? "Migrated!"
|
||||
: "Migrate";
|
||||
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2>Migrate user data</h2>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<label>Old user</label>
|
||||
<UserSearch
|
||||
inputName="old-user"
|
||||
onChange={(newUser) => setOldUserId(newUser.id)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>New user</label>
|
||||
<UserSearch
|
||||
inputName="new-user"
|
||||
onChange={(newUser) => setNewUserId(newUser.id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton
|
||||
type="submit"
|
||||
disabled={!oldUserId || !newUserId || navigation.state !== "idle"}
|
||||
_action="MIGRATE"
|
||||
state={fetcher.state}
|
||||
>
|
||||
{submitButtonText}
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2>Migrate user data</h2>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<label>Old user</label>
|
||||
<UserSearch
|
||||
inputName="old-user"
|
||||
onChange={(newUser) => setOldUserId(newUser.id)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>New user</label>
|
||||
<UserSearch
|
||||
inputName="new-user"
|
||||
onChange={(newUser) => setNewUserId(newUser.id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton
|
||||
type="submit"
|
||||
disabled={!oldUserId || !newUserId || navigation.state !== "idle"}
|
||||
_action="MIGRATE"
|
||||
state={fetcher.state}
|
||||
>
|
||||
{submitButtonText}
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
}
|
||||
|
||||
function LinkPlayer() {
|
||||
const fetcher = useFetcher();
|
||||
const fetcher = useFetcher();
|
||||
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2>Link player</h2>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<label>User</label>
|
||||
<UserSearch inputName="user" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Player ID</label>
|
||||
<input type="number" name="playerId" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton type="submit" _action="LINK_PLAYER" state={fetcher.state}>
|
||||
Link player
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2>Link player</h2>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<label>User</label>
|
||||
<UserSearch inputName="user" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Player ID</label>
|
||||
<input type="number" name="playerId" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton type="submit" _action="LINK_PLAYER" state={fetcher.state}>
|
||||
Link player
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
}
|
||||
|
||||
function GiveArtist() {
|
||||
const fetcher = useFetcher();
|
||||
const fetcher = useFetcher();
|
||||
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2>Add as artist</h2>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<label>User</label>
|
||||
<UserSearch inputName="user" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton type="submit" _action="ARTIST" state={fetcher.state}>
|
||||
Add as artist
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2>Add as artist</h2>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<label>User</label>
|
||||
<UserSearch inputName="user" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton type="submit" _action="ARTIST" state={fetcher.state}>
|
||||
Add as artist
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
}
|
||||
|
||||
function GiveVideoAdder() {
|
||||
const fetcher = useFetcher();
|
||||
const fetcher = useFetcher();
|
||||
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2>Give video adder</h2>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<label>User</label>
|
||||
<UserSearch inputName="user" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton type="submit" _action="VIDEO_ADDER" state={fetcher.state}>
|
||||
Add as video adder
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2>Give video adder</h2>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<label>User</label>
|
||||
<UserSearch inputName="user" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton type="submit" _action="VIDEO_ADDER" state={fetcher.state}>
|
||||
Add as video adder
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
}
|
||||
|
||||
function ForcePatron() {
|
||||
const fetcher = useFetcher();
|
||||
const fetcher = useFetcher();
|
||||
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2>Force patron</h2>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<label>User</label>
|
||||
<UserSearch inputName="user" />
|
||||
</div>
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2>Force patron</h2>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<label>User</label>
|
||||
<UserSearch inputName="user" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Tier</label>
|
||||
<select name="patronTier">
|
||||
<option value="1">Support</option>
|
||||
<option value="2">Supporter</option>
|
||||
<option value="3">Supporter+</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Tier</label>
|
||||
<select name="patronTier">
|
||||
<option value="1">Support</option>
|
||||
<option value="2">Supporter</option>
|
||||
<option value="3">Supporter+</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Patron till</label>
|
||||
<input name="patronTill" type="date" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton
|
||||
type="submit"
|
||||
_action="FORCE_PATRON"
|
||||
state={fetcher.state}
|
||||
>
|
||||
Save
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
<div>
|
||||
<label>Patron till</label>
|
||||
<input name="patronTill" type="date" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton
|
||||
type="submit"
|
||||
_action="FORCE_PATRON"
|
||||
state={fetcher.state}
|
||||
>
|
||||
Save
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
}
|
||||
|
||||
function BanUser() {
|
||||
const fetcher = useFetcher();
|
||||
const fetcher = useFetcher();
|
||||
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2 className="text-warning">Ban user</h2>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<label>User</label>
|
||||
<UserSearch inputName="user" />
|
||||
</div>
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2 className="text-warning">Ban user</h2>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<label>User</label>
|
||||
<UserSearch inputName="user" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Banned till</label>
|
||||
<input name="duration" type="datetime-local" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Banned till</label>
|
||||
<input name="duration" type="datetime-local" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Reason</label>
|
||||
<input name="reason" type="text" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton type="submit" _action="BAN_USER" state={fetcher.state}>
|
||||
Save
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
<div>
|
||||
<label>Reason</label>
|
||||
<input name="reason" type="text" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton type="submit" _action="BAN_USER" state={fetcher.state}>
|
||||
Save
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
}
|
||||
|
||||
function UnbanUser() {
|
||||
const fetcher = useFetcher();
|
||||
const fetcher = useFetcher();
|
||||
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2 className="text-warning">Unban user</h2>
|
||||
<div>
|
||||
<label>User</label>
|
||||
<UserSearch inputName="user" />
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton type="submit" _action="UNBAN_USER" state={fetcher.state}>
|
||||
Save
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2 className="text-warning">Unban user</h2>
|
||||
<div>
|
||||
<label>User</label>
|
||||
<UserSearch inputName="user" />
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton type="submit" _action="UNBAN_USER" state={fetcher.state}>
|
||||
Save
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
}
|
||||
|
||||
function RefreshPlusTiers() {
|
||||
const fetcher = useFetcher();
|
||||
const fetcher = useFetcher();
|
||||
|
||||
return (
|
||||
<fetcher.Form method="post">
|
||||
<h2>Refresh Plus Tiers</h2>
|
||||
<SubmitButton type="submit" _action="REFRESH" state={fetcher.state}>
|
||||
Refresh
|
||||
</SubmitButton>
|
||||
</fetcher.Form>
|
||||
);
|
||||
return (
|
||||
<fetcher.Form method="post">
|
||||
<h2>Refresh Plus Tiers</h2>
|
||||
<SubmitButton type="submit" _action="REFRESH" state={fetcher.state}>
|
||||
Refresh
|
||||
</SubmitButton>
|
||||
</fetcher.Form>
|
||||
);
|
||||
}
|
||||
|
||||
function CleanUp() {
|
||||
const fetcher = useFetcher();
|
||||
const fetcher = useFetcher();
|
||||
|
||||
return (
|
||||
<fetcher.Form method="post">
|
||||
<h2>DB Clean up</h2>
|
||||
<SubmitButton type="submit" _action="CLEAN_UP" state={fetcher.state}>
|
||||
Clean up
|
||||
</SubmitButton>
|
||||
</fetcher.Form>
|
||||
);
|
||||
return (
|
||||
<fetcher.Form method="post">
|
||||
<h2>DB Clean up</h2>
|
||||
<SubmitButton type="submit" _action="CLEAN_UP" state={fetcher.state}>
|
||||
Clean up
|
||||
</SubmitButton>
|
||||
</fetcher.Form>
|
||||
);
|
||||
}
|
||||
|
||||
function Seed() {
|
||||
const fetcher = useFetcher();
|
||||
const fetcher = useFetcher();
|
||||
|
||||
return (
|
||||
<fetcher.Form
|
||||
className="stack md items-start"
|
||||
method="post"
|
||||
action={SEED_URL}
|
||||
>
|
||||
<h2>Seed</h2>
|
||||
<SubmitButton state={fetcher.state}>Seed</SubmitButton>
|
||||
</fetcher.Form>
|
||||
);
|
||||
return (
|
||||
<fetcher.Form
|
||||
className="stack md items-start"
|
||||
method="post"
|
||||
action={SEED_URL}
|
||||
>
|
||||
<h2>Seed</h2>
|
||||
<SubmitButton state={fetcher.state}>Seed</SubmitButton>
|
||||
</fetcher.Form>
|
||||
);
|
||||
}
|
||||
|
||||
export const ErrorBoundary = Catcher;
|
||||
|
||||
Reference in New Issue
Block a user