Feat: Multiplayer sessions added using CRUD database.

This commit is contained in:
2026-02-10 11:49:38 +00:00
parent bbbd21d4ad
commit fa81fddbd4
6850 changed files with 808827 additions and 8 deletions

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
.env
*.log
*.log.*
env_tcg/
__pycache__/

40
README.md Normal file
View File

@@ -0,0 +1,40 @@
Magic: The Gathering trading card game commander life tracking tool.
Powered by flask
enter virtual environment:
python -m venv VIRTUAL_ENVIRONMENT_NAME
run module bundler:
npm run build
host for machine:
python -m flask run
host for local network:
python -m flask run --host=0.0.0.0
files dedicated to each page:
CSS
page
HTML
page
row
JavaScript
page
api
router
base - navigation buttons
PostgreSQL
get
save
table
staging table
audit table
Python
business object
controller
datastore
form
model

20
__init__.py Normal file
View File

@@ -0,0 +1,20 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Backend
Feature: Initialisation
Description:
Initialises the Flask application.
Initialises any extensions used in the project.
"""
from flask import Flask
app = Flask(__name__)
app.config.from_object('config')
# from app import routes
# import business_objects, lib, models

135
app.py Normal file
View File

@@ -0,0 +1,135 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: App General
Feature: App
Description:
Initialises the Flask application, sets the configuration based on the environment, and defines two routes (/ and /about) that render templates with the specified titles.
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
# internal
from config import app_config, Config
from controllers.legal.legal import routes_legal
from controllers.tcg.mtg_game import routes_mtg_game
from controllers.user.user import routes_user
from extensions import db, csrf, mail, oauth
from helpers.helper_app import Helper_App
# external
from flask import Flask, render_template, jsonify, request, render_template_string, send_from_directory, redirect, url_for, session
# from flask_appconfig import AppConfig
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
from flask_mail import Mail, Message
from flask_wtf.csrf import CSRFProtect
from werkzeug.exceptions import HTTPException
from authlib.integrations.flask_client import OAuth
import os
import sys
from logging.handlers import RotatingFileHandler
import traceback
import logging
sys.path.insert(0, os.path.dirname(__file__)) # Todo: why?
app = Flask(__name__)
app.secret_key = os.getenv('KEY_SECRET_FLASK')
# AppConfig(app)
app.config.from_object(app_config) # for db init with required keys
app.app_config = app_config
# app.config["config"] = app_config()
# logging
handler = RotatingFileHandler('app.log', maxBytes=10000, backupCount=3)
handler.setLevel(logging.DEBUG)
app.logger.addHandler(handler)
@app.errorhandler(Exception)
def internal_server_error(error):
if isinstance(error, HTTPException) and error.code == 404:
return "Not Found", 404
app.logger.error('Server Error: %s', (error))
app.logger.error('Request: %s %s %s %s %s',
request.remote_addr,
request.method,
request.scheme,
request.full_path,
request.headers)
app.logger.error('Request data: %s', request.get_data())
app.logger.error('Traceback: %s', traceback.format_exc())
return "Internal Server Error", 500
"""
@app.before_first_request
def clear_session():
session.clear()
"""
@app.before_request
def make_session_permanent():
session.permanent = True
csrf = CSRFProtect()
cors = CORS(app, resources={
r"/static/*": {
"origins": [app.config["URL_HOST"]],
"methods": ["GET"],
"max_age": 3600
}
})
csrf.init_app(app)
cors.init_app(app)
db.init_app(app)
mail.init_app(app)
oauth.init_app(app)
with app.app_context():
# config = app.config["config"]
db.create_all()
db.engine.url = app.config['SQLALCHEMY_DATABASE_URI']
oauth.register(
"auth0",
client_id = app.config['ID_AUTH0_CLIENT'],
client_secret = app.config['ID_AUTH0_CLIENT_SECRET'],
client_kwargs={
"scope": "openid profile email",
},
server_metadata_url=f'https://{app.config["DOMAIN_AUTH0"]}/.well-known/openid-configuration',
api_base_url = f'https://{app.config["DOMAIN_AUTH0"]}',
authorize_url = f'https://{app.config["DOMAIN_AUTH0"]}/authorize',
access_token_url = f'https://{app.config["DOMAIN_AUTH0"]}/oauth/token',
)
print(f"Registered clients: {list(oauth._clients.keys())}")
app.register_blueprint(routes_legal)
app.register_blueprint(routes_user)
app.register_blueprint(routes_mtg_game)
@app.template_filter('console_log')
def console_log(value):
Helper_App.console_log(value)
return value
@app.after_request
def add_cache_headers(response):
if request.path.startswith('/static/'):
# Cache static assets
response.headers['Cache-Control'] = 'public, max-age=31536000'
else:
# No caching for dynamic content
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
return response

View File

@@ -0,0 +1,11 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Module Initialisation
Feature: Business Objects
Description:
Initialises business objects module.
"""

32
business_objects/api.py Normal file
View File

@@ -0,0 +1,32 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Base Business Object
Description:
Abstract business object
"""
# internal
from extensions import db
import lib.argument_validation as av
# external
from typing import ClassVar
from flask import jsonify
class API():
@staticmethod
def get_standard_response(success: bool, status_code: int, message: str, data: any, errors: list, meta: dict):
return jsonify({
"success": success,
"status_code": status_code,
"message": message,
"data": data,
"errors": errors,
"meta": meta
})

102
business_objects/base.py Normal file
View File

@@ -0,0 +1,102 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Base Business Object
Description:
Abstract base class for all business objects in app
"""
# internal
from extensions import db
import lib.argument_validation as av
# external
from typing import ClassVar
class Base():
ATTR_ID_ACCESS_LEVEL: ClassVar[str] = 'id_access_level'
ATTR_ID_ADDRESS: ClassVar[str] = 'id_address'
ATTR_ID_CURRENCY: ClassVar[str] = 'id_currency'
ATTR_ID_MSG_ERROR_TYPE: ClassVar[str] = 'id_type'
ATTR_ID_REGION: ClassVar[str] = 'id_region'
ATTR_USER_ID: ClassVar[str] = 'user_id'
ATTR_USER_ID_MANAGER: ClassVar[str] = 'user_id_manager'
FLAG_ACCESS_LEVEL_REQUIRED: ClassVar[str] = 'access_level_required'
FLAG_ACTIVE: ClassVar[str] = 'active'
FLAG_ACTIVE_ONLY: ClassVar[str] = 'active_only'
FLAG_ADDRESS: ClassVar[str] = 'address'
FLAG_ADDRESS_LINE_1: ClassVar[str] = 'address_line_1'
FLAG_ADDRESS_LINE_2: ClassVar[str] = 'address_line_2'
FLAG_BACKGROUND_COLOUR: ClassVar[str] = 'background_colour'
FLAG_CAN_ADMIN: ClassVar[str] = 'can_admin'
FLAG_CAN_EDIT: ClassVar[str] = 'can_edit'
FLAG_CAN_VIEW: ClassVar[str] = 'can_view'
FLAG_CITY: ClassVar[str] = 'city'
FLAG_CODE: ClassVar[str] = 'code'
FLAG_COUNTY: ClassVar[str] = 'county'
FLAG_CREATED_BY: ClassVar[str] = 'created_by'
FLAG_CREATED_ON: ClassVar[str] = 'created_on'
FLAG_CURRENCY: ClassVar[str] = 'currency'
FLAG_CURRENCY_COST: ClassVar[str] = 'currency_cost'
FLAG_DATE_FROM: ClassVar[str] = 'date_from'
FLAG_DATE_TO: ClassVar[str] = 'date_to'
FLAG_DESCRIPTION: ClassVar[str] = 'description'
FLAG_DISPLAY_ORDER: ClassVar[str] = 'display_order'
FLAG_EDIT: ClassVar[str] = 'edit'
FLAG_EMAIL: ClassVar[str] = 'email'
FLAG_END_ON: ClassVar[str] = 'end_on'
FLAG_FAX: ClassVar[str] = 'fax'
FLAG_FIRSTNAME: ClassVar[str] = 'firstname'
FLAG_GUID: ClassVar[str] = 'guid'
FLAG_IS_NOT_EMPTY: ClassVar[str] = 'is_not_empty'
# FLAG_KEY_PRIMARY: ClassVar[str] = 'key_primary'
FLAG_MESSAGE: ClassVar[str] = 'message'
FLAG_NAME: ClassVar[str] = 'name'
FLAG_NAME_ATTR_OPTION_TEXT: ClassVar[str] = 'NAME_ATTR_OPTION_TEXT'
FLAG_NAME_ATTR_OPTION_VALUE: ClassVar[str] = 'NAME_ATTR_OPTION_VALUE'
FLAG_NAME_SINGULAR: ClassVar[str] = 'name_singular'
FLAG_NAME_PLURAL: ClassVar[str] = 'name_plural'
FLAG_NOTES: ClassVar[str] = "notes"
FLAG_PATH: ClassVar[str] = 'path'
FLAG_PHONE_NUMBER: ClassVar[str] = 'phone_number'
FLAG_POSTCODE: ClassVar[str] = 'postcode'
FLAG_PRICE: ClassVar[str] = 'price'
FLAG_PRIORITY: ClassVar[str] = 'priority'
FLAG_QUANTITY: ClassVar[str] = 'quantity'
FLAG_REGION: ClassVar[str] = 'region'
FLAG_ROWS: ClassVar[str] = 'rows'
FLAG_SEARCH: ClassVar[str] = 'search'
FLAG_START_ON: ClassVar[str] = 'start_on'
FLAG_SURNAME: ClassVar[str] = 'surname'
FLAG_SYMBOL: ClassVar[str] = 'symbol'
FLAG_TEXT_COLOUR: ClassVar[str] = 'text_colour'
FLAG_URL: ClassVar[str] = 'url'
FLAG_USER: ClassVar[str] = 'authorisedUser' # 'user' already used
FLAG_VALUE_LOCAL_VAT_EXCL: ClassVar[str] = 'value_local_vat_excl'
FLAG_VALUE_LOCAL_VAT_INCL: ClassVar[str] = 'value_local_vat_incl'
FLAG_WEBSITE: ClassVar[str] = 'website'
USER_ID_GUEST: ClassVar[int] = 7
"""
NAME_ATTR_OPTION_TEXT: ClassVar[str] = 'name-attribute-option-text'
NAME_ATTR_OPTION_VALUE: ClassVar[str] = 'name-attribute-option-value'
"""
def __repr__(self):
attrs = '\n'.join(f'{k}={v!r}' for k, v in self.__dict__.items())
return f'<{self.__class__.__name__}(\n{attrs}\n)>'
@classmethod
def output_bool(cls, value):
return av.input_bool(value, f'{cls.__name__} bool attribute', f'{cls.__name__}.output_bool')
@staticmethod
def convert_list_objects_to_list_options(objects):
return [object.to_json_option() for object in objects]
@classmethod
def get_shared_json_attributes(cls, object):
return {
cls.FLAG_NAME_ATTR_OPTION_TEXT: object.NAME_ATTR_OPTION_TEXT
, cls.FLAG_NAME_ATTR_OPTION_VALUE: object.NAME_ATTR_OPTION_VALUE
}

View File

@@ -0,0 +1,65 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Database Base Business Objects
Description:
Abstract base class for database objects
"""
# internal
# from helpers.DEPRECATED.helper_abc import Interface_ABC
from extensions import db
import lib.argument_validation as av
# external
from typing import ClassVar
from abc import abstractmethod, ABCMeta
from pydantic import BaseModel
from sqlalchemy.ext.declarative import DeclarativeMeta
# from flask_sqlalchemy import SQLAlchemy
class SQLAlchemy_ABCMeta(db.Model.__class__, ABCMeta):
pass
class SQLAlchemy_ABC(db.Model, metaclass=SQLAlchemy_ABCMeta):
__abstract__ = True
# id = db.Column(db.Integer, primary_key=True)
def __init__(self):
pass
def __repr__(self):
pass
def to_json(self):
pass
@classmethod
def from_json(cls, json):
pass
def to_temporary_record(self):
pass
def to_object_with_missing_attributes(self, excluded_attributes):
return {
column.name: getattr(self, column.name)
for column in self.__table__.columns
if column.name not in excluded_attributes
}
class Get_Many_Parameters_Base(BaseModel, metaclass=ABCMeta):
# a_user_id: int
def __init__(self, **kwargs): # , a_user_id
super().__init__(**kwargs) # a_user_id=a_user_id,
@classmethod
@abstractmethod
def get_default(cls): # , user_id
pass
@classmethod
@abstractmethod
def from_json(self):
pass
@abstractmethod
def to_json(self):
pass # return self.dict()

View File

@@ -0,0 +1,147 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: SQL Error Business Object
Description:
Business object for SQL errors returned by Get Many Stored Procedures
"""
# internal
from business_objects.base import Base
from business_objects.db_base import SQLAlchemy_ABC, Get_Many_Parameters_Base
from extensions import db
from helpers.helper_app import Helper_App
import lib.argument_validation as av
from lib import data_types
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Uuid
from typing import ClassVar
# db = SQLAlchemy()
class SQL_Error(SQLAlchemy_ABC, Base):
ATTR_ERROR_ID: ClassVar[str] = 'error_id'
FLAG_IS_BREAKING_ERROR: ClassVar[str] = 'is_breaking_error'
__tablename__ = 'error'
__table_args__ = { 'extend_existing': True }
error_id = db.Column(db.Integer, primary_key=True)
guid = db.Column(Uuid)
id_type = db.Column(db.Integer)
code_type = db.Column(db.Text)
name_type = db.Column(db.Text)
msg = db.Column(db.Text)
display_order = db.Column(db.Integer)
active = db.Column(db.Boolean)
def __init__(self):
self.error_id = 0
super().__init__()
@classmethod
def from_db_error(cls, record):
_m = f'{cls.__qualname__}.from_db_error'
Helper_App.console_log(_m)
Helper_App.console_log(f'record: {record}')
error = cls()
error.error_id = record[0]
error.guid = record[1]
error.id_type = record[2]
error.code_type = record[3]
error.name_type = record[4]
error.msg = record[5]
error.display_order = record[6]
error.active = av.input_bool(record[7], "active", _m)
return error
@classmethod
def from_exception(cls, exception: Exception, is_breaking: bool = True):
_m = f'{cls.__qualname__}.from_exception'
Helper_App.console_log(_m)
error = cls()
error.error_id = -1
error.id_type = -1
error.code_type = type(exception).__name__
error.name_type = type(exception).__name__
error.msg = str(exception)
Helper_App.console_log(f'Error: {error}')
return error
@classmethod
def from_json(cls, json):
_m = f'{cls.__qualname__}.from_json'
error = cls()
if json is None: return error
"""
# Helper_App.console_log(f'{_m}\njson: {json}')
error.error_id = json.get(cls.ATTR_ERROR_ID, -1)
error.id_type = json.get()
error.name = json[cls.FLAG_NAME]
error.code = json.get(cls.FLAG_CODE, error.name.upper().replace(" ", "_"))
error.active = av.input_bool(json[cls.FLAG_ACTIVE], cls.FLAG_ACTIVE, _m)
# Helper_App.console_log(f'Error: {error}')
return error
"""
def to_json(self):
return {
self.ATTR_ERROR_ID: self.error_id
, Base.FLAG_GUID: self.guid
, Base.ATTR_ID_MSG_ERROR_TYPE: self.id_type
, Base.FLAG_CODE: self.code_type
, Base.FLAG_NAME: self.name_type
, Base.FLAG_MESSAGE: self.msg
, Base.FLAG_DISPLAY_ORDER: self.display_order
, Base.FLAG_ACTIVE: self.active
}
def __repr__(self):
# Helper_App.console_log(f'{cls.__qualname__}.__repr__')
return f'''
{self.__class__.__name__}(
{self.ATTR_ERROR_ID}: {self.error_id}
{Base.ATTR_ID_MSG_ERROR_TYPE}: {self.id_type}
{Base.FLAG_CODE}: {self.code_type}
{Base.FLAG_NAME}: {self.name_type}
{Base.FLAG_MESSAGE}: {self.msg}
{Base.FLAG_DISPLAY_ORDER}: {self.display_order}
{Base.FLAG_ACTIVE}: {self.active}
)
'''
class Parameters_SQL_Error(Get_Many_Parameters_Base):
guid: str # UUID stored as string for Pydantic compatibility
@classmethod
def get_default(cls, guid):
return cls(
guid = guid
)
@classmethod
def from_json(cls, json):
return cls(
guid = json.get('a_guid', None)
)
def to_json(self):
return {
'a_guid': self.guid
}
@staticmethod
def get_type_hints():
return {
'a_guid': Uuid
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,172 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: MTG Deck Business Object
"""
# internal
from business_objects.base import Base
from business_objects.db_base import SQLAlchemy_ABC, Get_Many_Parameters_Base
import lib.argument_validation as av
from extensions import db
from helpers.helper_app import Helper_App
# external
from dataclasses import dataclass
from typing import ClassVar
from sqlalchemy.types import Text, Boolean
class MTG_Deck(SQLAlchemy_ABC, Base):
ATTR_DECK_ID: ClassVar[str] = 'deck_id'
ATTR_COMMANDER_BRACKET_ID: ClassVar[str] = 'commander_bracket_id'
FLAG_DECK: ClassVar[str] = 'deck'
FLAG_IS_COMMANDER: ClassVar[str] = 'is_commander'
NAME_ATTR_OPTION_VALUE: ClassVar[str] = ATTR_DECK_ID
NAME_ATTR_OPTION_TEXT: ClassVar[str] = Base.FLAG_NAME
__tablename__ = 'tcg_mtg_deck'
__table_args__ = { 'extend_existing': True }
deck_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text)
is_commander = db.Column(db.Boolean)
commander_bracket_id = db.Column(db.Integer)
active = db.Column(db.Boolean)
created_on = db.Column(db.DateTime)
created_by_user_id = db.Column(db.Integer)
updated_last_on = db.Column(db.DateTime)
updated_last_by_user_id = db.Column(db.Integer)
change_set_id = db.Column(db.Integer)
def __init__(self):
self.deck_id = 0
self.is_new = False
super().__init__()
@classmethod
def from_db_mtg_deck(cls, query_row):
_m = f'{cls.__qualname__}.from_db_mtg_deck'
deck = cls()
deck.deck_id = query_row[0]
deck.name = query_row[1]
deck.is_commander = av.input_bool(query_row[2], cls.FLAG_IS_COMMANDER, _m)
deck.commander_bracket_id = query_row[3]
deck.active = av.input_bool(query_row[4], cls.FLAG_ACTIVE, _m)
deck.created_on = query_row[5]
deck.created_by_user_id = query_row[6]
return deck
@classmethod
def from_json(cls, json):
_m = f'{cls.__qualname__}.from_json'
deck = cls()
if json is None: return deck
deck.deck_id = json.get(cls.ATTR_DECK_ID, -1)
deck.name = json.get(cls.FLAG_NAME, None)
deck.is_commander = av.input_bool(json.get(cls.FLAG_IS_COMMANDER, True), cls.FLAG_IS_COMMANDER, _m)
deck.commander_bracket_id = json.get(cls.ATTR_COMMANDER_BRACKET_ID, None)
deck.active = av.input_bool(json.get(cls.FLAG_ACTIVE, True), cls.FLAG_ACTIVE, _m)
deck.created_on = json.get(cls.FLAG_CREATED_ON, None)
deck.created_by_user_id = json.get(Base.ATTR_USER_ID, None)
return deck
def to_json(self):
as_json = {
**self.get_shared_json_attributes(self)
, self.ATTR_DECK_ID: self.deck_id
, self.FLAG_NAME: self.name
, self.FLAG_IS_COMMANDER: self.is_commander
, self.ATTR_COMMANDER_BRACKET_ID: self.commander_bracket_id
, self.FLAG_ACTIVE: self.active
, self.FLAG_CREATED_ON: self.created_on
, Base.ATTR_USER_ID: self.created_by_user_id
}
return as_json
def __repr__(self):
return f'''
{self.__class__.__name__}(
{self.ATTR_DECK_ID}: {self.deck_id}
{self.FLAG_NAME}: {self.name}
{self.FLAG_IS_COMMANDER}: {self.is_commander}
{self.ATTR_COMMANDER_BRACKET_ID}: {self.commander_bracket_id}
{self.FLAG_ACTIVE}: {self.active}
{self.FLAG_CREATED_ON}: {self.created_on}
{Base.ATTR_USER_ID}: {self.created_by_user_id}
)
'''
class Parameters_MTG_Deck(Get_Many_Parameters_Base):
get_all_deck: bool
get_inactive_deck: bool
deck_ids: str
deck_names: str
commander_bracket_ids: str
include_commander_option: bool
require_all_id_filters_met: bool
require_any_id_filters_met: bool
require_all_non_id_filters_met: bool
require_any_non_id_filters_met: bool
@classmethod
def get_default(cls):
return cls(
get_all_deck = True
, get_inactive_deck = False
, deck_ids = ''
, deck_names = ''
, commander_bracket_ids = ''
, include_commander_option = True
, require_all_id_filters_met = True
, require_any_id_filters_met = True
, require_all_non_id_filters_met = False
, require_any_non_id_filters_met = True
)
@classmethod
def from_json(cls, json):
return cls(
get_all_deck = json.get('a_get_all_deck', False)
, get_inactive_deck = json.get('a_get_inactive_deck', False)
, deck_ids = json.get('a_deck_ids', '')
, deck_names = json.get('a_deck_names', '')
, commander_bracket_ids = json.get('a_commander_bracket_ids', '')
, include_commander_option = json.get('a_include_commander_option', True)
, require_all_id_filters_met = json.get('a_require_all_id_filters_met', True)
, require_any_id_filters_met = json.get('a_require_any_id_filters_met', True)
, require_all_non_id_filters_met = json.get('a_require_all_non_id_filters_met', False)
, require_any_non_id_filters_met = json.get('a_require_any_non_id_filters_met', True)
)
def to_json(self):
return {
'a_get_all_deck': self.get_all_deck
, 'a_get_inactive_deck': self.get_inactive_deck
, 'a_deck_ids': self.deck_ids
, 'a_deck_names': self.deck_names
, 'a_commander_bracket_ids': self.commander_bracket_ids
, 'a_include_commander_option': self.include_commander_option
, 'a_require_all_id_filters_met': self.require_all_id_filters_met
, 'a_require_any_id_filters_met': self.require_any_id_filters_met
, 'a_require_all_non_id_filters_met': self.require_all_non_id_filters_met
, 'a_require_any_non_id_filters_met': self.require_any_non_id_filters_met
}
@staticmethod
def get_type_hints():
return {
'a_get_all_deck': Boolean
, 'a_get_inactive_deck': Boolean
, 'a_deck_ids': Text
, 'a_deck_names': Text
, 'a_commander_bracket_ids': Text
, 'a_include_commander_option': Boolean
, 'a_require_all_id_filters_met': Boolean
, 'a_require_any_id_filters_met': Boolean
, 'a_require_all_non_id_filters_met': Boolean
, 'a_require_any_non_id_filters_met': Boolean
}

View File

@@ -0,0 +1,152 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: MTG Deck Commander Bracket Business Object
"""
# internal
from business_objects.base import Base
from business_objects.db_base import SQLAlchemy_ABC, Get_Many_Parameters_Base
import lib.argument_validation as av
from extensions import db
from helpers.helper_app import Helper_App
# external
from dataclasses import dataclass
from typing import ClassVar
class MTG_Deck_Commander_Bracket(SQLAlchemy_ABC, Base):
ATTR_COMMANDER_BRACKET_ID: ClassVar[str] = 'commander_bracket_id'
FLAG_COMMANDER_BRACKET: ClassVar[str] = 'commander_bracket'
FLAG_DESCRIPTION: ClassVar[str] = 'description'
FLAG_DISPLAY_ORDER: ClassVar[str] = 'display_order'
NAME_ATTR_OPTION_VALUE: ClassVar[str] = ATTR_COMMANDER_BRACKET_ID
NAME_ATTR_OPTION_TEXT: ClassVar[str] = Base.FLAG_NAME
__tablename__ = 'tcg_mtg_deck_commander_bracket'
__table_args__ = { 'extend_existing': True }
commander_bracket_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text)
description = db.Column(db.Text)
display_order = db.Column(db.Integer)
active = db.Column(db.Boolean)
created_on = db.Column(db.DateTime)
created_by_user_id = db.Column(db.Integer)
updated_last_on = db.Column(db.DateTime)
updated_last_by_user_id = db.Column(db.Integer)
change_set_id = db.Column(db.Integer)
def __init__(self):
self.commander_bracket_id = 0
self.is_new = False
super().__init__()
@classmethod
def from_db_mtg_deck_commander_bracket(cls, query_row):
_m = f'{cls.__qualname__}.from_db_mtg_deck_commander_bracket'
bracket = cls()
bracket.commander_bracket_id = query_row[0]
bracket.name = query_row[1]
bracket.description = query_row[2]
bracket.display_order = query_row[3]
bracket.active = av.input_bool(query_row[4], cls.FLAG_ACTIVE, _m)
bracket.created_on = query_row[5]
bracket.created_by_user_id = query_row[6]
return bracket
@classmethod
def from_json(cls, json):
_m = f'{cls.__qualname__}.from_json'
bracket = cls()
if json is None: return bracket
bracket.commander_bracket_id = json.get(cls.ATTR_COMMANDER_BRACKET_ID, -1)
bracket.name = json.get(cls.FLAG_NAME, None)
bracket.description = json.get(cls.FLAG_DESCRIPTION, None)
bracket.display_order = json.get(cls.FLAG_DISPLAY_ORDER, 0)
bracket.active = av.input_bool(json.get(cls.FLAG_ACTIVE, True), cls.FLAG_ACTIVE, _m)
bracket.created_on = json.get(cls.FLAG_CREATED_ON, None)
bracket.created_by_user_id = json.get(Base.ATTR_USER_ID, None)
return bracket
def to_json(self):
as_json = {
**self.get_shared_json_attributes(self)
, self.ATTR_COMMANDER_BRACKET_ID: self.commander_bracket_id
, self.FLAG_NAME: self.name
, self.FLAG_DESCRIPTION: self.description
, self.FLAG_DISPLAY_ORDER: self.display_order
, self.FLAG_ACTIVE: self.active
, self.FLAG_CREATED_ON: self.created_on
, Base.ATTR_USER_ID: self.created_by_user_id
}
return as_json
def __repr__(self):
return f'''
{self.__class__.__name__}(
{self.ATTR_COMMANDER_BRACKET_ID}: {self.commander_bracket_id}
{self.FLAG_NAME}: {self.name}
{self.FLAG_DESCRIPTION}: {self.description}
{self.FLAG_DISPLAY_ORDER}: {self.display_order}
{self.FLAG_ACTIVE}: {self.active}
{self.FLAG_CREATED_ON}: {self.created_on}
{Base.ATTR_USER_ID}: {self.created_by_user_id}
)
'''
class Parameters_MTG_Deck_Commander_Bracket(Get_Many_Parameters_Base):
get_all_commander_bracket: bool
get_inactive_commander_bracket: bool
commander_bracket_ids: str
commander_bracket_names: str
user_ids: str
require_all_id_filters_met: bool
require_any_id_filters_met: bool
require_all_non_id_filters_met: bool
require_any_non_id_filters_met: bool
@classmethod
def get_default(cls, user_id_session):
return cls(
get_all_commander_bracket = True
, get_inactive_commander_bracket = False
, commander_bracket_ids = ''
, commander_bracket_names = ''
, user_ids = str(user_id_session)
, require_all_id_filters_met = True
, require_any_id_filters_met = True
, require_all_non_id_filters_met = False
, require_any_non_id_filters_met = True
)
@classmethod
def from_json(cls, json):
return cls(
get_all_commander_bracket = json.get('a_get_all_commander_bracket', False)
, get_inactive_commander_bracket = json.get('a_get_inactive_commander_bracket', False)
, commander_bracket_ids = json.get('a_commander_bracket_ids', '')
, commander_bracket_names = json.get('a_commander_bracket_names', '')
, user_ids = json.get('a_user_ids', '')
, require_all_id_filters_met = json.get('a_require_all_id_filters_met', True)
, require_any_id_filters_met = json.get('a_require_any_id_filters_met', True)
, require_all_non_id_filters_met = json.get('a_require_all_non_id_filters_met', False)
, require_any_non_id_filters_met = json.get('a_require_any_non_id_filters_met', True)
)
def to_json(self):
return {
'a_get_all_commander_bracket': self.get_all_commander_bracket
, 'a_get_inactive_commander_bracket': self.get_inactive_commander_bracket
, 'a_commander_bracket_ids': self.commander_bracket_ids
, 'a_commander_bracket_names': self.commander_bracket_names
, 'a_user_ids': self.user_ids
, 'a_require_all_id_filters_met': self.require_all_id_filters_met
, 'a_require_any_id_filters_met': self.require_any_id_filters_met
, 'a_require_all_non_id_filters_met': self.require_all_non_id_filters_met
, 'a_require_any_non_id_filters_met': self.require_any_non_id_filters_met
}

