mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-14 07:06:14 -05:00
Allow naming the model artist explicitly, and changing it later
This commit is contained in:
@@ -208,6 +208,7 @@ export interface PendingTrophy {
|
||||
acceptedAt: number | null;
|
||||
targetTrophyId: number | null;
|
||||
managerId: number | null;
|
||||
creatorId: number | null;
|
||||
}
|
||||
|
||||
export interface PendingTrophyApproval {
|
||||
|
||||
@@ -52,6 +52,34 @@ describe("trophy approvals", () => {
|
||||
expect(await trophyCount()).toBe(1);
|
||||
});
|
||||
|
||||
test("the named creator becomes the trophy's creator", async () => {
|
||||
const artist = await UserFactory.create();
|
||||
const submitter = await UserFactory.create();
|
||||
const organization = await TournamentOrganizationFactory.create({
|
||||
ownerId: submitter.id,
|
||||
});
|
||||
const pending = await TrophyFactory.createPending({
|
||||
organizationId: organization.id,
|
||||
submitterUserId: submitter.id,
|
||||
creatorId: artist.id,
|
||||
});
|
||||
|
||||
let accepted = null;
|
||||
for (const userId of reviewerIds.slice(0, TROPHY_APPROVALS_REQUIRED)) {
|
||||
accepted = await TrophyRepository.addApproval({
|
||||
pendingTrophyId: pending.id,
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
const trophy = await db
|
||||
.selectFrom("Trophy")
|
||||
.select(["creatorId", "managerId"])
|
||||
.where("id", "=", accepted!.id)
|
||||
.executeTakeFirstOrThrow();
|
||||
expect(trophy).toEqual({ creatorId: artist.id, managerId: submitter.id });
|
||||
});
|
||||
|
||||
test("ignores repeated approvals from the same user", async () => {
|
||||
await TrophyRepository.addApproval({
|
||||
pendingTrophyId,
|
||||
|
||||
@@ -494,7 +494,7 @@ export async function existsByName(args: {
|
||||
export async function findManagedBy(userId: number) {
|
||||
return db
|
||||
.selectFrom("Trophy")
|
||||
.select(["id", "name", "model", "organizationId", "managerId"])
|
||||
.select(["id", "name", "model", "organizationId", "managerId", "creatorId"])
|
||||
.where("managerId", "=", userId)
|
||||
.execute();
|
||||
}
|
||||
@@ -502,7 +502,7 @@ export async function findManagedBy(userId: number) {
|
||||
export async function findAllForEditing() {
|
||||
return db
|
||||
.selectFrom("Trophy")
|
||||
.select(["id", "name", "model", "organizationId", "managerId"])
|
||||
.select(["id", "name", "model", "organizationId", "managerId", "creatorId"])
|
||||
.where("code", "is", null)
|
||||
.execute();
|
||||
}
|
||||
@@ -616,6 +616,7 @@ export async function createPending(args: {
|
||||
submitterUserId: number;
|
||||
targetTrophyId?: number;
|
||||
managerId?: number;
|
||||
creatorId?: number;
|
||||
}) {
|
||||
return db
|
||||
.insertInto("PendingTrophy")
|
||||
@@ -631,6 +632,7 @@ export async function createPending(args: {
|
||||
declinedByUserId: null,
|
||||
targetTrophyId: args.targetTrophyId ?? null,
|
||||
managerId: args.managerId ?? null,
|
||||
creatorId: args.creatorId ?? null,
|
||||
})
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
@@ -660,6 +662,7 @@ const withTarget = (eb: ExpressionBuilder<DB, "PendingTrophy">) => {
|
||||
eb
|
||||
.selectFrom("Trophy")
|
||||
.leftJoin("User", "User.id", "Trophy.managerId")
|
||||
.leftJoin("User as Creator", "Creator.id", "Trophy.creatorId")
|
||||
.leftJoin(
|
||||
"TournamentOrganization",
|
||||
"TournamentOrganization.id",
|
||||
@@ -671,7 +674,9 @@ const withTarget = (eb: ExpressionBuilder<DB, "PendingTrophy">) => {
|
||||
"Trophy.model",
|
||||
"Trophy.organizationId",
|
||||
"Trophy.managerId",
|
||||
"Trophy.creatorId",
|
||||
"User.username as managerUsername",
|
||||
"Creator.username as creatorUsername",
|
||||
"TournamentOrganization.name as organizationName",
|
||||
"TournamentOrganization.slug as organizationSlug",
|
||||
])
|
||||
@@ -688,6 +693,15 @@ const withTargetManager = (eb: ExpressionBuilder<DB, "PendingTrophy">) => {
|
||||
).as("manager");
|
||||
};
|
||||
|
||||
const withPendingCreator = (eb: ExpressionBuilder<DB, "PendingTrophy">) => {
|
||||
return jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom("User")
|
||||
.select(["User.id", "User.username", "User.discordId"])
|
||||
.whereRef("User.id", "=", "PendingTrophy.creatorId"),
|
||||
).as("creator");
|
||||
};
|
||||
|
||||
function pendingBaseQuery() {
|
||||
return db
|
||||
.selectFrom("PendingTrophy")
|
||||
@@ -720,6 +734,7 @@ function pendingBaseQuery() {
|
||||
"PendingTrophy.acceptedAt",
|
||||
"PendingTrophy.targetTrophyId",
|
||||
"PendingTrophy.managerId",
|
||||
"PendingTrophy.creatorId",
|
||||
"Submitter.username as submitterUsername",
|
||||
"Submitter.discordId as submitterDiscordId",
|
||||
"Decliner.username as declinedByUsername",
|
||||
@@ -728,6 +743,7 @@ function pendingBaseQuery() {
|
||||
withApprovals(eb),
|
||||
withTarget(eb),
|
||||
withTargetManager(eb),
|
||||
withPendingCreator(eb),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -842,6 +858,7 @@ export async function addApproval(args: {
|
||||
"submitterUserId",
|
||||
"targetTrophyId",
|
||||
"managerId",
|
||||
"creatorId",
|
||||
])
|
||||
.where("id", "=", args.pendingTrophyId)
|
||||
.where("declinedAt", "is", null)
|
||||
@@ -864,6 +881,9 @@ export async function addApproval(args: {
|
||||
model: pending.model,
|
||||
organizationId: pending.organizationId,
|
||||
managerId: pending.managerId ?? pending.submitterUserId,
|
||||
...(pending.creatorId !== null
|
||||
? { creatorId: pending.creatorId }
|
||||
: {}),
|
||||
})
|
||||
.where("id", "=", pending.targetTrophyId)
|
||||
.execute();
|
||||
@@ -876,7 +896,7 @@ export async function addApproval(args: {
|
||||
name: pending.name,
|
||||
model: pending.model,
|
||||
organizationId: pending.organizationId,
|
||||
creatorId: pending.submitterUserId,
|
||||
creatorId: pending.creatorId ?? pending.submitterUserId,
|
||||
managerId: pending.managerId ?? pending.submitterUserId,
|
||||
})
|
||||
.returning("id")
|
||||
|
||||
@@ -67,6 +67,7 @@ export const action: ActionFunction = async ({ request }) => {
|
||||
submitterUserId: user.id,
|
||||
targetTrophyId: data.targetTrophyId,
|
||||
managerId: data.managerId,
|
||||
creatorId: data.creatorId ?? undefined,
|
||||
});
|
||||
|
||||
await notifyReviewersOfSubmission({
|
||||
@@ -90,6 +91,7 @@ export const action: ActionFunction = async ({ request }) => {
|
||||
description: data.description ?? "",
|
||||
organizationId: data.organizationId,
|
||||
submitterUserId: user.id,
|
||||
creatorId: data.creatorId ?? user.id,
|
||||
});
|
||||
|
||||
await notifyReviewersOfSubmission({
|
||||
|
||||
@@ -64,5 +64,32 @@ describe("trophy submissions before release", () => {
|
||||
await TrophyRepository.pendingBySubmitter(REGULAR_USER_TEST_ID);
|
||||
expect(pending.length).toBe(1);
|
||||
expect(pending[0].name).toBe("Regular Trophy");
|
||||
expect(pending[0].creatorId).toBe(REGULAR_USER_TEST_ID);
|
||||
});
|
||||
|
||||
test("a submission can name someone else as the creator", async () => {
|
||||
const organization = await TournamentOrganizationFactory.create({
|
||||
ownerId: ADMIN_ID,
|
||||
});
|
||||
const artist = await UserFactory.create();
|
||||
|
||||
const result = await submitAction(
|
||||
{
|
||||
_action: "CREATE",
|
||||
name: "Commissioned Trophy",
|
||||
model: decompressFromBase64(TrophyFactory.MODELS[0]) ?? "",
|
||||
organizationId: organization.id,
|
||||
creatorId: artist.id,
|
||||
description: null,
|
||||
},
|
||||
{ user: "regular" },
|
||||
);
|
||||
|
||||
expect(result).toBe(null);
|
||||
|
||||
const pending =
|
||||
await TrophyRepository.pendingBySubmitter(REGULAR_USER_TEST_ID);
|
||||
expect(pending[0].creatorId).toBe(artist.id);
|
||||
expect(pending[0].creator?.id).toBe(artist.id);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -197,10 +197,14 @@ function TrophyTermsGate({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
|
||||
function NewTrophyForm() {
|
||||
const { t } = useTranslation(["trophies"]);
|
||||
const { t } = useTranslation(["trophies", "forms"]);
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
||||
return (
|
||||
<SendouForm schema={createTrophyFormSchema}>
|
||||
<SendouForm
|
||||
schema={createTrophyFormSchema}
|
||||
defaultValues={{ creatorId: data.currentUserId }}
|
||||
>
|
||||
{({ FormField }) => (
|
||||
<>
|
||||
<FormField name="name" />
|
||||
@@ -213,6 +217,16 @@ function NewTrophyForm() {
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField name="creatorId">
|
||||
{({ error, value, onChange }: CustomFieldRenderProps) => (
|
||||
<UserField
|
||||
label={t("forms:labels.trophyCreator")}
|
||||
error={error}
|
||||
value={value as number | null}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField name="model">
|
||||
{({ name, error, value, onChange }: CustomFieldRenderProps) => (
|
||||
<ModelField
|
||||
@@ -224,9 +238,6 @@ function NewTrophyForm() {
|
||||
)}
|
||||
</FormField>
|
||||
<FormField name="description" />
|
||||
<FormMessage type="info">
|
||||
{t("trophies:new.form.creatorNotice")}
|
||||
</FormMessage>
|
||||
</>
|
||||
)}
|
||||
</SendouForm>
|
||||
@@ -273,6 +284,7 @@ function UpdateTrophyForm({
|
||||
}: {
|
||||
trophy: NewTrophyLoaderData["editableTrophies"][number];
|
||||
}) {
|
||||
const { t } = useTranslation(["forms"]);
|
||||
const decompressedModel = decompressTrophyModel(trophy.model) ?? "";
|
||||
|
||||
return (
|
||||
@@ -284,6 +296,7 @@ function UpdateTrophyForm({
|
||||
model: decompressedModel,
|
||||
organizationId: trophy.organizationId,
|
||||
managerId: trophy.managerId,
|
||||
creatorId: trophy.creatorId,
|
||||
description: "",
|
||||
}}
|
||||
>
|
||||
@@ -301,7 +314,18 @@ function UpdateTrophyForm({
|
||||
</FormField>
|
||||
<FormField name="managerId">
|
||||
{({ error, value, onChange }: CustomFieldRenderProps) => (
|
||||
<ManagerField
|
||||
<UserField
|
||||
label={t("forms:labels.trophyManager")}
|
||||
error={error}
|
||||
value={value as number | null}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField name="creatorId">
|
||||
{({ error, value, onChange }: CustomFieldRenderProps) => (
|
||||
<UserField
|
||||
label={t("forms:labels.trophyCreator")}
|
||||
error={error}
|
||||
value={value as number | null}
|
||||
onChange={onChange}
|
||||
@@ -325,20 +349,20 @@ function UpdateTrophyForm({
|
||||
);
|
||||
}
|
||||
|
||||
function ManagerField({
|
||||
function UserField({
|
||||
label,
|
||||
error,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
error?: string;
|
||||
value: number | null;
|
||||
onChange: (value: number | null) => void;
|
||||
}) {
|
||||
const { t } = useTranslation(["forms"]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Label required>{t("forms:labels.trophyManager")}</Label>
|
||||
<Label required>{label}</Label>
|
||||
<UserSearch
|
||||
initialUserId={value ?? undefined}
|
||||
onChange={(user) => onChange(user?.id ?? null)}
|
||||
@@ -718,6 +742,23 @@ function TrophyListRow({
|
||||
) : (
|
||||
pending.submitterUsername
|
||||
)}
|
||||
{pending.creator &&
|
||||
pending.creator.id !==
|
||||
(pending.manager?.id ?? pending.submitterUserId) ? (
|
||||
<>
|
||||
{" • "}
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="trophies:details.createdBy"
|
||||
values={{ name: pending.creator.username }}
|
||||
>
|
||||
Created by
|
||||
<Link to={userPage({ discordId: pending.creator.discordId })}>
|
||||
{pending.creator.username}
|
||||
</Link>
|
||||
</Trans>
|
||||
</>
|
||||
) : null}
|
||||
{pending.organizationName ? (
|
||||
<>
|
||||
{" • "}
|
||||
@@ -963,6 +1004,13 @@ function PendingTrophyDiff({
|
||||
newValue: newManagerName,
|
||||
changed: target.managerId !== newManagerId,
|
||||
},
|
||||
{
|
||||
label: t("forms:labels.trophyCreator"),
|
||||
oldValue: target.creatorUsername ?? "—",
|
||||
newValue: pending.creator?.username ?? target.creatorUsername ?? "—",
|
||||
changed:
|
||||
pending.creatorId !== null && target.creatorId !== pending.creatorId,
|
||||
},
|
||||
{
|
||||
label: t("forms:labels.trophyModel"),
|
||||
oldValue: "-----",
|
||||
|
||||
@@ -56,6 +56,7 @@ export const createTrophyFormSchema = v.object({
|
||||
}),
|
||||
model: trophyModelField(),
|
||||
organizationId: customField({ initialValue: null }, id),
|
||||
creatorId: customField({ initialValue: null }, v.nullish(id)),
|
||||
description: textAreaOptional({
|
||||
label: "labels.trophyInformation",
|
||||
maxLength: TROPHY_DESCRIPTION_MAX_LENGTH,
|
||||
@@ -73,6 +74,7 @@ export const updateTrophyFormSchema = v.object({
|
||||
model: trophyModelField(),
|
||||
organizationId: customField({ initialValue: null }, id),
|
||||
managerId: customField({ initialValue: null }, id),
|
||||
creatorId: customField({ initialValue: null }, v.nullish(id)),
|
||||
description: textAreaOptional({
|
||||
label: "labels.trophyInformation",
|
||||
maxLength: TROPHY_DESCRIPTION_MAX_LENGTH,
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "",
|
||||
"labels.trophyOrganization": "",
|
||||
"labels.trophyManager": "",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "",
|
||||
"labels.profileFavoriteTrophies": "",
|
||||
"labels.profileHiddenTrophies": "",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "",
|
||||
"special.supporter.description": "",
|
||||
"special.xp.description": "",
|
||||
"new.form.creatorNotice": "",
|
||||
"new.form.limitReached": "",
|
||||
"new.form.preview.light": "",
|
||||
"new.form.preview.dark": "",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "",
|
||||
"labels.trophyOrganization": "",
|
||||
"labels.trophyManager": "",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "",
|
||||
"labels.profileFavoriteTrophies": "",
|
||||
"labels.profileHiddenTrophies": "",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "",
|
||||
"special.supporter.description": "",
|
||||
"special.xp.description": "",
|
||||
"new.form.creatorNotice": "",
|
||||
"new.form.limitReached": "",
|
||||
"new.form.preview.light": "",
|
||||
"new.form.preview.dark": "",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "3D model state",
|
||||
"labels.trophyOrganization": "Organization",
|
||||
"labels.trophyManager": "Manager",
|
||||
"labels.trophyCreator": "3D model creator",
|
||||
"labels.trophyInformation": "Additional information",
|
||||
"labels.profileFavoriteTrophies": "Favorite trophies",
|
||||
"labels.profileHiddenTrophies": "Hidden trophies",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "View trophy page",
|
||||
"special.supporter.description": "Awarded for supporting sendou.ink",
|
||||
"special.xp.description": "Awarded for reaching {{value}} X Power",
|
||||
"new.form.creatorNotice": "The user who uploads a trophy is registered as its creator. This can not be changed. If you commissioned a trophy from someone and they want to be listed as its creator, they have to upload it themselves.",
|
||||
"new.form.limitReached": "You have reached the limit of {{limit}} pending trophies. Delete or wait for an existing submission to be reviewed before submitting another.",
|
||||
"new.form.preview.light": "Light mode",
|
||||
"new.form.preview.dark": "Dark mode",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "Estado del modelo 3D",
|
||||
"labels.trophyOrganization": "Organización",
|
||||
"labels.trophyManager": "Mánager",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "Información adicional",
|
||||
"labels.profileFavoriteTrophies": "Trofeos favoritos",
|
||||
"labels.profileHiddenTrophies": "Trofeos ocultos",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "Ver página del trofeo",
|
||||
"special.supporter.description": "Otorgado por apoyar a sendou.ink",
|
||||
"special.xp.description": "Otorgado por alcanzar {{value}} de Energía X",
|
||||
"new.form.creatorNotice": "El usuario que sube un trofeo queda registrado como su creador. Esto no se puede cambiar. Si le encargaste un trofeo a alguien y quiere aparecer como su creador, tiene que subirlo esa persona.",
|
||||
"new.form.limitReached": "Has alcanzado el límite de {{limit}} trofeos pendientes. Borra o espera a que se revise un envío existente antes de enviar otro.",
|
||||
"new.form.preview.light": "Modo claro",
|
||||
"new.form.preview.dark": "Modo oscuro",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "Estado del modelo 3D",
|
||||
"labels.trophyOrganization": "Organización",
|
||||
"labels.trophyManager": "Mánager",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "Información adicional",
|
||||
"labels.profileFavoriteTrophies": "Trofeos favoritos",
|
||||
"labels.profileHiddenTrophies": "Trofeos ocultos",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "Ver página del trofeo",
|
||||
"special.supporter.description": "Otorgado por apoyar a sendou.ink",
|
||||
"special.xp.description": "Otorgado por alcanzar {{value}} de Energía X",
|
||||
"new.form.creatorNotice": "El usuario que sube un trofeo queda registrado como su creador. Esto no se puede cambiar. Si le encargaste un trofeo a alguien y quiere aparecer como su creador, tiene que subirlo esa persona.",
|
||||
"new.form.limitReached": "Has alcanzado el límite de {{limit}} trofeos pendientes. Borra o espera a que se revise un envío existente antes de enviar otro.",
|
||||
"new.form.preview.light": "Modo claro",
|
||||
"new.form.preview.dark": "Modo oscuro",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "",
|
||||
"labels.trophyOrganization": "",
|
||||
"labels.trophyManager": "",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "",
|
||||
"labels.profileFavoriteTrophies": "",
|
||||
"labels.profileHiddenTrophies": "",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "",
|
||||
"special.supporter.description": "",
|
||||
"special.xp.description": "",
|
||||
"new.form.creatorNotice": "",
|
||||
"new.form.limitReached": "",
|
||||
"new.form.preview.light": "",
|
||||
"new.form.preview.dark": "",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "",
|
||||
"labels.trophyOrganization": "",
|
||||
"labels.trophyManager": "",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "",
|
||||
"labels.profileFavoriteTrophies": "",
|
||||
"labels.profileHiddenTrophies": "",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "",
|
||||
"special.supporter.description": "",
|
||||
"special.xp.description": "",
|
||||
"new.form.creatorNotice": "",
|
||||
"new.form.limitReached": "",
|
||||
"new.form.preview.light": "",
|
||||
"new.form.preview.dark": "",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "",
|
||||
"labels.trophyOrganization": "",
|
||||
"labels.trophyManager": "",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "",
|
||||
"labels.profileFavoriteTrophies": "",
|
||||
"labels.profileHiddenTrophies": "",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "",
|
||||
"special.supporter.description": "",
|
||||
"special.xp.description": "",
|
||||
"new.form.creatorNotice": "",
|
||||
"new.form.limitReached": "",
|
||||
"new.form.preview.light": "",
|
||||
"new.form.preview.dark": "",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "",
|
||||
"labels.trophyOrganization": "",
|
||||
"labels.trophyManager": "",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "",
|
||||
"labels.profileFavoriteTrophies": "",
|
||||
"labels.profileHiddenTrophies": "",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "",
|
||||
"special.supporter.description": "",
|
||||
"special.xp.description": "",
|
||||
"new.form.creatorNotice": "",
|
||||
"new.form.limitReached": "",
|
||||
"new.form.preview.light": "",
|
||||
"new.form.preview.dark": "",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "",
|
||||
"labels.trophyOrganization": "",
|
||||
"labels.trophyManager": "",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "",
|
||||
"labels.profileFavoriteTrophies": "",
|
||||
"labels.profileHiddenTrophies": "",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "",
|
||||
"special.supporter.description": "",
|
||||
"special.xp.description": "",
|
||||
"new.form.creatorNotice": "",
|
||||
"new.form.limitReached": "",
|
||||
"new.form.preview.light": "",
|
||||
"new.form.preview.dark": "",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "",
|
||||
"labels.trophyOrganization": "",
|
||||
"labels.trophyManager": "",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "",
|
||||
"labels.profileFavoriteTrophies": "",
|
||||
"labels.profileHiddenTrophies": "",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "",
|
||||
"special.supporter.description": "",
|
||||
"special.xp.description": "",
|
||||
"new.form.creatorNotice": "",
|
||||
"new.form.limitReached": "",
|
||||
"new.form.preview.light": "",
|
||||
"new.form.preview.dark": "",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "",
|
||||
"labels.trophyOrganization": "",
|
||||
"labels.trophyManager": "",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "",
|
||||
"labels.profileFavoriteTrophies": "",
|
||||
"labels.profileHiddenTrophies": "",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "",
|
||||
"special.supporter.description": "",
|
||||
"special.xp.description": "",
|
||||
"new.form.creatorNotice": "",
|
||||
"new.form.limitReached": "",
|
||||
"new.form.preview.light": "",
|
||||
"new.form.preview.dark": "",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "",
|
||||
"labels.trophyOrganization": "",
|
||||
"labels.trophyManager": "",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "",
|
||||
"labels.profileFavoriteTrophies": "",
|
||||
"labels.profileHiddenTrophies": "",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "",
|
||||
"special.supporter.description": "",
|
||||
"special.xp.description": "",
|
||||
"new.form.creatorNotice": "",
|
||||
"new.form.limitReached": "",
|
||||
"new.form.preview.light": "",
|
||||
"new.form.preview.dark": "",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "",
|
||||
"labels.trophyOrganization": "",
|
||||
"labels.trophyManager": "",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "",
|
||||
"labels.profileFavoriteTrophies": "",
|
||||
"labels.profileHiddenTrophies": "",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "",
|
||||
"special.supporter.description": "",
|
||||
"special.xp.description": "",
|
||||
"new.form.creatorNotice": "",
|
||||
"new.form.limitReached": "",
|
||||
"new.form.preview.light": "",
|
||||
"new.form.preview.dark": "",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "",
|
||||
"labels.trophyOrganization": "",
|
||||
"labels.trophyManager": "",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "",
|
||||
"labels.profileFavoriteTrophies": "",
|
||||
"labels.profileHiddenTrophies": "",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "",
|
||||
"special.supporter.description": "",
|
||||
"special.xp.description": "",
|
||||
"new.form.creatorNotice": "",
|
||||
"new.form.limitReached": "",
|
||||
"new.form.preview.light": "",
|
||||
"new.form.preview.dark": "",
|
||||
|
||||
@@ -459,6 +459,7 @@
|
||||
"labels.trophyModel": "",
|
||||
"labels.trophyOrganization": "",
|
||||
"labels.trophyManager": "",
|
||||
"labels.trophyCreator": "",
|
||||
"labels.trophyInformation": "",
|
||||
"labels.profileFavoriteTrophies": "",
|
||||
"labels.profileHiddenTrophies": "",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"display.viewTrophyPage": "",
|
||||
"special.supporter.description": "",
|
||||
"special.xp.description": "",
|
||||
"new.form.creatorNotice": "",
|
||||
"new.form.limitReached": "",
|
||||
"new.form.preview.light": "",
|
||||
"new.form.preview.dark": "",
|
||||
|
||||
16
migrations/20260912195930-pending-trophy-creator-id.ts
Normal file
16
migrations/20260912195930-pending-trophy-creator-id.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { Kysely } from "kysely";
|
||||
|
||||
/**
|
||||
* A submission names the model's creator. Before, the submitter always became
|
||||
* the creator, so a commissioned trophy had to be uploaded by its artist.
|
||||
*/
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await db.transaction().execute(async (trx) => {
|
||||
await trx.schema
|
||||
.alterTable("PendingTrophy")
|
||||
.addColumn("creatorId", "integer", (col) =>
|
||||
col.references("User.id").onDelete("set null"),
|
||||
)
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user