Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions FastApi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
venv/
*.zip
157 changes: 157 additions & 0 deletions FastApi/My_demos/my_demo/README (1).md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Workspace Bookings — README

Aplikacja demonstracyjna: **FastAPI + SQLite + statyczny frontend** + prosta konsola.
Działa na **macOS** i **Linux**.

## Wymagania

- Python **3.10+** (polecany 3.11/3.12)
- pip
- (opcjonalnie) `virtualenv` / `venv`
- Brak dodatkowych systemowych bibliotek (koła binarne dla `bcrypt`/`uvicorn` zwykle wystarczą)

## Struktura katalogów

```
my_demo/
├─ backend/
│ └─ app/
│ ├─ main.py # FastAPI, mounty frontendu, /logout, przekierowania
│ ├─ auth_router.py # rejestracja, logowanie, /me
│ ├─ reservations_* # CRUD do rezerwacji
│ ├─ security.py # bcrypt + JWT
│ ├─ models.py # SQLAlchemy modele (User, Reservation)
│ └─ db.py, settings.py
├─ frontend/
│ ├─ welcome/ # strona startowa
│ ├─ login/ # logowanie
│ ├─ registration/ # rejestracja (auto-login po sukcesie)
│ ├─ menu/ # dashboard, guard.js, itp.
│ └─ goodbye/ # ekran „Goodbye” + auto-redirect na welcome
└─ app.db # SQLite (tworzy się automatycznie)
```

## Konfiguracja środowiska (macOS & Linux)

W terminalu przejdź do katalogu projektu `my_demo/` i wykonaj:

```bash
# 1) Utwórz i aktywuj wirtualne środowisko
python3 -m venv venv
source venv/bin/activate

# 2) Zainstaluj zależności
pip install fastapi "uvicorn[standard]" "passlib[bcrypt]==1.7.4" "bcrypt==4.0.1" "python-jose[cryptography]" "SQLAlchemy>=2.0" alembic python-dotenv pydantic-settings email-validator
```

> Uwaga: przypięcie `passlib[bcrypt]==1.7.4` i `bcrypt==4.0.1` eliminuje znane ostrzeżenia/niezgodności wersji na niektórych systemach.

### (Opcjonalnie) `.env`

Możesz dodać plik `.env` w katalogu `my_demo/`:

```
APP_ENV=dev
SECRET_KEY=change_me_secret_key
ACCESS_TOKEN_EXPIRE_MINUTES=60
SQLITE_PATH=app.db
CORS_ORIGINS=*
```

## Uruchomienie backendu

Z katalogu `my_demo/`:

```bash
source venv/bin/activate
python3 -m uvicorn backend.app.main:app --reload
```

Serwer: http://127.0.0.1:8000

### Dostępne widoki (statyczny frontend)

- `GET /` → przekierowanie do `/welcome/index.html` (jeśli istnieje) lub `/login/index.html`
- `GET /welcome/` → strona startowa
- `GET /login/` → logowanie
- `GET /registration/` → rejestracja (**po sukcesie automatyczny login**, token zapisany w `localStorage`)
- `GET /dashboard` → przekierowanie do `/menu/dashboard.html`
- `GET /logout` → przekierowanie do `/goodbye/index.html`
Strona `goodbye`:
- czyści token z `localStorage`,
- po ~3s **automatycznie** przenosi na `/welcome/index.html`.

### API (wybrane)

- `POST /api/auth/register`
Body (JSON): `{"email":"[email protected]","password":"Haslo123!","full_name":"Twoje Imię"}`
- `POST /api/auth/login`
Body (JSON): `{"email":"[email protected]","password":"Haslo123!"}`
- `GET /api/auth/me`
Header: `Authorization: Bearer <token>`

Szybki test z curl:

```bash
# Rejestracja
curl -X POST http://127.0.0.1:8000/api/auth/register -H "Content-Type: application/json" -d '{"email":"[email protected]","password":"Test123!","full_name":"Test"}'

# Logowanie
curl -X POST http://127.0.0.1:8000/api/auth/login -H "Content-Type: application/json" -d '{"email":"[email protected]","password":"Test123!"}'
```

