Remove backend folder

This commit is contained in:
Arthur Belleville 2026-05-14 16:50:48 +02:00
parent b7f7a8964a
commit 5f63929477
No known key found for this signature in database
30 changed files with 4 additions and 1434 deletions

4
.gitignore vendored
View file

@ -48,3 +48,7 @@ supabase/.branches
# Podman
.podman-compose
# AI
.claude
.codex

View file

@ -1 +0,0 @@
3.12

View file

@ -1,49 +0,0 @@
# Use a Python image with uv pre-installed
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
# Install the project into `/app`
WORKDIR /app
# Enable bytecode compilation
ENV UV_COMPILE_BYTECODE=1
# Copy from the cache instead of linking since it's a mounted volume
ENV UV_LINK_MODE=copy
# Install system dependencies in one go
# Use optimization to reduce installation time and image size
# Update package lists and install dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
curl \
npm && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Get Rust
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
# Add .cargo/bin to PATH
ENV PATH="/root/.cargo/bin:${PATH}"
# Install the project's dependencies using the lockfile and settings
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-project --no-dev
# Then, add the rest of the project source code and install it
# Installing separately from its dependencies allows optimal layer caching
ADD . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev
# Place executables in the environment at the front of the path
ENV PATH="/app/.venv/bin:$PATH"
# Reset the entrypoint, don't invoke `uv`
ENTRYPOINT []
# Run the FastAPI application by default
# Uses `fastapi dev` to enable hot-reloading when the `watch` sync occurs
# Uses `--host 0.0.0.0` to allow access from outside the container
CMD ["fastapi", "run", "--host", "0.0.0.0", "--port", "80", "app/main.py"]

View file

View file

@ -1,31 +0,0 @@
# Domain
# This would be set to the production domain with an env var on deployment
DOMAIN=localhost
# FRONTEND_HOST=http://localhost:5173
# Environment: local, staging, production
ENVIRONMENT=local
PROJECT_NAME="XTablo"
# Backend
BACKEND_CORS_ORIGINS="http://localhost"
SECRET_KEY=local_dev
FIRST_SUPERUSER=baptiste@xtablo.com
FIRST_SUPERUSER_PASSWORD=admin12345_gxydlksjwqnlk
# run `supabase status`
# API URL
SUPABASE_URL=https://mhcafqvzbrrwvahpvvzd.supabase.co
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1oY2FmcXZ6YnJyd3ZhaHB2dnpkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDEyNDEzMjEsImV4cCI6MjA1NjgxNzMyMX0.Otxn5BWCPD2ABlMM59hCgeur9Tf_Q7PndAbTkqXDPtM
# service_role key
SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1oY2FmcXZ6YnJyd3ZhaHB2dnpkIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MTI0MTMyMSwiZXhwIjoyMDU2ODE3MzIxfQ.9r33CUsu6ZR4vyv4ed-UY6cLE1FZzSSxTNE8pFUKjN4
# Postgres
# DB URL: postgresql://postgres:postgres@localhost:54322/postgres
POSTGRES_SERVER=localhost
POSTGRES_PORT=54322
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres

View file

@ -1,16 +0,0 @@
from pydantic_settings import BaseSettings
from pathlib import Path
from dotenv import load_dotenv
import os
# Load environment variables from the .env file
env_path = Path(__file__).parent / ".env"
load_dotenv(dotenv_path=env_path)
class Settings(BaseSettings):
supabase_url: str = os.getenv("SUPABASE_URL")
supabase_key: str = os.getenv("SUPABASE_KEY")
secret_key: str = os.getenv("SECRET_KEY")
access_token_expire_minutes: int = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", 30))
settings = Settings()

View file

@ -1,64 +0,0 @@
import os
import json
import random
import inspect
from typing import Annotated, Dict, List, Optional
from contextlib import contextmanager
from app.routers.auth import get_supabase, router as auth_router
from fastapi import FastAPI, Depends, HTTPException, status, Request
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel, EmailStr, field_validator, ValidationInfo, Field, SecretStr
from pydantic_core.core_schema import FieldValidationInfo
from dotenv import load_dotenv
from supabase import Client
from uuid import uuid4
from datetime import datetime
# Initialize FastAPI app
app = FastAPI(title="XTablo API")
# CORS Middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["X-Error-Code", "X-Error-Message"]
)
app.include_router(auth_router, prefix="/auth")
# Security
security = HTTPBearer()
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
errors = exc.errors()
custom_errors = []
for error in errors:
custom_message = error["msg"]
if custom_message.startswith("Value error, "):
custom_message = custom_message.split(", ")[1]
custom_errors.append({
**error,
"form_location": error["loc"][-1],
"human_error": custom_message
})
return JSONResponse(status_code=422, content=jsonable_encoder(custom_errors))
@app.get("/ping")
async def ping():
"""Health check endpoint that returns a success status."""
return {"status": "success", "message": "API is running"}

