mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-25 07:32:19 -05:00
User submitted images to Kysely (#2610)
This commit is contained in:
parent
a2b3e36c49
commit
7b28ce3877
|
|
@ -7,7 +7,7 @@ let imageCounter = 0;
|
|||
const createArt = async ({ authorId }: { authorId: number }) => {
|
||||
imageCounter++;
|
||||
|
||||
return ArtRepository.insert({
|
||||
const art = await ArtRepository.insert({
|
||||
authorId,
|
||||
url: `https://example.com/image-${authorId}-${imageCounter}.png`,
|
||||
validatedAt: Date.now(),
|
||||
|
|
@ -15,6 +15,8 @@ const createArt = async ({ authorId }: { authorId: number }) => {
|
|||
linkedUsers: [],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
return art.id;
|
||||
};
|
||||
|
||||
describe("findShowcaseArts", () => {
|
||||
|
|
@ -126,7 +128,7 @@ describe("unlinkUserFromArt", () => {
|
|||
});
|
||||
|
||||
test("removes user link from art", async () => {
|
||||
const artId = await ArtRepository.insert({
|
||||
const art = await ArtRepository.insert({
|
||||
authorId: 1,
|
||||
url: "https://example.com/image-1.png",
|
||||
validatedAt: Date.now(),
|
||||
|
|
@ -135,7 +137,7 @@ describe("unlinkUserFromArt", () => {
|
|||
tags: [],
|
||||
});
|
||||
|
||||
await ArtRepository.unlinkUserFromArt({ userId: 2, artId });
|
||||
await ArtRepository.unlinkUserFromArt({ userId: 2, artId: art.id });
|
||||
|
||||
const result = await ArtRepository.findArtsByUserId(2, {
|
||||
includeAuthored: false,
|
||||
|
|
@ -155,7 +157,7 @@ describe("findShowcaseArtsByTag", () => {
|
|||
});
|
||||
|
||||
test("returns arts filtered by tag", async () => {
|
||||
const art1Id = await ArtRepository.insert({
|
||||
const art1 = await ArtRepository.insert({
|
||||
authorId: 1,
|
||||
url: "https://example.com/image-1.png",
|
||||
validatedAt: Date.now(),
|
||||
|
|
@ -181,7 +183,7 @@ describe("findShowcaseArtsByTag", () => {
|
|||
);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].id).toBe(art1Id);
|
||||
expect(result[0].id).toBe(art1.id);
|
||||
});
|
||||
|
||||
test("shows only one art per artist", async () => {
|
||||
|
|
@ -254,7 +256,7 @@ describe("findArtsByUserId", () => {
|
|||
});
|
||||
|
||||
test("returns tagged art", async () => {
|
||||
const artId = await ArtRepository.insert({
|
||||
const art = await ArtRepository.insert({
|
||||
authorId: 1,
|
||||
url: "https://example.com/image-1.png",
|
||||
validatedAt: Date.now(),
|
||||
|
|
@ -266,7 +268,7 @@ describe("findArtsByUserId", () => {
|
|||
const result = await ArtRepository.findArtsByUserId(2);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].id).toBe(artId);
|
||||
expect(result[0].id).toBe(art.id);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -312,7 +314,7 @@ describe("insert", () => {
|
|||
});
|
||||
|
||||
test("inserts art with all metadata", async () => {
|
||||
const artId = await ArtRepository.insert({
|
||||
const art = await ArtRepository.insert({
|
||||
authorId: 1,
|
||||
url: "https://example.com/image-1.png",
|
||||
validatedAt: Date.now(),
|
||||
|
|
@ -324,7 +326,7 @@ describe("insert", () => {
|
|||
const result = await ArtRepository.findArtsByUserId(1);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].id).toBe(artId);
|
||||
expect(result[0].id).toBe(art.id);
|
||||
expect(result[0].description).toBe("Test description");
|
||||
expect(result[0].tags).toHaveLength(1);
|
||||
expect(result[0].linkedUsers).toHaveLength(1);
|
||||
|
|
@ -350,7 +352,7 @@ describe("update", () => {
|
|||
});
|
||||
|
||||
test("updates art metadata", async () => {
|
||||
const artId = await ArtRepository.insert({
|
||||
const art = await ArtRepository.insert({
|
||||
authorId: 1,
|
||||
url: "https://example.com/image-1.png",
|
||||
validatedAt: Date.now(),
|
||||
|
|
@ -359,7 +361,7 @@ describe("update", () => {
|
|||
tags: [{ name: "Character" }],
|
||||
});
|
||||
|
||||
await ArtRepository.update(artId, {
|
||||
await ArtRepository.update(art.id, {
|
||||
description: "Updated",
|
||||
linkedUsers: [3],
|
||||
tags: [{ name: "Weapon" }],
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ type InsertArtArgs = Pick<Tables["Art"], "authorId" | "description"> &
|
|||
tags: TagsToAdd;
|
||||
};
|
||||
|
||||
export async function insert(args: InsertArtArgs): Promise<number> {
|
||||
export async function insert(args: InsertArtArgs) {
|
||||
return await db.transaction().execute(async (trx) => {
|
||||
const img = await trx
|
||||
.insertInto("UnvalidatedUserSubmittedImage")
|
||||
|
|
@ -336,7 +336,7 @@ export async function insert(args: InsertArtArgs): Promise<number> {
|
|||
artId: art.id,
|
||||
});
|
||||
|
||||
return art.id;
|
||||
return art;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
import { nanoid } from "nanoid";
|
||||
import * as ArtRepository from "~/features/art/ArtRepository.server";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import { s3UploadHandler } from "~/features/img-upload";
|
||||
import { s3UploadHandler } from "~/features/img-upload/s3.server";
|
||||
import { notify } from "~/features/notifications/core/notify.server";
|
||||
import { requireRole } from "~/modules/permissions/guards.server";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
|
|
@ -86,7 +86,7 @@ export const action: ActionFunction = async ({ request }) => {
|
|||
schema: newArtSchema,
|
||||
});
|
||||
|
||||
const addedArtId = await ArtRepository.insert({
|
||||
const addedArt = await ArtRepository.insert({
|
||||
authorId: user.id,
|
||||
description: data.description,
|
||||
url: fileName,
|
||||
|
|
@ -102,7 +102,7 @@ export const action: ActionFunction = async ({ request }) => {
|
|||
meta: {
|
||||
adderUsername: user.username,
|
||||
adderDiscordId: user.discordId,
|
||||
artId: addedArtId,
|
||||
artId: addedArt.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
654
app/features/img-upload/ImageRepository.server.test.ts
Normal file
654
app/features/img-upload/ImageRepository.server.test.ts
Normal file
|
|
@ -0,0 +1,654 @@
|
|||
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||
import { databaseTimestampNow } from "~/utils/dates";
|
||||
import { dbInsertUsers, dbReset } from "~/utils/Test";
|
||||
import * as ArtRepository from "../art/ArtRepository.server";
|
||||
import * as CalendarRepository from "../calendar/CalendarRepository.server";
|
||||
import * as TeamRepository from "../team/TeamRepository.server";
|
||||
import * as TournamentOrganizationRepository from "../tournament-organization/TournamentOrganizationRepository.server";
|
||||
import * as ImageRepository from "./ImageRepository.server";
|
||||
|
||||
let imageCounter = 0;
|
||||
let teamCounter = 0;
|
||||
let orgCounter = 0;
|
||||
|
||||
const createImage = async ({
|
||||
submitterUserId,
|
||||
validatedAt = null,
|
||||
}: {
|
||||
submitterUserId: number;
|
||||
validatedAt?: number | null;
|
||||
}) => {
|
||||
imageCounter++;
|
||||
const url = `https://example.com/image-${submitterUserId}-${imageCounter}.png`;
|
||||
|
||||
return ImageRepository.addNewImage({
|
||||
submitterUserId,
|
||||
url,
|
||||
validatedAt,
|
||||
type: "team-pfp",
|
||||
});
|
||||
};
|
||||
|
||||
const createTeam = async (ownerUserId: number) => {
|
||||
teamCounter++;
|
||||
const customUrl = `team-${teamCounter}`;
|
||||
await TeamRepository.create({
|
||||
name: `Team ${teamCounter}`,
|
||||
customUrl,
|
||||
ownerUserId,
|
||||
isMainTeam: true,
|
||||
});
|
||||
const team = await TeamRepository.findByCustomUrl(customUrl);
|
||||
if (!team) throw new Error("Team not found after creation");
|
||||
return team;
|
||||
};
|
||||
|
||||
const createOrganization = async (ownerId: number) => {
|
||||
orgCounter++;
|
||||
return TournamentOrganizationRepository.create({
|
||||
name: `Org ${orgCounter}`,
|
||||
ownerId,
|
||||
});
|
||||
};
|
||||
|
||||
const createCalendarEvent = async (authorId: number, avatarImgId?: number) => {
|
||||
return CalendarRepository.create({
|
||||
isFullTournament: false,
|
||||
authorId,
|
||||
badges: [],
|
||||
bracketUrl: `https://example.com/bracket-${Date.now()}`,
|
||||
description: null,
|
||||
discordInviteCode: null,
|
||||
name: `Event ${Date.now()}`,
|
||||
organizationId: null,
|
||||
startTimes: [databaseTimestampNow()],
|
||||
tags: null,
|
||||
mapPickingStyle: "AUTO_SZ",
|
||||
bracketProgression: null,
|
||||
deadlines: "DEFAULT",
|
||||
rules: null,
|
||||
avatarImgId,
|
||||
});
|
||||
};
|
||||
|
||||
describe("findById", () => {
|
||||
beforeEach(async () => {
|
||||
imageCounter = 0;
|
||||
await dbInsertUsers(3);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("finds image by id", async () => {
|
||||
const img = await createImage({ submitterUserId: 1 });
|
||||
|
||||
const result = await ImageRepository.findById(img.id);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.tournamentId).toBeNull();
|
||||
});
|
||||
|
||||
test("finds image with calendar event data", async () => {
|
||||
const img = await createImage({ submitterUserId: 1 });
|
||||
await createCalendarEvent(1, img.id);
|
||||
|
||||
const result = await ImageRepository.findById(img.id);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.tournamentId).toBeDefined();
|
||||
});
|
||||
|
||||
test("returns undefined for non-existent image", async () => {
|
||||
const result = await ImageRepository.findById(999);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteImageById", () => {
|
||||
beforeEach(async () => {
|
||||
imageCounter = 0;
|
||||
await dbInsertUsers(2);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("deletes image by id", async () => {
|
||||
const img = await createImage({ submitterUserId: 1 });
|
||||
|
||||
await ImageRepository.deleteImageById(img.id);
|
||||
|
||||
const result = await ImageRepository.findById(img.id);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("deletes associated art when deleting image", async () => {
|
||||
imageCounter++;
|
||||
const art = await ArtRepository.insert({
|
||||
authorId: 1,
|
||||
url: `https://example.com/art-${imageCounter}.png`,
|
||||
validatedAt: Date.now(),
|
||||
description: null,
|
||||
linkedUsers: [],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
const artsBefore = await ArtRepository.findArtsByUserId(1);
|
||||
expect(artsBefore).toHaveLength(1);
|
||||
expect(artsBefore[0].id).toBe(art.id);
|
||||
|
||||
const imgId = art.imgId;
|
||||
|
||||
await ImageRepository.deleteImageById(imgId);
|
||||
|
||||
const result = await ImageRepository.findById(imgId);
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
const artsAfter = await ArtRepository.findArtsByUserId(1);
|
||||
expect(artsAfter).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("countUnvalidatedArt", () => {
|
||||
beforeEach(async () => {
|
||||
imageCounter = 0;
|
||||
await dbInsertUsers(3);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("counts unvalidated art by author", async () => {
|
||||
imageCounter++;
|
||||
await ArtRepository.insert({
|
||||
authorId: 1,
|
||||
url: `https://example.com/art-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
description: null,
|
||||
linkedUsers: [],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
imageCounter++;
|
||||
await ArtRepository.insert({
|
||||
authorId: 1,
|
||||
url: `https://example.com/art-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
description: null,
|
||||
linkedUsers: [],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
const count = await ImageRepository.countUnvalidatedArt(1);
|
||||
|
||||
expect(count).toBe(2);
|
||||
});
|
||||
|
||||
test("does not count validated art", async () => {
|
||||
imageCounter++;
|
||||
await ArtRepository.insert({
|
||||
authorId: 1,
|
||||
url: `https://example.com/art-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
description: null,
|
||||
linkedUsers: [],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
imageCounter++;
|
||||
await ArtRepository.insert({
|
||||
authorId: 1,
|
||||
url: `https://example.com/art-${imageCounter}.png`,
|
||||
validatedAt: Date.now(),
|
||||
description: null,
|
||||
linkedUsers: [],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
const count = await ImageRepository.countUnvalidatedArt(1);
|
||||
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
test("returns 0 when author has no unvalidated art", async () => {
|
||||
const count = await ImageRepository.countUnvalidatedArt(1);
|
||||
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("countAllUnvalidated", () => {
|
||||
beforeEach(async () => {
|
||||
imageCounter = 0;
|
||||
teamCounter = 0;
|
||||
await dbInsertUsers(3);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("counts unvalidated images used in teams", async () => {
|
||||
const team = await createTeam(1);
|
||||
imageCounter++;
|
||||
await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url: `https://example.com/team-avatar-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
teamId: team.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
const count = await ImageRepository.countAllUnvalidated();
|
||||
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
test("counts unvalidated images used in art", async () => {
|
||||
imageCounter++;
|
||||
await ArtRepository.insert({
|
||||
authorId: 1,
|
||||
url: `https://example.com/art-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
description: null,
|
||||
linkedUsers: [],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
const count = await ImageRepository.countAllUnvalidated();
|
||||
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
test("counts unvalidated images used in calendar events", async () => {
|
||||
const img = await createImage({ submitterUserId: 1 });
|
||||
await createCalendarEvent(1, img.id);
|
||||
|
||||
const count = await ImageRepository.countAllUnvalidated();
|
||||
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
test("does not count validated images", async () => {
|
||||
const team = await createTeam(1);
|
||||
imageCounter++;
|
||||
await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url: `https://example.com/team-avatar-${imageCounter}.png`,
|
||||
validatedAt: Date.now(),
|
||||
teamId: team.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
const count = await ImageRepository.countAllUnvalidated();
|
||||
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
|
||||
test("counts multiple unvalidated images across different types", async () => {
|
||||
const team = await createTeam(1);
|
||||
imageCounter++;
|
||||
await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url: `https://example.com/team-avatar-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
teamId: team.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
imageCounter++;
|
||||
await ArtRepository.insert({
|
||||
authorId: 1,
|
||||
url: `https://example.com/art-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
description: null,
|
||||
linkedUsers: [],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
const count = await ImageRepository.countAllUnvalidated();
|
||||
|
||||
expect(count).toBe(2);
|
||||
});
|
||||
|
||||
test("returns 0 when no unvalidated images exist", async () => {
|
||||
const count = await ImageRepository.countAllUnvalidated();
|
||||
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("countUnvalidatedBySubmitterUserId", () => {
|
||||
beforeEach(async () => {
|
||||
imageCounter = 0;
|
||||
teamCounter = 0;
|
||||
await dbInsertUsers(3);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("counts unvalidated team images by submitter", async () => {
|
||||
const team = await createTeam(1);
|
||||
imageCounter++;
|
||||
await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url: `https://example.com/team-avatar-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
teamId: team.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
const count = await ImageRepository.countUnvalidatedBySubmitterUserId(1);
|
||||
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
test("does not count validated images", async () => {
|
||||
const team = await createTeam(1);
|
||||
imageCounter++;
|
||||
await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url: `https://example.com/team-avatar-${imageCounter}.png`,
|
||||
validatedAt: Date.now(),
|
||||
teamId: team.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
const count = await ImageRepository.countUnvalidatedBySubmitterUserId(1);
|
||||
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
|
||||
test("does not count images from other submitters", async () => {
|
||||
const team1 = await createTeam(1);
|
||||
const team2 = await createTeam(2);
|
||||
|
||||
imageCounter++;
|
||||
await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url: `https://example.com/team1-avatar-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
teamId: team1.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
imageCounter++;
|
||||
await ImageRepository.addNewImage({
|
||||
submitterUserId: 2,
|
||||
url: `https://example.com/team2-avatar-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
teamId: team2.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
const count = await ImageRepository.countUnvalidatedBySubmitterUserId(1);
|
||||
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
test("returns 0 when user has no unvalidated team images", async () => {
|
||||
const count = await ImageRepository.countUnvalidatedBySubmitterUserId(1);
|
||||
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateImage", () => {
|
||||
beforeEach(async () => {
|
||||
imageCounter = 0;
|
||||
await dbInsertUsers(2);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("marks image as validated", async () => {
|
||||
const img = await createImage({ submitterUserId: 1 });
|
||||
|
||||
await ImageRepository.validateImage(img.id);
|
||||
|
||||
const result = await ImageRepository.findById(img.id);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
test("validated image is not included in unvalidated count", async () => {
|
||||
const team = await createTeam(1);
|
||||
imageCounter++;
|
||||
const img = await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url: `https://example.com/team-avatar-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
teamId: team.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
const countBefore = await ImageRepository.countAllUnvalidated();
|
||||
expect(countBefore).toBe(1);
|
||||
|
||||
await ImageRepository.validateImage(img.id);
|
||||
|
||||
const countAfter = await ImageRepository.countAllUnvalidated();
|
||||
expect(countAfter).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("unvalidatedImages", () => {
|
||||
beforeEach(async () => {
|
||||
imageCounter = 0;
|
||||
teamCounter = 0;
|
||||
await dbInsertUsers(10);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("fetches unvalidated images with submitter info", async () => {
|
||||
const team = await createTeam(1);
|
||||
imageCounter++;
|
||||
const url = `https://example.com/team-avatar-${imageCounter}.png`;
|
||||
await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url,
|
||||
validatedAt: null,
|
||||
teamId: team.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
const result = await ImageRepository.unvalidatedImages();
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].submitterUserId).toBe(1);
|
||||
expect(result[0].username).toBe("user1");
|
||||
expect(result[0].url).toBe(url);
|
||||
});
|
||||
|
||||
test("does not fetch validated images", async () => {
|
||||
const team = await createTeam(1);
|
||||
imageCounter++;
|
||||
await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url: `https://example.com/team-avatar-${imageCounter}.png`,
|
||||
validatedAt: Date.now(),
|
||||
teamId: team.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
const result = await ImageRepository.unvalidatedImages();
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("fetches images from teams, art, and calendar events", async () => {
|
||||
const team = await createTeam(1);
|
||||
imageCounter++;
|
||||
await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url: `https://example.com/team-avatar-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
teamId: team.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
imageCounter++;
|
||||
await ArtRepository.insert({
|
||||
authorId: 2,
|
||||
url: `https://example.com/art-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
description: null,
|
||||
linkedUsers: [],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
const img2 = await createImage({ submitterUserId: 3 });
|
||||
await createCalendarEvent(3, img2.id);
|
||||
|
||||
const result = await ImageRepository.unvalidatedImages();
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
});
|
||||
|
||||
test("respects the max unvalidated images to show at once for approval limit constant", async () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const teamOwnerId = i + 1;
|
||||
const team = await createTeam(teamOwnerId);
|
||||
|
||||
await ImageRepository.addNewImage({
|
||||
submitterUserId: teamOwnerId,
|
||||
url: `https://example.com/team-avatar-${i}.png`,
|
||||
validatedAt: null,
|
||||
teamId: team.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
}
|
||||
|
||||
const result = await ImageRepository.unvalidatedImages();
|
||||
|
||||
expect(result.length).toBe(5);
|
||||
});
|
||||
|
||||
test("returns empty array when no unvalidated images exist", async () => {
|
||||
const result = await ImageRepository.unvalidatedImages();
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("addNewImage", () => {
|
||||
beforeEach(async () => {
|
||||
imageCounter = 0;
|
||||
teamCounter = 0;
|
||||
orgCounter = 0;
|
||||
await dbInsertUsers(3);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("creates image for team avatar", async () => {
|
||||
const team = await createTeam(1);
|
||||
imageCounter++;
|
||||
const url = `https://example.com/team-avatar-${imageCounter}.png`;
|
||||
|
||||
const img = await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url,
|
||||
validatedAt: null,
|
||||
teamId: team.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
expect(img.url).toBe(url);
|
||||
expect(img.submitterUserId).toBe(1);
|
||||
expect(img.validatedAt).toBeNull();
|
||||
|
||||
const result = await ImageRepository.findById(img.id);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
test("creates image for team banner", async () => {
|
||||
const team = await createTeam(1);
|
||||
imageCounter++;
|
||||
const url = `https://example.com/team-banner-${imageCounter}.png`;
|
||||
|
||||
const img = await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url,
|
||||
validatedAt: null,
|
||||
teamId: team.id,
|
||||
type: "team-banner",
|
||||
});
|
||||
|
||||
expect(img.url).toBe(url);
|
||||
expect(img.submitterUserId).toBe(1);
|
||||
expect(img.validatedAt).toBeNull();
|
||||
|
||||
const result = await ImageRepository.findById(img.id);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
test("creates image for organization avatar", async () => {
|
||||
const org = await createOrganization(1);
|
||||
imageCounter++;
|
||||
const url = `https://example.com/org-avatar-${imageCounter}.png`;
|
||||
|
||||
const img = await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url,
|
||||
validatedAt: null,
|
||||
organizationId: org.id,
|
||||
type: "org-pfp",
|
||||
});
|
||||
|
||||
expect(img.url).toBe(url);
|
||||
expect(img.submitterUserId).toBe(1);
|
||||
expect(img.validatedAt).toBeNull();
|
||||
|
||||
const result = await ImageRepository.findById(img.id);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
test("creates validated image when validatedAt is provided", async () => {
|
||||
const team = await createTeam(1);
|
||||
const validatedAt = Date.now();
|
||||
imageCounter++;
|
||||
|
||||
const img = await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url: `https://example.com/team-avatar-${imageCounter}.png`,
|
||||
validatedAt,
|
||||
teamId: team.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
expect(img.validatedAt).toBe(validatedAt);
|
||||
|
||||
const count = await ImageRepository.countAllUnvalidated();
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
|
||||
test("creates unvalidated image when validatedAt is null", async () => {
|
||||
const team = await createTeam(1);
|
||||
imageCounter++;
|
||||
|
||||
await ImageRepository.addNewImage({
|
||||
submitterUserId: 1,
|
||||
url: `https://example.com/team-avatar-${imageCounter}.png`,
|
||||
validatedAt: null,
|
||||
teamId: team.id,
|
||||
type: "team-pfp",
|
||||
});
|
||||
|
||||
const count = await ImageRepository.countAllUnvalidated();
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
import { db } from "~/db/sql";
|
||||
import { databaseTimestampNow } from "~/utils/dates";
|
||||
import { IMAGES_TO_VALIDATE_AT_ONCE } from "./upload-constants";
|
||||
import type { ImageUploadType } from "./upload-types";
|
||||
|
||||
/** Finds an unvalidated image by ID with associated calendar event data */
|
||||
export function findById(id: number) {
|
||||
return db
|
||||
.selectFrom("UnvalidatedUserSubmittedImage")
|
||||
|
|
@ -13,6 +17,7 @@ export function findById(id: number) {
|
|||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
/** Deletes an image and its associated art entry in a transaction */
|
||||
export function deleteImageById(id: number) {
|
||||
return db.transaction().execute(async (trx) => {
|
||||
await trx.deleteFrom("Art").where("Art.imgId", "=", id).execute();
|
||||
|
|
@ -22,3 +27,149 @@ export function deleteImageById(id: number) {
|
|||
.execute();
|
||||
});
|
||||
}
|
||||
|
||||
/** Counts unvalidated art images for a specific author */
|
||||
export async function countUnvalidatedArt(authorId: number) {
|
||||
const result = await db
|
||||
.selectFrom("UnvalidatedUserSubmittedImage")
|
||||
.innerJoin("Art", "Art.imgId", "UnvalidatedUserSubmittedImage.id")
|
||||
.select(({ fn }) => fn.countAll<number>().as("count"))
|
||||
.where("UnvalidatedUserSubmittedImage.validatedAt", "is", null)
|
||||
.where("Art.authorId", "=", authorId)
|
||||
.executeTakeFirstOrThrow();
|
||||
return result.count;
|
||||
}
|
||||
|
||||
const unvalidatedImagesBaseQuery = db
|
||||
.selectFrom("UnvalidatedUserSubmittedImage")
|
||||
.leftJoin("Team", (join) =>
|
||||
join.on((eb) =>
|
||||
eb.or([
|
||||
eb("UnvalidatedUserSubmittedImage.id", "=", eb.ref("Team.avatarImgId")),
|
||||
eb("UnvalidatedUserSubmittedImage.id", "=", eb.ref("Team.bannerImgId")),
|
||||
]),
|
||||
),
|
||||
)
|
||||
.leftJoin("Art", "UnvalidatedUserSubmittedImage.id", "Art.imgId")
|
||||
.leftJoin(
|
||||
"CalendarEvent",
|
||||
"UnvalidatedUserSubmittedImage.id",
|
||||
"CalendarEvent.avatarImgId",
|
||||
)
|
||||
.where("UnvalidatedUserSubmittedImage.validatedAt", "is", null)
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb("Team.id", "is not", null),
|
||||
eb("Art.id", "is not", null),
|
||||
eb("CalendarEvent.id", "is not", null),
|
||||
]),
|
||||
);
|
||||
|
||||
/** Counts all unvalidated images used in teams, art, or calendar events */
|
||||
export async function countAllUnvalidated() {
|
||||
const result = await unvalidatedImagesBaseQuery
|
||||
.select(({ fn }) => fn.countAll<number>().as("count"))
|
||||
.executeTakeFirstOrThrow();
|
||||
return result.count;
|
||||
}
|
||||
|
||||
/** Fetches unvalidated images for admin review with submitter info */
|
||||
export function unvalidatedImages() {
|
||||
return unvalidatedImagesBaseQuery
|
||||
.leftJoin(
|
||||
"User",
|
||||
"UnvalidatedUserSubmittedImage.submitterUserId",
|
||||
"User.id",
|
||||
)
|
||||
.select([
|
||||
"UnvalidatedUserSubmittedImage.id",
|
||||
"UnvalidatedUserSubmittedImage.url",
|
||||
"UnvalidatedUserSubmittedImage.submitterUserId",
|
||||
"User.username",
|
||||
])
|
||||
.limit(IMAGES_TO_VALIDATE_AT_ONCE)
|
||||
.execute();
|
||||
}
|
||||
|
||||
/** Counts unvalidated team images submitted by a specific user */
|
||||
export async function countUnvalidatedBySubmitterUserId(userId: number) {
|
||||
const result = await db
|
||||
.selectFrom("UnvalidatedUserSubmittedImage")
|
||||
.innerJoin("Team", (join) =>
|
||||
join.on((eb) =>
|
||||
eb.or([
|
||||
eb(
|
||||
"UnvalidatedUserSubmittedImage.id",
|
||||
"=",
|
||||
eb.ref("Team.avatarImgId"),
|
||||
),
|
||||
eb(
|
||||
"UnvalidatedUserSubmittedImage.id",
|
||||
"=",
|
||||
eb.ref("Team.bannerImgId"),
|
||||
),
|
||||
]),
|
||||
),
|
||||
)
|
||||
.select(({ fn }) => fn.countAll<number>().as("count"))
|
||||
.where("UnvalidatedUserSubmittedImage.validatedAt", "is", null)
|
||||
.where("UnvalidatedUserSubmittedImage.submitterUserId", "=", userId)
|
||||
.executeTakeFirstOrThrow();
|
||||
return result.count;
|
||||
}
|
||||
|
||||
/** Marks an image as validated by setting the current timestamp */
|
||||
export function validateImage(id: number) {
|
||||
return db
|
||||
.updateTable("UnvalidatedUserSubmittedImage")
|
||||
.set({ validatedAt: databaseTimestampNow() })
|
||||
.where("id", "=", id)
|
||||
.execute();
|
||||
}
|
||||
|
||||
/** Creates a new image and associates it with a team or organization */
|
||||
export function addNewImage({
|
||||
submitterUserId,
|
||||
url,
|
||||
validatedAt,
|
||||
teamId,
|
||||
organizationId,
|
||||
type,
|
||||
}: {
|
||||
submitterUserId: number;
|
||||
url: string;
|
||||
validatedAt: number | null;
|
||||
teamId?: number;
|
||||
organizationId?: number;
|
||||
type: ImageUploadType;
|
||||
}) {
|
||||
return db.transaction().execute(async (trx) => {
|
||||
const img = await trx
|
||||
.insertInto("UnvalidatedUserSubmittedImage")
|
||||
.values({ submitterUserId, url, validatedAt })
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
if (type === "team-pfp" && teamId) {
|
||||
await trx
|
||||
.updateTable("AllTeam")
|
||||
.set({ avatarImgId: img.id })
|
||||
.where("id", "=", teamId)
|
||||
.execute();
|
||||
} else if (type === "team-banner" && teamId) {
|
||||
await trx
|
||||
.updateTable("AllTeam")
|
||||
.set({ bannerImgId: img.id })
|
||||
.where("id", "=", teamId)
|
||||
.execute();
|
||||
} else if (type === "org-pfp" && organizationId) {
|
||||
await trx
|
||||
.updateTable("TournamentOrganization")
|
||||
.set({ avatarImgId: img.id })
|
||||
.where("id", "=", organizationId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
return img;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
} from "~/utils/remix.server";
|
||||
import { assertUnreachable } from "~/utils/types";
|
||||
import * as ImageRepository from "../ImageRepository.server";
|
||||
import { validateImage } from "../queries/validateImage";
|
||||
import { validateImageSchema } from "../upload-schemas.server";
|
||||
|
||||
export const action: ActionFunction = async ({ request }) => {
|
||||
|
|
@ -28,7 +27,7 @@ export const action: ActionFunction = async ({ request }) => {
|
|||
await ImageRepository.findById(imageId),
|
||||
);
|
||||
|
||||
validateImage(imageId);
|
||||
await ImageRepository.validateImage(imageId);
|
||||
|
||||
if (image.tournamentId) {
|
||||
clearTournamentDataCache(imageId);
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ import {
|
|||
parseSearchParams,
|
||||
} from "~/utils/remix.server";
|
||||
import { teamPage, tournamentOrganizationPage } from "~/utils/urls";
|
||||
import { addNewImage } from "../queries/addNewImage";
|
||||
import { countUnvalidatedImg } from "../queries/countUnvalidatedImg.server";
|
||||
import * as ImageRepository from "../ImageRepository.server";
|
||||
import { s3UploadHandler } from "../s3.server";
|
||||
import { MAX_UNVALIDATED_IMG_COUNT } from "../upload-constants";
|
||||
import { requestToImgType } from "../upload-utils";
|
||||
|
|
@ -41,7 +40,8 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||
: undefined;
|
||||
|
||||
errorToastIfFalsy(
|
||||
countUnvalidatedImg(user.id) < MAX_UNVALIDATED_IMG_COUNT,
|
||||
(await ImageRepository.countUnvalidatedBySubmitterUserId(user.id)) <
|
||||
MAX_UNVALIDATED_IMG_COUNT,
|
||||
"Too many unvalidated images",
|
||||
);
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||
const shouldAutoValidate =
|
||||
user.roles.includes("SUPPORTER") || validatedType === "org-pfp";
|
||||
|
||||
addNewImage({
|
||||
await ImageRepository.addNewImage({
|
||||
submitterUserId: user.id,
|
||||
teamId: team?.id,
|
||||
organizationId: organization?.id,
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
export { countUnvalidatedArt } from "./queries/countUnvalidatedArt.server";
|
||||
export { s3UploadHandler } from "./s3.server";
|
||||
export type { ImageUploadType } from "./upload-types";
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import { requireRole } from "~/modules/permissions/guards.server";
|
||||
import { countAllUnvalidatedImg } from "../queries/countAllUnvalidatedImg.server";
|
||||
import { unvalidatedImages } from "../queries/unvalidatedImages";
|
||||
import * as ImageRepository from "../ImageRepository.server";
|
||||
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const user = await requireUser(request);
|
||||
requireRole(user, "STAFF");
|
||||
|
||||
return {
|
||||
images: unvalidatedImages(),
|
||||
unvalidatedImgCount: countAllUnvalidatedImg(),
|
||||
images: await ImageRepository.unvalidatedImages(),
|
||||
unvalidatedImgCount: await ImageRepository.countAllUnvalidated(),
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { redirect } from "@remix-run/node";
|
|||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import * as TeamRepository from "~/features/team/TeamRepository.server";
|
||||
import { isTeamManager } from "~/features/team/team-utils";
|
||||
import { countUnvalidatedImg } from "../queries/countUnvalidatedImg.server";
|
||||
import * as ImageRepository from "../ImageRepository.server";
|
||||
import { requestToImgType } from "../upload-utils";
|
||||
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
|
|
@ -25,6 +25,8 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|||
|
||||
return {
|
||||
type: validatedType,
|
||||
unvalidatedImages: countUnvalidatedImg(user.id),
|
||||
unvalidatedImages: await ImageRepository.countUnvalidatedBySubmitterUserId(
|
||||
user.id,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
import { sql } from "~/db/sql";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import type { ImageUploadType } from "../upload-types";
|
||||
|
||||
const addImgStm = sql.prepare(/* sql */ `
|
||||
insert into "UnvalidatedUserSubmittedImage"
|
||||
("submitterUserId", "url", "validatedAt")
|
||||
values
|
||||
(@submitterUserId, @url, @validatedAt)
|
||||
returning *
|
||||
`);
|
||||
|
||||
const updateTeamAvatarStm = sql.prepare(/* sql */ `
|
||||
update "AllTeam"
|
||||
set "avatarImgId" = @avatarImgId
|
||||
where "id" = @teamId
|
||||
`);
|
||||
|
||||
const updateTeamBannerStm = sql.prepare(/* sql */ `
|
||||
update "AllTeam"
|
||||
set "bannerImgId" = @bannerImgId
|
||||
where "id" = @teamId
|
||||
`);
|
||||
|
||||
const updateOrganizationAvatarStm = sql.prepare(/* sql */ `
|
||||
update "TournamentOrganization"
|
||||
set "avatarImgId" = @avatarImgId
|
||||
where "id" = @organizationId
|
||||
`);
|
||||
|
||||
export const addNewImage = sql.transaction(
|
||||
({
|
||||
submitterUserId,
|
||||
url,
|
||||
validatedAt,
|
||||
teamId,
|
||||
organizationId,
|
||||
type,
|
||||
}: {
|
||||
submitterUserId: number;
|
||||
url: string;
|
||||
validatedAt: number | null;
|
||||
teamId?: number;
|
||||
organizationId?: number;
|
||||
type: ImageUploadType;
|
||||
}) => {
|
||||
const img = addImgStm.get({
|
||||
submitterUserId,
|
||||
url,
|
||||
validatedAt,
|
||||
}) as Tables["UserSubmittedImage"];
|
||||
|
||||
if (type === "team-pfp") {
|
||||
updateTeamAvatarStm.run({ avatarImgId: img.id, teamId: teamId ?? null });
|
||||
} else if (type === "team-banner") {
|
||||
updateTeamBannerStm.run({ bannerImgId: img.id, teamId: teamId ?? null });
|
||||
} else if (type === "org-pfp") {
|
||||
updateOrganizationAvatarStm.run({
|
||||
avatarImgId: img.id,
|
||||
organizationId: organizationId ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
return img;
|
||||
},
|
||||
);
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import { sql } from "~/db/sql";
|
||||
|
||||
const stm = sql.prepare(/*sql*/ `
|
||||
select count(*) as "count" from "UnvalidatedUserSubmittedImage"
|
||||
left join "Team" on
|
||||
"UnvalidatedUserSubmittedImage"."id" = "Team"."avatarImgId" or
|
||||
"UnvalidatedUserSubmittedImage"."id" = "Team"."bannerImgId"
|
||||
left join "Art" on
|
||||
"UnvalidatedUserSubmittedImage"."id" = "Art"."imgId"
|
||||
left join "CalendarEvent" on
|
||||
"UnvalidatedUserSubmittedImage"."id" = "CalendarEvent"."avatarImgId"
|
||||
where "UnvalidatedUserSubmittedImage"."validatedAt" is null
|
||||
and ("Team"."id" is not null or "Art"."id" is not null or "CalendarEvent"."id" is not null)
|
||||
`);
|
||||
|
||||
export function countAllUnvalidatedImg() {
|
||||
return (stm.get() as any).count as number;
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import { sql } from "~/db/sql";
|
||||
|
||||
const stm = sql.prepare(/*sql*/ `
|
||||
select count(*) as "count"
|
||||
from "UnvalidatedUserSubmittedImage"
|
||||
inner join "Art" on "Art"."imgId" = "UnvalidatedUserSubmittedImage"."id"
|
||||
where
|
||||
"validatedAt" is null
|
||||
and
|
||||
"Art"."authorId" = @authorId
|
||||
`);
|
||||
|
||||
export function countUnvalidatedArt(authorId: number) {
|
||||
return (stm.get({ authorId }) as any).count as number;
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import { sql } from "~/db/sql";
|
||||
|
||||
const stm = sql.prepare(/*sql*/ `
|
||||
select count(*) as "count" from "UnvalidatedUserSubmittedImage"
|
||||
inner join "Team" on
|
||||
"UnvalidatedUserSubmittedImage"."id" = "Team"."avatarImgId" or
|
||||
"UnvalidatedUserSubmittedImage"."id" = "Team"."bannerImgId"
|
||||
where "validatedAt" is null
|
||||
and "submitterUserId" = @userId
|
||||
`);
|
||||
|
||||
export function countUnvalidatedImg(userId: number) {
|
||||
return (stm.get({ userId }) as any).count as number;
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { sql } from "~/db/sql";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import { IMAGES_TO_VALIDATE_AT_ONCE } from "../upload-constants";
|
||||
|
||||
const stm = sql.prepare(/* sql */ `
|
||||
select
|
||||
"UnvalidatedUserSubmittedImage"."id",
|
||||
"UnvalidatedUserSubmittedImage"."url",
|
||||
"UnvalidatedUserSubmittedImage"."submitterUserId",
|
||||
"User"."username"
|
||||
from "UnvalidatedUserSubmittedImage"
|
||||
left join "User" on
|
||||
"UnvalidatedUserSubmittedImage"."submitterUserId" = "User"."id"
|
||||
left join "Team" on
|
||||
"UnvalidatedUserSubmittedImage"."id" = "Team"."avatarImgId" or
|
||||
"UnvalidatedUserSubmittedImage"."id" = "Team"."bannerImgId"
|
||||
left join "Art" on
|
||||
"UnvalidatedUserSubmittedImage"."id" = "Art"."imgId"
|
||||
left join "CalendarEvent" on
|
||||
"UnvalidatedUserSubmittedImage"."id" = "CalendarEvent"."avatarImgId"
|
||||
where "UnvalidatedUserSubmittedImage"."validatedAt" is null
|
||||
and ("Team"."id" is not null or "Art"."id" is not null or "CalendarEvent"."id" is not null)
|
||||
limit ${IMAGES_TO_VALIDATE_AT_ONCE}
|
||||
`);
|
||||
|
||||
type UnvalidatedImage = Pick<
|
||||
Tables["UserSubmittedImage"],
|
||||
"id" | "url" | "submitterUserId"
|
||||
> & {
|
||||
username: Tables["User"]["username"];
|
||||
};
|
||||
|
||||
export function unvalidatedImages() {
|
||||
return stm.all() as Array<UnvalidatedImage>;
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import { sql } from "~/db/sql";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
|
||||
const stm = sql.prepare(/* sql */ `
|
||||
update "UnvalidatedUserSubmittedImage"
|
||||
set "validatedAt" = @validatedAt
|
||||
where "id" = @id
|
||||
`);
|
||||
|
||||
export function validateImage(id: number) {
|
||||
stm.run({ validatedAt: dateToDatabaseTimestamp(new Date()), id });
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import * as ArtRepository from "~/features/art/ArtRepository.server";
|
||||
import { getUserId } from "~/features/auth/core/user.server";
|
||||
import { countUnvalidatedArt } from "~/features/img-upload";
|
||||
import * as ImageRepository from "~/features/img-upload/ImageRepository.server";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
import { notFoundIfFalsy } from "~/utils/remix.server";
|
||||
import { userParamsSchema } from "../user-page-schemas";
|
||||
|
|
@ -36,6 +36,8 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
|
|||
arts,
|
||||
tagCounts: tagCountsSortedArr.length > 0 ? tagCountsSortedArr : null,
|
||||
unvalidatedArtCount:
|
||||
user.id === loggedInUser?.id ? countUnvalidatedArt(user.id) : 0,
|
||||
user.id === loggedInUser?.id
|
||||
? await ImageRepository.countUnvalidatedArt(user.id)
|
||||
: 0,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { nanoid } from "nanoid";
|
|||
import type { Ok, Result } from "neverthrow";
|
||||
import type { z } from "zod/v4";
|
||||
import type { navItems } from "~/components/layout/nav-items";
|
||||
import { s3UploadHandler } from "~/features/img-upload";
|
||||
import { s3UploadHandler } from "~/features/img-upload/s3.server";
|
||||
import invariant from "./invariant";
|
||||
import { logger } from "./logger";
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user