feat: initial commit

This commit is contained in:
2026-03-05 22:12:38 +01:00
commit bce762a783
380 changed files with 51955 additions and 0 deletions
View File
Binary file not shown.
Binary file not shown.
+70
View File
@@ -0,0 +1,70 @@
"""JWT authentication utilities."""
import uuid
from datetime import datetime, timedelta
from typing import Optional
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import JWTError, jwt
from passlib.context import CryptContext
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.config import settings
from app.database import get_db
from app.models.user import User
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
bearer_scheme = HTTPBearer()
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain: str, hashed: str) -> bool:
return pwd_context.verify(plain, hashed)
def create_access_token(user_id: str, role: str) -> str:
expires = datetime.utcnow() + timedelta(minutes=settings.jwt_access_token_expire_minutes)
payload = {"sub": user_id, "role": role, "exp": expires}
return jwt.encode(payload, settings.jwt_secret_key, algorithm=settings.jwt_algorithm)
def decode_token(token: str) -> dict:
try:
return jwt.decode(token, settings.jwt_secret_key, algorithms=[settings.jwt_algorithm])
except JWTError as exc:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") from exc
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
db: AsyncSession = Depends(get_db),
) -> User:
payload = decode_token(credentials.credentials)
user_id = payload.get("sub")
if not user_id:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
result = await db.execute(select(User).where(User.id == uuid.UUID(user_id)))
user = result.scalar_one_or_none()
if not user or not user.is_active:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found or inactive")
return user
async def require_admin(user: User = Depends(get_current_user)) -> User:
if user.role.value != "admin":
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
return user
async def require_admin_or_pm(user: User = Depends(get_current_user)) -> User:
if user.role.value not in ("admin", "project_manager"):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Admin or Project Manager access required",
)
return user
+184
View File
@@ -0,0 +1,184 @@
"""Seed database with 7 Schaeffler product category templates."""
import asyncio
import uuid
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from sqlalchemy import select
STANDARD_FIELDS = {
"0": {"label": "Ebene1", "required": True},
"1": {"label": "Ebene2", "required": True},
"2": {"label": "Baureihe", "required": True},
"3": {"label": "PIM-ID (Klasse)", "required": False},
"4": {"label": "Produkt (Baureihe)", "required": False},
"5": {"label": "[Separator]", "required": False, "skip": True},
"6": {"label": "Gewähltes Produkt", "required": True},
"7": {"label": "Name CAD-Modell", "required": True},
"8": {"label": "Gewünschte Bildnummer", "required": False},
"9": {"label": "Lagertyp", "required": False},
"10": {"label": "Medias-Rendering", "required": False},
}
TEMPLATES = [
{
"name": "Tapered Roller Bearings (TRB)",
"category_key": "TRB",
"description": "Kegelrollenlager Tapered roller bearings",
"standard_fields": STANDARD_FIELDS,
"component_schema": {
"pairs": [
{"component_type": "Innenring / Inner ring", "required": False},
{"component_type": "Innenring / Inner ring 2", "required": False},
{"component_type": "Innenring / Inner ring 3", "required": False},
{"component_type": "Außenring / Outer ring", "required": False},
{"component_type": "Außenring / Outer ring 2", "required": False},
{"component_type": "Außenring / Outer ring 3", "required": False},
{"component_type": "Außenring / Outer ring 4", "required": False},
{"component_type": "Käfig / Cage", "required": False},
{"component_type": "Wälzkörper / Rolling Element", "required": False},
{"component_type": "Dichtungskern/Dichtungsträger", "required": False},
{"component_type": "Dichtung Außen / Dichtlippe", "required": False},
]
},
},
{
"name": "Kugellager (Ball Bearings)",
"category_key": "Kugellager",
"description": "Kugellager Ball bearings",
"standard_fields": STANDARD_FIELDS,
"component_schema": {
"pairs": [
{"component_type": "Innenring / Inner ring", "required": False},
{"component_type": "Außenring / Outer ring", "required": False},
{"component_type": "Wälzkörper / Rolling Element", "required": True},
{"component_type": "Käfig / Cage", "required": False},
{"component_type": "Dichtungskern/Dichtungsträger", "required": False},
{"component_type": "Axial - WS", "required": False},
{"component_type": "Axial - GS", "required": False},
]
},
},
{
"name": "Gleitlager (Plain Bearings)",
"category_key": "Gleitlager",
"description": "Gleitlager Plain / sliding bearings",
"standard_fields": STANDARD_FIELDS,
"component_schema": {
"pairs": [
{"component_type": "Innenring / Inner ring", "required": False},
{"component_type": "Außenring / Outer ring", "required": False},
{"component_type": "Gehause / Housing", "required": False},
{"component_type": "Sliding Layer", "required": False},
{"component_type": "Dichtungsträger / Sealing carrier", "required": False},
{"component_type": "Dichtlippe / Sealing lip", "required": False},
]
},
},
{
"name": "Spherical / Toroidal Roller Bearings (SRB/TORB)",
"category_key": "SRB_TORB",
"description": "Pendelrollenlager / Toroidalrollenlager SRB and TORB bearings",
"standard_fields": STANDARD_FIELDS,
"component_schema": {
"pairs": [
{"component_type": "Innenring / Inner ring", "required": False},
{"component_type": "Außenring / Outer ring", "required": False},
{"component_type": "Käfig / Cage", "required": False},
{"component_type": "Wälzkörper / Rolling element", "required": False},
{"component_type": "Bordscheibe IR / Loose Lip IR", "required": False},
{"component_type": "Dichtungsträger / Sealing carrier", "required": False},
]
},
},
{
"name": "Cylindrical Roller Bearings (CRB)",
"category_key": "CRB",
"description": "Zylinderrollenlager Cylindrical roller bearings",
"standard_fields": STANDARD_FIELDS,
"component_schema": {
"pairs": [
{"component_type": "Innenring", "required": False},
{"component_type": "Außenring", "required": False},
{"component_type": "Rollen", "required": False},
{"component_type": "Käfig", "required": False},
{"component_type": "Dichtung", "required": False},
{"component_type": "Halteringe", "required": False},
{"component_type": "Bordscheibe", "required": False},
]
},
},
{
"name": "Linear Guide Rails",
"category_key": "Linear_schiene",
"description": "Linearsysteme Linear guide rail systems",
"standard_fields": STANDARD_FIELDS,
"component_schema": {
"pairs": [
{"component_type": "Rail", "required": True},
]
},
},
{
"name": "End Plates (Anschlagplatten)",
"category_key": "Anschlagplatten",
"description": "Anschlagplatten End plates for guide rails",
"standard_fields": STANDARD_FIELDS,
"component_schema": {
"pairs": [
{"component_type": "Platte / Plate", "required": True},
{"component_type": "Schraube / Screw", "required": False},
{"component_type": "Nut BZ", "required": False},
]
},
},
]
async def seed(db_url: str, admin_email: str = "admin@schaeffler.com", admin_password: str = "Admin1234!"):
from app.models.template import Template
from app.models.user import User, UserRole
from app.utils.auth import hash_password
engine = create_async_engine(db_url, echo=False)
session_factory = async_sessionmaker(engine, expire_on_commit=False)
async with session_factory() as session:
# Seed templates
for tpl_data in TEMPLATES:
result = await session.execute(
select(Template).where(Template.category_key == tpl_data["category_key"])
)
existing = result.scalar_one_or_none()
if not existing:
tpl = Template(**tpl_data)
session.add(tpl)
print(f" + Template: {tpl_data['category_key']}")
else:
print(f" ~ Template already exists: {tpl_data['category_key']}")
# Seed admin user
result = await session.execute(select(User).where(User.email == admin_email))
if not result.scalar_one_or_none():
admin = User(
email=admin_email,
password_hash=hash_password(admin_password),
full_name="Schaeffler Admin",
role=UserRole.admin,
)
session.add(admin)
print(f" + Admin user: {admin_email}")
else:
print(f" ~ Admin user already exists: {admin_email}")
await session.commit()
await engine.dispose()
print("Seed complete.")
if __name__ == "__main__":
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
from app.config import settings
asyncio.run(seed(settings.database_url))