Skip to content

Commit

Permalink
Merge pull request #828 from rommapp/romm-774
Browse files Browse the repository at this point in the history
[ROMM-774] Games notes (edit + share)
  • Loading branch information
zurdi15 committed May 13, 2024
2 parents fdac52a + 4dbcab8 commit e70ff6c
Show file tree
Hide file tree
Showing 16 changed files with 1,048 additions and 11 deletions.
39 changes: 39 additions & 0 deletions backend/alembic/versions/0017_rom_notes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""empty message
Revision ID: 0017_rom_notes
Revises: 0016_user_last_login_active
Create Date: 2024-04-28 11:58:18.927734
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '0017_rom_notes'
down_revision = '0016_user_last_login_active'
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('rom_notes',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('last_edited_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('raw_markdown', sa.Text(), nullable=False),
sa.Column('is_public', sa.Boolean(), nullable=True),
sa.Column('rom_id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['rom_id'], ['roms.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('rom_id', 'user_id', name='unique_rom_user_note')
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('rom_notes')
# ### end Alembic commands ###
30 changes: 28 additions & 2 deletions backend/endpoints/responses/rom.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import re
from datetime import datetime
from typing import Optional, get_type_hints
from typing_extensions import TypedDict, NotRequired

from endpoints.responses.assets import SaveSchema, ScreenshotSchema, StateSchema
from fastapi import Request
from fastapi.responses import StreamingResponse
from handler import socket_handler
from handler import socket_handler, db_user_handler
from handler.metadata_handler.igdb_handler import IGDBMetadata
from handler.metadata_handler.moby_handler import MobyMetadata
from pydantic import BaseModel, computed_field, Field
Expand All @@ -25,6 +26,30 @@
total=False,
)

class RomNoteSchema(BaseModel):
id: int
user_id: int
rom_id: int
last_edited_at: datetime
raw_markdown: str
is_public: bool

class Config:
from_attributes = True

@computed_field
@property
def user__username(self) -> str:
return db_user_handler.get_user(self.user_id).username

@classmethod
def for_user(cls, db_rom: Rom, user_id: int) -> list["RomNoteSchema"]:
return [
cls.model_validate(n)
for n in db_rom.notes
# This is what filters out private notes
if n.user_id == user_id or n.is_public
]

class RomSchema(BaseModel):
id: int
Expand Down Expand Up @@ -78,6 +103,7 @@ class RomSchema(BaseModel):
user_saves: list[SaveSchema] = Field(default_factory=list)
user_states: list[StateSchema] = Field(default_factory=list)
user_screenshots: list[ScreenshotSchema] = Field(default_factory=list)
user_notes: list[RomNoteSchema] = Field(default_factory=list)

class Config:
from_attributes = True
Expand Down Expand Up @@ -114,10 +140,10 @@ def from_orm_with_request(cls, db_rom: Rom, request: Request) -> "RomSchema":
for s in db_rom.screenshots
if s.user_id == user_id
]
rom.user_notes = RomNoteSchema.for_user(db_rom, user_id)

return rom


class AddRomsResponse(TypedDict):
uploaded_roms: list[str]
skipped_roms: list[str]
Expand Down
20 changes: 20 additions & 0 deletions backend/endpoints/rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
AddRomsResponse,
CustomStreamingResponse,
RomSchema,
RomNoteSchema,
)
from exceptions.fs_exceptions import RomAlreadyExistsException
from fastapi import APIRouter, File, HTTPException, Query, Request, UploadFile, status
Expand Down Expand Up @@ -425,3 +426,22 @@ async def delete_roms(
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=error)

return {"msg": f"{len(roms_ids)} roms deleted successfully!"}

@protected_route(router.put, "/roms/{id}/note", ["notes.write"])
async def update_rom_note(request: Request, id: int) -> RomNoteSchema:
db_note = db_rom_handler.get_rom_note(id, request.user.id)
if not db_note:
db_note = db_rom_handler.add_rom_note(id, request.user.id)

data = await request.json()
db_rom_handler.update_rom_note(
db_note.id,
{
"last_edited_at": datetime.now(),
"raw_markdown": data.get("raw_markdown", db_note.raw_markdown),
"is_public": data.get("is_public", db_note.is_public),
}
)

