Files
grateful-journal/backend/models.py
2026-03-31 10:23:49 +05:30

226 lines
6.1 KiB
Python

from pydantic import BaseModel, Field # type: ignore
from datetime import datetime
from typing import Optional, List
from enum import Enum
from bson import ObjectId
# ========== Helper for ObjectId handling ==========
class PyObjectId(ObjectId):
"""Custom type for ObjectId serialization"""
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v):
if isinstance(v, ObjectId):
return v
if isinstance(v, str):
return ObjectId(v)
raise ValueError(f"Invalid ObjectId: {v}")
def __repr__(self):
return f"ObjectId('{self}')"
# ========== User Models ==========
class UserCreate(BaseModel):
email: str
displayName: Optional[str] = None
photoURL: Optional[str] = None
class UserUpdate(BaseModel):
displayName: Optional[str] = None
photoURL: Optional[str] = None
theme: Optional[str] = None
tutorial: Optional[bool] = None
class Config:
json_schema_extra = {
"example": {
"displayName": "John Doe",
"theme": "dark"
}
}
class User(BaseModel):
id: str = Field(alias="_id")
email: str
displayName: Optional[str] = None
photoURL: Optional[str] = None
createdAt: datetime
updatedAt: datetime
theme: str = "light"
tutorial: Optional[bool] = None
class Config:
from_attributes = True
populate_by_name = True
json_schema_extra = {
"example": {
"_id": "507f1f77bcf86cd799439011",
"email": "user@example.com",
"displayName": "John Doe",
"photoURL": "https://example.com/photo.jpg",
"createdAt": "2026-03-05T00:00:00Z",
"updatedAt": "2026-03-05T00:00:00Z",
"theme": "light"
}
}
# ========== Journal Entry Models ==========
class MoodEnum(str, Enum):
happy = "happy"
sad = "sad"
neutral = "neutral"
anxious = "anxious"
grateful = "grateful"
class EncryptionMetadata(BaseModel):
"""Encryption metadata for entries - zero-knowledge privacy"""
encrypted: bool = True
ciphertext: str # Base64-encoded encrypted content
nonce: str # Base64-encoded nonce used for encryption
algorithm: str = "XSalsa20-Poly1305" # crypto_secretbox algorithm
class Config:
json_schema_extra = {
"example": {
"encrypted": True,
"ciphertext": "base64_encoded_ciphertext...",
"nonce": "base64_encoded_nonce...",
"algorithm": "XSalsa20-Poly1305"
}
}
class JournalEntryCreate(BaseModel):
title: Optional[str] = None # Optional if encrypted
content: Optional[str] = None # Optional if encrypted
mood: Optional[MoodEnum] = None
tags: Optional[List[str]] = None
isPublic: Optional[bool] = False
# Logical journal date; defaults to today
entryDate: Optional[datetime] = None
# Encryption metadata - present if entry is encrypted
encryption: Optional[EncryptionMetadata] = None
class Config:
json_schema_extra = {
"example": {
"encryption": {
"encrypted": True,
"ciphertext": "base64_ciphertext...",
"nonce": "base64_nonce...",
"algorithm": "XSalsa20-Poly1305"
},
"mood": "grateful",
"tags": ["work", "family"],
"isPublic": False,
"entryDate": "2026-03-05T00:00:00Z"
}
}
class JournalEntryUpdate(BaseModel):
title: Optional[str] = None
content: Optional[str] = None
mood: Optional[MoodEnum] = None
tags: Optional[List[str]] = None
isPublic: Optional[bool] = None
encryption: Optional[EncryptionMetadata] = None
class Config:
json_schema_extra = {
"example": {
"title": "Updated Title",
"mood": "happy"
}
}
class JournalEntry(BaseModel):
id: str = Field(alias="_id")
userId: str # ObjectId as string
title: Optional[str] = None # None if encrypted
content: Optional[str] = None # None if encrypted
mood: Optional[MoodEnum] = None
tags: Optional[List[str]] = []
isPublic: bool = False
entryDate: datetime # Logical journal date
createdAt: datetime
updatedAt: datetime
encryption: Optional[EncryptionMetadata] = None # Present if encrypted
class Config:
from_attributes = True
populate_by_name = True
json_schema_extra = {
"example": {
"_id": "507f1f77bcf86cd799439011",
"userId": "507f1f77bcf86cd799439012",
"encryption": {
"encrypted": True,
"ciphertext": "base64_ciphertext...",
"nonce": "base64_nonce...",
"algorithm": "XSalsa20-Poly1305"
},
"mood": "grateful",
"tags": ["work", "family"],
"isPublic": False,
"entryDate": "2026-03-05T00:00:00Z",
"createdAt": "2026-03-05T12:00:00Z",
"updatedAt": "2026-03-05T12:00:00Z"
}
}
# ========== Pagination Models ==========
class PaginationMeta(BaseModel):
"""Pagination metadata for list responses"""
total: int
limit: int
skip: int
hasMore: bool
class Config:
json_schema_extra = {
"example": {
"total": 42,
"limit": 20,
"skip": 0,
"hasMore": True
}
}
class EntriesListResponse(BaseModel):
"""Response model for paginated entries"""
entries: List[JournalEntry]
pagination: PaginationMeta
class Config:
json_schema_extra = {
"example": {
"entries": [],
"pagination": {
"total": 42,
"limit": 20,
"skip": 0,
"hasMore": True
}
}
}