Files
full-stack-fastapi/backend/app/models.py
魏风 ff971e2cea
All checks were successful
Deploy to Production / deploy (push) Successful in 1m19s
feat: add mqtt raw data management with UTC+8 range UI
2026-03-20 17:46:37 +08:00

220 lines
6.3 KiB
Python

import uuid
from datetime import date, datetime, timezone
from typing import Any
from pydantic import EmailStr
from sqlalchemy import JSON, DateTime, LargeBinary
from sqlmodel import Field, Relationship, SQLModel
def get_datetime_utc() -> datetime:
return datetime.now(timezone.utc)
# Shared properties
class UserBase(SQLModel):
email: EmailStr = Field(unique=True, index=True, max_length=255)
is_active: bool = True
is_superuser: bool = False
full_name: str | None = Field(default=None, max_length=255)
# Properties to receive via API on creation
class UserCreate(UserBase):
password: str = Field(min_length=8, max_length=128)
class UserRegister(SQLModel):
email: EmailStr = Field(max_length=255)
password: str = Field(min_length=8, max_length=128)
full_name: str | None = Field(default=None, max_length=255)
# Properties to receive via API on update, all are optional
class UserUpdate(UserBase):
email: EmailStr | None = Field(default=None, max_length=255) # type: ignore
password: str | None = Field(default=None, min_length=8, max_length=128)
class UserUpdateMe(SQLModel):
full_name: str | None = Field(default=None, max_length=255)
email: EmailStr | None = Field(default=None, max_length=255)
class UpdatePassword(SQLModel):
current_password: str = Field(min_length=8, max_length=128)
new_password: str = Field(min_length=8, max_length=128)
# Database model, database table inferred from class name
class User(UserBase, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
hashed_password: str
created_at: datetime | None = Field(
default_factory=get_datetime_utc,
sa_type=DateTime(timezone=True), # type: ignore
)
locations: list["Location"] = Relationship(
back_populates="owner", cascade_delete=True
)
# Properties to return via API, id is always required
class UserPublic(UserBase):
id: uuid.UUID
created_at: datetime | None = None
class UsersPublic(SQLModel):
data: list[UserPublic]
count: int
# Shared properties
class LocationBase(SQLModel):
title: str = Field(min_length=1, max_length=255)
description: str | None = Field(default=None, max_length=255)
# Properties to receive on location creation
class LocationCreate(LocationBase):
pass
# Properties to receive on location update
class LocationUpdate(LocationBase):
title: str | None = Field(default=None, min_length=1, max_length=255) # type: ignore
# Database model, database table inferred from class name
class Location(LocationBase, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
created_at: datetime | None = Field(
default_factory=get_datetime_utc,
sa_type=DateTime(timezone=True), # type: ignore
)
owner_id: uuid.UUID = Field(
foreign_key="user.id", nullable=False, ondelete="CASCADE"
)
owner: User | None = Relationship(back_populates="locations")
# Properties to return via API, id is always required
class LocationPublic(LocationBase):
id: uuid.UUID
owner_id: uuid.UUID
created_at: datetime | None = None
class LocationsPublic(SQLModel):
data: list[LocationPublic]
count: int
class MqttRawDaySummary(SQLModel):
day: date
message_count: int
first_received_at: datetime | None = None
last_received_at: datetime | None = None
class MqttRawDaysPublic(SQLModel):
data: list[MqttRawDaySummary]
count: int
class TerminalMessageRaw(SQLModel, table=True):
__tablename__ = "terminal_message_raw"
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
device_id: str = Field(max_length=255, index=True)
topic: str = Field(max_length=255)
received_at: datetime = Field(
default_factory=get_datetime_utc,
sa_type=DateTime(timezone=True), # type: ignore
)
payload_bytes: bytes = Field(sa_type=LargeBinary)
payload_size: int = 0
payload_sha256: str = Field(default="", max_length=64, index=True)
qos: int = 0
retain: bool = False
parsed_at: datetime | None = Field(
default=None,
sa_type=DateTime(timezone=True), # type: ignore
index=True,
)
parse_error: str | None = Field(default=None, max_length=1024)
created_at: datetime = Field(
default_factory=get_datetime_utc,
sa_type=DateTime(timezone=True), # type: ignore
)
class TerminalDeviceState(SQLModel, table=True):
__tablename__ = "terminal_device_state"
device_id: str = Field(max_length=255, primary_key=True)
last_event_time: datetime | None = Field(
default=None, sa_type=DateTime(timezone=True)
) # type: ignore[call-overload]
fix_status: str | None = Field(default=None, max_length=64)
lat: float | None = None
lon: float | None = None
alt_m: float | None = None
satellites_used: int | None = None
satellite_signal_summary: dict[str, Any] | None = Field(default=None, sa_type=JSON)
rtcm_type_summary: dict[str, Any] | None = Field(default=None, sa_type=JSON)
updated_at: datetime = Field(
default_factory=get_datetime_utc,
sa_type=DateTime(timezone=True), # type: ignore
)
class TerminalRawArchiveManifest(SQLModel, table=True):
__tablename__ = "terminal_raw_archive_manifest"
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
device_id: str = Field(max_length=255, index=True)
range_start: datetime = Field(
sa_type=DateTime(timezone=True), # type: ignore
index=True,
)
range_end: datetime = Field(
sa_type=DateTime(timezone=True), # type: ignore
index=True,
)
file_path: str = Field(max_length=1024, unique=True)
file_size: int = 0
sha256: str = Field(max_length=64)
record_count: int = 0
created_at: datetime = Field(
default_factory=get_datetime_utc,
sa_type=DateTime(timezone=True), # type: ignore
)
# Generic message
class Message(SQLModel):
message: str
class DeleteResult(SQLModel):
deleted_count: int
message: str
# JSON payload containing access token
class Token(SQLModel):
access_token: str
token_type: str = "bearer"
# Contents of JWT token
class TokenPayload(SQLModel):
sub: str | None = None
class NewPassword(SQLModel):
token: str
new_password: str = Field(min_length=8, max_length=128)