Initial commit
This commit is contained in:
commit
25ba7968d5
7 changed files with 381 additions and 0 deletions
0
src/random_access/__init__.py
Normal file
0
src/random_access/__init__.py
Normal file
15
src/random_access/cli.py
Normal file
15
src/random_access/cli.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import click
|
||||
import uvicorn
|
||||
from random_access.main import app
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
"""Random Access Server CLI."""
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.option("--host", default="127.0.0.1", help="Host to bind to.")
|
||||
@click.option("--port", default=8000, help="Port to bind to.")
|
||||
def run(host, port):
|
||||
"""Run the FastAPI app."""
|
||||
uvicorn.run(app, host=host, port=port)
|
||||
15
src/random_access/db.py
Normal file
15
src/random_access/db.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from sqlmodel import SQLModel, create_engine, Session
|
||||
|
||||
DATABASE_URL = "sqlite:////home/micha/Documents/random-access/database.db" # sync SQLite URL
|
||||
|
||||
# connect_args needed for SQLite to allow multithreaded access
|
||||
connect_args = {"check_same_thread": False}
|
||||
|
||||
engine = create_engine(DATABASE_URL, echo=True, connect_args=connect_args)
|
||||
|
||||
def init_db():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
def get_session():
|
||||
with Session(engine) as session:
|
||||
yield session
|
||||
60
src/random_access/main.py
Normal file
60
src/random_access/main.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
from contextlib import asynccontextmanager
|
||||
from typing import Annotated
|
||||
from fastapi import FastAPI, Depends, HTTPException, Header
|
||||
from sqlmodel import select, Session
|
||||
from .db import get_session, init_db
|
||||
from .models import Player, World
|
||||
from secrets import token_urlsafe
|
||||
from argon2 import PasswordHasher
|
||||
|
||||
hasher = PasswordHasher()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_: FastAPI):
|
||||
init_db()
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
@app.post("/players/")
|
||||
def create_player(player: Player, session: Session = Depends(get_session)) -> Player:
|
||||
session.add(player)
|
||||
session.commit()
|
||||
session.refresh(player)
|
||||
return player
|
||||
|
||||
|
||||
@app.get("/players/location")
|
||||
def read_player_location(
|
||||
api_token: Annotated[str | None, Header()], session: Session = Depends(get_session)
|
||||
):
|
||||
if not api_token:
|
||||
raise HTTPException(status_code=401, detail="No API token provided")
|
||||
hashed_token = hasher.hash(api_token)
|
||||
results = session.exec(select(Player).where(Player.api_token == hashed_token))
|
||||
if len(results.all()) != 1:
|
||||
raise HTTPException(status_code=500, detail="Critical error, please report this!")
|
||||
|
||||
|
||||
|
||||
@app.get("/players/")
|
||||
def read_players(session: Session = Depends(get_session)) -> list[dict[str, int | str]]:
|
||||
players = [
|
||||
{
|
||||
"id": player.id,
|
||||
"username": player.in_game_name,
|
||||
"current_world_id": player.current_world_id,
|
||||
"level": player.level,
|
||||
}
|
||||
for player in list(session.exec(select(Player)).all())
|
||||
]
|
||||
return players
|
||||
|
||||
|
||||
@app.get("/worlds")
|
||||
def read_worlds(session: Session = Depends(get_session)):
|
||||
worlds = list(session.exec(select(World)).all())
|
||||
return worlds
|
||||
73
src/random_access/models.py
Normal file
73
src/random_access/models.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import datetime
|
||||
from decimal import Decimal
|
||||
from enum import Enum
|
||||
from typing import Optional, List
|
||||
from sqlmodel import Field, Relationship, SQLModel
|
||||
from sqlalchemy import Column, Integer
|
||||
|
||||
|
||||
class ItemCategory(str, Enum):
|
||||
ranged_weapon = "ranged_weapon"
|
||||
melee_weapon = "melee_weapon"
|
||||
armor_head = "armor_head"
|
||||
armor_torso = "armor_torso"
|
||||
armor_legs = "armor_legs"
|
||||
armor_shoes = "armor_shoes"
|
||||
potion = "potion"
|
||||
throwable_potion = "throwable_potion"
|
||||
special = "special"
|
||||
shield = "shield"
|
||||
resource = "resource"
|
||||
|
||||
|
||||
class World(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str
|
||||
|
||||
creator_id: Optional[int] = Field(default=None, foreign_key="player.id")
|
||||
creator: Optional["Player"] = Relationship(
|
||||
back_populates="personal_world", sa_relationship_kwargs={"foreign_keys": "[World.creator_id]"}
|
||||
)
|
||||
|
||||
inhabitants: List["Player"] = Relationship(
|
||||
back_populates="current_world", sa_relationship_kwargs={"foreign_keys": "[Player.current_world_id]"}
|
||||
)
|
||||
|
||||
|
||||
class Player(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str
|
||||
in_game_name: Optional[str]
|
||||
slack_id: str = Field(index=True)
|
||||
api_token: str = Field(index=True)
|
||||
token_last_updated: datetime.datetime
|
||||
|
||||
pos_x: int = Field(default=0)
|
||||
pos_y: int = Field(default=0)
|
||||
|
||||
inventory: List["Item"] = Relationship(back_populates="player")
|
||||
|
||||
level: int = Field(default=1, le=100)
|
||||
xp: int = Field(default=0, lt=1000)
|
||||
|
||||
personal_world: Optional["World"] = Relationship(
|
||||
back_populates="creator", sa_relationship_kwargs={"foreign_keys": "[World.creator_id]"}
|
||||
)
|
||||
|
||||
current_world_id: Optional[int] = Field(default=None, foreign_key="world.id")
|
||||
current_world: Optional["World"] = Relationship(
|
||||
back_populates="inhabitants", sa_relationship_kwargs={"foreign_keys": "[Player.current_world_id]"}
|
||||
)
|
||||
|
||||
|
||||
class Item(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str
|
||||
category: ItemCategory
|
||||
cost: Optional[Decimal] = None
|
||||
can_be_sold: bool = Field(default=False)
|
||||
is_breakable: bool = Field(default=False)
|
||||
hitpoints_remaining: Optional[Decimal] = None
|
||||
|
||||
player_id: Optional[int] = Field(default=None, foreign_key="player.id")
|
||||
player: Optional[Player] = Relationship(back_populates="inventory")
|
||||
Loading…
Add table
Add a link
Reference in a new issue