From 107c78a5f276c7a0aaa180731a2e650d492b8f0c Mon Sep 17 00:00:00 2001 From: Face <69168154+face-hh@users.noreply.github.com> Date: Sat, 28 Jun 2025 17:31:05 +0300 Subject: [PATCH] improve SEO on UGC, proxy S3 --- website/src/lib/components/self/SEO.svelte | 2 +- website/src/lib/utils.ts | 2 +- website/src/routes/+page.svelte | 2 +- .../routes/api/proxy/s3/[...path]/+server.ts | 35 +++++++++++++++++++ .../routes/coin/[coinSymbol]/+page.server.ts | 30 ++++++++++++++++ .../src/routes/coin/[coinSymbol]/+page.svelte | 18 +++++----- website/src/routes/coin/[coinSymbol]/+page.ts | 5 --- .../src/routes/hopium/[id]/+page.server.ts | 31 ++++++++++++++++ website/src/routes/hopium/[id]/+page.svelte | 11 +++--- website/src/routes/settings/+page.svelte | 1 - .../routes/user/[username]/+page.server.ts | 27 ++++++++++++++ .../src/routes/user/[username]/+page.svelte | 14 ++++---- website/src/routes/user/[username]/+page.ts | 5 --- 13 files changed, 149 insertions(+), 34 deletions(-) create mode 100644 website/src/routes/api/proxy/s3/[...path]/+server.ts create mode 100644 website/src/routes/coin/[coinSymbol]/+page.server.ts delete mode 100644 website/src/routes/coin/[coinSymbol]/+page.ts create mode 100644 website/src/routes/hopium/[id]/+page.server.ts create mode 100644 website/src/routes/user/[username]/+page.server.ts delete mode 100644 website/src/routes/user/[username]/+page.ts diff --git a/website/src/lib/components/self/SEO.svelte b/website/src/lib/components/self/SEO.svelte index fc1840f..3c07516 100644 --- a/website/src/lib/components/self/SEO.svelte +++ b/website/src/lib/components/self/SEO.svelte @@ -5,7 +5,7 @@ title = 'Rugplay', description = 'Experience realistic cryptocurrency trading simulation game with AI-powered markets, rug pull mechanics, and virtual currencies. Learn crypto trading without financial risk in this educational game.', type = 'website', - image = '/rugplay.svg', + image = '/apple-touch-icon.png', imageAlt = 'Rugplay Logo', keywords = '', author = 'Outpoot', diff --git a/website/src/lib/utils.ts b/website/src/lib/utils.ts index b5cee9c..cb8030a 100644 --- a/website/src/lib/utils.ts +++ b/website/src/lib/utils.ts @@ -31,7 +31,7 @@ export function getTimeBasedGreeting(name: string): string { export function getPublicUrl(key: string | null): string | null { if (!key) return null; - return `${PUBLIC_B2_ENDPOINT}/${PUBLIC_B2_BUCKET}/${key}`; + return `/api/proxy/s3/${key}`; } export function debounce(func: (...args: any[]) => void, wait: number) { diff --git a/website/src/routes/+page.svelte b/website/src/routes/+page.svelte index 0fee172..8ee3466 100644 --- a/website/src/routes/+page.svelte +++ b/website/src/routes/+page.svelte @@ -76,7 +76,7 @@ diff --git a/website/src/routes/api/proxy/s3/[...path]/+server.ts b/website/src/routes/api/proxy/s3/[...path]/+server.ts new file mode 100644 index 0000000..76f1ded --- /dev/null +++ b/website/src/routes/api/proxy/s3/[...path]/+server.ts @@ -0,0 +1,35 @@ +import { PUBLIC_B2_BUCKET, PUBLIC_B2_ENDPOINT } from "$env/static/public"; +import { error } from '@sveltejs/kit'; + +export async function GET({ params, request }) { + const path = params.path; + + if (!path) { + throw error(400, 'Path is required'); + } + + try { + const s3Url = `${PUBLIC_B2_ENDPOINT}/${PUBLIC_B2_BUCKET}/${path}`; + const response = await fetch(s3Url); + + if (!response.ok) { + throw error(response.status, 'Failed to fetch from S3'); + } + + const contentType = response.headers.get('content-type') || 'application/octet-stream'; + const buffer = await response.arrayBuffer(); + + return new Response(buffer, { + headers: { + 'Content-Type': contentType, + 'Cache-Control': 'public, max-age=60', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET', + 'Access-Control-Allow-Headers': 'Content-Type' + } + }); + } catch (e) { + console.error('Proxy error:', e); + throw error(500, 'Failed to proxy S3 request'); + } +} diff --git a/website/src/routes/coin/[coinSymbol]/+page.server.ts b/website/src/routes/coin/[coinSymbol]/+page.server.ts new file mode 100644 index 0000000..eead4c5 --- /dev/null +++ b/website/src/routes/coin/[coinSymbol]/+page.server.ts @@ -0,0 +1,30 @@ +import { error } from '@sveltejs/kit'; + +export async function load({ params, url, fetch }) { + const { coinSymbol } = params; + const timeframe = url.searchParams.get('timeframe') || '1m'; + + try { + const response = await fetch(`/api/coin/${coinSymbol}?timeframe=${timeframe}`); + + if (!response.ok) { + if (response.status === 404) { + throw error(404, 'Coin not found'); + } + throw error(500, 'Failed to load coin data'); + } + + const result = await response.json(); + + return { + coinSymbol, + coin: result.coin, + chartData: result.candlestickData || [], + volumeData: result.volumeData || [], + timeframe + }; + } catch (e) { + console.error('Failed to fetch coin data:', e); + throw error(500, 'Failed to load coin data'); + } +} diff --git a/website/src/routes/coin/[coinSymbol]/+page.svelte b/website/src/routes/coin/[coinSymbol]/+page.svelte index a99c394..8e0ddaf 100644 --- a/website/src/routes/coin/[coinSymbol]/+page.svelte +++ b/website/src/routes/coin/[coinSymbol]/+page.svelte @@ -31,14 +31,14 @@ const { data } = $props(); let coinSymbol = $derived(data.coinSymbol); - let coin = $state(null); - let loading = $state(true); - let chartData = $state([]); - let volumeData = $state([]); + let coin = $state(data.coin); + let loading = $state(false); + let chartData = $state(data.chartData); + let volumeData = $state(data.volumeData); let userHolding = $state(0); let buyModalOpen = $state(false); let sellModalOpen = $state(false); - let selectedTimeframe = $state('1m'); + let selectedTimeframe = $state(data.timeframe || '1m'); let lastPriceUpdateTime = 0; let shouldSignIn = $state(false); @@ -54,7 +54,6 @@ ]; onMount(async () => { - await loadCoinData(); await loadUserHolding(); websocketController.setCoin(coinSymbol.toUpperCase()); @@ -92,6 +91,7 @@ async function loadCoinData() { try { + loading = true; const response = await fetch(`/api/coin/${coinSymbol}?timeframe=${selectedTimeframe}`); if (!response.ok) { @@ -274,7 +274,7 @@ 1 ); - const processedChartData = chartData.map((candle) => { + const processedChartData = chartData.map((candle: { open: any; close: any; high: number; low: number; }) => { if (candle.open === candle.close) { const basePrice = candle.open; const variation = basePrice * 0.001; @@ -361,14 +361,14 @@ = 0 ? '+' : ''}${coin.change24h.toFixed(2)}%.` : `Virtual cryptocurrency trading page for ${coinSymbol.toUpperCase()} in the Rugplay simulation game.`} keywords={coin ? `${coin.name} cryptocurrency game, *${coin.symbol} virtual trading, ${coin.symbol} price simulation, cryptocurrency trading game, virtual coin ${coin.symbol}` : `${coinSymbol} virtual cryptocurrency, crypto trading simulation, virtual coin trading`} - image={coin?.icon ? getPublicUrl(coin.icon) : '/rugplay.svg'} + image={coin?.icon ? getPublicUrl(coin.icon) : '/apple-touch-icon.png'} imageAlt={coin ? `${coin.name} (${coin.symbol}) logo` : `${coinSymbol} cryptocurrency logo`} /> diff --git a/website/src/routes/coin/[coinSymbol]/+page.ts b/website/src/routes/coin/[coinSymbol]/+page.ts deleted file mode 100644 index 061fb84..0000000 --- a/website/src/routes/coin/[coinSymbol]/+page.ts +++ /dev/null @@ -1,5 +0,0 @@ -export async function load({ params }) { - return { - coinSymbol: params.coinSymbol - }; -} diff --git a/website/src/routes/hopium/[id]/+page.server.ts b/website/src/routes/hopium/[id]/+page.server.ts new file mode 100644 index 0000000..a92a3d0 --- /dev/null +++ b/website/src/routes/hopium/[id]/+page.server.ts @@ -0,0 +1,31 @@ +import { error } from '@sveltejs/kit'; + +export async function load({ params, fetch }) { + const questionId = parseInt(params.id); + + if (isNaN(questionId)) { + throw error(400, 'Invalid question ID'); + } + + try { + const response = await fetch(`/api/hopium/questions/${questionId}`); + + if (!response.ok) { + if (response.status === 404) { + throw error(404, 'Question not found'); + } + throw error(500, 'Failed to load question'); + } + + const result = await response.json(); + + return { + questionId, + question: result.question || result, + probabilityData: result.probabilityHistory || [] + }; + } catch (e) { + console.error('Failed to fetch question:', e); + throw error(500, 'Failed to load question'); + } +} diff --git a/website/src/routes/hopium/[id]/+page.svelte b/website/src/routes/hopium/[id]/+page.svelte index bc033bd..2e54c3a 100644 --- a/website/src/routes/hopium/[id]/+page.svelte +++ b/website/src/routes/hopium/[id]/+page.svelte @@ -28,9 +28,10 @@ import type { PredictionQuestion } from '$lib/types/prediction'; import HopiumQuestionSkeleton from '$lib/components/self/skeletons/HopiumQuestionSkeleton.svelte'; - let question = $state(null); - let loading = $state(true); - let probabilityData = $state([]); + const { data } = $props(); + let question = $state(data.question); + let loading = $state(false); + let probabilityData = $state(data.probabilityData); // Betting form let betSide = $state(true); @@ -38,7 +39,7 @@ let customBetAmount = $state(''); let userBalance = $derived($PORTFOLIO_SUMMARY ? $PORTFOLIO_SUMMARY.baseCurrencyBalance : 0); - let questionId = $derived(parseInt(page.params.id)); + let questionId = $derived(data.questionId); // Chart related let chartContainer = $state(); @@ -46,7 +47,6 @@ let lineSeries: any = null; onMount(() => { - fetchQuestion(); if ($USER_DATA) { fetchPortfolioSummary(); } @@ -54,6 +54,7 @@ async function fetchQuestion() { try { + loading = true; const response = await fetch(`/api/hopium/questions/${questionId}`); if (response.ok) { const result = await response.json(); diff --git a/website/src/routes/settings/+page.svelte b/website/src/routes/settings/+page.svelte index d60e9db..24f13f6 100644 --- a/website/src/routes/settings/+page.svelte +++ b/website/src/routes/settings/+page.svelte @@ -288,7 +288,6 @@ diff --git a/website/src/routes/user/[username]/+page.server.ts b/website/src/routes/user/[username]/+page.server.ts new file mode 100644 index 0000000..b0f5bc3 --- /dev/null +++ b/website/src/routes/user/[username]/+page.server.ts @@ -0,0 +1,27 @@ +import { error } from '@sveltejs/kit'; + +export async function load({ params, fetch }) { + const { username } = params; + + try { + const response = await fetch(`/api/user/${username}`); + + if (!response.ok) { + if (response.status === 404) { + throw error(404, 'User not found'); + } + throw error(500, 'Failed to load user profile'); + } + + const profileData = await response.json(); + + return { + username, + profileData, + recentTransactions: profileData.recentTransactions || [] + }; + } catch (e) { + console.error('Failed to fetch user profile:', e); + throw error(500, 'Failed to load user profile'); + } +} diff --git a/website/src/routes/user/[username]/+page.svelte b/website/src/routes/user/[username]/+page.svelte index 9b6c686..7737d26 100644 --- a/website/src/routes/user/[username]/+page.svelte +++ b/website/src/routes/user/[username]/+page.svelte @@ -20,15 +20,14 @@ Activity } from 'lucide-svelte'; import { goto } from '$app/navigation'; - import type { UserProfileData } from '$lib/types/user-profile'; import { USER_DATA } from '$lib/stores/user-data'; let { data } = $props(); let username = $derived(data.username); - let profileData = $state(null); - let recentTransactions = $state([]); - let loading = $state(true); + let profileData = $state(data.profileData); + let recentTransactions = $state(data.recentTransactions); + let loading = $state(false); let previousUsername = $state(null); @@ -37,8 +36,11 @@ ); onMount(async () => { - await fetchProfileData(); previousUsername = username; + + if (isOwnProfile) { + await fetchTransactions(); + } }); $effect(() => { @@ -350,7 +352,7 @@ ? `${profileData.profile.bio} - View ${profileData.profile.name}'s simulated trading activity and virtual portfolio in the Rugplay cryptocurrency simulation game.` : `View @${username}'s profile and simulated trading activity in Rugplay - cryptocurrency trading simulation game platform.`} type="profile" - image={profileData?.profile?.image ? getPublicUrl(profileData.profile.image) : '/rugplay.svg'} + image={profileData?.profile?.image ? getPublicUrl(profileData.profile.image) : '/apple-touch-icon.png'} imageAlt={profileData?.profile?.name ? `${profileData.profile.name}'s profile picture` : `@${username}'s profile`} diff --git a/website/src/routes/user/[username]/+page.ts b/website/src/routes/user/[username]/+page.ts deleted file mode 100644 index 253a906..0000000 --- a/website/src/routes/user/[username]/+page.ts +++ /dev/null @@ -1,5 +0,0 @@ -export async function load({ params }) { - return { - username: params.username - }; -}