game and maybe database fix
This commit is contained in:
@@ -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"
|
|
||||||
@@ -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",
|
||||||
|
|||||||
Generated
+8
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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
@@ -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?;
|
||||||
|
|
||||||
// Инициализируем схемы через репозиторий
|
// Инициализируем схемы через репозиторий
|
||||||
|
|||||||
Reference in New Issue
Block a user