Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 34 additions & 22 deletions src/lib/components/FeaturedProfiles.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,47 @@
const mobile = new IsMobile();

const players = [
{ name: 'ドラ右 (Doramigi)', id: 1815290, imageUrl: '/images/recaps/ドラ右.webp' },
{ name: 'Miya', id: 1002311, imageUrl: '/images/recaps/Miya.webp' },
{ name: 'あcola (acola)', id: 1787719, imageUrl: '/images/recaps/あcola.webp' },
{ name: 'Hurt', id: 836465, imageUrl: '/images/recaps/Hurt.webp' },
{ name: 'Sparg0', id: 94369, imageUrl: '/images/recaps/Sparg0.webp' },
{ name: 'Sonix', id: 165614, imageUrl: '/images/recaps/Sonix.webp' },
{ name: 'Syrup', id: 767151, imageUrl: '/images/recaps/Syrup.webp' },
{ name: 'Zomba', id: 252388, imageUrl: '/images/recaps/Zomba.webp' },
{ name: 'カルメロ (Carmelo)', id: 2262042, imageUrl: '/images/recaps/カルメロ.webp' },
{ name: 'らる (Raru)', id: 1787715, imageUrl: '/images/recaps/らる.webp' },
{ name: 'Shuton', id: 134839, imageUrl: '/images/recaps/Shuton.webp' },
{ name: 'Tweek', id: 10213, imageUrl: '/images/recaps/Tweek.webp' },
{ name: 'Glutonny', id: 2613, imageUrl: '/images/recaps/Glutonny.webp' },
{ name: 'MkLeo', id: 41259, imageUrl: '/images/recaps/MkLeo.webp' },
{
name: 'ドラ右 (Doramigi)',
slug: '82f63ee2',
imageUrl: '/images/recaps/ドラ右.webp'
},
{ name: 'Miya', slug: 'd8016ec8', imageUrl: '/images/recaps/Miya.webp' },
{
name: 'あcola (acola)',
slug: '830dec1e',
imageUrl: '/images/recaps/あcola.webp'
},
{ name: 'Hurt', slug: '9b4e3c26', imageUrl: '/images/recaps/Hurt.webp' },
{ name: 'Sparg0', slug: 'b5230de8', imageUrl: '/images/recaps/Sparg0.webp' },
{ name: 'Sonix', slug: '0301d9f0', imageUrl: '/images/recaps/Sonix.webp' },
{ name: 'Syrup', slug: '0f333fd3', imageUrl: '/images/recaps/Syrup.webp' },
{ name: 'Zomba', slug: '880cbc79', imageUrl: '/images/recaps/Zomba.webp' },
{
name: 'カルメロ (Carmelo)',
slug: '9e3d38c6',
imageUrl: '/images/recaps/カルメロ.webp'
},
{ name: 'らる (Raru)', slug: '50b1e9a9', imageUrl: '/images/recaps/らる.webp' },
{ name: 'Shuton', slug: '993a6b60', imageUrl: '/images/recaps/Shuton.webp' },
{ name: 'Tweek', slug: '0d650c15', imageUrl: '/images/recaps/Tweek.webp' },
{ name: 'Glutonny', slug: '7611d833', imageUrl: '/images/recaps/Glutonny.webp' },
{ name: 'MkLeo', slug: '3f297e74', imageUrl: '/images/recaps/MkLeo.webp' },
{
name: 'たまPだいふく (TamaPDaifuku)',
id: 1816581,
slug: 'f2633635',
imageUrl: '/images/recaps/たまPだいふく.webp'
},
{ name: 'Light', id: 95011, imageUrl: '/images/recaps/Light.webp' },
{ name: 'Peabnut', id: 37364, imageUrl: '/images/recaps/Peabnut.webp' },
{ name: 'Asimo', id: 964831, imageUrl: '/images/recaps/Asimo.webp' },
{ name: 'Wrath', id: 52384, imageUrl: '/images/recaps/Wrath.webp' },
{ name: 'Tea', id: 399160, imageUrl: '/images/recaps/Tea.webp' }
{ name: 'Light', slug: 'e2974569', imageUrl: '/images/recaps/Light.webp' },
{ name: 'Peabnut', slug: '9160966b', imageUrl: '/images/recaps/Peabnut.webp' },
{ name: 'Asimo', slug: 'ffa84c87', imageUrl: '/images/recaps/Asimo.webp' },
{ name: 'Wrath', slug: '9e90ab19', imageUrl: '/images/recaps/Wrath.webp' },
{ name: 'Tea', slug: 'ab90c078', imageUrl: '/images/recaps/Tea.webp' }
];

