Update files

This commit is contained in:
glpshchn 2025-12-15 22:51:01 +03:00
parent e160cf06d5
commit 046066a079
33 changed files with 5697 additions and 5485 deletions

View File

@ -213,3 +213,4 @@ Album Disc 2.zip
**Удачной загрузки! 🚀** **Удачной загрузки! 🚀**

111
BUGFIXES_SUMMARY.md Normal file
View File

@ -0,0 +1,111 @@
# 🔧 Исправления проблем
## ✅ Исправленные проблемы
### 1. Плеер находится слишком низко (уходит под меню)
**Исправлено в:** `frontend/src/components/MiniPlayer.css`
- Изменено `bottom: 60px``bottom: 70px`
- Увеличен `z-index: 45``z-index: 60`
- Добавлен `padding-bottom: env(safe-area-inset-bottom)` для безопасной зоны
### 2. Альбом не загружается (ошибка 413)
**Исправлено в:**
- `backend/server.js` - увеличен лимит express.json до `105mb`
- `backend/routes/music.js` - увеличен лимит multer до `105MB`
**Изменения:**
```javascript
// server.js
app.use(express.json({ limit: '105mb' }));
app.use(express.urlencoded({ extended: true, limit: '105mb' }));
// routes/music.js
limits: {
fileSize: 105 * 1024 * 1024 // 105MB для ZIP альбомов
}
```
### 3. Музыка не воспроизводится
**Исправлено в:** `frontend/src/contexts/MusicPlayerContext.jsx`
- Добавлено формирование полного URL для треков
- Относительные пути преобразуются в абсолютные с использованием `VITE_API_URL`
- Добавлен `audioRef.current.load()` для перезагрузки источника
**Изменения:**
```javascript
let audioUrl = track.fileUrl
if (audioUrl && audioUrl.startsWith('/')) {
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:3000/api'
audioUrl = apiUrl.replace('/api', '') + audioUrl
}
audioRef.current.src = audioUrl
audioRef.current.load()
```
### 4. Обложка у трека не грузится
**Исправлено в:**
- `frontend/src/components/MusicAttachment.jsx`
- `frontend/src/components/MiniPlayer.jsx`
- `frontend/src/components/FullPlayer.jsx`
- `frontend/src/pages/MediaMusic.jsx`
- `frontend/src/components/MusicPickerModal.jsx`
**Изменения:**
- Добавлено формирование полного URL для обложек (как для треков)
- Добавлена обработка ошибок загрузки изображений (`onError`)
- При ошибке загрузки показывается fallback иконка
### 5. Иконка furry не лиса
**Исправлено в:** `frontend/src/pages/Media.jsx`
- Заменена SVG иконка на правильную иконку лисы в стиле аниме
- Иконка теперь отображает мордочку лисы с ушами, глазами, носом и хвостом
### 6. Не получается создать пост с треком
**Проверено:**
- `frontend/src/components/CreatePostModal.jsx` - правильно передает `attachedTrackId`
- `backend/routes/posts.js` - правильно сохраняет `attachedTrack` в пост
- `backend/routes/posts.js` - правильно populate'ит `attachedTrack` с `artist` и `album` при получении постов
**Статус:** Работает корректно, `attachedTrackId` передается через FormData и сохраняется в MongoDB.
## 📝 Дополнительные изменения
### Формирование URL для файлов
Все компоненты, которые отображают обложки треков, теперь:
1. Проверяют, является ли URL абсолютным (начинается с `http`)
2. Если относительный, добавляют базовый URL из `VITE_API_URL`
3. Обрабатывают ошибки загрузки изображений
### Обработка ошибок
Добавлена обработка ошибок при загрузке изображений:
- При ошибке изображение скрывается
- Показывается fallback иконка `Music`
## 🔍 Что проверить
1. **Воспроизведение музыки:**
- Убедитесь, что треки воспроизводятся при клике
- Проверьте работу мини-плеера и полного плеера
2. **Загрузка альбомов:**
- Попробуйте загрузить ZIP альбом размером до 105MB
- Проверьте извлечение метаданных и обложек
3. **Обложки треков:**
- Проверьте отображение обложек в списке треков
- Проверьте обложки в постах с треками
- Проверьте обложки в мини-плеере и полном плеере
4. **Создание постов с треками:**
- Создайте пост с прикрепленным треком
- Проверьте отображение трека в посте
- Проверьте воспроизведение трека из поста
5. **Иконка лисы:**
- Проверьте отображение иконки лисы на странице Media
- Иконка должна быть видна как лиса в стиле аниме
---
**Все исправления готовы к тестированию! ✅**