View File

@@ -0,0 +1,192 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: MTG Game Business Object
"""
# internal
from business_objects.base import Base
from business_objects.db_base import SQLAlchemy_ABC, Get_Many_Parameters_Base
import lib.argument_validation as av
from extensions import db
from helpers.helper_app import Helper_App
# external
from dataclasses import dataclass
from typing import ClassVar
from sqlalchemy import bindparam
from sqlalchemy.types import Text, Boolean
class MTG_Game(SQLAlchemy_ABC, Base):
ATTR_GAME_ID: ClassVar[str] = 'game_id'
FLAG_END_ON: ClassVar[str] = 'end_on'
FLAG_GAME: ClassVar[str] = 'game'
FLAG_IS_COMMANDER: ClassVar[str] = 'is_commander'
FLAG_IS_DRAFT: ClassVar[str] = 'is_draft'
FLAG_IS_SEALED: ClassVar[str] = 'is_sealed'
FLAG_LOCATION_NAME: ClassVar[str] = 'location_name'
FLAG_NOTES: ClassVar[str] = 'notes'
FLAG_START_ON: ClassVar[str] = 'start_on'
FLAG_STARTING_LIFE: ClassVar[str] = 'starting_life'
NAME_ATTR_OPTION_VALUE: ClassVar[str] = ATTR_GAME_ID
NAME_ATTR_OPTION_TEXT: ClassVar[str] = FLAG_START_ON
__tablename__ = 'tcg_mtg_game'
__table_args__ = { 'extend_existing': True }
game_id = db.Column(db.Integer, primary_key=True)
notes = db.Column(db.Text)
is_commander = db.Column(db.Boolean)
is_draft = db.Column(db.Boolean)
is_sealed = db.Column(db.Boolean)
location_name = db.Column(db.Text)
start_on = db.Column(db.DateTime)
end_on = db.Column(db.DateTime)
starting_life = db.Column(db.Integer)
active = db.Column(db.Boolean)
created_on = db.Column(db.DateTime)
created_by_user_id = db.Column(db.Integer)
updated_last_on = db.Column(db.DateTime)
updated_last_by_user_id = db.Column(db.Integer)
change_set_id = db.Column(db.Integer)
def __init__(self):
self.game_id = 0
self.is_new = False
super().__init__()
@classmethod
def from_db_mtg_game(cls, query_row):
_m = f'{cls.__qualname__}.from_db_mtg_game'
game = cls()
game.game_id = query_row[0]
game.notes = query_row[1]
game.is_commander = av.input_bool(query_row[2], cls.FLAG_IS_COMMANDER, _m)
game.is_draft = av.input_bool(query_row[3], cls.FLAG_IS_DRAFT, _m)
game.is_sealed = av.input_bool(query_row[4], cls.FLAG_IS_SEALED, _m)
game.location_name = query_row[5]
game.start_on = query_row[6]
game.end_on = query_row[7]
game.starting_life = query_row[8]
game.active = av.input_bool(query_row[9], cls.FLAG_ACTIVE, _m)
game.created_on = query_row[10]
game.created_by_user_id = query_row[11]
return game
@classmethod
def from_json(cls, json):
_m = f'{cls.__qualname__}.from_json'
game = cls()
if json is None: return game
game.game_id = json.get(cls.ATTR_GAME_ID, -1)
game.notes = json.get(cls.FLAG_NOTES, None)
game.is_commander = av.input_bool(json.get(cls.FLAG_IS_COMMANDER, True), cls.FLAG_IS_COMMANDER, _m)
game.is_draft = av.input_bool(json.get(cls.FLAG_IS_DRAFT, False), cls.FLAG_IS_DRAFT, _m)
game.is_sealed = av.input_bool(json.get(cls.FLAG_IS_SEALED, False), cls.FLAG_IS_SEALED, _m)
game.location_name = json.get(cls.FLAG_LOCATION_NAME, None)
game.start_on = json.get(cls.FLAG_START_ON, None)
game.starting_life = json.get(cls.FLAG_STARTING_LIFE, 40)
game.end_on = json.get(cls.FLAG_END_ON, None)
game.active = av.input_bool(json.get(cls.FLAG_ACTIVE, True), cls.FLAG_ACTIVE, _m)
game.created_on = json.get(cls.FLAG_CREATED_ON, None)
game.created_by_user_id = json.get(Base.ATTR_USER_ID, None)
return game
def to_json(self):
as_json = {
**self.get_shared_json_attributes(self)
, self.ATTR_GAME_ID: self.game_id
, self.FLAG_NOTES: self.notes
, self.FLAG_IS_COMMANDER: self.is_commander
, self.FLAG_IS_DRAFT: self.is_draft
, self.FLAG_IS_SEALED: self.is_sealed
, self.FLAG_LOCATION_NAME: self.location_name
, self.FLAG_START_ON: self.start_on
, self.FLAG_STARTING_LIFE: self.starting_life
, self.FLAG_END_ON: self.end_on
, self.FLAG_ACTIVE: self.active
, self.FLAG_CREATED_ON: self.created_on
, Base.ATTR_USER_ID: self.created_by_user_id
}
return as_json
def __repr__(self):
return f'''
{self.__class__.__name__}(
{self.ATTR_GAME_ID}: {self.game_id}
{self.FLAG_NOTES}: {self.notes}
{self.FLAG_IS_COMMANDER}: {self.is_commander}
{self.FLAG_IS_DRAFT}: {self.is_draft}
{self.FLAG_IS_SEALED}: {self.is_sealed}
{self.FLAG_LOCATION_NAME}: {self.location_name}
{self.FLAG_START_ON}: {self.start_on}
{self.FLAG_END_ON}: {self.end_on}
{self.FLAG_STARTING_LIFE}: {self.starting_life}
{self.FLAG_ACTIVE}: {self.active}
{self.FLAG_CREATED_ON}: {self.created_on}
{Base.ATTR_USER_ID}: {self.created_by_user_id}
)
'''
class Parameters_MTG_Game(Get_Many_Parameters_Base):
get_all_game: bool
get_inactive_game: bool
game_ids: str
get_all_user: bool
get_inactive_user: bool
user_ids: str
require_all_id_filters_met: bool
require_any_id_filters_met: bool
@classmethod
def get_default(cls, user_id_session):
return cls(
get_all_game = True
, get_inactive_game = False
, game_ids = ''
, get_all_user = False
, get_inactive_user = False
, user_ids = '' if user_id_session is None else f'{user_id_session}'
, require_all_id_filters_met = True
, require_any_id_filters_met = True
)
@classmethod
def from_json(cls, json):
return cls(
get_all_game = json.get('a_get_all_game', False)
, get_inactive_game = json.get('a_get_inactive_game', False)
, game_ids = json.get('a_game_ids', '')
, get_all_user = json.get('a_get_all_user', False)
, get_inactive_user = json.get('a_get_inactive_user', False)
, user_ids = json.get('a_user_ids', '')
, require_all_id_filters_met = json.get('a_require_all_id_filters_met', True)
, require_any_id_filters_met = json.get('a_require_any_id_filters_met', True)
)
def to_json(self):
return {
'a_get_all_game': self.get_all_game
, 'a_get_inactive_game': self.get_inactive_game
, 'a_game_ids': str(self.game_ids)
, 'a_get_all_user': self.get_all_user
, 'a_get_inactive_user': self.get_inactive_user
, 'a_user_ids': str(self.user_ids)
, 'a_require_all_id_filters_met': self.require_all_id_filters_met
, 'a_require_any_id_filters_met': self.require_any_id_filters_met
}
@staticmethod
def get_type_hints():
return {
'a_get_all_game': Boolean
, 'a_get_inactive_game': Boolean
, 'a_game_ids': Text
, 'a_get_all_user': Boolean
, 'a_get_inactive_user': Boolean
, 'a_user_ids': Text
, 'a_require_all_id_filters_met': Boolean
, 'a_require_any_id_filters_met': Boolean
}

View File

@@ -0,0 +1,197 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: MTG Game Player Business Object
"""
# internal
from business_objects.base import Base
from business_objects.db_base import SQLAlchemy_ABC, Get_Many_Parameters_Base
import lib.argument_validation as av
from extensions import db
from helpers.helper_app import Helper_App
# external
from dataclasses import dataclass
from typing import ClassVar
from sqlalchemy import Uuid
from sqlalchemy.types import Text, Boolean
# import uuid
class MTG_Game_Player(SQLAlchemy_ABC, Base):
ATTR_PLAYER_ID: ClassVar[str] = 'player_id'
ATTR_GAME_ID: ClassVar[str] = 'game_id'
ATTR_DECK_ID: ClassVar[str] = 'deck_id'
FLAG_PLAYER: ClassVar[str] = 'player'
FLAG_NOTES: ClassVar[str] = 'notes'
FLAG_DISPLAY_ORDER: ClassVar[str] = 'display_order'
NAME_ATTR_OPTION_VALUE: ClassVar[str] = ATTR_PLAYER_ID
NAME_ATTR_OPTION_TEXT: ClassVar[str] = Base.FLAG_NAME
__tablename__ = 'tcg_mtg_game_player'
__table_args__ = { 'extend_existing': True }
player_id = db.Column(db.Integer, primary_key=True)
game_id = db.Column(db.Integer)
user_id = db.Column(db.Integer)
deck_id = db.Column(db.Integer)
name = db.Column(db.Text)
notes = db.Column(db.Text)
display_order = db.Column(db.Integer)
active = db.Column(db.Boolean)
created_on = db.Column(db.DateTime)
created_by_user_id = db.Column(db.Integer)
updated_last_on = db.Column(db.DateTime)
updated_last_by_user_id = db.Column(db.Integer)
change_set_id = db.Column(db.Integer)
def __init__(self):
self.player_id = 0
self.is_new = False
super().__init__()
@classmethod
def from_db_mtg_game_player(cls, query_row):
_m = f'{cls.__qualname__}.from_db_mtg_game_player'
player = cls()
player.player_id = query_row[0]
player.game_id = query_row[1]
player.user_id = query_row[2]
player.deck_id = query_row[3]
player.name = query_row[4]
player.notes = query_row[5]
player.display_order = query_row[6]
player.active = av.input_bool(query_row[7], cls.FLAG_ACTIVE, _m)
player.created_on = query_row[8]
player.created_by_user_id = query_row[9]
return player
@classmethod
def from_json(cls, json):
_m = f'{cls.__qualname__}.from_json'
player = cls()
if json is None: return player
player.player_id = json.get(cls.ATTR_PLAYER_ID, -1)
player.game_id = json.get(cls.ATTR_GAME_ID, None)
player.user_id = json.get(Base.ATTR_USER_ID, None)
player.deck_id = json.get(cls.ATTR_DECK_ID, None)
player.name = json.get(cls.FLAG_NAME, None)
player.notes = json.get(cls.FLAG_NOTES, None)
player.display_order = json.get(cls.FLAG_DISPLAY_ORDER, 0)
player.active = av.input_bool(json.get(cls.FLAG_ACTIVE, True), cls.FLAG_ACTIVE, _m)
player.created_on = json.get(cls.FLAG_CREATED_ON, None)
player.created_by_user_id = json.get(Base.ATTR_USER_ID, None)
return player
def to_json(self):
as_json = {
**self.get_shared_json_attributes(self)
, self.ATTR_PLAYER_ID: self.player_id
, self.ATTR_GAME_ID: self.game_id
, Base.ATTR_USER_ID: self.user_id
, self.ATTR_DECK_ID: self.deck_id
, self.FLAG_NAME: self.name
, self.FLAG_NOTES: self.notes
, self.FLAG_DISPLAY_ORDER: self.display_order
, self.FLAG_ACTIVE: self.active
, self.FLAG_CREATED_ON: self.created_on
}
return as_json
def __repr__(self):
return f'''
{self.__class__.__name__}(
{self.ATTR_PLAYER_ID}: {self.player_id}
{self.ATTR_GAME_ID}: {self.game_id}
{Base.ATTR_USER_ID}: {self.user_id}
{self.ATTR_DECK_ID}: {self.deck_id}
{self.FLAG_NAME}: {self.name}
{self.FLAG_NOTES}: {self.notes}
{self.FLAG_DISPLAY_ORDER}: {self.display_order}
{self.FLAG_ACTIVE}: {self.active}
{self.FLAG_CREATED_ON}: {self.created_on}
)
'''
class MTG_Game_Player_Temp(db.Model, Base):
__tablename__ = 'tcg_mtg_game_player_temp'
__table_args__ = { 'extend_existing': True }
temp_id = db.Column(db.Integer, primary_key=True)
player_id = db.Column(db.Integer)
game_id = db.Column(db.Integer)
user_id = db.Column(db.Integer)
deck_id = db.Column(db.Integer)
name = db.Column(db.Text)
notes = db.Column(db.Text)
display_order = db.Column(db.Integer)
active = db.Column(db.Boolean)
created_on = db.Column(db.DateTime)
guid = db.Column(Uuid) #, default = uuid.uuid4)
def __init__(self):
super().__init__()
@classmethod
def from_player(cls, player, guid):
_m = 'MTG_Game_Player_Temp.from_player'
temp = cls()
temp.player_id = player.player_id
temp.game_id = player.game_id
temp.user_id = player.user_id
temp.deck_id = player.deck_id
temp.name = player.name
temp.notes = player.notes
temp.display_order = player.display_order
temp.active = player.active
temp.created_on = player.created_on
temp.guid = guid
return temp
class Parameters_MTG_Game_Player(Get_Many_Parameters_Base):
get_all_game: bool
get_inactive_game: bool
game_ids: str
require_all_id_filters_met: bool
require_any_id_filters_met: bool
@classmethod
def get_default(cls, user_id_session):
return cls(
get_all_game = True
, get_inactive_game = False
, game_ids = ''
, require_all_id_filters_met = True
, require_any_id_filters_met = True
)
@classmethod
def from_json(cls, json):
return cls(
get_all_game = json.get('a_get_all_game', False)
, get_inactive_game = json.get('a_get_inactive_game', False)
, game_ids = json.get('a_game_ids', '')
, require_all_id_filters_met = json.get('a_require_all_id_filters_met', True)
, require_any_id_filters_met = json.get('a_require_any_id_filters_met', True)
)
def to_json(self):
return {
'a_get_all_game': self.get_all_game
, 'a_get_inactive_game': self.get_inactive_game
, 'a_game_ids': self.game_ids
, 'a_require_all_id_filters_met': self.require_all_id_filters_met
, 'a_require_any_id_filters_met': self.require_any_id_filters_met
}
@staticmethod
def get_type_hints():
return {
'a_get_all_game': Boolean
, 'a_get_inactive_game': Boolean
, 'a_game_ids': Text
, 'a_require_all_id_filters_met': Boolean
, 'a_require_any_id_filters_met': Boolean
}

View File

