Skip to content

Commit

Permalink
feat: raw sql replacing sqlalchemy in auth/me endpoint (#1334)
Browse files Browse the repository at this point in the history
* refactor: implemented raw sql replacing sqlalchemy

* fix: pytest failure with user info

* refactor sql using CONFLICT ON

* refactor: removed unnecessary use of COALESCE and Loop

---------

Co-authored-by: sujanadh <[email protected]>
  • Loading branch information
Sujanadh and sujanadh authored Mar 19, 2024
1 parent e3a9f6d commit dc170d4
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 40 deletions.
112 changes: 77 additions & 35 deletions src/backend/app/auth/auth_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@
#

"""Auth routes, to login, logout, and get user details."""
from datetime import datetime, timezone

from fastapi import APIRouter, Depends, HTTPException, Request, Response
from fastapi.responses import JSONResponse
from loguru import logger as log
from sqlalchemy import text
from sqlalchemy.orm import Session

from app.auth.osm import AuthUser, init_osm_auth, login_required
from app.config import settings
from app.db import database
from app.db.db_models import DbUser
from app.users import user_crud
from app.models.enums import UserRole

router = APIRouter(
prefix="/auth",
Expand Down Expand Up @@ -127,45 +128,86 @@ async def logout():
async def get_or_create_user(
db: Session,
user_data: AuthUser,
) -> DbUser:
):
"""Get user from User table if exists, else create."""
existing_user = await user_crud.get_user(db, user_data.id)

if existing_user:
# Update an existing user
if user_data.img_url:
existing_user.profile_img = user_data.img_url
db.commit()
return existing_user

user_by_username = await user_crud.get_user_by_username(db, user_data.username)
if user_by_username:
raise HTTPException(
status_code=400,
detail=(
f"User with this username {user_data.username} already exists. "
"Please contact the administrator."
),
try:
update_sql = text(
"""
INSERT INTO users (
id, username, profile_img, role, mapping_level,
is_email_verified, is_expert, tasks_mapped, tasks_validated,
tasks_invalidated, date_registered, last_validation_date
)
VALUES (
:user_id, :username, :profile_img, :role,
:mapping_level, FALSE, FALSE, 0, 0, 0,
:current_date, :current_date
)
ON CONFLICT (id)
DO UPDATE SET profile_img = :profile_img;
"""
)
role = UserRole(user_data.role).name
db.execute(
update_sql,
{
"user_id": user_data.id,
"username": user_data.username,
"profile_img": user_data.profile_img,
"role": role,
"mapping_level": "BEGINNER",
"current_date": datetime.now(timezone.utc),
},
)
db.commit()

# Add user to database
db_user = DbUser(
id=user_data.id,
username=user_data.username,
profile_img=user_data.img_url,
role=user_data.role,
)
db.add(db_user)
db.commit()

return db_user


@router.get("/me/", response_model=AuthUser)
get_sql = text(
"""
SELECT users.*,
user_roles.project_id as project_id,
organisation_managers.organisation_id as created_org,
COALESCE(user_roles.role, 'MAPPER') as project_role
FROM users
LEFT JOIN user_roles ON users.id = user_roles.user_id
LEFT JOIN organisation_managers on users.id = organisation_managers.user_id
WHERE users.id = :user_id;
"""
)
result = db.execute(
get_sql,
{"user_id": user_data.id},
)
db_user = result.first()

user = {
"id": db_user.id,
"username": db_user.username,
"profile_img": db_user.profile_img,
"role": db_user.role,
"project_id": db_user.project_id,
"project_role": db_user.project_role,
"created_org": db_user.created_org,
}
return user

except Exception as e:
# Check if the exception is due to username already existing
if 'duplicate key value violates unique constraint "users_username_key"' in str(
e
):
raise HTTPException(
status_code=400,
detail=f"User with this username {user_data.username} already exists.",
) from e
else:
raise HTTPException(status_code=400, detail=str(e)) from e


@router.get("/me/")
async def my_data(
db: Session = Depends(database.get_db),
user_data: AuthUser = Depends(login_required),
) -> AuthUser:
):
"""Read access token and get user details from OSM.
Args:
Expand Down
2 changes: 1 addition & 1 deletion src/backend/app/auth/osm.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class AuthUser(BaseModel):

id: int
username: str
img_url: Optional[str] = None
profile_img: Optional[str] = None
role: Optional[UserRole] = UserRole.MAPPER


Expand Down
6 changes: 3 additions & 3 deletions src/backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ async def admin_user(db):
),
)
# Upgrade role from default MAPPER (if user already exists)
db_user.role = UserRole.ADMIN
db_user["role"] = UserRole.ADMIN
db.commit()
return db_user

Expand Down Expand Up @@ -175,8 +175,8 @@ async def project(db, admin_user, organisation):
project_metadata,
odkproject["id"],
AuthUser(
username=admin_user.username,
id=admin_user.id,
username=admin_user["username"],
id=admin_user["id"],
role=UserRole.ADMIN,
),
)
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/utilfunctions/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const createLoginWindow = (redirectTo) => {
username: userRes.username,
osm_oauth_token: res.access_token,
id: userRes.id,
picture: userRes.img_url,
picture: userRes.profile_img,
redirect_to: redirectTo,
role: userRes.role,
}).toString();
Expand Down

0 comments on commit dc170d4

Please sign in to comment.