Cat Feeder  1.0.0
The Cat feeder project
Loading...
Searching...
No Matches
bonus.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: bonus.py
14# CREATION DATE: 19-11-2025
15# LAST Modified: 0:15:46 25-01-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: The endpoints that can be considered as bonus in the server.
21# // AR
22# +==== END CatFeeder =================+
23"""
24
25import os
26from display_tty import Disp, initialise_logger
27from fastapi import Request, Response
28from ...core import RuntimeControl, RuntimeManager, RI
29from ...crons import BackgroundTasks
30from ...server_header import ServerHeaders
31from ...utils import constants as CONST
32from ...http_codes import HCI, HttpDataTypes, HTTP_DEFAULT_TYPE
33from ...boilerplates import BoilerplateIncoming, BoilerplateResponses, BoilerplateNonHTTP
34
35
36class Bonus:
37 """_summary_
38 """
39
40 disp: Disp = initialise_logger(__qualname__, False)
41
42 def __init__(self, success: int = 0, error: int = 84, debug: bool = False) -> None:
43 """_summary_
44
45 Args:
46 runtime_data (RuntimeData): _description_
47 success (int, optional): _description_. Defaults to 0.
48 error (int, optional): _description_. Defaults to 84.
49 debug (bool, optional): _description_. Defaults to False.
50 """
51 # ------------------------ The logging function ------------------------
52 self.disp.update_disp_debug(debug)
53 self.disp.log_debug("Initialising...")
54 # -------------------------- Inherited values --------------------------
55 self.debug: bool = debug
56 self.success: int = success
57 self.error: int = error
58 self.runtime_manager: RuntimeManager = RI
59 # -------------------------- Shared instances --------------------------
60 self.boilerplate_incoming_initialised: BoilerplateIncoming = self.runtime_manager.get(
61 BoilerplateIncoming)
62 self.boilerplate_responses_initialised: BoilerplateResponses = self.runtime_manager.get(
63 BoilerplateResponses)
64 self.boilerplate_non_http_initialised: BoilerplateNonHTTP = self.runtime_manager.get(
65 BoilerplateNonHTTP)
66 self.runtime_controls_initialised: RuntimeControl = self.runtime_manager.get(
67 RuntimeControl)
68 self.server_headers_initialised: ServerHeaders = self.runtime_manager.get(
69 ServerHeaders)
70 self.background_tasks_initialised: BackgroundTasks = self.runtime_manager.get(
71 BackgroundTasks)
72 self.disp.log_debug("Initialised")
73
74 async def get_welcome(self, request: Request) -> Response:
75 """_summary_
76 The endpoint corresponding to ''.
77
78 Returns:
79 Response: _description_: The data to send back to the user as a response.
80 """
81 title = "get_welcome"
82 token = self.boilerplate_incoming_initialised.get_token_if_present(
83 request)
84 body = self.boilerplate_responses_initialised.build_response_body(
85 title="Home",
86 message="Welcome to the cat_feeder server.",
87 resp="",
88 token=token,
89 error=False
90 )
91 self.disp.log_debug(f"sent body : {body}", title)
92 self.disp.log_debug(
93 f"header = {self.server_headers_initialised.for_json()}", title
94 )
95 outgoing = HCI.success(
96 content=body,
97 content_type=HTTP_DEFAULT_TYPE,
98 headers=self.server_headers_initialised.for_json()
99 )
100 self.disp.log_debug(f"ready_to_go: {outgoing}", title)
101 return outgoing
102
103 async def get_root(self, request: Request) -> Response:
104 """_summary_
105 The endpoint corresponding to '/'.
106
107 Returns:
108 Response: _description_: The data to send back to the user as a response.
109 """
110 title = "get_root"
111 token = self.boilerplate_incoming_initialised.get_token_if_present(
112 request)
113 body = self.boilerplate_responses_initialised.build_response_body(
114 title="root",
115 message="Welcome to the cat_feeder server.",
116 resp="/",
117 token=token,
118 error=False
119 )
120 self.disp.log_debug(f"sent body : {body}", title)
121 self.disp.log_debug(
122 f"header = {self.server_headers_initialised.for_json()}", title
123 )
124 outgoing = HCI.success(
125 content=body,
126 content_type=HTTP_DEFAULT_TYPE,
127 headers=self.server_headers_initialised.for_json()
128 )
129 self.disp.log_debug(f"ready_to_go: {outgoing}", title)
130 return outgoing
131
132 async def get_api_v1(self, request: Request) -> Response:
133 """_summary_
134 The endpoint corresponding to '/'.
135
136 Returns:
137 Response: _description_: The data to send back to the user as a response.
138 """
139 title = "get_api_v1"
140 token = self.boilerplate_incoming_initialised.get_token_if_present(
141 request)
142 body = self.boilerplate_responses_initialised.build_response_body(
143 title="api_v1",
144 message="Welcome to the cat_feeder server.",
145 resp="/api/v1",
146 token=token,
147 error=False
148 )
149 self.disp.log_debug(f"sent body : {body}", title)
150 self.disp.log_debug(
151 f"header = {self.server_headers_initialised.for_json()}", title
152 )
153 outgoing = HCI.success(
154 content=body,
155 content_type=HTTP_DEFAULT_TYPE,
156 headers=self.server_headers_initialised.for_json()
157 )
158 self.disp.log_debug(f"ready_to_go: {outgoing}", title)
159 return outgoing
160
161 async def get_favicon(self, request: Request) -> Response:
162 """The endpoint to respond to the typical GET /favicon.ico path.
163
164 Args:
165 request (Request): The potential arguments passed in the emitted request.
166
167 Returns:
168 Response: The response to the emmiter.
169 """
170 icon = CONST.ICON_PATH
171 self.disp.log_debug(f"Favicon path: {icon}")
172 if os.path.isfile(icon):
173 return HCI.success(icon, content_type=HttpDataTypes.XICON)
174 return HCI.not_found("Icon not found in the expected directory", content_type=HttpDataTypes.TEXT)
175
176 async def get_static_logo(self, request: Request) -> Response:
177 """The endpoint to respond to the typical GET /static/logo.png path.
178
179 Args:
180 request (Request): The potential arguments passed in the emitted request.
181
182 Returns:
183 Response: The response to the emmiter.
184 """
185 icon = CONST.PNG_ICON_PATH
186 self.disp.log_debug(f"Static logo path: {icon}")
187 if os.path.isfile(icon):
188 return HCI.success(icon, content_type=HttpDataTypes.PNG)
189 return HCI.not_found("Icon not found in the expected directory", content_type=HttpDataTypes.TEXT)
190
191 async def post_stop_server(self, request: Request) -> Response:
192 """_summary_
193 The endpoint allowing a user to stop the server.
194
195 Returns:
196 Response: _description_: The data to send back to the user as a response.
197 """
198 title = "Stop server"
199 token = self.boilerplate_incoming_initialised.get_token_if_present(
200 request
201 )
202 if not token:
203 self.disp.log_error(
204 "Unauthenticated user tried to stop the server."
205 )
206 return self.boilerplate_responses_initialised.no_access_token(title=title, token=token)
207 if self.boilerplate_non_http_initialised.is_token_admin(token) is False:
208 self.disp.log_error(
209 "Non-admin user tried to stop the server.", title
210 )
211 body = self.boilerplate_responses_initialised.build_response_body(
212 title=title,
213 message="You do not have enough privileges to run this endpoint.",
214 resp="privilege to low",
215 token=token,
216 error=True
217 )
218 return HCI.unauthorized(content=body, content_type=HTTP_DEFAULT_TYPE, headers=self.server_headers_initialised.for_json())
219 body = self.boilerplate_responses_initialised.build_response_body(
220 title=title,
221 message="The server is stopping",
222 resp="success",
223 token=token,
224 error=False
225 )
226 self.disp.log_debug("Server shutting down...", f"{title}")
227 # Trigger graceful shutdown without signal capture
228 self.runtime_controls_initialised.graceful_shutdown()
229 status = self.background_tasks_initialised.safe_stop()
230 if status != self.success:
231 msg = "The server is stopping with errors, cron exited "
232 msg += f"with {status}."
233 self.disp.log_error(
234 msg,
235 "post_stop_server"
236 )
237 body = self.boilerplate_responses_initialised.build_response_body(
238 title=title,
239 message=msg,
240 resp="error",
241 token=token,
242 error=True
243 )
245 return HCI.internal_server_error(content=body, content_type=HTTP_DEFAULT_TYPE, headers=self.server_headers_initialised.for_json())
246 return HCI.success(content=body, content_type=HTTP_DEFAULT_TYPE, headers=self.server_headers_initialised.for_json())
247
248 async def get_health(self, request: Request) -> Response:
249 """Health check endpoint for container orchestration and monitoring.
250
251 Returns a simple 200 OK response to indicate the service is alive and responding.
252 Used by Docker healthchecks, Kubernetes probes, load balancers, etc.
253
254 Args:
255 request (Request): The incoming HTTP request.
256
257 Returns:
258 Response: HTTP 200 with status message.
259 """
260 body = self.boilerplate_responses_initialised.build_response_body(
261 title="Health Check",
262 message="Service is healthy",
263 resp="ok",
264 token=None,
265 error=False
266 )
267 return HCI.success(
268 content=body,
269 content_type=HTTP_DEFAULT_TYPE,
270 headers=self.server_headers_initialised.for_json()
271 )
272
273 # Dedicated beacon endpoint functions to avoid OpenAPI Operation ID conflicts
274 async def root_beacon_get(self, request: Request) -> Response:
275 """Root GET beacon endpoint."""
276 return await self.get_welcome(request)
277
278 async def root_beacon_post(self, request: Request) -> Response:
279 """Root POST beacon endpoint."""
280 return await self.get_welcome(request)
281
282 async def root_beacon_put(self, request: Request) -> Response:
283 """Root PUT beacon endpoint."""
284 return await self.get_welcome(request)
285
286 async def root_beacon_patch(self, request: Request) -> Response:
287 """Root PATCH beacon endpoint."""
288 return await self.get_welcome(request)
289
290 async def root_beacon_delete(self, request: Request) -> Response:
291 """Root DELETE beacon endpoint."""
292 return await self.get_welcome(request)
293
294 async def root_beacon_head(self, request: Request) -> Response:
295 """Root HEAD beacon endpoint."""
296 return await self.get_welcome(request)
297
298 async def root_beacon_options(self, request: Request) -> Response:
299 """Root OPTIONS beacon endpoint."""
300 return await self.get_welcome(request)
301
302 # Home beacon functions
303 async def home_beacon_get(self, request: Request) -> Response:
304 """Home GET beacon endpoint."""
305 return await self.get_root(request)
306
307 async def home_beacon_post(self, request: Request) -> Response:
308 """Home POST beacon endpoint."""
309 return await self.get_root(request)
310
311 async def home_beacon_put(self, request: Request) -> Response:
312 """Home PUT beacon endpoint."""
313 return await self.get_root(request)
314
315 async def home_beacon_patch(self, request: Request) -> Response:
316 """Home PATCH beacon endpoint."""
317 return await self.get_root(request)
318
319 async def home_beacon_delete(self, request: Request) -> Response:
320 """Home DELETE beacon endpoint."""
321 return await self.get_root(request)
322
323 async def home_beacon_head(self, request: Request) -> Response:
324 """Home HEAD beacon endpoint."""
325 return await self.get_root(request)
326
327 async def home_beacon_options(self, request: Request) -> Response:
328 """Home OPTIONS beacon endpoint."""
329 return await self.get_root(request)
330
331 # API v1 beacon functions
332 async def api_v1_beacon_get(self, request: Request) -> Response:
333 """API v1 GET beacon endpoint."""
334 return await self.get_api_v1(request)
335
336 async def api_v1_beacon_post(self, request: Request) -> Response:
337 """API v1 POST beacon endpoint."""
338 return await self.get_api_v1(request)
339
340 async def api_v1_beacon_put(self, request: Request) -> Response:
341 """API v1 PUT beacon endpoint."""
342 return await self.get_api_v1(request)
343
344 async def api_v1_beacon_patch(self, request: Request) -> Response:
345 """API v1 PATCH beacon endpoint."""
346 return await self.get_api_v1(request)
347
348 async def api_v1_beacon_delete(self, request: Request) -> Response:
349 """API v1 DELETE beacon endpoint."""
350 return await self.get_api_v1(request)
351
352 async def api_v1_beacon_head(self, request: Request) -> Response:
353 """API v1 HEAD beacon endpoint."""
354 return await self.get_api_v1(request)
355
356 async def api_v1_beacon_options(self, request: Request) -> Response:
357 """API v1 OPTIONS beacon endpoint."""
358 return await self.get_api_v1(request)
Response get_root(self, Request request)
Definition bonus.py:103
Response api_v1_beacon_head(self, Request request)
Definition bonus.py:352
Response get_static_logo(self, Request request)
Definition bonus.py:176
Response api_v1_beacon_patch(self, Request request)
Definition bonus.py:344
Response root_beacon_get(self, Request request)
Definition bonus.py:274
Response home_beacon_put(self, Request request)
Definition bonus.py:311
Response api_v1_beacon_delete(self, Request request)
Definition bonus.py:348
None __init__(self, int success=0, int error=84, bool debug=False)
Definition bonus.py:42
Response home_beacon_patch(self, Request request)
Definition bonus.py:315
Response api_v1_beacon_put(self, Request request)
Definition bonus.py:340
Response get_welcome(self, Request request)
Definition bonus.py:74
Response home_beacon_post(self, Request request)
Definition bonus.py:307
Response home_beacon_options(self, Request request)
Definition bonus.py:327
Response get_api_v1(self, Request request)
Definition bonus.py:132
Response post_stop_server(self, Request request)
Definition bonus.py:191
Response root_beacon_head(self, Request request)
Definition bonus.py:294
Response api_v1_beacon_get(self, Request request)
Definition bonus.py:332
Response api_v1_beacon_options(self, Request request)
Definition bonus.py:356
Response root_beacon_delete(self, Request request)
Definition bonus.py:290
Response home_beacon_get(self, Request request)
Definition bonus.py:303
Response root_beacon_patch(self, Request request)
Definition bonus.py:286
Response root_beacon_put(self, Request request)
Definition bonus.py:282
Response home_beacon_head(self, Request request)
Definition bonus.py:323
Response root_beacon_options(self, Request request)
Definition bonus.py:298
Response get_health(self, Request request)
Definition bonus.py:248
Response api_v1_beacon_post(self, Request request)
Definition bonus.py:336
Response home_beacon_delete(self, Request request)
Definition bonus.py:319
Response root_beacon_post(self, Request request)
Definition bonus.py:278
Response get_favicon(self, Request request)
Definition bonus.py:161