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

@ -76,7 +76,7 @@
<SEO
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"
/>

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();
let coinSymbol = $derived(data.coinSymbol);
let coin = $state<any>(null);
let loading = $state(true);
let chartData = $state<any[]>([]);
let volumeData = $state<any[]>([]);
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 @@
<SEO
title={coin
? `${coin.name} (*${coin.symbol}) - Rugplay`
: `Loading ${coinSymbol.toUpperCase()} - Rugplay Game`}
: `Loading ${coinSymbol.toUpperCase()} - Rugplay`}
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)}%.`
: `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`}
/>

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 HopiumQuestionSkeleton from '$lib/components/self/skeletons/HopiumQuestionSkeleton.svelte';
let question = $state<PredictionQuestion | null>(null);
let loading = $state(true);
let probabilityData = $state<any[]>([]);
const { data } = $props();
let question = $state(data.question);
let loading = $state(false);
let probabilityData = $state(data.probabilityData);
// Betting form
let betSide = $state<boolean>(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<HTMLDivElement>();
@ -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();

View file

@ -288,7 +288,6 @@
<SEO
title="Settings - Rugplay"
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"
/>

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
} 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<UserProfileData | null>(null);
let recentTransactions = $state<any[]>([]);
let loading = $state(true);
let profileData = $state(data.profileData);
let recentTransactions = $state(data.recentTransactions);
let loading = $state(false);
let previousUsername = $state<string | null>(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`}

View file

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