Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

misc: Make backend handle URLs with trailing slash #1059

Merged
merged 1 commit into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/endpoints/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
from endpoints.responses import MessageResponse
from endpoints.responses.oauth import TokenResponse
from exceptions.auth_exceptions import AuthCredentialsException, DisabledException
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi import Depends, HTTPException, Request, status
from fastapi.security.http import HTTPBasic
from handler.auth import auth_handler, oauth_handler
from handler.database import db_user_handler
from utils.router import APIRouter

ACCESS_TOKEN_EXPIRE_MINUTES: Final = 30
REFRESH_TOKEN_EXPIRE_DAYS: Final = 7
Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
CollectionNotFoundInDatabaseException,
CollectionPermissionError,
)
from fastapi import APIRouter, Request, UploadFile
from fastapi import Request, UploadFile
from handler.database import db_collection_handler
from handler.filesystem import fs_resource_handler
from handler.filesystem.base_handler import CoverSize
from logger.logger import log
from models.collection import Collection
from sqlalchemy.inspection import inspect
from utils.router import APIRouter

router = APIRouter()

Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
ConfigNotReadableException,
ConfigNotWritableException,
)
from fastapi import APIRouter, HTTPException, Request, status
from fastapi import HTTPException, Request, status
from logger.logger import log
from utils.router import APIRouter

router = APIRouter()

Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/feeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
TinfoilFeedSchema,
WebrcadeFeedSchema,
)
from fastapi import APIRouter, Request
from fastapi import Request
from handler.database import db_platform_handler, db_rom_handler
from models.rom import Rom
from utils.router import APIRouter

router = APIRouter()

Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/firmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
from decorators.auth import protected_route
from endpoints.responses import MessageResponse
from endpoints.responses.firmware import AddFirmwareResponse, FirmwareSchema
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, status
from fastapi import File, HTTPException, Request, UploadFile, status
from fastapi.responses import FileResponse
from handler.database import db_firmware_handler, db_platform_handler
from handler.filesystem import fs_firmware_handler
from handler.scan_handler import scan_firmware
from logger.logger import log
from utils.router import APIRouter

router = APIRouter()

Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/heartbeat.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
SCHEDULED_UPDATE_SWITCH_TITLEDB_CRON,
)
from endpoints.responses.heartbeat import HeartbeatResponse
from fastapi import APIRouter
from handler.database import db_user_handler
from handler.filesystem import fs_platform_handler
from handler.metadata.igdb_handler import IGDB_API_ENABLED
from handler.metadata.moby_handler import MOBY_API_ENABLED
from handler.metadata.sgdb_handler import STEAMGRIDDB_API_ENABLED
from utils import get_version
from utils.router import APIRouter

router = APIRouter()

Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
from endpoints.responses.platform import PlatformSchema
from exceptions.endpoint_exceptions import PlatformNotFoundInDatabaseException
from exceptions.fs_exceptions import PlatformAlreadyExistsException
from fastapi import APIRouter, Request
from fastapi import Request
from handler.database import db_platform_handler
from handler.filesystem import fs_platform_handler
from handler.metadata.igdb_handler import IGDB_PLATFORM_LIST
from handler.scan_handler import scan_platform
from logger.logger import log
from models.platform import Platform
from utils.router import APIRouter

router = APIRouter()

Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/raw.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from config import ASSETS_BASE_PATH
from decorators.auth import protected_route
from fastapi import APIRouter, Request
from fastapi import Request
from fastapi.responses import FileResponse
from utils.router import APIRouter

router = APIRouter()

Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@
)
from exceptions.endpoint_exceptions import RomNotFoundInDatabaseException
from exceptions.fs_exceptions import RomAlreadyExistsException
from fastapi import APIRouter, File, HTTPException, Query, Request, UploadFile, status
from fastapi import File, HTTPException, Query, Request, UploadFile, status
from fastapi.responses import FileResponse
from handler.database import db_platform_handler, db_rom_handler
from handler.filesystem import fs_resource_handler, fs_rom_handler
from handler.filesystem.base_handler import CoverSize
from handler.metadata import meta_igdb_handler, meta_moby_handler
from logger.logger import log
from stream_zip import NO_COMPRESSION_32, ZIP_AUTO, AsyncMemberFile, async_stream_zip
from utils.router import APIRouter

router = APIRouter()

Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/saves.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from decorators.auth import protected_route
from endpoints.responses import MessageResponse
from endpoints.responses.assets import SaveSchema, UploadedSavesResponse
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, status
from fastapi import File, HTTPException, Request, UploadFile, status
from handler.database import db_rom_handler, db_save_handler, db_screenshot_handler
from handler.filesystem import fs_asset_handler
from handler.scan_handler import scan_save
from logger.logger import log
from utils.router import APIRouter

