mirror of
http://88.130.71.182:3000/BlitTech/badoHair_be.git
synced 2026-06-12 23:23:22 +00:00
141 lines
4.5 KiB
Python
141 lines
4.5 KiB
Python
import asyncpg
|
|
from supabase import create_client, Client
|
|
|
|
from app.config import get_settings
|
|
from app.exceptions import AppError, UnauthorizedError
|
|
from app.models.auth import RegisterRequest, LoginRequest, RefreshRequest
|
|
|
|
|
|
def _client() -> Client:
|
|
s = get_settings()
|
|
return create_client(s.SUPABASE_URL, s.SUPABASE_ANON_KEY)
|
|
|
|
|
|
def _admin_client() -> Client:
|
|
s = get_settings()
|
|
return create_client(s.SUPABASE_URL, s.SUPABASE_SERVICE_ROLE_KEY)
|
|
|
|
|
|
async def register(req: RegisterRequest, db: asyncpg.Connection) -> dict:
|
|
# Use admin API to create user with email_confirm=True so no confirmation
|
|
# email is sent and the client can log in immediately after registration.
|
|
try:
|
|
result = _admin_client().auth.admin.create_user({
|
|
"email": req.email,
|
|
"password": req.password,
|
|
"email_confirm": True,
|
|
"user_metadata": {"full_name": req.resolved_name()},
|
|
})
|
|
except Exception as e:
|
|
raise AppError("REGISTRATION_FAILED", str(e), 400)
|
|
|
|
if not result.user:
|
|
raise AppError("REGISTRATION_FAILED", "Could not create account", 400)
|
|
|
|
user_id = str(result.user.id)
|
|
await db.execute(
|
|
"""
|
|
INSERT INTO profiles (id, email, full_name, phone, role)
|
|
VALUES ($1, $2, $3, $4, 'client')
|
|
ON CONFLICT (id) DO UPDATE SET
|
|
full_name = EXCLUDED.full_name,
|
|
email = EXCLUDED.email,
|
|
phone = COALESCE(EXCLUDED.phone, profiles.phone)
|
|
""",
|
|
user_id, req.email, req.resolved_name(), req.phone,
|
|
)
|
|
|
|
# Sign in immediately to return tokens
|
|
try:
|
|
login_result = _client().auth.sign_in_with_password({"email": req.email, "password": req.password})
|
|
except Exception as e:
|
|
raise AppError("REGISTRATION_FAILED", str(e), 400)
|
|
|
|
return {
|
|
"access_token": login_result.session.access_token,
|
|
"refresh_token": login_result.session.refresh_token,
|
|
"token_type": "bearer",
|
|
"expires_in": login_result.session.expires_in,
|
|
}
|
|
|
|
|
|
async def login(req: LoginRequest) -> dict:
|
|
try:
|
|
result = _client().auth.sign_in_with_password({"email": req.email, "password": req.password})
|
|
except Exception:
|
|
raise UnauthorizedError("Invalid email or password")
|
|
|
|
if not result.session:
|
|
raise UnauthorizedError("Invalid email or password")
|
|
|
|
return {
|
|
"access_token": result.session.access_token,
|
|
"refresh_token": result.session.refresh_token,
|
|
"token_type": "bearer",
|
|
"expires_in": result.session.expires_in,
|
|
}
|
|
|
|
|
|
async def refresh(req: RefreshRequest) -> dict:
|
|
try:
|
|
result = _client().auth.refresh_session(req.refresh_token)
|
|
except Exception:
|
|
raise UnauthorizedError("Invalid or expired refresh token")
|
|
|
|
return {
|
|
"access_token": result.session.access_token,
|
|
"refresh_token": result.session.refresh_token,
|
|
"token_type": "bearer",
|
|
"expires_in": result.session.expires_in,
|
|
}
|
|
|
|
|
|
async def forgot_password(email: str):
|
|
try:
|
|
_client().auth.reset_password_email(email)
|
|
except Exception:
|
|
pass # Always return success to avoid email enumeration
|
|
|
|
|
|
async def update_profile(user_id: str, db: asyncpg.Connection, full_name: str | None, phone: str | None) -> dict:
|
|
updates = {}
|
|
if full_name is not None:
|
|
updates["full_name"] = full_name
|
|
if phone is not None:
|
|
updates["phone"] = phone
|
|
|
|
if not updates:
|
|
row = await db.fetchrow("SELECT * FROM profiles WHERE id = $1", user_id)
|
|
return dict(row)
|
|
|
|
set_clauses = [f"{k} = ${i + 2}" for i, k in enumerate(updates)]
|
|
row = await db.fetchrow(
|
|
f"UPDATE profiles SET {', '.join(set_clauses)}, updated_at = now() WHERE id = $1 RETURNING *",
|
|
user_id, *updates.values(),
|
|
)
|
|
return dict(row)
|
|
|
|
|
|
async def create_admin_user(email: str, password: str, full_name: str, db: asyncpg.Connection) -> dict:
|
|
try:
|
|
result = _admin_client().auth.admin.create_user({
|
|
"email": email,
|
|
"password": password,
|
|
"email_confirm": True,
|
|
})
|
|
except Exception as e:
|
|
raise AppError("USER_CREATE_FAILED", str(e), 400)
|
|
|
|
user_id = str(result.user.id)
|
|
await db.execute(
|
|
"""
|
|
INSERT INTO profiles (id, email, full_name, role)
|
|
VALUES ($1, $2, $3, 'admin')
|
|
ON CONFLICT (id) DO UPDATE SET role = 'admin'
|
|
""",
|
|
user_id, email, full_name,
|
|
)
|
|
|
|
row = await db.fetchrow("SELECT * FROM profiles WHERE id = $1", user_id)
|
|
return dict(row)
|