Skip to content
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

Project structure refactoring #233

Merged
merged 27 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b6f4335
moving core methods to models
RuslanUC Sep 2, 2024
c38955e
Merge branch 'master' into move-core-functions-into-models
RuslanUC Sep 3, 2024
b7e83a5
move all methods that had one usage to usage place
RuslanUC Sep 3, 2024
7940a72
move login and register
RuslanUC Sep 4, 2024
f658d83
move some channel, guild member and role methods
RuslanUC Sep 4, 2024
8d84fb2
move guild creation methods, channel-message related methods
RuslanUC Sep 5, 2024
688f4af
move guild methods;
RuslanUC Sep 5, 2024
d8a8145
refactor errors
RuslanUC Sep 5, 2024
159a34a
move relationship, some channel and messages methods
RuslanUC Sep 6, 2024
a2c45e0
move more guild methods
RuslanUC Sep 6, 2024
832cbb0
move more guild methods
RuslanUC Sep 7, 2024
42f55e1
move more guild methods;
RuslanUC Sep 12, 2024
547d5e2
move email verification methods
RuslanUC Sep 13, 2024
6fc2125
rewrite getMutualGuildsJ using one query
RuslanUC Sep 14, 2024
4a7267b
add poethepoet;
RuslanUC Oct 12, 2024
aa7e28f
move geoip code to GeoIp class;
RuslanUC Oct 12, 2024
17be32d
move captcha to utils submodule
RuslanUC Oct 12, 2024
96891cc
move gifs to utils submodule
RuslanUC Oct 12, 2024
7c71d92
move singleton to utils submodule
RuslanUC Oct 12, 2024
fb45739
move the rest of classes to utils submodule
RuslanUC Oct 12, 2024
5f42177
some methods/arguments renaming in storage classes
RuslanUC Oct 12, 2024
05208c1
some warnings fix
RuslanUC Oct 12, 2024
eb11ef4
add readstate updating
RuslanUC Oct 15, 2024
023fe43
remove core.py;
RuslanUC Oct 22, 2024
515d02e
fix poe in github actions?
RuslanUC Oct 22, 2024
27ea985
dont use poe in gh actions yet
RuslanUC Oct 22, 2024
0cf1dea
fix s3/ftp not being installed in gh actions
RuslanUC Oct 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .coveragerc

This file was deleted.

11 changes: 6 additions & 5 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ jobs:
- name: Check out repository code
uses: actions/checkout@v4

- name: Install Poetry
run: pipx install poetry

- name: Setup Python
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install Poetry
uses: snok/install-poetry@v1
cache: "poetry"

- name: Install dependencies
run: poetry install --no-interaction
run: poetry install --no-interaction --all-extras

- name: Shutdown Ubuntu MySQL
if: matrix.database == 'mariadb' || matrix.database == 'mysql'
Expand Down
2,180 changes: 1,212 additions & 968 deletions poetry.lock

Large diffs are not rendered by default.

45 changes: 34 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,33 @@ yepcord = "yepcord.cli:main"
python = "^3.9"
quart = "0.19.6"
aiofiles = "^24.1.0"
websockets = "13.0.1"
uvicorn = "^0.30.6"
websockets = "^13.1"
uvicorn = "^0.32.0"
python-magic = "^0.4.27"
pillow = "^10.4.0"
pillow = "^11.0.0"
protobuf = "4.25.3"
python-dateutil = "^2.9.0.post0"
cryptography = "^43.0.0"
cryptography = "^43.0.3"
emoji = "^2.12.1"
bcrypt = "^4.2.0"
quart-schema = "0.20.0"
pydantic = "^2.8.2"
werkzeug = "3.0.4"
aioftp = "^0.22.3"
orjson = "^3.10.7"
aioftp = { version = "^0.23.1", optional = true }
orjson = "^3.10.9"
mailers = {version = "^3.0.5", extras = ["smtp"]}
redis = "^5.0.8"
click = "^8.1.7"
maxminddb = "^2.6.2"
wget = "3.2"
tortoise-orm = {extras = ["aiosqlite", "asyncmy", "accel"], version = "^0.21.6"}
uvloop = "^0.20.0"
tortoise-orm = {extras = ["aiosqlite", "asyncmy", "accel"], version = "^0.21.7"}
uvloop = "^0.21.0"
async-timeout = "^4.0.3"
aerich = "^0.7.2"
yc-protobuf3-to-dict = "^0.3.0"
s3lite = "^0.1.8"
fast-depends = "^2.4.11"
faststream = {extras = ["kafka", "nats", "rabbit", "redis"], version = "^0.5.20"}
s3lite = { version = "^0.1.8", optional = true }
fast-depends = "^2.4.12"
faststream = {extras = ["kafka", "nats", "rabbit", "redis"], version = "^0.5.28"}