router = APIRouter()

Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/screenshots.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from decorators.auth import protected_route
from endpoints.responses.assets import UploadedScreenshotsResponse
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, status
from fastapi import File, HTTPException, Request, UploadFile, status
from handler.database import db_rom_handler, db_screenshot_handler
from handler.filesystem import fs_asset_handler
from handler.scan_handler import scan_screenshot
from logger.logger import log
from utils.router import APIRouter

router = APIRouter()

Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/search.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import emoji
from decorators.auth import protected_route
from endpoints.responses.search import SearchCoverSchema, SearchRomSchema
from fastapi import APIRouter, HTTPException, Request, status
from fastapi import HTTPException, Request, status
from handler.database import db_rom_handler
from handler.metadata import meta_igdb_handler, meta_moby_handler, meta_sgdb_handler
from handler.metadata.igdb_handler import IGDB_API_ENABLED
from handler.metadata.moby_handler import MOBY_API_ENABLED
from handler.metadata.sgdb_handler import STEAMGRIDDB_API_ENABLED
from handler.scan_handler import _get_main_platform_igdb_id
from logger.logger import log
from utils.router import APIRouter

router = APIRouter()

Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/states.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from decorators.auth import protected_route
from endpoints.responses import MessageResponse
from endpoints.responses.assets import StateSchema, UploadedStatesResponse
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, status
from fastapi import File, HTTPException, Request, UploadFile, status
from handler.database import db_rom_handler, db_screenshot_handler, db_state_handler
from handler.filesystem import fs_asset_handler
from handler.scan_handler import scan_state
from logger.logger import log
from utils.router import APIRouter

router = APIRouter()

Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/stats.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from endpoints.responses.stats import StatsReturn
from fastapi import APIRouter
from handler.database import db_stats_handler
from utils.router import APIRouter

router = APIRouter()

Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/tasks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from decorators.auth import protected_route
from endpoints.responses import MessageResponse
from fastapi import APIRouter, Request
from fastapi import Request
from tasks.update_switch_titledb import update_switch_titledb_task
from utils.router import APIRouter

router = APIRouter()

Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
from endpoints.forms.identity import UserForm
from endpoints.responses import MessageResponse
from endpoints.responses.identity import UserSchema
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi import Depends, HTTPException, Request, status
from handler.auth import auth_handler
from handler.database import db_user_handler
from handler.filesystem import fs_asset_handler
from logger.logger import log
from models.user import Role, User
from utils.router import APIRouter

router = APIRouter()

Expand Down
3 changes: 2 additions & 1 deletion backend/handler/auth/tests/test_oauth.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import pytest
from decorators.auth import protected_route
from fastapi import APIRouter, Request
from fastapi import Request
from fastapi.exceptions import HTTPException
from handler.auth import oauth_handler
from handler.database import db_user_handler
from utils.router import APIRouter


def test_create_oauth_token():
Expand Down
35 changes: 35 additions & 0 deletions backend/utils/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from typing import Any, Callable

from fastapi import APIRouter as FastAPIRouter
from fastapi.types import DecoratedCallable


class APIRouter(FastAPIRouter):
"""FastAPI router that automatically adds an alternate route with a trailing slash.

This is needed as FastAPI does not include a built-in way to handle routes with and without
trailing slashes, without requiring a redirect or duplicating the route definition.

Reference: https://github.com/fastapi/fastapi/discussions/7298
"""

def api_route(
self, path: str, *, include_in_schema: bool = True, **kwargs: Any
) -> Callable[[DecoratedCallable], DecoratedCallable]:
if path.endswith("/") and len(path) > 1:
path = path[:-1]

add_path = super().api_route(
path, include_in_schema=include_in_schema, **kwargs
)

alternate_path = path + "/"
add_alternate_path = super().api_route(
alternate_path, include_in_schema=False, **kwargs
)

def decorator(func: DecoratedCallable) -> DecoratedCallable:
add_alternate_path(func)
return add_path(func)

return decorator
31 changes: 31 additions & 0 deletions backend/utils/test_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import itertools

import pytest
from fastapi import Request
from utils.router import APIRouter


@pytest.mark.parametrize(
"method, route_path",
itertools.product(
("get", "post", "put", "delete", "patch"),
("/test", "/test/"),
),
)
def test_route_path_with_trailing_slash(method, route_path):
router = APIRouter()

@router.get(route_path)
@router.post(route_path)
@router.put(route_path)
@router.delete(route_path)
@router.patch(route_path)
def test_route(request: Request):
return {"test": "test"}

assert test_route(Request({"type": "http", "method": method, "url": "/test"})) == {
"test": "test"
}
assert test_route(Request({"type": "http", "method": method, "url": "/test/"})) == {
"test": "test"
}
Loading