feat: add auth system, boss pokemon details, moves/abilities API, and run ownership
Some checks failed
CI / backend-tests (push) Failing after 1m16s
CI / frontend-tests (push) Successful in 57s

Add user authentication with login/signup/protected routes, boss pokemon
detail fields and result team tracking, moves and abilities selector
components and API, run ownership and visibility controls, and various
UI improvements across encounters, run list, and journal pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-20 21:41:38 +01:00
parent a6cb309b8b
commit 0a519e356e
69 changed files with 3574 additions and 693 deletions

View File

@@ -0,0 +1,62 @@
"""add boss pokemon details
Revision ID: l3a4b5c6d7e8
Revises: k2f3a4b5c6d7
Create Date: 2026-03-20 19:30:00.000000
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "l3a4b5c6d7e8"
down_revision: str | Sequence[str] | None = "k2f3a4b5c6d7"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
# Add ability reference
op.add_column(
"boss_pokemon",
sa.Column(
"ability_id", sa.Integer(), sa.ForeignKey("abilities.id"), nullable=True
),
)
op.create_index("ix_boss_pokemon_ability_id", "boss_pokemon", ["ability_id"])
# Add held item (plain string)
op.add_column(
"boss_pokemon",
sa.Column("held_item", sa.String(50), nullable=True),
)
# Add nature (plain string)
op.add_column(
"boss_pokemon",
sa.Column("nature", sa.String(20), nullable=True),
)
# Add move references (up to 4 moves)
for i in range(1, 5):
op.add_column(
"boss_pokemon",
sa.Column(
f"move{i}_id", sa.Integer(), sa.ForeignKey("moves.id"), nullable=True
),
)
op.create_index(f"ix_boss_pokemon_move{i}_id", "boss_pokemon", [f"move{i}_id"])
def downgrade() -> None:
for i in range(1, 5):
op.drop_index(f"ix_boss_pokemon_move{i}_id", "boss_pokemon")
op.drop_column("boss_pokemon", f"move{i}_id")
op.drop_column("boss_pokemon", "nature")
op.drop_column("boss_pokemon", "held_item")
op.drop_index("ix_boss_pokemon_ability_id", "boss_pokemon")
op.drop_column("boss_pokemon", "ability_id")

View File

@@ -0,0 +1,44 @@
"""add boss result team
Revision ID: m4b5c6d7e8f9
Revises: l3a4b5c6d7e8
Create Date: 2026-03-20 20:00:00.000000
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "m4b5c6d7e8f9"
down_revision: str | Sequence[str] | None = "l3a4b5c6d7e8"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.create_table(
"boss_result_team",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column(
"boss_result_id",
sa.Integer(),
sa.ForeignKey("boss_results.id", ondelete="CASCADE"),
nullable=False,
index=True,
),
sa.Column(
"encounter_id",
sa.Integer(),
sa.ForeignKey("encounters.id", ondelete="CASCADE"),
nullable=False,
index=True,
),
sa.Column("level", sa.SmallInteger(), nullable=False),
)
def downgrade() -> None:
op.drop_table("boss_result_team")

View File

@@ -0,0 +1,37 @@
"""create users table
Revision ID: n5c6d7e8f9a0
Revises: m4b5c6d7e8f9
Create Date: 2026-03-20 22:00:00.000000
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "n5c6d7e8f9a0"
down_revision: str | Sequence[str] | None = "m4b5c6d7e8f9"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.create_table(
"users",
sa.Column("id", sa.UUID(), primary_key=True),
sa.Column("email", sa.String(255), nullable=False, unique=True, index=True),
sa.Column("display_name", sa.String(100), nullable=True),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.func.now(),
),
)
def downgrade() -> None:
op.drop_table("users")

View File

@@ -0,0 +1,60 @@
"""add owner_id and visibility to runs
Revision ID: o6d7e8f9a0b1
Revises: n5c6d7e8f9a0
Create Date: 2026-03-20 22:01:00.000000
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "o6d7e8f9a0b1"
down_revision: str | Sequence[str] | None = "n5c6d7e8f9a0"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
# Create visibility enum
visibility_enum = sa.Enum("public", "private", name="run_visibility")
visibility_enum.create(op.get_bind(), checkfirst=True)
# Add owner_id (nullable FK to users)
op.add_column(
"nuzlocke_runs",
sa.Column("owner_id", sa.UUID(), nullable=True),
)
op.create_foreign_key(
"fk_nuzlocke_runs_owner_id",
"nuzlocke_runs",
"users",
["owner_id"],
["id"],
ondelete="SET NULL",
)
op.create_index("ix_nuzlocke_runs_owner_id", "nuzlocke_runs", ["owner_id"])
# Add visibility column with default 'public'
op.add_column(
"nuzlocke_runs",
sa.Column(
"visibility",
visibility_enum,
nullable=False,
server_default="public",
),
)
def downgrade() -> None:
op.drop_column("nuzlocke_runs", "visibility")
op.drop_index("ix_nuzlocke_runs_owner_id", table_name="nuzlocke_runs")
op.drop_constraint("fk_nuzlocke_runs_owner_id", "nuzlocke_runs", type_="foreignkey")
op.drop_column("nuzlocke_runs", "owner_id")
# Drop the enum type
sa.Enum(name="run_visibility").drop(op.get_bind(), checkfirst=True)