From e58e548f84495a06734c7697f28f096ba8489ca0 Mon Sep 17 00:00:00 2001 From: serov <1@dmserov.ru> Date: Sun, 23 Feb 2025 08:22:12 +0000 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=201.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1.py | 1273 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1273 insertions(+) create mode 100644 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": ''' + + + + + + Smart Player + + +
+ + + +''', + + 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( + + + + , +) +''', + + 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} : ; +}; + +function App() { + return ( + + + + } /> + } /> + + + + } /> + + + + ); +} + +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; + logout: () => void; +} + +export const useAuthStore = create((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 ( + + + + + Smart Player + + {error && {error}} + + setUsername(e.target.value)} + /> + setPassword(e.target.value)} + /> + + + + Нет аккаунта? Зарегистрироваться + + + + + + + ); +}; +''', + + 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 = ( +
+ + + Smart Player + + + + + + + + + + + + + + + + +
+ ); + + return ( + + + + + + + + Smart Player + + + + + + {drawer} + + + {drawer} + + + + + Добро пожаловать в Smart Player + + + + ); +}; +''', + + self.frontend_dir / "src" / "vite-env.d.ts": ''' +/// +''', + + 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 ( + + + + + Регистрация + + {error && {error}} + + setUsername(e.target.value)} + /> + setEmail(e.target.value)} + /> + setPassword(e.target.value)} + /> + + + + Уже есть аккаунт? Войти + + + + + + + ); +}; +''', + + 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(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 ( + <> + + setOpen(false)}> + Загрузка медиа + + setTitle(e.target.value)} + margin="normal" + /> + setArtist(e.target.value)} + margin="normal" + /> + + setFile(e.target.files?.[0])} /> + + + + + + + + ); +}; +''', + + 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(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 ( + + ); +}; + +export const PlaylistManager = () => { + const [playlists, setPlaylists] = useState([]); + + useEffect(() => { + fetch('http://localhost:8000/playlists/', { + headers: { + Authorization: `Bearer ${useAuthStore(state => state.token)}` + } + }) + .then((res) => res.json()) + .then(setPlaylists); + }, []); + + return ( + + Мои плейлисты + {playlists.map((playlist) => ( + + {playlist.name} + + ))} + + ); +}; +''', + + 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