Skip to content

Commit ae9de28

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents 6efe32f + 5d55b4f commit ae9de28

File tree

4 files changed

+45
-23
lines changed

4 files changed

+45
-23
lines changed

mystbin/backend/models/responses.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,16 @@ class Config:
9999
}
100100

101101

102-
class PasteGetAllResponse(BaseModel):
102+
class PasteGetAll(BaseModel):
103103
id: str
104-
loc: int
105-
charcount: int
104+
author_id: int
106105
created_at: datetime
106+
views: int
107107
expires: Optional[datetime] = None
108108
has_password: bool
109109

110+
class PasteGetAllResponse(BaseModel):
111+
pastes: List[PasteGetAll]
110112

111113
class TokenResponse(BaseModel):
112114
token: str

mystbin/backend/routers/pastes.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
__config = json.load(__f)
6363

6464
del __p, __f # micro-opt, don't keep unneeded variables in-ram
65+
HAS_BUNNYCDN = bool(__config["bunnycdn"]["token"])
6566

6667

6768
def generate_paste_id(n: int = 3):
@@ -134,6 +135,15 @@ async def find_discord_tokens(request: MystbinRequest, pastes: payloads.PastePut
134135
return tokens or None
135136

136137

138+
def respect_dnt(request: MystbinRequest):
139+
if request.headers.get("DNT", None) == "1":
140+
return "DNT"
141+
142+
if request.app.config["paste"]["log_ip"]:
143+
return request.headers.get("X-Forwarded-For", request.client.host)
144+
145+
return None
146+
137147
desc = f"""Post a paste.
138148
139149
This endpoint falls under the `postpastes` ratelimit bucket.
@@ -177,9 +187,7 @@ async def put_pastes(
177187
expires=payload.expires,
178188
author=author,
179189
password=payload.password,
180-
origin_ip=request.headers.get("x-forwarded-for", request.client.host)
181-
if request.app.config["paste"]["log_ip"]
182-
else None,
190+
origin_ip=respect_dnt(request)
183191
)
184192

185193
paste["notice"] = notice
@@ -217,7 +225,7 @@ async def post_rich_paste(
217225

218226
paste_id = generate_paste_id()
219227

220-
if images:
228+
if images and HAS_BUNNYCDN:
221229
async def _partial(target, spool: UploadFile):
222230
data = await spool.read()
223231
await request.app.state.client.put(target, data=data, headers=headers) # TODO figure out how to pass spooled object instead of load into memory
@@ -260,9 +268,7 @@ async def _partial(target, spool: UploadFile):
260268
expires=payload.expires,
261269
author=author,
262270
password=payload.password,
263-
origin_ip=request.headers.get("x-forwarded-for", request.client.host)
264-
if request.app.config["paste"]["log_ip"]
265-
else None,
271+
origin_ip=respect_dnt(request)
266272
)
267273

268274
paste["notice"] = notice
@@ -317,6 +323,7 @@ async def get_paste(request: MystbinRequest, paste_id: str, password: Optional[s
317323
response_model=List[responses.PasteGetAllResponse],
318324
responses={
319325
200: {"model": Optional[List[responses.PasteGetAllResponse]]},
326+
400: {"content": {"application/json": {"example": {"error": "You have provided an invalid query argument"}}}},
320327
401: {"model": errors.Unauthorized},
321328
},
322329
name="Get user pastes",
@@ -325,13 +332,17 @@ async def get_paste(request: MystbinRequest, paste_id: str, password: Optional[s
325332
@limit("getpaste")
326333
async def get_all_pastes(
327334
request: MystbinRequest,
328-
limit: Optional[int] = None,
335+
limit: int = 50,
336+
page: int = 1
329337
) -> Union[UJSONResponse, Dict[str, List[Dict[str, str]]]]:
330338
user = request.state.user
331339
if not user:
332340
return UJSONResponse({"error": "Unathorized", "notice": "You must be signed in to use this route"}, status_code=401)
341+
342+
if limit < 1 or page < 1:
343+
return UJSONResponse({"error": "limit and page must be greater than 1"}, status_code=400)
333344

334-
pastes = await request.app.state.db.get_all_user_pastes(user["id"], limit)
345+
pastes = await request.app.state.db.get_all_user_pastes(user["id"], limit, page)
335346
pastes = [dict(entry) for entry in pastes]
336347

337348
return UJSONResponse({"pastes": jsonable_encoder(pastes)})
@@ -450,7 +461,7 @@ async def delete_paste(request: MystbinRequest, paste_id: str) -> Union[UJSONRes
450461
403: {"model": errors.Forbidden},
451462
},
452463
status_code=200,
453-
name="Delete multiple pastes",
464+
name="Bulk delete pastes",
454465
description=desc,
455466
)
456467
@limit("deletepaste")
@@ -490,18 +501,23 @@ async def delete_pastes(
490501
"/documents",
491502
tags=["pastes"],
492503
deprecated=True,
493-
response_description='{"key": "string"}',
504+
responses={
505+
200: {"application/json": {"example": {"key": "string"}, "description": "A key containing the slug for your paste"}},
506+
400: {"application/json": {"example": {"error": "You have provided an invalid paste body"}}}
507+
},
494508
name="Hastebin create paste",
495509
description=desc,
496510
)
497511
@limit("postpastes")
498512
async def compat_create_paste(request: MystbinRequest):
499513
content = await request.body()
514+
limit = request.app.config["paste"]["character_limit"]
515+
if len(content) > limit:
516+
return UJSONResponse({"error": f"body: file size exceeds character limit of {limit}"}, status_code=400)
517+
500518
paste: Record = await request.app.state.db.put_paste(
501519
paste_id=generate_paste_id(),
502520
pages=[payloads.PasteFile(filename="file.txt", content=content.decode("utf8"))],
503-
origin_ip=request.headers.get("x-forwarded-for", request.client.host)
504-
if request.app.config["paste"]["log_ip"]
505-
else None,
521+
origin_ip=respect_dnt(request)
506522
)
507523
return UJSONResponse({"key": paste["id"]})

mystbin/backend/routers/user.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131

3232
@router.get(
33-
"/users/me",
33+
"/users/@me",
3434
tags=["users"],
3535
response_model=responses.User,
3636
responses={

mystbin/backend/utils/db.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ async def _do_query(
118118
except asyncio.TimeoutError:
119119
return None
120120
else:
121+
return response
122+
finally:
121123
if not conn:
122124
await self.pool.release(_conn)
123125

124-
return response
125-
126126
@wrapped_hook_callback
127127
async def get_all_pastes(self, page: int, count: int, reverse=False) -> List[Dict[str, Any]]:
128128
"""
@@ -388,7 +388,7 @@ async def set_paste_password(self, paste_id: str, password: str | None) -> Optio
388388
return None
389389

390390
@wrapped_hook_callback
391-
async def get_all_user_pastes(self, author_id: Optional[int], limit: Optional[int] = None) -> List[asyncpg.Record]:
391+
async def get_all_user_pastes(self, author_id: Optional[int], limit: int, page: int) -> List[asyncpg.Record]:
392392
"""Get all pastes for an author and/or with a limit.
393393
Parameters
394394
------------
@@ -403,15 +403,19 @@ async def get_all_user_pastes(self, author_id: Optional[int], limit: Optional[in
403403
The potential list of pastes.
404404
"""
405405
query = """
406-
SELECT id, author_id, created_at,
406+
SELECT id, author_id, created_at, views, expires
407407
CASE WHEN password IS NOT NULL THEN true ELSE false END AS has_password
408408
FROM pastes
409409
WHERE author_id = $1
410410
ORDER BY created_at DESC
411411
LIMIT $2
412+
OFFSET $3
412413
"""
413414

414-
response = await self._do_query(query, author_id, limit)
415+
assert page > 1 and limit > 1, ValueError("limit and page cannot be smaller than 1")
416+
response = await self._do_query(query, author_id, limit, page - 1)
417+
if not response:
418+
return []
415419

416420
return response
417421

0 commit comments

Comments
 (0)