This commit is contained in:
Iliyan Angelov
2025-11-21 01:20:51 +02:00
parent a38ab4fa82
commit 6f85b8cf17
242 changed files with 7154 additions and 14492 deletions

View File

@@ -1,68 +1,32 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field
class CookiePolicySettings(BaseModel):
"""
Admin-configurable global cookie policy.
Controls which categories can be used in the application.
"""
analytics_enabled: bool = Field(
default=True,
description="If false, analytics cookies/scripts should not be used at all.",
)
marketing_enabled: bool = Field(
default=True,
description="If false, marketing cookies/scripts should not be used at all.",
)
preferences_enabled: bool = Field(
default=True,
description="If false, preference cookies should not be used at all.",
)
analytics_enabled: bool = Field(default=True, description='If false, analytics cookies/scripts should not be used at all.')
marketing_enabled: bool = Field(default=True, description='If false, marketing cookies/scripts should not be used at all.')
preferences_enabled: bool = Field(default=True, description='If false, preference cookies should not be used at all.')
class CookiePolicySettingsResponse(BaseModel):
status: str = Field(default="success")
status: str = Field(default='success')
data: CookiePolicySettings
updated_at: Optional[datetime] = None
updated_by: Optional[str] = None
class CookieIntegrationSettings(BaseModel):
"""
IDs for well-known third-party integrations, configured by admin.
"""
ga_measurement_id: Optional[str] = Field(
default=None, description="Google Analytics 4 measurement ID (e.g. G-XXXXXXX)."
)
fb_pixel_id: Optional[str] = Field(
default=None, description="Meta (Facebook) Pixel ID."
)
ga_measurement_id: Optional[str] = Field(default=None, description='Google Analytics 4 measurement ID (e.g. G-XXXXXXX).')
fb_pixel_id: Optional[str] = Field(default=None, description='Meta (Facebook) Pixel ID.')
class CookieIntegrationSettingsResponse(BaseModel):
status: str = Field(default="success")
status: str = Field(default='success')
data: CookieIntegrationSettings
updated_at: Optional[datetime] = None
updated_by: Optional[str] = None
class PublicPrivacyConfig(BaseModel):
"""
Publicly consumable privacy configuration for the frontend.
Does not expose any secrets, only IDs and flags.
"""
policy: CookiePolicySettings
integrations: CookieIntegrationSettings
class PublicPrivacyConfigResponse(BaseModel):
status: str = Field(default="success")
data: PublicPrivacyConfig
status: str = Field(default='success')
data: PublicPrivacyConfig

View File