[tool.poetry.group.dev.dependencies]
pytest = "^8.2.0"
Expand All @@ -75,7 +75,30 @@ pyftpdlib = "1.5.8"
fake-s3 = "1.0.2"
types-protobuf = "^4.24.0.4"
pytest-httpx = "^0.30.0"
poethepoet = "^0.29.0"

[tool.poetry.extras]
s3 = ["s3lite"]
ftp = ["aioftp"]

[tool.poetry.group.profiling.dependencies]
viztracer = "^0.16.3"
pyinstrument = "^5.0.0"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.coverage.run]
omit = ["yepcord/yepcord/proto.py"]
data_file = "coverage.coverage"

[tool.poe.tasks]
migrate = "yepcord.cli migrate"
run = "yepcord.cli:main"
test = "pytest -x --cov-report=xml --cov-append --disable-warnings --cov=yepcord/yepcord"
i = "poetry install"
ie = "poetry install --all-extras"
u = "poetry update"
a = "poetry add"
ad = "poetry add --group dev"
2 changes: 1 addition & 1 deletion tests/api/test_applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from yepcord.rest_api.main import app
from yepcord.yepcord.snowflake import Snowflake
from .utils import TestClientType, create_users, create_application
from ..yep_image import YEP_IMAGE
from ..utils import register_app_error_handler
from ..yep_image import YEP_IMAGE

register_app_error_handler(app)

Expand Down
14 changes: 3 additions & 11 deletions tests/api/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import pytest_asyncio

from yepcord.rest_api.main import app
from yepcord.yepcord.utils.jwt import JWT
from yepcord.yepcord.config import Config
from yepcord.yepcord.snowflake import Snowflake
from yepcord.yepcord.utils import b64decode, b64encode
Expand All @@ -25,15 +26,6 @@ async def setup_db():
await app.ensure_async(func)()


def generateEmailVerificationToken(user_id: int, email: str, key: bytes):
key = new(key, str(user_id).encode('utf-8'), sha256).digest()
t = int(time())
sig = b64encode(new(key, f"{user_id}:{email}:{t}".encode('utf-8'), sha256).digest())
token = b64encode(dumps({"id": user_id, "email": email, "time": t}))
token += f".{sig}"
return token


@pt.mark.asyncio
async def test_login_nonexistent_user():
client: TestClientType = app.test_client()
Expand Down Expand Up @@ -109,15 +101,15 @@ async def test_verify_email():
client: TestClientType = app.test_client()
user = (await create_users(client, 1))[0]

token = generateEmailVerificationToken(int(user["id"]), user["email"], b64decode(Config.KEY))
token = JWT.encode({"id": int(user["id"]), "email": user["email"]}, b64decode(Config.KEY), expires_after=600)

resp = await client.post("/api/v9/auth/verify", json={"token": ""})
assert resp.status_code == 400
resp = await client.post("/api/v9/auth/verify", json={'token': "1"})
assert resp.status_code == 400

resp = await client.post("/api/v9/auth/verify", json={'token': token})
assert resp.status_code == 200
assert resp.status_code == 200, await resp.get_json()
json = await resp.get_json()
assert json["token"]
assert json["user_id"] == user["id"]
Expand Down
4 changes: 2 additions & 2 deletions tests/api/test_guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pytest_asyncio

from yepcord.rest_api.main import app
from yepcord.yepcord.classes.other import MFA
from yepcord.yepcord.utils.mfa import MFA
from yepcord.yepcord.enums import ChannelType
from yepcord.yepcord.snowflake import Snowflake
from tests.api.utils import TestClientType, create_users, create_guild, create_invite, enable_mfa, create_guild_channel, \
Expand Down Expand Up @@ -306,7 +306,7 @@ async def test_delete_guild():
resp = await client.post(f"/api/v9/guilds/{guild['id']}/delete", headers=headers1, json={"code": "wrong"})
assert resp.status_code == 400

resp = await client.post(f"/api/v9/guilds/{guild['id']}/delete", headers=headers1, json={"code": mfa.getCode()})
resp = await client.post(f"/api/v9/guilds/{guild['id']}/delete", headers=headers1, json={"code": mfa.get_code()})
assert resp.status_code == 204