## Uruchomienie aplikacji konsolowej (opcjonalnie)

W drugim terminalu (z aktywnym `venv`):

```bash
python3 console_py/src/app.py
```

## Typowe problemy i rozwiązania

- **`No module named uvicorn`**
Użyj formy modułu: `python3 -m uvicorn backend.app.main:app --reload`.
Upewnij się, że masz aktywne `venv`.

- **Bcrypt/Passlib ostrzeżenia/błędy**
Zainstaluj zgodne wersje:
`pip install --upgrade "passlib[bcrypt]==1.7.4" "bcrypt==4.0.1"`

- **Frontend ładuje „stare” pliki** (cache)
Zrób twardy reload (**Cmd+Shift+R / Ctrl+F5**) albo DevTools → Network → **Disable cache**,
ewentualnie dopisz `?v=dev1` do linków CSS/JS.

- **404 na plikach statycznych**
Upewnij się, że w `backend/app/main.py` są mounty: `/welcome`, `/login`, `/registration`, `/menu`, `/goodbye`
oraz że ścieżki w HTML są **względne** (np. `dashboard.js`, `logout_goodbye_white.css`) — bez `/frontend/...`.

## Deployment (skrót)

Na produkcję rozważ:

```bash
pip install "uvicorn[standard]" gunicorn
# uruchomienie przykładowe:
gunicorn -k uvicorn.workers.UvicornWorker backend.app.main:app -w 2 -b 0.0.0.0:8000
```

Front serwowany przez FastAPI (StaticFiles) lub przez nginx.
Pamiętaj o **SECRET_KEY** z `.env`.

## Licencja

Własna / do uzgodnienia.

---

### Quick start (TL;DR)

```bash
cd my_demo
python3 -m venv venv
source venv/bin/activate
pip install fastapi "uvicorn[standard]" "passlib[bcrypt]==1.7.4" "bcrypt==4.0.1" "python-jose[cryptography]" "SQLAlchemy>=2.0" alembic python-dotenv pydantic-settings email-validator
python3 -m uvicorn backend.app.main:app --reload
# -> otwórz: http://127.0.0.1:8000/welcome/
```
Binary file added FastApi/My_demos/my_demo/app.db
Binary file not shown.
20 changes: 20 additions & 0 deletions FastApi/My_demos/my_demo/backend/app/auth_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from .db import get_db
from .models import User
from .schemas import UserCreate, UserOut, LoginReq, TokenOut
from .security import hash_password, verify_password, create_access_token
from .deps import get_current_user
router = APIRouter(prefix='/api/auth', tags=['auth'])
@router.post('/register', response_model=UserOut)
def register(p:UserCreate, db:Session=Depends(get_db)):
if db.query(User).filter(User.email==p.email).first(): raise HTTPException(status_code=400, detail='User already exists')
u=User(email=p.email, password_hash=hash_password(p.password), full_name=p.full_name or '')
db.add(u); db.commit(); db.refresh(u); return u
@router.post('/login', response_model=TokenOut)
def login(p:LoginReq, db:Session=Depends(get_db)):
u=db.query(User).filter(User.email==p.email).first()
if not u or not verify_password(p.password, u.password_hash): raise HTTPException(status_code=401, detail='Invalid credentials')
return {'access_token': create_access_token(u.email), 'token_type':'bearer'}
@router.get('/me', response_model=UserOut)
def me(user:User=Depends(get_current_user)): return user
10 changes: 10 additions & 0 deletions FastApi/My_demos/my_demo/backend/app/console_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from typing import Optional, Dict, Any, List
from .deps import get_current_user
from .console_runner import run_console_task
router = APIRouter(prefix='/api/console', tags=['console'])
class ConsoleReq(BaseModel): module: Optional[str]=None; function: Optional[str]=None; params: Optional[Dict[str, Any]]=None; script: Optional[str]=None; args: Optional[List[Any]]=None
@router.post('/run')
def run(req: ConsoleReq, user=Depends(get_current_user)):
return {'ok': True, 'result': run_console_task(req)}
20 changes: 20 additions & 0 deletions FastApi/My_demos/my_demo/backend/app/console_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import os, sys, subprocess, importlib.util
from typing import Any, Dict
BASE = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'console_py'))

