diff --git a/app/__init__.py b/app/__init__.py index e78c13f..f450909 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,12 +1,14 @@ from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles -from .routers import decks, players +from .routers import api_router, html_router from .sql.models import Base from .sql.database import engine Base.metadata.create_all(bind=engine) app = FastAPI() +app.mount("/static", StaticFiles(directory="app/static"), name="static") -app.include_router(players.router) -app.include_router(decks.router) +app.include_router(api_router) +app.include_router(html_router) diff --git a/app/routers/__init__.py b/app/routers/__init__.py index e69de29..a68b1f2 100644 --- a/app/routers/__init__.py +++ b/app/routers/__init__.py @@ -0,0 +1,12 @@ +from fastapi import APIRouter + +from . import decks, players + +api_router = APIRouter(prefix="/api") +html_router = APIRouter() + +api_router.include_router(decks.api_router) +api_router.include_router(players.api_router) + +html_router.include_router(decks.html_router) +html_router.include_router(players.html_router) diff --git a/app/routers/decks.py b/app/routers/decks.py index a02f30a..5420507 100644 --- a/app/routers/decks.py +++ b/app/routers/decks.py @@ -1,13 +1,17 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi.responses import HTMLResponse from sqlalchemy.orm import Session +from ..templates import jinja_templates, _jsonify from ..sql import crud, schemas from ..sql.database import get_db -router = APIRouter() +from .players import read_player, list_players +api_router = APIRouter(tags=["deck"]) +html_router = APIRouter(include_in_schema=False) -@router.post("/deck", response_model=schemas.Deck, tags=["deck"], status_code=201) +@api_router.post("/deck", response_model=schemas.Deck, status_code=201) def create_deck(deck: schemas.DeckCreate, db: Session = Depends(get_db)): db_player = crud.get_player_by_id(db, deck.owner_id) if db_player is None: @@ -16,7 +20,7 @@ def create_deck(deck: schemas.DeckCreate, db: Session = Depends(get_db)): return crud.create_deck(db=db, deck=deck) -@router.get("/deck/{deck_id}", response_model=schemas.Deck, tags=["deck"]) +@api_router.get("/deck/{deck_id}", response_model=schemas.Deck) def read_deck(deck_id: str, db = Depends(get_db)): db_deck = crud.get_deck_by_id(db, deck_id) if db_deck is None: @@ -24,11 +28,52 @@ def read_deck(deck_id: str, db = Depends(get_db)): return db_deck -@router.get("/decks", response_model=list[schemas.Deck], tags=["deck"]) +@api_router.get("/decks", response_model=list[schemas.Deck]) def list_decks(skip: int = 0, limit: int = 100, db = Depends(get_db)): return crud.get_decks(db, skip=skip, limit=limit) -@router.delete("/deck/{deck_id}", tags=["deck"], status_code=204) +@api_router.delete("/deck/{deck_id}", status_code=204) def delete_deck(deck_id: str, db = Depends(get_db)): crud.delete_deck_by_id(db, int(deck_id)) + + +@html_router.get("/deck/create", response_class=HTMLResponse) +def deck_create_html(request: Request, db = Depends(get_db)): + players = list_players(db=db) + return jinja_templates.TemplateResponse( + request, + "deck_create.html", + { + "players": players + } + ) + + +@html_router.get("/deck/{deck_id}", response_class=HTMLResponse) +def deck_html(request: Request, deck_id: str, db = Depends(get_db)): + deck_info = read_deck(deck_id, db) + return jinja_templates.TemplateResponse( + request, + "deck_detail.html", + { + "deck": _jsonify(deck_info), + "owner": _jsonify(deck_info.owner) + } + ) + + +# TODO - pagination +@html_router.get("/decks", response_class=HTMLResponse) +def decks_html(request: Request, db = Depends(get_db)): + decks = list_decks(db=db) + print(decks) + return jinja_templates.TemplateResponse( + request, + "deck_list.html", + { + # TODO - investigate if there are any issues to passing the "live" object into the + # template, as opposed to the `_jsonify`'d one. + "decks": decks + } + ) \ No newline at end of file diff --git a/app/routers/players.py b/app/routers/players.py index ad918ab..be632d2 100644 --- a/app/routers/players.py +++ b/app/routers/players.py @@ -1,18 +1,20 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi.responses import HTMLResponse from sqlalchemy.orm import Session +from ..templates import jinja_templates from ..sql import crud, schemas from ..sql.database import get_db -router = APIRouter() +api_router = APIRouter(tags=["player"]) +html_router = APIRouter() - -@router.post("/player", response_model=schemas.Player, tags=["player"], status_code=201) +@api_router.post("/player", response_model=schemas.Player, status_code=201) def create_player(player: schemas.PlayerCreate, db: Session = Depends(get_db)): return crud.create_player(db=db, player=player) -@router.get("/player/{player_id}", response_model=schemas.Player, tags=["player"]) +@api_router.get("/player/{player_id}", response_model=schemas.Player) def read_player(player_id: str, db = Depends(get_db)): db_player = crud.get_player_by_id(db, player_id) if db_player is None: @@ -20,11 +22,31 @@ def read_player(player_id: str, db = Depends(get_db)): return db_player -@router.get("/players", response_model=list[schemas.Player], tags=["player"]) +@api_router.get("/players", response_model=list[schemas.Player]) def list_players(skip: int = 0, limit: int = 100, db = Depends(get_db)): return crud.get_players(db, skip=skip, limit=limit) -@router.delete("/player/{player_id}", tags=["player"], status_code=204) +@api_router.delete("/player/{player_id}", status_code=204) def delete_player(player_id: str, db = Depends(get_db)): crud.delete_player_by_id(db, int(player_id)) + + +@html_router.get("/player/create", response_class=HTMLResponse) +def player_create_html(request: Request, db = Depends(get_db)): + return jinja_templates.TemplateResponse( + request, + "player_create.html" + ) + + +@html_router.get("/player/{player_id}", response_class=HTMLResponse) +def player_html(request: Request, player_id: str, db = Depends(get_db)): + player_info = read_player(player_id, db) + return jinja_templates.TemplateResponse( + request, + "player_detail.html", + { + "player": player_info + } + ) diff --git a/app/static/js/base.js b/app/static/js/base.js new file mode 100644 index 0000000..491bfa0 --- /dev/null +++ b/app/static/js/base.js @@ -0,0 +1,3 @@ +$.ajaxSetup({ + contentType: "application/json; charset=utf-8" +}); \ No newline at end of file diff --git a/app/static/js/deck_create.js b/app/static/js/deck_create.js new file mode 100644 index 0000000..c285499 --- /dev/null +++ b/app/static/js/deck_create.js @@ -0,0 +1,16 @@ +$(document).ready(function() { + $('#create_button').click(function() { + $.post({ + url: '/api/deck', + data: JSON.stringify({ + 'name': $('#name').val(), + 'description': $('#description').val(), + 'owner_id': $('#owner_id').val() + }), + contentType: 'application/json; charset=utf-8' + }).done(function (response) { + deck_id = response['id']; + window.location.href = '/deck/' + deck_id + }); + }) +}) \ No newline at end of file diff --git a/app/static/js/player_create.js b/app/static/js/player_create.js new file mode 100644 index 0000000..a81dd9a --- /dev/null +++ b/app/static/js/player_create.js @@ -0,0 +1,14 @@ +$(document).ready(function() { + $('#create_button').click(function() { + $.post({ + url: '/api/player', + data: JSON.stringify({ + 'name': $('#name').val() + }), + contentType: 'application/json; charset=utf-8' + }).done(function (response) { + player_id = response['id']; + window.location.href = '/player/' + player_id + }); + }) +}) \ No newline at end of file diff --git a/app/templates/__init__.py b/app/templates/__init__.py new file mode 100644 index 0000000..438f5f9 --- /dev/null +++ b/app/templates/__init__.py @@ -0,0 +1,16 @@ +# *NO* idea if this layout is actually good or encouraged - +# but I couldn't put this instantiation in `app.__init__.py` or +# `app.routers.__init__.py` and then import from there into (say) +# `app.routers.decks.py` because: +# +# ImportError: cannot import name 'jinja_templates' from partially initialized module 'app.routers' (most likely due to a circular import) (/Users/scubbo/Code/edh-elo/app/routers/__init__.py) + +from fastapi.templating import Jinja2Templates + +jinja_templates = Jinja2Templates(directory="app/templates") + +# TODO - would this be better as a method on a class extending `db.Model` that the classes in `models.py` could then +# extend? +# (Probably not, as we'd still need to explicitly call it - it wouldn't be implicitly called _by_ Flask) +def _jsonify(o): + return {k: v for (k, v) in o.__dict__.items() if k != "_sa_instance_state"} diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..053fd33 --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,19 @@ + + +
+ {% block head %} +The description of the deck is: {{ description }}
+ {% if deck.description %} +The description of the deck is: {{ deck.description }}
{% endif %} -