Compare commits
22 Commits
3668e8c33f
...
Auth-code-
Author | SHA1 | Date | |
---|---|---|---|
6c6f96cc94 | |||
45d0b8e52d | |||
1055416640 | |||
52d9ad3399 | |||
30140f058f | |||
d5ba710885 | |||
6127dd8ba4 | |||
9e4bb1b99f | |||
d66b9004e0 | |||
626170964f | |||
1dd37a72b4 | |||
b39d9ada27 | |||
91842dcc51 | |||
09ba6a3478 | |||
de8a1abcbf
|
|||
898d590cf3 | |||
1c25d66027
|
|||
b7bbd937b4
|
|||
808edad6b4 | |||
b360b06d34 | |||
349d40daa4 | |||
8bae63231b |
102
alembic.ini
Normal file
102
alembic.ini
Normal file
@ -0,0 +1,102 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = migrations
|
||||
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
# defaults to the current working directory.
|
||||
prepend_sys_path = .
|
||||
|
||||
# timezone to use when rendering the date within the migration file
|
||||
# as well as the filename.
|
||||
# If specified, requires the python-dateutil library that can be
|
||||
# installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to dateutil.tz.gettz()
|
||||
# leave blank for localtime
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the
|
||||
# "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to migrations/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path.
|
||||
# The path separator used here should be the separator specified by "version_path_separator" below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions
|
||||
|
||||
# version path separator; As mentioned above, this is the character used to split
|
||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
||||
# Valid values for version_path_separator are:
|
||||
#
|
||||
# version_path_separator = :
|
||||
# version_path_separator = ;
|
||||
# version_path_separator = space
|
||||
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
# sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks = black
|
||||
# black.type = console_scripts
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
2
back/base.py
Normal file
2
back/base.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .db import Base
|
||||
from .models import UserDatabase, Announcement, Trashbox
|
16
back/db.py
16
back/db.py
@ -1,6 +1,13 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from typing import AsyncGenerator
|
||||
|
||||
from sqlalchemy import create_engine, select
|
||||
# from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.orm import sessionmaker, Session
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
from fastapi import Depends
|
||||
# from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
|
||||
|
||||
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
|
||||
|
||||
@ -8,6 +15,7 @@ engine = create_engine(
|
||||
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
|
||||
)
|
||||
|
||||
SessionLocal = sessionmaker(autoflush=True, bind=engine)
|
||||
SessionLocal = sessionmaker(bind=engine, autoflush=True, autocommit=False)
|
||||
|
||||
Base = declarative_base()
|
||||
database = SessionLocal()
|
||||
Base = declarative_base()
|
68
back/main.py
68
back/main.py
@ -19,34 +19,47 @@ import shutil
|
||||
import os
|
||||
|
||||
from .utils import *
|
||||
from .models import Announcement, Trashbox, UserDatabase, Base
|
||||
from .db import engine, SessionLocal
|
||||
from .db import Base, engine, SessionLocal, database
|
||||
from .models import Announcement, Trashbox, UserDatabase
|
||||
|
||||
from . import schema
|
||||
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
db = SessionLocal()
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
templates = Jinja2Templates(directory="./front/dist")
|
||||
|
||||
app.mount("/static", StaticFiles(directory = "./front/dist"))
|
||||
if not os.path.exists("./uploads"):
|
||||
os.mkdir("C:/Users/38812/porridger/uploads")
|
||||
app.mount("/uploads", StaticFiles(directory = "./uploads"))
|
||||
|
||||
|
||||
# Функция, создающая сессию БД при каждом запросе к нашему 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
|
||||
|
||||
|
||||
@app.get("/api/announcements")#адрес объявлений
|
||||
def annoncements_list(user_id: int = None, metro: str = None, category: str = None, booked_by: int = -1):
|
||||
def annoncements_list(user_id: int = None, metro: str = None, category: str = None, booked_by: int = 0):
|
||||
# Считываем данные из Body и отображаем их на странице.
|
||||
# В последствии будем вставлять данные в html-форму
|
||||
|
||||
a = db.query(Announcement)
|
||||
b = db.query(Announcement)
|
||||
c = db.query(Announcement)
|
||||
d = db.query(Announcement)
|
||||
e = db.query(Announcement)
|
||||
a = database.query(Announcement)
|
||||
b = database.query(Announcement)
|
||||
c = database.query(Announcement)
|
||||
d = database.query(Announcement)
|
||||
e = database.query(Announcement)
|
||||
|
||||
if user_id != None:
|
||||
b = a.filter(Announcement.user_id == user_id)
|
||||
@ -74,7 +87,7 @@ def single_annoncement(user_id:int):
|
||||
# Считываем данные из Body и отображаем их на странице.
|
||||
# В последствии будем вставлять данные в html-форму
|
||||
try:
|
||||
annoncement = db.get(Announcement, user_id)
|
||||
annoncement = database.get(Announcement, user_id)
|
||||
return {"id": annoncement.id, "user_id": annoncement.user_id, "name": annoncement.name,
|
||||
"category": annoncement.category, "best_by": annoncement.best_by, "address": annoncement.address,
|
||||
"description": annoncement.description, "metro": annoncement.metro, "latitude": annoncement.latitude,
|
||||
@ -86,7 +99,7 @@ def single_annoncement(user_id:int):
|
||||
|
||||
# Занести объявление в базу
|
||||
@app.put("/api/announcement")#адрес объявлений
|
||||
def put_in_db(name: Annotated[str, Form()], category: Annotated[str, Form()], bestBy: Annotated[int, Form()], address: Annotated[str, Form()], longtitude: Annotated[float, Form()], latitude: Annotated[float, Form()], description: Annotated[str, Form()], src: Annotated[UploadFile | None, File()], metro: Annotated[str, Form()], trashId: Annotated[int | None, Form()] = -1):
|
||||
def put_in_db(name: Annotated[str, Form()], category: Annotated[str, Form()], bestBy: Annotated[int, Form()], address: Annotated[str, Form()], longtitude: Annotated[float, Form()], latitude: Annotated[float, Form()], description: Annotated[str, Form()], src: UploadFile, metro: Annotated[str, Form()], trashId: Annotated[int, Form()] = None):
|
||||
# try:
|
||||
userId = 1 # temporary
|
||||
|
||||
@ -115,8 +128,8 @@ def put_in_db(name: Annotated[str, Form()], category: Annotated[str, Form()], be
|
||||
@app.delete("/api/announcement") #адрес объявления
|
||||
def delete_from_db(data = Body()):#функция удаления объекта из БД
|
||||
try:
|
||||
db.delete(user_id=data.user_id)#удаление из БД
|
||||
db.commit() # сохраняем изменения
|
||||
database.delete(user_id=data.user_id)#удаление из БД
|
||||
database.commit() # сохраняем изменения
|
||||
return {"Answer" : True}
|
||||
except:
|
||||
return {"Answer" : False}
|
||||
@ -129,21 +142,21 @@ def change_book_status(data: schema.Book):
|
||||
# Получаем id пользователя, который бронирует объявление
|
||||
temp_user_id = 1
|
||||
# Находим объявление по данному id
|
||||
announcement_to_change = db.query(Announcement).filter(id == data.id).first()
|
||||
announcement_to_change = database.query(Announcement).filter(id == data.id).first()
|
||||
# Изменяем поле booked_status на полученный id
|
||||
announcement_to_change.booked_status = temp_user_id
|
||||
return {"Success": True}
|
||||
except:
|
||||
return {"Success": False}
|
||||
|
||||
|
||||
# reginstration
|
||||
@app.post("/api/signup")
|
||||
def create_user(data = Body()):
|
||||
if db.query(UserDatabase).filter(User.email == data["email"]).first() == None:
|
||||
if database.query(UserDatabase).filter(UserDatabase.email == data["email"]).first() == None:
|
||||
new_user = UserDatabase(id=data["id"], email=data["email"], password=data["password"], name=data["name"], surname=data["surname"])
|
||||
db.add(new_user)
|
||||
db.commit()
|
||||
db.refresh(new_user) # обновляем состояние объекта
|
||||
database.add(new_user)
|
||||
database.commit()
|
||||
database.refresh(new_user) # обновляем состояние объекта
|
||||
return {"Success": True}
|
||||
return {"Success": False, "Message": "Пользователь с таким email уже зарегестрирован."}
|
||||
|
||||
@ -152,7 +165,8 @@ def create_user(data = Body()):
|
||||
async def login_for_access_token(
|
||||
form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
|
||||
):
|
||||
user = authenticate_user(db.query(UserDatabase).all(), form_data.username, form_data.password)
|
||||
# разобраться с первым параметром
|
||||
user = authenticate_user(database, form_data.username, form_data.password)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@ -163,11 +177,11 @@ async def login_for_access_token(
|
||||
access_token = create_access_token(
|
||||
data={"user_id": user.id}, expires_delta=access_token_expires
|
||||
)
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
return access_token
|
||||
|
||||
|
||||
@app.get("/api/users/me/", response_model=User)
|
||||
async def read_users_me(
|
||||
@app.get("/api/users/me/", response_model=schema.User)
|
||||
async def read_users_me( #!!!!!!!!!!!
|
||||
current_user: Annotated[User, Depends(get_current_active_user)]
|
||||
):
|
||||
return current_user
|
||||
@ -184,7 +198,7 @@ async def read_own_items(
|
||||
@app.get("/api/trashbox")
|
||||
def get_trashboxes(lat:float, lng:float):#крутая функция для работы с api
|
||||
BASE_URL='https://geointelect2.gate.petersburg.ru'#адрес сайта и мой токин
|
||||
my_token='eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhU1RaZm42bHpTdURYcUttRkg1SzN5UDFhT0FxUkhTNm9OendMUExaTXhFIn0.eyJleHAiOjE3Nzg2NTk4MjEsImlhdCI6MTY4Mzk2NTQyMSwianRpIjoiOTI2ZGMyNmEtMGYyZi00OTZiLWI0NTUtMWQyYWM5YmRlMTZkIiwiaXNzIjoiaHR0cHM6Ly9rYy5wZXRlcnNidXJnLnJ1L3JlYWxtcy9lZ3MtYXBpIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImJjYjQ2NzljLTU3ZGItNDU5ZC1iNWUxLWRlOGI4Yzg5MTMwMyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLXJlc3QtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjE2MGU1ZGVkLWFmMjMtNDkyNS05OTc1LTRhMzM0ZjVmNTkyOSIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtZWdzLWFwaSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiIxNjBlNWRlZC1hZjIzLTQ5MjUtOTk3NS00YTMzNGY1ZjU5MjkiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiLQktC70LDQtNC40LzQuNGAINCv0LrQvtCy0LvQtdCyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZTBmYzc2OGRhOTA4MjNiODgwZGQzOGVhMDJjMmQ5NTciLCJnaXZlbl9uYW1lIjoi0JLQu9Cw0LTQuNC80LjRgCIsImZhbWlseV9uYW1lIjoi0K_QutC-0LLQu9C10LIifQ.BRyUIyY-KKnZ9xqTNa9vIsfKF0UN2VoA9h4NN4y7IgBVLiiS-j43QbeE6qgjIQo0pV3J8jtCAIPvJbO-Ex-GNkw_flgMiGHhKEpsHPW3WK-YZ-XsZJzVQ_pOmLte-Kql4z97WJvolqiXT0nMo2dlX2BGvNs6JNbupvcuGwL4YYpekYAaFNYMQrxi8bSN-R7FIqxP-gzZDAuQSWRRSUqVBLvmgRhphTM-FAx1sX833oXL9tR7ze3eDR_obSV0y6cKVIr4eIlKxFd82qiMrN6A6CTUFDeFjeAGERqeBPnJVXU36MHu7Ut7eOVav9OUARARWRkrZRkqzTfZ1iqEBq5Tsg'
|
||||
my_token='eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhU1RaZm42bHpTdURYcUttRkg1SzN5UDFhT0FxUkhTNm9OendMUExaTXhFIn0.eyJleHAiOjE3ODM3ODk4NjgsImlhdCI6MTY4OTA5NTQ2OCwianRpIjoiNDUzNjQzZTgtYTkyMi00NTI4LWIzYmMtYWJiYTNmYjkyNTkxIiwiaXNzIjoiaHR0cHM6Ly9rYy5wZXRlcnNidXJnLnJ1L3JlYWxtcy9lZ3MtYXBpIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImJjYjQ2NzljLTU3ZGItNDU5ZC1iNWUxLWRlOGI4Yzg5MTMwMyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLXJlc3QtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6ImM2ZDJiOTZhLWMxNjMtNDAxZS05ZjMzLTI0MmE0NDcxMDY5OCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtZWdzLWFwaSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiJjNmQyYjk2YS1jMTYzLTQwMWUtOWYzMy0yNDJhNDQ3MTA2OTgiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiLQktC70LDQtNC40LzQuNGAINCv0LrQvtCy0LvQtdCyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZTBmYzc2OGRhOTA4MjNiODgwZGQzOGVhMDJjMmQ5NTciLCJnaXZlbl9uYW1lIjoi0JLQu9Cw0LTQuNC80LjRgCIsImZhbWlseV9uYW1lIjoi0K_QutC-0LLQu9C10LIifQ.E2bW0B-c6W5Lj63eP_G8eI453NlDMnW05l11TZT0GSsAtGayXGaolHtWrmI90D5Yxz7v9FGkkCmcUZYy1ywAdO9dDt_XrtFEJWFpG-3csavuMjXmqfQQ9SmPwDw-3toO64NuZVv6qVqoUlPPj57sLx4bLtVbB4pdqgyJYcrDHg7sgwz4d1Z3tAeUfSpum9s5ZfELequfpLoZMXn6CaYZhePaoK-CxeU3KPBPTPOVPKZZ19s7QY10VdkxLULknqf9opdvLs4j8NMimtwoIiHNBFlgQz10Cr7bhDKWugfvSRsICouniIiBJo76wrj5T92s-ztf1FShJuqnQcKE_QLd2A'
|
||||
head = {'Authorization': 'Bearer {}'.format(my_token)}
|
||||
|
||||
my_data={
|
||||
|
@ -1,12 +1,18 @@
|
||||
from sqlalchemy import Column, Integer, String
|
||||
|
||||
from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
|
||||
from fastapi import Depends
|
||||
from .db import Base
|
||||
|
||||
|
||||
class User(SQLAlchemyBaseUserTableUUID, Base):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class UserDatabase(Base):#класс пользователя
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)#айди пользователя
|
||||
id = Column(Integer, primary_key=True, index=True, unique=True)#айди пользователя
|
||||
phone = Column(Integer, nullable=True)#номер телефона пользователя
|
||||
email = Column(String)#электронная почта пользователя
|
||||
password = Column(String) # пароль
|
||||
@ -43,3 +49,26 @@ class Trashbox(Base):#класс мусорных баков
|
||||
longtitude = Column(Integer)
|
||||
category = Column(String)#категория продукта из объявления
|
||||
|
||||
|
||||
class Poems(Base):#класс поэзии
|
||||
__tablename__ = "poems"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True) #айди
|
||||
poem_text = Column(String) # текст стихотворения
|
||||
|
||||
# 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)
|
@ -1,5 +1,29 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Annotated, Union
|
||||
|
||||
class Book(BaseModel):
|
||||
id: int
|
||||
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
email: Union[str, None] = None
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
id: int
|
||||
phone: Union[int, None] = None
|
||||
email: str
|
||||
name: Union[str, None] = None
|
||||
surname: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class UserInDB(User):
|
||||
password: str
|
||||
hashed_password: str
|
2
back/service.py
Normal file
2
back/service.py
Normal file
@ -0,0 +1,2 @@
|
||||
from sqlalchemy.orm import Session
|
||||
|
119
back/utils.py
119
back/utils.py
@ -1,96 +1,24 @@
|
||||
# from passlib.context import CryptContext
|
||||
# import os
|
||||
# from datetime import datetime, timedelta
|
||||
# from typing import Union, Any
|
||||
# from jose import jwt
|
||||
|
||||
# ACCESS_TOKEN_EXPIRE_MINUTES = 30 # 30 minutes
|
||||
# REFRESH_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # 7 days
|
||||
# ALGORITHM = "HS256"
|
||||
# # В предположении, что попыток взлома не будет, возьмем простейший ключ
|
||||
# JWT_SECRET_KEY = "secret key" # может также быть os.environ["JWT_SECRET_KEY"]
|
||||
# JWT_REFRESH_SECRET_KEY = "refresh secret key" # может также быть os.environ["JWT_REFRESH_SECRET_KEY"]
|
||||
|
||||
|
||||
# def get_hashed_password(password: str) -> str:
|
||||
# return password_context.hash(password)
|
||||
|
||||
|
||||
# def verify_password(password: str, hashed_pass: str) -> bool:
|
||||
# return password_context.verify(password, hashed_pass)
|
||||
|
||||
|
||||
# def create_access_token(subject: Union[str, Any], expires_delta: int = None) -> str:
|
||||
# if expires_delta is not None:
|
||||
# expires_delta = datetime.utcnow() + expires_delta
|
||||
# else:
|
||||
# expires_delta = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
|
||||
# to_encode = {"exp": expires_delta, "sub": str(subject)}
|
||||
# encoded_jwt = jwt.encode(to_encode, JWT_SECRET_KEY, ALGORITHM)
|
||||
# return encoded_jwt
|
||||
|
||||
# def create_refresh_token(subject: Union[str, Any], expires_delta: int = None) -> str:
|
||||
# if expires_delta is not None:
|
||||
# expires_delta = datetime.utcnow() + expires_delta
|
||||
# else:
|
||||
# expires_delta = datetime.utcnow() + timedelta(minutes=REFRESH_TOKEN_EXPIRE_MINUTES)
|
||||
|
||||
# to_encode = {"exp": expires_delta, "sub": str(subject)}
|
||||
# encoded_jwt = jwt.encode(to_encode, JWT_REFRESH_SECRET_KEY, ALGORITHM)
|
||||
# return encoded_jwt
|
||||
|
||||
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Annotated, Union
|
||||
|
||||
from fastapi import Depends, FastAPI, HTTPException, status
|
||||
from fastapi import Depends, FastAPI, HTTPException, status, Response, Request
|
||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
from pydantic import BaseModel
|
||||
|
||||
# to get a string like this run:
|
||||
# openssl rand -hex 32
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
|
||||
from .db import Session, database
|
||||
from .models import UserDatabase
|
||||
|
||||
from .schema import Token, TokenData, UserInDB, User
|
||||
|
||||
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||
|
||||
|
||||
# fake_users_db = {
|
||||
# "johndoe": {
|
||||
# "email": "johndoe",
|
||||
# "full_name": "John Doe",
|
||||
# "email": "johndoe@example.com",
|
||||
# "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
|
||||
# "disabled": False,
|
||||
# }
|
||||
# }
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
email: Union[str, None] = None
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
email: str
|
||||
email: Union[str, None] = None
|
||||
# password: str
|
||||
# password: Union[str, None] = None
|
||||
full_name: Union[str, None] = None
|
||||
disabled: Union[bool, None] = None
|
||||
|
||||
|
||||
class UserInDB(User):
|
||||
hashed_password: str
|
||||
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
||||
@ -103,17 +31,16 @@ def get_password_hash(password):
|
||||
return pwd_context.hash(password)
|
||||
|
||||
|
||||
def get_user(db, email: str):
|
||||
user = None
|
||||
for person_with_correct_email in db:
|
||||
if person_with_correct_email.email == email:
|
||||
user = person_with_correct_email
|
||||
break
|
||||
return user #UserInDB(user_email)
|
||||
# проблема здесь
|
||||
def get_user(db: Session, email: str):
|
||||
user_with_required_email = db.query(UserDatabase).filter(UserDatabase.email == email).first()
|
||||
print(user_with_required_email)
|
||||
if user_with_required_email:
|
||||
return user_with_required_email
|
||||
return None
|
||||
|
||||
|
||||
|
||||
def authenticate_user(db, email: str, password: str):
|
||||
def authenticate_user(db: Session, email: str, password: str):
|
||||
user = get_user(db, email)
|
||||
if not user:
|
||||
return False
|
||||
@ -133,7 +60,7 @@ def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
|
||||
async def get_current_user(db: Session, token: Annotated[str, Depends(oauth2_scheme)]):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
@ -147,8 +74,8 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
|
||||
token_data = TokenData(email=email)
|
||||
except JWTError:
|
||||
raise credentials_exception
|
||||
user = get_user(fake_users_db, email=token_data.email)
|
||||
if user is None:
|
||||
user = get_user(db, email=token_data.email)
|
||||
if user == None:
|
||||
raise credentials_exception
|
||||
return user
|
||||
|
||||
@ -158,4 +85,8 @@ async def get_current_active_user(
|
||||
):
|
||||
if current_user.disabled:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
return current_user
|
||||
return current_user
|
||||
|
||||
|
||||
def get_db(request: Request):
|
||||
return request.state.db
|
12
front/src/api/putAnnouncement/index.ts
Normal file
12
front/src/api/putAnnouncement/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { API_URL } from '../../config'
|
||||
import { PutAnnouncement, PutAnnouncementResponse } from './types'
|
||||
|
||||
const composePutAnnouncementURL = () => (
|
||||
API_URL + '/announcement?'
|
||||
)
|
||||
|
||||
const processPutAnnouncement = (data: PutAnnouncementResponse): PutAnnouncement => {
|
||||
return data.Answer
|
||||
}
|
||||
|
||||
export { composePutAnnouncementURL, processPutAnnouncement }
|
17
front/src/api/putAnnouncement/types.ts
Normal file
17
front/src/api/putAnnouncement/types.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { isObject } from '../../utils/types'
|
||||
|
||||
type PutAnnouncementResponse = {
|
||||
Answer: boolean
|
||||
}
|
||||
|
||||
const isPutAnnouncementResponse = (obj: unknown): obj is PutAnnouncementResponse => (
|
||||
isObject(obj, {
|
||||
'Answer': 'boolean'
|
||||
})
|
||||
)
|
||||
|
||||
type PutAnnouncement = boolean
|
||||
|
||||
export type { PutAnnouncementResponse, PutAnnouncement }
|
||||
|
||||
export { isPutAnnouncementResponse }
|
@ -1,6 +1,7 @@
|
||||
import { LatLng } from 'leaflet'
|
||||
|
||||
import { API_URL } from '../../config'
|
||||
import { Trashbox, TrashboxResponse } from './types'
|
||||
|
||||
const composeTrashboxURL = (position: LatLng) => (
|
||||
API_URL + '/trashbox?' + new URLSearchParams({
|
||||
@ -9,4 +10,7 @@ const composeTrashboxURL = (position: LatLng) => (
|
||||
}).toString()
|
||||
)
|
||||
|
||||
export { composeTrashboxURL }
|
||||
const processTrashbox = (data: TrashboxResponse): Trashbox[] =>
|
||||
data
|
||||
|
||||
export { composeTrashboxURL, processTrashbox }
|
||||
|
@ -1,79 +1,31 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { API_URL } from '../../config'
|
||||
import { isLiteralUnion } from '../../utils/types'
|
||||
import { handleHTTPErrors } from '../../utils'
|
||||
|
||||
const addErrors = ['Не удалось опубликовать объявление', 'Неверный ответ от сервера', 'Неизвестная ошибка'] as const
|
||||
type AddError = typeof addErrors[number]
|
||||
|
||||
const isAddError = (obj: unknown): obj is AddError => (
|
||||
isLiteralUnion(obj, addErrors)
|
||||
)
|
||||
|
||||
const buttonStates = ['Опубликовать', 'Загрузка...', 'Опубликовано', 'Отменено'] as const
|
||||
type ButtonState = typeof buttonStates[number] | AddError
|
||||
|
||||
type AddResponse = {
|
||||
Answer: boolean
|
||||
}
|
||||
|
||||
const isAddResponse = (obj: unknown): obj is AddResponse => (
|
||||
typeof obj === 'object' && obj !== null && typeof Reflect.get(obj, 'Answer') === 'boolean'
|
||||
)
|
||||
import { useSend } from '..'
|
||||
import { composePutAnnouncementURL, processPutAnnouncement } from '../../api/putAnnouncement'
|
||||
import { isPutAnnouncementResponse } from '../../api/putAnnouncement/types'
|
||||
import useSendButtonCaption from '../useSendButtonCaption'
|
||||
|
||||
const useAddAnnouncement = () => {
|
||||
const [status, setStatus] = useState<ButtonState>('Опубликовать')
|
||||
const { doSend, loading, error } = useSend(
|
||||
composePutAnnouncementURL(),
|
||||
'PUT',
|
||||
true,
|
||||
isPutAnnouncementResponse,
|
||||
processPutAnnouncement,
|
||||
)
|
||||
|
||||
const timerIdRef = useRef<number>()
|
||||
const abortControllerRef = useRef<AbortController>()
|
||||
const { update, ...button } = useSendButtonCaption('Опубликовать', loading, error, 'Опубликовано')
|
||||
|
||||
const doAdd = async (formData: FormData) => {
|
||||
if (status === 'Загрузка...') {
|
||||
abortControllerRef.current?.abort()
|
||||
setStatus('Отменено')
|
||||
timerIdRef.current = setTimeout(() => setStatus('Опубликовать'), 3000)
|
||||
return
|
||||
}
|
||||
const doSendWithButton = useCallback(async (formData: FormData) => {
|
||||
const data = await doSend({}, {
|
||||
body: formData
|
||||
})
|
||||
update(data)
|
||||
|
||||
setStatus('Загрузка...')
|
||||
return data
|
||||
}, [doSend, update])
|
||||
|
||||
const abortController = new AbortController()
|
||||
abortControllerRef.current = abortController
|
||||
|
||||
try {
|
||||
const res = await fetch(API_URL + '/announcement', {
|
||||
method: 'PUT',
|
||||
body: formData,
|
||||
signal: abortController.signal
|
||||
})
|
||||
|
||||
handleHTTPErrors(res)
|
||||
|
||||
const data: unknown = await res.json()
|
||||
|
||||
if (!isAddResponse(data)) throw new Error('Неверный ответ от сервера')
|
||||
|
||||
if (!data.Answer) {
|
||||
throw new Error('Не удалось опубликовать объявление')
|
||||
}
|
||||
setStatus('Опубликовано')
|
||||
|
||||
} catch (err) {
|
||||
setStatus(isAddError(err) ? err : 'Неизвестная ошибка')
|
||||
timerIdRef.current = setTimeout(() => setStatus('Опубликовать'), 10000)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const abortController = abortControllerRef.current
|
||||
return () => {
|
||||
clearTimeout(timerIdRef.current)
|
||||
abortController?.abort()
|
||||
}
|
||||
})
|
||||
|
||||
return { doAdd, status }
|
||||
return { doSend: doSendWithButton, button }
|
||||
}
|
||||
|
||||
export default useAddAnnouncement
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { LatLng } from 'leaflet'
|
||||
|
||||
import { useFetch } from '../'
|
||||
import { composeTrashboxURL } from '../../api/trashbox'
|
||||
import { composeTrashboxURL, processTrashbox } from '../../api/trashbox'
|
||||
import { isTrashboxResponse } from '../../api/trashbox/types'
|
||||
|
||||
const useTrashboxes = (position: LatLng) => (
|
||||
@ -10,7 +10,7 @@ const useTrashboxes = (position: LatLng) => (
|
||||
'GET',
|
||||
true,
|
||||
isTrashboxResponse,
|
||||
(data) => data,
|
||||
processTrashbox,
|
||||
[]
|
||||
)
|
||||
)
|
||||
|
@ -24,6 +24,7 @@ function useSend<R, T>(
|
||||
/** Don't use in useEffect. If you need request result, go with useFetch instead */
|
||||
const doSend = useCallback(async (urlProps?: Record<string, string>, params?: Omit<RequestInit, 'method'>) => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort()
|
||||
|
44
front/src/hooks/useSendButtonCaption.ts
Normal file
44
front/src/hooks/useSendButtonCaption.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
function useSendButtonCaption(
|
||||
initial: string,
|
||||
loading: boolean,
|
||||
error: string | null,
|
||||
result = initial,
|
||||
singular = true
|
||||
) {
|
||||
const [caption, setCaption] = useState(initial)
|
||||
const [disabled, setDisabled] = useState(false)
|
||||
const [title, setTitle] = useState(initial)
|
||||
|
||||
const update = useCallback(<T extends NonNullable<unknown>>(data: T | undefined) => {
|
||||
if (data !== undefined) {
|
||||
setCaption(result)
|
||||
setTitle('Отправить ещё раз')
|
||||
|
||||
if (singular) {
|
||||
setDisabled(true)
|
||||
setTitle('')
|
||||
}
|
||||
}
|
||||
|
||||
}, [result, singular])
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
setCaption('Загрузка...')
|
||||
setTitle('Отменить и отправить ещё раз')
|
||||
}
|
||||
}, [loading])
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && error !== null) {
|
||||
setCaption(error + ', нажмите, чтобы попробовать ещё раз')
|
||||
setTitle('')
|
||||
}
|
||||
}, [error, loading])
|
||||
|
||||
return { update, children: caption, disabled, title }
|
||||
}
|
||||
|
||||
export default useSendButtonCaption
|
@ -1,11 +1,10 @@
|
||||
import { CSSProperties, FormEventHandler, useEffect, useState } from 'react'
|
||||
import { CSSProperties, FormEventHandler, useState } from 'react'
|
||||
import { Form, Button, Card } from 'react-bootstrap'
|
||||
import { MapContainer, TileLayer } from 'react-leaflet'
|
||||
import { latLng } from 'leaflet'
|
||||
|
||||
import { ClickHandler, LocationMarker, TrashboxMarkers } from '../components'
|
||||
import { useAddAnnouncement, useTrashboxes } from '../hooks/api'
|
||||
import { handleHTTPErrors } from '../utils'
|
||||
import { categories, categoryNames } from '../assets/category'
|
||||
import { stations, lines, lineNames } from '../assets/metro'
|
||||
import { fallbackError, gotError } from '../hooks/useFetch'
|
||||
@ -32,25 +31,7 @@ function AddPage() {
|
||||
|
||||
const address = useOsmAddresses(addressPosition)
|
||||
|
||||
useEffect(() => {
|
||||
if (!gotError(address))
|
||||
void (async () => {
|
||||
try {
|
||||
const res = await fetch(location.protocol + '//nominatim.openstreetmap.org/search?format=json&q=' + encodeURIComponent(address.data))
|
||||
|
||||
handleHTTPErrors(res)
|
||||
|
||||
const fetchData: unknown = await res.json()
|
||||
|
||||
console.log('f', fetchData)
|
||||
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})()
|
||||
}, [address])
|
||||
|
||||
const { doAdd, status } = useAddAnnouncement()
|
||||
const { doSend, button } = useAddAnnouncement()
|
||||
|
||||
const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
|
||||
event.preventDefault()
|
||||
@ -63,7 +44,7 @@ function AddPage() {
|
||||
formData.append('address', address.data || '') // if address.error
|
||||
formData.set('bestBy', new Date((formData.get('bestBy') as number | null) || 0).getTime().toString())
|
||||
|
||||
void doAdd(formData)
|
||||
void doSend(formData)
|
||||
}
|
||||
|
||||
return (
|
||||
@ -151,7 +132,7 @@ function AddPage() {
|
||||
</Form.Select>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group className='mb-3' controlId='password'>
|
||||
<Form.Group className='mb-3' controlId='trashbox'>
|
||||
<Form.Label>Пункт сбора мусора</Form.Label>
|
||||
<div className='mb-3'>
|
||||
{trashboxes.loading
|
||||
@ -195,9 +176,7 @@ function AddPage() {
|
||||
)}
|
||||
</Form.Group>
|
||||
|
||||
<Button variant='success' type='submit'>
|
||||
{status}
|
||||
</Button>
|
||||
<Button variant='success' type='submit' {...button} />
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
|
1
migrations/README
Normal file
1
migrations/README
Normal file
@ -0,0 +1 @@
|
||||
Generic single-database configuration.
|
85
migrations/env.py
Normal file
85
migrations/env.py
Normal file
@ -0,0 +1,85 @@
|
||||
from logging.config import fileConfig
|
||||
|
||||
from sqlalchemy import engine_from_config
|
||||
from sqlalchemy import pool
|
||||
|
||||
from alembic import context
|
||||
|
||||
|
||||
from back import db, base
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
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:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
# url = config.get_main_option("sqlalchemy.url")
|
||||
url = config.get_main_option(db.SQLALCHEMY_DATABASE_URL)
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
configuration = config.get_section(config.config_ini_section)
|
||||
configuration['sqlalchemy.url'] = db.SQLALCHEMY_DATABASE_URL
|
||||
connectable = engine_from_config(
|
||||
configuration,
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection, target_metadata=target_metadata
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
24
migrations/script.py.mako
Normal file
24
migrations/script.py.mako
Normal file
@ -0,0 +1,24 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else "pass"}
|
28
migrations/versions/0006eca30e2c_first.py
Normal file
28
migrations/versions/0006eca30e2c_first.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""first
|
||||
|
||||
Revision ID: 0006eca30e2c
|
||||
Revises:
|
||||
Create Date: 2023-07-23 22:32:43.496939
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0006eca30e2c'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
34
migrations/versions/18001c2231e3_poems_table_added.py
Normal file
34
migrations/versions/18001c2231e3_poems_table_added.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""Poems table added
|
||||
|
||||
Revision ID: 18001c2231e3
|
||||
Revises: 33c5716276b5
|
||||
Create Date: 2023-07-23 22:50:16.055961
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '18001c2231e3'
|
||||
down_revision = '33c5716276b5'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('poems',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('poem_text', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_poems_id'), 'poems', ['id'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_poems_id'), table_name='poems')
|
||||
op.drop_table('poems')
|
||||
# ### end Alembic commands ###
|
@ -0,0 +1,70 @@
|
||||
"""Try to make alembic see models
|
||||
|
||||
Revision ID: 33c5716276b5
|
||||
Revises: 0006eca30e2c
|
||||
Create Date: 2023-07-23 22:42:07.532395
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '33c5716276b5'
|
||||
down_revision = '0006eca30e2c'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('announcements',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('name', sa.String(), nullable=True),
|
||||
sa.Column('category', sa.String(), nullable=True),
|
||||
sa.Column('best_by', sa.Integer(), nullable=True),
|
||||
sa.Column('address', sa.String(), nullable=True),
|
||||
sa.Column('longtitude', sa.Integer(), nullable=True),
|
||||
sa.Column('latitude', sa.Integer(), nullable=True),
|
||||
sa.Column('description', sa.String(), nullable=True),
|
||||
sa.Column('src', sa.String(), nullable=True),
|
||||
sa.Column('metro', sa.String(), nullable=True),
|
||||
sa.Column('trashId', sa.Integer(), nullable=True),
|
||||
sa.Column('booked_by', sa.Integer(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_announcements_id'), 'announcements', ['id'], unique=False)
|
||||
op.create_table('trashboxes',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(), nullable=True),
|
||||
sa.Column('address', sa.String(), nullable=True),
|
||||
sa.Column('latitude', sa.Integer(), nullable=True),
|
||||
sa.Column('longtitude', sa.Integer(), nullable=True),
|
||||
sa.Column('category', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_trashboxes_id'), 'trashboxes', ['id'], unique=False)
|
||||
op.create_table('users',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('phone', sa.Integer(), nullable=True),
|
||||
sa.Column('email', sa.String(), nullable=True),
|
||||
sa.Column('password', sa.String(), nullable=True),
|
||||
sa.Column('hashed_password', sa.String(), nullable=True),
|
||||
sa.Column('name', sa.String(), nullable=True),
|
||||
sa.Column('surname', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=True)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_users_id'), table_name='users')
|
||||
op.drop_table('users')
|
||||
op.drop_index(op.f('ix_trashboxes_id'), table_name='trashboxes')
|
||||
op.drop_table('trashboxes')
|
||||
op.drop_index(op.f('ix_announcements_id'), table_name='announcements')
|
||||
op.drop_table('announcements')
|
||||
# ### end Alembic commands ###
|
Reference in New Issue
Block a user