mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Add TO permissions via UI
This commit is contained in:
parent
d753365d5d
commit
ba12a61ba9
|
|
@ -102,6 +102,7 @@ const basicSeeds = (variation?: SeedVariation | null) => [
|
|||
adminUser,
|
||||
makeAdminPatron,
|
||||
makeAdminVideoAdder,
|
||||
makeAdminTournamentOrganizer,
|
||||
nzapUser,
|
||||
users,
|
||||
fixAdminId,
|
||||
|
|
@ -251,6 +252,12 @@ function makeAdminVideoAdder() {
|
|||
sql.prepare(`update "User" set "isVideoAdder" = 1 where id = 1`).run();
|
||||
}
|
||||
|
||||
function makeAdminTournamentOrganizer() {
|
||||
sql
|
||||
.prepare(`update "User" set "isTournamentOrganizer" = 1 where id = 1`)
|
||||
.run();
|
||||
}
|
||||
|
||||
function adminUserWeaponPool() {
|
||||
for (const [i, weaponSplId] of [200, 1100, 2000, 4000].entries()) {
|
||||
sql
|
||||
|
|
|
|||
|
|
@ -753,6 +753,7 @@ export interface User {
|
|||
inGameName: string | null;
|
||||
isArtist: Generated<number | null>;
|
||||
isVideoAdder: Generated<number | null>;
|
||||
isTournamentOrganizer: Generated<number | null>;
|
||||
languages: string | null;
|
||||
motionSens: number | null;
|
||||
patronSince: number | null;
|
||||
|
|
|
|||
|
|
@ -76,6 +76,14 @@ export function makeVideoAdderByUserId(userId: number) {
|
|||
.execute();
|
||||
}
|
||||
|
||||
export function makeTournamentOrganizerByUserId(userId: number) {
|
||||
return db
|
||||
.updateTable("User")
|
||||
.set({ isTournamentOrganizer: 1 })
|
||||
.where("User.id", "=", userId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export async function linkUserAndPlayer({
|
||||
userId,
|
||||
playerId,
|
||||
|
|
|
|||
|
|
@ -68,6 +68,12 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||
await AdminRepository.makeVideoAdderByUserId(data.user);
|
||||
break;
|
||||
}
|
||||
case "TOURNAMENT_ORGANIZER": {
|
||||
validate(isMod(user), "Mod needed", 401);
|
||||
|
||||
await AdminRepository.makeTournamentOrganizerByUserId(data.user);
|
||||
break;
|
||||
}
|
||||
case "LINK_PLAYER": {
|
||||
validate(isMod(user), "Mod needed", 401);
|
||||
|
||||
|
|
@ -155,6 +161,10 @@ export const adminActionSchema = z.union([
|
|||
_action: _action("VIDEO_ADDER"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("TOURNAMENT_ORGANIZER"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("ARTIST"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export default function AdminPage() {
|
|||
{isMod(user) ? <LinkPlayer /> : null}
|
||||
{isMod(user) ? <GiveArtist /> : null}
|
||||
{isMod(user) ? <GiveVideoAdder /> : null}
|
||||
{isMod(user) ? <GiveTournamentOrganizer /> : null}
|
||||
{isMod(user) ? <UpdateFriendCode /> : null}
|
||||
|
||||
{process.env.NODE_ENV !== "production" || isAdmin(user) ? (
|
||||
|
|
@ -202,6 +203,31 @@ function GiveVideoAdder() {
|
|||
);
|
||||
}
|
||||
|
||||
function GiveTournamentOrganizer() {
|
||||
const fetcher = useFetcher();
|
||||
|
||||
return (
|
||||
<fetcher.Form className="stack md" method="post">
|
||||
<h2>Give tournament organizer</h2>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<label>User</label>
|
||||
<UserSearch inputName="user" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
<SubmitButton
|
||||
type="submit"
|
||||
_action="TOURNAMENT_ORGANIZER"
|
||||
state={fetcher.state}
|
||||
>
|
||||
Add as tournament organizer
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
);
|
||||
}
|
||||
|
||||
function UpdateFriendCode() {
|
||||
const fetcher = useFetcher();
|
||||
|
||||
|
|
|
|||
|
|
@ -45,10 +45,7 @@ import {
|
|||
canAddNewEvent,
|
||||
regClosesAtDate,
|
||||
} from "../calendar-utils";
|
||||
import {
|
||||
canCreateTournament,
|
||||
formValuesToBracketProgression,
|
||||
} from "../calendar-utils.server";
|
||||
import { formValuesToBracketProgression } from "../calendar-utils.server";
|
||||
|
||||
export const action: ActionFunction = async ({ request }) => {
|
||||
const user = await requireUser(request);
|
||||
|
|
@ -97,7 +94,9 @@ export const action: ActionFunction = async ({ request }) => {
|
|||
}
|
||||
: undefined,
|
||||
autoValidateAvatar: Boolean(user.patronTier),
|
||||
toToolsEnabled: canCreateTournament(user) ? Number(data.toToolsEnabled) : 0,
|
||||
toToolsEnabled: user.isTournamentOrganizer
|
||||
? Number(data.toToolsEnabled)
|
||||
: 0,
|
||||
toToolsMode:
|
||||
rankedModesShort.find((mode) => mode === data.toToolsMode) ?? null,
|
||||
bracketProgression: formValuesToBracketProgression(data),
|
||||
|
|
|
|||
|
|
@ -1,18 +1,9 @@
|
|||
import type { z } from "zod";
|
||||
import type { TournamentSettings } from "~/db/tables";
|
||||
import { isAdmin } from "~/permissions";
|
||||
import { BRACKET_NAMES } from "../tournament/tournament-constants";
|
||||
import type { newCalendarEventActionSchema } from "./actions/calendar.new.server";
|
||||
import { validateFollowUpBrackets } from "./calendar-utils";
|
||||
|
||||
const usersWithTournamentPerms =
|
||||
process.env.TOURNAMENT_PERMS?.split(",").map(Number) ?? [];
|
||||
export function canCreateTournament(user?: { id: number }) {
|
||||
if (!user) return false;
|
||||
|
||||
return isAdmin(user) || usersWithTournamentPerms.includes(user.id);
|
||||
}
|
||||
|
||||
export function formValuesToBracketProgression(
|
||||
args: z.infer<typeof newCalendarEventActionSchema>,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import { validate } from "~/utils/remix";
|
|||
import { makeTitle } from "~/utils/strings";
|
||||
import { tournamentBracketsPage } from "~/utils/urls";
|
||||
import { canAddNewEvent } from "../calendar-utils";
|
||||
import { canCreateTournament } from "../calendar-utils.server";
|
||||
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const t = await i18next.getFixedT(request);
|
||||
|
|
@ -72,23 +71,20 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|||
);
|
||||
}
|
||||
|
||||
const userCanCreateTournament = canCreateTournament(user);
|
||||
|
||||
return json({
|
||||
managedBadges: await BadgeRepository.findManagedByUserId(user.id),
|
||||
recentEventsWithMapPools:
|
||||
await CalendarRepository.findRecentMapPoolsByAuthorId(user.id),
|
||||
eventToEdit: canEditEvent ? eventToEdit : undefined,
|
||||
eventToCopy:
|
||||
userCanCreateTournament && !eventToEdit
|
||||
user.isTournamentOrganizer && !eventToEdit
|
||||
? await eventWithTournament("copyEventId")
|
||||
: undefined,
|
||||
recentTournaments:
|
||||
userCanCreateTournament && !eventToEdit
|
||||
user.isTournamentOrganizer && !eventToEdit
|
||||
? await CalendarRepository.findRecentTournamentsByAuthorId(user.id)
|
||||
: undefined,
|
||||
title: makeTitle([canEditEvent ? "Edit" : "New", t("pages.calendar")]),
|
||||
canCreateTournament: userCanCreateTournament,
|
||||
organizations: await TournamentOrganizationRepository.findByOrganizerUserId(
|
||||
user.id,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { CrossIcon } from "~/components/icons/Cross";
|
|||
import { TrashIcon } from "~/components/icons/Trash";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import type { Badge as BadgeType, CalendarEventTag } from "~/db/types";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import { MapPool } from "~/features/map-list-generator/core/map-pool";
|
||||
import {
|
||||
BRACKET_NAMES,
|
||||
|
|
@ -133,7 +134,6 @@ function TemplateTournamentForm() {
|
|||
|
||||
function EventForm() {
|
||||
const fetcher = useFetcher();
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const { t } = useTranslation();
|
||||
const { eventToEdit, eventToCopy } = useLoaderData<typeof loader>();
|
||||
const baseEvent = useBaseEvent();
|
||||
|
|
@ -142,6 +142,7 @@ function EventForm() {
|
|||
);
|
||||
const ref = React.useRef<HTMLFormElement>(null);
|
||||
const [avatarImg, setAvatarImg] = React.useState<File | null>(null);
|
||||
const user = useUser();
|
||||
|
||||
const handleSubmit = () => {
|
||||
const formData = new FormData(ref.current!);
|
||||
|
|
@ -179,12 +180,12 @@ function EventForm() {
|
|||
value={eventToCopy.tournamentId}
|
||||
/>
|
||||
) : null}
|
||||
{data.canCreateTournament && !eventToEdit && (
|
||||
{user?.isTournamentOrganizer && !eventToEdit ? (
|
||||
<TournamentEnabler
|
||||
checked={isTournament}
|
||||
setChecked={setIsTournament}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
<NameInput />
|
||||
<DescriptionTextarea supportsMarkdown={isTournament} />
|
||||
<OrganizationSelect />
|
||||
|
|
|
|||
|
|
@ -244,6 +244,7 @@ export function findLeanById(id: number) {
|
|||
...COMMON_USER_FIELDS,
|
||||
"User.isArtist",
|
||||
"User.isVideoAdder",
|
||||
"User.isTournamentOrganizer",
|
||||
"User.patronTier",
|
||||
"User.favoriteBadgeId",
|
||||
"User.languages",
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|||
patronTier: user.patronTier,
|
||||
isArtist: user.isArtist,
|
||||
isVideoAdder: user.isVideoAdder,
|
||||
isTournamentOrganizer: user.isTournamentOrganizer,
|
||||
inGameName: user.inGameName,
|
||||
friendCode: user.friendCode,
|
||||
languages: user.languages ? user.languages.split(",") : [],
|
||||
|
|
|
|||
5
migrations/067-tournament-organizer-perms.js
Normal file
5
migrations/067-tournament-organizer-perms.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export function up(db) {
|
||||
db.prepare(
|
||||
`alter table "User" add column "isTournamentOrganizer" integer default 0`,
|
||||
).run();
|
||||
}
|
||||
16
scripts/add-tos.ts
Normal file
16
scripts/add-tos.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import "dotenv/config";
|
||||
import * as AdminRepository from "~/features/admin/AdminRepository.server";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
async function main() {
|
||||
const input = process.argv[2]?.trim();
|
||||
|
||||
const userIds = input.split(",").map((id) => Number(id));
|
||||
|
||||
for (const userId of userIds) {
|
||||
await AdminRepository.makeTournamentOrganizerByUserId(userId);
|
||||
}
|
||||
logger.info(`Added TOs: ${userIds}`);
|
||||
}
|
||||
|
||||
main();
|
||||
Loading…
Reference in New Issue
Block a user