mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-09 19:47:54 -05:00
* Kysely initial * Badges initial * Badge routes migrated * Badges migrated * Calendar work * Fix one type problem * Calendar work * findResultsByUserId work * Calendar reworking finished * PlusSuggestions work * Migrated suggestions * Builds progress * Migrated builds * Admin migrated * Migrate articles * User search * Faster getUser * Selectable/insertable as global * Refresh prod db script + patronTier index * identifierToUserId * updateProfile * findByIdentifier * More indexes * User upsert * upsertLite * findAllPlusMembers * updateResultHighlights * updateMany * User finished migration * Fix types * Fix PlusVotingResult typing * PlusVotingRepository WIP * Migrated resultsByMonthYear * Migrated plusVotes (done with db. related migrations) * Plus code to features folder * Fix TODOs * Export * Fix range * Migrate some user pages * Move rest user routes * Move /play * Map list generator * Front page * Move map list generation logic * Move plus voting logic * Info * API * Adjust TODOs * theme * Auth * Remove TODO
158 lines
4.4 KiB
TypeScript
158 lines
4.4 KiB
TypeScript
import shuffle from "just-shuffle";
|
|
import { type InferResult, sql } from "kysely";
|
|
import { db } from "~/db/sql";
|
|
import type { Tables, TablesInsertable } from "~/db/tables";
|
|
import {
|
|
nextNonCompletedVoting,
|
|
type MonthYear,
|
|
rangeToMonthYear,
|
|
} from "~/features/plus-voting/core";
|
|
import { COMMON_USER_FIELDS } from "~/utils/kysely.server";
|
|
import type { Unwrapped } from "~/utils/types";
|
|
import * as PlusSuggestionRepository from "~/features/plus-suggestions/PlusSuggestionRepository.server";
|
|
import invariant from "tiny-invariant";
|
|
|
|
const resultsByMonthYearQuery = (args: MonthYear) =>
|
|
db
|
|
.selectFrom("PlusVotingResult")
|
|
.innerJoin("User", "PlusVotingResult.votedId", "User.id")
|
|
.select([
|
|
...COMMON_USER_FIELDS,
|
|
"PlusVotingResult.wasSuggested",
|
|
"PlusVotingResult.passedVoting",
|
|
"PlusVotingResult.tier",
|
|
"PlusVotingResult.score",
|
|
])
|
|
.where("PlusVotingResult.month", "=", args.month)
|
|
.where("PlusVotingResult.year", "=", args.year)
|
|
.orderBy(sql`"User"."discordName" collate nocase`, "asc");
|
|
type ResultsByMonthYearQueryReturnType = InferResult<
|
|
ReturnType<typeof resultsByMonthYearQuery>
|
|
>;
|
|
|
|
export type ResultsByMonthYearItem = Unwrapped<typeof resultsByMonthYear>;
|
|
export async function resultsByMonthYear(args: MonthYear) {
|
|
const rows = await resultsByMonthYearQuery(args).execute();
|
|
|
|
return groupPlusVotingResults(rows);
|
|
}
|
|
|
|
function groupPlusVotingResults(rows: ResultsByMonthYearQueryReturnType) {
|
|
const grouped: Record<
|
|
number,
|
|
{
|
|
passed: ResultsByMonthYearQueryReturnType;
|
|
failed: ResultsByMonthYearQueryReturnType;
|
|
}
|
|
> = {};
|
|
|
|
for (const row of rows) {
|
|
const playersOfTier = grouped[row.tier] ?? {
|
|
passed: [],
|
|
failed: [],
|
|
};
|
|
grouped[row.tier] = playersOfTier;
|
|
|
|
playersOfTier[row.passedVoting ? "passed" : "failed"].push(row);
|
|
}
|
|
|
|
return Object.entries(grouped)
|
|
.map(([tier, { passed, failed }]) => ({
|
|
tier: Number(tier),
|
|
passed,
|
|
failed,
|
|
}))
|
|
.sort((a, b) => a.tier - b.tier);
|
|
}
|
|
|
|
export type UsersForVoting = {
|
|
user: Pick<
|
|
Tables["User"],
|
|
"id" | "discordId" | "discordName" | "discordAvatar" | "bio"
|
|
>;
|
|
suggestion?: PlusSuggestionRepository.FindAllByMonthItem;
|
|
}[];
|
|
|
|
export async function usersForVoting(loggedInUser: {
|
|
id: number;
|
|
plusTier: number;
|
|
}) {
|
|
const members = await db
|
|
.selectFrom("User")
|
|
.innerJoin("PlusTier", "PlusTier.userId", "User.id")
|
|
.select([...COMMON_USER_FIELDS, "User.bio"])
|
|
.where("PlusTier.tier", "=", loggedInUser.plusTier)
|
|
.execute();
|
|
|
|
const suggestedUsers = (
|
|
await PlusSuggestionRepository.findAllByMonth(
|
|
rangeToMonthYear(nextNonCompletedVoting(new Date())),
|
|
)
|
|
).filter((suggestion) => suggestion.tier === loggedInUser.plusTier);
|
|
invariant(suggestedUsers);
|
|
|
|
const result: UsersForVoting = [];
|
|
|
|
for (const member of members) {
|
|
result.push({
|
|
user: {
|
|
id: member.id,
|
|
discordId: member.discordId,
|
|
discordName: member.discordName,
|
|
discordAvatar: member.discordAvatar,
|
|
bio: member.bio,
|
|
},
|
|
});
|
|
}
|
|
|
|
for (const suggestion of suggestedUsers) {
|
|
result.push({
|
|
user: {
|
|
id: suggestion.suggested.id,
|
|
discordId: suggestion.suggested.discordId,
|
|
discordName: suggestion.suggested.discordName,
|
|
discordAvatar: suggestion.suggested.discordAvatar,
|
|
bio: suggestion.suggested.bio,
|
|
},
|
|
suggestion,
|
|
});
|
|
}
|
|
|
|
return shuffle(result.filter(({ user }) => user.id !== loggedInUser.id));
|
|
}
|
|
|
|
export async function hasVoted(args: {
|
|
authorId: number;
|
|
month: number;
|
|
year: number;
|
|
}) {
|
|
const rows = await db
|
|
.selectFrom("PlusVote")
|
|
.select(({ eb }) => eb.lit(1).as("one"))
|
|
.where("PlusVote.authorId", "=", args.authorId)
|
|
.where("PlusVote.month", "=", args.month)
|
|
.where("PlusVote.year", "=", args.year)
|
|
.execute();
|
|
|
|
return rows.length > 0;
|
|
}
|
|
|
|
export type UpsertManyPlusVotesArgs = Pick<
|
|
TablesInsertable["PlusVote"],
|
|
"month" | "year" | "tier" | "authorId" | "votedId" | "score" | "validAfter"
|
|
>[];
|
|
export function upsertMany(votes: UpsertManyPlusVotesArgs) {
|
|
const firstVote = votes[0];
|
|
|
|
return db.transaction().execute(async (trx) => {
|
|
await trx
|
|
.deleteFrom("PlusVote")
|
|
.where("PlusVote.authorId", "=", firstVote.authorId)
|
|
.where("PlusVote.month", "=", firstVote.month)
|
|
.where("PlusVote.year", "=", firstVote.year)
|
|
.execute();
|
|
|
|
await trx.insertInto("PlusVote").values(votes).execute();
|
|
});
|
|
}
|