@@ -0,0 +1,176 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: MTG Game Round Business Object
"""
# internal
from business_objects.base import Base
from business_objects.db_base import SQLAlchemy_ABC, Get_Many_Parameters_Base
import lib.argument_validation as av
from extensions import db
from helpers.helper_app import Helper_App
# external
from dataclasses import dataclass
from typing import ClassVar
from sqlalchemy import Uuid
from sqlalchemy.types import Text, Boolean
class MTG_Game_Round(SQLAlchemy_ABC, Base):
ATTR_ROUND_ID: ClassVar[str] = 'round_id'
ATTR_GAME_ID: ClassVar[str] = 'game_id'
FLAG_ROUND: ClassVar[str] = 'round'
FLAG_NOTES: ClassVar[str] = 'notes'
NAME_ATTR_OPTION_VALUE: ClassVar[str] = ATTR_ROUND_ID
NAME_ATTR_OPTION_TEXT: ClassVar[str] = ATTR_ROUND_ID
__tablename__ = 'tcg_mtg_game_round'
__table_args__ = { 'extend_existing': True }
round_id = db.Column(db.Integer, primary_key=True)
game_id = db.Column(db.Integer)
notes = db.Column(db.Text)
display_order = db.Column(db.Integer)
active = db.Column(db.Boolean)
created_on = db.Column(db.DateTime)
created_by_user_id = db.Column(db.Integer)
updated_last_on = db.Column(db.DateTime)
updated_last_by_user_id = db.Column(db.Integer)
change_set_id = db.Column(db.Integer)
def __init__(self):
self.round_id = 0
self.is_new = False
super().__init__()
@classmethod
def from_db_mtg_game_round(cls, query_row):
_m = f'{cls.__qualname__}.from_db_mtg_game_round'
round = cls()
round.round_id = query_row[0]
round.game_id = query_row[1]
round.notes = query_row[2]
round.display_order = query_row[3]
round.active = av.input_bool(query_row[4], cls.FLAG_ACTIVE, _m)
round.created_on = query_row[5]
round.created_by_user_id = query_row[6]
return round
@classmethod
def from_json(cls, json):
_m = f'{cls.__qualname__}.from_json'
round = cls()
if json is None: return round
round.round_id = json.get(cls.ATTR_ROUND_ID, -1)
round.game_id = json.get(cls.ATTR_GAME_ID, None)
round.notes = json.get(cls.FLAG_NOTES, None)
round.display_order = json.get(cls.FLAG_DISPLAY_ORDER, -1)
round.active = av.input_bool(json.get(cls.FLAG_ACTIVE, True), cls.FLAG_ACTIVE, _m)
round.created_on = json.get(cls.FLAG_CREATED_ON, None)
round.created_by_user_id = json.get(Base.ATTR_USER_ID, None)
return round
def to_json(self):
as_json = {
**self.get_shared_json_attributes(self)
, self.ATTR_ROUND_ID: self.round_id
, self.ATTR_GAME_ID: self.game_id
, self.FLAG_NOTES: self.notes
, self.FLAG_DISPLAY_ORDER: self.display_order
, self.FLAG_ACTIVE: self.active
, self.FLAG_CREATED_ON: self.created_on
, Base.ATTR_USER_ID: self.created_by_user_id
}
return as_json
def __repr__(self):
return f'''
{self.__class__.__name__}(
{self.ATTR_ROUND_ID}: {self.round_id}
{self.ATTR_GAME_ID}: {self.game_id}
{self.FLAG_NOTES}: {self.notes}
{self.FLAG_DISPLAY_ORDER}: {self.display_order}
{self.FLAG_ACTIVE}: {self.active}
{self.FLAG_CREATED_ON}: {self.created_on}
{Base.ATTR_USER_ID}: {self.created_by_user_id}
)
'''
class MTG_Game_Round_Temp(db.Model, Base):
__tablename__ = 'tcg_mtg_game_round_temp'
__table_args__ = { 'extend_existing': True }
temp_id = db.Column(db.Integer, primary_key=True)
round_id = db.Column(db.Integer)
game_id = db.Column(db.Integer)
notes = db.Column(db.Text)
display_order = db.Column(db.Integer)
active = db.Column(db.Boolean)
created_on = db.Column(db.DateTime)
guid = db.Column(Uuid)
def __init__(self):
super().__init__()
@classmethod
def from_round(cls, round, guid):
_m = 'MTG_Game_Round_Temp.from_round'
temp = cls()
temp.round_id = round.round_id
temp.game_id = round.game_id
temp.notes = round.notes
temp.display_order = round.display_order
temp.active = round.active
temp.created_on = round.created_on
temp.guid = guid
return temp
class Parameters_MTG_Game_Round(Get_Many_Parameters_Base):
get_all_game: bool
get_inactive_game: bool
game_ids: str
require_all_id_filters_met: bool
require_any_id_filters_met: bool
@classmethod
def get_default(cls, user_id_session):
return cls(
get_all_game = True
, get_inactive_game = False
, game_ids = ''
, require_all_id_filters_met = True
, require_any_id_filters_met = True
)
@classmethod
def from_json(cls, json):
return cls(
get_all_game = json.get('a_get_all_game', False)
, get_inactive_game = json.get('a_get_inactive_game', False)
, game_ids = json.get('a_game_ids', '')
, require_all_id_filters_met = json.get('a_require_all_id_filters_met', True)
, require_any_id_filters_met = json.get('a_require_any_id_filters_met', True)
)
def to_json(self):
return {
'a_get_all_game': self.get_all_game
, 'a_get_inactive_game': self.get_inactive_game
, 'a_game_ids': self.game_ids
, 'a_require_all_id_filters_met': self.require_all_id_filters_met
, 'a_require_any_id_filters_met': self.require_any_id_filters_met
}
@staticmethod
def get_type_hints():
return {
'a_get_all_game': Boolean
, 'a_get_inactive_game': Boolean
, 'a_game_ids': Text
, 'a_require_all_id_filters_met': Boolean
, 'a_require_any_id_filters_met': Boolean
}

View File

@@ -0,0 +1,201 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: MTG Game Round Player Damage Business Object
"""
# internal
from business_objects.base import Base
from business_objects.db_base import SQLAlchemy_ABC, Get_Many_Parameters_Base
import lib.argument_validation as av
from extensions import db
from helpers.helper_app import Helper_App
# external
from dataclasses import dataclass
from typing import ClassVar
from sqlalchemy import Uuid
from sqlalchemy.types import Text, Boolean
class MTG_Game_Round_Player_Damage(SQLAlchemy_ABC, Base):
ATTR_DAMAGE_ID: ClassVar[str] = 'damage_id'
ATTR_PLAYER_ID: ClassVar[str] = 'player_id'
ATTR_ROUND_ID: ClassVar[str] = 'round_id'
ATTR_RECEIVED_FROM_COMMANDER_PLAYER_ID: ClassVar[str] = 'received_from_commander_player_id'
FLAG_COMMANDER_DEATHS: ClassVar[str] = 'commander_deaths'
FLAG_DAMAGE: ClassVar[str] = 'damage'
FLAG_HEALTH_CHANGE: ClassVar[str] = 'health_change'
FLAG_IS_ELIMINATED: ClassVar[str] = 'is_eliminated'
NAME_ATTR_OPTION_VALUE: ClassVar[str] = ATTR_DAMAGE_ID
NAME_ATTR_OPTION_TEXT: ClassVar[str] = ATTR_DAMAGE_ID
__tablename__ = 'tcg_mtg_game_round_player_damage'
__table_args__ = { 'extend_existing': True }
damage_id = db.Column(db.Integer, primary_key=True)
round_id = db.Column(db.Integer)
player_id = db.Column(db.Integer)
received_from_commander_player_id = db.Column(db.Integer)
health_change = db.Column(db.Integer)
commander_deaths = db.Column(db.Integer)
is_eliminated = db.Column(db.Boolean)
active = db.Column(db.Boolean)
created_on = db.Column(db.DateTime)
created_by_user_id = db.Column(db.Integer)
updated_last_on = db.Column(db.DateTime)
updated_last_by_user_id = db.Column(db.Integer)
change_set_id = db.Column(db.Integer)
def __init__(self):
self.damage_id = 0
self.is_new = False
super().__init__()
@classmethod
def from_db_mtg_game_round_player_damage(cls, query_row):
_m = f'{cls.__qualname__}.from_db_mtg_game_round_player_damage'
damage = cls()
damage.damage_id = query_row[0]
damage.round_id = query_row[1]
damage.player_id = query_row[2]
damage.received_from_commander_player_id = query_row[3]
damage.health_change = query_row[4]
damage.commander_deaths = query_row[5]
damage.is_eliminated = av.input_bool(query_row[6], cls.FLAG_IS_ELIMINATED, _m)
damage.active = av.input_bool(query_row[7], cls.FLAG_ACTIVE, _m)
damage.created_on = query_row[8]
damage.created_by_user_id = query_row[9]
return damage
@classmethod
def from_json(cls, json):
_m = f'{cls.__qualname__}.from_json'
damage = cls()
if json is None: return damage
damage.damage_id = json.get(cls.ATTR_DAMAGE_ID, -1)
damage.round_id = json.get(cls.ATTR_ROUND_ID, None)
damage.player_id = json.get(cls.ATTR_PLAYER_ID, None)
damage.received_from_commander_player_id = json.get(cls.ATTR_RECEIVED_FROM_COMMANDER_PLAYER_ID, None)
damage.health_change = json.get(cls.FLAG_HEALTH_CHANGE, 0)
damage.commander_deaths = json.get(cls.FLAG_COMMANDER_DEATHS, 0)
damage.is_eliminated = json.get(cls.FLAG_IS_ELIMINATED, False)
damage.active = av.input_bool(json.get(cls.FLAG_ACTIVE, True), cls.FLAG_ACTIVE, _m)
damage.created_on = json.get(cls.FLAG_CREATED_ON, None)
damage.created_by_user_id = json.get(Base.ATTR_USER_ID, None)
return damage
def to_json(self):
as_json = {
**self.get_shared_json_attributes(self)
, self.ATTR_DAMAGE_ID: self.damage_id
, self.ATTR_ROUND_ID: self.round_id
, self.ATTR_PLAYER_ID: self.player_id
, self.ATTR_RECEIVED_FROM_COMMANDER_PLAYER_ID: self.received_from_commander_player_id
, self.FLAG_HEALTH_CHANGE: self.health_change
, self.FLAG_COMMANDER_DEATHS: self.commander_deaths
, self.FLAG_IS_ELIMINATED: self.is_eliminated
, self.FLAG_ACTIVE: self.active
, self.FLAG_CREATED_ON: self.created_on
, Base.ATTR_USER_ID: self.created_by_user_id
}
return as_json
def __repr__(self):
return f'''
{self.__class__.__name__}(
{self.ATTR_DAMAGE_ID}: {self.damage_id}
{self.ATTR_ROUND_ID}: {self.round_id}
{self.ATTR_PLAYER_ID}: {self.player_id}
{self.ATTR_RECEIVED_FROM_COMMANDER_PLAYER_ID}: {self.received_from_commander_player_id}
{self.FLAG_HEALTH_CHANGE}: {self.health_change}
{self.FLAG_COMMANDER_DEATHS}: {self.commander_deaths}
{self.FLAG_IS_ELIMINATED}: {self.is_eliminated}
{self.FLAG_ACTIVE}: {self.active}
{self.FLAG_CREATED_ON}: {self.created_on}
{Base.ATTR_USER_ID}: {self.created_by_user_id}
)
'''
class MTG_Game_Round_Player_Damage_Temp(db.Model, Base):
__tablename__ = 'tcg_mtg_game_round_player_damage_temp'
__table_args__ = { 'extend_existing': True }
temp_id = db.Column(db.Integer, primary_key=True)
damage_id = db.Column(db.Integer)
round_id = db.Column(db.Integer)
player_id = db.Column(db.Integer)
received_from_commander_player_id = db.Column(db.Integer)
health_change = db.Column(db.Integer)
commander_deaths = db.Column(db.Integer)
is_eliminated = db.Column(db.Boolean)
active = db.Column(db.Boolean)
created_on = db.Column(db.DateTime)
guid = db.Column(Uuid)
def __init__(self):
super().__init__()
@classmethod
def from_damage(cls, damage, guid):
_m = 'MTG_Game_Round_Player_Damage_Temp.from_damage'
temp = cls()
temp.damage_id = damage.damage_id
temp.round_id = damage.round_id
temp.player_id = damage.player_id
temp.received_from_commander_player_id = damage.received_from_commander_player_id
temp.health_change = damage.health_change
temp.commander_deaths = damage.commander_deaths
temp.is_eliminated = damage.is_eliminated
temp.active = damage.active
temp.created_on = damage.created_on
temp.guid = guid
return temp
class Parameters_MTG_Game_Round_Player_Damage(Get_Many_Parameters_Base):
get_all_game: bool
get_inactive_game: bool
game_ids: str
require_all_id_filters_met: bool
require_any_id_filters_met: bool
@classmethod
def get_default(cls, user_id_session):
return cls(
get_all_game = True
, get_inactive_game = False
, game_ids = ''
, require_all_id_filters_met = True
, require_any_id_filters_met = True
)
@classmethod
def from_json(cls, json):
return cls(
get_all_game = json.get('a_get_all_game', False)
, get_inactive_game = json.get('a_get_inactive_game', False)
, game_ids = json.get('a_game_ids', '')
, require_all_id_filters_met = json.get('a_require_all_id_filters_met', True)
, require_any_id_filters_met = json.get('a_require_any_id_filters_met', True)
)
def to_json(self):
return {
'a_get_all_game': self.get_all_game
, 'a_get_inactive_game': self.get_inactive_game
, 'a_game_ids': self.game_ids
, 'a_require_all_id_filters_met': self.require_all_id_filters_met
, 'a_require_any_id_filters_met': self.require_any_id_filters_met
}
@staticmethod
def get_type_hints():
return {
'a_get_all_game': Boolean
, 'a_get_inactive_game': Boolean
, 'a_game_ids': Text
, 'a_require_all_id_filters_met': Boolean
, 'a_require_any_id_filters_met': Boolean
}

View File

@@ -0,0 +1,229 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: User Business Object
"""
# internal
from business_objects.base import Base
from business_objects.db_base import SQLAlchemy_ABC, Get_Many_Parameters_Base
import lib.argument_validation as av
from extensions import db
from mtg_commander_life_tracker.forms.tcg.user import Filters_User
from helpers.helper_app import Helper_App
# external
from dataclasses import dataclass
from typing import ClassVar, Optional
from sqlalchemy.types import Text, Boolean
class User(SQLAlchemy_ABC, Base):
ATTR_USER_AUTH0_ID: ClassVar[str] = 'user_auth0_id'
FLAG_IS_EMAIL_VERIFIED: ClassVar[str] = 'is_email_verified'
FLAG_IS_SUPER_USER: ClassVar[str] = 'is_super_user'
FLAG_PRIORITY_ACCESS_LEVEL: ClassVar[str] = 'priority_access_level'
NAME_ATTR_OPTION_TEXT: ClassVar[str] = Base.FLAG_NAME
NAME_ATTR_OPTION_VALUE: ClassVar[str] = Base.ATTR_USER_ID
__tablename__ = 'tcg_user'
__table_args__ = { 'extend_existing': True }
user_id = db.Column(db.Integer, primary_key=True)
user_auth0_id = db.Column(db.String(250))
firstname = db.Column(db.Text)
surname = db.Column(db.Text)
email = db.Column(db.String(256))
is_email_verified = db.Column(db.Boolean)
is_super_user = db.Column(db.Boolean)
priority_access_level = db.Column(db.Integer)
is_new = db.Column(db.Boolean)
active = db.Column(db.Boolean)
def __init__(self):
self.user_id = 0
self.is_new = False
super().__init__()
@classmethod
def from_db_user(cls, query_row):
"""Map from PostgreSQL FN_TCG_User_Get_Many result columns:
user_id, user_auth0_id, firstname, surname, email, is_email_verified,
is_super_user, active, created_on, created_by_user_id, updated_last_on,
updated_last_by_user_id, change_set_id
"""
_m = f'{cls.__qualname__}.from_db_user'
Helper_App.console_log(f'user record: {query_row}')
user = cls()
user.user_id = query_row[0]
user.user_auth0_id = query_row[1]
user.firstname = query_row[2]
user.surname = query_row[3]
user.email = query_row[4]
user.is_email_verified = av.input_bool(query_row[5], cls.FLAG_IS_EMAIL_VERIFIED, _m)
user.is_super_user = av.input_bool(query_row[6], cls.FLAG_IS_SUPER_USER, _m)
user.active = av.input_bool(query_row[7], 'active', _m)
return user
@classmethod
def from_json(cls, json):
_m = f'{cls.__qualname__}.from_json'
user = cls()
if json is None: return user
Helper_App.console_log(f'{_m}/n{json}')
user.user_id = json[cls.ATTR_USER_ID]
user.user_auth0_id = json[cls.ATTR_USER_AUTH0_ID]
user.firstname = json[cls.FLAG_FIRSTNAME]
user.surname = json[cls.FLAG_SURNAME]
user.email = json[cls.FLAG_EMAIL]
user.is_email_verified = av.input_bool(json[cls.FLAG_IS_EMAIL_VERIFIED], cls.FLAG_IS_EMAIL_VERIFIED, _m)
user.is_super_user = av.input_bool(json[cls.FLAG_IS_SUPER_USER], cls.FLAG_IS_SUPER_USER, _m)
return user
@classmethod
def from_json_auth0(cls, json):
_m = f'{cls.__qualname__}.from_json_auth0'
Helper_App.console_log(_m)
Helper_App.console_log(f'JSON: {json}')
user = cls()
if json is None: return user
user_info = json['userinfo']
Helper_App.console_log(f'user_info: {user_info}')
user.user_id = None
user.user_auth0_id = user_info['sub']
user.firstname = None
user.surname = None
user.email = user_info[cls.FLAG_EMAIL]
user.is_email_verified = av.input_bool(user_info['email_verified'], cls.FLAG_IS_EMAIL_VERIFIED, _m)
user.is_super_user = None
return user
def to_json(self):
as_json = {
**self.get_shared_json_attributes(self)
, self.ATTR_USER_ID: self.user_id
, self.ATTR_USER_AUTH0_ID: self.user_auth0_id
, self.FLAG_FIRSTNAME: self.firstname
, self.FLAG_SURNAME: self.surname
, self.FLAG_NAME: self.get_name()
, self.FLAG_EMAIL: self.email
, self.FLAG_IS_EMAIL_VERIFIED: self.is_email_verified
, self.FLAG_IS_SUPER_USER: self.is_super_user
, self.FLAG_PRIORITY_ACCESS_LEVEL: self.priority_access_level
}
return as_json
def __repr__(self):
return f'''
User (
{self.ATTR_USER_ID}: {self.user_id}
, {self.ATTR_USER_AUTH0_ID}: {self.user_auth0_id}
, {self.FLAG_FIRSTNAME}: {self.firstname}
, {self.FLAG_SURNAME}: {self.surname}
, {self.FLAG_NAME}: {self.get_name()}
, {self.FLAG_EMAIL}: {self.email}
, {self.FLAG_IS_EMAIL_VERIFIED}: {self.is_email_verified}
, {self.FLAG_IS_SUPER_USER}: {self.is_super_user}
, {self.FLAG_PRIORITY_ACCESS_LEVEL}: {self.priority_access_level}
) '''
def get_is_logged_in(self):
return (self.user_id is not None and self.user_id > 0 and self.user_id != Base.USER_ID_GUEST)
def get_name(self):
return f'{"" if self.firstname is None else self.firstname} {"" if self.surname is None else self.surname}'
class Parameters_User(Get_Many_Parameters_Base):
# user_id: Optional[int]
# auth0_user_id: str
get_all_user: bool
get_inactive_user: bool
user_ids: str
"""
user_auth0_ids: str
names_user: str
emails_user: str
"""
require_all_id_filters_met: bool
require_any_id_filters_met: bool
# require_all_non_id_filters_met: bool
# require_any_non_id_filters_met: bool
@classmethod
def from_form_filters_user(cls, form):
_m = f'{cls.__qualname__}.from_form_filters_user'
av.val_instance(form, 'form', _m, Filters_User)
get_inactive = not av.input_bool(form.active_only.data, "active", _m)
# user_id = '' if form.user_id.data is None else form.user_id.data
filters = cls.get_default()
filters.get_all_user = True # (user_id == '')
filters.get_inactive_user = get_inactive
filters.user_ids = '' # user_id
# filters.user_auth0_ids = ''
filters.require_all_id_filters_met = True
filters.require_any_id_filters_met = True
# filters.require_all_non_id_filters_met = False
# filters.require_any_non_id_filters_met = True
return filters
@classmethod
def from_user(cls, user):
av.val_instance(user, 'user', 'Parameters_User.from_user', User)
filters = cls.get_default()
filters.get_all_user = ((user.user_id is None or user.user_id == 0) and user.user_auth0_id is None)
filters.get_inactive_user = False
filters.user_ids = '' if user.user_id is None else str(user.user_id)
# filters.user_auth0_ids = user.user_auth0_id
# filters.names_user = user.get_name()
# filters.emails_user = user.email
return filters
@classmethod
def get_default(cls):
return cls(
# user_id = None
# , auth0_user_id = ''
get_all_user = False
, get_inactive_user = False
, user_ids = ''
# , user_auth0_ids = ''
# , names_user = ''
# , emails_user = ''
, require_all_id_filters_met = True
, require_any_id_filters_met = True
# , require_all_non_id_filters_met = False
# , require_any_non_id_filters_met = True
)
@classmethod
def from_json(self):
pass
def to_json(self):
return {
# 'a_user_id': self.user_id
# , 'a_auth0_user_id': self.auth0_user_id
'a_get_all_user': self.get_all_user
, 'a_get_inactive_user': self.get_inactive_user
, 'a_user_ids': self.user_ids
# , 'a_user_auth0_ids': self.user_auth0_ids
# , 'a_names_user': self.names_user
# , 'a_emails_user': self.emails_user
, 'a_require_all_id_filters_met': self.require_all_id_filters_met
, 'a_require_any_id_filters_met': self.require_any_id_filters_met
# , 'a_require_all_non_id_filters_met': self.require_all_non_id_filters_met
# , 'a_require_any_non_id_filters_met': self.require_any_non_id_filters_met
}
@staticmethod
def get_type_hints():
return {
'a_get_all_user': Boolean
, 'a_get_inactive_user': Boolean
, 'a_user_ids': Text
, 'a_require_all_id_filters_met': Boolean
, 'a_require_any_id_filters_met': Boolean
}

132
config.py Normal file
View File

@@ -0,0 +1,132 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Backend
Feature: Configuration
Description:
Configuration variables
"""
# IMPORTS
import os
from dotenv import load_dotenv, find_dotenv
from flask import current_app
load_dotenv(find_dotenv())
# CLASSES
class Config:
is_development = False
is_production = False
# Miscellaneous
DEBUG = False # av.input_bool(os.getenv('DEBUG'), 'DEBUG', 'Config')
TESTING = False
URL_HOST = os.getenv('URL_HOST')
SECRET_KEY = os.getenv('KEY_SECRET_FLASK') # gen cmd: openssl rand -hex 32
# Add other configuration variables as needed
# MySQL
SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI')
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ENGINE_OPTIONS = {
'pool_size': 10,
'pool_recycle': 280,
'pool_pre_ping': True,
'pool_timeout': 30,
}
# Auth0
SESSION_COOKIE_SECURE = True # depends on is_producction
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'None' # depends on is_producction
REMEMBER_COOKIE_SECURE = True
WTF_CSRF_ENABLED = True
# WTF_CSRF_CHECK_DEFAULT = False # We'll check it manually for API routes
# WTF_CSRF_HEADERS = ['X-CSRFToken'] # Accept CSRF token from this header
WTF_CSRF_TIME_LIMIT = None
WTF_CSRF_SSL_STRICT = False # Allows testing without HTTPS
ID_AUTH0_CLIENT = os.getenv('ID_AUTH0_CLIENT')
ID_AUTH0_CLIENT_SECRET = os.getenv('ID_AUTH0_CLIENT_SECRET')
DOMAIN_AUTH0 = os.getenv('DOMAIN_AUTH0')
ID_TOKEN_USER = 'user'
# PostgreSQL
DB_NAME = os.getenv('DB_NAME')
DB_USER = os.getenv('DB_USER')
DB_PASSWORD = os.getenv('DB_PASSWORD')
DB_HOST = os.getenv('DB_HOST')
# DB_PORT = os.getenv('DB_PORT')
# Store
# is_included_VAT = True
"""
KEY_IS_INCLUDED_VAT = 'is_included_VAT'
code_currency = 1
KEY_CODE_CURRENCY = 'id_currency'
code_region_delivery = 1
KEY_CODE_REGION_DELIVERY = 'id_region_delivery'
KEY_ID_CURRENCY = 'id_currency'
KEY_ID_REGION_DELIVERY = 'id_region_delivery'
"""
# id_currency = 1
# id_region_delivery = 1
# Mail
MAIL_DEBUG = False # av.input_bool(os.getenv('DEBUG'), 'DEBUG', 'Config')
MAIL_SERVER = 'mail.partsltd.co.uk' # 'smtp.gmail.com'
MAIL_PORT = 465 # 587
MAIL_USE_TLS = False
MAIL_USE_SSL = True
MAIL_USERNAME = os.getenv('MAIL_DEFAULT_SENDER')
MAIL_PASSWORD = os.getenv('MAIL_PASSWORD')
MAIL_DEFAULT_SENDER = os.getenv('MAIL_DEFAULT_SENDER')
MAIL_CONTACT_PUBLIC = os.getenv('MAIL_CONTACT_PUBLIC')
"""
# Recaptcha
RECAPTCHA_PUBLIC_KEY = os.getenv('RECAPTCHA_PUBLIC_KEY')
RECAPTCHA_PRIVATE_KEY = os.getenv('RECAPTCHA_PRIVATE_KEY')
"""
# ALTCHA
ALTCHA_API_KEY = os.getenv('ALTCHA_API_KEY')
ALTCHA_SECRET_KEY = os.getenv('ALTCHA_SECRET_KEY')
ALTCHA_REGION = 'eu'
class DevelopmentConfig(Config):
is_development = True
# Add development-specific configuration variables
DEBUG = True
MAIL_DEBUG = True
SESSION_COOKIE_SECURE = True # False
SESSION_COOKIE_SAMESITE = 'None' # 'Lax' # depends on is_producction
class ProductionConfig(Config):
is_production = True
# Add production-specific configuration variables
pass
# Set the configuration class based on the environment
# You can change 'development' to 'production' when deploying
config_env = os.getenv('FLASK_ENV', "development")
with open('app.log', 'a') as f:
print(f'config_env: {config_env}')
f.write(f'config_env: {config_env}\n')
# current_app.logger.error(f'config_env: {config_env}') # logger not yet initialised
if config_env == 'development':
app_config = DevelopmentConfig
elif config_env == 'production':
app_config = ProductionConfig
else:
raise ValueError("Invalid configuration environment")
# environment variables
"""
SET KEY_SECRET_FLASK=nips
SET ID_AUTH0_CLIENT=
SET ID_AUTH0_CLIENT_SECRET=
SET DOMAIN_AUTH0=
SET MAIL_PASSWORD=
SET RECAPTCHA_PUBLIC_KEY=
SET RECAPTCHA_PRIVATE_KEY=
SET SQLALCHEMY_DATABASE_URI=
SET URL_HOST=
"""
# SET SQLALCHEMY_DATABASE_URI = mysql://username:password@localhost/dbname
# Replace 'username', 'password', 'localhost', and 'dbname' with your actual database credentials

0
controllers/__init__.py Normal file
View File

View File

View File

View File

