diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/IoT-Systems.iml b/.idea/IoT-Systems.iml new file mode 100644 index 0000000..688aa87 --- /dev/null +++ b/.idea/IoT-Systems.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml new file mode 100644 index 0000000..3b61c61 --- /dev/null +++ b/.idea/dbnavigator.xml @@ -0,0 +1,425 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..977601b --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..df29e35 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5aef1d1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/store/.gitignore b/store/.gitignore index 75b0912..f810ad9 100644 --- a/store/.gitignore +++ b/store/.gitignore @@ -1,3 +1,5 @@ venv __pycache__ -.idea \ No newline at end of file +.idea + +.idea/ diff --git a/store/__init__.py b/store/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/store/database.py b/store/database.py new file mode 100644 index 0000000..7d86055 --- /dev/null +++ b/store/database.py @@ -0,0 +1,15 @@ +from sqlalchemy import MetaData +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base + +from config import POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_HOST, POSTGRES_PORT, POSTGRES_DB + + +DATABASE_URL = f"postgresql+psycopg2://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}" +engine = create_engine(DATABASE_URL) + +Base = declarative_base() + +metadata = MetaData() + +SessionLocal = sessionmaker(bind=engine) diff --git a/store/main.py b/store/main.py index 81d51a1..fb61018 100644 --- a/store/main.py +++ b/store/main.py @@ -1,11 +1,8 @@ -import asyncio import json -from typing import Set, Dict, List, Any +from typing import Set, Dict, List from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, Body from fastapi.encoders import jsonable_encoder from sqlalchemy import ( - create_engine, - MetaData, Table, Column, Integer, @@ -13,25 +10,14 @@ from sqlalchemy import ( Float, DateTime, ) -from sqlalchemy.orm import sessionmaker from sqlalchemy.sql import select -from datetime import datetime -from pydantic import BaseModel, field_validator -from config import ( - POSTGRES_HOST, - POSTGRES_PORT, - POSTGRES_DB, - POSTGRES_USER, - POSTGRES_PASSWORD, -) + +from database import metadata, SessionLocal +from schemas import ProcessedAgentData, ProcessedAgentDataInDB # FastAPI app setup app = FastAPI() -# SQLAlchemy setup -DATABASE_URL = f"postgresql+psycopg2://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}" -engine = create_engine(DATABASE_URL) -metadata = MetaData() -# Define the ProcessedAgentData table + processed_agent_data = Table( "processed_agent_data", metadata, @@ -45,57 +31,6 @@ processed_agent_data = Table( Column("longitude", Float), Column("timestamp", DateTime), ) -SessionLocal = sessionmaker(bind=engine) - - -# SQLAlchemy model -class ProcessedAgentDataInDB(BaseModel): - id: int - road_state: str - user_id: int - x: float - y: float - z: float - latitude: float - longitude: float - timestamp: datetime - - -# FastAPI models -class AccelerometerData(BaseModel): - x: float - y: float - z: float - - -class GpsData(BaseModel): - latitude: float - longitude: float - - -class AgentData(BaseModel): - user_id: int - accelerometer: AccelerometerData - gps: GpsData - timestamp: datetime - - @classmethod - @field_validator("timestamp", mode="before") - def check_timestamp(cls, value): - if isinstance(value, datetime): - return value - try: - return datetime.fromisoformat(value) - except (TypeError, ValueError): - raise ValueError( - "Invalid timestamp format. Expected ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)." - ) - - -class ProcessedAgentData(BaseModel): - road_state: str - agent_data: AgentData - # WebSocket subscriptions subscriptions: Dict[int, Set[WebSocket]] = {} @@ -163,14 +98,34 @@ async def create_processed_agent_data(data: List[ProcessedAgentData], user_id: i response_model=ProcessedAgentDataInDB, ) def read_processed_agent_data(processed_agent_data_id: int): - # Get data by id - pass + session = SessionLocal() + try: + stmt = select(processed_agent_data).where( + processed_agent_data.c.id == processed_agent_data_id + ) + res = session.execute(stmt).fetchone() + if not res: + raise HTTPException(status_code=404, detail="Not found") + + return dict(res._mapping) + + finally: + session.close() @app.get("/processed_agent_data/", response_model=list[ProcessedAgentDataInDB]) def list_processed_agent_data(): - # Get list of data - pass + session = SessionLocal() + try: + stmt = select(processed_agent_data) + res = session.execute(stmt).fetchall() + if not res: + raise HTTPException(status_code=404, detail="Not found") + + return [dict(r._mapping) for r in res] + + finally: + session.close() @app.put( diff --git a/store/schemas.py b/store/schemas.py new file mode 100644 index 0000000..3d13dee --- /dev/null +++ b/store/schemas.py @@ -0,0 +1,51 @@ +from datetime import datetime + +from pydantic import BaseModel, field_validator + + +class ProcessedAgentDataInDB(BaseModel): + id: int + road_state: str + user_id: int + x: float + y: float + z: float + latitude: float + longitude: float + timestamp: datetime + + +# FastAPI models +class AccelerometerData(BaseModel): + x: float + y: float + z: float + + +class GpsData(BaseModel): + latitude: float + longitude: float + + +class AgentData(BaseModel): + user_id: int + accelerometer: AccelerometerData + gps: GpsData + timestamp: datetime + + @classmethod + @field_validator("timestamp", mode="before") + def check_timestamp(cls, value): + if isinstance(value, datetime): + return value + try: + return datetime.fromisoformat(value) + except (TypeError, ValueError): + raise ValueError( + "Invalid timestamp format. Expected ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)." + ) + + +class ProcessedAgentData(BaseModel): + road_state: str + agent_data: AgentData