add banban service code
This commit is contained in:
@@ -1,71 +1,121 @@
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from config import settings
|
||||
from utils.logger import session_logger
|
||||
import asyncio
|
||||
import urllib.parse
|
||||
|
||||
|
||||
def _build_connection_string() -> str:
|
||||
encoded_password = urllib.parse.quote_plus(settings.db_password)
|
||||
return f"mysql+aiomysql://{settings.db_user}:{encoded_password}@{settings.db_host}:{settings.db_port}/{settings.db_name}?charset=utf8mb4"
|
||||
|
||||
|
||||
def _build_sync_connection_string() -> str:
|
||||
encoded_password = urllib.parse.quote_plus(settings.db_password)
|
||||
return f"mysql+pymysql://{settings.db_user}:{encoded_password}@{settings.db_host}:{settings.db_port}/{settings.db_name}?charset=utf8mb4"
|
||||
|
||||
|
||||
class DatabaseManager:
|
||||
_instance = None
|
||||
_lock = asyncio.Lock()
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.engine = None
|
||||
self.async_engine = None
|
||||
self.sync_engine = None
|
||||
self.async_session = None
|
||||
self.sync_session_maker = None
|
||||
self._initialized = False
|
||||
|
||||
|
||||
@classmethod
|
||||
async def get_instance(cls):
|
||||
async with cls._lock:
|
||||
if cls._instance is None:
|
||||
cls._instance = DatabaseManager()
|
||||
|
||||
|
||||
if not cls._instance._initialized:
|
||||
await cls._instance.initialize()
|
||||
|
||||
|
||||
return cls._instance
|
||||
|
||||
|
||||
async def initialize(self):
|
||||
if self._initialized:
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
encoded_password = urllib.parse.quote_plus(settings.db_password)
|
||||
connection_string = f"mysql+aiomysql://{settings.db_user}:{encoded_password}@{settings.db_host}:{settings.db_port}/{settings.db_name}?charset=utf8mb4"
|
||||
self.engine = create_async_engine(
|
||||
connection_string = _build_connection_string()
|
||||
self.async_engine = create_async_engine(
|
||||
connection_string,
|
||||
echo=settings.db_echo,
|
||||
pool_pre_ping=True,
|
||||
pool_recycle=3600,
|
||||
)
|
||||
|
||||
|
||||
self.async_session = sessionmaker(
|
||||
self.engine, expire_on_commit=False, class_=AsyncSession
|
||||
self.async_engine, expire_on_commit=False, class_=AsyncSession
|
||||
)
|
||||
|
||||
|
||||
sync_connection_string = _build_sync_connection_string()
|
||||
self.sync_engine = create_engine(
|
||||
sync_connection_string,
|
||||
echo=settings.db_echo,
|
||||
pool_pre_ping=True,
|
||||
pool_recycle=3600,
|
||||
)
|
||||
|
||||
self.sync_session_maker = sessionmaker(
|
||||
bind=self.sync_engine, expire_on_commit=False
|
||||
)
|
||||
|
||||
self._initialized = True
|
||||
session_logger.info("system", "database", f"数据库连接初始化成功: {settings.db_host}:{settings.db_port}/{settings.db_name}")
|
||||
except Exception as e:
|
||||
session_logger.error("system", "database", f"数据库连接初始化失败: {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
async def get_session(self):
|
||||
"""获取数据库会话"""
|
||||
"""获取异步数据库会话"""
|
||||
if not self._initialized:
|
||||
await self.initialize()
|
||||
return self.async_session()
|
||||
|
||||
|
||||
def get_sync_session(self):
|
||||
"""获取同步数据库会话"""
|
||||
if not self._initialized:
|
||||
raise RuntimeError("DatabaseManager not initialized. Call get_instance() first.")
|
||||
return self.sync_session_maker()
|
||||
|
||||
async def close(self):
|
||||
"""关闭数据库连接"""
|
||||
if self.engine:
|
||||
await self.engine.dispose()
|
||||
self._initialized = False
|
||||
session_logger.info("system", "database", "数据库连接已关闭")
|
||||
if self.async_engine:
|
||||
await self.async_engine.dispose()
|
||||
if self.sync_engine:
|
||||
self.sync_engine.dispose()
|
||||
self._initialized = False
|
||||
session_logger.info("system", "database", "数据库连接已关闭")
|
||||
|
||||
|
||||
db_manager = None
|
||||
|
||||
|
||||
async def get_db_manager():
|
||||
global db_manager
|
||||
if db_manager is None:
|
||||
db_manager = await DatabaseManager.get_instance()
|
||||
return db_manager
|
||||
|
||||
|
||||
def get_sync_session_maker():
|
||||
"""获取同步 session maker(同步版本,优先复用已有 manager)"""
|
||||
global db_manager
|
||||
if db_manager is not None and db_manager._initialized:
|
||||
return db_manager.sync_session_maker
|
||||
|
||||
sync_engine = create_engine(
|
||||
_build_sync_connection_string(),
|
||||
echo=settings.db_echo,
|
||||
pool_pre_ping=True,
|
||||
pool_recycle=3600,
|
||||
)
|
||||
return sessionmaker(bind=sync_engine, expire_on_commit=False)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
from sqlalchemy import Column, String, Text, Float, DateTime, Integer, Boolean, func, ForeignKey, JSON
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.schema import UniqueConstraint
|
||||
import datetime
|
||||
from typing import Optional
|
||||
from sqlalchemy import Column, String, Text, Float, DateTime, Integer, Boolean, func, ForeignKey, JSON, UniqueConstraint, BigInteger, Numeric, SmallInteger, Date, Index, Time, text
|
||||
from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped
|
||||
|
||||
Base = declarative_base()
|
||||
from datetime import date, time, datetime
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
class DeviceConfig(Base):
|
||||
__tablename__ = "device_configs"
|
||||
@@ -13,7 +15,7 @@ class DeviceConfig(Base):
|
||||
selected_role_key = Column(String(64), nullable=False)
|
||||
preferred_language = Column(String(10), nullable=True)
|
||||
volume = Column(Integer, nullable=True)
|
||||
last_update_time = Column(Float, nullable=False, default=lambda: datetime.datetime.now().timestamp())
|
||||
last_update_time = Column(Float, nullable=False, default=lambda: datetime.now().timestamp())
|
||||
created_at = Column(DateTime, nullable=False, server_default=func.now())
|
||||
updated_at = Column(DateTime, nullable=False, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
@@ -30,7 +32,7 @@ class ConversationHistory(Base):
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
device_id = Column(String(64), nullable=False, index=True)
|
||||
role_key = Column(String(64), nullable=False, index=True)
|
||||
last_interaction_time = Column(Float, nullable=False, default=lambda: datetime.datetime.now().timestamp())
|
||||
last_interaction_time = Column(Float, nullable=False, default=lambda: datetime.now().timestamp())
|
||||
created_at = Column(DateTime, nullable=False, server_default=func.now())
|
||||
updated_at = Column(DateTime, nullable=False, server_default=func.now(), onupdate=func.now())
|
||||
__table_args__ = (
|
||||
@@ -43,7 +45,7 @@ class ConversationMessage(Base):
|
||||
conversation_id = Column(Integer, ForeignKey("conversation_histories.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||
is_user = Column(Boolean, nullable=False, default=False) # True表示用户消息,False表示助手消息
|
||||
content = Column(Text, nullable=False)
|
||||
timestamp = Column(Float, nullable=False, default=lambda: datetime.datetime.now().timestamp())
|
||||
timestamp = Column(Float, nullable=False, default=lambda: datetime.now().timestamp())
|
||||
created_at = Column(DateTime, nullable=False, server_default=func.now())
|
||||
updated_at = Column(DateTime, nullable=False, server_default=func.now(), onupdate=func.now())
|
||||
__table_args__ = (
|
||||
@@ -129,7 +131,7 @@ class SystemConfig(Base):
|
||||
|
||||
class Card(Base):
|
||||
__tablename__ = "cards"
|
||||
|
||||
|
||||
card_id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
card_uuid = Column(String(64), unique=True, nullable=False)
|
||||
device_id = Column(String(64), index=True)
|
||||
@@ -138,7 +140,232 @@ class Card(Base):
|
||||
total_swaps = Column(Integer, server_default="0")
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
__table_args__ = (
|
||||
{'mysql_charset': 'utf8mb4', 'mysql_collate': 'utf8mb4_unicode_ci'}
|
||||
)
|
||||
|
||||
|
||||
class Parent(Base):
|
||||
__tablename__ = "parents"
|
||||
|
||||
user_id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
openid: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
|
||||
unionid: Mapped[Optional[str]] = mapped_column(String(64))
|
||||
nickname: Mapped[Optional[str]] = mapped_column(String(64))
|
||||
avatar_url: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
avatar_file_key: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
phone: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
status: Mapped[int] = mapped_column(Integer, server_default="1")
|
||||
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||
|
||||
|
||||
class Child(Base):
|
||||
__tablename__ = "children"
|
||||
|
||||
child_id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
child_name: Mapped[str] = mapped_column(String(32), nullable=False)
|
||||
child_gender: Mapped[int] = mapped_column(Integer, server_default="2")
|
||||
child_birthday: Mapped[Optional[date]] = mapped_column(Date)
|
||||
status: Mapped[int] = mapped_column(Integer, server_default="1")
|
||||
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||
|
||||
|
||||
class ParentChildRelation(Base):
|
||||
__tablename__ = "parent_child_relations"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("user_id", "child_id", name="uq_user_child"),
|
||||
Index("idx_pcr_user_id", "user_id"),
|
||||
Index("idx_pcr_child_id", "child_id"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
child_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
relation_type: Mapped[int] = mapped_column(Integer, server_default="9")
|
||||
is_primary: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||
status: Mapped[int] = mapped_column(Integer, server_default="1")
|
||||
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||
|
||||
|
||||
class DeviceBinding(Base):
|
||||
__tablename__ = "device_bindings"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("device_id", name="uq_device_binding_device"),
|
||||
UniqueConstraint("child_id", name="uq_device_binding_child"),
|
||||
Index("idx_device_bindings_owner_user_id", "owner_user_id"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
device_id: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
|
||||
owner_user_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
child_id: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
status: Mapped[int] = mapped_column(Integer, server_default="1")
|
||||
bound_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
||||
unbound_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||
|
||||
|
||||
class DeviceBindSession(Base):
|
||||
__tablename__ = "device_bind_sessions"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
bind_token: Mapped[str] = mapped_column(String(36), unique=True, nullable=False)
|
||||
device_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
initiator_user_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
target_child_id: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
challenge_code_hash: Mapped[Optional[str]] = mapped_column(String(64))
|
||||
challenge_set_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
expires_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
||||
max_attempt_count: Mapped[int] = mapped_column(Integer, server_default="5")
|
||||
attempt_count: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
status: Mapped[int] = mapped_column(Integer, server_default="1")
|
||||
confirmed_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
consumed_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||
|
||||
|
||||
class DeviceBindHistory(Base):
|
||||
__tablename__ = "device_bind_history"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
device_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
child_id: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
bound_by_user_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
unbound_by_user_id: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
bind_source: Mapped[int] = mapped_column(Integer, server_default="1")
|
||||
bound_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
||||
unbound_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
unbind_reason: Mapped[Optional[str]] = mapped_column(String(191))
|
||||
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||
|
||||
|
||||
class DeviceSetting(Base):
|
||||
__tablename__ = "device_settings"
|
||||
|
||||
setting_id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
device_id: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
|
||||
sleep_mode: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
disable_time_start: Mapped[Optional[time]] = mapped_column(Time)
|
||||
disable_time_end: Mapped[Optional[time]] = mapped_column(Time)
|
||||
timezone: Mapped[str] = mapped_column(String(32), server_default=text("'Asia/Shanghai'"))
|
||||
volume: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
brightness: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
disable_weekdays: Mapped[Optional[str]] = mapped_column(String(32))
|
||||
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||
updated_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime,
|
||||
server_default=text("CURRENT_TIMESTAMP"),
|
||||
onupdate=datetime.utcnow,
|
||||
)
|
||||
|
||||
|
||||
class IMConversation(Base):
|
||||
__tablename__ = "im_conversations"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("conversation_type", "pair_key", name="uq_conv_type_pair"),
|
||||
Index("idx_im_conv_participant_a", "participant_a_type", "participant_a_id"),
|
||||
Index("idx_im_conv_participant_b", "participant_b_type", "participant_b_id"),
|
||||
Index("idx_im_conv_last_message_at", "last_message_at"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||
conversation_type: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
participant_a_type: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
participant_a_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
participant_b_type: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
participant_b_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
pair_key: Mapped[str] = mapped_column(String(191), nullable=False)
|
||||
status: Mapped[int] = mapped_column(Integer, default=1)
|
||||
last_seq: Mapped[int] = mapped_column(BigInteger, default=0, index=True)
|
||||
message_count: Mapped[int] = mapped_column(BigInteger, default=0)
|
||||
last_message_preview: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
last_message_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
ext_json: Mapped[Optional[dict]] = mapped_column(JSON)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
|
||||
)
|
||||
|
||||
|
||||
class IMMessage(Base):
|
||||
__tablename__ = "im_messages"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("conversation_id", "seq", name="uq_im_msg_conv_seq"),
|
||||
UniqueConstraint("conversation_id", "client_msg_id", name="uq_im_msg_client"),
|
||||
Index("idx_im_msg_sender", "sender_type", "sender_id", "created_at"),
|
||||
Index("idx_im_msg_receiver", "receiver_type", "receiver_id", "created_at"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||
conversation_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||
seq: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||
sender_type: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
sender_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
receiver_type: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
receiver_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
content_type: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
content_text: Mapped[Optional[str]] = mapped_column(Text)
|
||||
content_json: Mapped[Optional[dict]] = mapped_column(JSON)
|
||||
media_file_key: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
media_duration_ms: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
media_mime_type: Mapped[Optional[str]] = mapped_column(String(64))
|
||||
media_size_bytes: Mapped[Optional[int]] = mapped_column(BigInteger)
|
||||
media_transcript_text: Mapped[Optional[str]] = mapped_column(Text)
|
||||
client_msg_id: Mapped[Optional[str]] = mapped_column(String(64))
|
||||
sender_name_snapshot: Mapped[Optional[str]] = mapped_column(String(64))
|
||||
sender_avatar_snapshot: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
receiver_name_snapshot: Mapped[Optional[str]] = mapped_column(String(64))
|
||||
receiver_avatar_snapshot: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
ext_json: Mapped[Optional[dict]] = mapped_column(JSON)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
|
||||
|
||||
class ChildLocationCurrent(Base):
|
||||
__tablename__ = "child_location_current"
|
||||
|
||||
child_id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
||||
device_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
coord_type: Mapped[str] = mapped_column(String(16), default="gcj02")
|
||||
lat: Mapped[float] = mapped_column(Numeric(10, 7), nullable=False)
|
||||
lng: Mapped[float] = mapped_column(Numeric(10, 7), nullable=False)
|
||||
accuracy_m: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
altitude_m: Mapped[Optional[float]] = mapped_column(Numeric(8, 2))
|
||||
speed_mps: Mapped[Optional[float]] = mapped_column(Numeric(8, 2))
|
||||
heading_deg: Mapped[Optional[int]] = mapped_column(SmallInteger)
|
||||
source: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
battery_pct: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
device_time: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
||||
server_time: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
|
||||
)
|
||||
|
||||
|
||||
class ChildLocationHistory(Base):
|
||||
__tablename__ = "child_location_history"
|
||||
__table_args__ = (
|
||||
Index("idx_child_loc_hist_child", "child_id", "created_at"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||
child_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||
device_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
coord_type: Mapped[str] = mapped_column(String(16), default="gcj02")
|
||||
lat: Mapped[float] = mapped_column(Numeric(10, 7), nullable=False)
|
||||
lng: Mapped[float] = mapped_column(Numeric(10, 7), nullable=False)
|
||||
accuracy_m: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
altitude_m: Mapped[Optional[float]] = mapped_column(Numeric(8, 2))
|
||||
speed_mps: Mapped[Optional[float]] = mapped_column(Numeric(8, 2))
|
||||
heading_deg: Mapped[Optional[int]] = mapped_column(SmallInteger)
|
||||
source: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
battery_pct: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
device_time: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
||||
server_time: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
|
||||
Reference in New Issue
Block a user