This commit is contained in:
sbb45
2026-06-02 21:32:45 +05:00
parent b808816099
commit 90ecfc669a
13 changed files with 979 additions and 1163 deletions
+4 -7
View File
@@ -1,8 +1,5 @@
MYSQL_HOST=127.0.0.1
MYSQL_USER=root
MYSQL_PASSWORD=12345678
MYSQL_DATABASE=laravel9
MYSQL_PORT=3306
ADMIN_HASHED_PASSWORD="$2b$10$JQm79m.C64TWTwc7qmbJLOW2UnDLCUsL5e34eFb1MhRPqGBBxw0QK" ADMIN_HASHED_PASSWORD="$2b$10$JQm79m.C64TWTwc7qmbJLOW2UnDLCUsL5e34eFb1MhRPqGBBxw0QK"
JWT_SECRET=!^AqCAhL^XZP$f5cEtKhuo7@!96c%8gChuyJvUH JWT_SECRET=!^AqCAhL^XZP$f5cEtKhuo7@!96c%8gChuyJvUH
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=
+5
View File
@@ -40,3 +40,8 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
# SQLite database
/data/*.db
/data/*.db-shm
/data/*.db-wal
Binary file not shown.
+2 -25
View File
@@ -13,9 +13,7 @@ services:
- "3000" - "3000"
volumes: volumes:
- uploads_data:/app/uploads - uploads_data:/app/uploads
depends_on: - sqlite_data:/app/data
db:
condition: service_healthy
networks: networks:
- backend - backend
healthcheck: healthcheck:
@@ -45,30 +43,9 @@ services:
retries: 5 retries: 5
start_period: 10s start_period: 10s
db:
image: mysql:8.4
container_name: strike_arena_db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
expose:
- "3306"
volumes:
- db_data:/var/lib/mysql
- ./init/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- backend
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "--password=${MYSQL_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
volumes: volumes:
db_data:
uploads_data: uploads_data:
sqlite_data:
networks: networks:
backend: backend:
+130
View File
@@ -0,0 +1,130 @@
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS rooms (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
cpu TEXT,
ram TEXT,
gpu TEXT,
mouse TEXT,
keyboard TEXT,
headphones TEXT,
monitor TEXT,
bracket TEXT,
chair TEXT
);
INSERT OR IGNORE INTO rooms VALUES
(1,'solo','5 отдельных комнат с мощным железом для игры соло','intel i7-13700kf','kingston 16gX2=32G/3200','Asus dual4070 012g','Logitech superlight 2','Hyper X origins core','Hyper X Cloud II','ASUS ROG 380 Hz','NB Display stand','DX Racer Gladiator'),
(2,'DUO / TRIO','4 комнаты на двоих и 3 на троих','intel i5-14600kf','kingston 16gX2=32G/3200','MSI 4060 Ventus 2X','Logitech superlight 1','Hyper X origins core','Hyper X Cloud II','ASUS ROG 310 Hz','NB Display stand','DX Racer Gladiator'),
(3,'BOOTCAMP 5/6','2 буткэмпа на пять и 1 на шесть игроков','intel i5-12400f','kingston 16gX2=32G/3200','MSI 4060 Ventus 2X','Logitech G403','Hyper X origins core','Hyper X Cloud II','ASUS ROG 310 Hz','NB Display stand','DX Racer Gladiator'),
(4,'ps','Консольный зал','PlayStation 5','2x DualShock','LG 55','','','','','','');
CREATE TABLE IF NOT EXISTS faq (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT NOT NULL
);
INSERT OR IGNORE INTO faq VALUES
(1,'Можно ли загрузить свою игру?','Да, вы можете установить свою игру самостоятельно, однако после перезагрузки компьютера игра удалится. Если вам нужно долгосрочное использование игры, сообщите нам заранее, и мы поможем всё настроить.'),
(2,'Какая у вас версия игр?','Мы всегда используем только самые свежие и актуальные версии игр. Наши операторы регулярно следят за обновлениями.'),
(3,'Установлены ли CS-2, CS:GO, CS 1.6?','Да, у нас установлены все популярные версии Counter-Strike: CS-2, CS:GO и CS 1.6.'),
(5,'Сколько стоит комп в общем зале?','Общего зала у нас нет. Наш клуб является VIP-форматом и предлагает только приватные кабинки. Вы можете забронировать место в BootCamp 5/6, если вам комфортно играть рядом с другими игроками.'),
(6,'Можно ли распечатать документы?','Да, можно распечатать документы в чёрно-белом формате. Стоимость распечатки — 100 тг за одну сторону листа А4.'),
(7,'Указанная цена за 1 компьютер или за весь буткэмп?','Все цены указаны за одно игровое место (1 компьютер).'),
(8,'Почему такие высокие цены?','Наш клуб ориентирован на тех, кто ценит приватность, комфорт и тишину. Наши гости часто отмечают, что наши цены полностью оправданы высоким уровнем комфорта и качества обслуживания.'),
(9,'Как можно забронировать место?','Есть два способа бронирования:
Через администратора с предварительной оплатой 1000 тг за каждый компьютер.
Онлайн через регистрацию с дополнительной скидкой 10%.');
CREATE TABLE IF NOT EXISTS contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
value TEXT NOT NULL
);
INSERT OR IGNORE INTO contacts VALUES
(1,'phone','+7 700 062 8559'),
(2,'whatsapp','https://wa.me/77752608559'),
(3,'tg','https://t.me/StrikeArenaAstana'),
(4,'gis2','https://2gis.kz/astana/firm/70000001097382279'),
(5,'inst','https://www.instagram.com/strikearena_astana');
CREATE TABLE IF NOT EXISTS games (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
image_url TEXT,
platform TEXT DEFAULT 'pc'
);
INSERT OR IGNORE INTO games VALUES
(14,'Мир танков','image.png','pc'),
(15,'Sims4','image-1.png','pc'),
(16,'Pubg','image-2.png','pc'),
(17,'Generals zerohour','image-3.png','pc'),
(18,'Dota2','image-4.png','pc'),
(19,'Valorant','image-5.png','pc'),
(20,'CS2','image-6.png','pc'),
(21,'League of legends','image-7.png','pc'),
(22,'CS1.6','image-8.png','pc'),
(23,'Genshin Impact','image-9.png','pc'),
(24,'Call of duty','image-10.png','pc'),
(25,'War thunder','image-11.png','pc'),
(26,'Gta 5','image-12.png','pc'),
(27,'Warface','image-13.png','pc'),
(28,'InZoi','image-14.png','pc'),
(29,'Gta SA','image-15.png','pc'),
(30,'It takes two','image-16.png','pc'),
(31,'Heroes of might and magic','image-17.png','pc'),
(32,'Left4dead 2','image-18.png','pc'),
(33,'Frozen throne','image-19.png','pc'),
(34,'Overwatch 2','image-20.png','pc'),
(35,'Nfs carbon','image-21.png','pc'),
(36,'Fortnite','image-22.png','pc'),
(37,'Red dead redemption 2','image-23.png','pc');
CREATE TABLE IF NOT EXISTS prices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
game_type TEXT NOT NULL,
room_type TEXT NOT NULL,
day_type TEXT,
hour_1 REAL,
hour_3 REAL,
hour_5 REAL,
night REAL
);
INSERT OR IGNORE INTO prices VALUES
(1,'ПК','solo','ПН-ЧТ',2800,5600,8400,10000),
(2,'ПК','solo','ПТ-ВС',3000,6000,9000,11000),
(3,'ПК','duo','ПН-ЧТ',2500,5000,7500,9500),
(4,'ПК','duo','ПТ-ВС',2700,5400,8100,10500),
(5,'ПК','trio','ПН-ЧТ',2300,4600,6900,9000),
(6,'ПК','trio','ПТ-ВС',2500,5000,7500,9800),
(7,'ПК','five','ПН-ЧТ',2100,4200,6300,8500),
(8,'ПК','five','ПТ-ВС',2300,4600,6900,9200),
(9,'ПК','six','ПН-ЧТ',2000,4000,6000,8200),
(10,'ПК','six','ПТ-ВС',2200,4400,6600,9000),
(11,'PS','lounge','2000',4000,9000,9000,14000),
(13,'PS','vip_normal','2500',5000,10000,10000,15000),
(15,'PS','vip_big','3000',6000,11000,12000,17000);
CREATE TABLE IF NOT EXISTS bookings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
room TEXT NOT NULL,
date_time TEXT NOT NULL,
time_slot TEXT NOT NULL,
name TEXT NOT NULL,
game TEXT,
contact TEXT NOT NULL,
contact_method TEXT,
status TEXT DEFAULT 'pending',
created_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_date_room_time ON bookings (date_time, room, time_slot);
CREATE INDEX IF NOT EXISTS idx_status ON bookings (status);
+774 -1096
View File
File diff suppressed because it is too large Load Diff
+10 -5
View File
@@ -3,21 +3,21 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "NODE_OPTIONS='--localstorage-file=/tmp/next-dev-storage' next dev", "dev": "next dev",
"build": "NODE_OPTIONS='--localstorage-file=/tmp/next-build-storage' next build", "build": "next build",
"start": "NODE_OPTIONS='--localstorage-file=/tmp/next-start-storage' next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"postbuild": "next-sitemap" "postbuild": "next-sitemap"
}, },
"dependencies": { "dependencies": {
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
"better-sqlite3": "^12.10.0",
"bootstrap": "^5.3.6", "bootstrap": "^5.3.6",
"formidable": "^3.5.4", "formidable": "^3.5.4",
"framer-motion": "^12.10.1", "framer-motion": "^12.10.1",
"jose": "^6.0.11", "jose": "^6.0.11",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"mysql2": "^3.14.1", "next": "^15.5.19",
"next": "15.3.2",
"next-sitemap": "^4.2.3", "next-sitemap": "^4.2.3",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
@@ -25,15 +25,20 @@
"styled-components": "^6.1.17", "styled-components": "^6.1.17",
"zustand": "^5.0.4" "zustand": "^5.0.4"
}, },
"overrides": {
"postcss": "^8.5.10"
},
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
"@types/better-sqlite3": "^7.6.13",
"@types/formidable": "^3.4.5", "@types/formidable": "^3.4.5",
"@types/jsonwebtoken": "^9.0.9", "@types/jsonwebtoken": "^9.0.9",
"@types/node": "^22.15.17", "@types/node": "^22.15.17",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"@types/react-input-mask": "^3.0.6", "@types/react-input-mask": "^3.0.6",
"cross-env": "^10.1.0",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "15.3.2", "eslint-config-next": "15.3.2",
"typescript": "^5" "typescript": "^5"
+1 -1
View File
@@ -126,7 +126,7 @@ export async function POST(req: NextRequest) {
// Создаем бронирование // Создаем бронирование
const [result] = await bd.query( const [result] = await bd.query(
`INSERT INTO bookings (type, room, date_time, time_slot, name, game, contact, contact_method, status, created_at) `INSERT INTO bookings (type, room, date_time, time_slot, name, game, contact, contact_method, status, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`, VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
[type, room, dateTime, time, name, game || null, contact, contactMethod || null, status || 'pending'] [type, room, dateTime, time, name, game || null, contact, contactMethod || null, status || 'pending']
) as any[]; ) as any[];
+1 -2
View File
@@ -1,7 +1,6 @@
import { NextRequest, NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import { bd } from '@/lib/bd' import { bd } from '@/lib/bd'
import { verifyAdminToken } from '@/lib/verifyAdmin' import { verifyAdminToken } from '@/lib/verifyAdmin'
import type { ResultSetHeader, FieldPacket } from 'mysql2'
// GET — получить все записи // GET — получить все записи
export async function GET(req: NextRequest) { export async function GET(req: NextRequest) {
@@ -22,7 +21,7 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: 'Заполните все поля' }, { status: 400 }) return NextResponse.json({ error: 'Заполните все поля' }, { status: 400 })
} }
const [result]: [ResultSetHeader, FieldPacket[]] = await bd.query( const [result] = await bd.query(
'INSERT INTO faq (title, description) VALUES (?, ?)', 'INSERT INTO faq (title, description) VALUES (?, ?)',
[title, description] [title, description]
) )
+1 -2
View File
@@ -1,7 +1,6 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { verifyAdminToken } from '@/lib/verifyAdmin'; import { verifyAdminToken } from '@/lib/verifyAdmin';
import { bd } from '@/lib/bd'; import { bd } from '@/lib/bd';
import type { ResultSetHeader } from 'mysql2';
import fs from 'fs/promises'; import fs from 'fs/promises';
import path from 'path'; import path from 'path';
@@ -39,7 +38,7 @@ export async function POST(req: NextRequest) {
// сохраняем файл // сохраняем файл
await fs.writeFile(filePath, buffer); await fs.writeFile(filePath, buffer);
const [result] = await bd.query<ResultSetHeader>( const [result] = await bd.query(
'INSERT INTO games (title, image_url, platform) VALUES (?, ?, ?)', 'INSERT INTO games (title, image_url, platform) VALUES (?, ?, ?)',
[title, fileName, platform] [title, fileName, platform]
); );
+1 -1
View File
@@ -47,7 +47,7 @@ export async function POST(req: NextRequest) {
// Создаем бронирование // Создаем бронирование
const [result] = await bd.query( const [result] = await bd.query(
`INSERT INTO bookings (type, room, date_time, time_slot, name, game, contact, contact_method, status, created_at) `INSERT INTO bookings (type, room, date_time, time_slot, name, game, contact, contact_method, status, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', NOW())`, VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', datetime('now'))`,
[type, room, dateTime, time, name, game || null, contact, contactMethod || null] [type, room, dateTime, time, name, game || null, contact, contactMethod || null]
) as any[]; ) as any[];
-15
View File
@@ -3,7 +3,6 @@ import "./globals.css";
import "../styles/reset.css"; import "../styles/reset.css";
import React from "react"; import React from "react";
import StartupLoader from "@/components/StartupLoader"; import StartupLoader from "@/components/StartupLoader";
import Script from "next/script";
export const viewport = { export const viewport = {
themeColor: "#00ff90", themeColor: "#00ff90",
@@ -61,20 +60,6 @@ export default function RootLayout({
<body> <body>
{children} {children}
<StartupLoader /> <StartupLoader />
<Script
id="binotel-getcall"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
(function(d, w, s) {
var widgetHash = 'j2he8ic2huirt42fsxdj', gcw = d.createElement(s);
gcw.type = 'text/javascript'; gcw.async = true;
gcw.src = '//widgets.binotel.com/getcall/widgets/' + widgetHash + '.js';
var sn = d.getElementsByTagName(s)[0]; sn.parentNode.insertBefore(gcw, sn);
})(document, window, 'script');
`,
}}
/>
</body> </body>
</html> </html>
); );
+50 -9
View File
@@ -1,9 +1,50 @@
import mysql from 'mysql2/promise' import Database from 'better-sqlite3';
import fs from 'fs';
export const bd = mysql.createPool({ import path from 'path';
host: process.env.MYSQL_HOST,
user: process.env.MYSQL_USER, const DB_PATH = path.join(process.cwd(), 'data', 'strike-arena.db');
password: process.env.MYSQL_PASSWORD, const INIT_SQL = path.join(process.cwd(), 'init', 'init.sqlite.sql');
database: process.env.MYSQL_DATABASE,
charset: 'utf8mb4', function openDb(): Database.Database {
}) const dir = path.dirname(DB_PATH);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
const db = new Database(DB_PATH);
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
// Seed on first run (no tables yet)
const tables = db.prepare("SELECT count(*) as n FROM sqlite_master WHERE type='table'").get() as { n: number };
if (tables.n === 0 && fs.existsSync(INIT_SQL)) {
const sql = fs.readFileSync(INIT_SQL, 'utf-8');
db.exec(sql);
}
return db;
}
// Singleton: reuse across hot-reload in dev
declare global {
// eslint-disable-next-line no-var
var __sqlite_db: Database.Database | undefined;
}
const db: Database.Database = global.__sqlite_db ?? (global.__sqlite_db = openDb());
function isSelect(sql: string) {
return /^\s*(SELECT|PRAGMA|WITH)/i.test(sql.trim());
}
export const bd = {
query: async (sql: string, params: unknown[] = []): Promise<any[]> => {
const stmt = db.prepare(sql);
if (isSelect(sql)) {
return [stmt.all(...(params as any[]))];
}
const info = stmt.run(...(params as any[]));
return [{ insertId: info.lastInsertRowid, affectedRows: info.changes }];
},
execute: async (sql: string, params: unknown[] = []): Promise<any[]> => {
return bd.query(sql, params);
},
};