db_note = db_rom_handler.get_rom_note(id, request.user.id)
return db_note
2 changes: 2 additions & 0 deletions backend/handler/auth_handler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"platforms.read": "View platforms",
"assets.read": "View assets",
"assets.write": "Modify assets",
"notes.read": "View notes",
"notes.write": "Modify notes",
}

WRITE_SCOPES_MAP: Final = {
Expand Down
5 changes: 5 additions & 0 deletions backend/handler/db_handler/db_notes_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from decorators.database import begin_session
from handler.db_handler import DBHandler
from models.rom import RomNote
from sqlalchemy import and_, delete, select, update
from sqlalchemy.orm import Session
27 changes: 26 additions & 1 deletion backend/handler/db_handler/db_roms_handler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from decorators.database import begin_session
from handler.db_handler import DBHandler
from models.rom import Rom
from models.rom import Rom, RomNote
from sqlalchemy import and_, delete, func, select, update, or_, Select
from sqlalchemy.orm import Session

Expand Down Expand Up @@ -103,3 +103,28 @@ def purge_roms(self, platform_id: int, roms: list[str], session: Session = None)
.where(and_(Rom.platform_id == platform_id, Rom.file_name.not_in(roms)))
.execution_options(synchronize_session="evaluate")
)

@begin_session
def get_rom_note(
self, rom_id: int, user_id: int, session: Session = None
):
return session.scalars(
select(RomNote).filter_by(rom_id=rom_id, user_id=user_id).limit(1)
).first()

@begin_session
def add_rom_note(
self, rom_id: int, user_id: int, session: Session = None
):
return session.merge(RomNote(rom_id=rom_id, user_id=user_id))

@begin_session
def update_rom_note(
self, id: int, data: dict, session: Session = None
):
return session.execute(
update(RomNote)
.where(RomNote.id == id)
.values(**data)
.execution_options(synchronize_session="evaluate")
)
32 changes: 32 additions & 0 deletions backend/models/rom.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from functools import cached_property
from datetime import datetime

from config import FRONTEND_RESOURCES_PATH
from models.assets import Save, Screenshot, State
Expand All @@ -13,6 +14,9 @@
String,
Text,
BigInteger,
DateTime,
func,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, relationship

Expand Down Expand Up @@ -75,6 +79,9 @@ class Rom(BaseModel):
screenshots: Mapped[list[Screenshot]] = relationship(
"Screenshot", lazy="selectin", back_populates="rom"
)
notes: Mapped[list["RomNote"]] = relationship(
"RomNote", lazy="selectin", back_populates="rom"
)

@property
def platform_slug(self) -> str:
Expand Down Expand Up @@ -156,3 +163,28 @@ def game_modes(self) -> list[str]:

def __repr__(self) -> str:
return self.file_name

class RomNote(BaseModel):
__tablename__ = "rom_notes"
__table_args__ = (
UniqueConstraint("rom_id", "user_id", name="unique_rom_user_note"),
)

id: int = Column(Integer(), primary_key=True, autoincrement=True)
last_edited_at: datetime = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
raw_markdown: str = Column(Text, nullable=False, default="")
is_public: bool = Column(Boolean, default=False)

rom_id: int = Column(
Integer(),
ForeignKey("roms.id", ondelete="CASCADE"),
nullable=False,
)
user_id: int = Column(
Integer(),
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
)

rom = relationship("Rom", back_populates="notes")
user = relationship("User", back_populates="notes")
5 changes: 5 additions & 0 deletions backend/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class Role(enum.Enum):


class User(BaseModel, SimpleUser):
from models.rom import RomNote

__tablename__ = "users"
__table_args__ = {"extend_existing": True}

Expand All @@ -39,6 +41,9 @@ class User(BaseModel, SimpleUser):
screenshots: Mapped[list[Screenshot]] = relationship(
"Screenshot", lazy="selectin", back_populates="user"
)
notes: Mapped[list[RomNote]] = relationship(
"RomNote", lazy="selectin", back_populates="user"
)

@property
def oauth_scopes(self):
Expand Down
Loading

0 comments on commit e70ff6c

Please sign in to comment.