Skip to content

Commit ab1e333

Browse files
authored
Merge pull request #6 from ehs5/eel
CRMScript fetcher v2.0.0 using eel and Vue.js
2 parents 2cb7a9c + b580c39 commit ab1e333

43 files changed

Lines changed: 6923 additions & 718 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
dist/
21
build/
32
Icon mockups/
4-
spec_crmscript_fetcher.spec
5-
notes.txt
3+
dist/

.idea/crmscript_fetcher.iml

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/inspectionProfiles/profiles_settings.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CRMScript Fetcher.spec

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# -*- mode: python ; coding: utf-8 -*-
2+
3+
4+
block_cipher = None
5+
6+
7+
a = Analysis(['main.py'],
8+
pathex=[],
9+
binaries=[],
10+
datas=[('C:\\Users\\espen\\PycharmProjects\\crmscript_fetcher\\venv\\lib\\site-packages\\eel\\eel.js', 'eel'), ('vue', 'vue'), ('tenant_settings.json', '.'), ('crmscript_fetcher.crmscript', '.'), ('pyproject.toml', '.')],
11+
hiddenimports=['bottle_websocket'],
12+
hookspath=[],
13+
hooksconfig={},
14+
runtime_hooks=[],
15+
excludes=[],
16+
win_no_prefer_redirects=False,
17+
win_private_assemblies=False,
18+
cipher=block_cipher,
19+
noarchive=False)
20+
pyz = PYZ(a.pure, a.zipped_data,
21+
cipher=block_cipher)
22+
23+
exe = EXE(pyz,
24+
a.scripts,
25+
[],
26+
exclude_binaries=True,
27+
name='CRMScript Fetcher',
28+
debug=False,
29+
bootloader_ignore_signals=False,
30+
strip=False,
31+
upx=True,
32+
console=False,
33+
disable_windowed_traceback=False,
34+
target_arch=None,
35+
codesign_identity=None,
36+
entitlements_file=None , icon='icon.ico')
37+
coll = COLLECT(exe,
38+
a.binaries,
39+
a.zipfiles,
40+
a.datas,
41+
strip=False,
42+
upx=True,
43+
upx_exclude=[],
44+
name='CRMScript Fetcher')

bridge.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Using eel, this file exposes Python functions so that Vue.js can call them
2+
import eel
3+
4+
from fetch_service import FetchService
5+
from tenant_service import TenantService
6+
from utility import get_fetcher_script, get_current_version
7+
from utility import ask_directory_path
8+
from utility import open_directory
9+
10+
# Exposing Tenant Service methods
11+
tenant_service = TenantService()
12+
eel.expose(tenant_service.get_all_tenants)
13+
eel.expose(tenant_service.add_tenant)
14+
eel.expose(tenant_service.update_tenant)
15+
eel.expose(tenant_service.delete_tenant)
16+
17+
# Exposing Fetch Service methods
18+
fetch_service = FetchService()
19+
eel.expose(fetch_service.fetch)
20+
21+
# Utility methods
22+
eel.expose(get_fetcher_script)
23+
eel.expose(ask_directory_path)
24+
eel.expose(open_directory)
25+
eel.expose(get_current_version)

fetch.py

Lines changed: 0 additions & 79 deletions
This file was deleted.

