Initial Commit

This commit is contained in:
belviskhoremk
2026-05-12 00:34:21 +00:00
commit d2dc43b16f
57 changed files with 6056 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
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:
try:
result = _client().auth.sign_up({"email": req.email, "password": req.password})
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 NOTHING
""",
user_id, req.email, req.resolved_name(), req.phone,
)
if result.session:
return {
"access_token": result.session.access_token,
"refresh_token": result.session.refresh_token,
"token_type": "bearer",
"expires_in": result.session.expires_in,
}
return {"message": "Account created successfully."}
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)