@@ -1,64 +1,58 @@
from pydantic import BaseModel, EmailStr, Field, validator
from typing import Optional
class RegisterRequest(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
email: EmailStr
password: str = Field(..., min_length=8)
phone: Optional[str] = None
@validator("password")
@validator('password')
def validate_password(cls, v):
if len(v) < 8:
raise ValueError("Password must be at least 8 characters")
if not any(c.isupper() for c in v):
raise ValueError("Password must contain at least one uppercase letter")
if not any(c.islower() for c in v):
raise ValueError("Password must contain at least one lowercase letter")
if not any(c.isdigit() for c in v):
raise ValueError("Password must contain at least one number")
raise ValueError('Password must be at least 8 characters')
if not any((c.isupper() for c in v)):
raise ValueError('Password must contain at least one uppercase letter')
if not any((c.islower() for c in v)):
raise ValueError('Password must contain at least one lowercase letter')
if not any((c.isdigit() for c in v)):
raise ValueError('Password must contain at least one number')
return v
@validator("phone")
@validator('phone')
def validate_phone(cls, v):
if v and not v.isdigit() or (v and len(v) not in [10, 11]):
raise ValueError("Phone must be 10-11 digits")
if v and (not v.isdigit()) or (v and len(v) not in [10, 11]):
raise ValueError('Phone must be 10-11 digits')
return v
class LoginRequest(BaseModel):
email: EmailStr
password: str
rememberMe: Optional[bool] = False
mfaToken: Optional[str] = None
class RefreshTokenRequest(BaseModel):
refreshToken: Optional[str] = None
class ForgotPasswordRequest(BaseModel):
email: EmailStr
class ResetPasswordRequest(BaseModel):
token: str
password: str = Field(..., min_length=8)
@validator("password")
@validator('password')
def validate_password(cls, v):
if len(v) < 8:
raise ValueError("Password must be at least 8 characters")
if not any(c.isupper() for c in v):
raise ValueError("Password must contain at least one uppercase letter")
if not any(c.islower() for c in v):
raise ValueError("Password must contain at least one lowercase letter")
if not any(c.isdigit() for c in v):
raise ValueError("Password must contain at least one number")
raise ValueError('Password must be at least 8 characters')
if not any((c.isupper() for c in v)):
raise ValueError('Password must contain at least one uppercase letter')
if not any((c.islower() for c in v)):
raise ValueError('Password must contain at least one lowercase letter')
if not any((c.isdigit() for c in v)):
raise ValueError('Password must contain at least one number')
return v
class UserResponse(BaseModel):
id: int
name: str
@@ -71,38 +65,30 @@ class UserResponse(BaseModel):
class Config:
from_attributes = True
class AuthResponse(BaseModel):
user: UserResponse
token: str
refreshToken: Optional[str] = None
class TokenResponse(BaseModel):
token: str
class MessageResponse(BaseModel):
status: str
message: str
class MFAInitResponse(BaseModel):
secret: str
qr_code: str # Base64 data URL
qr_code: str
class EnableMFARequest(BaseModel):
secret: str
verification_token: str
class VerifyMFARequest(BaseModel):
token: str
is_backup_code: Optional[bool] = False
class MFAStatusResponse(BaseModel):
mfa_enabled: bool
backup_codes_count: int
backup_codes_count: int

View File

@@ -1,70 +1,24 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field
class CookieCategoryPreferences(BaseModel):
"""
Granular consent for different cookie categories.
- necessary: required for the site to function (always true, not revocable)
- analytics: usage analytics, performance tracking
- marketing: advertising, remarketing cookies
- preferences: UI / language / personalization preferences
"""
necessary: bool = Field(
default=True,
description="Strictly necessary cookies (always enabled as they are required for core functionality).",
)
analytics: bool = Field(
default=False, description="Allow anonymous analytics and performance cookies."
)
marketing: bool = Field(
default=False, description="Allow marketing and advertising cookies."
)
preferences: bool = Field(
default=False,
description="Allow preference cookies (e.g. language, layout settings).",
)
necessary: bool = Field(default=True, description='Strictly necessary cookies (always enabled as they are required for core functionality).')
analytics: bool = Field(default=False, description='Allow anonymous analytics and performance cookies.')
marketing: bool = Field(default=False, description='Allow marketing and advertising cookies.')
preferences: bool = Field(default=False, description='Allow preference cookies (e.g. language, layout settings).')
class CookieConsent(BaseModel):
"""
Persisted cookie consent state.
Stored in an HttpOnly cookie and exposed via the API.
"""
version: int = Field(
default=1, description="Consent schema version for future migrations."
)
updated_at: datetime = Field(
default_factory=datetime.utcnow, description="Last time consent was updated."
)
has_decided: bool = Field(
default=False,
description="Whether the user has actively made a consent choice.",
)
categories: CookieCategoryPreferences = Field(
default_factory=CookieCategoryPreferences,
description="Granular per-category consent.",
)
version: int = Field(default=1, description='Consent schema version for future migrations.')
updated_at: datetime = Field(default_factory=datetime.utcnow, description='Last time consent was updated.')
has_decided: bool = Field(default=False, description='Whether the user has actively made a consent choice.')
categories: CookieCategoryPreferences = Field(default_factory=CookieCategoryPreferences, description='Granular per-category consent.')
class CookieConsentResponse(BaseModel):
status: str = Field(default="success")
status: str = Field(default='success')
data: CookieConsent
class UpdateCookieConsentRequest(BaseModel):
"""
Request body for updating cookie consent.
'necessary' is ignored on write and always treated as True by the server.
"""
analytics: Optional[bool] = None
marketing: Optional[bool] = None
preferences: Optional[bool] = None
preferences: Optional[bool] = None