Merge pull request #70 from jcreek/65-add-a-proper-error-logging-solution

65 add a proper error logging solution
This commit is contained in:
Olly
2024-11-05 22:29:30 +00:00
committed by GitHub
13 changed files with 350 additions and 251 deletions
+3 -1
View File
@@ -3,4 +3,6 @@ PUBLIC_SUPABASE_ANON_KEY=""
SUPABASE_SERVICE_ROLE_KEY=""
STRIPE_SECRET_KEY=""
STRIPE_ENDPOINT_SECRET=""
VITE_PRODUCT_ID_EXAMPLEPRODUCT=""
VITE_PRODUCT_ID_EXAMPLEPRODUCT=""
VITE_AXIOM_DATASET=""
VITE_AXIOM_TOKEN=""
+3 -1
View File
@@ -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"
+207
View File
@@ -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:
-126
View File
@@ -1,126 +0,0 @@
<script lang="ts">
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);
}
</script>
<div class="card shrink-0 w-full max-w-sm shadow-2xl bg-base-100">
<form class="card-body">
<div class="form-control">
<label class="input input-bordered flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4 opacity-70"
><path
d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z"
/><path
d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z"
/>
</svg>
<input type="text" class="grow" placeholder="Email" required bind:value={email} />
</label>
</div>
<div class="form-control">
<label class="input input-bordered flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4 opacity-70"
><path
fill-rule="evenodd"
d="M14 6a4 4 0 0 1-4.899 3.899l-1.955 1.955a.5.5 0 0 1-.353.146H5v1.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2.293a.5.5 0 0 1 .146-.353l3.955-3.955A4 4 0 1 1 14 6Zm-4-2a.75.75 0 0 0 0 1.5.5.5 0 0 1 .5.5.75.75 0 0 0 1.5 0 2 2 0 0 0-2-2Z"
clip-rule="evenodd"
/>
</svg>
<input type="password" class="grow" placeholder="Password" required bind:value={password} />
</label>
</div>
<div class="form-control">
<button id="forgot-password" class="label-text-alt link link-hover" on:click={forgotPassword}>
Forgot your password?
</button>
</div>
<div class="form-control mt-2">
<button class="btn btn-primary" on:click={signInWithEmail}>Sign In</button>
</div>
</form>
</div>
-68
View File
@@ -1,68 +0,0 @@
<script lang="ts">
let email = '';
let password = '';
// Access the supabase client from the layout data
export let supabase: any;
async function signUpNewUser() {
try {
const { data, error } = await supabase.auth.signUp({
email: email,
password: password,
options: {
// Redirect URL after successful sign-up
redirectTo: '/account'
}
});
if (error) {
throw error;
}
// Handle success (optional)
} catch (error: any) {
console.error('Sign up error:', error.message);
// Handle error
}
}
</script>
<form class="card-body">
<div class="form-control">
<label class="input input-bordered flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4 opacity-70"
><path
d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z"
/><path
d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z"
/>
</svg>
<input type="text" class="grow" placeholder="Email" required bind:value={email} />
</label>
</div>
<div class="form-control">
<label class="input input-bordered flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4 opacity-70"
><path
fill-rule="evenodd"
d="M14 6a4 4 0 0 1-4.899 3.899l-1.955 1.955a.5.5 0 0 1-.353.146H5v1.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2.293a.5.5 0 0 1 .146-.353l3.955-3.955A4 4 0 1 1 14 6Zm-4-2a.75.75 0 0 0 0 1.5.5.5 0 0 1 .5.5.75.75 0 0 0 1.5 0 2 2 0 0 0-2-2Z"
clip-rule="evenodd"
/>
</svg>
<input type="password" class="grow" placeholder="Password" required bind:value={password} />
</label>
</div>
<div class="form-control mt-6">
<button class="btn btn-primary" on:click={signUpNewUser}>Sign Up</button>
</div>
</form>
+40
View File
@@ -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;
+53 -37
View File
@@ -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);
}
};
@@ -489,7 +505,7 @@ const getUserSubscriptions = async (userId: string) => {
.eq('user_id', userId);
if (error) {
console.error('Error retrieving subscriptions:', error.message);
logger.error('Error retrieving subscriptions:', error.message);
throw new Error('Failed to fetch subscriptions');
}
@@ -520,7 +536,7 @@ const getUserSubscriptions = async (userId: string) => {
return subscriptionDetails;
} catch (error) {
console.error('An error occurred while retrieving subscription details:', error);
logger.error('An error occurred while retrieving subscription details:', error);
throw new Error('Could not retrieve subscription details');
}
};
+8 -4
View File
@@ -1,5 +1,6 @@
import { fail, redirect } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
import logger from '$lib/utils/logger/logger';
import { getUserSubscriptions, getUserTransactions } from '$lib/utils/supabase/admin';
export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession } }) => {
@@ -39,13 +40,16 @@ export const actions: Actions = {
})
.eq('id', session?.user.id);
console.error('Failed to update user:', error);
if (error) {
logger.error('Failed to update user name', {
userId: session?.user.id,
error
});
}
if (error) {
return fail(500, {
name,
message: 'Failed to update profile',
error: error.message
name
});
}
+2 -1
View File
@@ -2,6 +2,7 @@ import { type RequestHandler, redirect } from '@sveltejs/kit';
import { stripe as stripeClient } from '$lib/utils/stripe';
import { getStripeCustomerId } from '$lib/utils/supabase/admin';
import type Stripe from 'stripe';
import logger from '$lib/utils/logger/logger';
// Create a checkout session
export const POST: RequestHandler = async ({ request, cookies, locals: { safeGetSession } }) => {
@@ -34,7 +35,7 @@ export const POST: RequestHandler = async ({ request, cookies, locals: { safeGet
try {
stripeCustomerId = await getStripeCustomerId(userEmail!, userId);
} catch (error) {
console.error('Error fetching Stripe customer ID:', error);
logger.error('Error fetching Stripe customer ID:', error);
}
const stripeCheckoutSessionObject = {
+2 -1
View File
@@ -2,6 +2,7 @@ import { type RequestHandler, redirect } from '@sveltejs/kit';
import { stripe as stripeClient } from '$lib/utils/stripe';
import { getStripeCustomerId } from '$lib/utils/supabase/admin';
import type Stripe from 'stripe';
import logger from '$lib/utils/logger/logger';
// Create a subscription checkout session
export const POST: RequestHandler = async ({ request, cookies, locals: { safeGetSession } }) => {
@@ -29,7 +30,7 @@ export const POST: RequestHandler = async ({ request, cookies, locals: { safeGet
try {
stripeCustomerId = await getStripeCustomerId(userEmail!, userId);
} catch (error) {
console.error('Error fetching Stripe customer ID:', error);
logger.error('Error fetching Stripe customer ID:', error);
}
const stripeCheckoutSessionObject = {
+3 -2
View File
@@ -10,6 +10,7 @@ import {
recordProductPurchase
} from '$lib/utils/supabase/admin';
import { stripe as stripeClient } from '$lib/utils/stripe';
import logger from '$lib/utils/logger/logger';
export const POST: RequestHandler = async ({ request }) => {
const body = await request.text();
@@ -26,7 +27,7 @@ export const POST: RequestHandler = async ({ request }) => {
try {
event = stripeClient.webhooks.constructEvent(body, signature, STRIPE_ENDPOINT_SECRET);
} catch (err) {
console.error(`⚠️ Webhook signature verification failed.`, err.message);
logger.error(`⚠️ Webhook signature verification failed.`, err.message);
return {
status: 400,
body: {}
@@ -104,7 +105,7 @@ export const POST: RequestHandler = async ({ request }) => {
break;
}
default:
console.log(`Unhandled event type ${event.type}`);
logger.info(`Unhandled event type ${event.type}`);
}
} catch (err) {
const message = err instanceof Error ? err.message : 'An unknown error has occurred';
+3 -2
View File
@@ -1,13 +1,14 @@
import { redirect } from '@sveltejs/kit';
import Stripe from 'stripe';
import { getProductById, upsertCustomerToSupabase } from '$lib/utils/supabase/admin';
import logger from '$lib/utils/logger/logger';
export const load = async ({ fetch, locals: { safeGetSession } }) => {
const { session } = await safeGetSession();
const response = await fetch('/api/checkout/status');
if (!response.ok) {
console.error('Failed to fetch checkout status');
logger.error('Failed to fetch checkout status');
return;
}
@@ -30,7 +31,7 @@ export const load = async ({ fetch, locals: { safeGetSession } }) => {
const stripeCustomerId = checkoutSession.customer as string;
await upsertCustomerToSupabase(session.user.id, stripeCustomerId);
} else {
console.error('User ID is undefined');
logger.error('User ID is undefined');
}
return {
+26 -8
View File
@@ -1,19 +1,37 @@
import { json } from '@sveltejs/kit';
import { getActiveProductsWithPrices } from '$lib/utils/supabase/admin';
import logger from '$lib/utils/logger/logger';
export const GET = async ({ url }) => {
const limitParam: string | null = url.searchParams.get('limit');
const offsetParam: string | null = url.searchParams.get('offset');
try {
const limitParam = url.searchParams.get('limit');
const offsetParam = url.searchParams.get('offset');
const limit = limitParam ? parseInt(limitParam, 10) : 10;
const offset = offsetParam ? parseInt(offsetParam, 10) : 0;
const limit = limitParam ? Number.parseInt(limitParam, 10) : 10;
const offset = offsetParam ? Number.parseInt(offsetParam, 10) : 0;
const { products, count } = await getActiveProductsWithPrices(limit, offset);
return json({ products, count });
} catch (error) {
console.error(error);
return json({ error: error.message }, { status: 500 });
} catch (error: unknown) {
if (error instanceof Error) {
logger.error('Failed to fetch products', {
error: error.message,
stack: error.stack,
params: {
limit: limitParam,
offset: offsetParam
}
});
return json({ error: error.message }, { status: 500 });
} else {
logger.error('Failed to fetch products', {
error,
params: {
limit: limitParam,
offset: offsetParam
}
});
return json({ error }, { status: 500 });
}
}
};