built ability to create a character

This commit is contained in:
2025-11-03 21:42:11 -06:00
parent 875acbbe65
commit efdf3570c5
13 changed files with 44 additions and 186 deletions

View File

@@ -1,15 +1,22 @@
import os import os
from flask import Flask, redirect, url_for, request, g, session, flash from flask import Flask, redirect, url_for, request, g, session, flash
from .blueprints.auth.routes import auth_bp
from .blueprints.main.routes import main_bp # import blueprints
from .blueprints.public.routes import public_bp from app.blueprints.auth import auth_bp
from app.blueprints.main import main_bp
from app.blueprints.char import char_bp
from app.blueprints.public import public_bp
from app.blueprints.ajax import ajax_bp
# load_dotenv() # load_dotenv()
from .utils.settings import get_settings from .utils.settings import get_settings
from app.utils.logging import get_logger
from .utils.session_user import SessionUser from .utils.session_user import SessionUser
logger = get_logger()
settings = get_settings() settings = get_settings()
def create_app(): def create_app():
app = Flask(__name__, template_folder="templates") app = Flask(__name__, template_folder="templates")
app.config.update( app.config.update(
@@ -25,7 +32,9 @@ def create_app():
# Blueprints # Blueprints
app.register_blueprint(auth_bp) app.register_blueprint(auth_bp)
app.register_blueprint(main_bp) app.register_blueprint(main_bp)
app.register_blueprint(char_bp)
app.register_blueprint(public_bp) app.register_blueprint(public_bp)
app.register_blueprint(ajax_bp)
@app.before_request @app.before_request
def require_login(): def require_login():
@@ -68,7 +77,6 @@ def create_app():
@app.before_request @app.before_request
def load_user(): def load_user():
user_data = session.get("user") user_data = session.get("user")
print(user_data)
if user_data: if user_data:
g.current_user = SessionUser( g.current_user = SessionUser(

View File

@@ -1,113 +0,0 @@
# app/auth/routes.py
from flask import Blueprint, render_template, request, redirect, url_for, flash, session,make_response
from flask_login import login_user, logout_user, login_required
from app.services.appwrite_client import AppWriteClient
auth_bp = Blueprint("auth", __name__, url_prefix="/auth")
@auth_bp.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
email = request.form.get("email", "").strip()
password = request.form.get("password", "")
name = (request.form.get("name") or "").strip() or None
if not email or not password:
flash("Email and password are required.", "error")
return redirect(url_for("auth.register"))
try:
aw = AppWriteClient()
aw.create_new_user(email,password,name)
login_valid, error = aw.log_user_in(email,password)
if login_valid:
flash("Account created and you are now logged in.", "success")
return redirect(url_for("main.dashboard"))
else:
flash(str(error), "error")
except Exception as e:
flash(str(e), "error")
return redirect(url_for("auth.register"))
return render_template("auth/register.html")
@auth_bp.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
email = request.form.get("email", "").strip()
password = request.form.get("password", "")
if not email or not password:
flash("Email and password are required.", "error")
return redirect(url_for("auth.login"))
aw = AppWriteClient()
login_valid, error = aw.log_user_in(email,password)
try:
if login_valid:
username = session.get("user",{}).get("name","User")
flash(f"Welcome Back {username}", "success")
return redirect(url_for("main.dashboard"))
else:
flash(str(error), "error")
except Exception as e:
flash(str(e), "error")
return redirect(url_for("auth.login"))
# Get method
return render_template("auth/login.html")
@auth_bp.route("/logout", methods=["GET", "POST"])
def logout():
aw = AppWriteClient()
aw.log_user_out()
session.clear()
flash("Signed out.", "success")
return redirect(url_for("auth.login"))
@auth_bp.route("/send", methods=["POST"])
def send():
"""
Sends a verification email to the currently logged-in user.
Appwrite will redirect the user back to /verify/callback with userId & secret.
"""
try:
aw = AppWriteClient()
aw.send_email_verification()
flash("Verification email sent. Please check your inbox.", "info")
except Exception as e:
flash(f"Could not send verification email: {e}", "error")
# Go back to where the user came from, or your dashboard
return redirect(request.referrer or url_for("main.dashboard"))
@auth_bp.route("/callback", methods=["GET"])
def callback():
"""
Completes verification after user clicks the email link.
Requires the user to be logged in (Appwrite Account endpoints need a session).
If not logged in, we stash the link params and send them to log in first.
"""
aw = AppWriteClient()
user_id = request.args.get("userId")
secret = request.args.get("secret")
if not user_id or not secret:
flash("Invalid verification link.", "error")
return redirect(url_for("auth.login"))
try:
# If we don't currently have an Appwrite session, ask them to log in, then resume.
if not aw.verify_email(user_id,secret):
session["pending_verification"] = {"userId": user_id, "secret": secret}
flash("Please log in to complete email verification.", "warning")
return redirect(url_for("auth.login"))
# We have a session; complete verification
flash("Email verified! You're all set.", "success")
return redirect(url_for("main.dashboard"))
except Exception as e:
flash(f"Verification failed: {e}", "error")
return redirect(url_for("auth.login"))

View File

@@ -1,10 +0,0 @@
from flask import Blueprint, redirect, url_for, render_template, session,flash
from app.services.appwrite_client import AppWriteClient
aw = AppWriteClient()
main_bp = Blueprint("main", __name__, url_prefix="/main")
@main_bp.route("/dashboard")
def dashboard():
return render_template("main/dashboard.html", profile="", jwt_info="")

View File

@@ -1,10 +0,0 @@
from flask import Blueprint, redirect, url_for, render_template, session,flash
from app.services.appwrite_client import AppWriteClient
public_bp = Blueprint("public", __name__, url_prefix="/")
@public_bp.route("/")
def home():
return render_template("public/home.html")

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import os import os
from typing import Optional, Dict, Any, Mapping, Union, List from typing import Optional, Dict, Any, Mapping, Union, List
from flask import session, redirect, url_for from flask import session, redirect, url_for, request
from appwrite.client import Client from appwrite.client import Client
from appwrite.services.account import Account from appwrite.services.account import Account
from appwrite.id import ID from appwrite.id import ID
@@ -60,6 +60,7 @@ class AppWriteClient:
client = (Client() client = (Client()
.set_endpoint(ENDPOINT) .set_endpoint(ENDPOINT)
.set_project(PROJECT_ID) .set_project(PROJECT_ID)
.set_forwarded_user_agent(request.headers.get('user-agent'))
) )
if session[self.session_key] is not None: if session[self.session_key] is not None:
@@ -82,6 +83,25 @@ class AppWriteClient:
user = user_account.get() user = user_account.get()
session['user']=user session['user']=user
def get_user_from_jwt_token(self, jwt_token:str):
try:
client = (Client()
.set_endpoint(ENDPOINT)
.set_project(PROJECT_ID)
.set_jwt(jwt_token)
)
return Account(client).get()
except Exception as e:
return {}
def mint_jwt(self):
try:
client = self._get_user_client()
account = Account(client)
return account.create_jwt()
except Exception as e:
return ""
def log_user_in(self, email:str,password:str): def log_user_in(self, email:str,password:str):
admin_client = self._get_admin_client() admin_client = self._get_admin_client()
try: try:

View File

@@ -29,11 +29,10 @@
<p class="mb-0"><span class="version">v 0.1.0</span></p> <p class="mb-0"><span class="version">v 0.1.0</span></p>
</footer> </footer>
<!-- App Write JS Client -->
<script src="https://cdn.jsdelivr.net/npm/appwrite@17.0.0"></script>
<!-- Bootstrap JS --> <!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha256-CDOy6cOibCWEdsRiZuaHf8dSGGJRYuBGC+mjoJimHGw=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha256-CDOy6cOibCWEdsRiZuaHf8dSGGJRYuBGC+mjoJimHGw=" crossorigin="anonymous"></script>
{% include "_flash_sticky.html" %} {% include "_flash_sticky.html" %}
{% block scripts %}{% endblock %} {% block scripts %}{% endblock %}
</body> </body>

View File

@@ -59,7 +59,7 @@
<!-- Bootstrap JS --> <!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha256-CDOy6cOibCWEdsRiZuaHf8dSGGJRYuBGC+mjoJimHGw=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha256-CDOy6cOibCWEdsRiZuaHf8dSGGJRYuBGC+mjoJimHGw=" crossorigin="anonymous"></script>
<!-- <script src="/static/halfmoon/halfmoon-1.1.1.min.js"></script> -->
{% include "_flash_sticky.html" %} {% include "_flash_sticky.html" %}
{% block scripts %}{% endblock %} {% block scripts %}{% endblock %}
</body> </body>

View File

@@ -10,10 +10,12 @@
<h1 class="mb-0"> <h1 class="mb-0">
Code of Conquest Dashboard Code of Conquest Dashboard
</h1> </h1>
<p class="mb-0"></p> <p class="mb-0">
<img src="{{ url_for('static', filename='images/COC_Logo.png') }}" alt="logo" width="300" height="300"> {{jwt_info}}
{{profile}}
</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -5,8 +5,9 @@
<h5 class="card-title mb-4"> <h5 class="card-title mb-4">
<i class="fa-solid fa-right-to-bracket me-2"></i> Welcome to Code of Conquest <i class="fa-solid fa-right-to-bracket me-2"></i> Welcome to Code of Conquest
</h5> </h5>
<img src="{{ url_for('static', filename='images/COC_Logo.png') }}" alt="logo" width="300" height="300"><br />
<p style="text-align: justify;"> <p style="text-align: justify;">
In the world of Code of Conquest, the line between hero and villain blurs as you embark on a legendary adventure. In the world of Code of Conquest, the line between hero and villain blurs as you embark on a legendary adventure.
This immersive game drops you into a realm of wonder and danger, where every decision, every action, and every This immersive game drops you into a realm of wonder and danger, where every decision, every action, and every
roll of the dice determines the fate of your character.<br /><br /> roll of the dice determines the fate of your character.<br /><br />

View File

@@ -85,7 +85,7 @@ def configure_logging(settings=None) -> None:
return return
if settings is None: if settings is None:
from app.core.utils.settings import get_settings # lazy import from app.utils.settings import get_settings # lazy import
settings = get_settings() settings = get_settings()
env = settings.env.value env = settings.env.value

View File

@@ -1,27 +0,0 @@
import time
from flask import session
from ..services.appwrite_client import AppwriteAccountClient
def ensure_fresh_appwrite_jwt(skew_seconds: int = 120) -> str:
"""
Returns a valid Appwrite JWT, refreshing it if it's missing or expiring soon.
Relies on the saved Appwrite session cookie in Flask's session.
"""
jwt_info = session.get("appwrite_jwt")
now = int(time.time())
if jwt_info and isinstance(jwt_info, dict):
exp = int(jwt_info.get("expire", 0))
# If token still safely valid, reuse it
if exp - now > skew_seconds and "jwt" in jwt_info:
return jwt_info["jwt"]
# Need to mint a new JWT using the user's Appwrite session cookie
cookies = session.get("appwrite_cookies")
if not cookies:
raise RuntimeError("Missing Appwrite session; user must sign in again.")
aw = AppwriteAccountClient(cookies=cookies)
new_jwt = aw.create_jwt() # -> {"jwt": "...", "expire": <unix>}
session["appwrite_jwt"] = new_jwt
return new_jwt["jwt"]

View File

@@ -22,18 +22,6 @@
│ (auth, game OLTP + semantic vectors) │ │ (auth, game OLTP + semantic vectors) │
└─────────────────────────────────────────────┘ └─────────────────────────────────────────────┘
``` ```
## Front end / Back end auth
---
```
[Frontend] ── login(email,pass) ─▶ [API Gateway] ─▶ [Auth Service]
│ │
│ <── Set-Cookie: access_token=JWT ───┘
├─▶ call /game/start (cookie auto-attached)
└─▶ logout → clear cookie
```
--- ---
## Services & responsibilities ## Services & responsibilities

View File

@@ -1,9 +1,9 @@
appwrite==13.5.0
blinker==1.9.0 blinker==1.9.0
certifi==2025.10.5 certifi==2025.10.5
charset-normalizer==3.4.4 charset-normalizer==3.4.4
click==8.3.0 click==8.3.0
Flask==3.1.2 Flask==3.1.2
Flask-Login==0.6.3
idna==3.11 idna==3.11
itsdangerous==2.2.0 itsdangerous==2.2.0
Jinja2==3.1.6 Jinja2==3.1.6