Expand Down
19 changes: 10 additions & 9 deletions tests/api/test_mfa.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
import pytest_asyncio

from yepcord.rest_api.main import app
from yepcord.yepcord.classes.other import MFA, JWT
from yepcord.yepcord.utils.jwt import JWT
from yepcord.yepcord.config import Config
from yepcord.yepcord.utils import b64decode
from yepcord.yepcord.utils.mfa import MFA
from .utils import TestClientType, create_users, enable_mfa
from ..utils import register_app_error_handler

Expand All @@ -23,7 +24,7 @@ async def setup_db():
def generateMfaVerificationKey(nonce: str, mfa_key: str, key: bytes):
if not (payload := JWT.decode(nonce, key + b64decode(mfa_key))):
return
token = JWT.encode({"code": payload["code"]}, key)
token = JWT.encode({"code": payload["c"]}, key)
signature = token.split(".")[2]
return signature.replace("-", "").replace("_", "")[:8].upper()

Expand Down Expand Up @@ -58,21 +59,21 @@ async def test_mfa_enable():
secret = "a" * 16
mfa = MFA(secret, 0)

code = mfa.getCode()
code = mfa.get_code()
invalid_code = (code + str((int(code[-1]) + 1) % 10))[1:]
resp = await client.post("/api/v9/users/@me/mfa/totp/enable", headers=headers,
json={'code': invalid_code, 'secret': secret, 'password': user["password"]})
assert resp.status_code == 400

resp = await client.post("/api/v9/users/@me/mfa/totp/enable", headers=headers,
json={'code': mfa.getCode(), 'secret': secret, 'password': user["password"]})
json={'code': mfa.get_code(), 'secret': secret, 'password': user["password"]})
assert resp.status_code == 200
json = await resp.get_json()
assert json["token"]
user["token"] = json["token"]
headers = {"Authorization": user["token"]}
resp = await client.post("/api/v9/users/@me/mfa/totp/enable", headers=headers,
json={'code': mfa.getCode(), 'secret': secret, 'password': user["password"]})
json={'code': mfa.get_code(), 'secret': secret, 'password': user["password"]})
assert resp.status_code == 404

check_codes(json["backup_codes"], user_id)
Expand Down Expand Up @@ -146,7 +147,7 @@ async def test_login_with_mfa():
assert json["mfa"]
assert (ticket := json["ticket"])

code = mfa.getCode()
code = mfa.get_code()
invalid_code = (code + str((int(code[-1]) + 1) % 10))[1:]

resp = await client.post('/api/v9/auth/mfa/totp', json={"ticket": "", "code": ""}) # No ticket
Expand All @@ -158,7 +159,7 @@ async def test_login_with_mfa():
resp = await client.post('/api/v9/auth/mfa/totp', json={"ticket": ticket, "code": invalid_code}) # Invalid code
assert resp.status_code == 400

resp = await client.post('/api/v9/auth/mfa/totp', json={"ticket": ticket, "code": mfa.getCode()})
resp = await client.post('/api/v9/auth/mfa/totp', json={"ticket": ticket, "code": mfa.get_code()})
assert resp.status_code == 200
json = await resp.get_json()
assert json["token"]
Expand All @@ -178,12 +179,12 @@ async def test_disable_mfa():
resp = await client.post("/api/v9/users/@me/mfa/totp/disable", headers=headers, json={'code': ""})
assert resp.status_code == 400

resp = await client.post("/api/v9/users/@me/mfa/totp/disable", headers=headers, json={'code': mfa.getCode()})
resp = await client.post("/api/v9/users/@me/mfa/totp/disable", headers=headers, json={'code': mfa.get_code()})
assert resp.status_code == 200
json = await resp.get_json()
assert json["token"]
user["token"] = json["token"]
headers = {"Authorization": user["token"]}

resp = await client.post("/api/v9/users/@me/mfa/totp/disable", headers=headers, json={'code': mfa.getCode()})
resp = await client.post("/api/v9/users/@me/mfa/totp/disable", headers=headers, json={'code': mfa.get_code()})
assert resp.status_code == 404
33 changes: 32 additions & 1 deletion tests/api/test_user_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import pytest_asyncio

from yepcord.rest_api.main import app
from .utils import TestClientType, create_users, create_guild
from yepcord.yepcord.enums import ChannelType
from .utils import TestClientType, create_users, create_guild, create_invite
from ..yep_image import YEP_IMAGE
from ..utils import register_app_error_handler