@@ -0,0 +1,78 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: App Routing
Feature: Legal Routes
Description:
Legal Section Controller.
"""
# IMPORTS
# internal
# from models.model_view_home import Model_View_Home
from models.model_view_license import Model_View_License
from models.model_view_privacy_policy import Model_View_Privacy_Policy
from models.model_view_accessibility_report import Model_View_Accessibility_Report
from models.model_view_accessibility_statement import Model_View_Accessibility_Statement
from models.model_view_retention_schedule import Model_View_Retention_Schedule
import lib.argument_validation as av
# external
from flask import render_template, Blueprint, send_from_directory
routes_legal = Blueprint('routes_legal', __name__)
# snore
@routes_legal.route('/license', methods=['GET'])
def license():
try:
model = Model_View_License()
html_body = render_template('pages/legal/_license.html', model = model)
except Exception as e:
return str(e)
return html_body
@routes_legal.route('/accessibility-statement', methods=['GET'])
def accessibility_statement():
try:
model = Model_View_Accessibility_Statement()
html_body = render_template('pages/legal/_accessibility_statement.html', model = model)
except Exception as e:
return str(e)
return html_body
@routes_legal.route('/accessibility-report', methods=['GET'])
def accessibility_report():
try:
model = Model_View_Accessibility_Report()
html_body = render_template('pages/legal/_accessibility_report.html', model = model)
except Exception as e:
return str(e)
return html_body
@routes_legal.route('/retention-schedule', methods=['GET'])
def retention_schedule():
try:
model = Model_View_Retention_Schedule()
html_body = render_template('pages/legal/_retention_schedule.html', model = model)
except Exception as e:
return str(e)
return html_body
@routes_legal.route('/privacy-policy', methods=['GET'])
def privacy_policy():
try:
model = Model_View_Privacy_Policy()
html_body = render_template('pages/legal/_privacy_policy.html', model = model)
except Exception as e:
return str(e)
return html_body
@routes_legal.route('/robots.txt', methods=['GET'])
def robots_txt():
return send_from_directory('static', 'docs/robots.txt')
@routes_legal.route('/favicon.ico', methods=['GET'])
def favicon_ico():
return send_from_directory('static', 'images/logo.ico', mimetype='image/vnd.microsoft.icon') # -and-company-name-curved-0.5
@routes_legal.route('/sitemap.xml', methods=['GET'])
def sitemap_xml():
return send_from_directory('static', 'docs/sitemap.xml')

View File

378
controllers/tcg/mtg_game.py Normal file
View File

@@ -0,0 +1,378 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: App Routing
Feature: TCG - MTG Game Routes
Description:
MTG Game Page Controller.
"""
# IMPORTS
# internal
from business_objects.api import API
from business_objects.tcg.mtg_game import MTG_Game
from business_objects.tcg.mtg_game_player import MTG_Game_Player, Parameters_MTG_Game_Player
from business_objects.tcg.mtg_game_round import MTG_Game_Round, Parameters_MTG_Game_Round
from business_objects.tcg.mtg_game_round_player_damage import MTG_Game_Round_Player_Damage, Parameters_MTG_Game_Round_Player_Damage
from business_objects.tcg.mtg_deck import MTG_Deck, Parameters_MTG_Deck
from business_objects.tcg.user import User, Parameters_User
from datastores.datastore_mtg import DataStore_MTG
from datastores.datastore_user import DataStore_User
from forms.tcg.game import Filters_MTG_Game
from helpers.helper_app import Helper_App
from models.model_view_mtg_base import Model_View_MTG_Base
from models.model_view_mtg_game import Model_View_MTG_Game
from models.model_view_mtg_games import Model_View_MTG_Games
from models.model_view_mtg_home import Model_View_MTG_Home
import lib.argument_validation as av
# external
from flask import Flask, render_template, jsonify, request, render_template_string, send_from_directory, redirect, url_for, session, Blueprint, current_app, flash, Response
from flask_mail import Mail, Message
from extensions import db, oauth, mail
from urllib.parse import quote_plus, urlencode
from authlib.integrations.flask_client import OAuth
from authlib.integrations.base_client import OAuthError
from urllib.parse import quote, urlparse, parse_qs
import json
import base64
import hmac
import hashlib
import datetime
routes_mtg_game = Blueprint('routes_mtg_game', __name__)
@routes_mtg_game.route(Model_View_MTG_Base.HASH_PAGE_MTG_GAMES, methods=['GET'])
def games():
Helper_App.console_log('mtg games')
Helper_App.console_log(f'request_args: {request.args}')
try:
form_filters = Filters_MTG_Game.from_json(request.args)
except Exception as e:
Helper_App.console_log(f'Error: {e}')
form_filters = Filters_MTG_Game()
Helper_App.console_log(f'form_filters={form_filters}')
datastore = DataStore_MTG()
user_session = datastore.get_user_session()
if not user_session.get_is_logged_in():
return redirect(url_for('routes_mtg_game.home'))
parameters_game = form_filters.to_parameters(user_id_session = user_session.user_id)
model = Model_View_MTG_Games(parameters_game = parameters_game)
model.form_filters = form_filters
Helper_App.console_log(f'form_filters={form_filters}')
return render_template('pages/tcg/mtg/_games.html', model=model)
@routes_mtg_game.route(f'{Model_View_MTG_Base.HASH_PAGE_MTG_GAME}/<int:game_id>', methods=['GET'])
def game_detail(game_id):
Helper_App.console_log(f'mtg game detail: {game_id}')
model = Model_View_MTG_Game(game_id=game_id)
if not model.is_user_logged_in:
return redirect(url_for('routes_mtg_game.home'))
if model.game is None:
flash('Game not found', 'error')
return redirect(url_for('routes_mtg_game.games'))
return render_template('pages/tcg/mtg/_game.html', model=model)
@routes_mtg_game.route(Model_View_MTG_Base.HASH_SAVE_MTG_GAME, methods=['POST'])
def save_game():
_m = 'routes_mtg_game.save_game'
data = Helper_App.get_request_data(request)
Helper_App.console_log(f'{_m}\n{data}')
try:
"""
form_filters = Filters_MTG_Game.from_json(data.get(Model_View_MTG_Base.FLAG_FORM_FILTERS, {}))
if not form_filters.validate_on_submit():
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Filters form invalid.\n{form_filters.errors}'
})
form_filters = Filters_MTG_Game.get_default()
"""
datastore = DataStore_MTG()
user_session = datastore.get_user_session()
if not user_session.get_is_logged_in():
raise Exception('User not logged in')
games = data.get(Model_View_MTG_Base.FLAG_GAME, [])
if len(games) == 0:
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: 'No games to save.'
})
Helper_App.console_log(f"Games to save: {games}")
obj_game = MTG_Game.from_json(games[0])
Helper_App.console_log(f'obj_game={obj_game}')
game_id, errors = DataStore_MTG.save_mtg_game(obj_game)
if len(errors) > 0:
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Error saving games.\n{Model_View_MTG_Game.convert_list_objects_to_json(errors)}'
})
"""
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_SUCCESS,
Model_View_MTG_Base.FLAG_DATA: Model_View_MTG_Base.convert_list_objects_to_json(model_return.games)
})
"""
return redirect(url_for('routes_mtg_game.game_detail', game_id = game_id))
except Exception as e:
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Bad data received by controller.\n{e}'
})
@routes_mtg_game.route(Model_View_MTG_Base.HASH_PAGE_MTG_HOME, methods=['GET'])
def home():
model = Model_View_MTG_Home()
if model.user.get_is_logged_in():
return redirect(url_for('routes_mtg_game.games'))
return render_template('pages/tcg/mtg/_home.html', model=model)
@routes_mtg_game.route(Model_View_MTG_Base.HASH_PAGE_MTG_TRIAL_GAME, methods=['GET'])
def trial_game():
return send_from_directory('templates/pages/tcg/mtg', 'trial_game.html')
@routes_mtg_game.route('/mtg/api/game/<int:game_id>/players', methods=['GET'])
def get_game_players(game_id):
"""Get players for a game with enriched user and deck information."""
_m = 'routes_mtg_game.get_game_players'
Helper_App.console_log(f'{_m}: game_id={game_id}')
try:
datastore = DataStore_MTG()
datastore_user = DataStore_User()
user_session = datastore.get_user_session()
if not user_session.get_is_logged_in():
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: 'User not logged in'
}), 401
# Get players for this game
parameters_player = Parameters_MTG_Game_Player.get_default(user_session.user_id)
parameters_player.game_ids = str(game_id)
parameters_player.get_all_game = False
players, errors = datastore.get_many_mtg_game_player(parameters_player)
if len(errors) > 0:
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Error fetching players: {errors}'
}), 500
players_json = [p.to_json() for p in players]
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_SUCCESS,
Model_View_MTG_Base.FLAG_DATA: players_json
})
except Exception as e:
Helper_App.console_log(f'{_m} Error: {str(e)}')
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Error: {str(e)}'
}), 500
@routes_mtg_game.route('/mtg/api/game/<int:game_id>/rounds', methods=['GET'])
def get_many_game_round(game_id):
"""Get rounds for a game."""
_m = 'routes_mtg_game.get_many_game_round'
Helper_App.console_log(f'{_m}: game_id={game_id}')
try:
datastore = DataStore_MTG()
user_session = datastore.get_user_session()
if not user_session.get_is_logged_in():
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: 'User not logged in'
}), 401
# Get rounds for this game
parameters_round = Parameters_MTG_Game_Round.get_default(user_session.user_id)
parameters_round.game_ids = str(game_id)
parameters_round.get_all_game = False
rounds, errors = datastore.get_many_mtg_game_round(parameters_round)
if len(errors) > 0:
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Error fetching rounds: {errors}'
}), 500
rounds_json = [r.to_json() for r in rounds]
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_SUCCESS,
Model_View_MTG_Base.FLAG_DATA: rounds_json
})
except Exception as e:
Helper_App.console_log(f'{_m} Error: {str(e)}')
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Error: {str(e)}'
}), 500
@routes_mtg_game.route('/mtg/api/game/<int:game_id>/damage-records', methods=['GET'])
def get_many_game_player_damage(game_id):
"""Get damage records for a game."""
_m = 'routes_mtg_game.get_many_game_player_damage'
Helper_App.console_log(f'{_m}: game_id={game_id}')
try:
datastore = DataStore_MTG()
user_session = datastore.get_user_session()
if not user_session.get_is_logged_in():
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: 'User not logged in'
}), 401
parameters_damage = Parameters_MTG_Game_Round_Player_Damage.get_default(user_session.user_id)
parameters_damage.game_ids = str(game_id)
parameters_damage.get_all_game = False
damages, errors = datastore.get_many_mtg_game_round_player_damage(parameters_damage)
if len(errors) > 0:
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Error fetching damage records: {errors}'
}), 500
damage_records = [d.to_json() for d in damages]
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_SUCCESS,
Model_View_MTG_Base.FLAG_DATA: damage_records
})
except Exception as e:
Helper_App.console_log(f'{_m} Error: {str(e)}')
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Error: {str(e)}'
}), 500
@routes_mtg_game.route(Model_View_MTG_Base.HASH_SAVE_MTG_GAME_PLAYER, methods=['POST'])
def save_game_player():
_m = 'routes_mtg_game.save_game_player'
data = Helper_App.get_request_data(request)
Helper_App.console_log(f'{_m}\n{data}')
try:
"""
form_filters = Filters_MTG_Game.from_json(data.get(Model_View_MTG_Base.FLAG_FORM_FILTERS, {}))
if not form_filters.validate_on_submit():
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Filters form invalid.\n{form_filters.errors}'
})
form_filters = Filters_MTG_Game.get_default()
"""
datastore = DataStore_MTG()
user_session = datastore.get_user_session()
if not user_session.get_is_logged_in():
raise Exception('User not logged in')
players = data.get(Model_View_MTG_Base.FLAG_PLAYER, [])
if len(players) == 0:
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: 'No players to save.'
})
objs_player = []
for player in players:
obj_player = MTG_Game_Player.from_json(player)
Helper_App.console_log(f'obj_player = {obj_player}')
objs_player.append(obj_player)
success, errors = DataStore_MTG.save_mtg_game_player(objs_player)
if len(errors) > 0:
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Error saving players.\n{Model_View_MTG_Game.convert_list_objects_to_json(errors)}'
})
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_SUCCESS if success else Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_DATA: None
})
# return redirect(url_for('routes_mtg_game.game_detail', game_id = game_id))
except Exception as e:
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Bad data received by controller.\n{e}'
})
@routes_mtg_game.route(Model_View_MTG_Base.HASH_SAVE_MTG_GAME_ROUND_PLAYER_DAMAGE, methods=['POST'])
def save_game_round_player_damage():
_m = 'routes_mtg_game.save_game_round_player_damage'
data = Helper_App.get_request_data(request)
Helper_App.console_log(f'{_m}\n{data}')
try:
datastore = DataStore_MTG()
user_session = datastore.get_user_session()
if not user_session.get_is_logged_in():
raise Exception('User not logged in')
rounds = data.get(Model_View_MTG_Base.FLAG_ROUND, [])
if len(rounds) == 0:
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: 'No rounds to save.'
})
objs_round = []
for round in rounds:
obj_round = MTG_Game_Round.from_json(round)
Helper_App.console_log(f'obj_round = {obj_round}')
objs_round.append(obj_round)
damages = data.get(Model_View_MTG_Base.FLAG_DAMAGE, [])
if len(damages) == 0:
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: 'No damages to save.'
})
objs_damage = []
for damage in damages:
obj_damage = MTG_Game_Round_Player_Damage.from_json(damage)
Helper_App.console_log(f'obj_damage = {obj_damage}')
objs_damage.append(obj_damage)
success, errors = DataStore_MTG.save_mtg_game_round_player_damage(objs_round, objs_damage)
if len(errors) > 0:
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Error saving round player damages.\n{Model_View_MTG_Game.convert_list_objects_to_json(errors)}'
})
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_SUCCESS if success else Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_DATA: None
})
# return redirect(url_for('routes_mtg_game.game_detail', game_id = game_id))
except Exception as e:
return jsonify({
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE,
Model_View_MTG_Base.FLAG_MESSAGE: f'Bad data received by controller.\n{e}'
})

255
controllers/user/user.py Normal file
View File

@@ -0,0 +1,255 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: App Routing
Feature: User Routes
Description:
Initialises the Flask application, sets the configuration based on the environment, and defines two routes (/ and /about) that render templates with the specified titles.
"""
# IMPORTS
# internal
from models.model_view_base import Model_View_Base
from models.model_view_user import Model_View_User
from business_objects.tcg.user import User, Parameters_User
from datastores.datastore_user import DataStore_User
from mtg_commander_life_tracker.forms.tcg.user import Filters_User
from helpers.helper_app import Helper_App
import lib.argument_validation as av
# external
from flask import Flask, render_template, jsonify, request, render_template_string, send_from_directory, redirect, url_for, session, Blueprint, current_app
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import exc
from flask_wtf.csrf import generate_csrf
from werkzeug.exceptions import BadRequest
from extensions import oauth # db,
from urllib.parse import quote_plus, urlencode
from authlib.integrations.flask_client import OAuth
from authlib.integrations.base_client import OAuthError
from urllib.parse import quote, urlparse, parse_qs
from functools import wraps
db = SQLAlchemy()
routes_user = Blueprint('routes_user', __name__)
def handle_db_disconnect(f):
@wraps(f)
def decorated_function(*args, **kwargs):
try:
return f(*args, **kwargs)
except exc.OperationalError as e:
if "MySQL server has gone away" in str(e):
# Close the session and create a new connection
db.session.remove()
db.session.rollback()
# Retry the operation
return f(*args, **kwargs)
raise
return decorated_function
# User authentication
@routes_user.route("/login", methods=['POST', 'OPTIONS']) # required endpoint for Auth0
def login():
oauth = current_app.extensions['authlib.integrations.flask_client']
try:
# Helper_App.console_log('login')
# Helper_App.console_log(f'method={request.method}')
try:
data = request.json
try:
data = request.get_json()
except:
data = {}
except:
data = {}
# Helper_App.console_log(f'data={data}')
hash_callback = data.get(Model_View_Base.FLAG_CALLBACK, Model_View_Base.HASH_PAGE_MTG_HOME)
# Helper_App.console_log(f'hash_callback: {hash_callback}')
uri_redirect = url_for('routes_user.login_callback', _external=True)
# Helper_App.console_log(f'redirect uri: {uri_redirect}')
# Helper_App.console_log(f'Before red')
# Helper_App.console_log(f"Registered clients: {list(oauth._clients.keys())}")
try:
with current_app.app_context():
red = oauth.auth0.authorize_redirect(
redirect_uri = uri_redirect
, state = quote(hash_callback)
)
except Exception as e:
Helper_App.console_log(f"Error: {str(e)}")
# Helper_App.console_log(f'redirect: {red}')
headers = red.headers['Location']
# Helper_App.console_log(f'headers: {headers}')
parsed_url = urlparse(headers)
query_params = parse_qs(parsed_url.query)
Helper_App.console_log(f"""
OAuth Authorize Redirect URL:
Base URL: {parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}
{parsed_url}
Query Parameters: {query_params}
""")
return jsonify({'Success': True, Model_View_Base.FLAG_STATUS: Model_View_Base.FLAG_SUCCESS, f'{Model_View_Base.FLAG_CALLBACK}': headers})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 400
@routes_user.route("/login_callback")
@handle_db_disconnect
def login_callback():
oauth = current_app.extensions['authlib.integrations.flask_client']
# Helper_App.console_log('login_callback')
try:
# Helper_App.console_log(f'Redceived state: {request.args.get("state")}')
token = None
try:
token = oauth.auth0.authorize_access_token()
except Exception as e:
Helper_App.console_log(f"Error: {str(e)}")
session[current_app.config['ID_TOKEN_USER']] = token
try:
hash_callback = token.get('hash_callback')
if hash_callback is None:
Helper_App.console_log('hash is none')
state = request.args.get('state')
Helper_App.console_log(f'state: {state}')
hash_callback = state
Helper_App.console_log(f'hash_callback: {hash_callback}')
except:
Helper_App.console_log("get hash callback failed")
error_state = request.args.get(Model_View_User.FLAG_ERROR_OAUTH)
has_error = error_state is not None
if has_error:
error_description = request.args.get(Model_View_User.FLAG_ERROR_DESCRIPTION_OAUTH)
error_text = f'Error: {error_state}: {error_description}'
Helper_App.console_log(error_text)
return redirect(f"{current_app.config['URL_HOST']}{hash_callback}") # login()
user = User.from_json_auth0(token)
Helper_App.console_log(f'user: {user}')
"""
session[Model_View_Base.FLAG_USER] = user.to_json()
Helper_App.console_log(f'user stored on session')
"""
datastore_user = DataStore_User()
try:
saved_user, errors = datastore_user.login_user(user)
if (len(errors) > 0): raise ValueError(f'Database errors: {errors}')
Helper_App.console_log('User logged in')
Helper_App.console_log(f'user ({str(type(saved_user))}): {saved_user}')
Helper_App.console_log(f'user key: {Model_View_Base.FLAG_USER}')
saved_user_json = saved_user.to_json()
Helper_App.console_log(f'User JSON: {saved_user_json}')
session[Model_View_Base.FLAG_USER] = saved_user_json
Helper_App.console_log(f'user stored on session')
except Exception as e:
Helper_App.console_log(f'User not found: {saved_user}\nDatabase query error: {errors}')
Helper_App.console_log(f'user session: {session.get(Model_View_Base.FLAG_USER, "(Key not found)")}')
return redirect(f"{current_app.config['URL_HOST']}{hash_callback}")
except Exception as e:
return jsonify({Model_View_Base.FLAG_STATUS: Model_View_Base.FLAG_FAILURE, Model_View_Base.FLAG_MESSAGE: f'Controller error.\n{e}'})
@routes_user.route("/logout")
def logout():
session.clear()
url_logout = f"https://{current_app.config['DOMAIN_AUTH0']}/v2/logout?" + urlencode(
{
"returnTo": url_for("routes_user.logout_callback", _external=True),
"client_id": current_app.config['ID_AUTH0_CLIENT'],
}
)
Helper_App.console_log(f"Redirecting to {url_logout}")
return redirect(url_logout)
@routes_user.route("/logout_callback")
@handle_db_disconnect
def logout_callback():
return redirect(url_for('routes_mtg_game.home'))
@routes_user.route(Model_View_User.HASH_PAGE_USER_ACCOUNT)
def user():
try:
user_session = Model_View_User.get_user_session()
if not user_session.get_is_logged_in():
return redirect(url_for('routes_mtg_game.home'))
form_filters = Filters_User.get_default()
model = Model_View_User(form_filters_old = form_filters)
model._title = model.user.firstname
# model.users = [model.user]
html_body = render_template('pages/user/_user.html', model = model)
except Exception as e:
return str(e)
return html_body
@routes_user.route(Model_View_User.HASH_PAGE_USER_ACCOUNTS)
def users():
try:
Helper_App.console_log(f'request_args: {request.args}')
user_session = Model_View_User.get_user_session()
if (not user_session.get_is_logged_in()) or (not user_session.can_admin_user):
return redirect(url_for('routes_mtg_game.home'))
try:
form_filters = Filters_User.from_json(request.args)
except Exception as e:
Helper_App.console_log(f'Error: {e}')
form_filters = Filters_User.get_default()
model = Model_View_User(form_filters, hash_page_current = Model_View_User.HASH_PAGE_USER_ACCOUNTS)
html_body = render_template('pages/user/_users.html', model = model)
except Exception as e:
return str(e)
return html_body
@routes_user.route(Model_View_User.HASH_SAVE_USER_USER, methods=['POST'])
def save_user():
data = Helper_App.get_request_data(request)
try:
form_filters = Filters_User.from_json(data[Model_View_User.FLAG_FORM_FILTERS])
if not form_filters.validate_on_submit():
return jsonify({
Model_View_User.FLAG_STATUS: Model_View_User.FLAG_FAILURE,
Model_View_User.FLAG_MESSAGE: f'Filters form invalid.\n{form_filters.errors}'
})
model_return = Model_View_User(form_filters_old=form_filters)
if not model_return.is_user_logged_in:
return redirect(url_for('routes_mtg_game.home'))
users = data[Model_View_User.FLAG_USER]
if len(users) == 0:
return jsonify({
Model_View_User.FLAG_STATUS: Model_View_User.FLAG_FAILURE,
Model_View_User.FLAG_MESSAGE: f'No users.'
})
objs_user = []
for user in users:
objs_user.append(User.from_json(user))
Helper_App.console_log(f'objs_user={objs_user}')
errors = DataStore_User.save_users(data.get('comment', 'No comment'), objs_user)
if (len(errors) > 0):
return jsonify({
Model_View_User.FLAG_STATUS: Model_View_User.FLAG_FAILURE,
Model_View_User.FLAG_MESSAGE: f'Error saving users.\n{model_return.convert_list_objects_to_json(errors)}'
})
return jsonify({
Model_View_User.FLAG_STATUS: Model_View_User.FLAG_SUCCESS,
Model_View_User.FLAG_DATA: Model_View_User.convert_list_objects_to_json(model_return.users)
})
except Exception as e:
return jsonify({
Model_View_User.FLAG_STATUS: Model_View_User.FLAG_FAILURE,
Model_View_User.FLAG_MESSAGE: f'Bad data received by controller.\n{e}'
})

11
datastores/__init__.py Normal file
View File

@@ -0,0 +1,11 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Module Initialisation
Feature: DataStores
Description:
Initialises datastores module.
"""

View File

