merge: maximum performance extraction (parallel pages + crash-safe partial JSONL)
# Conflicts: # .env.dev.sops # .env.prod.sops # extract/padelnomics_extract/src/padelnomics_extract/playtomic_tenants.py
This commit is contained in:
103
.env.dev.sops
103
.env.dev.sops
@@ -1,77 +1,76 @@
|
|||||||
#ENC[AES256_GCM,data:ErezNQ==,iv:GtNs5EgH0f1gI5cDTApW6Vvw2x90NI2rAGXBJpMmLgQ=,tag:EmNenJvfpuNP3HeYvD8UsA==,type:comment]
|
#ENC[AES256_GCM,data:9JNa0g==,iv:KnGl3/4KQWkVnFXn9iKU5z5Ys6KXWOnSEoE/Jjks2pw=,tag:ZD3nrOQmhUjPkZiwtV330g==,type:comment]
|
||||||
APP_NAME=ENC[AES256_GCM,data:+vZp0sqaYRWXFlI=,iv:kvBQt4XxEHI7E1v//c9Z/rqjvaJXTgx5cvHzHqzaI3c=,tag:EaJb6qZXvzzJEapM96j1+g==,type:str]
|
APP_NAME=ENC[AES256_GCM,data:Vic/MJYoxZo8JAI=,iv:n1SEGQaGeZtYMtLmDRFiljDBbNKFvCzZPNtaFBNauYY=,tag:Smsd20Ba56QZKVFpRmhRPQ==,type:str]
|
||||||
SECRET_KEY=ENC[AES256_GCM,data:UCK7JrrYd5oHSHdT4MyB+qg/L/Si2LrZxOEEkjJUV/s=,iv:tyZjyOe+7PBPa83jKMfSRrjCuKsqSk2StJLBteE91qc=,tag:Lmz32nb9Xv4RcY7akFjXLw==,type:str]
|
SECRET_KEY=ENC[AES256_GCM,data:a3Bhj3gSQaE3llRWBYzpjoFDhhhSsNee67jXJs7+qn4=,iv:yvrx78X5Ut4DBSlmBnIn09ESVc/tuDiwiV4njmjcvko=,tag:cbFUTAEpX+isQD9FCVllsw==,type:str]
|
||||||
BASE_URL=ENC[AES256_GCM,data:T8gU8YF64yAH7umlYGh63Ju4nQOm,iv:woziZtqBwtv57Mohdc7QJwt5r7BGYkNq9yo6LxUus4Y=,tag:RVDFUzblyENvi6Uv7Agp5g==,type:str]
|
BASE_URL=ENC[AES256_GCM,data:LcbPDZf9Pwcuv7RxN9xhNfa9Tufi,iv:cOdjW9nNe+BuDXh+dL4b5LFQL2mKBiKV0FaEsDGMAQc=,tag:3uAn3AIwsztIfGpkQLD5Fg==,type:str]
|
||||||
DEBUG=ENC[AES256_GCM,data:1Hv6qw==,iv:3Y8a1DCCaNgGsuaBVmUrjEqW1nUG+I8Dqi4XXgIWcNc=,tag:5Hw12JrPTc5T4xeV3CaEeg==,type:str]
|
DEBUG=ENC[AES256_GCM,data:qrEGkA==,iv:bCyEDWiEzolHo4vabiyYTsqM0eUaBmNbXYYu4wCsaeE=,tag:80gnDNbdZHRWVEYtuA1M2Q==,type:str]
|
||||||
#ENC[AES256_GCM,data:zATUJ7DT3TrnC+wYuxoo5H4Cnzvx+qtKBubK5JdlZIku71vaESrQ40LtzRf7bzE+nBWrlbLzoZA1hThR1xQUAF3YQFp6tMnECg==,iv:4d/TqqrUf/SMMfP65cRlHePm1E7uUiElXGAO1dVpQvs=,tag:oQVHdf+8uAPYo7qEBoAuvQ==,type:comment]
|
#ENC[AES256_GCM,data:YmlGAWpXxRCqam3oTWtGxHDXC+svEXI4HyUxrm/8OcKTuJsYPcL1WcnYqrP5Mf5lU5qPezEXUrrgZy8vjVW6qAbb0IA2PMM4Kg==,iv:dx6Dn99dJgjwyvUp8NAygXjRQ50yKYFeC73Oqt9WvmY=,tag:6JLF2ixSAv39VkKt6+cecQ==,type:comment]
|
||||||
ADMIN_EMAILS=ENC[AES256_GCM,data:1fSFoO+nZ4PfdnoQfA==,iv:dyBsQa38WLH7YhhY03l+i4iaNmXXi/YauyT0CO/xtaY=,tag:otWDrofdNcFigotMcv96tg==,type:str]
|
ADMIN_EMAILS=ENC[AES256_GCM,data:hlG8b32WlD4ems3VKQ==,iv:wWO08dmX4oLhHulXg4HUG0PjRnFiX19RUTkTvjqIw5I=,tag:KMjXsBt7aE/KqlCfV+fdMg==,type:str]
|
||||||
#ENC[AES256_GCM,data:d8rqleReUHUt,iv:5f4bzvxWzty2PIkrAGb6iw+Nb2evkfPFFoH1F7kdFaY=,tag:GpqLD6QJcGGyEyUWbcPsQg==,type:comment]
|
#ENC[AES256_GCM,data:b2wQxnL8Q2Bp,iv:q8ep3yUPzCumpZpljoVL2jbcPdsI5c2piiZ0x5k10Mw=,tag:IbjkT0Mjgu9n+6FGiPVihg==,type:comment]
|
||||||
DATABASE_PATH=ENC[AES256_GCM,data:m6BUOfuhyTcqZPI=,iv:t31+AEMO+Pa7jKjUPjVhe3DUe3yhY02xOF81tn7+Zkw=,tag:F8N72pVRqfJda6p5exHvjg==,type:str]
|
DATABASE_PATH=ENC[AES256_GCM,data:pEpMUrL7ZHAzMT4=,iv:eDGudDVsW5vF0sENri7gQrFlCEdoWYP6hT5ZeXXs3Zg=,tag:Gl91C6uRdCiJ7Jo1Z/MQsg==,type:str]
|
||||||
#ENC[AES256_GCM,data:zqiXDW4=,iv:wT9f8UaixLuj54yqtDX+SFRDCqhtCpuGeVWj6kXL9mc=,tag:eAdQVPZTURDT81ty9LjptQ==,type:comment]
|
#ENC[AES256_GCM,data:xVzlko4=,iv:glHTshoRIkIaJNpn4onyAxPOtXTsNh/JohXJyyu4Ars=,tag:fQ/53HdxYXs2JTMx6O8rrA==,type:comment]
|
||||||
MAGIC_LINK_EXPIRY_MINUTES=ENC[AES256_GCM,data:rzo=,iv:0zOl1jc3LwGxqF+UVHVAqBp1Sj7asyxc0RuG6OhTl5c=,tag:MhP/tAt2nzGyaY4haSNUwg==,type:str]
|
MAGIC_LINK_EXPIRY_MINUTES=ENC[AES256_GCM,data:Ua4=,iv:ou1kEn+fa42lZDsXaPvpodJcvAF+EZC9UIGNK/tBV/U=,tag:+ed8Bm/8pdIksH7O2X8WwQ==,type:str]
|
||||||
SESSION_LIFETIME_DAYS=ENC[AES256_GCM,data:CKI=,iv:RpK3WEPqZOQ9UgXNiH87DyD4mTRfJLUVt6Rka+rITQI=,tag:tXpGVO88TbovPWcGnogexA==,type:str]
|
SESSION_LIFETIME_DAYS=ENC[AES256_GCM,data:/gk=,iv:kKWy+FaoLp8kAWpZzpoUHX8nVFRaA4yuTTVzN2TSYTs=,tag:QypZTVmTo4lXd7PKTWrBdA==,type:str]
|
||||||
#ENC[AES256_GCM,data:4F7//y4HtysenIogVqS6,iv:dzJEBKbprd9Sc5uKtpj7+eYxMhWHuqeRnfmJt3l61eM=,tag:SDOu+zmhdRwZVtV8Tjy0Lw==,type:comment]
|
#ENC[AES256_GCM,data:8HLEkeUESRt3bOxIQsma,iv:kzt8+SFNJw2r3LqwwQPzs9bCdacYSfHWPzIvTxARI4k=,tag:n+F13eILUiJCZ3NtQdo26g==,type:comment]
|
||||||
#ENC[AES256_GCM,data:7jnSYJ2+MHYKp6H904+2BVSHBwhVqeJsLubNgud5vzC+pSG8/Aaq8l5Jne+73QbDYUJPDIW83yc4Elgd72SDh4zLeq7bSoY59mNM3Q==,iv:jDHERGIEAfCWAD6TrYrV6wM8dmlx2xxoItMA1PSv6mM=,tag:1jQjhbkr7dnTAfLGPDPPzg==,type:comment]
|
#ENC[AES256_GCM,data:cGkjKPIfdOPWoZFEXTAgw5lsu0LIcNhu1y3ab47kKbVEZMiCk+0KrEUNJcqbQ+ProBQ6F6N38PUhUl0lhKKjMqjepMZUUrUTqFp0Tw==,iv:WyFISLnRnSSOkra/p7bOs5BQWx+qFaaeeZM50EdrIgA=,tag:UM0EiRGy+RFXfdJqRuv3Jw==,type:comment]
|
||||||
#
|
#
|
||||||
#ENC[AES256_GCM,data:P4C+IQhLXzs9SBKhIWgdKvXpMn/dnE0qeaMaQZsg3BYxNVFQYvJL46miAwvIV+eRNtdWZMXdYve6iOJ3fsx0H+hC2xl7HAOLZZrSZP/GUDs=,iv:9Em6JDy8MKSbX51g/5S7R2BdeRwtXh/8RkgNdIwNudw=,tag:hDJNBhfudzrDHWAQ8VwZAA==,type:comment]
|
#ENC[AES256_GCM,data:icBc0Zv+oedobh8DOTwV2Fc+N0C9CqjZvLciC1dmEIygr/P5oOBLH9Bnhf7XW3X+fiLUtLPQPUIh9CjX+wVeX+MpUZj8ksS0meoz1O2kSBs=,iv:oWreqF6QxaFZn2r35uqY7yy/nItwy3k3VuXAcLyqMbI=,tag:ezQO+m6qkQSEwe17vYtYcw==,type:comment]
|
||||||
#ENC[AES256_GCM,data:FYpzYV8avmJuvjRf07b9tDCAgwfE0AKBcG/Vd8NzKaq1W75mzE17N5MRY7xvwIrHEPpyvgm9lZfZAiWVhS6Nw2V8E/b/,iv:o680eJd30GT03Xnjcsm+P1bH+d6XKYCnpSkt1r9hRXk=,tag:VH1mGp19gxU7df7fLs3RTg==,type:comment]
|
#ENC[AES256_GCM,data:BmlWl0f3aiOrEVglJisqHb507/ipmyRCUvkygs2jBfh2gw3BJgrCAAqoK+DekvIls2myRn7RynqWTMZKGXtJMHu5SEA8,iv:QjPoSzyNl6CBYmJAd2OfFEEoXO3jz0LL2VNegP0mY8Q=,tag:miTvj9PK1a00BKaAjUTICw==,type:comment]
|
||||||
#ENC[AES256_GCM,data:NSK0Ph74ABD9TD+TmJ+J1xKo59DDVGWno+I96XSsaBX9rKGLDkEI1SKyqsrwW9ReuZYy/5E=,iv:Y8QSzOMMiLVSuUUVCPbMaACzsHu1Cd8j8rqoAN99INg=,tag:Mixnrsfjq/Q+TOkGbWx1fw==,type:comment]
|
#ENC[AES256_GCM,data:kzSYQCopgU9wXpw61WGfYpRtOjV3iEVvZ09RP4OqVl+Rqnd2wCKREKKrB7F15bp4BB3OzMo=,iv:TFFpROfYKaUlWQm3ISYkyYdZCarSJbqHItLMUplYiXc=,tag:Xbk3ii3DYE4yPe8cJOt+Tw==,type:comment]
|
||||||
#ENC[AES256_GCM,data:ayv+LfzbDxOZF39CMYGWGR1r4LS5vmSO3p27Jm/p+9W+z73FmsYJeBY/k1sRYz5+RRFupJW/L18=,iv:DwtFFLL2zqUNxvEFg5tQEz0eq5quflqcDOCR6Y3IhVM=,tag:EpMqnrZUwkv4M+gw8YQu8w==,type:comment]
|
#ENC[AES256_GCM,data:qbh0Mnu6wEbDaBideJQCZa74G/DqSWuoiy22zCGoKqKZ2YVQEf0QxRCO/DgOD8rdp1Neqp8u4oE=,iv:dKV76b8sT1ghlyEadeAqTjtNTXrBp9n5ZbGMGi2/GyU=,tag:1HsI5iDekTqxeYmcEpL3HA==,type:comment]
|
||||||
#ENC[AES256_GCM,data:9/K3pVWOl0cmXDg9VUcJjoX2RrFuLnqYGxK+hM9FXdUC4qjB9fZtaWNlYNIVqzNtjihHtpB2ex9Q5+9JCQI=,iv:iRt4qxShtLbe4dYBE2jY1KgeCEM4S3IrnqEpatkpUdk=,tag:DII9n35Qnx2n7qNlYvQuPg==,type:comment]
|
#ENC[AES256_GCM,data:kTLnLnwPVVDFKYncBbFjGmnbxmNfpPXSpKyZu5ZQx6PeVs5s6hpDa55zRXxAetyBAHsmV99ZV2q1NTDXF6Q=,iv:m/ulRFQcGl15vi2ohMwVeYBmcRtp274ROiKXPsyJkfQ=,tag:T0E5p4d/inHyuupbg7bZHQ==,type:comment]
|
||||||
#ENC[AES256_GCM,data:pAL6rk14PeuF4kQs/dNret8ZtWgRsDfx5YBcEZtUPJSk7qHaDFFpZmG0Ok7mVRL1OIyoQXVOlIs=,iv:JgJ408wpCkET9xDv7yBOP+iN/bOg7huyc4xFcw6BWo8=,tag:1Esx3pxQBX6AHU3t7B58ug==,type:comment]
|
#ENC[AES256_GCM,data:aziyEFGCGxbc+q2ma2QN4MvdhQ6bnYuZA7Cgqr6p3zGjPG3oybTWwILejJqD2lHmULXh0UN0qco=,iv:XZwwEUOAEXIUyXiiHFS/bdL91bWKIhZ5IzcXWXAR63Y=,tag:JLsDM4s2yh4aBFQtxWLhDA==,type:comment]
|
||||||
#ENC[AES256_GCM,data:VnpXVaOfq//CGtKnUtq6kLqm+qXPLQS8y30HaZ8YNbOMZEwUV21xvSC8B347148qHt1rHhGM2QFp/kKS8IxoLngMq9QWj3No,iv:v0eQpUn51pdGDSpg2A6WGX0plFYil0K4okICi0OJwGQ=,tag:BJnMZ3Vmx5Qw7MMLsFlsZg==,type:comment]
|
#ENC[AES256_GCM,data:JroBWIbSs5a7+lg/AtBNPxgbtxaztjmVzI1JXuhBmfWD45Qp+w8ePZg9PA1FYzPtEATPHso6m3tZdMTrPtv4Jj6ig/+KHOLn,iv:CbJp6MHOU4yk9OdynQTPwVgZj9Djw9IC9TE90go5RDk=,tag:NTVDg0mFeZsxfVeIFwFhPw==,type:comment]
|
||||||
#
|
#
|
||||||
#ENC[AES256_GCM,data:k3uooGSCEetsRz85KBWdAo1k+DZMWbVc3V/PumOEhdfxHp2I0vRApdIfqimF7cJmW+uE5/aSK1zHMqc4EUgq8A5N3gO2LVmMkhA=,iv:0d1oROXKygJ3xnwQ+TZcjK02PpCEA3ydL6reRdAp6ZI=,tag:C8rWINR7+41JHxqmFGEyLQ==,type:comment]
|
#ENC[AES256_GCM,data:JDxXJ3IJVaBF+MOWe0WXBOPnee48RyjNDtflwVM2FFbLSp1h2uYf6+aRjC4w6pb/R1pl9+AzjjlQaTukjQVwXfbBVKH8SAbhOdU=,iv:K9kvgAbltLIcBo4vZ8NUiaL/Ik+x5Arl9Pj5sh3SIHo=,tag:IjneoyVD+dxd6N2PVV43ww==,type:comment]
|
||||||
RESEND_API_KEY=
|
RESEND_API_KEY=
|
||||||
EMAIL_FROM=ENC[AES256_GCM,data:k4dZes/5yKevE9ywxItbe5s2fU0=,iv:NmGOq9a6/CfcEKzaGkz2knYuz8Eeluaf600kzYImYRg=,tag:oh8dvUP9MtuJFx75O5YOTw==,type:str]
|
EMAIL_FROM=ENC[AES256_GCM,data:QX6duq5wx3z98o39nRXTrPpNXwI=,iv:ikpykHOeHRay+k3B4MvHn2SOuHNGOIuvjetOt+cjTrQ=,tag:8ryM56ogWySp9RAv0/ABTg==,type:str]
|
||||||
LEADS_EMAIL=ENC[AES256_GCM,data:hlI9HMFloMmLEvm9deqz/eAR6ak=,iv:+ACxp0Q0uGB75BymlWP6to0f+njE5ybUTOIkxrNIn/E=,tag:XizFAGcWmGhbMMARt5PLxw==,type:str]
|
LEADS_EMAIL=ENC[AES256_GCM,data:aVFgeh0Yx7W/88noeURvf8rirv8=,iv:5KjsCMAsu1Ywz4BI2JjB1KmQ6QM94U1zlNGJ3BKl7Uo=,tag:voi0kjdhz0SsOQHqtMID4Q==,type:str]
|
||||||
RESEND_WEBHOOK_SECRET=
|
RESEND_WEBHOOK_SECRET=
|
||||||
#ENC[AES256_GCM,data:PyHRX/kgXwa24gyol3okcZ0KP29OS0T39/6bx7mbFQ7wfsnIDaUWja0/3F5tTDycvsy465M4Fe6onAQpKiyzf9CuNw==,iv:ZE3vSHGW+rD/dy2olWvTYBvQI/1SZImAAPW66HOns+I=,tag:r+LHnyBSX4PpH7OF21rPyA==,type:comment]
|
#ENC[AES256_GCM,data:1HqXvAspvNIUNpCxJwge3mEsyO0Y/EWvD3vbLxkgGqIex0hABcupX/Nzk15u8iOY5JWvvEuAO414MNt6mFvnWBDpEw==,iv:N7gCzTNJAR/ljx5gGsX+ieZctya8vQbCIb3hw49OhXg=,tag:PJKNyzhrit5VgIXl+cNlbQ==,type:comment]
|
||||||
#ENC[AES256_GCM,data:J0vWvAfeSQlhthf5eWm8USRUAfkVfgIqmAnt3GsC8r692x8HLAX3NbZ9FN7pz5SgiofS/pNjqb/no0TINyUjsQL10jwRXPtPDzr3,iv:i3SYbUl2Xsi6Ua8Hgg9u2f4y9U9n3QrclRmiripgQ7c=,tag:D2cXlCE7wkHDUnPf5hNdgQ==,type:comment]
|
#ENC[AES256_GCM,data:do6DZ/1Osc5y4xseG8Q8bDX84JBHLzvmVbHiqxP7ChlicmzYBkZ85g43BuM7V0KInFTFgvaC8xmFic+2d37Holuf1ywdAjbLkRhg,iv:qrNmhPbmFDr2ynIF5EdOLZl3FI5f68WDrxuHMkAzuuU=,tag:761gYOlEdNM+e1//1MbCHg==,type:comment]
|
||||||
#ENC[AES256_GCM,data:sUC7z1bskxOjA8d9lWwxNfxelL22yYNuha/qbIUq9tT9XbiuDczKXNc2sNmP6QJdscSuXcX0dizk8qfmsAYwOm4IBc20JUmeYC6oPw==,iv:X4ghkHGgslkYSup814U86/mOiWljJBd7dc/4ZCdoyTo=,tag:z+cr+vaI3dENwJJivpnViw==,type:comment]
|
#ENC[AES256_GCM,data:dseLIQiUEU20xJqoq2dkFho9SnKyoyQ8pStjvfxwnj8v18/ua0TH/PDx/qwIp9z5kEIvbsz5ycJesFfKPhLA5juGcdCbi5zBmZRWYg==,iv:7JUmRnohJt0H5yoJXVD3IauuJkpPHDPyY02OWHWb9Nw=,tag:KcM6JGT01Aa1kTx+U30UKQ==,type:comment]
|
||||||
#ENC[AES256_GCM,data:bmLZN2890ZS2BMdXfFbPiou/RErEwEc0IHLhO2mVZyBljHxauIU36sIUdor0xIHsvNUH,iv:i4GIqVbNJ/NdX39AbzdB3JGs9ExvNxilguuvbzzkCVU=,tag:0D9vRXuno8P+LEWWg6S6CA==,type:comment]
|
#ENC[AES256_GCM,data:GgXo4zkhJsxXEk8F5a/+wdbvBUGN00MUAutZYLDEqqN4T1rZu92fioOLx7MEoC0b8i61,iv:f1hUBoZpmnzXNcikf/anVNdRSHNwVmmjdIcba3eiRI4=,tag:uWpF40uuiXyWqKrYGyLVng==,type:comment]
|
||||||
PADDLE_API_KEY=
|
PADDLE_API_KEY=
|
||||||
PADDLE_CLIENT_TOKEN=
|
PADDLE_CLIENT_TOKEN=
|
||||||
PADDLE_WEBHOOK_SECRET=
|
PADDLE_WEBHOOK_SECRET=
|
||||||
PADDLE_NOTIFICATION_SETTING_ID=
|
PADDLE_NOTIFICATION_SETTING_ID=
|
||||||
PADDLE_ENVIRONMENT=ENC[AES256_GCM,data:QUmkNRMhtA==,iv:ynAcdJvkBhDC01fhXiPNzVF0rJ/06kbPfn1fJDyuwfE=,tag:JBaSLOK1pgrSd1RnouarlQ==,type:str]
|
PADDLE_ENVIRONMENT=ENC[AES256_GCM,data:KIGNxEaodA==,iv:SRebaYRpVJR0LpfalBZJLTE8qBGwWZB/Fx3IokQF99Q=,tag:lcC56e4FjVkCiyaq41vxcQ==,type:str]
|
||||||
#ENC[AES256_GCM,data:HdyY6Znpwyz45cIncl5UQ0q/COW9nTpDJtRpFIh/axiHSoJVb8xZMPMGZs+ZBlsdY2Fk6DLbk/QgQKou,iv:0dF92m4g6xOFl2tyI/XzN6iZ7Akjw0GmQoxtfi2UHAg=,tag:1FRSwRfSMjWHLdU+Z8nq8w==,type:comment]
|
#ENC[AES256_GCM,data:2Hs7ds2ppeRqKB7EiAAbWqlainKdZ+eTYZSvPloirT4Hlsuf+zTwtJTA6RzHNCuK4em//jhOx8R2k80I,iv:1N6CNPqYWp3z8lm5e2Vp6OlpgHdMOiD7dsEYp23nMtA=,tag:ulWP/BFFoLljLMVCrsgizw==,type:comment]
|
||||||
UMAMI_API_URL=ENC[AES256_GCM,data:CpNizZHSgMxaXoI58ZqaHf8anQSPnlaScHqtYA==,iv:xcIXxNjo8/kPdB5RAlyHTvHfo51cEgxlKD0bum7JMOU=,tag:nFOLENvS9QnneD9X6IyE4Q==,type:str]
|
UMAMI_API_URL=ENC[AES256_GCM,data:oX/m95YB+S2ziUKoxDhsDzMhGZfxppw+w603tQ==,iv:GAj7ccF6seiCfLAh2XIjUi13RpgNA3GONMtINcG+KMw=,tag:mUfRlvaEWrw2QWFydtnbNA==,type:str]
|
||||||
UMAMI_API_TOKEN=
|
UMAMI_API_TOKEN=
|
||||||
#ENC[AES256_GCM,data:aQSM5Lzgkz9NUUIugBA=,iv:2XKv8zYRiQJOObbPMZPF0KYjTKPfNwm3UDFuwhWhD3U=,tag:53tLhXhELygfPr4fpT+75A==,type:comment]
|
#ENC[AES256_GCM,data:HTG/nKNl9NMicZVt5nU=,iv:MfRqX6tzdl6SC61xjRxTrVRpTWGmmqslL/Vdy88Jtyo=,tag:NhOgm3+qJelmQaAAnITFKA==,type:comment]
|
||||||
RATE_LIMIT_REQUESTS=ENC[AES256_GCM,data:rvuL,iv:yj7Jz/srP/mM5sq9ZVprXJonRz8yIYFObMEb9BH/gts=,tag:UPTPgf7o7XD6VRX8SM6Z3A==,type:str]
|
RATE_LIMIT_REQUESTS=ENC[AES256_GCM,data:hy3B,iv:kouDI24Gac/S7aQMXRcl+emwE6/WU+F9egNhQ+MayrA=,tag:iZXV92kqnS0MppvW6Km5oQ==,type:str]
|
||||||
RATE_LIMIT_WINDOW=ENC[AES256_GCM,data:Ko0=,iv:HqyLf2FGYGvs39pHNAHVylRDmwGD7ZE5tzoVrJH5lWU=,tag:a6DUeRYQQgzo/onCCglk4w==,type:str]
|
RATE_LIMIT_WINDOW=ENC[AES256_GCM,data:vE8=,iv:lS6cQX3VzHeVrlYHQTXYGgib1rYI9G4XoW/f5YSjVWs=,tag:3Bn8PIktDxD7HvUTHw6mnw==,type:str]
|
||||||
#ENC[AES256_GCM,data:VjeIPAbAtb9UChZpMNB7JgylhPjJWf5TN2yirhXbvy0GwQz1ggfHOwMCfKT3gdQ0sS6538IVVrM7g7+t2LQU7UtwwXA/m/g7t7OOsMMLdus=,iv:h465KTCd4djaAYXNwTIoEYb0Jv+n++ggH1LSg9lGWsA=,tag:5kbWbQ/MKQnRD4dDaanVOw==,type:comment]
|
#ENC[AES256_GCM,data:KRlMK35PPFBTe7FOkbanuskbA4oFj51Fg290lRtwyHKoJxi7fHg7cueojwCiRSJestRguwV8g9UP4MC9bKzWssdFqvfdr7XEUuA3a+WWD9I=,iv:RZhJJS6tNZHecxn/862nnl8dg8OwsVYB/R0yPxYMXgw=,tag:dqXgcU8OSyJzOPJp+7Z+cA==,type:comment]
|
||||||
LITESTREAM_R2_BUCKET=
|
LITESTREAM_R2_BUCKET=
|
||||||
LITESTREAM_R2_ACCESS_KEY_ID=
|
LITESTREAM_R2_ACCESS_KEY_ID=
|
||||||
LITESTREAM_R2_SECRET_ACCESS_KEY=
|
LITESTREAM_R2_SECRET_ACCESS_KEY=
|
||||||
LITESTREAM_R2_ENDPOINT=
|
LITESTREAM_R2_ENDPOINT=
|
||||||
#ENC[AES256_GCM,data:m6v0sC7LAUHYcgslJ6U=,iv:M8WIwU9a+1DHgzHzUIbufU7f1M5EWCmMrND0ewn/4Zk=,tag:IvA37+vuLIhazIdyD9OKzA==,type:comment]
|
#ENC[AES256_GCM,data:4To0MRZIt3HxO7qjh4E=,iv:/caczOlTPECDF6mA1PKO8Xm4NeR1RZjgpt2Vuq+rfkQ=,tag:S/UGMqHZQX/Q20N+Ah30WQ==,type:comment]
|
||||||
DUCKDB_PATH=ENC[AES256_GCM,data:f5cPVJ4hPitrUiGSADEbka7D80i9,iv:9w2lPLwCxGga0spvsvG5b8HAwb+UC3NiPMChhbPsTmI=,tag:D981doEKe/3QuJ5+8PefYA==,type:str]
|
DUCKDB_PATH=ENC[AES256_GCM,data:sql4dtOLeX1aY/kdaxAzCk47hm3t,iv:S63x40+5blcF8qYxMjqUhs2moukuy2yEQRPbUvXZSYo=,tag:lTLYjtyZNiv06o/hm6Grxg==,type:str]
|
||||||
SERVING_DUCKDB_PATH=ENC[AES256_GCM,data:jMbf6irWdBtvv2Fkt03IgrHf6HEg,iv:qlNdxAhNWAjlxN47CmSLsXS+edD/aCZajV2jMoyuvSw=,tag:AA/7hMNULpQYlP3aQ66MYQ==,type:str]
|
SERVING_DUCKDB_PATH=ENC[AES256_GCM,data:xE05ajjqmYggI9oz4w1GBucUn0bI,iv:/C3D+iWSNk1XJ/xclTzdJTqOHR12Gwmo1xIxH/4nyL0=,tag:eNcgrb+QvL/y1jE8mb0DHg==,type:str]
|
||||||
LANDING_DIR=ENC[AES256_GCM,data:0uX40uNxOg+wPToC,iv:ZegMiav+MbEQUhsxY55MH0OLJZpQl6eS3mekg1fyA28=,tag:EWiXChA2hts0y9R96FviFA==,type:str]
|
LANDING_DIR=ENC[AES256_GCM,data:PNPOE7/MV/iQ24mf,iv:lg486nzb/vlOyTHVQ0HEO4fK18IEJNnuSc/CrQwUsHk=,tag:zecZp+Xfw+dL5GtUeIOg/A==,type:str]
|
||||||
#ENC[AES256_GCM,data:iFa0hLjQSPP8Wn0mbEFVK8coTnFnI0gqnvGNC42sTJVy+b6DHL6It0mzIgO1mImRdsS0+QFPqRx8z990WtDg2QpRaguyY6R4xe0=,iv:Qe4g7sxTRJCWjC004lO1kNKuKnw+P+eU3mwU3PQ6kG4=,tag:U4T9Fwq44UXOdcKmvTcNYQ==,type:comment]
|
#ENC[AES256_GCM,data:bsiiPYvTz0LtdIgopkPEtcgmtDzZU0W6uton/sqm++5UymV33B0m47LIpdH9xQurQtmoZwMCBkAe0FCqqz62D1dAIH1Q6lzzLqg=,iv:rr7aShvtJtAnBzcbr/O0wOONpDBzwbR/Wbx/YPPsKpM=,tag:YH1wdokUuudFvagnPuT8aA==,type:comment]
|
||||||
REPO_DIR=ENC[AES256_GCM,data:qw==,iv:gZKl9jg2mLHEPzAmLFhM+i3+hkEtA1UaKip7W1/fmaA=,tag:FBJSEpk70ws5rlYT/KvcLA==,type:str]
|
REPO_DIR=ENC[AES256_GCM,data:vg==,iv:TNMZ6lrajWy6C9q89/AbRkBawBc2YaGsn2elbO8V2Wk=,tag:va77fkt8VDpPG8pZu490uw==,type:str]
|
||||||
WORKFLOWS_PATH=ENC[AES256_GCM,data:2PhIF4TwbOC1VxJf5x412tfGAYhuicOWR/ArUY0dIg==,iv:CgCfmTRqat9eGKdCb7n2eYBq31SultpMx5EOo/5brGk=,tag:d7PTWh/kS1hI7WxXaZR1bQ==,type:str]
|
WORKFLOWS_PATH=ENC[AES256_GCM,data:PehxEUMb1K3F1557BY3IqKD7sbJcoaIjnQvboBRJ1g==,iv:WfniguOksC3onCSyDlBpfKC8bE9DAt7evoeYX0K0lvE=,tag:sdRWDqkk9dtuESvfbRBfCQ==,type:str]
|
||||||
ALERT_WEBHOOK_URL=
|
ALERT_WEBHOOK_URL=
|
||||||
NTFY_TOKEN=
|
NTFY_TOKEN=
|
||||||
#ENC[AES256_GCM,data:uuuY0NpFnt4Ihe8=,iv:qhOHBKuz3RlzcFWESucb7LxN54T2QKS/hQ7p+PwQbK8=,tag:Q9AGw0s6t7nrtoFbZKbQxQ==,type:comment]
|
#ENC[AES256_GCM,data:BCyQYjRnTx8yW9A=,iv:4OPCP+xzRLUJrpoFewVnbZRKnZH4sAbV76SM//2k5wU=,tag:HxwEp7VFVZUN/VjPiL/+Vw==,type:comment]
|
||||||
PROXY_URLS=ENC[AES256_GCM,data:tioCCvPFz4kZDY+KWz9VVpF4GFlWizq62opU0UWWRWJpbuMntNxyITxxp/VSLt+nMKBgL2RsD69qeCjWIktO2B4Ys5qEPznPTjDQx0LkSqFHjeMifFH+4yLh7JJtNMm9es3txYFj63QaT3LOnh/yXoM+VeSKl8BlW8n+H0yVf+0WWD4lkTDeY4wag4AjxL12/0M5FRsSyLG9tUYiPE/K6dL0eqprZ2Zwp367lTIcnF9mVXE9lRJXm8/wKe479JED/Vbcndp1TKZc9C6T8SvlnY9JQSxmhUOr9JMSofXug7OrBr8OEIFcjt10LfUJabsV462BuuCxsG9rX9Jjxytn83oIcTMIL2A2W2e8pLY2w7AND03q/KVzU7tGa55QVdMsNB/ZC6lwwZQpEHxpfr1g/62NliBtsOdD/XSzOmJDOghnYbnDuEp6C/E/IyFDcdOL8SnUv2uVzkyZZGIOs2eSaXT5TxKO7qWpMWwELAK+EGaB9aBT1YvD9nLP/DJ9JlGP8O76pwu+v7cR/upz3la4OvU5LC8WGzi6e7Fh1TlFVVWIzq6wjnD/Rai6L9gzhixCriNDbmZr9YLl2XeLNHZ8cCROm9nWxMBTAjoIk0efzEmcpY0JY6V/uAQbfmJ+YWo=,iv:Rm5NFF4XILpH+GU37PQwWvNJbQnK+Q9lS9LFtCSgMZc=,tag:DIcae33nOPI/6gag8VxJ8w==,type:str]
|
PROXY_URLS=
|
||||||
EXTRACT_WORKERS=ENC[AES256_GCM,data:Pg==,iv:TtnwhOjQuffZ1j/jqPcKWaKZKPWuuTN+pTgohUieFVA=,tag:O+cLuOWVi6SPlpOAgk+ZYg==,type:str]
|
RECHECK_WINDOW_MINUTES=ENC[AES256_GCM,data:YWM=,iv:iY5+uMazLAFdwyLT7Gr7MaF1QHBIgHuoi6nF2VbSsOA=,tag:dc6AmuJdTQ55gVe16uzs6A==,type:str]
|
||||||
RECHECK_WINDOW_MINUTES=ENC[AES256_GCM,data:v8c=,iv:1OLd9YKt7usSJjfMEv/nQVBdn1Yomw4v7IK6Tg1PBTM=,tag:26UlNnOpCbzjrM/9d2QWbw==,type:str]
|
|
||||||
PROXY_URLS_FALLBACK=
|
PROXY_URLS_FALLBACK=
|
||||||
CIRCUIT_BREAKER_THRESHOLD=
|
CIRCUIT_BREAKER_THRESHOLD=
|
||||||
#ENC[AES256_GCM,data:pA90yPzfo293IovEhFv6+z/EOj2xuNPkOOx0u2Im9b5tPrrGks8hNbmcJabL6qOQe5+YwIccdSsTscJ+DocigNrIZcMY/+0h,iv:3+tiv1qAVX+Ex4Y49qTj094B8nxqhPEshTYhimlC1xE=,tag:gxcDM6Yyb6pUPO15NkqxCQ==,type:comment]
|
#ENC[AES256_GCM,data:ZcX/OEbrMfKizIQYq3CYGnvzeTEX7KsmQaz2+Jj1rG5tbTy2aljQBIEkjtiwuo8NsNAD+FhIGRGVfBmKe1CAKME1MuiCbgSG,iv:4BSkeD3jZFawP09qECcqyuiWcDnCNSgbIjBATYhazq4=,tag:Ep1d2Uk700MOlWcLWaQ/ig==,type:comment]
|
||||||
GSC_SERVICE_ACCOUNT_PATH=
|
GSC_SERVICE_ACCOUNT_PATH=
|
||||||
GSC_SITE_URL=
|
GSC_SITE_URL=
|
||||||
BING_WEBMASTER_API_KEY=
|
BING_WEBMASTER_API_KEY=
|
||||||
BING_SITE_URL=
|
BING_SITE_URL=
|
||||||
#ENC[AES256_GCM,data:ObwV9+nxI3OjibZaWomt41y0b+bdHgcdH3r3xddgMyU=,iv:LwdgMBeoUid9GGRwmn/HG8HvpdDkwFnH6eV5Tr2w3hs=,tag:MxfkT6hQ2hptiim1VKtNaw==,type:comment]
|
#ENC[AES256_GCM,data:ECsuDMQipS6YmFpSm1vqCsR2fUW2zN1Mg9VcUlw0roM=,iv:j+F6Akx2bklGMkFTux230YcZjMibA+Qp+qvgkGXl4Jw=,tag:7aO0wbmP/qB73wLgtiSJ2w==,type:comment]
|
||||||
GEONAMES_USERNAME=ENC[AES256_GCM,data:62LndEyH65LKGXI=,iv:BBm1OOxgJa2fkuCyneTgcHg3ArYfkA35NOOd2yT9MW8=,tag:MzRGfuDOnA9pbrvXerOWRA==,type:str]
|
GEONAMES_USERNAME=ENC[AES256_GCM,data:aSkVdLNrhiF6tlg=,iv:eemFGwDIv3EG/P3lVHGZj96MieIsr85e4xYmEIpZyfM=,tag:McpZMNOIO3FDkSebae2gOQ==,type:str]
|
||||||
CENSUS_API_KEY=
|
CENSUS_API_KEY=
|
||||||
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrOW1qbkdDUlZobGRqbDFM\nS0NRTmdSazY5S08yOURBZ1lWeDBuNi95aTI4ClVRTWlnWXplWjVuNVE2dmRzMk1p\nRk5ZZHlpT2RVcjJqb2FpTEdOZXlKbTgKLS0tIHN6UU1tazd0cmNscVUzTmcwNE5L\nNE5uSEtiY3hwSzE1bm90b1VNZzYwK28KJlCJWkMx7dMoBtJi252GwERp6kSkuVFu\nJBwnSJ2KC5/zzjcfnLprgc/b3s4RsbLYDTkcPcuF08X9F3R8uqScdQ==\n-----END AGE ENCRYPTED FILE-----\n
|
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxNWNmUzVNUGdWRnE0ZFpF\nM0JQZWZ3UDdEVzlwTmIxakxOZXBkT2x2ZlNrClRtV2M3S2daSGxUZmFDSWQ2Nmh4\neU51QndFcUxlSE00RFovOVJTcDZmUUUKLS0tIDcvL3hRMDRoMWZZSXljNzA3WG5o\nMWFic21MV0krMzlIaldBTVU0ZDdlTE0K7euGQtA+9lHNws+x7TMCArZamm9att96\nL8cXoUDWe5fNI5+M1bXReqVfNwPTwZsV6j/+ZtYKybklIzWz02Ex4A==\n-----END AGE ENCRYPTED FILE-----\n
|
||||||
sops_age__list_0__map_recipient=age1f5002gj4s78jju45jd28kuejtcfhn5cdujz885fl7z2p9ym68pnsgky87a
|
sops_age__list_0__map_recipient=age1f5002gj4s78jju45jd28kuejtcfhn5cdujz885fl7z2p9ym68pnsgky87a
|
||||||
sops_lastmodified=2026-02-24T20:32:28Z
|
sops_lastmodified=2026-02-24T21:27:19Z
|
||||||
sops_mac=ENC[AES256_GCM,data:hQspZvYHHTsf1vFtNUvhiyRSkfynxNRUjGCjdK9GPGC30aXIzhg2si/M8wmk3VyhTMOuSFVtNgiiefvChO0N4hwqDkp2llEnkwy/uvtnCTLKNF8I90GS4ZOXNdYN5bTAS/0EF3gWOTPKl+EhJQ4lAvb3k+PY4fI7bZIkWf7sSbo=,iv:JpsFyMTpI0lfncfzPvt0snAqyNfCGCrSiQinIuDivUM=,tag:DBAIePZdHq3lfTn3AycBgQ==,type:str]
|
sops_mac=ENC[AES256_GCM,data:KuL4wOGAEnMeXEDUKH7MXPhRFln4jTMKJAikTmkyYYxlFsxbTy3o+i5wwpfEZ7oqq/76v7XE2rhg9KMMLfnbZ2rLH9I/6kJRDtlZUUBCdKI6FCRnFbsgmzhuoXMHuFrj4B054u/C8QN2YwL7Mke+Gs9fglxvBrmhN58JAIOaxew=,iv:qu7rdFffw8IBHRP9a1tpPlRexg0b2f6lcpLu9AVbl5k=,tag:h7NbJ4bl/B8/CGVM/iW1Uw==,type:str]
|
||||||
sops_unencrypted_suffix=_unencrypted
|
sops_unencrypted_suffix=_unencrypted
|
||||||
sops_version=3.12.1
|
sops_version=3.12.1
|
||||||
|
|||||||
119
.env.prod.sops
119
.env.prod.sops
@@ -1,64 +1,61 @@
|
|||||||
#ENC[AES256_GCM,data:Wav/NA==,iv:aL+BhiA8jZhSxEbxDAYuC6t/AOJhTmTL1e4CxZKMFWA=,tag:dm1w5IiHOqWU5rGshI+Lfg==,type:comment]
|
#ENC[AES256_GCM,data:8qKvOA==,iv:Xci2F8lcBpT7dmhzaDe6sfrtQi+yQD7e2CQsYLAdCnY=,tag:3duziYwr7PoGQILUuY8nBA==,type:comment]
|
||||||
APP_NAME=ENC[AES256_GCM,data:POhoUbLUGvXG9b4=,iv:+O4EWtOIvkENCYVyKR2i43MzAfcN/qYmuKLzAW5cBMs=,tag:9uVonTXvK9is4wysYgx83Q==,type:str]
|
APP_NAME=ENC[AES256_GCM,data:ldJf4P0iD9ziMVg=,iv:hiVl2whhd02yZCafzBfbxX5/EU/suvzO4kSiWho2oUo=,tag:qzrr57sTPX8HPyDVwVL4sw==,type:str]
|
||||||
SECRET_KEY=ENC[AES256_GCM,data:Sc9tp5z2+m3k,iv:T68rsh79CCuWd4UPzoDhNmxt6PcDF3wYcUC1/kzJFgU=,tag:Qk/NEsVc1ozZC+gDFbw7VA==,type:str]
|
SECRET_KEY=ENC[AES256_GCM,data:Pll2sBGZsUJ0,iv:Dz+rq47dV3TmJXIQu+P+TmKXKFYsxbkY7/5js1cPrWA=,tag:IVAValYSELDRUMisbMwbAQ==,type:str]
|
||||||
BASE_URL=ENC[AES256_GCM,data:CVWA4DYxDj8ALfxeFjryj/15y8sqsA==,iv:Q90DnZyg5lnyClvoRtkANc7Pw7Kf0b/IjGCI6X0EC34=,tag:UUnnGJQWhtXMsxo/9WtoPg==,type:str]
|
BASE_URL=ENC[AES256_GCM,data:50k/RqlZ1EHqGM4UkSmTaCsuJgyU4w==,iv:f8zKr2jkts4RsawA97hzICHwj9Quzgp+Dw8AhQ7GSWA=,tag:9KhNvwmoOtDyuIql7okeew==,type:str]
|
||||||
DEBUG=ENC[AES256_GCM,data:gp1pqrY=,iv:un/NxyBzL2bsVcYfw5p6YEFDS7PQ58tsgzd/edjKxvw=,tag:+/2hHRk4ePIQWewrAwQwzQ==,type:str]
|
DEBUG=ENC[AES256_GCM,data:O0/uRF4=,iv:cZ+vyUuXjQOYYRf4l8lWS3JIWqL/w3pnlCTDPAZpB1E=,tag:OmJE9oJpzYzth0xwaMqADQ==,type:str]
|
||||||
#ENC[AES256_GCM,data:IBz3HceUcQ2ousTJJw==,iv:d2VdDCd3YM35u2wLtJi/wAwIGD4gwAyL6lYEaGAjREQ=,tag:+9tArSTm/sZhOW23hT/XmQ==,type:comment]
|
#ENC[AES256_GCM,data:xmJc6WTb3yumHzvLeA==,iv:9jKuYaDgm4zR/DTswIMwsajV0s5UTe+AOX4Sue0GPCs=,tag:b/7H9js1HmFYjuQE4zJz8w==,type:comment]
|
||||||
ADMIN_EMAILS=ENC[AES256_GCM,data:IcQNF/E1F557,iv:BZS+H2aujJk/iJ8KNphdKfbNfEPTCCF/YMS+XylA4LY=,tag:jVNngz8DBmY/y0wyI9upPQ==,type:str]
|
ADMIN_EMAILS=ENC[AES256_GCM,data:dtEDXPbN5Y5q,iv:k1GSkJh+L4kOM8V0cGYnz0/CsmvwdVRNHk0qpBulSS0=,tag:rUpVgROj2qD8a5IufnBrJw==,type:str]
|
||||||
#ENC[AES256_GCM,data:Mh0RRWSB8xzw,iv:NP2x+wqF64GZz9/VauC0CX+0yJHntHlS5TLMTH3jIIU=,tag:2BIIEevah5ZXmQb1MdH24g==,type:comment]
|
#ENC[AES256_GCM,data:S7Pdg9tcom3N,iv:OjmYk3pqbZHKPS1Y06w1y8BE7CU0y6Vx2wnio9tEhus=,tag:YAOGbrHQ+UOcdSQFWdiCDA==,type:comment]
|
||||||
DATABASE_PATH=ENC[AES256_GCM,data:m0MbpPzsUpsNTl4=,iv:W2kV+ZFFjs2mrifZALMLtvYcFsDpEuJ9302zNMEeLQA=,tag:NJEe/EZeisS/dRZE0xsdSA==,type:str]
|
DATABASE_PATH=ENC[AES256_GCM,data:qxQs7dG0RWMA1rs=,iv:5ZUyk02hCPQESr2vFz3mfnUhUF74LbO6YK5+HFBbxUQ=,tag:daQxiWAhzCB2cScjzjYwaA==,type:str]
|
||||||
#ENC[AES256_GCM,data:cab2fnw=,iv:0O2xG1Kd72eBOzuPsENcEN2yglgGketYJ4x6eoBlBFg=,tag:e7UDXywCT8I8i+HoJTDnXQ==,type:comment]
|
#ENC[AES256_GCM,data:aWgKm9Y=,iv:8iT6GHSzWhM+fRX9PIY9wAs7lXj/ADS6eZK9BBSEdaQ=,tag:aSLsj52ybnod7Qfmx9BLQA==,type:comment]
|
||||||
MAGIC_LINK_EXPIRY_MINUTES=ENC[AES256_GCM,data:7bU=,iv:Vov5Eqkg17sOr0dMuKH2Yu7GEh+NrA5eQisR1S6l0x0=,tag:Dzv7DynR9bw2vGUYBLyTLw==,type:str]
|
MAGIC_LINK_EXPIRY_MINUTES=ENC[AES256_GCM,data:YSE=,iv:GYm1EWku7+OG+fCIbUHWsfYbnEQVNhlBmWBC1OCV1NA=,tag:L2kdm7tMWOO/cf+VDd+OdQ==,type:str]
|
||||||
SESSION_LIFETIME_DAYS=ENC[AES256_GCM,data:vUE=,iv:NOGW2PmabHJ4Jg4qqGAthVJm215oP6ZXDxotmGcnaxU=,tag:6HUzPr5vC/H4xTjYDBsZeg==,type:str]
|
SESSION_LIFETIME_DAYS=ENC[AES256_GCM,data:9Og=,iv:3nStZVZVB24aAtNrtLXZ0oIehTDyu2IzdXoMH59t+3o=,tag:+FQ4n1XeSS12zUGXt/1RKQ==,type:str]
|
||||||
#ENC[AES256_GCM,data:hkqR+T2LqHF0P6Rk9ct0,iv:muiERgZJgUzNCkraL6fpJW3Juq57UwPkcHYBoqHzPZo=,tag:hL79NkHIjiK2APU4u8TYgA==,type:comment]
|
#ENC[AES256_GCM,data:mtqp/c5zZxlcB4HrOrfi,iv:eJaN+ZnAIaNHF5iovcz0QynILq9GjqVcwoyN2ZhLmpI=,tag:WyXU7ho5T/CE609id9dOzA==,type:comment]
|
||||||
RESEND_API_KEY=ENC[AES256_GCM,data:c22My3N0oeaD,iv:5HN/Ya+UdlEH47wF5Bt0yrm+QSWICSVnjllAJbICgbk=,tag:blThzzKKFgSCAIhFjMPiwg==,type:str]
|
RESEND_API_KEY=ENC[AES256_GCM,data:U5aEnItbJ/Af,iv:7BTFimeMbPtK6ANXMr7VwO5TJ7IaRk+HAOZy+TEXMVI=,tag:sDhW5icVloSck1iafu3H0A==,type:str]
|
||||||
EMAIL_FROM=ENC[AES256_GCM,data:/xYHYA1ldi1vbp+SB4OpY6yiLf81aBsKk3I81jWZUYG23w==,iv:C3bvthAepEZuceWKRPxijtxbUPI+REmByVgMDwTt56s=,tag:Ue9dOmZmcwk1LJ6C5/38Jg==,type:str]
|
EMAIL_FROM=ENC[AES256_GCM,data:BTGeWUjG9qCBvRQr9kK5sfdzQ1CfuNgpkU/AL3Qu6GJ2ng==,iv:0XjqD8hCqleSJR2FrDajlnUul8o4GkK0f1MOP96MRkw=,tag:0PwZwxuBbUFYdiRYTlDffg==,type:str]
|
||||||
LEADS_EMAIL=ENC[AES256_GCM,data:OBxQ0O2S0pz+37t6QfGkeyfEeP4Uj6iiFRl44JTIPW6Ptg==,iv:Tat5MDknzUQxx9aRsiOdOYfZhFSyMeiK8GPdSaBLCKo=,tag:zeDlnndu2ltPUGw3cLxSnQ==,type:str]
|
LEADS_EMAIL=ENC[AES256_GCM,data:jkpWqodUgR2QoB96zvE5aH/tA9Sh0nPcl75P3i43ecFILw==,iv:vNtB/9gdrTDm6vNIjnH6JShYyqmG7h9jd2uzwFwjhO8=,tag:cG5T3CwQfZO/jTYFnwJSgA==,type:str]
|
||||||
RESEND_WEBHOOK_SECRET=ENC[AES256_GCM,data:IkwtdB8OVNoc,iv:HtDFBAeysVW+lF40+Q41OkuKeT+IqMj5QjN3WiWJSUs=,tag:ag8SkdpBJ2kgMQgQjf9ytA==,type:str]
|
RESEND_WEBHOOK_SECRET=ENC[AES256_GCM,data:EQpvkWFyt8H7,iv:6QiZIDo5Ps39vf9MKkiqSJir7BH9zhoLREJ425y3FIs=,tag:kjO4dczb2E5FKfO6OVaQvw==,type:str]
|
||||||
#ENC[AES256_GCM,data:YYm/fBT+tQ==,iv:SfHfEjAUDcx43txowHIOYW8lbESf1aA3G3kfduvwWCc=,tag:kU30Pw6OpOxkwJnDLP3rMw==,type:comment]
|
#ENC[AES256_GCM,data:HW8JOkd7Hw==,iv:Qfwm2ZHT8TKANrLrRQqHnceQVUTiuzT2hSjLN8hSq5Q=,tag:hvVLmGGUBRlsm2qy9jxIvA==,type:comment]
|
||||||
PADDLE_API_KEY=ENC[AES256_GCM,data:TiTjORwV6MOp,iv:Jl6P4e9M9EHhzAPUqZp2vP9Y5jk4Qk8lBf+ZhR7iUOQ=,tag:FeJ1xhBScZz5mbU80c2c0Q==,type:str]
|
PADDLE_API_KEY=ENC[AES256_GCM,data:d3rKjWFrFepp,iv:TGjG9VTC4pZFgnp5daE+jBrRCUJddqgRaV7rQ61llhU=,tag:KKaYPfUgLC58zhC8s3B4cQ==,type:str]
|
||||||
PADDLE_CLIENT_TOKEN=ENC[AES256_GCM,data:zuOECQEwof2g,iv:2wywplC0WoFjeahjBhItLCQJuzZjPnc30eiBt+dKPIw=,tag:8e3lQt4/VRJLCIIrDtBKtQ==,type:str]
|
PADDLE_CLIENT_TOKEN=ENC[AES256_GCM,data:JPmeLZx16WuV,iv:52EczBQM+fvEQuzoY8Aon0RBZiLzf1vrbT9Kx+b/WUE=,tag:+abUTzCgxulamobp13PbWQ==,type:str]
|
||||||
PADDLE_WEBHOOK_SECRET=ENC[AES256_GCM,data:51gkXZ/DqOZf,iv:r4hTwBeLvq2gH+ctltQUe2ErCxaQtiMjZ6+W/Bh6A3M=,tag:CY/X8ZgVgj7W7MAANz5jtw==,type:str]
|
PADDLE_WEBHOOK_SECRET=ENC[AES256_GCM,data:fk2PbtpwoGRB,iv:QOhOd4rKmVjMA1EUQUjSj/y/OM7I435K/s4aqShjQNw=,tag:RIfbUCXAQGmCiE9FODHgpA==,type:str]
|
||||||
PADDLE_NOTIFICATION_SETTING_ID=ENC[AES256_GCM,data:F/A9e6nLY0FE,iv:wt0hB0vIf6WR+Hxp5lTjabIBpcIyRqE7S/h8NW6RT48=,tag:CiUqrb5vH9XiiJ3Di+DEQQ==,type:str]
|
PADDLE_NOTIFICATION_SETTING_ID=ENC[AES256_GCM,data:igRsm8JOO1SP,iv:vQgOZcMHt6YoE+U2d6tT8sILOwsTx3glHVBBatR6Sk8=,tag:1tApDyZmZNiwd3bVm0uZGw==,type:str]
|
||||||
PADDLE_ENVIRONMENT=ENC[AES256_GCM,data:oyN5vq6WU9+GPQ==,iv:5Vp6AawTjCaet/Vw3ElfOiQs6m78QC46+rMCfA0KCZk=,tag:TRQxuYr6gs6EMPWnI7k5mQ==,type:str]
|
PADDLE_ENVIRONMENT=ENC[AES256_GCM,data:A1qXlv+9hjdIug==,iv:nu9kRQZgGLFXXT2I5GaRzp13YgQxU2ucr9azEA4XTUQ=,tag:RBxwE2j9v/RCiEMIa+6ICw==,type:str]
|
||||||
#ENC[AES256_GCM,data:ETizP+U6,iv:bzs+KIw54HXbCdQncCJJ9NAUiMo4xdE8nAxHxZtl5HM=,tag:5Hq5QuWQxqlfC1gISfVs9w==,type:comment]
|
#ENC[AES256_GCM,data:F3dSfSGV,iv:Zjzmp9Vb+LBkqV6xBIMF2cK8ON9crH3fHcOog4+LOpo=,tag:7V8E9ChwYY9ceTaYdg3Lbw==,type:comment]
|
||||||
UMAMI_API_URL=ENC[AES256_GCM,data:3I67uzhmagf8RPwRD7HiXLCnCjG10em98fVsuA==,iv:qACYc8jKe/816rql5COoUL351e58qCzVi1cdGA5JfZ0=,tag:VwmpCJittpYaN5S6grCKMw==,type:str]
|
UMAMI_API_URL=ENC[AES256_GCM,data:4nJZc/opX4rsqAxO6XxD1Es5ySMh7nUtcGt6Kg==,iv:DcmhRe1IJKS0tOFgdJQQv2A1kO5K8VVT8aW0Vq5hVlY=,tag:Sglu4nnAiLIzr+ovJ/hEKQ==,type:str]
|
||||||
UMAMI_API_TOKEN=ENC[AES256_GCM,data:MXlJOXEh45OT,iv:9nsj2IXpc/EHEaPrj8RkKZC5aH+ky2x6geeWoWuPtJM=,tag:nuTxbjhUenue2krhB5ZNuQ==,type:str]
|
UMAMI_API_TOKEN=ENC[AES256_GCM,data:Xv1eTWtiJ6PL,iv:9sYsI2dJaQt6gpC/ev0b2dSk48PzuojTg18xXnBSWvk=,tag:DAMDHk0b9IG7T9MpkpzAkQ==,type:str]
|
||||||
#ENC[AES256_GCM,data:I3LUWXInlHVEt5HfDpY=,iv:OVGbuQUc1wB3phmwAONAx0DDudF7EReZ/JFwjrNTHU0=,tag:S7nu/WpB5wKU7K7LLwCgAQ==,type:comment]
|
#ENC[AES256_GCM,data:wAePRqqMZL2oCJB812A=,iv:jaLmjd0GW2dnEQ3KgWcvAs7Q7aDwlCexM9W7pH27kss=,tag:h7/yIdc13+3pmqyCc0OPkg==,type:comment]
|
||||||
RATE_LIMIT_REQUESTS=ENC[AES256_GCM,data:1mFf,iv:YFckwytvkxgrLKGGOpKyZKE61t3QpkxLAbXR2Th03R0=,tag:zo3vU1BlwcE778jcf2ugBw==,type:str]
|
RATE_LIMIT_REQUESTS=ENC[AES256_GCM,data:W3Nt,iv:ycMAxrPq44S6qezQIa50rc7GDplo1YvAO6VUERGQUxA=,tag:uzendLuSVbmSPcVPEgLiqQ==,type:str]
|
||||||
RATE_LIMIT_WINDOW=ENC[AES256_GCM,data:dQ0=,iv:hD+YjNfATvzmVc/cpHs4z/601ezfoxRuXkllqShcio0=,tag:Zfw/fZoEmxat9WjPtx+qQw==,type:str]
|
RATE_LIMIT_WINDOW=ENC[AES256_GCM,data:r8o=,iv:m5uKo3N8mb7FWI70SgaaHSyC3CNeD8XxjEx8ENit9uI=,tag:gKXEXsIwtBr3sm7xqLRHIw==,type:str]
|
||||||
#ENC[AES256_GCM,data:ZWwLbf5Y3Y0OobFHq45HGcGn6MsG,iv:Y/H/ZafbT+2YArnBe/mM1KkHFuuUDQsAf3JQgonIDUU=,tag:QW0MvN4e3mDsXGHdkaAF7Q==,type:comment]
|
#ENC[AES256_GCM,data:E6JgKjxuqFdPtVEv6Xiz1kqcT4ar,iv:hL7P7/X7nEqFwnlf72QEeHhViQ17HZbsCP/M4gcTJiA=,tag:FjCPSvrBboCWjfIS/fab0A==,type:comment]
|
||||||
LITESTREAM_R2_BUCKET=ENC[AES256_GCM,data:avxiVQAIebLG,iv:2CZ1MGjGScdgNgm0nVexurhJFC6UQEmi2ldVR/RxBr4=,tag:oZT+tGt+/EGQhpH8jiwHmw==,type:str]
|
LITESTREAM_R2_BUCKET=ENC[AES256_GCM,data:opg8kQY3PKnZ,iv:lPHUBDwHgBulOyt9WWgZhBQae8t2WKYvLHSFQrG3N/w=,tag:qtyIz4fbh40aLp7ZawBJiA==,type:str]
|
||||||
LITESTREAM_R2_ACCESS_KEY_ID=ENC[AES256_GCM,data:7Mn+GggjzjbN,iv:vmUfPamn8pJR/vFtnO2rhD2+ALHUiZ8ahPTGzncsyVM=,tag:v1Tp97ZsiOJocqDoXCb58g==,type:str]
|
LITESTREAM_R2_ACCESS_KEY_ID=ENC[AES256_GCM,data:6jaEysPtRal7,iv:s5aLx7LdZ3ZLA9oL5vXXDfDDGI7gd5/CukNrMpPLJNk=,tag:Igp3bqW52raBfEeUaUvZ7A==,type:str]
|
||||||
LITESTREAM_R2_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:CN+aCZHaIANh,iv:X34JZ/OC5AalWZVAlDoN0G1Kch2z1Q3Uo18PJa1NTE8=,tag:wAqiEz6UCHcaISCOzOgtgg==,type:str]
|
LITESTREAM_R2_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:QfXhwh9L2rhr,iv:OaYlzTiu4onCNu5HfytYTCJa5p2QLShhO5j5Y038IOs=,tag:i13aQ2ICePyCU/Ob+EA7Nw==,type:str]
|
||||||
LITESTREAM_R2_ENDPOINT=ENC[AES256_GCM,data:C8UWFesY4/M+,iv:6ti84Kr0aDxs1S7Y17uNb78j2EzjovAki6N6cAm7agY=,tag:R9Q2Ug+HXpXYm/gTF/be1g==,type:str]
|
LITESTREAM_R2_ENDPOINT=ENC[AES256_GCM,data:hLneNsFmgQ6+,iv:RNefJ3QbviHPURxcK2xYJU7qWpMfWInCxYQ/4xDIwfw=,tag:FhMiHGrNcsXaSmdG4NXgfQ==,type:str]
|
||||||
#ENC[AES256_GCM,data:qq7NU6KOZT/mwuTunBs=,iv:5R65DWMFkHdoeKsBiuDQFJqLdoH+nwTsU/DbWD+4yWM=,tag:Daf2m6DEi5pAJ+NQSXFD+g==,type:comment]
|
#ENC[AES256_GCM,data:YGV2exKdGOUkblNZZos=,iv:NuabFM/gNHIzYmDMRZ2tglFYdMPVFuHFGd+AAWvvu6Q=,tag:gZRoNNEmjL9v3nC8j9YkHw==,type:comment]
|
||||||
DUCKDB_PATH=ENC[AES256_GCM,data:nvlaHUuTDDHl2vx3CMVCRXra6QuJ,iv:2M/eptAN/wNZgZTC0kye4dF0rWUGkjdK+hwAOuyHU1s=,tag:9rExFDqVe2e9KWCTdgnQjw==,type:str]
|
DUCKDB_PATH=ENC[AES256_GCM,data:GgOEQ5B1KeQrVavhoMU/JGXcVu3H,iv:XY8JiaosxaUDv5PwizrZFWuNKMSOeuE3cfVyp51r++8=,tag:RnoDE5+7WQolFLejfRZ//w==,type:str]
|
||||||
SERVING_DUCKDB_PATH=ENC[AES256_GCM,data:ZdbRf/E5WVf9CfJ/mA7CM/C80RRW,iv:0dG5+oloGs8tOno4Lu8gJFVQ/ilTQVhqiZn7jis8u1E=,tag:kmphDZt1Y/D8qfbLJQCLwg==,type:str]
|
SERVING_DUCKDB_PATH=ENC[AES256_GCM,data:U2X9KmlgnWXM9uCfhHCJ03HMGCLm,iv:KHHdBTq+ct4AG7Jt4zLog/5jbDC7LvHA6KzWNTDS/Yw=,tag:m5uIG/bS4vaBooSYoYa6SA==,type:str]
|
||||||
LANDING_DIR=ENC[AES256_GCM,data:NQQbZyOeMGCMxQOo,iv:E7+B45xAqG71yt4gc4aa3YA1KTJWDD7OcY9SieMDNd4=,tag:c2S1aDMD8zDQfmFew5sM/g==,type:str]
|
LANDING_DIR=ENC[AES256_GCM,data:NkEmV8LOwEiN9Sal,iv:mQHBVT6lNoEEEVbl7a5bNN5qoF/LvTyWXQvvkv/z/B0=,tag:IgA5A1nfF91fOBdYxEN71g==,type:str]
|
||||||
#ENC[AES256_GCM,data:BASJNxsOqfahZJ4=,iv:Fxwpluhe/V1xVMqO4rWwRamKEIHGfUrGMsx1XmKZv+4=,tag:2MthdZr4YMkVREbSydQC9g==,type:comment]
|
#ENC[AES256_GCM,data:jvZYm7ceM4jtNRg=,iv:nuv65SDTZiaVukVZ40seBZevpqP8uiKCgJyQcIrY524=,tag:cq6gB3vmJzJWIXCLHaIc9g==,type:comment]
|
||||||
REPO_DIR=ENC[AES256_GCM,data:FmiIf4/s9qcF1KFriNfZIg==,iv:ur2jpVrM0rHYT55Ir3Wvb7InWr7J3Bxdt3s/D8w222w=,tag:Yk+BUcS2Ae2KzJzLQklcFA==,type:str]
|
REPO_DIR=ENC[AES256_GCM,data:ae8i6PpGFaiYFA/gGIhczg==,iv:nmsIRMPJYocIO6Z2Gz4OIzAOvSpdgDYmUaIr2hInFo0=,tag:EmAYG5NujnHg8lPaO/uAnQ==,type:str]
|
||||||
WORKFLOWS_PATH=ENC[AES256_GCM,data:JAKGfDMe0wptVhisI7YC9GMBdslurVuaSG8L9aXI7A==,iv:s9P3m14zWUs3REQvypfQ3j/n/Klt4hPzwGqbGDOEOR8=,tag:wTDLzQ/KUjde7zDpG7rYiQ==,type:str]
|
WORKFLOWS_PATH=ENC[AES256_GCM,data:sGU4l68Pbb1thsPyG104mWXWD+zJGTIcR/TqVbPmew==,iv:+xhGkX+ep4kFEAU65ELdDrfjrl/WyuaOi35JI3OB/zM=,tag:brauZhFq8twPXmvhZKjhDQ==,type:str]
|
||||||
ALERT_WEBHOOK_URL=ENC[AES256_GCM,data:3roAiuwKnSNzSjU9d5wIPRJAW7e9l7vtxc7Lj5Sf9Uo=,iv:vC738fioTDsWrVB///9GkKV7Jc67hlvuvcfnSAzwpuQ=,tag:v29o37UGSoA6/S7VxwENaQ==,type:str]
|
ALERT_WEBHOOK_URL=ENC[AES256_GCM,data:4sXQk8zklruC525J279TUUatdDJQ43qweuoPhtpI82Y=,iv:1NT5IsslsZjo/0xU9OGFf717G56FnSkKSZ2L1+U3peU=,tag:bhZ67zlDiq7VaY47LFWOVw==,type:str]
|
||||||
NTFY_TOKEN=ENC[AES256_GCM,data:hQerDktsRYZaBaunhS0koSVtehopZ1Cp/p+9v/FbIZU=,iv:b204WBPs3GYfgfCWr1mi58QN+iLO2Ekl0jrRQWcKoAY=,tag:+7h3on0TNiJUqg7EREZRYw==,type:str]
|
NTFY_TOKEN=ENC[AES256_GCM,data:YlOxhsRJ8P1y4kk6ugWm41iyRCsM6oAWjvbU9lGcD0A=,iv:JZXOvi3wTOPV9A46c7fMiqbszNCvXkOgh9i/H1hob24=,tag:8xnPimgy7sesOAnxhaXmpg==,type:str]
|
||||||
SUPERVISOR_GIT_PULL=ENC[AES256_GCM,data:AQ==,iv:/gJIcFRMd2jU97paHwXKzREQBOeJ2vq9l5ux9gDewO0=,tag:cMa4+fgk4oqUqc+DfBkSSQ==,type:str]
|
SUPERVISOR_GIT_PULL=ENC[AES256_GCM,data:mg==,iv:KgqMVYj12FjOzWxtA1T0r0pqCDJ6MtHzMjE+4W/W+s4=,tag:czFaOqhHG8nqrQ8AZ8QiGw==,type:str]
|
||||||
#ENC[AES256_GCM,data:u1BQCX8MCMcj1zU=,iv:Dtk4K4eZ50w3zbZKlZxrvytsWlYFteXFv+0QBYJJ59U=,tag:8iveGLmKeOrVk2bMVG7D7w==,type:comment]
|
#ENC[AES256_GCM,data:hzAZvCWc4RTk290=,iv:RsSI4OpAOQGcFVpfXDZ6t705yWmlO0JEWwWF5uQu9As=,tag:UPqFtA2tXiSa0vzJAv8qXg==,type:comment]
|
||||||
PROXY_URLS=ENC[AES256_GCM,data:KH3171qwQp4rkbP1gJXYUqicmZuS5q+7xQ3Hv/Pb2MG1Sp/RbQwpEMR7X2ZiQYO6WUaIcZW/87qAEAShFe/w+qPHACSi//f7zwxv5vHNAhBaxMeU4tPudh9XGAvfyKonNdrU1rrPWIyQPVZ4ZKOUWJsetOy6P1Xly3PLpk8xh4jfLUmKuHEbuQcJInRqhcJdjSecLcAvaDmElZCDhr6dhq2PVQdciS/0LkDLj1xkZ09UrfKbS0fQD8//SM7VX+mPUNjIVK+4kytTbccrK3w79HEgFUnWa4q5PDnzYUFzexKu8pJEQwEl5gkBoHZo8lDoNNP2qed/pdEE9aMxNOSDJsoidW/vIhSxk1GmYwLuzLXAMcLdvujjrgLrt1lvZEyuWwGyRDhsYSp6SA80U48B3UrgFK3xXzwf1YuZHpcCN3Zjb82KLgXJIUzK+LA9kDiGMdzyEtNT1q6+RdCHS7I4XXgcFPaTJL6J/avUqwU31YpvSMM0LItJeKM9I5FL5BbC/IDhE1ax26Px27Hos710+lI3eUu2RViw82d650gqSu2IFfGbGv9ECsTOyUR8no427iqyB/003DAkgxjbSCs6oJIayKHJNULWAMks2mx35gGQathVLdmysfSfLYizP9I=,iv:IZTwqbh76CRXYPVYyZ6PlSjKof+dE1XHvomV8nA6xWM=,tag:de07uKNUmsGOX/2trFqWfw==,type:str]
|
PROXY_URLS=ENC[AES256_GCM,data:L2Oobpi6Pq8m,iv:14mXi+8mLv2e20IKVL0VlxZiHW/1BmeQP4a6ns5930g=,tag:pVJasNjv6N/UApVm+KD+XA==,type:str]
|
||||||
EXTRACT_WORKERS=ENC[AES256_GCM,data:nQ==,iv:rUlSbl5+wdpY9OATciWw7u/Wniyi+ctJDyQ2jzdLI1U=,tag:NaVN4gJo2M5dK0Hq9/H67w==,type:str]
|
RECHECK_WINDOW_MINUTES=ENC[AES256_GCM,data:L2s=,iv:fV3mCKmK5fxUmIWRePELBDAPTb8JZqasVIhnAl55kYw=,tag:XL+PO6sblz/7WqHC3dtk1w==,type:str]
|
||||||
RECHECK_WINDOW_MINUTES=ENC[AES256_GCM,data:kFU=,iv:2yLZn5eC8UXCfOPNa+1fOnRnaY3JhMlh9AdBRAyrCZU=,tag:Dalqeod45g0mn4Mn1YEMvQ==,type:str]
|
#ENC[AES256_GCM,data:RC+t2vqLwLjapdAUql8rQls=,iv:Kkiz3ND0g0MRAgcPJysIYMzSQS96Rq+3YP5yO7yWfIY=,tag:Y6TbZd81ihIwn+U515qd1g==,type:comment]
|
||||||
#ENC[AES256_GCM,data:GK/IYt5yyMbVLGx/P8Hzyfc=,iv:mzIaCOzunFypa+eJhf/gAWxZDixH6bxauOZjo6ARVEQ=,tag:r1650Kgs7frUDAVJqpQ3Yw==,type:comment]
|
GSC_SERVICE_ACCOUNT_PATH=ENC[AES256_GCM,data:Vki6yHk+gd4n,iv:rxzKvwrGnAkLcpS41EZ097E87NrIpNZGFfl4iXFvr40=,tag:EZkBJpCq5rSpKYVC4H3JHQ==,type:str]
|
||||||
GSC_SERVICE_ACCOUNT_PATH=ENC[AES256_GCM,data:ZgsW82cm38rR,iv:Qz4oSAKQ2U3dBgy1GtDl56GGzCqnmvldA9ByiQLjH94=,tag:qxlF/5CvhNd4fOPajwrPvg==,type:str]
|
GSC_SITE_URL=ENC[AES256_GCM,data:K0i1xRym+laMP6kgOMEfUyoAn2eNgQ==,iv:kyb+grzFq1e5CG/0NJRO3LkSXexOuCK07uJYApAdWsA=,tag:faljHqYjGTgrR/Zbh27/Yw==,type:str]
|
||||||
GSC_SITE_URL=ENC[AES256_GCM,data:nz5OC1nciCmds6bl257oo9IX3SA7kA==,iv:+LV0kDG4xfwL3E3oGEsUB1h3W7ZpRijv3LipWq+GHg8=,tag:zUmIKWfHd6KwD+cMZb0ZKw==,type:str]
|
BING_WEBMASTER_API_KEY=ENC[AES256_GCM,data:kSQxJOpsYCuJ,iv:Kc4jJpOd64PATeBjidNHTwBr/bNnCeqsTrUqAAYM5Vs=,tag:4jBxqgpyomzMLwiC9XpfVQ==,type:str]
|
||||||
BING_WEBMASTER_API_KEY=ENC[AES256_GCM,data:3h4h5BIApJdX,iv:lG/eNi0HwBF+FL5gyhDUiqHrLdMe+s8/PbXNqInR/i8=,tag:dvXNTf/Gn0RNE+A0U2fzPQ==,type:str]
|
BING_SITE_URL=ENC[AES256_GCM,data:M33VI97DyxH8gRR3ZUXoXg4QrEv5og==,iv:GxZtwfbBVihUbp6XNQKzAalhO1GfQF1l1j1MeEIBCFQ=,tag:9njlBp4v684PeFl3HebyIg==,type:str]
|
||||||
BING_SITE_URL=ENC[AES256_GCM,data:ij5VYDk61wpIF21DdG1B6oEwN4yzUg==,iv:gJWQIYMCpjdVV+BoWxlpYy5uyM4M9WJQuHgud4Z7djY=,tag:rQwlCHjLSiLFjrATleiHVQ==,type:str]
|
#ENC[AES256_GCM,data:OTUMKNkRW0zrupNppXthwE1oieILhNjM+cjx5hFn69g=,iv:48ID2qtSe9ggD2X+G/iUqp3v2uwEc7fZw8lxHIvVXmk=,tag:okBn0Npk1K9dDOFWA/AB1A==,type:comment]
|
||||||
#ENC[AES256_GCM,data:CJ83yX864ts1yvmP7pQXIbDqXbmwu6o9tAm4AMBH0A4=,iv:GgOY+GkLnS+mMu/iQSe4EFyMjC/tGnSR25GvvYS/gdA=,tag:sHqmi1Z+PMflJo/rHbTdUA==,type:comment]
|
GEONAMES_USERNAME=ENC[AES256_GCM,data:UXd/S2TzXPiGmLY=,iv:OMURM5E6SFEsaqroUlH76DEnr7C/ujNk9UQnbWT0hK4=,tag:VsjjS12QDbudiEhdAQ/OCQ==,type:str]
|
||||||
GEONAMES_USERNAME=ENC[AES256_GCM,data:d9jN+L/93b2wDrY=,iv:IsmVx459v4vw/XREahd3SN88U8yCKQwaVaTty7b2big=,tag:OgO3lzdCt/Yg0S2Z/t9Row==,type:str]
|
CENSUS_API_KEY=
|
||||||
CENSUS_API_KEY=ENC[AES256_GCM,data:KmceIQqzlvJ3l7yPCksP1ecT0KFqo5/owrRx34/u3Md4WUTj2RGn3g==,iv:1YVk6/OUZNxXOkzZ5qZXj6RNhy4mfEnus+uBnRcRLKE=,tag:rP1y2XEvQwim1rq/GjMPNw==,type:str]
|
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqck9GdHVkUmIzNnlvMW5k\nVkNtazZ0ZytzZ25vMU5SckdFLzcrTFNYOVZZCmNjbU9yV0lTRlB5cEpMVC81QTdu\nS2ZDc0ZkNnRBNFhFMEN1bjY3YVhwZEEKLS0tIGE5TEdYenVOV1IwcE0wYnlKNElF\ncXV1K0xuczZzZ3JnL1lrSC9QWHIwNGsKfW4ARke6Cj83BpQc8weayL3v8SVgQ+Fp\n99aVWp103O1fumksR1w4u0X7fSNRrgAmpY/yyZuEvsoIY8ELFVcqgQ==\n-----END AGE ENCRYPTED FILE-----\n
|
||||||
PROXY_URLS_FALLBACK=ENC[AES256_GCM,data:hB9OdyCSGXVz6U1wPuSElzW/2rzHinqPBFFiJv9ugiXYlwj1uviGe0SHZ68AQHB050s/1ku3jDnzPwdM,iv:d1e9RLg5LLCPGKeZS1lkmh44Tt9GEW+ZC2YBMU5rnxA=,tag:whOwZYndrq7VB8flwLmW3w==,type:str]
|
|
||||||
CIRCUIT_BREAKER_THRESHOLD=ENC[AES256_GCM,data:kF8=,iv:6d/79VZFtIQtxKcvtVKF5/69KIBr7bIWpTPKt+vt6fU=,tag:yQStmU9Gjhs5CJw/r9b9rA==,type:str]
|
|
||||||
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhRU1MWm44bGpuSlRyVUpT\nU0syT2x5eU9lSnR6Nk9DejFNOUJYaFUyeENvClJpU0d6dTR6emoyRDIvaUhnTDNU\nL0hpc0w5QWI0K1hKa1p3NTVCU3Jyd0kKLS0tIDBOUkdjNTI1a0ZLMzFpcjV2QmZS\nNHltZ2hIcDFRVkVmSEQ1bnFKdk9YdW8KqJSaCtueN7XdOBfgzOfJYDHUga2rgWXF\n5XbTf4WE8HMqtWWgAq6ZQLFGSbJAhKmPlscjyp/VflFZvKvVqm3qbw==\n-----END AGE ENCRYPTED FILE-----\n
|
|
||||||
sops_age__list_0__map_recipient=age1f5002gj4s78jju45jd28kuejtcfhn5cdujz885fl7z2p9ym68pnsgky87a
|
sops_age__list_0__map_recipient=age1f5002gj4s78jju45jd28kuejtcfhn5cdujz885fl7z2p9ym68pnsgky87a
|
||||||
sops_lastmodified=2026-02-24T20:32:28Z
|
sops_lastmodified=2026-02-24T21:29:26Z
|
||||||
sops_mac=ENC[AES256_GCM,data:htupj3WWPJ9KAxkgnsFyX8xnnfBeSna3nEjMA/RoFaDLBxkfFhXJOqiGTGzYAnsoc6KKxSdLy1Raa9wRqmmM0hmSqptBr/9axGthNmTg4m8UAgCzCG/ZJSL+hlTvmnL7Y2p3ryvk9w8Tw8jchdbFWgP5C6wmA1YAMOVBW+BfHxo=,iv:5NUe020D/1j8ISDFhZGyW8pobsKQtga2DJCmIV7yyIA=,tag:TlcQaU6z9ktkVJEduyoWtA==,type:str]
|
sops_mac=ENC[AES256_GCM,data:zYvusl8/pvL6FwXAtsKi4BhuiDt8KaZPNHXkw0ywIOgNFG5mvcQozcDj42+TIo+Yuum1o7WHqshKc70w0Mq4fskq3TsjVnjWgw7xYRr5s3ylN5ZknbbCoMP4cp6YrkNCe/8hR64miguYqqEQlf9NdgL52uamF5lV5irI/EtLouw=,iv:RcL2b8ccnMxKhXxAocTG9G6gv2BkTb++MUpkFK8MfbM=,tag:+0avRrQjNOHDUeAV1dLW3g==,type:str]
|
||||||
sops_unencrypted_suffix=_unencrypted
|
sops_unencrypted_suffix=_unencrypted
|
||||||
sops_version=3.12.1
|
sops_version=3.12.1
|
||||||
|
|||||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -39,6 +39,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
queries, geometry columns).
|
queries, geometry columns).
|
||||||
- **SOPS secrets** — `GEONAMES_USERNAME=padelnomics` and `CENSUS_API_KEY` added to both
|
- **SOPS secrets** — `GEONAMES_USERNAME=padelnomics` and `CENSUS_API_KEY` added to both
|
||||||
`.env.dev.sops` and `.env.prod.sops`.
|
`.env.dev.sops` and `.env.prod.sops`.
|
||||||
|
- **Crash-safe partial JSONL** — `utils.load_partial_results()` and `flush_partial_batch()`
|
||||||
|
provide a generic opt-in mechanism for incremental progress flushing during long extractions.
|
||||||
|
Any extractor processing items one-by-one can flush every N records and resume from a
|
||||||
|
`.partial.jsonl` sidecar file after a crash.
|
||||||
- **Methodology page updated** — `/en/market-score` now documents both scores with:
|
- **Methodology page updated** — `/en/market-score` now documents both scores with:
|
||||||
Two Scores intro section, component cards for each score (4 Marktreife + 5 Marktpotenzial),
|
Two Scores intro section, component cards for each score (4 Marktreife + 5 Marktpotenzial),
|
||||||
score band interpretations, expanded FAQ (7 entries). Section headings use the padelnomics
|
score band interpretations, expanded FAQ (7 entries). Section headings use the padelnomics
|
||||||
@@ -52,6 +56,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
First "padelnomics Market Score" mention in each article template now links
|
First "padelnomics Market Score" mention in each article template now links
|
||||||
to the methodology page (hub-and-spoke internal linking).
|
to the methodology page (hub-and-spoke internal linking).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **`EXTRACT_WORKERS` env var removed** — worker count is now derived from `PROXY_URLS` length
|
||||||
|
(one worker per proxy). No proxies → single-threaded. No manual tuning needed.
|
||||||
|
- **Playtomic tenants extractor** — parallel batch page fetching when proxies are configured.
|
||||||
|
Each page in a batch fires concurrently using its own session + proxy. Expected speedup:
|
||||||
|
~2.5 min → ~15 s with 10 Webshare datacenter proxies.
|
||||||
|
- **Playtomic availability extractor** — three performance changes:
|
||||||
|
1. No per-request `time.sleep()` on success when a proxy is active (throttle only when
|
||||||
|
running direct). Retry/backoff sleeps for 429 and 5xx responses are unchanged.
|
||||||
|
2. Worker count auto-detected from proxy count (drops `EXTRACT_WORKERS`).
|
||||||
|
3. True crash resumption via `.partial.jsonl` sidecar: progress flushed every 50 venues,
|
||||||
|
resume skips already-fetched venues and merges prior results into the final file.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- **`datetime.utcnow()` deprecation warnings** — replaced all 94 occurrences
|
- **`datetime.utcnow()` deprecation warnings** — replaced all 94 occurrences
|
||||||
across 22 files (source + tests) with `utcnow()` / `utcnow_iso()` helpers
|
across 22 files (source + tests) with `utcnow()` / `utcnow_iso()` helpers
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ unauthenticated /v1/availability endpoint for each venue's next-day slots.
|
|||||||
This is the highest-value source: daily snapshots enable occupancy rate
|
This is the highest-value source: daily snapshots enable occupancy rate
|
||||||
estimation, pricing benchmarking, and demand signal detection.
|
estimation, pricing benchmarking, and demand signal detection.
|
||||||
|
|
||||||
Parallel mode: set EXTRACT_WORKERS=N and PROXY_URLS=... to fetch N venues
|
Parallel mode: worker count is derived from PROXY_URLS (one worker per proxy).
|
||||||
concurrently (one proxy per worker). Without proxies, runs single-threaded.
|
Without proxies, runs single-threaded with per-request throttling.
|
||||||
|
|
||||||
|
Crash resumption: progress is flushed to a .partial.jsonl sidecar file every
|
||||||
|
PARTIAL_FLUSH_SIZE records. On restart the already-fetched venues are skipped
|
||||||
|
and prior results are merged into the final file. At most PARTIAL_FLUSH_SIZE
|
||||||
|
records (a few seconds of work with 10 workers) are lost on crash.
|
||||||
|
|
||||||
Recheck mode: re-queries venues with slots starting within the next 90 minutes.
|
Recheck mode: re-queries venues with slots starting within the next 90 minutes.
|
||||||
Writes a separate recheck file for more accurate occupancy measurement.
|
Writes a separate recheck file for more accurate occupancy measurement.
|
||||||
@@ -29,7 +34,7 @@ import niquests
|
|||||||
|
|
||||||
from ._shared import HTTP_TIMEOUT_SECONDS, USER_AGENT, run_extractor, setup_logging
|
from ._shared import HTTP_TIMEOUT_SECONDS, USER_AGENT, run_extractor, setup_logging
|
||||||
from .proxy import load_fallback_proxy_urls, load_proxy_urls, make_tiered_cycler
|
from .proxy import load_fallback_proxy_urls, load_proxy_urls, make_tiered_cycler
|
||||||
from .utils import get_last_cursor, landing_path, write_gzip_atomic
|
from .utils import flush_partial_batch, landing_path, load_partial_results, write_gzip_atomic
|
||||||
|
|
||||||
logger = setup_logging("padelnomics.extract.playtomic_availability")
|
logger = setup_logging("padelnomics.extract.playtomic_availability")
|
||||||
|
|
||||||
@@ -40,7 +45,6 @@ AVAILABILITY_URL = "https://api.playtomic.io/v1/availability"
|
|||||||
THROTTLE_SECONDS = 1
|
THROTTLE_SECONDS = 1
|
||||||
MAX_VENUES_PER_RUN = 20_000
|
MAX_VENUES_PER_RUN = 20_000
|
||||||
MAX_RETRIES_PER_VENUE = 2
|
MAX_RETRIES_PER_VENUE = 2
|
||||||
MAX_WORKERS = int(os.environ.get("EXTRACT_WORKERS", "1"))
|
|
||||||
RECHECK_WINDOW_MINUTES = int(os.environ.get("RECHECK_WINDOW_MINUTES", "90"))
|
RECHECK_WINDOW_MINUTES = int(os.environ.get("RECHECK_WINDOW_MINUTES", "90"))
|
||||||
CIRCUIT_BREAKER_THRESHOLD = int(os.environ.get("CIRCUIT_BREAKER_THRESHOLD") or "10")
|
CIRCUIT_BREAKER_THRESHOLD = int(os.environ.get("CIRCUIT_BREAKER_THRESHOLD") or "10")
|
||||||
|
|
||||||
@@ -49,6 +53,9 @@ CIRCUIT_BREAKER_THRESHOLD = int(os.environ.get("CIRCUIT_BREAKER_THRESHOLD") or "
|
|||||||
# batch still complete.
|
# batch still complete.
|
||||||
PARALLEL_BATCH_SIZE = 100
|
PARALLEL_BATCH_SIZE = 100
|
||||||
|
|
||||||
|
# Flush partial results to disk every N records — lose at most this many on crash.
|
||||||
|
PARTIAL_FLUSH_SIZE = 50
|
||||||
|
|
||||||
# Thread-local storage for per-worker sessions
|
# Thread-local storage for per-worker sessions
|
||||||
_thread_local = threading.local()
|
_thread_local = threading.local()
|
||||||
|
|
||||||
@@ -85,22 +92,6 @@ def _load_tenant_ids(landing_dir: Path) -> list[str]:
|
|||||||
return ids
|
return ids
|
||||||
|
|
||||||
|
|
||||||
def _parse_resume_cursor(cursor: str | None, target_date: str) -> int:
|
|
||||||
"""Parse cursor_value to find resume index. Returns 0 if no valid cursor."""
|
|
||||||
if not cursor:
|
|
||||||
return 0
|
|
||||||
parts = cursor.split(":", 1)
|
|
||||||
if len(parts) != 2:
|
|
||||||
return 0
|
|
||||||
cursor_date, cursor_index = parts
|
|
||||||
if cursor_date != target_date:
|
|
||||||
return 0
|
|
||||||
try:
|
|
||||||
return int(cursor_index)
|
|
||||||
except ValueError:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Per-venue fetch (used by both serial and parallel modes)
|
# Per-venue fetch (used by both serial and parallel modes)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -150,7 +141,8 @@ def _fetch_venue_availability(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
time.sleep(THROTTLE_SECONDS)
|
if not proxy_url:
|
||||||
|
time.sleep(THROTTLE_SECONDS)
|
||||||
return {"tenant_id": tenant_id, "slots": resp.json()}
|
return {"tenant_id": tenant_id, "slots": resp.json()}
|
||||||
|
|
||||||
except niquests.exceptions.RequestException as e:
|
except niquests.exceptions.RequestException as e:
|
||||||
@@ -178,6 +170,7 @@ def _fetch_venues_parallel(
|
|||||||
worker_count: int,
|
worker_count: int,
|
||||||
cycler: dict,
|
cycler: dict,
|
||||||
fallback_urls: list[str],
|
fallback_urls: list[str],
|
||||||
|
on_result=None,
|
||||||
) -> tuple[list[dict], int]:
|
) -> tuple[list[dict], int]:
|
||||||
"""Fetch availability for multiple venues in parallel.
|
"""Fetch availability for multiple venues in parallel.
|
||||||
|
|
||||||
@@ -185,6 +178,9 @@ def _fetch_venues_parallel(
|
|||||||
completes, checks the circuit breaker: if it opened and there is no
|
completes, checks the circuit breaker: if it opened and there is no
|
||||||
fallback configured, stops submitting further batches.
|
fallback configured, stops submitting further batches.
|
||||||
|
|
||||||
|
on_result: optional callable(result: dict) invoked inside the lock for
|
||||||
|
each successful result — used for incremental partial-file flushing.
|
||||||
|
|
||||||
Returns (venues_data, venues_errored).
|
Returns (venues_data, venues_errored).
|
||||||
"""
|
"""
|
||||||
venues_data: list[dict] = []
|
venues_data: list[dict] = []
|
||||||
@@ -216,6 +212,8 @@ def _fetch_venues_parallel(
|
|||||||
if result is not None:
|
if result is not None:
|
||||||
venues_data.append(result)
|
venues_data.append(result)
|
||||||
cycler["record_success"]()
|
cycler["record_success"]()
|
||||||
|
if on_result is not None:
|
||||||
|
on_result(result)
|
||||||
else:
|
else:
|
||||||
venues_errored += 1
|
venues_errored += 1
|
||||||
cycler["record_failure"]()
|
cycler["record_failure"]()
|
||||||
@@ -265,41 +263,56 @@ def extract(
|
|||||||
logger.info("Already have %s — skipping", dest)
|
logger.info("Already have %s — skipping", dest)
|
||||||
return {"files_written": 0, "files_skipped": 1, "bytes_written": 0}
|
return {"files_written": 0, "files_skipped": 1, "bytes_written": 0}
|
||||||
|
|
||||||
# Resume from cursor if crashed mid-run
|
# Crash resumption: load already-fetched venues from partial file
|
||||||
last_cursor = get_last_cursor(conn, EXTRACTOR_NAME)
|
partial_path = dest.with_suffix(".partial.jsonl")
|
||||||
resume_index = _parse_resume_cursor(last_cursor, target_date)
|
prior_results, already_done = load_partial_results(partial_path, id_key="tenant_id")
|
||||||
if resume_index > 0:
|
if already_done:
|
||||||
logger.info("Resuming from index %d (cursor: %s)", resume_index, last_cursor)
|
logger.info("Resuming: %d venues already fetched from partial file", len(already_done))
|
||||||
|
|
||||||
venues_to_process = tenant_ids[:MAX_VENUES_PER_RUN]
|
all_venues_to_process = tenant_ids[:MAX_VENUES_PER_RUN]
|
||||||
if resume_index > 0:
|
venues_to_process = [tid for tid in all_venues_to_process if tid not in already_done]
|
||||||
venues_to_process = venues_to_process[resume_index:]
|
|
||||||
|
|
||||||
# Set up tiered proxy cycler with circuit breaker
|
# Set up tiered proxy cycler with circuit breaker
|
||||||
proxy_urls = load_proxy_urls()
|
proxy_urls = load_proxy_urls()
|
||||||
fallback_urls = load_fallback_proxy_urls()
|
fallback_urls = load_fallback_proxy_urls()
|
||||||
worker_count = min(MAX_WORKERS, len(proxy_urls)) if proxy_urls else 1
|
worker_count = len(proxy_urls) if proxy_urls else 1
|
||||||
cycler = make_tiered_cycler(proxy_urls, fallback_urls, CIRCUIT_BREAKER_THRESHOLD)
|
cycler = make_tiered_cycler(proxy_urls, fallback_urls, CIRCUIT_BREAKER_THRESHOLD)
|
||||||
|
|
||||||
start_min_str = start_min.strftime("%Y-%m-%dT%H:%M:%S")
|
start_min_str = start_min.strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
start_max_str = start_max.strftime("%Y-%m-%dT%H:%M:%S")
|
start_max_str = start_max.strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
|
|
||||||
|
# Partial file for incremental crash-safe progress
|
||||||
|
partial_file = open(partial_path, "a") # noqa: SIM115
|
||||||
|
partial_lock = threading.Lock()
|
||||||
|
pending_batch: list[dict] = []
|
||||||
|
|
||||||
|
def _on_result(result: dict) -> None:
|
||||||
|
# Called inside _fetch_venues_parallel's lock — no additional locking needed.
|
||||||
|
# In serial mode, called single-threaded — also safe without extra locking.
|
||||||
|
pending_batch.append(result)
|
||||||
|
if len(pending_batch) >= PARTIAL_FLUSH_SIZE:
|
||||||
|
flush_partial_batch(partial_file, partial_lock, pending_batch)
|
||||||
|
pending_batch.clear()
|
||||||
|
|
||||||
|
new_venues_data: list[dict] = []
|
||||||
|
venues_errored = 0
|
||||||
|
|
||||||
if worker_count > 1:
|
if worker_count > 1:
|
||||||
logger.info("Parallel mode: %d workers, %d proxies", worker_count, len(proxy_urls))
|
logger.info("Parallel mode: %d workers, %d proxies", worker_count, len(proxy_urls))
|
||||||
venues_data, venues_errored = _fetch_venues_parallel(
|
new_venues_data, venues_errored = _fetch_venues_parallel(
|
||||||
venues_to_process, start_min_str, start_max_str, worker_count, cycler, fallback_urls,
|
venues_to_process, start_min_str, start_max_str, worker_count, cycler, fallback_urls,
|
||||||
|
on_result=_on_result,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.info("Serial mode: 1 worker, %d venues", len(venues_to_process))
|
logger.info("Serial mode: 1 worker, %d venues", len(venues_to_process))
|
||||||
venues_data = []
|
|
||||||
venues_errored = 0
|
|
||||||
for i, tenant_id in enumerate(venues_to_process):
|
for i, tenant_id in enumerate(venues_to_process):
|
||||||
result = _fetch_venue_availability(
|
result = _fetch_venue_availability(
|
||||||
tenant_id, start_min_str, start_max_str, cycler["next_proxy"](),
|
tenant_id, start_min_str, start_max_str, cycler["next_proxy"](),
|
||||||
)
|
)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
venues_data.append(result)
|
new_venues_data.append(result)
|
||||||
cycler["record_success"]()
|
cycler["record_success"]()
|
||||||
|
_on_result(result)
|
||||||
else:
|
else:
|
||||||
venues_errored += 1
|
venues_errored += 1
|
||||||
circuit_opened = cycler["record_failure"]()
|
circuit_opened = cycler["record_failure"]()
|
||||||
@@ -313,7 +326,14 @@ def extract(
|
|||||||
i + 1, len(venues_to_process), venues_errored,
|
i + 1, len(venues_to_process), venues_errored,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Write consolidated file
|
# Final flush of any remaining partial batch
|
||||||
|
if pending_batch:
|
||||||
|
flush_partial_batch(partial_file, partial_lock, pending_batch)
|
||||||
|
pending_batch.clear()
|
||||||
|
partial_file.close()
|
||||||
|
|
||||||
|
# Consolidate prior (resumed) + new results into final file
|
||||||
|
venues_data = prior_results + new_venues_data
|
||||||
captured_at = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
|
captured_at = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
payload = json.dumps({
|
payload = json.dumps({
|
||||||
"date": target_date,
|
"date": target_date,
|
||||||
@@ -324,6 +344,9 @@ def extract(
|
|||||||
}).encode()
|
}).encode()
|
||||||
|
|
||||||
bytes_written = write_gzip_atomic(dest, payload)
|
bytes_written = write_gzip_atomic(dest, payload)
|
||||||
|
if partial_path.exists():
|
||||||
|
partial_path.unlink()
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"%d venues scraped (%d errors) -> %s (%s bytes)",
|
"%d venues scraped (%d errors) -> %s (%s bytes)",
|
||||||
len(venues_data), venues_errored, dest, f"{bytes_written:,}",
|
len(venues_data), venues_errored, dest, f"{bytes_written:,}",
|
||||||
@@ -333,7 +356,7 @@ def extract(
|
|||||||
"files_written": 1,
|
"files_written": 1,
|
||||||
"files_skipped": 0,
|
"files_skipped": 0,
|
||||||
"bytes_written": bytes_written,
|
"bytes_written": bytes_written,
|
||||||
"cursor_value": f"{target_date}:{len(tenant_ids[:MAX_VENUES_PER_RUN])}",
|
"cursor_value": f"{target_date}:{len(all_venues_to_process)}",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -426,7 +449,7 @@ def extract_recheck(
|
|||||||
# Set up tiered proxy cycler with circuit breaker
|
# Set up tiered proxy cycler with circuit breaker
|
||||||
proxy_urls = load_proxy_urls()
|
proxy_urls = load_proxy_urls()
|
||||||
fallback_urls = load_fallback_proxy_urls()
|
fallback_urls = load_fallback_proxy_urls()
|
||||||
worker_count = min(MAX_WORKERS, len(proxy_urls)) if proxy_urls else 1
|
worker_count = len(proxy_urls) if proxy_urls else 1
|
||||||
cycler = make_tiered_cycler(proxy_urls, fallback_urls, CIRCUIT_BREAKER_THRESHOLD)
|
cycler = make_tiered_cycler(proxy_urls, fallback_urls, CIRCUIT_BREAKER_THRESHOLD)
|
||||||
|
|
||||||
if worker_count > 1 and len(venues_to_recheck) > 10:
|
if worker_count > 1 and len(venues_to_recheck) > 10:
|
||||||
|
|||||||
@@ -10,8 +10,13 @@ API notes (discovered 2026-02):
|
|||||||
- `size=100` is the maximum effective page size
|
- `size=100` is the maximum effective page size
|
||||||
- ~14K venues globally as of Feb 2026
|
- ~14K venues globally as of Feb 2026
|
||||||
|
|
||||||
Rate: 1 req / 2 s when running direct (see docs/data-sources-inventory.md §1.2).
|
Parallel mode: when PROXY_URLS is set, fires batch_size = len(proxy_urls)
|
||||||
No throttle when PROXY_URLS is set — IP rotation removes per-IP rate concern.
|
pages concurrently. Each page gets its own fresh session + proxy. Pages beyond
|
||||||
|
the last one return empty lists (safe — just triggers the done condition).
|
||||||
|
Without proxies, falls back to single-threaded with THROTTLE_SECONDS between
|
||||||
|
pages.
|
||||||
|
|
||||||
|
Rate: 1 req / 2 s per IP (see docs/data-sources-inventory.md §1.2).
|
||||||
|
|
||||||
Landing: {LANDING_DIR}/playtomic/{year}/{month}/tenants.json.gz
|
Landing: {LANDING_DIR}/playtomic/{year}/{month}/tenants.json.gz
|
||||||
"""
|
"""
|
||||||
@@ -19,11 +24,12 @@ Landing: {LANDING_DIR}/playtomic/{year}/{month}/tenants.json.gz
|
|||||||
import json
|
import json
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import time
|
import time
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import niquests
|
import niquests
|
||||||
|
|
||||||
from ._shared import HTTP_TIMEOUT_SECONDS, run_extractor, setup_logging
|
from ._shared import HTTP_TIMEOUT_SECONDS, USER_AGENT, run_extractor, setup_logging
|
||||||
from .proxy import load_proxy_urls, make_round_robin_cycler
|
from .proxy import load_proxy_urls, make_round_robin_cycler
|
||||||
from .utils import landing_path, write_gzip_atomic
|
from .utils import landing_path, write_gzip_atomic
|
||||||
|
|
||||||
@@ -37,6 +43,30 @@ PAGE_SIZE = 100
|
|||||||
MAX_PAGES = 500 # safety bound — ~50K venues max, well above current ~14K
|
MAX_PAGES = 500 # safety bound — ~50K venues max, well above current ~14K
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_one_page(proxy_url: str | None, page: int) -> tuple[int, list[dict]]:
|
||||||
|
"""Fetch a single page using a fresh session with the given proxy.
|
||||||
|
|
||||||
|
Returns (page, tenants_list). Raises on HTTP error.
|
||||||
|
"""
|
||||||
|
s = niquests.Session()
|
||||||
|
s.headers["User-Agent"] = USER_AGENT
|
||||||
|
if proxy_url:
|
||||||
|
s.proxies = {"http": proxy_url, "https": proxy_url}
|
||||||
|
params = {"sport_ids": "PADEL", "size": PAGE_SIZE, "page": page}
|
||||||
|
resp = s.get(PLAYTOMIC_TENANTS_URL, params=params, timeout=HTTP_TIMEOUT_SECONDS)
|
||||||
|
resp.raise_for_status()
|
||||||
|
tenants = resp.json()
|
||||||
|
assert isinstance(tenants, list), f"Expected list from Playtomic API, got {type(tenants)}"
|
||||||
|
return (page, tenants)
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_pages_parallel(pages: list[int], next_proxy) -> list[tuple[int, list[dict]]]:
|
||||||
|
"""Fetch multiple pages concurrently. Returns [(page_num, tenants_list), ...]."""
|
||||||
|
with ThreadPoolExecutor(max_workers=len(pages)) as pool:
|
||||||
|
futures = [pool.submit(_fetch_one_page, next_proxy(), p) for p in pages]
|
||||||
|
return [f.result() for f in as_completed(futures)]
|
||||||
|
|
||||||
|
|
||||||
def extract(
|
def extract(
|
||||||
landing_dir: Path,
|
landing_dir: Path,
|
||||||
year_month: str,
|
year_month: str,
|
||||||
@@ -49,54 +79,63 @@ def extract(
|
|||||||
dest = dest_dir / "tenants.json.gz"
|
dest = dest_dir / "tenants.json.gz"
|
||||||
|
|
||||||
proxy_urls = load_proxy_urls()
|
proxy_urls = load_proxy_urls()
|
||||||
cycler = make_round_robin_cycler(proxy_urls) if proxy_urls else None
|
next_proxy = make_round_robin_cycler(proxy_urls) if proxy_urls else None
|
||||||
if cycler:
|
batch_size = len(proxy_urls) if proxy_urls else 1
|
||||||
logger.info("proxy rotation enabled (%d proxies, no throttle)", len(proxy_urls))
|
|
||||||
|
if next_proxy:
|
||||||
|
logger.info("Parallel mode: %d pages per batch (%d proxies)", batch_size, len(proxy_urls))
|
||||||
else:
|
else:
|
||||||
logger.info("no proxies configured — throttle %ds per page", THROTTLE_SECONDS)
|
logger.info("Serial mode: 1 page at a time (no proxies)")
|
||||||
|
|
||||||
all_tenants: list[dict] = []
|
all_tenants: list[dict] = []
|
||||||
seen_ids: set[str] = set()
|
seen_ids: set[str] = set()
|
||||||
|
page = 0
|
||||||
|
done = False
|
||||||
|
|
||||||
for page in range(MAX_PAGES):
|
while not done and page < MAX_PAGES:
|
||||||
if cycler:
|
batch_end = min(page + batch_size, MAX_PAGES)
|
||||||
proxy = cycler()
|
pages_to_fetch = list(range(page, batch_end))
|
||||||
if proxy:
|
|
||||||
session.proxies = {"http": proxy, "https": proxy}
|
|
||||||
|
|
||||||
params = {
|
if next_proxy and len(pages_to_fetch) > 1:
|
||||||
"sport_ids": "PADEL",
|
logger.info(
|
||||||
"size": PAGE_SIZE,
|
"Fetching pages %d-%d in parallel (%d workers, total so far: %d)",
|
||||||
"page": page,
|
page, batch_end - 1, len(pages_to_fetch), len(all_tenants),
|
||||||
}
|
)
|
||||||
|
results = _fetch_pages_parallel(pages_to_fetch, next_proxy)
|
||||||
|
else:
|
||||||
|
# Serial: reuse the shared session, throttle between pages
|
||||||
|
page_num = pages_to_fetch[0]
|
||||||
|
logger.info("GET page=%d (total so far: %d)", page_num, len(all_tenants))
|
||||||
|
params = {"sport_ids": "PADEL", "size": PAGE_SIZE, "page": page_num}
|
||||||
|
resp = session.get(PLAYTOMIC_TENANTS_URL, params=params, timeout=HTTP_TIMEOUT_SECONDS)
|
||||||
|
resp.raise_for_status()
|
||||||
|
tenants = resp.json()
|
||||||
|
assert isinstance(tenants, list), (
|
||||||
|
f"Expected list from Playtomic API, got {type(tenants)}"
|
||||||
|
)
|
||||||
|
results = [(page_num, tenants)]
|
||||||
|
|
||||||
logger.info("GET page=%d (total so far: %d)", page, len(all_tenants))
|
# Process pages in order so the done-detection on < PAGE_SIZE is deterministic
|
||||||
|
for p, tenants in sorted(results):
|
||||||
|
new_count = 0
|
||||||
|
for tenant in tenants:
|
||||||
|
tid = tenant.get("tenant_id") or tenant.get("id")
|
||||||
|
if tid and tid not in seen_ids:
|
||||||
|
seen_ids.add(tid)
|
||||||
|
all_tenants.append(tenant)
|
||||||
|
new_count += 1
|
||||||
|
|
||||||
resp = session.get(PLAYTOMIC_TENANTS_URL, params=params, timeout=HTTP_TIMEOUT_SECONDS)
|
logger.info(
|
||||||
resp.raise_for_status()
|
"page=%d got=%d new=%d total=%d", p, len(tenants), new_count, len(all_tenants),
|
||||||
|
)
|
||||||
|
|
||||||
tenants = resp.json()
|
# Last page — fewer than PAGE_SIZE results means we've exhausted the list
|
||||||
assert isinstance(tenants, list), (
|
if len(tenants) < PAGE_SIZE:
|
||||||
f"Expected list from Playtomic API, got {type(tenants)}"
|
done = True
|
||||||
)
|
break
|
||||||
|
|
||||||
new_count = 0
|
page = batch_end
|
||||||
for tenant in tenants:
|
if not next_proxy:
|
||||||
tid = tenant.get("tenant_id") or tenant.get("id")
|
|
||||||
if tid and tid not in seen_ids:
|
|
||||||
seen_ids.add(tid)
|
|
||||||
all_tenants.append(tenant)
|
|
||||||
new_count += 1
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"page=%d got=%d new=%d total=%d", page, len(tenants), new_count, len(all_tenants)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Last page — fewer than PAGE_SIZE results means we've exhausted the list
|
|
||||||
if len(tenants) < PAGE_SIZE:
|
|
||||||
break
|
|
||||||
|
|
||||||
if not cycler:
|
|
||||||
time.sleep(THROTTLE_SECONDS)
|
time.sleep(THROTTLE_SECONDS)
|
||||||
|
|
||||||
payload = json.dumps({"tenants": all_tenants, "count": len(all_tenants)}).encode()
|
payload = json.dumps({"tenants": all_tenants, "count": len(all_tenants)}).encode()
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
Proxies are configured via the PROXY_URLS environment variable (comma-separated).
|
Proxies are configured via the PROXY_URLS environment variable (comma-separated).
|
||||||
When unset, all functions return None/no-op — extractors fall back to direct requests.
|
When unset, all functions return None/no-op — extractors fall back to direct requests.
|
||||||
|
|
||||||
Two routing modes:
|
|
||||||
round-robin — distribute requests evenly across proxies (default)
|
|
||||||
sticky — same key always maps to same proxy (for session-tracked sites)
|
|
||||||
|
|
||||||
Tiered proxy with circuit breaker:
|
Tiered proxy with circuit breaker:
|
||||||
Primary tier (PROXY_URLS) is used by default — typically cheap datacenter proxies.
|
Primary tier (PROXY_URLS) is used by default — typically cheap datacenter proxies.
|
||||||
Fallback tier (PROXY_URLS_FALLBACK) activates once consecutive failures >= threshold.
|
Fallback tier (PROXY_URLS_FALLBACK) activates once consecutive failures >= threshold.
|
||||||
@@ -141,17 +137,3 @@ def make_tiered_cycler(
|
|||||||
"is_fallback_active": is_fallback_active,
|
"is_fallback_active": is_fallback_active,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def make_sticky_selector(proxy_urls: list[str]):
|
|
||||||
"""Consistent-hash proxy selector — same key always maps to same proxy.
|
|
||||||
|
|
||||||
Use when the target site tracks sessions by IP (e.g. Cloudflare).
|
|
||||||
Returns a callable: select_proxy(key: str) -> str | None
|
|
||||||
"""
|
|
||||||
if not proxy_urls:
|
|
||||||
return lambda key: None
|
|
||||||
|
|
||||||
def select_proxy(key: str) -> str:
|
|
||||||
return proxy_urls[hash(key) % len(proxy_urls)]
|
|
||||||
|
|
||||||
return select_proxy
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ if you add multiple data sources, extract them to a shared workspace package.
|
|||||||
|
|
||||||
import gzip
|
import gzip
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import json
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -117,6 +119,50 @@ def content_hash(data: bytes, prefix_bytes: int = 8) -> str:
|
|||||||
return hashlib.sha256(data).hexdigest()[:prefix_bytes]
|
return hashlib.sha256(data).hexdigest()[:prefix_bytes]
|
||||||
|
|
||||||
|
|
||||||
|
def load_partial_results(partial_path: Path, id_key: str) -> tuple[list[dict], set[str]]:
|
||||||
|
"""Load already-completed records from a partial JSONL file (crash recovery).
|
||||||
|
|
||||||
|
Returns (records, seen_ids). If the file doesn't exist, returns ([], set()).
|
||||||
|
Gracefully handles a truncated last line from a mid-write crash.
|
||||||
|
"""
|
||||||
|
records: list[dict] = []
|
||||||
|
seen_ids: set[str] = set()
|
||||||
|
if not partial_path.exists():
|
||||||
|
return records, seen_ids
|
||||||
|
with open(partial_path) as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
record = json.loads(line)
|
||||||
|
records.append(record)
|
||||||
|
rid = record.get(id_key)
|
||||||
|
if rid:
|
||||||
|
seen_ids.add(rid)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
break # truncated last line from crash — skip it
|
||||||
|
return records, seen_ids
|
||||||
|
|
||||||
|
|
||||||
|
def flush_partial_batch(
|
||||||
|
partial_file,
|
||||||
|
lock: threading.Lock,
|
||||||
|
batch: list[dict],
|
||||||
|
) -> None:
|
||||||
|
"""Thread-safe batch write of JSON records to the partial JSONL file.
|
||||||
|
|
||||||
|
Writes all records in one lock acquisition with a single flush.
|
||||||
|
Call with batches of ~50 records for good I/O throughput vs crash safety tradeoff.
|
||||||
|
On crash, at most one batch worth of records is lost.
|
||||||
|
"""
|
||||||
|
assert batch, "batch must not be empty"
|
||||||
|
with lock:
|
||||||
|
for record in batch:
|
||||||
|
partial_file.write(json.dumps(record, separators=(",", ":")) + "\n")
|
||||||
|
partial_file.flush()
|
||||||
|
|
||||||
|
|
||||||
def write_gzip_atomic(path: Path, data: bytes) -> int:
|
def write_gzip_atomic(path: Path, data: bytes) -> int:
|
||||||
"""Gzip compress data and write to path atomically via .tmp sibling.
|
"""Gzip compress data and write to path atomically via .tmp sibling.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user