mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-25 21:00:28 -05:00
Change comments data model a bit + adjust sorting
This commit is contained in:
@@ -27,6 +27,7 @@ module.exports = {
|
||||
"@typescript-eslint/no-unsafe-call": 0,
|
||||
"@typescript-eslint/no-unsafe-argument": 0,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
|
||||
@@ -32,95 +32,110 @@ export function create(
|
||||
createStm.run(args);
|
||||
}
|
||||
|
||||
const findStm = sql.prepare(`
|
||||
WITH gs as (
|
||||
SELECT
|
||||
"tier",
|
||||
json_object(
|
||||
'info',
|
||||
json_object(
|
||||
'id',
|
||||
suggested."id",
|
||||
'discordId',
|
||||
suggested."discordId",
|
||||
'discordName',
|
||||
suggested."discordName",
|
||||
'discordDiscriminator',
|
||||
suggested."discordDiscriminator",
|
||||
'discordAvatar',
|
||||
suggested."discordAvatar"
|
||||
),
|
||||
'suggestions',
|
||||
json_group_array(
|
||||
json_object(
|
||||
'id',
|
||||
"PlusSuggestion"."id",
|
||||
'author',
|
||||
json_object(
|
||||
'id',
|
||||
author."id",
|
||||
'discordId',
|
||||
author."discordId",
|
||||
'discordName',
|
||||
author."discordName",
|
||||
'discordDiscriminator',
|
||||
author."discordDiscriminator"
|
||||
),
|
||||
'createdAt',
|
||||
"createdAt",
|
||||
'text',
|
||||
"text"
|
||||
)
|
||||
)
|
||||
) as users
|
||||
FROM
|
||||
"PlusSuggestion"
|
||||
JOIN "User" AS author ON "PlusSuggestion"."authorId" = author."id"
|
||||
JOIN "User" AS suggested ON "PlusSuggestion"."suggestedId" = suggested."id"
|
||||
WHERE
|
||||
"month" = $month
|
||||
AND "year" = $year
|
||||
AND "tier" >= $plusTier
|
||||
GROUP BY
|
||||
"suggestedId",
|
||||
"tier"
|
||||
)
|
||||
SELECT
|
||||
tier,
|
||||
json_group_array(users) as users
|
||||
from
|
||||
gs
|
||||
GROUP BY
|
||||
"tier"
|
||||
const findVisibleForUserStm = sql.prepare(`
|
||||
SELECT
|
||||
suggestion."id",
|
||||
suggestion."createdAt",
|
||||
suggestion."text",
|
||||
suggestion."tier",
|
||||
author."id" as "authorId",
|
||||
author."discordId" as "authorDiscordId",
|
||||
author."discordName" as "authorDiscordName",
|
||||
author."discordDiscriminator" as "authorDiscordDiscriminator",
|
||||
suggested."id" as "suggestedId",
|
||||
suggested."discordId" as "suggestedDiscordId",
|
||||
suggested."discordName" as "suggestedDiscordName",
|
||||
suggested."discordDiscriminator" as "suggestedDiscordDiscriminator",
|
||||
suggested."discordAvatar" as "suggestedDiscordAvatar"
|
||||
FROM "PlusSuggestion" as suggestion
|
||||
JOIN "User" AS author ON suggestion."authorId" = author."id"
|
||||
JOIN "User" AS suggested ON suggestion."suggestedId" = suggested."id"
|
||||
WHERE
|
||||
"month" = $month
|
||||
AND "year" = $year
|
||||
AND "tier" >= $plusTier
|
||||
ORDER BY
|
||||
"createdAt" ASC
|
||||
`);
|
||||
|
||||
export type FindResult = {
|
||||
tier: number;
|
||||
users: {
|
||||
info: Pick<
|
||||
export interface FindVisibleForUserSuggestedUserInfo {
|
||||
info: Pick<
|
||||
User,
|
||||
| "id"
|
||||
| "discordId"
|
||||
| "discordName"
|
||||
| "discordDiscriminator"
|
||||
| "discordAvatar"
|
||||
>;
|
||||
suggestions: (Pick<PlusSuggestion, "id" | "createdAt" | "text"> & {
|
||||
author: Pick<
|
||||
User,
|
||||
| "id"
|
||||
| "discordId"
|
||||
| "discordName"
|
||||
| "discordDiscriminator"
|
||||
| "discordAvatar"
|
||||
"id" | "discordId" | "discordName" | "discordDiscriminator"
|
||||
>;
|
||||
suggestions: (Pick<PlusSuggestion, "id" | "createdAt" | "text"> & {
|
||||
author: Pick<
|
||||
User,
|
||||
"id" | "discordId" | "discordName" | "discordDiscriminator"
|
||||
>;
|
||||
})[];
|
||||
}[];
|
||||
}[];
|
||||
})[];
|
||||
}
|
||||
export interface FindVisibleForUser {
|
||||
[tier: string]: FindVisibleForUserSuggestedUserInfo[];
|
||||
}
|
||||
|
||||
export function find(args: MonthYear & Pick<User, "plusTier">) {
|
||||
if (!args.plusTier) return;
|
||||
export function findVisibleForUser(
|
||||
args: MonthYear & Pick<User, "plusTier">
|
||||
): FindVisibleForUser {
|
||||
return sortNewestPlayersToBeSuggestedFirst(
|
||||
mapFindVisibleForUserRowsToResult(findVisibleForUserStm.all(args))
|
||||
);
|
||||
}
|
||||
|
||||
return findStm.all(args).map((row) => ({
|
||||
...row,
|
||||
users: JSON.parse(row.users).map(JSON.parse),
|
||||
})) as FindResult;
|
||||
function mapFindVisibleForUserRowsToResult(rows: any[]): FindVisibleForUser {
|
||||
return rows.reduce((result: FindVisibleForUser, row) => {
|
||||
if (!result[row.tier]) result[row.tier] = [];
|
||||
|
||||
const suggestionInfo = {
|
||||
id: row.id,
|
||||
createdAt: row.createdAt,
|
||||
text: row.text,
|
||||
author: {
|
||||
id: row.authorId,
|
||||
discordId: row.authorDiscordId,
|
||||
discordName: row.authorDiscordName,
|
||||
discordDiscriminator: row.authorDiscordDiscriminator,
|
||||
},
|
||||
};
|
||||
|
||||
const existingSuggestion = result[row.tier].find(
|
||||
(suggestion) => suggestion.info.id === row.suggestedId
|
||||
);
|
||||
|
||||
if (existingSuggestion) {
|
||||
existingSuggestion.suggestions.push(suggestionInfo);
|
||||
} else {
|
||||
result[row.tier].push({
|
||||
info: {
|
||||
id: row.suggestedId,
|
||||
discordId: row.suggestedDiscordId,
|
||||
discordName: row.suggestedDiscordName,
|
||||
discordDiscriminator: row.suggestedDiscriminator,
|
||||
discordAvatar: row.suggestedDiscordAvatar,
|
||||
},
|
||||
suggestions: [suggestionInfo],
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function sortNewestPlayersToBeSuggestedFirst(
|
||||
suggestions: FindVisibleForUser
|
||||
): FindVisibleForUser {
|
||||
return Object.fromEntries(
|
||||
Object.entries(suggestions).map(([tier, suggestions]) => [
|
||||
tier,
|
||||
suggestions.sort(
|
||||
(a, b) => b.suggestions[0].createdAt - a.suggestions[0].createdAt
|
||||
),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
const tiersSuggestedForStm = sql.prepare(`
|
||||
|
||||
@@ -4,7 +4,7 @@ import { allTruthy } from "./utils/arrays";
|
||||
|
||||
interface CanAddCommentToSuggestionFEArgs {
|
||||
user?: Pick<User, "id">;
|
||||
suggestions: plusSuggestions.FindResult;
|
||||
suggestions: plusSuggestions.FindVisibleForUser;
|
||||
suggested: { id: User["id"]; plusTier: NonNullable<User["plusTier"]> };
|
||||
}
|
||||
export function canAddCommentToSuggestionFE(
|
||||
@@ -56,9 +56,8 @@ function alreadyCommentedByUser({
|
||||
suggested,
|
||||
}: CanAddCommentToSuggestionFEArgs) {
|
||||
return Boolean(
|
||||
suggestions
|
||||
.find(({ tier }) => tier === suggested.plusTier)
|
||||
?.users.find((u) => u.info.id === suggested.id)
|
||||
suggestions[suggested.plusTier]
|
||||
?.find((u) => u.info.id === suggested.id)
|
||||
?.suggestions.some((s) => s.author.id === user?.id)
|
||||
);
|
||||
}
|
||||
@@ -68,9 +67,7 @@ function playerAlreadySuggested({
|
||||
suggested,
|
||||
}: Pick<CanAddCommentToSuggestionBEArgs, "suggestions" | "suggested">) {
|
||||
return Boolean(
|
||||
suggestions
|
||||
.find(({ tier }) => tier === suggested.plusTier)
|
||||
?.users.find((u) => u.info.id === suggested.id)
|
||||
suggestions[suggested.plusTier]?.find((u) => u.info.id === suggested.id)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
validate,
|
||||
} from "~/utils/remix";
|
||||
import { discordFullName } from "~/utils/strings";
|
||||
import type { Unpacked } from "~/utils/types";
|
||||
import { actualNumber } from "~/utils/zod";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
@@ -53,13 +52,13 @@ export const action: ActionFunction = async ({ request }) => {
|
||||
});
|
||||
const user = await requireUser(request);
|
||||
|
||||
const suggestions = db.plusSuggestions.find({
|
||||
const suggestions = db.plusSuggestions.findVisibleForUser({
|
||||
...upcomingVoting(new Date()),
|
||||
plusTier: user.plusTier,
|
||||
});
|
||||
|
||||
const targetSuggestion = suggestions
|
||||
?.flatMap((s) => s.users)
|
||||
const targetSuggestion = Object.values(suggestions)
|
||||
?.flat()
|
||||
.flatMap((u) => u.suggestions)
|
||||
.find((s) => s.id === data.suggestionId);
|
||||
|
||||
@@ -72,7 +71,7 @@ export const action: ActionFunction = async ({ request }) => {
|
||||
};
|
||||
|
||||
export interface PlusSuggestionsLoaderData {
|
||||
suggestions?: plusSuggestions.FindResult;
|
||||
suggestions?: plusSuggestions.FindVisibleForUser;
|
||||
suggestedForTiers: number[];
|
||||
}
|
||||
|
||||
@@ -80,7 +79,7 @@ export const loader: LoaderFunction = async ({ request }) => {
|
||||
const user = await requireUser(request);
|
||||
|
||||
return json<PlusSuggestionsLoaderData>({
|
||||
suggestions: db.plusSuggestions.find({
|
||||
suggestions: db.plusSuggestions.findVisibleForUser({
|
||||
...upcomingVoting(new Date()),
|
||||
plusTier: user.plusTier,
|
||||
}),
|
||||
@@ -94,16 +93,15 @@ export const loader: LoaderFunction = async ({ request }) => {
|
||||
export default function PlusSuggestionsPage() {
|
||||
const data = useLoaderData<PlusSuggestionsLoaderData>();
|
||||
const [tierVisible, setTierVisible] = React.useState(
|
||||
data.suggestions?.[0].tier ?? 0
|
||||
tierVisibleInitialState(data.suggestions)
|
||||
);
|
||||
|
||||
if (!data.suggestions) {
|
||||
return <SuggestedForInfo />;
|
||||
}
|
||||
|
||||
const visibleSuggestions = data.suggestions.find(
|
||||
({ tier }) => tier === tierVisible
|
||||
);
|
||||
invariant(tierVisible);
|
||||
const visibleSuggestions = data.suggestions[tierVisible];
|
||||
invariant(visibleSuggestions);
|
||||
|
||||
return (
|
||||
@@ -113,28 +111,32 @@ export default function PlusSuggestionsPage() {
|
||||
<SuggestedForInfo />
|
||||
<div className="stack md">
|
||||
<div className="plus__radios">
|
||||
{data.suggestions.map(({ tier, users }) => {
|
||||
const id = String(tier);
|
||||
return (
|
||||
<div key={id} className="plus__radio-container">
|
||||
<label htmlFor={id} className="plus__radio-label">
|
||||
+{tier}{" "}
|
||||
<span className="plus__users-count">({users.length})</span>
|
||||
</label>
|
||||
<input
|
||||
id={id}
|
||||
name="tier"
|
||||
type="radio"
|
||||
checked={tierVisible === tier}
|
||||
onChange={() => setTierVisible(tier)}
|
||||
data-cy={`plus${tier}-radio`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{Object.entries(data.suggestions)
|
||||
.sort((a, b) => Number(a[0]) - Number(b[0]))
|
||||
.map(([tier, suggestions]) => {
|
||||
const id = String(tier);
|
||||
return (
|
||||
<div key={id} className="plus__radio-container">
|
||||
<label htmlFor={id} className="plus__radio-label">
|
||||
+{tier}{" "}
|
||||
<span className="plus__users-count">
|
||||
({suggestions.length})
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
id={id}
|
||||
name="tier"
|
||||
type="radio"
|
||||
checked={tierVisible === tier}
|
||||
onChange={() => setTierVisible(tier)}
|
||||
data-cy={`plus${tier}-radio`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="stack lg">
|
||||
{visibleSuggestions.users.map((u) => (
|
||||
{visibleSuggestions.map((u) => (
|
||||
<SuggestedUser
|
||||
key={`${u.info.id}-${tierVisible}`}
|
||||
suggested={u}
|
||||
@@ -148,6 +150,13 @@ export default function PlusSuggestionsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
function tierVisibleInitialState(
|
||||
suggestions?: plusSuggestions.FindVisibleForUser
|
||||
) {
|
||||
if (!suggestions) return;
|
||||
return String(Math.min(...Object.keys(suggestions).map(Number)));
|
||||
}
|
||||
|
||||
function SuggestedForInfo() {
|
||||
const data = useLoaderData<PlusSuggestionsLoaderData>();
|
||||
const user = useUser();
|
||||
@@ -178,10 +187,8 @@ function SuggestedUser({
|
||||
suggested,
|
||||
tier,
|
||||
}: {
|
||||
suggested: Unpacked<
|
||||
Unpacked<NonNullable<PlusSuggestionsLoaderData["suggestions"]>>["users"]
|
||||
>;
|
||||
tier: number;
|
||||
suggested: plusSuggestions.FindVisibleForUserSuggestedUserInfo;
|
||||
tier: string;
|
||||
}) {
|
||||
const data = useLoaderData<PlusSuggestionsLoaderData>();
|
||||
const user = useUser();
|
||||
@@ -200,7 +207,7 @@ function SuggestedUser({
|
||||
{canAddCommentToSuggestionFE({
|
||||
user,
|
||||
suggestions: data.suggestions,
|
||||
suggested: { id: suggested.info.id, plusTier: tier },
|
||||
suggested: { id: suggested.info.id, plusTier: Number(tier) },
|
||||
}) ? (
|
||||
// TODO: resetScroll={false} https://twitter.com/ryanflorence/status/1527775882797907969
|
||||
<LinkButton
|
||||
|
||||
@@ -34,7 +34,7 @@ export const action: ActionFunction = async ({ request }) => {
|
||||
});
|
||||
const user = await requireUser(request);
|
||||
|
||||
const suggestions = db.plusSuggestions.find({
|
||||
const suggestions = db.plusSuggestions.findVisibleForUser({
|
||||
...upcomingVoting(new Date()),
|
||||
plusTier: user.plusTier,
|
||||
});
|
||||
@@ -65,19 +65,19 @@ export default function PlusCommentModalPage() {
|
||||
const data = matches.at(-2)!.data as PlusSuggestionsLoaderData;
|
||||
|
||||
const targetUserId = Number(params.userId);
|
||||
const tierSuggestedTo = Number(params.tier);
|
||||
|
||||
const userBeingCommented = data.suggestions
|
||||
?.find(({ tier }) => tier === tierSuggestedTo)
|
||||
?.users.find((u) => u.info.id === targetUserId);
|
||||
const tierSuggestedTo = String(params.tier);
|
||||
|
||||
invariant(data.suggestions);
|
||||
const userBeingCommented = data.suggestions[tierSuggestedTo]?.find(
|
||||
(u) => u.info.id === targetUserId
|
||||
);
|
||||
|
||||
if (
|
||||
!userBeingCommented ||
|
||||
!canAddCommentToSuggestionFE({
|
||||
user,
|
||||
suggestions: data.suggestions,
|
||||
suggested: { id: targetUserId, plusTier: tierSuggestedTo },
|
||||
suggested: { id: targetUserId, plusTier: Number(tierSuggestedTo) },
|
||||
})
|
||||
) {
|
||||
return <Redirect to={PLUS_SUGGESTIONS_PAGE} />;
|
||||
|
||||
Reference in New Issue
Block a user