mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-20 10:30:16 -04:00
Beginning implementation of users with supabase crossing auth and db
This commit is contained in:
parent
bdc6600a3f
commit
acba02dd55
84
backend/api/supabase_user.py
Normal file
84
backend/api/supabase_user.py
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from ..services import SupabaseUserService
|
||||||
|
from ..models.user_model import User, UserTypeEnum
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
api = APIRouter(prefix="/api/supabaseuser")
|
||||||
|
|
||||||
|
openapi_tags = {
|
||||||
|
"name": "Supabase Users",
|
||||||
|
"description": "User profile search and related operations.",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@api.get("/", response_model=User, tags=["Supabase Users"])
|
||||||
|
def get_user(id: str, user_svc: SupabaseUserService = Depends()):
|
||||||
|
return user_svc.get_user(id)
|
||||||
|
|
||||||
|
|
||||||
|
@api.post("/", response_model=User, tags=["Supabase Users"])
|
||||||
|
def create_user(
|
||||||
|
id: str,
|
||||||
|
email: str,
|
||||||
|
password: str,
|
||||||
|
role: UserTypeEnum,
|
||||||
|
user_svc: SupabaseUserService = Depends(),
|
||||||
|
):
|
||||||
|
subject = user_svc.get_user(id)
|
||||||
|
if subject.role != UserTypeEnum.ADMIN:
|
||||||
|
raise Exception(f"Insufficient permissions for user {subject.uuid}")
|
||||||
|
return user_svc.create_user(email=email, password=password, role=role)
|
||||||
|
|
||||||
|
|
||||||
|
@api.put("/", response_model=User, tags=["Supabase Users"])
|
||||||
|
def update_user(uuid: str, user: User, user_svc: SupabaseUserService = Depends()):
|
||||||
|
subject = user_svc.get_user(uuid)
|
||||||
|
if subject.role != UserTypeEnum.ADMIN:
|
||||||
|
raise Exception(f"Insufficient permissions for user {subject.uuid}")
|
||||||
|
|
||||||
|
return user_svc.update_user(user)
|
||||||
|
|
||||||
|
|
||||||
|
@api.delete("/", response_model=None, tags=["Supabase Users"])
|
||||||
|
def delete_user(uuid: str, user: User, user_svc: SupabaseUserService = Depends()):
|
||||||
|
subject = user_svc.get_user(uuid)
|
||||||
|
if subject.role != UserTypeEnum.ADMIN:
|
||||||
|
raise Exception(f"Insufficient permissions for user {subject.uuid}")
|
||||||
|
|
||||||
|
return user_svc.delete_user(user.id)
|
||||||
|
|
||||||
|
|
||||||
|
@api.get("/login", response_model=List[User], tags=["Supabase Users"])
|
||||||
|
def login_user(
|
||||||
|
email: str,
|
||||||
|
password: str,
|
||||||
|
user_svc: SupabaseUserService = Depends(),
|
||||||
|
):
|
||||||
|
return user_svc.login_user(email=email, password=password)
|
||||||
|
|
||||||
|
|
||||||
|
@api.get("/logout", response_model=User, tags=["Supabase Users"])
|
||||||
|
def logout_user(
|
||||||
|
id: str,
|
||||||
|
user_svc: SupabaseUserService = Depends(),
|
||||||
|
):
|
||||||
|
return user_svc.logout_user(id=id)
|
||||||
|
|
||||||
|
|
||||||
|
@api.get("/forgot-password", response_model=User, tags=["Supabase Users"])
|
||||||
|
def forgot_password(
|
||||||
|
email: str,
|
||||||
|
user_svc: SupabaseUserService = Depends(),
|
||||||
|
):
|
||||||
|
return user_svc.forgot_password(email=email)
|
||||||
|
|
||||||
|
|
||||||
|
@api.get("/update-password", response_model=User, tags=["Supabase Users"])
|
||||||
|
def update_password(
|
||||||
|
id: str,
|
||||||
|
new_password: str,
|
||||||
|
user_svc: SupabaseUserService = Depends(),
|
||||||
|
):
|
||||||
|
return user_svc.update_password(new_password=new_password)
|
|
@ -3,7 +3,7 @@ from fastapi.responses import JSONResponse
|
||||||
from fastapi.middleware.gzip import GZipMiddleware
|
from fastapi.middleware.gzip import GZipMiddleware
|
||||||
|
|
||||||
|
|
||||||
from .api import user, health, service, resource, tag
|
from .api import user, health, service, resource, tag, supabase_user
|
||||||
|
|
||||||
description = """
|
description = """
|
||||||
Welcome to the **COMPASS** RESTful Application Programming Interface.
|
Welcome to the **COMPASS** RESTful Application Programming Interface.
|
||||||
|
@ -18,13 +18,14 @@ app = FastAPI(
|
||||||
health.openapi_tags,
|
health.openapi_tags,
|
||||||
service.openapi_tags,
|
service.openapi_tags,
|
||||||
resource.openapi_tags,
|
resource.openapi_tags,
|
||||||
tag.openapi_tags
|
tag.openapi_tags,
|
||||||
|
supabase_user.openapi_tags,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
app.add_middleware(GZipMiddleware)
|
app.add_middleware(GZipMiddleware)
|
||||||
|
|
||||||
feature_apis = [user, health, service, resource, tag]
|
feature_apis = [user, health, service, resource, tag, supabase_user]
|
||||||
|
|
||||||
for feature_api in feature_apis:
|
for feature_api in feature_apis:
|
||||||
app.include_router(feature_api.api)
|
app.include_router(feature_api.api)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from .user import UserService
|
from .user import UserService
|
||||||
from .resource import ResourceService
|
from .resource import ResourceService
|
||||||
from .tag import TagService
|
from .tag import TagService
|
||||||
from .service import ServiceService
|
from .service import ServiceService
|
||||||
|
from .supabase_user import SupabaseUserService
|
||||||
|
|
201
backend/services/supabase_user.py
Normal file
201
backend/services/supabase_user.py
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
from fastapi import Depends, HTTPException
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from sqlalchemy import select
|
||||||
|
from supabase import create_client, Client
|
||||||
|
import os
|
||||||
|
|
||||||
|
from ..database import db_session
|
||||||
|
from ..entities.user_entity import UserEntity
|
||||||
|
from ..models.user_model import User
|
||||||
|
from backend.services.user import UserService
|
||||||
|
from ..models.enum_for_models import ProgramTypeEnum, UserTypeEnum
|
||||||
|
|
||||||
|
|
||||||
|
class SupabaseUserService:
|
||||||
|
def __init__(self, session: Session = Depends(db_session)):
|
||||||
|
self._session = session
|
||||||
|
self.client = create_client(
|
||||||
|
os.getenv("SUPABASE_URL"), os.getenv("SUPABASE_KEY")
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_user(self, id: str) -> User:
|
||||||
|
"""
|
||||||
|
Gets a user by id from the database
|
||||||
|
Returns: A User Pydantic model
|
||||||
|
"""
|
||||||
|
query = select(UserEntity).where(UserEntity.uuid == id)
|
||||||
|
result = self._session.execute(query)
|
||||||
|
user_entity = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if user_entity is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404, detail=f"No user found with matching id: {id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return user_entity.to_model()
|
||||||
|
|
||||||
|
def create_user(self, email: str, password: str, role: UserTypeEnum) -> User:
|
||||||
|
"""
|
||||||
|
Creates a new User Entity and adds to database
|
||||||
|
Args: email and password
|
||||||
|
Returns: User model
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not email and not password:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail="Email and password required"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create user in Supabase Auth
|
||||||
|
auth_response = self.client.auth.sign_up(
|
||||||
|
{"email": email, "password": password}
|
||||||
|
)
|
||||||
|
|
||||||
|
if auth_response.user is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Supabase auth error: {auth_response}",
|
||||||
|
)
|
||||||
|
|
||||||
|
supabase_user = auth_response.user
|
||||||
|
|
||||||
|
# Create user in database using existing UserService
|
||||||
|
randomUserName = email.split("@")[0] # Simple username generation
|
||||||
|
user_model = User(
|
||||||
|
uuid=supabase_user.id,
|
||||||
|
email=email,
|
||||||
|
username=randomUserName,
|
||||||
|
role=role,
|
||||||
|
program=[ProgramTypeEnum.DOMESTIC],
|
||||||
|
experience=0,
|
||||||
|
group="volunteer",
|
||||||
|
)
|
||||||
|
user_entity = UserEntity.from_model(user_model)
|
||||||
|
# add new user to table
|
||||||
|
self._session.add(user_entity)
|
||||||
|
self._session.commit()
|
||||||
|
|
||||||
|
except:
|
||||||
|
raise Exception(f"Failed to create user")
|
||||||
|
|
||||||
|
return user_entity.to_model()
|
||||||
|
|
||||||
|
def delete_user(self, user_id: int) -> None:
|
||||||
|
"""
|
||||||
|
Delete a user
|
||||||
|
Args: the user ID to delete
|
||||||
|
Returns: none
|
||||||
|
"""
|
||||||
|
|
||||||
|
obj = self._session.get(UserEntity, user_id) # Get user entity from database
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f"No matching user found")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Delete user from Supabase Auth
|
||||||
|
# This is commented out because it may not be necessary to delete the user from Supabase Auth
|
||||||
|
# self.client.auth.admin.delete_user(obj.uuid, should_soft_delete=False)
|
||||||
|
|
||||||
|
# Delete user from database
|
||||||
|
self._session.delete(obj)
|
||||||
|
self._session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
self._session.rollback()
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500, detail=f"Failed to delete user: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_user(self, user: User) -> User:
|
||||||
|
"""
|
||||||
|
Updates a user
|
||||||
|
Args: User to be updated
|
||||||
|
Returns: The updated User
|
||||||
|
"""
|
||||||
|
obj = self._session.get(UserEntity, user.id)
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
raise Exception(f"No matching user found")
|
||||||
|
|
||||||
|
obj.username = user.username if user.username else obj.username
|
||||||
|
obj.role = user.role if user.role else obj.role
|
||||||
|
obj.email = user.email if user.email else obj.email
|
||||||
|
obj.program = user.program if user.program else obj.program
|
||||||
|
obj.experience = user.experience if user.experience else obj.experience
|
||||||
|
obj.group = user.group if user.group else obj.group
|
||||||
|
|
||||||
|
self._session.commit()
|
||||||
|
|
||||||
|
return obj.to_model()
|
||||||
|
|
||||||
|
def get_user_by_email(self, email: str) -> User:
|
||||||
|
"""
|
||||||
|
Gets a user by email from the database
|
||||||
|
Returns: A User Pydantic model
|
||||||
|
"""
|
||||||
|
query = select(UserEntity).where(UserEntity.email == email)
|
||||||
|
result = self._session.execute(query)
|
||||||
|
user_entity = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if user_entity is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404, detail=f"No user found with email: {email}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return user_entity.to_model()
|
||||||
|
|
||||||
|
def login_user(self, email: str, password: str):
|
||||||
|
"""
|
||||||
|
Login user with Supabase Auth
|
||||||
|
Returns: Auth session data
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
auth_response = self.client.auth.sign_in_with_password(
|
||||||
|
{"email": email, "password": password}
|
||||||
|
)
|
||||||
|
|
||||||
|
if auth_response.error:
|
||||||
|
raise HTTPException(status_code=401, detail="Invalid login credentials")
|
||||||
|
|
||||||
|
# You can then fetch the user from your database if needed
|
||||||
|
user = self.get_user(auth_response.user.id)
|
||||||
|
|
||||||
|
return {"user": user, "session": auth_response.session}
|
||||||
|
except Exception as e:
|
||||||
|
if isinstance(e, HTTPException):
|
||||||
|
raise e
|
||||||
|
raise HTTPException(status_code=500, detail=f"Login failed: {str(e)}")
|
||||||
|
|
||||||
|
def logout_user(self):
|
||||||
|
"""
|
||||||
|
Logout user from Supabase Auth
|
||||||
|
Returns: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.client.auth.sign_out()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"Logout failed: {str(e)}")
|
||||||
|
|
||||||
|
def forgot_password(self, email: str):
|
||||||
|
"""
|
||||||
|
Send a password reset email to the user
|
||||||
|
Returns: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = self.client.auth.reset_password_for_email(email)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500, detail=f"Error sending reset email: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_password(self, password: str):
|
||||||
|
"""
|
||||||
|
Update the user's password
|
||||||
|
Returns: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = self.client.auth.update_user({"password": password})
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500, detail=f"Error updating password: {str(e)}"
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user