mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-01 23:58:36 -05:00
Move app to apps/web-react in pnpm workspace layout
This commit is contained in:
198
apps/web-react/app/features/admin/actions/admin.server.ts
Normal file
198
apps/web-react/app/features/admin/actions/admin.server.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import type { ActionFunctionArgs } from "react-router";
|
||||
import * as AdminRepository from "~/features/admin/AdminRepository.server";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import { refreshBannedCache } from "~/features/ban/core/banned.server";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
import { parseFormData } from "~/form/parse.server";
|
||||
import { requireRole } from "~/modules/permissions/guards.server";
|
||||
import {
|
||||
errorToast,
|
||||
notFoundIfNullish,
|
||||
successToast,
|
||||
} from "~/utils/remix.server";
|
||||
import { errorIsSqliteForeignKeyConstraintFailure } from "~/utils/sql";
|
||||
import { assertUnreachable } from "~/utils/types";
|
||||
import { normalizeFriendCode } from "~/utils/zod";
|
||||
import { adminActionSchema } from "../admin-schemas";
|
||||
import {
|
||||
sendUserBannedWebhook,
|
||||
sendUserUnbannedWebhook,
|
||||
} from "../core/discord-webhook.server";
|
||||
import { plusTiersFromVotingAndLeaderboard } from "../core/plus-tier.server";
|
||||
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
const result = await parseFormData({
|
||||
request,
|
||||
schema: adminActionSchema,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
return { fieldErrors: result.fieldErrors };
|
||||
}
|
||||
|
||||
const data = result.data;
|
||||
const user = requireUser();
|
||||
|
||||
let message: string;
|
||||
switch (data._action) {
|
||||
case "MIGRATE": {
|
||||
requireRole("STAFF");
|
||||
|
||||
try {
|
||||
const errorMessage = await AdminRepository.migrate({
|
||||
oldUserId: data.oldUser,
|
||||
newUserId: data.newUser,
|
||||
});
|
||||
|
||||
if (errorMessage) {
|
||||
errorToast(`Migration failed. Reason: ${errorMessage}`);
|
||||
}
|
||||
|
||||
await refreshBannedCache();
|
||||
|
||||
message = "Account migrated";
|
||||
break;
|
||||
} catch (err) {
|
||||
if (errorIsSqliteForeignKeyConstraintFailure(err)) {
|
||||
errorToast(
|
||||
"New user has data preventing the migration (e.g. member of tournament teams or SendouQ played)",
|
||||
);
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
case "REFRESH": {
|
||||
requireRole("ADMIN");
|
||||
|
||||
await AdminRepository.replacePlusTiers(
|
||||
await plusTiersFromVotingAndLeaderboard(),
|
||||
);
|
||||
|
||||
message = "Plus tiers refreshed";
|
||||
break;
|
||||
}
|
||||
case "FORCE_PATRON": {
|
||||
requireRole("ADMIN");
|
||||
|
||||
await AdminRepository.forcePatron({
|
||||
id: data.user,
|
||||
patronStartedAt: new Date(),
|
||||
patronTier: Number(data.patronTier),
|
||||
patronExpiresAt: data.patronExpiresAt,
|
||||
});
|
||||
|
||||
message = "Patron status updated";
|
||||
break;
|
||||
}
|
||||
case "ARTIST": {
|
||||
requireRole("STAFF");
|
||||
|
||||
await AdminRepository.makeArtistByUserId(data.user);
|
||||
|
||||
message = "Artist permissions given";
|
||||
break;
|
||||
}
|
||||
case "VIDEO_ADDER": {
|
||||
requireRole("STAFF");
|
||||
|
||||
await AdminRepository.makeVideoAdderByUserId(data.user);
|
||||
|
||||
message = "VoD adder permissions given";
|
||||
break;
|
||||
}
|
||||
case "TOURNAMENT_ORGANIZER": {
|
||||
requireRole("ADMIN");
|
||||
|
||||
await AdminRepository.makeTournamentOrganizerByUserId(data.user);
|
||||
|
||||
message = "Tournament permissions given";
|
||||
break;
|
||||
}
|
||||
case "LINK_PLAYER": {
|
||||
requireRole("STAFF");
|
||||
|
||||
await AdminRepository.linkUserAndPlayer({
|
||||
userId: data.user,
|
||||
playerId: data.playerId,
|
||||
});
|
||||
|
||||
message = "Linked user and player";
|
||||
break;
|
||||
}
|
||||
case "BAN_USER": {
|
||||
requireRole("STAFF");
|
||||
|
||||
const bannedUser = notFoundIfNullish(
|
||||
await UserRepository.findLeanById(data.user),
|
||||
);
|
||||
const banExpiresAt = data.expiresAt ?? null;
|
||||
|
||||
await AdminRepository.banUser({
|
||||
bannedReason: data.reason ?? null,
|
||||
userId: data.user,
|
||||
banned: banExpiresAt ?? 1,
|
||||
bannedByUserId: user.id,
|
||||
});
|
||||
|
||||
await refreshBannedCache();
|
||||
|
||||
sendUserBannedWebhook({
|
||||
bannedUser,
|
||||
bannedBy: user,
|
||||
reason: data.reason ?? null,
|
||||
expiresAt: banExpiresAt,
|
||||
});
|
||||
|
||||
message = "User banned";
|
||||
break;
|
||||
}
|
||||
case "UNBAN_USER": {
|
||||
requireRole("STAFF");
|
||||
|
||||
const unbannedUser = notFoundIfNullish(
|
||||
await UserRepository.findLeanById(data.user),
|
||||
);
|
||||
|
||||
await AdminRepository.unbanUser({
|
||||
userId: data.user,
|
||||
unbannedByUserId: user.id,
|
||||
});
|
||||
|
||||
await refreshBannedCache();
|
||||
|
||||
sendUserUnbannedWebhook({
|
||||
unbannedUser,
|
||||
unbannedBy: user,
|
||||
});
|
||||
|
||||
message = "User unbanned";
|
||||
break;
|
||||
}
|
||||
case "UPDATE_FRIEND_CODE": {
|
||||
requireRole("STAFF");
|
||||
|
||||
await UserRepository.insertFriendCode({
|
||||
friendCode: normalizeFriendCode(data.friendCode),
|
||||
submitterUserId: user.id,
|
||||
userId: data.user,
|
||||
});
|
||||
|
||||
message = "Friend code updated";
|
||||
break;
|
||||
}
|
||||
case "API_ACCESS": {
|
||||
requireRole("ADMIN");
|
||||
|
||||
await AdminRepository.makeApiAccesserByUserId(data.user);
|
||||
|
||||
message = "API access granted";
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertUnreachable(data);
|
||||
}
|
||||
}
|
||||
|
||||
return successToast(message);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { ActionFunctionArgs } from "react-router";
|
||||
import { clearCombinedStreamsCache } from "~/features/core/streams/streams.server";
|
||||
import { parseFormDataWithImages } from "~/form/parse.server";
|
||||
import { requireRole } from "~/modules/permissions/guards.server";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import { assertUnreachable } from "~/utils/types";
|
||||
import { externalStreamActionSchema } from "../admin-schemas";
|
||||
import * as ExternalStreamRepository from "../ExternalStreamRepository.server";
|
||||
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
requireRole("ADMIN");
|
||||
|
||||
const result = await parseFormDataWithImages({
|
||||
request,
|
||||
schema: externalStreamActionSchema,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
return { fieldErrors: result.fieldErrors };
|
||||
}
|
||||
|
||||
const data = result.data;
|
||||
|
||||
switch (data._action) {
|
||||
case "CREATE": {
|
||||
await ExternalStreamRepository.insert({
|
||||
name: data.name,
|
||||
url: data.url,
|
||||
avatarImgId: data.avatar,
|
||||
startsAt: dateToDatabaseTimestamp(data.startTime),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "DELETE": {
|
||||
await ExternalStreamRepository.deleteById(data.id);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertUnreachable(data);
|
||||
}
|
||||
}
|
||||
|
||||
clearCombinedStreamsCache();
|
||||
|
||||
return null;
|
||||
};
|
||||
Reference in New Issue
Block a user