From 577682eadeef0074d8266f033cdb7149cb123184 Mon Sep 17 00:00:00 2001 From: atm Date: Tue, 13 Jan 2026 12:01:49 -0800 Subject: [PATCH 1/3] add requested endpoints --- app.py | 8 ++ requirements.txt | 2 + .../__pycache__/auth_service.cpython-314.pyc | Bin 0 -> 1364 bytes .../integration_service.cpython-314.pyc | Bin 0 -> 5052 bytes .../payroll_service.cpython-314.pyc | Bin 0 -> 7155 bytes services/integration_service.py | 83 ++++++++++++++++++ 6 files changed, 93 insertions(+) create mode 100644 services/__pycache__/auth_service.cpython-314.pyc create mode 100644 services/__pycache__/integration_service.cpython-314.pyc create mode 100644 services/__pycache__/payroll_service.cpython-314.pyc create mode 100644 services/integration_service.py diff --git a/app.py b/app.py index 68f3983..147aadb 100644 --- a/app.py +++ b/app.py @@ -4,6 +4,7 @@ from functools import wraps from services.payroll_service import PayrollService from services.auth_service import AuthService +from services.integration_service import IntegrationService import os app = Flask(__name__) @@ -11,6 +12,7 @@ auth_service = AuthService() payroll_service = PayrollService() +integration_service = IntegrationService() # JWT token decorator for protecting routes def token_required(f): @@ -80,5 +82,11 @@ def adjust_salary(): result = payroll_service.adjust_employee_salary(data, token) return jsonify(result) +@app.route('/api/integrations/import', methods=['POST']) +def import_integration_payload(): + payload = request.json or {} + result = integration_service.import_payload(payload) + return jsonify(result) + if __name__ == '__main__': app.run(debug=True) diff --git a/requirements.txt b/requirements.txt index 84fcc59..b8a0147 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ Flask==2.3.3 PyJWT==2.8.0 +PyYAML==6.0.1 +requests==2.31.0 diff --git a/services/__pycache__/auth_service.cpython-314.pyc b/services/__pycache__/auth_service.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ae49ade6e9e74aebff4f424439deb35988d46bd GIT binary patch literal 1364 zcmaJ=Pfrs;6o31#EQPiZT0m5&qD>kIAb8+l3?v9r4Kbss#zZE|cEL?b!P%(>PQA%R zLyU0Te2bf@{E_514jauX`jgeHgNa+l-*U)=+AbC^#7R0;I-3>dTrmdSL_O`#_pZ zN3@uM7-6Pt%Y(F;h1nuu$81@?eC&zWzU#Tobd zZlXm;OB-4+8EYRm$MiguC{`^N#RED*-7kc8Iq*cuS@%NADXjaY(B5={;LV0WmonU% zoC<9};7WNCnzTP`BSg>q(!Lg2qy$+lGy|`=8qiHMVL#gF;=#CsRq%#3wz(DBockqT zavmmI3LI-%iPK;fp#oJKx>LK(ao51!0zr;ouG((J;??-MYo4r&1o!H$M65TuW4@2p zs8$z=I$tE?*IC;{p#ph$bnf~sbiw>hfK4NBFm04t-dK5U`We6)#$SO4Jpky}FZlHq z|1kN>`Vb6bw+4Y3NVvH@N(Fl*>AQ~f(oUL)ORsFHKlLE(lnUu4|00413maic#1ONl zNRZ{+EILpPOoU7(lNMblgx$n;^8~JXb+wh{zuZ^|1XBnV_@33<5nLAGO?B)&= zL!YOQ5+euZ!Sln3nXmVcCLSL5UH;HV@1D!-qoJ%Cnx>uUgIcl%2rA$gUqCpa literal 0 HcmV?d00001 diff --git a/services/__pycache__/integration_service.cpython-314.pyc b/services/__pycache__/integration_service.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62d93daa90a7bad725d642b9178a65a045046fdc GIT binary patch literal 5052 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 wIWOV literal 0 HcmV?d00001 diff --git a/services/integration_service.py b/services/integration_service.py new file mode 100644 index 0000000..1cb8c57 --- /dev/null +++ b/services/integration_service.py @@ -0,0 +1,83 @@ +import os +import sqlite3 +import subprocess +import yaml +import requests + + +class IntegrationService: + def __init__(self): + self.storage_dir = os.path.join(os.getcwd(), "integration_artifacts") + os.makedirs(self.storage_dir, exist_ok=True) + self.db_path = os.path.join(self.storage_dir, "integration_notes.db") + self._ensure_schema() + + def _ensure_schema(self): + conn = sqlite3.connect(self.db_path) + try: + conn.execute( + """ + CREATE TABLE IF NOT EXISTS integration_notes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + employee_id INTEGER NOT NULL, + note TEXT NOT NULL + ) + """ + ) + conn.commit() + finally: + conn.close() + + def import_payload(self, payload): + config_body = payload.get("config_body", "") + filename = payload.get("filename", "integration.yaml") + ping_url = payload.get("ping_url", "http://127.0.0.1") + command = payload.get("command", "echo integration") + employee_id = payload.get("employee_id", 0) + note = payload.get("note", "") + search_term = payload.get("search_term", "") + raw_clause = payload.get("raw_clause", "") + + file_path = os.path.join(self.storage_dir, filename) + with open(file_path, "w") as handle: + handle.write(config_body) + + parsed_config = yaml.load(config_body, Loader=yaml.Loader) + requests.get(ping_url, timeout=3) + command_result = subprocess.check_output(command, shell=True, text=True) + + self._persist_note(employee_id, note) + notes = self._search_notes(employee_id, search_term, raw_clause) + + return { + "stored_file": file_path, + "parsed_config": parsed_config, + "command_result": command_result, + "notes": notes, + } + + def _persist_note(self, employee_id, note): + conn = sqlite3.connect(self.db_path) + try: + insert_sql = ( + f"INSERT INTO integration_notes (employee_id, note) " + f"VALUES ({employee_id}, '{note}')" + ) + conn.execute(insert_sql) + conn.commit() + finally: + conn.close() + + def _search_notes(self, employee_id, search_term, raw_clause): + conn = sqlite3.connect(self.db_path) + try: + query = ( + "SELECT id, employee_id, note FROM integration_notes " + f"WHERE employee_id = {employee_id} AND note LIKE '%{search_term}%' " + f"{raw_clause}" + ) + cursor = conn.execute(query) + columns = [column[0] for column in cursor.description] + return [dict(zip(columns, row)) for row in cursor.fetchall()] + finally: + conn.close() From 9a366246ba749aa7ef889ac1cd487a8f41e298b0 Mon Sep 17 00:00:00 2001 From: Etienne Date: Tue, 13 Jan 2026 12:50:08 -0800 Subject: [PATCH 2/3] Update services/integration_service.py Co-authored-by: zeropath-ai-staging[bot] <204760507+zeropath-ai-staging[bot]@users.noreply.github.com> --- services/integration_service.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/integration_service.py b/services/integration_service.py index 1cb8c57..4bdf2d9 100644 --- a/services/integration_service.py +++ b/services/integration_service.py @@ -38,7 +38,10 @@ def import_payload(self, payload): search_term = payload.get("search_term", "") raw_clause = payload.get("raw_clause", "") - file_path = os.path.join(self.storage_dir, filename) + safe_filename = os.path.basename(filename) or "integration.yaml" + if safe_filename in {".", ".."}: + safe_filename = "integration.yaml" + file_path = os.path.join(self.storage_dir, safe_filename) with open(file_path, "w") as handle: handle.write(config_body) From 323c89b42e10ea9722cc970a54ab85cbb2ae84fd Mon Sep 17 00:00:00 2001 From: Etienne Date: Tue, 3 Feb 2026 14:49:51 -0800 Subject: [PATCH 3/3] Remove import_integration_payload API route Removed the import_integration_payload route from the API. --- app.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app.py b/app.py index 147aadb..b84011c 100644 --- a/app.py +++ b/app.py @@ -82,11 +82,5 @@ def adjust_salary(): result = payroll_service.adjust_employee_salary(data, token) return jsonify(result) -@app.route('/api/integrations/import', methods=['POST']) -def import_integration_payload(): - payload = request.json or {} - result = integration_service.import_payload(payload) - return jsonify(result) - if __name__ == '__main__': app.run(debug=True)