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,
|
adminUser,
|
||||||
makeAdminPatron,
|
makeAdminPatron,
|
||||||
makeAdminVideoAdder,
|
makeAdminVideoAdder,
|
||||||
|
makeAdminTournamentOrganizer,
|
||||||
nzapUser,
|
nzapUser,
|
||||||
users,
|
users,
|
||||||
fixAdminId,
|
fixAdminId,
|
||||||
|
|
@ -251,6 +252,12 @@ function makeAdminVideoAdder() {
|
||||||
sql.prepare(`update "User" set "isVideoAdder" = 1 where id = 1`).run();
|
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() {
|
function adminUserWeaponPool() {
|
||||||
for (const [i, weaponSplId] of [200, 1100, 2000, 4000].entries()) {
|
for (const [i, weaponSplId] of [200, 1100, 2000, 4000].entries()) {
|
||||||
sql
|
sql
|
||||||
|
|
|
||||||
|
|
@ -753,6 +753,7 @@ export interface User {
|
||||||
inGameName: string | null;
|
inGameName: string | null;
|
||||||
isArtist: Generated<number | null>;
|
isArtist: Generated<number | null>;
|
||||||
isVideoAdder: Generated<number | null>;
|
isVideoAdder: Generated<number | null>;
|
||||||
|
isTournamentOrganizer: Generated<number | null>;
|
||||||
languages: string | null;
|
languages: string | null;
|
||||||
motionSens: number | null;
|
motionSens: number | null;
|
||||||
patronSince: number | null;
|
patronSince: number | null;
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,14 @@ export function makeVideoAdderByUserId(userId: number) {
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function makeTournamentOrganizerByUserId(userId: number) {
|
||||||
|
return db
|
||||||
|
.updateTable("User")
|
||||||
|
.set({ isTournamentOrganizer: 1 })
|
||||||
|
.where("User.id", "=", userId)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
export async function linkUserAndPlayer({
|
export async function linkUserAndPlayer({
|
||||||
userId,
|
userId,
|
||||||
playerId,
|
playerId,
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,12 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
await AdminRepository.makeVideoAdderByUserId(data.user);
|
await AdminRepository.makeVideoAdderByUserId(data.user);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "TOURNAMENT_ORGANIZER": {
|
||||||
|
validate(isMod(user), "Mod needed", 401);
|
||||||
|
|
||||||
|
await AdminRepository.makeTournamentOrganizerByUserId(data.user);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "LINK_PLAYER": {
|
case "LINK_PLAYER": {
|
||||||
validate(isMod(user), "Mod needed", 401);
|
validate(isMod(user), "Mod needed", 401);
|
||||||
|
|
||||||
|
|
@ -155,6 +161,10 @@ export const adminActionSchema = z.union([
|
||||||
_action: _action("VIDEO_ADDER"),
|
_action: _action("VIDEO_ADDER"),
|
||||||
user: z.preprocess(actualNumber, z.number().positive()),
|
user: z.preprocess(actualNumber, z.number().positive()),
|
||||||
}),
|
}),
|
||||||
|
z.object({
|
||||||
|
_action: _action("TOURNAMENT_ORGANIZER"),
|
||||||
|
user: z.preprocess(actualNumber, z.number().positive()),
|
||||||
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
_action: _action("ARTIST"),
|
_action: _action("ARTIST"),
|
||||||
user: z.preprocess(actualNumber, z.number().positive()),
|
user: z.preprocess(actualNumber, z.number().positive()),
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ export default function AdminPage() {
|
||||||
{isMod(user) ? <LinkPlayer /> : null}
|
{isMod(user) ? <LinkPlayer /> : null}
|
||||||
{isMod(user) ? <GiveArtist /> : null}
|
{isMod(user) ? <GiveArtist /> : null}
|
||||||
{isMod(user) ? <GiveVideoAdder /> : null}
|
{isMod(user) ? <GiveVideoAdder /> : null}
|
||||||
|
{isMod(user) ? <GiveTournamentOrganizer /> : null}
|
||||||
{isMod(user) ? <UpdateFriendCode /> : null}
|
{isMod(user) ? <UpdateFriendCode /> : null}
|
||||||
|
|
||||||
{process.env.NODE_ENV !== "production" || isAdmin(user) ? (
|
{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() {
|
function UpdateFriendCode() {
|
||||||
const fetcher = useFetcher();
|
const fetcher = useFetcher();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,7 @@ import {
|
||||||
canAddNewEvent,
|
canAddNewEvent,
|
||||||
regClosesAtDate,
|
regClosesAtDate,
|
||||||
} from "../calendar-utils";
|
} from "../calendar-utils";
|
||||||
import {
|
import { formValuesToBracketProgression } from "../calendar-utils.server";
|
||||||
canCreateTournament,
|
|
||||||
formValuesToBracketProgression,
|
|
||||||
} from "../calendar-utils.server";
|
|
||||||
|
|
||||||
export const action: ActionFunction = async ({ request }) => {
|
export const action: ActionFunction = async ({ request }) => {
|
||||||
const user = await requireUser(request);
|
const user = await requireUser(request);
|
||||||
|
|
@ -97,7 +94,9 @@ export const action: ActionFunction = async ({ request }) => {
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
autoValidateAvatar: Boolean(user.patronTier),
|
autoValidateAvatar: Boolean(user.patronTier),
|
||||||
toToolsEnabled: canCreateTournament(user) ? Number(data.toToolsEnabled) : 0,
|
toToolsEnabled: user.isTournamentOrganizer
|
||||||
|
? Number(data.toToolsEnabled)
|
||||||
|
: 0,
|
||||||
toToolsMode:
|
toToolsMode:
|
||||||
rankedModesShort.find((mode) => mode === data.toToolsMode) ?? null,
|
rankedModesShort.find((mode) => mode === data.toToolsMode) ?? null,
|
||||||
bracketProgression: formValuesToBracketProgression(data),
|
bracketProgression: formValuesToBracketProgression(data),
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,9 @@
|
||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
import type { TournamentSettings } from "~/db/tables";
|
import type { TournamentSettings } from "~/db/tables";
|
||||||
import { isAdmin } from "~/permissions";
|
|
||||||
import { BRACKET_NAMES } from "../tournament/tournament-constants";
|
import { BRACKET_NAMES } from "../tournament/tournament-constants";
|
||||||
import type { newCalendarEventActionSchema } from "./actions/calendar.new.server";
|
import type { newCalendarEventActionSchema } from "./actions/calendar.new.server";
|
||||||
import { validateFollowUpBrackets } from "./calendar-utils";
|
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(
|
export function formValuesToBracketProgression(
|
||||||
args: z.infer<typeof newCalendarEventActionSchema>,
|
args: z.infer<typeof newCalendarEventActionSchema>,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import { validate } from "~/utils/remix";
|
||||||
import { makeTitle } from "~/utils/strings";
|
import { makeTitle } from "~/utils/strings";
|
||||||
import { tournamentBracketsPage } from "~/utils/urls";
|
import { tournamentBracketsPage } from "~/utils/urls";
|
||||||
import { canAddNewEvent } from "../calendar-utils";
|
import { canAddNewEvent } from "../calendar-utils";
|
||||||
import { canCreateTournament } from "../calendar-utils.server";
|
|
||||||
|
|
||||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
const t = await i18next.getFixedT(request);
|
const t = await i18next.getFixedT(request);
|
||||||
|
|
@ -72,23 +71,20 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userCanCreateTournament = canCreateTournament(user);
|
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
managedBadges: await BadgeRepository.findManagedByUserId(user.id),
|
managedBadges: await BadgeRepository.findManagedByUserId(user.id),
|
||||||
recentEventsWithMapPools:
|
recentEventsWithMapPools:
|
||||||
await CalendarRepository.findRecentMapPoolsByAuthorId(user.id),
|
await CalendarRepository.findRecentMapPoolsByAuthorId(user.id),
|
||||||
eventToEdit: canEditEvent ? eventToEdit : undefined,
|
eventToEdit: canEditEvent ? eventToEdit : undefined,
|
||||||
eventToCopy:
|
eventToCopy:
|
||||||
userCanCreateTournament && !eventToEdit
|
user.isTournamentOrganizer && !eventToEdit
|
||||||
? await eventWithTournament("copyEventId")
|
? await eventWithTournament("copyEventId")
|
||||||
: undefined,
|
: undefined,
|
||||||
recentTournaments:
|
recentTournaments:
|
||||||
userCanCreateTournament && !eventToEdit
|
user.isTournamentOrganizer && !eventToEdit
|
||||||
? await CalendarRepository.findRecentTournamentsByAuthorId(user.id)
|
? await CalendarRepository.findRecentTournamentsByAuthorId(user.id)
|
||||||
: undefined,
|
: undefined,
|
||||||
title: makeTitle([canEditEvent ? "Edit" : "New", t("pages.calendar")]),
|
title: makeTitle([canEditEvent ? "Edit" : "New", t("pages.calendar")]),
|
||||||
canCreateTournament: userCanCreateTournament,
|
|
||||||
organizations: await TournamentOrganizationRepository.findByOrganizerUserId(
|
organizations: await TournamentOrganizationRepository.findByOrganizerUserId(
|
||||||
user.id,
|
user.id,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { CrossIcon } from "~/components/icons/Cross";
|
||||||
import { TrashIcon } from "~/components/icons/Trash";
|
import { TrashIcon } from "~/components/icons/Trash";
|
||||||
import type { Tables } from "~/db/tables";
|
import type { Tables } from "~/db/tables";
|
||||||
import type { Badge as BadgeType, CalendarEventTag } from "~/db/types";
|
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 { MapPool } from "~/features/map-list-generator/core/map-pool";
|
||||||
import {
|
import {
|
||||||
BRACKET_NAMES,
|
BRACKET_NAMES,
|
||||||
|
|
@ -133,7 +134,6 @@ function TemplateTournamentForm() {
|
||||||
|
|
||||||
function EventForm() {
|
function EventForm() {
|
||||||
const fetcher = useFetcher();
|
const fetcher = useFetcher();
|
||||||
const data = useLoaderData<typeof loader>();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { eventToEdit, eventToCopy } = useLoaderData<typeof loader>();
|
const { eventToEdit, eventToCopy } = useLoaderData<typeof loader>();
|
||||||
const baseEvent = useBaseEvent();
|
const baseEvent = useBaseEvent();
|
||||||
|
|
@ -142,6 +142,7 @@ function EventForm() {
|
||||||
);
|
);
|
||||||
const ref = React.useRef<HTMLFormElement>(null);
|
const ref = React.useRef<HTMLFormElement>(null);
|
||||||
const [avatarImg, setAvatarImg] = React.useState<File | null>(null);
|
const [avatarImg, setAvatarImg] = React.useState<File | null>(null);
|
||||||
|
const user = useUser();
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
const formData = new FormData(ref.current!);
|
const formData = new FormData(ref.current!);
|
||||||
|
|
@ -179,12 +180,12 @@ function EventForm() {
|
||||||
value={eventToCopy.tournamentId}
|
value={eventToCopy.tournamentId}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{data.canCreateTournament && !eventToEdit && (
|
{user?.isTournamentOrganizer && !eventToEdit ? (
|
||||||
<TournamentEnabler
|
<TournamentEnabler
|
||||||
checked={isTournament}
|
checked={isTournament}
|
||||||
setChecked={setIsTournament}
|
setChecked={setIsTournament}
|
||||||
/>
|
/>
|
||||||
)}
|
) : null}
|
||||||
<NameInput />
|
<NameInput />
|
||||||
<DescriptionTextarea supportsMarkdown={isTournament} />
|
<DescriptionTextarea supportsMarkdown={isTournament} />
|
||||||
<OrganizationSelect />
|
<OrganizationSelect />
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,7 @@ export function findLeanById(id: number) {
|
||||||
...COMMON_USER_FIELDS,
|
...COMMON_USER_FIELDS,
|
||||||
"User.isArtist",
|
"User.isArtist",
|
||||||
"User.isVideoAdder",
|
"User.isVideoAdder",
|
||||||
|
"User.isTournamentOrganizer",
|
||||||
"User.patronTier",
|
"User.patronTier",
|
||||||
"User.favoriteBadgeId",
|
"User.favoriteBadgeId",
|
||||||
"User.languages",
|
"User.languages",
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
patronTier: user.patronTier,
|
patronTier: user.patronTier,
|
||||||
isArtist: user.isArtist,
|
isArtist: user.isArtist,
|
||||||
isVideoAdder: user.isVideoAdder,
|
isVideoAdder: user.isVideoAdder,
|
||||||
|
isTournamentOrganizer: user.isTournamentOrganizer,
|
||||||
inGameName: user.inGameName,
|
inGameName: user.inGameName,
|
||||||
friendCode: user.friendCode,
|
friendCode: user.friendCode,
|
||||||
languages: user.languages ? user.languages.split(",") : [],
|
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