game and maybe database fix

This commit is contained in:
2026-04-23 14:39:21 +07:00
parent efcfcd6c6e
commit 963eb01357
7 changed files with 56 additions and 53 deletions
-50
View File
@@ -1,50 +0,0 @@
-include .env.prod
export
VPS_USER ?= root
VPS_IP ?= 77.110.106.113
DEPLOY_DIR ?= /root/backend
.PHONY: all build deploy restart logs
all: build deploy restart
# Собираем всё локально перед отправкой
build:
@echo "🌐 Собираем фронт..."
cd frontend && pnpm build
@echo "🦀 Собираем бэк под Linux..."
# Если ты на Mac/Win, используй кросс-компиляцию или собирай внутри Docker
cargo build --release
deploy:
@echo "🚀 [3/4] Подготовка структуры и деплой на $(VPS_IP)..."
# Создаем папки
ssh $(VPS_USER)@$(VPS_IP) "mkdir -p $(DEPLOY_DIR)/target/release"
# 1. ОТПРАВЛЯЕМ КОНФИГ
scp .env.prod $(VPS_USER)@$(VPS_IP):$(DEPLOY_DIR)/.env
# 2. ОТПРАВЛЯЕМ ПРОЕКТ
# Сначала все исключения, потом ОТКУДА (./) и КУДА (ssh...)
rsync -avz --delete \
--exclude 'target' \
--exclude 'node_modules' \
--exclude '.git' \
--exclude 'frontend/src' \
--exclude '.env*' \
./ $(VPS_USER)@$(VPS_IP):$(DEPLOY_DIR)/
# 3. ОТПРАВЛЯЕМ БИНАРНИК
rsync -avz target/release/$(BINARY_NAME) $(VPS_USER)@$(VPS_IP):$(DEPLOY_DIR)/target/release/
ssh $(VPS_USER)@$(VPS_IP) "chmod +x $(DEPLOY_DIR)/target/release/$(BINARY_NAME)"
restart:
@echo "🔄 [4/4] Перезапуск с полной очисткой кэша..."
ssh $(VPS_USER)@$(VPS_IP) "cd $(DEPLOY_DIR) && docker compose down && docker rmi backend-app:latest || true"
ssh $(VPS_USER)@$(VPS_IP) "cd $(DEPLOY_DIR) && docker compose up -d --build"
logs:
ssh $(VPS_USER)@$(VPS_IP) "cd $(DEPLOY_DIR) && docker compose logs -f app"
+1
View File
@@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cyberhack": "^1.0.0",
"framer-motion": "^12.38.0", "framer-motion": "^12.38.0",
"lucide-react": "^1.8.0", "lucide-react": "^1.8.0",
"react": "^19.2.4", "react": "^19.2.4",
+8
View File
@@ -11,6 +11,9 @@ importers:
clsx: clsx:
specifier: ^2.1.1 specifier: ^2.1.1
version: 2.1.1 version: 2.1.1
cyberhack:
specifier: ^1.0.0
version: 1.0.0
framer-motion: framer-motion:
specifier: ^12.38.0 specifier: ^12.38.0
version: 12.38.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) version: 12.38.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
@@ -721,6 +724,9 @@ packages:
csstype@3.2.3: csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
cyberhack@1.0.0:
resolution: {integrity: sha512-6NHeZzzhHzCv3U3PotO1ntSEFgIkUn6+b3qm3+vV3Tiwhfms/jjmF4DHYNdOenCYhKUOyU2jL1DdHwdP1EkL/Q==}
debug@4.4.3: debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'} engines: {node: '>=6.0'}
@@ -1910,6 +1916,8 @@ snapshots:
csstype@3.2.3: {} csstype@3.2.3: {}
cyberhack@1.0.0: {}
debug@4.4.3: debug@4.4.3:
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
+3 -1
View File
@@ -1,7 +1,8 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { BrowserRouter, Routes, Route, useNavigate } from "react-router-dom"; import { BrowserRouter, Routes, Route, useNavigate } from "react-router-dom";
import Profile from "./Profile"; import Profile from "./Profile";
import { TOKEN_KEY } from "./api"; // <-- Импортируем константу import HackGame from "./HackGame"; // <-- ИМПОРТ НОВОЙ СТРАНИЦЫ
import { TOKEN_KEY } from "./api";
function AuthHandler({ children }: { children: React.ReactNode }) { function AuthHandler({ children }: { children: React.ReactNode }) {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -33,6 +34,7 @@ export default function App() {
<Routes> <Routes>
<Route path="/" element={<Profile />} /> <Route path="/" element={<Profile />} />
<Route path="/profile" element={<Profile />} /> <Route path="/profile" element={<Profile />} />
<Route path="/hack" element={<HackGame />} /> {/* <-- НОВЫЙ РОУТ */}
</Routes> </Routes>
</AuthHandler> </AuthHandler>
</BrowserRouter> </BrowserRouter>
+32
View File
@@ -0,0 +1,32 @@
import { useNavigate } from "react-router-dom";
import { Cyberhack, type CyberhackResult } from "cyberhack";
export default function HackGame() {
const navigate = useNavigate();
// WASM сам сделает location.assign(redirectUrl) после завершения,
// но на всякий случай оставим onComplete, если захочешь логировать в будущем
const handleComplete = (result: CyberhackResult) => {
console.log("Взлом завершен:", result);
};
return (
<div className="fixed inset-0 z-50 bg-black flex items-center justify-center">
{/* Кнопка отмены, если юзер передумал взламывать */}
<button
onClick={() => navigate("/profile")}
className="absolute top-4 right-4 text-muted-foreground hover:text-white z-50 text-xs uppercase tracking-widest border border-white/10 px-3 py-1 rounded transition-colors"
>
[Abort Sequence]
</button>
<Cyberhack
timeLimit={45}
baseValue={50}
// WASM перенаправит сюда: /profile?coins=XXX
redirectUrl="/profile"
locale={(localStorage.getItem("lang") as "en" | "ru") || "en"}
onComplete={handleComplete}
/>
</div>
);
}
+11 -1
View File
@@ -10,9 +10,10 @@ import {
Activity, Activity,
CheckCircle2, CheckCircle2,
Globe, Globe,
Terminal,
} from "lucide-react"; } from "lucide-react";
import { fetchProfile } from "./api"; import { fetchProfile } from "./api";
import { useNavigate } from "react-router-dom";
// --- ЛОКАЛИЗАЦИЯ И СТАТИКА --- // --- ЛОКАЛИЗАЦИЯ И СТАТИКА ---
const DICT = { const DICT = {
en: { en: {
@@ -93,6 +94,7 @@ export default function Profile() {
const [error, setError] = useState<string | null>(null); // <-- ДОБАВЛЕНО const [error, setError] = useState<string | null>(null); // <-- ДОБАВЛЕНО
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const [showPlans, setShowPlans] = useState(false); const [showPlans, setShowPlans] = useState(false);
const navigate = useNavigate();
const [lang, setLang] = useState<"en" | "ru">( const [lang, setLang] = useState<"en" | "ru">(
(localStorage.getItem("lang") as "en" | "ru") || "en", (localStorage.getItem("lang") as "en" | "ru") || "en",
@@ -239,6 +241,14 @@ export default function Profile() {
</div> </div>
</div> </div>
<button
onClick={() => navigate("/hack")}
className="w-full relative z-10 bg-primary/10 hover:bg-primary/30 border border-primary/50 text-primary font-bold tracking-widest uppercase p-4 rounded-3xl transition-all flex justify-center items-center gap-3 shadow-[0_0_20px_rgba(139,61,255,0.15)] hover:shadow-[0_0_30px_rgba(139,61,255,0.4)] group overflow-hidden"
>
<div className="absolute inset-0 bg-[linear-gradient(45deg,transparent_25%,rgba(139,61,255,0.2)_50%,transparent_75%)] bg-[length:250%_250%,100%_100%] animate-[shimmer_2s_infinite] opacity-0 group-hover:opacity-100" />
<Terminal className="w-5 h-5 group-hover:animate-pulse relative z-10" />
<span className="relative z-10">Run Breach Protocol</span>
</button>
{/* SUBSCRIPTIONS */} {/* SUBSCRIPTIONS */}
<div className="bg-white/[0.03] backdrop-blur-xl border border-white/10 rounded-3xl p-6 shadow-[0_8px_32px_0_rgba(0,0,0,0.3)]"> <div className="bg-white/[0.03] backdrop-blur-xl border border-white/10 rounded-3xl p-6 shadow-[0_8px_32px_0_rgba(0,0,0,0.3)]">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
+1 -1
View File
@@ -34,7 +34,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let pool = PgPoolOptions::new() let pool = PgPoolOptions::new()
.max_connections(20) .max_connections(20)
.connect(&db_url) .connect(&db_url.trim())
.await?; .await?;
// Инициализируем схемы через репозиторий // Инициализируем схемы через репозиторий