Ticket Refactor to accept ServerReply objects, and keep the internal flow returning Result objects
This commit is contained in:
parent
2208cedb7e
commit
af368db8b7
8 changed files with 642 additions and 342 deletions
|
|
@ -5,13 +5,14 @@ if TYPE_CHECKING:
|
||||||
from core.essentials.observers.ConnectionObserver import ConnectionObserver
|
from core.essentials.observers.ConnectionObserver import ConnectionObserver
|
||||||
from core.observers.TicketObserver import TicketObserver
|
from core.observers.TicketObserver import TicketObserver
|
||||||
|
|
||||||
|
from core.utils.type_checker import type_checker
|
||||||
from core.services.prepare_tickets.ticket_prep_orchestrator import ticket_prep_orchestrator
|
from core.services.prepare_tickets.ticket_prep_orchestrator import ticket_prep_orchestrator
|
||||||
from core.services.prepare_tickets.make_sure_pub_key_exists import make_sure_pub_key_exists
|
from core.services.prepare_tickets.make_sure_pub_key_exists import make_sure_pub_key_exists
|
||||||
from core.services.prepare_tickets import ticket_tracker
|
from core.services.prepare_tickets import ticket_tracker
|
||||||
from core.services.helpers.get_how_many_profiles_were_ordered import (
|
from core.services.helpers.get_how_many_profiles_were_ordered import (
|
||||||
get_how_many_profiles_were_ordered,
|
get_how_many_profiles_were_ordered,
|
||||||
)
|
)
|
||||||
from core.observers.BaseObserver import BaseObserver
|
# from core.observers.BaseObserver import BaseObserver
|
||||||
from core.models.Result import Result, ResultError
|
from core.models.Result import Result, ResultError
|
||||||
from core.errors.logger import logger
|
from core.errors.logger import logger
|
||||||
|
|
||||||
|
|
@ -33,13 +34,15 @@ If it doesn't have a config file, then it uses the temp billing id to get the pu
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Bulk Prep:
|
# Bulk Prep:
|
||||||
|
|
||||||
|
@type_checker
|
||||||
def prepare_tickets(
|
def prepare_tickets(
|
||||||
how_many_profiles: int,
|
how_many_profiles: int,
|
||||||
ticket_observer: TicketObserver,
|
ticket_observer: TicketObserver,
|
||||||
connection_observer: ConnectionObserver,
|
connection_observer: ConnectionObserver,
|
||||||
which_ticket: Optional[int] = None,
|
which_ticket: Optional[int] = None,
|
||||||
billing_code: Optional[int] = None
|
billing_code: Optional[int] = None
|
||||||
) -> dict:
|
) -> Result:
|
||||||
|
|
||||||
# these two are for preparation of a single ticket to a profile.
|
# these two are for preparation of a single ticket to a profile.
|
||||||
# can't have one blank without the other. (profile or ticket)
|
# can't have one blank without the other. (profile or ticket)
|
||||||
|
|
@ -82,33 +85,46 @@ def prepare_tickets(
|
||||||
billing_code=billing_code # only relevant for single ticket prep
|
billing_code=billing_code # only relevant for single ticket prep
|
||||||
)
|
)
|
||||||
|
|
||||||
# rest of this function is evaluating the results:
|
if prep_results.valid:
|
||||||
|
notification = "Done! All Tickets Ready!"
|
||||||
if prep_results == False or prep_results == None:
|
|
||||||
return {"valid": False, "message": "error"}
|
|
||||||
|
|
||||||
if "valid" not in prep_results:
|
|
||||||
return {"valid": False, "message": "error"}
|
|
||||||
|
|
||||||
if prep_results["valid"] == True:
|
|
||||||
notification = f"Done! All Tickets Ready!"
|
|
||||||
if ticket_observer is not None:
|
if ticket_observer is not None:
|
||||||
ticket_observer.notify("preparing", subject=notification)
|
ticket_observer.notify("preparing", subject=notification)
|
||||||
return prep_results
|
else:
|
||||||
|
notification = "Ticket Preparation Failed"
|
||||||
if "how_many_failed" in prep_results:
|
|
||||||
how_many_failed = prep_results.get("how_many_failed", 0)
|
|
||||||
failed_validations = prep_results.get("failed_validations", None)
|
|
||||||
if failed_validations:
|
|
||||||
notification = f"Error with Ticket Preparation or Verification!"
|
|
||||||
if ticket_observer is not None:
|
if ticket_observer is not None:
|
||||||
ticket_observer.notify("preparing", subject=notification)
|
ticket_observer.notify("preparing", subject=notification)
|
||||||
|
|
||||||
return prep_results
|
return prep_results
|
||||||
|
|
||||||
notification = f"Error with Ticket Preparation or Verification!"
|
|
||||||
if ticket_observer is not None:
|
|
||||||
ticket_observer.notify("preparing", subject=notification)
|
|
||||||
return prep_results
|
|
||||||
|
# if prep_results == False or prep_results == None:
|
||||||
|
# return {"valid": False, "message": "error"}
|
||||||
|
|
||||||
|
# if "valid" not in prep_results:
|
||||||
|
# return {"valid": False, "message": "error"}
|
||||||
|
|
||||||
|
# if prep_results["valid"] == True:
|
||||||
|
# notification = f"Done! All Tickets Ready!"
|
||||||
|
# if ticket_observer is not None:
|
||||||
|
# ticket_observer.notify("preparing", subject=notification)
|
||||||
|
# return prep_results
|
||||||
|
|
||||||
|
# if "how_many_failed" in prep_results:
|
||||||
|
# how_many_failed = prep_results.get("how_many_failed", 0)
|
||||||
|
# failed_validations = prep_results.get("failed_validations", None)
|
||||||
|
# if failed_validations:
|
||||||
|
# notification = f"Error with Ticket Preparation or Verification!"
|
||||||
|
# if ticket_observer is not None:
|
||||||
|
# ticket_observer.notify("preparing", subject=notification)
|
||||||
|
# return prep_results
|
||||||
|
|
||||||
|
# notification = f"Error with Ticket Preparation or Verification!"
|
||||||
|
# if ticket_observer is not None:
|
||||||
|
# ticket_observer.notify("preparing", subject=notification)
|
||||||
|
# return prep_results
|
||||||
|
|
||||||
|
|
||||||
# No profile required
|
# No profile required
|
||||||
|
|
@ -167,7 +183,7 @@ def respawn_billing_code_into_ticket(
|
||||||
valid = ticket_result.get('valid', False)
|
valid = ticket_result.get('valid', False)
|
||||||
|
|
||||||
if not valid:
|
if not valid:
|
||||||
notification = f"Error in Respawn Prep!"
|
notification = "Error in Respawn Prep!"
|
||||||
if ticket_observer is not None:
|
if ticket_observer is not None:
|
||||||
ticket_observer.notify("preparing", subject=notification)
|
ticket_observer.notify("preparing", subject=notification)
|
||||||
return prep_error_result(ticket_result)
|
return prep_error_result(ticket_result)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class ApiCall(Enum):
|
||||||
TRANSLATION_DICT = {
|
TRANSLATION_DICT = {
|
||||||
ServerError.WRONG_LOCATION: ResultError.WRONG_LOCATION,
|
ServerError.WRONG_LOCATION: ResultError.WRONG_LOCATION,
|
||||||
ServerError.NODE_ERROR: ResultError.WRONG_LOCATION,
|
ServerError.NODE_ERROR: ResultError.WRONG_LOCATION,
|
||||||
ServerError.BTCPAY: ResultError.SERVER_SIDE,
|
ServerError.CRYPTO_NODE: ResultError.SERVER_SIDE,
|
||||||
ServerError.INVALID: ResultError.INVALID_INPUT,
|
ServerError.INVALID: ResultError.INVALID_INPUT,
|
||||||
ServerError.MISSING_DATA: ResultError.INVALID_INPUT,
|
ServerError.MISSING_DATA: ResultError.INVALID_INPUT,
|
||||||
ServerError.UNPAID: ResultError.SUBSCRIPTION,
|
ServerError.UNPAID: ResultError.SUBSCRIPTION,
|
||||||
|
|
@ -71,7 +71,6 @@ def format_server_response(api_response: ApiResponse|dict, desired_type: str) ->
|
||||||
return Result(valid=False, error_type=ResultError.MISSING_DATA, message="Server Replied it was valid, but failed to provide the data.")
|
return Result(valid=False, error_type=ResultError.MISSING_DATA, message="Server Replied it was valid, but failed to provide the data.")
|
||||||
|
|
||||||
|
|
||||||
# return Result(valid=False, error_type=error_type, message=server_reply.message)
|
|
||||||
|
|
||||||
|
|
||||||
def translate_server_failure(server_reply: ServerReply) -> Result:
|
def translate_server_failure(server_reply: ServerReply) -> Result:
|
||||||
|
|
@ -89,12 +88,13 @@ def invalid_server_format(error_msg: Optional[str] = None) -> Result:
|
||||||
|
|
||||||
|
|
||||||
def api_call_and_extraction(
|
def api_call_and_extraction(
|
||||||
desired_type: str,
|
desired_type: Optional[str],
|
||||||
method: ApiCall,
|
method: ApiCall,
|
||||||
url: str,
|
url: str,
|
||||||
connection_observer: ConnectionObserver,
|
connection_observer: ConnectionObserver,
|
||||||
payload: dict,
|
payload: dict,
|
||||||
billing_code: Optional[str] = None
|
billing_code: Optional[str] = None,
|
||||||
|
return_server_reply: bool = False,
|
||||||
) -> Result:
|
) -> Result:
|
||||||
"""Do an API call, extract the data, and if there's an error, then translate the server's reply to a Result Object"""
|
"""Do an API call, extract the data, and if there's an error, then translate the server's reply to a Result Object"""
|
||||||
|
|
||||||
|
|
@ -152,6 +152,11 @@ def api_call_and_extraction(
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
return invalid_server_format()
|
return invalid_server_format()
|
||||||
|
|
||||||
|
# Opt into getting the ServerReply object (instead of Result object):
|
||||||
|
if return_server_reply and desired_type is None:
|
||||||
|
logger.info("Validation successfull. Returning a raw ServerReply object")
|
||||||
|
return server_reply
|
||||||
|
|
||||||
logger.info("Validation successfull. Checking if the server's reply says the operation worked..")
|
logger.info("Validation successfull. Checking if the server's reply says the operation worked..")
|
||||||
if not server_reply.valid:
|
if not server_reply.valid:
|
||||||
error_message = f"Server reply was correctly structured, but the actual invoice failed to be created. The raw data is {server_reply}"
|
error_message = f"Server reply was correctly structured, but the actual invoice failed to be created. The raw data is {server_reply}"
|
||||||
|
|
@ -169,12 +174,3 @@ def api_call_and_extraction(
|
||||||
data = getattr(server_reply, desired_type)
|
data = getattr(server_reply, desired_type)
|
||||||
logger.info(f"Successfully returning the data of {data}")
|
logger.info(f"Successfully returning the data of {data}")
|
||||||
return Result(valid=True, data=data)
|
return Result(valid=True, data=data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# else:
|
|
||||||
# error_msg = f"Server Replied it was valid, but failed to provide the data for the desired type of {desired_type}."
|
|
||||||
# logger.error(error_msg)
|
|
||||||
# return Result(valid=False, error_type=ResultError.MISSING_DATA, message=error_msg)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,11 @@ class ServerError(str, Enum):
|
||||||
QUANTITY = "quantity"
|
QUANTITY = "quantity"
|
||||||
UNPAID = "unpaid"
|
UNPAID = "unpaid"
|
||||||
INVALID = "invalid"
|
INVALID = "invalid"
|
||||||
|
ALREADY_USED = "already_used"
|
||||||
MISSING_DATA = "missing_data"
|
MISSING_DATA = "missing_data"
|
||||||
WRONG_LOCATION = "wrong_location"
|
WRONG_LOCATION = "wrong_location"
|
||||||
NODE_ERROR = "node_error"
|
NODE_ERROR = "node_error"
|
||||||
BTCPAY = "btcpay"
|
CRYPTO_NODE = "crypto_node"
|
||||||
SERVER_SIDE = "server_side"
|
SERVER_SIDE = "server_side"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -37,6 +38,8 @@ class ServerReply():
|
||||||
boolean_result: Optional[bool] = None
|
boolean_result: Optional[bool] = None
|
||||||
conf: Optional[str] = None
|
conf: Optional[str] = None
|
||||||
payment_status: Optional[bool] = None
|
payment_status: Optional[bool] = None
|
||||||
|
expires_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass_json
|
@dataclass_json
|
||||||
|
|
|
||||||
|
|
@ -4,22 +4,25 @@ from typing import TYPE_CHECKING
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from essentials.observers.ConnectionObserver import ConnectionObserver
|
from essentials.observers.ConnectionObserver import ConnectionObserver
|
||||||
from core.observers.TicketObserver import TicketObserver
|
from core.observers.TicketObserver import TicketObserver
|
||||||
|
|
||||||
# services
|
# services
|
||||||
from core.services.networking.httpx import connect
|
# from core.services.networking.httpx import connect
|
||||||
from core.services.networking.api_requests.ApiResponseModel import ApiResponse, ErrorType
|
# from core.services.networking.api_requests.ApiResponseModel import ApiResponse, ErrorType
|
||||||
from core.services.networking.make_url import make_url
|
from core.services.networking.make_url import make_url
|
||||||
# from core.services.payment_phase.ticket_config_tools import get_which_billing_key
|
# from core.services.payment_phase.ticket_config_tools import get_which_billing_key
|
||||||
|
|
||||||
# models & their controllers
|
# models & their controllers
|
||||||
from core.models.session.SessionProfile import SessionProfile
|
from core.models.manage.server_reply_tools import api_call_and_extraction, ApiCall, translate_server_failure
|
||||||
from core.models.system.SystemProfile import SystemProfile
|
from core.models.pydantic_models.ServerReply import ServerReply, ServerError
|
||||||
from core.models.Subscription import Subscription # not sure if needed
|
# from core.models.session.SessionProfile import SessionProfile
|
||||||
|
# from core.models.system.SystemProfile import SystemProfile
|
||||||
|
|
||||||
# utils
|
# utils
|
||||||
from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file
|
from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file
|
||||||
# constants & errors
|
# constants & errors
|
||||||
from core.Constants import Constants
|
from core.Constants import Constants
|
||||||
from core.errors.exceptions import InvalidData, ServerSideError
|
# from core.errors.exceptions import InvalidData, ServerSideError
|
||||||
|
from core.models.Result import Result, ResultError
|
||||||
from core.errors.logger import logger
|
from core.errors.logger import logger
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
@ -76,12 +79,24 @@ def prep_single_ticket_payload(billing_code: str, list_of_all_blinded_data: list
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def confirmed_data_result(blind_signatures: list) -> Result:
|
||||||
|
if (
|
||||||
|
blind_signatures == "error"
|
||||||
|
or blind_signatures == None
|
||||||
|
or blind_signatures == False
|
||||||
|
):
|
||||||
|
error_msg = f"The server's blind signature was blank or invalid. It said: {blind_signatures}"
|
||||||
|
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
|
||||||
|
|
||||||
|
return Result(valid=True, data=blind_signatures)
|
||||||
|
|
||||||
|
|
||||||
def send_blind_commitments(
|
def send_blind_commitments(
|
||||||
list_of_all_blinded_data: list,
|
list_of_all_blinded_data: list,
|
||||||
ticket_observer: TicketObserver,
|
ticket_observer: TicketObserver,
|
||||||
connection_observer: ConnectionObserver,
|
connection_observer: ConnectionObserver,
|
||||||
billing_code: Optional[int] = None
|
billing_code: Optional[int] = None
|
||||||
) -> list | None:
|
) -> Result:
|
||||||
"""
|
"""
|
||||||
Purpose:
|
Purpose:
|
||||||
1) Oversee payload prep
|
1) Oversee payload prep
|
||||||
|
|
@ -105,61 +120,121 @@ def send_blind_commitments(
|
||||||
|
|
||||||
# send it:
|
# send it:
|
||||||
url = make_url(which_endpoint)
|
url = make_url(which_endpoint)
|
||||||
try:
|
|
||||||
api_reply_object = connect.single_endpoint(
|
|
||||||
method="post",
|
server_reply = api_call_and_extraction(
|
||||||
|
desired_type=None, # get it all
|
||||||
|
method=ApiCall.POST,
|
||||||
url=url,
|
url=url,
|
||||||
observer=connection_observer,
|
connection_observer=connection_observer,
|
||||||
payload=payload
|
payload=payload,
|
||||||
|
return_server_reply=True # get it all
|
||||||
)
|
)
|
||||||
|
|
||||||
# # did API call work?
|
# this is a connection error or invalid format itself:
|
||||||
if not api_reply_object.valid:
|
if isinstance(server_reply, Result) or not isinstance(server_reply, ServerReply):
|
||||||
logger.error(f"[SEND BLIND COMMITMENTS] Second Post request failed. {api_reply_object.error_type} {api_reply_object.message}")
|
ticket_observer.notify("error", subject=server_reply.message)
|
||||||
ticket_observer.notify("error", subject=api_reply_object.message)
|
return server_reply
|
||||||
return None
|
|
||||||
|
|
||||||
# assuming it worked, extract the data:
|
# Worked:
|
||||||
reply = api_reply_object.data
|
if server_reply.valid:
|
||||||
|
return confirmed_data_result(blind_signatures=server_reply.signed_data)
|
||||||
|
|
||||||
# is it in valid format?
|
error_type = server_reply.error_type
|
||||||
if not isinstance(reply, dict):
|
message = server_reply.message
|
||||||
final_error_msg = f"API returned an invalid format {reply}"
|
ticket_observer.notify("error", subject=message)
|
||||||
logger.error(final_error_msg)
|
|
||||||
ticket_observer.notify("error", subject=final_error_msg)
|
|
||||||
return None
|
|
||||||
|
|
||||||
if reply.get("valid") == True:
|
logger.error(f"Send blind commitments failed. {str(error_type)} {message}")
|
||||||
# this 'signed_data' variable is a list,
|
|
||||||
signed_data = reply.get("signed_data")
|
|
||||||
# it's then sending it to the other functions,
|
|
||||||
# to iterate through, when they save and unblind,
|
|
||||||
return signed_data
|
|
||||||
|
|
||||||
# but what if it didn't work?
|
# WRONG QUANTITY:
|
||||||
elif reply.get("valid") == False:
|
if error_type == ServerError.QUANTITY:
|
||||||
error_msg = reply.get("message")
|
quantity_required = server_reply.quantity
|
||||||
error_msg_with_context = (
|
if not quantity_required or quantity_required < 1:
|
||||||
f"Issues with Signing. Server's error message said: {error_msg}"
|
error_msg = f"Server claims it got an invalid quantity, but yet it suggested to give it just {quantity_required} items."
|
||||||
|
ticket_observer.notify("error", subject=error_msg)
|
||||||
|
return Result(
|
||||||
|
valid=False,
|
||||||
|
error_type=ResultError.INVALID_INPUT,
|
||||||
|
message=error_msg
|
||||||
)
|
)
|
||||||
ticket_observer.notify("error", subject=error_msg_with_context)
|
|
||||||
return None
|
|
||||||
|
|
||||||
if reply == False:
|
logger.info(f"Retrying with a different size of {quantity_required}.")
|
||||||
error_msg = f"It did not send. There's an error with sending the data to server. The payload either was or was going to be: {payload}"
|
resized_list = list_of_all_blinded_data[:quantity_required]
|
||||||
ticket_observer.notify("error", subject=error_msg)
|
resized_payload = prep_payload_for_bulk(resized_list)
|
||||||
return None
|
second_server_reply = api_call_and_extraction(
|
||||||
else:
|
desired_type="signed_data",
|
||||||
error_msg = "The JSON returned from the server is not formatted correctly to even be able to figure why it did not work. This is a critical error. Contact customer support"
|
method=ApiCall.POST,
|
||||||
ticket_observer.notify("error", subject=error_msg)
|
url=url,
|
||||||
return None
|
connection_observer=connection_observer,
|
||||||
|
payload=resized_payload,
|
||||||
|
return_server_reply=False
|
||||||
|
)
|
||||||
|
if not second_server_reply.valid:
|
||||||
|
return second_server_reply
|
||||||
|
return confirmed_data_result(blind_signatures=server_reply.signed_data)
|
||||||
|
|
||||||
except InvalidData as e:
|
|
||||||
logger.error(f"InvalidData error: {e}", exc_info=True)
|
return translate_server_failure(server_reply=server_reply)
|
||||||
return None
|
|
||||||
except ServerSideError as e:
|
|
||||||
logger.error(f"ServerSideError: {e}", exc_info=True)
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Generic Python Error {e}", exc_info=True)
|
# # # did API call work?
|
||||||
return None
|
# if not api_reply_object.valid:
|
||||||
|
# ticket_observer.notify("error", subject=api_reply_object.message)
|
||||||
|
# return None
|
||||||
|
|
||||||
|
# # assuming it worked, extract the data:
|
||||||
|
# reply = api_reply_object.data
|
||||||
|
|
||||||
|
# # is it in valid format?
|
||||||
|
# if not isinstance(reply, dict):
|
||||||
|
# final_error_msg = f"API returned an invalid format {reply}"
|
||||||
|
# logger.error(final_error_msg)
|
||||||
|
# ticket_observer.notify("error", subject=final_error_msg)
|
||||||
|
# return None
|
||||||
|
|
||||||
|
# if reply.get("valid") == True:
|
||||||
|
# # this 'signed_data' variable is a list,
|
||||||
|
# signed_data = reply.get("signed_data")
|
||||||
|
# # it's then sending it to the other functions,
|
||||||
|
# # to iterate through, when they save and unblind,
|
||||||
|
# return signed_data
|
||||||
|
|
||||||
|
# # but what if it didn't work?
|
||||||
|
# elif reply.get("valid") == False:
|
||||||
|
# error_msg = reply.get("message")
|
||||||
|
# error_msg_with_context = (
|
||||||
|
# f"Issues with Signing. Server's error message said: {error_msg}"
|
||||||
|
# )
|
||||||
|
# ticket_observer.notify("error", subject=error_msg_with_context)
|
||||||
|
# return None
|
||||||
|
|
||||||
|
# if reply == False:
|
||||||
|
# error_msg = f"It did not send. There's an error with sending the data to server. The payload either was or was going to be: {payload}"
|
||||||
|
# ticket_observer.notify("error", subject=error_msg)
|
||||||
|
# return None
|
||||||
|
# else:
|
||||||
|
# error_msg = "The JSON returned from the server is not formatted correctly to even be able to figure why it did not work. This is a critical error. Contact customer support"
|
||||||
|
# ticket_observer.notify("error", subject=error_msg)
|
||||||
|
# return None
|
||||||
|
|
||||||
|
|
||||||
|
# except InvalidData as e:
|
||||||
|
# logger.error(f"InvalidData error: {e}", exc_info=True)
|
||||||
|
# return None
|
||||||
|
# except ServerSideError as e:
|
||||||
|
# logger.error(f"ServerSideError: {e}", exc_info=True)
|
||||||
|
# return None
|
||||||
|
# except Exception as e:
|
||||||
|
# logger.error(f"Generic Python Error {e}", exc_info=True)
|
||||||
|
# return None
|
||||||
|
|
||||||
|
|
||||||
|
# api_reply_object = connect.single_endpoint(
|
||||||
|
# method="post",
|
||||||
|
# url=url,
|
||||||
|
# observer=connection_observer,
|
||||||
|
# payload=payload
|
||||||
|
# )
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,20 @@ from typing import TYPE_CHECKING
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.observers.TicketObserver import TicketObserver
|
from core.observers.TicketObserver import TicketObserver
|
||||||
from essentials.observers.ConnectionObserver import ConnectionObserver
|
from essentials.observers.ConnectionObserver import ConnectionObserver
|
||||||
# services
|
|
||||||
|
|
||||||
from core.services.crypto.make_commitments import make_ALL_commitments
|
from core.services.crypto.make_commitments import make_ALL_commitments
|
||||||
from core.services.prepare_tickets.unblind_all_tickets import unblind_ALL_tickets
|
from core.services.prepare_tickets.unblind_all_tickets import unblind_ALL_tickets
|
||||||
from core.services.prepare_tickets.setup_ticket_tracker import setup_ticket_tracker
|
from core.services.prepare_tickets.setup_ticket_tracker import setup_ticket_tracker
|
||||||
from core.services.prepare_tickets import ticket_tracker
|
# from core.services.prepare_tickets import ticket_tracker
|
||||||
from core.services.prepare_tickets.send_blind_commitments import send_blind_commitments
|
from core.services.prepare_tickets.send_blind_commitments import send_blind_commitments
|
||||||
from core.services.prepare_tickets.validate_blind_signatures import validate_blind_signatures
|
from core.services.prepare_tickets.validate_blind_signatures import validate_blind_signatures
|
||||||
from core.services.prepare_tickets.save_ALL_blind_sigs import save_ALL_blind_sigs
|
from core.services.prepare_tickets.save_ALL_blind_sigs import save_ALL_blind_sigs
|
||||||
from core.utils.basic_operations.does_file_exist import does_file_exist
|
from core.utils.basic_operations.does_file_exist import does_file_exist
|
||||||
from core.controllers.ConfigurationController import ConfigurationController
|
from core.controllers.ConfigurationController import ConfigurationController
|
||||||
|
from core.utils.type_checker import type_checker
|
||||||
# errors & constants
|
from core.models.Result import Result, ResultError
|
||||||
from core.Constants import Constants
|
from core.Constants import Constants
|
||||||
from core.errors.exceptions import MissingData, CriticalFailure
|
|
||||||
from core.errors.logger import logger
|
from core.errors.logger import logger
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
@ -34,50 +34,39 @@ TicketPrepController's respawn_billing_code_into_ticket
|
||||||
with which_ticket and billing_code. (and how_many_profiles is one)
|
with which_ticket and billing_code. (and how_many_profiles is one)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
REJECTED_INPUTS = [False, None, "error"]
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Called by:
|
||||||
|
TicketPrepController's prepare_tickets = For Bulk Tickets
|
||||||
|
TicketPrepController's respawn_billing_code_into_ticket = For a Single Ticket
|
||||||
|
"""
|
||||||
|
|
||||||
|
@type_checker
|
||||||
def ticket_prep_orchestrator(
|
def ticket_prep_orchestrator(
|
||||||
how_many_profiles: int,
|
how_many_profiles: int,
|
||||||
ticket_observer: TicketObserver,
|
ticket_observer: TicketObserver,
|
||||||
connection_observer: ConnectionObserver,
|
connection_observer: ConnectionObserver,
|
||||||
which_ticket: Optional[int] = None,
|
which_ticket: Optional[int] = None,
|
||||||
billing_code: Optional[int] = None
|
billing_code: Optional[int] = None
|
||||||
) -> dict:
|
) -> Result:
|
||||||
"""
|
"""Orchestrate the entire ticket preparation operation."""
|
||||||
Purpose:
|
|
||||||
Orchestrate the entire ticket preparation operation.
|
|
||||||
|
|
||||||
Rank:
|
|
||||||
Feature's Primary Cross-Module Orchestrator
|
|
||||||
|
|
||||||
Called by:
|
|
||||||
TicketPrepController's prepare_tickets = For Bulk Tickets
|
|
||||||
TicketPrepController's respawn_billing_code_into_ticket = For a Single Ticket
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dicts. Shouldn't raise errors on it's own.
|
|
||||||
"""
|
|
||||||
if not billing_code and which_ticket:
|
if not billing_code and which_ticket:
|
||||||
logger.error("Can't have no billing_code, if you have a ticket to replace")
|
error_msg = "Can't have no billing_code, if you have a ticket to replace"
|
||||||
return {
|
logger.error(error_msg)
|
||||||
"valid": False,
|
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
|
||||||
"message": "missing_data",
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
#####################################################
|
#####################################################
|
||||||
# MAKE BLIND COMMITMENTS
|
# MAKE BLIND COMMITMENTS
|
||||||
#####################################################
|
#####################################################
|
||||||
# make all commitments, but get the blinded ones:
|
# make all commitments, but get the blinded ones:
|
||||||
list_of_all_blinded_commitments = make_ALL_commitments(how_many_profiles, which_ticket)
|
list_of_all_blinded_commitments = make_ALL_commitments(how_many_profiles, which_ticket)
|
||||||
|
|
||||||
# did we actually save them? if not, try again,
|
if not list_of_all_blinded_commitments:
|
||||||
if list_of_all_blinded_commitments is None:
|
error_msg = "We failed to make and save all the blinded commitments"
|
||||||
list_of_all_blinded_commitments = make_ALL_commitments(how_many_profiles, which_ticket)
|
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
|
||||||
# still failed to save?!
|
|
||||||
if list_of_all_blinded_commitments is None:
|
|
||||||
return {
|
|
||||||
"valid": False,
|
|
||||||
"message": "cant_save",
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################
|
#####################################################
|
||||||
# SEND BLIND TO GET SIGNED
|
# SEND BLIND TO GET SIGNED
|
||||||
|
|
@ -88,23 +77,16 @@ def ticket_prep_orchestrator(
|
||||||
ticket_observer.notify("preparing", subject=notification)
|
ticket_observer.notify("preparing", subject=notification)
|
||||||
|
|
||||||
# then send the entire blinded list to the server to sign:
|
# then send the entire blinded list to the server to sign:
|
||||||
blind_signatures = send_blind_commitments(
|
bling_sig_result = send_blind_commitments(
|
||||||
list_of_all_blinded_commitments, ticket_observer, connection_observer, billing_code
|
list_of_all_blinded_commitments, ticket_observer, connection_observer, billing_code
|
||||||
)
|
)
|
||||||
|
|
||||||
# Recieve signatures:
|
if not bling_sig_result.valid:
|
||||||
if (
|
return bling_sig_result
|
||||||
blind_signatures == "error"
|
|
||||||
or blind_signatures == None
|
# Recieve signatures:
|
||||||
or blind_signatures == False
|
blind_signatures = bling_sig_result.data
|
||||||
):
|
|
||||||
return {
|
|
||||||
"valid": False,
|
|
||||||
"message": f"The server's blind signature was blank or invalid. It said: {blind_signatures}",
|
|
||||||
"error_code": "invalid_reply"
|
|
||||||
}
|
|
||||||
|
|
||||||
else:
|
|
||||||
#####################################################
|
#####################################################
|
||||||
# EVALUATE AND SAVE THE SERVER'S REPLIES
|
# EVALUATE AND SAVE THE SERVER'S REPLIES
|
||||||
#####################################################
|
#####################################################
|
||||||
|
|
@ -113,7 +95,14 @@ def ticket_prep_orchestrator(
|
||||||
if ticket_observer is not None:
|
if ticket_observer is not None:
|
||||||
ticket_observer.notify("preparing", subject=notification)
|
ticket_observer.notify("preparing", subject=notification)
|
||||||
did_they_ALL_save = save_ALL_blind_sigs(blind_signatures, which_ticket)
|
did_they_ALL_save = save_ALL_blind_sigs(blind_signatures, which_ticket)
|
||||||
|
if not did_they_ALL_save:
|
||||||
|
error_msg = "Failed to save the blind signatures locally! We continue with it in memory.."
|
||||||
|
logger.error(error_msg)
|
||||||
|
ticket_observer.notify("preparing", subject=error_msg)
|
||||||
|
|
||||||
|
#####################################################
|
||||||
|
# VERIFY BLIND SIGS
|
||||||
|
#####################################################
|
||||||
# verify the server's blind signatures against the public key,
|
# verify the server's blind signatures against the public key,
|
||||||
notification = "Evaluating the Server's Blind Replies"
|
notification = "Evaluating the Server's Blind Replies"
|
||||||
ticket_observer.notify("preparing", subject=notification)
|
ticket_observer.notify("preparing", subject=notification)
|
||||||
|
|
@ -123,27 +112,36 @@ def ticket_prep_orchestrator(
|
||||||
|
|
||||||
logger.debug(f"failed_validations is {failed_validations}")
|
logger.debug(f"failed_validations is {failed_validations}")
|
||||||
|
|
||||||
|
#####################################################
|
||||||
|
# FAILED BLIND SIG VERIFICATION
|
||||||
|
#####################################################
|
||||||
# did verification of any of the blind sigs fail?
|
# did verification of any of the blind sigs fail?
|
||||||
how_many_failed = len(failed_validations)
|
how_many_failed = len(failed_validations)
|
||||||
if how_many_failed >= 1:
|
if how_many_failed >= 1:
|
||||||
notification = f"Verification failed for {how_many_failed} blind signatures."
|
notification = f"Verification failed for {how_many_failed} blind signatures. You still have to unblind them but its paused for the error."
|
||||||
if ticket_observer is not None:
|
if ticket_observer is not None:
|
||||||
ticket_observer.notify("preparing", subject=notification)
|
ticket_observer.notify("preparing", subject=notification)
|
||||||
logger.debug(
|
logger.error(notification)
|
||||||
f"Verification failed for {how_many_failed} blind signatures."
|
return Result(
|
||||||
|
valid=False,
|
||||||
|
error_type=ResultError.MATH,
|
||||||
|
data=failed_validations,
|
||||||
|
message=notification
|
||||||
)
|
)
|
||||||
return {
|
|
||||||
"valid": False,
|
#####################################################
|
||||||
"message": "verification_failed",
|
# PAID
|
||||||
"how_many_failed": how_many_failed,
|
#####################################################
|
||||||
"failed_validations": failed_validations,
|
# saving on config that it's paid:
|
||||||
}
|
config_marked = ConfigurationController.mark_as_paid(True)
|
||||||
|
if not config_marked:
|
||||||
|
return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message="Failed to mark as paid in the config. You still have the tickets locally, just can't use them till this file system error is resolved.")
|
||||||
|
|
||||||
#####################################################
|
#####################################################
|
||||||
# UNBLIND
|
# UNBLIND
|
||||||
#####################################################
|
#####################################################
|
||||||
# Unblind the signatures & combine with unblinded commitment:
|
# Unblind the signatures & combine with unblinded commitment:
|
||||||
notification = f"Unblinding Signatures & Preparing Tickets..."
|
notification = "Unblinding Signatures & Preparing Tickets..."
|
||||||
ticket_observer.notify("preparing", subject=notification)
|
ticket_observer.notify("preparing", subject=notification)
|
||||||
did_prep_work = unblind_ALL_tickets(
|
did_prep_work = unblind_ALL_tickets(
|
||||||
ALL_signed_blind_signatures=blind_signatures,
|
ALL_signed_blind_signatures=blind_signatures,
|
||||||
|
|
@ -158,12 +156,7 @@ def ticket_prep_orchestrator(
|
||||||
if not does_file_exist(Constants.TICKET_TRACKER_PATH):
|
if not does_file_exist(Constants.TICKET_TRACKER_PATH):
|
||||||
setup_tracker = setup_ticket_tracker(how_many_profiles)
|
setup_tracker = setup_ticket_tracker(how_many_profiles)
|
||||||
if not setup_tracker:
|
if not setup_tracker:
|
||||||
return {"valid": False, "message": "failed"}
|
return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message="Failed to setup the ticket tracker. You still have the tickets locally, just can't use them till this file system error is resolved.")
|
||||||
|
|
||||||
# saving on config that it's setup:
|
|
||||||
config_marked = ConfigurationController.mark_as_paid(True)
|
|
||||||
if not config_marked:
|
|
||||||
return {'valid': False, 'message': 'file_system'}
|
|
||||||
|
|
||||||
#####################################################
|
#####################################################
|
||||||
# FINAL EVALUATION
|
# FINAL EVALUATION
|
||||||
|
|
@ -171,19 +164,29 @@ def ticket_prep_orchestrator(
|
||||||
if did_prep_work:
|
if did_prep_work:
|
||||||
# this means it unblinded, & setup the tracker without erroring out,
|
# this means it unblinded, & setup the tracker without erroring out,
|
||||||
# but it does NOT mean that verification of the blind sigs worked.
|
# but it does NOT mean that verification of the blind sigs worked.
|
||||||
return {"valid": True, "message": "worked"}
|
ConfigurationController.tickets_setup(True)
|
||||||
else:
|
return Result(valid=True, message="Setup worked.")
|
||||||
return {"valid": False, "message": "failed"}
|
|
||||||
|
|
||||||
except CriticalFailure as e:
|
|
||||||
error_msg = f"CriticalFailure error: {e}"
|
else:
|
||||||
logger.error(error_msg, exc_info=True)
|
error_msg = "You have the blinded data, but we were unable to unblind it."
|
||||||
return {"valid": False, "message": error_msg}
|
logger.error(error_msg)
|
||||||
except MissingData as e:
|
return Result(valid=False, error_type=ResultError.MATH, message=error_msg)
|
||||||
error_msg = f"MissingData: {e}"
|
|
||||||
logger.error(error_msg, exc_info=True)
|
|
||||||
return {"valid": False, "message": error_msg}
|
|
||||||
except:
|
|
||||||
error_msg = f"Generic Python Error"
|
|
||||||
logger.error(error_msg, exc_info=True)
|
|
||||||
return {"valid": False, "message": error_msg}
|
# except CriticalFailure as e:
|
||||||
|
# error_msg = f"CriticalFailure error: {e}"
|
||||||
|
# logger.error(error_msg, exc_info=True)
|
||||||
|
# return {"valid": False, "message": error_msg}
|
||||||
|
# except MissingData as e:
|
||||||
|
# error_msg = f"MissingData: {e}"
|
||||||
|
# logger.error(error_msg, exc_info=True)
|
||||||
|
# return {"valid": False, "message": error_msg}
|
||||||
|
# except:
|
||||||
|
# error_msg = f"Generic Python Error"
|
||||||
|
# logger.error(error_msg, exc_info=True)
|
||||||
|
# return {"valid": False, "message": error_msg}
|
||||||
|
|
|
||||||
|
|
@ -314,6 +314,7 @@ def destroy_profile(
|
||||||
# RESPAWN TICKET
|
# RESPAWN TICKET
|
||||||
####################################
|
####################################
|
||||||
which_ticket = profile.ticket
|
which_ticket = profile.ticket
|
||||||
|
if which_ticket:
|
||||||
respawn_result = respawn_profile(
|
respawn_result = respawn_profile(
|
||||||
profile=profile,
|
profile=profile,
|
||||||
ticket_observer=ticket_observer,
|
ticket_observer=ticket_observer,
|
||||||
|
|
|
||||||
|
|
@ -573,7 +573,7 @@ def wipe_subscription(profile_id: int) -> Result:
|
||||||
|
|
||||||
|
|
||||||
@type_checker
|
@type_checker
|
||||||
def save_billing_code(profile_id: int, billing_code: str, expires_at: datetime) -> Result:
|
def save_billing_code(profile_id: int, billing_code: str, expires_at: datetime, which_ticket: Optional[int] = None) -> Result:
|
||||||
"""Checks if the billing code is already saved, and if not, saves it."""
|
"""Checks if the billing code is already saved, and if not, saves it."""
|
||||||
|
|
||||||
logger.info("Saving billing code..")
|
logger.info("Saving billing code..")
|
||||||
|
|
@ -597,6 +597,8 @@ def save_billing_code(profile_id: int, billing_code: str, expires_at: datetime)
|
||||||
expires_at=expires_at,
|
expires_at=expires_at,
|
||||||
billing_pending=True
|
billing_pending=True
|
||||||
)
|
)
|
||||||
|
if which_ticket:
|
||||||
|
profile.ticket = which_ticket
|
||||||
ProfileController.update(profile)
|
ProfileController.update(profile)
|
||||||
return Result(valid=True)
|
return Result(valid=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -3,135 +3,339 @@ from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from essentials.observers.ConnectionObserver import ConnectionObserver
|
from essentials.observers.ConnectionObserver import ConnectionObserver
|
||||||
##############
|
|
||||||
from core.services.using_tickets.send_unblinded import send_unblinded_ticket_to_server
|
from core.services.subscriptions.subscriptions_by_protocol import save_billing_code
|
||||||
from core.utils.basic_operations.write_string_to_text_file import write_string_to_text_file
|
from core.utils.get_raw_string import get_raw_string
|
||||||
from core.services.prepare_tickets.ticket_tracker import update_ticket_tracker
|
from core.observers.TicketObserver import TicketObserver
|
||||||
from core.models.Subscription import Subscription
|
from core.services.helpers.does_ticket_file_exist import does_ticket_file_exist
|
||||||
|
from core.services.prepare_tickets.ticket_tracker import does_ticket_tracker_exist
|
||||||
|
from core.models.pydantic_models.ServerReply import ServerReply, ServerError
|
||||||
|
from core.services.payment_phase.payment_state import TicketPaymentData
|
||||||
|
from core.services.networking.make_url import make_url
|
||||||
|
from core.models.manage.server_reply_tools import api_call_and_extraction, ApiCall, translate_server_failure
|
||||||
from core.models.Result import Result, ResultError
|
from core.models.Result import Result, ResultError
|
||||||
from core.errors.logger import logger
|
from core.errors.logger import logger
|
||||||
from core.Constants import Constants
|
|
||||||
from core.models.enum_models import ProtocolChoice
|
from core.models.enum_models import ProtocolChoice
|
||||||
|
from core.utils.type_checker import type_checker
|
||||||
|
from core.services.prepare_tickets.ticket_tracker import update_ticket_tracker
|
||||||
|
from core.controllers.ProfileController import ProfileController
|
||||||
|
|
||||||
|
# from core.models.BaseProfile import BaseProfile as Profile
|
||||||
|
# from core.services.using_tickets.send_unblinded import send_unblinded_ticket_to_server
|
||||||
|
# from core.models.Subscription import Subscription
|
||||||
|
# from core.utils.basic_operations.write_string_to_text_file import write_string_to_text_file
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
CUSTOMER_SERVICE_MSG = "Please speak with customer service."
|
CUSTOMER_SERVICE_MSG = "Please speak with customer service."
|
||||||
|
|
||||||
def use_ticket_orchestrator(
|
@type_checker
|
||||||
which_ticket: int,
|
def save_ticket_use(
|
||||||
which_location: int, # Location.id
|
profile_id: int,
|
||||||
protocol_choice: ProtocolChoice,
|
billing_code: str,
|
||||||
connection_observer: ConnectionObserver,
|
expires_at: datetime,
|
||||||
|
location_id: int,
|
||||||
|
which_ticket: int
|
||||||
) -> Result:
|
) -> Result:
|
||||||
"""
|
|
||||||
Purpose:
|
|
||||||
Coordinates using a ticket.
|
|
||||||
Rank:
|
|
||||||
Orchestrator
|
|
||||||
Method:
|
|
||||||
1) Send to the server
|
|
||||||
2) Evaluate the response
|
|
||||||
3) updates the 'ticker_tracker' JSON with new data
|
|
||||||
4) Formats error messages for UI
|
|
||||||
Called by:
|
|
||||||
UseTicketController
|
|
||||||
Returns:
|
|
||||||
Return Objects
|
|
||||||
do NOT raise errors
|
|
||||||
"""
|
|
||||||
|
|
||||||
###################################
|
saved_for_profile = save_billing_code(
|
||||||
# SEND TO THE SERVER
|
profile_id=profile_id,
|
||||||
###################################
|
billing_code=billing_code,
|
||||||
api_reply_object = send_unblinded_ticket_to_server(
|
expires_at=expires_at,
|
||||||
which_ticket, which_location, protocol_choice, connection_observer
|
which_ticket=which_ticket
|
||||||
)
|
)
|
||||||
|
|
||||||
# NETWORK ERROR.
|
|
||||||
if not api_reply_object.valid:
|
|
||||||
return Result(valid=False, error_type=ResultError.CONNECTION, message=f"Could not establish a connection to Ticketing Server. {api_reply_object.message}.")
|
|
||||||
|
|
||||||
###################################
|
|
||||||
# VERIFY DATA STRUCTURE
|
|
||||||
###################################
|
|
||||||
raw_data_dict = api_reply_object.data
|
|
||||||
|
|
||||||
# INVALID STRUCTURE:
|
|
||||||
if not isinstance(raw_data_dict, dict):
|
|
||||||
error_msg = f"The Ticketing Server gave an invalid data format {type(raw_data_dict)} in it's reply."
|
|
||||||
logger.error(f"{error_msg} with raw value of {raw_data_dict}.")
|
|
||||||
return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message=f"{error_msg} {CUSTOMER_SERVICE_MSG}")
|
|
||||||
|
|
||||||
if "valid" not in raw_data_dict:
|
|
||||||
error_msg = "The Ticketing Server gave an invalid data format that won't even tell us if it's valid or not."
|
|
||||||
logger.error(f"{error_msg} with a raw value of {raw_data_dict}")
|
|
||||||
return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message=f"{error_msg} {CUSTOMER_SERVICE_MSG}")
|
|
||||||
|
|
||||||
logger.debug("If we made it to this point, the API server connected and gave a correctly structured reply.")
|
|
||||||
|
|
||||||
###################################
|
|
||||||
# EXTRACT & ACT
|
|
||||||
###################################
|
|
||||||
valid = raw_data_dict.get("valid", None)
|
|
||||||
billing_code = raw_data_dict.get("billing_code", None)
|
|
||||||
expires_at = raw_data_dict.get("expires_at", None)
|
|
||||||
message = raw_data_dict.get("message", None)
|
|
||||||
|
|
||||||
logger.info(f"Was the ticket use valid? {valid}")
|
|
||||||
|
|
||||||
# GOOD OR ALREADY USED
|
|
||||||
if valid or message == "already_used":
|
|
||||||
logger.info("This is a valid use, or a previously valid use that's still good.")
|
|
||||||
|
|
||||||
# update the ticket tracker:
|
# update the ticket tracker:
|
||||||
saved = update_ticket_tracker(
|
saved_ticket_tracker = update_ticket_tracker(
|
||||||
which_ticket=which_ticket,
|
which_ticket=which_ticket,
|
||||||
status="used",
|
status="used",
|
||||||
location=which_location,
|
location=location_id,
|
||||||
subscription=billing_code
|
subscription=billing_code
|
||||||
)
|
)
|
||||||
|
|
||||||
# Emergency:
|
if not saved_for_profile.valid:
|
||||||
if not saved:
|
return saved_for_profile # result object
|
||||||
emergency_save_billing(
|
|
||||||
which_ticket=which_ticket,
|
if saved_ticket_tracker:
|
||||||
billing_code=billing_code
|
return Result(valid=True)
|
||||||
|
else:
|
||||||
|
return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message="Unable to update the ticket tracker that the ticket is used.")
|
||||||
|
|
||||||
|
|
||||||
|
def make_payload(which_ticket: int, which_location: int, protocol_choice: ProtocolChoice) -> Result:
|
||||||
|
"""Prepare the JSON to send to the billing server"""
|
||||||
|
# ticket
|
||||||
|
ticket_data = get_raw_string(str(which_ticket), "unblinded_final_ticket")
|
||||||
|
|
||||||
|
# key
|
||||||
|
which_key = TicketPaymentData().which_key()
|
||||||
|
if not which_key:
|
||||||
|
return Result(valid=False, error_type=ResultError.MISSING_DATA, message="Which ticket billing key is missing from your config file. Did you wipe it?")
|
||||||
|
|
||||||
|
# prep the JSON payload:
|
||||||
|
payload = {
|
||||||
|
"which_key": which_key,
|
||||||
|
"which_location": which_location,
|
||||||
|
"protocol_choice": protocol_choice.value,
|
||||||
|
"ticket_data": ticket_data,
|
||||||
|
}
|
||||||
|
return Result(valid=True, data=payload)
|
||||||
|
|
||||||
|
|
||||||
|
@type_checker
|
||||||
|
def use_ticket_orchestrator(
|
||||||
|
profile_id: int,
|
||||||
|
which_ticket: int,
|
||||||
|
ticket_observer: TicketObserver,
|
||||||
|
connection_observer: ConnectionObserver,
|
||||||
|
) -> Result:
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# PROFILE DATA
|
||||||
|
###################################
|
||||||
|
profile = ProfileController.get(profile_id)
|
||||||
|
if not profile:
|
||||||
|
error_msg = f"Invalid profile id of {profile_id}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
return Result(valid=False, message=error_msg, error_type=ResultError.INVALID_INPUT)
|
||||||
|
|
||||||
|
if profile.ticket:
|
||||||
|
error_msg = f"This profile already has a ticket of {profile.ticket}."
|
||||||
|
logger.error(error_msg)
|
||||||
|
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
|
||||||
|
|
||||||
|
location_id = profile.location.id
|
||||||
|
protocol_choice = ProtocolChoice(profile.connection.code)
|
||||||
|
PRE_REQS = [profile_id, location_id, protocol_choice, which_ticket]
|
||||||
|
if None in PRE_REQS:
|
||||||
|
return Result(valid=False, error_type=ResultError.MISSING_DATA, message="You are missing the profile data to use a ticket with.")
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# FILTER TICKET DATA
|
||||||
|
###################################
|
||||||
|
# does the ticket's file exist:
|
||||||
|
ticket_exists = does_ticket_file_exist(which_ticket)
|
||||||
|
if ticket_exists == False:
|
||||||
|
error_msg = "The ticket file does not exist in the correct folder."
|
||||||
|
ticket_observer.notify("failed_input", subject=error_msg)
|
||||||
|
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
|
||||||
|
|
||||||
|
# does the file keeping track of ALL tickets exist:
|
||||||
|
does_the_file_exist, path_of_file = does_ticket_tracker_exist()
|
||||||
|
if does_the_file_exist == False:
|
||||||
|
error_msg = f"The ticket tracker organizer file does not exist. Check the folder {path_of_file}"
|
||||||
|
ticket_observer.notify("failed_input", subject=error_msg)
|
||||||
|
return Result(valid=False, error_type=ResultError.MISSING_FILE, message=error_msg)
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# PREP PAYLOAD
|
||||||
|
###################################
|
||||||
|
payload_result = make_payload(which_ticket=which_ticket, protocol_choice=protocol_choice)
|
||||||
|
if not payload_result.valid:
|
||||||
|
return payload_result
|
||||||
|
payload = payload_result.data
|
||||||
|
logger.info("Payload ready.")
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# SEND TO API
|
||||||
|
###################################
|
||||||
|
which_endpoint = "validate"
|
||||||
|
url = make_url(which_endpoint)
|
||||||
|
|
||||||
|
ticket_observer.notify("connecting", "Connecting..")
|
||||||
|
server_reply = api_call_and_extraction(
|
||||||
|
desired_type=None,
|
||||||
|
method=ApiCall.POST,
|
||||||
|
url=url,
|
||||||
|
connection_observer=connection_observer,
|
||||||
|
payload=payload,
|
||||||
|
return_server_reply=True
|
||||||
)
|
)
|
||||||
|
|
||||||
subscription = Subscription(
|
# this is a connection error or invalid format itself:
|
||||||
|
if isinstance(server_reply, Result) or not isinstance(server_reply, ServerReply):
|
||||||
|
ticket_observer.notify("error", subject=server_reply.message)
|
||||||
|
return server_reply
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# SUCCESS
|
||||||
|
###################################
|
||||||
|
if server_reply.valid or server_reply.error_type == ServerError.ALREADY_USED:
|
||||||
|
billing_code = server_reply.billing_code
|
||||||
|
expires_at = server_reply.expires_at
|
||||||
|
|
||||||
|
if not billing_code or not expires_at:
|
||||||
|
return Result(valid=False, error_type=ResultError.SERVER_SIDE, message="Server Replied it worked, but failed to provide a valid code.")
|
||||||
|
|
||||||
|
return save_ticket_use(
|
||||||
|
profile_id=profile_id,
|
||||||
billing_code=billing_code,
|
billing_code=billing_code,
|
||||||
expires_at=expires_at,
|
expires_at=expires_at,
|
||||||
|
location_id=location_id,
|
||||||
|
which_ticket=which_ticket
|
||||||
)
|
)
|
||||||
|
|
||||||
# even if not saved return it back, so it can be saved with the profile by other functions,
|
###################################
|
||||||
return Result(valid=True, data=subscription)
|
|
||||||
|
|
||||||
# EXPIRED
|
# EXPIRED
|
||||||
if not valid and message == "expired":
|
###################################
|
||||||
logger.error(f"Expired Ticket! Wiping slot {which_ticket}")
|
if server_reply.error_type == ServerError.EXPIRED:
|
||||||
saved = update_ticket_tracker(
|
error_msg = "This ticket is expired. Do you want to wipe ALL your tickets? To start over."
|
||||||
which_ticket=which_ticket,
|
return Result(valid=False, error_type=ResultError.TICKET_EXPIRED, message=error_msg)
|
||||||
status="unused",
|
|
||||||
location=None,
|
|
||||||
subscription=None
|
|
||||||
)
|
|
||||||
if not saved:
|
|
||||||
logger.error(f"Failed to save {which_ticket} is expired! That's okay the data is worthless to save.")
|
|
||||||
return Result(valid=False, error_type=ResultError.SUBSCRIPTION, message=f"This ticket {which_ticket}'s subscription is expired. Do you want to wipe it?")
|
|
||||||
|
|
||||||
# UNKNOWN
|
###################################
|
||||||
return Result(valid=False, error_type=ResultError.UNKNOWN, message=f"Unknown error with this data: {raw_data_dict}. {CUSTOMER_SERVICE_MSG}")
|
# WRONG LOCATION
|
||||||
|
###################################
|
||||||
|
if server_reply.error_type == ServerError.WRONG_LOCATION:
|
||||||
|
error_msg = "The location entered is invalid. Do you want to sync location data now?"
|
||||||
|
return Result(valid=False, error_type=ResultError.NEED_SYNC, message=error_msg)
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# NODE DOWN
|
||||||
|
###################################
|
||||||
|
if server_reply.error_type == ServerError.NODE_ERROR:
|
||||||
|
error_msg = f"{server_reply.message}. Do you want to switch locations for this profile?"
|
||||||
|
return Result(valid=False, error_type=ResultError.NODE_ERROR, message=error_msg) # Each GUI will navigate to edit.
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# NO SOLUTION:
|
||||||
|
###################################
|
||||||
|
return translate_server_failure(server_reply=server_reply)
|
||||||
|
|
||||||
|
|
||||||
def emergency_save_billing(
|
|
||||||
which_ticket: int,
|
# # # the actual work here, everything else is just handling:
|
||||||
billing_code: str
|
# # ticket_observer.notify("connecting", "Connecting..")
|
||||||
) -> None:
|
# # reply_object = use_ticket_orchestrator(
|
||||||
docstring = """
|
# # which_ticket=which_ticket,
|
||||||
If ticket tracker fails to save it to the JSON. Emergency save to a random text file.
|
# # which_location=which_location,
|
||||||
"""
|
# # protocol_choice=protocol_choice,
|
||||||
logger.error(docstring)
|
# # connection_observer=connection_observer
|
||||||
which_ticket_as_str = str(which_ticket)
|
# # )
|
||||||
write_string_to_text_file(
|
# # return reply_object
|
||||||
billing_code,
|
|
||||||
f"{Constants.HV_TICKETING_CONFIG_HOME}/emergency_code_for_profile_{which_ticket_as_str}.txt",
|
|
||||||
)
|
# @type_checker
|
||||||
|
# def use_ticket_orchestrator(
|
||||||
|
# which_ticket: int,
|
||||||
|
# which_location: int, # Location.id
|
||||||
|
# protocol_choice: ProtocolChoice,
|
||||||
|
# connection_observer: ConnectionObserver,
|
||||||
|
# ) -> Result:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
# """
|
||||||
|
# Purpose:
|
||||||
|
# Coordinates using a ticket.
|
||||||
|
# Method:
|
||||||
|
# 1) Send to the server
|
||||||
|
# 2) Evaluate the response
|
||||||
|
# 3) updates the 'ticker_tracker' JSON with new data
|
||||||
|
# 4) Formats error messages for UI
|
||||||
|
# """
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# save_billing_code(profile_id: int, billing_code: str, expires_at: datetime)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ###################################
|
||||||
|
# # SEND TO THE SERVER
|
||||||
|
# ###################################
|
||||||
|
# api_reply_object = send_unblinded_ticket_to_server(
|
||||||
|
# which_ticket, which_location, protocol_choice, connection_observer
|
||||||
|
# )
|
||||||
|
|
||||||
|
# # NETWORK ERROR.
|
||||||
|
# if not api_reply_object.valid:
|
||||||
|
# return Result(valid=False, error_type=ResultError.CONNECTION, message=f"Could not establish a connection to Ticketing Server. {api_reply_object.message}.")
|
||||||
|
|
||||||
|
# ###################################
|
||||||
|
# # VERIFY DATA STRUCTURE
|
||||||
|
# ###################################
|
||||||
|
# raw_data_dict = api_reply_object.data
|
||||||
|
|
||||||
|
# # INVALID STRUCTURE:
|
||||||
|
# if not isinstance(raw_data_dict, dict):
|
||||||
|
# error_msg = f"The Ticketing Server gave an invalid data format {type(raw_data_dict)} in it's reply."
|
||||||
|
# logger.error(f"{error_msg} with raw value of {raw_data_dict}.")
|
||||||
|
# return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message=f"{error_msg} {CUSTOMER_SERVICE_MSG}")
|
||||||
|
|
||||||
|
# if "valid" not in raw_data_dict:
|
||||||
|
# error_msg = "The Ticketing Server gave an invalid data format that won't even tell us if it's valid or not."
|
||||||
|
# logger.error(f"{error_msg} with a raw value of {raw_data_dict}")
|
||||||
|
# return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message=f"{error_msg} {CUSTOMER_SERVICE_MSG}")
|
||||||
|
|
||||||
|
# logger.debug("If we made it to this point, the API server connected and gave a correctly structured reply.")
|
||||||
|
|
||||||
|
# ###################################
|
||||||
|
# # EXTRACT & ACT
|
||||||
|
# ###################################
|
||||||
|
# valid = raw_data_dict.get("valid", None)
|
||||||
|
# billing_code = raw_data_dict.get("billing_code", None)
|
||||||
|
# expires_at = raw_data_dict.get("expires_at", None)
|
||||||
|
# message = raw_data_dict.get("message", None)
|
||||||
|
|
||||||
|
# logger.info(f"Was the ticket use valid? {valid}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# # GOOD OR ALREADY USED
|
||||||
|
# if valid or message == "already_used":
|
||||||
|
# logger.info("This is a valid use, or a previously valid use that's still good.")
|
||||||
|
|
||||||
|
|
||||||
|
# def save_billing(billing_code: str):
|
||||||
|
|
||||||
|
# # Emergency:
|
||||||
|
# if not saved:
|
||||||
|
# emergency_save_billing(
|
||||||
|
# which_ticket=which_ticket,
|
||||||
|
# billing_code=billing_code
|
||||||
|
# )
|
||||||
|
|
||||||
|
# subscription = Subscription(
|
||||||
|
# billing_code=billing_code,
|
||||||
|
# expires_at=expires_at,
|
||||||
|
# )
|
||||||
|
|
||||||
|
# # even if not saved return it back, so it can be saved with the profile by other functions,
|
||||||
|
# return Result(valid=True, data=subscription)
|
||||||
|
|
||||||
|
# # EXPIRED
|
||||||
|
# if not valid and message == "expired":
|
||||||
|
# logger.error(f"Expired Ticket! Wiping slot {which_ticket}")
|
||||||
|
# saved = update_ticket_tracker(
|
||||||
|
# which_ticket=which_ticket,
|
||||||
|
# status="unused",
|
||||||
|
# location=None,
|
||||||
|
# subscription=None
|
||||||
|
# )
|
||||||
|
# if not saved:
|
||||||
|
# logger.error(f"Failed to save {which_ticket} is expired! That's okay the data is worthless to save.")
|
||||||
|
# return Result(valid=False, error_type=ResultError.SUBSCRIPTION, message=f"This ticket {which_ticket}'s subscription is expired. Do you want to wipe it?")
|
||||||
|
|
||||||
|
# # UNKNOWN
|
||||||
|
# return Result(valid=False, error_type=ResultError.UNKNOWN, message=f"Unknown error with this data: {raw_data_dict}. {CUSTOMER_SERVICE_MSG}")
|
||||||
|
|
||||||
|
|
||||||
|
# def emergency_save_billing(
|
||||||
|
# which_ticket: int,
|
||||||
|
# billing_code: str
|
||||||
|
# ) -> None:
|
||||||
|
# docstring = """
|
||||||
|
# If ticket tracker fails to save it to the JSON. Emergency save to a random text file.
|
||||||
|
# """
|
||||||
|
# logger.error(docstring)
|
||||||
|
# which_ticket_as_str = str(which_ticket)
|
||||||
|
# write_string_to_text_file(
|
||||||
|
# billing_code,
|
||||||
|
# f"{Constants.HV_TICKETING_CONFIG_HOME}/emergency_code_for_profile_{which_ticket_as_str}.txt",
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue