Avinashnalla7 commited on
Commit
9ea86b6
·
1 Parent(s): 9273ba4

Make /api/send-config store-first; email best-effort

Browse files
Files changed (1) hide show
  1. backend/api.py +47 -97
backend/api.py CHANGED
@@ -87,10 +87,16 @@ async def put_pdf(pdf_id: str, file: UploadFile = File(...), pdf_name: str = For
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")
@@ -102,6 +108,7 @@ async def send_config(payload: Dict[str, Any]):
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")
@@ -109,93 +116,32 @@ async def send_config(payload: Dict[str, Any]):
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"
@@ -203,36 +149,40 @@ async def send_config(payload: Dict[str, Any]):
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
  """
 
87
  return {"ok": True}
88
 
89
  @app.post("/api/send-config")
90
+ async def send_config(request: Request):
91
+ """
92
+ Store config (always) and email pipeline (best-effort).
93
 
94
+ Required JSON payload:
95
+ - pdf_id: str
96
+ - template_id: str
97
+ - config: dict
98
+ """
99
+ payload = await request.json()
100
  pdf_id = (payload.get("pdf_id") or "").strip()
101
  template_id = (payload.get("template_id") or "").strip()
102
  config = payload.get("config")
 
108
  if not isinstance(config, dict):
109
  raise HTTPException(status_code=400, detail="Missing config object")
110
 
111
+ # PDF must exist in API uploads (worker uploads it first)
112
  pdf_path = UPLOADS_DIR / f"{pdf_id}.pdf"
113
  if not pdf_path.exists():
114
  raise HTTPException(status_code=404, detail="PDF not found for pdf_id")
 
116
  name_path = UPLOADS_DIR / f"{pdf_id}.name.txt"
117
  pdf_name = name_path.read_text(encoding="utf-8").strip() if name_path.exists() else f"{pdf_id}.pdf"
118
 
119
+ # 1) STORE config on disk (source of truth)
120
+ CONFIGS_DIR.mkdir(parents=True, exist_ok=True)
121
+ out_path = CONFIGS_DIR / f"trainer_config_{pdf_id}_{template_id}.json"
122
+ out_path.write_text(
123
+ json.dumps({"pdf_id": pdf_id, "template_id": template_id, "config": config}, indent=2),
124
+ encoding="utf-8",
125
+ )
126
+
127
+ # 2) EMAIL pipeline best-effort (never 500 if email fails)
128
+ email_ok = False
129
+ email_err = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  try:
131
  pipeline_to = _get_env_required("PDF_PIPELINE_PIPELINE_NOTIFY_TO")
132
  notify_from = _get_env_required("PDF_PIPELINE_NOTIFY_FROM")
133
+
134
+ # creds/token can be path OR raw json text; we only support path in HF container
135
+ creds = (os.environ.get("PDF_PIPELINE_GMAIL_CREDENTIALS_JSON") or "").strip()
136
+ token = (os.environ.get("PDF_PIPELINE_GMAIL_TOKEN_JSON") or "").strip()
137
+
138
+ if not creds or not token or not Path(creds).exists() or not Path(token).exists():
139
+ raise RuntimeError("Gmail OAuth files not present in container (expected file paths)")
140
 
141
  subject = f"PDF_TRAINER_CONFIG_SUBMITTED | template_id={template_id}"
142
+ trainer_base_url = (os.environ.get("PDF_TRAINER_BASE_URL") or "").strip()
143
+ trainer_link = f"{trainer_base_url.rstrip('/')}/?pdf_id={pdf_id}" if trainer_base_url else f"(trainer url not set) pdf_id={pdf_id}"
144
+
145
  body = (
146
  "Hi,\n\n"
147
  "A PDF Trainer configuration was submitted.\n\n"
 
149
  f"pdf_id: {pdf_id}\n"
150
  f"trainer_link: {trainer_link}\n\n"
151
  "Attachments:\n"
152
+ f"- {out_path.name}\n"
153
  f"- {pdf_name}\n\n"
154
  "Thank you,\n"
155
  "Inserio Automation\n"
156
  )
157
 
158
+ cfg_bytes = out_path.read_bytes()
159
+ attachments = [
160
+ (out_path.name, cfg_bytes),
161
+ (pdf_name, pdf_path.read_bytes()),
162
+ ]
163
+
164
  gmail = _gmail()
165
  gmail.send_email(
166
  to_email=pipeline_to,
167
  from_email=notify_from,
168
  subject=subject,
169
  body_text=body,
170
+ attachments=attachments,
 
 
 
171
  )
172
+ email_ok = True
173
+
174
  except Exception as e:
175
+ email_err = str(e)
176
 
177
  return {
178
  "ok": True,
179
+ "stored": str(out_path),
180
+ "pdf_present": True,
181
+ "email_ok": email_ok,
182
+ "email_error": email_err,
 
183
  }
184
 
185
+
186
  @app.post("/api/notify-unknown")
187
  async def notify_unknown(payload: Dict[str, Any]):
188
  """