@@ -0,0 +1,225 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: DataStores
Feature: Base DataStore
Description:
Datastore for Store
"""
# internal
# from routes import bp_home
import lib.argument_validation as av
from business_objects.sql_error import SQL_Error, Parameters_SQL_Error
from business_objects.tcg.user import User
# from helpers.helper_db_sql import Helper_DB_SQL
# from models.model_view_store_checkout import Model_View_Store_Checkout # circular!
from extensions import db
from helpers.helper_app import Helper_App
# external
from sqlalchemy import text
from flask import Flask, session, current_app
from pydantic import BaseModel, ConfigDict
import time
from sqlalchemy.exc import OperationalError
from typing import ClassVar
import uuid as uuid_lib
class DataStore_Base(BaseModel):
def __init__(self, **kwargs):
super().__init__(**kwargs)
@staticmethod
def db_procedure_execute(proc_name, argument_dict_list = None, argument_types_dict = None):
"""Execute a PostgreSQL procedure using autocommit to allow internal COMMIT/ROLLBACK"""
_m = 'DataStore_Base.db_procedure_execute'
av.val_str(proc_name, 'proc_name', _m)
proc_string = f'CALL {proc_name}('
has_arguments = argument_dict_list is not None
if has_arguments:
arg_keys = list(argument_dict_list.keys())
for i in range(len(arg_keys)):
param_name = arg_keys[i]
# Add explicit PostgreSQL CAST for typed parameters
if argument_types_dict and param_name in argument_types_dict:
type_name = argument_types_dict[param_name].__name__
pg_type = DataStore_Base.TYPE_CAST_MAP.get(type_name)
if pg_type:
param_expr = f'CAST(:{param_name} AS {pg_type})'
else:
param_expr = f':{param_name}'
else:
param_expr = f':{param_name}'
proc_string += f'{"" if i == 0 else ", "}{param_name} := {param_expr}'
proc_string += ')'
stmt = text(proc_string)
Helper_App.console_log(f'{_m}\nproc_string: {stmt}\nargs: {argument_dict_list}')
rows = []
with db.engine.connect().execution_options(isolation_level="AUTOCOMMIT") as conn:
if has_arguments:
result = conn.execute(stmt, argument_dict_list)
else:
result = conn.execute(stmt)
# Fetch all rows as mappings before connection closes
if result.returns_rows:
rows = result.mappings().fetchall()
Helper_App.console_log(f'result: {rows}')
return rows
# Map SQLAlchemy types to PostgreSQL type names for CAST()
TYPE_CAST_MAP: ClassVar[dict[str, str]] = {
'String': 'varchar',
'Text': 'text',
'Integer': 'integer',
'Boolean': 'boolean',
'UUID': 'uuid',
'Uuid': 'uuid',
}
@staticmethod
def db_function_execute(func_name, argument_dict_list = None, argument_types_dict = None):
"""Execute a PostgreSQL function that returns a table using SELECT * FROM function_name()"""
_m = 'DataStore_Base.db_function_execute'
av.val_str(func_name, 'func_name', _m)
func_string = f'SELECT * FROM {func_name}('
has_arguments = argument_dict_list is not None
if has_arguments:
arg_keys = list(argument_dict_list.keys())
for i in range(len(arg_keys)):
param_name = arg_keys[i]
# Add explicit PostgreSQL CAST for typed parameters
if argument_types_dict and param_name in argument_types_dict:
type_name = argument_types_dict[param_name].__name__
pg_type = DataStore_Base.TYPE_CAST_MAP.get(type_name)
if pg_type:
param_expr = f'CAST(:{param_name} AS {pg_type})'
else:
param_expr = f':{param_name}'
else:
param_expr = f':{param_name}'
func_string += f'{"" if i == 0 else ", "}{param_name} := {param_expr}'
func_string += ')'
stmt = text(func_string)
Helper_App.console_log(f'{_m}\nfunc_string: {stmt}\nargs: {argument_dict_list}')
if has_arguments:
result = db.session.execute(stmt, argument_dict_list)
else:
result = db.session.execute(stmt)
Helper_App.console_log(f'result: {result}')
return result
@staticmethod
def db_cursor_clear(cursor):
while cursor.nextset():
Helper_App.console_log(f'unexpected result set: {cursor.fetchall()}')
@staticmethod
def get_user_session():
Helper_App.console_log('DataStore_Base.get_user_session')
user = User.from_json(session.get(User.FLAG_USER))
"""
if user.user_id <= 0:
user.user_id = 3
"""
Helper_App.console_log(f'User: {user}')
return user
@staticmethod
def upload_bulk(permanent_table_name, records, batch_size):
_m = 'DataStore_Base.upload_bulk'
Helper_App.console_log(f'{_m}\nstarting...')
Helper_App.console_log(f'permanent_table_name: {permanent_table_name}')
if db.session.dirty or db.session.new or db.session.deleted:
Helper_App.console_log("Session is not clean")
return
# Assuming `permanent_table_name` is a string representing the table name
table_object = db.metadata.tables.get(permanent_table_name)
Helper_App.console_log(f'Tables: {list(db.metadata.tables.keys())}')
if table_object is None:
Helper_App.console_log(f"Table {permanent_table_name} not found in metadata.")
return
else:
expected_columns = set(column.name for column in db.inspect(table_object).columns)
Helper_App.console_log(f'table name: {table_object.name}')
Helper_App.console_log(f'expected_columns: {expected_columns}')
max_retries = 3
initial_backoff = 1
for i in range(0, len(records), batch_size):
batch = records[i:i + batch_size]
try:
retries = 0
while retries < max_retries:
try:
# Helper_App.console_log(f'Before upload batch.')
db.session.add_all(batch)
# db.session.bulk_save_objects(batch)
# Helper_App.console_log(f'Before commit batch.')
db.session.commit()
# Helper_App.console_log(f'Batch uploaded.')
break
except OperationalError as e:
if "Lock wait timeout exceeded" not in str(e) or retries == max_retries - 1:
raise
wait_time = initial_backoff * (2 ** retries)
current_app.logger.warning(f"Lock timeout encountered. Retrying in {wait_time} seconds... (Attempt {retries + 1}/{max_retries})")
time.sleep(wait_time)
retries += 1
# Ensure the session is clean for the retry
db.session.rollback()
except Exception as e:
db.session.rollback()
raise e
Helper_App.console_log(f'Records uploaded in batches.')
@classmethod
def get_many_error(cls, guid):
_m = f'{cls.__qualname__}.get_many_error'
# user = cls.get_user_session()
arguments = Parameters_SQL_Error.get_default(guid)
argument_dict = arguments.to_json()
argument_types = Parameters_SQL_Error.get_type_hints()
Helper_App.console_log(f'{_m}\nargument_dict: {argument_dict}')
errors = []
try:
error_result = cls.db_function_execute('tcg.public.FN_Error_Get_Many', argument_dict, argument_types)
error_result_set = error_result.fetchall()
Helper_App.console_log(f'raw errors: {error_result_set}')
errors = []
error_indexes = {}
for row in error_result_set:
new_error = SQL_Error.from_db_error(row)
error_indexes[new_error.error_id] = len(errors)
errors.append(new_error)
Helper_App.console_log(f'error {str(type(new_error))}: {new_error}')
except Exception as e:
Helper_App.console_log(f'Error: {str(e)}')
error = SQL_Error()
error.msg = str(e)
errors.append(error)
return errors
@classmethod
def clear_error(cls, guid):
_m = f'{cls.__qualname__}.clear_error'
# user = cls.get_user_session()
arguments = Parameters_SQL_Error.get_default(guid)
argument_dict = arguments.to_json()
argument_types = Parameters_SQL_Error.get_type_hints()
Helper_App.console_log(f'{_m}\nargument_dict: {argument_dict}')
cls.db_procedure_execute('tcg.public.USP_Error_Clear', argument_dict, argument_types)

341
datastores/datastore_mtg.py Normal file
View File

@@ -0,0 +1,341 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: DataStores
Feature: MTG DataStore
Description:
Datastore for MTG game data
"""
# internal
import lib.argument_validation as av
from business_objects.tcg.mtg_game import MTG_Game, Parameters_MTG_Game
from business_objects.tcg.mtg_game_player import MTG_Game_Player, Parameters_MTG_Game_Player, MTG_Game_Player_Temp
from business_objects.tcg.mtg_game_round import MTG_Game_Round, Parameters_MTG_Game_Round, MTG_Game_Round_Temp
from business_objects.tcg.mtg_game_round_player_damage import MTG_Game_Round_Player_Damage, Parameters_MTG_Game_Round_Player_Damage, MTG_Game_Round_Player_Damage_Temp
from business_objects.tcg.mtg_deck import MTG_Deck, Parameters_MTG_Deck
from business_objects.sql_error import SQL_Error, Parameters_SQL_Error
from datastores.datastore_base import DataStore_Base
from helpers.helper_app import Helper_App
from helpers.helper_db_sql import Helper_DB_SQL
from extensions import db
# external
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Uuid
from sqlalchemy.types import Text, Boolean, Integer
from sqlalchemy.dialects.postgresql import TIMESTAMP
from datetime import datetime
db = SQLAlchemy()
class DataStore_MTG(DataStore_Base):
def __init__(self):
super().__init__()
@classmethod
def get_many_mtg_game(cls, game_filters):
_m = f'{cls.__qualname__}.get_many_mtg_game'
# user = cls.get_user_session()
argument_dict = game_filters.to_json()
argument_types = Parameters_MTG_Game.get_type_hints()
Helper_App.console_log(f'argument_dict: {argument_dict}')
games = []
errors = []
try:
game_result = cls.db_function_execute('tcg.public.FN_TCG_MTG_Game_Get_Many', argument_dict, argument_types)
game_result_set = game_result.fetchall()
Helper_App.console_log(f'raw games: {game_result_set}')
games = []
game_indexes = {}
for row in game_result_set:
new_game = MTG_Game.from_db_mtg_game(row)
game_indexes[new_game.game_id] = len(games)
games.append(new_game)
Helper_App.console_log(f'game {str(type(new_game))}: {new_game}')
except Exception as e:
Helper_App.console_log(f'Error: {str(e)}')
error = SQL_Error()
error.msg = str(e)
errors.append(error)
return games, errors
@classmethod
def save_mtg_game(cls, game):
_m = f'{cls.__qualname__}.save_mtg_game'
user = cls.get_user_session()
guid = Helper_DB_SQL.create_guid()
game_id = None
success = None
argument_dict = {
'a_comment': 'Save game'
, 'a_end_on': game.end_on
, 'a_game_id': game.game_id
, 'a_guid': guid
, 'a_location_name': game.location_name
, 'a_notes': game.notes
, 'a_starting_life': game.starting_life
, 'a_start_on': game.start_on
, 'a_user_id': user.user_id
, 'a_do_delete': False
, 'a_is_commander': True
, 'a_is_draft': False
, 'a_is_sealed': False
, 'o_game_id': game_id
, 'o_success': success
}
argument_type_hints = {
'a_comment': Text
, 'a_end_on': TIMESTAMP
, 'a_game_id': Integer
, 'a_guid': Uuid
, 'a_location_name': Text
, 'a_notes': Text
, 'a_starting_life': Integer
, 'a_start_on': TIMESTAMP
, 'a_user_id': Integer
, 'a_do_delete': Boolean
, 'a_is_commander': Boolean
, 'a_is_draft': Boolean
, 'a_is_sealed': Boolean
, 'o_game_id': Integer
, 'o_success': Boolean
}
Helper_App.console_log(f'argument_dict: {argument_dict}')
errors = []
try:
rows = cls.db_procedure_execute('tcg.public.USP_TCG_MTG_Game_Save', argument_dict, argument_type_hints)
row = rows[0] if rows else None
game_id = row['o_game_id'] if row else None
success = row['o_success'] if row else False
Helper_App.console_log(f'Game ID: {game_id}')
Helper_App.console_log(f'Success: {success}')
if not success:
errors = cls.get_many_error(guid = guid)
except Exception as e:
Helper_App.console_log(f'Error: {str(e)}')
error = SQL_Error()
error.msg = str(e)
errors.append(error)
cls.clear_error(guid = guid)
return game_id, errors
@classmethod
def get_many_mtg_game_player(cls, player_filters):
_m = f'{cls.__qualname__}.get_many_mtg_game_player'
argument_dict = player_filters.to_json()
argument_types = Parameters_MTG_Game_Player.get_type_hints()
Helper_App.console_log(f'{_m}\nargument_dict: {argument_dict}')
players = []
errors = []
try:
result = cls.db_function_execute('tcg.public.FN_TCG_MTG_Game_Player_Get_Many', argument_dict, argument_types)
result_set = result.fetchall()
Helper_App.console_log(f'raw players: {result_set}')
for row in result_set:
new_player = MTG_Game_Player.from_db_mtg_game_player(row)
players.append(new_player)
Helper_App.console_log(f'player {str(type(new_player))}: {new_player}')
except Exception as e:
Helper_App.console_log(f'Error: {str(e)}')
error = SQL_Error()
error.msg = str(e)
errors.append(error)
return players, errors
@classmethod
def get_many_mtg_game_round(cls, round_filters):
_m = f'{cls.__qualname__}.get_many_mtg_game_round'
argument_dict = round_filters.to_json()
argument_types = Parameters_MTG_Game_Round.get_type_hints()
Helper_App.console_log(f'{_m}\nargument_dict: {argument_dict}')
rounds = []
errors = []
try:
result = cls.db_function_execute('tcg.public.FN_TCG_MTG_Game_Round_Get_Many', argument_dict, argument_types)
result_set = result.fetchall()
Helper_App.console_log(f'raw rounds: {result_set}')
for row in result_set:
new_round = MTG_Game_Round.from_db_mtg_game_round(row)
rounds.append(new_round)
Helper_App.console_log(f'round {str(type(new_round))}: {new_round}')
except Exception as e:
Helper_App.console_log(f'Error: {str(e)}')
error = SQL_Error()
error.msg = str(e)
errors.append(error)
return rounds, errors
@classmethod
def get_many_mtg_game_round_player_damage(cls, damage_filters):
_m = f'{cls.__qualname__}.get_many_mtg_game_round_player_damage'
argument_dict = damage_filters.to_json()
argument_types = Parameters_MTG_Game_Round_Player_Damage.get_type_hints()
Helper_App.console_log(f'{_m}\nargument_dict: {argument_dict}')
damages = []
errors = []
try:
result = cls.db_function_execute('tcg.public.FN_TCG_MTG_Game_Round_Player_Damage_Get_Many', argument_dict, argument_types)
result_set = result.fetchall()
Helper_App.console_log(f'raw damages: {result_set}')
for row in result_set:
new_damage = MTG_Game_Round_Player_Damage.from_db_mtg_game_round_player_damage(row)
damages.append(new_damage)
Helper_App.console_log(f'damage {str(type(new_damage))}: {new_damage.damage_id}')
except Exception as e:
Helper_App.console_log(f'Error: {str(e)}')
error = SQL_Error()
error.msg = str(e)
errors.append(error)
return damages, errors
@classmethod
def get_many_mtg_deck(cls, deck_filters):
_m = f'{cls.__qualname__}.get_many_mtg_deck'
argument_dict = deck_filters.to_json()
argument_types = Parameters_MTG_Deck.get_type_hints()
Helper_App.console_log(f'{_m}\nargument_dict: {argument_dict}')
decks = []
errors = []
try:
result = cls.db_function_execute('tcg.public.FN_TCG_MTG_Deck_Get_Many', argument_dict, argument_types)
result_set = result.fetchall()
Helper_App.console_log(f'raw decks: {result_set}')
for row in result_set:
new_deck = MTG_Deck.from_db_mtg_deck(row)
decks.append(new_deck)
Helper_App.console_log(f'deck {str(type(new_deck))}: {new_deck}')
except Exception as e:
Helper_App.console_log(f'Error: {str(e)}')
error = SQL_Error()
error.msg = str(e)
errors.append(error)
return decks, errors
@classmethod
def save_mtg_game_player(cls, players):
_m = f'{cls.__qualname__}.save_mtg_game_player'
user = cls.get_user_session()
guid = Helper_DB_SQL.create_guid()
success = None
argument_dict = {
'a_comment': 'Save game player'
, 'a_guid': guid
, 'a_user_id': user.user_id
, 'o_success': success
}
argument_type_hints = {
'a_comment': Text
, 'a_guid': Uuid
, 'a_user_id': Integer
, 'o_success': Boolean
}
Helper_App.console_log(f'argument_dict: {argument_dict}')
objs_player_temp = []
for player in players:
obj_player_temp = MTG_Game_Player_Temp.from_player(player = player, guid = guid)
objs_player_temp.append(obj_player_temp)
success = False
errors = []
try:
cls.upload_bulk(
permanent_table_name = MTG_Game_Player_Temp.__tablename__
, records = objs_player_temp
, batch_size = 1000
)
rows = cls.db_procedure_execute('tcg.public.USP_TCG_MTG_Game_Player_Save', argument_dict, argument_type_hints)
row = rows[0] if rows else None
success = row['o_success'] if row else False
Helper_App.console_log(f'Success: {success}')
if not success:
errors = cls.get_many_error(guid = guid)
Helper_App.console_log(f'Errors: {str(errors)}')
except Exception as e:
Helper_App.console_log(f'Error: {str(e)}')
error = SQL_Error()
error.msg = str(e)
errors.append(error)
cls.clear_error(guid = guid)
return success, errors
@classmethod
def save_mtg_game_round_player_damage(cls, rounds, damages):
_m = f'{cls.__qualname__}.save_mtg_game_round_player_damage'
user = cls.get_user_session()
guid = Helper_DB_SQL.create_guid()
success = None
argument_dict = {
'a_comment': 'Save game player'
, 'a_guid': guid
, 'a_user_id': user.user_id
, 'o_success': success
}
argument_type_hints = {
'a_comment': Text
, 'a_guid': Uuid
, 'a_user_id': Integer
, 'o_success': Boolean
}
Helper_App.console_log(f'argument_dict: {argument_dict}')
objs_round_temp = []
for round in rounds:
obj_round_temp = MTG_Game_Round_Temp.from_round(round = round, guid = guid)
objs_round_temp.append(obj_round_temp)
objs_damage_temp = []
for damage in damages:
obj_damage_temp = MTG_Game_Round_Player_Damage_Temp.from_damage(damage = damage, guid = guid)
objs_damage_temp.append(obj_damage_temp)
success = False
errors = []
try:
cls.upload_bulk(
permanent_table_name = MTG_Game_Round_Temp.__tablename__
, records = objs_round_temp
, batch_size = 1000
)
cls.upload_bulk(
permanent_table_name = MTG_Game_Round_Player_Damage_Temp.__tablename__
, records = objs_damage_temp
, batch_size = 1000
)
rows = cls.db_procedure_execute('tcg.public.USP_TCG_MTG_Game_Round_Damage_Save', argument_dict, argument_type_hints)
row = rows[0] if rows else None
success = row['o_success'] if row else False
Helper_App.console_log(f'Success: {success}')
if not success:
errors = cls.get_many_error(guid = guid)
Helper_App.console_log(f'Errors: {str(errors)}')
except Exception as e:
Helper_App.console_log(f'Error: {str(e)}')
error = SQL_Error()
error.msg = str(e)
errors.append(error)
cls.clear_error(guid = guid)
return success, errors

View File

@@ -0,0 +1,107 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: DataStores
Feature: User DataStore
Description:
Datastore for Users
"""
# internal
# from routes import bp_home
import lib.argument_validation as av
from business_objects.sql_error import SQL_Error
from business_objects.tcg.user import User, Parameters_User
from datastores.datastore_base import DataStore_Base
from helpers.helper_app import Helper_App
from helpers.helper_db_sql import Helper_DB_SQL
# from models.model_view_store_checkout import Model_View_Store_Checkout # circular!
from extensions import db
# external
# from abc import ABC, abstractmethod, abstractproperty
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import stripe
import os
from flask import Flask, session, current_app
from pydantic import BaseModel, ConfigDict
from typing import ClassVar
from datetime import datetime
db = SQLAlchemy()
class DataStore_User(DataStore_Base):
def __init__(self):
super().__init__()
@classmethod
def get_many_user(cls, user_filters):
_m = f'{cls.__qualname__}.get_many_user'
Helper_App.console_log(_m)
Helper_App.console_log(f'user_filters: {user_filters}')
av.val_instance(user_filters, 'user_filters', _m, Parameters_User)
argument_dict = user_filters.to_json()
Helper_App.console_log(f'argument_dict: {argument_dict}')
users = []
errors = []
try:
user_result = cls.db_function_execute('tcg.public.FN_TCG_User_Get_Many', argument_dict, Parameters_User.get_type_hints())
user_result_set = user_result.fetchall()
Helper_App.console_log(f'raw users: {user_result_set}')
for row in user_result_set:
Helper_App.console_log(f'row: {row}')
user = User.from_db_user(row)
users.append(user)
Helper_App.console_log(f'user {str(type(user))}: {user}')
except Exception as e:
Helper_App.console_log(f'Error: {str(e)}')
error = SQL_Error()
error.msg = str(e)
errors.append(error)
return users, errors
@classmethod
def login_user(cls, user):
_m = f'{cls}.login_user'
# av.val_str(comment, 'comment', _m)
guid = Helper_DB_SQL.create_guid_str()
Helper_App.console_log(f'login user: {user}')
success = False
user_id = None
try:
argument_dict_list = {
'a_user_auth0_id': user.user_auth0_id
, 'a_email': user.email
, 'a_guid': guid
, 'a_is_email_verified': user.is_email_verified
, 'o_success': success
, 'o_user_id': user_id
}
rows = cls.db_procedure_execute('tcg.public.USP_TCG_User_Login', argument_dict_list)
row = rows[0] if rows else None
success = row['o_success'] if row else False
user_id = row['o_user_id'] if row else None
Helper_App.console_log('User logged in')
Helper_App.console_log(f'Success: {success}\nUser ID: {user_id}')
user.user_id = user_id
user_filters = Parameters_User.from_user(user)
users, errors = cls.get_many_user(user_filters = user_filters)
user = users[0] if len(users) > 0 else None
if user is not None:
user.is_logged_in = True
return user, errors
except Exception as e:
return None, SQL_Error.from_exception(e)

14
extensions.py Normal file
View File

@@ -0,0 +1,14 @@
from flask import Flask, render_template, jsonify, request, render_template_string, send_from_directory, redirect, url_for, session, current_app
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
from flask_mail import Mail, Message
from flask_wtf.csrf import CSRFProtect
from authlib.integrations.flask_client import OAuth
csrf = CSRFProtect()
# cors = CORS()
db = SQLAlchemy()
mail = Mail()
oauth = OAuth()

0
forms/__init__.py Normal file
View File

70
forms/base.py Normal file
View File

@@ -0,0 +1,70 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Backend
Feature: Form Base and Meta Classes - data input
Description:
Defines Flask-WTF base forms for handling user input.
"""
# internal
from helpers.helper_app import Helper_App
# external
from flask_wtf import FlaskForm
from abc import ABCMeta, abstractmethod
from wtforms import SelectField, BooleanField, SubmitField
from wtforms.validators import InputRequired, NumberRange, Regexp, DataRequired, Optional
class Form_Base_Meta(type(FlaskForm), ABCMeta):
pass
class Form_Base(FlaskForm, metaclass=Form_Base_Meta):
@classmethod
@abstractmethod
def from_json(cls, json):
Helper_App.console_log(f'Error: Parent classes of {cls.__qualname__} must define cls.from_json')
@classmethod
def get_default(cls):
return cls()
@classmethod
def get_select_option_blank(cls, is_valid = True):
value = cls.get_select_valid_option_default_value() if is_valid else cls.get_select_invalid_option_default_value()
return (value, 'Select')
@classmethod
def get_select_option_all(cls):
return (cls.get_select_valid_option_default_value(), 'All')
@staticmethod
def get_select_valid_option_default_value():
return '0'
@staticmethod
def get_select_invalid_option_default_value():
return ''
def __repr__(self):
fields = ', '.join(
f"{name}={field.data}" for name, field in self._fields.items()
)
return f"{self.__class__.__name__}({fields})"
'''
class Filters_Stored_Procedure_Base(Form_Base):
"""
@abstractmethod
def __repr__(self):
pass
@classmethod
@abstractmethod
def from_json(cls, json):
pass
"""
@abstractmethod
def to_json(self):
pass
'''

0
forms/tcg/__init__.py Normal file
View File

185
forms/tcg/game.py Normal file
View File

@@ -0,0 +1,185 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Backend
Feature: MTG Game Form
Description:
Defines Flask-WTF form for handling user input on MTG Games page.
"""
# IMPORTS
# internal
from business_objects.base import Base
from business_objects.tcg.mtg_game import MTG_Game, Parameters_MTG_Game
from business_objects.tcg.mtg_game_player import MTG_Game_Player, Parameters_MTG_Game_Player
from business_objects.tcg.mtg_game_round import MTG_Game_Round, Parameters_MTG_Game_Round
from business_objects.tcg.mtg_game_round_player_damage import Parameters_MTG_Game_Round_Player_Damage
from business_objects.tcg.mtg_deck import MTG_Deck, Parameters_MTG_Deck
from helpers.helper_app import Helper_App
from forms.base import Form_Base
import lib.argument_validation as av
# external
from flask import Flask, render_template, request, flash, redirect, url_for, current_app
from flask_wtf import FlaskForm
from wtforms import SelectField, BooleanField, StringField, SubmitField, DateField
from wtforms.validators import DataRequired, Email, ValidationError, Optional
import markupsafe
from abc import ABCMeta, abstractmethod
import json
class Filters_MTG_Game(Form_Base):
search = StringField(
'Search'
)
active_only = BooleanField(
'Active'
, default=True
)
is_commander = BooleanField(
'Commander'
, default=False
)
is_draft = BooleanField(
'Draft'
, default=False
)
is_sealed = BooleanField(
'Sealed'
, default=False
)
date_from = DateField(
'From Date'
, validators=[Optional()]
)
date_to = DateField(
'To Date'
, validators=[Optional()]
)
@classmethod
def from_json(cls, json):
_m = f'{cls.__qualname__}.from_json'
Helper_App.console_log(f'{_m}\njson: {json}')
filters = cls()
filters.search.data = json.get(Base.FLAG_SEARCH, '')
filters.active_only.data = av.input_bool(json.get(Base.FLAG_ACTIVE_ONLY, True), Base.FLAG_ACTIVE_ONLY, _m)
filters.is_commander.data = av.input_bool(json.get(MTG_Game.FLAG_IS_COMMANDER, False), MTG_Game.FLAG_IS_COMMANDER, _m)
filters.is_draft.data = av.input_bool(json.get(MTG_Game.FLAG_IS_DRAFT, False), MTG_Game.FLAG_IS_DRAFT, _m)
filters.is_sealed.data = av.input_bool(json.get(MTG_Game.FLAG_IS_SEALED, False), MTG_Game.FLAG_IS_SEALED, _m)
filters.date_from.data = json.get(Base.FLAG_DATE_FROM, None)
filters.date_to.data = json.get(Base.FLAG_DATE_TO, None)
return filters
def to_json(self):
return {
Base.FLAG_SEARCH: self.search.data
, Base.FLAG_ACTIVE_ONLY: self.active_only.data
, MTG_Game.FLAG_IS_COMMANDER: self.is_commander.data
, MTG_Game.FLAG_IS_DRAFT: self.is_draft.data
, MTG_Game.FLAG_IS_SEALED: self.is_sealed.data
, Base.FLAG_DATE_FROM: self.date_from.data
, Base.FLAG_DATE_TO: self.date_to.data
}
def to_parameters(self, user_id_session):
return Parameters_MTG_Game (
get_all_game = True
, get_inactive_game = not self.active_only.data
, game_ids = ''
, get_all_user = False
, get_inactive_user = False
, user_ids = '' if user_id_session is None else str(user_id_session)
, require_all_id_filters_met = True
, require_any_id_filters_met = True
, require_all_non_id_filters_met = False
, require_any_non_id_filters_met = True
)
class Filters_MTG_Game_Player(Form_Base):
search = StringField(
'Search'
)
active_only = BooleanField(
'Active'
, default=True
)
@classmethod
def from_json(cls, json):
_m = f'{cls.__qualname__}.from_json'
Helper_App.console_log(f'{_m}\njson: {json}')
filters = cls()
filters.search.data = json.get(Base.FLAG_SEARCH, '')
filters.active_only.data = av.input_bool(json.get(Base.FLAG_ACTIVE_ONLY, True), Base.FLAG_ACTIVE_ONLY, _m)
return filters
def to_json(self):
return {
Base.FLAG_SEARCH: self.search.data
, Base.FLAG_ACTIVE_ONLY: self.active_only.data
}
def to_parameters(self, user_id_session, game_id=None):
params = Parameters_MTG_Game_Player(
get_all_player = True
, get_inactive_player = not self.active_only.data
, player_ids = ''
, game_ids = str(game_id) if game_id else ''
, user_ids = str(user_id_session) if user_id_session else ''
, deck_ids = ''
, require_all_id_filters_met = True
, require_any_id_filters_met = True
, require_all_non_id_filters_met = False
, require_any_non_id_filters_met = True
)
return params
class Filters_MTG_Deck(Form_Base):
search = StringField(
'Search'
)
active_only = BooleanField(
'Active'
, default=True
)
is_commander = BooleanField(
'Commander'
, default=False
)
@classmethod
def from_json(cls, json):
_m = f'{cls.__qualname__}.from_json'
Helper_App.console_log(f'{_m}\njson: {json}')
filters = cls()
filters.search.data = json.get(Base.FLAG_SEARCH, '')
filters.active_only.data = av.input_bool(json.get(Base.FLAG_ACTIVE_ONLY, True), Base.FLAG_ACTIVE_ONLY, _m)
filters.is_commander.data = av.input_bool(json.get(MTG_Deck.FLAG_IS_COMMANDER, False), MTG_Deck.FLAG_IS_COMMANDER, _m)
return filters
def to_json(self):
return {
Base.FLAG_SEARCH: self.search.data
, Base.FLAG_ACTIVE_ONLY: self.active_only.data
, MTG_Deck.FLAG_IS_COMMANDER: self.is_commander.data
}
def to_parameters(self, user_id_session):
return Parameters_MTG_Deck(
get_all_deck = True
, get_inactive_deck = not self.active_only.data
, deck_ids = ''
, deck_names = ''
, commander_bracket_ids = ''
, user_ids = str(user_id_session) if user_id_session else ''
, require_all_id_filters_met = True
, require_any_id_filters_met = True
, require_all_non_id_filters_met = False
, require_any_non_id_filters_met = True
)

55
forms/tcg/user.py Normal file
View File

@@ -0,0 +1,55 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Backend
Feature: User Form
Description:
Defines Flask-WTF form for handling user input on User page.
"""
# IMPORTS
# internal
from business_objects.base import Base
# from business_objects.user.user import User # Circular
from helpers.helper_app import Helper_App
# from models.model_view_store import Model_View_Store # circular
# from models.model_view_base import Model_View_Base
from forms.base import Form_Base
import lib.argument_validation as av
# external
from flask import Flask, render_template, request, flash, redirect, url_for, current_app
from flask_wtf import FlaskForm
from wtforms import SelectField, BooleanField, StringField, SubmitField
from wtforms.validators import DataRequired, Email, ValidationError
import markupsafe
from flask_wtf.recaptcha import RecaptchaField
from abc import ABCMeta, abstractmethod
import json
class Filters_User(Form_Base):
search = StringField(
'Search'
)
active_only = BooleanField(
'Active'
, default = True
)
@classmethod
def from_json(cls, json):
_m = f'{cls.__qualname__}.from_json'
Helper_App.console_log(f'{_m}\njson: {json}')
filters = cls()
filters.search.data = json[Base.FLAG_SEARCH]
filters.active_only.data = av.input_bool(json[Base.FLAG_ACTIVE_ONLY], Base.FLAG_ACTIVE_ONLY, f'{cls.__name__}.from_json')
return filters
def to_json(self):
return {
Base.FLAG_SEARCH: self.search.data
, Base.FLAG_ACTIVE_ONLY: self.active_only.data
}

