Compare commits

..

No commits in common. "7ecfe6faa45e8f7fcc481d22551cd8d132d1059e" and "7a4b3978a7f9226d0be4c9d50be9671e06308217" have entirely different histories.

29 changed files with 2938 additions and 344 deletions

View File

@ -29,7 +29,4 @@ dist-ssr
uploads/ uploads/
.env .env
poems.txt
poem_pic/
__pycache__ __pycache__

2
.gitignore vendored
View File

@ -30,6 +30,4 @@ uploads/
.env .env
poem_pic/ poem_pic/
poem_pic/
__pycache__ __pycache__

View File

@ -35,5 +35,5 @@ Only docker/podman are required
```sh ```sh
docker build . -t porridger:build docker build . -t porridger:build
docker run --name porridger -p 8000:8000 -v ./sql_app.db:/srv/sql_app.db -v ./poems.txt:/srv/poems.txt -v ./poem_pic:/srv/poem_pic -v uploads:/srv/uploads porridger:build docker run --name porridger -p 8000:8000 -v ./sql_app.db:/srv/sql_app.db -v uploads:/srv/uploads -v poem_pic:/srv/poem_pic porridger:build
``` ```

View File

@ -4,9 +4,8 @@
# path to migration scripts # path to migration scripts
script_location = migrations script_location = migrations
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s # template used to generate migration files
# Uncomment the line below if you want the files to be prepended with date and time # file_template = %%(rev)s_%%(slug)s
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
# sys.path path, will be prepended to sys.path if present. # sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory. # defaults to the current working directory.
@ -49,16 +48,11 @@ prepend_sys_path = .
# version_path_separator = space # version_path_separator = space
version_path_separator = os # Use os.pathsep. Default configuration used for new projects. version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
# set to 'true' to search source files recursively
# in each "version_locations" directory
# new in Alembic version 1.10
# recursive_version_locations = false
# the output encoding used when revision files # the output encoding used when revision files
# are written from script.py.mako # are written from script.py.mako
# output_encoding = utf-8 # output_encoding = utf-8
; sqlalchemy.url = driver://user:pass@localhost/dbname # sqlalchemy.url = driver://user:pass@localhost/dbname
[post_write_hooks] [post_write_hooks]
@ -72,12 +66,6 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
# black.entrypoint = black # black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME # black.options = -l 79 REVISION_SCRIPT_FILENAME
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
# hooks = ruff
# ruff.type = exec
# ruff.executable = %(here)s/.venv/bin/ruff
# ruff.options = --fix REVISION_SCRIPT_FILENAME
# Logging configuration # Logging configuration
[loggers] [loggers]
keys = root,sqlalchemy,alembic keys = root,sqlalchemy,alembic

View File

@ -1,17 +1,18 @@
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy.sql import text, literal_column
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated from typing import Annotated
from fastapi import Depends from fastapi import Depends
from sqlalchemy import select, or_, and_ from . import auth_utils, orm_models, pydantic_schemas
import datetime import datetime
from . import auth_utils, orm_models, pydantic_schemas # Переменные для получения данных о мусорках с внешнего API
# url API
BASE_URL='https://geointelect2.gate.petersburg.ru'#адрес сайта и мой токин
# токен для получения данных
my_token='eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhU1RaZm42bHpTdURYcUttRkg1SzN5UDFhT0FxUkhTNm9OendMUExaTXhFIn0.eyJleHAiOjE3ODM3ODk4NjgsImlhdCI6MTY4OTA5NTQ2OCwianRpIjoiNDUzNjQzZTgtYTkyMi00NTI4LWIzYmMtYWJiYTNmYjkyNTkxIiwiaXNzIjoiaHR0cHM6Ly9rYy5wZXRlcnNidXJnLnJ1L3JlYWxtcy9lZ3MtYXBpIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImJjYjQ2NzljLTU3ZGItNDU5ZC1iNWUxLWRlOGI4Yzg5MTMwMyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLXJlc3QtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6ImM2ZDJiOTZhLWMxNjMtNDAxZS05ZjMzLTI0MmE0NDcxMDY5OCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtZWdzLWFwaSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiJjNmQyYjk2YS1jMTYzLTQwMWUtOWYzMy0yNDJhNDQ3MTA2OTgiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiLQktC70LDQtNC40LzQuNGAINCv0LrQvtCy0LvQtdCyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZTBmYzc2OGRhOTA4MjNiODgwZGQzOGVhMDJjMmQ5NTciLCJnaXZlbl9uYW1lIjoi0JLQu9Cw0LTQuNC80LjRgCIsImZhbWlseV9uYW1lIjoi0K_QutC-0LLQu9C10LIifQ.E2bW0B-c6W5Lj63eP_G8eI453NlDMnW05l11TZT0GSsAtGayXGaolHtWrmI90D5Yxz7v9FGkkCmcUZYy1ywAdO9dDt_XrtFEJWFpG-3csavuMjXmqfQQ9SmPwDw-3toO64NuZVv6qVqoUlPPj57sLx4bLtVbB4pdqgyJYcrDHg7sgwz4d1Z3tAeUfSpum9s5ZfELequfpLoZMXn6CaYZhePaoK-CxeU3KPBPTPOVPKZZ19s7QY10VdkxLULknqf9opdvLs4j8NMimtwoIiHNBFlgQz10Cr7bhDKWugfvSRsICouniIiBJo76wrj5T92s-ztf1FShJuqnQcKE_QLd2A'
# Загружаем стихи # Загружаем стихи
async def add_poems_to_db(async_db: AsyncSession): def add_poems_to_db(db: Session):
f1 = open('poems.txt', encoding='utf-8', mode='r')#открыть фаил для чтения на русском f1 = open('text121.txt', encoding='utf-8', mode='r')#открыть фаил для чтения на русском
for a in range(1, 110): for a in range(1, 110):
f1.seek(0)#перейти к началу f1.seek(0)#перейти к началу
i=0 i=0
@ -35,78 +36,40 @@ async def add_poems_to_db(async_db: AsyncSession):
author += str1 author += str1
poem = orm_models.Poems(title=name, text=stixi, author=author) poem = orm_models.Poems(title=name, text=stixi, author=author)
# В конце каждой итерации добавляем в базу данных # В конце каждой итерации добавляем в базу данных
async_db.add(poem) db.add(poem)
async_db.commit() db.commit()
# db.refresh(poem)
# close the file # close the file
f1.close() f1.close()
async def filter_ann(schema: pydantic_schemas.SortAnnouncements, db: AsyncSession):
def filter_ann(schema: pydantic_schemas.SortAnnouncements, db: Annotated[Session, Depends(auth_utils.get_db)]):
"""Функция для последовательного применения различных фильтров (через схему SortAnnouncements)""" """Функция для последовательного применения различных фильтров (через схему SortAnnouncements)"""
res = db.query(orm_models.Announcement)
fields = schema.__dict__ # параметры передоваемой схемы SortAnnouncements (ключи и значения) fields = schema.__dict__ # параметры передоваемой схемы SortAnnouncements (ключи и значения)
# проходим по названиям фильтров и их значениям # проходим по названиям фильтров и их значениям
# выбираем все строки for name, filt in fields.items():
query = await db.execute(select(orm_models.Announcement))
res = set(query.scalars().all())
for name, filt_val in fields.items():
# res = await db.execute(statement)
# если фильтр задан # если фильтр задан
if filt_val is not None: if filt is not None:
if name == "obsolete": d = {name: filt}
filt_val = bool(filt_val) # фильтруем
filter_query = await db.execute(select(orm_models.Announcement).where(literal_column(f"announcements.{name}") == filt_val)) res = res.filter_by(**d)
filtered = set(filter_query.scalars().all())
res = res.intersection(filtered)
# # отфильтровываем подходящие объявления
# res = await db.execute(
# select(orm_models.Announcement).where(
# ((schema.obsolete == None) | ((schema.obsolete != None) & (orm_models.Announcement.obsolete == schema.obsolete)))
# & ((schema.user_id == None) | ((schema.user_id != None) & (orm_models.Announcement.user_id == schema.user_id)))
# & ((schema.metro == None) | ((schema.metro != None) & (orm_models.Announcement.metro == schema.metro)))
# & ((schema.category == None) | ((schema.category != None) & (orm_models.Announcement.category == schema.category)))
# )
# )
# .where(schema.user_id != None and orm_models.Announcement.user_id == schema.user_id)
# .where(schema.metro != None and orm_models.Announcement.metro == schema.metro)
# .where(schema.category != None and orm_models.Announcement.category == schema.category)
# statement = text("SELECT * FROM announcements "
# "WHERE announcements.obsolete = :obsolete "
# "INTERSECT"
# "SELECT * FROM announcements "
# "WHERE announcements.user_id == :user_id "
# "INTERSECT"
# "SELECT * FROM announcements "
# "WHERE announcements.metro == :metro "
# "INTERSECT"
# "SELECT * FROM announcements "
# "WHERE announcements.category == :category")
# res = await db.execute(statement,
# {"obsolete": schema.obsolete,
# "user_id": schema.user_id,
# "metro": schema.metro,
# "category": schema.category}
# )
# возвращаем все подходящие объявления # возвращаем все подходящие объявления
return res return res.all()
async def check_obsolete(db: AsyncSession, current_date: datetime.date): def check_obsolete(db: Annotated[Session, Depends(auth_utils.get_db)], current_date: datetime.date):
""" """
Функция участвует в процессе обновления поля obsolete у всех объявлений раз в сутки Функция участвует в процессе обновления поля obsolete у всех объявлений раз в сутки
""" """
# обращаемся ко всем объявлениям бд # обращаемся ко всем объявлениям бд
query_announcements = await db.execute(select(orm_models.Announcement)) announcements = db.query(orm_models.Announcement).all()
announcements = query_announcements.scalars().all()
# для каждого объявления # для каждого объявления
for ann in announcements: for ann in announcements:
# если просрочено # если просрочено
if ann.best_by < current_date: if ann.best_by < current_date:
ann.obsolete = True ann.obsolete = True
await db.commit() db.commit()
await db.refresh(ann) # обновляем состояние объекта db.refresh(ann) # обновляем состояние объекта

