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

[ROMM-774] Games notes (edit + share) #828

Merged
merged 5 commits into from
May 13, 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
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
Loading