type FeaturedProfile = (typeof players)[number];

const urlToRecap = (player: FeaturedProfile) => `/user/${player.id}`;
const urlToRecap = (player: FeaturedProfile) => `/user/${player.slug}`;
</script>

<section class="featured-profiles">
Expand All @@ -46,7 +58,7 @@
</div>

<div class="grid">
{#each players as player (player.id)}
{#each players as player (player.slug)}
<a href={localizeHref(urlToRecap(player))} class="profile-link">
<img
src={player.imageUrl}
Expand Down
7 changes: 4 additions & 3 deletions src/lib/components/SearchPlayer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@
<Command.Empty class="command-empty">{m['search.no_results']()}</Command.Empty>
{:else if results.length > 0}
<Command.Group>
{#each results as player (player.id)}
{#each results as player (player.slug)}
{@const slug = player.slug.replace('user/', '')}
<Command.LinkItem
class="command-link"
href={localizeHref(`/user/${player.id}`)}
value={player.id.toString()}
href={localizeHref(`/user/${slug}`)}
value={player.slug}
>
<div class="player-item-content">
{#if player.image}
Expand Down
28 changes: 16 additions & 12 deletions src/lib/remotes/players.remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ export const searchPlayerQuery = query(v.pipe(v.string(), v.trim()), async (game
return (
res.data.players?.nodes
?.map((player) => {
// By design a user always has an id and a gamerTag
// By design a user always has a gamerTag and a slug
// Typescript skill issue here
if (!player?.user?.id || !player?.gamerTag) return null;
if (!player?.gamerTag || !player?.user?.slug) return null;

return {
id: parseInt(player.user.id),
slug: player.user.slug,
gamerTag: player.gamerTag,
prefix: player.prefix,
image: player.user.images?.[0]?.url || '',
Expand All @@ -87,12 +87,12 @@ export const searchPlayerQuery = query(v.pipe(v.string(), v.trim()), async (game
});

export type PlayerResult = {
id: number;
slug: string;
gamerTag: string;
image: string;
country: string;
nbEvent: number;
lastEvent: number;
lastEvent?: number;
};

type PlayerStats = {
Expand Down Expand Up @@ -155,11 +155,11 @@ type PlayerStats = {
*/
export const getPlayerStats = query(
v.object({
userId: v.pipe(v.number(), v.minValue(1)),
slug: v.pipe(v.string(), v.minLength(1)),
year: v.pipe(v.number(), v.minValue(2000), v.maxValue(new Date().getFullYear()))
}),
async ({ userId, year }) => {
const key = makeRecapStatsKey(year, userId);
async ({ slug, year }) => {
const key = makeRecapStatsKey(year, slug);

if (env.ALLOW_CACHING === 'true') {
const cached = await redis.get(key);
Expand All @@ -169,7 +169,7 @@ export const getPlayerStats = query(
// Get userinfo
const {
data: { user }
} = await fetchStartGG(getUserInfo, { userId: userId.toString() });
} = await fetchStartGG(getUserInfo, { slug });
if (!user) error(404, 'User not found');

const userInfo = {
Expand All @@ -185,11 +185,15 @@ export const getPlayerStats = query(
}
};

const stringUserId = userId.toString();
const userId = user.id;

if (!userId) {
error(500, 'User ID not found');
}

// Get attended events
const eventsIds = await getThisYearEvents(stringUserId, year);
const events = await getEvents(stringUserId, eventsIds);
const eventsIds = await getThisYearEvents(slug, year);
const events = await getEvents(userId, eventsIds);

// Count the number of tournaments attended by month
const tournamentsByMonth = aggregateTournamentsByMonth(events);
Expand Down
18 changes: 9 additions & 9 deletions src/lib/server/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,26 @@ export const redis = new Redis(env.REDIS_URL as string, options);
/**
* Creates a key pointing to a video URL for the user's recap for a given year
* @param year year of the recap to get
* @param userId start.gg id of the player
* @param userSlug start.gg slug of the player
* @returns A redis key pointing to a recap URL for the user for a given year
*/
export const makeRecapUrlKey = (year: number, userId: number): string =>
`recap:url:${year}:${userId}`;
export const makeRecapUrlKey = (year: number, userSlug: string): string =>
`recap:url:${year}:${userSlug}`;

/**
* Creates a key pointing to a still image URL for the user's recap for a given year
* @param year year of the recap to get
* @param userId start.gg id of the player
* @param userSlug start.gg slug of the player
* @returns A redis key pointing to a recap still image URL for the user for a given year
*/
export const makeStillUrlKey = (year: number, userId: number): string =>
`recap:still:url:${year}:${userId}`;
export const makeStillUrlKey = (year: number, userSlug: string): string =>
`recap:still:url:${year}:${userSlug}`;

/**
* Creates a key pointing to stats for a user in a given year
* @param year year of the recap to get
* @param userId start.gg id of the player
* @param userSlug start.gg slug of the player
* @returns A redis key pointing to stats for a given player in a given year
*/
export const makeRecapStatsKey = (year: number, userId: number | '*'): string =>
`recap:stats:${year}:${userId}`;
export const makeRecapStatsKey = (year: number, userSlug: string | '*'): string =>
`recap:stats:${year}:${userSlug}`;
8 changes: 4 additions & 4 deletions src/lib/startgg/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ export function aggregateTournamentsByMonth(
* Given a user ID and a year, fetches all events for that user in the specified year.
* It retrieves the page info to determine the total number of pages and then fetches
* each page of events until it reaches events from previous years.
* @param userId The ID of the user whose events to fetch.
* @param slug The slug of the user whose events to fetch.
* @param year The year for which to fetch events.
* @returns An array of events for the specified user in the specified year.
*/
export const getThisYearEvents = async (userId: string, year: number) => {
export const getThisYearEvents = async (slug: string, year: number) => {
// Get page info
const tournamentEventsPageInfoRes = await fetchStartGG(getTournamentsEventsPageInfo, {
userId
slug
});

const pages = tournamentEventsPageInfoRes.data?.user?.events?.pageInfo?.totalPages;
Expand All @@ -97,7 +97,7 @@ export const getThisYearEvents = async (userId: string, year: number) => {
for (let page = 1; page <= pages && shouldContinue; page++) {
const paginatedTournamentsEventsRes = await fetchStartGG(getPaginatedTournamentsEventsStartAt, {
page: page,
userId
slug
});

const pageEvents = paginatedTournamentsEventsRes?.data.user?.events?.nodes || [];
Expand Down
21 changes: 12 additions & 9 deletions src/lib/startgg/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const searchPlayerByGamerTag = graphql(`
gamerTag
user {
id
slug
location {
country
}
Expand All @@ -33,17 +34,18 @@ export const searchPlayerByGamerTag = graphql(`
}
`);

export const getUserById = graphql(`
query GetUserById($id: ID!) {
user(id: $id) {
export const getUserBySlug = graphql(`
query GetUserBySlug($slug: String!) {
user(slug: $slug) {
id
slug
}
}
`);

export const getTournamentsEventsPageInfo = graphql(`
query GetTournamentsEventsPageInfo($userId: ID!) {
user(id: $userId) {
query GetTournamentsEventsPageInfo($slug: String!) {
user(slug: $slug) {
events(
query: {
filter: { videogameId: [1386], eventType: 1, past: true, upcoming: false }
Expand All @@ -60,8 +62,8 @@ export const getTournamentsEventsPageInfo = graphql(`
`);

export const getPaginatedTournamentsEventsStartAt = graphql(`
query GetTournamentsEventsStartAt($userId: ID!, $page: Int!) {
user(id: $userId) {
query GetTournamentsEventsStartAt($slug: String!, $page: Int!) {
user(slug: $slug) {
events(
query: {
filter: { videogameId: [1386], eventType: 1 }
Expand All @@ -79,8 +81,9 @@ export const getPaginatedTournamentsEventsStartAt = graphql(`
`);

export const getUserInfo = graphql(`
query GetUserInfo($userId: ID!) {
user(id: $userId) {
query GetUserInfo($slug: String!) {
user(slug: $slug) {
id
images(type: "profile") {
url
}
Expand Down
14 changes: 14 additions & 0 deletions src/routes/(app)/user/[slug]/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { fetchStartGG } from '$lib/startgg/fetch';
import { getUserBySlug } from '$lib/startgg/queries';
import { redirect } from '@sveltejs/kit';

export const load = async ({ params }) => {
const slug = `user/${params.slug}`;
const {
data: { user }
} = await fetchStartGG(getUserBySlug, { slug });

if (!user || !user.slug) throw redirect(302, '/');

return { userSlug: user.slug };
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
let renderingProgress = $state<number | undefined>(undefined);
let downloadButtonProps = $state<ButtonProps>();

let userId = $derived(data.userId);
let userSlug = $derived(data.userSlug);
let shareUrl = $derived(page.url.href);
let isDebug = $derived(page.url.searchParams.get('debug') === 'true');

Expand All @@ -47,7 +47,7 @@
// Trigger the render
const renderReq = await fetch(`/api/render`, {
method: 'POST',
body: JSON.stringify({ stats, userId, filename, year })
body: JSON.stringify({ stats, userSlug, filename, year })
});

if (!renderReq.ok) {
Expand All @@ -68,7 +68,7 @@
const checkProgress = async () => {
const progressReq = await fetch('/api/render/progress', {
method: 'POST',
body: JSON.stringify({ renderId, bucketName, userId: data.userId, year })
body: JSON.stringify({ renderId, bucketName, userSlug, year })
});

if (!progressReq.ok) {
Expand Down Expand Up @@ -116,7 +116,7 @@
try {
const renderReq = await fetch(`/api/render/still`, {
method: 'POST',
body: JSON.stringify({ stats, userId })
body: JSON.stringify({ stats, userSlug })
});

if (!renderReq.ok) {
Expand All @@ -136,7 +136,7 @@
</script>

<div class="content">
{#await getPlayerStats({ userId, year: 2025 })}
{#await getPlayerStats({ slug: userSlug, year: 2025 })}
<div class="loading-container">
<p class="heading">{m['recap.loading']({ year: YEAR })}</p>
</div>
Expand Down
13 changes: 0 additions & 13 deletions src/routes/(app)/user/[userId]/+page.server.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/routes/api/render/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'dotenv/config';
import { z } from 'zod';

const schema = z.object({
userId: z.number(),
userSlug: z.string(),
stats: mainSchema,
filename: z.string()
});
Expand All @@ -23,7 +23,7 @@ export const POST: RequestHandler = async ({ request }) => {
}

// Check if a render is not available
const key = makeRecapUrlKey(results.data.stats.thisIsMyRecapProps.year, results.data.userId);
const key = makeRecapUrlKey(results.data.stats.thisIsMyRecapProps.year, results.data.userSlug);
const videoUrl = await redis.get(key);
if (videoUrl) {
return json({
Expand Down
Loading