Coverage for backend / app / core / schemas.py: 100%
139 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-17 21:34 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-17 21:34 +0000
1"""Schemas for the JAM database
2Create schemas should be used to create entries in the database.
3Out schemas should be used to return data to the user.
4Min schemas should be used to return minimal data to the user (enough to display the entry as a badge) and should not
5contain reference to other tables.
6Update schemas should be used to update existing entries in the database."""
8import datetime as dt
9from typing import Literal
11from pydantic import BaseModel, field_validator
13ThemeMode = Literal["dark", "light", "system"]
15from app.base_schemas import Out, OwnedOut, EmailField
18# ------------------------------------------------------- SETTINGS ------------------------------------------------------
21class SettingCreate(BaseModel):
22 """Setting create schema"""
24 name: str
25 value: str
26 description: str | None = None
27 is_active: bool = True
30class SettingOut(SettingCreate, Out):
31 """Setting output schema"""
33 pass
36class SettingUpdate(SettingCreate):
37 """Keyword update schema"""
39 name: str | None = None
40 value: str | None = None
43# ------------------------------------------------------ REGISTER ------------------------------------------------------
46class UserRegister(BaseModel):
47 """User create schema"""
49 email: EmailField
50 password: str
51 first_name: str
52 last_name: str
55# -------------------------------------------------------- LOGIN -------------------------------------------------------
58class UserLogin(BaseModel):
59 """User login schema"""
61 email: EmailField
62 password: str
65class Token(BaseModel):
66 access_token: str
67 token_type: str
70class TokenData(BaseModel):
71 id: str | None = None
72 token_version: int = 0
73 is_demo: bool = False
76# ------------------------------------------------- USER PREFERENCES ---------------------------------------------------
79class UserPreferencesCreate(BaseModel):
80 """User preferences create schema
81 Defaults are handled in the database layer."""
83 theme: str | None = None
84 dark_mode: ThemeMode = "system"
85 chase_threshold: int | None = None
86 deadline_threshold: int | None = None
87 update_limit: int | None = None
88 default_currency: str | None = None
89 extension_banner_dismissed: bool | None = None
92class UserPreferencesUpdate(UserPreferencesCreate):
93 """User preferences update schema"""
95 pass
98class UserPreferencesOut(Out, UserPreferencesUpdate):
99 """User preferences output schema"""
101 pass
104# --------------------------------------------------- PREMIUM DETAILS --------------------------------------------------
107class PremiumDetailsCreate(BaseModel):
108 """Premium details create schema"""
110 is_active: bool = False
111 job_scraping_active: bool = True
112 job_rating_active: bool = True
115class PremiumDetailsOut(PremiumDetailsCreate, Out):
116 """Premium details output schema"""
118 pass
121class PremiumDetailsUpdate(PremiumDetailsCreate):
122 """Premium details update schema"""
124 pass
127class CurrentUserPremiumDetailsUpdate(BaseModel):
128 """Premium details update schema"""
130 job_scraping_active: bool | None = None
131 job_rating_active: bool | None = None
134# ------------------------------------------------------- STRIPE -------------------------------------------------------
137class StripeDetails(BaseModel):
138 """Stripe details schema"""
140 subscription_status: str | None = None
141 trial_end_date: int | None = None
144# -------------------------------------------------------- USERS -------------------------------------------------------
147class UserCreate(BaseModel):
148 """User create schema for the admin endpoint"""
150 email: EmailField
151 password: str
152 is_active: bool = True
153 is_admin: bool = False
154 is_demo: bool = False
155 first_name: str | None = None
156 last_name: str | None = None
157 premium: PremiumDetailsCreate | None = None
158 preferences: UserPreferencesCreate | None = None
161class UserOut(Out):
162 """User output schema for the admin endpoint"""
164 email: EmailField
165 is_active: bool
166 is_admin: bool
167 is_demo: bool
168 is_verified: bool
169 last_login: dt.datetime | None
170 previous_login: dt.datetime | None
171 app_version: str | None
172 first_name: str | None = None
173 last_name: str | None = None
174 name: str | None = None
175 token_version: int
176 pending_email_change: str | None
177 preferences: UserPreferencesOut | None
178 premium: PremiumDetailsOut | None
179 stripe_details: StripeDetails | None
182class UserUpdate(BaseModel):
183 """User account update schema for the admin endpoint"""
185 email: EmailField | None = None
186 password: str | None = None
187 is_active: bool = True
188 is_admin: bool = False
189 is_demo: bool = False
190 first_name: str | None = None
191 last_name: str | None = None
192 preferences: UserPreferencesUpdate | None = None
193 premium: PremiumDetailsUpdate | None = None
196class CurrentUserUpdate(BaseModel):
197 """User account update schema"""
199 email: EmailField | None = None
200 current_password: str | None = None
201 password: str | None = None
202 first_name: str | None = None
203 last_name: str | None = None
204 app_version: str | None = None
205 preferences: UserPreferencesUpdate | None = None
206 premium: CurrentUserPremiumDetailsUpdate | None = None
209class CurrentUserUpdateResponse(BaseModel):
210 success: bool
211 message: str
212 logged_out: bool | None = None
215# ------------------------------------------------- USER QUALIFICATIONS ------------------------------------------------
218class UserQualificationUpsert(BaseModel):
219 """User qualification create schema"""
221 id: int | None = None
222 experience: str | None = None
223 skills: str | None = None
224 education: str | None = None
225 qualities: str | None = None
226 interests: str | None = None
228 @field_validator("experience")
229 @classmethod
230 def validate_experience(cls, v: str | None) -> str | None:
231 EXPERIENCE_CHAR_LIMIT: int = 10000
232 if v and len(v) > EXPERIENCE_CHAR_LIMIT:
233 raise ValueError(f"Experience must not exceed {EXPERIENCE_CHAR_LIMIT} characters")
234 return v
236 @field_validator("skills", "education", "qualities", "interests")
237 @classmethod
238 def validate_other_fields(cls, v: str | None) -> str | None:
239 OTHER_CHAR_LIMIT: int = 3500
240 if v and len(v) > OTHER_CHAR_LIMIT:
241 raise ValueError(f"This field must not exceed {OTHER_CHAR_LIMIT} characters")
242 return v
245class UserQualificationOut(UserQualificationUpsert, OwnedOut):
246 """User qualification output schema"""
248 pass
251# --------------------------------------------------- PASSWORD RESET ---------------------------------------------------
254class PasswordResetRequest(BaseModel):
255 """Email request schema for password reset"""
257 email: EmailField
260class PasswordReset(BaseModel):
261 """Password reset schema"""
263 token: str
264 new_password: str
267# ---------------------------------------------------- EMAIL CHANGE ----------------------------------------------------
270class CheckPendingEmailResponse(BaseModel):
271 """Response for checking pending email"""
273 has_pending_email: bool
274 pending_email: str | None = None
277# -------------------------------------------------- ACCOUNT DELETION --------------------------------------------------
280class AccountDeleteRequest(BaseModel):
281 """Account deletion request schema"""
283 password: str