improve SEO on UGC, proxy S3
This commit is contained in:
parent
6e4e8a4eaa
commit
107c78a5f2
13 changed files with 149 additions and 34 deletions
|
|
@ -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"
|
||||
/>
|
||||
|
||||
|
|
|
|||
35
website/src/routes/api/proxy/s3/[...path]/+server.ts
Normal file
35
website/src/routes/api/proxy/s3/[...path]/+server.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
30
website/src/routes/coin/[coinSymbol]/+page.server.ts
Normal file
30
website/src/routes/coin/[coinSymbol]/+page.server.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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`}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
export async function load({ params }) {
|
||||
return {
|
||||
coinSymbol: params.coinSymbol
|
||||
};
|
||||
}
|
||||
31
website/src/routes/hopium/[id]/+page.server.ts
Normal file
31
website/src/routes/hopium/[id]/+page.server.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
|
||||
|
|
|
|||
27
website/src/routes/user/[username]/+page.server.ts
Normal file
27
website/src/routes/user/[username]/+page.server.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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`}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
export async function load({ params }) {
|
||||
return {
|
||||
username: params.username
|
||||
};
|
||||
}
|
||||
Reference in a new issue