built ability to create a character
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
@@ -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"))
|
|
||||||
@@ -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="")
|
|
||||||
@@ -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")
|
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -10,8 +10,10 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"]
|
|
||||||
12
docs/arch.md
12
docs/arch.md
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user