diff --git a/.gitignore b/.gitignore index b86d9c2..60b6d5f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ dist/ uploads/ *.egg-info/ .DS_Store +.idea \ No newline at end of file diff --git a/backend/app/config.py b/backend/app/config.py index 3ab5cf6..6528fa5 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -15,6 +15,12 @@ class Settings(BaseSettings): MINIO_BUCKET: str = "filam3d" MINIO_SECURE: bool = False + JWT_SECRET: str = "change-me-in-production-please" + JWT_ALGORITHM: str = "HS256" + JWT_EXPIRE_HOURS: int = 24 + ADMIN_DEFAULT_EMAIL: str = "admin@filam3d.ru" + ADMIN_DEFAULT_PASSWORD: str = "admin123" + model_config = {"env_file": ["../.env", ".env"]} diff --git a/backend/app/main.py b/backend/app/main.py index dddeb82..7d41e72 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -6,10 +6,12 @@ from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from sqlalchemy import select +from app.config import settings from app.database import async_session, engine, Base -from app.models import Material +from app.models import Material, AdminUser from app.seed.materials import MATERIALS -from app.routers import calculate, materials, orders, ai_advisor +from app.services.auth import hash_password +from app.routers import calculate, materials, orders, ai_advisor, admin # Configure logging logging.basicConfig( @@ -50,6 +52,22 @@ async def lifespan(app: FastAPI): else: logger.info("Materials already exist, skipping seed") + # Seed default admin user + logger.info("Checking admin user...") + async with async_session() as session: + result = await session.execute(select(AdminUser).limit(1)) + if result.scalar_one_or_none() is None: + admin_user = AdminUser( + email=settings.ADMIN_DEFAULT_EMAIL, + password_hash=hash_password(settings.ADMIN_DEFAULT_PASSWORD), + name="Admin", + ) + session.add(admin_user) + await session.commit() + logger.info("Default admin created: %s", settings.ADMIN_DEFAULT_EMAIL) + else: + logger.info("Admin user already exists, skipping") + logger.info("=== Application ready ===") yield logger.info("=== Application shutdown ===") @@ -82,6 +100,7 @@ app.include_router(calculate.router, prefix="/api") app.include_router(materials.router, prefix="/api") app.include_router(orders.router, prefix="/api") app.include_router(ai_advisor.router, prefix="/api") +app.include_router(admin.router, prefix="/api/admin") @app.get("/api/health") diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 0c65c3e..8ec4548 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -1,5 +1,7 @@ from app.models.material import Material from app.models.calculation import Calculation from app.models.order import Order +from app.models.admin_user import AdminUser +from app.models.app_settings import AppSettings -__all__ = ["Material", "Calculation", "Order"] +__all__ = ["Material", "Calculation", "Order", "AdminUser", "AppSettings"] diff --git a/backend/requirements.txt b/backend/requirements.txt index 036699f..566aabc 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -11,3 +11,5 @@ python-multipart==0.0.20 httpx==0.28.1 minio==7.2.12 google-genai==1.14.0 +pyjwt==2.10.1 +bcrypt==4.2.1 diff --git a/docker-compose.yml b/docker-compose.yml index 0673d70..8eab17c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,3 @@ services: MINIO_SECRET_KEY: SuperSecretPassword123! MINIO_BUCKET: ${MINIO_BUCKET:-filam3d} MINIO_SECURE: ${MINIO_SECURE:-false} - - frontend: - build: ./frontend - network_mode: host diff --git a/frontend/Dockerfile b/frontend/Dockerfile deleted file mode 100644 index 597940a..0000000 --- a/frontend/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM node:20-alpine - -WORKDIR /app - -COPY package.json . -RUN npm install - -COPY . . - -EXPOSE 5173 - -CMD ["npm", "run", "dev"] diff --git a/frontend/index.html b/frontend/index.html index a759158..6ec7abe 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,10 +3,28 @@ + + + + + + + + + + + + + + + + + + - Filam3D — Калькулятор 3D-печати + Filam3D — 3D-печать на заказ | Калькулятор стоимости онлайн
diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 10461c0..535d7b1 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,7 +1,7 @@ + + diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 15f69d7..aa8b5c2 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -2,14 +2,109 @@ import { createRouter, createWebHistory } from 'vue-router' import CalculatorView from '../views/CalculatorView.vue' import MaterialsView from '../views/MaterialsView.vue' import OrderView from '../views/OrderView.vue' +import BlogView from '../views/BlogView.vue' +import ArticleView from '../views/ArticleView.vue' +import AdminLogin from '../views/admin/AdminLogin.vue' +import AdminLayout from '../views/admin/AdminLayout.vue' +import AdminDashboard from '../views/admin/AdminDashboard.vue' +import AdminOrders from '../views/admin/AdminOrders.vue' +import AdminMaterials from '../views/admin/AdminMaterials.vue' +import AdminSettings from '../views/admin/AdminSettings.vue' +import AdminUsers from '../views/admin/AdminUsers.vue' const routes = [ - { path: '/', name: 'calculator', component: CalculatorView }, - { path: '/materials', name: 'materials', component: MaterialsView }, - { path: '/order/:calcId', name: 'order', component: OrderView }, + { + path: '/', + name: 'calculator', + component: CalculatorView, + meta: { title: 'Калькулятор 3D-печати — Filam3D' }, + }, + { + path: '/materials', + name: 'materials', + component: MaterialsView, + meta: { title: 'Материалы для 3D-печати — Filam3D' }, + }, + { + path: '/order/:calcId', + name: 'order', + component: OrderView, + meta: { title: 'Оформление заказа — Filam3D' }, + }, + { + path: '/blog', + name: 'blog', + component: BlogView, + meta: { title: 'Блог о 3D-печати — статьи и руководства — Filam3D' }, + }, + { + path: '/blog/:slug', + name: 'article', + component: ArticleView, + }, + { + path: '/admin/login', + name: 'admin-login', + component: AdminLogin, + meta: { title: 'Вход — Админ-панель Filam3D' }, + }, + { + path: '/admin', + component: AdminLayout, + meta: { requiresAdmin: true }, + children: [ + { + path: '', + name: 'admin-dashboard', + component: AdminDashboard, + meta: { title: 'Дашборд — Админ-панель Filam3D' }, + }, + { + path: 'orders', + name: 'admin-orders', + component: AdminOrders, + meta: { title: 'Заказы — Админ-панель Filam3D' }, + }, + { + path: 'materials', + name: 'admin-materials', + component: AdminMaterials, + meta: { title: 'Материалы — Админ-панель Filam3D' }, + }, + { + path: 'settings', + name: 'admin-settings', + component: AdminSettings, + meta: { title: 'Настройки — Админ-панель Filam3D' }, + }, + { + path: 'users', + name: 'admin-users', + component: AdminUsers, + meta: { title: 'Администраторы — Админ-панель Filam3D' }, + }, + ], + }, ] -export default createRouter({ +const router = createRouter({ history: createWebHistory(), routes, + scrollBehavior() { + return { top: 0 } + }, }) + +router.beforeEach((to) => { + if (to.meta.title) { + document.title = to.meta.title + } + if (to.meta.requiresAdmin || to.matched.some((r) => r.meta.requiresAdmin)) { + const token = localStorage.getItem('admin_token') + if (!token) { + return { name: 'admin-login' } + } + } +}) + +export default router diff --git a/frontend/src/views/CalculatorView.vue b/frontend/src/views/CalculatorView.vue index 12524f2..512a4d7 100644 --- a/frontend/src/views/CalculatorView.vue +++ b/frontend/src/views/CalculatorView.vue @@ -1,58 +1,128 @@