Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Ramchike committed Nov 19, 2024
2 parents df92b8f + 795d91a commit 477c78a
Show file tree
Hide file tree
Showing 54 changed files with 594 additions and 145 deletions.
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
DB_HOST=localhost
DB_HOST=db
DB_PORT=5432
DB_USER=user
DB_PASSWORD=password
Expand All @@ -11,3 +11,6 @@ TG_CHANNEL_LINK=https://t.me/channel

API_URL=http://127.0.0.1:8000
ROOT_PATH=/api

LOKI_URL=http://loki:3100/loki/api/v1/push
RABBIT_URL=amqp://rabbitmq:5672/
19 changes: 10 additions & 9 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ env:
BUILD_NAME: ${{ github.event.head_commit.message }}

jobs:
build:
runs-on: self-hosted
# build:
# runs-on: self-hosted

steps:
- name: Checkout
uses: actions/checkout@v4
# steps:
# - name: Checkout
# uses: actions/checkout@v4

- name: Build on github
run: docker compose build
# - name: Build on github
# run: docker compose build

build-and-run:
runs-on: self-hosted
needs: build
# needs: build

steps:
- name: Checkout
Expand Down Expand Up @@ -72,7 +72,8 @@ jobs:
fail-report:
runs-on: self-hosted
if: failure()
needs: [build, build-and-run]
# needs: [build, build-and-run]
needs: [build-and-run]

steps:
- name: Checkout
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ data/
/frontend/node_modules
/frontend/dist
frontend/tsconfig.app.tsbuildinfo
/.idea
frontend/tsconfig.app.tsbuildinfo
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Знакомства 2107

