Skip to content
This repository was archived by the owner on Jan 1, 2026. It is now read-only.

Commit 9fedd95

Browse files
authored
Add basic registration (#8)
* Refactor user registration to use PostgreSQL stored procedure and sanitize username input * Refactor user registration to use UserRegister schema and remove UserCreate * Refactor username sanitization: move sanitize_username function to string_utils and remove username.py * Enhance user registration: add HTTPException for existing users and improve code structure * Add docstring to user registration endpoint for clarity * Fix attribute name in User model: update language_id to language_iso_code for clarity * Refactor language preference handling: rename language_iso_code to language_id in User model and registration schema for consistency
1 parent dfcebeb commit 9fedd95

File tree

4 files changed

+63
-18
lines changed

4 files changed

+63
-18
lines changed

app/models/user.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import uuid
1010
from datetime import datetime
1111

12-
from sqlalchemy import Boolean, Column, DateTime, Integer, String, Text
12+
from sqlalchemy import Boolean, Column, DateTime, Integer, Text
1313
from sqlalchemy.dialects.postgresql import UUID
1414

1515
from app.models import Base
@@ -44,7 +44,7 @@ class User(Base):
4444

4545
user_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
4646

47-
username = Column(String(50), unique=True, nullable=False)
47+
username = Column(Text, unique=True, nullable=False)
4848

4949
email_encrypted = Column(Text, nullable=False)
5050
email_hash = Column(Text, unique=True, nullable=False)
Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from fastapi import APIRouter, Depends
1+
from fastapi import APIRouter, Depends, HTTPException
2+
from sqlalchemy import text
23
from sqlalchemy.ext.asyncio import AsyncSession
34

4-
from app.models.user import User
5-
from app.routes.v1.schemas.user.create import UserCreate
5+
from app.routes.v1.schemas.user.register import UserRegister
66
from app.utility.database import get_db
77
from app.utility.security import (
88
encrypt_email,
@@ -11,23 +11,59 @@
1111
hash_password,
1212
hash_phone,
1313
)
14+
from app.utility.string_utils import sanitize_username
1415

1516
router = APIRouter()
1617

1718

1819
@router.post("/register")
19-
async def register(data: UserCreate, db: AsyncSession = Depends(get_db)):
20+
async def register(data: UserRegister, db: AsyncSession = Depends(get_db)):
2021
"""Endpoint for user registration."""
21-
user = User(
22-
username=data.username,
23-
email_encrypted=encrypt_email(data.email),
24-
email_hash=hash_email(data.email),
25-
password_hash=hash_password(data.password),
26-
phone_encrypted=encrypt_phone(data.phone) if data.phone else None,
27-
phone_hash=hash_phone(data.phone) if data.phone else None,
28-
language_id=data.language_id,
22+
username = sanitize_username(data.username)
23+
email_encrypted = encrypt_email(data.email)
24+
email_hash = hash_email(data.email)
25+
password_hash = hash_password(data.password)
26+
phone_encrypted = encrypt_phone(data.phone) if data.phone else None
27+
phone_hash = hash_phone(data.phone) if data.phone else None
28+
language_id = data.language_id
29+
30+
# Check if user is available
31+
result = await db.execute(
32+
text(
33+
"""
34+
SELECT is_user_available(:username, :email_hash, :phone_hash) AS available
35+
"""
36+
),
37+
{"username": username, "email_hash": email_hash, "phone_hash": phone_hash},
38+
)
39+
available = result.scalar()
40+
41+
if not available:
42+
raise HTTPException(409, "Username, email, or phone already exists")
43+
44+
await db.execute(
45+
text(
46+
"""
47+
CALL register_user(
48+
:username,
49+
:email_encrypted,
50+
:email_hash,
51+
:password_hash,
52+
:phone_encrypted,
53+
:phone_hash,
54+
:preferred_language_id
55+
)
56+
"""
57+
),
58+
{
59+
"username": username,
60+
"email_encrypted": email_encrypted,
61+
"email_hash": email_hash,
62+
"password_hash": password_hash,
63+
"phone_encrypted": phone_encrypted,
64+
"phone_hash": phone_hash,
65+
"preferred_language_id": language_id,
66+
},
2967
)
30-
db.add(user)
3168
await db.commit()
32-
await db.refresh(user)
33-
return {"message": "User registered successfully", "user_id": str(user.user_id)}
69+
return {"message": "User registered successfully", "username": username}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from pydantic import BaseModel, EmailStr
22

33

4-
class UserCreate(BaseModel):
4+
class UserRegister(BaseModel):
55
username: str
66
email: EmailStr
77
password: str

app/utility/string_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import re
2+
3+
4+
def sanitize_username(username: str) -> str:
5+
"""
6+
Replace all characters not allowed by the database username constraint
7+
(^[a-zA-Z0-9_]+$) with underscores.
8+
"""
9+
return re.sub(r"[^a-zA-Z0-9_]", "_", username)

0 commit comments

Comments
 (0)