improve SEO on UGC, proxy S3

This commit is contained in:
Face 2025-06-28 17:31:05 +03:00
parent 6e4e8a4eaa
commit 107c78a5f2
13 changed files with 149 additions and 34 deletions

View file

@ -5,7 +5,7 @@
title = 'Rugplay', 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.', 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', type = 'website',
image = '/rugplay.svg', image = '/apple-touch-icon.png',
imageAlt = 'Rugplay Logo', imageAlt = 'Rugplay Logo',
keywords = '', keywords = '',
author = 'Outpoot', author = 'Outpoot',

View file

@ -31,7 +31,7 @@ export function getTimeBasedGreeting(name: string): string {
export function getPublicUrl(key: string | null): string | null { export function getPublicUrl(key: string | null): string | null {
if (!key) return 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) { export function debounce(func: (...args: any[]) => void, wait: number) {

View file

@ -76,7 +76,7 @@
<SEO <SEO
title="Rugplay" title="Rugplay"
description="Experience realistic cryptocurrency trading simulation with AI-powered markets, rug pull mechanics, and virtual currencies. Learn crypto trading without financial risk in this educational trading game." description="A realistic crypto trading simulator that lets you experience the risks and mechanics of decentralized exchanges without real financial consequences. Create coins, trade with liquidity pools, and learn about 'rug pulls' in a... relatively safe environment :)"
keywords="crypto simulation game, trading practice game, rug pull simulation, virtual cryptocurrency game" keywords="crypto simulation game, trading practice game, rug pull simulation, virtual cryptocurrency game"
/> />

View file

@ -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');
}
}

View file

@ -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');
}
}

View file

