Introduced new ProfileRepo to cache profiles. All profile access is now coordinated through the ProfileController. And the ProfileController's previous bloat mission-creep of connections, is now moved to it's own service repo
This commit is contained in:
parent
81002cd76d
commit
732b17fb4a
11 changed files with 471 additions and 254 deletions
|
|
@ -35,6 +35,7 @@ class ApplicationController:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def launch(version: ApplicationVersion, profile: SessionProfile, port_number: int = None, asynchronous: bool = False, profile_observer: Optional[ProfileObserver] = None, connection_observer: ConnectionObserver = None, ticket_observer: TicketObserver = None):
|
def launch(version: ApplicationVersion, profile: SessionProfile, port_number: int = None, asynchronous: bool = False, profile_observer: Optional[ProfileObserver] = None, connection_observer: ConnectionObserver = None, ticket_observer: TicketObserver = None):
|
||||||
|
|
||||||
|
from core.services.profiles.profile_connections import enable_profile, disable_profile
|
||||||
from core.controllers.ProfileController import ProfileController
|
from core.controllers.ProfileController import ProfileController
|
||||||
|
|
||||||
persistent_state_path = f'{profile.get_data_path()}/persistent-state'
|
persistent_state_path = f'{profile.get_data_path()}/persistent-state'
|
||||||
|
|
@ -99,7 +100,8 @@ class ApplicationController:
|
||||||
if not fork_process_id:
|
if not fork_process_id:
|
||||||
|
|
||||||
ApplicationController.__run_process(initialization_file_path, profile, display, session_state)
|
ApplicationController.__run_process(initialization_file_path, profile, display, session_state)
|
||||||
ProfileController.disable(profile, False, profile_observer=profile_observer, ticket_observer=ticket_observer, connection_observer=connection_observer)
|
# ProfileController.disable(profile, False, profile_observer=profile_observer, ticket_observer=ticket_observer, connection_observer=connection_observer)
|
||||||
|
disable_profile(profile, False, profile_observer=profile_observer, ticket_observer=ticket_observer, connection_observer=connection_observer)
|
||||||
|
|
||||||
time.sleep(1.0)
|
time.sleep(1.0)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
@ -107,7 +109,8 @@ class ApplicationController:
|
||||||
else:
|
else:
|
||||||
|
|
||||||
ApplicationController.__run_process(initialization_file_path, profile, display, session_state)
|
ApplicationController.__run_process(initialization_file_path, profile, display, session_state)
|
||||||
ProfileController.disable(profile, False, profile_observer=profile_observer, ticket_observer=ticket_observer, connection_observer=connection_observer)
|
# ProfileController.disable(profile, False, profile_observer=profile_observer, ticket_observer=ticket_observer, connection_observer=connection_observer)
|
||||||
|
disable_profile(profile, False, profile_observer=profile_observer, ticket_observer=ticket_observer, connection_observer=connection_observer)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _sync(proxies: Optional[dict] = None):
|
def _sync(proxies: Optional[dict] = None):
|
||||||
|
|
|
||||||
|
|
@ -77,8 +77,11 @@ class ConnectionController:
|
||||||
if not ConnectionUnprotectedError in ignore:
|
if not ConnectionUnprotectedError in ignore:
|
||||||
raise ConnectionUnprotectedError('Connection unprotected while the system is not using a WireGuard interface.')
|
raise ConnectionUnprotectedError('Connection unprotected while the system is not using a WireGuard interface.')
|
||||||
else:
|
else:
|
||||||
from core.controllers.ProfileController import ProfileController
|
from core.services.profiles.profile_connections import disable_profile
|
||||||
ProfileController.disable(profile=profile, connection_observer=connection_observer, ticket_observer=ticket_observer)
|
disable_profile(profile=profile, connection_observer=connection_observer, ticket_observer=ticket_observer)
|
||||||
|
# from core.controllers.ProfileController import ProfileController
|
||||||
|
# ProfileController.disable(profile=profile, connection_observer=connection_observer, ticket_observer=ticket_observer)
|
||||||
|
|
||||||
|
|
||||||
if profile.connection.code == 'tor':
|
if profile.connection.code == 'tor':
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from core.services.profiles.ProfileRepository import ProfileRepository
|
||||||
from core.services.networking.systemwide.systemwide_wireguard import terminate_system_connection
|
from core.services.networking.systemwide.systemwide_wireguard import terminate_system_connection
|
||||||
from core.services.networking.general_connection_tools.testing_evaluating import system_uses_wireguard_interface
|
from core.services.networking.general_connection_tools.testing_evaluating import system_uses_wireguard_interface
|
||||||
from core.services.networking.general_connection_tools.connection_enable import establish_connection
|
from core.services.networking.general_connection_tools.connection_enable import establish_connection
|
||||||
|
|
@ -8,8 +9,8 @@ from core.services.subscriptions import subscriptions
|
||||||
from core.errors.exceptions import FirewallError
|
from core.errors.exceptions import FirewallError
|
||||||
from core.models.Result import Result, ResultError
|
from core.models.Result import Result, ResultError
|
||||||
from core.observers.TicketObserver import TicketObserver
|
from core.observers.TicketObserver import TicketObserver
|
||||||
from core.services.assassin.ticket_respawn import respawn_profile
|
# from core.services.assassin.ticket_respawn import respawn_profile
|
||||||
from core.services.assassin import assassin_tools
|
# from core.services.assassin import assassin_tools
|
||||||
from core.errors.logger import logger
|
from core.errors.logger import logger
|
||||||
|
|
||||||
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError
|
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError
|
||||||
|
|
@ -32,230 +33,55 @@ import time
|
||||||
|
|
||||||
|
|
||||||
class ProfileController:
|
class ProfileController:
|
||||||
|
# Initialize the singleton repository
|
||||||
|
_repo = ProfileRepository()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get(id: int) -> Union[SessionProfile, SystemProfile, None]:
|
def get(id: int) -> Union[SessionProfile, SystemProfile, None]:
|
||||||
return Profile.find_by_id(id)
|
# Controller no longer talks to the Model; it talks to the Repo
|
||||||
|
return ProfileController._repo.get(id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all():
|
def get_all():
|
||||||
return Profile.all()
|
return ProfileController._repo.get_all()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create(profile: Union[SessionProfile, SystemProfile], profile_observer: ProfileObserver = None):
|
def create(profile: Union[SessionProfile, SystemProfile], profile_observer: ProfileObserver = None):
|
||||||
|
# Call the helper with the 'created' event
|
||||||
|
ProfileController._save_and_sync(profile, 'created', profile_observer)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update(profile: Union[SessionProfile, SystemProfile], profile_observer: ProfileObserver = None):
|
||||||
|
# Call the helper with the 'updated' event
|
||||||
|
ProfileController._save_and_sync(profile, 'updated', profile_observer)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _save_and_sync(profile, event_type: str, observer: ProfileObserver = None):
|
||||||
|
"""Private helper to handle the actual persistence and cache synchronization."""
|
||||||
|
# 1. Persist to disk
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
if profile_observer is not None:
|
# 2. update cache to prevent drift
|
||||||
profile_observer.notify('created', profile)
|
ProfileController._repo.update_cache(profile)
|
||||||
|
|
||||||
|
# 3. Notify observer
|
||||||
|
if observer is not None:
|
||||||
|
observer.notify(event_type, profile)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def enable(
|
def delete(profile: Union[SessionProfile, SystemProfile], profile_observer: ProfileObserver = None):
|
||||||
profile: Union[SessionProfile, SystemProfile],
|
"""Remove the profile from both disk and cache"""
|
||||||
ignore: tuple[type[Exception]] = (),
|
|
||||||
pristine: bool = False,
|
|
||||||
asynchronous: bool = False,
|
|
||||||
profile_observer: ProfileObserver = None,
|
|
||||||
application_version_observer: ApplicationVersionObserver = None,
|
|
||||||
connection_observer: ConnectionObserver = None,
|
|
||||||
ticket_observer: TicketObserver = None,
|
|
||||||
max_resolution: Optional[str] = None
|
|
||||||
) -> Result:
|
|
||||||
|
|
||||||
from core.controllers.ConnectionController import ConnectionController
|
# 1. Save to temp variable:
|
||||||
|
profile_id = profile.id
|
||||||
|
|
||||||
# =========== ALREADY ENABLED ============
|
# 2. Permanently delete from disk
|
||||||
if ProfileController.is_enabled(profile):
|
|
||||||
if not ProfileStateConflictError in ignore:
|
|
||||||
raise ProfileStateConflictError('The profile is already enabled or its session was not properly terminated.')
|
|
||||||
else:
|
|
||||||
ProfileController.disable(profile)
|
|
||||||
|
|
||||||
# =========== PRISTINE ============
|
|
||||||
if pristine:
|
|
||||||
profile.delete_data()
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# SESSION
|
|
||||||
# ============================================================================
|
|
||||||
if profile.is_session_profile():
|
|
||||||
|
|
||||||
# ASSASSIN MODE
|
|
||||||
if profile.assassin:
|
|
||||||
assassin_result = assassin_tools.create(
|
|
||||||
profile=profile,
|
|
||||||
max_resolution=max_resolution,
|
|
||||||
ticket_observer=ticket_observer,
|
|
||||||
connection_observer=connection_observer
|
|
||||||
)
|
|
||||||
if not assassin_result.valid:
|
|
||||||
error_msg = f"Error with setting up Assassin: {assassin_result.error_type} & {assassin_result.message}"
|
|
||||||
logger.error(error_msg)
|
|
||||||
raise ProfileActivationError(f'Assassin NOT enabled: {assassin_result.message}')
|
|
||||||
# random assassin values become the main profile:
|
|
||||||
profile = assassin_result.data
|
|
||||||
# CONCLUSION OF ASSASSIN CODE
|
|
||||||
|
|
||||||
application_version = profile.application_version
|
|
||||||
|
|
||||||
if not application_version.is_installed():
|
|
||||||
ApplicationVersionController.install(application_version, application_version_observer=application_version_observer, connection_observer=connection_observer)
|
|
||||||
|
|
||||||
try:
|
|
||||||
port_number = establish_connection(profile, ignore=ignore, connection_observer=connection_observer, ticket_observer=ticket_observer)
|
|
||||||
except ConnectionError:
|
|
||||||
raise ProfileActivationError('The profile could not be enabled.')
|
|
||||||
except ValueError:
|
|
||||||
raise ProfileActivationError('The profile could not be enabled.')
|
|
||||||
|
|
||||||
if profile_observer is not None:
|
|
||||||
profile_observer.notify('enabled', profile)
|
|
||||||
|
|
||||||
ApplicationController.launch(application_version, profile, port_number, asynchronous=asynchronous, profile_observer=profile_observer, ticket_observer=ticket_observer, connection_observer=connection_observer)
|
|
||||||
return Result(valid=True)
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# SYSTEMWIDE
|
|
||||||
# ============================================================================
|
|
||||||
if profile.is_system_profile():
|
|
||||||
try:
|
|
||||||
connection_result = establish_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
|
||||||
if connection_result.valid:
|
|
||||||
if profile_observer is not None:
|
|
||||||
profile_observer.notify('enabled', profile)
|
|
||||||
return Result(valid=True)
|
|
||||||
else:
|
|
||||||
logger.error(f"Couldn't enable the profile: {connection_result.error_type}")
|
|
||||||
error_msg = connection_result.message
|
|
||||||
raise ProfileActivationError(error_msg)
|
|
||||||
except FirewallError:
|
|
||||||
raise
|
|
||||||
except ConnectionError:
|
|
||||||
raise ProfileActivationError('The profile could not be enabled.')
|
|
||||||
except ValueError:
|
|
||||||
raise ProfileActivationError('The profile could not be enabled.')
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def disable(
|
|
||||||
profile: Union[SessionProfile, SystemProfile],
|
|
||||||
explicitly: bool = True,
|
|
||||||
ignore: tuple[type[Exception]] = (),
|
|
||||||
profile_observer: ProfileObserver = None,
|
|
||||||
ticket_observer: TicketObserver = None,
|
|
||||||
connection_observer: ConnectionObserver = None,
|
|
||||||
wipe_assassin: bool = False
|
|
||||||
):
|
|
||||||
|
|
||||||
from core.controllers.ConnectionController import ConnectionController
|
|
||||||
|
|
||||||
if profile.is_session_profile():
|
|
||||||
|
|
||||||
# START ASSASSIN SECTION
|
|
||||||
if profile.assassin and wipe_assassin:
|
|
||||||
print("triggering to WIPE assassin")
|
|
||||||
assassin_result = assassin_tools.wipe(
|
|
||||||
profile=profile,
|
|
||||||
ticket_observer=ticket_observer,
|
|
||||||
connection_observer=connection_observer
|
|
||||||
)
|
|
||||||
if not assassin_result.valid:
|
|
||||||
error_msg = f"Error with wiping respawn of Assassin: {assassin_result.error_type} & {assassin_result.message}"
|
|
||||||
logger.error(error_msg)
|
|
||||||
raise ProfileDeactivationError(f'Assassin NOT disabled: {assassin_result.message}')
|
|
||||||
# END ASSASSIN SECTION
|
|
||||||
|
|
||||||
if SessionStateController.exists(profile.id):
|
|
||||||
|
|
||||||
session_state = SessionStateController.get(profile.id)
|
|
||||||
|
|
||||||
if session_state is not None:
|
|
||||||
|
|
||||||
for port_number in session_state.network_port_numbers.tor:
|
|
||||||
ConnectionController.terminate_tor_session_connection(port_number)
|
|
||||||
|
|
||||||
session_state.dissolve(session_state.id)
|
|
||||||
|
|
||||||
if profile_observer is not None:
|
|
||||||
|
|
||||||
profile_observer.notify('disabled', profile, dict(
|
|
||||||
explicitly=explicitly,
|
|
||||||
))
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# SYSTEMWIDE
|
|
||||||
# ============================================================================
|
|
||||||
if profile.is_system_profile():
|
|
||||||
|
|
||||||
subjects = ProfileController.get_all().values()
|
|
||||||
|
|
||||||
for subject in subjects:
|
|
||||||
|
|
||||||
if subject.is_session_profile():
|
|
||||||
|
|
||||||
if subject.connection.is_unprotected() and ProfileController.is_enabled(subject) and not ConnectionUnprotectedError in ignore:
|
|
||||||
raise ConnectionUnprotectedError('Disabling this system connection would leave one or more sessions exposed.')
|
|
||||||
|
|
||||||
if SystemStateController.exists():
|
|
||||||
|
|
||||||
system_state = SystemStateController.get()
|
|
||||||
|
|
||||||
if profile.id != system_state.profile_id:
|
|
||||||
raise ProfileDeactivationError('The profile could not be disabled.')
|
|
||||||
|
|
||||||
try:
|
|
||||||
# ================= SETTINGS =================
|
|
||||||
firewall_setting = get_firewall_setting()
|
|
||||||
dns_setting = get_dns_setting()
|
|
||||||
# ======= KILL SYSTEMWIDE =============
|
|
||||||
if profile.connection.code == "wireguard":
|
|
||||||
terminate_system_connection(
|
|
||||||
firewall_setting=firewall_setting,
|
|
||||||
dns_setting=dns_setting
|
|
||||||
)
|
|
||||||
elif profile.connection.code in ("hysteria2", "vless"):
|
|
||||||
end_singbox()
|
|
||||||
else:
|
|
||||||
raise ProfileDeactivationError('Unsupported protocol.')
|
|
||||||
# ================= UPDATE UI ================
|
|
||||||
# if it made it this far, it worked in theory.
|
|
||||||
if profile_observer is not None:
|
|
||||||
profile_observer.notify('disabled', profile, dict(
|
|
||||||
explicitly=explicitly,
|
|
||||||
))
|
|
||||||
except ConnectionTerminationError:
|
|
||||||
raise ProfileDeactivationError('The profile could not be disabled.')
|
|
||||||
except ValueError:
|
|
||||||
raise ProfileDeactivationError('The profile could not be disabled.')
|
|
||||||
except FirewallError:
|
|
||||||
raise
|
|
||||||
|
|
||||||
time.sleep(1.0)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def destroy(profile: Union[SessionProfile, SystemProfile], profile_observer: ProfileObserver = None, ticket_observer: TicketObserver = None, connection_observer: ConnectionObserver = None):
|
|
||||||
|
|
||||||
####################################
|
|
||||||
# DESTROY TICKET
|
|
||||||
####################################
|
|
||||||
which_ticket = profile.ticket
|
|
||||||
respawned = respawn_profile(
|
|
||||||
profile=profile,
|
|
||||||
ticket_observer=ticket_observer,
|
|
||||||
connection_observer=connection_observer
|
|
||||||
)
|
|
||||||
if which_ticket and not respawned:
|
|
||||||
logger.error("Serious issue with respawning the ticket. We might raise an error here.")
|
|
||||||
|
|
||||||
####################################
|
|
||||||
# DESTROY PROFILE
|
|
||||||
####################################
|
|
||||||
ProfileController.disable(profile)
|
|
||||||
profile.delete()
|
profile.delete()
|
||||||
|
|
||||||
if profile_observer is not None:
|
# 3. update cache with the temp variable to prevent drift
|
||||||
profile_observer.notify('destroyed', profile)
|
ProfileController._repo.invalidate(profile_id=profile_id)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def attach_subscription(profile: Union[SessionProfile, SystemProfile], subscription: Subscription):
|
def attach_subscription(profile: Union[SessionProfile, SystemProfile], subscription: Subscription):
|
||||||
|
|
@ -289,24 +115,6 @@ class ProfileController:
|
||||||
else:
|
else:
|
||||||
raise MissingSubscriptionError()
|
raise MissingSubscriptionError()
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_enabled(profile: Union[SessionProfile, SystemProfile]):
|
|
||||||
|
|
||||||
from core.controllers.ConnectionController import ConnectionController
|
|
||||||
|
|
||||||
if profile.is_session_profile():
|
|
||||||
|
|
||||||
session_state = SessionStateController.get_or_new(profile.id)
|
|
||||||
return len(session_state.network_port_numbers.all) > 0 or len(session_state.process_ids) > 0
|
|
||||||
|
|
||||||
if profile.is_system_profile():
|
|
||||||
|
|
||||||
system_state = SystemStateController.get()
|
|
||||||
|
|
||||||
if system_state is not None and system_state.profile_id is profile.id:
|
|
||||||
return system_uses_wireguard_interface()
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_invoice(profile: Union[SessionProfile, SystemProfile]):
|
def get_invoice(profile: Union[SessionProfile, SystemProfile]):
|
||||||
|
|
@ -347,12 +155,3 @@ class ProfileController:
|
||||||
def has_wireguard_configuration(profile: Union[SessionProfile, SystemProfile]):
|
def has_wireguard_configuration(profile: Union[SessionProfile, SystemProfile]):
|
||||||
return profile.has_wireguard_configuration()
|
return profile.has_wireguard_configuration()
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def turn_on_assassin(profile: SessionProfile):
|
|
||||||
profile.assassin = True
|
|
||||||
profile.save()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def turn_off_assassin(profile: SessionProfile):
|
|
||||||
profile.assassin = False
|
|
||||||
profile.save()
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from core.errors.logger import logger
|
||||||
from core.models.Result import Result, ResultError
|
from core.models.Result import Result, ResultError
|
||||||
from core.services.networking.systemwide.general_tools.manage_sudo_configs import remove_sudo_config
|
from core.services.networking.systemwide.general_tools.manage_sudo_configs import remove_sudo_config
|
||||||
|
|
||||||
|
from core.controllers.ProfileController import ProfileController
|
||||||
from core.models.BaseProfile import BaseProfile as Profile
|
from core.models.BaseProfile import BaseProfile as Profile
|
||||||
from core.models.BaseProfile import ProfileType
|
from core.models.BaseProfile import ProfileType
|
||||||
|
|
||||||
|
|
@ -110,7 +111,7 @@ def update_profile(
|
||||||
|
|
||||||
logger.info("[UPDATE PROFILE] Passing to Profile model to save..")
|
logger.info("[UPDATE PROFILE] Passing to Profile model to save..")
|
||||||
try:
|
try:
|
||||||
profile.save()
|
ProfileController.update(profile)
|
||||||
logger.info("[UPDATE PROFILE] Save worked, passing to the GUI the Result..")
|
logger.info("[UPDATE PROFILE] Save worked, passing to the GUI the Result..")
|
||||||
return Result(valid=True)
|
return Result(valid=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -181,5 +182,5 @@ def change_systemwide_connection(profile: Profile, new_value: str) -> Result:
|
||||||
return Result(valid=True, data=profile)
|
return Result(valid=True, data=profile)
|
||||||
|
|
||||||
|
|
||||||
def save_billing_code(profile: Profile, billing_code: str) -> bool:
|
# def save_billing_code(profile: Profile, billing_code: str) -> bool:
|
||||||
|
|
||||||
|
|
@ -109,6 +109,37 @@ class BaseProfile(ABC):
|
||||||
|
|
||||||
|
|
||||||
def save(self: Self, app_version_dict: dict = None):
|
def save(self: Self, app_version_dict: dict = None):
|
||||||
|
"""
|
||||||
|
First warn via the logs, if the caller is bypassing the Cache and accessing it directly.
|
||||||
|
Then persist changes in the profile/model to disk.
|
||||||
|
"""
|
||||||
|
#--------------------------
|
||||||
|
# Cache Drift Protection
|
||||||
|
#--------------------------
|
||||||
|
# 1. Get the call stack
|
||||||
|
# stack[0] is this current 'save' method
|
||||||
|
# stack[1] is the immediate caller
|
||||||
|
stack = inspect.stack()
|
||||||
|
caller_frame = stack[1]
|
||||||
|
|
||||||
|
caller_function_name = caller_frame.function
|
||||||
|
caller_module_name = caller_frame.filename # Full path to the file
|
||||||
|
|
||||||
|
# 2. Only the Repo or Controller is allowed to save
|
||||||
|
allowed_callers = ['create', 'update_cache']
|
||||||
|
is_allowed_file = "core/controllers/ProfileController.py" in caller_module_name or "core/services/profiles/ProfileRepository.py" in caller_module_name
|
||||||
|
|
||||||
|
if caller_function_name not in allowed_callers and not is_allowed_file:
|
||||||
|
logger.warning(
|
||||||
|
f"ARCHITECTURE VIOLATION: .save() called directly from "
|
||||||
|
f"function '{caller_function_name}' in {caller_module_name}. "
|
||||||
|
f"Please route this through ProfileController to ensure cache integrity."
|
||||||
|
)
|
||||||
|
|
||||||
|
#--------------------------
|
||||||
|
# Actual Save
|
||||||
|
#--------------------------
|
||||||
|
|
||||||
# === SERIALIZATION ===
|
# === SERIALIZATION ===
|
||||||
config_dict = self.to_dict()
|
config_dict = self.to_dict()
|
||||||
|
|
||||||
|
|
@ -337,3 +368,8 @@ class BaseProfile(ABC):
|
||||||
# if profile['location'].get('time_zone') is not None:
|
# if profile['location'].get('time_zone') is not None:
|
||||||
# location.time_zone = profile['location']['time_zone']
|
# location.time_zone = profile['location']['time_zone']
|
||||||
# profile['location'] = location
|
# profile['location'] = location
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,8 @@ def use_ticket_get_sub(
|
||||||
assassins_subscription = Subscription(billing_code=billing_code)
|
assassins_subscription = Subscription(billing_code=billing_code)
|
||||||
profile.subscription = assassins_subscription
|
profile.subscription = assassins_subscription
|
||||||
profile.ticket = which_ticket
|
profile.ticket = which_ticket
|
||||||
profile.save()
|
from core.controllers.ProfileController import ProfileController
|
||||||
|
ProfileController.update(profile)
|
||||||
return Result(valid=True, data=profile)
|
return Result(valid=True, data=profile)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -157,7 +158,7 @@ def wipe(
|
||||||
profile.name = "assassin"
|
profile.name = "assassin"
|
||||||
profile.subscription = None
|
profile.subscription = None
|
||||||
profile.ticket = None
|
profile.ticket = None
|
||||||
profile.save()
|
ProfileController.update(profile)
|
||||||
|
|
||||||
# DELETE the pre-existing WG config:
|
# DELETE the pre-existing WG config:
|
||||||
profile.delete_wireguard_configuration()
|
profile.delete_wireguard_configuration()
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# from core.controllers.ProfileController import ProfileController
|
||||||
from core.controllers.tickets.TicketPrepController import respawn_billing_code_into_ticket
|
from core.controllers.tickets.TicketPrepController import respawn_billing_code_into_ticket
|
||||||
from core.services.prepare_tickets.ticket_tracker import find_ticket_by_sub
|
from core.services.prepare_tickets.ticket_tracker import find_ticket_by_sub
|
||||||
from core.services.prepare_tickets.ticket_tracker import get_all_unused_tickets
|
from core.services.prepare_tickets.ticket_tracker import get_all_unused_tickets
|
||||||
|
|
@ -46,7 +47,9 @@ def respawn_profile(
|
||||||
|
|
||||||
# Regardless of how it was acquired,
|
# Regardless of how it was acquired,
|
||||||
if not which_ticket:
|
if not which_ticket:
|
||||||
return Result(valid=False, error_type=ResultError.TICKET, message="This profile is not associated with a ticket. And/or you lack a ticket slot to respawn.")
|
error_msg = "This profile is not associated with a ticket. And/or you lack a ticket slot to respawn."
|
||||||
|
logger.error(error_msg)
|
||||||
|
return Result(valid=False, error_type=ResultError.TICKET, message=error_msg)
|
||||||
|
|
||||||
notification = f"Respawn Started for Ticket {which_ticket}"
|
notification = f"Respawn Started for Ticket {which_ticket}"
|
||||||
logger.info(notification)
|
logger.info(notification)
|
||||||
|
|
@ -78,7 +81,8 @@ def respawn_profile(
|
||||||
|
|
||||||
# save the ticket,
|
# save the ticket,
|
||||||
profile.ticket = which_ticket
|
profile.ticket = which_ticket
|
||||||
profile.save()
|
from core.controllers.ProfileController import ProfileController
|
||||||
|
ProfileController.update(profile)
|
||||||
return Result(valid=True, message="This profile fully respawned and had it's billing code wiped.")
|
return Result(valid=True, message="This profile fully respawned and had it's billing code wiped.")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
54
core/services/profiles/ProfileRepository.py
Normal file
54
core/services/profiles/ProfileRepository.py
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
from core.models.BaseProfile import BaseProfile
|
||||||
|
|
||||||
|
# generic
|
||||||
|
from typing import Dict, List, Union, Optional
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
|
class ProfileRepository:
|
||||||
|
"""
|
||||||
|
Singleton Repository that mediates between the Controller and the Model.
|
||||||
|
Handles caching logic to prevent redundant file system hits.
|
||||||
|
"""
|
||||||
|
_instance = None
|
||||||
|
_lock = Lock() # Ensure singleton thread-safety
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
with cls._lock:
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super(ProfileRepository, cls).__new__(cls)
|
||||||
|
# The actual cache: { profile_id: profile_object }
|
||||||
|
cls._instance._cache: Dict[int, any] = {}
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def get(self, profile_id: int):
|
||||||
|
# 1. Check cache first
|
||||||
|
if profile_id in self._cache:
|
||||||
|
return self._cache[profile_id]
|
||||||
|
|
||||||
|
# 2. Fallback to disk via the Model
|
||||||
|
profile = BaseProfile.find_by_id(profile_id)
|
||||||
|
|
||||||
|
# 3. Update cache for next time
|
||||||
|
if profile:
|
||||||
|
self._cache[profile_id] = profile
|
||||||
|
|
||||||
|
return profile
|
||||||
|
|
||||||
|
def get_all(self):
|
||||||
|
# First, get the current list of IDs from the model, to avoid drift, from new files added to disk
|
||||||
|
all_profiles = BaseProfile.all()
|
||||||
|
|
||||||
|
# Update our internal cache with everything currently on disk
|
||||||
|
for p in all_profiles:
|
||||||
|
self._cache[id] = p
|
||||||
|
|
||||||
|
return all_profiles
|
||||||
|
|
||||||
|
def update_cache(self, profile):
|
||||||
|
"""Explicitly updates the cache to prevent drift after a save."""
|
||||||
|
self._cache[profile.id] = profile
|
||||||
|
|
||||||
|
def invalidate(self, profile_id: int):
|
||||||
|
"""Removes an item from cache if it's deleted from disk."""
|
||||||
|
self._cache.pop(profile_id, None)
|
||||||
|
|
||||||
314
core/services/profiles/profile_connections.py
Normal file
314
core/services/profiles/profile_connections.py
Normal file
|
|
@ -0,0 +1,314 @@
|
||||||
|
from core.controllers.ConnectionController import ConnectionController
|
||||||
|
from core.controllers.ProfileController import ProfileController
|
||||||
|
from core.services.networking.systemwide.systemwide_wireguard import terminate_system_connection
|
||||||
|
from core.services.networking.general_connection_tools.testing_evaluating import system_uses_wireguard_interface
|
||||||
|
from core.services.networking.general_connection_tools.connection_enable import establish_connection
|
||||||
|
from core.services.networking.systemwide.systemwide_utils import get_firewall_setting, get_dns_setting
|
||||||
|
from core.services.networking.systemwide.encrypted_proxy.singbox_runner import end_singbox
|
||||||
|
from core.services.networking.systemwide import killswitch
|
||||||
|
from core.services.subscriptions import subscriptions
|
||||||
|
from core.errors.exceptions import FirewallError
|
||||||
|
from core.models.Result import Result, ResultError
|
||||||
|
from core.observers.TicketObserver import TicketObserver
|
||||||
|
from core.services.assassin.ticket_respawn import respawn_profile
|
||||||
|
from core.services.assassin import assassin_tools
|
||||||
|
from core.errors.logger import logger
|
||||||
|
|
||||||
|
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError
|
||||||
|
from core.controllers.ApplicationController import ApplicationController
|
||||||
|
from core.controllers.ApplicationVersionController import ApplicationVersionController
|
||||||
|
from core.controllers.SessionStateController import SessionStateController
|
||||||
|
from core.controllers.SystemStateController import SystemStateController
|
||||||
|
from core.models.BaseProfile import BaseProfile as Profile
|
||||||
|
from core.models.Subscription import Subscription
|
||||||
|
from core.models.session.SessionProfile import SessionProfile
|
||||||
|
from core.models.system.SystemProfile import SystemProfile
|
||||||
|
from core.observers.ApplicationVersionObserver import ApplicationVersionObserver
|
||||||
|
from core.observers.ConnectionObserver import ConnectionObserver
|
||||||
|
from core.observers.ProfileObserver import ProfileObserver
|
||||||
|
from core.services.WebServiceApiService import WebServiceApiService
|
||||||
|
|
||||||
|
# generic
|
||||||
|
from typing import Union, Optional
|
||||||
|
import base64
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def is_enabled(profile: Union[SessionProfile, SystemProfile]) -> int | None:
|
||||||
|
"""Return the quantity of profiles enabled, or False if none are."""
|
||||||
|
|
||||||
|
if profile.is_session_profile():
|
||||||
|
|
||||||
|
session_state = SessionStateController.get_or_new(profile.id)
|
||||||
|
return len(session_state.network_port_numbers.all) > 0 or len(session_state.process_ids) > 0
|
||||||
|
|
||||||
|
if profile.is_system_profile():
|
||||||
|
|
||||||
|
system_state = SystemStateController.get()
|
||||||
|
|
||||||
|
if system_state is not None and system_state.profile_id is profile.id:
|
||||||
|
return system_uses_wireguard_interface()
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def enable_session_profile(
|
||||||
|
profile: Union[SessionProfile, SystemProfile],
|
||||||
|
ignore: tuple[type[Exception]] = (),
|
||||||
|
asynchronous: bool = False,
|
||||||
|
profile_observer: ProfileObserver = None,
|
||||||
|
application_version_observer: ApplicationVersionObserver = None,
|
||||||
|
connection_observer: ConnectionObserver = None,
|
||||||
|
ticket_observer: TicketObserver = None,
|
||||||
|
max_resolution: Optional[str] = None
|
||||||
|
) -> Result:
|
||||||
|
"""
|
||||||
|
Launch a session profile.
|
||||||
|
|
||||||
|
1) by starting a connnection on a port,
|
||||||
|
2) and then starting the application using that networking port
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ASSASSIN MODE
|
||||||
|
if profile.assassin:
|
||||||
|
assassin_result = assassin_tools.create(
|
||||||
|
profile=profile,
|
||||||
|
max_resolution=max_resolution,
|
||||||
|
ticket_observer=ticket_observer,
|
||||||
|
connection_observer=connection_observer
|
||||||
|
)
|
||||||
|
if not assassin_result.valid:
|
||||||
|
error_msg = f"Error with setting up Assassin: {assassin_result.error_type} & {assassin_result.message}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise ProfileActivationError(f'Assassin NOT enabled: {assassin_result.message}')
|
||||||
|
# random assassin values become the main profile:
|
||||||
|
profile = assassin_result.data
|
||||||
|
# CONCLUSION OF ASSASSIN CODE
|
||||||
|
|
||||||
|
application_version = profile.application_version
|
||||||
|
|
||||||
|
if not application_version.is_installed():
|
||||||
|
ApplicationVersionController.install(application_version, application_version_observer=application_version_observer, connection_observer=connection_observer)
|
||||||
|
|
||||||
|
try:
|
||||||
|
port_number = establish_connection(profile, ignore=ignore, connection_observer=connection_observer, ticket_observer=ticket_observer)
|
||||||
|
except ConnectionError:
|
||||||
|
raise ProfileActivationError('The profile could not be enabled.')
|
||||||
|
except ValueError:
|
||||||
|
raise ProfileActivationError('The profile could not be enabled.')
|
||||||
|
|
||||||
|
if profile_observer is not None:
|
||||||
|
profile_observer.notify('enabled', profile)
|
||||||
|
|
||||||
|
ApplicationController.launch(application_version, profile, port_number, asynchronous=asynchronous, profile_observer=profile_observer, ticket_observer=ticket_observer, connection_observer=connection_observer)
|
||||||
|
return Result(valid=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def enable_profile(
|
||||||
|
profile: Union[SessionProfile, SystemProfile],
|
||||||
|
ignore: tuple[type[Exception]] = (),
|
||||||
|
pristine: bool = False,
|
||||||
|
asynchronous: bool = False,
|
||||||
|
profile_observer: ProfileObserver = None,
|
||||||
|
application_version_observer: ApplicationVersionObserver = None,
|
||||||
|
connection_observer: ConnectionObserver = None,
|
||||||
|
ticket_observer: TicketObserver = None,
|
||||||
|
max_resolution: Optional[str] = None
|
||||||
|
) -> Result:
|
||||||
|
|
||||||
|
"""Orchestrate Enabling both types of profiles"""
|
||||||
|
|
||||||
|
# =========== ALREADY ENABLED ============
|
||||||
|
if is_enabled(profile):
|
||||||
|
if not ProfileStateConflictError in ignore:
|
||||||
|
raise ProfileStateConflictError('The profile is already enabled or its session was not properly terminated.')
|
||||||
|
else:
|
||||||
|
ProfileController.disable(profile)
|
||||||
|
|
||||||
|
# =========== PRISTINE ============
|
||||||
|
if pristine:
|
||||||
|
profile.delete_data()
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# SESSION
|
||||||
|
# ============================================================================
|
||||||
|
if profile.is_session_profile():
|
||||||
|
return enable_session_profile(
|
||||||
|
profile=profile,
|
||||||
|
ignore=ignore,
|
||||||
|
asynchronous=asynchronous,
|
||||||
|
profile_observer=profile_observer,
|
||||||
|
application_version_observer=application_version_observer,
|
||||||
|
connection_observer=connection_observer,
|
||||||
|
ticket_observer=ticket_observer,
|
||||||
|
max_resolution=max_resolution
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# SYSTEMWIDE
|
||||||
|
# ============================================================================
|
||||||
|
if profile.is_system_profile():
|
||||||
|
try:
|
||||||
|
connection_result = establish_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||||
|
if connection_result.valid:
|
||||||
|
if profile_observer is not None:
|
||||||
|
profile_observer.notify('enabled', profile)
|
||||||
|
return Result(valid=True)
|
||||||
|
else:
|
||||||
|
logger.error(f"Couldn't enable the profile: {connection_result.error_type}")
|
||||||
|
error_msg = connection_result.message
|
||||||
|
raise ProfileActivationError(error_msg)
|
||||||
|
except FirewallError:
|
||||||
|
raise
|
||||||
|
except ConnectionError:
|
||||||
|
raise ProfileActivationError('The profile could not be enabled.')
|
||||||
|
except ValueError:
|
||||||
|
raise ProfileActivationError('The profile could not be enabled.')
|
||||||
|
|
||||||
|
|
||||||
|
def disable_profile(
|
||||||
|
profile: Union[SessionProfile, SystemProfile],
|
||||||
|
explicitly: bool = True,
|
||||||
|
ignore: tuple[type[Exception]] = (),
|
||||||
|
profile_observer: ProfileObserver = None,
|
||||||
|
ticket_observer: TicketObserver = None,
|
||||||
|
connection_observer: ConnectionObserver = None,
|
||||||
|
wipe_assassin: bool = False
|
||||||
|
) -> Result:
|
||||||
|
|
||||||
|
"""Orchestrate disabling a profile."""
|
||||||
|
|
||||||
|
if profile.is_session_profile():
|
||||||
|
|
||||||
|
# START ASSASSIN SECTION
|
||||||
|
if profile.assassin and wipe_assassin:
|
||||||
|
print("triggering to WIPE assassin")
|
||||||
|
assassin_result = assassin_tools.wipe(
|
||||||
|
profile=profile,
|
||||||
|
ticket_observer=ticket_observer,
|
||||||
|
connection_observer=connection_observer
|
||||||
|
)
|
||||||
|
if not assassin_result.valid:
|
||||||
|
error_msg = f"Error with wiping respawn of Assassin: {assassin_result.error_type} & {assassin_result.message}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise ProfileDeactivationError(f'Assassin NOT disabled: {assassin_result.message}')
|
||||||
|
# END ASSASSIN SECTION
|
||||||
|
|
||||||
|
if SessionStateController.exists(profile.id):
|
||||||
|
|
||||||
|
session_state = SessionStateController.get(profile.id)
|
||||||
|
|
||||||
|
if session_state is not None:
|
||||||
|
|
||||||
|
for port_number in session_state.network_port_numbers.tor:
|
||||||
|
ConnectionController.terminate_tor_session_connection(port_number)
|
||||||
|
|
||||||
|
session_state.dissolve(session_state.id)
|
||||||
|
|
||||||
|
if profile_observer is not None:
|
||||||
|
|
||||||
|
profile_observer.notify('disabled', profile, dict(
|
||||||
|
explicitly=explicitly,
|
||||||
|
))
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# SYSTEMWIDE
|
||||||
|
# ============================================================================
|
||||||
|
if profile.is_system_profile():
|
||||||
|
|
||||||
|
subjects = ProfileController.get_all().values()
|
||||||
|
|
||||||
|
for subject in subjects:
|
||||||
|
|
||||||
|
if subject.is_session_profile():
|
||||||
|
|
||||||
|
if subject.connection.is_unprotected() and is_enabled(subject) and not ConnectionUnprotectedError in ignore:
|
||||||
|
raise ConnectionUnprotectedError('Disabling this system connection would leave one or more sessions exposed.')
|
||||||
|
|
||||||
|
if SystemStateController.exists():
|
||||||
|
|
||||||
|
system_state = SystemStateController.get()
|
||||||
|
|
||||||
|
if profile.id != system_state.profile_id:
|
||||||
|
raise ProfileDeactivationError('The profile could not be disabled.')
|
||||||
|
|
||||||
|
try:
|
||||||
|
# ================= SETTINGS =================
|
||||||
|
firewall_setting = get_firewall_setting()
|
||||||
|
dns_setting = get_dns_setting()
|
||||||
|
# ======= KILL SYSTEMWIDE =============
|
||||||
|
if profile.connection.code == "wireguard":
|
||||||
|
terminate_system_connection(
|
||||||
|
firewall_setting=firewall_setting,
|
||||||
|
dns_setting=dns_setting
|
||||||
|
)
|
||||||
|
elif profile.connection.code in ("hysteria2", "vless"):
|
||||||
|
end_singbox()
|
||||||
|
else:
|
||||||
|
raise ProfileDeactivationError('Unsupported protocol.')
|
||||||
|
# ================= UPDATE UI ================
|
||||||
|
# if it made it this far, it worked in theory.
|
||||||
|
if profile_observer is not None:
|
||||||
|
profile_observer.notify('disabled', profile, dict(
|
||||||
|
explicitly=explicitly,
|
||||||
|
))
|
||||||
|
except ConnectionTerminationError:
|
||||||
|
raise ProfileDeactivationError('The profile could not be disabled.')
|
||||||
|
except ValueError:
|
||||||
|
raise ProfileDeactivationError('The profile could not be disabled.')
|
||||||
|
except FirewallError:
|
||||||
|
raise
|
||||||
|
|
||||||
|
time.sleep(1.0)
|
||||||
|
|
||||||
|
|
||||||
|
def turn_on_assassin(profile: SessionProfile):
|
||||||
|
profile.assassin = True
|
||||||
|
ProfileController.update(profile)
|
||||||
|
|
||||||
|
|
||||||
|
def turn_off_assassin(profile: SessionProfile):
|
||||||
|
profile.assassin = False
|
||||||
|
ProfileController.update(profile)
|
||||||
|
|
||||||
|
|
||||||
|
def destroy_profile(
|
||||||
|
profile: Union[SessionProfile, SystemProfile],
|
||||||
|
profile_observer: ProfileObserver = None,
|
||||||
|
ticket_observer: TicketObserver = None,
|
||||||
|
connection_observer: ConnectionObserver = None
|
||||||
|
) -> Result:
|
||||||
|
"""
|
||||||
|
Permanently end a profile, by:
|
||||||
|
|
||||||
|
1) Respawning it's ticket if it has one
|
||||||
|
2) Disabling it,
|
||||||
|
3) Destroying it on disk & cache
|
||||||
|
"""
|
||||||
|
####################################
|
||||||
|
# RESPAWN TICKET
|
||||||
|
####################################
|
||||||
|
which_ticket = profile.ticket
|
||||||
|
respawn_result = respawn_profile(
|
||||||
|
profile=profile,
|
||||||
|
ticket_observer=ticket_observer,
|
||||||
|
connection_observer=connection_observer
|
||||||
|
)
|
||||||
|
if which_ticket and not respawn_result.valid:
|
||||||
|
logger.error(f"Unable to respawn the ticket. {respawn_result.message} {respawn_result.error_type}")
|
||||||
|
|
||||||
|
####################################
|
||||||
|
# DESTROY PROFILE
|
||||||
|
####################################
|
||||||
|
logger.info("Disabling profile")
|
||||||
|
disable_profile(profile)
|
||||||
|
|
||||||
|
logger.info("Wiping profile from disk..")
|
||||||
|
ProfileController.delete(profile)
|
||||||
|
|
||||||
|
if profile_observer is not None:
|
||||||
|
profile_observer.notify('destroyed', profile)
|
||||||
|
|
||||||
|
return Result(valid=True, message="Deleted Profile")
|
||||||
|
|
@ -88,7 +88,8 @@ def activate_subscription(
|
||||||
raise InvalidSubscriptionError()
|
raise InvalidSubscriptionError()
|
||||||
|
|
||||||
profile.subscription = subscription
|
profile.subscription = subscription
|
||||||
profile.save()
|
from core.controllers.ProfileController import ProfileController
|
||||||
|
ProfileController.update(profile)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from core.models.manage.server_reply_tools import api_call_and_extraction, ApiCall
|
from core.models.manage.server_reply_tools import api_call_and_extraction, ApiCall
|
||||||
|
from core.controllers.ProfileController import ProfileController
|
||||||
from core.models.BaseProfile import BaseProfile as Profile
|
from core.models.BaseProfile import BaseProfile as Profile
|
||||||
from core.services.payment_phase.payment_state import OverallPaymentCycle, PaymentData, TicketPaymentData
|
from core.services.payment_phase.payment_state import OverallPaymentCycle, PaymentData, TicketPaymentData
|
||||||
from core.models.enum_models import ProtocolChoice, PaymentCategory
|
from core.models.enum_models import ProtocolChoice, PaymentCategory
|
||||||
|
|
@ -242,7 +243,7 @@ def create_individual_invoice(payment_cycle: OverallPaymentCycle, connection_obs
|
||||||
logger.info(f"creating an individual invoice for profile {profile_id}")
|
logger.info(f"creating an individual invoice for profile {profile_id}")
|
||||||
|
|
||||||
# Get profile object:
|
# Get profile object:
|
||||||
profile = Profile.find_by_id(profile_id)
|
profile = ProfileController.get(profile_id)
|
||||||
if not profile:
|
if not profile:
|
||||||
error_msg = f"Invalid profile id of {profile_id}"
|
error_msg = f"Invalid profile id of {profile_id}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
|
|
@ -315,7 +316,7 @@ def create_individual_invoice(payment_cycle: OverallPaymentCycle, connection_obs
|
||||||
expires_at=expires_at,
|
expires_at=expires_at,
|
||||||
billing_pending=True
|
billing_pending=True
|
||||||
)
|
)
|
||||||
profile.save()
|
ProfileController.update(profile)
|
||||||
payment_cycle.got_invoice = True
|
payment_cycle.got_invoice = True
|
||||||
return Result(valid=True, data=final_invoice)
|
return Result(valid=True, data=final_invoice)
|
||||||
|
|
||||||
|
|
@ -430,7 +431,7 @@ def get_profile_subscription(profile_id: int, save_pending: Optional[bool] = Fal
|
||||||
Optional flag to SAVE that the billing is pending. (metaphor of a POST request)
|
Optional flag to SAVE that the billing is pending. (metaphor of a POST request)
|
||||||
"""
|
"""
|
||||||
print(f"starting lookup of profile id {profile_id}")
|
print(f"starting lookup of profile id {profile_id}")
|
||||||
profile = Profile.find_by_id(profile_id)
|
profile = ProfileController.get(profile_id)
|
||||||
if not profile:
|
if not profile:
|
||||||
error_msg = f"Invalid profile id of {profile_id}"
|
error_msg = f"Invalid profile id of {profile_id}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
|
|
@ -440,7 +441,7 @@ def get_profile_subscription(profile_id: int, save_pending: Optional[bool] = Fal
|
||||||
# Optional save pending flag:
|
# Optional save pending flag:
|
||||||
if save_pending:
|
if save_pending:
|
||||||
profile.billing_pending = True
|
profile.billing_pending = True
|
||||||
profile.save()
|
ProfileController.update(profile)
|
||||||
|
|
||||||
print(f"returning profile {profile}")
|
print(f"returning profile {profile}")
|
||||||
return profile.subscription
|
return profile.subscription
|
||||||
|
|
@ -490,14 +491,14 @@ def mark_profile_as_paid(profile_id: int):
|
||||||
logger.info(f"Marking profile_id {str(profile_id)} as paid..")
|
logger.info(f"Marking profile_id {str(profile_id)} as paid..")
|
||||||
|
|
||||||
# Get real profile object:
|
# Get real profile object:
|
||||||
profile = Profile.find_by_id(profile_id)
|
profile = ProfileController.get(profile_id)
|
||||||
if not profile:
|
if not profile:
|
||||||
error_msg = f"Invalid profile id of {profile_id}"
|
error_msg = f"Invalid profile id of {profile_id}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
return Result(valid=False, message=error_msg, error_type=ResultError.INVALID_INPUT)
|
return Result(valid=False, message=error_msg, error_type=ResultError.INVALID_INPUT)
|
||||||
|
|
||||||
profile.subscription.billing_pending = False
|
profile.subscription.billing_pending = False
|
||||||
profile.save()
|
ProfileController.update(profile)
|
||||||
return Result(valid=True)
|
return Result(valid=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -513,7 +514,7 @@ def save_billing_code(profile_id: int, billing_code: str, expires_at: datetime)
|
||||||
return Result(valid=False, message=error_msg, error_type=ResultError.MISSING_DATA)
|
return Result(valid=False, message=error_msg, error_type=ResultError.MISSING_DATA)
|
||||||
|
|
||||||
# Get profile object:
|
# Get profile object:
|
||||||
profile = Profile.find_by_id(profile_id)
|
profile = ProfileController.get(profile_id)
|
||||||
if not profile:
|
if not profile:
|
||||||
error_msg = f"Invalid profile id of {profile_id}"
|
error_msg = f"Invalid profile id of {profile_id}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
|
|
@ -525,7 +526,7 @@ 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
|
||||||
)
|
)
|
||||||
profile.save()
|
ProfileController.update(profile)
|
||||||
return Result(valid=True)
|
return Result(valid=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Legacy system wouldn't create or save Subscription object with {str(billing_code)} and {str(expires_at)} or type {type(expires_at)}, with error: {str(e)}"
|
error_msg = f"Legacy system wouldn't create or save Subscription object with {str(billing_code)} and {str(expires_at)} or type {type(expires_at)}, with error: {str(e)}"
|
||||||
|
|
@ -542,7 +543,7 @@ def save_subscription(profile: Profile, subscription: Subscription) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
profile.subscription = subscription
|
profile.subscription = subscription
|
||||||
profile.save()
|
ProfileController.update(profile)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue