diff --git a/backend/accounts-service/src/main/java/com/banking/accountsservice/service/FinancialDataService.java b/backend/accounts-service/src/main/java/com/banking/accountsservice/service/FinancialDataService.java index b419591..9b8ce9f 100644 --- a/backend/accounts-service/src/main/java/com/banking/accountsservice/service/FinancialDataService.java +++ b/backend/accounts-service/src/main/java/com/banking/accountsservice/service/FinancialDataService.java @@ -36,6 +36,9 @@ public class FinancialDataService { private HttpClient httpClient; + // Cached Plaid sandbox access token (acquired via public_token create+exchange). + private volatile String plaidAccessToken; + @PostConstruct void init() { this.httpClient = HttpClient.newBuilder() @@ -60,22 +63,92 @@ public void enrichAsync(String accountNumber) { } private CompletableFuture fetchPlaidBalance(String accountNumber) { - String body = "{\"access_token\":\"access-sandbox-" + accountNumber + "\"}"; - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://sandbox.plaid.com/accounts/balance/get")) + return ensurePlaidAccessToken().thenCompose(token -> { + if (token == null) { + logger.warn("Plaid access token unavailable; skipping balance call"); + return CompletableFuture.completedFuture(null); + } + String body = "{\"access_token\":\"" + token + "\"}"; + HttpRequest request = plaidRequest("/accounts/balance/get", body); + return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(resp -> logger.info("Plaid balance response: status={}", resp.statusCode())) + .exceptionally(ex -> { + logger.warn("Plaid balance call failed: {}", ex.getMessage()); + return null; + }); + }); + } + + // Plaid sandbox tokens are not constructible: create a public_token then exchange it + // for an access_token. Cached and reused across accounts. + private CompletableFuture ensurePlaidAccessToken() { + String cached = plaidAccessToken; + if (cached != null) { + return CompletableFuture.completedFuture(cached); + } + String createBody = "{\"institution_id\":\"ins_109508\",\"initial_products\":[\"transactions\"]}"; + HttpRequest createReq = plaidRequest("/sandbox/public_token/create", createBody); + return httpClient.sendAsync(createReq, HttpResponse.BodyHandlers.ofString()) + .thenCompose(createResp -> { + String publicToken = extractJsonString(createResp.body(), "public_token"); + if (publicToken == null) { + logger.warn("Plaid public_token create failed: status={}", createResp.statusCode()); + return CompletableFuture.completedFuture((String) null); + } + HttpRequest exchangeReq = plaidRequest("/item/public_token/exchange", + "{\"public_token\":\"" + publicToken + "\"}"); + return httpClient.sendAsync(exchangeReq, HttpResponse.BodyHandlers.ofString()) + .thenApply(exchangeResp -> { + String token = extractJsonString(exchangeResp.body(), "access_token"); + if (token != null) { + plaidAccessToken = token; + logger.info("Plaid sandbox access token acquired"); + } else { + logger.warn("Plaid token exchange failed: status={}", exchangeResp.statusCode()); + } + return token; + }); + }) + .exceptionally(ex -> { + logger.warn("Plaid token setup failed: {}", ex.getMessage()); + return null; + }); + } + + private HttpRequest plaidRequest(String path, String body) { + return HttpRequest.newBuilder() + .uri(URI.create("https://sandbox.plaid.com" + path)) .timeout(TIMEOUT) .header("Content-Type", "application/json") .header("PLAID-CLIENT-ID", plaidClientId) .header("PLAID-SECRET", plaidSecret) .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); + } - return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenAccept(resp -> logger.info("Plaid balance response: status={}", resp.statusCode())) - .exceptionally(ex -> { - logger.warn("Plaid balance call failed: {}", ex.getMessage()); - return null; - }); + // Minimal "key":"value" string extractor — avoids pulling in a JSON dependency here. + private static String extractJsonString(String json, String key) { + if (json == null) { + return null; + } + String needle = "\"" + key + "\""; + int k = json.indexOf(needle); + if (k < 0) { + return null; + } + int colon = json.indexOf(':', k + needle.length()); + if (colon < 0) { + return null; + } + int q1 = json.indexOf('"', colon + 1); + if (q1 < 0) { + return null; + } + int q2 = json.indexOf('"', q1 + 1); + if (q2 < 0) { + return null; + } + return json.substring(q1 + 1, q2); } private CompletableFuture fetchExchangeRates() { diff --git a/kubernetes/base/deployments/accounts-service-deployment.yaml b/kubernetes/base/deployments/accounts-service-deployment.yaml index 9965f2f..70c0bb1 100644 --- a/kubernetes/base/deployments/accounts-service-deployment.yaml +++ b/kubernetes/base/deployments/accounts-service-deployment.yaml @@ -45,14 +45,8 @@ spec: key: secret - name: JAVA_OPTS value: "-Xms128m -Xmx384m -XX:MaxMetaspaceSize=128m -XX:ReservedCodeCacheSize=32m -XX:MaxDirectMemorySize=16m -XX:+UseSerialGC -XX:+UseStringDeduplication -XX:+UseContainerSupport -Djava.security.egd=file:/dev/./urandom -Dspring.main.lazy-initialization=true" - - name: AWS_S3_BUCKET - value: "banking-app-statements" - name: AWS_REGION value: "us-east-1" - - name: AWS_ACCESS_KEY_ID - value: "AKIAIOSFODNN7EXAMPLE" - - name: AWS_SECRET_ACCESS_KEY - value: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" envFrom: - configMapRef: name: app-config diff --git a/kubernetes/overlays/speedscale/accounts-service-thirdparty-env.yaml b/kubernetes/overlays/speedscale/accounts-service-thirdparty-env.yaml new file mode 100644 index 0000000..4cf7e62 --- /dev/null +++ b/kubernetes/overlays/speedscale/accounts-service-thirdparty-env.yaml @@ -0,0 +1,44 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: banking-accounts + namespace: banking-app +spec: + template: + spec: + containers: + - name: accounts-service + imagePullPolicy: IfNotPresent + env: + - name: PLAID_CLIENT_ID + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: PLAID_CLIENT_ID + - name: PLAID_SECRET + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: PLAID_SECRET + - name: EXCHANGE_RATES_APP_ID + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: EXCHANGE_RATES_APP_ID + - name: MOODYS_API_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: MOODYS_API_KEY + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: AWS_ACCESS_KEY_ID + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: AWS_SECRET_ACCESS_KEY + - name: AWS_S3_BUCKET + value: "speedscale-banking-demo-statements" diff --git a/kubernetes/overlays/speedscale/ai-service-thirdparty-env.yaml b/kubernetes/overlays/speedscale/ai-service-thirdparty-env.yaml new file mode 100644 index 0000000..ce2dceb --- /dev/null +++ b/kubernetes/overlays/speedscale/ai-service-thirdparty-env.yaml @@ -0,0 +1,36 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: banking-ai + namespace: banking-app +spec: + template: + spec: + containers: + - name: ai-service + env: + - name: AI_API_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: AI_API_KEY + - name: OPENAI_API_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: OPENAI_API_KEY + - name: GEMINI_API_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: GEMINI_API_KEY + - name: XAI_API_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: XAI_API_KEY + - name: OPENROUTER_API_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: OPENROUTER_API_KEY diff --git a/kubernetes/overlays/speedscale/banking-thirdparty-keys-sealed.yaml b/kubernetes/overlays/speedscale/banking-thirdparty-keys-sealed.yaml new file mode 100644 index 0000000..80762a2 --- /dev/null +++ b/kubernetes/overlays/speedscale/banking-thirdparty-keys-sealed.yaml @@ -0,0 +1,37 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + name: banking-thirdparty-keys + namespace: banking-app +spec: + encryptedData: + AI_API_KEY: AgCAkjG7zSdmPv03KClcxGvVTSINginI/X0GqN4cnRxGM5IaN+0OShsqRHX3CJa3PMwZs0zzrkpTQ0ENAYGudQViFrtWfZvpF3Pis3tRWBjpqg01dZ5QL1dVeKqtwtP4x00aDAjF3j6t9CPX2ggg2370RAsyZ3pFQI1rnGhN3r2L4U9lvaGFvng5JIk23MZq8d1u4Nvh/ba5SsVyHRCgAf7qWT/uCltgzaxv9hp2m5JHh9jwTyaUKcefIXc0aRHIjsclLTXAToQGP2mqwRDz/W4QsjrHLHZkJry6w5WIXyhOhm+yqt3J0Uy4LzTVJaCbtllqtOVpVqJm6I5NJrxU5eVjAgheCmAWwhEWKWhCVyHy1C8N8wAlF8r8+N56txusnw2G4+1Ipo1lM6hTaRJmeuPA/8h98pbgqvokeobR0w5t8QFvOf9lCvZdjY9Q1bxhpdWr24rbM7Zos9/FFF8p6GlCZzgji9KMCELCHtRuzS8c36DNj3fe/DNN9M7O2bZ66E1kcbca64BlcS2Zt2FRM5um2X6JShrnorCKXqFiEheLKnAwQH+D1tP3/4fnSfBdjeB/fq21NVzv7KJuighlNMI7pJz+Uhy1SQ1f7eSQeso6GQpatcv5la1JvZRlx7xsue+JwV8wyQd4povCSRaSUJ7RUVxI1EVtbfu3agtt6Doh8dGG1Tp7Miy1mTLFQ3r0ocRZMg6UAoMjh+MP2vJF46SBFogRbPu9Gfi0GkEdQ8J9rQ/a0e9shrhc1hVFsfWaqD38EpZoAWIUFoPkG3/TQQ1T1mGXqu+HevcYgLgEMWgMfNKJ7olSG+FqI8i/cQo0H0U6ZKFM3J7CbOwEBSo= + AWS_ACCESS_KEY_ID: AgCxd5ZVK1Aae3pBqsbAThBXXxoORlZAb/Ut9dx2VoWt0e4tEdT0JLmVN/OFIiqjAX43mKZhqQgV94JszLDfr3REDdMSIeUYTHYBisjTAxdUfBn/xmPoJZlSgvLj6AdxO2R23WntpdCPBMXx4Y0kWTja10JcQ8S5oRNqYTA1aZNJ4NUHwqP8OC4WgdXlwxaA23lMbYOohbcnlpg0kaBX3lZnvyl7NcuoA+nA9hqMi7HUZtWRuFVpHBtuY/qDA9lXxYALuIwsrVm/xPYTB7nvBYUBzXZDrOn96t/+8UuSJ4DTcctENgcpLFA2lqoMI19Vk2069XnfGGPuplU4ARjEeduX1yT0DHgfDXSsnZu2JzpuMW8qOmHY+k+TpnXc8m/RDDnCyTpjFYmz97xBAClIboWOOcQPonXqeNv/3UXOCNXCGN7nMPJx9x8dwBST7IjpyASFFTGutfKs23pSNDI90D8HgzR8S6McONDEB2n0s4bixX5EnPt5Aq1g+Ni/+Qwh8U9fNjYQQMx/AtKWwWnAbMt1guXdh9Vw3VWNckqEHucompCsS8kwPt8Raw61lDXvipXSUUtrgb8eiEJF65DA8ZeFqj4ipMt3Zqq5CwCkOUevUk1d3yeebCSiTgOOsZRZ7LQU9nFXLQWJ/349vH44fODnpifxh9Uy+sRDl/RYrEVcQOIWA0N3VrnxZ8tl2J8LMyoG1agqTEo34PelrzFLHgq644wYXA== + AWS_SECRET_ACCESS_KEY: AgC+wmWxZEdh0HGwVXIwkNiKM8NPiahpU2p5eLWGsBafTNP+RyIK6cYNxMoH11TmOzmr1dvII830vXm0aChNsLcUnGq42pBrraOuRKJWxxGc6VSrmaBm63+0rGgS3U7Afj4+vMfu2vOgL9cDMGyzldcSDKvSvJc1QhgOrTFWdY5ADL7L5mhYuTc7THGs3fmhuN/UqVk/kwdPLrcGWKDPYtvdSA4qr7AlQQPDrCsyET+cG4+q2pD7oUvtV61Pr/ZaOrXUzD0G2KEIxOEAgmKMTRDt9iHYRkWyAtX46RACN6A93UJCjUcKQGElJCpwwH0XtYKV/YNqrbO04R9MLh6KD+LoGZ73zPoCPI3KbZI39Vw0egtD1/Sb9wruD/DlI/Bdez/JxKsvFLSbplMfYCuB3k4TkOF4aijNVKL1Z/Pi41n4AV8lfMb1FwM3VD5MJ8hW/c2+urinmlpPXCCzcSUCt/EXeHTE6+4rYZKqbwQrOegP452qjPN+NmNEpUO3v8Xjt9DIt7bATkUgA92PvSzwGe2IZykJqvqvj/rHxNPkjeOvltU1f1wpmt7CL+f7l7wsXW8UNlYtbt/2CvtSZsroK7MbKutagbotR/dX/3/ZGjxZzfXHNW2o0WmSQcHkLxXs6cavc+viEseDps9DtkHzuUKwpwcqthSbIZ3Voc9cDZx6neya8/gPVumktDCPohTWLplhuu4ILevWEnhWqw6lU9s8nTlLzhYwjwSwdQ3R1XWQh/tuieTCsmbL + COMPLY_API_KEY: AgDW56zEbRugo2jfKzh9nLsaZLBZThOi41OzcGjfvQRQCoVtmNrbl/8Ntz8QZnXHbe1GwXtEtoTkX5SR11v4h88lfje3MuJu4NFQZ57Uw5vwy/zphh4llwdsZupBnY/YXv+sSsPWwY9ETJ3EVLHZ6d1pLj2T9E41GQ4SDUHAY9H/YDpntmxJ+rbYLJCuNGMlKvTaqUmwuiFkfxQuPpj/Cctpti0JYZAOoe2HH7iZhGJng/+Pjfny4nMcK9oj7qVJVCMjOi+g4AC6CE+1FEYwSv8dcYrvT3mfLCyWSO67DbWEaCJsTgMm/yoEDBRGf/nEJoBk4tCjLqH82AdVFtUDBWdut4Wqj0X1KFh0QmKLpfZbz3ctpIUQgj/EzqbeRp6q4OL/dqzKpIuCWrBDxXld9tc8nLFFPtc4HMirTNvPSozyCune0ppS8hOIxc5d4+mVFbYzUjmkeBXLAgn7gVoH/nvsohDaOVQPt/YoAQ7B58rPyEQnX4ffbYsz0VMj+Ad+hybVnM3dLWqfQcF/3v+U8HksAyMuhnFpwG7PgG/QjTCMMO8j5DzuGySgNhbiAmttSBXjHQZ6LDRyqFakzAdiNdOSL72lw2qzLys1nGa56RUmx5vLb/VFECUd6WYqeuj1M2VGX7qUcX7G2xm1XH+A2+e/dN0iT3Z54bZjkb+EKUKncu0N87Iov3kbXH6QYjlCXsE9t7uJwryerdDX3oTkasopg0wiZF9Ftxb9fUggyhiwrQ== + EXCHANGE_RATES_APP_ID: AgA1kqc+YKtI53beu2cHajdZqsguO1aDsQfEsjYJjVNEzC5mdF/O8X/DH6s/U5gBAUhzoA3NA2SR4jWHIaL9oNxifLmV6n4KDad5HywJKd6VogidMI0lgbCMvpjFambxrEIaqhlEacu7wsHFFDmPwRHyUURy2jHYspQjyWHsbiYiaftT1+VCID4MMt4aJodoDXZYaslAAVR33tKTAW6GyxoZtnSI5IY89qrHnxnn91iadI5CZATYFe/SwDl52nVWWMYPNoHknegrPlEnQRiT/9Q11jbN82q9YAhbzrt766FrYPVDojKaWbcmNvEtVcF5UDIzzbsH0CJ37Tb5HB+S8iOT+EHnIQ4yo7W5brfwGES3BNlzcQOkOtrCuiuWudlvNko74JkG7itv3Q2JSr2u/gy2jH0cQ9Nl7mKJIEu9uLQxa5xH2adXt9eGKtNiuFwrnL3AwBaq9V5Tdbh/m0YxSEuvMdC+SdzR/sUPAe/ywuIvZKsEIdL+RZVTX1PoICNwtcSLHwmtqNPVUpWYwaSd1+EsTwDcGVjHQbo4aBTGRsJ5u+n6HWYie/pYs1JezoifeBLHCcM4BcxahP9nqpD4+vdLH1jrhi95R7WvTQ6791j0nMnpEIPfsRznmgS0q55dYJ9llUu0aDIZs0iJf7J/CaXDZGk41DJuTHC0eBDOSDTF83DSWFWsLEZCivPR9TYVra9Pki/4DymX6PcxJPSYsY+w+hLACFbFcqGfrS7EEY7Bhw== + GEMINI_API_KEY: AgCtCZhrcwtYkOiqKYiONzpbei++hay52fHWD9Wd0cE0MPuJqY0IN1ou5nbfzS7fDebeXEQx84bFo+P6XX3tv7k1YhYhP2D4CMbORhn0ozd+j/4uMS+4LO92KTjU8zKrhVsH9OdXQoP8GSAxgDLiAes07Z6qwyj2ivnfPz1i5cKEdxZ6OmKXMQWvtVVyfPFfYUkD6giNbgkPDmvEO+b0mWV9zs7F94eJWc4pnh24QYgnK+UbKArYAHhm24zvVFN2iEjjfJS4P59R9Wz0/ulYRZI4FOAQ1EBfT1y5sWLnWm74xgwyn5rEKlRIt83JfH7Pd1oyhzvJ7vxUaev+2peOFHT9D0/s7zbpPgBVS84HB8n7ldQ6i/hNds0oRys0FmOEwqNdrT6Q5E52pGSYUIYnaXq7h3JycRNjWoYnJMe8OZQaas67GZQ1N0G2fr0K7UKPvq88Et6gGCpxEuMpvwF89Sm4/toHtBdu81xr92WmI0/2wij5Di2i0ezwhgSqkUbSKFfXKkg4+1UN1qxYxr739pjY9I4AaBuhoTTHK8pLBBZ/2NfYP1O4e8yEBNMVbxF/7yCKaHwVYT/XFVz+4Sxf5QC6AjEgbonAViW/bdRlNcLj4rnXH8nYjfRAkmfpnkS0SKRLejMoMvhTuWmYGrVzPI+DrCbgmE3DhmjSXl6zCxQDibWPTQAZaB4mNQfFkiA9t3kD2sAs5JkDGkVplp4ZkYIlmHFIEcCAohKyZ+OG744zO3wSLA7CYX5DU/XAW/fGInJMx6OuiA== + HIBP_API_KEY: AgAOPpBSzvW93FawgRQS9ew9y3W7eGltqmti3jnprhI9NLdmj9S1XUnaCofft0SSL9MaGl33vnguoBevxJSLb0GQXpg3Bdij2IASsJ/8DcvxGPtV7BsCriyieAd3kvVA7m0GwtwUuDpS4NkFJeahNOzMbODKae/SffBq0z9EoWvMjD0U1rhoroigSV7QdaH6RIg9Wxs9ok6dwQCyR+XYaN6iXF8d15LXSk8mPLmul54OAs9LwyFh2F3FrzAu+Aiy97fTk73TblQzWlHOodz2apgM8ZzjQCQGZC+VYdb2bkOp89Vfe25aiN6v2BLH5Nd2LENmRg2p+kcH08wY4AaSV97qt/U3jKckTis2t8nXAffUw83W0Usa4GN0E8FfFIgZYXXD2R1HzMy/iEcu8q3rdsJFN4FAg0gHXbyWYQaTW6hWrGdAkVtJadSt7RzUuEcJegdflkueXAIeTTJkDyzjy/wgYCttUgo9/v3CClCFUE/NRLomT4PXf9seeK357X7ktSGAb70NjN4IVEzKAwKG6lBKvzLwfS3djXc7PpvCK39yjd7+lRhAWqZnVPay05V1L5WwfjI8hf+WbVj0awJRHCzyrfo4Hsy6UnPigG9Ep0HnKQ0FTpApwsebXZDJ5G95Kbw295TebfydeTNu0tFFVdOSHwOH28+r3wy0aLwLnZynOvthYXYOC2X+676EAqjscdjG943RqZZf1V52Y/UkLRoi573Xv9kskZdSePxWWs7vzg== + JUMIO_API_TOKEN: AgClWDEc9ch2qwyRQ+P0aSfJmwrGuthANLtYwVuYhWYPa9FmeX68w1XekSaVyggD06T32t2yV7zOQ80SSzT1siqkSr5DLCOv/6ROXajIBVJ43nsCh5EadTD1GPcZVYBjf/LCyaN2mNT8noYhN4NQIePT6SBH56sfq6CIXmw39jN9FTscebRc8HRtqE37wGxH4G5lyMS2owF8HdpMXhl+dk1+ZJnZjsaK1sm28WiDhqZSIXPcSM6BR4TDXw9f8bi+mM32YkoZSZnBlE71PTj+ZEC4W5HIsnUkOG1puTeuO5jQ42512mH8PIuQFrqVDaaitlPROCGulExUOt3gNDK7adrH5Sl0OtrxYjOuS9E8+5kJ0fX7e102ly/ClH8uCBdhFU0ZBMp/WqiwDXSsAHSjJbif8x24UPUFPPHIqoMycqBArOs7iCv9Hlwig/6PglWej9v7CWIFNMhM0H3LPWvxR1alITp75USx3SOxdWFS9mK0TYfA4OLeZ4Y1e4IFB6PJ5pKya5yp2K93b4IhI2eeUp7hC2kzngtYV2gwz56fE3b2MMEdEIA0CJlawMbODYBPWYeSc63gtApZG2haYbUlkNr4hHkJ6PozwPe748uSaxNlWthpw1gPs/92syjkW7Ft8xAqFnjoU0kk0lkoc5Qm9BbGpr/fIRywewYnkVOTwTNSdPhZeDEnSTFDipnbynXneXOuQziBXohl9taRFJ1SlLBAc3H+bxbUFiLRpmGZ6GLLg1JigLSSuw7t + MAXMIND_ACCOUNT_ID: AgDD5UfTdaW5U1xEjxBFS9jBjAKLViQflgG4uQG2vVIWYWz+QaFpfkrmooG0TZwU9fXswv0YUdFcnKM+acQlADgywcjtxtQE2GtMrqheaasnRVfCJRpQQDSYn3SH1ZNMvOSAdr6SXplOfkID6XoVp6Rg4Ck1ifmCw47Ksrl6gh5sw8f57i7C2R0FzGHGzv+wHEdJBL1Sav9rni/InMTl+y3IEtH1UcV0sCWvi4QU2PAIYPsZiS0fp2R3oO9G6XNQajDwEAdSgdhKxtIyfuNOGifdfG1P+4WwGeF1KpO24/DVhivjSZn27X9w/M0N75F1WVO5OpJEckTg0Mj4GANKgYohmMaYGaHR1JXwwN6ylqLqb1k+fkxqQIFYp4AHQ4ZHHR6eZfA+GARkvoitKmMBo+FLfHLgKAc6EVnoS8MgZDQaaTvDdyKMiTa0UTIKfeWB60tXWM1AKpSeB15w4SwSE8FuiZI5/AKhjCWb78P5fw+8V9WYkMCe+giXiWToHiKbofAH7h3arp5F+dhkQqKGqZ23zwJ4SEfBkr/k+SLOnCrSqO29joDNGdl+4+glqYE1au6OsvL/lqnqeTNfulSGhDg8I8ZytJ7tgSzd5KgeyFxygJ1qW8y7w7Ei5EfEj7ZBXELfNMB8KQXvE5hKrrspxxUB27RSZD24fNKwV90Pdn/m7IUEU3IB9bFHadcxGmK5C0sewCqOERiZ + MAXMIND_LICENSE_KEY: AgCX5TetoG4dJB9am0q7Cv2nP47IqEXUVOTA7GBSoOCbnCZ+tYek0uYaLl1U186NI/H+BcEwo/PlqVHXzk0lPCSNwrPtoyNq3pd5W3XSAqC36O3Inco5KCDELDE6IJ8TFZPQthn2ua405+CvQggwVBRV8/C8rzvPXpM7+hDcX+mMogD56J5o+M3AXLQ6WtMBBh15o2xiLAAABvQeqO9ixFcVBNxpZBwNjj4KjnPEun3+Bwqtufa2LSnVtOg11uSjmKoi/1J7O7YGgsVykJXcMEAsrI3VVTdzrHJdFQWsMlrXvN0Ite/yRHMimLwLpRRoO0HBHK8Qwdnz/RYqTzLuUFyRDHrNP+pkb5ceoIoCpTJOs+8upfwsdS24yrq/qKuM7juR7ICvvHhqvE20P88nf6sDjM4uZmic5kV1iTCzZTPJENWMCjteMnpBsroxziitODx8lEGSzvAAiyscB6jhEZGby0lu6NfIzDXDWegyjN1e730aX8p0tAWeqTTw6b/ZbM8UztUPZlkEpTANUy4bv5i3aBCM/Snp8b6ecOUniAtecUCkpi87QguvNV0ZNadaUomqvjV4n/GTysAZCO8WgFAvpNxzErIg4pjhALma5UjLAc9g9Usqpc5js6Q+a2dN4E7OPOumvApGv2MZL0TGcL22EnMhFFV1qacO0zT6BGulXQh06YnR5GmWyR9BZ0yu1ATMrL/NiDby/QD9l1Q0WMyP + MOODYS_API_KEY: AgBfgeTPXv4kPisVRWokXJ0jMBBzdOHMu2wbHbcfL+SROEA2zfaLEmr381kj5+WRaVVFdOsNQIkXSY9MROVBrbgKuAZQP/8Kn8UXiNB/uCfDkbQJ3obh4Rw7P2ppxLYZUhFpuLE7K8jJxQvynIeMxlk9WS0rEG0OmZfmDiJ/kFItRaoP/CDbJR245+CeyU9wemXaQbL2AzDRIJlF9c/6CAG0srtzsOVDgRnrZjZegneJbbT51B+MGDG1rmygNteLWTXZfxQ9DvPPdBPQ9pT3G02nPOJNXF1JeHXXKd/BYyCrSEwIz9ViukEnzUnJkofIMEaIT/L5M/d1OgsP8zbyMRCaylo7BW0g0S3Guxu5Y9VtTQyYzIrNeM3XQF71chPFa43GbLfiOXy40o0JdPf/VqOilrq4WLOGvQqILHfz8dUbOUePVeNIQ/sfcgoqryNjr2maDs5s+0ewdf3t0zvBg7LGjPZZZEYVYUGbrryrtyi3Vv7gt55Tv9u0mpgmTRju7qYmfmuD8cVOx1yiBhTLB0/x05s+5acsKyW21/ygJDf+XNQrAXBr07OX01s+0FMvMm20ZqCTeT/Hd2LfukTjqhObp03CjoZUD9PdNMVblxHDnm4p0WAF8bNn7soYnyuf7VGLg+CpvUF1+AHzRtN+xD5cb8z/7e50iWt/r3UbG4RzZFLuCtVW+efTxJUHqgrVFIESdDh21QsG2XHAwlZh8D891JXe5AYsidw6rK4TAwxY6sYP1F8m67e3UCyVNA== + OPENAI_API_KEY: AgC9r7yOWRDEQ+MN/lN3aO03sk/bzfUEqwPzxFCNXqs8dkUDxzkO5iLR/+Bh4ayvBDMdaXOoeE7LjgkxLOH+RYp3AyBgKF+Z+aMazl/0IGZ+ez8wYTiNDSj2iwPdxcJZ+Lf2sYPGItDwVFhgftPou7ML1NvRqgERrw7HWsIhYejDo7iRK/ce2XZ1xeUgEtGvigivBn0hUNyxrlkU9fJqF24v3q36O2eDOtlOgpa5+1rfAsX7olfG9XSISY6VhLQVaNnIuhs46rbaVDMIBkdbjfx69j9SnA0Wzap/psaBp5EZJJ9E9waIfEHgXHpghOa0bWaMXP+hTKpgliDb9dVJgC1zRchy5FKhynBOCyQBogcY2VEPy2Z54jwos8vB7lEmwqzxYyJpGlABKE6wAMe23lh9nY/D/3s8LfdMNickx1J0Mb2j1bDmY7G0+SXm6neJEY2FyudVxgSdRv3Gz+AQT4P2QBC+XMe7BoeEdgLVXqN4gNImCj7hiEyZ74oYaa/PqWINGJW5MotZbFzKCKBo26PI8GY1fXsdnYXa3W+8cpafEKQDF6KcBgH/bSEB095JTU7GfP5UrCmoREuoqysS4yCNir1wwwMLbGc0Ah7dt+TPPFY1bXR7S/7IBVeJ/+63uJm5JxmecCfpzorfECkdsBYGoHR0D5HrXRZDeop9K0wAzju5NdkwjazLNhu2OeOaXFHoklVyy8bYtymnLC+9Qy3YAk5pEMV6RvtzxY54MwhtZkMZCaDyPZigCUAoXP3UsAHi5lG/Hng2+VErE8/E5JitgLuMNOxFcAaNHUFdTROTvEEgS/rswHV+LDORrW6XYJcIgU/BeSxbrb//ut8AdLa/B8fYT40lY7+kuxfFuGf7jxy0XQN3kKMIcvXWiCg+zRTW5zJyQyaUR8lwYvqT98XNl0Gb+w== + OPENROUTER_API_KEY: AgC9XSP+mJxvHyPgsjeSQYAkArDmHcMdRLsObg44ZBxWmz9fVzcZBOh6J+TsFwLf1w7vqh9fXe7K/zAj5b6/hLRE936AjLUMPkurPxSZhvHart3JXWl7flqxKkeC8Xdk4GqRufHuR8cu4VgAlDSq5crnE/pz+PmQgIFAjwax1hDqbJjJLW8vmlZo24/PPjZmqp7BTwVMNa2FyOA1SqQZsI6wMAj7AW2LJWvvK1t8jXCC9RWxrGjexR9HRoXshFbbzya7NhQ9NFEf1rsr3wvzBMzZoN9zhPTYIbCEpCVBdwtQRWrKysOTUH5VL5FEk81Qy9B7AOu5s1nT0V/kr2nF6UvW1xNosVYfjxs5j+SNLLOQdpwrdvh2SdtTfpWCRsAymUAIo7llUqHP9iXv0XblccUbwyg5VAEM3he+VB+jsdywY35RXMgPCQXbaaxDYaSh4ts1239fmdf3WeIEeUfdeq/V2H7nKhUbkAdyjpIKZC09LJ4c6xHMstVFA8p86FjL+zMHzeuZLx/4fx63YpmRrM96+61vzCOmkVEFynmWIeRvWGR7/GmBfUgBlEkenhZqIQep52VwmvCT4fCeAr8W/HytI4pg+xOXJPdQnUtUt87YTqWgw8Qh7nO1S3omrFY4QdG4ga/DkJ5CNpyT5GU3BZrz6+mBFaw9EW+43grdFl8Kt+MUfx6uL8wSKZjrrjx/5ilYi+8uHX0Ya3N9mMlpImoPMypT0RnS7tUgIhb0x6HpFvDqOHIpbdJBXFiiSsWpLCMhyYoVQAZjLKSFTvurB1IkANJOdNREI54Z + PAYPAL_ACCESS_TOKEN: AgCavDNO/tH3BhW/2uAezruE+IUENp0rDEYBkJWAHsHK3FlzRbggtVplVt4r0CYGob4qmr/VKeuI6c5zVv1TGXrg2lXv52XdzH+SZ7z5ATx/QMkx4t6N39/BfedZTPFr/Ffu2Cf/6twBS6jzfo7FUXVHWv2YdHQTYV4C3J7+cScPVp5GW0PCKwkDk3hGeoySaLXHXoqGIEJz8BzKojMTDWmkgORJ5R3AAuVEsGUHDUWOzck83gjNb8tUb9J3t07U/lneplEQ/TuBlqjQukAITHE2Ootz/iHICLnxnQhlnNC0jbtBGvQc1Afkmxhaqyx941wO9xdfWRJ9SD4TdQDOa54BE6/2yQL8TsF3NOToGgfcjGA5mFj1pqog4t048G21TI2tgSo5psAb8W3aq1TRc4NaJ7vSrI+unFZ4O7Ix+vefolCEhB4IR84ZNuUqoSHltwO/lZ8QK7Fznil/0CEby9AfjdILYOKkKvr00bXzq1ORnXAwCR5WKvN0Au4nGRnqQTKC57G2nG6VSDDq8Bxrpz1u4KbcSncjkCR5+Qg1yZ8GJXK1sr9RLlbzor+z2SKKeqgNakSUq8McYR6f5d5x9XuOiHL6kShR5to/sbnOzA24HxR0hjeSXfCf4NHMrW/XBJDQdxKBgFWtX3F5olDzev9luGJEXvqWBmDTYT/M4MS/NLX8F9lpLg/m5v3Jk8BQirvSD1Qhjp766PDRvXVD64B5Mn8XBjgR4PmTGmNHslMn9Iqk1ocPJaP5syAuRm1JEPDBMdWbSewx/MdpDAjE5b18dDDT25z0cn7yddBtOfKnDabB0zUZ5eu1Ei8HvsbhDRax + PLAID_CLIENT_ID: AgBFjfPgyte+JwqcCYcGuhjplhko9i6G6V6+eQfhgvPIAwdcN0VMNd9hoz0oTGmtu/P1qM9klmB++DUac3HbzTjl1yf/a8CbCw02EEehVPnKrv/EKDqkVbvc/T44ezQFWbNl8HdzPejUm7HeIMwh63I9MM1DDnIUUlUUxwrdcwGJWWUEMeLpY4hVvmvBcVgn/awlHvbnHl3iC+LH3BzzwmLIFjKMfHDcHNnnhz47exV6qbore02P2b/QnM9Zp9tGxz0qKkgtI/sVUVYS2NZ9OccAHEuopsKx9yJeIm97huAycSyPTwhnAsoqQTxtkmCqp4m9Getq01kxge0tMiJySgLwOr0ZejEWIxIxsjEpe+RFD45akWjqcpJxkwcdX4hXcrSmjoCJRBcjv7y7ynjfvJdTpvkbm+yTc2B57hP3rImHNN3og/zZf/ilp8CEBXvS1ZMj1pCpHmiDyYiN0CNWXeFs9OgnocVzBAP+KstWmCiX/3GJvR985cPmX1hR2zsloO3h7FGOCd7Q2aRLrqpNEWdr+vZ0DryUsTaoCoqYRiPMM48oFsz7HktZN1YlMEJ7xl/jMc8jB30lmqiNNnNp8Q8/esIFcR98KXV9nsjQYHII8MPpj5eKSSoia1Ctr0lXRoOeitwH8oyxANc30ti/QDXoHTHC1uhu0UhSoJCEmcAINAlw1PSIAYng5tmhEadcwCqYTVNVDbOsrENeNWDsE0847R/s+F2uFF8= + PLAID_SECRET: AgBn5bfIwiM50cwhI3MHBHZS689CPdSu0YOBUIGAK7kNj6WaEEFAQ8QuPkCS1L2oqHF4l+RpuH7bdunm+ZFpgfKxKUIutDiJwkSZhAL+X+Bv0IgVJ8qe5Sq0ZUE0YYY4wOEaY0WPev03Avaga2r1dxU2c7gC5QlczqSYVnwRhr1gKAtMgTilHCg79jt1g+aYiojp8AKHeUjOrWyGQItqTmqsZJjK1dsVn40l/pxLA0BA1sX5cDb2DVJPUva9PUAxXwtG9tZF+oFkHYurxVtPE8bXDrcA/6yW+OcujWPbuAP8Dyk+msA4U+/Fl7GwDruHkYRWne5UIg+Qpvvmqcx4JefFE2oowJWzEUb/1boNgY8tHK9BybUP/76yNOFoH528MKYw+T1zGHzClFkSaVmQQgolNokLCZYsAcWlhSc53XYB1/LzXQG+eEq+YHBPOTJkfhFZSag/vLG9tJA/SiirzfQfAjPYZlSQp4hhXMMSNLL85c5InTdEbT+dCP20E0TogD2kWEgsqGuQEA96lseF271YT6bgBdH1VK3lYcG3Xj3cbH7elvitE2jWe4uSidyvWSgzuPvUhNnMau8jEX0pTLt4IaoG/A9OUgklL54vo+UFSXK3BD0YvxfMSaS3dEO0F7hOa0QOvwQb68fGOA+a1+LGD6SjDpqnWvamkTlE1IOdUKaR/ClxPnqzmXVVs9GnciHU7PuT7p2UwS4eEptrPk7OFk41NNupvUBO2lD/uzo= + SENDGRID_API_KEY: AgBo7CgjCbLKtEVMshI8c2v2A/wQRS+8cHrn7YQiuOD2Ot0Ko/btgpfck4qOq2FmlsA6CDx1cO5JmJSAI2QDLxxTCPIUZtl/MGw5BYRZtaTOxxzIok/1HNG0F7ZPjSKXBU9NWzmN+ilF3qLbuP1jdywzmnHDDquILiaKa8GSr2bQF+FDUnjrnY6IiC29jmITHCfqXLi2sPsLfEaYXfld3bonAJPyOK5ZrXNEq0cJGxjcdMnLItqHod+9+XehhYGWU5DA+1Y38Ysi7dbWas/VsU7ayVtZnDFnVK5Hchfm8vaHtvv4q0tLT/mgXOaFIMnElU6W+pBYf2s2bOpFMl3mm6QXIC83zfViv8tH9a7TKHR3kJhbU8hjdM1yj6T7nmu3goJ97QcW3vyC9K6etO1hNLu+OBKLHSW+0Hg4riSa3AIg1ucTeQfh+hl0SvhH47SC14MAKPKG3sxO3+s6BaU3IElDiCROSv4IY6VWhiYl3frtjB9qIVJrlP4nAEw4c+nlV22tsuCmfVe9nQbjGZ1FaPl4wkB3KDWW/EZl0TY20sh+xYFsW9Mg5pHPI3grMfm5+cIZHKhWnV9AjnKSkS7xCYteJx8PoXC7N2YxSfeOr6D2R/QO0vy13/c5EGZ42LnKNwm2RJB1bbDrSANn2ysbab7DCJ4/TY1Hl6aFGiyYSeyIoLlrFEKDtLg2Li2J4sIeYnM9wHORIGlT+HxVinMVELWPYZpCEhPtIAIOKXFOiSVjGjqvt6YIWIZEchL/xvsOlDR0b9EdLiqG861ogpVY694cJZw4wz4= + SIFT_API_KEY: AgC0ucNg9zyyD/tKCAKNMH67qtsrqp6SQV4/Fy2k04eGpsWSBFL6JOGF6u327G1TAA8UKM2uc6J9eVQhxfdIYTLxk58U7qc4z538v4zELgoVpjlfdhCgqvZV7zNPELN7g3bX8FhP9W9Qu5WDsdXQ+xIBEM0tSn+KLT3LG45GZmnfvZAx87HEbaUn4jndH27qMTSNhlTP7PqqgxgOK+sSL2UiwASX0Xj3Fy9wpyXkWVHMBlRxJkyS6vTf94KSxPvXy7m/v0XFjRsb+2FIQywGkyBnyfiSQ9rz7cSf6JDthtO3mx0OgIRtHYa8Cg6f/njLX14xpiLXqiBqMkMubiFd3SCjc/KUR4rDCJRZG8Y70gBPGozI0sFNAGMXCoP4QBgXt5cSentEu3u/G3JDIGcKfMDskb67jjdlRZe3WT6ZTSzDyh1BFrWNS4DF1TmvAdOXrOrg1Oq1rk71o3OkrB2Oy5R6xMBS2urOjYW07LRWuFPyYs9pywCiAFX4kxnR1PflMZdIQT8UnBZtZ+HdctC+648W+eJwH/g4RrSSSk8B1TCVSehbnlvxOZdSrI9WRl2fd+Ry4ydGpJKj3UEczFOLYlT1JWkZk1VMBFr6jAc6y6hApxjef+ECQmBrXDq1W3S5GizE1dwO/tDiqebe9odJNHTLDQMejrJrdNsKHal0W5ZllWQE8EUZoVCp5y276dw3LtVf7c8CnoP/upPAOWCGoK9hwjDXAGSqk+UCzJjYB7qIsA== + SLACK_WEBHOOK_URL: AgBwqKNdVcCzN6DFhuUcuctrcJp5aDw1xvuhr+zfMqb2oaJwpj9W3tW58yEQLXeP6o788zkOqZlJkZ67nmT7RaBGLjKAWOqOg7SYHv+Nf06r3+2TXMcG4h67knwwfsZVsktPDRSCd3Fdsv+TawYgdnbBqlebWviAGrmQkWhM23nn9z1YnzeiUYQcOWKHch/rkqMek8pEp2NWckIBU+4V722nUmbBdC6BZtZdSg6rqQhC5zyWXI2cr2uhPk3LIXpJqzWdPx9kIgLOiZiZ1DwoaTzFpsAT13HLcrx4PtZ4BzuNgck2fiiUKjuJZhZlt10qGkL/+pq7ZNY/M/UU94vZyv0EvMJiE2+VWL1ZqLqMSnt96CJ/WxEi7jx2zPokAralzrs++RBtA2NofDkQ2JlFS2aarV7erb4VaeK5u6TUVel1ixs7OWdPX8BqkXoaz7QeCrey6FH9f1zIkwBVOjgFxDx6A5MG5WflC733/9kxDfR/5du0aOdbWoT8LgrvtguiOsU8PkErbEmtxladVWfXt4MW2BOWAaHT5RLNnPMZS9dv26XtIeSi6H30Iuvdu1LSsXEin6zXigtzz+sN/pUnfnWN+EKGSuJeeg6eY3yE0ZMRLX4Z2nrYBbd/Ye8vHPYvgPWyeJhGlGHdL0x1FEkFdOvYXMyjktYNmQv1p7uLZc5mRV9OiPweEcvXU1b0Q/IU1h/VlQ+Sr+HpScNBq/c1X0ZUJXWrX/pnZD5wRxcAVMojzTEGb5/qdzONXVWuEzvHe8ZE/Yeix541evf+CSwtwWNramioFKX4Ba8/dGKnsbJs + SOCURE_API_KEY: AgCKcJ6O7N/kObFyNZpZ0S/gBL2dDq6sWBvXlvFZA6ZLH8SPWcrpW4Ww4BEo2QnbdboQ8ymZNavlaRY1j/sWfuYzIkwiJhy0CasPQVAQlX3awX6c8Gb0pounkJPHffT0HR1s8mZlelg6g2dMWO0fvR5JaRIKIqzRWCv6oJQfowmAWxBas0a7m6nuRcDNK1o++8sG+AU887cfrw4UW4NPPOeI4As0aHYvzCQWa+F9B0Gveesgrvy9/Tc2DjMpmhG0A4W+Hqyw7Eja278cH7jmpxBoJ2u3PpohRrd8/N+8aDltm60/8xjpAgoLYP5mR/6p0/39vhlU98UdiFvSJ4TudDcrhZpjGIv5Cg7oZ55DwiZp3TPdtGXW9fFOCgNXBvZKNVK8ntgKDsSgZeuPC/rYfZvK/fifxyXbRAzUGXZxcUhSs879eIg9lz1iHpES8IvpPP+UFGWu0WBbIvL8w2PbKq9+pTJOcXk3cAHA8C7am2NJYUCPFSM81nYRWXjFbx+XND4wcJhxYw8iXX3R/BUZC9cnUrzyaIO9ao8mUVfZbhAn+Zkt42LMElYdbkXL+1xH57nz2XlQoVocyPCVqUwurkIToeEZsO3issS7eLTutOyXt3585T32i/S/mxJm8Omq3wC7UJyTuoW5jMJvYx0jQjHt/quxnrYfB0PkHfN7Xb4t+f92qMOE2sFX08HtEW848rhmgeeG0rteX3oc560tOWMz7Yj2ak/uSwwD/d1Sp+9+pnDZs5A= + STRIPE_API_KEY: AgBb+szp0244gAAw6yafmuQTmf0c0iJ1QNm5K2g4vd2qqzXU0K9NE3gz408znH3qm9DZgO0a52QiwUS6gak3dpTFZ65+r+H0kV09E4YFKvV9f/0uanfjEFQsHIGU7oJBXV7TbBx+5JzaVnaVgS4QsIejA2KjTJSHg4VZbi9WECFYmlqLl70oInmW9NrfhcWfeB2BnzxY2f+ekblVD1Yg62w38vmbZOdRsB84/WlWvKVj70dxRWhH6CXnFrnYS15KXYLH0YC8X57N/6NZPP+iIYK5S/maiwjRFvv8E+PDJirx7lBX1yZZ1bLRihX4GIK/b1zeUV3Z/F/v+mcEjdfw0eMqwMGrKJ8g2h1UaAdx7QsmiIuobcJxwTbPe3YBPwDxjz7zBFGu9SUcIbqDRD9wyJR+qql5I9Sx8oO90h6IIS37xfspvFpyxPZiSX4mqMFZeglPL5LN9BWIrXRNf7gJU2lx4jvO5GmNCVpXks+aSkvkPK0Qdf2hb954NTFxB+pYY6/6DzLcc2sUsIJD07HBkrx0odvCDbQegpykLLUEoAjodVgk42cRvZvSnwUu328aJNNE6zrd0yjLGy/sUXujzYxGendK7zWKNGLIArSGEWsKc5rAdFLE22F83OYPZMnknzsSXAMmRtUznGGfhqbaPfnDXiWmhDmxEiAOYGJLNgDUg0kRzAI92L08+ANuLgvKPfd1ljlU9tesmL06loypDhmUwLDBZ1PUHo3f9Hd9/3IYJriBADUf2eG2mJw39uox3ekn30NhnAJiodW6tbZJuK7EsAqoJMOgWOmU3yAG9csIj5A90xgzrUCG1WyP/bu/ueEWYkiaNIZG+jaK0g== + TWILIO_ACCOUNT_SID: AgCU5IWqnQf/5s9D7tv0ADgsqBrKszfb2IS2GP1rV1zq007s2+snli00XzYHYid6rg47ysPMXBbLWwh5BYGxUdSESteI+XZWby6qnvfReYTax77ian1mX7J6xxI97hx6kFR/5BtvpS1rNVGlMXXKCYcaVL+TydBbRWhFD9e5gC9OY2H4bX+l+4yeGAKEtzASiz4Oparmr0Gxmq0gnvi8upCqSj5d2VFxHScnqzZbwq0deROyRxctcPZV5uPV2XQU3hAKH5lE5ikf8+4iWE/kYB8uth4DicjbcrIpZdMY0KK5i4uAHPGIXl1xnyzxBH1BGMMqMJMBWbPVAHTb/Eanu+82mkxO5ygI2DDvXdH3+V1FvSlJNhY0v+tR7EtNqydfbi3/qkqg2yft45MG1kD64jQBAF+chiPYzuX9QFgGLlZivGIlI6pdSQrq4HfQ/Kug0Csxj8EWg7t6ULIsWg1GDNwlVoHLeRSHBTnUUcvBKsr33JFLLDsNB4i6lajyvepw22inYQJb+OccufsGEyTXjH0ltt9if5X9JAozWJItBAG2wDhSZrWTiMDfKvFniInKjIVAmiNbAKM2mrZdMxvDRDwcVCf7bOjXEN4HLF3vtEC+r/jqlLNZuIcyC822gH2DZbmjXH++I1+MCYCaEAOWMLx23e/w9PHS2Jo4wyIUbyIJeJepOQl5X114cOhwkZ350m9YWyGVYGbxSc58gNU4QVuD3Rzo4byfJnvxasDL3ZSygvTa + TWILIO_AUTH_TOKEN: AgCTXFtMgc+3Mcd7xHuKVQ5gD2SFpFBt3yCMp8rwOqPeD05NvDF4AfT7sPpx108+wHgqTrmle/D5Y6IiUkBKH9YBYfGLS52EpYruYyPZ7nbCf+z0BlGfl8dhtS0/RlU5iXPX76oJcH0cQ4/ixlOOw9PNMa4YCVvCHw1uVQYLPp3bbcvR+AemQ277QmuJ7OwdjOZj+0gcUPp5GY3kwsEEhBCiwLb4HfuNp2eEtPG7ekVs7zdfRfD5ieILB8J+1KMmoX+VywGE9uCd3iv8T1mk/gLdysGtWp2KyKZCYFLR22nmCYJ4Qu/QMP5fMYU5mbxHQnRS+BlwmbPpI+RcL1xpKTbn7OWiH/DUuUbSai2ks3dtBtdhiibz8lV0OzmRRZ20A0EYhlc39acpuzWgByLX6FtAdOYKrynBNplql49HkxALvMgpu2/qtjMzZmprU3GxJ7VD5soSIoyKT8FLWji7bPl+N9uX2pAbxnUEE8hTaQ8DiAdYxgb98rW3Y8jNyjV3F9MwedjM65URp9G+qTODsy8eSL1t+mznNgjSLXlhzjnumqV7i3pqIJdpw8kHT4tLq9eOM3+nj0PDd9HWDgQW3R41sO0DvLywNB/7iCoPMVNBsxxate125xLUoG1LO5afriLF1l05I14aDD/GMDFCX0fxUdcgFvVVrmkFi07Myb4slvJNWDKuMyMawdiIHSEDtzuhcHPwqk/3cZQPLm0uCtVwSJf1g7wl0nUvXXuDiC5DxA== + XAI_API_KEY: AgA0Lqfia/FLQGpkk310Iov1NU2Z04tcWFDFlxGcNR9CMTOLPQ0pWb+P3S1kvr3lGZwkUpeB0ifSgLRYQ6AslRqYk/oUP2TBz1VXSG1ZYiMLkIaC41C1NqM4LjU0m5tMqICLy+puIr5vEkIINHRe2NbVRVVTCn3XcggUGo/7uqFzqQpcICmVF3qaf4WjWUp0X2j5BUva0ANf4ck2hoDLRMyNZsq/qw9qoFTefNvPXN35ai7GhSVvZ/OydG87DmfjFlSbeDq4LeAUEcZZSzcLSECQss2Q8Znfsj+ioPFOdj9jDmfBXBZhvcJwN2WBdE0HQlncfibJ5cM3B2XvtTGhZhrrMhtgc+2rUKTlSGrhRboe+z/4lQN3pXiQWfDNgbftLduKJWelAADxFreXg+niixsrvCcfaP0ISijDk/CczSSV2Ueu+550GGqRzdYkrGUOctjkHQJNcVm4hbUgr0A29ieOAppz6Kk8SnggqSkZ8eoupDY1eSk7EX52ITpYae6BInOdeGRBJKt1NDqm0RcxXKx5p/z8uyfNynB8uCH9NvEnUmOS8zhMXV37AvOv4zMwtIAoRffFd1vS3KssI2ARnm7xucnC6qmmKT8tklkfowY2a0VUxgWRnGiE3WTyTwxFZCEE/W+Kdrz6fUuKwMymNGv1s4BL19RD9MzUq0NrwMEE3ILQBTNKSXniC8Vh9ppULDpzvcxwRB04p/Bw9IMYCkz/lo7YZfJlYsEwaBVnQNa5VF2R7TRDBRyX52+yUS0YnLATeRLzezUn5PgceJ1dHqKDR31khCjBo79bXXFwtL2YSLh7QrY= + template: + metadata: + name: banking-thirdparty-keys + namespace: banking-app + type: Opaque diff --git a/kubernetes/overlays/speedscale/fraud-service-thirdparty-env.yaml b/kubernetes/overlays/speedscale/fraud-service-thirdparty-env.yaml new file mode 100644 index 0000000..442f59e --- /dev/null +++ b/kubernetes/overlays/speedscale/fraud-service-thirdparty-env.yaml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: banking-fraud + namespace: banking-app +spec: + template: + spec: + containers: + - name: fraud-service + env: + - name: STRIPE_API_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: STRIPE_API_KEY + - name: SIFT_API_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: SIFT_API_KEY + - name: MAXMIND_ACCOUNT_ID + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: MAXMIND_ACCOUNT_ID + - name: MAXMIND_LICENSE_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: MAXMIND_LICENSE_KEY diff --git a/kubernetes/overlays/speedscale/kustomization.yaml b/kubernetes/overlays/speedscale/kustomization.yaml index 143892d..8411b17 100644 --- a/kubernetes/overlays/speedscale/kustomization.yaml +++ b/kubernetes/overlays/speedscale/kustomization.yaml @@ -5,6 +5,12 @@ resources: - ../../base - ../../observability - speedscale-metrics-services.yaml +- banking-thirdparty-keys-sealed.yaml + +# accounts-service: locally-built image with the real Plaid create→exchange→balance flow +images: +- name: ghcr.io/speedscale/microsvc/accounts-service + newTag: demo-plaid patches: # Add Istio injection and Speedscale labels to the existing banking-app namespace @@ -13,9 +19,6 @@ patches: kind: Namespace name: banking-app patch: |- - - op: add - path: /metadata/labels/istio-injection - value: enabled - op: add path: /metadata/labels/speedscale value: "true" @@ -26,4 +29,11 @@ patches: - path: api-gateway-annotations.yaml - path: frontend-annotations.yaml - path: fraud-service-annotations.yaml -- path: notification-service-annotations.yaml \ No newline at end of file +- path: notification-service-annotations.yaml +# Real third-party API keys (sourced from banking-thirdparty-keys SealedSecret) +- path: accounts-service-thirdparty-env.yaml +- path: transactions-service-thirdparty-env.yaml +- path: fraud-service-thirdparty-env.yaml +- path: notification-service-thirdparty-env.yaml +- path: user-service-thirdparty-env.yaml +- path: ai-service-thirdparty-env.yaml \ No newline at end of file diff --git a/kubernetes/overlays/speedscale/notification-service-thirdparty-env.yaml b/kubernetes/overlays/speedscale/notification-service-thirdparty-env.yaml new file mode 100644 index 0000000..239ee67 --- /dev/null +++ b/kubernetes/overlays/speedscale/notification-service-thirdparty-env.yaml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: banking-notification + namespace: banking-app +spec: + template: + spec: + containers: + - name: notification-service + env: + - name: SENDGRID_API_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: SENDGRID_API_KEY + - name: TWILIO_ACCOUNT_SID + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: TWILIO_ACCOUNT_SID + - name: TWILIO_AUTH_TOKEN + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: TWILIO_AUTH_TOKEN + - name: SLACK_WEBHOOK_URL + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: SLACK_WEBHOOK_URL diff --git a/kubernetes/overlays/speedscale/transactions-service-thirdparty-env.yaml b/kubernetes/overlays/speedscale/transactions-service-thirdparty-env.yaml new file mode 100644 index 0000000..72de797 --- /dev/null +++ b/kubernetes/overlays/speedscale/transactions-service-thirdparty-env.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: banking-transactions + namespace: banking-app +spec: + template: + spec: + containers: + - name: transactions-service + env: + - name: PAYMENT_STRIPE_API_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: STRIPE_API_KEY + - name: PAYMENT_PAYPAL_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: PAYPAL_ACCESS_TOKEN + - name: PAYMENT_COMPLY_API_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: COMPLY_API_KEY diff --git a/kubernetes/overlays/speedscale/user-service-thirdparty-env.yaml b/kubernetes/overlays/speedscale/user-service-thirdparty-env.yaml new file mode 100644 index 0000000..e39f2e9 --- /dev/null +++ b/kubernetes/overlays/speedscale/user-service-thirdparty-env.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: banking-user + namespace: banking-app +spec: + template: + spec: + containers: + - name: user-service + env: + - name: SOCURE_API_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: SOCURE_API_KEY + - name: JUMIO_API_TOKEN + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: JUMIO_API_TOKEN + - name: HIBP_API_KEY + valueFrom: + secretKeyRef: + name: banking-thirdparty-keys + key: HIBP_API_KEY