Merge remote main into css-rework

This commit is contained in:
hfcRed 2025-12-28 13:22:01 +01:00
commit 4c0ff39da2
14 changed files with 82 additions and 44 deletions

View File

@ -0,0 +1,31 @@
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { cors } from "remix-utils/cors";
import { z } from "zod/v4";
import { identifierToUserIdQuery } from "~/features/user-page/UserRepository.server";
import { notFoundIfFalsy, parseParams } from "~/utils/remix.server";
import { handleOptionsRequest } from "../api-public-utils.server";
import type { GetUserIdsResponse } from "../schema";
const paramsSchema = z.object({
identifier: z.string(),
});
export const loader = async ({ params, request }: LoaderFunctionArgs) => {
await handleOptionsRequest(request);
const { identifier } = parseParams({ params, schema: paramsSchema });
const user = notFoundIfFalsy(
await identifierToUserIdQuery(identifier)
.select(["User.discordId", "User.customUrl"])
.executeTakeFirst(),
);
const result: GetUserIdsResponse = {
id: user.id,
discordId: user.discordId,
customUrl: user.customUrl,
};
return await cors(request, json(result));
};

View File

@ -42,6 +42,20 @@ export interface GetUserResponse {
currentRank: SeasonalRank | null;
}
/** GET /api/user/{userId|discordId|customUrl}/ids */
export interface GetUserIdsResponse {
id: number;
/**
* @example "79237403620945920"
*/
discordId: string;
/**
* @example "sendou"
*/
customUrl: string | null;
}
/** GET /api/team/{teamId} */
export interface GetTeamResponse {

View File

@ -955,6 +955,10 @@
"displayName": "Rapid Rushdown",
"authorDiscordId": "528851510222782474"
},
"runspeed": {
"displayName": "Run Speed Up",
"authorDiscordId": "528851510222782474"
},
"s50badge": {
"displayName": "Anarchy Adventures",
"authorDiscordId": "336683473714544641"

View File

@ -261,6 +261,7 @@ describe("matchMapList()", () => {
preferences: [{ userId: 2, preferences: emptyPreferences }],
id: 2,
},
["SZ"],
);
const szMaps = result.filter((m) => m.mode === "SZ");
@ -317,6 +318,7 @@ describe("matchMapList()", () => {
],
id: 2,
},
["SZ"],
);
const szMaps = result.filter((m) => m.mode === "SZ");

View File

