Repository navigation
Expand file tree
/
Copy pathapp.py
More file actions
204 lines (174 loc) · 6.4 KB
/
Copy pathapp.py
File metadata and controls
204 lines (174 loc) · 6.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
import os
import atexit
import re
from datetime import timedelta
from apscheduler.schedulers.background import BackgroundScheduler
from flask import Flask, has_request_context, render_template, send_file
import config
from flask_oidc import OpenIDConnect
from flask_session import Session
from redis import Redis
from utils.wiis import fetch_wii_color_from_number
from utils.utils import (
format_serial,
format_playtime,
close_db_connections,
cache as query_cache,
)
from utils.theme import get_user_theme
from channels.cmoc import get_artisan_ids_from_wii_number
# Import blueprint modules
from routes.auth import (
auth_routes_bp,
set_oidc as set_oidc_auth,
get_logged_in_user_info,
)
from routes.public import public_routes_bp
from routes.trending import trending_bp
from routes.digicard import digicard_bp, set_oidc as set_oidc_digicard
from routes.misc import misc_routes_bp
from routes.coupons_admin import coupons_admin_bp
from utils.cache import init_cache, generate_top_page_cache
from utils.db import init_db
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = config.db_url
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] = config.secret_key
app.config["OIDC_CLIENT_SECRETS"] = config.oidc_client_secrets_json
app.config["OIDC_SCOPES"] = "openid profile email offline_access achievements groups"
app.config["OIDC_OVERWRITE_REDIRECT_URI"] = config.oidc_redirect_uri
app.config["SESSION_TYPE"] = "redis"
app.config["SESSION_REDIS"] = Redis(
host=config.redis_host, password=config.redis_password
)
app.config["SESSION_PERMANENT"] = True
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(days=30)
app.config["SESSION_USE_SIGNER"] = True
oidc = OpenIDConnect(app)
Session(app)
init_cache(app)
app.config["CACHE_TYPE"] = "SimpleCache"
query_cache.init_app(app)
# SQLAlchemy scoped session teardown for the checkout database
init_db(app)
@app.route("/sw.js")
def service_worker():
"""Serve the service worker at the origin root so its scope covers the site."""
response = send_file(os.path.join(os.path.dirname(__file__), "static", "sw.js"))
response.headers["Service-Worker-Allowed"] = "/"
response.headers["Cache-Control"] = "no-cache"
return response
# Register template filters
app.jinja_env.filters["format_serial"] = format_serial
app.jinja_env.filters["format_playtime"] = format_playtime
app.jinja_env.filters["slugify"] = lambda value: re.sub(
r"[^a-z0-9-]", "", str(value).lower().replace(" ", "-")
)
app.jinja_env.filters["user_theme"] = get_user_theme
@app.context_processor
def inject_artisan_id():
"""Inject artisan IDs for logged-in user into template context."""
artisan_ids = []
is_coupon_admin = False
# Only check for user login if we're in a request context
if has_request_context() and oidc.user_loggedin:
user_info = get_logged_in_user_info()
if user_info and user_info.get("linked_wii_no"):
wii_number = user_info["linked_wii_no"][0]
artisan_ids = get_artisan_ids_from_wii_number(wii_number)
groups = (user_info or {}).get("groups") or []
is_coupon_admin = config.coupon_admin_group_uuid in groups
return dict(artisan_ids=artisan_ids, is_coupon_admin=is_coupon_admin)
@app.context_processor
def utility_processor():
return dict(fetch_wii_color_from_number=fetch_wii_color_from_number)
# Set OIDC instance for blueprints that need it
set_oidc_auth(oidc)
set_oidc_digicard(oidc)
# Register blueprints
app.register_blueprint(auth_routes_bp)
app.register_blueprint(public_routes_bp)
app.register_blueprint(trending_bp)
app.register_blueprint(digicard_bp)
app.register_blueprint(misc_routes_bp)
app.register_blueprint(coupons_admin_bp)
# Background scheduler for cache generation
scheduler = BackgroundScheduler()
scheduler.add_job(generate_top_page_cache, "cron", hour=0, minute=0)
scheduler.start()
# Close all database connections when the server shuts down
atexit.register(close_db_connections)
# If any cache file is missing on startup, generate the cache immediately
cache_dir = os.path.join(os.path.dirname(__file__), "cache")
required_cache_files = [
"top_most_played.html",
"top_best_games.html",
"top_favorites.html",
]
cache_files = set(os.listdir(cache_dir)) if os.path.exists(cache_dir) else set()
if not all(f in cache_files for f in required_cache_files):
print("Cache directory incomplete. Generating cache...")
generate_top_page_cache()
# Global error handlers
@app.errorhandler(404)
def handle_404(error):
"""Handle 404 Not Found errors"""
user_info = get_logged_in_user_info()
return (
render_template(
"errors/error.html",
error_code=404,
error_title="Page Not Found",
error_message="The page you're looking for doesn't exist.",
error_details="Make sure the URL is correct and try again.",
user_info=user_info,
),
404,
)
@app.errorhandler(500)
def handle_500(error):
"""Handle 500 Internal Server errors"""
user_info = get_logged_in_user_info()
return (
render_template(
"errors/error.html",
error_code=500,
error_title="Internal Server Error",
error_message="Something went wrong on our end.",
error_details="The server encountered an unexpected error. Please try again later.",
user_info=user_info,
),
500,
)
@app.errorhandler(403)
def handle_403(error):
"""Handle 403 Forbidden errors"""
user_info = get_logged_in_user_info()
return (
render_template(
"errors/error.html",
error_code=403,
error_title="Access Forbidden",
error_message="You don't have permission to access this resource.",
error_details="If you believe this is a mistake, please contact an administrator.",
user_info=user_info,
),
403,
)
@app.errorhandler(400)
def handle_400(error):
"""Handle 400 Bad Request errors"""
user_info = get_logged_in_user_info()
return (
render_template(
"errors/error.html",
error_code=400,
error_title="Bad Request",
error_message="The request was invalid or malformed.",
error_details="Please check your input and try again.",
user_info=user_info,
),
400,
)
if __name__ == "__main__":
app.run(host="::", port=8080, debug=True)