diff --git a/.env.example b/.env.example index 6d0104e..c7e4a6b 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,6 @@ PUBLIC_SUPABASE_ANON_KEY="" SUPABASE_SERVICE_ROLE_KEY="" STRIPE_SECRET_KEY="" STRIPE_ENDPOINT_SECRET="" -VITE_PRODUCT_ID_EXAMPLEPRODUCT="" \ No newline at end of file +VITE_PRODUCT_ID_EXAMPLEPRODUCT="" +VITE_AXIOM_DATASET="" +VITE_AXIOM_TOKEN="" \ No newline at end of file diff --git a/package.json b/package.json index 6dd387c..d706928 100644 --- a/package.json +++ b/package.json @@ -47,10 +47,12 @@ }, "type": "module", "dependencies": { + "@axiomhq/winston": "^1.2.0", "@supabase/ssr": "^0.4.0", "@supabase/supabase-js": "^2.44.2", "stripe": "^15.4.0", - "ts-debounce": "^4.0.0" + "ts-debounce": "^4.0.0", + "winston": "^3.15.0" }, "engines": { "node": ">=18.13.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 437f249..a4ac133 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@axiomhq/winston': + specifier: ^1.2.0 + version: 1.2.0 '@supabase/ssr': specifier: ^0.4.0 version: 0.4.1(@supabase/supabase-js@2.45.4) @@ -20,6 +23,9 @@ importers: ts-debounce: specifier: ^4.0.0 version: 4.0.0 + winston: + specifier: ^3.15.0 + version: 3.15.0 devDependencies: '@sveltejs/adapter-auto': specifier: ^3.0.0 @@ -134,6 +140,14 @@ packages: peerDependencies: ajv: '>=8' + '@axiomhq/js@1.2.0': + resolution: {integrity: sha512-ALvNo8YeDwE7EHWr9TGijdfo/gbmgTB5XHHBcIcu6Ra+A9+2ltBO5UvTzvSSGPaxn4neEyh+8ESzEBJVEjNAnA==} + engines: {node: '>=16'} + + '@axiomhq/winston@1.2.0': + resolution: {integrity: sha512-PE3dy49MZj9lrgTLcADu9iw8xeLxIA6Vcdc35JwgDDKDpvxy68+rz7LKWvhtnNi5T5Xpc5QMVjO3OGGDD35HrQ==} + engines: {node: '>=16'} + '@babel/code-frame@7.24.7': resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} @@ -709,6 +723,13 @@ packages: '@canvas/image-data@1.0.0': resolution: {integrity: sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==} + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + + '@dabh/diagnostics@2.0.3': + resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -1187,6 +1208,9 @@ packages: '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -1270,6 +1294,10 @@ packages: '@vite-pwa/assets-generator': optional: true + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1436,6 +1464,9 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -1505,6 +1536,9 @@ packages: color-string@1.9.1: resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + color@4.2.3: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} @@ -1512,6 +1546,9 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + colorspace@1.1.4: + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} @@ -1683,6 +1720,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -1801,6 +1841,14 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -1838,10 +1886,16 @@ packages: picomatch: optional: true + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + fetch-retry@6.0.0: + resolution: {integrity: sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag==} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1864,6 +1918,9 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -2229,6 +2286,9 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -2273,6 +2333,10 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + logform@2.6.1: + resolution: {integrity: sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==} + engines: {node: '>= 12.0.0'} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2420,6 +2484,9 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2606,6 +2673,10 @@ packages: resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} engines: {node: ^14.13.1 || >=16.0.0} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} @@ -2648,6 +2719,10 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readable-stream@4.5.2: + resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2735,6 +2810,10 @@ packages: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + sander@0.5.1: resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} @@ -2827,6 +2906,9 @@ packages: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} deprecated: Please use @jridgewell/sourcemap-codec instead + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + streamx@2.20.1: resolution: {integrity: sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==} @@ -3010,6 +3092,9 @@ packages: text-decoder@1.2.0: resolution: {integrity: sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==} + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -3048,6 +3133,10 @@ packages: tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -3148,6 +3237,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + vite-plugin-pwa@0.20.5: resolution: {integrity: sha512-aweuI/6G6n4C5Inn0vwHumElU/UEpNuO+9iZzwPZGTCH87TeZ6YFMrEY6ZUBQdIHHlhTsbMDryFARcSuOdsz9Q==} engines: {node: '>=16.0.0'} @@ -3233,6 +3326,14 @@ packages: engines: {node: '>= 8'} hasBin: true + winston-transport@4.8.0: + resolution: {integrity: sha512-qxSTKswC6llEMZKgCQdaWgDuMJQnhuvF5f2Nk3SNXc4byfQ+voo2mX1Px9dkNOuR8p0KAjfPG29PuYUSIb+vSA==} + engines: {node: '>= 12.0.0'} + + winston@3.15.0: + resolution: {integrity: sha512-RhruH2Cj0bV0WgNL+lOfoUBI4DVfdUNjVnJGVovWZmrcKtrFTTRzgXYK2O9cymSGjrERCtaAeHwMNnUWXlwZow==} + engines: {node: '>= 12.0.0'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -3351,6 +3452,16 @@ snapshots: jsonpointer: 5.0.1 leven: 3.1.0 + '@axiomhq/js@1.2.0': + dependencies: + fetch-retry: 6.0.0 + uuid: 8.3.2 + + '@axiomhq/winston@1.2.0': + dependencies: + '@axiomhq/js': 1.2.0 + winston-transport: 4.8.0 + '@babel/code-frame@7.24.7': dependencies: '@babel/highlight': 7.24.7 @@ -4114,6 +4225,14 @@ snapshots: '@canvas/image-data@1.0.0': {} + '@colors/colors@1.6.0': {} + + '@dabh/diagnostics@2.0.3': + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -4562,6 +4681,8 @@ snapshots: '@types/resolve@1.20.2': {} + '@types/triple-beam@1.3.5': {} + '@types/trusted-types@2.0.7': {} '@types/ws@8.5.12': @@ -4668,6 +4789,10 @@ snapshots: optionalDependencies: '@vite-pwa/assets-generator': 0.2.6 + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + acorn-jsx@5.3.2(acorn@8.12.1): dependencies: acorn: 8.12.1 @@ -4857,6 +4982,11 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + builtin-modules@3.3.0: {} cac@6.7.14: {} @@ -4931,6 +5061,11 @@ snapshots: color-name: 1.1.4 simple-swizzle: 0.2.2 + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + color@4.2.3: dependencies: color-convert: 2.0.1 @@ -4938,6 +5073,11 @@ snapshots: colorette@2.0.20: {} + colorspace@1.1.4: + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + commander@12.1.0: {} commander@2.20.3: {} @@ -5086,6 +5226,8 @@ snapshots: emoji-regex@9.2.2: {} + enabled@2.0.0: {} + end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -5303,6 +5445,10 @@ snapshots: esutils@2.0.3: {} + event-target-shim@5.0.1: {} + + events@3.3.0: {} + expand-template@2.0.3: {} fast-deep-equal@3.1.3: {} @@ -5333,11 +5479,15 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fecha@4.2.3: {} + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 + fetch-retry@6.0.0: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -5363,6 +5513,8 @@ snapshots: flatted@3.3.1: {} + fn.name@1.1.0: {} + for-each@0.3.3: dependencies: is-callable: 1.2.7 @@ -5706,6 +5858,8 @@ snapshots: kolorist@1.8.0: {} + kuler@2.0.0: {} + leven@3.1.0: {} levn@0.4.1: @@ -5737,6 +5891,15 @@ snapshots: lodash@4.17.21: {} + logform@2.6.1: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.5.0 + triple-beam: 1.4.1 + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -5854,6 +6017,10 @@ snapshots: dependencies: wrappy: 1.0.2 + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -6012,6 +6179,8 @@ snapshots: pretty-bytes@6.1.1: {} + process@0.11.10: {} + pump@3.0.2: dependencies: end-of-stream: 1.4.4 @@ -6062,6 +6231,14 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readable-stream@4.5.2: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -6171,6 +6348,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.1.4 + safe-stable-stringify@2.5.0: {} + sander@0.5.1: dependencies: es6-promise: 3.3.1 @@ -6280,6 +6459,8 @@ snapshots: sourcemap-codec@1.4.8: {} + stack-trace@0.0.10: {} + streamx@2.20.1: dependencies: fast-fifo: 1.3.2 @@ -6546,6 +6727,8 @@ snapshots: dependencies: b4a: 1.6.6 + text-hex@1.0.0: {} + text-table@0.2.0: {} thenify-all@1.6.0: @@ -6582,6 +6765,8 @@ snapshots: dependencies: punycode: 2.3.1 + triple-beam@1.4.1: {} + ts-api-utils@1.3.0(typescript@5.6.2): dependencies: typescript: 5.6.2 @@ -6684,6 +6869,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@8.3.2: {} + vite-plugin-pwa@0.20.5(@vite-pwa/assets-generator@0.2.6)(vite@5.4.6(@types/node@22.5.5)(terser@5.33.0))(workbox-build@7.1.1)(workbox-window@7.1.0): dependencies: debug: 4.3.7 @@ -6758,6 +6945,26 @@ snapshots: dependencies: isexe: 2.0.0 + winston-transport@4.8.0: + dependencies: + logform: 2.6.1 + readable-stream: 4.5.2 + triple-beam: 1.4.1 + + winston@3.15.0: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.3 + async: 3.2.6 + is-stream: 2.0.1 + logform: 2.6.1 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.5.0 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.8.0 + word-wrap@1.2.5: {} workbox-background-sync@7.1.0: diff --git a/src/lib/components/CookieConsent.svelte b/src/lib/components/CookieConsent.svelte index 09984b7..760cc72 100644 --- a/src/lib/components/CookieConsent.svelte +++ b/src/lib/components/CookieConsent.svelte @@ -21,7 +21,9 @@

We use cookies to ensure you get the best experience on our website. By continuing, you agree - to our use of cookies. + to our use of cookies. For details, please see our Privacy Policy.

- import { createEventDispatcher } from 'svelte'; - import { general } from '$lib/stores/generalStore.js'; - - const dispatch = createEventDispatcher(); - - function emitSignedInEvent() { - dispatch('signedIn', {}); - } - - let email = ''; - let password = ''; - - // Access the supabase client from the layout data - export let supabase: any; - - async function signInWithEmail() { - // TODO use the data and error from the response - const { data, error } = await supabase.auth - .signInWithPassword({ - email: email, - password: password - }) - .then(() => { - emitSignedInEvent(); - }); - } - - async function forgotPassword() { - if (email === '') { - general.update((value) => { - return { - ...value, - hideToast: false, - toastMessage: 'Please enter your email address', - toastType: 'error' - }; - }); - - setTimeout(() => { - general.update((value) => { - return { ...value, hideToast: true, toastMessage: '', toastType: 'success' }; - }); - }, 5000); - - return; - } - - const { data, error } = await supabase.auth.resetPasswordForEmail(email, { - redirectTo: 'https://example.com/auth/update-password' - }); - - if (error) { - console.error('Error sending password reset email:', error.message); - general.update((value) => { - return { - ...value, - hideToast: false, - toastMessage: 'Error sending password reset email. Please try again.', - toastType: 'error' - }; - }); - } else { - console.log('Password reset email sent:', data); - general.update((value) => { - return { - ...value, - hideToast: false, - toastMessage: 'Password reset email sent. Please check your inbox.', - toastType: 'success' - }; - }); - } - - setTimeout(() => { - general.update((value) => { - return { ...value, hideToast: true, toastMessage: '', toastType: 'success' }; - }); - }, 5000); - } - - -
-
-
- -
-
- -
-
- -
-
- -
-
-
diff --git a/src/lib/components/SignUp.svelte b/src/lib/components/SignUp.svelte deleted file mode 100644 index dbb92f1..0000000 --- a/src/lib/components/SignUp.svelte +++ /dev/null @@ -1,68 +0,0 @@ - - -
-
- -
-
- -
-
- -
-
diff --git a/src/lib/utils/logger/logger.ts b/src/lib/utils/logger/logger.ts new file mode 100644 index 0000000..b56c756 --- /dev/null +++ b/src/lib/utils/logger/logger.ts @@ -0,0 +1,40 @@ +import { createLogger, transports, format } from 'winston'; +import { WinstonTransport as AxiomTransport } from '@axiomhq/winston'; +import { VITE_AXIOM_DATASET, VITE_AXIOM_TOKEN } from '$env/static/private'; + +const axiomTransport = new AxiomTransport({ + dataset: VITE_AXIOM_DATASET, + token: VITE_AXIOM_TOKEN +}); + +const logger = createLogger({ + level: 'info', + defaultMeta: { service: 'example-site' }, // Change this so you can identify your website if you have multiple logging into Axiom + format: format.combine( + format.errors({ stack: true }), // Captures error stack traces + format.timestamp(), + format.json() // Formats logs as JSON for structured logging + ), + transports: [ + new transports.Console({ + format: format.combine( + format.colorize(), + format.printf(({ timestamp, level, message, stack }) => { + // Stack traces will be included in error messages automatically + return `${timestamp} [${level.toUpperCase()}]: ${message}${stack ? `\n${stack}` : ''}`; + }) + ) + }), + axiomTransport + ], + exceptionHandlers: [axiomTransport], // Logs uncaught exceptions + rejectionHandlers: [axiomTransport] // Logs unhandled promise rejections +}); + +// Uncomment this for testing your initial logging setup +// logger.log({ +// level: 'info', +// message: 'Logger successfully setup' +// }); + +export default logger; diff --git a/src/lib/utils/supabase/admin.ts b/src/lib/utils/supabase/admin.ts index 4052695..b7eb4f8 100644 --- a/src/lib/utils/supabase/admin.ts +++ b/src/lib/utils/supabase/admin.ts @@ -4,6 +4,7 @@ import type { Database, Tables, TablesInsert } from '$lib/types/supabase'; import { PUBLIC_SUPABASE_URL } from '$env/static/public'; import { SUPABASE_SERVICE_ROLE_KEY } from '$env/static/private'; import { stripe as stripeClient } from '$lib/utils/stripe'; +import logger from '$lib/utils/logger/logger'; const toDateTime = (secs: number) => { const t = new Date(+0); // Unix epoch start. @@ -39,7 +40,7 @@ const upsertProductRecord = async (product: stripe.Product) => { const { error: upsertError } = await supabaseAdmin.from('products').upsert([productData]); if (upsertError) throw new Error(`Product insert/update failed: ${upsertError.message}`); - console.log(`Product inserted/updated: ${product.id}`); + logger.info(`Product inserted/updated: ${product.id}`); }; const upsertPriceRecord = async (price: stripe.Price, retryCount = 0, maxRetries = 3) => { @@ -61,7 +62,7 @@ const upsertPriceRecord = async (price: stripe.Price, retryCount = 0, maxRetries if (upsertError?.message.includes('foreign key constraint')) { if (retryCount < maxRetries) { - console.log(`Retry attempt ${retryCount + 1} for price ID: ${price.id}`); + logger.info(`Retry attempt ${retryCount + 1} for price ID: ${price.id}`); await new Promise((resolve) => setTimeout(resolve, 2000)); await upsertPriceRecord(price, retryCount + 1, maxRetries); } else { @@ -72,7 +73,7 @@ const upsertPriceRecord = async (price: stripe.Price, retryCount = 0, maxRetries } else if (upsertError) { throw new Error(`Price insert/update failed: ${upsertError.message}`); } else { - console.log(`Price inserted/updated: ${price.id}`); + logger.info(`Price inserted/updated: ${price.id}`); } }; @@ -82,13 +83,13 @@ const deleteProductRecord = async (product: stripe.Product) => { .delete() .eq('id', product.id); if (deletionError) throw new Error(`Product deletion failed: ${deletionError.message}`); - console.log(`Product deleted: ${product.id}`); + logger.info(`Product deleted: ${product.id}`); }; const deletePriceRecord = async (price: stripe.Price) => { const { error: deletionError } = await supabaseAdmin.from('prices').delete().eq('id', price.id); if (deletionError) throw new Error(`Price deletion failed: ${deletionError.message}`); - console.log(`Price deleted: ${price.id}`); + logger.info(`Price deleted: ${price.id}`); }; const upsertCustomerToSupabase = async (uuid: string, customerId: string) => { @@ -254,11 +255,18 @@ const manageSubscriptionStatusChange = async ( trial_end: subscription.trial_end ? toDateTime(subscription.trial_end).toISOString() : null }; - const { error: upsertError } = await supabaseAdmin - .from('subscriptions') - .upsert([subscriptionData]); - if (upsertError) throw new Error(`Subscription insert/update failed: ${upsertError.message}`); - console.log(`Inserted/updated subscription [${subscription.id}] for user [${uuid}]`); + try { + const { error: upsertError } = await supabaseAdmin + .from('subscriptions') + .upsert([subscriptionData]); + if (upsertError) { + throw new Error(`Subscription insert/update failed: ${upsertError.message}`); + } + } catch (error) { + logger.error(error); + } + + logger.info(`Inserted/updated subscription [${subscription.id}] for user [${uuid}]`); // // For a new subscription copy the billing details to the customer object. // // NOTE: This is a costly operation and should happen at the very end. @@ -271,7 +279,7 @@ const manageSubscriptionStatusChange = async ( }; const recordProductPurchase = async (userId: string, productId: string, priceId: string) => { - console.log( + logger.info( `Recording product purchase for user [${userId}] and product [${productId}] with price [${priceId}]` ); @@ -284,40 +292,48 @@ const recordProductPurchase = async (userId: string, productId: string, priceId: throw new Error(`Failed to record product purchase: ${error.message}`); } } catch (error) { - console.error(error); + logger.error(error); } - console.log(`Product purchased recorded for user [${userId}] and product [${productId}]`); + logger.info(`Product purchased recorded for user [${userId}] and product [${productId}]`); }; const hasProductAccess = async (userId: string, productId: string) => { // Check if user has purchased the product - const { data: purchases, error: purchaseError } = await supabaseAdmin - .from('purchases') - .select('*') - .eq('user_id', userId) - .eq('product_id', productId); + try { + const { data: purchases, error: purchaseError } = await supabaseAdmin + .from('purchases') + .select('*') + .eq('user_id', userId) + .eq('product_id', productId); - if (purchaseError) throw new Error(purchaseError.message); + if (purchaseError) throw new Error(purchaseError.message); - if (purchases && purchases.length > 0) { - // User has purchased the product - return true; + if (purchases && purchases.length > 0) { + // User has purchased the product + return true; + } + } catch (error) { + logger.error(error); } // Check if user has an active subscription for the product - const { data: subscriptions, error: subError } = await supabaseAdmin - .from('subscriptions') - .select('*') - .eq('user_id', userId) - .eq('product_id', productId) - .in('status', ['active', 'trialing']); + try { + const { data: subscriptions, error: subError } = await supabaseAdmin + .from('subscriptions') + .select('*') + .eq('user_id', userId) + .eq('product_id', productId) + .in('status', ['active', 'trialing']); - if (subError) throw new Error(subError.message); + if (subError) throw new Error(subError.message); - if (subscriptions && subscriptions.length > 0) { - // User has an active subscription - return true; + if (subscriptions && subscriptions.length > 0) { + // User has an active subscription + return true; + } + } catch (error) { + logger.error(error); } return false; @@ -381,7 +397,7 @@ const updateUserCredits = async (userId: string, creditsChange: number, descript await logCreditTransaction(userId, creditsChange, description); - console.log( + logger.info( `${creditsChange > 0 ? 'Added' : 'Deducted'} ${Math.abs(creditsChange)} credits to user [${userId}]` ); }; @@ -390,7 +406,7 @@ const addCredits = async (userId: string, creditsToAdd: number, description: str try { await updateUserCredits(userId, creditsToAdd, description); } catch (error) { - console.error(error); + logger.error(error); } }; @@ -398,7 +414,7 @@ const deductCredits = async (userId: string, creditsToDeduct: number, descriptio try { await updateUserCredits(userId, -creditsToDeduct, description); } catch (error) { - console.error(error); + logger.error(error); } }; @@ -480,6 +496,94 @@ const getProductById = async (productId: string) => { return product as Product; }; +const getUserSubscriptions = async (userId: string) => { + try { + // Step 1: Retrieve subscription data + const { data: subscriptions, error } = await supabaseAdmin + .from('subscriptions') + .select('*') + .eq('user_id', userId); + + if (error) { + logger.error('Error retrieving subscriptions:', error.message); + throw new Error('Failed to fetch subscriptions'); + } + + // Step 2: Map through each subscription and fetch product and price details + const subscriptionDetails = await Promise.all( + subscriptions.map(async (sub) => { + // Fetch product and price details from Stripe + const product = await stripeClient.products.retrieve(sub.product_id); + const price = await stripeClient.prices.retrieve(sub.price_id); + + const expiryDate = new Date(sub.current_period_end); + + // Format output data + return { + productName: product.name, + amount: (price.unit_amount / 100).toFixed(2), + currency: price.currency.toUpperCase(), + interval: price.recurring?.interval || 'one-time', + expiryDate: expiryDate.toLocaleDateString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + }), + status: sub.status + }; + }) + ); + + return subscriptionDetails; + } catch (error) { + logger.error('An error occurred while retrieving subscription details:', error); + throw new Error('Could not retrieve subscription details'); + } +}; + +const getUserTransactions = async (userId: string) => { + try { + // Step 1: Retrieve the customer ID associated with the user + const { data, error } = await supabaseAdmin + .from('customers') + .select('stripe_customer_id') + .eq('id', userId) + .single(); + + if (error) { + console.error('Error retrieving user data:', error.message); + throw new Error('Failed to fetch user data'); + } + + const customerId = data?.stripe_customer_id; + if (!customerId) { + throw new Error('No Stripe customer ID found for this user'); + } + + // Step 2: Fetch charge transactions from Stripe + const charges = await stripeClient.charges.list({ customer: customerId }); + + // Step 3: Format transaction data + const transactions = charges.data.map((charge) => ({ + amount: (charge.amount / 100).toFixed(2), + currency: charge.currency.toUpperCase(), + description: charge.description ?? 'No description provided', + status: charge.status, + created: new Date(charge.created * 1000).toLocaleDateString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + }), + receipt_url: charge.receipt_url + })); + + return transactions; + } catch (error) { + console.error('An error occurred while retrieving transactions:', error); + throw new Error('Could not retrieve transactions'); + } +}; + export { upsertProductRecord, upsertPriceRecord, @@ -495,5 +599,7 @@ export { getActiveProductsWithPrices, getProductById, upsertCustomerToSupabase, - getStripeCustomerId + getStripeCustomerId, + getUserSubscriptions, + getUserTransactions }; diff --git a/src/routes/(policies)/cookies/+page.svelte b/src/routes/(policies)/cookies/+page.svelte new file mode 100644 index 0000000..511b4e0 --- /dev/null +++ b/src/routes/(policies)/cookies/+page.svelte @@ -0,0 +1,85 @@ +
+

Cookie Policy

+

Effective Date: [Insert Date]

+ +
+

1. Introduction

+

+ This Cookie Policy explains how [Your Company/Website Name] ("we", "us", or "our") uses + cookies and similar technologies to collect and store information when you visit our website. +

+
+ +
+

2. What Are Cookies?

+

+ Cookies are small text files stored on your device (computer, tablet, smartphone) by your web + browser when you visit a website. They help us remember your preferences and improve your + experience on our website. +

+
+ +
+

3. Types of Cookies We Use

+
    +
  • + Essential Cookies: These cookies are necessary for the + website to function properly and cannot be disabled in our systems. They include login + authentication and session management cookies. +
  • +
  • + Performance Cookies: These cookies help us understand how + visitors interact with our website by collecting information on usage patterns, such as + pages visited and links clicked. +
  • +
  • + Functional Cookies: These cookies allow us to remember + choices you make (such as your language preference) to provide a more personalised + experience. +
  • +
  • + Targeting/Advertising Cookies: These cookies are used to + deliver relevant advertisements and track the effectiveness of our marketing campaigns. +
  • +
+
+ +
+

4. How We Use Cookies

+

+ We use cookies to improve the functionality and performance of our website, analyse visitor + usage, and provide personalised content and advertisements. This helps us improve our website + and better understand our audience. +

+
+ +
+

5. Managing Your Cookie Preferences

+

+ You have the right to accept or reject cookies. You can manage your cookie preferences by + adjusting your browser settings. However, disabling certain cookies may affect your experience + on our website. For specific instructions on managing cookies, please consult your browser's + help documentation. +

+
+ +
+

6. Changes to Our Cookie Policy

+

+ We may update this Cookie Policy from time to time. Significant changes will be posted on this + page. We encourage you to review this policy periodically to stay informed about our use of + cookies. +

+
+ +
+

Contact Us

+

+ For questions about our Cookie Policy, please contact us at [Your Contact Email] + or visit our Contact Page. +

+
+
diff --git a/src/routes/(policies)/privacy/+page.svelte b/src/routes/(policies)/privacy/+page.svelte new file mode 100644 index 0000000..6901837 --- /dev/null +++ b/src/routes/(policies)/privacy/+page.svelte @@ -0,0 +1,125 @@ +
+

Privacy Policy

+

Effective Date: [Insert Date]

+ +
+

1. Introduction

+

+ This Privacy Policy explains how we at [Your Company/Website Name] collect, use, and protect + your personal information when you visit our website. We are committed to safeguarding your + data and complying with relevant data protection laws, including the General Data Protection + Regulation (GDPR). +

+
+ +
+

2. Information We Collect

+

+ We collect personal and non-personal information to provide our services and enhance your + experience. +

+
    +
  • + Personal Information: Details like your name, email + address, and payment information collected when you make a purchase or create an account. +
  • +
  • + Usage Data: Information such as IP address, browser type, + and browsing history for analytics and functionality. +
  • +
+
+ +
+

3. How We Use Your Information

+
    +
  • To process orders and manage customer support.
  • +
  • To improve website functionality and provide a better user experience.
  • +
  • To send marketing emails, if you opt in (you may unsubscribe at any time).
  • +
+
+ +
+

4. Sharing Information with Third Parties

+

+ We work with trusted third-party providers to offer secure services, such as payment + processing and authentication. +

+
+

Stripe

+

+ For payment processing, we use Stripe. Please refer to their + Privacy Policy for details. +

+
+
+

Supabase

+

+ For database and authentication, we use Supabase. See their + Privacy Policy for more information. +

+
+
+ +
+

5. Cookies and Tracking

+

+ We use cookies to provide essential site functionality, analytics, and improved user + experience. By continuing to use our site, you consent to our use of cookies. You can manage + your cookie preferences in your browser settings. For more details, please refer to our Cookie Policy. +

+
+ +
+

6. Your Data Rights

+

+ Under GDPR, you have the right to access, correct, delete, and restrict the processing of your + personal data. To make a request, please contact us at [Your Contact Email]. +

+
+ +
+

7. Data Security

+

+ We take reasonable steps to protect your information through encryption and secure access + protocols. However, no online service can be 100% secure. +

+
+ +
+

8. Policy Updates

+

+ We may update this policy periodically. Significant changes will be posted on this page, so + please review it regularly. +

+
+ +
+

Contact Us

+

+ If you have questions about this Privacy Policy, please contact us at [Your Contact Email] + or visit our Contact Page. +

+
+
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 9e50d24..7dfb5cd 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -195,8 +195,12 @@ d="M22.672 15.226l-2.432.811.841 2.515c.33 1.019-.209 2.127-1.23 2.456-1.15.325-2.148-.321-2.463-1.226l-.84-2.518-5.013 1.677.84 2.517c.391 1.203-.434 2.542-1.831 2.542-.88 0-1.601-.564-1.86-1.314l-.842-2.516-2.431.809c-1.135.328-2.145-.317-2.463-1.229-.329-1.018.211-2.127 1.231-2.456l2.432-.809-1.621-4.823-2.432.808c-1.355.384-2.558-.59-2.558-1.839 0-.817.509-1.582 1.327-1.846l2.433-.809-.842-2.515c-.33-1.02.211-2.129 1.232-2.458 1.02-.329 2.13.209 2.461 1.229l.842 2.515 5.011-1.677-.839-2.517c-.403-1.238.484-2.553 1.843-2.553.819 0 1.585.509 1.85 1.326l.841 2.517 2.431-.81c1.02-.33 2.131.211 2.461 1.229.332 1.018-.21 2.126-1.23 2.456l-2.433.809 1.622 4.823 2.433-.809c1.242-.401 2.557.484 2.557 1.838 0 .819-.51 1.583-1.328 1.847m-8.992-6.428l-5.01 1.675 1.619 4.828 5.011-1.674-1.62-4.829z" > -

Copyright © 2024 - All right reserved

+

Copyright © 2024 - All rights reserved

+
+ Privacy Policy + Cookie Policy +