def _import_module(m):
path=os.path.join(BASE,m+'.py');
if not os.path.exists(path): raise FileNotFoundError(m)
spec=importlib.util.spec_from_file_location(m,path); mod=importlib.util.module_from_spec(spec); spec.loader.exec_module(mod) #type: ignore
return mod

def run_console_task(req):
if getattr(req,'module',None) and getattr(req,'function',None):
mod=_import_module(req.module); fn=getattr(mod, req.function)
params=req.params or {}
return {'mode':'import','output': (fn(**params) if isinstance(params,dict) else fn(params))}
if getattr(req,'script',None):
sp=os.path.join(BASE, req.script); args=[str(a) for a in (req.args or [])]
proc=subprocess.run([sys.executable, sp]+args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
return {'mode':'subprocess','returncode':proc.returncode,'stdout':proc.stdout,'stderr':proc.stderr}
raise ValueError('Provide either (module + function) or (script).')
11 changes: 11 additions & 0 deletions FastApi/My_demos/my_demo/backend/app/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase
from .settings import settings
engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, connect_args={'check_same_thread': False}, pool_pre_ping=True)
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
class Base(DeclarativeBase): pass

def get_db():
db = SessionLocal();
try: yield db
finally: db.close()
18 changes: 18 additions & 0 deletions FastApi/My_demos/my_demo/backend/app/deps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from fastapi import Depends, HTTPException, Header
from jose import jwt, JWTError
from sqlalchemy.orm import Session
from .settings import settings
from .db import get_db
from .models import User
ALGO='HS256'

def get_current_user(authorization:str|None=Header(default=None), db:Session=Depends(get_db)) -> User:
if not authorization or not authorization.lower().startswith('bearer '):
raise HTTPException(status_code=401, detail='Missing Bearer token')
token = authorization.split()[1]
try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGO])
except JWTError: raise HTTPException(status_code=401, detail='Invalid token')
email = payload.get('sub');
user = db.query(User).filter(User.email==email).first()
if not user: raise HTTPException(status_code=401, detail='User not found')
return user
104 changes: 104 additions & 0 deletions FastApi/My_demos/my_demo/backend/app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# backend/app/main.py
from pathlib import Path

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse

from .settings import settings
from .db import engine, Base
from . import models # noqa: F401 (ensure models are imported so tables exist)

from .auth_router import router as auth_router
from .reservations_router import router as reservations_router
from .console_router import router as console_router

# DEV: create tables on first run (for prod prefer Alembic migrations)
Base.metadata.create_all(bind=engine)

app = FastAPI(title="Full App (FastAPI + SQLite)")

