Cat Feeder  1.0.0
The Cat feeder project
Loading...
Searching...
No Matches
non_web.py
Go to the documentation of this file.
1r"""
2# +==== BEGIN CatFeeder =================+
3# LOGO:
4# ..............(..../\
5# ...............)..(.')
6# ..............(../..)
7# ...............\‍(__)|
8# Inspired by Joan Stark
9# source https://www.asciiart.eu/
10# animals/cats
11# /STOP
12# PROJECT: CatFeeder
13# FILE: incoming.py
14# CREATION DATE: 11-10-2025
15# LAST Modified: 21:32:4 01-02-2026
16# DESCRIPTION:
17# This is the backend server in charge of making the actual website work.
18# /STOP
19# COPYRIGHT: (c) Cat Feeder
20# PURPOSE: This is the the class in charge of containing the non-http boilerplates.
21# // AR
22# +==== END CatFeeder =================+
23"""
24
25from typing import TYPE_CHECKING, Union, List, Optional, Dict, Any
26import re
27import uuid
28from random import randint
29from datetime import datetime, timedelta
30from fastapi import Response
31
32from display_tty import Disp, initialise_logger
33
34from ..core import FinalSingleton
35from ..core.runtime_manager import RuntimeManager, RI
36from ..utils import constants as CONST
37from ..sql.sql_manager import SQL
38
39if TYPE_CHECKING:
40 from .responses import BoilerplateResponses
41
42
44 """_summary_
45 """
46
47 disp: Disp = initialise_logger(__qualname__, False)
48
49 def __init__(self, success: int = 0, error: int = 84, debug: bool = False) -> None:
50 """Initialize the BoilerplateNonHTTP instance.
51 The initializer configures the logger, stores commonly used status codes and
52 runtime manager references, and obtains shared instances such as the
53 database link and optional response helper.
54
55 Args:
56 success (int): Numeric code representing a successful operation. Defaults to 0.
57 error (int): Numeric code representing an error. Defaults to 84.
58 debug (bool): Enable debug logging when True. Defaults to False.
59 """
60 # ------------------------ The logging function ------------------------
61 self.disp.update_disp_debug(debug)
62 self.disp.log_debug("Initialising...")
63 # -------------------------- Inherited values --------------------------
64 self.debug: bool = debug
65 self.success: int = success
66 self.error: int = error
67 self.runtime_manager: RuntimeManager = RI
68 # -------------------------- Shared instances --------------------------
69 self.database_link: SQL = RI.get(SQL)
70 self.boilerplate_responsesboilerplate_responses: Optional[BoilerplateResponses] = RI.get_if_exists(
71 "BoilerplateResponses", None)
72 self.disp.log_debug("Initialised")
73
74 def pause(self) -> str:
75 """_summary_
76 This is a pause function that works in the same wat as the batch pause command.
77 It pauses the program execution until the user presses the enter key.
78
79 Returns:
80 str: _description_: The input from the user
81 """
82 return input("Press enter to continue...")
83
84 def set_lifespan(self, seconds: int) -> datetime:
85 """
86 The function to set the lifespan of the user token
87 Args:
88 seconds (int): Seconds
89
90 Returns:
91 datetime: The datetime of the lifespan of the token
92 """
93 current_time = datetime.now()
94 offset_time = current_time + timedelta(seconds=seconds)
95 return offset_time
96
97 def is_token_admin(self, token: str) -> bool:
98 """Check if a given token correspond to a user that is an administrator or not.
99
100 Args:
101 token (str): The token to analyse.
102
103 Returns:
104 bool: The administrative status.
105 """
106 title: str = "is_token_admin"
107 usr_id: Union[
108 str, Response, None
109 ] = self.get_user_id_from_token(title, token)
110 if not isinstance(usr_id, str):
111 return False
112 current_user_raw: Union[int, List[Dict[str, Any]]] = self.database_link.get_data_from_table(
113 table=CONST.TAB_ACCOUNTS,
114 column="*",
115 where=f"id='{usr_id}'",
116 beautify=True
117 )
118 self.disp.log_debug(f"Queried data = {current_user_raw}")
119 if isinstance(current_user_raw, int) or current_user_raw == []:
120 return False
121 if "admin" in current_user_raw[0]:
122 if str(current_user_raw[0].get("admin", "0")) == "1":
123 self.disp.log_warning(
124 f"User account {usr_id} with name {current_user_raw[0].get('username', '<unknown_username>')} is an admin"
125 )
126 self.disp.log_warning(
127 "They probably called an admin endpoint."
128 )
129 return True
130 return False
131
132 def update_lifespan(self, token: str) -> int:
133 """Refresh the expiration date for a connection token.
134 The function computes a new expiration datetime using :meth:`set_lifespan`
135 and persists it to the database using the configured SQL manager. The
136 stored value is formatted for SQL using :meth:`SQL.datetime_to_string`.
137
138 Args:
139 token (str): The connection token whose expiration should be updated.
140
141 Returns:
142 int: The status code returned by the database update operation. This will be `self.success` on success or an error code on failure.
143 """
144 self.disp.log_debug("The token is still valid, updating lifespan.")
145 new_date = self.set_lifespan(
146 CONST.UA_TOKEN_LIFESPAN
147 )
148 self.disp.log_debug(f"New token lifespan: {new_date}")
149 new_date_str = self.database_link.datetime_to_string(
150 datetime_instance=new_date,
151 date_only=False,
152 sql_mode=True
153 )
154 self.disp.log_debug(f"string date: {new_date_str}")
155 status = self.database_link.update_data_in_table(
156 table=CONST.TAB_CONNECTIONS,
157 data=[new_date_str],
158 column=["expiration_date"],
159 where=f"token='{token}'"
160 )
161 if status == self.success:
162 self.disp.log_debug("Token expiration date updated.")
163 return status
164 self.disp.log_error("Failed to update token lifespan.")
165 return status
166
167 def is_token_correct(self, token: str) -> bool:
168 """_summary_
169 Check if the token is correct.
170 Args:
171 token (str): _description_: The token to check
172
173 Returns:
174 bool: _description_: True if the token is correct, False otherwise
175 """
176 title = "is_token_correct"
177 self.disp.log_debug("Checking if the token is correct.", title)
178 if isinstance(token, str) is False:
179 return False
180 login_table = self.database_link.get_data_from_table(
181 CONST.TAB_CONNECTIONS,
182 ["expiration_date"],
183 where=f"token='{token}'",
184 beautify=False
185 )
186 if isinstance(login_table, int):
187 return False
188 if len(login_table) != 1:
189 return False
190 self.disp.log_debug(f"login_table = {login_table}", title)
191 if datetime.now() > login_table[0][0]:
192 self.disp.log_warning(
193 "The provided token is invalid due to excessive idle time."
194 )
195 return False
196 self.disp.log_debug("The token is still valid, updating lifespan.")
197 status = self.update_lifespan(token)
198 if status != self.success:
199 self.disp.log_warning(
200 f"Failed to update expiration_date for {token}.",
201 title
202 )
203 return True
204
205 def generate_token(self) -> str:
206 """_summary_
207 This is a function that will generate a token for the user.
208 Returns:
209 str: _description_: The token generated
210 """
211 title = "generate_token"
212 token = str(uuid.uuid4())
213 user_token = self.database_link.get_data_from_table(
214 table=CONST.TAB_CONNECTIONS,
215 column="token",
216 where=f"token='{token}'",
217 beautify=False
218 )
219 if isinstance(user_token, int):
220 return token
221 if isinstance(user_token, list) and token not in user_token:
222 return token
223 self.disp.log_debug(f"user_token = {user_token}", title)
224 while not isinstance(user_token, int):
225 token = str(uuid.uuid4())
226 user_token = self.database_link.get_data_from_table(
227 table=CONST.TAB_CONNECTIONS,
228 column="token",
229 where=f"token='{token}'",
230 beautify=False
231 )
232 if user_token == []:
233 user_token = self.success
234 self.disp.log_debug(f"user_token = {user_token}", title)
235 if isinstance(user_token, int) and user_token == self.error:
236 return token
237 if isinstance(user_token, list) and token not in user_token:
238 return token
239 return token
240
241 def server_show_item_content(self, function_name: str = "show_item_content", item_name: str = "", item: object = None, show: bool = True) -> None:
242 """_summary_
243 This is a function that will display the content of an item.
244 The purpose of this function is more for debugging purposes.
245 Args:
246 function_name (str, optional): _description_. Defaults to "show_item_content".
247 item (object, optional): _description_. Defaults to None.
248 """
249 if show is False:
250 return
251 self.disp.log_debug(
252 f"({function_name}) dir({item_name}) = {dir(item)}",
253 "pet_server_show_item_content"
254 )
255 for i in dir(item):
256 if i in ("auth", "session", "user"):
257 self.disp.log_debug(
258 f"({function_name}) skipping {item_name}.{i}"
259 )
260 continue
261 self.disp.log_debug(
262 f"({function_name}) {item_name}.{i} = {getattr(item, i)}"
263 )
264
265 def check_date(self, date: str = "DD/MM/YYYY") -> bool:
266 """_summary_
267 This is a function that will check if the date is correct or not.
268 Args:
269 date (str, optional): _description_: The date to check. Defaults to "DD/MM/YYYY".
270
271 Returns:
272 bool: _description_: True if the date is correct, False otherwise
273 """
274 # First a quick format check, then validate actual calendar date
275 pattern = re.compile(
276 r"^(0[1-9]|[12][0-9]|3[01])/(0[1-9]|1[0-2])/\d{4}$"
277 )
278 if not pattern.match(date):
279 return False
280 try:
281 # Validate real calendar date (catches things like 31/02/2020)
282 datetime.strptime(date, "%d/%m/%Y")
283 return True
284 except Exception:
285 return False
286
287 def generate_check_token(self, token_size: int = 4) -> str:
288 """_summary_
289 Create a token that can be used for e-mail verification.
290
291 Returns:
292 str: _description_
293 """
294 if isinstance(token_size, (int, float)) is False:
295 token_size = 4
296 token_size = int(token_size)
297 token_size = max(token_size, 0)
298 code = f"{randint(CONST.RANDOM_MIN, CONST.RANDOM_MAX)}"
299 for i in range(token_size):
300 code += f"-{randint(CONST.RANDOM_MIN, CONST.RANDOM_MAX)}"
301 return code
302
303 def update_single_data(self, table: str, column_finder: str, column_to_update: str, data_finder: str, request_body: dict) -> int:
304 """
305 The function in charge of updating the data in the database
306 """
307 if self.database_link.update_data_in_table(
308 table,
309 [request_body[column_to_update]],
310 [column_to_update],
311 f"{column_finder}='{data_finder}'"
312 ) == self.error:
313 return self.error
314 return self.success
315
316 def get_user_id_from_token(self, title: str, token: str) -> Optional[Union[str, Response]]:
317 """_summary_
318 The function in charge of getting the user id based of the provided content.
319
320 Args:
321 title (str): _description_: The title of the endpoint calling it
322 token (str): _description_: The token of the user account
323
324 Returns:
325 Optional[Union[str, Response]]: _description_: Returns as string id if success, otherwise, a pre-made response for the endpoint.
326 """
327 function_title = "get_user_id_from_token"
328 usr_id_node: str = "user_id"
329 self.boilerplate_responsesboilerplate_responses: Optional[BoilerplateResponses] = RI.get_if_exists(
330 "BoilerplateResponses", self.boilerplate_responsesboilerplate_responses)
332 self.disp.log_error(
333 "BoilerplateResponses not found, retuning None",
334 f"{title}:{function_title}"
335 )
336 return None
337 self.disp.log_debug(
338 f"Getting user id based on {token}", function_title
339 )
340 current_user_raw: Union[int, List[Dict[str, Any]]] = self.database_link.get_data_from_table(
341 table=CONST.TAB_CONNECTIONS,
342 column="*",
343 where=f"token='{token}'",
344 beautify=True
345 )
346 if isinstance(current_user_raw, int):
347 return self.boilerplate_responsesboilerplate_responses.user_not_found(title, token)
348 current_user: List[Dict[str, Any]] = current_user_raw
349 self.disp.log_debug(f"current_user = {current_user}", function_title)
350 if current_user == self.error:
351 return self.boilerplate_responsesboilerplate_responses.user_not_found(title, token)
352 self.disp.log_debug(
353 f"user_length = {len(current_user)}", function_title
354 )
355 if len(current_user) == 0 or len(current_user) > 1:
356 return self.boilerplate_responsesboilerplate_responses.user_not_found(title, token)
357 self.disp.log_debug(
358 f"current_user[0] = {current_user[0]}", function_title
359 )
360 if usr_id_node not in current_user[0]:
361 return self.boilerplate_responsesboilerplate_responses.user_not_found(title, token)
362 msg = "str(current_user[0]["
363 msg += f"{usr_id_node}]) = {str(current_user[0][usr_id_node])}"
364 self.disp.log_debug(msg, function_title)
365 return str(current_user[0][usr_id_node])
366
367 def update_user_data(self, title: str, usr_id: str, line_content: List[Optional[Union[str, int, float]]]) -> Optional[Union[int, Response]]:
368 """_summary_
369 Update the account information based on the provided line.
370
371 Args:
372 title (str): _description_: This is the title of the endpoint
373 usr_id (str): _description_: This is the id of the user that needs to be updated
374 line_content (List[str]): _description_: The content of the line to be edited.
375
376 Returns:
377 Union[int, Response]: _description_
378 """
379 self.boilerplate_responsesboilerplate_responses: Optional[BoilerplateResponses] = RI.get_if_exists(
380 "BoilerplateResponses", self.boilerplate_responsesboilerplate_responses)
382 return None
383 self.disp.log_debug(f"Compile line_content: {line_content}.", title)
384 columns_raw: Union[int, List[str]] = self.database_link.get_table_column_names(
385 CONST.TAB_ACCOUNTS
386 )
387 if isinstance(columns_raw, int):
388 return columns_raw
389 columns: List[str] = columns_raw
390 self.disp.log_debug(f"Removing id from columns: {columns}.", title)
391 columns.pop(0)
392 status = self.database_link.update_data_in_table(
393 table=CONST.TAB_ACCOUNTS,
394 data=line_content,
395 column=columns,
396 where=f"id='{usr_id}'"
397 )
398 if status == self.error:
399 return self.boilerplate_responsesboilerplate_responses.internal_server_error(title, usr_id)
400 return self.success
401
402 def remove_user_from_tables(self, where: str, tables: List[str]) -> Union[int, Dict[str, int]]:
403 """_summary_
404 Remove the user from the provided tables.
405
406 Args:
407 where (str): _description_: The id of the user to remove
408 tables (List[str]): _description_: The tables to remove the user from
409
410 Returns:
411 int: _description_: The status of the operation
412 """
413 title = "remove_user_from_tables"
414 if not isinstance(tables, (List, tuple, str)):
415 self.disp.log_error(
416 f"Expected tables to be of type list but got {type(tables)}",
417 title
418 )
419 return self.error
420 if isinstance(tables, str):
421 self.disp.log_warning(
422 "Tables is of type str, converting to list[str].", title
423 )
424 tables = [tables]
425 deletion_status = {}
426 for table in tables:
427 status = self.database_link.remove_data_from_table(
428 table=table,
429 where=where
430 )
431 deletion_status[str(table)] = status
432 if status == self.error:
433 self.disp.log_warning(
434 f"Failed to remove data from table: {table}",
435 title
436 )
437 return deletion_status
438
439 def hide_api_key(self, api_key: str) -> str:
440 """_summary_
441 Hide the api key from the user.
442
443 Args:
444 api_key (str): _description_: The api key to hide
445
446 Returns:
447 str: _description_: The hidden api key
448 """
449 title = "hide_api_key"
450 self.disp.log_debug(f"api_key = {api_key}", title)
451 if api_key is None:
452 api_key = "No api key"
453 else:
454 api_key = "Some api key"
455 self.disp.log_debug(f"api_key after: {api_key}", title)
456 return api_key
Optional[Union[str, Response]] get_user_id_from_token(self, str title, str token)
Definition non_web.py:316
None __init__(self, int success=0, int error=84, bool debug=False)
Definition non_web.py:49
int update_single_data(self, str table, str column_finder, str column_to_update, str data_finder, dict request_body)
Definition non_web.py:303
Optional[BoilerplateResponses] boilerplate_responses
Definition non_web.py:70
Union[int, Dict[str, int]] remove_user_from_tables(self, str where, List[str] tables)
Definition non_web.py:402
bool check_date(self, str date="DD/MM/YYYY")
Definition non_web.py:265
Optional[Union[int, Response]] update_user_data(self, str title, str usr_id, List[Optional[Union[str, int, float]]] line_content)
Definition non_web.py:367
None server_show_item_content(self, str function_name="show_item_content", str item_name="", object item=None, bool show=True)
Definition non_web.py:241