From 4bb95dfb77ff548a3050059ebb857a1d73e1ee3c Mon Sep 17 00:00:00 2001 From: OrusskiyVR Date: Mon, 26 May 2025 17:11:52 +0300 Subject: [PATCH 01/11] WIP --- server/user_service/requirements.txt | 48 +++++++++++++++++++++ server/user_service/src/auth/auth.py | 2 - server/user_service/src/config.py | 18 +++++++- server/user_service/src/database/init_db.py | 10 +++++ server/user_service/src/main.py | 5 ++- 5 files changed, 79 insertions(+), 4 deletions(-) diff --git a/server/user_service/requirements.txt b/server/user_service/requirements.txt index e69de29..2762d89 100644 --- a/server/user_service/requirements.txt +++ b/server/user_service/requirements.txt @@ -0,0 +1,48 @@ +annotated-types==0.7.0 +anyio==4.9.0 +async-timeout==5.0.1 +asyncpg==0.30.0 +beautifulsoup4==4.13.4 +certifi==2025.4.26 +charset-normalizer==3.4.2 +click==8.1.8 +colorama==0.4.6 +dnspython==2.7.0 +email_validator==2.2.0 +exceptiongroup==1.3.0 +fastapi==0.115.12 +fastapi-cli==0.0.7 +greenlet==3.2.2 +h11==0.16.0 +httpcore==1.0.9 +httptools==0.6.4 +httpx==0.28.1 +idna==3.10 +Jinja2==3.1.6 +markdown-it-py==3.0.0 +MarkupSafe==3.0.2 +mdurl==0.1.2 +passlib==1.7.4 +psycopg2-binary==2.9.10 +pydantic==2.11.5 +pydantic_core==2.33.2 +Pygments==2.19.1 +python-dotenv==1.1.0 +python-multipart==0.0.20 +PyYAML==6.0.2 +requests==2.32.3 +rich==14.0.0 +rich-toolkit==0.14.6 +shellingham==1.5.4 +sniffio==1.3.1 +soupsieve==2.7 +SQLAlchemy==2.0.41 +starlette==0.46.2 +typer==0.15.4 +typing-inspection==0.4.1 +typing_extensions==4.13.2 +urllib3==2.4.0 +uvicorn==0.34.2 +uvloop==0.21.0 +watchfiles==1.0.5 +websockets==15.0.1 diff --git a/server/user_service/src/auth/auth.py b/server/user_service/src/auth/auth.py index 4631314..a09092f 100644 --- a/server/user_service/src/auth/auth.py +++ b/server/user_service/src/auth/auth.py @@ -2,8 +2,6 @@ from sqlalchemy.ext.asyncio import AsyncSession from datetime import datetime, timedelta, timezone -from jose import jwt - from database.schemas import UserInDB, TokenData from database import cruds diff --git a/server/user_service/src/config.py b/server/user_service/src/config.py index 66fc382..f237f40 100644 --- a/server/user_service/src/config.py +++ b/server/user_service/src/config.py @@ -1,10 +1,26 @@ import os from abc import ABC from dataclasses import asdict, dataclass +import logging class CfgBase(ABC): dict: callable = asdict class PostgresCfg(CfgBase): - url: str = os.getenv("DATABASE_URL") \ No newline at end of file + url: str = os.getenv("DATABASE_URL") + +class ServiceLogger(): + def __init__(self): + self.baseconfig = logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + handlers=[ + logging.StreamHandler(), + logging.FileHandler("service.log") + ] + ) + self.logger = logging.getLogger(__name__) + + def __call__(self): + return self.logger \ No newline at end of file diff --git a/server/user_service/src/database/init_db.py b/server/user_service/src/database/init_db.py index e69de29..65ccf55 100644 --- a/server/user_service/src/database/init_db.py +++ b/server/user_service/src/database/init_db.py @@ -0,0 +1,10 @@ +import asyncio +from database.database import engine +from database.models import Base + +async def init_models(): + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + +if __name__ == "__main__": + asyncio.run(init_models()) \ No newline at end of file diff --git a/server/user_service/src/main.py b/server/user_service/src/main.py index ba3ee38..d19b3ef 100644 --- a/server/user_service/src/main.py +++ b/server/user_service/src/main.py @@ -6,6 +6,7 @@ from database import cruds from database.schemas import UserCreate, UserGet, UserLogin from auth.auth import authenticate_user, create_access_token +from config import ServiceLogger @asynccontextmanager async def lifespan(app: FastAPI): @@ -21,9 +22,11 @@ async def lifespan(app: FastAPI): print("Shutting down...") app = FastAPI(lifespan=lifespan, title="EV Route User Service") +logger = ServiceLogger() @app.post("/register", response_model=UserGet, status_code=status.HTTP_201_CREATED) async def add_user(user: UserCreate, db: AsyncSession = Depends(get_db)): + logger.info("") return await cruds.add_user(db, user) @app.post("/login", response_model=UserGet) @@ -43,4 +46,4 @@ async def get_user(login: str, db: AsyncSession = Depends(get_db)): @app.patch("/user/car", response_model=UserGet, status_code=status.HTTP_202_ACCEPTED) async def update_car(login: str, new_car: int, db: AsyncSession = Depends(get_db)): - return await cruds.update_user_car(db, login, new_car) + return await cruds.update_user_car(db, login, new_car) \ No newline at end of file From 9012b6606b3b7fc2655118dcbe75d97df7dcaf9d Mon Sep 17 00:00:00 2001 From: OrusskiyVR Date: Tue, 27 May 2025 14:08:19 +0300 Subject: [PATCH 02/11] WIP --- server/data_service/src/database/cruds.py | 4 +- server/data_service/src/main.py | 15 +++--- server/gateway/src/routers/user.py | 2 +- server/gateway/src/utils.py | 48 ------------------- server/station_service/requirements.txt | 4 ++ server/station_service/src/database/models.py | 2 +- .../station_service/src/database/schemas.py | 6 +-- server/station_service/src/main.py | 41 ++++++++++++---- server/station_service/src/stations.py | 37 +++++++++----- server/user_service/requirements.txt | 3 ++ server/user_service/src/auth/auth.py | 15 ++++-- server/user_service/src/config.py | 21 +------- server/user_service/src/database/cruds.py | 15 +++--- server/user_service/src/database/schemas.py | 11 ++--- server/user_service/src/logger_config.py | 20 ++++++++ server/user_service/src/main.py | 35 +++++++++----- 16 files changed, 150 insertions(+), 129 deletions(-) delete mode 100644 server/gateway/src/utils.py create mode 100644 server/user_service/src/logger_config.py diff --git a/server/data_service/src/database/cruds.py b/server/data_service/src/database/cruds.py index 70aa228..79afa34 100644 --- a/server/data_service/src/database/cruds.py +++ b/server/data_service/src/database/cruds.py @@ -33,11 +33,11 @@ async def get_all_cars(db: AsyncSession) -> list[CarGet]: result = await db.execute(stmt) return result.scalars().all() -async def get_car_by_name(db: AsyncSession, name: str) -> CarGet | None: +async def get_car_by_name(db: AsyncSession, name: str) -> list[CarGet] | None: stmt = select(Car).where(Car.name == name) result = await db.execute(stmt) - return result.scalar_one_or_none() + return result.scalars().all() async def get_car(db: AsyncSession, id: int) -> CarGet | None: stmt = select(Car).where(Car.id == id) diff --git a/server/data_service/src/main.py b/server/data_service/src/main.py index 017a67b..db8dcd3 100644 --- a/server/data_service/src/main.py +++ b/server/data_service/src/main.py @@ -1,4 +1,5 @@ from fastapi import FastAPI, Depends, status +from fastapi.middleware.cors import CORSMiddleware from sqlalchemy.ext.asyncio import AsyncSession from database.database import get_db from EV_cars import parse_ev_cars @@ -12,17 +13,17 @@ async def lifespan(app: FastAPI): try: print("Starting up") - app.add_middleware( - allow_methods=["GET", "POST"], - allow_origins=["*"] - ) await init_models() yield finally: print("Shutting down...") app = FastAPI(lifespan=lifespan, title="EV Route Car Service") - +app.add_middleware( + CORSMiddleware, + allow_methods=["GET", "POST"], + allow_origins=["*"] +) @app.post("/car", response_model=CarCreate, status_code=status.HTTP_201_CREATED) async def add_car(car: CarCreate, db: AsyncSession = Depends(get_db)): @@ -32,7 +33,7 @@ async def add_car(car: CarCreate, db: AsyncSession = Depends(get_db)): async def add_cars(cars: list[CarCreate], db: AsyncSession = Depends(get_db)): return await cruds.add_cars(db, cars) -@app.get("/car", response_model=CarGet) +@app.get("/car", response_model=list[CarGet]) async def get_car_by_name(name: str, db: AsyncSession = Depends(get_db)): return await cruds.get_car_by_name(db, name) @@ -40,6 +41,6 @@ async def get_car_by_name(name: str, db: AsyncSession = Depends(get_db)): async def get_car(car_id: int, db: AsyncSession = Depends(get_db)): return await cruds.get_car(db, car_id) -@app.get("/cars", response_model=CarGet) +@app.get("/cars", response_model=list[CarGet]) async def get_cars(db: AsyncSession = Depends(get_db)): return await cruds.get_all_cars(db) \ No newline at end of file diff --git a/server/gateway/src/routers/user.py b/server/gateway/src/routers/user.py index 3821aed..bafbe9c 100644 --- a/server/gateway/src/routers/user.py +++ b/server/gateway/src/routers/user.py @@ -22,7 +22,7 @@ def save_user(data: UserData): ON CONFLICT (email) DO UPDATE SET car_model = EXCLUDED.car_model, battery_capacity = EXCLUDED.battery_capacity, - connector_type = EXCLUDED.connector_type + connector_type = EXCLUDED.conector_type """, (data.email, data.car_model, data.battery_capacity, data.connector_type) ) diff --git a/server/gateway/src/utils.py b/server/gateway/src/utils.py deleted file mode 100644 index d77654c..0000000 --- a/server/gateway/src/utils.py +++ /dev/null @@ -1,48 +0,0 @@ -import jwt -import bcrypt -from datetime import datetime, timedelta - -SECRET_KEY = "secret" -ALGORITHM = "HS256" - -def hash_password(password: str) -> str: - return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode() - -def verify_password(password: str, hashed: str) -> bool: - return bcrypt.checkpw(password.encode(), hashed.encode()) - -def create_access_token(data: dict, expires_delta: timedelta = timedelta(hours=1)): - to_encode = data.copy() - to_encode["exp"] = datetime.utcnow() + expires_delta - return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - -def decode_token(token: str): - return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - - -# async def authenticate_user(self, db: AsyncSession, login: str, password: str) -> UserInDB | None: -# user = await cruds.get_user(db, login) -# if not user or not pwd_context.verify(password, user.password_hash): -# return None -# return user - -# def create_access_token(self, data: TokenData, expires_delta: timedelta) -> str: -# to_encode = data.model_dump() -# expire = datetime.now(timezone.utc) + expires_delta -# to_encode.update({"exp": expire}) -# return jwt.encode(to_encode, self.SECRET_KEY, algorithm=self.ALGORITHM) - -# SECRET_KEY = "secret" -# ALGORITHM = "HS256" - -# security = HTTPBearer() - -# def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)): -# token = credentials.credentials -# try: -# payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) -# return payload -# except jwt.ExpiredSignatureError: -# raise HTTPException(status_code=401, detail="Token expired") -# except jwt.InvalidTokenError: -# raise HTTPException(status_code=401, detail="Invalid token") \ No newline at end of file diff --git a/server/station_service/requirements.txt b/server/station_service/requirements.txt index e69de29..8ea05d5 100644 --- a/server/station_service/requirements.txt +++ b/server/station_service/requirements.txt @@ -0,0 +1,4 @@ +SQLAlchemy +fastapi[standard] +requests +asyncpg \ No newline at end of file diff --git a/server/station_service/src/database/models.py b/server/station_service/src/database/models.py index 3dcd8af..a16d4e0 100644 --- a/server/station_service/src/database/models.py +++ b/server/station_service/src/database/models.py @@ -12,4 +12,4 @@ class Station(Base): latitude: Mapped[float] = mapped_column(Float, index=True, nullable=False) longtitude: Mapped[float] = mapped_column(Float, index=True, nullable=False) connection_type: Mapped[str] = mapped_column(String, index=True, nullable=False) - power_kw: Mapped[int] = mapped_column(Integer, nullable=False) \ No newline at end of file + power_kw: Mapped[int] = mapped_column(__name_pos=Integer, nullable=True) \ No newline at end of file diff --git a/server/station_service/src/database/schemas.py b/server/station_service/src/database/schemas.py index 5db06f0..d6ef8ee 100644 --- a/server/station_service/src/database/schemas.py +++ b/server/station_service/src/database/schemas.py @@ -5,12 +5,12 @@ class StationCreate(BaseModel): latitude: float longtitude: float connection_type: str - power_kw: int + power_kw: int | None -class StationGet(BaseModel): +class StationGet(BaseModel): id: int name: str latitude: float longtitude: float connection_type: str - power_kw: int \ No newline at end of file + power_kw: int | None \ No newline at end of file diff --git a/server/station_service/src/main.py b/server/station_service/src/main.py index 33d639a..1816ba8 100644 --- a/server/station_service/src/main.py +++ b/server/station_service/src/main.py @@ -1,28 +1,53 @@ from fastapi import FastAPI, Depends, status +from fastapi.middleware.cors import CORSMiddleware from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select + from database.database import get_db -from stations import get_all_stations -from database.init_db import init_models -from contextlib import asynccontextmanager from database import cruds from database.schemas import StationCreate, StationGet +from database.models import Station + +from stations import all_stations_info +from database.init_db import init_models +from contextlib import asynccontextmanager @asynccontextmanager async def lifespan(app: FastAPI): try: print("Starting up") - app.add_middleware( - allow_methods=["GET", "POST"], - allow_origins=["*"] - ) await init_models() + + db_gen = get_db() + session = await anext(db_gen) + try: + result = await session.execute(select(Station).limit(1)) + existing = result.scalar_one_or_none() + + if True: + print("Станции не найдены. Загружаем из API...") + origin_stations = all_stations_info() + await cruds.add_stations(session, origin_stations) + await session.commit() + else: + print("Станции уже есть, пропускаем.") + except Exception as e: + await session.rollback() + print(str(e)) + raise e + finally: + await db_gen.aclose() yield finally: print("Shutting down...") app = FastAPI(lifespan=lifespan, title="EV Route Station Service") - +app.add_middleware( + CORSMiddleware, + allow_methods=["GET", "POST"], + allow_origins=["*"] +) @app.post("/station", response_model=StationCreate, status_code=status.HTTP_201_CREATED) async def add_station(station: StationCreate, db: AsyncSession = Depends(get_db)): diff --git a/server/station_service/src/stations.py b/server/station_service/src/stations.py index 330bbb5..5da7a93 100644 --- a/server/station_service/src/stations.py +++ b/server/station_service/src/stations.py @@ -3,14 +3,19 @@ import os API_BASE_URL = "https://api.openchargemap.io/v3" -API_KEY = os.getenv("API_KEY") +API_KEY = "0693cf5e-35eb-460b-acb3-585ca9dd2547" #os.getenv("API_KEY") URL_PARAMS = { 'output': 'json', 'key': API_KEY } -def get_ref_data(): +def get_ref_data_base_params(): + ''' + Возвращает описание возможных значений полей по нужным направлениям: + 1) Типы зарядок + 2) Страны + ''' new_url = f"{API_BASE_URL}/referencedata?{'&'.join(f'{key}={value}' for key, value in URL_PARAMS.items())}" response = requests.get(new_url) response.raise_for_status() @@ -21,13 +26,19 @@ def get_ref_data(): return conn_types, countries def get_good_charg_ids(): + result_id = [] GOOD_CHARG = ['CHAdeMO', 'CCS (Type 1)', 'CCS (Type 2)'] - - pass + connectors, _ = get_ref_data_base_params() + + for conn in connectors: + if conn['Title'] in GOOD_CHARG: + result_id.append(conn['ID']) + return result_id -def get_all_info_by_country(country_id: int): +def get_all_info_by_country(country_id: int, connector_ids: list[int] = [2, 32, 33]): result = [] - new_url = f"{API_BASE_URL}/poi?{'&'.join(f'{key}={value}' for key, value in URL_PARAMS.items())}&countryid={country_id}&connectiontypeid=2, 32, 33" + new_url = f"""{API_BASE_URL}/poi?{'&'.join(f'{key}={value}' for key, value in URL_PARAMS.items())}&countryid={country_id}&connectiontypeid={', '.join(str(charge_id) for charge_id in connector_ids)}""" + response = requests.get(new_url) response.raise_for_status() data = response.json() @@ -35,7 +46,6 @@ def get_all_info_by_country(country_id: int): result.append(get_use_info_from_json(station=station)) return result - def get_use_info_from_json(station: dict) -> StationCreate: address_info = station['AddressInfo'] name = address_info['Title'] @@ -44,8 +54,11 @@ def get_use_info_from_json(station: dict) -> StationCreate: connections_info = station['Connections'] for connection in connections_info: - connection_type = connection['ConnectionType'] + connection_type = connection['ConnectionType']['Title'] power_kw = connection['PowerKW'] + if connection['PowerKW'] is not None: + power_kw = int(connection['PowerKW']) + return StationCreate(name=name, latitude=latitude, @@ -56,10 +69,12 @@ def get_use_info_from_json(station: dict) -> StationCreate: def all_stations_info(): stations = [] - for country_id in range(251): country_stations = get_all_info_by_country(country_id=country_id) - stations.append(country_stations) + stations.extend(country_stations) return stations -stations = all_stations_info() \ No newline at end of file +if __name__ == '__main__': + stations = all_stations_info() + + print(stations) \ No newline at end of file diff --git a/server/user_service/requirements.txt b/server/user_service/requirements.txt index 2762d89..96f97f2 100644 --- a/server/user_service/requirements.txt +++ b/server/user_service/requirements.txt @@ -46,3 +46,6 @@ uvicorn==0.34.2 uvloop==0.21.0 watchfiles==1.0.5 websockets==15.0.1 +PyJWT +bcrypt +passlib[bcrypt] \ No newline at end of file diff --git a/server/user_service/src/auth/auth.py b/server/user_service/src/auth/auth.py index a09092f..a387d7a 100644 --- a/server/user_service/src/auth/auth.py +++ b/server/user_service/src/auth/auth.py @@ -1,20 +1,23 @@ from passlib.context import CryptContext from sqlalchemy.ext.asyncio import AsyncSession from datetime import datetime, timedelta, timezone - +import jwt +from pydantic import SecretStr from database.schemas import UserInDB, TokenData from database import cruds pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + class AuthService: def __init__(self, secret_key: str, algorithm: str = "HS256"): self.SECRET_KEY = secret_key self.ALGORITHM = algorithm - async def authenticate_user(self, db: AsyncSession, login: str, password: str) -> UserInDB | None: + async def authenticate_user(self, db: AsyncSession, login: str, password: SecretStr) -> UserInDB | None: user = await cruds.get_user(db, login) - if not user or not pwd_context.verify(password, user.password_hash): + password_orig = password.get_secret_value() + if not user or not pwd_context.verify(password_orig, user.password_hash): return None return user @@ -22,4 +25,8 @@ def create_access_token(self, data: TokenData, expires_delta: timedelta) -> str: to_encode = data.model_dump() expire = datetime.now(timezone.utc) + expires_delta to_encode.update({"exp": expire}) - return jwt.encode(to_encode, self.SECRET_KEY, algorithm=self.ALGORITHM) \ No newline at end of file + return jwt.encode(to_encode, self.SECRET_KEY, algorithm=self.ALGORITHM) + + +# def decode_token(token: str): +# return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) \ No newline at end of file diff --git a/server/user_service/src/config.py b/server/user_service/src/config.py index f237f40..dd2c5bc 100644 --- a/server/user_service/src/config.py +++ b/server/user_service/src/config.py @@ -1,26 +1,9 @@ import os from abc import ABC -from dataclasses import asdict, dataclass -import logging - +from dataclasses import asdict class CfgBase(ABC): dict: callable = asdict class PostgresCfg(CfgBase): - url: str = os.getenv("DATABASE_URL") - -class ServiceLogger(): - def __init__(self): - self.baseconfig = logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - handlers=[ - logging.StreamHandler(), - logging.FileHandler("service.log") - ] - ) - self.logger = logging.getLogger(__name__) - - def __call__(self): - return self.logger \ No newline at end of file + url: str = os.getenv("DATABASE_URL") \ No newline at end of file diff --git a/server/user_service/src/database/cruds.py b/server/user_service/src/database/cruds.py index 1b7140b..3f2c556 100644 --- a/server/user_service/src/database/cruds.py +++ b/server/user_service/src/database/cruds.py @@ -9,16 +9,15 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") async def add_user(db: AsyncSession, user: UserCreate) -> UserGet: hashed_password = pwd_context.hash(user.password.get_secret_value()) - new_user = UserInDB(login=user.login, - password_hash=hashed_password) + new_user = User(login=user.login, + password_hash=hashed_password) try: db.add(new_user) - db.flush() + await db.flush() return UserGet(id=new_user.id, login=new_user.login, car_id=new_user.car_id) except IntegrityError as ie: - await db.rollback() raise HTTPException(status_code=400, detail=f"Логин уже занят: {str(ie)}") async def get_db_user(db: AsyncSession, login: str) -> UserInDB | None: @@ -29,10 +28,14 @@ async def get_db_user(db: AsyncSession, login: str) -> UserInDB | None: async def get_user(db: AsyncSession, login: str) -> UserGet | None: stmt = select(User).where(User.login == login) result = await db.execute(stmt) - return result.scalar_one_or_none() + user = result.scalar_one_or_none() + + if user is None: + raise HTTPException(status_code=404, detail="Пользователь не найден") + return user async def update_user_car(db: AsyncSession, login: int, new_car: int | None) -> UserGet: - query = select(UserInDB).where(UserInDB.login == login) + query = select(User).where(User.login == login) result = await db.execute(statement=query) user = result.scalar_one_or_none() diff --git a/server/user_service/src/database/schemas.py b/server/user_service/src/database/schemas.py index 1612a95..1c6502e 100644 --- a/server/user_service/src/database/schemas.py +++ b/server/user_service/src/database/schemas.py @@ -16,14 +16,13 @@ class UserInDB(BaseModel): class UserGet(BaseModel): id: int login: str - car_id: str | None + car_id: int | None + +class AccessToken(BaseModel): + access_token: str + token_type: str = "bearer" class TokenData(BaseModel): sub: str exp: datetime | None = None scopes: list[str] = [] - - class Config: - json_encoders = { - datetime: lambda v: v.timestamp() # Для корректной сериализации в JSON - } \ No newline at end of file diff --git a/server/user_service/src/logger_config.py b/server/user_service/src/logger_config.py new file mode 100644 index 0000000..111efaf --- /dev/null +++ b/server/user_service/src/logger_config.py @@ -0,0 +1,20 @@ +import logging + +def setup_logger(name=__name__): + logger = logging.getLogger(name) + logger.setLevel(logging.INFO) + + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) + + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + + # Если логгер уже имеет обработчики, не добавляем их снова + if not logger.handlers: + logger.addHandler(console_handler) + + return logger + +logger = setup_logger() \ No newline at end of file diff --git a/server/user_service/src/main.py b/server/user_service/src/main.py index d19b3ef..8d3a682 100644 --- a/server/user_service/src/main.py +++ b/server/user_service/src/main.py @@ -1,44 +1,53 @@ +from datetime import timedelta from fastapi import FastAPI, Depends, status, HTTPException +from fastapi.middleware.cors import CORSMiddleware from sqlalchemy.ext.asyncio import AsyncSession from database.database import get_db from database.init_db import init_models from contextlib import asynccontextmanager from database import cruds -from database.schemas import UserCreate, UserGet, UserLogin -from auth.auth import authenticate_user, create_access_token -from config import ServiceLogger +from database.schemas import TokenData, UserCreate, UserGet, UserLogin, AccessToken +from auth.auth import AuthService +from logger_config import logger +import os @asynccontextmanager async def lifespan(app: FastAPI): try: print("Starting up") - app.add_middleware( - allow_methods=["GET", "POST", "PATCH"], - allow_origins=["*"] - ) await init_models() yield finally: print("Shutting down...") app = FastAPI(lifespan=lifespan, title="EV Route User Service") -logger = ServiceLogger() +app.add_middleware( +CORSMiddleware, + allow_methods=["GET", "POST", "PATCH"], + allow_origins=["*"] +) + +secret_key = os.getenv("SECRET_KEY") +auth_service = AuthService(secret_key=secret_key) @app.post("/register", response_model=UserGet, status_code=status.HTTP_201_CREATED) async def add_user(user: UserCreate, db: AsyncSession = Depends(get_db)): - logger.info("") + logger.info("Начали регистрацию") return await cruds.add_user(db, user) -@app.post("/login", response_model=UserGet) +@app.post("/login", response_model=AccessToken) async def login(user_data: UserLogin, db: AsyncSession = Depends(get_db)): - user = await authenticate_user(db, user_data.login, user_data.password) + user = await auth_service.authenticate_user(db, user_data.login, user_data.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect login or password", ) - access_token = create_access_token(data={"sub": user.login}) - return {"access_token": access_token, "token_type": "bearer"} + access_token = auth_service.create_access_token( + data=TokenData(sub=user.login), + expires_delta=timedelta(minutes=30) + ) + return AccessToken(access_token=access_token) @app.get("/user", response_model=UserGet) async def get_user(login: str, db: AsyncSession = Depends(get_db)): From 792d366ac4d41bb82e267315058c70dd6f40e593 Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov <114882226+ZhuchkaTriplesix@users.noreply.github.com> Date: Tue, 27 May 2025 14:52:00 +0300 Subject: [PATCH 03/11] Update schemas.py default None --- server/station_service/src/database/schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/station_service/src/database/schemas.py b/server/station_service/src/database/schemas.py index d6ef8ee..732c985 100644 --- a/server/station_service/src/database/schemas.py +++ b/server/station_service/src/database/schemas.py @@ -13,4 +13,4 @@ class StationGet(BaseModel): latitude: float longtitude: float connection_type: str - power_kw: int | None \ No newline at end of file + power_kw: int | None = None From f83fd5319ce0f80fa29572d81140f68dfa0f8dca Mon Sep 17 00:00:00 2001 From: OrusskiyVR Date: Tue, 27 May 2025 15:31:43 +0300 Subject: [PATCH 04/11] GateWay added --- server/data_service/src/database/cruds.py | 6 +- server/gateway/src/config.py | 10 ---- server/gateway/src/main.py | 4 +- server/gateway/src/routers/auth.py | 45 -------------- server/gateway/src/routers/data.py | 52 ++++++++++++++++ server/gateway/src/routers/routing.py | 9 +++ server/gateway/src/routers/station.py | 53 ++++++++--------- server/gateway/src/routers/user.py | 62 ++++++++++---------- server/station_service/src/database/cruds.py | 2 +- server/station_service/src/main.py | 4 +- 10 files changed, 127 insertions(+), 120 deletions(-) delete mode 100644 server/gateway/src/routers/auth.py create mode 100644 server/gateway/src/routers/data.py create mode 100644 server/gateway/src/routers/routing.py diff --git a/server/data_service/src/database/cruds.py b/server/data_service/src/database/cruds.py index 79afa34..42f6934 100644 --- a/server/data_service/src/database/cruds.py +++ b/server/data_service/src/database/cruds.py @@ -1,5 +1,6 @@ from database.schemas import CarCreate, CarGet from database.models import Car +from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select @@ -43,5 +44,8 @@ async def get_car(db: AsyncSession, id: int) -> CarGet | None: stmt = select(Car).where(Car.id == id) result = await db.execute(stmt) - return result.scalar_one_or_none() + car = result.scalar_one_or_none() + if car is None: + raise HTTPException(status_code=404, detail="Пользователь не найден") + return car diff --git a/server/gateway/src/config.py b/server/gateway/src/config.py index 66fc382..e69de29 100644 --- a/server/gateway/src/config.py +++ b/server/gateway/src/config.py @@ -1,10 +0,0 @@ -import os -from abc import ABC -from dataclasses import asdict, dataclass - - -class CfgBase(ABC): - dict: callable = asdict - -class PostgresCfg(CfgBase): - url: str = os.getenv("DATABASE_URL") \ No newline at end of file diff --git a/server/gateway/src/main.py b/server/gateway/src/main.py index 4e4f0ef..13904b7 100644 --- a/server/gateway/src/main.py +++ b/server/gateway/src/main.py @@ -3,9 +3,7 @@ from contextlib import asynccontextmanager import logging import uvicorn - -from server.gateway.src.routers import auth, route -from server.gateway.src.routers import user, station +from routers import auth, route, user, station logging.basicConfig( level=logging.INFO, diff --git a/server/gateway/src/routers/auth.py b/server/gateway/src/routers/auth.py deleted file mode 100644 index 2e99684..0000000 --- a/server/gateway/src/routers/auth.py +++ /dev/null @@ -1,45 +0,0 @@ -from fastapi import APIRouter, HTTPException, Depends -from pydantic import BaseModel, EmailStr -from utils import hash_password, verify_password, create_access_token -import psycopg2 - -router = APIRouter() - -SECRET_KEY = "secret" - -conn = psycopg2.connect(dbname="evroutesusers", user="roki", password="roki", host="localhost") -cursor = conn.cursor() - -class LoginData(BaseModel): - email: EmailStr - password: str - -class RegisterData(BaseModel): - email: EmailStr - password: str - role: str = "user" - -@router.post("/register") -def register(data: RegisterData): - cursor.execute("SELECT 1 FROM users WHERE email = %s", (data.email,)) - if cursor.fetchone(): - raise HTTPException(status_code=400, detail="User already exists") - - hashed_pw = hash_password(data.password) - - cursor.execute( - "INSERT INTO users (email, password_hash, role) VALUES (%s, %s, %s)", - (data.email, hashed_pw, data.role) - ) - conn.commit() - return {"status": "registered"} - -@router.post("/login") -def login(data: LoginData): - cursor.execute("SELECT password_hash, role FROM users WHERE email = %s", (data.email,)) - row = cursor.fetchone() - if not row or not verify_password(data.password, row[0]): - raise HTTPException(status_code=401, detail="Invalid credentials") - - token = create_access_token({"email": data.email, "role": row[1]}) - return {"access_token": token} \ No newline at end of file diff --git a/server/gateway/src/routers/data.py b/server/gateway/src/routers/data.py new file mode 100644 index 0000000..d7d9784 --- /dev/null +++ b/server/gateway/src/routers/data.py @@ -0,0 +1,52 @@ +from fastapi import APIRouter, Request +import httpx +from config import SERVICES + +router = APIRouter() + +@router.post("/add_car") +async def add_car(request: Request): + body = await request.body() + async with httpx.AsyncClient() as client: + response = await client.post(f"{SERVICES['data']}/car", content=body, headers=request.headers) + return response.json() + +@router.post("/add_cars") +async def add_cars(request: Request): + body = await request.body() + async with httpx.AsyncClient() as client: + response = await client.post(f"{SERVICES['data']}/cars", content=body, headers=request.headers) + return response.json() + +@router.get("/get_car_by_name") +async def get_car_by_name(request: Request): + query_params = str(request.query_params) + async with httpx.AsyncClient() as client: + request_url = f"{SERVICES['data']}/car" + if query_params: + request_url += f"?{query_params}" + + response = await client.get(request_url) + response.raise_for_status() + return response.json() + + +@router.get("/get_car/{car_id}") +async def get_user_by_login(car_id: int, request: Request): + query_params = str(request.query_params) + async with httpx.AsyncClient() as client: + request_url = f"{SERVICES['data']}/car/{car_id}" + if query_params: + request_url += f"?{query_params}" + + response = await client.get(request_url) + response.raise_for_status() + return response.json() + + +@router.get("/get_cars") +async def update_user_car(request: Request): + body = await request.body() + async with httpx.AsyncClient() as client: + response = await client.post(f"{SERVICES['data']}/cars", content=body, headers=request.headers) + return response.json() diff --git a/server/gateway/src/routers/routing.py b/server/gateway/src/routers/routing.py new file mode 100644 index 0000000..262b4b3 --- /dev/null +++ b/server/gateway/src/routers/routing.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI +from .auth import router as auth_router +from .user import router as user_router +from .station import router as station_router + +def include_routes(app: FastAPI): + app.include_router(auth_router) + app.include_router(user_router) + app.include_router(station_router) \ No newline at end of file diff --git a/server/gateway/src/routers/station.py b/server/gateway/src/routers/station.py index 7554805..1cb3e7a 100644 --- a/server/gateway/src/routers/station.py +++ b/server/gateway/src/routers/station.py @@ -1,34 +1,31 @@ -from fastapi import APIRouter -from pydantic import BaseModel -import psycopg2 +from fastapi import APIRouter, Request +import httpx +from config import SERVICES router = APIRouter() -conn = psycopg2.connect(dbname="evroutesstations", user="roki", password="roki", host="localhost") -cursor = conn.cursor() +@router.post("/add_station") +async def add_station(request: Request): + body = await request.body() + async with httpx.AsyncClient() as client: + response = await client.post(f"{SERVICES['station']}/station", content=body, headers=request.headers) + return response.json() -class Station(BaseModel): - name: str - latitude: float - longitude: float - connector_type: str - power_kW: int - working_hours: str +@router.post("/add_stations") +async def login(request: Request): + body = await request.body() + async with httpx.AsyncClient() as client: + response = await client.post(f"{SERVICES['station']}/stations", content=body, headers=request.headers) + return response.json() -@router.post("/") -def add_station(station: Station): - cursor.execute( - """ - INSERT INTO stations (name, latitude, longitude, connector_type, power_kW, working_hours) - VALUES (%s, %s, %s, %s, %s, %s) - """, - (station.name, station.latitude, station.longitude, station.connector_type, station.power_kW, station.working_hours) - ) - conn.commit() - return {"status": "ok"} +@router.get("/all_stations") +async def get_user_by_login(request: Request): + query_params = str(request.query_params) + async with httpx.AsyncClient() as client: + request_url = f"{SERVICES['station']}/stations" + if query_params: + request_url += f"?{query_params}" -@router.get("/") -def get_stations(): - cursor.execute("SELECT * FROM stations") - rows = cursor.fetchall() - return rows \ No newline at end of file + response = await client.get(request_url) + response.raise_for_status() + return response.json() \ No newline at end of file diff --git a/server/gateway/src/routers/user.py b/server/gateway/src/routers/user.py index bafbe9c..cb4c8ad 100644 --- a/server/gateway/src/routers/user.py +++ b/server/gateway/src/routers/user.py @@ -1,36 +1,38 @@ -from fastapi import APIRouter -from pydantic import BaseModel -import psycopg2 +from fastapi import APIRouter, Request +import httpx +from config import SERVICES router = APIRouter() -conn = psycopg2.connect(dbname="evroutesuserinfo", user="roki", password="roki", host="localhost") -cursor = conn.cursor() +@router.post("/register") +async def add_user(request: Request): + body = await request.body() + async with httpx.AsyncClient() as client: + response = await client.post(f"{SERVICES['auth']}/register", content=body, headers=request.headers) + return response.json() -class UserData(BaseModel): - email: str - car_model: str - battery_capacity: float - connector_type: str +@router.post("/login") +async def login(request: Request): + body = await request.body() + async with httpx.AsyncClient() as client: + response = await client.post(f"{SERVICES['auth']}/login", content=body, headers=request.headers) + return response.json() -@router.post("/") -def save_user(data: UserData): - cursor.execute( - """ - INSERT INTO users (email, car_model, battery_capacity, connector_type) - VALUES (%s, %s, %s, %s) - ON CONFLICT (email) DO UPDATE SET - car_model = EXCLUDED.car_model, - battery_capacity = EXCLUDED.battery_capacity, - connector_type = EXCLUDED.conector_type - """, - (data.email, data.car_model, data.battery_capacity, data.connector_type) - ) - conn.commit() - return {"status": "saved"} +@router.get("/user_by_login") +async def get_user_by_login(request: Request): + query_params = str(request.query_params) + async with httpx.AsyncClient() as client: + request_url = f"{SERVICES['auth']}/user" + if query_params: + request_url += f"?{query_params}" -@router.get("/{email}") -def get_user(email: str): - cursor.execute("SELECT * FROM users WHERE email = %s", (email,)) - row = cursor.fetchone() - return row \ No newline at end of file + response = await client.get(request_url) + response.raise_for_status() + return response.json() + +@router.get("/user_change_car") +async def update_user_car(request: Request): + body = await request.body() + async with httpx.AsyncClient() as client: + response = await client.post(f"{SERVICES['auth']}/user/car", content=body, headers=request.headers) + return response.json() diff --git a/server/station_service/src/database/cruds.py b/server/station_service/src/database/cruds.py index 61a0e88..01d9553 100644 --- a/server/station_service/src/database/cruds.py +++ b/server/station_service/src/database/cruds.py @@ -31,5 +31,5 @@ async def add_stations(db: AsyncSession, stations: list[StationCreate]) -> list[ async def get_all_stations(db: AsyncSession) -> list[StationGet]: stmt = select(Station) - result = await db.execute(stmt) + result = await db.execute(statement=stmt) return result.scalars().all() diff --git a/server/station_service/src/main.py b/server/station_service/src/main.py index 1816ba8..ec0c27a 100644 --- a/server/station_service/src/main.py +++ b/server/station_service/src/main.py @@ -25,7 +25,7 @@ async def lifespan(app: FastAPI): result = await session.execute(select(Station).limit(1)) existing = result.scalar_one_or_none() - if True: + if existing is None: print("Станции не найдены. Загружаем из API...") origin_stations = all_stations_info() await cruds.add_stations(session, origin_stations) @@ -57,6 +57,6 @@ async def add_station(station: StationCreate, db: AsyncSession = Depends(get_db) async def add_statios(stations: list[StationCreate], db: AsyncSession = Depends(get_db)): return await cruds.add_stations(db, stations) -@app.get("/stations", response_model=StationGet) +@app.get("/stations", response_model=list[StationGet]) async def get_stations(db: AsyncSession = Depends(get_db)): return await cruds.get_all_stations(db) \ No newline at end of file From 4c80e24bef476f3e75e55c73d06c3d61d86804b6 Mon Sep 17 00:00:00 2001 From: OrusskiyVR Date: Thu, 5 Jun 2025 13:19:14 +0300 Subject: [PATCH 05/11] routing added --- server/data_service/requirements.txt | 1 - server/data_service/src/main.py | 21 ++++ server/route_service/requirements.txt | 45 ++++++++ server/route_service/src/database/schemas.py | 9 ++ server/route_service/src/main.py | 3 + server/route_service/src/utils.py | 107 +++++++++++++++++++ 6 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 server/route_service/src/database/schemas.py create mode 100644 server/route_service/src/utils.py diff --git a/server/data_service/requirements.txt b/server/data_service/requirements.txt index 46b7e34..3c43f62 100644 --- a/server/data_service/requirements.txt +++ b/server/data_service/requirements.txt @@ -36,7 +36,6 @@ typing_extensions==4.13.2 uvicorn==0.34.2 watchfiles==1.0.5 websockets==15.0.1 -psycopg2-binary beautifulsoup4 requests SQLAlchemy \ No newline at end of file diff --git a/server/data_service/src/main.py b/server/data_service/src/main.py index db8dcd3..6c5d33e 100644 --- a/server/data_service/src/main.py +++ b/server/data_service/src/main.py @@ -3,17 +3,38 @@ from sqlalchemy.ext.asyncio import AsyncSession from database.database import get_db from EV_cars import parse_ev_cars +from database.database import get_db + from database.init_db import init_models from contextlib import asynccontextmanager from database import cruds from database.schemas import CarCreate, CarGet + @asynccontextmanager async def lifespan(app: FastAPI): try: print("Starting up") await init_models() + + db_gen = get_db() + session = await anext(db_gen) + try: + + if True: + print("Машины не найдены. Загружаем из API...") + cars = parse_ev_cars() + await cruds.add_cars(session, cars) + await session.commit() + else: + print("Станции уже есть, пропускаем.") + except Exception as e: + await session.rollback() + print(str(e)) + raise e + finally: + await db_gen.aclose() yield finally: print("Shutting down...") diff --git a/server/route_service/requirements.txt b/server/route_service/requirements.txt index e69de29..4c56479 100644 --- a/server/route_service/requirements.txt +++ b/server/route_service/requirements.txt @@ -0,0 +1,45 @@ +psycopg2-binary +geopy +geopandas +shapely +annotated-types==0.7.0 +anyio==4.9.0 +asyncpg==0.30.0 +certifi==2025.4.26 +click==8.1.8 +colorama==0.4.6 +dnspython==2.7.0 +email_validator==2.2.0 +fastapi==0.115.12 +fastapi-cli==0.0.7 +greenlet==3.2.2 +h11==0.16.0 +httpcore==1.0.9 +httptools==0.6.4 +httpx==0.28.1 +idna==3.10 +Jinja2==3.1.6 +markdown-it-py==3.0.0 +MarkupSafe==3.0.2 +mdurl==0.1.2 +pydantic==2.11.5 +pydantic_core==2.33.2 +Pygments==2.19.1 +python-dotenv==1.1.0 +python-multipart==0.0.20 +PyYAML==6.0.2 +rich==14.0.0 +rich-toolkit==0.14.6 +shellingham==1.5.4 +sniffio==1.3.1 +SQLAlchemy==2.0.41 +starlette==0.46.2 +typer==0.15.4 +typing-inspection==0.4.1 +typing_extensions==4.13.2 +uvicorn==0.34.2 +watchfiles==1.0.5 +websockets==15.0.1 +beautifulsoup4 +requests +SQLAlchemy \ No newline at end of file diff --git a/server/route_service/src/database/schemas.py b/server/route_service/src/database/schemas.py new file mode 100644 index 0000000..daeda27 --- /dev/null +++ b/server/route_service/src/database/schemas.py @@ -0,0 +1,9 @@ +from pydantic import BaseModel + +class StationGet(BaseModel): + id: int + name: str + latitude: float + longtitude: float + connection_type: str + power_kw: int | None = None \ No newline at end of file diff --git a/server/route_service/src/main.py b/server/route_service/src/main.py index e69de29..09a09f3 100644 --- a/server/route_service/src/main.py +++ b/server/route_service/src/main.py @@ -0,0 +1,3 @@ +from utils import main + +main() \ No newline at end of file diff --git a/server/route_service/src/utils.py b/server/route_service/src/utils.py new file mode 100644 index 0000000..2be8f8f --- /dev/null +++ b/server/route_service/src/utils.py @@ -0,0 +1,107 @@ +from geopy.distance import geodesic +from dataclasses import dataclass +import requests +from database.schemas import StationGet + +from sqlalchemy import create_engine, MetaData, Table, select +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session +from sqlalchemy import String, Float, Integer + +from sqlalchemy import create_engine, text +from sqlalchemy.orm import sessionmaker + +import geopandas as gpd +from shapely.geometry import LineString, Point, MultiPoint +from shapely.ops import nearest_points + + +@dataclass +class MapPoint: + latitude: float + longitude: float + + def __iter__(self): + yield self.latitude + yield self.longitude + +def get_geo_distance(point_a: MapPoint, point_b: MapPoint): + return geodesic(point_a, point_b).kilometers + +# OSRM maps API +def get_road_distance(point_a: MapPoint, point_b: MapPoint) -> float: + url = f"http://router.project-osrm.org/route/v1/driving/{point_a.longitude},{point_a.latitude};{point_b.longitude},{point_b.latitude}" + response = requests.get(url).json() + print(response) + distance_km = response["routes"][0]["distance"] / 1000 + return distance_km + +def get_close_stations_to_route(point_a: MapPoint, point_b: MapPoint, stations: list[StationGet], radius_search: int = 90_000): + route_points = [point_a, point_b] + + route_line = LineString(route_points) + route_geo_frame = gpd.GeoDataFrame(geometry=[route_line], crs="EPSG:4326") + route_geo_frame = route_geo_frame.to_crs("EPSG:3857") + + buffer_zone = route_geo_frame.buffer(radius_search) + + poi_geo_frame = gpd.GeoDataFrame( + data=[station.model_dump() for station in stations], + geometry=gpd.points_from_xy([station.longtitude for station in stations], + [station.latitude for station in stations]), + crs="EPSG:4326" + ) + poi_geo_frame = poi_geo_frame.to_crs(route_geo_frame.crs) + + poi_in_buffer = poi_geo_frame[poi_geo_frame.within(buffer_zone)] + print(f"Найдено {len(poi_in_buffer)} POI в радиусе от маршрута") + + +def main(): + kiev = MapPoint(50.450441, 30.523550) + lvov = MapPoint(49.839323, 24.029898) + + class Base(DeclarativeBase): + pass + + # Модель Station + class Station(Base): + __tablename__ = "stations" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + name: Mapped[str] = mapped_column(String(100), index=True, nullable=False) # Указал длину String + latitude: Mapped[float] = mapped_column(Float, index=True, nullable=False) + longtitude: Mapped[float] = mapped_column(Float, index=True, nullable=False) # Исправлено опечатку (было longtitude) + connection_type: Mapped[str] = mapped_column(String(50), index=True, nullable=False) + power_kw: Mapped[int] = mapped_column(Integer, nullable=True) # Исправлено __name_pos на Integer + + + + # Подключение к PostgreSQL (используя psycopg2) + DATABASE_URL = "postgresql+psycopg2://fastapi:secret@postgres:5432/fastapi_dev" + engine = create_engine(DATABASE_URL) + + Session = sessionmaker(bind=engine) + session = Session() + + try: + query = select(Station) # Или select(Station.name, Station.latitude) + result = session.execute(query).scalars().all() # Получаем все записи + stations: list[StationGet] = [ + StationGet( + id=station.id, + name=station.name, + latitude=station.latitude, + longtitude=station.longtitude, + connection_type=station.connection_type, + power_kw=station.power_kw + ) + for station in result + ] + get_close_stations_to_route(kiev, lvov, stations) + + + finally: + # Закрываем сессию и соединение + session.close() + engine.dispose() + From 12606d95dc1d99de29aaaf597bcc88e1b0134577 Mon Sep 17 00:00:00 2001 From: Slava Date: Thu, 5 Jun 2025 16:33:56 +0300 Subject: [PATCH 06/11] gateway complete --- server/gateway/requirements.txt | 1 - server/gateway/src/config.py | 6 ++++++ server/gateway/src/main.py | 10 ++------- server/gateway/src/routers/route.py | 27 ++++++++++++------------ server/gateway/src/routers/routing.py | 14 ++++++------ server/gateway/src/routers/user.py | 8 +++---- server/user_service/src/auth/__init__.py | 0 7 files changed, 33 insertions(+), 33 deletions(-) create mode 100644 server/user_service/src/auth/__init__.py diff --git a/server/gateway/requirements.txt b/server/gateway/requirements.txt index 65c38d9..9cb0fb4 100644 --- a/server/gateway/requirements.txt +++ b/server/gateway/requirements.txt @@ -17,7 +17,6 @@ Jinja2==3.1.6 markdown-it-py==3.0.0 MarkupSafe==3.0.2 mdurl==0.1.2 -psycopg2==2.9.10 pydantic==2.11.4 pydantic_core==2.33.2 Pygments==2.19.1 diff --git a/server/gateway/src/config.py b/server/gateway/src/config.py index e69de29..39edf86 100644 --- a/server/gateway/src/config.py +++ b/server/gateway/src/config.py @@ -0,0 +1,6 @@ +SERVICES = { + "user": "http://127.0.0.1:8006", + "station": "http://127.0.0.1:8005", + "route": "http://127.0.0.1:8004", + "data": "http://127.0.0.1:8003", +} \ No newline at end of file diff --git a/server/gateway/src/main.py b/server/gateway/src/main.py index 13904b7..fda4ca8 100644 --- a/server/gateway/src/main.py +++ b/server/gateway/src/main.py @@ -3,7 +3,7 @@ from contextlib import asynccontextmanager import logging import uvicorn -from routers import auth, route, user, station +from routers.routing import include_routes logging.basicConfig( level=logging.INFO, @@ -22,16 +22,10 @@ allow_headers=["*"], ) -# Подключение маршрутов -app.include_router(auth.router, prefix="/auth") -app.include_router(user.router, prefix="/user") -app.include_router(station.router, prefix="/stations") -app.include_router(route.router, prefix="/route") -#app.include_router(data.router, prefix="/data") - @asynccontextmanager async def lifespan(app: FastAPI): logger.info("Gateway server is starting...") + include_routes(app) yield logger.info("Gateway server is shutting down...") diff --git a/server/gateway/src/routers/route.py b/server/gateway/src/routers/route.py index f51661f..ea0adc5 100644 --- a/server/gateway/src/routers/route.py +++ b/server/gateway/src/routers/route.py @@ -1,18 +1,17 @@ -from fastapi import APIRouter -from pydantic import BaseModel +from fastapi import APIRouter, Request +import httpx +from config import SERVICES router = APIRouter() -class RouteRequest(BaseModel): - start: str - end: str - battery_level: int +@router.get("/get_extra_stations") +async def add_car(request: Request): + query_params = str(request.query_params) + async with httpx.AsyncClient() as client: + request_url = f"{SERVICES['route']}/get_path" + if query_params: + request_url += f"?{query_params}" -@router.post("/") -def calculate_route(data: RouteRequest): - return { - "start": data.start, - "end": data.end, - "estimated_time": "2h 30m", - "stations_on_route": [] - } \ No newline at end of file + response = await client.get(request_url) + response.raise_for_status() + return response.json() diff --git a/server/gateway/src/routers/routing.py b/server/gateway/src/routers/routing.py index 262b4b3..416d6d9 100644 --- a/server/gateway/src/routers/routing.py +++ b/server/gateway/src/routers/routing.py @@ -1,9 +1,11 @@ from fastapi import FastAPI -from .auth import router as auth_router -from .user import router as user_router -from .station import router as station_router +from routers.data import router as data_router +from routers.user import router as user_router +from routers.station import router as station_router +from routers.route import router as route_router def include_routes(app: FastAPI): - app.include_router(auth_router) - app.include_router(user_router) - app.include_router(station_router) \ No newline at end of file + app.include_router(data_router, prefix="/data") + app.include_router(user_router, prefix="/user") + app.include_router(station_router, prefix="/station") + app.include_router(route_router, prefix="/route") \ No newline at end of file diff --git a/server/gateway/src/routers/user.py b/server/gateway/src/routers/user.py index cb4c8ad..d82733a 100644 --- a/server/gateway/src/routers/user.py +++ b/server/gateway/src/routers/user.py @@ -8,21 +8,21 @@ async def add_user(request: Request): body = await request.body() async with httpx.AsyncClient() as client: - response = await client.post(f"{SERVICES['auth']}/register", content=body, headers=request.headers) + response = await client.post(f"{SERVICES['user']}/register", content=body, headers=request.headers) return response.json() @router.post("/login") async def login(request: Request): body = await request.body() async with httpx.AsyncClient() as client: - response = await client.post(f"{SERVICES['auth']}/login", content=body, headers=request.headers) + response = await client.post(f"{SERVICES['user']}/login", content=body, headers=request.headers) return response.json() @router.get("/user_by_login") async def get_user_by_login(request: Request): query_params = str(request.query_params) async with httpx.AsyncClient() as client: - request_url = f"{SERVICES['auth']}/user" + request_url = f"{SERVICES['user']}/user" if query_params: request_url += f"?{query_params}" @@ -34,5 +34,5 @@ async def get_user_by_login(request: Request): async def update_user_car(request: Request): body = await request.body() async with httpx.AsyncClient() as client: - response = await client.post(f"{SERVICES['auth']}/user/car", content=body, headers=request.headers) + response = await client.post(f"{SERVICES['user']}/user/car", content=body, headers=request.headers) return response.json() diff --git a/server/user_service/src/auth/__init__.py b/server/user_service/src/auth/__init__.py new file mode 100644 index 0000000..e69de29 From 3545014d30c79b8167cb13450aa2c9edb5a3567d Mon Sep 17 00:00:00 2001 From: Slava Date: Mon, 9 Jun 2025 07:11:38 +0300 Subject: [PATCH 07/11] Final --- Client/.idea/deploymentTargetSelector.xml | 2 +- Client/.idea/gradle.xml | 1 + Client/.idea/misc.xml | 17 +- Client/app/build.gradle.kts | 46 +-- Client/app/src/main/AndroidManifest.xml | 9 +- .../example/evroutes/EVRoutesApplication.kt | 17 ++ .../java/com/example/evroutes/MainActivity.kt | 129 ++++++++- .../evroutes/data/model/LoginRequest.kt | 2 +- .../example/evroutes/data/model/MapModels.kt | 63 ++++ .../evroutes/data/model/RegisterRequest.kt | 5 +- .../evroutes/data/network/AuthApiService.kt | 4 +- .../evroutes/data/network/MapApiService.kt | 17 ++ .../evroutes/data/network/RetrofitClient.kt | 13 +- .../evroutes/data/repository/MapRepository.kt | 68 +++++ .../example/evroutes/ui/auth/AuthViewModel.kt | 2 +- .../example/evroutes/ui/auth/LoginScreen.kt | 5 +- .../com/example/evroutes/ui/map/MapScreen.kt | 249 ++++++++++++++++ .../example/evroutes/ui/map/MapViewModel.kt | 269 ++++++++++++++++++ .../evroutes/ui/map/YandexMapComposable.kt | 58 ++++ .../main/res/xml/network_security_config.xml | 6 + Client/build.gradle.kts | 2 +- Client/gradle/libs.versions.toml | 29 +- Client/settings.gradle.kts | 3 + server/gateway/src/config.py | 8 +- server/gateway/src/main.py | 9 +- server/gateway/src/routers/route.py | 15 +- server/gateway/src/routers/station.py | 2 +- server/route_service/Dockerfile | 2 +- server/route_service/src/database/schemas.py | 12 +- server/route_service/src/main.py | 20 +- server/route_service/src/utils.py | 166 +++++++---- server/station_service/src/database/cruds.py | 4 +- server/station_service/src/database/models.py | 2 +- .../station_service/src/database/schemas.py | 4 +- server/station_service/src/main.py | 61 ++-- server/station_service/src/stations.py | 11 +- 36 files changed, 1148 insertions(+), 184 deletions(-) create mode 100644 Client/app/src/main/java/com/example/evroutes/EVRoutesApplication.kt create mode 100644 Client/app/src/main/java/com/example/evroutes/data/model/MapModels.kt create mode 100644 Client/app/src/main/java/com/example/evroutes/data/network/MapApiService.kt create mode 100644 Client/app/src/main/java/com/example/evroutes/data/repository/MapRepository.kt create mode 100644 Client/app/src/main/java/com/example/evroutes/ui/map/MapScreen.kt create mode 100644 Client/app/src/main/java/com/example/evroutes/ui/map/MapViewModel.kt create mode 100644 Client/app/src/main/java/com/example/evroutes/ui/map/YandexMapComposable.kt create mode 100644 Client/app/src/main/res/xml/network_security_config.xml diff --git a/Client/.idea/deploymentTargetSelector.xml b/Client/.idea/deploymentTargetSelector.xml index 3a82024..3864900 100644 --- a/Client/.idea/deploymentTargetSelector.xml +++ b/Client/.idea/deploymentTargetSelector.xml @@ -4,7 +4,7 @@