Pushed Edit Profile GUI logic into core. Cut ProfileController's role in this, and setup an isolated update_profile function to pass saves to the model
This commit is contained in:
parent
5235f168a3
commit
00d6fc7738
4 changed files with 158 additions and 14 deletions
|
|
@ -35,13 +35,6 @@ class ProfileController:
|
||||||
if profile_observer is not None:
|
if profile_observer is not None:
|
||||||
profile_observer.notify('created', profile)
|
profile_observer.notify('created', profile)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def update(profile: Union[SessionProfile, SystemProfile], profile_observer: ProfileObserver = None):
|
|
||||||
|
|
||||||
profile.save()
|
|
||||||
|
|
||||||
if profile_observer is not None:
|
|
||||||
profile_observer.notify('updated', profile)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def enable(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):
|
def enable(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):
|
||||||
|
|
|
||||||
117
core/controllers/profile_state/update_profile.py
Normal file
117
core/controllers/profile_state/update_profile.py
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
# utils
|
||||||
|
from core.errors.logger import logger
|
||||||
|
from core.models.Result import Result, ResultError
|
||||||
|
|
||||||
|
# JSON Models
|
||||||
|
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
|
||||||
|
|
||||||
|
# ORM Models
|
||||||
|
from core.models.orm_models.Location import Location
|
||||||
|
from core.models.orm_models.Operator import Operator
|
||||||
|
from core.models.orm_calls.location_calls import get_profile_location_data
|
||||||
|
|
||||||
|
SYSTEMWIDE_CHOICES = ['wireguard', 'hysteria2', 'vless']
|
||||||
|
SESSION_CHOICES = ['wireguard', 'tor', 'proxy']
|
||||||
|
|
||||||
|
"""
|
||||||
|
Steps:
|
||||||
|
1) Looks up the profile by id number
|
||||||
|
2) Filters what values to save, using the ORM for some such as location
|
||||||
|
3) Saves it with the abstract class' json serialization methods, which use the ORM methods
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Result Object
|
||||||
|
|
||||||
|
Called by:
|
||||||
|
GUI's editor_page
|
||||||
|
|
||||||
|
Raises Errors:
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
|
||||||
|
def update_profile(
|
||||||
|
profile_id: int,
|
||||||
|
key: str,
|
||||||
|
new_value: str) -> Result:
|
||||||
|
|
||||||
|
final_data = None
|
||||||
|
logger.info(f"[UPDATE PROFILE] Recieved profile_id {profile_id}, key {key}, and new_value {new_value}.")
|
||||||
|
|
||||||
|
if not key or not new_value:
|
||||||
|
return Result(valid=False, data=f"Invalid Inputs of {key} and {new_value}", error_type=ResultError.INVALID_INPUT)
|
||||||
|
|
||||||
|
profile = Profile.find_by_id(profile_id)
|
||||||
|
if not profile:
|
||||||
|
error_msg = f"Invalid profile id of {profile_id}"
|
||||||
|
return Result(valid=False, message=error_msg, error_type=ResultError.INVALID_INPUT)
|
||||||
|
|
||||||
|
# logger.info(f"[UPDATE PROFILE] Found a relevant profile {profile_id}.")
|
||||||
|
|
||||||
|
if key == 'dimentions':
|
||||||
|
profile.resolution = new_value
|
||||||
|
|
||||||
|
elif key == 'name':
|
||||||
|
profile.name = new_value
|
||||||
|
|
||||||
|
# ============= CONNECTION TYPE =============
|
||||||
|
elif key == 'connection':
|
||||||
|
if new_value == 'tor':
|
||||||
|
profile.connection.code = new_value
|
||||||
|
profile.connection.masked = True
|
||||||
|
|
||||||
|
elif new_value == 'just proxy':
|
||||||
|
profile.connection.code = 'system'
|
||||||
|
profile.connection.masked = True
|
||||||
|
else:
|
||||||
|
error_msg = 'System wide profiles not supported atm'
|
||||||
|
return Result(valid=False, message=error_msg, error_type=ResultError.NOT_SUPPORTED)
|
||||||
|
|
||||||
|
# ============= BROWSER =============
|
||||||
|
elif key == 'browser':
|
||||||
|
browser_type, browser_version = new_value.split(':', 1)
|
||||||
|
profile.application_version.application_code = browser_type
|
||||||
|
profile.application_version.version_number = browser_version
|
||||||
|
|
||||||
|
elif key == 'protocol':
|
||||||
|
# ============= SYSTEMWIDE =============
|
||||||
|
if profile.connection == 'system-wide':
|
||||||
|
if new_value in SYSTEMWIDE_CHOICES:
|
||||||
|
profile.connection.code = new_value
|
||||||
|
final_data = "edit_session"
|
||||||
|
else:
|
||||||
|
error_msg = f"{new_value} is not a systemwide choice"
|
||||||
|
return Result(valid=False, message=error_msg, error_type=ResultError.NOT_SUPPORTED)
|
||||||
|
|
||||||
|
# ============= SESSION =============
|
||||||
|
else:
|
||||||
|
if new_value in SESSION_CHOICES:
|
||||||
|
profile.connection.code = new_value
|
||||||
|
if new_value == 'wireguard':
|
||||||
|
profile.connection.masked = False
|
||||||
|
|
||||||
|
# ============= LOCATION =============
|
||||||
|
elif key == "location":
|
||||||
|
country_code, city_code = new_value.split("_")
|
||||||
|
location = get_profile_location_data( # SQLAlchemy
|
||||||
|
country_code=country_code,
|
||||||
|
city_code=city_code
|
||||||
|
)
|
||||||
|
# SQLAlchemy foreign key assignment — fills in id, timezone, operator, etc.
|
||||||
|
profile.location = location
|
||||||
|
|
||||||
|
# Subscription gets wiped on a location change, and is outside ORM
|
||||||
|
profile.subscription = None
|
||||||
|
else:
|
||||||
|
return Result(valid=False, message="Invalid Value to Edit", error_type=ResultError.INVALID_INPUT)
|
||||||
|
|
||||||
|
logger.info("[UPDATE PROFILE] Passing to Profile model to save..")
|
||||||
|
try:
|
||||||
|
profile.save()
|
||||||
|
logger.info("[UPDATE PROFILE] Save worked, passing to the GUI the Result..")
|
||||||
|
return Result(valid=True, data=final_data)
|
||||||
|
except:
|
||||||
|
error_msg = "Profile save failed"
|
||||||
|
return Result(valid=False, message=error_msg, error_type=ResultError.UNKNOWN)
|
||||||
|
|
@ -41,14 +41,17 @@ def execute_location_sql(country_code: str, city_code: str, session: Session) ->
|
||||||
|
|
||||||
def get_profile_location_data(country_code: str, city_code: str) -> Location:
|
def get_profile_location_data(country_code: str, city_code: str) -> Location:
|
||||||
# with get_session() as session:
|
# with get_session() as session:
|
||||||
location_object = execute_location_sql(country_code, city_code)
|
api_reply_object = execute_location_sql(country_code, city_code)
|
||||||
if location_object.valid:
|
if api_reply_object.valid:
|
||||||
return location_object.data
|
data = api_reply_object.data
|
||||||
|
location_obj = api_reply_object.data
|
||||||
|
return location_obj
|
||||||
else:
|
else:
|
||||||
logger.error(f"[BaseProfile] Got invalid SQL Query which could not be solved by the wrapper, with error message {location_object.message} and type {location_object.error_type}")
|
logger.error(f"[BaseProfile] Got invalid SQL Query which could not be solved by the wrapper, with error message {location_object.message} and type {location_object.error_type}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass_json
|
@dataclass_json
|
||||||
@dataclass
|
@dataclass
|
||||||
class BaseProfile(ABC):
|
class BaseProfile(ABC):
|
||||||
|
|
@ -88,8 +91,10 @@ class BaseProfile(ABC):
|
||||||
def is_system_profile(self):
|
def is_system_profile(self):
|
||||||
return type(self).__name__ == 'SystemProfile'
|
return type(self).__name__ == 'SystemProfile'
|
||||||
|
|
||||||
|
|
||||||
def save(self: Self):
|
def save(self: Self):
|
||||||
config_dict = self.to_dict() # Get dict, not JSON string
|
config_dict = self.to_dict()
|
||||||
|
|
||||||
location_dict = self.location.to_dict() # this is from SQLAlchemy, and not JSON-models, that's why it's separate.
|
location_dict = self.location.to_dict() # this is from SQLAlchemy, and not JSON-models, that's why it's separate.
|
||||||
|
|
||||||
if self.location:
|
if self.location:
|
||||||
|
|
@ -224,10 +229,10 @@ class BaseProfile(ABC):
|
||||||
city_code = profile.location.code
|
city_code = profile.location.code
|
||||||
|
|
||||||
# =========== GET DATA USING THAT COUNTRY & LOCATION ===========
|
# =========== GET DATA USING THAT COUNTRY & LOCATION ===========
|
||||||
location_object = get_profile_location_data(country_code, city_code)
|
location_dict = get_profile_location_data(country_code, city_code)
|
||||||
|
|
||||||
if location_object:
|
if location_dict:
|
||||||
profile['location'] = location_object
|
profile['location'] = location_dict
|
||||||
|
|
||||||
# this needs error handling if there's no location or malconformed config.
|
# this needs error handling if there's no location or malconformed config.
|
||||||
|
|
||||||
|
|
|
||||||
29
core/models/Result.py
Normal file
29
core/models/Result.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional, Any
|
||||||
|
|
||||||
|
class ResultError(Enum):
|
||||||
|
"""Classified error categories."""
|
||||||
|
SUCCESS = "success"
|
||||||
|
NOT_SUPPORTED = "NOT_SUPPORTED"
|
||||||
|
INVALID_INPUT = "invalid_input"
|
||||||
|
CONNECTION = "connection"
|
||||||
|
DATABASE = "database"
|
||||||
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Result:
|
||||||
|
valid: bool
|
||||||
|
error_type: ResultError = ResultError.SUCCESS
|
||||||
|
data: Optional[Any] = None
|
||||||
|
message: Optional[str] = None
|
||||||
|
|
||||||
|
def user_message(self) -> str:
|
||||||
|
"""Human-readable error for the UI."""
|
||||||
|
messages = {
|
||||||
|
DBErrorType.SUCCESS: "Operation completed successfully.",
|
||||||
|
DBErrorType.UNKNOWN: f"Error: {self.message}",
|
||||||
|
}
|
||||||
|
return messages.get(self.error_type, "Unknown error")
|
||||||
Loading…
Reference in a new issue