diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 35d0b8c..3bd366e 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -34,13 +34,7 @@ jobs: - name: Run yarn audit run: yarn audit --level high - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: javascript - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 docker-security: name: Docker Security Scan @@ -59,4 +53,3 @@ jobs: image-ref: 'quixotic:scan' format: 'sarif' output: 'trivy-results.sarif' - diff --git a/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl b/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl index e14eee3..3b4acf1 100644 Binary files a/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl and b/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl differ diff --git a/.serena/memories/layout_issue_analysis.md b/.serena/memories/layout_issue_analysis.md new file mode 100644 index 0000000..ecdc886 --- /dev/null +++ b/.serena/memories/layout_issue_analysis.md @@ -0,0 +1,31 @@ +# Layout Issue Analysis + +## Problem Description +User reports that the application layout is broken: +1. "Nothing found" notification shows always +2. Search field that was at bottom is now at top + +## Current Structure Analysis +Based on code review of public/index.html and public/script.ts: + +### HTML Structure (Current): +1. Search form (input + button) - at TOP +2. Welcome placeholder - initially visible +3. Loading spinner - hidden by default +4. Results list - hidden by default +5. No results placeholder - hidden by default + +### JavaScript Logic Issues Found: +1. **No Results always showing**: In `showNoResults()` method, the noResults element is made visible with `classList.remove('tg-hidden')` but there's no logic to hide it when new search starts or results are found. + +2. **Search field position**: The search form is structurally at the top in HTML, which matches user complaint. + +### Display Logic Flow: +- `showLoading()`: Hides welcome, shows loading, hides results, hides noResults +- `displayResults()`: Calls `hideLoading()`, shows results if any, otherwise calls `showNoResults()` +- `showNoResults()`: Calls `hideLoading()`, hides results, SHOWS noResults (but doesn't hide welcome) + +## Root Causes: +1. **NoResults visibility issue**: The noResults element is never hidden when starting a new search +2. **Welcome placeholder management**: Not properly hidden when showing noResults +3. **Search position**: Structurally at top, needs to be moved to bottom if that was the intended design \ No newline at end of file diff --git a/.serena/memories/telegram_token_fix.md b/.serena/memories/telegram_token_fix.md new file mode 100644 index 0000000..8320e1f --- /dev/null +++ b/.serena/memories/telegram_token_fix.md @@ -0,0 +1,18 @@ +# Telegram Bot Token Fix + +## Problem +Docker container was showing "⚠️ TELEGRAM_BOT_TOKEN not found or invalid - bot will not start" error despite the token being present in `.env.docker` file. + +## Root Cause +The `docker-compose.yml` file was missing the `env_file` configuration to load environment variables from `.env.docker`. + +## Solution +Added `env_file: - .env.docker` at the top level of docker-compose.yml to ensure all services load environment variables from the .env.docker file. + +## Files Modified +- `docker-compose.yml`: Added env_file configuration + +## Token Details +- Token is present in `.env.docker`: `8262100335:AAFUBycadhKwV4oWPtF_Uq3c_R95SfWUElM` +- WEB_APP_URL is configured: `https://quixy.uk` +- Environment variables are properly referenced in services \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 0d34124..5dcac30 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,8 @@ services: image: traefik:v3.0 container_name: quixotic-traefik restart: unless-stopped + env_file: + - .env.docker command: - --api.dashboard=true - --api.insecure=false @@ -37,6 +39,8 @@ services: image: postgres:15-alpine container_name: quixotic-postgres restart: unless-stopped + env_file: + - .env.docker environment: POSTGRES_DB: ${POSTGRES_DB:-quixotic} POSTGRES_USER: ${POSTGRES_USER:-quixotic} @@ -59,10 +63,13 @@ services: dockerfile: Dockerfile container_name: quixotic-app restart: unless-stopped + env_file: + - .env.docker environment: - NODE_ENV=production - PORT=3000 - DATABASE_URL=postgresql://${POSTGRES_USER:-quixotic}:${POSTGRES_PASSWORD:-quixotic123}@postgres:5432/${POSTGRES_DB:-quixotic} + - DATABASE_SSL=false volumes: - downloads:/app/downloads labels: diff --git a/public/index.html b/public/index.html index e4b9875..d65d72c 100644 --- a/public/index.html +++ b/public/index.html @@ -12,34 +12,28 @@
-
-
🎵
Найти музыку
-
Введите название песни или исполнителя для поиска на YouTube
+
Введите название песни или исполнителя для поиска на SoundCloud
- -
- -
+
Поиск музыки...
-
+
-
+
🔍
Ничего не найдено
Попробуйте изменить поисковый запрос
+
- \ No newline at end of file + diff --git a/public/script.ts b/public/script.ts index f3ec4aa..d241f22 100644 --- a/public/script.ts +++ b/public/script.ts @@ -110,6 +110,7 @@ class QuixoticApp { this.welcomePlaceholder.classList.add('tg-hidden'); this.loading.classList.remove('tg-hidden'); this.loading.classList.add('tg-spinner--visible'); + this.results.classList.add('tg-hidden'); this.results.classList.remove('tg-list--visible'); this.noResults.classList.add('tg-hidden'); this.searchBtn.disabled = true; @@ -147,12 +148,14 @@ class QuixoticApp {
`).join(''); + this.results.classList.remove('tg-hidden'); this.results.classList.add('tg-list--visible'); this.noResults.classList.add('tg-hidden'); } private showNoResults(): void { this.hideLoading(); + this.results.classList.add('tg-hidden'); this.results.classList.remove('tg-list--visible'); this.noResults.classList.remove('tg-hidden'); } diff --git a/public/style.css b/public/style.css index e413518..0d2f2ee 100644 --- a/public/style.css +++ b/public/style.css @@ -67,6 +67,7 @@ body { .tg-content { flex: 1; padding: var(--tg-spacing-lg); + padding-bottom: 140px; display: flex; flex-direction: column; gap: var(--tg-spacing-xl); @@ -74,9 +75,17 @@ body { /* Form components */ .tg-form { + position: fixed; + bottom: 0; + left: 0; + right: 0; display: flex; flex-direction: column; gap: var(--tg-spacing-md); + padding: var(--tg-spacing-lg); + background: var(--tg-color-bg); + border-top: 1px solid var(--tg-color-secondary-bg); + z-index: 100; } .tg-input-wrapper { diff --git a/src/bot.ts b/src/bot.ts index d37f218..06163a1 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -84,7 +84,7 @@ export class QuixoticBot { await this.bot.sendMessage(chatId, '🎵 Добро пожаловать в Quixotic!\n\n' + - 'Найди любую песню на YouTube и получи MP3 файл прямо в чат.\n\n' + + 'Найди любую песню на SoundCloud и получи MP3 файл прямо в чат.\n\n' + 'Нажми кнопку ниже, чтобы начать поиск:', { reply_markup: keyboard } ); @@ -98,7 +98,7 @@ export class QuixoticBot { this.bot.onText(/\/help/, async (msg: Message) => { const chatId = msg.chat.id; - const helpText = `🎵 *Quixotic - YouTube to MP3* + const helpText = `🎵 *Quixotic - SoundCloud to MP3* *Как пользоваться:* 1️⃣ Нажми кнопку "Открыть Quixotic" @@ -112,8 +112,8 @@ export class QuixoticBot { /history - История поиска *Возможности:* -✅ Поиск по YouTube -✅ Высокое качество MP3 (128kbps) +✅ Поиск по SoundCloud +✅ Высокое качество MP3 (192kbps) ✅ Быстрая конвертация ✅ История поиска`; @@ -218,19 +218,7 @@ export class QuixoticBot { } private async getSearchHistory(userId: number): Promise { - return new Promise((resolve, reject) => { - this.db['db'].all( - `SELECT query, created_at FROM search_history - WHERE user_id = ? - ORDER BY created_at DESC - LIMIT 10`, - [userId], - (err: Error | null, rows: SearchResult[]) => { - if (err) reject(err); - else resolve(rows || []); - } - ); - }); + return this.db.getSearchHistory(userId); } private async sendAudioFile(chatId: number, audioUrl: string, title: string): Promise { @@ -272,4 +260,4 @@ if (require.main === module) { } new QuixoticBot(token, webAppUrl); -} \ No newline at end of file +} diff --git a/src/database.ts b/src/database.ts index 0c47880..8c0a43c 100644 --- a/src/database.ts +++ b/src/database.ts @@ -1,4 +1,4 @@ -import { Pool, QueryResult } from 'pg'; +import { Pool } from 'pg'; interface TelegramUser { id: number; @@ -52,7 +52,7 @@ export class Database { await this.pool.query(`CREATE TABLE IF NOT EXISTS downloads ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id), - youtube_id TEXT NOT NULL, + track_id TEXT NOT NULL, title TEXT NOT NULL, file_path TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP @@ -85,10 +85,10 @@ export class Database { return result.rows[0].id; } - async addDownload(userId: number, youtubeId: string, title: string, filePath: string): Promise { + async addDownload(userId: number, trackId: string, title: string, filePath: string): Promise { const result = await this.pool.query( - 'INSERT INTO downloads (user_id, youtube_id, title, file_path) VALUES ($1, $2, $3, $4) RETURNING id', - [userId, youtubeId, title, filePath] + 'INSERT INTO downloads (user_id, track_id, title, file_path) VALUES ($1, $2, $3, $4) RETURNING id', + [userId, trackId, title, filePath] ); return result.rows[0].id; } @@ -101,7 +101,15 @@ export class Database { return result.rows[0] || undefined; } + async getSearchHistory(userId: number, limit: number = 10): Promise<{query: string, created_at: string}[]> { + const result = await this.pool.query( + 'SELECT query, created_at FROM search_history WHERE user_id = $1 ORDER BY created_at DESC LIMIT $2', + [userId, limit] + ); + return result.rows; + } + async close(): Promise { await this.pool.end(); } -} \ No newline at end of file +} diff --git a/src/server.ts b/src/server.ts index 8d7a2b0..a6b8744 100644 --- a/src/server.ts +++ b/src/server.ts @@ -198,8 +198,8 @@ app.get('/health', (req: Request, res: Response) => { }); // Error handler -app.use((err: Error, req: Request, res: Response, next: any) => { - console.error(err.stack); +app.use((_err: Error, _req: Request, res: Response, _next: any) => { + console.error(_err.stack); res.status(500).json({ error: 'Something went wrong!' }); }); @@ -238,11 +238,16 @@ app.listen(port, () => { const botToken = process.env.TELEGRAM_BOT_TOKEN; const webAppUrl = process.env.WEB_APP_URL || `http://localhost:${port}`; -if (botToken) { - const bot = new QuixoticBot(botToken, webAppUrl); - console.log('🤖 Telegram bot started'); +if (botToken && botToken.length > 10) { + try { + new QuixoticBot(botToken, webAppUrl); + console.log('🤖 Telegram bot started'); + } catch (error: any) { + console.error('❌ Bot initialization failed:', error.message); + console.warn('⚠️ Bot disabled due to error'); + } } else { - console.warn('⚠️ TELEGRAM_BOT_TOKEN not found - bot will not start'); + console.warn('⚠️ TELEGRAM_BOT_TOKEN not found or invalid - bot will not start'); } -export default app; \ No newline at end of file +export default app;