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:
|
||||
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
|
||||
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:
|
||||
# with get_session() as session:
|
||||
location_object = execute_location_sql(country_code, city_code)
|
||||
if location_object.valid:
|
||||
return location_object.data
|
||||
api_reply_object = execute_location_sql(country_code, city_code)
|
||||
if api_reply_object.valid:
|
||||
data = api_reply_object.data
|
||||
location_obj = api_reply_object.data
|
||||
return location_obj
|
||||
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}")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class BaseProfile(ABC):
|
||||
|
|
@ -88,8 +91,10 @@ class BaseProfile(ABC):
|
|||
def is_system_profile(self):
|
||||
return type(self).__name__ == 'SystemProfile'
|
||||
|
||||
|
||||
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.
|
||||
|
||||
if self.location:
|
||||
|
|
@ -224,10 +229,10 @@ class BaseProfile(ABC):
|
|||
city_code = profile.location.code
|
||||
|
||||
# =========== 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:
|
||||
profile['location'] = location_object
|
||||
if location_dict:
|
||||
profile['location'] = location_dict
|
||||
|
||||
# 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