diff --git a/server/app.py b/server/app.py index b30e574..1d80298 100644 --- a/server/app.py +++ b/server/app.py @@ -45,6 +45,7 @@ from entertainment_decider.models import ( MediaCollectionLink, MediaElement, generate_preference_list, + setup_custom_tables, ) from entertainment_decider.extractors.collection import ( collection_extract_uri, @@ -187,6 +188,7 @@ for key, val in os.environ.items(): db.bind(**flask_app.config["PONY"]) db.generate_mapping(create_tables=True) +setup_custom_tables() Pony(flask_app) diff --git a/server/entertainment_decider/models.py b/server/entertainment_decider/models.py index 3d04086..6c83b7b 100644 --- a/server/entertainment_decider/models.py +++ b/server/entertainment_decider/models.py @@ -13,6 +13,8 @@ from typing import ( Dict, Iterable, List, + Mapping, + NewType, Optional, Set, Tuple, @@ -25,9 +27,20 @@ import requests from pony import orm from pony.orm.core import Query as PonyQuery +from .common import trim + db = orm.Database() +SafeStr = NewType("SafeStr", str) +""" +Use this type for strings which are expected to be safe to insert into SQL statements. +They may be included into a SQL statement by quoting them manually: f"SELECT * FROM '{safe_str}'" + +DO NOT CAST STRINGS WHICH MAY BE SET BY USERS TO PREVENT SQL INJECTION ATTACKS. +""" + + T = TypeVar("T") Query = Union[List[T], PonyQuery] @@ -687,3 +700,31 @@ class CollectionUriMapping(db.Entity): id: int = orm.PrimaryKey(int, auto=True) uri: str = orm.Required(str, unique=True) element: MediaCollection = orm.Required(MediaCollection) + + +#### +## Custom Table Framework +#### + + +# TODO replace fixed table names with dynamic resolved ones +CUSTOM_TABLE_DEFINITIONS: Mapping[SafeStr, str] = { + SafeStr(table_name): trim(table_sql) + for table_name, table_sql in { + } +} + + +def table_exists(table_name: SafeStr) -> bool: + # TODO may be mariadb specific + return db.exists(f"SHOW TABLE STATUS WHERE Name = '{table_name}'") + + +def setup_custom_tables(): + """ + Creates & fills custom tables (especially cache tables) if they do not exist. + This should not destroy already existing data and should behave indempotent. + """ + for table_name, table_sql in CUSTOM_TABLE_DEFINITIONS.items(): + if not table_exists(table_name): + db.execute(table_sql)