-
Notifications
You must be signed in to change notification settings - Fork 0
Build production-ready anonymous matchmaking backend (FastAPI, Redis, Postgres, Docker) #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/matchbot | ||
| REDIS_URL=redis://localhost:6379/0 | ||
| SECRET_KEY=change-me-in-prod | ||
| ACCESS_TOKEN_EXPIRE_MINUTES=1440 | ||
| MATCHMAKING_MIN_TRUST_SCORE=40 | ||
| TRUST_COOLDOWN_THRESHOLD=40 | ||
| TRUST_SUSPEND_THRESHOLD=20 | ||
| TRUST_PENALTY_ON_REPORT=20 | ||
| TRUST_REGEN_DAYS=3 | ||
| TRUST_REGEN_AMOUNT=5 | ||
| BASE_SESSION_MINUTES=15 | ||
| PREMIUM_SESSION_MINUTES=30 | ||
| PREMIUM_MATCHMAKING_BOOST=true | ||
| RATE_LIMIT_PER_MINUTE=60 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| FROM python:3.11-slim | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| ENV PYTHONDONTWRITEBYTECODE=1 | ||
| ENV PYTHONUNBUFFERED=1 | ||
|
|
||
| COPY requirements.txt . | ||
| RUN pip install --no-cache-dir -r requirements.txt | ||
|
|
||
| COPY . . | ||
|
|
||
| EXPOSE 8000 | ||
| CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,28 +1,166 @@ | ||
| # Decrypt | ||
| ### [+] Created By HTR-TECH (@***tahmid.rayat***) | ||
| ### [+] Disclaimer : | ||
| ***Decrypter is a tool to decrypt Encrypted Bash Scripts into a Readable Format.This Tool is created for Educational Purpose only.I am not responsible for any misuse of this tool.*** | ||
| # Anonymous Matchmaking Chat Bot (FastAPI) | ||
|
|
||
| <img src="https://raw.githubusercontent.com/htr-tech/release-download/master/images/decrypter.png" alt="" border="0" /> | ||
| Production-oriented anonymous matchmaking backend with JWT auth, Redis queue matchmaking, trust/reporting logic, ad delivery, premium controls, and admin APIs. | ||
|
|
||
| ### [+] Installation | ||
| ```apt update``` | ||
| ## 1) Project Structure | ||
|
|
||
| ```apt install git python2 -y``` | ||
| ```text | ||
| . | ||
| ├── app | ||
| │ ├── api | ||
| │ │ ├── admin.py | ||
| │ │ ├── auth.py | ||
| │ │ ├── match.py | ||
| │ │ ├── user.py | ||
| │ │ └── ws.py | ||
| │ ├── core | ||
| │ │ ├── config.py | ||
| │ │ ├── deps.py | ||
| │ │ ├── redis_client.py | ||
| │ │ └── security.py | ||
| │ ├── db | ||
| │ │ ├── base.py | ||
| │ │ └── session.py | ||
| │ ├── models | ||
| │ │ └── models.py | ||
| │ ├── schemas | ||
| │ │ ├── auth.py | ||
| │ │ └── common.py | ||
| │ ├── services | ||
| │ │ ├── ads.py | ||
| │ │ ├── maintenance.py | ||
| │ │ ├── matchmaking.py | ||
| │ │ └── trust.py | ||
| │ ├── websocket | ||
| │ │ └── manager.py | ||
| │ └── main.py | ||
| ├── alembic | ||
| │ ├── env.py | ||
| │ └── versions | ||
| │ └── 0001_initial.py | ||
| ├── scripts | ||
| │ └── maintenance.py | ||
| ├── .env.example | ||
| ├── Dockerfile | ||
| ├── docker-compose.yml | ||
| ├── requirements.txt | ||
| └── alembic.ini | ||
| ``` | ||
|
|
||
| ## 2) Core Features Included | ||
|
|
||
| - Daily verification via admin-managed token (`/admin/daily-token`, `/user/verify`) | ||
| - User profile fields: gender, preferred gender, trust score, premium, account status | ||
| - Matchmaking using Redis waiting queue with trust/gender/block checks | ||
| - Anonymous WebSocket chat per room (`/ws/chat/{room_id}?token=JWT`) | ||
| - Report flow that stores reports, penalizes trust, blocks pairing | ||
| - Ads served after session close for non-premium users | ||
| - Premium activation endpoint (`/user/premium/activate`) | ||
| - Admin APIs for stats, reports, trust distribution, ad CRUD, blocked pairs | ||
| - Rate limiting per IP via Redis | ||
| - JWT authentication + admin RBAC | ||
|
|
||
| ## 3) Database Models | ||
|
|
||
| Implemented SQLAlchemy models: | ||
| - `Users` | ||
| - `ChatRooms` | ||
| - `Messages` | ||
| - `Reports` | ||
| - `BlockedPairs` | ||
| - `Ads` | ||
| - `DailyVerificationToken` | ||
| - `UserVerification` | ||
|
|
||
| See: `app/models/models.py`. | ||
|
|
||
| ## 4) Configuration Reference | ||
|
|
||
| ```git clone https://github.com/hax0rtahm1d/decrypt``` | ||
| All key business rules are configurable in `app/core/config.py` (or via env vars): | ||
|
|
||
| ```cd decrypt``` | ||
| - **Trust thresholds / penalties / regen** | ||
| - `TRUST_COOLDOWN_THRESHOLD` | ||
| - `TRUST_SUSPEND_THRESHOLD` | ||
| - `TRUST_PENALTY_ON_REPORT` | ||
| - `TRUST_REGEN_DAYS` | ||
| - `TRUST_REGEN_AMOUNT` | ||
| - **Daily token behavior** | ||
| - Token value set each day by admin endpoint: `POST /admin/daily-token` | ||
| - User verification recorded in `user_verifications` table (`/user/verify`) | ||
| - **Ad logic** | ||
| - Selection & impression update in `app/services/ads.py` | ||
| - Delivery after chat close in `POST /match/rooms/{room_id}/close` | ||
| - **Premium benefits** | ||
| - `PREMIUM_MATCHMAKING_BOOST` (priority queue) | ||
| - `PREMIUM_SESSION_MINUTES` (longer sessions) | ||
| - No ads for premium users in room close handler | ||
|
|
||
| ```python2 dec.py``` | ||
| ## 5) Local Run Instructions | ||
|
|
||
| ### Or, Use Single Command | ||
| ### Prerequisites | ||
| - Python 3.11+ | ||
| - PostgreSQL | ||
| - Redis | ||
|
|
||
| ### Setup | ||
|
|
||
| ```bash | ||
| python -m venv .venv | ||
| source .venv/bin/activate | ||
| pip install -r requirements.txt | ||
| cp .env.example .env | ||
| ``` | ||
|
|
||
| ### Migrate DB | ||
|
|
||
| ```bash | ||
| alembic upgrade head | ||
| ``` | ||
| apt update && apt install git python2 -y && git clone https://github.com/hax0rtahm1d/decrypt && cd decrypt && python2 dec.py | ||
|
|
||
| ### Start API | ||
|
|
||
| ```bash | ||
| uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 | ||
| ``` | ||
|
|
||
| ## [+] Find Me on : | ||
| [](https://github.com/htr-tech) | ||
| [](https://www.instagram.com/tahmid.rayat) | ||
| [](https://m.me/tahmid.rayat.official) | ||
| Health check: `GET /health` | ||
|
|
||
| ## 6) Docker Deployment Instructions | ||
|
|
||
| ### Build & Run | ||
|
|
||
| ```bash | ||
| docker compose up --build | ||
| ``` | ||
|
|
||
| ### Run migrations in API container | ||
|
|
||
| ```bash | ||
| docker compose exec api alembic upgrade head | ||
| ``` | ||
|
|
||
| App available at `http://localhost:8000`. | ||
|
|
||
| ## 7) API Highlights | ||
|
|
||
| - `POST /auth/register` | ||
| - `POST /auth/login` | ||
| - `POST /user/verify` | ||
| - `POST /match/find` | ||
| - `POST /match/rooms/{room_id}/report` | ||
| - `POST /match/rooms/{room_id}/close` | ||
| - `GET /admin/stats/users` | ||
| - `GET /admin/stats/chats` | ||
| - `GET /admin/reports` | ||
| - `POST /admin/ads`, `PATCH /admin/ads/{ad_id}`, `DELETE /admin/ads/{ad_id}` | ||
| - `POST /admin/daily-token` | ||
| - `WS /ws/chat/{room_id}?token=<JWT>` | ||
|
|
||
| ## 8) Operational Notes | ||
|
|
||
| - Schedule `python scripts/maintenance.py` every minute (cron/Celery/worker) to: | ||
| - Auto-close expired rooms | ||
| - Release completed cooldown/suspension windows | ||
| - Apply trust regeneration checks | ||
| - Use HTTPS and rotate `SECRET_KEY` in production. | ||
| - Add centralized logging, tracing, and audit logs before going live. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| [alembic] | ||
| script_location = alembic | ||
| sqlalchemy.url = postgresql+asyncpg://postgres:postgres@db:5432/matchbot | ||
|
|
||
| [loggers] | ||
| keys = root,sqlalchemy,alembic | ||
|
|
||
| [handlers] | ||
| keys = console | ||
|
|
||
| [formatters] | ||
| keys = generic | ||
|
|
||
| [logger_root] | ||
| level = WARN | ||
| handlers = console | ||
|
|
||
| [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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| from logging.config import fileConfig | ||
|
|
||
| from alembic import context | ||
| from sqlalchemy import engine_from_config, pool | ||
|
|
||
| from app.db.base import Base | ||
| from app.models import models # noqa | ||
|
|
||
| config = context.config | ||
| if config.config_file_name is not None: | ||
| fileConfig(config.config_file_name) | ||
|
|
||
| target_metadata = Base.metadata | ||
|
|
||
|
|
||
| def run_migrations_offline() -> None: | ||
| url = config.get_main_option("sqlalchemy.url") | ||
| context.configure(url=url, target_metadata=target_metadata, literal_binds=True) | ||
| with context.begin_transaction(): | ||
| context.run_migrations() | ||
|
|
||
|
|
||
| def run_migrations_online() -> None: | ||
| connectable = engine_from_config( | ||
| config.get_section(config.config_ini_section, {}), | ||
| 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() | ||
|
Comment on lines
+23
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Check alembic.ini for the database URL configuration
fd 'alembic.ini' --exec cat {}Repository: huzely/Decrypt Length of output: 614 🏁 Script executed: # Also verify the current state of alembic/env.py to confirm it matches the review
fd 'alembic/env.py' -x head -40 {}Repository: huzely/Decrypt Length of output: 40 🏁 Script executed: # Display the full content of alembic/env.py to verify current state
cat -n alembic/env.py | head -50Repository: huzely/Decrypt Length of output: 1397 🏁 Script executed: # Also check imports to confirm the current driver setup
grep -n "engine_from_config\|async_engine_from_config\|import asyncio" alembic/env.pyRepository: huzely/Decrypt Length of output: 148
The Update to the async migration pattern using Async-compatible online migration from logging.config import fileConfig
from alembic import context
-from sqlalchemy import engine_from_config, pool
+from sqlalchemy import pool
+from sqlalchemy.ext.asyncio import async_engine_from_config
+import asyncio
from app.db.base import Base
-from app.models import models # noqa
+from app.models import models # noqa: F401
config = context.config
if config.config_file_name is not None:
fileConfig(config.config_file_name)
target_metadata = Base.metadata
def run_migrations_offline() -> None:
url = config.get_main_option("sqlalchemy.url")
context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
with context.begin_transaction():
context.run_migrations()
-def run_migrations_online() -> None:
- connectable = engine_from_config(
+async def run_async_migrations() -> None:
+ connectable = async_engine_from_config(
config.get_section(config.config_ini_section, {}),
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()
+ async with connectable.connect() as connection:
+ await connection.run_sync(do_run_migrations)
+ await connectable.dispose()
+
+
+def do_run_migrations(connection) -> None:
+ context.configure(connection=connection, target_metadata=target_metadata)
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+def run_migrations_online() -> None:
+ asyncio.run(run_async_migrations())
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()Also clarify the noqa directive on line 7 to 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| if context.is_offline_mode(): | ||
| run_migrations_offline() | ||
| else: | ||
| run_migrations_online() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
|
||
|
|
||
| # 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() -> None: | ||
| ${upgrades if upgrades else "pass"} | ||
|
|
||
|
|
||
| def downgrade() -> None: | ||
| ${downgrades if downgrades else "pass"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Alembic environment still uses the synchronous
engine_from_configpath even thoughalembic.iniis configured withpostgresql+asyncpg. With asyncpg URLs,alembic upgrade headrequires the async migration pattern (async_engine_from_configwithrun_sync), so migrations cannot run successfully with the current setup.Useful? React with 👍 / 👎.