diff --git a/back/db.py b/back/db.py index 23d40cf..9c7f1b4 100644 --- a/back/db.py +++ b/back/db.py @@ -1,8 +1,8 @@ from typing import AsyncGenerator -from sqlalchemy import create_engine, select +from sqlalchemy import create_engine, select, MetaData # from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine -from sqlalchemy.orm import sessionmaker, Session +from sqlalchemy.orm import sessionmaker, Session, DeclarativeBase from sqlalchemy.ext.declarative import declarative_base from fastapi import Depends @@ -18,4 +18,18 @@ engine = create_engine( SessionLocal = sessionmaker(bind=engine, autoflush=True, autocommit=False) database = SessionLocal() -Base = declarative_base() \ No newline at end of file +# Base = declarative_base() + + +# add your model's MetaData object here +# for 'autogenerate' support +# in your application's model: + +class Base(DeclarativeBase): + metadata = MetaData(naming_convention={ + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_`%(constraint_name)s`", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s" + }) \ No newline at end of file diff --git a/back/delete_db.py b/back/delete_db.py index ba91297..669ca3b 100644 --- a/back/delete_db.py +++ b/back/delete_db.py @@ -2,4 +2,5 @@ from sqlalchemy import Table, MetaData from .db import engine tbl = Table('UserDatabase', MetaData(), autoload_with=engine) -tbl.drop(engine, checkfirst=False) \ No newline at end of file +tbl.drop(engine, checkfirst=False) +a = input() \ No newline at end of file diff --git a/back/main.py b/back/main.py index b005efd..dea9027 100644 --- a/back/main.py +++ b/back/main.py @@ -19,7 +19,7 @@ import shutil import os from .db import Base, engine, SessionLocal, database -from .service import add_poems_to_db, generate_poem +from .service import add_poems_to_db, generate_poem, check_obsolete from . import schemas, models, utils Base.metadata.create_all(bind=engine) @@ -36,32 +36,37 @@ app.mount("/uploads", StaticFiles(directory = "./uploads")) # # Записываем стихи в базу данных, если их еще нет (запускать только если стихов в базе нет). # add_poems_to_db(database) -@app.get("/api/announcements")#адрес объявлений -def annoncements_list(owner_id: int = None, metro: str = None, category: str = None, booked_by: int = 0): +@app.get("/api/announcements", response_model=List[schemas.Announcement]) #адрес объявлений +def annoncements_list(params_to_sort: schemas.SortAnnouncements): # Считываем данные из Body и отображаем их на странице. # В последствии будем вставлять данные в html-форму - a = database.query(models.Announcement) - b = database.query(models.Announcement) - c = database.query(models.Announcement) - d = database.query(models.Announcement) - e = database.query(models.Announcement) + # Фильтруем по сроку годности + not_expired = check_obsolete(current_date=datetime.date.today()) + # Фильтруем по другим параметрам и делаем пересечение с not_expired + result = not_expired.intersect(get_query_results(params_to_sort)) - if owner_id != None: - b = a.filter(models.Announcement.owner_id == owner_id) - if metro != None: - c = a.filter(models.Announcement.metro == metro) + # a = database.query(models.Announcement) + # b = database.query(models.Announcement) + # c = database.query(models.Announcement) + # d = database.query(models.Announcement) + # e = database.query(models.Announcement) - if category != None: - d = a.filter(models.Announcement.category == category) + # if owner_id != None: + # b = a.filter(models.Announcement.owner_id == owner_id) - if not any([category, owner_id, metro]) and booked_by == -1: - result = a.all() + # if metro != None: + # c = a.filter(models.Announcement.metro == metro) + + # if category != None: + # d = a.filter(models.Announcement.category == category) + + # if not any([category, owner_id, metro]) and booked_by == 0: + # result = a.all() - else: - result = b.intersect(c, d, e).all() - + # else: + # result = b.intersect(c, d, e).all() return {"Success" : True, "list_of_announcements": result, "poem": generate_poem(database)} @@ -179,6 +184,25 @@ async def read_own_items( return [{"Current user name": current_user.name, "Current user surname": current_user.surname}] +# начисляем баллы пользователю +@app.post("/api/user/rating") +def add_points(data: schemas.AddPoints): + user = utils.get_user(data.user_id) + if not user: + raise HTTPException(status_code=404, detail="Item not found") + user.rating += data.rate + return {"Success": True} + + +# получаем данные о баллах пользователя +@app.get("/api/user/rating") +def add_points(user_id: int): + user = utils.get_user(user_id) + if not user: + raise HTTPException(status_code=404, detail="Item not found") + return {"rating": user.rating} + + @app.get("/api/trashbox", response_model=List[schemas.TrashboxResponse]) def get_trashboxes(lat:float, lng:float):#крутая функция для работы с api BASE_URL='https://geointelect2.gate.petersburg.ru'#адрес сайта и мой токин @@ -214,3 +238,8 @@ def get_trashboxes(lat:float, lng:float):#крутая функция для р @app.get("/{rest_of_path:path}") async def react_app(req: Request, rest_of_path: str): return templates.TemplateResponse('index.html', { 'request': req }) + + +@app.post("api/announcement/dispose") +def dispose(despose_data: schemas.DisposeData, current_user: Annotated[schemas.User, Depends(utils.get_current_user)]): + current_user.rating += 60 \ No newline at end of file diff --git a/back/models.py b/back/models.py index ef59272..b1f6455 100644 --- a/back/models.py +++ b/back/models.py @@ -1,12 +1,17 @@ -from sqlalchemy import Column, Integer, String, Boolean, Float, DateTime, Date, ForeignKey +from sqlalchemy import Column, Integer, String, Boolean, Float, DateTime, Date, ForeignKey, Enum, UniqueConstraint from fastapi import Depends from .db import Base, engine from sqlalchemy.orm import relationship -# class User(SQLAlchemyBaseUserTableUUID, Base): -# name = Column(String, nullable=True)#имя пользователя +# импортируем модуль для создания перечислений +import enum +# # класс, необходимый для создания перечисления +# class State(enum.Enum): +# published = 1 +# taken = 2 +# obsolete = 3 class User(Base):#класс пользователя @@ -18,30 +23,34 @@ class User(Base):#класс пользователя name = Column(String, nullable=True)#имя пользователя surname = Column(String)#фамилия пользователя disabled = Column(Boolean, default=False) + rating = Column(Integer, default=0) #баллы пользователя + reg_date = Column(Date) # дата регистрации - items = relationship("Announcement", back_populates="owner") + items = relationship("Announcement", back_populates="users") class Announcement(Base): #класс объявления __tablename__ = "announcements" - id = Column(Integer, primary_key=True, index=True)#айди объявления - owner_id = Column(Integer, ForeignKey("users.id"))#айди создателя объявления + id = Column(Integer, primary_key=True, index=True) # айди объявления + user_id = Column(Integer, ForeignKey("users.id", name="fk_users_id")) # айди создателя объявления name = Column(String) # название объявления - category = Column(String)#категория продукта из объявления - best_by = Column(Date)#срок годности продукта из объявления + category = Column(String) #категория продукта из объявления + best_by = Column(Date) #срок годности продукта из объявления address = Column(String) longtitude = Column(Integer) latitude = Column(Integer) - description = Column(String)#описание продукта в объявлении + description = Column(String) #описание продукта в объявлении src = Column(String, nullable=True) #изображение продукта в объявлении - metro = Column(String)#ближайщее метро от адреса нахождения продукта + metro = Column(String) #ближайщее метро от адреса нахождения продукта trashId = Column(Integer, nullable=True) - booked_by = Column(Integer)#статус бронирования (либо -1, либо айди бронирующего) + booked_by = Column(Integer) #статус бронирования (либо -1, либо айди бронирующего) + # state = Column(Enum(State), default=State.published) # состояние объявления (опубликовано, забронировано, устарело) + obsolete = Column(Boolean, default=False) # состояние объявления (по-умолчанию считаем его актуальным) owner = relationship("User", back_populates="items") -class Trashbox(Base):#класс мусорных баков +class Trashbox(Base): #класс мусорных баков __tablename__ = "trashboxes" id = Column(Integer, primary_key=True, index=True)#айди @@ -49,7 +58,7 @@ class Trashbox(Base):#класс мусорных баков address = Column(String) latitude = Column(Integer) longtitude = Column(Integer) - category = Column(String)#категория продукта из объявления + category = Column(String) #категория продукта из объявления class Poems(Base):#класс поэзии diff --git a/back/schemas.py b/back/schemas.py index 408bebd..14d085e 100644 --- a/back/schemas.py +++ b/back/schemas.py @@ -71,3 +71,21 @@ class TrashboxRequest(TrashboxBase): class DisposeRequest(BaseModel): ann_id: int trashbox: TrashboxRequest + +class SortAnnouncements(BaseModel): + expired: Union[int, None] = False + user_id: Union[int, None] = None + metro: Union[str, None] = None + category: Union[str, None] = None + # booked_by: Union[int, None] = None + +class DisposeData(BaseModel): + ann_id: int # id объявления + trash_id: int # id мусорки + trash_category: str # категория мусора + + +# схема для начисления баллов +class AddPoints(BaseModel): + user_id: int + rate: int \ No newline at end of file diff --git a/back/service.py b/back/service.py index 6022edc..a37db93 100644 --- a/back/service.py +++ b/back/service.py @@ -1,6 +1,9 @@ from sqlalchemy.orm import Session -from .models import Poems +from typing import Annotated, Union +from fastapi import Depends +from . import models, schemas, utils import random +import datetime # Загружаем стихи @@ -31,20 +34,34 @@ def add_poems_to_db(db: Session): def generate_poem(db: Session): # генерируем 1 случайное id и выбираем объект бд с этим id rand_id = random.randint(1, 102) - poem = db.query(Poems).filter(Poems.id == rand_id).first() + poem = db.query(models.Poems).filter(models.Poems.id == rand_id).first() # возвращаем название и текст стихотворения return {"name": poem.poem_name, "text": poem.poem_text, "author":""} # добавить поле author в Poems -# Функция, создающая сессию БД при каждом запросе к нашему API. -# Срабатывает до запуска остальных функций. -# Всегда закрывает сессию при окончании работы с ней -# @app.middleware("http") -# async def db_session_middleware(request: Request, call_next): -# response = Response("Internal server error", status_code=500) -# try: -# request.state.db = SessionLocal() -# response = await call_next(request) -# finally: -# request.state.db.close() -# return response \ No newline at end of file +def get_query_results(db: Annotated[Session, Depends(utils.get_db)], schema: schemas.SortAnnouncements): + """Функция для последовательного применения различных фильтров (через схему SortAnnouncements)""" + res = db.query(models.Announcement) + fields = schema.__dict__ # параметры передоваемой схемы SortAnnouncements (ключи и значения) + for name, filt in fields.items(): + if filt is not None: + d = {name: filt} + res = res.filter_by(**d) + return res.all() + + +def check_obsolete(db: Annotated[Session, Depends(utils.get_db)], current_date: datetime.date): + """ + Возвращаем список объектов базы данных типа Announcement + , удовлетворяющих условию obsolete == True + """ + list_to_return = [] + not_obsolete = database.query(models.Announcement).filter(models.Announcement.obsolete == True).all() + for ann in not_obsolete: + if ann.best_by < current_date: + ann.obsolete = False + else: + list_to_return.append(ann) + + return list_to_return + diff --git a/back/utils.py b/back/utils.py index 175952c..51dbac9 100644 --- a/back/utils.py +++ b/back/utils.py @@ -36,15 +36,15 @@ def get_password_hash(password): return pwd_context.hash(password) -def get_user(db: Session, user_id: int): +def get_user(db: Annotated[Session, Depends(get_db)], user_id: int): user_with_required_id = db.query(models.User).filter(models.User.id == user_id).first() if user_with_required_id: return user_with_required_id return None -def authenticate_user(db: Session, email: str, password: str): - user = get_user(db, user_id) +def authenticate_user(db: Annotated[Session, Depends(get_db)], email: str, password: str): + user = get_user(user_id = user_id) if not user: return False if not verify_password(password, user.hashed_password): @@ -81,7 +81,6 @@ async def get_current_user(db: Annotated[Session, Depends(get_db)], token: Annot if user is None: raise credentials_exception return schemas.User(id=user.id, email=user.email, name=user.name, surname=user.surname, disabled=user.disabled, items=user.items) - # return user async def get_current_active_user( diff --git a/migrations/env.py b/migrations/env.py index d3c1002..f5d66b4 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -17,12 +17,7 @@ config = context.config if config.config_file_name is not None: fileConfig(config.config_file_name) -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata target_metadata = base.Base.metadata -# target_metadata = None # other values from the config, defined by the needs of env.py, # can be acquired: diff --git a/migrations/versions/4e4d30fd58fc_points_added_to_user_model_owner_id_.py b/migrations/versions/4e4d30fd58fc_points_added_to_user_model_owner_id_.py new file mode 100644 index 0000000..1e774be --- /dev/null +++ b/migrations/versions/4e4d30fd58fc_points_added_to_user_model_owner_id_.py @@ -0,0 +1,48 @@ +"""points added to user model; owner_id -> user_id; added state(announcement table) + +Revision ID: 4e4d30fd58fc +Revises: 00529d20660b +Create Date: 2023-08-05 09:38:03.284306 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4e4d30fd58fc' +down_revision = '00529d20660b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('announcements', schema=None) as batch_op: + batch_op.add_column(sa.Column('user_id', sa.Integer(), nullable=True)) + batch_op.add_column(sa.Column('state', sa.Enum('published', 'taken', 'obsolete', name='state'), nullable=True)) + # batch_op.drop_constraint(None, type_='foreignkey') + batch_op.create_foreign_key('fk_users_id', 'users', ['user_id'], ['id']) + batch_op.drop_column('owner_id') + + with op.batch_alter_table('users', schema=None) as batch_op: + batch_op.add_column(sa.Column('rating', sa.Integer(), nullable=True)) + batch_op.add_column(sa.Column('reg_date', sa.Date(), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('users', schema=None) as batch_op: + batch_op.drop_column('reg_date') + batch_op.drop_column('rating') + + with op.batch_alter_table('announcements', schema=None) as batch_op: + batch_op.add_column(sa.Column('owner_id', sa.INTEGER(), nullable=True)) + # batch_op.drop_constraint('fk_users_id', type_='foreignkey') + batch_op.create_foreign_key(None, 'users', ['owner_id'], ['id']) + batch_op.drop_column('state') + batch_op.drop_column('user_id') + + # ### end Alembic commands ### diff --git a/migrations/versions/faecbd04e5eb_state_enum_obsolete_boolean.py b/migrations/versions/faecbd04e5eb_state_enum_obsolete_boolean.py new file mode 100644 index 0000000..da76452 --- /dev/null +++ b/migrations/versions/faecbd04e5eb_state_enum_obsolete_boolean.py @@ -0,0 +1,34 @@ +"""state(enum) -> obsolete(Boolean) + +Revision ID: faecbd04e5eb +Revises: 4e4d30fd58fc +Create Date: 2023-08-05 23:57:31.784676 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'faecbd04e5eb' +down_revision = '4e4d30fd58fc' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('announcements', schema=None) as batch_op: + batch_op.add_column(sa.Column('obsolete', sa.Boolean(), nullable=True)) + batch_op.drop_column('state') + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('announcements', schema=None) as batch_op: + batch_op.add_column(sa.Column('state', sa.VARCHAR(length=9), nullable=True)) + batch_op.drop_column('obsolete') + + # ### end Alembic commands ###