feat(backend): migrate auth and notifications off email
Some checks failed
Backend CI/CD / test (push) Has been cancelled
Backend CI/CD / deploy (push) Has been cancelled

This commit is contained in:
2026-05-21 10:28:04 +03:30
parent b4903f7cb1
commit b7b21a6cc6
35 changed files with 2784 additions and 1390 deletions

View File

@@ -1,16 +1,20 @@
"""Authentication-related API schemas."""
from ninja import Schema, ModelSchema
from datetime import datetime
from typing import Optional
from core.media import PREVIEW_VARIANT, THUMBNAIL_VARIANT, derivative_url
from ninja import ModelSchema, Schema
from apps.users.models import User
from core.media import PREVIEW_VARIANT, THUMBNAIL_VARIANT, derivative_url
class UserRegistrationSchema(Schema):
username: str
email: str
mobile: str
code: str
password: str
email: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
university: Optional[str] = None
@@ -18,10 +22,71 @@ class UserRegistrationSchema(Schema):
year_of_study: Optional[int] = None
major: Optional[str] = None
class UserLoginSchema(Schema):
email: str
identifier: str
password: str
class UserOtpLoginSchema(Schema):
mobile: str
code: str
class RegisterOtpVerifySchema(Schema):
mobile: str
code: str
class OtpSendSchema(Schema):
mobile: str
mode: str
class MobileOtpSendSchema(Schema):
mobile: str
class MobileOtpVerifySchema(Schema):
mobile: str
code: str
class GoogleFlowSchema(Schema):
flow: str
class GoogleClaimVerifySchema(Schema):
flow: str
code: str
class GoogleCompleteSchema(Schema):
flow: str
mobile: str
username: Optional[str] = None
student_id: Optional[str] = None
year_of_study: Optional[int] = None
major: Optional[str] = None
university: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
class GoogleFlowResponseSchema(Schema):
status: str
email: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
avatar_url: Optional[str] = None
resolution: Optional[str] = None
mobile: Optional[str] = None
mobile_hint: Optional[str] = None
detail: Optional[str] = None
access_token: Optional[str] = None
refresh_token: Optional[str] = None
class UserProfileSchema(ModelSchema):
profile_picture: Optional[str] = None
profile_picture_thumbnail_url: Optional[str] = None
@@ -29,27 +94,32 @@ class UserProfileSchema(ModelSchema):
student_id: Optional[str] = None
major: Optional[str] = None
university: Optional[str] = None
mobile: Optional[str] = None
requires_mobile_verification: bool
has_google_link: bool
class Meta:
model = User
fields = [
'id',
'username',
'email',
'first_name',
'last_name',
'student_id',
'year_of_study',
'major',
'university',
'bio',
'date_joined',
'is_email_verified',
'is_active',
'is_staff',
'is_superuser',
'is_deleted',
'deleted_at',
"id",
"username",
"email",
"mobile",
"first_name",
"last_name",
"student_id",
"year_of_study",
"major",
"university",
"bio",
"date_joined",
"is_email_verified",
"is_mobile_verified",
"is_active",
"is_staff",
"is_superuser",
"is_deleted",
"deleted_at",
]
@staticmethod
@@ -60,14 +130,18 @@ class UserProfileSchema(ModelSchema):
def resolve_university(obj):
return obj.get_university_display()
@staticmethod
def resolve_requires_mobile_verification(obj):
return obj.requires_mobile_verification
@staticmethod
def resolve_has_google_link(obj):
return obj.has_google_link
@staticmethod
def resolve_profile_picture(obj, context):
"""
Resolves the absolute URL for the profile picture.
`context` contains the request object, which is needed for build_absolute_uri.
"""
request = context['request']
if obj.profile_picture and hasattr(obj.profile_picture, 'url'):
request = context["request"]
if obj.profile_picture and hasattr(obj.profile_picture, "url"):
return request.build_absolute_uri(obj.profile_picture.url)
return None
@@ -87,27 +161,26 @@ class UserProfileSchema(ModelSchema):
class UserListSchema(ModelSchema):
major: Optional[str] = None
university: Optional[str] = None
mobile: Optional[str] = None
class Meta:
model = User
fields = [
'id',
'username',
'email',
'first_name',
'last_name',
'is_active',
'is_staff',
'is_superuser',
'date_joined',
'major',
'university',
"id",
"username",
"email",
"mobile",
"first_name",
"last_name",
"is_active",
"is_staff",
"is_superuser",
"date_joined",
"major",
"university",
"is_mobile_verified",
]
@staticmethod
def resolve_full_name(obj):
return obj.get_full_name()
@staticmethod
def resolve_major(obj):
return obj.get_major_display()
@@ -116,7 +189,9 @@ class UserListSchema(ModelSchema):
def resolve_university(obj):
return obj.get_university_display()
class UserUpdateSchema(Schema):
email: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
bio: Optional[str] = None
@@ -125,20 +200,33 @@ class UserUpdateSchema(Schema):
university: Optional[str] = None
student_id: Optional[str] = None
class TokenSchema(Schema):
access_token: str
refresh_token: str
token_type: str = "bearer"
class TokenRefreshIn(Schema):
refresh_token: str
class PasswordResetRequestSchema(Schema):
email: str
class PasswordResetConfirmSchema(Schema):
token: str
class PasswordResetSchema(Schema):
mobile: str
code: str
new_password: str
class UsernameCheckSchema(Schema):
exists: bool
class MobileLookupSchema(Schema):
exists: bool
has_password: bool
class OtpSendResponseSchema(Schema):
message: str
expires_in_seconds: int
expires_at: datetime