mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-30 07:12:36 -05:00
Move to models folder
This commit is contained in:
133
app/models/Tournament.ts
Normal file
133
app/models/Tournament.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import type { Prisma } from ".prisma/client";
|
||||
import { db } from "~/utils/db.server";
|
||||
|
||||
export type FindById = Prisma.PromiseReturnType<typeof findById>;
|
||||
export function findById(id: string) {
|
||||
return db.tournament.findUnique({
|
||||
where: { id },
|
||||
include: { organizer: true, teams: { include: { members: true } } },
|
||||
});
|
||||
}
|
||||
|
||||
export type FindByNameForUrl = Prisma.PromiseReturnType<
|
||||
typeof findByNameForUrl
|
||||
>;
|
||||
export function findByNameForUrl(tournamentNameForUrl: string) {
|
||||
return db.tournament.findMany({
|
||||
where: {
|
||||
nameForUrl: tournamentNameForUrl.toLowerCase(),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
description: true,
|
||||
startTime: true,
|
||||
checkInStartTime: true,
|
||||
bannerBackground: true,
|
||||
bannerTextHSLArgs: true,
|
||||
seeds: true,
|
||||
organizer: {
|
||||
select: {
|
||||
name: true,
|
||||
discordInvite: true,
|
||||
twitter: true,
|
||||
nameForUrl: true,
|
||||
ownerId: true,
|
||||
},
|
||||
},
|
||||
mapPool: {
|
||||
select: {
|
||||
id: true,
|
||||
mode: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
brackets: {
|
||||
select: {
|
||||
type: true,
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
select: {
|
||||
checkedInTime: true,
|
||||
id: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
members: {
|
||||
select: {
|
||||
captain: true,
|
||||
member: {
|
||||
select: {
|
||||
id: true,
|
||||
discordAvatar: true,
|
||||
discordName: true,
|
||||
discordId: true,
|
||||
discordDiscriminator: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export type FindByNameForUrlWithInviteCodes = Prisma.PromiseReturnType<
|
||||
typeof findByNameForUrlWithInviteCodes
|
||||
>;
|
||||
|
||||
export function findByNameForUrlWithInviteCodes(tournamentNameForUrl: string) {
|
||||
return db.tournament.findMany({
|
||||
where: {
|
||||
nameForUrl: tournamentNameForUrl.toLowerCase(),
|
||||
},
|
||||
select: {
|
||||
startTime: true,
|
||||
organizer: {
|
||||
select: {
|
||||
nameForUrl: true,
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
inviteCode: true,
|
||||
checkedInTime: true,
|
||||
members: {
|
||||
select: {
|
||||
captain: true,
|
||||
member: {
|
||||
select: {
|
||||
id: true,
|
||||
discordAvatar: true,
|
||||
discordName: true,
|
||||
discordId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export type UpdateSeeds = Prisma.PromiseReturnType<typeof updateSeeds>;
|
||||
export function updateSeeds({
|
||||
tournamentId,
|
||||
seeds,
|
||||
}: {
|
||||
tournamentId: string;
|
||||
seeds: string[];
|
||||
}) {
|
||||
return db.tournament.update({
|
||||
where: {
|
||||
id: tournamentId,
|
||||
},
|
||||
data: {
|
||||
seeds,
|
||||
},
|
||||
});
|
||||
}
|
||||
59
app/models/TournamentTeam.ts
Normal file
59
app/models/TournamentTeam.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { Prisma } from ".prisma/client";
|
||||
import { db } from "~/utils/db.server";
|
||||
|
||||
export type Create = Prisma.PromiseReturnType<typeof create>;
|
||||
export function create({
|
||||
userId,
|
||||
teamName,
|
||||
tournamentId,
|
||||
}: {
|
||||
userId: string;
|
||||
teamName: string;
|
||||
tournamentId: string;
|
||||
}) {
|
||||
return db.tournamentTeam.create({
|
||||
data: {
|
||||
name: teamName.trim(),
|
||||
tournamentId,
|
||||
members: {
|
||||
create: {
|
||||
memberId: userId,
|
||||
tournamentId,
|
||||
captain: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export type FindById = Prisma.PromiseReturnType<typeof findById>;
|
||||
export function findById(id: string) {
|
||||
return db.tournamentTeam.findUnique({
|
||||
where: { id },
|
||||
include: { tournament: { include: { organizer: true } }, members: true },
|
||||
});
|
||||
}
|
||||
|
||||
export type CheckIn = Prisma.PromiseReturnType<typeof checkIn>;
|
||||
export function checkIn(id: string) {
|
||||
return db.tournamentTeam.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
checkedInTime: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export type CheckOut = Prisma.PromiseReturnType<typeof checkOut>;
|
||||
export function checkOut(id: string) {
|
||||
return db.tournamentTeam.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
checkedInTime: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
39
app/models/TournamentTeamMember.ts
Normal file
39
app/models/TournamentTeamMember.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { Prisma } from ".prisma/client";
|
||||
import { db } from "~/utils/db.server";
|
||||
|
||||
export type Create = Prisma.PromiseReturnType<typeof create>;
|
||||
export function create({
|
||||
userId,
|
||||
teamId,
|
||||
tournamentId,
|
||||
}: {
|
||||
userId: string;
|
||||
teamId: string;
|
||||
tournamentId: string;
|
||||
}) {
|
||||
return db.tournamentTeamMember.create({
|
||||
data: {
|
||||
tournamentId,
|
||||
teamId,
|
||||
memberId: userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export type Del = Prisma.PromiseReturnType<typeof del>;
|
||||
export function del({
|
||||
memberId,
|
||||
tournamentId,
|
||||
}: {
|
||||
memberId: string;
|
||||
tournamentId: string;
|
||||
}) {
|
||||
return db.tournamentTeamMember.delete({
|
||||
where: {
|
||||
memberId_tournamentId: {
|
||||
memberId,
|
||||
tournamentId,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
25
app/models/TrustRelationship.ts
Normal file
25
app/models/TrustRelationship.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Prisma } from ".prisma/client";
|
||||
import { db } from "~/utils/db.server";
|
||||
|
||||
export type Upsert = Prisma.PromiseReturnType<typeof upsert>;
|
||||
export function upsert({
|
||||
trustGiverId,
|
||||
trustReceiverId,
|
||||
}: {
|
||||
trustGiverId: string;
|
||||
trustReceiverId: string;
|
||||
}) {
|
||||
return db.trustRelationships.upsert({
|
||||
where: {
|
||||
trustGiverId_trustReceiverId: {
|
||||
trustGiverId,
|
||||
trustReceiverId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
trustGiverId,
|
||||
trustReceiverId,
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
import { Prisma } from ".prisma/client";
|
||||
import { db } from "~/utils/db.server";
|
||||
|
||||
export type PrismaTournaments = Prisma.PromiseReturnType<typeof tournaments>;
|
||||
|
||||
export function tournaments(tournamentNameForUrl: string) {
|
||||
return db.tournament.findMany({
|
||||
where: {
|
||||
nameForUrl: tournamentNameForUrl.toLowerCase(),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
description: true,
|
||||
startTime: true,
|
||||
checkInStartTime: true,
|
||||
bannerBackground: true,
|
||||
bannerTextHSLArgs: true,
|
||||
seeds: true,
|
||||
organizer: {
|
||||
select: {
|
||||
name: true,
|
||||
discordInvite: true,
|
||||
twitter: true,
|
||||
nameForUrl: true,
|
||||
ownerId: true,
|
||||
},
|
||||
},
|
||||
mapPool: {
|
||||
select: {
|
||||
id: true,
|
||||
mode: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
brackets: {
|
||||
select: {
|
||||
type: true,
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
select: {
|
||||
checkedInTime: true,
|
||||
id: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
members: {
|
||||
select: {
|
||||
captain: true,
|
||||
member: {
|
||||
select: {
|
||||
id: true,
|
||||
discordAvatar: true,
|
||||
discordName: true,
|
||||
discordId: true,
|
||||
discordDiscriminator: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export type PrismaTournamentsWithInviteCodes = Prisma.PromiseReturnType<
|
||||
typeof tournamentsWithInviteCodes
|
||||
>;
|
||||
|
||||
export function tournamentsWithInviteCodes(tournamentNameForUrl: string) {
|
||||
return db.tournament.findMany({
|
||||
where: {
|
||||
nameForUrl: tournamentNameForUrl.toLowerCase(),
|
||||
},
|
||||
select: {
|
||||
startTime: true,
|
||||
organizer: {
|
||||
select: {
|
||||
nameForUrl: true,
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
inviteCode: true,
|
||||
checkedInTime: true,
|
||||
members: {
|
||||
select: {
|
||||
captain: true,
|
||||
member: {
|
||||
select: {
|
||||
id: true,
|
||||
discordAvatar: true,
|
||||
discordName: true,
|
||||
discordId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export type PrismaTournamentById = Prisma.PromiseReturnType<
|
||||
typeof tournamentById
|
||||
>;
|
||||
|
||||
export function tournamentById(id: string) {
|
||||
return db.tournament.findUnique({
|
||||
where: { id },
|
||||
include: { organizer: true, teams: { include: { members: true } } },
|
||||
});
|
||||
}
|
||||
|
||||
export type PrismaTournamentTeamById = Prisma.PromiseReturnType<
|
||||
typeof tournamentTeamById
|
||||
>;
|
||||
|
||||
export function tournamentTeamById(id: string) {
|
||||
return db.tournamentTeam.findUnique({
|
||||
where: { id },
|
||||
include: { tournament: { include: { organizer: true } }, members: true },
|
||||
});
|
||||
}
|
||||
|
||||
export type PrismaCreateTournamentTeam = Prisma.PromiseReturnType<
|
||||
typeof createTournamentTeam
|
||||
>;
|
||||
|
||||
export function createTournamentTeam({
|
||||
userId,
|
||||
teamName,
|
||||
tournamentId,
|
||||
}: {
|
||||
userId: string;
|
||||
teamName: string;
|
||||
tournamentId: string;
|
||||
}) {
|
||||
return db.tournamentTeam.create({
|
||||
data: {
|
||||
name: teamName.trim(),
|
||||
tournamentId,
|
||||
members: {
|
||||
create: {
|
||||
memberId: userId,
|
||||
tournamentId,
|
||||
captain: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export type PrismaCreateTournamentTeamMember = Prisma.PromiseReturnType<
|
||||
typeof createTournamentTeamMember
|
||||
>;
|
||||
|
||||
export function createTournamentTeamMember({
|
||||
userId,
|
||||
teamId,
|
||||
tournamentId,
|
||||
}: {
|
||||
userId: string;
|
||||
teamId: string;
|
||||
tournamentId: string;
|
||||
}) {
|
||||
return db.tournamentTeamMember.create({
|
||||
data: {
|
||||
tournamentId,
|
||||
teamId,
|
||||
memberId: userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export type PrismaDeleteTournamentTeamMember = Prisma.PromiseReturnType<
|
||||
typeof deleteTournamentTeamMember
|
||||
>;
|
||||
|
||||
export function deleteTournamentTeamMember({
|
||||
memberId,
|
||||
tournamentId,
|
||||
}: {
|
||||
memberId: string;
|
||||
tournamentId: string;
|
||||
}) {
|
||||
return db.tournamentTeamMember.delete({
|
||||
where: {
|
||||
memberId_tournamentId: {
|
||||
memberId,
|
||||
tournamentId,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export type PrismaUpdateTournamentSeeds = Prisma.PromiseReturnType<
|
||||
typeof updateTournamentSeeds
|
||||
>;
|
||||
|
||||
export function updateTournamentSeeds({
|
||||
tournamentId,
|
||||
seeds,
|
||||
}: {
|
||||
tournamentId: string;
|
||||
seeds: string[];
|
||||
}) {
|
||||
return db.tournament.update({
|
||||
where: {
|
||||
id: tournamentId,
|
||||
},
|
||||
data: {
|
||||
seeds,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export type PrismaUpdateTeamCheckIn = Prisma.PromiseReturnType<
|
||||
typeof updateTeamCheckIn
|
||||
>;
|
||||
|
||||
export function updateTeamCheckIn(id: string) {
|
||||
return db.tournamentTeam.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
checkedInTime: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export type PrismaUpdateTeamCheckOut = Prisma.PromiseReturnType<
|
||||
typeof updateTeamCheckOut
|
||||
>;
|
||||
|
||||
export function updateTeamCheckOut(id: string) {
|
||||
return db.tournamentTeam.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
checkedInTime: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export type PrismaUpsertTrustRelationship = Prisma.PromiseReturnType<
|
||||
typeof upsertTrustRelationship
|
||||
>;
|
||||
|
||||
export function upsertTrustRelationship({
|
||||
trustGiverId,
|
||||
trustReceiverId,
|
||||
}: {
|
||||
trustGiverId: string;
|
||||
trustReceiverId: string;
|
||||
}) {
|
||||
return db.trustRelationships.upsert({
|
||||
where: {
|
||||
trustGiverId_trustReceiverId: {
|
||||
trustGiverId,
|
||||
trustReceiverId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
trustGiverId,
|
||||
trustReceiverId,
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
import { isTournamentAdmin } from "~/core/tournament/permissions";
|
||||
import { sortTeamsBySeed } from "~/core/tournament/utils";
|
||||
import { Serialized, Unpacked } from "~/utils";
|
||||
import * as db from "./tournament.prisma";
|
||||
import * as Tournament from "~/models/Tournament";
|
||||
import * as TournamentTeam from "~/models/TournamentTeam";
|
||||
import * as TournamentTeamMember from "~/models/TournamentTeamMember";
|
||||
import * as TrustRelationship from "~/models/TrustRelationship";
|
||||
|
||||
export type FindTournamentByNameForUrlI = Serialized<
|
||||
Prisma.PromiseReturnType<typeof findTournamentByNameForUrl>
|
||||
@@ -19,7 +22,7 @@ export async function findTournamentByNameForUrl({
|
||||
organizationNameForUrl: string;
|
||||
tournamentNameForUrl: string;
|
||||
}) {
|
||||
const tournaments = await db.tournaments(tournamentNameForUrl);
|
||||
const tournaments = await Tournament.findByNameForUrl(tournamentNameForUrl);
|
||||
|
||||
const result = tournaments.find(
|
||||
(tournament) =>
|
||||
@@ -49,7 +52,7 @@ function discordInviteToUrl(discordInvite: string) {
|
||||
return `https://discord.com/invite/${discordInvite}`;
|
||||
}
|
||||
|
||||
function addCSSProperties(tournament: Unpacked<db.PrismaTournaments>) {
|
||||
function addCSSProperties(tournament: Unpacked<Tournament.FindByNameForUrl>) {
|
||||
const { bannerTextHSLArgs, ...rest } = tournament;
|
||||
|
||||
return {
|
||||
@@ -74,7 +77,9 @@ export async function ownTeamWithInviteCode({
|
||||
tournamentNameForUrl: string;
|
||||
userId?: string;
|
||||
}) {
|
||||
const tournaments = await db.tournamentsWithInviteCodes(tournamentNameForUrl);
|
||||
const tournaments = await Tournament.findByNameForUrlWithInviteCodes(
|
||||
tournamentNameForUrl
|
||||
);
|
||||
|
||||
const tournament = tournaments.find(
|
||||
(tournament) =>
|
||||
@@ -99,7 +104,9 @@ export async function findTournamentWithInviteCodes({
|
||||
organizationNameForUrl: string;
|
||||
tournamentNameForUrl: string;
|
||||
}) {
|
||||
const tournaments = await db.tournamentsWithInviteCodes(tournamentNameForUrl);
|
||||
const tournaments = await Tournament.findByNameForUrlWithInviteCodes(
|
||||
tournamentNameForUrl
|
||||
);
|
||||
|
||||
const result = tournaments.find(
|
||||
(tournament) =>
|
||||
@@ -111,7 +118,7 @@ export async function findTournamentWithInviteCodes({
|
||||
return result;
|
||||
}
|
||||
|
||||
export const createTournamentTeam = db.createTournamentTeam;
|
||||
export const createTournamentTeam = TournamentTeam.create;
|
||||
|
||||
export async function joinTeamViaInviteCode({
|
||||
tournamentId,
|
||||
@@ -122,7 +129,7 @@ export async function joinTeamViaInviteCode({
|
||||
inviteCode: string;
|
||||
userId: string;
|
||||
}) {
|
||||
const tournament = await db.tournamentById(tournamentId);
|
||||
const tournament = await Tournament.findById(tournamentId);
|
||||
|
||||
if (!tournament) throw new Response("Invalid tournament id", { status: 400 });
|
||||
|
||||
@@ -142,13 +149,13 @@ export async function joinTeamViaInviteCode({
|
||||
)!.memberId;
|
||||
|
||||
return Promise.all([
|
||||
db.createTournamentTeamMember({
|
||||
TournamentTeamMember.create({
|
||||
teamId: tournamentTeamToJoin.id,
|
||||
userId,
|
||||
tournamentId,
|
||||
}),
|
||||
// TODO: this could also be put to queue and scheduled for later
|
||||
db.upsertTrustRelationship({ trustReceiverId, trustGiverId: userId }),
|
||||
TrustRelationship.upsert({ trustReceiverId, trustGiverId: userId }),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -161,7 +168,7 @@ export async function putPlayerToTeam({
|
||||
captainId: string;
|
||||
newPlayerId: string;
|
||||
}) {
|
||||
const tournamentTeam = await db.tournamentTeamById(teamId);
|
||||
const tournamentTeam = await TournamentTeam.findById(teamId);
|
||||
|
||||
if (!tournamentTeam) throw new Response("Invalid team id", { status: 400 });
|
||||
|
||||
@@ -179,7 +186,7 @@ export async function putPlayerToTeam({
|
||||
throw new Response("Not captain of the team", { status: 401 });
|
||||
}
|
||||
|
||||
return db.createTournamentTeamMember({
|
||||
return TournamentTeamMember.create({
|
||||
tournamentId: tournamentTeam.tournament.id,
|
||||
teamId,
|
||||
userId: newPlayerId,
|
||||
@@ -199,7 +206,7 @@ export async function removePlayerFromTeam({
|
||||
throw new Response("Can't remove captain", { status: 400 });
|
||||
}
|
||||
|
||||
const tournamentTeam = await db.tournamentTeamById(teamId);
|
||||
const tournamentTeam = await TournamentTeam.findById(teamId);
|
||||
|
||||
if (!tournamentTeam) throw new Response("Invalid team id", { status: 400 });
|
||||
if (tournamentTeam.checkedInTime) {
|
||||
@@ -215,7 +222,7 @@ export async function removePlayerFromTeam({
|
||||
throw new Response("Not captain of the team", { status: 401 });
|
||||
}
|
||||
|
||||
return db.deleteTournamentTeamMember({
|
||||
return TournamentTeamMember.del({
|
||||
memberId: playerId,
|
||||
tournamentId: tournamentTeam.tournament.id,
|
||||
});
|
||||
@@ -228,7 +235,7 @@ export async function checkIn({
|
||||
teamId: string;
|
||||
userId: string;
|
||||
}) {
|
||||
const tournamentTeam = await db.tournamentTeamById(teamId);
|
||||
const tournamentTeam = await TournamentTeam.findById(teamId);
|
||||
|
||||
if (!tournamentTeam) throw new Response("Invalid team id", { status: 400 });
|
||||
|
||||
@@ -258,7 +265,7 @@ export async function checkIn({
|
||||
|
||||
// TODO: fail if tournament has started
|
||||
|
||||
return db.updateTeamCheckIn(teamId);
|
||||
return TournamentTeam.checkIn(teamId);
|
||||
}
|
||||
|
||||
export async function checkOut({
|
||||
@@ -268,7 +275,7 @@ export async function checkOut({
|
||||
teamId: string;
|
||||
userId: string;
|
||||
}) {
|
||||
const tournamentTeam = await db.tournamentTeamById(teamId);
|
||||
const tournamentTeam = await TournamentTeam.findById(teamId);
|
||||
if (!tournamentTeam) throw new Response("Invalid team id", { status: 400 });
|
||||
if (
|
||||
!isTournamentAdmin({
|
||||
@@ -281,7 +288,7 @@ export async function checkOut({
|
||||
|
||||
// TODO: fail if tournament has started
|
||||
|
||||
return db.updateTeamCheckOut(teamId);
|
||||
return TournamentTeam.checkOut(teamId);
|
||||
}
|
||||
|
||||
export async function updateSeeds({
|
||||
@@ -293,7 +300,7 @@ export async function updateSeeds({
|
||||
userId: string;
|
||||
newSeeds: string[];
|
||||
}) {
|
||||
const tournament = await db.tournamentById(tournamentId);
|
||||
const tournament = await Tournament.findById(tournamentId);
|
||||
if (!tournament) throw new Response("Invalid tournament id", { status: 400 });
|
||||
if (
|
||||
!isTournamentAdmin({
|
||||
@@ -305,5 +312,5 @@ export async function updateSeeds({
|
||||
}
|
||||
|
||||
// TODO: fail if tournament has started
|
||||
return db.updateTournamentSeeds({ tournamentId, seeds: newSeeds });
|
||||
return Tournament.updateSeeds({ tournamentId, seeds: newSeeds });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user