Skip to content

Commit 970040f

Browse files
committed
* Performance Optimized
1 parent 11772f8 commit 970040f

File tree

12 files changed

+518
-410
lines changed

12 files changed

+518
-410
lines changed

checker/src/checker.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
checker = Enochecker("onlyleveling", SERVICE_PORT)
3232

3333

34-
# enochecker_test -a localhost -p 12626 -A 192.168.178.91
34+
# enochecker_test -a localhost -p 12626 -A 192.168.178.58
3535

3636
def app():
3737
return checker.app
@@ -132,17 +132,6 @@ async def get_own_items(self, token, raise_on_error=True):
132132
return data["items"]
133133
return data
134134

135-
async def get_users(self):
136-
self.logger.info("[get_users]")
137-
resp = await self.client.get("/users/", timeout=self.timeout)
138-
139-
if resp.status_code != 200:
140-
raise MumbleException(f"Failed to get user list (status={resp.status_code})")
141-
try:
142-
return resp.json()
143-
except Exception as e:
144-
raise MumbleException(f"User list not valid JSON: {e}, response={resp.text}")
145-
146135
async def create_access_token(self, data: dict, seckey: str, expires_delta: timedelta = None):
147136
self.logger.info(f"[TOKEN] Using SECRET_KEY={seckey}")
148137
ALGORITHM = "HS256"
@@ -737,6 +726,7 @@ async def exploit_jwt(
737726
con = Connection(logger, client)
738727
logger.info("[exploit] Starting exploit attack SK")
739728

729+
flag_user = task.attack_info
740730
flag_user = task.attack_info
741731
logger.info(f"[exploit] Processing user: {flag_user}")
742732
keys = ['0', '00', '01', '10', '11']

service/backend/Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ WORKDIR /app
66
COPY requirements.txt .
77
RUN pip install --no-cache-dir -r requirements.txt
88
RUN apt-get clean && apt-get update && apt-get install -y netcat-openbsd sqlite3 && rm -rf /var/lib/apt/lists/*
9+
RUN pip install psycopg2-binary
10+
RUN pip install py-spy
911

1012
COPY generate_secret.sh .
1113

service/backend/auth.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@
22
from jose import jwt, JWTError
33
from datetime import datetime, timedelta
44
import os
5+
import anyio
56

67
SECRET_KEY = os.environ.get("SECRET_KEY")
78
ALGORITHM = "HS256"
89
ACCESS_TOKEN_EXPIRE_MINUTES = 30
910

10-
pwd_context = CryptContext(schemes=["sha256_crypt"], deprecated="auto")
11-
12-
11+
pwd_context = CryptContext(
12+
schemes=["sha256_crypt"],
13+
deprecated="auto",
14+
sha256_crypt__rounds=1000
15+
)
1316
def verify_password(plain_password, hashed_password):
1417
return pwd_context.verify(plain_password, hashed_password)
1518

16-
17-
def get_password_hash(password):
18-
return pwd_context.hash(password)
19-
19+
async def get_password_hash_async(password: str) -> str:
20+
return await anyio.to_thread.run_sync(pwd_context.hash, password)
2021

2122
def create_access_token(data: dict, expires_delta: timedelta = None):
2223
to_encode = data.copy()
@@ -28,7 +29,6 @@ def create_access_token(data: dict, expires_delta: timedelta = None):
2829
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
2930
return encoded_jwt
3031

31-
3232
def get_username_from_token(token: str):
3333
try:
3434
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])

service/backend/crud.py

Lines changed: 120 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,166 @@
1-
from sqlalchemy.orm import Session
1+
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
2+
from sqlalchemy.ext.asyncio import AsyncSession
3+
from sqlalchemy import select, update, func
4+
from sqlalchemy.orm import joinedload
25
import models
36
import schemas
4-
from auth import get_password_hash
7+
from auth import get_password_hash_async # <-- USE ASYNC HASH!
58
import datetime
6-
from sqlalchemy.orm import joinedload
7-
8-
9-
def get_user_by_username(db: Session, username: str):
10-
return db.query(models.User).filter(models.User.username == username).first()
119

10+
async def get_user_by_username(db: AsyncSession, username: str):
11+
result = await db.execute(
12+
select(models.User).where(models.User.username == username).options(joinedload(models.User.items))
13+
)
14+
return result.unique().scalar_one_or_none()
1215

13-
def create_user(db: Session, user: schemas.UserCreate, username_key: str):
16+
async def create_user(db: AsyncSession, user: schemas.UserCreate, username_key: str):
17+
hashed_password = await get_password_hash_async(user.password) # <--- ASYNC!
1418
db_user = models.User(
1519
username=user.username,
16-
hashed_password=get_password_hash(user.password),
20+
hashed_password=hashed_password,
1721
username_key=username_key
1822
)
1923
db.add(db_user)
20-
db.commit()
21-
db.refresh(db_user)
22-
return db_user
23-
24-
25-
def get_users(db: Session, skip: int = 0, limit: int = 10):
26-
return db.query(models.User).offset(skip).limit(limit).all()
27-
24+
try:
25+
await db.commit()
26+
await db.refresh(db_user)
27+
return db_user
28+
except IntegrityError:
29+
await db.rollback()
30+
raise ValueError("User already exists or invalid data.")
31+
except SQLAlchemyError as e:
32+
await db.rollback()
33+
raise e
34+
35+
async def get_users(db: AsyncSession, skip: int = 0, limit: int = 10):
36+
result = await db.execute(
37+
select(models.User)
38+
.offset(skip)
39+
.limit(limit)
40+
.options(joinedload(models.User.items))
41+
)
42+
return result.scalars().all()
2843

29-
def get_items_by_user(db: Session, user_id: int):
30-
user = db.query(models.User).options(joinedload(models.User.items)).filter(models.User.id == user_id).first()
44+
async def get_items_by_user(db: AsyncSession, user_id: int):
45+
result = await db.execute(
46+
select(models.User)
47+
.where(models.User.id == user_id)
48+
.options(joinedload(models.User.items))
49+
)
50+
user = result.unique().scalar_one_or_none()
3151
return user.items if user else []
3252

33-
def get_user_by_id(db: Session, user_id: int):
34-
return (
35-
db.query(models.User)
36-
.filter(models.User.id == user_id)
53+
async def get_user_by_id(db: AsyncSession, user_id: int):
54+
result = await db.execute(
55+
select(models.User)
56+
.where(models.User.id == user_id)
3757
.options(joinedload(models.User.items))
38-
.first()
3958
)
59+
return result.unique().scalar_one_or_none()
4060

41-
def update_item_note(db: Session, item_id: int, note: str):
42-
item = db.query(models.Item).filter(models.Item.id == item_id).first()
61+
async def update_item_note(db: AsyncSession, item_id: int, note: str):
62+
result = await db.execute(
63+
select(models.Item).where(models.Item.id == item_id)
64+
)
65+
item = result.scalar_one_or_none()
4366
if not item:
4467
return None
4568
if item.note and item.note.strip() != "":
4669
return "locked"
4770
item.note = note
48-
db.commit()
49-
db.refresh(item)
71+
try:
72+
await db.commit()
73+
await db.refresh(item)
74+
except SQLAlchemyError:
75+
await db.rollback()
76+
raise
5077
return item
5178

52-
53-
def add_item_to_user(db, user, item):
79+
async def add_item_to_user(db: AsyncSession, user, item):
5480
if item not in user.items:
5581
user.items.append(item)
56-
db.commit()
57-
db.refresh(user)
82+
try:
83+
await db.commit()
84+
await db.refresh(user)
85+
except SQLAlchemyError:
86+
await db.rollback()
87+
raise
5888
return item
5989

60-
61-
def create_dungeon(db: Session, dungeon: schemas.DungeonCreate):
90+
async def create_dungeon(db: AsyncSession, dungeon: schemas.DungeonCreate):
6291
db_dungeon = models.Dungeon(**dungeon.model_dump())
6392
db.add(db_dungeon)
64-
db.commit()
65-
db.refresh(db_dungeon)
93+
try:
94+
await db.commit()
95+
await db.refresh(db_dungeon)
96+
except SQLAlchemyError:
97+
await db.rollback()
98+
raise
6699
return db_dungeon
67100

101+
async def get_dungeon_by_id(db: AsyncSession, dungeon_id: int):
102+
result = await db.execute(
103+
select(models.Dungeon).where(models.Dungeon.id == dungeon_id)
104+
)
105+
return result.scalar_one_or_none()
68106

69-
def get_dungeon_by_id(db: Session, dungeon_id: int):
70-
return db.query(models.Dungeon).filter(models.Dungeon.id == dungeon_id).first()
71-
72-
73-
def get_dungeons(db: Session, skip: int = 0, limit: int = 100):
74-
return db.query(models.Dungeon).offset(skip).limit(limit).all()
107+
async def get_dungeons(db: AsyncSession, skip: int = 0, limit: int = 100):
108+
result = await db.execute(
109+
select(models.Dungeon).offset(skip).limit(limit)
110+
)
111+
return result.scalars().all()
75112

113+
async def record_completed_dungeon(db: AsyncSession, user_id: int, dungeon_id: int):
114+
result = await db.execute(
115+
select(models.CompletedDungeon).where(
116+
models.CompletedDungeon.user_id == user_id,
117+
models.CompletedDungeon.dungeon_id == dungeon_id
118+
)
119+
)
120+
db_completed_dungeon = result.scalar_one_or_none()
76121

77-
def record_completed_dungeon(db: Session, user_id: int, dungeon_id: int):
78-
db_completed_dungeon = db.query(models.CompletedDungeon).filter(
79-
models.CompletedDungeon.user_id == user_id,
80-
models.CompletedDungeon.dungeon_id == dungeon_id
81-
).first()
122+
now = datetime.datetime.now().isoformat()
82123

83124
if db_completed_dungeon:
84-
db_completed_dungeon.completion_timestamp = datetime.datetime.now().isoformat()
85-
db.commit()
86-
db.refresh(db_completed_dungeon)
125+
db_completed_dungeon.completion_timestamp = now
126+
await db.commit()
127+
await db.refresh(db_completed_dungeon)
87128
return db_completed_dungeon
88129
else:
89130
new_completed_dungeon = models.CompletedDungeon(
90131
user_id=user_id,
91132
dungeon_id=dungeon_id,
92-
completion_timestamp=datetime.datetime.now().isoformat()
133+
completion_timestamp=now
93134
)
94135
db.add(new_completed_dungeon)
95-
db.commit()
96-
db.refresh(new_completed_dungeon)
136+
await db.commit()
137+
await db.refresh(new_completed_dungeon)
97138
return new_completed_dungeon
98139

140+
async def get_user_completed_dungeons(db: AsyncSession, user_id: int):
141+
result = await db.execute(
142+
select(models.CompletedDungeon).where(models.CompletedDungeon.user_id == user_id)
143+
)
144+
return result.scalars().all()
99145

100-
def get_user_completed_dungeons(db: Session, user_id: int):
101-
return db.query(models.CompletedDungeon).filter(models.CompletedDungeon.user_id == user_id).all()
102-
103-
104-
def has_completed_dungeon(db: Session, user_id: int, dungeon_id: int) -> bool:
105-
return db.query(models.CompletedDungeon).filter(
106-
models.CompletedDungeon.user_id == user_id,
107-
models.CompletedDungeon.dungeon_id == dungeon_id
108-
).first() is not None
146+
async def has_completed_dungeon(db: AsyncSession, user_id: int, dungeon_id: int) -> bool:
147+
result = await db.execute(
148+
select(models.CompletedDungeon).where(
149+
models.CompletedDungeon.user_id == user_id,
150+
models.CompletedDungeon.dungeon_id == dungeon_id
151+
)
152+
)
153+
return result.scalar_one_or_none() is not None
154+
155+
async def create_item_for_user(db: AsyncSession, item: schemas.ItemCreate, user_id: int):
156+
db_item = models.Item(**item.model_dump())
157+
db.add(db_item)
158+
await db.commit()
159+
await db.refresh(db_item)
160+
user_result = await db.execute(select(models.User).where(models.User.id == user_id))
161+
user = user_result.scalar_one_or_none()
162+
if user:
163+
user.items.append(db_item)
164+
await db.commit()
165+
await db.refresh(user)
166+
return db_item

service/backend/database.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
1-
from sqlalchemy import create_engine
2-
from sqlalchemy.ext.declarative import declarative_base
3-
from sqlalchemy.orm import sessionmaker
1+
# database.py
2+
import os
3+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
4+
from sqlalchemy.orm import declarative_base
45

5-
SQLALCHEMY_DATABASE_URL = "sqlite:////app/db/sqlitedb.db"
6+
DATABASE_URL = "postgresql+asyncpg://backend:backend@backend-db:5432/backenddb"
67

7-
engine = create_engine(
8-
SQLALCHEMY_DATABASE_URL,
9-
connect_args={"check_same_thread": False},
10-
pool_size=20,
11-
max_overflow=30
8+
# Async Engine & Session
9+
engine = create_async_engine(
10+
DATABASE_URL,
11+
echo=True,
12+
pool_size=10, # this is important
13+
max_overflow=20, # and this too
1214
)
13-
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
15+
async_session = async_sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
1416

1517
Base = declarative_base()
18+
19+
# Dependency for FastAPI (yield!)
20+
async def get_async_db():
21+
async with async_session() as session:
22+
try:
23+
yield session
24+
finally:
25+
await session.close()
26+
27+
# (OPTIONAL) Table creation for Startup
28+
async def init_db():
29+
async with engine.begin() as conn:
30+
await conn.run_sync(Base.metadata.create_all)

service/backend/gunicorn.conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import multiprocessing
22

33
worker_class = "uvicorn.workers.UvicornWorker"
4-
workers = 2
4+
workers = 4
55
bind = "0.0.0.0:2626"
66
timeout = 90
77
keepalive = 3600

0 commit comments

Comments
 (0)