11
helpers/__init__.py Normal file
View File

@@ -0,0 +1,11 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Module Initialisation
Feature: Helpers
Description:
Initialises helpers module.
"""

43
helpers/helper_app.py Normal file
View File

@@ -0,0 +1,43 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Helpers
Feature: App Helper
"""
# internal
# external
from pydantic import BaseModel, ConfigDict
from flask import current_app
# from flask_sqlalchemy import SQLAlchemy
class Helper_App(BaseModel):
@staticmethod
def get_request_data(request):
Helper_App.console_log(f'request={request}')
data = {}
try:
data = request.json
except:
try:
data = request.data
except:
try:
data = request.form
except:
pass
Helper_App.console_log(f'data={data}')
return data
@staticmethod
def console_log(message):
if current_app.app_config.is_development:
print(message)
elif current_app.app_config.is_production:
pass
current_app.logger.info(message)

42
helpers/helper_db_sql.py Normal file
View File

@@ -0,0 +1,42 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Helpers
Feature: MySQL Database Helper
Notes: This architecture does not work with Flask-SQLAlchemy - db connection must be initialised with Flask app initialisation
"""
# internal
# external
from pydantic import BaseModel, ConfigDict
from flask import Flask, render_template, jsonify, request, render_template_string, send_from_directory, redirect, url_for, session
from flask_sqlalchemy import SQLAlchemy
import uuid
class Helper_DB_SQL(BaseModel):
app: Flask
model_config = ConfigDict(arbitrary_types_allowed=True)
def __init__(self, app):
super().__init__(app=app)
# self.app = app
def get_db_connection(self):
db = SQLAlchemy()
db.init_app(self.app)
with self.app.app_context():
db.create_all()
db.engine.url = self.app.config['SQLALCHEMY_DATABASE_URI']
return db
@staticmethod
def create_guid_str():
return str(uuid.uuid4())
@staticmethod
def create_guid():
return str(uuid.uuid4())

11
lib/__init__.py Normal file
View File

@@ -0,0 +1,11 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Module Initialisation
Feature: Library
Description:
Initialises library module.
"""

1301
lib/argument_validation.py Normal file

File diff suppressed because it is too large Load Diff

37
lib/data_types.py Normal file
View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
"""
Created on Thu Apr 27 12:33:59 2023
@author: Edward Middleton-Smith
Argument Validation
"""
# CLASSES
# ATTRIBUTE DECLARATION
# METHODS
# FUNCTION
# ARGUMENTS
# ARGUMENT VALIDATION
# ATTRIBUTE + VARIABLE INSTANTIATION
# METHODS
# RETURNS
# NORMAL METHODS
# FUNCTION
# ARGUMENTS
# ARGUMENT VALIDATION
# VARIABLE INSTANTIATION
# METHODS
# RETURNS
# IMPORTS
# CLASSES
# METHODS
def get_enum_member_by_text(enum_class, text):
for member in enum_class.__members__.values():
if member.name == text:
return member
raise ValueError(f'{text} is not in {enum_class}')

11
models/__init__.py Normal file
View File

@@ -0,0 +1,11 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Module Initialisation
Feature: Models
Description:
Initialises view data models module.
"""

View File

@@ -0,0 +1,22 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Legal View Models
Feature: Accessibility Report View Model
Description:
Data model for accessibility report view
"""
# internal
from models.model_view_base import Model_View_Base
# from routes import bp_home
# external
class Model_View_Accessibility_Report(Model_View_Base):
def __init__(self, hash_page_current=Model_View_Base.HASH_PAGE_ACCESSIBILITY_REPORT):
super().__init__(hash_page_current=hash_page_current)
self._title = 'Accessibility Report'

View File

@@ -0,0 +1,22 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Legal View Models
Feature: Accessibility Statement View Model
Description:
Data model for accessibility statement view
"""
# internal
from models.model_view_base import Model_View_Base
# from routes import bp_home
# external
class Model_View_Accessibility_Statement(Model_View_Base):
def __init__(self, hash_page_current=Model_View_Base.HASH_PAGE_ACCESSIBILITY_STATEMENT):
super().__init__(hash_page_current=hash_page_current)
self._title = 'Accessibility Statement'

346
models/model_view_base.py Normal file
View File

@@ -0,0 +1,346 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: Base View Model
Description:
Base data model for views
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
# internal
# from routes import bp_home
from business_objects.base import Base
from business_objects.tcg.user import User
from datastores.datastore_base import DataStore_Base
from datastores.datastore_user import DataStore_User
from helpers.helper_app import Helper_App
import lib.argument_validation as av
# external
from abc import ABC, abstractmethod
from flask_sqlalchemy import SQLAlchemy
from flask import Flask, session, current_app, jsonify
from pydantic import BaseModel, ConfigDict
from typing import ClassVar
class Model_View_Base(BaseModel, ABC):
ATTR_DATA_AMOUNT: ClassVar[str] = 'data-amount'
ATTR_TEXT_COLLAPSED: ClassVar[str] = 'textCollapsed'
ATTR_TEXT_EXPANDED: ClassVar[str] = 'textExpanded'
ATTR_USER_ID: ClassVar[str] = Base.ATTR_USER_ID
ATTR_VALUE_CURRENT: ClassVar[str] = 'current-value'
ATTR_VALUE_PREVIOUS: ClassVar[str] = 'previous-value'
COLOUR_ACCENT: ClassVar[str] = '#C77DFF'
COLOUR_ERROR: ClassVar[str] = 'red'
COLOUR_PAGE_BACKGROUND: ClassVar[str] = '#E0AAFF'
COLOUR_PAGE_BACKGROUND_1: ClassVar[str] = '#F5ECFE'
COLOUR_PAGE_BACKGROUND_2: ClassVar[str] = '#FAE0E2'
COLOUR_PRIMARY: ClassVar[str] = '#240046'
COLOUR_SECONDARY: ClassVar[str] = '#3C096C'
COLOUR_TEXT: ClassVar[str] = '#10002B'
COLOUR_TEXT_BACKGROUND: ClassVar[str] = 'white'
COLOUR_TEXT_LINK_UNVISITED: ClassVar[str] = '#0000EE'
COLOUR_TEXT_LINK_VISITED: ClassVar[str] = '#551A8B'
COMPANY_ADDRESS_SHORT: ClassVar[str] = 'Russet, Sawbridge Road, Grandborough, United Kingdom, CV23 8DN'
COMPANY_NUMBER: ClassVar[str] = '13587499'
ENDPOINT_GET_ALTCHA_CHALLENGE: ClassVar[str] = 'routes_core_contact.create_altcha_challenge'
ENDPOINT_PAGE_ACCESSIBILITY_REPORT: ClassVar[str] = 'routes_legal.accessibility_report'
ENDPOINT_PAGE_ACCESSIBILITY_STATEMENT: ClassVar[str] = 'routes_legal.accessibility_statement'
ENDPOINT_PAGE_DATA_RETENTION_SCHEDULE: ClassVar[str] = 'routes_legal.retention_schedule'
ENDPOINT_PAGE_ERROR_NO_PERMISSION: ClassVar[str] = 'routes_core.error_no_permission'
ENDPOINT_PAGE_HOME: ClassVar[str] = 'routes_mtg_game.home'
ENDPOINT_PAGE_LICENSE: ClassVar[str] = 'routes_legal.license'
ENDPOINT_PAGE_PRIVACY_POLICY: ClassVar[str] = 'routes_legal.privacy_policy'
ENDPOINT_POST_COMMAND: ClassVar[str] = 'routes_core_contact.contact_post'
FLAG_ACTIVE: ClassVar[str] = Base.FLAG_ACTIVE
FLAG_ACTIVE_ONLY: ClassVar[str] = Base.FLAG_ACTIVE_ONLY
FLAG_ADD: ClassVar[str] = 'add'
# FLAG_ADD_DELETE: ClassVar[str] = 'add-delete'
FLAG_BENEFITS: ClassVar[str] = 'benefits'
FLAG_BOOL_FALSE: ClassVar[str] = 'false'
FLAG_BOOL_TRUE: ClassVar[str] = 'true'
# FLAG_BRIBE: ClassVar[str] = Bribe.FLAG_BRIBE
FLAG_BUTTON: ClassVar[str] = 'button'
FLAG_BUTTON_LIGHT: ClassVar[str] = 'button-light'
FLAG_BUTTON_PRIMARY: ClassVar[str] = 'button-primary'
FLAG_BUTTON_SUCCESS: ClassVar[str] = 'button-success'
FLAG_CANCEL: ClassVar[str] = 'button-cancel'
FLAG_CALLBACK: ClassVar[str] = 'callback'
FLAG_CAPTCHA: ClassVar[str] = 'captcha'
FLAG_CARD: ClassVar[str] = 'card'
FLAG_CHECKBOX: ClassVar[str] = 'checkbox'
FLAG_CLOSE_TEMPORARY_ELEMENT: ClassVar[str] = 'button-temporary-element-close'
FLAG_CODE: ClassVar[str] = Base.FLAG_CODE
FLAG_COLLAPSIBLE: ClassVar[str] = 'collapsible'
FLAG_COLUMN: ClassVar[str] = 'column'
FLAG_COMMENT: ClassVar[str] = 'comment'
FLAG_CONTAINER: ClassVar[str] = 'container'
FLAG_CONTAINER_ICON_AND_LABEL: ClassVar[str] = 'container-icon-label'
FLAG_CONTAINER_INPUT: ClassVar[str] = 'container-input'
# FLAG_CONTAINER_SAVE_CANCEL_BUTTONS: ClassVar[str] = 'container-save-cancel-buttons'
FLAG_CSRF_TOKEN: ClassVar[str] = 'X-CSRFToken'
FLAG_CTA_1: ClassVar[str] = 'cta-1'
FLAG_CTA_2: ClassVar[str] = 'cta-2'
FLAG_DATA: ClassVar[str] = 'data'
FLAG_DATE_FROM: ClassVar[str] = Base.FLAG_DATE_FROM
FLAG_DATE_TO: ClassVar[str] = Base.FLAG_DATE_TO
FLAG_DDL_PREVIEW: ClassVar[str] = "ddl-preview"
FLAG_DELETE: ClassVar[str] = 'delete'
FLAG_DESCRIPTION: ClassVar[str] = Base.FLAG_DESCRIPTION
FLAG_DETAIL: ClassVar[str] = 'detail'
FLAG_DIALOG: ClassVar[str] = 'dialog'
FLAG_DIRTY: ClassVar[str] = 'dirty'
FLAG_DISPLAY_ORDER: ClassVar[str] = Base.FLAG_DISPLAY_ORDER
FLAG_EDIT: ClassVar[str] = 'edit'
FLAG_EMAIL: ClassVar[str] = Base.FLAG_EMAIL
FLAG_END_ON: ClassVar[str] = Base.FLAG_END_ON
FLAG_ERROR: ClassVar[str] = 'error'
FLAG_EXPANDED: ClassVar[str] = 'expanded'
FLAG_FAILURE: ClassVar[str] = 'failure'
FLAG_FAQ: ClassVar[str] = 'faq'
FLAG_FEATURES: ClassVar[str] = 'features'
FLAG_FILTER: ClassVar[str] = 'filter'
FLAG_FIRSTNAME: ClassVar[str] = Base.FLAG_FIRSTNAME
FLAG_FORM: ClassVar[str] = 'form'
FLAG_FORM_FILTERS: ClassVar[str] = 'form-filters'
FLAG_HAMBURGER: ClassVar[str] = 'hamburger'
FLAG_ICON: ClassVar[str] = "icon"
FLAG_IMAGE_LOGO: ClassVar[str] = 'image-logo'
FLAG_INITIALISED: ClassVar[str] = 'initialised'
FLAG_INPUT_ANSWER: ClassVar[str] = 'input-answer'
FLAG_IS_CHECKED: ClassVar[str] = 'is_checked'
FLAG_IS_COLLAPSED: ClassVar[str] = 'is_collapsed'
FLAG_LABEL_QUESTION: ClassVar[str] = 'label-question'
FLAG_LEFT_HAND_STUB: ClassVar[str] = 'lhs'
FLAG_LOGO: ClassVar[str] = 'logo'
FLAG_MESSAGE: ClassVar[str] = 'message'
FLAG_MODAL: ClassVar[str] = 'modal'
FLAG_NAME: ClassVar[str] = Base.FLAG_NAME
FLAG_NAME_ATTR_OPTION_TEXT: ClassVar[str] = Base.FLAG_NAME_ATTR_OPTION_TEXT
FLAG_NAME_ATTR_OPTION_VALUE: ClassVar[str] = Base.FLAG_NAME_ATTR_OPTION_VALUE
FLAG_NAME_PLURAL: ClassVar[str] = Base.FLAG_NAME_PLURAL
# FLAG_NAME_SINGULAR: ClassVar[str] = Base.FLAG_NAME_SINGULAR
FLAG_NAV_ADMIN_HOME: ClassVar[str] = 'navAdminHome'
FLAG_NAV_MTG_DECKS: ClassVar[str] = 'navMtgDecks'
FLAG_NAV_MTG_GAME: ClassVar[str] = 'navMtgGame'
FLAG_NAV_MTG_GAMES: ClassVar[str] = 'navMtgGames'
FLAG_NAV_MTG_HOME: ClassVar[str] = 'navMtgHome'
FLAG_NAV_MTG_TRIAL_GAME: ClassVar[str] = 'navMtgTrialGame'
FLAG_NAV_HOME: ClassVar[str] = 'navHome'
FLAG_NAV_USER_ACCOUNT: ClassVar[str] = 'navUserAccount'
FLAG_NAV_USER_ACCOUNT: ClassVar[str] = 'navUserAccounts'
FLAG_NAV_USER_LOGIN: ClassVar[str] = 'navUserLogin'
FLAG_NAV_USER_LOGOUT: ClassVar[str] = 'navUserLogout'
FLAG_NOTES: ClassVar[str] = "notes"
FLAG_OVERLAY: ClassVar[str] = 'overlay'
FLAG_PAGE_BODY: ClassVar[str] = 'page-body'
FLAG_PRICE: ClassVar[str] = Base.FLAG_PRICE
FLAG_PRICING: ClassVar[str] = 'pricing'
FLAG_QUANTITY: ClassVar[str] = 'quantity'
FLAG_RIGHT_HAND_SIDE: ClassVar[str] = 'rhs'
FLAG_ROW: ClassVar[str] = 'row'
FLAG_ROW_NEW: ClassVar[str] = 'row-new'
FLAG_ROWS: ClassVar[str] = Base.FLAG_ROWS
FLAG_SAVE: ClassVar[str] = 'save'
FLAG_SCROLLABLE: ClassVar[str] = 'scrollable'
FLAG_SEARCH: ClassVar[str] = Base.FLAG_SEARCH
FLAG_SLIDER: ClassVar[str] = 'slider'
FLAG_START_ON: ClassVar[str] = Base.FLAG_START_ON
FLAG_STATUS: ClassVar[str] = 'status'
FLAG_SUBMIT: ClassVar[str] = 'submit'
FLAG_SUCCESS: ClassVar[str] = 'success'
FLAG_SURNAME: ClassVar[str] = Base.FLAG_SURNAME
FLAG_TABLE_MAIN: ClassVar[str] = 'table-main'
FLAG_TEMPORARY_ELEMENT: ClassVar[str] = 'temporary-element'
FLAG_TESTIMONIAL: ClassVar[str] = 'testimonial'
FLAG_USER: ClassVar[str] = User.FLAG_USER
# FLAG_VALUE_PROPOSITION: ClassVar[str] = 'value-proposition'
FLAG_WEBSITE: ClassVar[str] = Base.FLAG_WEBSITE
HASH_GET_ALTCHA_CHALLENGE: ClassVar[str] = '/altcha/create-challenge'
HASH_PAGE_ACCESSIBILITY_REPORT: ClassVar[str] = '/accessibility-report'
HASH_PAGE_ACCESSIBILITY_STATEMENT: ClassVar[str] = '/accessibility-statement'
HASH_PAGE_DATA_RETENTION_SCHEDULE: ClassVar[str] = '/retention-schedule'
HASH_PAGE_MTG_DECKS: ClassVar[str] = '/decks'
HASH_PAGE_MTG_GAME: ClassVar[str] = '/game'
HASH_PAGE_MTG_GAMES: ClassVar[str] = '/' # '/games'
HASH_PAGE_MTG_HOME: ClassVar[str] = '/home'
HASH_PAGE_MTG_TRIAL_GAME: ClassVar[str] = '/trial-game'
HASH_PAGE_ERROR_NO_PERMISSION: ClassVar[str] = '/error'
HASH_PAGE_LICENSE: ClassVar[str] = '/license'
HASH_PAGE_PRIVACY_POLICY: ClassVar[str] = '/privacy-policy'
HASH_PAGE_USER_ACCOUNT: ClassVar[str] = '/user/user'
HASH_PAGE_USER_ACCOUNTS: ClassVar[str] = '/user/users'
HASH_PAGE_USER_LOGIN: ClassVar[str] = '/login'
HASH_PAGE_USER_LOGOUT: ClassVar[str] = '/logout'
HASH_SAVE_MTG_GAME: ClassVar[str] = '/mtg/save-game'
HASH_SAVE_MTG_GAME_PLAYER: ClassVar[str] = '/mtg/save-game-player'
HASH_SAVE_MTG_GAME_ROUND: ClassVar[str] = '/mtg/save-game-round'
HASH_SAVE_MTG_GAME_ROUND_PLAYER_DAMAGE: ClassVar[str] = '/mtg/save-game-round-player-damage'
HASH_SAVE_MTG_DECK: ClassVar[str] = '/mtg/save-deck'
HASH_SAVE_USER_USER: ClassVar[str] = '/user/save-user'
ID_BUTTON_ADD: ClassVar[str] = 'buttonAdd'
ID_BUTTON_APPLY_FILTERS: ClassVar[str] = 'buttonApplyFilters'
ID_BUTTON_CANCEL: ClassVar[str] = 'buttonCancel'
ID_BUTTON_HAMBURGER: ClassVar[str] = 'buttonHamburger'
ID_BUTTON_SAVE: ClassVar[str] = 'buttonSave'
ID_CONTAINER_TEMPLATE_ELEMENTS: ClassVar[str] = 'container-template-elements'
ID_CSRF_TOKEN: ClassVar[str] = 'X-CSRFToken'
ID_FORM_CONTACT: ClassVar[str] = 'formContact'
ID_FORM_FILTERS: ClassVar[str] = 'formFilters'
ID_LABEL_ERROR: ClassVar[str] = 'labelError'
ID_OVERLAY_CONFIRM: ClassVar[str] = 'overlayConfirm'
ID_OVERLAY_ERROR: ClassVar[str] = 'overlayError'
ID_OVERLAY_HAMBURGER: ClassVar[str] = 'overlayHamburger'
ID_PAGE_BODY: ClassVar[str] = 'pageBody'
ID_TABLE_MAIN: ClassVar[str] = 'tableMain'
ID_TEXTAREA_CONFIRM: ClassVar[str] = 'textareaConfirm'
NAME_COMPANY: ClassVar[str] = 'Shuffle & Skirmish'
NAME_COMPANY_SHORT: ClassVar[str] = 'Shuffle & Skirmish'
NAME_CSRF_TOKEN: ClassVar[str] = 'csrf-token'
USERNAME_DISCORD: ClassVar[str] = 'Fetch Metrics'
USERNAME_FACEBOOK: ClassVar[str] = 'Fetch Metrics'
USERNAME_GITHUB: ClassVar[str] = 'Teddy-1024'
USERNAME_INSTAGRAM: ClassVar[str] = 'fetchmetrics'
USERNAME_LINKEDIN: ClassVar[str] = 'fetchmetrics'
USERNAME_REDDIT: ClassVar[str] = 'Fetch-Metrics'
USERNAME_TIKTOK: ClassVar[str] = 'fetchmetrics'
USERNAME_TWITTER: ClassVar[str] = 'FetchMetrics'
URL_DISCORD: ClassVar[str] = f'https://discord.gg/HBSvutXSZf'
URL_FACEBOOK: ClassVar[str] = 'https://www.facebook.com/profile.php?id=61579039227559'
URL_GITHUB: ClassVar[str] = f'https://github.com/{USERNAME_GITHUB}'
URL_INSTAGRAM: ClassVar[str] = f'https://www.instagram.com/{USERNAME_INSTAGRAM}/'
URL_LINKEDIN: ClassVar[str] = f'https://www.linkedin.com/company/{USERNAME_LINKEDIN}'
URL_LINKEDIN_PERSONAL: ClassVar[str] = f'https://www.linkedin.com/in/teddyms'
URL_REDDIT: ClassVar[str] = f'https://www.reddit.com/u/{USERNAME_REDDIT}/s/gZKEz2ZwHN'
URL_TIKTOK: ClassVar[str] = f'https://www.tiktok.com/@{USERNAME_TIKTOK}'
URL_TWITTER: ClassVar[str] = f'https://x.com/{USERNAME_TWITTER}'
_title: str
hash_page_current: str
app: Flask = None
session: None = None
is_user_logged_in: bool = None
user: User = None
access_levels: list = None
model_config = ConfigDict(arbitrary_types_allowed=True)
@property
# @abstractmethod
def title(self):
if self._title is None:
raise NotImplementedError("Model Title required.")
return self._title
def __init__(self, hash_page_current, **kwargs):
BaseModel.__init__(self, hash_page_current=hash_page_current, **kwargs)
self.app = current_app
with self.app.app_context():
self.session = session
Helper_App.console_log(f'session: {self.session}')
datastore_base = DataStore_Base()
self.user = datastore_base.get_user_session()
self.is_user_logged_in = self.user.get_is_logged_in()
# Helper_App.console_log(f'model_view_base init end - model.user: {self.user}')
def get_url_host(self):
return self.app.config['URL_HOST']
@staticmethod
def output_bool(boolean):
return str(boolean).lower()
@staticmethod
def get_user_session():
datastore_user = DataStore_User()
return datastore_user.get_user_session()
@staticmethod
def get_many_access_level(filters=None):
_m = 'Model_View_Store.get_many_access_level'
# av.val_instance(filters, 'filters', _m, Filters_Access_Level)
access_levels, errors = DataStore_Base.get_many_access_level(filters)
return access_levels
@staticmethod
def get_many_unit_measurement(filters=None):
_m = 'Model_View_Store.get_many_unit_measurement'
# av.val_instance(filters, 'filters', _m, Filters_Unit_Measurement)
units_measurement, errors = DataStore_Base.get_many_unit_measurement(filters)
return units_measurement
@staticmethod
def convert_list_objects_to_json(list_objects):
return [obj.to_json() for obj in list_objects]
@staticmethod
def convert_list_objects_to_list_options(list_objects):
return Base.convert_list_objects_to_list_options(list_objects)
@staticmethod
def convert_list_objects_to_dict_by_attribute_key(list_objects, key):
return {getattr(obj, key): obj for obj in list_objects}
@staticmethod
def convert_list_objects_to_dict_json_by_attribute_key(list_objects, key):
return {getattr(obj, key): obj.to_json() for obj in list_objects}
@staticmethod
def convert_list_objects_to_dict_by_attribute_key_default(list_objects):
if list_objects is None or len(list_objects) == 0:
return {}
obj_class = list_objects[0].__class__
return Model_View_Base.convert_list_objects_to_dict_by_attribute_key(list_objects, getattr(obj_class, obj_class.FLAG_NAME_ATTR_OPTION_VALUE))
@staticmethod
def convert_list_objects_to_dict_json_by_attribute_key_default(list_objects):
if list_objects is None or len(list_objects) == 0:
return {}
obj_class = list_objects[0].__class__
return Model_View_Base.convert_list_objects_to_dict_json_by_attribute_key(list_objects, getattr(obj_class, obj_class.FLAG_NAME_ATTR_OPTION_VALUE))
@staticmethod
def convert_dict_values_to_json(dict):
return {key: dict[key].to_json() for key in dict.keys()}
@staticmethod
def convert_list_objects_to_preview_str(list_objects):
preview_str = ''
for obj in list_objects:
if preview_str != '':
preview_str += '\n'
obj_json = obj.to_json()
preview_str += obj_json[obj_json[Base.FLAG_NAME_ATTR_OPTION_TEXT]]
return preview_str
@staticmethod
def join_with_linebreaks(strs):
str_multiline = ''
for str in strs:
if str_multiline != '':
str_multiline += '\n'
str_multiline += str
return str_multiline
@staticmethod
def format_date(date):
if date is None:
return ''
return date.strftime('%Y-%m-%d')
@staticmethod
def format_datetime(date_time):
if date_time is None:
return ''
return date_time.strftime('%Y-%m-%dT%H:%M')
@staticmethod
def format_datetime_text(date_time):
if date_time is None:
return ''
return date_time.strftime('%d/%m/%Y %H:%M')
@staticmethod
def jsonify(data):
return jsonify(data)
def get_mail_contact_public(self):
return self.app.config['MAIL_CONTACT_PUBLIC']
@staticmethod
def format_null_string_as_blank(string):
return '' if string is None else string

View File

@@ -0,0 +1,22 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Legal View Models
Feature: License View Model
Description:
Data model for license view
"""
# internal
from models.model_view_base import Model_View_Base
# from routes import bp_home
# external
class Model_View_License(Model_View_Base):
def __init__(self, hash_page_current=Model_View_Base.HASH_PAGE_LICENSE):
super().__init__(hash_page_current=hash_page_current)
self._title = 'License'