@ -28,7 +28,7 @@ type WeightsMap = Map<string, number>;
async function calculateMapWeights(
groupOnePreferences: UserMapModePreferences[],
groupTwoPreferences: UserMapModePreferences[],
modesIncluded: ModeShort[],
modesIncluded: readonly ModeShort[],
): Promise<WeightsMap> {
const teamOneVotes: WeightsMap = new Map();
const teamTwoVotes: WeightsMap = new Map();
@ -130,7 +130,7 @@ async function applyDefaultWeights(
}
function countVotesForTeam(
modesIncluded: ModeShort[],
modesIncluded: readonly ModeShort[],
preferences: UserMapModePreferences[],
votesMap: WeightsMap,
) {
@ -160,23 +160,13 @@ export async function matchMapList(
groupOne: {
preferences: { userId: number; preferences: UserMapModePreferences }[];
id: number;
ignoreModePreferences?: boolean;
},
groupTwo: {
preferences: { userId: number; preferences: UserMapModePreferences }[];
id: number;
ignoreModePreferences?: boolean;
},
modesIncluded: readonly ModeShort[],
): Promise<TournamentMapListMap[]> {
const modesIncluded = mapModePreferencesToModeList(
groupOne.ignoreModePreferences
? []
: groupOne.preferences.map(({ preferences }) => preferences.modes),
groupTwo.ignoreModePreferences
? []
: groupTwo.preferences.map(({ preferences }) => preferences.modes),
);
const weights = await calculateMapWeights(
groupOne.preferences.map((p) => p.preferences),
groupTwo.preferences.map((p) => p.preferences),

View File

@ -16,6 +16,7 @@ import { groupAfterMorph } from "../core/groups";
import { refreshSendouQInstance, SendouQ } from "../core/SendouQ.server";
import * as PrivateUserNoteRepository from "../PrivateUserNoteRepository.server";
import { lookingSchema } from "../q-schemas.server";
import { resolveFutureMatchModes } from "../q-utils";
import { SendouQError } from "../q-utils.server";
// this function doesn't throw normally because we are assuming
@ -137,36 +138,38 @@ export const action: ActionFunction = async ({ request }) => {
break;
}
case "MATCH_UP_RECHALLENGE":
case "MATCH_UP": {
if (!isGroupManager()) return null;
const ourGroup = SendouQ.findOwnGroup(user.id);
const ownGroup = SendouQ.findOwnGroup(user.id);
const theirGroup = SendouQ.findUncensoredGroupById(data.targetGroupId);
if (!ourGroup || !theirGroup) return null;
if (!ownGroup || !theirGroup) return null;
const ourGroupPreferences =
await SQGroupRepository.mapModePreferencesByGroupId(ourGroup.id);
const ownGroupPreferences =
await SQGroupRepository.mapModePreferencesByGroupId(ownGroup.id);
const theirGroupPreferences =
await SQGroupRepository.mapModePreferencesByGroupId(theirGroup.id);
const modesIncluded = resolveFutureMatchModes(ownGroup, theirGroup);
const mapList = await matchMapList(
{
id: ourGroup.id,
preferences: ourGroupPreferences,
id: ownGroup.id,
preferences: ownGroupPreferences,
},
{
id: theirGroup.id,
preferences: theirGroupPreferences,
ignoreModePreferences: data._action === "MATCH_UP_RECHALLENGE",
},
modesIncluded,
);
const createdMatch = await SQMatchRepository.create({
alphaGroupId: ourGroup.id,
alphaGroupId: ownGroup.id,
bravoGroupId: theirGroup.id,
mapList,
memento: createMatchMemento({
own: { group: ourGroup, preferences: ourGroupPreferences },
own: { group: ownGroup, preferences: ownGroupPreferences },
their: { group: theirGroup, preferences: theirGroupPreferences },
mapList,
}),
@ -174,10 +177,10 @@ export const action: ActionFunction = async ({ request }) => {
await refreshSendouQInstance();
if (ourGroup.chatCode && theirGroup.chatCode) {
if (ownGroup.chatCode && theirGroup.chatCode) {
ChatSystemMessage.send([
{
room: ourGroup.chatCode,
room: ownGroup.chatCode,
type: "MATCH_STARTED",
revalidateOnly: true,
},
@ -191,7 +194,7 @@ export const action: ActionFunction = async ({ request }) => {
notify({
userIds: [
...ourGroup.members.map((m) => m.id),
...ownGroup.members.map((m) => m.id),
...theirGroup.members.map((m) => m.id),
],
defaultSeenUserIds: [user.id],

View File

@ -80,10 +80,7 @@ export function GroupCard({
const isOwnGroup = group.id === ownGroup?.id;
const futureMatchModes = ownGroup
? resolveFutureMatchModes({
ownGroup,
theirGroup: group,
})
? resolveFutureMatchModes(ownGroup, group)
: null;
const enableKicking = group.usersRole === "OWNER" && !displayOnly;

View File

@ -58,10 +58,6 @@ export const lookingSchema = z.union([
_action: _action("MATCH_UP"),
targetGroupId: id,
}),
z.object({
_action: _action("MATCH_UP_RECHALLENGE"),
targetGroupId: id,
}),
z.object({
_action: _action("GIVE_MANAGER"),
userId: id,

View File

@ -27,15 +27,12 @@ export function userCanJoinQueueAt(
return canJoinQueueAt;
}
export function resolveFutureMatchModes({
ownGroup,
theirGroup,
}: {
ownGroup: Pick<SQGroup, "modePreferences">;
theirGroup: Pick<SQGroup, "modePreferences">;
}) {
const ourModes = ownGroup.modePreferences;
const theirModes = theirGroup.modePreferences;
export function resolveFutureMatchModes(
groupA: Pick<SQGroup, "modePreferences">,
groupB: Pick<SQGroup, "modePreferences">,
) {
const ourModes = groupA.modePreferences;
const theirModes = groupB.modePreferences;
const overlap = ourModes.filter((mode) => theirModes.includes(mode));
if (overlap.length > 0) {

View File

@ -24,7 +24,7 @@ import {
import { safeNumberParse } from "~/utils/number";
import type { ChatUser } from "../chat/chat-types";
const identifierToUserIdQuery = (identifier: string) =>
export const identifierToUserIdQuery = (identifier: string) =>
db
.selectFrom("User")
.select("User.id")

View File

@ -256,6 +256,10 @@ export default [
"/user/:identifier",
"features/api-public/routes/user.$identifier.ts",
),
route(
"/user/:identifier/ids",
"features/api-public/routes/user.$identifier.ids.ts",
),
route(
"/calendar/:year/:week",
"features/api-public/routes/calendar.$year.$week.ts",

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 957 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB