diff --git a/app/db/tables.ts b/app/db/tables.ts index ab96f2c28..edd84803b 100644 --- a/app/db/tables.ts +++ b/app/db/tables.ts @@ -208,6 +208,7 @@ export interface PendingTrophy { acceptedAt: number | null; targetTrophyId: number | null; managerId: number | null; + creatorId: number | null; } export interface PendingTrophyApproval { diff --git a/app/features/trophies/TrophyRepository.server.test.ts b/app/features/trophies/TrophyRepository.server.test.ts index 5dedbdf20..87d0dde77 100644 --- a/app/features/trophies/TrophyRepository.server.test.ts +++ b/app/features/trophies/TrophyRepository.server.test.ts @@ -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, diff --git a/app/features/trophies/TrophyRepository.server.ts b/app/features/trophies/TrophyRepository.server.ts index 3eb99ad20..1f98a9045 100644 --- a/app/features/trophies/TrophyRepository.server.ts +++ b/app/features/trophies/TrophyRepository.server.ts @@ -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) => { 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) => { "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) => { ).as("manager"); }; +const withPendingCreator = (eb: ExpressionBuilder) => { + 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") diff --git a/app/features/trophies/actions/trophies.new.server.ts b/app/features/trophies/actions/trophies.new.server.ts index 372927203..3970b7cb2 100644 --- a/app/features/trophies/actions/trophies.new.server.ts +++ b/app/features/trophies/actions/trophies.new.server.ts @@ -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({ diff --git a/app/features/trophies/routes/trophies.new.test.ts b/app/features/trophies/routes/trophies.new.test.ts index f544bcd51..ed37635a7 100644 --- a/app/features/trophies/routes/trophies.new.test.ts +++ b/app/features/trophies/routes/trophies.new.test.ts @@ -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); }); }); diff --git a/app/features/trophies/routes/trophies.new.tsx b/app/features/trophies/routes/trophies.new.tsx index 2ddad9d28..10ffbe091 100644 --- a/app/features/trophies/routes/trophies.new.tsx +++ b/app/features/trophies/routes/trophies.new.tsx @@ -197,10 +197,14 @@ function TrophyTermsGate({ children }: { children: React.ReactNode }) { } function NewTrophyForm() { - const { t } = useTranslation(["trophies"]); + const { t } = useTranslation(["trophies", "forms"]); + const data = useLoaderData(); return ( - + {({ FormField }) => ( <> @@ -213,6 +217,16 @@ function NewTrophyForm() { /> )} + + {({ error, value, onChange }: CustomFieldRenderProps) => ( + + )} + {({ name, error, value, onChange }: CustomFieldRenderProps) => ( - - {t("trophies:new.form.creatorNotice")} - )} @@ -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({ {({ error, value, onChange }: CustomFieldRenderProps) => ( - + )} + + + {({ error, value, onChange }: CustomFieldRenderProps) => ( + void; }) { - const { t } = useTranslation(["forms"]); - return (
- + onChange(user?.id ?? null)} @@ -718,6 +742,23 @@ function TrophyListRow({ ) : ( pending.submitterUsername )} + {pending.creator && + pending.creator.id !== + (pending.manager?.id ?? pending.submitterUserId) ? ( + <> + {" • "} + + Created by + + {pending.creator.username} + + + + ) : 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: "-----", diff --git a/app/features/trophies/trophies-schemas.ts b/app/features/trophies/trophies-schemas.ts index a0a296ebd..a23c19af3 100644 --- a/app/features/trophies/trophies-schemas.ts +++ b/app/features/trophies/trophies-schemas.ts @@ -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, diff --git a/locales/da/forms.json b/locales/da/forms.json index 4df643ff3..7083ed9d3 100644 --- a/locales/da/forms.json +++ b/locales/da/forms.json @@ -459,6 +459,7 @@ "labels.trophyModel": "", "labels.trophyOrganization": "", "labels.trophyManager": "", + "labels.trophyCreator": "", "labels.trophyInformation": "", "labels.profileFavoriteTrophies": "", "labels.profileHiddenTrophies": "", diff --git a/locales/da/trophies.json b/locales/da/trophies.json index d8e86b2c5..eb513dd24 100644 --- a/locales/da/trophies.json +++ b/locales/da/trophies.json @@ -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": "", diff --git a/locales/de/forms.json b/locales/de/forms.json index c9e233edd..a70c43cb9 100644 --- a/locales/de/forms.json +++ b/locales/de/forms.json @@ -459,6 +459,7 @@ "labels.trophyModel": "", "labels.trophyOrganization": "", "labels.trophyManager": "", + "labels.trophyCreator": "", "labels.trophyInformation": "", "labels.profileFavoriteTrophies": "", "labels.profileHiddenTrophies": "", diff --git a/locales/de/trophies.json b/locales/de/trophies.json index d8e86b2c5..eb513dd24 100644 --- a/locales/de/trophies.json +++ b/locales/de/trophies.json @@ -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": "", diff --git a/locales/en/forms.json b/locales/en/forms.json index 8f4ec317b..503f48b09 100644 --- a/locales/en/forms.json +++ b/locales/en/forms.json @@ -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", diff --git a/locales/en/trophies.json b/locales/en/trophies.json index 6d79e57dd..5fee52c10 100644 --- a/locales/en/trophies.json +++ b/locales/en/trophies.json @@ -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", diff --git a/locales/es-ES/forms.json b/locales/es-ES/forms.json index e6844c341..a97253b87 100644 --- a/locales/es-ES/forms.json +++ b/locales/es-ES/forms.json @@ -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", diff --git a/locales/es-ES/trophies.json b/locales/es-ES/trophies.json index beaa6557a..974c95279 100644 --- a/locales/es-ES/trophies.json +++ b/locales/es-ES/trophies.json @@ -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", diff --git a/locales/es-US/forms.json b/locales/es-US/forms.json index a1c5651fa..eddfd5d62 100644 --- a/locales/es-US/forms.json +++ b/locales/es-US/forms.json @@ -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", diff --git a/locales/es-US/trophies.json b/locales/es-US/trophies.json index a34b2d6f9..e114eb62e 100644 --- a/locales/es-US/trophies.json +++ b/locales/es-US/trophies.json @@ -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", diff --git a/locales/fr-CA/forms.json b/locales/fr-CA/forms.json index 0bb62c774..f40342a98 100644 --- a/locales/fr-CA/forms.json +++ b/locales/fr-CA/forms.json @@ -459,6 +459,7 @@ "labels.trophyModel": "", "labels.trophyOrganization": "", "labels.trophyManager": "", + "labels.trophyCreator": "", "labels.trophyInformation": "", "labels.profileFavoriteTrophies": "", "labels.profileHiddenTrophies": "", diff --git a/locales/fr-CA/trophies.json b/locales/fr-CA/trophies.json index d8e86b2c5..eb513dd24 100644 --- a/locales/fr-CA/trophies.json +++ b/locales/fr-CA/trophies.json @@ -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": "", diff --git a/locales/fr-EU/forms.json b/locales/fr-EU/forms.json index c31262794..ec04a0c63 100644 --- a/locales/fr-EU/forms.json +++ b/locales/fr-EU/forms.json @@ -459,6 +459,7 @@ "labels.trophyModel": "", "labels.trophyOrganization": "", "labels.trophyManager": "", + "labels.trophyCreator": "", "labels.trophyInformation": "", "labels.profileFavoriteTrophies": "", "labels.profileHiddenTrophies": "", diff --git a/locales/fr-EU/trophies.json b/locales/fr-EU/trophies.json index d8e86b2c5..eb513dd24 100644 --- a/locales/fr-EU/trophies.json +++ b/locales/fr-EU/trophies.json @@ -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": "", diff --git a/locales/he/forms.json b/locales/he/forms.json index 251c243c0..1d2cb4782 100644 --- a/locales/he/forms.json +++ b/locales/he/forms.json @@ -459,6 +459,7 @@ "labels.trophyModel": "", "labels.trophyOrganization": "", "labels.trophyManager": "", + "labels.trophyCreator": "", "labels.trophyInformation": "", "labels.profileFavoriteTrophies": "", "labels.profileHiddenTrophies": "", diff --git a/locales/he/trophies.json b/locales/he/trophies.json index d8e86b2c5..eb513dd24 100644 --- a/locales/he/trophies.json +++ b/locales/he/trophies.json @@ -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": "", diff --git a/locales/it/forms.json b/locales/it/forms.json index 4cc94fe7e..0e64fc414 100644 --- a/locales/it/forms.json +++ b/locales/it/forms.json @@ -459,6 +459,7 @@ "labels.trophyModel": "", "labels.trophyOrganization": "", "labels.trophyManager": "", + "labels.trophyCreator": "", "labels.trophyInformation": "", "labels.profileFavoriteTrophies": "", "labels.profileHiddenTrophies": "", diff --git a/locales/it/trophies.json b/locales/it/trophies.json index d8e86b2c5..eb513dd24 100644 --- a/locales/it/trophies.json +++ b/locales/it/trophies.json @@ -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": "", diff --git a/locales/ja/forms.json b/locales/ja/forms.json index 130a2dfb2..4e5271bdc 100644 --- a/locales/ja/forms.json +++ b/locales/ja/forms.json @@ -459,6 +459,7 @@ "labels.trophyModel": "", "labels.trophyOrganization": "", "labels.trophyManager": "", + "labels.trophyCreator": "", "labels.trophyInformation": "", "labels.profileFavoriteTrophies": "", "labels.profileHiddenTrophies": "", diff --git a/locales/ja/trophies.json b/locales/ja/trophies.json index d8e86b2c5..eb513dd24 100644 --- a/locales/ja/trophies.json +++ b/locales/ja/trophies.json @@ -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": "", diff --git a/locales/ko/forms.json b/locales/ko/forms.json index 9713f5bfb..4d8f8e820 100644 --- a/locales/ko/forms.json +++ b/locales/ko/forms.json @@ -459,6 +459,7 @@ "labels.trophyModel": "", "labels.trophyOrganization": "", "labels.trophyManager": "", + "labels.trophyCreator": "", "labels.trophyInformation": "", "labels.profileFavoriteTrophies": "", "labels.profileHiddenTrophies": "", diff --git a/locales/ko/trophies.json b/locales/ko/trophies.json index d8e86b2c5..eb513dd24 100644 --- a/locales/ko/trophies.json +++ b/locales/ko/trophies.json @@ -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": "", diff --git a/locales/nl/forms.json b/locales/nl/forms.json index 5f80798cf..df6617603 100644 --- a/locales/nl/forms.json +++ b/locales/nl/forms.json @@ -459,6 +459,7 @@ "labels.trophyModel": "", "labels.trophyOrganization": "", "labels.trophyManager": "", + "labels.trophyCreator": "", "labels.trophyInformation": "", "labels.profileFavoriteTrophies": "", "labels.profileHiddenTrophies": "", diff --git a/locales/nl/trophies.json b/locales/nl/trophies.json index d8e86b2c5..eb513dd24 100644 --- a/locales/nl/trophies.json +++ b/locales/nl/trophies.json @@ -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": "", diff --git a/locales/pl/forms.json b/locales/pl/forms.json index 14da8d479..1ae3231aa 100644 --- a/locales/pl/forms.json +++ b/locales/pl/forms.json @@ -459,6 +459,7 @@ "labels.trophyModel": "", "labels.trophyOrganization": "", "labels.trophyManager": "", + "labels.trophyCreator": "", "labels.trophyInformation": "", "labels.profileFavoriteTrophies": "", "labels.profileHiddenTrophies": "", diff --git a/locales/pl/trophies.json b/locales/pl/trophies.json index d8e86b2c5..eb513dd24 100644 --- a/locales/pl/trophies.json +++ b/locales/pl/trophies.json @@ -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": "", diff --git a/locales/pt-BR/forms.json b/locales/pt-BR/forms.json index 3af6da436..f9db4e81e 100644 --- a/locales/pt-BR/forms.json +++ b/locales/pt-BR/forms.json @@ -459,6 +459,7 @@ "labels.trophyModel": "", "labels.trophyOrganization": "", "labels.trophyManager": "", + "labels.trophyCreator": "", "labels.trophyInformation": "", "labels.profileFavoriteTrophies": "", "labels.profileHiddenTrophies": "", diff --git a/locales/pt-BR/trophies.json b/locales/pt-BR/trophies.json index d8e86b2c5..eb513dd24 100644 --- a/locales/pt-BR/trophies.json +++ b/locales/pt-BR/trophies.json @@ -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": "", diff --git a/locales/ru/forms.json b/locales/ru/forms.json index 3c0bc1a5a..4e4ef18cc 100644 --- a/locales/ru/forms.json +++ b/locales/ru/forms.json @@ -459,6 +459,7 @@ "labels.trophyModel": "", "labels.trophyOrganization": "", "labels.trophyManager": "", + "labels.trophyCreator": "", "labels.trophyInformation": "", "labels.profileFavoriteTrophies": "", "labels.profileHiddenTrophies": "", diff --git a/locales/ru/trophies.json b/locales/ru/trophies.json index d8e86b2c5..eb513dd24 100644 --- a/locales/ru/trophies.json +++ b/locales/ru/trophies.json @@ -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": "", diff --git a/locales/zh/forms.json b/locales/zh/forms.json index 73625d528..378268ffb 100644 --- a/locales/zh/forms.json +++ b/locales/zh/forms.json @@ -459,6 +459,7 @@ "labels.trophyModel": "", "labels.trophyOrganization": "", "labels.trophyManager": "", + "labels.trophyCreator": "", "labels.trophyInformation": "", "labels.profileFavoriteTrophies": "", "labels.profileHiddenTrophies": "", diff --git a/locales/zh/trophies.json b/locales/zh/trophies.json index d8e86b2c5..eb513dd24 100644 --- a/locales/zh/trophies.json +++ b/locales/zh/trophies.json @@ -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": "", diff --git a/migrations/20260912195930-pending-trophy-creator-id.ts b/migrations/20260912195930-pending-trophy-creator-id.ts new file mode 100644 index 000000000..79ef24537 --- /dev/null +++ b/migrations/20260912195930-pending-trophy-creator-id.ts @@ -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): Promise { + await db.transaction().execute(async (trx) => { + await trx.schema + .alterTable("PendingTrophy") + .addColumn("creatorId", "integer", (col) => + col.references("User.id").onDelete("set null"), + ) + .execute(); + }); +}