mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-30 07:12:36 -05:00
Add match id (optional) to user report
This commit is contained in:
@@ -3177,6 +3177,12 @@ async function userReports() {
|
||||
0, 0, 0, 1, 2, 2, 2, 2, 5, 5, 7, 8, 10, 11, 11,
|
||||
];
|
||||
|
||||
// attach a real SendouQ match to some reports so the admin tab's match links have data
|
||||
const someMatch = await db
|
||||
.selectFrom("GroupMatch")
|
||||
.select("id")
|
||||
.executeTakeFirst();
|
||||
|
||||
await db
|
||||
.insertInto("UserReport")
|
||||
.values(
|
||||
@@ -3185,6 +3191,7 @@ async function userReports() {
|
||||
reporterUserId: 10 + i,
|
||||
category: USER_REPORT_CATEGORIES[i % USER_REPORT_CATEGORIES.length],
|
||||
description: faker.lorem.sentences({ min: 1, max: 3 }),
|
||||
matchId: i % 3 === 0 ? (someMatch?.id ?? null) : null,
|
||||
createdAt: dateToDatabaseTimestamp(
|
||||
sub(new Date(), { months: monthsAgo, days: (i * 3) % 7, hours: i }),
|
||||
),
|
||||
|
||||
@@ -1305,6 +1305,7 @@ export interface UserReport {
|
||||
reporterUserId: number;
|
||||
category: UserReportCategory;
|
||||
description: string;
|
||||
matchId: number | null;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,17 @@ import * as PlayerStatRepository from "./PlayerStatRepository.server";
|
||||
import * as ReportedWeaponRepository from "./ReportedWeaponRepository.server";
|
||||
import * as SkillRepository from "./SkillRepository.server";
|
||||
|
||||
/** Whether a GroupMatch with the given id exists. */
|
||||
export async function exists(id: number) {
|
||||
const row = await db
|
||||
.selectFrom("GroupMatch")
|
||||
.select("id")
|
||||
.where("id", "=", id)
|
||||
.executeTakeFirst();
|
||||
|
||||
return Boolean(row);
|
||||
}
|
||||
|
||||
export async function findById(id: number) {
|
||||
const result = await db
|
||||
.selectFrom("GroupMatch")
|
||||
|
||||
@@ -12,7 +12,7 @@ import { USER_REPORT_CATEGORY_LABELS } from "~/features/user-report/user-report-
|
||||
import { SendouForm } from "~/form";
|
||||
import { useFormatDistanceToNow } from "~/hooks/intl/useFormatDistanceToNow";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { userPage } from "~/utils/urls";
|
||||
import { sendouQMatchPage, userPage } from "~/utils/urls";
|
||||
import { action } from "../actions/u.$identifier.admin.server";
|
||||
import { SubPageHeader } from "../components/SubPageHeader";
|
||||
import { loader } from "../loaders/u.$identifier.admin.server";
|
||||
@@ -230,6 +230,14 @@ function Reports() {
|
||||
- {formatDistanceToNow(report.createdAt)}
|
||||
</summary>
|
||||
<p className="ml-2 whitespace-pre-wrap">{report.description}</p>
|
||||
{report.matchId !== null ? (
|
||||
<Link
|
||||
className="ml-2 text-xs"
|
||||
to={sendouQMatchPage(report.matchId)}
|
||||
>
|
||||
SendouQ match #{report.matchId}
|
||||
</Link>
|
||||
) : null}
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,7 @@ export function upsert(
|
||||
oc.columns(["reportedUserId", "reporterUserId"]).doUpdateSet({
|
||||
category: args.category,
|
||||
description: args.description,
|
||||
matchId: args.matchId,
|
||||
createdAt: databaseTimestampNow(),
|
||||
}),
|
||||
)
|
||||
@@ -65,6 +66,7 @@ export function findAllByReportedUserId(reportedUserId: number) {
|
||||
"UserReport.id",
|
||||
"UserReport.category",
|
||||
"UserReport.description",
|
||||
"UserReport.matchId",
|
||||
"UserReport.createdAt",
|
||||
"UserReport.reporterUserId",
|
||||
"User.username as reporterUsername",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMatches } from "react-router";
|
||||
import { SendouDialog } from "~/components/elements/Dialog";
|
||||
import { toastQueue } from "~/components/elements/Toast";
|
||||
import { FormMessage } from "~/components/FormMessage";
|
||||
@@ -6,6 +7,8 @@ import { SendouForm } from "~/form";
|
||||
import { userReportPage } from "~/utils/urls";
|
||||
import { reportUserSchema } from "../user-report-schemas";
|
||||
|
||||
const SENDOUQ_MATCH_ROUTE_ID = "features/sendouq-match/routes/q.match.$id";
|
||||
|
||||
/**
|
||||
* Modal for reporting a user to the staff, posting to the `/user-report/:id` resource
|
||||
* route. Re-reporting the same user overwrites the previous report. Rendered wherever
|
||||
@@ -21,6 +24,7 @@ export function ReportUserDialog({
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation(["user"]);
|
||||
const prefilledMatchId = useSendouQMatchIdFromRoute();
|
||||
|
||||
return (
|
||||
<SendouDialog
|
||||
@@ -30,6 +34,7 @@ export function ReportUserDialog({
|
||||
<SendouForm
|
||||
schema={reportUserSchema}
|
||||
action={userReportPage(userId)}
|
||||
defaultValues={{ matchId: prefilledMatchId }}
|
||||
onSuccess={() => {
|
||||
toastQueue.add(
|
||||
{ message: "Report sent to the staff", variant: "success" },
|
||||
@@ -42,6 +47,7 @@ export function ReportUserDialog({
|
||||
<>
|
||||
<FormField name="category" />
|
||||
<FormField name="description" />
|
||||
<FormField name="matchId" />
|
||||
<FormMessage type="info">
|
||||
{t("user:card.report.falseReportsWarning")}
|
||||
</FormMessage>
|
||||
@@ -51,3 +57,17 @@ export function ReportUserDialog({
|
||||
</SendouDialog>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the SendouQ match id from the current route so a report opened from a match page
|
||||
* prefills its "Match ID" field. Returns `undefined` on any other page.
|
||||
*/
|
||||
function useSendouQMatchIdFromRoute() {
|
||||
const matches = useMatches();
|
||||
|
||||
const matchRoute = matches.find(
|
||||
(match) => match.id === SENDOUQ_MATCH_ROUTE_ID,
|
||||
);
|
||||
|
||||
return matchRoute?.params.id;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { UserReportCategory } from "~/db/tables";
|
||||
import { logger } from "~/utils/logger";
|
||||
import { SENDOU_INK_BASE_URL, userPage } from "~/utils/urls";
|
||||
import { SENDOU_INK_BASE_URL, sendouQMatchPage, userPage } from "~/utils/urls";
|
||||
import { USER_REPORT_CATEGORY_LABELS } from "../user-report-constants";
|
||||
|
||||
const EMBED_DESCRIPTION_MAX_LENGTH = 1000;
|
||||
@@ -15,6 +15,7 @@ export function sendUserReportWebhook(args: {
|
||||
reporter: { username: string; discordId: string; customUrl: string | null };
|
||||
category: UserReportCategory;
|
||||
description: string;
|
||||
matchId: number | null;
|
||||
isUpdate: boolean;
|
||||
reportCounts: { lastMonth: number; lastYear: number };
|
||||
}) {
|
||||
@@ -50,6 +51,14 @@ export function sendUserReportWebhook(args: {
|
||||
name: "Description",
|
||||
value: truncate(args.description),
|
||||
},
|
||||
...(args.matchId !== null
|
||||
? [
|
||||
{
|
||||
name: "SendouQ match",
|
||||
value: `[#${args.matchId}](${SENDOU_INK_BASE_URL}${sendouQMatchPage(args.matchId)})`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: "Reports against this user",
|
||||
value: `Last month: ${args.reportCounts.lastMonth} • Last year: ${args.reportCounts.lastYear}`,
|
||||
|
||||
@@ -9,10 +9,8 @@ import {
|
||||
} from "~/utils/remix.server";
|
||||
import { sendUserReportWebhook } from "../core/discord-webhook.server";
|
||||
import * as UserReportRepository from "../UserReportRepository.server";
|
||||
import {
|
||||
reportUserParamsSchema,
|
||||
reportUserSchema,
|
||||
} from "../user-report-schemas";
|
||||
import { reportUserParamsSchema } from "../user-report-schemas";
|
||||
import { reportUserSchemaServer } from "../user-report-schemas.server";
|
||||
|
||||
export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
const user = requireUser();
|
||||
@@ -28,7 +26,10 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
await UserRepository.findLeanById(reportedUserId),
|
||||
);
|
||||
|
||||
const result = await parseFormData({ request, schema: reportUserSchema });
|
||||
const result = await parseFormData({
|
||||
request,
|
||||
schema: reportUserSchemaServer,
|
||||
});
|
||||
if (!result.success) {
|
||||
return { fieldErrors: result.fieldErrors };
|
||||
}
|
||||
@@ -38,6 +39,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
reporterUserId: user.id,
|
||||
category: result.data.category,
|
||||
description: result.data.description,
|
||||
matchId: result.data.matchId,
|
||||
});
|
||||
|
||||
const reportCounts =
|
||||
@@ -48,6 +50,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
reporter: user,
|
||||
category: result.data.category,
|
||||
description: result.data.description,
|
||||
matchId: result.data.matchId,
|
||||
isUpdate,
|
||||
reportCounts,
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { UserReportCategory } from "~/db/tables";
|
||||
|
||||
export const USER_REPORT = {
|
||||
DESCRIPTION_MAX_LENGTH: 2000,
|
||||
MATCH_ID_MAX_LENGTH: 10,
|
||||
};
|
||||
|
||||
/** English display names, shown on the staff-only admin tab and in the Discord webhook embed. */
|
||||
|
||||
22
app/features/user-report/user-report-schemas.server.ts
Normal file
22
app/features/user-report/user-report-schemas.server.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { z } from "zod";
|
||||
import * as SQMatchRepository from "~/features/sendouq-match/SQMatchRepository.server";
|
||||
import { reportUserSchema } from "./user-report-schemas";
|
||||
|
||||
export const reportUserSchemaServer = z.object({
|
||||
...reportUserSchema.shape,
|
||||
// cast to the concrete value type: the field's `.nullable()` makes its inferred
|
||||
// type a union that Zod's `.refine` overload can't resolve
|
||||
matchId: (reportUserSchema.shape.matchId as z.ZodType<string | null>)
|
||||
.refine(
|
||||
async (matchId) => {
|
||||
if (!matchId) return true;
|
||||
|
||||
const id = Number(matchId);
|
||||
if (!Number.isInteger(id) || id <= 0) return false;
|
||||
|
||||
return SQMatchRepository.exists(id);
|
||||
},
|
||||
{ message: "forms:errors.matchNotFound" },
|
||||
)
|
||||
.transform((matchId) => (matchId ? Number(matchId) : null)),
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { select, textAreaRequired } from "~/form/fields";
|
||||
import { select, textAreaRequired, textFieldOptional } from "~/form/fields";
|
||||
import { id } from "~/utils/zod";
|
||||
import { USER_REPORT } from "./user-report-constants";
|
||||
|
||||
@@ -21,6 +21,11 @@ export const reportUserSchema = z.object({
|
||||
label: "labels.description",
|
||||
maxLength: USER_REPORT.DESCRIPTION_MAX_LENGTH,
|
||||
}),
|
||||
matchId: textFieldOptional({
|
||||
label: "labels.reportMatchId",
|
||||
bottomText: "bottomTexts.reportMatchId",
|
||||
maxLength: USER_REPORT.MATCH_ID_MAX_LENGTH,
|
||||
}),
|
||||
});
|
||||
|
||||
export const reportUserParamsSchema = z.object({
|
||||
|
||||
BIN
db-test.sqlite3
BIN
db-test.sqlite3
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,6 @@
|
||||
import { NZAP_TEST_ID } from "~/db/seed/constants";
|
||||
import { LFG_PAGE } from "~/utils/urls";
|
||||
import { ADMIN_ID } from "~/features/admin/admin-constants";
|
||||
import { LFG_PAGE, SENDOUQ_PAGE, sendouQMatchPage } from "~/utils/urls";
|
||||
import {
|
||||
expect,
|
||||
impersonate,
|
||||
@@ -41,4 +42,24 @@ test.describe("User report", () => {
|
||||
await reportsList.locator("summary").first().click();
|
||||
await expect(page.getByText(description)).toBeVisible();
|
||||
});
|
||||
|
||||
test("prefills the match id when reporting from a match page", async ({
|
||||
page,
|
||||
}) => {
|
||||
await seed(page, "IN_SQ_MATCH");
|
||||
await impersonate(page, ADMIN_ID);
|
||||
await navigate({ page, url: SENDOUQ_PAGE });
|
||||
await expect(page).toHaveURL(/\/q\/match\/\d+/);
|
||||
const matchId = page.url().split("/match/")[1];
|
||||
|
||||
await navigate({
|
||||
page,
|
||||
url: `${sendouQMatchPage(Number(matchId))}?tab=rosters`,
|
||||
});
|
||||
|
||||
await page.getByRole("button", { name: /N-ZAP/ }).first().click();
|
||||
await page.getByTestId("report-user-button").click();
|
||||
|
||||
await expect(page.getByLabel("Match ID")).toHaveValue(matchId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "",
|
||||
"errors.maxLength": "",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "",
|
||||
"errors.notAllowedCharacters": "",
|
||||
"errors.atLeastOneOption": "",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "",
|
||||
"bottomTexts.banUserExpiresAtHelp": "",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "",
|
||||
"errors.maxLength": "",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "",
|
||||
"errors.notAllowedCharacters": "",
|
||||
"errors.atLeastOneOption": "",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "",
|
||||
"bottomTexts.banUserExpiresAtHelp": "",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "Tentatek",
|
||||
"options.xpDivision.JPN": "Takoroka",
|
||||
"bottomTexts.unverifiedXp": "Self-report a peak XP higher than your verified X Rank XP, by up to 200 points. Requires linked X Rank placements to report against.",
|
||||
"bottomTexts.reportMatchId": "SendouQ match this report relates to (optional).",
|
||||
"errors.unverifiedXpNotAboveVerified": "Peak XP must be higher than your verified X Rank XP, by at most 200 points. Link your X Rank placements first.",
|
||||
"labels.link": "Link",
|
||||
"labels.tag": "Tag",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "Must be at least {{min}} characters",
|
||||
"errors.maxLength": "Must be at most {{max}} characters",
|
||||
"errors.mustBeWholeNumber": "Must be a whole number (no decimals)",
|
||||
"errors.matchNotFound": "No SendouQ match found with this ID",
|
||||
"errors.invalidUrl": "Must be a valid URL",
|
||||
"errors.notAllowedCharacters": "Contains not allowed characters",
|
||||
"errors.atLeastOneOption": "At least one option must be selected",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "Ban expiration date",
|
||||
"bottomTexts.banUserExpiresAtHelp": "Leave empty for a permanent ban",
|
||||
"labels.reportCategory": "Category",
|
||||
"labels.reportMatchId": "Match ID",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "Inappropriate content",
|
||||
"options.userReportCategory.ALTING": "Alting",
|
||||
"options.userReportCategory.HARASSMENT": "Harassment",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "Etiqueta",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "Debe tener al menos {{min}} caracteres",
|
||||
"errors.maxLength": "Debe tener como máximo {{max}} caracteres",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "Debe ser una URL válida",
|
||||
"errors.notAllowedCharacters": "Contiene caracteres no permitidos",
|
||||
"errors.atLeastOneOption": "Debe seleccionarse al menos una opción",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "Fecha de expiración del baneo",
|
||||
"bottomTexts.banUserExpiresAtHelp": "Deja vacío para un baneo permanente",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "",
|
||||
"errors.maxLength": "",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "",
|
||||
"errors.notAllowedCharacters": "",
|
||||
"errors.atLeastOneOption": "",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "",
|
||||
"bottomTexts.banUserExpiresAtHelp": "",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "",
|
||||
"errors.maxLength": "",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "",
|
||||
"errors.notAllowedCharacters": "",
|
||||
"errors.atLeastOneOption": "",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "",
|
||||
"bottomTexts.banUserExpiresAtHelp": "",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "",
|
||||
"errors.maxLength": "",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "",
|
||||
"errors.notAllowedCharacters": "",
|
||||
"errors.atLeastOneOption": "",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "",
|
||||
"bottomTexts.banUserExpiresAtHelp": "",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "",
|
||||
"errors.maxLength": "",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "",
|
||||
"errors.notAllowedCharacters": "",
|
||||
"errors.atLeastOneOption": "",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "",
|
||||
"bottomTexts.banUserExpiresAtHelp": "",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "",
|
||||
"errors.maxLength": "",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "",
|
||||
"errors.notAllowedCharacters": "",
|
||||
"errors.atLeastOneOption": "",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "",
|
||||
"bottomTexts.banUserExpiresAtHelp": "",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "",
|
||||
"errors.maxLength": "",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "",
|
||||
"errors.notAllowedCharacters": "",
|
||||
"errors.atLeastOneOption": "",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "",
|
||||
"bottomTexts.banUserExpiresAtHelp": "",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "",
|
||||
"errors.maxLength": "",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "",
|
||||
"errors.notAllowedCharacters": "",
|
||||
"errors.atLeastOneOption": "",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "",
|
||||
"bottomTexts.banUserExpiresAtHelp": "",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "",
|
||||
"errors.maxLength": "",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "",
|
||||
"errors.notAllowedCharacters": "",
|
||||
"errors.atLeastOneOption": "",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "",
|
||||
"bottomTexts.banUserExpiresAtHelp": "",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "",
|
||||
"errors.maxLength": "",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "",
|
||||
"errors.notAllowedCharacters": "",
|
||||
"errors.atLeastOneOption": "",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "",
|
||||
"bottomTexts.banUserExpiresAtHelp": "",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "",
|
||||
"errors.maxLength": "",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "",
|
||||
"errors.notAllowedCharacters": "",
|
||||
"errors.atLeastOneOption": "",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "",
|
||||
"bottomTexts.banUserExpiresAtHelp": "",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "",
|
||||
"labels.tag": "",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "",
|
||||
"errors.maxLength": "",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "",
|
||||
"errors.notAllowedCharacters": "",
|
||||
"errors.atLeastOneOption": "",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "",
|
||||
"bottomTexts.banUserExpiresAtHelp": "",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"options.xpDivision.WEST": "",
|
||||
"options.xpDivision.JPN": "",
|
||||
"bottomTexts.unverifiedXp": "",
|
||||
"bottomTexts.reportMatchId": "",
|
||||
"errors.unverifiedXpNotAboveVerified": "",
|
||||
"labels.link": "链接",
|
||||
"labels.tag": "标签",
|
||||
@@ -59,6 +60,7 @@
|
||||
"errors.minLength": "字数不能少于 {{min}} 个字符",
|
||||
"errors.maxLength": "字数不能多于 {{max}} 个字符",
|
||||
"errors.mustBeWholeNumber": "",
|
||||
"errors.matchNotFound": "",
|
||||
"errors.invalidUrl": "请输入有效的 URL 地址",
|
||||
"errors.notAllowedCharacters": "包含非法字符",
|
||||
"errors.atLeastOneOption": "请至少选择一个选项",
|
||||
@@ -224,6 +226,7 @@
|
||||
"labels.banUserExpiresAt": "封禁结束日期",
|
||||
"bottomTexts.banUserExpiresAtHelp": "留空则表示永久封禁",
|
||||
"labels.reportCategory": "",
|
||||
"labels.reportMatchId": "",
|
||||
"options.userReportCategory.INAPPROPRIATE_CONTENT": "",
|
||||
"options.userReportCategory.ALTING": "",
|
||||
"options.userReportCategory.HARASSMENT": "",
|
||||
|
||||
13
migrations/158-user-report-match-id.js
Normal file
13
migrations/158-user-report-match-id.js
Normal file
@@ -0,0 +1,13 @@
|
||||
export function up(db) {
|
||||
db.transaction(() => {
|
||||
db.prepare(
|
||||
/* sql */ `
|
||||
alter table "UserReport"
|
||||
add column "matchId" integer
|
||||
references "GroupMatch"("id") on delete set null
|
||||
`,
|
||||
).run();
|
||||
|
||||
db.pragma("foreign_key_check");
|
||||
})();
|
||||
}
|
||||
Reference in New Issue
Block a user