View File

@@ -0,0 +1,66 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: MTG Parent View Model
Description:
Parent data model for MTG views
"""
# internal
from business_objects.tcg.mtg_game import MTG_Game, Parameters_MTG_Game
from business_objects.tcg.mtg_game_player import MTG_Game_Player, Parameters_MTG_Game_Player
from business_objects.tcg.mtg_game_round import MTG_Game_Round, Parameters_MTG_Game_Round
from business_objects.tcg.mtg_game_round_player_damage import MTG_Game_Round_Player_Damage, Parameters_MTG_Game_Round_Player_Damage
from business_objects.tcg.mtg_deck import MTG_Deck, Parameters_MTG_Deck
from helpers.helper_app import Helper_App
import lib.argument_validation as av
from models.model_view_base import Model_View_Base
# external
from flask import send_file, jsonify
from flask_sqlalchemy import SQLAlchemy
import locale
from typing import ClassVar
from abc import abstractmethod
class Model_View_MTG_Base(Model_View_Base):
ATTR_GAME_ID: ClassVar[str] = MTG_Game.ATTR_GAME_ID
ATTR_PLAYER_ID: ClassVar[str] = MTG_Game_Player.ATTR_PLAYER_ID
ATTR_ROUND_ID: ClassVar[str] = MTG_Game_Round.ATTR_ROUND_ID
ATTR_DAMAGE_ID: ClassVar[str] = MTG_Game_Round_Player_Damage.ATTR_DAMAGE_ID
ATTR_DECK_ID: ClassVar[str] = MTG_Deck.ATTR_DECK_ID
FLAG_COMMANDER_DEATHS: ClassVar[str] = MTG_Game_Round_Player_Damage.FLAG_COMMANDER_DEATHS
FLAG_DAMAGE: ClassVar[str] = MTG_Game_Round_Player_Damage.FLAG_DAMAGE
FLAG_DECK: ClassVar[str] = MTG_Deck.FLAG_DECK
FLAG_GAME: ClassVar[str] = MTG_Game.FLAG_GAME
FLAG_HEALTH_CHANGE: ClassVar[str] = MTG_Game_Round_Player_Damage.FLAG_HEALTH_CHANGE
FLAG_IS_COMMANDER: ClassVar[str] = MTG_Game.FLAG_IS_COMMANDER
FLAG_IS_DRAFT: ClassVar[str] = MTG_Game.FLAG_IS_DRAFT
FLAG_IS_SEALED: ClassVar[str] = MTG_Game.FLAG_IS_SEALED
FLAG_LOCATION_NAME: ClassVar[str] = MTG_Game.FLAG_LOCATION_NAME
FLAG_PLAYER: ClassVar[str] = MTG_Game_Player.FLAG_PLAYER
FLAG_ROUND: ClassVar[str] = MTG_Game_Round.FLAG_ROUND
FLAG_STARTING_LIFE: ClassVar[str] = MTG_Game.FLAG_STARTING_LIFE
HASH_GET_MTG_GAME_PLAYERS: ClassVar[str] = '/mtg/api/game/<game_id>/players'
HASH_GET_MTG_GAME_ROUNDS: ClassVar[str] = '/mtg/api/game/<game_id>/rounds'
HASH_GET_MTG_GAME_DAMAGE_RECORDS: ClassVar[str] = '/mtg/api/game/<game_id>/damage-records'
HASH_SAVE_MTG_GAME: ClassVar[str] = '/mtg/save-game'
HASH_SAVE_MTG_GAME_PLAYER: ClassVar[str] = '/mtg/save-game-player'
HASH_SAVE_MTG_GAME_ROUND: ClassVar[str] = '/mtg/save-game-round'
HASH_SAVE_MTG_GAME_ROUND_PLAYER_DAMAGE: ClassVar[str] = '/mtg/save-game-round-player-damage'
HASH_SAVE_MTG_DECK: ClassVar[str] = '/mtg/save-deck'
is_page_mtg: bool = True
def __init__(self, hash_page_current, **kwargs):
_m = 'Model_View_MTG_Base.__init__'
Helper_App.console_log(f'{_m}\nstarting')
super().__init__(hash_page_current=hash_page_current, **kwargs)
self.is_page_mtg = True
if self.hash_page_current == Model_View_MTG_Base.HASH_PAGE_MTG_TRIAL_GAME:
self._title = 'MTG Home'

View File

@@ -0,0 +1,98 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: MTG Games View Model
Description:
Data model for MTG games view
"""
# internal
from business_objects.tcg.mtg_deck import MTG_Deck, Parameters_MTG_Deck
from business_objects.tcg.mtg_game import MTG_Game, Parameters_MTG_Game
from business_objects.tcg.mtg_game_player import MTG_Game_Player, Parameters_MTG_Game_Player
from business_objects.tcg.mtg_game_round import MTG_Game_Round, Parameters_MTG_Game_Round
from business_objects.tcg.mtg_game_round_player_damage import MTG_Game_Round_Player_Damage, Parameters_MTG_Game_Round_Player_Damage
from business_objects.tcg.user import User, Parameters_User
from datastores.datastore_mtg import DataStore_MTG
from datastores.datastore_user import DataStore_User
from models.model_view_mtg_base import Model_View_MTG_Base
from helpers.helper_app import Helper_App
import lib.argument_validation as av
# external
from pydantic import BaseModel
from typing import ClassVar
class Model_View_MTG_Game(Model_View_MTG_Base):
ATTR_RECEIVED_FROM_COMMANDER_PLAYER_ID: ClassVar[str] = MTG_Game_Round_Player_Damage.ATTR_RECEIVED_FROM_COMMANDER_PLAYER_ID
FLAG_IS_ELIMINATED: ClassVar[str] = MTG_Game_Round_Player_Damage.FLAG_IS_ELIMINATED
PLAYER_SETUP_WRAPPER_TEMPLATE_ID: ClassVar[str] = 'player-setup-wrapper-template'
damage_records: list = None
decks: list = None
game: MTG_Game = None
players: list = None
rounds: list = None
users: list = None
"""
parameters_player: Parameters_MTG_Game_Player = None
parameters_round: Parameters_MTG_Game_Round = None
parameters_damage: Parameters_MTG_Game_Round_Player_Damage = None
parameters_deck: Parameters_MTG_Deck = None
"""
def __init__(self, game_id, hash_page_current=Model_View_MTG_Base.HASH_PAGE_MTG_GAME):
_m = 'Model_View_MTG_Game.__init__'
Helper_App.console_log(f'{_m}\nstarting...')
super().__init__(hash_page_current=hash_page_current)
self._title = 'MTG Game'
datastore = DataStore_MTG()
user_session = datastore.get_user_session()
# Get the specific game
parameters_game = Parameters_MTG_Game.get_default(user_session.user_id)
parameters_game.game_ids = str(game_id)
parameters_game.get_all_game = False
games, errors = datastore.get_many_mtg_game(parameters_game)
if len(games) > 0:
self.game = games[0]
# Get all decks
parameters_deck = Parameters_MTG_Deck.get_default()
parameters_deck.get_all_deck = True
parameters_deck.require_all_id_filters_met = False
parameters_deck.require_any_id_filters_met = False
parameters_deck.require_all_non_id_filters_met = False
parameters_deck.require_any_non_id_filters_met = False
self.decks, errors = datastore.get_many_mtg_deck(parameters_deck)
# Get all users
parameters_user = Parameters_User.get_default()
parameters_user.get_all_user = True
parameters_user.require_all_id_filters_met = False
parameters_user.require_any_id_filters_met = False
self.users, errors = DataStore_User.get_many_user(parameters_user)
# Get players for this game
parameters_player = Parameters_MTG_Game_Player.get_default(user_session.user_id)
parameters_player.get_all_game = False
parameters_player.game_ids = str(game_id)
self.players, errors = datastore.get_many_mtg_game_player(parameters_player)
# Get rounds for this game
parameters_round = Parameters_MTG_Game_Round.get_default(user_session.user_id)
parameters_round.get_all_game = False
parameters_round.game_ids = str(game_id)
self.rounds, errors = datastore.get_many_mtg_game_round(parameters_round)
# Get damage for this game
parameters_damage = Parameters_MTG_Game_Round_Player_Damage.get_default(user_session.user_id)
parameters_damage.get_all_game = False
parameters_damage.game_ids = str(game_id)
self.damage_records, errors = datastore.get_many_mtg_game_round_player_damage(parameters_damage)

View File

@@ -0,0 +1,45 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: MTG Games View Model
Description:
Data model for MTG games view
"""
# internal
from business_objects.tcg.mtg_game import MTG_Game, Parameters_MTG_Game
from datastores.datastore_mtg import DataStore_MTG
from models.model_view_mtg_base import Model_View_MTG_Base
from helpers.helper_app import Helper_App
import lib.argument_validation as av
# external
from pydantic import BaseModel
from typing import ClassVar
class Model_View_MTG_Games(Model_View_MTG_Base):
form_filters: object = None
games: list = None
parameters_game: Parameters_MTG_Game = None
def __init__(self, parameters_game=None, hash_page_current=Model_View_MTG_Base.HASH_PAGE_MTG_GAMES):
_m = 'Model_View_MTG_Games.__init__'
Helper_App.console_log(f'{_m}\nstarting...')
super().__init__(hash_page_current=hash_page_current)
self._title = 'MTG Games'
datastore = DataStore_MTG()
# Initialize parameters with defaults if not provided
if parameters_game is None:
user_session = datastore.get_user_session()
self.parameters_game = Parameters_MTG_Game.get_default(user_session.user_id)
else:
self.parameters_game = parameters_game
Helper_App.console_log(f'Query args: {self.parameters_game}')
self.games, errors = datastore.get_many_mtg_game(self.parameters_game)

View File

@@ -0,0 +1,32 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: MTG Games View Model
Description:
Data model for MTG games view
"""
# internal
from business_objects.tcg.user import User, Parameters_User
from datastores.datastore_mtg import DataStore_MTG
from datastores.datastore_user import DataStore_User
from models.model_view_mtg_base import Model_View_MTG_Base
from helpers.helper_app import Helper_App
import lib.argument_validation as av
# external
from pydantic import BaseModel
from typing import ClassVar
class Model_View_MTG_Home(Model_View_MTG_Base):
def __init__(self, hash_page_current=Model_View_MTG_Base.HASH_PAGE_MTG_HOME):
_m = 'Model_View_MTG_Home.__init__'
Helper_App.console_log(f'{_m}\nstarting...')
super().__init__(hash_page_current=hash_page_current)
self._title = 'MTG Home'

View File

@@ -0,0 +1,22 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Legal View Models
Feature: Privacy Policy View Model
Description:
Data model for privacy policy view
"""
# internal
from models.model_view_base import Model_View_Base
# from routes import bp_home
# external
class Model_View_Privacy_Policy(Model_View_Base):
def __init__(self, hash_page_current=Model_View_Base.HASH_PAGE_PRIVACY_POLICY):
super().__init__(hash_page_current=hash_page_current)
self._title = 'Privacy Policy'

View File

@@ -0,0 +1,22 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Legal View Models
Feature: Retention Schedule View Model
Description:
Data model for retention schedule view
"""
# internal
from models.model_view_base import Model_View_Base
# from routes import bp_home
# external
class Model_View_Retention_Schedule(Model_View_Base):
def __init__(self, hash_page_current=Model_View_Base.HASH_PAGE_DATA_RETENTION_SCHEDULE):
super().__init__(hash_page_current=hash_page_current)
self._title = 'Retention Schedule'

49
models/model_view_user.py Normal file
View File

