Skip to content

Commit 86e685f

Browse files
feat: split lua into own files (#619)
* add * Delete test.py * feat: split lua codes into own file
1 parent 05ca8c7 commit 86e685f

File tree

8 files changed

+106
-63
lines changed

8 files changed

+106
-63
lines changed

src/backend/app/guards/rate_limit.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,7 @@
44
from starlette.requests import HTTPConnection
55

66
from app.deps import RedisDep
7-
8-
LUA_RATELIMIT = """
9-
local key = KEYS[1]
10-
local now = tonumber(ARGV[1])
11-
local window = tonumber(ARGV[2])
12-
local limit = tonumber(ARGV[3])
13-
local clear_before = now - window
14-
15-
redis.call('ZREMRANGEBYSCORE', key, 0, clear_before)
16-
local amount = redis.call('ZCARD', key)
17-
18-
if amount < limit then
19-
redis.call('ZADD', key, now, now)
20-
redis.call('EXPIRE', key, window)
21-
return 0
22-
else
23-
return 1
24-
end
25-
"""
7+
from app.lua import rate_limit
268

279

2810
async def rate_limiter_guard(request: HTTPConnection, redis_client: RedisDep):
@@ -41,7 +23,7 @@ async def rate_limiter_guard(request: HTTPConnection, redis_client: RedisDep):
4123
key = f"rl:{user_id}:{endpoint.__name__}:{window}"
4224

4325
is_limited = await redis_client.eval(
44-
LUA_RATELIMIT, 1, key, str(now), str(window), str(limit)
26+
rate_limit.code, 1, key, str(now), str(window), str(limit)
4527
) # type: ignore[misc]
4628

4729
if is_limited:

src/backend/app/lua/__init__.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from pathlib import Path
2+
from typing import Final
3+
4+
BASE_DIR: Final[Path] = Path(__file__).parent
5+
6+
7+
class LuaModule:
8+
"""Typed representation of a loaded Lua file."""
9+
10+
__slots__ = ("__file__", "code")
11+
12+
def __init__(self, file: Path, code: str) -> None:
13+
self.__file__: str = str(file)
14+
self.code: str = code
15+
16+
def __repr__(self) -> str:
17+
return f"<LuaModule file={self.__file__!r}>"
18+
19+
20+
def _load_lua_file(path: Path) -> LuaModule:
21+
return LuaModule(path, path.read_text(encoding="utf-8"))
22+
23+
24+
# Load modules and export to globals
25+
_modules: dict[str, LuaModule] = {
26+
file.stem: _load_lua_file(file) for file in BASE_DIR.glob("*.lua")
27+
}
28+
29+
globals().update(_modules)
30+
31+
32+
def get(name: str) -> LuaModule:
33+
"""Type-safe access to Lua modules."""
34+
return _modules[name]

src/backend/app/lua/__init__.pyi

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class LuaModule:
2+
__file__: str
3+
code: str
4+
5+
json_remove_file_by_key: LuaModule
6+
json_remove_upload_by_key: LuaModule
7+
json_update_uploaded_bytes_by_key: LuaModule
8+
rate_limit: LuaModule
9+
10+
__all__: list[str]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
local raw = redis.call('JSON.GET', KEYS[1], '$.files')
2+
if not raw then return nil end
3+
local outer = cjson.decode(raw)
4+
local files = outer[1]
5+
for i, f in ipairs(files) do
6+
if f['key'] == ARGV[1] then
7+
local removed = cjson.encode(f)
8+
redis.call('JSON.DEL', KEYS[1], '$.files[' .. (i-1) .. ']')
9+
return removed
10+
end
11+
end
12+
return nil
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
local raw = redis.call('JSON.GET', KEYS[1], '$.active_uploads')
2+
if not raw then return nil end
3+
local outer = cjson.decode(raw)
4+
local uploads = outer[1]
5+
for i, u in ipairs(uploads) do
6+
if u['upload_key'] == ARGV[1] then
7+
redis.call('JSON.DEL', KEYS[1], '$.active_uploads[' .. (i-1) .. ']')
8+
return 1
9+
end
10+
end
11+
return nil
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
local raw = redis.call('JSON.GET', KEYS[1], '$.active_uploads')
2+
if not raw then return nil end
3+
local outer = cjson.decode(raw)
4+
local uploads = outer[1]
5+
for i, u in ipairs(uploads) do
6+
if u['upload_key'] == ARGV[1] then
7+
redis.call('JSON.SET', KEYS[1], '$.active_uploads[' .. (i-1) .. '].uploaded_bytes', ARGV[2])
8+
return 1
9+
end
10+
end
11+
return nil

src/backend/app/lua/rate_limit.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
local key = KEYS[1]
2+
local now = tonumber(ARGV[1])
3+
local window = tonumber(ARGV[2])
4+
local limit = tonumber(ARGV[3])
5+
local clear_before = now - window
6+
7+
redis.call('ZREMRANGEBYSCORE', key, 0, clear_before)
8+
local amount = redis.call('ZCARD', key)
9+
10+
if amount < limit then
11+
redis.call('ZADD', key, now, now)
12+
redis.call('EXPIRE', key, window)
13+
return 0
14+
else
15+
return 1
16+
end

src/backend/app/states/room.py

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
import uuid
55
from datetime import datetime, timedelta, timezone
66

7+
from app.lua import (
8+
json_remove_file_by_key,
9+
json_remove_upload_by_key,
10+
json_update_uploaded_bytes_by_key,
11+
)
712
from app.schemas.reverse import (
813
ActiveUpload,
914
AddHostOut,
@@ -199,21 +204,7 @@ async def remove_file(cls, room_id: str, file_key: str) -> RoomFileEntry | None:
199204
"""
200205
key = _room_key(room_id)
201206
client = cls._client()
202-
script = """
203-
local raw = redis.call('JSON.GET', KEYS[1], '$.files')
204-
if not raw then return nil end
205-
local outer = cjson.decode(raw)
206-
local files = outer[1]
207-
for i, f in ipairs(files) do
208-
if f['key'] == ARGV[1] then
209-
local removed = cjson.encode(f)
210-
redis.call('JSON.DEL', KEYS[1], '$.files[' .. (i-1) .. ']')
211-
return removed
212-
end
213-
end
214-
return nil
215-
"""
216-
result = await client.eval(script, 1, key, file_key) # type: ignore[misc]
207+
result = await client.eval(json_remove_file_by_key.code, 1, key, file_key) # type: ignore[misc]
217208
if result is None:
218209
return None
219210

@@ -260,38 +251,14 @@ async def update_active_upload(
260251
cls, room_id: str, upload_key: str, uploaded_bytes: int
261252
) -> None:
262253
key = _room_key(room_id)
263-
script = """
264-
local raw = redis.call('JSON.GET', KEYS[1], '$.active_uploads')
265-
if not raw then return nil end
266-
local outer = cjson.decode(raw)
267-
local uploads = outer[1]
268-
for i, u in ipairs(uploads) do
269-
if u['upload_key'] == ARGV[1] then
270-
redis.call('JSON.SET', KEYS[1], '$.active_uploads[' .. (i-1) .. '].uploaded_bytes', ARGV[2])
271-
return 1
272-
end
273-
end
274-
return nil
275-
"""
276-
await cls._client().eval(script, 1, key, upload_key, uploaded_bytes) # type:ignore[misc]
254+
await cls._client().eval(
255+
json_update_uploaded_bytes_by_key.code, 1, key, upload_key, uploaded_bytes
256+
) # type:ignore[misc]
277257

278258
@classmethod
279259
async def remove_active_upload(cls, room_id: str, upload_key: str) -> None:
280260
key = _room_key(room_id)
281-
script = """
282-
local raw = redis.call('JSON.GET', KEYS[1], '$.active_uploads')
283-
if not raw then return nil end
284-
local outer = cjson.decode(raw)
285-
local uploads = outer[1]
286-
for i, u in ipairs(uploads) do
287-
if u['upload_key'] == ARGV[1] then
288-
redis.call('JSON.DEL', KEYS[1], '$.active_uploads[' .. (i-1) .. ']')
289-
return 1
290-
end
291-
end
292-
return nil
293-
"""
294-
await cls._client().eval(script, 1, key, upload_key) # type:ignore[misc]
261+
await cls._client().eval(json_remove_upload_by_key.code, 1, key, upload_key) # type:ignore[misc]
295262

296263
@classmethod
297264
async def client_online(cls, room_id: str, client_id: str, is_host: bool) -> None:

0 commit comments

Comments
 (0)