Avinashnalla7 commited on
Commit
9273ba4
·
1 Parent(s): 90b38dc

send-config: store json+pdf + sftp + best-effort email

Browse files
Files changed (2) hide show
  1. backend/api.py +136 -12
  2. requirements.txt +2 -0
backend/api.py CHANGED
@@ -86,30 +86,154 @@ async def put_pdf(pdf_id: str, file: UploadFile = File(...), pdf_name: str = For
86
  (UPLOADS_DIR / f"{pdf_id}.name.txt").write_text(pdf_name.strip(), encoding="utf-8")
87
  return {"ok": True}
88
 
89
-
90
  @app.post("/api/send-config")
91
- async def send_config(request: Request):
92
- payload = await request.json()
 
93
 
94
  pdf_id = (payload.get("pdf_id") or "").strip()
95
  template_id = (payload.get("template_id") or "").strip()
96
  config = payload.get("config")
97
 
98
- if not pdf_id or not template_id or not isinstance(config, dict):
99
- raise HTTPException(status_code=400, detail="Missing pdf_id / template_id / config")
 
 
 
 
100
 
101
  pdf_path = UPLOADS_DIR / f"{pdf_id}.pdf"
102
  if not pdf_path.exists():
103
  raise HTTPException(status_code=404, detail="PDF not found for pdf_id")
104
 
105
- CONFIGS_DIR.mkdir(parents=True, exist_ok=True)
106
- out_path = CONFIGS_DIR / f"{pdf_id}__{template_id}.json"
107
- out_path.write_text(
108
- json.dumps(payload, indent=2),
109
- encoding="utf-8"
110
- )
111
 
112
- return {"ok": True, "stored": str(out_path)}@app.post("/api/notify-unknown")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  async def notify_unknown(payload: Dict[str, Any]):
114
  """
115
  UNKNOWN TEMPLATE NOTIFICATION (rep email)
 
86
  (UPLOADS_DIR / f"{pdf_id}.name.txt").write_text(pdf_name.strip(), encoding="utf-8")
87
  return {"ok": True}
88
 
 
89
  @app.post("/api/send-config")
90
+ async def send_config(payload: Dict[str, Any]):
91
+ import os, json, io
92
+ from fastapi import HTTPException
93
 
94
  pdf_id = (payload.get("pdf_id") or "").strip()
95
  template_id = (payload.get("template_id") or "").strip()
96
  config = payload.get("config")
97
 
98
+ if not pdf_id:
99
+ raise HTTPException(status_code=400, detail="Missing pdf_id")
100
+ if not template_id:
101
+ raise HTTPException(status_code=400, detail="Missing template_id")
102
+ if not isinstance(config, dict):
103
+ raise HTTPException(status_code=400, detail="Missing config object")
104
 
105
  pdf_path = UPLOADS_DIR / f"{pdf_id}.pdf"
106
  if not pdf_path.exists():
107
  raise HTTPException(status_code=404, detail="PDF not found for pdf_id")
108
 
109
+ name_path = UPLOADS_DIR / f"{pdf_id}.name.txt"
110
+ pdf_name = name_path.read_text(encoding="utf-8").strip() if name_path.exists() else f"{pdf_id}.pdf"
 
 
 
 
111
 
112
+ cfg_obj = {"pdf_id": pdf_id, "template_id": template_id, "config": config}
113
+ cfg_bytes = (json.dumps(cfg_obj, indent=2) + "\n").encode("utf-8")
114
+ pdf_bytes = pdf_path.read_bytes()
115
+
116
+ # Local store (always)
117
+ local_dir = (UPLOADS_DIR / "configs" / template_id / pdf_id)
118
+ local_dir.mkdir(parents=True, exist_ok=True)
119
+ (local_dir / f"trainer_config_{pdf_id}_{template_id}.json").write_bytes(cfg_bytes)
120
+ (local_dir / pdf_name).write_bytes(pdf_bytes)
121
+
122
+ # SFTP store (best-effort)
123
+ sftp_status = {"enabled": False, "ok": False, "error": ""}
124
+ host = (os.environ.get("SFTP_HOST") or "").strip()
125
+ user = (os.environ.get("SFTP_USER") or "").strip()
126
+ root = (os.environ.get("SFTP_ROOT") or "").strip().rstrip("/")
127
+ if host and user and root:
128
+ sftp_status["enabled"] = True
129
+ try:
130
+ import paramiko
131
+ port = int((os.environ.get("SFTP_PORT") or "22").strip())
132
+ timeout = int((os.environ.get("SFTP_TIMEOUT_SECONDS") or "20").strip())
133
+ password = (os.environ.get("SFTP_PASS") or "").strip()
134
+ pkey_text = (os.environ.get("SFTP_PRIVATE_KEY") or "").strip()
135
+
136
+ client = paramiko.SSHClient()
137
+ client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
138
+
139
+ pkey = None
140
+ if pkey_text:
141
+ try:
142
+ pkey = paramiko.RSAKey.from_private_key(io.StringIO(pkey_text))
143
+ except Exception:
144
+ pkey = paramiko.Ed25519Key.from_private_key(io.StringIO(pkey_text))
145
+
146
+ client.connect(
147
+ hostname=host,
148
+ port=port,
149
+ username=user,
150
+ password=password if (password and not pkey) else None,
151
+ pkey=pkey,
152
+ timeout=timeout,
153
+ banner_timeout=timeout,
154
+ auth_timeout=timeout,
155
+ )
156
+
157
+ sftp = client.open_sftp()
158
+
159
+ def mkdir_p(path: str):
160
+ parts = path.strip("/").split("/")
161
+ cur = ""
162
+ for part in parts:
163
+ cur += "/" + part
164
+ try:
165
+ sftp.stat(cur)
166
+ except IOError:
167
+ sftp.mkdir(cur)
168
+
169
+ base = f"{root}/{template_id}/{pdf_id}".replace("//", "/")
170
+ mkdir_p(base)
171
+
172
+ with sftp.open(f"{base}/trainer_config_{pdf_id}_{template_id}.json", "wb") as f:
173
+ f.write(cfg_bytes)
174
+ with sftp.open(f"{base}/{pdf_name}", "wb") as f:
175
+ f.write(pdf_bytes)
176
+
177
+ sftp_status["ok"] = True
178
+ except Exception as e:
179
+ sftp_status["error"] = str(e)
180
+ finally:
181
+ try:
182
+ sftp.close()
183
+ except Exception:
184
+ pass
185
+ try:
186
+ client.close()
187
+ except Exception:
188
+ pass
189
+
190
+ # Email (best-effort: must NOT 500 the request)
191
+ email_status = {"ok": False, "error": ""}
192
+ try:
193
+ pipeline_to = _get_env_required("PDF_PIPELINE_PIPELINE_NOTIFY_TO")
194
+ notify_from = _get_env_required("PDF_PIPELINE_NOTIFY_FROM")
195
+ trainer_base_url = (os.environ.get("PDF_TRAINER_BASE_URL") or "https://saadmin-pdf-trainer-ui.hf.space").strip()
196
+ trainer_link = f"{trainer_base_url.rstrip('/')}/?pdf_id={pdf_id}"
197
+
198
+ subject = f"PDF_TRAINER_CONFIG_SUBMITTED | template_id={template_id}"
199
+ body = (
200
+ "Hi,\n\n"
201
+ "A PDF Trainer configuration was submitted.\n\n"
202
+ f"template_id: {template_id}\n"
203
+ f"pdf_id: {pdf_id}\n"
204
+ f"trainer_link: {trainer_link}\n\n"
205
+ "Attachments:\n"
206
+ f"- trainer_config_{pdf_id}_{template_id}.json\n"
207
+ f"- {pdf_name}\n\n"
208
+ "Thank you,\n"
209
+ "Inserio Automation\n"
210
+ )
211
+
212
+ gmail = _gmail()
213
+ gmail.send_email(
214
+ to_email=pipeline_to,
215
+ from_email=notify_from,
216
+ subject=subject,
217
+ body_text=body,
218
+ attachments=[
219
+ (f"trainer_config_{pdf_id}_{template_id}.json", cfg_bytes),
220
+ (pdf_name, pdf_bytes),
221
+ ],
222
+ )
223
+ email_status["ok"] = True
224
+ except Exception as e:
225
+ email_status["error"] = str(e)
226
+
227
+ return {
228
+ "ok": True,
229
+ "pdf_id": pdf_id,
230
+ "template_id": template_id,
231
+ "local_dir": str(local_dir),
232
+ "sftp": sftp_status,
233
+ "email": email_status,
234
+ }
235
+
236
+ @app.post("/api/notify-unknown")
237
  async def notify_unknown(payload: Dict[str, Any]):
238
  """
239
  UNKNOWN TEMPLATE NOTIFICATION (rep email)
requirements.txt CHANGED
@@ -14,3 +14,5 @@ openai>=1.0.0
14
 
15
 
16
  python-multipart
 
 
 
14
 
15
 
16
  python-multipart
17
+
18
+ paramiko