View file

@ -1,83 +0,0 @@
from fastapi import Depends, HTTPException, status, Request
from fastapi.routing import APIRouter
from fastapi.encoders import jsonable_encoder
from fastapi.security import OAuth2PasswordBearer
from fastapi.responses import RedirectResponse
from supabase import Client
from app.config import settings
from jose import JWTError, jwt
import os
from app.routers.helpers import get_supabase, get_current_user_required, get_current_user_optional
from app.schemas.user import UserCreate, UserLogin, UserOut
from app.schemas.token import RefreshResponse, RefreshToken
router = APIRouter(tags=["auth"])
@router.get("/login/google")
async def login_with_google(supabase: Client = Depends(get_supabase)):
try:
response = supabase.auth.sign_in_with_oauth({
"provider": "google",
"options": {
"redirect_to": "http://localhost:8000/auth/callback"
}
})
return {"auth_url": response.url}
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)
@router.get("/callback")
async def google_callback(request: Request, supabase: Client = Depends(get_supabase)):
code = request.query_params.get("code")
if not code:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Missing authorization code")
supabase.auth.exchange_code_for_session({"auth_code": code})
return RedirectResponse(url="http://localhost:5173")
@router.get("/users/me")
async def get_me(
user = Depends(get_current_user_required),
):
try:
return jsonable_encoder(user)
except IndexError:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found in database"
)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)
@router.post("/refresh", response_model=RefreshResponse)
async def refresh_token(refresh_request: RefreshToken, supabase: Client = Depends(get_supabase)):
"""Refresh the access token using a valid refresh token."""
try:
# Validate the refresh token and get new tokens
response = supabase.auth.refresh_session(refresh_request.refresh_token)
# Extract user data
user_data = {
"id": response.user.id,
"email": response.user.email,
"first_name": response.user.user_metadata.get("first_name", "Unknown"),
"last_name": response.user.user_metadata.get("last_name", "Unknown"),
"business_name": response.user.user_metadata.get("business_name", "Unknown")
}
# Return the new tokens and user data
return {
"access_token": response.session.access_token,
"refresh_token": response.session.refresh_token,
"expires_at": int(response.session.expires_at),
"user": user_data
}
except Exception as e:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=f"Failed to refresh token: {str(e)}")

View file

@ -1,40 +0,0 @@
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from typing import Optional
from supabase import Client
from app.config import settings
from supabase import create_client
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login", auto_error=False)
def get_supabase() -> Client:
supabase_client = create_client(settings.supabase_url, settings.supabase_key)
return supabase_client
# Updated current user dependency
async def get_user_from_token(
token: str = Depends(oauth2_scheme),
supabase: Client = Depends(get_supabase)
):
try:
# Get user from Supabase auth
return supabase.auth.get_user(token)
except Exception as e:
return None
def get_current_user_required(user: Optional[dict] = Depends(get_user_from_token)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
if not user:
raise credentials_exception
return user
def get_current_user_optional(
user: Optional[dict] = Depends(get_user_from_token)
) -> Optional[dict]:
return user

View file

@ -1,11 +0,0 @@
from pydantic import BaseModel
class RefreshToken(BaseModel):
refresh_token: str
class RefreshResponse(BaseModel):
access_token: str
refresh_token: str
expires_at: int
user: dict

View file

@ -1,43 +0,0 @@
from pydantic import BaseModel, EmailStr, field_validator, Field, SecretStr
from pydantic_core.core_schema import FieldValidationInfo
class UserCreate(BaseModel):
email: EmailStr
first_name: str
last_name: str
password: str
confirm_password: str
business_name: str
@field_validator("email")
def email_must_contain_at_symbol(cls, v):
if '@' not in v:
raise ValueError("Entrer un email valide")
return v
@field_validator("password")
def password_must_contain_at_least_8_characters(cls, v):
if len(v) < 8:
raise ValueError("Le mot de passe doit contenir au moins 8 caractères")
return v
@field_validator("business_name")
def business_name_must_contain_at_least_3_characters(cls, v):
if len(v) < 3:
raise ValueError("Le nom de la société doit contenir au moins 3 caractères")
return v
@field_validator('confirm_password', mode='before')
def passwords_match(cls, v, info: FieldValidationInfo):
if 'password' in info.data and v != info.data['password']:
raise ValueError('Les mots de passe ne correspondent pas')
return v
class UserLogin(BaseModel):
email: EmailStr
password: str
class UserOut(BaseModel):
email: EmailStr
business_name: str

View file

@ -1,12 +0,0 @@
[project]
name = "backend"
version = "0.1.0"
description = "XTablo API"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi[standard]>=0.115.11",
"pydantic-settings>=2.8.1",
"python-jose>=3.4.0",
"supabase>=2.13.0",
]

File diff suppressed because it is too large Load diff