2# +==== BEGIN CatFeeder =================+
5# ...............)..(.')
7# ...............\(__)|
8# Inspired by Joan Stark
9# source https://www.asciiart.eu/
14# CREATION DATE: 11-10-2025
15# LAST Modified: 19:0:27 31-01-2026
17# This is the backend server in charge of making the actual website work.
18# Backend server constants and configuration management.
20# This module contains all the configuration constants and utility functions
21# required to run the CatFeeder server. It manages environment variables,
22# TOML configurations, database settings, server configurations, and provides
23# helper functions for data manipulation.
25# Module-level constants include:
26# - Database connection parameters (host, port, user, password, database)
27# - Server configurations (workers, timeout, development/production settings)
28# - Verification settings (email delays, token sizes)
29# - Table names and database schema constants
30# - JSON response key constants
31# - User data field mappings and indices
33# COPYRIGHT: (c) Cat Feeder
34# PURPOSE: This is the file in charge of containing the constants that run the server.
36# +==== END CatFeeder =================+
40from typing
import List, Tuple, Any, Dict
41from pathlib
import Path
43from display_tty
import Disp, initialise_logger
45from ..config
import TOMLLoader, EnvLoader, refresh_debug
47IDISP: Disp = initialise_logger(
"Constants",
False)
57ROOT_DIRECTORY: Path = Path(__file__).parent.parent.parent.parent
60ASSETS_DIRECTORY: Path = ROOT_DIRECTORY /
"assets"
63DEBUG: bool = refresh_debug(
False)
66REDIRECT_URI = ENV.get_environment_variable(
"REDIRECT_URI")
69DB_HOST = ENV.get_environment_variable(
"DB_HOST")
70DB_PORT_RAW = ENV.get_environment_variable(
"DB_PORT")
71if not DB_PORT_RAW.isdigit():
72 raise TypeError(
"Expected a number for the database port")
73DB_PORT = int(DB_PORT_RAW)
74DB_USER = ENV.get_environment_variable(
"DB_USER")
75DB_PASSWORD = ENV.get_environment_variable(
"DB_PASSWORD")
76DB_DATABASE = ENV.get_environment_variable(
"DB_DATABASE")
80SERVER_WORKERS = TOML.get_toml_variable(
81 "Server_configuration",
"workers",
None
83SERVER_LIFESPAN = TOML.get_toml_variable(
84 "Server_configuration",
"lifespan",
"auto"
86SERVER_TIMEOUT_KEEP_ALIVE = TOML.get_toml_variable(
87 "Server_configuration",
"timeout_keep_alive", 30
91SUCCESS = int(TOML.get_toml_variable(
92 "Server_configuration.status_codes",
"success", 0
94ERROR = int(TOML.get_toml_variable(
95 "Server_configuration.status_codes",
"error", -84
100SERVER_DEV_RELOAD = TOML.get_toml_variable(
101 "Server_configuration.development",
"reload",
False
103SERVER_DEV_RELOAD_DIRS = TOML.get_toml_variable(
104 "Server_configuration.development",
"reload_dirs",
None
106SERVER_DEV_LOG_LEVEL = TOML.get_toml_variable(
107 "Server_configuration.development",
"log_level",
"info"
109SERVER_DEV_USE_COLOURS = TOML.get_toml_variable(
110 "Server_configuration.development",
"use_colours",
True
114SERVER_PROD_PROXY_HEADERS = TOML.get_toml_variable(
115 "Server_configuration.production",
"proxy_headers",
True
117SERVER_PROD_FORWARDED_ALLOW_IPS = TOML.get_toml_variable(
118 "Server_configuration.production",
"forwarded_allow_ips",
None
122DATABASE_POOL_NAME = TOML.get_toml_variable(
123 "Server_configuration.database",
"pool_name",
None
125DATABASE_MAX_POOL_CONNECTIONS = int(TOML.get_toml_variable(
126 "Server_configuration.database",
"max_pool_connections", 10
128DATABASE_RESET_POOL_NODE_CONNECTION = TOML.get_toml_variable(
129 "Server_configuration.database",
"reset_pool_node_connection",
True
131DATABASE_CONNECTION_TIMEOUT = int(TOML.get_toml_variable(
132 "Server_configuration.database",
"connection_timeout", 10
134DATABASE_LOCAL_INFILE = TOML.get_toml_variable(
135 "Server_configuration.database",
"local_infile",
False
137DATABASE_INIT_COMMAND = TOML.get_toml_variable(
138 "Server_configuration.database",
"init_command",
None
140DATABASE_DEFAULT_FILE = TOML.get_toml_variable(
141 "Server_configuration.database",
"default_file",
None
143DATABASE_SSL_KEY = TOML.get_toml_variable(
144 "Server_configuration.database",
"ssl_key",
None
146DATABASE_SSL_CERT = TOML.get_toml_variable(
147 "Server_configuration.database",
"ssl_cert",
None
149DATABASE_SSL_CA = TOML.get_toml_variable(
150 "Server_configuration.database",
"ssl_ca",
None
152DATABASE_SSL_CIPHER = TOML.get_toml_variable(
153 "Server_configuration.database",
"ssl_cipher",
None
155DATABASE_SSL_VERIFY_CERT = TOML.get_toml_variable(
156 "Server_configuration.database",
"ssl_verify_cert",
False
158DATABASE_SSL = TOML.get_toml_variable(
159 "Server_configuration.database",
"ssl",
None
161DATABASE_AUTOCOMMIT = TOML.get_toml_variable(
162 "Server_configuration.database",
"autocommit",
False
164DATABASE_COLLATION = TOML.get_toml_variable(
165 "Server_configuration.database",
"collation",
"utf8mb4_unicode_ci"
169EMAIL_VERIFICATION_DELAY = int(TOML.get_toml_variable(
170 "Verification",
"email_verification_delay", 120
172CHECK_TOKEN_SIZE = int(TOML.get_toml_variable(
173 "Verification",
"check_token_size", 4
175RANDOM_MIN = int(TOML.get_toml_variable(
176 "Verification",
"random_min", 100000
178RANDOM_MAX = int(TOML.get_toml_variable(
179 "Verification",
"random_max", 999999
183API_REQUEST_DELAY = int(TOML.get_toml_variable(
184 "Services",
"api_request_delay", 5
188FRONT_END_ASSETS_REFRESH: int = int(TOML.get_toml_variable(
189 "Frontend",
"asset_cache_refresh_interval", 300
193JSON_TITLE: str =
"title"
194JSON_MESSAGE: str =
"msg"
195JSON_ERROR: str =
"error"
196JSON_RESP: str =
"resp"
197JSON_LOGGED_IN: str =
"logged in"
198JSON_UID: str =
"user_uid"
202TAB_ACCOUNTS =
"Users"
203TAB_ACTIONS =
"Actions"
204TAB_SERVICES =
"Services"
205TAB_CONNECTIONS =
"Connections"
206TAB_VERIFICATION =
"Verification"
207TAB_ACTIVE_OAUTHS =
"ActiveOauths"
208TAB_ACTION_LOGGING =
"ActionLoging"
209TAB_ACTION_TEMPLATE =
"ActionTemplate"
210TAB_USER_OAUTH_CONNECTION =
"UserOauthConnection"
213CHAR_NODE_KEY: str =
"node"
214CHAR_ACTIVE_KEY: str =
"active"
215CHAR_NAME_KEY: str =
"name"
216CHAR_UID_KEY: str =
"uid"
217CHAR_ID_DEFAULT_INDEX: int = 0
221USERNAME_INDEX_DB: int = 1
222PASSWORD_INDEX_DB: int = 2
223FIRSTNAME_INDEX_DB: int = 3
224LASTNAME_INDEX_DB: int = 4
225BIRTHDAY_INDEX_DB: int = 5
226GENDER_INDEX_DB: int = 7
227ROLE_INDEX_DB: int = 10
228UD_USERNAME_KEY: str =
"username"
229UD_FIRSTNAME_KEY: str =
"firstname"
230UD_LASTNAME_KEY: str =
"lastname"
231UD_BIRTHDAY_KEY: str =
"birthday"
232UD_GENDER_KEY: str =
"gender"
233UD_ROLE_KEY: str =
"role"
234UD_ADMIN_KEY: str =
"admin"
235UD_LOGIN_TIME_KEY: str =
"login_time"
236UD_LOGGED_IN_KEY: str =
"logged_in"
239REQUEST_TOKEN_KEY =
"token"
240REQUEST_BEARER_KEY =
"authorization"
243THREAD_CACHE_REFRESH_DELAY = 10
246UA_TOKEN_LIFESPAN: int = 7200
247UA_EMAIL_KEY: str =
"email"
248UA_LIFESPAN_KEY: str =
"lifespan"
251USER_INFO_BANNED: List[str] = [
"password",
"method",
"favicon"]
252USER_INFO_ADMIN_NODE: str =
"admin"
256 ASSETS_DIRECTORY /
"icon" /
"cat_feeder" /
"favicon.ico"
260PNG_ICON_PATH: str = str(
261 ASSETS_DIRECTORY /
"icon" /
"cat_feeder" /
"logo_256x256.png"
265TABLE_COLUMNS_TO_IGNORE: Tuple[str, ...] = (
"id",
"creation_date",
"edit_date")
267TABLE_COLUMNS_TO_IGNORE_USER: Tuple[str, ...] = (
268 "id",
"creation_date",
"edit_date",
"last_connection",
"deletion_date"
272BUCKET_NAME: str = ENV.get_environment_variable(
"BUCKET_NAME")
275STYLE_DIRECTORY: Path = ASSETS_DIRECTORY /
"css"
276HTML_DIRECTORY: Path = ASSETS_DIRECTORY /
"html"
277JS_DIRECTORY: Path = ASSETS_DIRECTORY /
"js" /
"web"
278IMG_DIRECTORY: Path = ASSETS_DIRECTORY /
"icon" /
"img"
281 GOOGLE_SITE_VERIFICATION_CODE: str = ENV.get_environment_variable(
282 "GOOGLE_SITE_VERIFICATION_CODE"
285 GOOGLE_SITE_VERIFICATION_CODE =
""
288def clean_list(raw_input: List[Any], items: Tuple[Any, ...], disp: Disp) -> List[Any]:
289 """Remove specified items from a list if they are present.
291 Iterates through the input list and removes all occurrences of items
292 specified in the items tuple. Logs debug information for each removal.
295 raw_input (List[Any]): The list to check and modify.
296 items (Tuple[Any, Any]): The items to remove from the list.
297 disp (Disp): The logging object for debug output.
300 List[Any]: The modified list with specified items removed.
303 disp.log_debug(f
"initial list: {raw_input}")
304 for index, item
in enumerate(raw_input):
307 disp.log_debug(f
"index to pop: {index}, item: {item}")
308 max_length = len(to_pop)
309 while max_length > 0:
310 node = to_pop[max_length-1]
311 node_value = raw_input.pop(node)
312 disp.log_debug(f
"Popped item[{max_length-1}] = {node} -> {node_value}")
314 disp.log_debug(f
"final list: {raw_input}")
318def clean_dict(raw_input: Dict[str, Any], items: Tuple[Any, ...], disp: Disp) -> Dict[str, Any]:
319 """Remove specified keys from a dictionary if they are present.
321 Iterates through the input dictionary and removes all keys specified
322 in the items tuple. Logs debug information for each removal.
325 input (Dict[str, Any]): The dictionary to check and modify.
326 items (Tuple[Any, Any]): The keys to remove from the dictionary.
327 disp (Disp): The logging object for debug output.
330 Dict[str, Any]: The modified dictionary with specified keys removed.
333 if item
in raw_input:
334 disp.log_debug(f
"key to pop: {item}, value: {raw_input[item]}")
336 disp.log_debug(f
"Popped key: {item}")
337 disp.log_debug(f
"final dictionary: {raw_input}")
342 """Mask a single email segment, showing first and last character."""
343 if len(segment) <= 2:
347 return f
"{segment[0]}[...]{segment[-1]}"
351 """Mask user email for privacy while preserving structure and shape.
353 Masks each word/segment separately, showing only first and last character.
354 Segments are separated by special characters: . + - @
357 user_email (str): Email address to mask.
360 str: Masked email (e.g., t[...]t.m[...]e+r[...]m@g[...]l.c[...]m).
364 disp.log_warning(
"Got empty email string to mask")
365 return "<unknown_email>"
367 if "@" not in user_email:
368 disp.log_warning(f
"Got invalid email format: '{user_email}'")
369 return "<invalid_email>"
371 disp.log_debug(f
"Masking email: {user_email}")
374 segments = re.split(
r"([.+\-@])", user_email)
375 disp.log_debug(f
"Split segments: {segments}")
379 for segment
in segments:
385 is_special_char = re.match(
r"[.+\-@]", segment)
389 disp.log_debug(f
"Keeping special character: '{segment}'")
390 masked_parts.append(segment)
394 disp.log_debug(f
"Masked segment '{segment}' -> '{masked_segment}'")
395 masked_parts.append(masked_segment)
398 result =
"".join(masked_parts)
399 disp.log_debug(f
"Final masked email: {result}")
List[Any] clean_list(List[Any] raw_input, Tuple[Any,...] items, Disp disp)
str mask_email_segment(str segment)
Dict[str, Any] clean_dict(Dict[str, Any] raw_input, Tuple[Any,...] items, Disp disp)
str hide_user_email(str user_email, Disp disp)