"""Authentication-related API schemas.""" from datetime import datetime from typing import List, Optional, Literal from ninja import ModelSchema, Schema from pydantic import Field from apps.gamification.api.schemas import PublicGamificationBadgeSchema from apps.users.models import User from apps.blog.permissions import can_access_blog_admin, can_review_blog_posts, can_write_blog_posts from core.media import PREVIEW_VARIANT, THUMBNAIL_VARIANT, derivative_url class UserRegistrationSchema(Schema): username: 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 student_id: Optional[str] = None year_of_study: Optional[int] = None major: Optional[str] = None class UserLoginSchema(Schema): 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 profile_picture_preview_url: Optional[str] = None student_id: Optional[str] = None major: Optional[str] = None major_code: Optional[str] = None university: Optional[str] = None university_code: Optional[str] = None mobile: Optional[str] = None requires_mobile_verification: bool has_google_link: bool can_access_blog_admin: bool can_write_blog_posts: bool can_review_blog_posts: bool show_public_profile_details: bool = False class Meta: model = User fields = [ "id", "username", "email", "mobile", "first_name", "last_name", "student_id", "year_of_study", "major", "university", "bio", "date_joined", "is_email_verified", "is_mobile_verified", "show_public_profile_details", "is_active", "is_staff", "is_superuser", "is_deleted", "deleted_at", ] @staticmethod def resolve_major(obj): return obj.get_major_display() @staticmethod def resolve_major_code(obj): return obj.major.code if obj.major_id else None @staticmethod def resolve_university(obj): return obj.get_university_display() @staticmethod def resolve_university_code(obj): return obj.university.code if obj.university_id else None @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_can_access_blog_admin(obj): return can_access_blog_admin(obj) @staticmethod def resolve_can_write_blog_posts(obj): return can_write_blog_posts(obj) @staticmethod def resolve_can_review_blog_posts(obj): return can_review_blog_posts(obj) @staticmethod def resolve_profile_picture(obj, context): request = context["request"] if obj.profile_picture and hasattr(obj.profile_picture, "url"): return request.build_absolute_uri(obj.profile_picture.url) return None @staticmethod def resolve_profile_picture_thumbnail_url(obj, context): request = context["request"] url = derivative_url(obj.profile_picture, THUMBNAIL_VARIANT) return request.build_absolute_uri(url) if url else None @staticmethod def resolve_profile_picture_preview_url(obj, context): request = context["request"] url = derivative_url(obj.profile_picture, PREVIEW_VARIANT) return request.build_absolute_uri(url) if url else None class UserListSchema(ModelSchema): major: Optional[str] = None university: Optional[str] = None mobile: Optional[str] = None profile_picture: Optional[str] = None profile_picture_thumbnail_url: Optional[str] = None profile_picture_preview_url: Optional[str] = None student_id: Optional[str] = None year_of_study: Optional[int] = None bio: Optional[str] = None is_email_verified: bool is_mobile_verified: bool is_deleted: bool deleted_at: Optional[datetime] = None can_access_blog_admin: bool can_write_blog_posts: bool can_review_blog_posts: bool class Meta: model = User fields = [ "id", "username", "email", "mobile", "first_name", "last_name", "student_id", "year_of_study", "bio", "is_active", "is_staff", "is_superuser", "date_joined", "major", "university", "is_email_verified", "is_mobile_verified", "is_deleted", "deleted_at", ] @staticmethod def resolve_major(obj): return obj.get_major_display() @staticmethod def resolve_university(obj): return obj.get_university_display() @staticmethod def resolve_can_access_blog_admin(obj): return can_access_blog_admin(obj) @staticmethod def resolve_can_write_blog_posts(obj): return can_write_blog_posts(obj) @staticmethod def resolve_can_review_blog_posts(obj): return can_review_blog_posts(obj) @staticmethod def resolve_profile_picture(obj, context): request = context["request"] if obj.profile_picture and hasattr(obj.profile_picture, "url"): return request.build_absolute_uri(obj.profile_picture.url) return None @staticmethod def resolve_profile_picture_thumbnail_url(obj, context): request = context["request"] url = derivative_url(obj.profile_picture, THUMBNAIL_VARIANT) return request.build_absolute_uri(url) if url else None @staticmethod def resolve_profile_picture_preview_url(obj, context): request = context["request"] url = derivative_url(obj.profile_picture, PREVIEW_VARIANT) return request.build_absolute_uri(url) if url else None class AuthorizationRoleSchema(Schema): key: str label: str description: str enabled: bool = False locked: bool = False class UserAuthorizationSchema(Schema): id: int username: str email: Optional[str] = None mobile: Optional[str] = None first_name: str last_name: str is_active: bool is_staff: bool is_superuser: bool groups: List[str] roles: List[AuthorizationRoleSchema] class UserAuthorizationUpdateSchema(Schema): is_staff: bool = False groups: List[str] = [] class UserUpdateSchema(Schema): username: Optional[str] = None email: Optional[str] = None first_name: Optional[str] = None last_name: Optional[str] = None bio: Optional[str] = Field(default=None, max_length=500) year_of_study: Optional[int] = None major: Optional[str] = None university: Optional[str] = None student_id: Optional[str] = None show_public_profile_details: Optional[bool] = None class PublicProfileEventSchema(Schema): title: str slug: str start_time: datetime end_time: datetime registration_start_date: Optional[datetime] = None registration_end_date: Optional[datetime] = None effective_registration_end_date: datetime capacity: Optional[int] = None registration_count: int = 0 status: str status_label: str is_cancelled: bool = False image_url: Optional[str] = None participation: Literal["confirmed", "attended"] class PublicMemberProfileSchema(Schema): username: str first_name: str last_name: str full_name: str profile_picture: Optional[str] = None bio: str = "" details_visible: bool mobile: Optional[str] = None email: Optional[str] = None student_id: Optional[str] = None year_of_study: Optional[int] = None university: Optional[str] = None major: Optional[str] = None badges: List[PublicGamificationBadgeSchema] = [] events: List[PublicProfileEventSchema] = [] class TokenSchema(Schema): access_token: str refresh_token: str token_type: str = "bearer" class TokenRefreshIn(Schema): refresh_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