// Plan: Upload Attachments Early v2 — does PB admin auth INLINE so binary survives. // (v1 relied on a pre-computed $json.pb_token from a separate PocketBase // Admin Auth HTTP node — but that node strips binary, so v1's uploads had // no bytes to send. v2 inlines the auth via this.helpers.httpRequest.) // // On each invocation: // 1. Read PB admin creds from PocketBase Connection (the Set node upstream) // 2. POST to /api/collections/_superusers/auth-with-password → get token // 3. Walk the binary on the incoming item(s), emit one item per attachment // with the freshly-minted token + all the upload prep data // 4. Items carry the binary so the next HTTP node can multipart-upload it // // On no-attachments emails, emits one placeholder item with no binary. const inputs = $input.all(); if (inputs.length === 0) { return [{ json: { _attachment_count: 0 }, binary: {} }]; } // 1. Read PB admin creds from PocketBase Connection (the Set node upstream). // We use $('Node Name').item.json so we don't depend on what $json carries // (the HTTP node that previously did the auth replaced $json with its // own response shape). let ctx = {}; try { ctx = $('PocketBase Connection').item.json || {}; } catch (e) { ctx = inputs[0].json || {}; } const pbUrl = String(ctx.pb_url || ''); const pbEmail = String(ctx.pb_admin_email || ''); const pbPassword = String(ctx.pb_admin_password || ''); const messageId = String(ctx.message_id || ''); const effectiveMessageId = String(ctx._effective_message_id || messageId); if (!pbUrl || !pbEmail || !pbPassword) { throw new Error('PB credentials missing from PocketBase Connection: ' + JSON.stringify({ hasUrl: !!pbUrl, hasEmail: !!pbEmail, hasPass: !!pbPassword })); } // 2. POST to PB to mint an admin token. This happens INSIDE the Code node, // so binary on the input items is preserved (unlike going through an HTTP // Request node, which strips binary). let pbToken = ''; let pbAdminId = ''; try { const authResp = await this.helpers.httpRequest({ method: 'POST', url: pbUrl + '/api/collections/_superusers/auth-with-password', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: { identity: pbEmail, password: pbPassword }, json: true, timeout: 15000, }); pbToken = String(authResp.token || ''); pbAdminId = String((authResp.admin && authResp.admin.id) || ''); if (!pbToken) { throw new Error('PB auth returned no token: ' + JSON.stringify(authResp)); } } catch (authErr) { throw new Error('PB admin auth failed: ' + (authErr.message || authErr)); } // 3. Walk binary on every input item and emit one upload-prep item per // attachment. The binary is re-anchored under a stable `attachment` // property so the next HTTP node can reference it predictably. const out = []; let attachmentIndex = 0; for (const input of inputs) { const bin = input.binary || {}; for (const key of Object.keys(bin)) { const b = bin[key]; if (!b) continue; const filename = String(b.fileName || 'attachment'); const contentType = String(b.mimeType || 'application/octet-stream'); out.push({ json: { pb_url: pbUrl, pb_token: pbToken, pb_admin_id: pbAdminId, email_message_id: effectiveMessageId, filename: filename, content_type: contentType, size_bytes: Number(b.fileSize || 0), binary_key: key, _upload_index: attachmentIndex, }, binary: { attachment: b }, }); attachmentIndex++; } } if (out.length === 0) { // No attachments — emit a placeholder item with no binary so the chain // continues. The downstream HTTP POST will see no binary and PB will // reject (caught by onError: continueRegularOutput on the HTTP node). out.push({ json: { pb_url: pbUrl, pb_token: pbToken, pb_admin_id: pbAdminId, email_message_id: effectiveMessageId, filename: '', content_type: '', size_bytes: 0, binary_key: '__none__', _upload_index: 0, _no_attachments: true, }, binary: {}, }); } return out;