Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d3496d8c67 | |||
| ffbc4849a7 | |||
| a29cfc74af | |||
| aa2f79b125 | |||
| 8551d14591 |
@@ -3,6 +3,8 @@ from sqlalchemy.dialects.postgresql import ENUM
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
import enum
|
||||
import json
|
||||
from typing import Any, Optional
|
||||
|
||||
from app.database import Base
|
||||
|
||||
@@ -38,3 +40,32 @@ class Asset(Base):
|
||||
|
||||
owner = relationship("User", back_populates="assets")
|
||||
world = relationship("World", back_populates="assets")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Computed properties
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def has_gltf_metadata(self) -> bool:
|
||||
"""Return True when metadata_json contains glTF-specific keys.
|
||||
|
||||
glTF metadata is considered present when the stored JSON contains at
|
||||
least one of the keys that *extract_gltf_metadata* produces
|
||||
(mesh_count, vertex_count, has_animation, texture_refs,
|
||||
bounding_box_min, bounding_box_max).
|
||||
"""
|
||||
if not self.metadata_json:
|
||||
return False
|
||||
try:
|
||||
data: dict[str, Any] = json.loads(self.metadata_json)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return False
|
||||
gltf_keys = {
|
||||
"mesh_count",
|
||||
"vertex_count",
|
||||
"has_animation",
|
||||
"texture_refs",
|
||||
"bounding_box_min",
|
||||
"bounding_box_max",
|
||||
}
|
||||
return bool(gltf_keys & data.keys())
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# MVP Vertical Slice: Client-Server Wiring — Plan
|
||||
|
||||
## Goal
|
||||
Prove the real Godot client and the real backend can talk: **a user can register/log in
|
||||
and see a list of worlds from the game client**. Nothing more. Gameplay, join-a-world,
|
||||
chat, avatar, Discovery search/tags/trending, Social/Economy/Trust & Safety are all
|
||||
explicitly out of scope.
|
||||
|
||||
## Verified backend contract (as of this writing)
|
||||
- `POST /api/auth/register` — JSON body `{username, email, password, date_of_birth}`.
|
||||
`date_of_birth` is a `YYYY-MM-DD` string. Returns `201 {access_token, token_type}`.
|
||||
Rejects under-18 with `400`.
|
||||
- `POST /api/auth/login` — OAuth2 **form-encoded** (`application/x-www-form-urlencoded`)
|
||||
`username` + `password` (this is NOT a JSON endpoint). Returns `{access_token, token_type}`.
|
||||
- `GET /api/worlds` — **requires auth** (`CurrentUser`), returns a JSON array of
|
||||
`{id, name, description, visibility, owner_id, created_at}`. There is no unauthenticated
|
||||
public-worlds path, so the worlds list screen can only be shown after login/register.
|
||||
- `GET /api/health` — unauthenticated, `{status, app}`.
|
||||
- Base URL: uvicorn default `http://127.0.0.1:8000` (must be configurable in the client).
|
||||
- CORS is irrelevant to the native Godot `HTTPRequest` client (no browser origin).
|
||||
|
||||
## Task breakdown
|
||||
| # | Type | Task | Depends on |
|
||||
|---|------|------|-----------|
|
||||
| 1 | architecture | `ApiClient` autoload: HTTP + JWT storage + error contract | — |
|
||||
| 2 | implementation | Main-menu **login** wiring (form-encoded) | 1 |
|
||||
| 3 | implementation | Main-menu **register** wiring (JSON + age gate) | 2 |
|
||||
| 4 | implementation | **World list** screen consuming `GET /api/worlds` | 2 |
|
||||
| 5 | testing | End-to-end headless smoke test + manual verify | 1,3,4 |
|
||||
|
||||
## Design decisions captured (resolved in task 1)
|
||||
- Token storage: in-memory singleton property for the MVP; a `save_to_disk` seam is left
|
||||
open but not required yet (no re-login on restart in this slice).
|
||||
- Error contract: every call returns a discriminated result (`ok`/`error`) so callers
|
||||
never crash on network failure; non-2xx surfaces `{status, detail}` from the backend.
|
||||
- Base URL: read from a ProjectSetting, defaulting to `http://127.0.0.1:8000`.
|
||||
- Login is form-encoded; register is JSON — the client helper must support both content types.
|
||||
@@ -0,0 +1 @@
|
||||
uid://cjd0uhll7iylj
|
||||
@@ -0,0 +1 @@
|
||||
uid://bgnc3iamfgf6k
|
||||
Reference in New Issue
Block a user