Skip to content

Commit

Permalink
pagination & docs
Browse files Browse the repository at this point in the history
  • Loading branch information
peterrauscher committed Mar 3, 2024
1 parent beaa5d0 commit 0ea73a2
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 19 deletions.
37 changes: 30 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,41 @@
# SoleSearch: Ingest

[Read the Docs](https://api.solesearch.io/docs)

[OpenAPI Schema](https://api.solesearch.io/openapi.json)

# Endpoints

# Filtering
## `/sneakers/{product_id}`

# Sorting
Returns a single sneaker found by the specified product_id. Product IDs are guaranteed to be unique in the SoleSearch database.

By default, all queries are sorted in reverse chronological order by release date. You can sort on other fields using the `sort` query paramter.
## `/sneakers/sku/{sku}`

Returns a single sneaker found by the specified SKU.

SKU format differs by manufacturer, however, some brands may have overlapping SKUs. To ensure you find the correct product, either use the `/sneakers/product_id` endpoint, or specify the `brand` in the query params. Example:

`/sneakers/sku/ABCDEF-123?brand=Nike`

## Query Parameters
## `/sneakers`

### sort
### Filtering

The `sort` query parameter allows you to specify the field by which you want to sort the results of your query.
By default, all products in the database are returned when calling `/sneakers`. You can filter with the following query parameters:

- brand
- name
- audience
- colorway
- releaseDate
- released

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.

### Sorting

By default, all queries are sorted in reverse chronological order by release date. You can sort on other fields using the `sort` query paramter.

#### Sortable Fields:

Expand All @@ -31,7 +54,7 @@ GET /sneakers?sort=price

Returns results starting from the highest retail price.

### sortOrder
#### sortOrder

The `sortOrder` query parameter is used in conjunction with the `sort` parameter to determine the direction of the sorting. It allows you to specify whether you want the results to be sorted in ascending or descending order. If not specified, results are sorted in descending order.

Expand Down
46 changes: 34 additions & 12 deletions src/api/routes/sneakers.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,49 @@
from datetime import UTC, datetime
import os
from datetime import UTC, datetime
from typing import Annotated

from core.models.details import Audience
from core.models.shoes import Sneaker
from fastapi import APIRouter, HTTPException, Query
from fastapi import APIRouter, HTTPException, Query, Request

from api.data.models import PaginatedSneakersResponse, SortKey, SortOrder
from api.util import url_for_query

router = APIRouter(
prefix="/sneakers",
)

MAX_LIMIT = int(os.environ.get("SOLESEARCH_MAX_LIMIT", 0))
MAX_LIMIT = int(os.environ.get("SOLESEARCH_MAX_LIMIT", 100))
DEFAULT_LIMIT = int(os.environ.get("SOLESEARCH_DEFAULT_LIMIT", 20))


@router.get("/")
async def get_sneakers(
request: Request,
brand: str | None = None,
name: str | None = None,
colorway: str | None = None,
audience: Audience | None = None,
releaseDate: str | None = None,
released: bool | None = None,
sort: SortKey = SortKey.RELEASE_DATE,
order: SortOrder = SortOrder.DESCENDING,
page: Annotated[int | None, Query(gte=1)] = None,
pageSize: Annotated[int | None, Query(gte=1, lte=MAX_LIMIT)] = None,
):
) -> PaginatedSneakersResponse:
query = Sneaker.find()
if not page:
page = 1
if not pageSize:
pageSize = DEFAULT_LIMIT
if brand:
query = query.find(Sneaker.brand == brand)
query = query.find({"brand": {"$regex": f"^{brand}", "$options": "i"}})
if name:
query = query.find(Sneaker.name == name)
query = query.find({"name": {"$regex": f"^{name}", "$options": "i"}})
if colorway:
query = query.find({"colorway": {"$regex": f"^{colorway}", "$options": "i"}})
if audience:
query = query.find(Sneaker.audience == audience)
query = query.find({"audience": {"$regex": f"^{audience.value}"}})
if released is not None:
now = datetime.now(UTC)
if released:
Expand All @@ -61,15 +66,32 @@ async def get_sneakers(
date_obj = datetime.strptime(releaseDate, "%Y-%m-%d")
query = query.find(Sneaker.releaseDate == date_obj)
total_count = await query.count()
print(total_count)
order = "+" if order == SortOrder.ASCENDING else "-"
sneakers_list = (
await query.sort(f"{order}{sort.value}")
params = dict(request.query_params)
if total_count > page * pageSize:
params["page"] = page + 1
next_page = url_for_query(request, "get_sneakers", **params)
else:
next_page = None
if page > 1:
params["page"] = page - 1
previous_page = url_for_query(request, "get_sneakers", **params)
else:
previous_page = None
items = (
await query.sort(f"{'+' if order == SortOrder.ASCENDING else '-'}{sort.value}")
.skip((page - 1) * pageSize)
.limit(pageSize)
.to_list()
)
return sneakers_list
result = PaginatedSneakersResponse(
total=total_count,
page=page,
pageSize=pageSize,
nextPage=next_page,
previousPage=previous_page,
items=items,
)
return result


@router.get("/{product_id}")
Expand Down
10 changes: 10 additions & 0 deletions src/api/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from urllib.parse import urlencode, urlparse, urlunparse

from fastapi import Request


def url_for_query(request: Request, name: str, **params: str) -> str:
url = str(request.url_for(name))
parsed = urlparse(url)
parsed = parsed._replace(query=urlencode(params))
return urlunparse(parsed)

0 comments on commit 0ea73a2

Please sign in to comment.