app.add_middleware(
CORSMiddleware,
allow_origins=[o.strip() for o in (settings.CORS_ORIGINS or "*").split(",")],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

FRONTEND_DIR = Path(__file__).resolve().parent.parent.parent / "frontend"

# ---- Static mounts (order doesn't matter) ----
if (FRONTEND_DIR / "welcome").exists():
app.mount("/welcome", StaticFiles(directory=str(FRONTEND_DIR / "welcome")), name="welcome")
if (FRONTEND_DIR / "login").exists():
app.mount("/login", StaticFiles(directory=str(FRONTEND_DIR / "login")), name="login")
if (FRONTEND_DIR / "registration").exists():
app.mount("/registration", StaticFiles(directory=str(FRONTEND_DIR / "registration")), name="registration")
if (FRONTEND_DIR / "menu").exists():
app.mount("/menu", StaticFiles(directory=str(FRONTEND_DIR / "menu")), name="menu")
if (FRONTEND_DIR / "goodbye").exists():
app.mount("/goodbye", StaticFiles(directory=str(FRONTEND_DIR / "goodbye")), name="goodbye")

# ---- Views (HTML redirects to the correct static files) ----
@app.get("/", response_class=HTMLResponse)
def root():
if (FRONTEND_DIR / "welcome" / "index.html").exists():
return RedirectResponse(url="/welcome/index.html")
if (FRONTEND_DIR / "login" / "index.html").exists():
return RedirectResponse(url="/login/index.html")
return HTMLResponse("<h1>Upload your frontend: welcome/index.html or login/index.html</h1>", status_code=200)

@app.get("/register", response_class=HTMLResponse)
def register_view():
if (FRONTEND_DIR / "registration" / "index2.html").exists():
return RedirectResponse(url="/registration/index2.html")
if (FRONTEND_DIR / "registration" / "index.html").exists():
return RedirectResponse(url="/registration/index.html")
return HTMLResponse("<h1>Upload your frontend: registration/index.html</h1>", status_code=200)

@app.get("/dashboard", response_class=HTMLResponse)
def dashboard_view():
if (FRONTEND_DIR / "menu" / "dashboard.html").exists():
return RedirectResponse(url="/menu/dashboard.html")
if (FRONTEND_DIR / "menu" / "index.html").exists():
return RedirectResponse(url="/menu/index.html")
return HTMLResponse("<h1>Upload your frontend: menu/dashboard.html</h1>", status_code=200)

@app.get("/logout", response_class=HTMLResponse)
def logout_view():
# Klient czyści token w JS; tu podajemy stronę pożegnalną
if (FRONTEND_DIR / "goodbye" / "index.html").exists():
return RedirectResponse(url="/goodbye/index.html")
return HTMLResponse("<h1>Logged out. (goodbye view missing)</h1>", status_code=200)

# ---- Debug helpers (możesz usunąć po diagnozie) ----
@app.get("/__debug/where")
def debug_where():
p_login = FRONTEND_DIR / "login" / "index.html"
p_welcome = FRONTEND_DIR / "welcome" / "index.html"
p_menu = FRONTEND_DIR / "menu" / "dashboard.html"
return JSONResponse(
{
"FRONTEND_DIR": str(FRONTEND_DIR.resolve()),
"welcome_index_exists": p_welcome.exists(),
"welcome_index_path": str(p_welcome.resolve()) if p_welcome.exists() else None,
"login_index_exists": p_login.exists(),
"login_index_path": str(p_login.resolve()) if p_login.exists() else None,
"dashboard_exists": p_menu.exists(),
"dashboard_path": str(p_menu.resolve()) if p_menu.exists() else None,
}
)

@app.get("/__debug/login-html", response_class=HTMLResponse)
def debug_login_html():
p = FRONTEND_DIR / "login" / "index.html"
if p.exists():
return HTMLResponse(p.read_text(encoding="utf-8", errors="ignore")[:400])
return HTMLResponse("login/index.html: MISSING", status_code=404)

# ---- API routers ----
app.include_router(auth_router)
app.include_router(reservations_router)
app.include_router(console_router)
28 changes: 28 additions & 0 deletions FastApi/My_demos/my_demo/backend/app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from datetime import datetime
from sqlalchemy import String, Integer, DateTime, func, Boolean, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from .db import Base

class User(Base):
__tablename__ = "users"

id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False)
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
full_name: Mapped[str] = mapped_column(String(255), default="")
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
is_superuser: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

class Reservation(Base):
__tablename__ = "reservations"

id: Mapped[str] = mapped_column(String(64), primary_key=True)
seat_id: Mapped[str] = mapped_column(String(64), index=True, nullable=False)
date: Mapped[str] = mapped_column(String(10), index=True, nullable=False) # YYYY-MM-DD
name: Mapped[str] = mapped_column(String(255), nullable=False)
email: Mapped[str] = mapped_column(String(255), nullable=False)
notes: Mapped[str] = mapped_column(String(1024), default="")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

__table_args__ = (UniqueConstraint("seat_id", "date", name="uq_resv_seat_date"),)
Loading