Expand Down Expand Up @@ -67,6 +68,7 @@ async def test_get_my_profile():
assert resp.status_code == 200
json = await resp.get_json()
assert json["user"]["id"] == user_id
assert len(json["mutual_guilds"]) > 0


@pt.mark.asyncio
Expand Down Expand Up @@ -166,3 +168,32 @@ async def test_hypesquad_change_house():

resp = await client.post("/api/v9/hypesquad/online", headers=headers, json={'house_id': 4})
assert resp.status_code == 400


@pt.mark.asyncio
async def test_get_other_profile():
client: TestClientType = app.test_client()
user, user2 = await create_users(client, 2)
guild = await create_guild(client, user, "Test Guild")
headers = {"Authorization": user["token"]}
channel = [channel for channel in guild["channels"] if channel["type"] == ChannelType.GUILD_TEXT][0]
invite = await create_invite(client, user, channel["id"])

resp = await client.post(f"/api/v9/invites/{invite['code']}", headers={"Authorization": user2["token"]})
assert resp.status_code == 200

resp = await client.get(f"/api/v9/users/{user2['id']}/profile?with_mutual_guilds=true", headers=headers)
assert resp.status_code == 200
json = await resp.get_json()
assert json["user"]["id"] == user2["id"]
assert len(json["mutual_guilds"]) > 0
assert json["mutual_guilds"] == [{"id": guild["id"], "nick": None}]

resp = await client.patch(f"/api/v9/guilds/{guild['id']}/members/{user2['id']}", headers=headers,
json={"nick": "TEST"})
assert resp.status_code == 200

resp = await client.get(f"/api/v9/users/{user2['id']}/profile?with_mutual_guilds=true", headers=headers)
assert resp.status_code == 200
json = await resp.get_json()
assert json["mutual_guilds"] == [{"id": guild["id"], "nick": "TEST"}]
13 changes: 8 additions & 5 deletions tests/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@
from contextlib import asynccontextmanager
from datetime import datetime, timedelta
from hashlib import sha256
from typing import Optional, Union
from typing import Optional, Union, Awaitable, Callable

from quart.typing import TestWebsocketConnectionProtocol

from yepcord.rest_api.main import app as _app
from yepcord.yepcord.classes.other import MFA
from yepcord.yepcord.utils.mfa import MFA
from yepcord.yepcord.enums import ChannelType, GatewayOp
from yepcord.yepcord.snowflake import Snowflake
from yepcord.yepcord.utils import getImage
from tests.yep_image import YEP_IMAGE

TestClientType = _app.test_client_class

#User.y.hash_password = lambda password, user_id: password
#User.check_password = lambda self, password: self.password == password


async def create_user(app: TestClientType, email: str, password: str, username: str, *, exp_code: int=200) -> Optional[str]:
response = await app.post('/api/v9/auth/register', json={
Expand Down Expand Up @@ -55,7 +58,7 @@ async def create_users(app: TestClientType, count: int = 1) -> list[dict]:

async def enable_mfa(app: TestClientType, user: dict, mfa: MFA) -> None:
resp = await app.post("/api/v9/users/@me/mfa/totp/enable", headers={"Authorization": user["token"]},
json={"code": mfa.getCode(), "secret": mfa.key, "password": user["password"]})
json={"code": mfa.get_code(), "secret": mfa.key, "password": user["password"]})
assert resp.status_code == 200, (resp.status_code, await resp.get_json())
json = await resp.get_json()
assert json["token"]
Expand Down Expand Up @@ -390,7 +393,7 @@ def __init__(self, token: str):
self.heartbeatTask: Optional[asyncio.Task] = None
self.mainTask: Optional[asyncio.Task] = None

self.handlers = {
self.handlers: dict[int, Callable[[TestWebsocketConnectionProtocol, Optional[dict]], Awaitable[None]]] = {
GatewayOp.HELLO: self.handle_hello
}
self.listeners: list[GatewayClient.EventListener] = []
Expand All @@ -405,7 +408,7 @@ async def run(self, ws: TestWebsocketConnectionProtocol, task=True) -> None:
return

while self.running:
msg = await ws.receive_json()
msg: dict = await ws.receive_json()
if msg["op"] in self.handlers:
await self.handlers[msg["op"]](ws, msg.get("data"))

Expand Down
Loading
Loading