diff --git a/app/blueprints/char.py b/app/blueprints/char.py index 0289a5c..683ffcb 100644 --- a/app/blueprints/char.py +++ b/app/blueprints/char.py @@ -1,8 +1,15 @@ -from flask import Blueprint, g, jsonify, current_app +from flask import Blueprint, g, request, jsonify, current_app from typing import cast -from app.utils.typed_flask import CoCFlask +from dataclasses import asdict -# from app.services.appwrite_client import AppWriteClient +from app.utils.typed_flask import CoCFlask +from app.utils.session_user import SessionUser, import_g_session +from app.game.generators.entity_factory import build_char +from app.services.appwrite_db import AppwriteTables + +from app.utils.logging import get_logger + +logging = get_logger(__file__) # type cast flask to my custom flask app so the app.api methods are available in the IDE / typed correctly. app = cast(CoCFlask,current_app) @@ -10,8 +17,36 @@ app = cast(CoCFlask,current_app) # blueprint def char_bp = Blueprint("char", __name__, url_prefix="/char") +# return CURRENT USER +# {"user":g.appwrite_user} -@char_bp.route("/", methods=["GET", "POST"]) -def char(): - return app.api.ok({"user":g.appwrite_user}) +@char_bp.route("/new", methods=["POST"]) +def new(): + api_user = import_g_session(g.appwrite_user) + + data = request.get_json(silent=True) + + name = data.get("name") + origin_story = data.get("origin_story") + race_id = data.get("race_id") + profession_id = data.get("profession_id") + + try: + player = build_char(name=name,origin_story=origin_story,race_id=race_id,profession_id=profession_id) + player_dict = asdict(player) + uuid = player.uuid + + tablesdb = AppwriteTables() + tablesdb.save_character_for_user_id(api_user.id,player_dict) + + + logging.info(f"Created char {uuid} for {api_user.id} - {api_user.name} - {api_user.email}") + + except Exception as e: + logging.error(f"Unable to create char for user: {api_user.id} due to {e}") + player_dict = {} + + uuid = player_dict.get("uuid",{}) + print(f"Returned {uuid}") + return app.api.ok(uuid) \ No newline at end of file diff --git a/app/game/generators/abilities_factory.py b/app/game/generators/abilities_factory.py index 0cb5875..42ba2da 100644 --- a/app/game/generators/abilities_factory.py +++ b/app/game/generators/abilities_factory.py @@ -51,6 +51,9 @@ PATH_THEMES = _load_path_themes(themes_filename) # ----------------- Generator ----------------- def generate_abilities_direct_damage(class_name: str,path: str,level: int, primary_stat:str , per_tier: int = 2,content_version: int = 1) -> List[Ability]: + if path not in PATH_THEMES: + return [] + theme = PATH_THEMES[path] rng = random.Random(_stable_seed(class_name, path, level, per_tier, version=content_version)) spells: List[Ability] = [] diff --git a/app/game/generators/entity_factory.py b/app/game/generators/entity_factory.py index 1374203..4fb10f2 100644 --- a/app/game/generators/entity_factory.py +++ b/app/game/generators/entity_factory.py @@ -13,24 +13,25 @@ dice = Dice() progression = DEFAULT_LEVEL_PROGRESSION -def build_char(name:str, origin_story:str, race_id:str, profession_id:str,ability_pathway:str,level:int=1) -> Entity: +def build_char(name:str, origin_story:str, race_id:str, profession_id:str,ability_pathway:str="",level:int=1) -> Entity: races = RaceRepository() professions = ProfessionRepository() race = races.get(race_id) profession = professions.get(profession_id) - profession.ability_paths e = Entity( uuid = str(uuid.uuid4()), name = name, origin_story = origin_story, race =race, - ability_pathway=ability_pathway, profession = profession ) + if ability_pathway != "": + e.ability_pathway=ability_pathway + # apply race ability scores for stat, delta in vars(race.ability_scores).items(): diff --git a/app/game/systems/leveling.py b/app/game/systems/leveling.py index d93ce1b..e31c3ad 100644 --- a/app/game/systems/leveling.py +++ b/app/game/systems/leveling.py @@ -32,7 +32,7 @@ def set_level(entity:Entity, target_level: int, prog: LevelProgression) -> None: # entity get's a random number of spells based on the number of levels gained skills_per_level = calculate_skills_gained(current,target_level) - + skills = newly_unlocked_abilities(class_name=entity.profession.name, path=entity.ability_pathway, level=target_level, diff --git a/app/services/appwrite_db.py b/app/services/appwrite_db.py new file mode 100644 index 0000000..57e71f0 --- /dev/null +++ b/app/services/appwrite_db.py @@ -0,0 +1,102 @@ +from __future__ import annotations +import os +from dataclasses import dataclass, asdict +from enum import StrEnum +from typing import Final, Optional +from flask import current_app + +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.query import Query +from appwrite.id import ID + +from app.game.models.entities import Entity + +from app.utils.logging import get_logger +from app.utils.settings import get_settings, Environment + +settings = get_settings() +logger = get_logger(__file__) + + +class Env(StrEnum): + PROD = "prod" + DEV = "dev" + +# --- Database schemas (strongly-typed namespaces) ---------------------------- + +@dataclass(frozen=True) +class Database: + """Schema for a single database: each attribute is a table name (or ID).""" + id: str + characters: str + inventory: str + # add more tables here as you grow your schema + +@dataclass(frozen=True) +class Databases: + """Top-level namespace exposing prod/dev as attributes.""" + prod: Database + dev: Database + +DB: Final[Databases] = Databases( + prod=Database( + id="SETME", # actual DB / ID + characters="SETME", # actual table / ID + inventory="inventory", + ), + dev=Database( + id="69041f9600177b675485", + characters="69050f830024afb0d253", + inventory="inventory", + ), +) + +class AppwriteTables: + + def __init__(self): + print() + self.client = (Client() + .set_endpoint(settings.appwrite_endpoint) + .set_project(settings.appwrite_project_id) + .set_key(settings.appwrite_api_key) + ) + self.tables_db = TablesDB(self.client) + self.env = Env.DEV + if settings.env == Environment.PROD: + self.env = Env.PROD + + @property + def db(self) -> Database: + # Gives autocompletion for .character, .users, etc. + return DB.prod if self.env is Env.PROD else DB.dev + + def get_characters_for_user_id(self, user_id: str) -> Optional[dict]: + + try: + result = self.tables_db.list_rows( + self.db.id, + self.db.characters, + [ + Query.equal('player_id', [str(user_id)]), + ] + ) + except Exception as e: + logger.error(f"Unable to list rows for char. User id: {user_id}") + return {} + + return result.get("rows",{}) + + def save_character_for_user_id(self, user_id:str, player_dict:dict): + result = self.tables_db.create_row( + database_id = self.db.id, + table_id = self.db.characters, + row_id = ID.unique(), + data = { + "player_id": str(user_id), + "char_data": str(player_dict), + }, + ) + print(result) + return result + diff --git a/app/utils/session_user.py b/app/utils/session_user.py new file mode 100644 index 0000000..de1bb1d --- /dev/null +++ b/app/utils/session_user.py @@ -0,0 +1,37 @@ +from dataclasses import dataclass +from typing import Optional, cast + + +@dataclass +class SessionUser: + id: str = "" + registered_on: str = "" + name: str = "" + email: str = "" + email_verified: bool = False + phone: str = "" + phone_verified: bool = False + mfa: bool = False + + + @property + def is_authenticated(self) -> bool: + return True + + @property + def email_verification(self) -> bool: + return self.email_verified + + @property + def phone_verification(self) -> bool: + return self.phone_verification + +def import_g_session(g_session:dict) -> SessionUser: + u = SessionUser( + id=g_session.get("$id"), + name=g_session.get("name"), + registered_on=g_session.get("registration"), + email=g_session.get("email"), + email_verified=g_session.get("emailVerification") + ) + return u \ No newline at end of file