Skip to content

Commit aa044b7

Browse files
authored
Merge pull request #3549 from rommapp/feat/screenshot-CRUD-endpoints
feat: User screenshots support + dropzone refactor
2 parents 2203faa + e974e3b commit aa044b7

70 files changed

Lines changed: 3097 additions & 1368 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""Update rom_file.category column enum to include screenshots.
2+
3+
Revision ID: 0085_rom_category_screenshot
4+
Revises: 0084_add_roms_search_index
5+
Create Date: 2026-06-17 00:00:00.000000
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
from utils.database import is_postgresql
13+
14+
# revision identifiers, used by Alembic.
15+
revision = "0085_rom_category_screenshot"
16+
down_revision = "0084_add_roms_search_index"
17+
branch_labels = None
18+
depends_on = None
19+
20+
21+
def upgrade() -> None:
22+
connection = op.get_bind()
23+
24+
if is_postgresql(connection):
25+
# `ALTER TYPE ... ADD VALUE` must run outside a transaction in PostgreSQL.
26+
# autocommit_block breaks out of alembic's wrapping transaction for
27+
# exactly this operation.
28+
with op.get_context().autocommit_block():
29+
op.execute(
30+
"ALTER TYPE romfilecategory ADD VALUE IF NOT EXISTS 'SCREENSHOT'"
31+
)
32+
else:
33+
rom_file_category_enum = sa.Enum(
34+
"GAME",
35+
"DLC",
36+
"HACK",
37+
"MANUAL",
38+
"PATCH",
39+
"UPDATE",
40+
"MOD",
41+
"DEMO",
42+
"TRANSLATION",
43+
"PROTOTYPE",
44+
"CHEAT",
45+
"SOUNDTRACK",
46+
"SCREENSHOT",
47+
name="romfilecategory",
48+
)
49+
with op.batch_alter_table("rom_files", schema=None) as batch_op:
50+
batch_op.alter_column(
51+
"category", type_=rom_file_category_enum, nullable=True
52+
)
53+
54+
55+
def downgrade() -> None:
56+
# PostgreSQL cannot remove enum values, so the downgrade is a no-op there.
57+
# On other backends, narrow the enum back to its pre-screenshot set. Any
58+
# rows still holding the new value would block this, but the upload flow
59+
# is additive and a re-upgrade is a no-op.
60+
connection = op.get_bind()
61+
62+
if is_postgresql(connection):
63+
return
64+
65+
rom_file_category_enum = sa.Enum(
66+
"GAME",
67+
"DLC",
68+
"HACK",
69+
"MANUAL",
70+
"PATCH",
71+
"UPDATE",
72+
"MOD",
73+
"DEMO",
74+
"TRANSLATION",
75+
"PROTOTYPE",
76+
"CHEAT",
77+
"SOUNDTRACK",
78+
name="romfilecategory",
79+
)
80+
with op.batch_alter_table("rom_files", schema=None) as batch_op:
81+
batch_op.alter_column("category", type_=rom_file_category_enum, nullable=True)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Add is_gallery + is_public flags to screenshots for per-user community
2+
gallery screenshots (distinct from auto-captured save/state thumbnails).
3+
4+
Revision ID: 0086_screenshot_visibility
5+
Revises: 0085_rom_category_screenshot
6+
Create Date: 2026-06-18 00:00:00.000000
7+
8+
"""
9+
10+
import sqlalchemy as sa
11+
from alembic import op
12+
13+
# revision identifiers, used by Alembic.
14+
revision = "0086_screenshot_visibility"
15+
down_revision = "0085_rom_category_screenshot"
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade() -> None:
21+
op.add_column(
22+
"screenshots",
23+
sa.Column(
24+
"is_gallery",
25+
sa.Boolean(),
26+
nullable=False,
27+
server_default=sa.text("false"),
28+
),
29+
if_not_exists=True,
30+
)
31+
op.add_column(
32+
"screenshots",
33+
sa.Column(
34+
"is_public",
35+
sa.Boolean(),
36+
nullable=False,
37+
server_default=sa.text("false"),
38+
),
39+
if_not_exists=True,
40+
)
41+
op.create_index(
42+
"idx_screenshots_public", "screenshots", ["is_public"], if_not_exists=True
43+
)
44+
45+
46+
def downgrade() -> None:
47+
op.drop_index("idx_screenshots_public", table_name="screenshots", if_exists=True)
48+
op.drop_column("screenshots", "is_public", if_exists=True)
49+
op.drop_column("screenshots", "is_gallery", if_exists=True)

backend/endpoints/responses/assets.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,15 @@ class BaseAsset(BaseModel):
3030

3131

3232
class ScreenshotSchema(BaseAsset):
33-
pass
33+
is_gallery: bool = False
34+
is_public: bool = False
35+
36+
37+
class UserScreenshotSchema(ScreenshotSchema):
38+
"""A gallery screenshot enriched with its owner's username, for the
39+
community (My / Community) view. Mirrors UserNoteSchema."""
40+
41+
username: str
3442

3543

3644
class SaveSchema(BaseAsset):

backend/endpoints/responses/rom.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
from fastapi import Request
99
from pydantic import ConfigDict, Field, computed_field, field_validator
1010

11-
from endpoints.responses.assets import SaveSchema, ScreenshotSchema, StateSchema
11+
from endpoints.responses.assets import (
12+
SaveSchema,
13+
ScreenshotSchema,
14+
StateSchema,
15+
UserScreenshotSchema,
16+
)
1217
from handler.metadata.flashpoint_handler import FlashpointMetadata
1318
from handler.metadata.gamelist_handler import GamelistMetadata
1419
from handler.metadata.hasheous_handler import HasheousMetadata
@@ -460,6 +465,7 @@ class DetailedRomSchema(RomSchema):
460465
user_saves: list[SaveSchema]
461466
user_states: list[StateSchema]
462467
user_screenshots: list[ScreenshotSchema]
468+
all_user_screenshots: list[UserScreenshotSchema]
463469
user_collections: list[UserCollectionSchema]
464470
all_user_notes: list[UserNoteSchema]
465471

@@ -524,6 +530,27 @@ def from_orm_with_request(cls, db_rom: Rom, request: Request) -> DetailedRomSche
524530
all_notes.sort(key=lambda x: x.updated_at, reverse=True)
525531
db_rom.all_user_notes = all_notes # type: ignore[assignment]
526532

533+
# Gallery screenshots visible to this user: own (public + private) plus
534+
# other users' public ones. Mirrors the notes flow above. Excludes the
535+
# auto-captured save/state thumbnails (is_gallery == False).
536+
from handler.database import db_screenshot_handler
537+
538+
gallery_screenshots = db_screenshot_handler.get_rom_gallery_screenshots(
539+
rom_id=db_rom.id, user_id=user_id
540+
)
541+
db_rom.all_user_screenshots = [ # type: ignore[assignment]
542+
UserScreenshotSchema.model_validate(
543+
{
544+
**{
545+
field: getattr(s, field)
546+
for field in ScreenshotSchema.model_fields
547+
},
548+
"username": s.user.username,
549+
}
550+
)
551+
for s in gallery_screenshots
552+
]
553+
527554
return cls.model_validate(db_rom)
528555

529556
@field_validator("user_saves")

backend/endpoints/roms/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
from .files import router as files_router
7676
from .manual import router as manual_router
7777
from .notes import router as notes_router
78+
from .screenshot import router as screenshot_router
7879
from .soundtrack import router as soundtrack_router
7980
from .upload import router as upload_router
8081

@@ -86,6 +87,7 @@
8687
router.include_router(files_router)
8788
router.include_router(manual_router)
8889
router.include_router(soundtrack_router)
90+
router.include_router(screenshot_router)
8991
router.include_router(notes_router)
9092

9193

0 commit comments

Comments
 (0)