@@ -0,0 +1,49 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: User View Model
Description:
Data model for user view
"""
# internal
from business_objects.tcg.user import User, Parameters_User
from datastores.datastore_user import DataStore_User
from mtg_commander_life_tracker.forms.tcg.user import Filters_User
from helpers.helper_app import Helper_App
from models.model_view_base import Model_View_Base
# from routes import bp_home
# external
from typing import ClassVar
class Model_View_User(Model_View_Base):
FLAG_ERROR_OAUTH: ClassVar[str] = 'error'
FLAG_ERROR_DESCRIPTION_OAUTH: ClassVar[str] = 'error_description'
FLAG_FIRSTNAME: ClassVar[str] = User.FLAG_FIRSTNAME
FLAG_IS_EMAIL_VERIFIED: ClassVar[str] = User.FLAG_IS_EMAIL_VERIFIED
FLAG_IS_SUPER_USER: ClassVar[str] = User.FLAG_IS_SUPER_USER
FLAG_SURNAME: ClassVar[str] = User.FLAG_SURNAME
FLAG_STATE_OAUTH: ClassVar[str] = 'state'
form_filters: Filters_User = None
form_filters_old: Filters_User
users: list = None
def __init__(self, form_filters_old, hash_page_current = Model_View_Base.HASH_PAGE_USER_ACCOUNT):
super().__init__(hash_page_current = hash_page_current, form_filters_old = form_filters_old)
self._title = 'Users'
self.form_filters = form_filters_old
Helper_App.console_log(f'Form filters: {self.form_filters}')
datastore = DataStore_User()
parameters_user = Parameters_User.from_form_filters_user(self.form_filters)
if self.hash_page_current == Model_View_Base.HASH_PAGE_USER_ACCOUNT:
parameters_user.user_ids = str(self.user.user_id)
Helper_App.console_log(f'Query args: {parameters_user}')
self.users, errors = datastore.get_many_user(parameters_user)

1
node_modules/.bin/acorn generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../acorn/bin/acorn

1
node_modules/.bin/baseline-browser-mapping generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../baseline-browser-mapping/dist/cli.js

1
node_modules/.bin/browserslist generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../browserslist/cli.js

1
node_modules/.bin/cssesc generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../cssesc/bin/cssesc

1
node_modules/.bin/envinfo generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../envinfo/dist/cli.js

1
node_modules/.bin/flat generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../flat/cli.js

1
node_modules/.bin/glob generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../glob/dist/esm/bin.mjs

1
node_modules/.bin/import-local-fixture generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../import-local/fixtures/cli.js

1
node_modules/.bin/jsesc generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../jsesc/bin/jsesc

1
node_modules/.bin/json5 generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../json5/lib/cli.js

1
node_modules/.bin/nanoid generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../nanoid/bin/nanoid.cjs

1
node_modules/.bin/node-which generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../which/bin/node-which

1
node_modules/.bin/parser generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../@babel/parser/bin/babel-parser.js

1
node_modules/.bin/regjsparser generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../regjsparser/bin/parser

1
node_modules/.bin/resolve generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../resolve/bin/resolve

1
node_modules/.bin/semver generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../semver/bin/semver.js

1
node_modules/.bin/terser generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../terser/bin/terser

1
node_modules/.bin/update-browserslist-db generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../update-browserslist-db/cli.js

1
node_modules/.bin/webpack generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../webpack/bin/webpack.js

1
node_modules/.bin/webpack-cli generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../webpack-cli/bin/cli.js

4197
node_modules/.package-lock.json generated vendored Normal file

File diff suppressed because it is too large Load Diff

22
node_modules/@babel/code-frame/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

19
node_modules/@babel/code-frame/README.md generated vendored Normal file
View File

@@ -0,0 +1,19 @@
# @babel/code-frame
> Generate errors that contain a code frame that point to source locations.
See our website [@babel/code-frame](https://babeljs.io/docs/babel-code-frame) for more information.
## Install
Using npm:
```sh
npm install --save-dev @babel/code-frame
```
or using yarn:
```sh
yarn add @babel/code-frame --dev
```

217
node_modules/@babel/code-frame/lib/index.js generated vendored Normal file
View File

@@ -0,0 +1,217 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var picocolors = require('picocolors');
var jsTokens = require('js-tokens');
var helperValidatorIdentifier = require('@babel/helper-validator-identifier');
function isColorSupported() {
return (typeof process === "object" && (process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false") ? false : picocolors.isColorSupported
);
}
const compose = (f, g) => v => f(g(v));
function buildDefs(colors) {
return {
keyword: colors.cyan,
capitalized: colors.yellow,
jsxIdentifier: colors.yellow,
punctuator: colors.yellow,
number: colors.magenta,
string: colors.green,
regex: colors.magenta,
comment: colors.gray,
invalid: compose(compose(colors.white, colors.bgRed), colors.bold),
gutter: colors.gray,
marker: compose(colors.red, colors.bold),
message: compose(colors.red, colors.bold),
reset: colors.reset
};
}
const defsOn = buildDefs(picocolors.createColors(true));
const defsOff = buildDefs(picocolors.createColors(false));
function getDefs(enabled) {
return enabled ? defsOn : defsOff;
}
const sometimesKeywords = new Set(["as", "async", "from", "get", "of", "set"]);
const NEWLINE$1 = /\r\n|[\n\r\u2028\u2029]/;
const BRACKET = /^[()[\]{}]$/;
let tokenize;
const JSX_TAG = /^[a-z][\w-]*$/i;
const getTokenType = function (token, offset, text) {
if (token.type === "name") {
const tokenValue = token.value;
if (helperValidatorIdentifier.isKeyword(tokenValue) || helperValidatorIdentifier.isStrictReservedWord(tokenValue, true) || sometimesKeywords.has(tokenValue)) {
return "keyword";
}
if (JSX_TAG.test(tokenValue) && (text[offset - 1] === "<" || text.slice(offset - 2, offset) === "</")) {
return "jsxIdentifier";
}
const firstChar = String.fromCodePoint(tokenValue.codePointAt(0));
if (firstChar !== firstChar.toLowerCase()) {
return "capitalized";
}
}
if (token.type === "punctuator" && BRACKET.test(token.value)) {
return "bracket";
}
if (token.type === "invalid" && (token.value === "@" || token.value === "#")) {
return "punctuator";
}
return token.type;
};
tokenize = function* (text) {
let match;
while (match = jsTokens.default.exec(text)) {
const token = jsTokens.matchToToken(match);
yield {
type: getTokenType(token, match.index, text),
value: token.value
};
}
};
function highlight(text) {
if (text === "") return "";
const defs = getDefs(true);
let highlighted = "";
for (const {
type,
value
} of tokenize(text)) {
if (type in defs) {
highlighted += value.split(NEWLINE$1).map(str => defs[type](str)).join("\n");
} else {
highlighted += value;
}
}
return highlighted;
}
let deprecationWarningShown = false;
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
function getMarkerLines(loc, source, opts, startLineBaseZero) {
const startLoc = Object.assign({
column: 0,
line: -1
}, loc.start);
const endLoc = Object.assign({}, startLoc, loc.end);
const {
linesAbove = 2,
linesBelow = 3
} = opts || {};
const startLine = startLoc.line - startLineBaseZero;
const startColumn = startLoc.column;
const endLine = endLoc.line - startLineBaseZero;
const endColumn = endLoc.column;
let start = Math.max(startLine - (linesAbove + 1), 0);
let end = Math.min(source.length, endLine + linesBelow);
if (startLine === -1) {
start = 0;
}
if (endLine === -1) {
end = source.length;
}
const lineDiff = endLine - startLine;
const markerLines = {};
if (lineDiff) {
for (let i = 0; i <= lineDiff; i++) {
const lineNumber = i + startLine;
if (!startColumn) {
markerLines[lineNumber] = true;
} else if (i === 0) {
const sourceLength = source[lineNumber - 1].length;
markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1];
} else if (i === lineDiff) {
markerLines[lineNumber] = [0, endColumn];
} else {
const sourceLength = source[lineNumber - i].length;
markerLines[lineNumber] = [0, sourceLength];
}
}
} else {
if (startColumn === endColumn) {
if (startColumn) {
markerLines[startLine] = [startColumn, 0];
} else {
markerLines[startLine] = true;
}
} else {
markerLines[startLine] = [startColumn, endColumn - startColumn];
}
}
return {
start,
end,
markerLines
};
}
function codeFrameColumns(rawLines, loc, opts = {}) {
const shouldHighlight = opts.forceColor || isColorSupported() && opts.highlightCode;
const startLineBaseZero = (opts.startLine || 1) - 1;
const defs = getDefs(shouldHighlight);
const lines = rawLines.split(NEWLINE);
const {
start,
end,
markerLines
} = getMarkerLines(loc, lines, opts, startLineBaseZero);
const hasColumns = loc.start && typeof loc.start.column === "number";
const numberMaxWidth = String(end + startLineBaseZero).length;
const highlightedLines = shouldHighlight ? highlight(rawLines) : rawLines;
let frame = highlightedLines.split(NEWLINE, end).slice(start, end).map((line, index) => {
const number = start + 1 + index;
const paddedNumber = ` ${number + startLineBaseZero}`.slice(-numberMaxWidth);
const gutter = ` ${paddedNumber} |`;
const hasMarker = markerLines[number];
const lastMarkerLine = !markerLines[number + 1];
if (hasMarker) {
let markerLine = "";
if (Array.isArray(hasMarker)) {
const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
const numberOfMarkers = hasMarker[1] || 1;
markerLine = ["\n ", defs.gutter(gutter.replace(/\d/g, " ")), " ", markerSpacing, defs.marker("^").repeat(numberOfMarkers)].join("");
if (lastMarkerLine && opts.message) {
markerLine += " " + defs.message(opts.message);
}
}
return [defs.marker(">"), defs.gutter(gutter), line.length > 0 ? ` ${line}` : "", markerLine].join("");
} else {
return ` ${defs.gutter(gutter)}${line.length > 0 ? ` ${line}` : ""}`;
}
}).join("\n");
if (opts.message && !hasColumns) {
frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`;
}
if (shouldHighlight) {
return defs.reset(frame);
} else {
return frame;
}
}
function index (rawLines, lineNumber, colNumber, opts = {}) {
if (!deprecationWarningShown) {
deprecationWarningShown = true;
const message = "Passing lineNumber and colNumber is deprecated to @babel/code-frame. Please use `codeFrameColumns`.";
if (process.emitWarning) {
process.emitWarning(message, "DeprecationWarning");
} else {
const deprecationError = new Error(message);
deprecationError.name = "DeprecationWarning";
console.warn(new Error(message));
}
}
colNumber = Math.max(colNumber, 0);
const location = {
start: {
column: colNumber,
line: lineNumber
}
};
return codeFrameColumns(rawLines, location, opts);
}
exports.codeFrameColumns = codeFrameColumns;
exports.default = index;
exports.highlight = highlight;
//# sourceMappingURL=index.js.map

1
node_modules/@babel/code-frame/lib/index.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

32
node_modules/@babel/code-frame/package.json generated vendored Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "@babel/code-frame",
"version": "7.29.0",
"description": "Generate errors that contain a code frame that point to source locations.",
"author": "The Babel Team (https://babel.dev/team)",
"homepage": "https://babel.dev/docs/en/next/babel-code-frame",
"bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-code-frame"
},
"main": "./lib/index.js",
"dependencies": {
"@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"devDependencies": {
"charcodes": "^0.2.0",
"import-meta-resolve": "^4.1.0",
"strip-ansi": "^4.0.0"
},
"engines": {
"node": ">=6.9.0"
},
"type": "commonjs"
}

22
node_modules/@babel/compat-data/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

19
node_modules/@babel/compat-data/README.md generated vendored Normal file
View File

@@ -0,0 +1,19 @@
# @babel/compat-data
> The compat-data to determine required Babel plugins
See our website [@babel/compat-data](https://babeljs.io/docs/babel-compat-data) for more information.
## Install
Using npm:
```sh
npm install --save @babel/compat-data
```
or using yarn:
```sh
yarn add @babel/compat-data
```

2
node_modules/@babel/compat-data/corejs2-built-ins.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// Todo (Babel 8): remove this file as Babel 8 drop support of core-js 2
module.exports = require("./data/corejs2-built-ins.json");

View File

@@ -0,0 +1,2 @@
// Todo (Babel 8): remove this file now that it is included in babel-plugin-polyfill-corejs3
module.exports = require("./data/corejs3-shipped-proposals.json");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
[
"esnext.promise.all-settled",
"esnext.string.match-all",
"esnext.global-this"
]

View File

@@ -0,0 +1,18 @@
{
"es6.module": {
"chrome": "61",
"and_chr": "61",
"edge": "16",
"firefox": "60",
"and_ff": "60",
"node": "13.2.0",
"opera": "48",
"op_mob": "45",
"safari": "10.1",
"ios": "10.3",
"samsung": "8.2",
"android": "61",
"electron": "2.0",
"ios_saf": "10.3"
}
}

View File

@@ -0,0 +1,35 @@
{
"transform-async-to-generator": [
"bugfix/transform-async-arrows-in-class"
],
"transform-parameters": [
"bugfix/transform-edge-default-parameters",
"bugfix/transform-safari-id-destructuring-collision-in-function-expression"
],
"transform-function-name": [
"bugfix/transform-edge-function-name"
],
"transform-block-scoping": [
"bugfix/transform-safari-block-shadowing",
"bugfix/transform-safari-for-shadowing"
],
"transform-template-literals": [
"bugfix/transform-tagged-template-caching"
],
"transform-optional-chaining": [
"bugfix/transform-v8-spread-parameters-in-optional-chaining"
],
"proposal-optional-chaining": [
"bugfix/transform-v8-spread-parameters-in-optional-chaining"
],
"transform-class-properties": [
"bugfix/transform-v8-static-class-fields-redefine-readonly",
"bugfix/transform-firefox-class-in-computed-class-key",
"bugfix/transform-safari-class-field-initializer-scope"
],
"proposal-class-properties": [
"bugfix/transform-v8-static-class-fields-redefine-readonly",
"bugfix/transform-firefox-class-in-computed-class-key",
"bugfix/transform-safari-class-field-initializer-scope"
]
}

View File

@@ -0,0 +1,203 @@
{
"bugfix/transform-async-arrows-in-class": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "11",
"node": "7.6",
"deno": "1",
"ios": "11",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"bugfix/transform-edge-default-parameters": {
"chrome": "49",
"opera": "36",
"edge": "18",
"firefox": "52",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-edge-function-name": {
"chrome": "51",
"opera": "38",
"edge": "79",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"bugfix/transform-safari-block-shadowing": {
"chrome": "49",
"opera": "36",
"edge": "12",
"firefox": "44",
"safari": "11",
"node": "6",
"deno": "1",
"ie": "11",
"ios": "11",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-safari-for-shadowing": {
"chrome": "49",
"opera": "36",
"edge": "12",
"firefox": "4",
"safari": "11",
"node": "6",
"deno": "1",
"ie": "11",
"ios": "11",
"samsung": "5",
"rhino": "1.7.13",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-safari-id-destructuring-collision-in-function-expression": {
"chrome": "49",
"opera": "36",
"edge": "14",
"firefox": "2",
"safari": "16.3",
"node": "6",
"deno": "1",
"ios": "16.3",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-tagged-template-caching": {
"chrome": "41",
"opera": "28",
"edge": "12",
"firefox": "34",
"safari": "13",
"node": "4",
"deno": "1",
"ios": "13",
"samsung": "3.4",
"rhino": "1.7.14",
"opera_mobile": "28",
"electron": "0.21"
},
"bugfix/transform-v8-spread-parameters-in-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-optional-chaining": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "74",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"proposal-optional-chaining": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "74",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"transform-parameters": {
"chrome": "49",
"opera": "36",
"edge": "15",
"firefox": "52",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-async-to-generator": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "10.1",
"node": "7.6",
"deno": "1",
"ios": "10.3",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"transform-template-literals": {
"chrome": "41",
"opera": "28",
"edge": "13",
"firefox": "34",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "3.4",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-function-name": {
"chrome": "51",
"opera": "38",
"edge": "14",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-block-scoping": {
"chrome": "50",
"opera": "37",
"edge": "14",
"firefox": "53",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
}
}

838
node_modules/@babel/compat-data/data/plugins.json generated vendored Normal file
View File

@@ -0,0 +1,838 @@
{
"transform-explicit-resource-management": {
"chrome": "134",
"edge": "134",
"firefox": "141",
"node": "24",
"electron": "35.0"
},
"transform-duplicate-named-capturing-groups-regex": {
"chrome": "126",
"opera": "112",
"edge": "126",
"firefox": "129",
"safari": "17.4",
"node": "23",
"ios": "17.4",
"electron": "31.0"
},
"transform-regexp-modifiers": {
"chrome": "125",
"opera": "111",
"edge": "125",
"firefox": "132",
"node": "23",
"samsung": "27",
"electron": "31.0"
},
"transform-unicode-sets-regex": {
"chrome": "112",
"opera": "98",
"edge": "112",
"firefox": "116",
"safari": "17",
"node": "20",
"deno": "1.32",
"ios": "17",
"samsung": "23",
"opera_mobile": "75",
"electron": "24.0"
},
"bugfix/transform-v8-static-class-fields-redefine-readonly": {
"chrome": "98",
"opera": "84",
"edge": "98",
"firefox": "75",
"safari": "15",
"node": "12",
"deno": "1.18",
"ios": "15",
"samsung": "11",
"opera_mobile": "52",
"electron": "17.0"
},
"bugfix/transform-firefox-class-in-computed-class-key": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "126",
"safari": "16",
"node": "12",
"deno": "1",
"ios": "16",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"bugfix/transform-safari-class-field-initializer-scope": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "69",
"safari": "16",
"node": "12",
"deno": "1",
"ios": "16",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"transform-class-static-block": {
"chrome": "94",
"opera": "80",
"edge": "94",
"firefox": "93",
"safari": "16.4",
"node": "16.11",
"deno": "1.14",
"ios": "16.4",
"samsung": "17",
"opera_mobile": "66",
"electron": "15.0"
},
"proposal-class-static-block": {
"chrome": "94",
"opera": "80",
"edge": "94",
"firefox": "93",
"safari": "16.4",
"node": "16.11",
"deno": "1.14",
"ios": "16.4",
"samsung": "17",
"opera_mobile": "66",
"electron": "15.0"
},
"transform-private-property-in-object": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "90",
"safari": "15",
"node": "16.9",
"deno": "1.9",
"ios": "15",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"proposal-private-property-in-object": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "90",
"safari": "15",
"node": "16.9",
"deno": "1.9",
"ios": "15",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-class-properties": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "90",
"safari": "14.1",
"node": "12",
"deno": "1",
"ios": "14.5",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"proposal-class-properties": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "90",
"safari": "14.1",
"node": "12",
"deno": "1",
"ios": "14.5",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"transform-private-methods": {
"chrome": "84",
"opera": "70",
"edge": "84",
"firefox": "90",
"safari": "15",
"node": "14.6",
"deno": "1",
"ios": "15",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"proposal-private-methods": {
"chrome": "84",
"opera": "70",
"edge": "84",
"firefox": "90",
"safari": "15",
"node": "14.6",
"deno": "1",
"ios": "15",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"transform-numeric-separator": {
"chrome": "75",
"opera": "62",
"edge": "79",
"firefox": "70",
"safari": "13",
"node": "12.5",
"deno": "1",
"ios": "13",
"samsung": "11",
"rhino": "1.7.14",
"opera_mobile": "54",
"electron": "6.0"
},
"proposal-numeric-separator": {
"chrome": "75",
"opera": "62",
"edge": "79",
"firefox": "70",
"safari": "13",
"node": "12.5",
"deno": "1",
"ios": "13",
"samsung": "11",
"rhino": "1.7.14",
"opera_mobile": "54",
"electron": "6.0"
},
"transform-logical-assignment-operators": {
"chrome": "85",
"opera": "71",
"edge": "85",
"firefox": "79",
"safari": "14",
"node": "15",
"deno": "1.2",
"ios": "14",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"proposal-logical-assignment-operators": {
"chrome": "85",
"opera": "71",
"edge": "85",
"firefox": "79",
"safari": "14",
"node": "15",
"deno": "1.2",
"ios": "14",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"transform-nullish-coalescing-operator": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "72",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"proposal-nullish-coalescing-operator": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "72",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"transform-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"proposal-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-json-strings": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "62",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "9",
"rhino": "1.7.14",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-json-strings": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "62",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "9",
"rhino": "1.7.14",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-optional-catch-binding": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "58",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-optional-catch-binding": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "58",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-parameters": {
"chrome": "49",
"opera": "36",
"edge": "18",
"firefox": "52",
"safari": "16.3",
"node": "6",
"deno": "1",
"ios": "16.3",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-async-generator-functions": {
"chrome": "63",
"opera": "50",
"edge": "79",
"firefox": "57",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "8",
"opera_mobile": "46",
"electron": "3.0"
},
"proposal-async-generator-functions": {
"chrome": "63",
"opera": "50",
"edge": "79",
"firefox": "57",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "8",
"opera_mobile": "46",
"electron": "3.0"
},
"transform-object-rest-spread": {
"chrome": "60",
"opera": "47",
"edge": "79",
"firefox": "55",
"safari": "11.1",
"node": "8.3",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"opera_mobile": "44",
"electron": "2.0"
},
"proposal-object-rest-spread": {
"chrome": "60",
"opera": "47",
"edge": "79",
"firefox": "55",
"safari": "11.1",
"node": "8.3",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"opera_mobile": "44",
"electron": "2.0"
},
"transform-dotall-regex": {
"chrome": "62",
"opera": "49",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "8.10",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"rhino": "1.7.15",
"opera_mobile": "46",
"electron": "3.0"
},
"transform-unicode-property-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-unicode-property-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-named-capturing-groups-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-async-to-generator": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "11",
"node": "7.6",
"deno": "1",
"ios": "11",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"transform-exponentiation-operator": {
"chrome": "52",
"opera": "39",
"edge": "14",
"firefox": "52",
"safari": "10.1",
"node": "7",
"deno": "1",
"ios": "10.3",
"samsung": "6",
"rhino": "1.7.14",
"opera_mobile": "41",
"electron": "1.3"
},
"transform-template-literals": {
"chrome": "41",
"opera": "28",
"edge": "13",
"firefox": "34",
"safari": "13",
"node": "4",
"deno": "1",
"ios": "13",
"samsung": "3.4",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-literals": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "53",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.15",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-function-name": {
"chrome": "51",
"opera": "38",
"edge": "79",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-arrow-functions": {
"chrome": "47",
"opera": "34",
"edge": "13",
"firefox": "43",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"rhino": "1.7.13",
"opera_mobile": "34",
"electron": "0.36"
},
"transform-block-scoped-functions": {
"chrome": "41",
"opera": "28",
"edge": "12",
"firefox": "46",
"safari": "10",
"node": "4",
"deno": "1",
"ie": "11",
"ios": "10",
"samsung": "3.4",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-classes": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-object-super": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-shorthand-properties": {
"chrome": "43",
"opera": "30",
"edge": "12",
"firefox": "33",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.14",
"opera_mobile": "30",
"electron": "0.27"
},
"transform-duplicate-keys": {
"chrome": "42",
"opera": "29",
"edge": "12",
"firefox": "34",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "3.4",
"opera_mobile": "29",
"electron": "0.25"
},
"transform-computed-properties": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "34",
"safari": "7.1",
"node": "4",
"deno": "1",
"ios": "8",
"samsung": "4",
"rhino": "1.8",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-for-of": {
"chrome": "51",
"opera": "38",
"edge": "15",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-sticky-regex": {
"chrome": "49",
"opera": "36",
"edge": "13",
"firefox": "3",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"rhino": "1.7.15",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-unicode-escapes": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "53",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.15",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-unicode-regex": {
"chrome": "50",
"opera": "37",
"edge": "13",
"firefox": "46",
"safari": "12",
"node": "6",
"deno": "1",
"ios": "12",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-spread": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-destructuring": {
"chrome": "51",
"opera": "38",
"edge": "15",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-block-scoping": {
"chrome": "50",
"opera": "37",
"edge": "14",
"firefox": "53",
"safari": "11",
"node": "6",
"deno": "1",
"ios": "11",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-typeof-symbol": {
"chrome": "48",
"opera": "35",
"edge": "12",
"firefox": "36",
"safari": "9",
"node": "6",
"deno": "1",
"ios": "9",
"samsung": "5",
"rhino": "1.8",
"opera_mobile": "35",
"electron": "0.37"
},
"transform-new-target": {
"chrome": "46",
"opera": "33",
"edge": "14",
"firefox": "41",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-regenerator": {
"chrome": "50",
"opera": "37",
"edge": "13",
"firefox": "53",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-member-expression-literals": {
"chrome": "7",
"opera": "12",
"edge": "12",
"firefox": "2",
"safari": "5.1",
"node": "0.4",
"deno": "1",
"ie": "9",
"android": "4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "12",
"electron": "0.20"
},
"transform-property-literals": {
"chrome": "7",
"opera": "12",
"edge": "12",
"firefox": "2",
"safari": "5.1",
"node": "0.4",
"deno": "1",
"ie": "9",
"android": "4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "12",
"electron": "0.20"
},
"transform-reserved-words": {
"chrome": "13",
"opera": "10.50",
"edge": "12",
"firefox": "2",
"safari": "3.1",
"node": "0.6",
"deno": "1",
"ie": "9",
"android": "4.4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "10.1",
"electron": "0.20"
},
"transform-export-namespace-from": {
"chrome": "72",
"deno": "1.0",
"edge": "79",
"firefox": "80",
"node": "13.2.0",
"opera": "60",
"opera_mobile": "51",
"safari": "14.1",
"ios": "14.5",
"samsung": "11.0",
"android": "72",
"electron": "5.0"
},
"proposal-export-namespace-from": {
"chrome": "72",
"deno": "1.0",
"edge": "79",
"firefox": "80",
"node": "13.2.0",
"opera": "60",
"opera_mobile": "51",
"safari": "14.1",
"ios": "14.5",
"samsung": "11.0",
"android": "72",
"electron": "5.0"
}
}

2
node_modules/@babel/compat-data/native-modules.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/native-modules.json");

View File

@@ -0,0 +1,2 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/overlapping-plugins.json");

40
node_modules/@babel/compat-data/package.json generated vendored Normal file
View File

@@ -0,0 +1,40 @@
{
"name": "@babel/compat-data",
"version": "7.29.0",
"author": "The Babel Team (https://babel.dev/team)",
"license": "MIT",
"description": "The compat-data to determine required Babel plugins",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-compat-data"
},
"publishConfig": {
"access": "public"
},
"exports": {
"./plugins": "./plugins.js",
"./native-modules": "./native-modules.js",
"./corejs2-built-ins": "./corejs2-built-ins.js",
"./corejs3-shipped-proposals": "./corejs3-shipped-proposals.js",
"./overlapping-plugins": "./overlapping-plugins.js",
"./plugin-bugfixes": "./plugin-bugfixes.js"
},
"scripts": {
"build-data": "./scripts/download-compat-table.sh && node ./scripts/build-data.mjs && node ./scripts/build-modules-support.mjs && node ./scripts/build-bugfixes-targets.mjs"
},
"keywords": [
"babel",
"compat-table",
"compat-data"
],
"devDependencies": {
"@mdn/browser-compat-data": "^6.0.8",
"core-js-compat": "^3.48.0",
"electron-to-chromium": "^1.5.278"
},
"engines": {
"node": ">=6.9.0"
},
"type": "commonjs"
}

2
node_modules/@babel/compat-data/plugin-bugfixes.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/plugin-bugfixes.json");

2
node_modules/@babel/compat-data/plugins.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/plugins.json");

22
node_modules/@babel/core/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

19
node_modules/@babel/core/README.md generated vendored Normal file
View File

@@ -0,0 +1,19 @@
# @babel/core
> Babel compiler core.
See our website [@babel/core](https://babeljs.io/docs/babel-core) for more information or the [issues](https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A%20core%22+is%3Aopen) associated with this package.
## Install
Using npm:
```sh
npm install --save-dev @babel/core
```
or using yarn:
```sh
yarn add @babel/core --dev
```

View File

@@ -0,0 +1,5 @@
"use strict";
0 && 0;
//# sourceMappingURL=cache-contexts.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":[],"sources":["../../src/config/cache-contexts.ts"],"sourcesContent":["import type { ConfigContext } from \"./config-chain.ts\";\nimport type {\n CallerMetadata,\n TargetsListOrObject,\n} from \"./validation/options.ts\";\n\nexport type { ConfigContext as FullConfig };\n\nexport type FullPreset = {\n targets: TargetsListOrObject;\n} & ConfigContext;\nexport type FullPlugin = {\n assumptions: Record<string, boolean>;\n} & FullPreset;\n\n// Context not including filename since it is used in places that cannot\n// process 'ignore'/'only' and other filename-based logic.\nexport type SimpleConfig = {\n envName: string;\n caller: CallerMetadata | undefined;\n};\nexport type SimplePreset = {\n targets: TargetsListOrObject;\n} & SimpleConfig;\nexport type SimplePlugin = {\n assumptions: Record<string, boolean>;\n} & SimplePreset;\n"],"mappings":"","ignoreList":[]}

261
node_modules/@babel/core/lib/config/caching.js generated vendored Normal file
View File

@@ -0,0 +1,261 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.assertSimpleType = assertSimpleType;
exports.makeStrongCache = makeStrongCache;
exports.makeStrongCacheSync = makeStrongCacheSync;
exports.makeWeakCache = makeWeakCache;
exports.makeWeakCacheSync = makeWeakCacheSync;
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _async = require("../gensync-utils/async.js");
var _util = require("./util.js");
const synchronize = gen => {
return _gensync()(gen).sync;
};
function* genTrue() {
return true;
}
function makeWeakCache(handler) {
return makeCachedFunction(WeakMap, handler);
}
function makeWeakCacheSync(handler) {
return synchronize(makeWeakCache(handler));
}
function makeStrongCache(handler) {
return makeCachedFunction(Map, handler);
}
function makeStrongCacheSync(handler) {
return synchronize(makeStrongCache(handler));
}
function makeCachedFunction(CallCache, handler) {
const callCacheSync = new CallCache();
const callCacheAsync = new CallCache();
const futureCache = new CallCache();
return function* cachedFunction(arg, data) {
const asyncContext = yield* (0, _async.isAsync)();
const callCache = asyncContext ? callCacheAsync : callCacheSync;
const cached = yield* getCachedValueOrWait(asyncContext, callCache, futureCache, arg, data);
if (cached.valid) return cached.value;
const cache = new CacheConfigurator(data);
const handlerResult = handler(arg, cache);
let finishLock;
let value;
if ((0, _util.isIterableIterator)(handlerResult)) {
value = yield* (0, _async.onFirstPause)(handlerResult, () => {
finishLock = setupAsyncLocks(cache, futureCache, arg);
});
} else {
value = handlerResult;
}
updateFunctionCache(callCache, cache, arg, value);
if (finishLock) {
futureCache.delete(arg);
finishLock.release(value);
}
return value;
};
}
function* getCachedValue(cache, arg, data) {
const cachedValue = cache.get(arg);
if (cachedValue) {
for (const {
value,
valid
} of cachedValue) {
if (yield* valid(data)) return {
valid: true,
value
};
}
}
return {
valid: false,
value: null
};
}
function* getCachedValueOrWait(asyncContext, callCache, futureCache, arg, data) {
const cached = yield* getCachedValue(callCache, arg, data);
if (cached.valid) {
return cached;
}
if (asyncContext) {
const cached = yield* getCachedValue(futureCache, arg, data);
if (cached.valid) {
const value = yield* (0, _async.waitFor)(cached.value.promise);
return {
valid: true,
value
};
}
}
return {
valid: false,
value: null
};
}
function setupAsyncLocks(config, futureCache, arg) {
const finishLock = new Lock();
updateFunctionCache(futureCache, config, arg, finishLock);
return finishLock;
}
function updateFunctionCache(cache, config, arg, value) {
if (!config.configured()) config.forever();
let cachedValue = cache.get(arg);
config.deactivate();
switch (config.mode()) {
case "forever":
cachedValue = [{
value,
valid: genTrue
}];
cache.set(arg, cachedValue);
break;
case "invalidate":
cachedValue = [{
value,
valid: config.validator()
}];
cache.set(arg, cachedValue);
break;
case "valid":
if (cachedValue) {
cachedValue.push({
value,
valid: config.validator()
});
} else {
cachedValue = [{
value,
valid: config.validator()
}];
cache.set(arg, cachedValue);
}
}
}
class CacheConfigurator {
constructor(data) {
this._active = true;
this._never = false;
this._forever = false;
this._invalidate = false;
this._configured = false;
this._pairs = [];
this._data = void 0;
this._data = data;
}
simple() {
return makeSimpleConfigurator(this);
}
mode() {
if (this._never) return "never";
if (this._forever) return "forever";
if (this._invalidate) return "invalidate";
return "valid";
}
forever() {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._never) {
throw new Error("Caching has already been configured with .never()");
}
this._forever = true;
this._configured = true;
}
never() {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._forever) {
throw new Error("Caching has already been configured with .forever()");
}
this._never = true;
this._configured = true;
}
using(handler) {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._never || this._forever) {
throw new Error("Caching has already been configured with .never or .forever()");
}
this._configured = true;
const key = handler(this._data);
const fn = (0, _async.maybeAsync)(handler, `You appear to be using an async cache handler, but Babel has been called synchronously`);
if ((0, _async.isThenable)(key)) {
return key.then(key => {
this._pairs.push([key, fn]);
return key;
});
}
this._pairs.push([key, fn]);
return key;
}
invalidate(handler) {
this._invalidate = true;
return this.using(handler);
}
validator() {
const pairs = this._pairs;
return function* (data) {
for (const [key, fn] of pairs) {
if (key !== (yield* fn(data))) return false;
}
return true;
};
}
deactivate() {
this._active = false;
}
configured() {
return this._configured;
}
}
function makeSimpleConfigurator(cache) {
function cacheFn(val) {
if (typeof val === "boolean") {
if (val) cache.forever();else cache.never();
return;
}
return cache.using(() => assertSimpleType(val()));
}
cacheFn.forever = () => cache.forever();
cacheFn.never = () => cache.never();
cacheFn.using = cb => cache.using(() => assertSimpleType(cb()));
cacheFn.invalidate = cb => cache.invalidate(() => assertSimpleType(cb()));
return cacheFn;
}
function assertSimpleType(value) {
if ((0, _async.isThenable)(value)) {
throw new Error(`You appear to be using an async cache handler, ` + `which your current version of Babel does not support. ` + `We may add support for this in the future, ` + `but if you're on the most recent version of @babel/core and still ` + `seeing this error, then you'll need to synchronously handle your caching logic.`);
}
if (value != null && typeof value !== "string" && typeof value !== "boolean" && typeof value !== "number") {
throw new Error("Cache keys must be either string, boolean, number, null, or undefined.");
}
return value;
}
class Lock {
constructor() {
this.released = false;
this.promise = void 0;
this._resolve = void 0;
this.promise = new Promise(resolve => {
this._resolve = resolve;
});
}
release(value) {
this.released = true;
this._resolve(value);
}
}
0 && 0;
//# sourceMappingURL=caching.js.map

1
node_modules/@babel/core/lib/config/caching.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More