Skip to content

Commit 8b74ce5

Browse files
committed
projections for faster queries, text index for faster searching
1 parent 543c921 commit 8b74ce5

6 files changed

Lines changed: 87 additions & 22 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ By default, all products in the database are returned when calling `/sneakers`.
3131
- releaseDate
3232
- released
3333

34-
Filtering is case insensitive, but the search will start from the beginning of each field. For example, a search `/sneakers?brand=jordan` will return all sneakers with a brand starting with `Jordan`, `jordan`, `JOrdan`, etc... but a search `/sneakers?brand=ordan` will not find any `Jordan`s.
34+
Filtering is case insensitive, but the search will always start at the beginning of each field. For example, a search `/sneakers?brand=jordan` will return all sneakers with a brand starting with `Jordan`, `jordan`, `JORdan`, etc... but a search `/sneakers?brand=ordan` will not find any `Jordan` brand sneakers. For this reason, those needing more comprehensive "search" functionality should use the `/search` endpoint.
3535

3636
### Sorting
3737

src/api/data/models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from enum import Enum
22
from typing import List
33

4+
from beanie import Document
45
from pydantic import BaseModel
56

6-
from beanie import Document
7-
from core.models.shoes import Sneaker
7+
from core.models.shoes import Sneaker, SneakerView
88

99

1010
class Token(Document):
@@ -21,7 +21,7 @@ class PaginatedSneakersResponse(BaseModel):
2121
pageSize: int
2222
nextPage: str | None
2323
previousPage: str | None
24-
items: List[Sneaker]
24+
items: List[SneakerView]
2525

2626

2727
class SortKey(str, Enum):

src/api/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
load_dotenv(os.path.join(os.getcwd(), ".env"))
1111

1212
from beanie import init_beanie
13-
from core.models.shoes import Sneaker
1413
from fastapi import FastAPI
1514
from fastapi.middleware.cors import CORSMiddleware
1615
from mangum import Mangum
1716
from starlette.middleware.sessions import SessionMiddleware
1817

1918
from api.data.instance import DATABASE_NAME, client
2019
from api.routes import auth, sneakers
20+
from core.models.shoes import Sneaker
2121

2222
desc = """
2323
### The Bloomberg Terminal of Sneakers
@@ -30,7 +30,7 @@
3030
redoc_url=None, # Disable redoc, keep only swagger
3131
title="SoleSearch",
3232
version=__version__,
33-
contact={"name": "SoleSearch Developer Support", "email": "support@solesearch.io"},
33+
contact={"name": "SoleSearch Emails Support", "email": "support@solesearch.io"},
3434
description=desc,
3535
responses={404: {"description": "Not found"}}, # Custom 404 page
3636
)

src/api/routes/search.py

Whitespace-only changes.

src/api/routes/sneakers.py

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import os
22
from datetime import UTC, datetime
3-
import os
43
from typing import Annotated
54

6-
from core.models.details import Audience
7-
from core.models.shoes import Sneaker
85
from fastapi import APIRouter, HTTPException, Query, Request
96

107
from api.data.models import PaginatedSneakersResponse, SortKey, SortOrder
118
from api.util import url_for_query
9+
from core.models.details import Audience
10+
from core.models.shoes import Sneaker, SneakerView
1211

1312
router = APIRouter(
1413
prefix="/sneakers",
@@ -21,16 +20,79 @@
2120
@router.get("/")
2221
async def get_sneakers(
2322
request: Request,
24-
brand: str | None = None,
25-
name: str | None = None,
26-
colorway: str | None = None,
27-
audience: Audience | None = None,
28-
releaseDate: str | None = None,
29-
released: bool | None = None,
30-
sort: SortKey = SortKey.RELEASE_DATE,
31-
order: SortOrder = SortOrder.DESCENDING,
32-
page: Annotated[int | None, Query(gte=1)] = None,
33-
pageSize: Annotated[int | None, Query(gte=1, lte=MAX_LIMIT)] = None,
23+
brand: Annotated[
24+
str | None,
25+
Query(
26+
title="Brand", description="Filter by the brand of the shoes.", min_length=3
27+
),
28+
] = None,
29+
name: Annotated[
30+
str | None,
31+
Query(
32+
title="Product Name",
33+
description="Filter by the name of the shoes.",
34+
min_length=3,
35+
),
36+
] = None,
37+
colorway: Annotated[
38+
str | None,
39+
Query(
40+
title="Colorway",
41+
description="Filter by the colorway of the shoes.",
42+
min_length=3,
43+
),
44+
] = None,
45+
audience: Annotated[
46+
Audience | None,
47+
Query(
48+
title="Audience",
49+
description="Filter on the gender/audience of the shoes. See Audience for possible values.",
50+
min_length=3,
51+
),
52+
] = None,
53+
releaseDate: Annotated[
54+
str | None,
55+
Query(
56+
title="Release Date",
57+
description="Filter by the release date of the shoes. Can be a specific date or an inequality. Operators are (lt, lte, gt, gte). Example usage: lt:2021-01-01",
58+
),
59+
] = None,
60+
released: Annotated[
61+
bool | None,
62+
Query(
63+
title="Released?",
64+
description="Filter by whether the shoes have been released or not. Overrides any filter on releaseDate if set.",
65+
),
66+
] = None,
67+
sort: Annotated[
68+
SortKey,
69+
Query(
70+
title="Sort By",
71+
description="The field to sort by.",
72+
),
73+
] = SortKey.RELEASE_DATE,
74+
order: Annotated[
75+
SortOrder,
76+
Query(
77+
title="Sort Order",
78+
description="The order to sort in based on the sort key.",
79+
),
80+
] = SortOrder.DESCENDING,
81+
page: Annotated[
82+
int | None,
83+
Query(
84+
gte=1, title="Page Number", description="The page number of the result set."
85+
),
86+
] = None,
87+
pageSize: Annotated[
88+
int | None,
89+
Query(
90+
gte=1,
91+
lte=MAX_LIMIT,
92+
title="Page Size",
93+
description=f"The number of items on each page. Must be in the range [1-{MAX_LIMIT}] (inclusive).",
94+
),
95+
] = None,
3496
) -> PaginatedSneakersResponse:
3597
query = Sneaker.find()
3698
if not page:
@@ -44,7 +106,9 @@ async def get_sneakers(
44106
if colorway:
45107
query = query.find({"colorway": {"$regex": f"^{colorway}", "$options": "i"}})
46108
if audience:
47-
query = query.find({"audience": {"$regex": f"^{audience.value}"}})
109+
query = query.find(
110+
{"audience": {"$regex": f"^{audience.value}", "$options": "i"}}
111+
)
48112
if released is not None:
49113
now = datetime.now(UTC)
50114
if released:
@@ -82,6 +146,7 @@ async def get_sneakers(
82146
await query.sort(f"{'+' if order == SortOrder.ASCENDING else '-'}{sort.value}")
83147
.skip((page - 1) * pageSize)
84148
.limit(pageSize)
149+
.project(SneakerView)
85150
.to_list()
86151
)
87152
result = PaginatedSneakersResponse(

src/api/util.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from fastapi import Request
44

55

6-
def url_for_query(request: Request, name: str, **params: str) -> str:
7-
url = str(request.url_for(name))
6+
def url_for_query(request: Request, fastapi_function_name: str, **params: str) -> str:
7+
url = str(request.url_for(fastapi_function_name))
88
parsed = urlparse(url)
99
parsed = parsed._replace(query=urlencode(params))
1010
return urlunparse(parsed)

0 commit comments

Comments
 (0)