@ -31,14 +31,14 @@
const { data } = $props(); const { data } = $props();
let coinSymbol = $derived(data.coinSymbol); let coinSymbol = $derived(data.coinSymbol);
let coin = $state<any>(null); let coin = $state(data.coin);
let loading = $state(true); let loading = $state(false);
let chartData = $state<any[]>([]); let chartData = $state(data.chartData);
let volumeData = $state<any[]>([]); let volumeData = $state(data.volumeData);
let userHolding = $state(0); let userHolding = $state(0);
let buyModalOpen = $state(false); let buyModalOpen = $state(false);
let sellModalOpen = $state(false); let sellModalOpen = $state(false);
let selectedTimeframe = $state('1m'); let selectedTimeframe = $state(data.timeframe || '1m');
let lastPriceUpdateTime = 0; let lastPriceUpdateTime = 0;
let shouldSignIn = $state(false); let shouldSignIn = $state(false);
@ -54,7 +54,6 @@
]; ];
onMount(async () => { onMount(async () => {
await loadCoinData();
await loadUserHolding(); await loadUserHolding();
websocketController.setCoin(coinSymbol.toUpperCase()); websocketController.setCoin(coinSymbol.toUpperCase());
@ -92,6 +91,7 @@
async function loadCoinData() { async function loadCoinData() {
try { try {
loading = true;
const response = await fetch(`/api/coin/${coinSymbol}?timeframe=${selectedTimeframe}`); const response = await fetch(`/api/coin/${coinSymbol}?timeframe=${selectedTimeframe}`);
if (!response.ok) { if (!response.ok) {
@ -274,7 +274,7 @@
1 1
); );
const processedChartData = chartData.map((candle) => { const processedChartData = chartData.map((candle: { open: any; close: any; high: number; low: number; }) => {
if (candle.open === candle.close) { if (candle.open === candle.close) {
const basePrice = candle.open; const basePrice = candle.open;
const variation = basePrice * 0.001; const variation = basePrice * 0.001;
@ -361,14 +361,14 @@
<SEO <SEO
title={coin title={coin
? `${coin.name} (*${coin.symbol}) - Rugplay` ? `${coin.name} (*${coin.symbol}) - Rugplay`
: `Loading ${coinSymbol.toUpperCase()} - Rugplay Game`} : `Loading ${coinSymbol.toUpperCase()} - Rugplay`}
description={coin description={coin
? `Trade ${coin.name} (*${coin.symbol}) in the Rugplay simulation game. Current price: $${formatPrice(coin.currentPrice)}, Market cap: ${formatMarketCap(coin.marketCap)}, 24h change: ${coin.change24h >= 0 ? '+' : ''}${coin.change24h.toFixed(2)}%.` ? `Trade ${coin.name} (*${coin.symbol}) in the Rugplay simulation game. Current price: $${formatPrice(coin.currentPrice)}, Market cap: ${formatMarketCap(coin.marketCap)}, 24h change: ${coin.change24h >= 0 ? '+' : ''}${coin.change24h.toFixed(2)}%.`
: `Virtual cryptocurrency trading page for ${coinSymbol.toUpperCase()} in the Rugplay simulation game.`} : `Virtual cryptocurrency trading page for ${coinSymbol.toUpperCase()} in the Rugplay simulation game.`}
keywords={coin keywords={coin
? `${coin.name} cryptocurrency game, *${coin.symbol} virtual trading, ${coin.symbol} price simulation, cryptocurrency trading game, virtual coin ${coin.symbol}` ? `${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`} : `${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`} imageAlt={coin ? `${coin.name} (${coin.symbol}) logo` : `${coinSymbol} cryptocurrency logo`}
/> />

View file

@ -1,5 +0,0 @@
export async function load({ params }) {
return {
coinSymbol: params.coinSymbol
};
}

View file

@ -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');
}
}

View file

@ -28,9 +28,10 @@
import type { PredictionQuestion } from '$lib/types/prediction'; import type { PredictionQuestion } from '$lib/types/prediction';
import HopiumQuestionSkeleton from '$lib/components/self/skeletons/HopiumQuestionSkeleton.svelte'; import HopiumQuestionSkeleton from '$lib/components/self/skeletons/HopiumQuestionSkeleton.svelte';
let question = $state<PredictionQuestion | null>(null); const { data } = $props();
let loading = $state(true); let question = $state(data.question);
let probabilityData = $state<any[]>([]); let loading = $state(false);
let probabilityData = $state(data.probabilityData);
// Betting form // Betting form
let betSide = $state<boolean>(true); let betSide = $state<boolean>(true);
@ -38,7 +39,7 @@
let customBetAmount = $state(''); let customBetAmount = $state('');
let userBalance = $derived($PORTFOLIO_SUMMARY ? $PORTFOLIO_SUMMARY.baseCurrencyBalance : 0); let userBalance = $derived($PORTFOLIO_SUMMARY ? $PORTFOLIO_SUMMARY.baseCurrencyBalance : 0);
let questionId = $derived(parseInt(page.params.id)); let questionId = $derived(data.questionId);
// Chart related // Chart related
let chartContainer = $state<HTMLDivElement>(); let chartContainer = $state<HTMLDivElement>();
@ -46,7 +47,6 @@
let lineSeries: any = null; let lineSeries: any = null;
onMount(() => { onMount(() => {
fetchQuestion();
if ($USER_DATA) { if ($USER_DATA) {
fetchPortfolioSummary(); fetchPortfolioSummary();
} }
@ -54,6 +54,7 @@
async function fetchQuestion() { async function fetchQuestion() {
try { try {
loading = true;
const response = await fetch(`/api/hopium/questions/${questionId}`); const response = await fetch(`/api/hopium/questions/${questionId}`);
if (response.ok) { if (response.ok) {
const result = await response.json(); const result = await response.json();

View file

@ -288,7 +288,6 @@
<SEO <SEO
title="Settings - Rugplay" title="Settings - Rugplay"
description="Manage your Rugplay account settings, profile information, audio preferences, and privacy options." description="Manage your Rugplay account settings, profile information, audio preferences, and privacy options."
noindex={true}
keywords="game account settings, profile settings game, privacy settings, audio settings game" keywords="game account settings, profile settings game, privacy settings, audio settings game"
/> />

View file

@ -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');
}
}

View file

@ -20,15 +20,14 @@
Activity Activity
} from 'lucide-svelte'; } from 'lucide-svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import type { UserProfileData } from '$lib/types/user-profile';
import { USER_DATA } from '$lib/stores/user-data'; import { USER_DATA } from '$lib/stores/user-data';
let { data } = $props(); let { data } = $props();
let username = $derived(data.username); let username = $derived(data.username);
let profileData = $state<UserProfileData | null>(null); let profileData = $state(data.profileData);
let recentTransactions = $state<any[]>([]); let recentTransactions = $state(data.recentTransactions);
let loading = $state(true); let loading = $state(false);
let previousUsername = $state<string | null>(null); let previousUsername = $state<string | null>(null);
@ -37,8 +36,11 @@
); );
onMount(async () => { onMount(async () => {
await fetchProfileData();
previousUsername = username; previousUsername = username;
if (isOwnProfile) {
await fetchTransactions();
}
}); });
$effect(() => { $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.` ? `${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.`} : `View @${username}'s profile and simulated trading activity in Rugplay - cryptocurrency trading simulation game platform.`}
type="profile" 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 imageAlt={profileData?.profile?.name
? `${profileData.profile.name}'s profile picture` ? `${profileData.profile.name}'s profile picture`
: `@${username}'s profile`} : `@${username}'s profile`}

View file

@ -1,5 +0,0 @@
export async function load({ params }) {
return {
username: params.username
};
}