Compare commits
4 commits
c63dc171bc
...
f2b813300d
Author | SHA1 | Date | |
---|---|---|---|
f2b813300d | |||
46f3aa6caf | |||
3124b35f67 | |||
7342415ad0 |
25 changed files with 373 additions and 204 deletions
|
@ -1,7 +1,7 @@
|
||||||
DB_USER=
|
|
||||||
DB_PASS=
|
|
||||||
DB_HOST=
|
DB_HOST=
|
||||||
DB_PORT=
|
DB_PORT=
|
||||||
|
DB_USER=
|
||||||
|
DB_PASS=
|
||||||
DB_NAME=
|
DB_NAME=
|
||||||
|
|
||||||
SECRET_KEY=
|
SECRET_KEY=
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from pydantic import BaseSettings
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
|
|
|
@ -54,3 +54,13 @@ class IncorrectLengthOfNicknameException(BlackPhoenixException):
|
||||||
detail = "Ник должен быть не короче 2 и не длиннее 30 символов"
|
detail = "Ник должен быть не короче 2 и не длиннее 30 символов"
|
||||||
|
|
||||||
|
|
||||||
|
class UDontHavePermissionException(BlackPhoenixException):
|
||||||
|
status_code = status.HTTP_409_CONFLICT
|
||||||
|
detail = "У вас нет прав для этого действия"
|
||||||
|
|
||||||
|
|
||||||
|
class MessageNotFoundException(BlackPhoenixException):
|
||||||
|
status_code = status.HTTP_404_NOT_FOUND
|
||||||
|
detail = "Сообщение не найдено"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
from starlette.staticfiles import StaticFiles
|
||||||
|
|
||||||
from app.users.chat.router import router as chat_router
|
from app.users.chat.router import router as chat_router
|
||||||
from app.users.router import router as user_router
|
from app.users.router import router as user_router
|
||||||
|
from app.pages.router import router as pages_router
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
app.include_router(chat_router)
|
app.include_router(chat_router)
|
||||||
app.include_router(user_router)
|
app.include_router(user_router)
|
||||||
|
app.include_router(pages_router)
|
||||||
|
|
||||||
|
app.mount("/static", StaticFiles(directory="app/static"), name="static")
|
||||||
|
|
||||||
|
|
||||||
@app.get('/')
|
@app.get('/')
|
||||||
|
|
|
@ -5,15 +5,15 @@ from logging.config import fileConfig
|
||||||
from sqlalchemy import engine_from_config, pool
|
from sqlalchemy import engine_from_config, pool
|
||||||
from alembic import context
|
from alembic import context
|
||||||
|
|
||||||
|
sys.path.insert(0, dirname(dirname(abspath(__file__))))
|
||||||
|
|
||||||
from app.database import DATABASE_URL, Base
|
from app.database import DATABASE_URL, Base
|
||||||
from app.users.models import Users # noqa
|
from app.users.models import Users # noqa
|
||||||
from app.users.chat.models import Chats # noqa
|
from app.users.chat.models import Chats, Messages, UsersXChats # noqa
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
# this is the Alembic Config object, which provides
|
||||||
# access to the values within the .ini file in use.
|
# access to the values within the .ini file in use.
|
||||||
|
|
||||||
sys.path.insert(0, dirname(dirname(abspath(__file__))))
|
|
||||||
|
|
||||||
config = context.config
|
config = context.config
|
||||||
|
|
||||||
config.set_main_option('sqlalchemy.url', f'{DATABASE_URL}?async_fallback=True')
|
config.set_main_option('sqlalchemy.url', f'{DATABASE_URL}?async_fallback=True')
|
||||||
|
@ -29,6 +29,7 @@ if config.config_file_name is not None:
|
||||||
# target_metadata = mymodel.Base.metadata
|
# target_metadata = mymodel.Base.metadata
|
||||||
target_metadata = Base.metadata
|
target_metadata = 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,
|
||||||
# can be acquired:
|
# can be acquired:
|
||||||
# my_important_option = config.get_main_option("my_important_option")
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
|
69
app/migrations/versions/2913a8a70afb_изменение_models_py.py
Normal file
69
app/migrations/versions/2913a8a70afb_изменение_models_py.py
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
"""Изменение models.py
|
||||||
|
|
||||||
|
Revision ID: 2913a8a70afb
|
||||||
|
Revises:
|
||||||
|
Create Date: 2024-02-01 14:31:04.662656
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '2913a8a70afb'
|
||||||
|
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! ###
|
||||||
|
op.create_table('users',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('email', sa.String(), nullable=False),
|
||||||
|
sa.Column('username', sa.String(), nullable=False),
|
||||||
|
sa.Column('hashed_password', sa.String(), nullable=False),
|
||||||
|
sa.Column('role', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('black_phoenix', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('avatar_image', sa.String(), server_default='app/static/images/ту уже пешка BP.png', nullable=True),
|
||||||
|
sa.Column('date_of_birth', sa.Date(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('chats',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('chat_for', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['chat_for'], ['users.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('messages',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('message', sa.String(), nullable=False),
|
||||||
|
sa.Column('image_url', sa.String(), nullable=True),
|
||||||
|
sa.Column('chat_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||||
|
sa.Column('visibility', sa.Boolean(), server_default='true', nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['chat_id'], ['chats.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('usersxchats',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('chat_id', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['chat_id'], ['chats.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('usersxchats')
|
||||||
|
op.drop_table('messages')
|
||||||
|
op.drop_table('chats')
|
||||||
|
op.drop_table('users')
|
||||||
|
# ### end Alembic commands ###
|
|
@ -1,32 +0,0 @@
|
||||||
"""Убрал обязательность авы
|
|
||||||
|
|
||||||
Revision ID: 3eb642de804e
|
|
||||||
Revises: 43eac1ddf80a
|
|
||||||
Create Date: 2024-01-29 19:37:41.136288
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = '3eb642de804e'
|
|
||||||
down_revision: Union[str, None] = '43eac1ddf80a'
|
|
||||||
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! ###
|
|
||||||
op.add_column('users', sa.Column('avatar_image', sa.String(), nullable=True))
|
|
||||||
op.drop_column('users', 'image')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('users', sa.Column('image', sa.VARCHAR(), autoincrement=False, nullable=False))
|
|
||||||
op.drop_column('users', 'avatar_image')
|
|
||||||
# ### end Alembic commands ###
|
|
|
@ -1,30 +0,0 @@
|
||||||
"""Добавил колонку юзерам
|
|
||||||
|
|
||||||
Revision ID: 43eac1ddf80a
|
|
||||||
Revises: 90665e133296
|
|
||||||
Create Date: 2024-01-29 19:35:47.727712
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = '43eac1ddf80a'
|
|
||||||
down_revision: Union[str, None] = '90665e133296'
|
|
||||||
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! ###
|
|
||||||
op.add_column('users', sa.Column('date_of_birth', sa.Date(), nullable=False))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('users', 'date_of_birth')
|
|
||||||
# ### end Alembic commands ###
|
|
|
@ -1,30 +0,0 @@
|
||||||
"""Добавил колонку юзерам
|
|
||||||
|
|
||||||
Revision ID: 90665e133296
|
|
||||||
Revises: 9845ad4fed24
|
|
||||||
Create Date: 2024-01-29 18:50:09.853356
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = '90665e133296'
|
|
||||||
down_revision: Union[str, None] = '9845ad4fed24'
|
|
||||||
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! ###
|
|
||||||
op.add_column('users', sa.Column('image', sa.String(), nullable=False))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('users', 'image')
|
|
||||||
# ### end Alembic commands ###
|
|
|
@ -1,45 +0,0 @@
|
||||||
"""Database Creation
|
|
||||||
|
|
||||||
Revision ID: 9845ad4fed24
|
|
||||||
Revises: e434e2885475
|
|
||||||
Create Date: 2024-01-29 18:32:13.361975
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = '9845ad4fed24'
|
|
||||||
down_revision: Union[str, None] = 'e434e2885475'
|
|
||||||
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! ###
|
|
||||||
op.create_table('chats',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('chat', sa.JSON(), nullable=True),
|
|
||||||
sa.Column('allowed_users', sa.JSON(), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
op.create_table('users',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('email', sa.String(), nullable=False),
|
|
||||||
sa.Column('username', sa.String(), nullable=False),
|
|
||||||
sa.Column('hashed_password', sa.String(), nullable=False),
|
|
||||||
sa.Column('role', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('black_phoenix', sa.Integer(), nullable=False),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table('users')
|
|
||||||
op.drop_table('chats')
|
|
||||||
# ### end Alembic commands ###
|
|
|
@ -1,30 +0,0 @@
|
||||||
"""Database Creation
|
|
||||||
|
|
||||||
Revision ID: e434e2885475
|
|
||||||
Revises:
|
|
||||||
Create Date: 2024-01-29 18:30:03.314179
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = 'e434e2885475'
|
|
||||||
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 ###
|
|
14
app/pages/router.py
Normal file
14
app/pages/router.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix="/pages",
|
||||||
|
tags=["Страницы"]
|
||||||
|
)
|
||||||
|
|
||||||
|
templates = Jinja2Templates(directory="app/templates")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/base")
|
||||||
|
async def base(request: Request):
|
||||||
|
return templates.TemplateResponse("base.html", {"request": request})
|
BIN
app/static/images/ту уже пешка BP.png
Executable file
BIN
app/static/images/ту уже пешка BP.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
36
app/templates/base.html
Normal file
36
app/templates/base.html
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
{% block head %}{% endblock %}
|
||||||
|
<title>BlackPhoenix</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="flex justify-between text-3xl my-3">
|
||||||
|
<ul>
|
||||||
|
<li class="BpMainButton"><button>Black Phoenix</button></li>
|
||||||
|
<li class="BpMyAccount"><button>Мой аккаунт</button></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ul,li{
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.BpMainButton{
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.BpMyAccount{
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</nav>
|
||||||
|
<hr>
|
||||||
|
<div id="content">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,7 +1,65 @@
|
||||||
|
from sqlalchemy import insert, select, update, and_
|
||||||
|
|
||||||
from app.dao.base import BaseDAO
|
from app.dao.base import BaseDAO
|
||||||
|
from app.database import async_session_maker
|
||||||
from app.users.models import Users
|
from app.users.models import Users
|
||||||
from app.users.chat.models import Chats
|
from app.users.chat.models import Chats, Messages
|
||||||
|
|
||||||
|
|
||||||
class ChatDAO(BaseDAO):
|
class ChatDAO(BaseDAO):
|
||||||
model = Chats
|
model = Chats
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def create(cls, user_id):
|
||||||
|
query = insert(Chats).values(chat_for=user_id)
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
await session.execute(query)
|
||||||
|
await session.commit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def send_message(cls, user_id, chat_id, message, image_url):
|
||||||
|
query = insert(Messages).values(chat_id=chat_id, user_id=user_id, message=message, image_url=image_url)
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
await session.execute(query)
|
||||||
|
await session.commit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_message_by_id(cls, message_id):
|
||||||
|
query = select(Messages.__table__.columns).where(
|
||||||
|
and_(
|
||||||
|
Messages.id == message_id,
|
||||||
|
Messages.visibility == True
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
result = await session.execute(query)
|
||||||
|
result = result.mappings().all()
|
||||||
|
if result:
|
||||||
|
return result[0]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_last_message(cls, chat_id):
|
||||||
|
query = select(Messages.__table__.columns).where(
|
||||||
|
and_(
|
||||||
|
Messages.chat_id == chat_id,
|
||||||
|
Messages.visibility == True
|
||||||
|
)
|
||||||
|
).order_by(Messages.created_at.desc()).limit(1)
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
result = await session.execute(query)
|
||||||
|
result = result.mappings().all()
|
||||||
|
if result:
|
||||||
|
return result[0]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def delete_message(cls, message_id):
|
||||||
|
query = update(Messages).where(Messages.id == message_id).values(visibility=False)
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
await session.execute(query)
|
||||||
|
await session.commit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
from sqlalchemy import Column, Integer, JSON
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from sqlalchemy import func, ForeignKey, DateTime
|
||||||
|
from sqlalchemy.orm import mapped_column, Mapped, relationship
|
||||||
|
|
||||||
from app.database import Base
|
from app.database import Base
|
||||||
|
|
||||||
|
@ -7,9 +10,44 @@ from app.database import Base
|
||||||
class Chats(Base):
|
class Chats(Base):
|
||||||
__tablename__ = "chats"
|
__tablename__ = "chats"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
chat = Column(JSON)
|
chat_for = mapped_column(ForeignKey("users.id"))
|
||||||
allowed_users = Column(JSON)
|
|
||||||
|
message = relationship("Messages", back_populates="chat")
|
||||||
|
usersxchats = relationship("UsersXChats", back_populates="chat")
|
||||||
|
user_to_exclude = relationship("Users", back_populates="chat")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Чат #{self.id} с {self.allowed_users}."
|
return f"Чат #{self.id}."
|
||||||
|
|
||||||
|
|
||||||
|
class Messages(Base):
|
||||||
|
__tablename__ = "messages"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
chat_id = mapped_column(ForeignKey("chats.id"))
|
||||||
|
user_id = mapped_column(ForeignKey("users.id"))
|
||||||
|
message: Mapped[str]
|
||||||
|
image_url: Mapped[Optional[str]]
|
||||||
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
visibility: Mapped[bool] = mapped_column(server_default='true')
|
||||||
|
|
||||||
|
chat = relationship("Chats", back_populates="message")
|
||||||
|
user = relationship("Users", back_populates="message")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"#{self.id} {self.text} от {self.user_id}. Написано {self.created_at}"
|
||||||
|
|
||||||
|
|
||||||
|
class UsersXChats(Base):
|
||||||
|
__tablename__ = "usersxchats"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
user_id = mapped_column(ForeignKey("users.id"))
|
||||||
|
chat_id = mapped_column(ForeignKey("chats.id"))
|
||||||
|
|
||||||
|
chat = relationship("Chats", back_populates="usersxchats")
|
||||||
|
user = relationship("Users", back_populates="usersxchats")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Юзер #{self.user_id} допущен к чату {self.chat_id}"
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
from fastapi import APIRouter, WebSocket
|
from fastapi import APIRouter, WebSocket, Depends, status
|
||||||
from starlette.websockets import WebSocketDisconnect
|
from starlette.websockets import WebSocketDisconnect
|
||||||
|
|
||||||
|
from app.exceptions import UDontHavePermissionException, MessageNotFoundException
|
||||||
|
from app.users.chat.dao import ChatDAO
|
||||||
|
from app.users.chat.shemas import SMessage
|
||||||
from app.users.chat.websocket import manager
|
from app.users.chat.websocket import manager
|
||||||
|
from app.users.dao import UserDAO
|
||||||
|
from app.users.dependencies import get_current_user
|
||||||
|
from app.users.models import Users
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
prefix="/chat",
|
prefix="/chat",
|
||||||
|
@ -9,18 +15,65 @@ router = APIRouter(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("")
|
@router.post("", status_code=status.HTTP_201_CREATED)
|
||||||
async def root():
|
async def add_message_to_chat(
|
||||||
pass
|
chat_id: int,
|
||||||
|
message: str,
|
||||||
|
image_url: str = None,
|
||||||
|
user: Users = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
send_message_to_chat = await ChatDAO.send_message(
|
||||||
|
user_id=user.id,
|
||||||
|
chat_id=chat_id,
|
||||||
|
message=message,
|
||||||
|
image_url=image_url
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.websocket("/ws/{user_id}")
|
@router.delete("/delete_message", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def websocket_endpoint(websocket: WebSocket, user_id: int):
|
async def delete_message_from_chat(
|
||||||
|
message_id: int,
|
||||||
|
user: Users = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
get_message_sender = await ChatDAO.get_message_by_id(message_id=message_id)
|
||||||
|
if get_message_sender is None:
|
||||||
|
raise MessageNotFoundException
|
||||||
|
if get_message_sender["user_id"] != user.id:
|
||||||
|
get_user_role = await UserDAO.get_user_role(user_id=user.id)
|
||||||
|
if not get_user_role == 1:
|
||||||
|
raise UDontHavePermissionException
|
||||||
|
deleted_message = await ChatDAO.delete_message(message_id=message_id)
|
||||||
|
return deleted_message
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/get_last_message", response_model=SMessage)
|
||||||
|
async def get_last_message(chat_id: int, user: Users = Depends(get_current_user)):
|
||||||
|
message = dict(await ChatDAO.get_last_message(chat_id=chat_id))
|
||||||
|
user_avatar = await UserDAO.get_user_avatar(user_id=user.id)
|
||||||
|
message["user_avatar"] = user_avatar
|
||||||
|
print(message)
|
||||||
|
if message is not None:
|
||||||
|
return message
|
||||||
|
raise MessageNotFoundException
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/create_chat")
|
||||||
|
async def create_chat(
|
||||||
|
user_to_exclude: int,
|
||||||
|
user: Users = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
created_chat = await ChatDAO.create(user_id=user_to_exclude)
|
||||||
|
return created_chat
|
||||||
|
|
||||||
|
|
||||||
|
@router.websocket("/ws")
|
||||||
|
async def websocket_endpoint(websocket: WebSocket, user: Users = Depends(get_current_user)):
|
||||||
await manager.connect(websocket)
|
await manager.connect(websocket)
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
data = await websocket.receive_text()
|
data = await websocket.receive_text()
|
||||||
await manager.broadcast(f"User {user_id}: {data}")
|
|
||||||
|
await manager.broadcast(f"User {user.id}: {data}")
|
||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
manager.disconnect(websocket)
|
manager.disconnect(websocket)
|
||||||
await manager.broadcast(f"User {user_id}: себался")
|
await manager.broadcast(f"User {user.id}: себался")
|
||||||
|
|
16
app/users/chat/shemas.py
Normal file
16
app/users/chat/shemas.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class SMessage(BaseModel):
|
||||||
|
message: str
|
||||||
|
image_url: Optional[str] = None
|
||||||
|
chat_id: int
|
||||||
|
user_id: int
|
||||||
|
created_at: datetime
|
||||||
|
user_avatar: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
|
@ -3,6 +3,7 @@ from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
from app.dao.base import BaseDAO
|
from app.dao.base import BaseDAO
|
||||||
from app.database import async_session_maker
|
from app.database import async_session_maker
|
||||||
|
from app.users.chat.models import UsersXChats
|
||||||
from app.users.models import Users
|
from app.users.models import Users
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,3 +20,24 @@ class UserDAO(BaseDAO):
|
||||||
result = await session.execute(query)
|
result = await session.execute(query)
|
||||||
return result.scalar()
|
return result.scalar()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_user_role(cls, user_id: int):
|
||||||
|
query = select(Users.role).where(Users.id == user_id)
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
result = await session.execute(query)
|
||||||
|
return result.scalar()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_user_rights(cls, user_id: int):
|
||||||
|
query = select(UsersXChats.__table__.columns).where(UsersXChats.user_id == user_id)
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
result = await session.execute(query)
|
||||||
|
print(result)
|
||||||
|
return result.mappings().all()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_user_avatar(cls, user_id: int):
|
||||||
|
query = select(Users.avatar_image).where(Users.id == user_id)
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
result = await session.execute(query)
|
||||||
|
return result.scalar()
|
||||||
|
|
|
@ -8,10 +8,11 @@ from app.exceptions import (IncorrectTokenFormatException,
|
||||||
TokenAbsentException, TokenExpiredException,
|
TokenAbsentException, TokenExpiredException,
|
||||||
UserIsNotPresentException)
|
UserIsNotPresentException)
|
||||||
from app.users.dao import UserDAO
|
from app.users.dao import UserDAO
|
||||||
|
from app.users.models import Users
|
||||||
|
|
||||||
|
|
||||||
def get_token(request: Request):
|
def get_token(request: Request):
|
||||||
token = request.cookies.get("booking_access_token")
|
token = request.cookies.get("black_phoenix_access_token")
|
||||||
if not token:
|
if not token:
|
||||||
raise TokenAbsentException
|
raise TokenAbsentException
|
||||||
return token
|
return token
|
||||||
|
@ -34,3 +35,7 @@ async def get_current_user(token: str = Depends(get_token)):
|
||||||
if not user:
|
if not user:
|
||||||
raise UserIsNotPresentException
|
raise UserIsNotPresentException
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_user_rights(user: Users = Depends(get_current_user)): # Надо дописать
|
||||||
|
user_rights = UserDAO.get_user_rights()
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
from sqlalchemy import Column, Integer, String, Date
|
from datetime import date
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from sqlalchemy.orm import mapped_column, Mapped, relationship
|
||||||
|
|
||||||
from app.database import Base
|
from app.database import Base
|
||||||
|
|
||||||
|
@ -6,14 +9,18 @@ from app.database import Base
|
||||||
class Users(Base):
|
class Users(Base):
|
||||||
__tablename__ = "users"
|
__tablename__ = "users"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
email = Column(String, nullable=False)
|
email: Mapped[str]
|
||||||
username = Column(String, nullable=False)
|
username: Mapped[str]
|
||||||
hashed_password = Column(String, nullable=False)
|
hashed_password: Mapped[str]
|
||||||
role = Column(Integer, nullable=False)
|
role: Mapped[int]
|
||||||
black_phoenix = Column(Integer, nullable=False)
|
black_phoenix: Mapped[int]
|
||||||
avatar_image = Column(String)
|
avatar_image: Mapped[Optional[str]] = mapped_column(server_default='app/static/images/ту уже пешка BP.png')
|
||||||
date_of_birth = Column(Date, nullable=False)
|
date_of_birth: Mapped[date]
|
||||||
|
|
||||||
|
message = relationship("Messages", back_populates="user")
|
||||||
|
usersxchats = relationship("UsersXChats", back_populates="user")
|
||||||
|
chat = relationship("Chats", back_populates="user_to_exclude")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Юзер {self.username}"
|
return f"Юзер {self.username}"
|
||||||
|
|
|
@ -40,7 +40,7 @@ async def register_user(response: Response, user_data: SUserRegister):
|
||||||
role=0, black_phoenix=0)
|
role=0, black_phoenix=0)
|
||||||
user = await authenticate_user_by_email(user_data.email, user_data.password)
|
user = await authenticate_user_by_email(user_data.email, user_data.password)
|
||||||
access_token = create_access_token({"sub": str(user.id)})
|
access_token = create_access_token({"sub": str(user.id)})
|
||||||
response.set_cookie("booking_access_token", access_token, httponly=True)
|
response.set_cookie("black_phoenix_access_token", access_token, httponly=True)
|
||||||
return {"access_token": access_token}
|
return {"access_token": access_token}
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,13 +52,13 @@ async def login_user(response: Response, user_data: SUserLogin):
|
||||||
if not user:
|
if not user:
|
||||||
raise IncorrectAuthDataException
|
raise IncorrectAuthDataException
|
||||||
access_token = create_access_token({"sub": str(user.id)})
|
access_token = create_access_token({"sub": str(user.id)})
|
||||||
response.set_cookie("booking_access_token", access_token, httponly=True)
|
response.set_cookie("black_phoenix_access_token", access_token, httponly=True)
|
||||||
return {"access_token": access_token}
|
return {"access_token": access_token}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/logout")
|
@router.post("/logout")
|
||||||
async def logout_user(response: Response):
|
async def logout_user(response: Response):
|
||||||
response.delete_cookie("booking_access_token")
|
response.delete_cookie("black_phoenix_access_token")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/me")
|
@router.get("/me")
|
||||||
|
|
|
@ -8,7 +8,7 @@ class SUserLogin(BaseModel):
|
||||||
password: str
|
password: str
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
orm_mode = True
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
class SUserRegister(BaseModel):
|
class SUserRegister(BaseModel):
|
||||||
|
@ -18,5 +18,5 @@ class SUserRegister(BaseModel):
|
||||||
date_of_birth: date
|
date_of_birth: date
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
orm_mode = True
|
from_attributes = True
|
||||||
|
|
||||||
|
|
|
@ -60,8 +60,10 @@ prometheus-fastapi-instrumentator==6.1.0
|
||||||
prompt-toolkit==3.0.43
|
prompt-toolkit==3.0.43
|
||||||
pyasn1==0.5.1
|
pyasn1==0.5.1
|
||||||
pycodestyle==2.11.1
|
pycodestyle==2.11.1
|
||||||
pydantic==1.10.14
|
pydantic==2.6.0
|
||||||
pydantic_core==2.14.6
|
pydantic-extra-types==2.5.0
|
||||||
|
pydantic-settings==2.1.0
|
||||||
|
pydantic_core==2.16.1
|
||||||
pyflakes==3.2.0
|
pyflakes==3.2.0
|
||||||
pyright==1.1.347
|
pyright==1.1.347
|
||||||
pytest==7.4.4
|
pytest==7.4.4
|
||||||
|
|
Loading…
Add table
Reference in a new issue