import { faker } from "@faker-js/faker"; import capitalize from "just-capitalize"; import shuffle from "just-shuffle"; import invariant from "tiny-invariant"; import { ADMIN_DISCORD_ID } from "~/constants"; import { db } from "~/db"; import { sql } from "~/db/sql"; import { abilities, type AbilityType, clothesGearIds, headGearIds, modesShort, shoesGearIds, mainWeaponIds, } from "~/modules/in-game-lists"; import { lastCompletedVoting, nextNonCompletedVoting, } from "~/modules/plus-server"; import allTags from "~/routes/calendar/tags.json"; import { dateToDatabaseTimestamp } from "~/utils/dates"; import type { UpsertManyPlusVotesArgs } from "./models/plusVotes/queries.server"; const ADMIN_TEST_AVATAR = "e424e1ba50d2019fdc4730d261e56c55"; const NZAP_TEST_DISCORD_ID = "455039198672453645"; const NZAP_TEST_AVATAR = "f809176af93132c3db5f0a5019e96339"; // https://cdn.discordapp.com/avatars/455039198672453645/f809176af93132c3db5f0a5019e96339.webp?size=160 const NZAP_TEST_ID = 2; const AMOUNT_OF_CALENDAR_EVENTS = 200; const basicSeeds = [ adminUser, nzapUser, users, userBios, lastMonthsVoting, lastMonthSuggestions, thisMonthsSuggestions, badgesToAdmin, badgesToUsers, badgeManagers, patrons, calendarEvents, calendarEventBadges, calendarEventResults, adminBuilds, ]; export function seed() { wipeDB(); for (const seedFunc of basicSeeds) { seedFunc(); } } function wipeDB() { const tablesToDelete = [ "Build", "CalendarEventDate", "CalendarEventResultPlayer", "CalendarEventResultTeam", "CalendarEventBadge", "CalendarEvent", "User", "PlusVote", "PlusSuggestion", "PlusVote", "TournamentBadgeOwner", "BadgeManager", ]; for (const table of tablesToDelete) { sql.prepare(`delete from "${table}"`).run(); } } function adminUser() { db.users.upsert({ discordDiscriminator: "4059", discordId: ADMIN_DISCORD_ID, discordName: "Sendou", twitch: "Sendou", youtubeId: "UCWbJLXByvsfQvTcR4HLPs5Q", discordAvatar: ADMIN_TEST_AVATAR, twitter: "sendouc", }); } function nzapUser() { db.users.upsert({ discordDiscriminator: "6227", discordId: NZAP_TEST_DISCORD_ID, discordName: "N-ZAP", twitch: null, youtubeId: null, discordAvatar: NZAP_TEST_AVATAR, twitter: null, }); } function users() { const usedNames = new Set(); new Array(500).fill(null).map(fakeUser(usedNames)).forEach(db.users.upsert); } function userBios() { for (let id = 3; id < 500; id++) { if (Math.random() < 0.25) continue; // 75% have bio sql.prepare(`UPDATE "User" SET bio = $bio WHERE id = $id`).run({ id, bio: faker.lorem.paragraphs( faker.helpers.arrayElement([1, 1, 1, 2, 3, 4]), "\n\n" ), }); } } function fakeUser(usedNames: Set) { return () => ({ discordAvatar: null, discordDiscriminator: String(faker.random.numeric(4)), discordId: String(faker.random.numeric(17)), discordName: uniqueDiscordName(usedNames), twitch: null, twitter: null, youtubeId: null, }); } function uniqueDiscordName(usedNames: Set) { let result = faker.random.word(); while (usedNames.has(result)) { result = faker.random.word(); } usedNames.add(result); return result; } const idToPlusTier = (id: number) => { if (id < 30) return 1; if (id < 80) return 2; if (id <= 150) return 3; // these ids failed the voting if (id >= 200 && id <= 209) return 1; if (id >= 210 && id <= 219) return 2; if (id >= 220 && id <= 229) return 3; throw new Error("Invalid id - no plus tier"); }; function lastMonthsVoting() { const votes: UpsertManyPlusVotesArgs = []; const { month, year } = lastCompletedVoting(new Date()); const fiveMinutesAgo = new Date(new Date().getTime() - 5 * 60 * 1000); for (let id = 1; id < 151; id++) { if (id === 2) continue; // omit N-ZAP user for testing; votes.push({ authorId: 1, month, year, score: 1, tier: idToPlusTier(id), validAfter: fiveMinutesAgo, votedId: id, }); } for (let id = 200; id < 225; id++) { votes.push({ authorId: 1, month, year, score: -1, tier: idToPlusTier(id), validAfter: fiveMinutesAgo, votedId: id, }); } db.plusVotes.upsertMany(votes); } function lastMonthSuggestions() { const usersSuggested = [ 3, 10, 14, 90, 120, 140, 200, 201, 203, 204, 205, 216, 217, 218, 219, 220, ]; const { month, year } = lastCompletedVoting(new Date()); for (const id of usersSuggested) { db.plusSuggestions.create({ authorId: 1, month, year, suggestedId: id, text: faker.lorem.lines(), tier: idToPlusTier(id), }); } } function thisMonthsSuggestions() { const usersInPlus = db.users .findAll() .filter((u) => u.plusTier && u.id !== 1); // exclude admin const { month, year } = nextNonCompletedVoting(new Date()); for (let userId = 150; userId < 190; userId++) { const amountOfSuggestions = faker.helpers.arrayElement([1, 1, 2, 3, 4]); for (let i = 0; i < amountOfSuggestions; i++) { const suggester = usersInPlus.shift(); invariant(suggester); invariant(suggester.plusTier); db.plusSuggestions.create({ authorId: suggester.id, month, year, suggestedId: userId, text: faker.lorem.lines(), tier: suggester.plusTier, }); } } } function badgesToAdmin() { const availableBadgeIds = shuffle( sql .prepare(`select "id" from "Badge"`) .all() .map((b) => b.id) ).slice(0, 8) as number[]; const badgesWithDuplicates = availableBadgeIds.flatMap((id) => new Array(faker.helpers.arrayElement([1, 1, 1, 2, 3, 4])) .fill(null) .map(() => id) ); for (const id of badgesWithDuplicates) { sql .prepare( `insert into "TournamentBadgeOwner" ("badgeId", "userId") values ($id, $userId)` ) .run({ id, userId: 1 }); } } function getAvailableBadgeIds() { return shuffle( sql .prepare(`select "id" from "Badge"`) .all() .map((b) => b.id) ); } function badgesToUsers() { const availableBadgeIds = getAvailableBadgeIds(); let userIds = sql .prepare(`select "id" from "User" where id != 2`) // no badges for N-ZAP .all() .map((u) => u.id) as number[]; for (const id of availableBadgeIds) { userIds = shuffle(userIds); for ( let i = 0; i < faker.datatype.number({ min: 1, max: 24, }); i++ ) { const userToGetABadge = userIds.shift()!; sql .prepare( `insert into "TournamentBadgeOwner" ("badgeId", "userId") values ($id, $userId)` ) .run({ id, userId: userToGetABadge }); userIds.push(userToGetABadge); } } } function badgeManagers() { // make N-ZAP user manager of several badges for (let id = 1; id <= 10; id++) { sql .prepare( `insert into "BadgeManager" ("badgeId", "userId") values ($id, $userId)` ) .run({ id, userId: 2 }); } } function patrons() { const userIds = sql .prepare(`select "id" from "User" order by random() limit 50`) .all() .map((u) => u.id); for (const id of userIds) { sql .prepare( `update user set "patronTier" = $patronTier, "patronSince" = $patronSince where id = $id` ) .run({ id, patronSince: dateToDatabaseTimestamp(faker.date.past()), patronTier: faker.helpers.arrayElement([1, 1, 2, 2, 2, 3, 3, 4]), }); } } function userIdsInRandomOrder() { return sql .prepare(`select "id" from "User" order by random()`) .all() .map((u) => u.id) as number[]; } function calendarEvents() { const userIds = userIdsInRandomOrder(); for (let id = 1; id <= AMOUNT_OF_CALENDAR_EVENTS; id++) { const tags = shuffle(Object.keys(allTags)).filter((tag) => tag !== "BADGE"); sql .prepare( ` insert into "CalendarEvent" ( "id", "name", "description", "discordInviteCode", "bracketUrl", "authorId", "tags" ) values ( $id, $name, $description, $discordInviteCode, $bracketUrl, $authorId, $tags ) ` ) .run({ id, name: `${capitalize(faker.word.adjective())} ${capitalize( faker.word.noun() )}`, description: faker.lorem.paragraph(), discordInviteCode: faker.lorem.word(), bracketUrl: faker.internet.url(), authorId: id === 1 ? NZAP_TEST_ID : userIds.pop(), tags: Math.random() > 0.2 ? tags .slice( 0, faker.helpers.arrayElement([ 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 5, 6, ]) ) .join(",") : null, }); const twoDayEvent = Math.random() > 0.9; const startTime = id % 2 === 0 ? faker.date.soon(42) : faker.date.recent(42); startTime.setMinutes(0, 0, 0); sql .prepare( ` insert into "CalendarEventDate" ( "eventId", "startTime" ) values ( $eventId, $startTime ) ` ) .run({ eventId: id, startTime: dateToDatabaseTimestamp(startTime), }); if (twoDayEvent) { startTime.setDate(startTime.getDate() + 1); sql .prepare( ` insert into "CalendarEventDate" ( "eventId", "startTime" ) values ( $eventId, $startTime ) ` ) .run({ eventId: id, startTime: dateToDatabaseTimestamp(startTime), }); } } } function calendarEventBadges() { for (let eventId = 1; eventId <= AMOUNT_OF_CALENDAR_EVENTS; eventId++) { if (Math.random() > 0.25) continue; const availableBadgeIds = getAvailableBadgeIds(); for ( let i = 0; i < faker.helpers.arrayElement([1, 1, 1, 1, 2, 2, 3]); i++ ) { sql .prepare( `insert into "CalendarEventBadge" ("eventId", "badgeId") values ($eventId, $badgeId)` ) .run({ eventId, badgeId: availableBadgeIds.pop() }); } } } function calendarEventResults() { let userIds = userIdsInRandomOrder(); const eventIdsOfPast = new Set( sql .prepare( `select "CalendarEvent"."id" from "CalendarEvent" join "CalendarEventDate" on "CalendarEventDate"."eventId" = "CalendarEvent"."id" where "CalendarEventDate"."startTime" < $startTime` ) .all({ startTime: dateToDatabaseTimestamp(new Date()) }) .map((r) => r.id) ); for (const eventId of eventIdsOfPast) { // event id = 1 needs to be without results for e2e tests if (Math.random() < 0.3 || eventId === 1) continue; db.calendarEvents.upsertReportedScores({ eventId, participantCount: faker.datatype.number({ min: 10, max: 250 }), results: new Array(faker.helpers.arrayElement([1, 1, 2, 3, 3, 3, 8, 8])) .fill(null) // eslint-disable-next-line no-loop-func .map((_, i) => ({ placement: i + 1, teamName: capitalize(faker.word.noun()), players: new Array( faker.helpers.arrayElement([1, 2, 3, 4, 4, 4, 4, 4, 5, 6]) ) .fill(null) .map(() => { const withStringName = Math.random() < 0.2; return { name: withStringName ? faker.name.firstName() : null, userId: withStringName ? null : userIds.pop()!, }; }), })), }); userIds = userIdsInRandomOrder(); } } function adminBuilds() { for (let i = 0; i < 50; i++) { const randomOrderHeadGear = shuffle(headGearIds.slice()); const randomOrderClothesGear = shuffle(clothesGearIds.slice()); const randomOrderShoesGear = shuffle(shoesGearIds.slice()); const randomOrderWeaponIds = shuffle(mainWeaponIds.slice()); const randomAbility = (legalTypes: AbilityType[]) => { const randomOrderAbilities = shuffle([...abilities]); return randomOrderAbilities.find((a) => legalTypes.includes(a.type))! .name; }; db.builds.create({ title: `${capitalize(faker.word.adjective())} ${capitalize( faker.word.noun() )}`, ownerId: 1, description: Math.random() < 0.75 ? faker.lorem.paragraph() : null, headGearSplId: randomOrderHeadGear[0]!, clothesGearSplId: randomOrderClothesGear[0]!, shoesGearSplId: randomOrderShoesGear[0]!, weaponSplIds: new Array( faker.helpers.arrayElement([1, 1, 1, 2, 2, 3, 4, 5]) ) .fill(null) .map(() => randomOrderWeaponIds.pop()!), modes: Math.random() < 0.75 ? modesShort.filter(() => Math.random() < 0.5) : null, abilities: [ [ randomAbility(["HEAD_MAIN_ONLY", "STACKABLE"]), randomAbility(["STACKABLE"]), randomAbility(["STACKABLE"]), randomAbility(["STACKABLE"]), ], [ randomAbility(["CLOTHES_MAIN_ONLY", "STACKABLE"]), randomAbility(["STACKABLE"]), randomAbility(["STACKABLE"]), randomAbility(["STACKABLE"]), ], [ randomAbility(["SHOES_MAIN_ONLY", "STACKABLE"]), randomAbility(["STACKABLE"]), randomAbility(["STACKABLE"]), randomAbility(["STACKABLE"]), ], ], }); } }