Skip to content

Commit

Permalink
collections get_roms endpoint added
Browse files Browse the repository at this point in the history
  • Loading branch information
zurdi15 committed Jul 2, 2024
1 parent ac6975a commit 7324f9a
Show file tree
Hide file tree
Showing 32 changed files with 726 additions and 195 deletions.
3 changes: 2 additions & 1 deletion backend/alembic/versions/0022_collections_.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def upgrade() -> None:
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("name", sa.String(length=400), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("logo_path", sa.String(length=1000), nullable=True),
sa.Column("path_cover_l", sa.String(length=1000), nullable=True),
sa.Column("path_cover_s", sa.String(length=1000), nullable=True),
sa.Column("roms", sa.JSON(), nullable=False),
sa.Column("user_id", sa.Integer(), nullable=False),
sa.Column("is_public", sa.Boolean(), nullable=False),
Expand Down
26 changes: 18 additions & 8 deletions backend/endpoints/collections.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from decorators.auth import protected_route
from endpoints.responses import MessageResponse
from endpoints.responses.collection import CollectionSchema
from exceptions.endpoint_exceptions import CollectionNotFoundInDatabaseException
from exceptions.endpoint_exceptions import (
CollectionAlreadyExistsException,
CollectionNotFoundInDatabaseException,
)
from fastapi import APIRouter, Request
from handler.database import db_collection_handler
from handler.filesystem import fs_collection_handler
from logger.logger import log
from models.collection import Collection

router = APIRouter()

Expand All @@ -22,11 +25,18 @@ async def add_collection(request: Request) -> CollectionSchema:
"""

data = await request.json()
cleaned_data = {}
cleaned_data["name"] = data["name"]
cleaned_data["description"] = data["description"]
cleaned_data["user_id"] = request.user.id
return fs_collection_handler.add_collection(cleaned_data)
cleaned_data = {
"name": data["name"],
"description": data["description"],
"user_id": request.user.id,
}
collection_db = db_collection_handler.get_collection_by_name(
cleaned_data["name"], request.user.id
)
if collection_db:
raise CollectionAlreadyExistsException(cleaned_data["name"])
collection = Collection(**cleaned_data)
return db_collection_handler.add_collection(collection)


@protected_route(router.get, "/collections", ["collections.read"])
Expand All @@ -41,7 +51,7 @@ def get_collections(request: Request) -> list[CollectionSchema]:
list[CollectionSchema]: List of collections
"""

return db_collection_handler.get_collections()
return db_collection_handler.get_collections(user_id=request.user.id)


@protected_route(router.get, "/collections/{id}", ["collections.read"])
Expand Down
3 changes: 1 addition & 2 deletions backend/endpoints/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ async def add_platforms(request: Request) -> PlatformSchema:
except PlatformAlreadyExistsException:
log.info(f"Detected platform: {fs_slug}")
scanned_platform = scan_platform(fs_slug, [fs_slug])
platform = db_platform_handler.add_platform(scanned_platform)
return platform
return db_platform_handler.add_platform(scanned_platform)


@protected_route(router.get, "/platforms", ["platforms.read"])
Expand Down
5 changes: 3 additions & 2 deletions backend/endpoints/responses/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ class CollectionSchema(BaseModel):
id: int
name: str
description: str
logo_path: str = ""
roms: set[int] = {}
path_cover_l: str | None
path_cover_s: str | None
roms: set[int]
rom_count: int
user_id: int
user__username: str
Expand Down
3 changes: 3 additions & 0 deletions backend/endpoints/rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def add_roms(
def get_roms(
request: Request,
platform_id: int | None = None,
collection_id: int | None = None,
search_term: str = "",
limit: int | None = None,
order_by: str = "name",
Expand All @@ -108,8 +109,10 @@ def get_roms(
Returns:
list[RomSchema]: List of roms stored in the database
"""

db_roms = db_rom_handler.get_roms(
platform_id=platform_id,
collection_id=collection_id,
search_term=search_term.lower(),
order_by=order_by.lower(),
order_dir=order_dir.lower(),
Expand Down
2 changes: 2 additions & 0 deletions backend/handler/auth/base_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"firmware.read": "View firmware",
"roms.user.read": "View user-rom properties",
"roms.user.write": "Modify user-rom properties",
"collections.read": "View collections",
"collections.write": "Modify collections",
}

WRITE_SCOPES_MAP: Final = {
Expand Down
2 changes: 2 additions & 0 deletions backend/handler/database/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .collections_handler import DBCollectionsHandler
from .firmware_handler import DBFirmwareHandler
from .platforms_handler import DBPlatformsHandler
from .roms_handler import DBRomsHandler
Expand All @@ -15,3 +16,4 @@
db_state_handler = DBStatesHandler()
db_stats_handler = DBStatsHandler()
db_user_handler = DBUsersHandler()
db_collection_handler = DBCollectionsHandler()
32 changes: 21 additions & 11 deletions backend/handler/database/collections_handler.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,42 @@
from decorators.database import begin_session
from models.collection import Collection
from models.rom import Rom
from sqlalchemy import Select, delete, or_, select
from sqlalchemy.orm import Query, Session
from sqlalchemy import Select, and_, delete, or_, select
from sqlalchemy.orm import Session

from .base_handler import DBBaseHandler


class DBCollectionsHandler(DBBaseHandler):
@begin_session
def add_collection(
self, data: dict, query: Query = None, session: Session = None
self, collection: Collection, session: Session = None
) -> Collection | None:
collection = session.merge(**data)
collection = session.merge(collection)
session.flush()
return session.scalar(select(Collection).filter_by(id=collection.id).limit(1))

return session.scalar(query.filter_by(id=collection.id).limit(1))
@begin_session
def get_collection(self, id: int, session: Session = None) -> Collection | None:
return session.scalar(select(Collection).filter_by(id=id).limit(1))

@begin_session
def get_collection(
self, id: int, *, query: Query = None, session: Session = None
def get_collection_by_name(
self, name: str, user_id: int, session: Session = None
) -> Collection | None:
return session.scalar(query.filter_by(id=id).limit(1))
return session.scalar(
select(Collection).filter_by(name=name, user_id=user_id).limit(1)
)

@begin_session
def get_collections(self, *, session: Session = None) -> Select[tuple[Collection]]:
def get_collections(
self, user_id: int, session: Session = None
) -> Select[tuple[Collection]]:
return (
session.scalars(select(Collection).order_by(Collection.name.asc())) # type: ignore[attr-defined]
session.scalars(
select(Collection)
.filter_by(user_id=user_id)
.order_by(Collection.name.asc())
) # type: ignore[attr-defined]
.unique()
.all()
)
Expand Down
35 changes: 24 additions & 11 deletions backend/handler/database/roms_handler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import functools

from decorators.database import begin_session
from models.collection import Collection
from models.rom import Rom, RomUser
from sqlalchemy import and_, delete, func, or_, select, update
from sqlalchemy.orm import Query, Session, selectinload
Expand Down Expand Up @@ -44,10 +45,26 @@ def wrapper(*args, **kwargs):


class DBRomsHandler(DBBaseHandler):
def _filter(self, data, platform_id: int | None, search_term: str):
def _filter(
self,
data,
platform_id: int | None,
collection_id: int | None,
search_term: str,
session: Session,
):
if platform_id:
data = data.filter(Rom.platform_id == platform_id)

elif collection_id:
collection = (
session.query(Collection)
.filter(Collection.id == collection_id)
.one_or_none()
)
if collection:
data = data.filter(Rom.id.in_(collection.roms))

if search_term:
data = data.filter(
or_(
Expand Down Expand Up @@ -90,24 +107,20 @@ def get_roms(
self,
*,
platform_id: int | None = None,
collection_id: int | None = None,
search_term: str = "",
order_by: str = "name",
order_dir: str = "asc",
limit: int | None = None,
query: Query = None,
session: Session = None,
) -> list[Rom]:
return (
session.scalars(
self._order(
self._filter(query, platform_id, search_term),
order_by,
order_dir,
).limit(limit)
)
.unique()
.all()
filtered_query = self._filter(
query, platform_id, collection_id, search_term, session
)
ordered_query = self._order(filtered_query, order_by, order_dir)
limited_query = ordered_query.limit(limit)
return session.scalars(limited_query).unique().all()

@begin_session
@with_details
Expand Down
2 changes: 2 additions & 0 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from config import DEV_HOST, DEV_PORT, DISABLE_CSRF_PROTECTION, ROMM_AUTH_SECRET_KEY
from endpoints import (
auth,
collections,
config,
feeds,
firmware,
Expand Down Expand Up @@ -93,6 +94,7 @@ async def lifespan(app: FastAPI):
app.include_router(raw.router)
app.include_router(screenshots.router)
app.include_router(firmware.router)
app.include_router(collections.router)

app.mount("/ws", socket_handler.socket_app)

Expand Down
4 changes: 3 additions & 1 deletion backend/models/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ class Collection(BaseModel):

name: Mapped[str] = mapped_column(String(length=400))
description: Mapped[str | None] = mapped_column(Text)
logo_path: Mapped[str | None] = mapped_column(String(length=1000), default="")

path_cover_l: Mapped[str | None] = mapped_column(Text, default="")
path_cover_s: Mapped[str | None] = mapped_column(Text, default="")

roms: Mapped[set[int]] = mapped_column(
JSON, default=[], doc="Rom id's that belong to this collection"
Expand Down
2 changes: 2 additions & 0 deletions backend/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

if TYPE_CHECKING:
from models.assets import Save, Screenshot, State
from models.collection import Collection
from models.rom import RomUser


Expand Down Expand Up @@ -40,6 +41,7 @@ class User(BaseModel, SimpleUser):
states: Mapped[list[State]] = relationship(back_populates="user")
screenshots: Mapped[list[Screenshot]] = relationship(back_populates="user")
rom_users: Mapped[list[RomUser]] = relationship(back_populates="user")
collections: Mapped[list[Collection]] = relationship(back_populates="user")

@property
def oauth_scopes(self):
Expand Down
1 change: 1 addition & 0 deletions frontend/src/__generated__/index.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions frontend/src/__generated__/models/CollectionSchema.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions frontend/src/components/common/Collection/Card.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script setup lang="ts">
import type { Collection } from "@/stores/collections";
import CollectionCover from "@/components/common/Collection/Cover.vue";
defineProps<{ collection: Collection }>();
</script>

<template>
<v-hover v-slot="{ isHovering, props }">
<v-card
v-bind="props"
class="bg-terciary transform-scale"
:class="{ 'on-hover': isHovering }"
:elevation="isHovering ? 20 : 3"
:to="{ name: 'collection', params: { collection: collection.id } }"
>
<v-card-text>
<v-row class="pa-1 justify-center bg-primary">
<div
:title="collection.name?.toString()"
class="px-2 text-truncate text-caption"
>
{{ collection.name }}
</div>
</v-row>
<v-row>
<collection-cover :collection="collection" />
<v-chip
class="bg-chip position-absolute"
size="x-small"
style="bottom: 0.5rem; right: 0.5rem"
label
>
{{ collection.rom_count }}
</v-chip>
</v-row>
</v-card-text>
</v-card>
</v-hover>
</template>
31 changes: 31 additions & 0 deletions frontend/src/components/common/Collection/Cover.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script setup lang="ts">
import type { Collection } from "@/stores/collections";
import { useTheme } from "vuetify";
defineProps<{ collection: Collection }>();
const theme = useTheme();
</script>

<template>
<v-img
:aspect-ratio="2 / 3"
:src="
collection.path_cover_l ??
`/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
"
:lazy-src="
collection.path_cover_s ??
`/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
"
cover
>
<template #error>
<v-img
:aspect-ratio="2 / 3"
src="assets/default/cover/big_dark_empty.png"
cover
>
</v-img>
</template>
</v-img>
</template>
Loading

0 comments on commit 7324f9a

Please sign in to comment.