Tokens and components in styles.css, shared Ui (dialogs, toasts), icons, bottom tab bar on mobile, keyboard/gamepad navigation, library shelf with filters and conversion badges, hero game page, restyled player screens. Games list now returns per-game version counts. Adds docs/UI_DESIGN.md. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
119 lines
5.1 KiB
Python
119 lines
5.1 KiB
Python
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from sqlalchemy import (
|
|
ARRAY,
|
|
BigInteger,
|
|
Boolean,
|
|
DateTime,
|
|
ForeignKey,
|
|
Integer,
|
|
String,
|
|
Text,
|
|
UniqueConstraint,
|
|
func,
|
|
)
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.db import Base
|
|
|
|
|
|
class Role(Base):
|
|
__tablename__ = "roles"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
name: Mapped[str] = mapped_column(String(64), unique=True)
|
|
permissions: Mapped[list[str]] = mapped_column(ARRAY(String), default=list)
|
|
is_system: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
username: Mapped[str] = mapped_column(String(64), unique=True)
|
|
password_hash: Mapped[str] = mapped_column(String(255))
|
|
role_id: Mapped[int] = mapped_column(ForeignKey("roles.id"))
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
must_change_password: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
must_change_username: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
token_version: Mapped[int] = mapped_column(Integer, default=0)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
role: Mapped[Role] = relationship(lazy="joined")
|
|
|
|
@property
|
|
def permissions(self) -> list[str]:
|
|
return sorted(self.role.permissions) if self.is_active else []
|
|
|
|
|
|
class Game(Base):
|
|
__tablename__ = "games"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
title: Mapped[str] = mapped_column(String(255))
|
|
description: Mapped[str] = mapped_column(Text, default="")
|
|
tags: Mapped[list[str]] = mapped_column(ARRAY(String), default=list)
|
|
engine: Mapped[str] = mapped_column(String(32), default="renpy")
|
|
cover_hash: Mapped[str | None] = mapped_column(String(80))
|
|
# the version "Play" opens; translations are published by merging into it
|
|
main_version_id: Mapped[int | None] = mapped_column(
|
|
ForeignKey("game_versions.id", ondelete="SET NULL", use_alter=True))
|
|
created_by_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"))
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
created_by: Mapped[User | None] = relationship(lazy="joined")
|
|
|
|
|
|
class GameVersion(Base):
|
|
__tablename__ = "game_versions"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
game_id: Mapped[int] = mapped_column(ForeignKey("games.id", ondelete="CASCADE"), index=True)
|
|
parent_id: Mapped[int | None] = mapped_column(ForeignKey("game_versions.id", ondelete="SET NULL"))
|
|
owner_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"))
|
|
name: Mapped[str] = mapped_column(String(255))
|
|
description: Mapped[str] = mapped_column(Text, default="")
|
|
# uploading -> queued -> converting -> ready | failed; importing = app.import_dir run
|
|
status: Mapped[str] = mapped_column(String(16), default="uploading", index=True)
|
|
# draft: visible to the owner and reviewers only
|
|
visibility: Mapped[str] = mapped_column(String(16), default="published")
|
|
kind: Mapped[str] = mapped_column(String(16), default="original") # original|translation|mod
|
|
progress: Mapped[str] = mapped_column(String(255), default="")
|
|
upload_name: Mapped[str | None] = mapped_column(String(255))
|
|
manifest: Mapped[dict[str, Any] | None] = mapped_column(JSONB)
|
|
report: Mapped[dict[str, Any] | None] = mapped_column(JSONB)
|
|
size_bytes: Mapped[int] = mapped_column(BigInteger, default=0)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
owner: Mapped[User | None] = relationship(lazy="joined")
|
|
|
|
|
|
class VersionHistory(Base):
|
|
"""Previous manifests of a version that was changed in place (e.g. a merged translation)."""
|
|
__tablename__ = "version_history"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
version_id: Mapped[int] = mapped_column(ForeignKey("game_versions.id", ondelete="CASCADE"),
|
|
index=True)
|
|
user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"))
|
|
note: Mapped[str] = mapped_column(String(255), default="")
|
|
manifest: Mapped[dict[str, Any]] = mapped_column(JSONB)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
|
|
class Save(Base):
|
|
__tablename__ = "saves"
|
|
__table_args__ = (UniqueConstraint("user_id", "version_id", "slot"),)
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
|
|
version_id: Mapped[int] = mapped_column(ForeignKey("game_versions.id", ondelete="CASCADE"))
|
|
slot: Mapped[str] = mapped_column(String(32))
|
|
title: Mapped[str] = mapped_column(String(255), default="")
|
|
data: Mapped[dict[str, Any]] = mapped_column(JSONB)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
|
)
|