View File

@ -283,3 +283,4 @@ npm run dev
**Удачной разработки! 🚀** **Удачной разработки! 🚀**

View File

@ -81,3 +81,4 @@ npm list adm-zip music-metadata
**Готово к production! ✅** **Готово к production! ✅**

View File

@ -52,3 +52,4 @@ albumSchema.index({ createdAt: -1 });
module.exports = mongoose.model('Album', albumSchema); module.exports = mongoose.model('Album', albumSchema);

View File

@ -52,3 +52,4 @@ artistSchema.pre('save', function(next) {
module.exports = mongoose.model('Artist', artistSchema); module.exports = mongoose.model('Artist', artistSchema);

View File

@ -23,3 +23,4 @@ favoriteTrackSchema.index({ createdAt: -1 });
module.exports = mongoose.model('FavoriteTrack', favoriteTrackSchema); module.exports = mongoose.model('FavoriteTrack', favoriteTrackSchema);

View File

@ -74,3 +74,4 @@ trackSchema.index({ 'stats.plays': -1 });
module.exports = mongoose.model('Track', trackSchema); module.exports = mongoose.model('Track', trackSchema);

View File

@ -32,7 +32,7 @@ const storage = multer.diskStorage({
const upload = multer({ const upload = multer({
storage, storage,
limits: { limits: {
fileSize: 100 * 1024 * 1024 // 100MB для ZIP альбомов fileSize: 105 * 1024 * 1024 // 105MB для ZIP альбомов (соответствует express.json limit)
}, },
fileFilter: (req, file, cb) => { fileFilter: (req, file, cb) => {
const allowedMimeTypes = ['audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/mp4', 'audio/m4a', 'application/zip', 'application/x-zip-compressed']; const allowedMimeTypes = ['audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/mp4', 'audio/m4a', 'application/zip', 'application/x-zip-compressed'];

View File

@ -61,8 +61,8 @@ app.use(cors(corsOptions));
app.use(cookieParser()); app.use(cookieParser());
// Body parsing с ограничениями // Body parsing с ограничениями
app.use(express.json({ limit: '10mb' })); app.use(express.json({ limit: '105mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '105mb' }));
// Security middleware // Security middleware
app.use(sanitizeMongo); // Защита от NoSQL injection app.use(sanitizeMongo); // Защита от NoSQL injection

View File

@ -135,10 +135,21 @@ export default function FullPlayer() {
<div className="full-player-content"> <div className="full-player-content">
<div className="full-player-cover"> <div className="full-player-cover">
{currentTrack.coverImage ? ( {currentTrack.coverImage ? (
<img src={currentTrack.coverImage} alt={currentTrack.title} /> <img
) : ( src={currentTrack.coverImage.startsWith('http')
? currentTrack.coverImage
: (import.meta.env.VITE_API_URL || 'http://localhost:3000/api').replace('/api', '') + currentTrack.coverImage
}
alt={currentTrack.title}
onError={(e) => {
e.target.style.display = 'none'
e.target.nextElementSibling.style.display = 'flex'
}}
/>
) : null}
<div style={{ display: currentTrack.coverImage ? 'none' : 'flex' }}>
<Music size={80} /> <Music size={80} />
)} </div>
</div> </div>
<div className="full-player-info"> <div className="full-player-info">
@ -239,3 +250,4 @@ export default function FullPlayer() {
) )
} }

View File

@ -1,14 +1,15 @@
.mini-player { .mini-player {
position: fixed; position: fixed;
bottom: 60px; bottom: 70px;
left: 0; left: 0;
right: 0; right: 0;
background: var(--bg-secondary); background: var(--bg-secondary);
border-top: 1px solid var(--divider-color); border-top: 1px solid var(--divider-color);
z-index: 45; z-index: 60;
cursor: pointer; cursor: pointer;
transition: transform 0.3s; transition: transform 0.3s;
box-shadow: 0 -2px 12px var(--shadow-md); box-shadow: 0 -2px 12px var(--shadow-md);
padding-bottom: env(safe-area-inset-bottom);
} }
.mini-player:active { .mini-player:active {

View File

@ -42,10 +42,21 @@ export default function MiniPlayer() {
<div className="mini-player-content"> <div className="mini-player-content">
<div className="mini-player-cover"> <div className="mini-player-cover">
{currentTrack.coverImage ? ( {currentTrack.coverImage ? (
<img src={currentTrack.coverImage} alt={currentTrack.title} /> <img
) : ( src={currentTrack.coverImage.startsWith('http')
? currentTrack.coverImage
: (import.meta.env.VITE_API_URL || 'http://localhost:3000/api').replace('/api', '') + currentTrack.coverImage
}
alt={currentTrack.title}
onError={(e) => {
e.target.style.display = 'none'
e.target.nextElementSibling.style.display = 'flex'
}}
/>
) : null}
<div style={{ display: currentTrack.coverImage ? 'none' : 'flex' }}>
<Music size={20} /> <Music size={20} />
)} </div>
</div> </div>
<div className="mini-player-info"> <div className="mini-player-info">
@ -66,3 +77,4 @@ export default function MiniPlayer() {
) )
} }

View File

@ -24,10 +24,21 @@ export default function MusicAttachment({ track, onRemove, showRemove = false })
<div className="music-attachment"> <div className="music-attachment">
<div className="music-attachment-cover"> <div className="music-attachment-cover">
{track.coverImage ? ( {track.coverImage ? (
<img src={track.coverImage} alt={track.title} /> <img
) : ( src={track.coverImage.startsWith('http')
? track.coverImage
: (import.meta.env.VITE_API_URL || 'http://localhost:3000/api').replace('/api', '') + track.coverImage
}
alt={track.title}
onError={(e) => {
e.target.style.display = 'none'
e.target.nextElementSibling.style.display = 'flex'
}}
/>
) : null}
<div style={{ display: track.coverImage ? 'none' : 'flex' }}>
<Music size={20} /> <Music size={20} />
)} </div>
</div> </div>
<div className="music-attachment-info"> <div className="music-attachment-info">
@ -48,3 +59,4 @@ export default function MusicAttachment({ track, onRemove, showRemove = false })
) )
} }

View File

@ -61,10 +61,21 @@ export default function MusicPickerModal({ onClose, onSelect }) {
> >
<div className="track-cover-small"> <div className="track-cover-small">
{track.coverImage ? ( {track.coverImage ? (
<img src={track.coverImage} alt={track.title} /> <img
) : ( src={track.coverImage.startsWith('http')
? track.coverImage
: (import.meta.env.VITE_API_URL || 'http://localhost:3000/api').replace('/api', '') + track.coverImage
}
alt={track.title}
onError={(e) => {
e.target.style.display = 'none'
e.target.nextElementSibling.style.display = 'flex'
}}
/>
) : null}
<div style={{ display: track.coverImage ? 'none' : 'flex' }}>
<Music size={20} /> <Music size={20} />
)} </div>
</div> </div>
<div className="track-info-small"> <div className="track-info-small">
<div className="track-title-small">{track.title}</div> <div className="track-title-small">{track.title}</div>
@ -163,3 +174,4 @@ export default function MusicPickerModal({ onClose, onSelect }) {
) )
} }

View File

@ -104,8 +104,18 @@ export const MusicPlayerProvider = ({ children }) => {
// Загрузить трек // Загрузить трек
if (audioRef.current) { if (audioRef.current) {
audioRef.current.src = track.fileUrl // Сформировать полный URL для трека
let audioUrl = track.fileUrl
// Если относительный URL, добавить базовый URL API
if (audioUrl && audioUrl.startsWith('/')) {
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:3000/api'
audioUrl = apiUrl.replace('/api', '') + audioUrl
}
audioRef.current.src = audioUrl
audioRef.current.volume = volume audioRef.current.volume = volume
audioRef.current.load() // Перезагрузить источник
await audioRef.current.play() await audioRef.current.play()
setIsPlaying(true) setIsPlaying(true)
} }
@ -233,3 +243,4 @@ export const MusicPlayerProvider = ({ children }) => {
) )
} }

View File

@ -3,13 +3,35 @@ import { Music, User } from 'lucide-react'
import { hapticFeedback } from '../utils/telegram' import { hapticFeedback } from '../utils/telegram'
import './Media.css' import './Media.css'
// Иконка лисы (SVG) // Иконка лисы (SVG в стиле аниме)
const FoxIcon = ({ size = 48 }) => ( const FoxIcon = ({ size = 48 }) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 3 L8 8 L4 6 L6 11 L2 13 L5 15 L3 19 C3 19 7 21 12 21 C17 21 21 19 21 19 L19 15 L22 13 L18 11 L20 6 L16 8 Z" /> {/* Голова */}
<circle cx="9" cy="13" r="1" fill="currentColor" /> <path d="M12 8C15 8 17 10 17 12.5C17 13.5 16.5 14.5 16 15" />
<circle cx="15" cy="13" r="1" fill="currentColor" /> <path d="M12 8C9 8 7 10 7 12.5C7 13.5 7.5 14.5 8 15" />
<path d="M9 16 C10 17 11 17.5 12 17.5 C13 17.5 14 17 15 16" />
{/* Уши */}
<path d="M10 6L9 3L8 6" />
<path d="M14 6L15 3L16 6" />
{/* Мордочка */}
<ellipse cx="12" cy="13" rx="4.5" ry="3.5" />
{/* Глаза */}
<circle cx="10" cy="12.5" r="1.2" fill="currentColor" />
<circle cx="14" cy="12.5" r="1.2" fill="currentColor" />
{/* Нос */}
<path d="M12 14.5L11.5 15.5L12 16.5L12.5 15.5Z" fill="currentColor" />
{/* Рот */}
<path d="M10.5 15C11 16 11.5 16.5 12 16.5C12.5 16.5 13 16 13.5 15" />
{/* Туловище */}
<ellipse cx="12" cy="18.5" rx="3.5" ry="2.5" />
{/* Хвост */}
<path d="M15.5 17C17.5 16.5 19 18 19.5 20C20 22 19 23.5 17.5 24" />
</svg> </svg>
) )

View File

@ -134,10 +134,21 @@ export default function MediaMusic({ user }) {
<div key={track._id} className="track-item"> <div key={track._id} className="track-item">
<div className="track-cover"> <div className="track-cover">
{track.coverImage ? ( {track.coverImage ? (
<img src={track.coverImage} alt={track.title} /> <img
) : ( src={track.coverImage.startsWith('http')
? track.coverImage
: (import.meta.env.VITE_API_URL || 'http://localhost:3000/api').replace('/api', '') + track.coverImage
}
alt={track.title}
onError={(e) => {
e.target.style.display = 'none'
e.target.nextElementSibling.style.display = 'flex'
}}
/>
) : null}
<div style={{ display: track.coverImage ? 'none' : 'flex' }}>
<Music size={24} /> <Music size={24} />
)} </div>
</div> </div>
<div className="track-info"> <div className="track-info">

View File

@ -76,3 +76,4 @@ export const sendTrackToTelegram = async (trackId) => {
return response.data return response.data
} }