View File

@ -10,7 +10,6 @@ from fastapi.requests import Request
from typing import Any, Annotated, List, Union from typing import Any, Annotated, List, Union
from starlette.staticfiles import StaticFiles from starlette.staticfiles import StaticFiles
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import select
import requests import requests
from uuid import uuid4 from uuid import uuid4
@ -23,18 +22,15 @@ import pathlib
import shutil import shutil
import os import os
from .db import database
from . import add_poems_and_filters, auth_utils, orm_models, pydantic_schemas from . import add_poems_and_filters, auth_utils, orm_models, pydantic_schemas
from .config import TRASHBOXES_BASE_URL, TRASHBOXES_TOKEN
# создаем приложение Fastapi # создаем приложение Fastapi
app = FastAPI() app = FastAPI()
# Jinja2 - шаблоны # Jinja2 - шаблоны
templates = Jinja2Templates(directory="./front/dist") templates = Jinja2Templates(directory="./front/dist")
# хранение картинок для стихов
app.mount("/poem_pic", StaticFiles(directory = "./poem_pic"))
# создаем эндпоинт для хранения статических файлов # создаем эндпоинт для хранения статических файлов
app.mount("/static", StaticFiles(directory = "./front/dist")) app.mount("/static", StaticFiles(directory = "./front/dist"))
# проверяем, что папка uploads еще не создана # проверяем, что папка uploads еще не создана
@ -43,98 +39,85 @@ if not os.path.exists("./uploads"):
# создаем эндпоинт для хранения файлов пользователя # создаем эндпоинт для хранения файлов пользователя
app.mount("/uploads", StaticFiles(directory = "./uploads")) app.mount("/uploads", StaticFiles(directory = "./uploads"))
# эндпоинт для возвращения согласия в pdf
@app.get("/privacy_policy.pdf")
async def privacy_policy():
return FileResponse("./privacy_policy.pdf")
# получение списка объявлений # получение списка объявлений
@app.get("/api/announcements", response_model=List[pydantic_schemas.Announcement])#адрес объявлений @app.get("/api/announcements", response_model=List[pydantic_schemas.Announcement])#адрес объявлений
async def announcements_list(db: Annotated[Session, Depends(auth_utils.get_session)], obsolete: Union[bool, None] = False, user_id: Union[int, None] = None, def announcements_list(db: Annotated[Session, Depends(auth_utils.get_db)], obsolete: Union[bool, None] = False, user_id: Union[int, None] = None,
metro: Union[str, None] = None,category: Union[str, None] = None): metro: Union[str, None] = None,category: Union[str, None] = None):
# параметры для сортировки (схема pydantic schemas.SortAnnouncements) # параметры для сортировки (схема pydantic schemas.SortAnnouncements)
params_to_sort = pydantic_schemas.SortAnnouncements(obsolete=obsolete, user_id=user_id, metro=metro, category=category) params_to_sort = pydantic_schemas.SortAnnouncements(obsolete=obsolete, user_id=user_id, metro=metro, category=category)
# получаем результат # получаем результат
result = await add_poems_and_filters.filter_ann(db=db, schema=params_to_sort) result = add_poems_and_filters.filter_ann(db=db, schema=params_to_sort)
return result return result
# получаем данные одного объявления # получаем данные одного объявления
@app.get("/api/announcement", response_model=pydantic_schemas.AnnResponce) @app.get("/api/announcement", response_model=pydantic_schemas.AnnResponce)
async def single_announcement(ann_id:int, db: Annotated[Session, Depends(auth_utils.get_session)]): # передаем индекс обявления def single_announcement(ann_id:int, db: Annotated[Session, Depends(auth_utils.get_db)]): # передаем индекс обявления
# Считываем данные из Body и отображаем их на странице. # Считываем данные из Body и отображаем их на странице.
# В последствии будем вставлять данные в html-форму # В последствии будем вставлять данные в html-форму
announcement = await db.get(orm_models.Announcement, ann_id) try:
#announcement = await db.execute(select(orm_models.Announcement)).scalars().all() announcement = db.get(orm_models.Announcement, ann_id)
if not announcement: return announcement
raise HTTPException(status_code=404, detail="Item not found") except:
return announcement return {"Answer" : False} #если неуданый доступ, то сообщаем об этом
# Занести объявление в базу данных # Занести объявление в базу данных
@app.put("/api/announcement") @app.put("/api/announcement")
async def put_in_db(name: Annotated[str, Form()], category: Annotated[str, Form()], bestBy: Annotated[datetime.date, Form()], def put_in_db(name: Annotated[str, Form()], category: Annotated[str, Form()], bestBy: Annotated[datetime.date, Form()],
address: Annotated[str, Form()], longtitude: Annotated[float, Form()], latitude: Annotated[float, Form()], address: Annotated[str, Form()], longtitude: Annotated[float, Form()], latitude: Annotated[float, Form()],
description: Annotated[str, Form()], metro: Annotated[str, Form()], current_user: Annotated[pydantic_schemas.User, Depends(auth_utils.get_current_active_user)], description: Annotated[str, Form()], metro: Annotated[str, Form()], current_user: Annotated[pydantic_schemas.User, Depends(auth_utils.get_current_active_user)],
db: Annotated[Session, Depends(auth_utils.get_session)], src: Union[UploadFile, None] = None, trashId: Annotated[int, Form()] = None): db: Annotated[Session, Depends(auth_utils.get_db)], src: Union[UploadFile, None] = None, trashId: Annotated[int, Form()] = None):
# имя загруженного файла по умолчанию - пустая строка
uploaded_name = ""
# если пользователь загрузил картинку
if src:
# процесс сохранения картинки
f = src.file
f.seek(0, os.SEEK_END)
if f.tell() > 0:
f.seek(0)
destination = pathlib.Path("./uploads/" + str(hash(f)) + pathlib.Path(src.filename).suffix.lower())
with destination.open('wb') as buffer:
shutil.copyfileobj(f, buffer)
# изменяем название директории загруженного файла
uploaded_name = "/uploads/" + destination.name
# создаем объект Announcement
temp_ancmt = orm_models.Announcement(user_id=current_user.id, name=name, category=category, best_by=bestBy,
address=address, longtitude=longtitude, latitude=latitude, description=description, metro=metro,
trashId=trashId, src=uploaded_name, booked_by=0)
try: try:
# имя загруженного файла по умолчанию - пустая строка
uploaded_name = ""
# если пользователь загрузил картинку
if src:
# процесс сохранения картинки
f = src.file
f.seek(0, os.SEEK_END)
if f.tell() > 0:
f.seek(0)
destination = pathlib.Path("./uploads/" + str(hash(f)) + pathlib.Path(src.filename).suffix.lower())
with destination.open('wb') as buffer:
shutil.copyfileobj(f, buffer)
# изменяем название директории загруженного файла
uploaded_name = "/uploads/" + destination.name
# создаем объект Announcement
temp_ancmt = orm_models.Announcement(user_id=current_user.id, name=name, category=category, best_by=bestBy,
address=address, longtitude=longtitude, latitude=latitude, description=description, metro=metro,
trashId=trashId, src=uploaded_name, booked_by=0)
db.add(temp_ancmt) # добавляем в бд db.add(temp_ancmt) # добавляем в бд
await db.commit() # сохраняем изменения db.commit() # сохраняем изменения
await db.refresh(temp_ancmt) # обновляем состояние объекта db.refresh(temp_ancmt) # обновляем состояние объекта
return {"Answer" : True}
return {"Success": True}
except: except:
raise HTTPException(status_code=500, detail="problem with adding object to db") return {"Answer" : False}
# Удалить объявления из базы # Удалить объявления из базы
@app.delete("/api/announcement") #адрес объявления @app.delete("/api/announcement") #адрес объявления
async def delete_from_db(announcement: pydantic_schemas.DelAnnouncement, db: Annotated[Session, Depends(auth_utils.get_session)]): # функция удаления объекта из БД def delete_from_db(announcement: pydantic_schemas.DelAnnouncement, db: Annotated[Session, Depends(auth_utils.get_db)]): # функция удаления объекта из БД
# находим объект с заданным id в бд
#to_delete = db.query(orm_models.Announcement).filter(orm_models.Announcement.id==announcement.id).first()
query = await db.execute(select(orm_models.Announcement).where(orm_models.Announcement.id==announcement.id))
to_delete = query.scalars().first()
if not to_delete:
raise HTTPException(status_code=404, detail="Item not found. Can't delete")
try: try:
await db.delete(to_delete) # удаление из БД # находим объект с заданным id в бд
await db.commit() # сохраняем изменения to_delete = db.query(orm_models.Announcement).filter(orm_models.Announcement.id==announcement.id).first()
db.delete(to_delete) # удаление из БД
return {"Success": True} db.commit() # сохраняем изменения
return {"Answer" : True}
except: except:
raise HTTPException(status_code=500, detail="Problem with adding to database") return {"Answer" : False}
# Забронировать объявление # Забронировать объявление
@app.post("/api/book") @app.post("/api/book")
async def change_book_status(data: pydantic_schemas.Book, current_user: Annotated[pydantic_schemas.User, Depends(auth_utils.get_current_user)], def change_book_status(data: pydantic_schemas.Book, current_user: Annotated[pydantic_schemas.User, Depends(auth_utils.get_current_user)],
db: Annotated[Session, Depends(auth_utils.get_session)]): db: Annotated[Session, Depends(auth_utils.get_db)]):
# Находим объявление по данному id # Находим объявление по данному id
#announcement_to_change = db.query(orm_models.Announcement).filter(orm_models.Announcement.id == data.id).first() announcement_to_change = db.query(orm_models.Announcement).filter(orm_models.Announcement.id == data.id).first()
query = await db.execute(select(orm_models.Announcement).where(orm_models.Announcement.id == data.id))
announcement_to_change = query.scalars().first()
# Проверяем, что объявление с данным id существует # Проверяем, что объявление с данным id существует
if not announcement_to_change: if not announcement_to_change:
raise HTTPException(status_code=404, detail="Item not found") raise HTTPException(status_code=404, detail="Item not found")
@ -145,28 +128,25 @@ async def change_book_status(data: pydantic_schemas.Book, current_user: Annotate
# Инкрементируем поле booked_by на 1 # Инкрементируем поле booked_by на 1
announcement_to_change.booked_by += 1 announcement_to_change.booked_by += 1
# фиксируем изменения в бд # фиксируем изменения в бд
await db.commit() db.commit()
await db.refresh(announcement_to_change) db.refresh(announcement_to_change)
return {"Success": True} return {"Success": True}
# reginstration # reginstration
@app.post("/api/signup") @app.post("/api/signup")
async def create_user(nickname: Annotated[str, Form()], password: Annotated[str, Form()], db: Annotated[Session, Depends(auth_utils.get_session)], def create_user(nickname: Annotated[str, Form()], password: Annotated[str, Form()], db: Annotated[Session, Depends(auth_utils.get_db)],
name: Annotated[str, Form()]=None, surname: Annotated[str, Form()]=None, avatar: Annotated[UploadFile, Form()]=None): name: Annotated[str, Form()]=None, surname: Annotated[str, Form()]=None, avatar: Annotated[UploadFile, Form()]=None):
# проверяем, что юзера с введенным никнеймом не существует в бд # проверяем, что юзера с введенным никнеймом не существует в бд
#if db.query(orm_models.User).filter(orm_models.User.nickname == nickname).first() == None: if db.query(orm_models.User).filter(orm_models.User.nickname == nickname).first() == None:
query_user = await db.execute(select(orm_models.User).where(orm_models.User.nickname == nickname))
user_with_entered_nick = query_user.scalars().first()
if user_with_entered_nick == None:
# создаем нового юзера # создаем нового юзера
new_user = orm_models.User(nickname=nickname, hashed_password=auth_utils.get_password_hash(password), new_user = orm_models.User(nickname=nickname, hashed_password=auth_utils.get_password_hash(password),
name=name, surname=surname, reg_date=datetime.date.today()) name=name, surname=surname, reg_date=datetime.date.today())
# добавляем в бд # добавляем в бд
db.add(new_user) db.add(new_user)
await db.commit() db.commit()
await db.refresh(new_user) # обновляем состояние объекта db.refresh(new_user) # обновляем состояние объекта
return {"Success": True} return {"Success": True}
return {"Success": False, "Message": "Пользователь с таким email уже зарегестрирован"} return {"Success": False, "Message": "Пользователь с таким email уже зарегестрирован"}
@ -174,10 +154,10 @@ async def create_user(nickname: Annotated[str, Form()], password: Annotated[str,
# функция для генерации токена после успешного входа пользователя # функция для генерации токена после успешного входа пользователя
@app.post("/api/token", response_model=pydantic_schemas.Token) @app.post("/api/token", response_model=pydantic_schemas.Token)
async def login_for_access_token( async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()], db: Annotated[Session, Depends(auth_utils.get_session)] form_data: Annotated[OAuth2PasswordRequestForm, Depends()], db: Annotated[Session, Depends(auth_utils.get_db)]
): ):
# пробуем найти юзера в бд по введенным паролю и никнейму # пробуем найти юзера в бд по введенным паролю и никнейму
user = await auth_utils.authenticate_user(db, form_data.username, form_data.password) user = auth_utils.authenticate_user(db, form_data.username, form_data.password)
# если не нашли - кидаем ошибку # если не нашли - кидаем ошибку
if not user: if not user:
raise HTTPException( raise HTTPException(
@ -196,70 +176,53 @@ async def login_for_access_token(
# получаем данные успешно вошедшего пользователя # получаем данные успешно вошедшего пользователя
@app.get("/api/users/me", response_model=pydantic_schemas.User) # @app.get("/api/users/me", response_model=pydantic_schemas.User) #
def read_users_me(current_user: Annotated[pydantic_schemas.User, Depends(auth_utils.get_current_active_user)]): async def read_users_me(current_user: Annotated[pydantic_schemas.User, Depends(auth_utils.get_current_active_user)]):
return current_user return current_user
# изменяем рейтинг пользователя # изменяем рейтинг пользователя
@app.post("/api/user/rating") @app.post("/api/user/rating")
async def add_points(data: pydantic_schemas.AddRating, current_user: Annotated[pydantic_schemas.User, Depends(auth_utils.get_current_user)], db: Annotated[Session, Depends(auth_utils.get_session)]): def add_points(data: pydantic_schemas.AddRating, current_user: Annotated[pydantic_schemas.User, Depends(auth_utils.get_current_user)], db: Annotated[Session, Depends(auth_utils.get_db)]):
# проверяем, # проверяем,
if current_user.id != data.user_id: if current_user.id != data.user_id:
user = await auth_utils.get_user_by_id(db, data.user_id) user = auth_utils.get_user_by_id(db, data.user_id)
if not user: if not user:
raise HTTPException(status_code=404, detail="Item not found") raise HTTPException(status_code=404, detail="Item not found")
user.rating = (user.rating*user.num_of_ratings + data.rate)/(user.num_of_ratings + 1) user.rating = (user.rating*user.num_of_ratings + data.rate)/(user.num_of_ratings + 1)
user.num_of_ratings += 1 user.num_of_ratings += 1
await db.commit() db.commit()
await db.refresh(user) # обновляем состояние объекта db.refresh(user) # обновляем состояние объекта
return {"Success": True} return {"Success": True}
# получаем рейтинг пользователя # получаем рейтинг пользователя
@app.get("/api/user/rating") @app.get("/api/user/rating")
async def add_points(user_id: int, db: Annotated[Session, Depends(auth_utils.get_session)]): def add_points(user_id: int, db: Annotated[Session, Depends(auth_utils.get_db)]):
user = await auth_utils.get_user_by_id(db, user_id=user_id) user = auth_utils.get_user_by_id(db, user_id=user_id)
if not user: if not user:
raise HTTPException(status_code=404, detail="Item not found") raise HTTPException(status_code=404, detail="Item not found")
return {"rating": user.rating} return {"rating": user.rating}
# Отправляем стихи # Отправляем стихи
@app.get("/api/user/poem", response_model=pydantic_schemas.Poem) @app.get("/api/user/poem", response_model=pydantic_schemas.Poem) # пока не работает
async def poems_to_front(db: Annotated[Session, Depends(auth_utils.get_session)]): def poems_to_front(db: Annotated[Session, Depends(auth_utils.get_db)]): # db: Annotated[Session, Depends(utils.get_db)]
#num_of_poems = db.query(orm_models.Poems).count() # определяем кол-во стихов в бд num_of_poems = db.query(orm_models.Poems).count() # определяем кол-во стихов в бд
query = await db.execute(select(orm_models.Poems)) # определяем кол-во стихов в бд
num_of_poems = len(query.scalars().all())
# если стихов в бд нет
if num_of_poems < 1: if num_of_poems < 1:
await add_poems_and_filters.add_poems_to_db(db) # добавляем поэмы в базу данных add_poems_and_filters.add_poems_to_db(database) # добавляем поэмы в базу данных
# после добавления стихов снова определяем кол-во стихов в бд
query = await db.execute(select(orm_models.Poems))
num_of_poems = len(query.scalars().all())
rand_id = random.randint(1, num_of_poems) # генерируем номер стихотворения rand_id = random.randint(1, num_of_poems) # генерируем номер стихотворения
#poem = db.query(orm_models.Poems).filter(orm_models.Poems.id == rand_id).first() # находим стих в бд poem = db.query(orm_models.Poems).filter(orm_models.Poems.id == rand_id).first() # находим стих в бд
query_poem = await db.execute(select(orm_models.Poems).where(orm_models.Poems.id == rand_id)) # находим стих в бд
poem = query_poem.scalars().first()
if not poem: if not poem:
raise HTTPException(status_code=404, detail="Poem not found") raise HTTPException(status_code=404, detail="Poem not found")
return poem return poem
trashboxes_category = {
"PORRIDGE": ["Опасные отходы", "Иное"],
"conspects": ["Бумага"],
"milk": ["Стекло", "Тетра Пак", "Иное"],
"bred": ["Пластик", "Иное"],
"wathing": ["Пластик", "Опасные отходы", "Иное"],
"cloth": ["Одежда"],
"fruits_vegatables": ["Иное"],
"other_things": ["Металл", "Бумага", "Стекло", "Иное", "Тетра Пак", "Батарейки", "Крышечки", "Шины",
"Опасные отходы", "Лампочки", "Пластик"]
}
@app.get("/api/trashbox", response_model=List[pydantic_schemas.TrashboxResponse]) @app.get("/api/trashbox", response_model=List[pydantic_schemas.TrashboxResponse])
async def get_trashboxes(data: pydantic_schemas.TrashboxRequest = Depends()): #крутая функция для работы с api def get_trashboxes(data: pydantic_schemas.TrashboxRequest = Depends()):#крутая функция для работы с api
# json, передаваемый стороннему API # json, передаваемый стороннему API
head = {'Authorization': 'Bearer ' + TRASHBOXES_TOKEN} BASE_URL= "https://geointelect2.gate.petersburg.ru"
my_token="eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhU1RaZm42bHpTdURYcUttRkg1SzN5UDFhT0FxUkhTNm9OendMUExaTXhFIn0.eyJleHAiOjE3ODYyMjUzMzMsImlhdCI6MTY5MTUzMDkzMywianRpIjoiYjU0MmU3MTQtYzJkMS00NTY2LWJkY2MtYmQ5NzA0ODY1ZjgzIiwiaXNzIjoiaHR0cHM6Ly9rYy5wZXRlcnNidXJnLnJ1L3JlYWxtcy9lZ3MtYXBpIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImJjYjQ2NzljLTU3ZGItNDU5ZC1iNWUxLWRlOGI4Yzg5MTMwMyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLXJlc3QtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjJhOTgwMzUyLTY1M2QtNGZlZC1iMDI1LWQ1N2U0NDRjZmM3NiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtZWdzLWFwaSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiIyYTk4MDM1Mi02NTNkLTRmZWQtYjAyNS1kNTdlNDQ0Y2ZjNzYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiLQktC70LDQtNC40LzQuNGAINCv0LrQvtCy0LvQtdCyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZTBmYzc2OGRhOTA4MjNiODgwZGQzOGVhMDJjMmQ5NTciLCJnaXZlbl9uYW1lIjoi0JLQu9Cw0LTQuNC80LjRgCIsImZhbWlseV9uYW1lIjoi0K_QutC-0LLQu9C10LIifQ.FTKiC1hpWcOkmSW9QZpC-RY7Ko50jw1mDMfXIWYxlQ-zehLm2CLmOnHvYoOoI39k2OzeCIAB9ZdRrrGZc6G9Z1eFELUjNGEqKxSC1Phj9ATemKgbOKEttk-OGc-rFr9VPA8_SnfvLts6wTI2YK33YBIxCF5nCbnr4Qj3LeEQ0d6Hy8PO4ATrBF5EOeuAZRprvIEjXe_f8N9ONKckCPB-xFB4P2pZlVXGoCNoewGEcY3zXH4khezN6zcVr6tpc6G8dBv9EqT_v92IDSg-aXQk6ysA0cO0-6x5w1-_qU0iHGIAPsLNV9IKBoFbjc0JH6cWabldPRH12NP1trvYfqKDGQ"
head = {'Authorization': 'Bearer {}'.format(my_token)}
# Данные пользователя (местоположение, количество мусорок, которое пользователь хочет видеть) # Данные пользователя (местоположение, количество мусорок, которое пользователь хочет видеть)
my_data={ my_data={
'x' : f"{data.Lng}", 'x' : f"{data.Lng}",
@ -267,12 +230,27 @@ async def get_trashboxes(data: pydantic_schemas.TrashboxRequest = Depends()): #
'limit' : '1' 'limit' : '1'
} }
# Перевод категории с фронта на категорию с сайта # Перевод категории с фронта на категорию с сайта
list_of_category = trashboxes_category[data.Category] match data.Category:
case "PORRIDGE":
list_of_category = ["Опасные отходы", "Иное"]
case "conspects":
list_of_category = ["Бумага"]
case "milk":
list_of_category = ["Стекло", "Тетра Пак", "Иное"]
case "bred":
list_of_category = ["Пластик", "Иное"]
case "wathing":
list_of_category = ["Пластик", "Опасные отходы", "Иное"]
case "cloth":
list_of_category = ["Одежда"]
case "fruits_vegatables":
list_of_category = ["Иное"]
case "other_things":
list_of_category = ["Металл", "Бумага", "Стекло", "Иное", "Тетра Пак", "Батарейки", "Крышечки", "Шины",
"Опасные отходы", "Лампочки", "Пластик"]
# Получение ответа от стороннего апи # Получение ответа от стороннего апи
response = requests.post(TRASHBOXES_BASE_URL + "/nearest_recycling/get", headers=head, data=my_data, timeout=10) response = requests.post(f"{BASE_URL}/nearest_recycling/get", headers=head, data=my_data, timeout=10)
infos = response.json() infos = response.json()
# Чтение ответа # Чтение ответа
trashboxes = [] trashboxes = []
for trashbox in infos["results"]: for trashbox in infos["results"]:
@ -299,10 +277,10 @@ async def react_app(req: Request, rest_of_path: str):
@app.post("/api/announcement/dispose") @app.post("/api/announcement/dispose")
async def dispose(data: pydantic_schemas.DisposeRequest, current_user_schema: Annotated[pydantic_schemas.User, Depends(auth_utils.get_current_user)], def dispose(data: pydantic_schemas.DisposeRequest, current_user_schema: Annotated[pydantic_schemas.User, Depends(auth_utils.get_current_user)],
db: Annotated[Session, Depends(auth_utils.get_session)]): db: Annotated[Session, Depends(auth_utils.get_db)]):
# Находим в бд текущего юзера # Находим в бд текущего юзера
current_user = await auth_utils.get_user_by_id(db, current_user_schema.id) current_user = auth_utils.get_user_by_id(db, current_user_schema.id)
# Начисляем баллы пользователю за утилизацию # Начисляем баллы пользователю за утилизацию
current_user.points += 60 current_user.points += 60
# В полученном json переходим к данным мусорки # В полученном json переходим к данным мусорки
@ -314,14 +292,12 @@ async def dispose(data: pydantic_schemas.DisposeRequest, current_user_schema: An
db.add(new_trashox) db.add(new_trashox)
# в соответствии с логикой api, после утилизации объявление пользователя удаляется # в соответствии с логикой api, после утилизации объявление пользователя удаляется
# находим объявление с айди data.ann_id # находим объявление с айди data.ann_id
#ann_to_del = db.query(orm_models.Announcement).filter(orm_models.Announcement.id == data.ann_id).first() # ann_to_del = db.query(orm_models.Announcement).filter(orm_models.Announcement.id == data.ann_id).first() # находим стих в бд
query_ann = await db.execute(select(orm_models.Announcement).where(orm_models.Announcement.id == data.ann_id)) # находим объявление в бд
ann_to_del = query_ann.scalars().first()
if not ann_to_del: if not ann_to_del:
raise HTTPException(status_code=404, detail="Announcement not found") raise HTTPException(status_code=404, detail="Announcement not found")
# удаляем объявление из бд # удаляем объявление из бд
await db.delete(ann_to_del) db.delete(ann_to_del)
await db.commit() db.commit()
await db.refresh(new_trashox) # обновляем состояние объекта db.refresh(new_trashox) # обновляем состояние объекта
return {"Success": True} return {"Success": True}

View File

@ -5,22 +5,25 @@ from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt from jose import JWTError, jwt
from passlib.context import CryptContext from passlib.context import CryptContext
from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy.ext.asyncio import AsyncSession
from .db import SessionLocal from .db import database
from . import orm_models, pydantic_schemas from . import orm_models, pydantic_schemas
from .config import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES
SECRET_KEY = "651a52941cf5de14d48ef5d7af115709"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 1440
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token")
async def get_session() -> AsyncSession: def get_db():
async with SessionLocal() as session: db = database
yield session try:
yield db
finally:
db.close()
def verify_password(plain_password, hashed_password): def verify_password(plain_password, hashed_password):
@ -31,24 +34,22 @@ def get_password_hash(password):
return pwd_context.hash(password) return pwd_context.hash(password)
async def get_user_by_nickname(db: Annotated[AsyncSession, Depends(get_session)], nickname: str): def get_user_by_nickname(db: Annotated[Session, Depends(get_db)], nickname: str):
query = await db.execute(select(orm_models.User).where(orm_models.User.nickname == nickname)) user_with_required_id = db.query(orm_models.User).filter(orm_models.User.nickname == nickname).first()
user_with_required_nickname = query.scalars().first() if user_with_required_id:
if user_with_required_nickname: return user_with_required_id
return user_with_required_nickname
return None return None
async def get_user_by_id(db: Annotated[AsyncSession, Depends(get_session)], user_id: int): def get_user_by_id(db: Annotated[Session, Depends(get_db)], user_id: int):
query = await db.execute(select(orm_models.User).where(orm_models.User.id == user_id)) user_with_required_id = db.query(orm_models.User).filter(orm_models.User.id == user_id).first()
user_with_required_id = query.scalars().first()
if user_with_required_id: if user_with_required_id:
return user_with_required_id return user_with_required_id
return None return None
async def authenticate_user(db: Annotated[AsyncSession, Depends(get_session)], nickname: str, password: str): def authenticate_user(db: Annotated[Session, Depends(get_db)], nickname: str, password: str):
user = await get_user_by_nickname(db=db, nickname=nickname) user = get_user_by_nickname(db=db, nickname=nickname)
if not user: if not user:
return False return False
if not verify_password(password, user.hashed_password): if not verify_password(password, user.hashed_password):
@ -67,7 +68,7 @@ def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None
return encoded_jwt return encoded_jwt
async def get_current_user(db: Annotated[AsyncSession, Depends(get_session)], token: Annotated[str, Depends(oauth2_scheme)]): async def get_current_user(db: Annotated[Session, Depends(get_db)], token: Annotated[str, Depends(oauth2_scheme)]):
credentials_exception = HTTPException( credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials", detail="Could not validate credentials",
@ -81,14 +82,14 @@ async def get_current_user(db: Annotated[AsyncSession, Depends(get_session)], to
token_data = pydantic_schemas.TokenData(user_id=user_id) token_data = pydantic_schemas.TokenData(user_id=user_id)
except JWTError: except JWTError:
raise credentials_exception raise credentials_exception
user = await get_user_by_id(db, user_id=token_data.user_id) user = get_user_by_id(db, user_id=token_data.user_id)
if user is None: if user is None:
raise credentials_exception raise credentials_exception
return pydantic_schemas.User(id=user.id, nickname=user.nickname, name=user.name, surname=user.surname, return pydantic_schemas.User(id=user.id, nickname=user.nickname, name=user.name, surname=user.surname,
disabled=user.disabled, items=user.announcements, reg_date=user.reg_date, points=user.points) disabled=user.disabled, items=user.announcements, reg_date=user.reg_date, points=user.points)
def get_current_active_user( async def get_current_active_user(
current_user: Annotated[pydantic_schemas.User, Depends(get_current_user)] current_user: Annotated[pydantic_schemas.User, Depends(get_current_user)]
): ):
if current_user.disabled: if current_user.disabled:

View File

@ -1,13 +0,0 @@
import os
from dotenv import load_dotenv
load_dotenv('.env')
TRASHBOXES_TOKEN = os.environ.get("TRASHBOXES_TOKEN")
TRASHBOXES_BASE_URL = os.environ.get("TRASHBOXES_BASE_URL")
SECRET_KEY = os.environ.get("SECRET_KEY")
ALGORITHM = os.environ.get("ALGORITHM")
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.environ.get("ACCESS_TOKEN_EXPIRE_MINUTES"))
SQLALCHEMY_DATABASE_URL = os.environ.get("SQLALCHEMY_DATABASE_URL")

View File

@ -1,20 +1,32 @@
from asyncio import current_task from typing import AsyncGenerator
from sqlalchemy.ext.asyncio import AsyncSession, async_scoped_session, create_async_engine
from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine, MetaData
# from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from .config import SQLALCHEMY_DATABASE_URL
engine = create_async_engine(SQLALCHEMY_DATABASE_URL) SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
SessionLocal = sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False) engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
async_session = SessionLocal() SessionLocal = sessionmaker(bind=engine, autoflush=True, autocommit=False)
# async_session = async_scoped_session(SessionLocal, scopefunc=current_task)
database = SessionLocal()
Base = declarative_base() Base = declarative_base()
# Создаем таблицы # # add your model's MetaData object here
async def init_models(): # # for 'autogenerate' support
async with engine.begin() as conn: # # in your application's model:
await conn.run_sync(Base.metadata.create_all)
# 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"
# })

View File

@ -1,6 +0,0 @@
from sqlalchemy import Table, MetaData, text
from .db import engine, Base
tbl = Table('Poems', MetaData(), autoload_with=engine)
tbl.drop(engine, checkfirst=False)
a = input()

View File

@ -3,8 +3,6 @@ import uvicorn
from .api import app as app_fastapi from .api import app as app_fastapi
from .scheduler import app as app_rocketry from .scheduler import app as app_rocketry
from .db import init_models
class Server(uvicorn.Server): class Server(uvicorn.Server):
"""Customized uvicorn.Server """Customized uvicorn.Server
@ -18,9 +16,6 @@ class Server(uvicorn.Server):
async def main(): async def main():
"Run scheduler and the API" "Run scheduler and the API"
await init_models()
server = Server(config=uvicorn.Config(app_fastapi, workers=1, loop="asyncio", host="0.0.0.0")) server = Server(config=uvicorn.Config(app_fastapi, workers=1, loop="asyncio", host="0.0.0.0"))
api = asyncio.create_task(server.serve()) api = asyncio.create_task(server.serve())

View File

@ -1,7 +1,6 @@
from sqlalchemy import Column, Integer, String, Boolean, Float, Date, ForeignKey from sqlalchemy import Column, Integer, String, Boolean, Float, Date, ForeignKey
from sqlalchemy.orm import relationship
from .db import Base, engine from .db import Base, engine
from sqlalchemy.orm import relationship
class User(Base):#класс пользователя class User(Base):#класс пользователя
@ -18,8 +17,8 @@ class User(Base):#класс пользователя
num_of_ratings = Column(Integer, default=0) # количество оценок (т.е. то, сколько раз другие пользователи оценили текущего) num_of_ratings = Column(Integer, default=0) # количество оценок (т.е. то, сколько раз другие пользователи оценили текущего)
reg_date = Column(Date) # дата регистрации reg_date = Column(Date) # дата регистрации
announcements = relationship("Announcement", back_populates="user", lazy='selectin') announcements = relationship("Announcement", back_populates="user")
trashboxes_chosen = relationship("Trashbox", back_populates="user", lazy='selectin') trashboxes_chosen = relationship("Trashbox", back_populates="user")
class Announcement(Base): #класс объявления class Announcement(Base): #класс объявления
__tablename__ = "announcements" __tablename__ = "announcements"
@ -67,3 +66,24 @@ class Poems(Base):#класс поэзии
author = Column(String) # автор стихотворения author = Column(String) # автор стихотворения
# Создаем описанные выше таблицы
Base.metadata.create_all(bind=engine)
# from typing import AsyncGenerator
# from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
# from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
# # This function can be called during the initialization of the FastAPI app.
# async def create_db_and_tables():
# async with engine.begin() as conn:
# await conn.run_sync(Base.metadata.create_all)
# async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
# async with async_session_maker() as session:
# yield session
# async def get_user_db(session: AsyncSession = Depends(get_async_session)):
# yield SQLAlchemyUserDatabase(session, User)

View File

@ -2,7 +2,7 @@ from . import add_poems_and_filters
from rocketry import Rocketry from rocketry import Rocketry
from rocketry.conds import daily from rocketry.conds import daily
import datetime import datetime
from .db import async_session from .db import database
app = Rocketry(execution="async") app = Rocketry(execution="async")
@ -10,5 +10,5 @@ app = Rocketry(execution="async")
@app.task('daily') @app.task('daily')
async def daily_check(): async def daily_check():
# Фильтруем по сроку годности # Фильтруем по сроку годности
await add_poems_and_filters.check_obsolete(async_session, current_date=datetime.date.today()) add_poems_and_filters.check_obsolete(database, current_date=datetime.date.today())

2706
back/text121.txt Normal file

File diff suppressed because it is too large Load Diff

2
back/unimportant.env Normal file
View File

@ -0,0 +1,2 @@
TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhU1RaZm42bHpTdURYcUttRkg1SzN5UDFhT0FxUkhTNm9OendMUExaTXhFIn0.eyJleHAiOjE3ODYyMjUzMzMsImlhdCI6MTY5MTUzMDkzMywianRpIjoiYjU0MmU3MTQtYzJkMS00NTY2LWJkY2MtYmQ5NzA0ODY1ZjgzIiwiaXNzIjoiaHR0cHM6Ly9rYy5wZXRlcnNidXJnLnJ1L3JlYWxtcy9lZ3MtYXBpIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImJjYjQ2NzljLTU3ZGItNDU5ZC1iNWUxLWRlOGI4Yzg5MTMwMyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLXJlc3QtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjJhOTgwMzUyLTY1M2QtNGZlZC1iMDI1LWQ1N2U0NDRjZmM3NiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtZWdzLWFwaSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiIyYTk4MDM1Mi02NTNkLTRmZWQtYjAyNS1kNTdlNDQ0Y2ZjNzYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiLQktC70LDQtNC40LzQuNGAINCv0LrQvtCy0LvQtdCyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZTBmYzc2OGRhOTA4MjNiODgwZGQzOGVhMDJjMmQ5NTciLCJnaXZlbl9uYW1lIjoi0JLQu9Cw0LTQuNC80LjRgCIsImZhbWlseV9uYW1lIjoi0K_QutC-0LLQu9C10LIifQ.FTKiC1hpWcOkmSW9QZpC-RY7Ko50jw1mDMfXIWYxlQ-zehLm2CLmOnHvYoOoI39k2OzeCIAB9ZdRrrGZc6G9Z1eFELUjNGEqKxSC1Phj9ATemKgbOKEttk-OGc-rFr9VPA8_SnfvLts6wTI2YK33YBIxCF5nCbnr4Qj3LeEQ0d6Hy8PO4ATrBF5EOeuAZRprvIEjXe_f8N9ONKckCPB-xFB4P2pZlVXGoCNoewGEcY3zXH4khezN6zcVr6tpc6G8dBv9EqT_v92IDSg-aXQk6ysA0cO0-6x5w1-_qU0iHGIAPsLNV9IKBoFbjc0JH6cWabldPRH12NP1trvYfqKDGQ"
DOMAIN = "https://geointelect2.gate.petersburg.ru"

View File

@ -6,7 +6,7 @@ const composePutAnnouncementURL = () => (
) )
const processPutAnnouncement = (data: PutAnnouncementResponse): PutAnnouncement => { const processPutAnnouncement = (data: PutAnnouncementResponse): PutAnnouncement => {
return data.Success return data.Answer
} }
export { composePutAnnouncementURL, processPutAnnouncement } export { composePutAnnouncementURL, processPutAnnouncement }

View File

@ -1,12 +1,12 @@
import { isObject } from '../../utils/types' import { isObject } from '../../utils/types'
type PutAnnouncementResponse = { type PutAnnouncementResponse = {
Success: boolean, Answer: boolean,
} }
const isPutAnnouncementResponse = (obj: unknown): obj is PutAnnouncementResponse => ( const isPutAnnouncementResponse = (obj: unknown): obj is PutAnnouncementResponse => (
isObject(obj, { isObject(obj, {
'Success': 'boolean', 'Answer': 'boolean',
}) })
) )

View File

@ -6,11 +6,11 @@ const composeRemoveAnnouncementURL = () => (
) )
function processRemoveAnnouncement(data: RemoveAnnouncementResponse): RemoveAnnouncement { function processRemoveAnnouncement(data: RemoveAnnouncementResponse): RemoveAnnouncement {
if (!data.Success) { if (!data.Answer) {
throw new Error('Не удалось закрыть объявление') throw new Error('Не удалось закрыть объявление')
} }
return data.Success return data.Answer
} }
export { composeRemoveAnnouncementURL, processRemoveAnnouncement } export { composeRemoveAnnouncementURL, processRemoveAnnouncement }

View File

@ -1,12 +1,12 @@
import { isObject } from '../../utils/types' import { isObject } from '../../utils/types'
type RemoveAnnouncementResponse = { type RemoveAnnouncementResponse = {
Success: boolean, Answer: boolean,
} }
const isRemoveAnnouncementResponse = (obj: unknown): obj is RemoveAnnouncementResponse => ( const isRemoveAnnouncementResponse = (obj: unknown): obj is RemoveAnnouncementResponse => (
isObject(obj, { isObject(obj, {
'Success': 'boolean', 'Answer': 'boolean',
}) })
) )

View File

@ -14,8 +14,8 @@ function useAddAnnouncement() {
processPutAnnouncement processPutAnnouncement
) )
async function handleAdd(formData: FormData) { function handleAdd(formData: FormData) {
await doSend({}, { void doSend({}, {
body: formData, body: formData,
}) })
} }

View File

@ -15,8 +15,8 @@ function useBook() {
processBook, processBook,
) )
const handleBook = useCallback(async (id: number) => { const handleBook = useCallback((id: number) => {
await doSend({}, { void doSend({}, {
body: JSON.stringify({ body: JSON.stringify({
id, id,
}), }),

View File

@ -12,7 +12,7 @@ function useSendButtonCaption(
const [title, setTitle] = useState(initial) const [title, setTitle] = useState(initial)
const update = useCallback(<T extends NonNullable<unknown>>(data: T | null | undefined) => { const update = useCallback(<T extends NonNullable<unknown>>(data: T | null | undefined) => {
if (data !== undefined && data !== null) { // not loading or error if (data !== undefined) { // not loading
setCaption(result) setCaption(result)
setTitle('Отправить ещё раз') setTitle('Отправить ещё раз')

View File

@ -1 +1 @@
Generic single-database configuration with an async dbapi. Generic single-database configuration.

View File

@ -1,12 +1,11 @@
import asyncio
from logging.config import fileConfig from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool from sqlalchemy import pool
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config
from alembic import context from alembic import context
from back import auxiliary_for_alembic, db from back import auxiliary_for_alembic, db
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
@ -18,10 +17,6 @@ config = context.config
if config.config_file_name is not None: if config.config_file_name is not None:
fileConfig(config.config_file_name) 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 = auxiliary_for_alembic.Base.metadata target_metadata = auxiliary_for_alembic.Base.metadata
# other values from the config, defined by the needs of env.py, # other values from the config, defined by the needs of env.py,
@ -30,7 +25,7 @@ target_metadata = auxiliary_for_alembic.Base.metadata
# ... etc. # ... etc.
def run_migrations_offline() -> None: def run_migrations_offline():
"""Run migrations in 'offline' mode. """Run migrations in 'offline' mode.
This configures the context with just a URL This configures the context with just a URL
@ -42,49 +37,44 @@ def run_migrations_offline() -> None:
script output. script output.
""" """
# url = config.get_main_option("sqlalchemy.url")
url = config.get_main_option(db.SQLALCHEMY_DATABASE_URL) url = config.get_main_option(db.SQLALCHEMY_DATABASE_URL)
context.configure( context.configure(
url=url, url=url,
target_metadata=target_metadata, target_metadata=target_metadata,
literal_binds=True, literal_binds=True,
dialect_opts={"paramstyle": "named"}, dialect_opts={"paramstyle": "named"},
render_as_batch=True
) )
with context.begin_transaction(): with context.begin_transaction():
context.run_migrations() context.run_migrations()
def do_run_migrations(connection: Connection) -> None: def run_migrations_online():
context.configure(connection=connection, target_metadata=target_metadata) """Run migrations in 'online' mode.
with context.begin_transaction(): In this scenario we need to create an Engine
context.run_migrations()
async def run_async_migrations() -> None:
"""In this scenario we need to create an Engine
and associate a connection with the context. and associate a connection with the context.
""" """
configuration = config.get_section(config.config_ini_section) configuration = config.get_section(config.config_ini_section)
configuration['sqlalchemy.url'] = db.SQLALCHEMY_DATABASE_URL configuration['sqlalchemy.url'] = db.SQLALCHEMY_DATABASE_URL
connectable = async_engine_from_config( connectable = engine_from_config(
configuration, configuration,
prefix="sqlalchemy.", prefix="sqlalchemy.",
poolclass=pool.NullPool, poolclass=pool.NullPool,
) )
async with connectable.connect() as connection: with connectable.connect() as connection:
await connection.run_sync(do_run_migrations) context.configure(
connection=connection,
target_metadata=target_metadata,
render_as_batch=True
)
await connectable.dispose() with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode."""
asyncio.run(run_async_migrations())
if context.is_offline_mode(): if context.is_offline_mode():

View File

@ -5,22 +5,20 @@ Revises: ${down_revision | comma,n}
Create Date: ${create_date} Create Date: ${create_date}
""" """
from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
${imports if imports else ""} ${imports if imports else ""}
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)} revision = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)} down_revision = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} branch_labels = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} depends_on = ${repr(depends_on)}
def upgrade() -> None: def upgrade():
${upgrades if upgrades else "pass"} ${upgrades if upgrades else "pass"}
def downgrade() -> None: def downgrade():
${downgrades if downgrades else "pass"} ${downgrades if downgrades else "pass"}

View File

@ -1,30 +0,0 @@
"""lazy=selectin added to user table relationships
Revision ID: 8e631a2fe6b8
Revises:
Create Date: 2023-09-02 23:45:08.799366
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '8e631a2fe6b8'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

Binary file not shown.

View File

@ -1,7 +1,5 @@
aiosqlite==0.19.0
annotated-types==0.5.0 annotated-types==0.5.0
anyio==3.7.1 anyio==3.7.1
asyncpg==0.28.0
certifi==2023.7.22 certifi==2023.7.22
charset-normalizer==3.2.0 charset-normalizer==3.2.0
click==8.1.6 click==8.1.6
@ -17,7 +15,6 @@ pyasn1==0.5.0
pydantic==1.10.10 pydantic==1.10.10
pydantic_core==2.4.0 pydantic_core==2.4.0
python-dateutil==2.8.2 python-dateutil==2.8.2
python-dotenv==1.0.0
python-jose==3.3.0 python-jose==3.3.0
python-multipart==0.0.6 python-multipart==0.0.6
redbird==0.7.1 redbird==0.7.1