![image](https://github.com/user-attachments/assets/8b00b471-8251-4967-864a-5c6d6598bc94)<br>
**Telegram Web App** для поиска пары на вальс (и не только) для старшеклассников **ГБОУ Школа 2107**

## Настройка

Переименуйте `.env.example` в `.env` и обновите в нём следующие значения:
- **DB_HOST**, **DB_PORT**, **DB_USER**, **DB_PASSWORD**, **DB_NAME** замените на свои значения подключения к базе данных или поставьте на стандартные (например postgres, postgres, postgres), потому что порты БД не выведены в сеть и остаются внутри закрытой докер сети.
- **TG_TOKEN** - токен вашего ТГ бота
- **TG_ADMIN_CHAT** - ID закрытого админ-чата формата `-100XXXXXXXXXX`
- Поля, которые при необходимости можно удалить из .env
- **TG_CHANNEL_ID** - ID канала, подписка на который необходима для использования аппа. Формата `-100XXXXXXXXXX`
- **TG_CHANNEL_LINK** - ссылка на канал формата `t.me/channel`
- **API_URL** - Итоговый URL API сервиса *(https://example.com/api)*
- **ROOT_PATH** - Коренной путь для API, используемый после проксирования *(в случае если архитектура изменяться не будет, оставить на текущее)*
- **LOKI_URL** - на данный момент не используется
- **RABBIT_URL** - путь до RabbitMQ *(в случае если архитектура изменяться не будет, оставить на текущее)*

Бот должен быть добавлен в админ чат, выдача ему админ-прав необязательна, его команды будут работать только в этом чате
Бот также должен быть добавлен в канал, в случае если вы используете эту проверку (в противном случае строки, отвечающие за это, должны быть удалены из .env)

Архитектура подразумевает проксирование через внешний веб-сервер, например **Apache** или **Nginx**, поэтому необходимо настроить полное проксирование до `127.0.0.1:8022`, данный адрес при стандартной конфигурации будет работать только в локальной сети.
При необходимости можно настроить проксирование до сервиса **Grafana**, расположенного по адресу `127.0.0.1:8023`. В случае если в этом нет необходимости, объявление этого сервиса стоит удалить из `docker-compose.yaml`

## Запуск

`docker compose up -d --build` запустит все сервисы и приложение будет готово к работе, в случае если проксирование из пункта настройки было настроено.

## Credits

**Александр Замараев** - Backend разработчик, системный администратор, DevOps
- [Github](https://github.com/Zoom-Developer)
- [ВКонтакте](https://vk.com/zoom_developer)
- [Telegram](https://t.me/zoomdevs)

**Рамир Воробьёв** - Frontend разработчик, UI/UX дизайнер
- [Github](https://github.com/Ramchike)
- [ВКонтакте](https://vk.com/ramchike)
- [Telegram](https://t.me/ramchike)
2 changes: 1 addition & 1 deletion backend/entry.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
alembic upgrade head
gunicorn src.main:app --worker-class uvicorn.workers.UvicornWorker --workers 2 --bind "0.0.0.0:80" --access-logfile -
gunicorn src.main:app --worker-class uvicorn.workers.UvicornWorker --bind "0.0.0.0:80" --access-logfile -
Binary file modified backend/requirements.txt
Binary file not shown.
9 changes: 5 additions & 4 deletions backend/src/application/likes/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ async def answer_focus(self, user: User, status: bool) -> None:
else:
if status and user.focus_user.is_active:
await self.repo.insert(user, user.focus_user)
await TelegramService().send_message(
"🥰 Твоя анкета кому-то понравилась"
"\n⚡️ Скорее заходи в приложение и посмотри кто это!",
chat_id = user.focus_user.id
await TelegramService().send_media(
text = f"🥰 Твоя анкета понравилась {user.fullname} из {user.literal}"
f"\n⚡️ Скорее заходи в приложение и ответь {'ему' if user.male else 'ей'}! <i>(Анкета появится в ленте после текущей)</i>",
chat_id = user.focus_user.id,
files = [attachment.url for attachment in user.attachments]
)
await UserService().select_focus(user)

Expand Down
5 changes: 4 additions & 1 deletion backend/src/application/tg/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ async def send_to_chat(self, text: str) -> None:
await self.send_message(text, chat_id = -1)

async def send_media_to_chat(self, text: str, files: list[str]) -> None:
await self.send_media(-1, text, files)

async def send_media(self, chat_id: int, text: str, files: list[str]) -> None:
await broker.publish(
SendMediaTelegramMessage(chat_id = -1, text = text, files = files),
SendMediaTelegramMessage(chat_id = chat_id, text = text, files = files),
"tg_media"
)

Expand Down
4 changes: 1 addition & 3 deletions backend/src/application/user/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ async def register(self, userdata: dict, data: BaseUser, avatar: bytes) -> User:

async def select_focus(self, user: User) -> User | None:
if not user.is_active:
return

user.is_active = True
return None
user.focus_user = None
user.focus_is_liked = False
focus = await self.repo.get_noviewed(user)
Expand Down
10 changes: 5 additions & 5 deletions backend/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@
TG_CHANNEL_LINK = os.environ.get("TG_CHANNEL_LINK")
TG_API_URL = "https://api.telegram.org/bot" + TG_TOKEN

# ----- [[ TELEGRAM ]] -----

RABBIT_URL = os.environ.get("RABBIT_URL")

# ----- [[ SETTINGS ]] -----

MAX_AVATAR_SIZE = 1024 * 1024 * 3 # Bytes
Expand All @@ -37,6 +33,8 @@
"9Ф",
"9Г",
"9В",
"10П",
"10Ч",
"10К",
"10С",
"10Ю",
Expand Down Expand Up @@ -69,4 +67,6 @@
# ----- [[ OTHER ]] -----

API_URL = os.environ.get("API_URL")
ROOT_PATH = os.environ.get("ROOT_PATH", "")
ROOT_PATH = os.environ.get("ROOT_PATH", "")
RABBIT_URL = os.environ.get("RABBIT_URL")
LOKI_URL = os.environ.get("LOKI_URL")
7 changes: 4 additions & 3 deletions backend/src/domain/user/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@


class BaseUser(BaseModel):
name: str = Field(min_length=3, max_length=21, examples=["Иван"], description="Имя")
surname: str = Field(min_length=3, max_length=21, examples=["Иванов"], description="Фамилия")
name: str = Field(max_length=21, examples=["Иван"], description="Имя")
surname: str = Field(max_length=21, examples=["Иванов"], description="Фамилия")
desc: str = Field(max_length=300, examples=["Главный айтишник класса"], description="Описание")
literal: CLASS_LITERAL = Field(description="Класс")
male: bool = Field(description="Пол (мужчина или нет)")
Expand All @@ -19,13 +19,14 @@ class BaseUser(BaseModel):
def name_validator(val: str):
val = val.strip()
if " " in val:
raise HTTPException(422, "name and surname should be one word")
raise ValueError("name and surname should be one word")
return val


class UserDTO(BaseUser):
attachments: list[str] = Field(examples=[[f"{API_URL}/attachments/abcde1234567890"]], description="Вложения пользователя")
verify: bool = Field(description="Верифицирован-ли пользователь")
is_admin: bool = Field(description="Является ли пользователь модератором")

@field_validator("attachments", mode="before")
@classmethod
Expand Down
7 changes: 4 additions & 3 deletions backend/src/domain/user/repository.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sqlalchemy import func
from sqlalchemy import func, case
from sqlalchemy.orm import joinedload
from sqlmodel import desc, select
from src.config import CLASS_LITERAL
Expand All @@ -21,6 +21,7 @@ async def get_liked(self, user: User) -> User | None:
query = select(User) \
.join(Like, Like.user_id == User.id) \
.filter((Like.target_id == user.id) & (Like.is_mutually == False) & User.is_active) \
.order_by(Like.created_at) \
.limit(1)
res = await self.session.exec(query)
return res.first()
Expand All @@ -31,15 +32,15 @@ async def get_noviewed(self, user: User) -> User | None:
user.focus_is_liked = True
return liked
query = select(User).where(
(User.id != user.id) & (User.male != user.male) & User.is_active
(User.id != user.id) & (User.male != user.male) & (User.is_active)
& (~User.id.in_(
select(View.target_id).where(View.user_id == user.id).scalar_subquery()
))
& (~User.id.in_(
select(Like.target_id).where(Like.user_id == user.id).scalar_subquery()
))
) \
.order_by(func.random()) \
.order_by(func.random() * case((User.verify == True, 1), else_=0.5)) \
.limit(1)
res = await self.session.exec(query)
return res.first()
Expand Down
24 changes: 22 additions & 2 deletions backend/src/infrastructure/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
from fastapi import FastAPI, APIRouter, Request
from starlette.middleware.cors import CORSMiddleware
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
# from logging_loki import LokiQueueHandler
# from multiprocessing import Queue
import logging

from src.config import ROOT_PATH
from src.config import IS_PROD, LOKI_URL, ROOT_PATH
from src.infrastructure.db import get_session, CTX_SESSION
from src.infrastructure.exc import HTTPError
# from src.infrastructure.utils import PrometheusMiddleware, metrics

def create_app(
routers: list[APIRouter],
Expand Down Expand Up @@ -44,15 +47,32 @@ async def lifespan(app: FastAPI):
allow_origins = ["*"],
allow_methods = ["*"],
allow_headers = ["*"],
expose_headers = ["*"],
allow_credentials = True
)
app.add_middleware(
ProxyHeadersMiddleware,
trusted_hosts = ["*"] # Direct access not allowed to API
)

# app.add_middleware(PrometheusMiddleware, app_name="mt2107", exclude_paths=["/metrics", "/system/ping"])
# app.add_route("/metrics", metrics, include_in_schema=False)

app.add_exception_handler(HTTPError, HTTPError.handler)

logging.getLogger("uvicorn.access").addFilter(LoggingFilter(ignoring_log_endpoints))
logger = logging.getLogger("uvicorn.access")
logger.addFilter(LoggingFilter(ignoring_log_endpoints))
# if IS_PROD:
# loki_logs_handler = LokiQueueHandler(
# Queue(-1),
# url=LOKI_URL,
# tags={"application": "mt2107"}
# )
# logger.addHandler(loki_logs_handler)

logging.basicConfig(
format = '[%(asctime)s.%(msecs)03dZ] %(name)s %(levelname)s %(message)s'
)

@app.middleware("http")
async def session_middleware(request: Request, coro):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""is_admin
Revision ID: 89c117ff40e4
Revises: ffe8d61cff53
Create Date: 2024-11-18 00:04:07.064495
"""
from alembic import op
import sqlalchemy as sa
import sqlmodel


# revision identifiers, used by Alembic.
revision = '89c117ff40e4'
down_revision = 'ffe8d61cff53'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('is_admin', sa.Boolean(), server_default=sa.text('false'), nullable=False))
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'is_admin')
# ### end Alembic commands ###
1 change: 1 addition & 0 deletions backend/src/infrastructure/db/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class User(SQLModel, table=True):
)
focus_is_liked: bool = Field(sa_column_kwargs={"server_default": false()})
is_banned: bool = Field(sa_column_kwargs={"server_default": false()})
is_admin: bool = Field(sa_column_kwargs={"server_default": false()})
ban_reason: Optional[str]
created_at: datetime = Field(sa_column=Column(
TIMESTAMP(timezone=True),
Expand Down
3 changes: 2 additions & 1 deletion backend/src/infrastructure/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .models import *
from .models import *
from .grafana import *
Loading

0 comments on commit 477c78a

Please sign in to comment.