commit e58e548f84495a06734c7697f28f096ba8489ca0
Author: serov <1@dmserov.ru>
Date:   Sun Feb 23 08:22:12 2025 +0000

    Добавить 1.py

diff --git a/1.py b/1.py
new file mode 100644
index 0000000..97887c1
--- /dev/null
+++ b/1.py
@@ -0,0 +1,1273 @@
+import os
+import subprocess
+import sys
+import json
+import shutil
+from pathlib import Path
+
+class ProjectSetup:
+    def __init__(self):
+        self.root_dir = Path.cwd()
+        self.backend_dir = self.root_dir / "backend"
+        self.frontend_dir = self.root_dir / "frontend"
+        self.venv_python = None
+        
+    def clean(self):
+        """Очистка старых файлов и директорий"""
+        print("Очистка проекта...")
+        if self.backend_dir.exists():
+            shutil.rmtree(self.backend_dir)
+        if self.frontend_dir.exists():
+            shutil.rmtree(self.frontend_dir)
+            
+    def create_backend(self):
+        """Настройка backend"""
+        print("\nНастройка backend...")
+        
+        # Создаем структуру директорий
+        app_dir = self.backend_dir / "app"
+        app_dir.mkdir(parents=True)
+        
+        # Путь к Python 3.11
+        python_path = "python.exe"  # Измените путь на ваш
+        
+        # Создаем виртуальное окружение с Python 3.11
+        print("Создание виртуального окружения...")
+        subprocess.run([python_path, "-m", "venv", str(self.backend_dir / "venv")], check=True)
+        
+        # Определяем путь к Python в виртуальном окружении
+        if sys.platform == "win32":
+            self.venv_python = self.backend_dir / "venv" / "Scripts" / "python.exe"
+        else:
+            self.venv_python = self.backend_dir / "venv" / "bin" / "python"
+            
+        # Обновляем pip
+        subprocess.run([str(self.venv_python), "-m", "pip", "install", "--upgrade", "pip"], check=True)
+        
+        # Создаем файлы проекта
+        self.create_backend_files()
+        
+        # Устанавливаем зависимости
+        print("Установка зависимостей...")
+        requirements_txt = self.backend_dir / "requirements.txt"
+        subprocess.run([str(self.venv_python), "-m", "pip", "install", "-r", str(requirements_txt)], check=True)
+        
+        # Инициализируем базу данных
+        self.init_database()
+        
+    def create_frontend(self):
+        """Настройка frontend"""
+        print("\nНастройка frontend...")
+        self.frontend_dir.mkdir(parents=True)
+        os.chdir(self.frontend_dir)
+        
+        # Инициализируем package.json
+        package_json = {
+            "name": "smart-player-frontend",
+            "private": True,
+            "version": "0.1.0",
+            "type": "module",
+            "scripts": {
+                "dev": "vite",
+                "build": "tsc && vite build",
+                "preview": "vite preview"
+            },
+            "dependencies": {
+                "@emotion/react": "^11.11.0",
+                "@emotion/styled": "^11.11.0",
+                "@mui/icons-material": "^5.11.16",
+                "@mui/material": "^5.13.0",
+                "axios": "^1.4.0",
+                "react": "^18.2.0",
+                "react-dom": "^18.2.0",
+                "react-router-dom": "^6.11.1",
+                "zustand": "^4.3.8"
+            },
+            "devDependencies": {
+                "@types/react": "^18.2.6",
+                "@types/react-dom": "^18.2.4",
+                "@vitejs/plugin-react": "^4.0.0",
+                "typescript": "^5.0.4",
+                "vite": "^4.3.5"
+            }
+        }
+        
+        with open("package.json", "w") as f:
+            json.dump(package_json, f, indent=2)
+            
+        # Устанавливаем зависимости
+        print("Установка npm пакетов...")
+        subprocess.run("npm install", shell=True, check=True)
+        
+        # Создаем файлы frontend
+        self.create_frontend_files()
+        
+    def create_backend_files(self):
+        """Создание файлов backend"""
+        files = {
+            self.backend_dir / "app" / "__init__.py": "",
+            
+            self.backend_dir / "app" / "config.py": '''
+from pydantic_settings import BaseSettings
+
+class Settings(BaseSettings):
+    DATABASE_URL: str = "sqlite:///./smartplayer.db"
+    SECRET_KEY: str = "your-super-secret-key-keep-it-safe"
+    ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
+    ALGORITHM: str = "HS256"
+
+    model_config = {
+        "env_file": ".env",
+        "env_file_encoding": "utf-8"
+    }
+
+settings = Settings()
+''',
+
+            self.backend_dir / "app" / "database.py": '''
+from sqlalchemy import create_engine
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+from .config import settings
+
+engine = create_engine(settings.DATABASE_URL, connect_args={"check_same_thread": False})
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+Base = declarative_base()
+
+def get_db():
+    db = SessionLocal()
+    try:
+        yield db
+    finally:
+        db.close()
+''',
+
+            self.backend_dir / "app" / "models.py": '''
+from sqlalchemy import Boolean, Column, Integer, String, DateTime, ForeignKey, Table, Enum
+import enum
+from sqlalchemy.orm import relationship
+from datetime import datetime
+from .database import Base
+
+class MediaType(str, enum.Enum):
+    AUDIO = "audio"
+    VIDEO = "video"
+    STREAM = "stream"
+
+# Таблица для связи many-to-many между плейлистами и медиа
+playlist_media = Table('playlist_media', Base.metadata,
+    Column('playlist_id', Integer, ForeignKey('playlists.id')),
+    Column('media_id', Integer, ForeignKey('media.id'))
+)
+
+class User(Base):
+    __tablename__ = "users"
+
+    id = Column(Integer, primary_key=True, index=True)
+    email = Column(String, unique=True, index=True)
+    username = Column(String, unique=True, index=True)
+    hashed_password = Column(String)
+    is_active = Column(Boolean, default=True)
+    is_admin = Column(Boolean, default=False)
+    created_at = Column(DateTime, default=datetime.utcnow)
+    
+    # Связи
+    playlists = relationship("Playlist", back_populates="owner")
+    media_items = relationship("Media", back_populates="owner")
+    streams = relationship("Stream", back_populates="owner")
+
+class Media(Base):
+    __tablename__ = "media"
+
+    id = Column(Integer, primary_key=True, index=True)
+    title = Column(String, index=True)
+    artist = Column(String, index=True)
+    duration = Column(Integer)  # Длительность в секундах
+    file_path = Column(String)
+    media_type = Column(Enum(MediaType))
+    thumbnail_path = Column(String, nullable=True)
+    created_at = Column(DateTime, default=datetime.utcnow)
+    owner_id = Column(Integer, ForeignKey("users.id"))
+
+    owner = relationship("User", back_populates="media_items")
+    playlists = relationship("Playlist", secondary=playlist_media, back_populates="media")
+
+class Stream(Base):
+    __tablename__ = "streams"
+
+    id = Column(Integer, primary_key=True, index=True)
+    title = Column(String, index=True)
+    description = Column(String, nullable=True)
+    stream_key = Column(String, unique=True)
+    is_live = Column(Boolean, default=False)
+    thumbnail_path = Column(String, nullable=True)
+    created_at = Column(DateTime, default=datetime.utcnow)
+    owner_id = Column(Integer, ForeignKey("users.id"))
+
+    owner = relationship("User", back_populates="streams")
+
+class Playlist(Base):
+    __tablename__ = "playlists"
+
+    id = Column(Integer, primary_key=True, index=True)
+    name = Column(String, index=True)
+    description = Column(String)
+    is_public = Column(Boolean, default=False)
+    created_at = Column(DateTime, default=datetime.utcnow)
+    owner_id = Column(Integer, ForeignKey("users.id"))
+
+    owner = relationship("User", back_populates="playlists")
+    media = relationship("Media", secondary=playlist_media, back_populates="playlists")
+''',
+
+            self.backend_dir / "app" / "schemas.py": '''
+from pydantic import BaseModel, EmailStr
+from typing import Optional, List
+from datetime import datetime
+from .models import MediaType
+
+class UserBase(BaseModel):
+    email: EmailStr
+    username: str
+
+class User(BaseModel):
+    id: int
+    is_active: bool
+    is_admin: bool
+    created_at: datetime
+
+    model_config = {
+        "from_attributes": True
+    }
+
+class Media(BaseModel):
+    id: int
+    file_path: str
+    created_at: datetime
+    owner_id: int
+
+    model_config = {
+        "from_attributes": True
+    }
+
+class Stream(BaseModel):
+    id: int
+    stream_key: str
+    is_live: bool
+    created_at: datetime
+    owner_id: int
+
+    model_config = {
+        "from_attributes": True
+    }
+
+class Playlist(BaseModel):
+    id: int
+    created_at: datetime
+    owner_id: int
+    media_items: List[Media] = []
+
+    model_config = {
+        "from_attributes": True
+    }    
+
+class UserCreate(UserBase):
+    password: str
+
+class User(UserBase):
+    id: int
+    is_active: bool
+    is_admin: bool
+    created_at: datetime
+
+    class Config:
+        orm_mode = True
+
+class Token(BaseModel):
+    access_token: str
+    token_type: str
+    user: User
+
+class MediaBase(BaseModel):
+    title: str
+    artist: str
+    duration: int
+    media_type: MediaType
+    thumbnail_path: Optional[str] = None
+
+class MediaCreate(MediaBase):
+    file_path: str
+
+class Media(MediaBase):
+    id: int
+    file_path: str
+    created_at: datetime
+    owner_id: int
+
+    class Config:
+        orm_mode = True
+
+class StreamBase(BaseModel):
+    title: str
+    description: Optional[str] = None
+    thumbnail_path: Optional[str] = None
+
+class StreamCreate(StreamBase):
+    pass
+
+class Stream(StreamBase):
+    id: int
+    stream_key: str
+    is_live: bool
+    created_at: datetime
+    owner_id: int
+
+    class Config:
+        orm_mode = True
+
+class PlaylistBase(BaseModel):
+    name: str
+    description: Optional[str] = None
+    is_public: bool = False
+
+class PlaylistCreate(PlaylistBase):
+    pass
+
+class Playlist(PlaylistBase):
+    id: int
+    created_at: datetime
+    owner_id: int
+    media_items: List[Media] = []
+
+    class Config:
+        orm_mode = True
+''',
+
+            self.backend_dir / "app" / "auth.py": '''
+from datetime import datetime, timedelta
+from typing import Optional
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from fastapi import Depends, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer
+from sqlalchemy.orm import Session
+from . import models, schemas
+from .database import get_db
+from .config import settings
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+# Определите oauth2_scheme
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")  # Добавьте эту строку
+
+def verify_password(plain_password, hashed_password):
+    return pwd_context.verify(plain_password, hashed_password)
+
+def get_password_hash(password):
+    return pwd_context.hash(password)
+
+def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
+    to_encode = data.copy()
+    if expires_delta:
+        expire = datetime.utcnow() + expires_delta
+    else:
+        expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
+    to_encode.update({"exp": expire})
+    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
+    return encoded_jwt
+
+def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
+    credentials_exception = HTTPException(
+        status_code=status.HTTP_401_UNAUTHORIZED,
+        detail="Could not validate credentials",
+        headers={"WWW-Authenticate": "Bearer"},
+    )
+    try:
+        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
+        username: str = payload.get("sub")
+        if username is None:
+            raise credentials_exception
+    except JWTError:
+        raise credentials_exception
+    
+    user = db.query(models.User).filter(models.User.username == username).first()
+    if user is None:
+        raise credentials_exception
+    return user
+
+def get_current_active_user(current_user: models.User = Depends(get_current_user)):
+    if not current_user.is_active:
+        raise HTTPException(status_code=400, detail="Inactive user")
+    return current_user
+''',
+
+            self.backend_dir / "app" / "main.py": '''
+from fastapi import FastAPI, Depends, HTTPException, status, File, UploadFile
+from fastapi.security import OAuth2PasswordRequestForm
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.staticfiles import StaticFiles
+from sqlalchemy.orm import Session
+from typing import List, Optional
+import os
+import secrets
+from . import models, schemas, auth
+from .database import engine, get_db
+from .models import MediaType
+
+models.Base.metadata.create_all(bind=engine)
+
+app = FastAPI(title="Smart Player API")
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=["http://localhost:5173"],
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+@app.post("/token", response_model=schemas.Token)
+async def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
+    user = db.query(models.User).filter(models.User.username == form_data.username).first()
+    if not user or not auth.verify_password(form_data.password, user.hashed_password):
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail="Incorrect username or password",
+            headers={"WWW-Authenticate": "Bearer"},
+        )
+    
+    access_token = auth.create_access_token(data={"sub": user.username})
+    return {
+        "access_token": access_token,
+        "token_type": "bearer",
+        "user": user
+    }
+
+@app.get("/users/me", response_model=schemas.User)
+async def read_users_me(current_user: models.User = Depends(auth.get_current_active_user)):
+    return current_user
+
+@app.post("/upload/", response_model=schemas.Media)
+async def upload_media(
+    file: UploadFile = File(...),
+    title: str = Form(...),
+    artist: str = Form(...),
+    media_type: MediaType = Form(...),
+    db: Session = Depends(get_db),
+    current_user: models.User = Depends(auth.get_current_active_user)
+):
+    # Сохраняем файл на диск
+    file_path = f"uploads/{file.filename}"
+    with open(file_path, "wb") as buffer:
+        buffer.write(await file.read())
+
+    # Создаем запись в базе данных
+    new_media = models.Media(
+        title=title,
+        artist=artist,
+        duration=0,  # Можно рассчитать длительность позже
+        file_path=file_path,
+        media_type=media_type,
+        owner_id=current_user.id
+    )
+    db.add(new_media)
+    db.commit()
+    db.refresh(new_media)
+    return new_media
+
+@app.post("/media/", response_model=schemas.Media)
+async def create_media(
+    file: UploadFile = File(...),
+    title: str = Form(...),
+    artist: str = Form(...),
+    media_type: MediaType = Form(...),
+    db: Session = Depends(get_db),
+    current_user: models.User = Depends(auth.get_current_active_user)
+):
+    # Логика обработки загрузки файла
+    pass
+
+@app.post("/streams/", response_model=schemas.Stream)
+async def create_stream(
+    title: str = Form(...),
+    description: Optional[str] = Form(None),
+    db: Session = Depends(get_db),
+    current_user: models.User = Depends(auth.get_current_active_user)
+):
+    # Логика создания потока
+    pass
+''',
+
+            self.backend_dir / "create_admin.py": '''
+import sys
+from pathlib import Path
+backend_dir = Path(__file__).resolve().parent
+sys.path.insert(0, str(backend_dir))
+
+from app.database import SessionLocal, engine, Base
+from app.models import User
+from app.auth import get_password_hash
+
+def create_admin():
+    Base.metadata.create_all(bind=engine)
+    db = SessionLocal()
+    try:
+        admin = db.query(User).filter(User.username == "admin").first()
+        if not admin:
+            admin = User(
+                email="admin@example.com",
+                username="admin",
+                hashed_password=get_password_hash("admin"),
+                is_active=True,
+                is_admin=True
+            )
+            db.add(admin)
+            db.commit()
+            print("Админ создан успешно!")
+            print("Логин: admin")
+            print("Пароль: admin")
+        else:
+            print("Админ уже существует")
+    finally:
+        db.close()
+
+if __name__ == "__main__":
+    create_admin()
+''',
+
+            self.backend_dir / "requirements.txt": '''
+fastapi==0.103.1
+uvicorn==0.23.2
+sqlalchemy==1.4.46
+pydantic==2.10.6
+pydantic-settings==2.8.0
+python-jose[cryptography]==3.4.0
+passlib==1.7.4
+bcrypt==4.0.1
+python-multipart==0.0.6
+python-dotenv==1.0.0
+email-validator==2.0.0.post1
+''',
+        }
+
+        # Создаем все файлы
+        for file_path, content in files.items():
+            file_path.parent.mkdir(parents=True, exist_ok=True)
+            with open(file_path, 'w', encoding='utf-8') as f:
+                f.write(content.strip())
+
+    def create_frontend_files(self):
+        """Создание файлов frontend"""
+        files = {
+            self.frontend_dir / "index.html": '''
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Smart Player</title>
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.tsx"></script>
+  </body>
+</html>
+''',
+
+            self.frontend_dir / "src" / "main.tsx": '''
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import { CssBaseline } from '@mui/material'
+import App from './App'
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+  <React.StrictMode>
+    <CssBaseline />
+    <App />
+  </React.StrictMode>,
+)
+''',
+
+            self.frontend_dir / "src" / "App.tsx": '''
+import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
+import { ThemeProvider, createTheme } from '@mui/material';
+import { LoginPage } from './components/LoginPage';
+import { Layout } from './components/Layout';
+import { useAuthStore } from './store/authStore';
+import { RegisterPage } from './components/RegisterPage';
+
+const theme = createTheme();
+
+const PrivateRoute = ({ children }: { children: React.ReactNode }) => {
+  const isAuthenticated = useAuthStore(state => state.isAuthenticated);
+  return isAuthenticated ? <>{children}</> : <Navigate to="/login" />;
+};
+
+function App() {
+  return (
+    <ThemeProvider theme={theme}>
+      <BrowserRouter>
+        <Routes>
+          <Route path="/login" element={<LoginPage />} />
+          <Route path="/register" element={<RegisterPage />} />
+          <Route path="/" element={
+            <PrivateRoute>
+              <Layout />
+            </PrivateRoute>
+          } />
+        </Routes>
+      </BrowserRouter>
+    </ThemeProvider>
+  );
+}
+
+export default App;
+''',
+
+            self.frontend_dir / "src" / "store" / "authStore.ts": '''
+import create from 'zustand'
+
+interface User {
+  id: number;
+  username: string;
+  email: string;
+  is_admin: boolean;
+}
+
+interface AuthState {
+  isAuthenticated: boolean;
+  user: User | null;
+  token: string | null;
+  login: (username: string, password: string) => Promise<void>;
+  logout: () => void;
+}
+
+export const useAuthStore = create<AuthState>((set) => ({
+  isAuthenticated: !!localStorage.getItem('token'),
+  user: JSON.parse(localStorage.getItem('user') || 'null'),
+  token: localStorage.getItem('token'),
+  
+  login: async (username: string, password: string) => {
+    const formData = new FormData();
+    formData.append('username', username);
+    formData.append('password', password);
+    
+    const response = await fetch('http://localhost:8000/token', {
+      method: 'POST',
+      body: formData
+    });
+    
+    if (!response.ok) throw new Error('Auth failed');
+    
+    const data = await response.json();
+    set({ 
+      isAuthenticated: true, 
+      user: data.user,
+      token: data.access_token 
+    });
+    
+    localStorage.setItem('token', data.access_token);
+    localStorage.setItem('user', JSON.stringify(data.user));
+  },
+  
+  logout: () => {
+    set({ isAuthenticated: false, user: null, token: null });
+    localStorage.removeItem('token');
+    localStorage.removeItem('user');
+  }
+}));
+''',
+
+            self.frontend_dir / "src" / "components" / "LoginPage.tsx": '''
+import { useState } from 'react';
+import { useNavigate, Link as RouterLink } from 'react-router-dom';
+import { Box, Button, TextField, Typography, Container, Paper, Alert, Link } from '@mui/material';
+import { useAuthStore } from '../store/authStore';
+
+export const LoginPage = () => {
+  const [username, setUsername] = useState('');
+  const [password, setPassword] = useState('');
+  const [error, setError] = useState('');
+  const login = useAuthStore(state => state.login);
+  const navigate = useNavigate();
+
+  const handleSubmit = async (e: React.FormEvent) => {
+    e.preventDefault();
+    try {
+      await login(username, password);
+      navigate('/');
+    } catch (err) {
+      setError('Неверные учетные данные');
+    }
+  };
+
+  return (
+    <Container component="main" maxWidth="xs">
+      <Box sx={{
+        marginTop: 8,
+        display: 'flex',
+        flexDirection: 'column',
+        alignItems: 'center',
+      }}>
+        <Paper elevation={3} sx={{ p: 4, width: '100%' }}>
+          <Typography component="h1" variant="h5" align="center">
+            Smart Player
+          </Typography>
+          {error && <Alert severity="error" sx={{ mt: 2 }}>{error}</Alert>}
+          <Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
+            <TextField
+              margin="normal"
+              required
+              fullWidth
+              label="Имя пользователя"
+              autoFocus
+              value={username}
+              onChange={(e) => setUsername(e.target.value)}
+            />
+            <TextField
+              margin="normal"
+              required
+              fullWidth
+              label="Пароль"
+              type="password"
+              value={password}
+              onChange={(e) => setPassword(e.target.value)}
+            />
+            <Button
+              type="submit"
+              fullWidth
+              variant="contained"
+              sx={{ mt: 3, mb: 2 }}
+            >
+              Войти
+            </Button>
+            <Box sx={{ textAlign: 'center' }}>
+              <Link component={RouterLink} to="/register" variant="body2">
+                Нет аккаунта? Зарегистрироваться
+              </Link>
+            </Box>
+          </Box>
+        </Paper>
+      </Box>
+    </Container>
+  );
+};
+''',
+
+            self.frontend_dir / "src" / "components" / "Layout.tsx": '''
+import { useState } from 'react';
+import { Box, Drawer, AppBar, Toolbar, Typography, List, ListItem, ListItemIcon, ListItemText, IconButton } from '@mui/material';
+import { Menu as MenuIcon, Dashboard as DashboardIcon, ExitToApp as LogoutIcon } from '@mui/icons-material';
+import { useNavigate } from 'react-router-dom';
+import { useAuthStore } from '../store/authStore';
+
+const drawerWidth = 240;
+
+export const Layout = () => {
+  const [mobileOpen, setMobileOpen] = useState(false);
+  const navigate = useNavigate();
+  const logout = useAuthStore(state => state.logout);
+
+  const handleDrawerToggle = () => {
+    setMobileOpen(!mobileOpen);
+  };
+
+  const handleLogout = () => {
+    logout();
+    navigate('/login');
+  };
+
+  const drawer = (
+    <div>
+      <Toolbar>
+        <Typography variant="h6" noWrap component="div">
+          Smart Player
+        </Typography>
+      </Toolbar>
+      <List>
+        <ListItem button>
+          <ListItemIcon>
+            <DashboardIcon />
+          </ListItemIcon>
+          <ListItemText primary="Панель управления" />
+        </ListItem>
+        <ListItem button onClick={handleLogout}>
+          <ListItemIcon>
+            <LogoutIcon />
+          </ListItemIcon>
+          <ListItemText primary="Выход" />
+        </ListItem>
+      </List>
+    </div>
+  );
+
+  return (
+    <Box sx={{ display: 'flex' }}>
+      <AppBar position="fixed">
+        <Toolbar>
+          <IconButton
+            color="inherit"
+            aria-label="open drawer"
+            edge="start"
+            onClick={handleDrawerToggle}
+            sx={{ mr: 2, display: { sm: 'none' } }}
+          >
+            <MenuIcon />
+          </IconButton>
+          <Typography variant="h6" noWrap component="div">
+            Smart Player
+          </Typography>
+        </Toolbar>
+      </AppBar>
+      <Box
+        component="nav"
+        sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}
+      >
+        <Drawer
+          variant="temporary"
+          open={mobileOpen}
+          onClose={handleDrawerToggle}
+          ModalProps={{ keepMounted: true }}
+          sx={{
+            display: { xs: 'block', sm: 'none' },
+            '& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
+          }}
+        >
+          {drawer}
+        </Drawer>
+        <Drawer
+          variant="permanent"
+          sx={{
+            display: { xs: 'none', sm: 'block' },
+            '& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
+          }}
+          open
+        >
+          {drawer}
+        </Drawer>
+      </Box>
+      <Box
+        component="main"
+        sx={{
+          flexGrow: 1,
+          p: 3,
+          width: { sm: `calc(100% - ${drawerWidth}px)` },
+          marginTop: '64px'
+        }}
+      >
+        <Typography paragraph>
+          Добро пожаловать в Smart Player
+        </Typography>
+      </Box>
+    </Box>
+  );
+};
+''',
+
+            self.frontend_dir / "src" / "vite-env.d.ts": '''
+/// <reference types="vite/client" />
+''',
+
+            self.frontend_dir / "tsconfig.json": '''
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "useDefineForClassFields": true,
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
+    "module": "ESNext",
+    "skipLibCheck": true,
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "react-jsx",
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true
+  },
+  "include": ["src"],
+  "references": [{ "path": "./tsconfig.node.json" }]
+}
+''',
+
+            self.frontend_dir / "tsconfig.node.json": '''
+{
+  "compilerOptions": {
+    "composite": true,
+    "skipLibCheck": true,
+    "module": "ESNext",
+    "moduleResolution": "bundler",
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["vite.config.ts"]
+}
+''',
+
+            self.frontend_dir / "vite.config.ts": '''
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+export default defineConfig({
+  plugins: [react()],
+})
+''',
+
+            self.frontend_dir / "src" / "components" / "RegisterPage.tsx": '''
+import { useState } from 'react';
+import { useNavigate, Link as RouterLink } from 'react-router-dom';
+import { 
+  Box, 
+  Button, 
+  TextField, 
+  Typography, 
+  Container, 
+  Paper, 
+  Alert,
+  Link
+} from '@mui/material';
+
+export const RegisterPage = () => {
+  const [username, setUsername] = useState('');
+  const [email, setEmail] = useState('');
+  const [password, setPassword] = useState('');
+  const [error, setError] = useState('');
+  const navigate = useNavigate();
+
+  const handleSubmit = async (e: React.FormEvent) => {
+    e.preventDefault();
+    try {
+      const response = await fetch('http://localhost:8000/register', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({
+          username,
+          email,
+          password
+        })
+      });
+
+      if (!response.ok) {
+        const error = await response.json();
+        throw new Error(error.detail || 'Registration failed');
+      }
+
+      // Регистрация успешна, перенаправляем на страницу входа
+      navigate('/login');
+    } catch (err) {
+      setError(err instanceof Error ? err.message : 'Registration failed');
+    }
+  };
+
+  return (
+    <Container component="main" maxWidth="xs">
+      <Box sx={{
+        marginTop: 8,
+        display: 'flex',
+        flexDirection: 'column',
+        alignItems: 'center',
+      }}>
+        <Paper elevation={3} sx={{ p: 4, width: '100%' }}>
+          <Typography component="h1" variant="h5" align="center">
+            Регистрация
+          </Typography>
+          {error && <Alert severity="error" sx={{ mt: 2 }}>{error}</Alert>}
+          <Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
+            <TextField
+              margin="normal"
+              required
+              fullWidth
+              label="Имя пользователя"
+              autoFocus
+              value={username}
+              onChange={(e) => setUsername(e.target.value)}
+            />
+            <TextField
+              margin="normal"
+              required
+              fullWidth
+              label="Email"
+              type="email"
+              value={email}
+              onChange={(e) => setEmail(e.target.value)}
+            />
+            <TextField
+              margin="normal"
+              required
+              fullWidth
+              label="Пароль"
+              type="password"
+              value={password}
+              onChange={(e) => setPassword(e.target.value)}
+            />
+            <Button
+              type="submit"
+              fullWidth
+              variant="contained"
+              sx={{ mt: 3, mb: 2 }}
+            >
+              Зарегистрироваться
+            </Button>
+            <Box sx={{ textAlign: 'center' }}>
+              <Link component={RouterLink} to="/login" variant="body2">
+                Уже есть аккаунт? Войти
+              </Link>
+            </Box>
+          </Box>
+        </Paper>
+      </Box>
+    </Container>
+  );
+};
+''',
+
+            self.frontend_dir / "src" / "components" / "UploadMedia.tsx": '''
+import { useState } from 'react';
+import { Button, TextField, Dialog, DialogTitle, DialogContent, DialogActions, Box, Alert } from '@mui/material';
+
+interface UploadMediaProps {
+  onUpload: () => void;
+}
+
+export const UploadMedia = ({ onUpload }: UploadMediaProps) => {
+  const [open, setOpen] = useState(false);
+  const [title, setTitle] = useState('');
+  const [artist, setArtist] = useState('');
+  const [mediaType, setMediaType] = useState('audio');
+  const [file, setFile] = useState<File | null>(null);
+
+  const handleSubmit = async (e: React.FormEvent) => {
+    e.preventDefault();
+    if (!file) return;
+
+    const formData = new FormData();
+    formData.append('file', file);
+    formData.append('title', title);
+    formData.append('artist', artist);
+    formData.append('media_type', mediaType);
+
+    const response = await fetch('http://localhost:8000/upload/', {
+      method: 'POST',
+      body: formData,
+      headers: {
+        Authorization: `Bearer ${useAuthStore(state => state.token)}`
+      }
+    });
+
+    if (response.ok) {
+      setOpen(false);
+      onUpload();
+    } else {
+      alert('Ошибка при загрузке файла');
+    }
+  };
+
+  return (
+    <>
+      <Button variant="contained" component="label" onClick={() => setOpen(true)}>
+        Загрузить медиа
+      </Button>
+      <Dialog open={open} onClose={() => setOpen(false)}>
+        <DialogTitle>Загрузка медиа</DialogTitle>
+        <DialogContent>
+          <TextField
+            label="Название"
+            fullWidth
+            value={title}
+            onChange={(e) => setTitle(e.target.value)}
+            margin="normal"
+          />
+          <TextField
+            label="Исполнитель"
+            fullWidth
+            value={artist}
+            onChange={(e) => setArtist(e.target.value)}
+            margin="normal"
+          />
+          <select value={mediaType} onChange={(e) => setMediaType(e.target.value)}>
+            <option value="audio">Аудио</option>
+            <option value="video">Видео</option>
+          </select>
+          <input type="file" onChange={(e) => setFile(e.target.files?.[0])} />
+        </DialogContent>
+        <DialogActions>
+          <Button onClick={() => setOpen(false)}>Отмена</Button>
+          <Button onClick={handleSubmit}>Загрузить</Button>
+        </DialogActions>
+      </Dialog>
+    </>
+  );
+};
+''',
+
+            self.frontend_dir / "src" / "components" / "CreateStream.tsx": '''
+import { useState } from 'react';
+import { 
+  Button, 
+  TextField, 
+  Dialog, 
+  DialogTitle, 
+  DialogContent, 
+  DialogActions,
+  Box,
+  Alert,
+  Typography
+} from '@mui/material';
+
+interface CreateStreamProps {
+  open: boolean;
+  onClose: () => void;
+  onCreated: () => void;
+}
+
+export const CreateStream = ({ open, onClose, onCreated }: CreateStreamProps) => {
+  // ... содержимое компонента ...
+};
+''',
+
+            self.frontend_dir / "src" / "components" / "MediaPlayer.tsx": '''
+import { useRef, useEffect } from 'react';
+import { Box, Paper } from '@mui/material';
+
+interface MediaPlayerProps {
+  src: string;
+  type: 'audio' | 'video';
+  poster?: string;
+}
+
+export const MediaPlayer = ({ src, type, poster }: MediaPlayerProps) => {
+  // ... содержимое компонента ...
+};
+''',
+
+            self.frontend_dir / "src" / "components" / "StreamPlayer.tsx": '''
+import { useEffect, useRef } from 'react';
+import { Box, Paper, Typography } from '@mui/material';
+import Hls from 'hls.js';
+
+interface StreamPlayerProps {
+  streamId: string;
+  title: string;
+}
+
+export const StreamPlayer = ({ streamId }: { streamId: string }) => {
+  const videoRef = useRef<HTMLVideoElement>(null);
+
+  useEffect(() => {
+    const video = videoRef.current;
+    if (video) {
+      const hls = new Hls();
+      hls.loadSource(`/streams/${streamId}/playlist.m3u8`);
+      hls.attachMedia(video);
+      return () => hls.destroy();
+    }
+  }, [streamId]);
+
+  return (
+    <video ref={videoRef} controls autoPlay width="640" height="360">
+      Ваш браузер не поддерживает HTML5 видео.
+    </video>
+  );
+};
+
+export const PlaylistManager = () => {
+  const [playlists, setPlaylists] = useState<Playlist[]>([]);
+
+  useEffect(() => {
+    fetch('http://localhost:8000/playlists/', {
+      headers: {
+        Authorization: `Bearer ${useAuthStore(state => state.token)}`
+      }
+    })
+      .then((res) => res.json())
+      .then(setPlaylists);
+  }, []);
+
+  return (
+    <Box>
+      <Typography variant="h5">Мои плейлисты</Typography>
+      {playlists.map((playlist) => (
+        <Paper key={playlist.id} elevation={3} style={{ padding: '16px', marginBottom: '16px' }}>
+          <Typography>{playlist.name}</Typography>
+        </Paper>
+      ))}
+    </Box>
+  );
+};
+''',
+
+            self.frontend_dir / "package.json": '''
+{
+  "name": "smart-player-frontend",
+  "private": true,
+  "version": "0.1.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "tsc && vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@emotion/react": "^11.11.0",
+    "@emotion/styled": "^11.11.0",
+    "@mui/icons-material": "^5.11.16",
+    "@mui/material": "^5.13.0",
+    "axios": "^1.4.0",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
+    "react-router-dom": "^6.11.1",
+    "zustand": "^4.3.8",
+    "hls.js": "^1.4.12"
+  },
+  "devDependencies": {
+    "@types/react": "^18.2.6",
+    "@types/react-dom": "^18.2.4",
+    "@vitejs/plugin-react": "^4.0.0",
+    "typescript": "^5.0.4",
+    "vite": "^4.3.5"
+  }
+}
+''',
+        }
+
+        # Создаем все файлы
+        for file_path, content in files.items():
+            file_path.parent.mkdir(parents=True, exist_ok=True)
+            with open(file_path, 'w', encoding='utf-8') as f:
+                f.write(content.strip())
+                
+    def init_database(self):
+        """Инициализация базы данных"""
+        print("Инициализация базы данных...")
+        
+        # Создаем .env файл
+        env_file = self.backend_dir / ".env"
+        with open(env_file, "w", encoding='utf-8') as f:
+            f.write("DATABASE_URL=sqlite:///./smartplayer.db\n")
+            f.write("SECRET_KEY=your-super-secret-key-keep-it-safe\n")
+            f.write("ACCESS_TOKEN_EXPIRE_MINUTES=30\n")
+        
+        # Запускаем скрипт создания админа
+        subprocess.run([str(self.venv_python), str(self.backend_dir / "create_admin.py")], check=True)
+        
+    def run(self):
+        """Запуск проекта"""
+        try:
+            self.clean()
+            self.create_backend()
+            self.create_frontend()
+            print("\nПроект успешно создан!")
+            print("\nДля запуска backend:")
+            print(f"cd {self.backend_dir} && {self.venv_python} -m uvicorn app.main:app --reload")
+            print("\nДля запуска frontend:")
+            print(f"cd {self.frontend_dir} && npm run dev")
+        except Exception as e:
+            print(f"\nОшибка при создании проекта: {e}")
+            sys.exit(1)
+
+if __name__ == "__main__":
+    setup = ProjectSetup()
+    setup.run() 
\ No newline at end of file