fetch_service.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import json
2+
import requests
3+
from requests import Response
4+
from data_creator import DataCreator
5+
6+
CURRENT_CRMSCRIPT_VERSION = 2
7+
8+
class FetchService:
9+
"""
10+
Coordinates the fetch operation at the service level.
11+
Handles tenant validation, fetch execution, and response formatting.
12+
"""
13+
14+
@staticmethod
15+
def build_script_url(tenant: dict) -> str:
16+
"""Builds the URL we call SuperOffice with to fetch data."""
17+
script_url: str = (
18+
f"{tenant.get('url')}/scripts/customer.fcgi?action=safeParse"
19+
f"&includeId={tenant.get('include_id')}"
20+
f"&key={tenant.get('key')}"
21+
)
22+
23+
# Append fetch options to URL
24+
for key, value in tenant["fetch_options"].items():
25+
script_url += f"&{key}={str(value)}"
26+
27+
return script_url
28+
29+
def get_superoffice_data(self, tenant: dict) -> tuple[dict | None, str]:
30+
"""
31+
Fetches JSON data from SuperOffice.
32+
Returns tuple of (data, error_message).
33+
"""
34+
script_url = self.build_script_url(tenant)
35+
print(f"Getting JSON data from SuperOffice using endpoint: {script_url}")
36+
37+
try:
38+
# Do GET request to Superoffice
39+
response: Response = requests.get(script_url)
40+
response.raise_for_status() # Raises exception for any bad HTTP status
41+
42+
except requests.ConnectionError as e:
43+
error = f"Failed to connect to SuperOffice: {str(e)}"
44+
print(error)
45+
return None, error
46+
except requests.Timeout as e:
47+
error = f"Request to SuperOffice timed out: {str(e)}"
48+
print(error)
49+
return None, error
50+
except requests.HTTPError as e:
51+
error = f"HTTP error occurred: {str(e)}"
52+
print(error)
53+
return None, error
54+
except requests.RequestException as e:
55+
error = f"Failed to fetch data from SuperOffice: {str(e)}"
56+
print(error)
57+
return None, error
58+
59+
# Parse JSON and return data as dictionary from method
60+
try:
61+
data: dict = json.loads(response.text)
62+
print("JSON fetched!")
63+
return data, ""
64+
except json.JSONDecodeError as e:
65+
error: str = (f"Invalid JSON response from server<br><br>Contacting URL: {script_url}<br<br>"
66+
f"{str(e)}<br><br>"
67+
f"GET returned body:<br>{response.text}")
68+
print(error)
69+
return None, error
70+
71+
@staticmethod
72+
def validate_tenant(tenant: dict) -> str:
73+
"""
74+
Validates tenant configuration.
75+
Returns error message if invalid, empty string if valid.
76+
"""
77+
errors = []
78+
79+
if tenant.get("include_id") == "":
80+
errors.append("Script include ID cannot be empty")
81+
82+
if tenant.get("key") == "":
83+
errors.append("Script key cannot be empty")
84+
85+
if tenant.get("url") == "":
86+
errors.append("SuperOffice Service URL cannot be empty")
87+
88+
if tenant.get("local_directory") == "":
89+
errors.append("Local directory path cannot be empty")
90+
91+
if all(not option for option in tenant.get("fetch_options").values()):
92+
errors.append("You must check at least one fetch option")
93+
94+
if errors:
95+
return "Can not fetch CRMScripts because tenant settings are invalid:<br>" + \
96+
"<br>".join(f"- {error}" for error in errors)
97+
98+
return ""
99+
100+
def fetch(self, tenant) -> dict:
101+
"""
102+
Main entry point for fetching data from SuperOffice for a specific tenant.
103+
"""
104+
105+
# The result that is returned to frontend
106+
result: dict = {
107+
"success": False,
108+
"validation_error": False,
109+
"error": "",
110+
"info": ""
111+
}
112+
113+
try:
114+
# Make sure tenant is valid before trying to fetch
115+
validation_error: str = self.validate_tenant(tenant)
116+
if validation_error:
117+
result["validation_error"] = True
118+
result["error"] = validation_error
119+
return result
120+
121+
# Fetch data from SuperOffice
122+
data: dict | None
123+
error: str
124+
data, error = self.get_superoffice_data(tenant)
125+
126+
if error:
127+
result["error"] = error
128+
return result
129+
130+
if not data:
131+
raise Exception("No data returned from GET request")
132+
133+
# Get script version
134+
# Version 1 had no script_version key in JSON, so we default to that if none is present
135+
script_version: int = data.get("script_version", 1)
136+
137+
if CURRENT_CRMSCRIPT_VERSION > script_version:
138+
result["info"] = (f"Note! The fetcher CRMScript in use is not of the latest version. "
139+
f"Updating the script is recommended. Current script version is: {CURRENT_CRMSCRIPT_VERSION}")
140+
141+
# Create files and folder based on the JSON returned
142+
try:
143+
data_creator = DataCreator(data, script_version, tenant)
144+
success: bool = data_creator.create()
145+
146+
if not success:
147+
raise Exception("Failed to create local data files. Might be due to invalid script version?")
148+
149+
except Exception as e:
150+
result["error"] = f"Error creating local files: {str(e)}"
151+
return result
152+
153+
# Fetch and data creation was successful
154+
result["success"] = True
155+
return result
156+
157+
# Something went wrong somewhere, return error to frontend
158+
except Exception as e:
159+
result["error"] = f"Unexpected error: {str(e)}"
160+
return result

0 commit comments

Comments
 (0)