mirror of
https://github.com/rybbit-io/rybbit.git
synced 2025-05-11 12:25:36 +02:00
auth (#3)
* auth * Add default user * setup auth * add auth to all endpoints * Add url logging * Add tomato.gg * Add analytics * Add correct url * fix auth * log url * change base url * replace api * wip * Test * test changes * bump * Add trusted origin * f * i almost give up * stop using middleware * Fix auth * Fix build
This commit is contained in:
parent
0871ed0ef7
commit
0dc058749d
40 changed files with 1515 additions and 128 deletions
398
client/package-lock.json
generated
398
client/package-lock.json
generated
|
@ -14,9 +14,10 @@
|
||||||
"@phosphor-icons/react": "^2.1.7",
|
"@phosphor-icons/react": "^2.1.7",
|
||||||
"@radix-ui/react-dialog": "^1.1.5",
|
"@radix-ui/react-dialog": "^1.1.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.5",
|
"@radix-ui/react-dropdown-menu": "^2.1.5",
|
||||||
|
"@radix-ui/react-label": "^2.1.2",
|
||||||
"@radix-ui/react-popover": "^1.1.5",
|
"@radix-ui/react-popover": "^1.1.5",
|
||||||
"@radix-ui/react-select": "^2.1.5",
|
"@radix-ui/react-select": "^2.1.5",
|
||||||
"@radix-ui/react-slot": "^1.1.1",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"@tanstack/react-query": "^5.64.2",
|
"@tanstack/react-query": "^5.64.2",
|
||||||
"@tanstack/react-query-devtools": "^5.64.2",
|
"@tanstack/react-query-devtools": "^5.64.2",
|
||||||
|
@ -25,6 +26,7 @@
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/pg": "^8.11.11",
|
"@types/pg": "^8.11.11",
|
||||||
"@uidotdev/usehooks": "^2.4.1",
|
"@uidotdev/usehooks": "^2.4.1",
|
||||||
|
"better-auth": "^1.1.16",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"countries-list": "^3.1.1",
|
"countries-list": "^3.1.1",
|
||||||
|
@ -88,6 +90,19 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@better-auth/utils": {
|
||||||
|
"version": "0.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.2.3.tgz",
|
||||||
|
"integrity": "sha512-Ap1GaSmo6JYhJhxJOpUB0HobkKPTNzfta+bLV89HfpyCAHN7p8ntCrmNFHNAVD0F6v0mywFVEUg1FUhNCc81Rw==",
|
||||||
|
"dependencies": {
|
||||||
|
"uncrypto": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@better-fetch/fetch": {
|
||||||
|
"version": "1.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.12.tgz",
|
||||||
|
"integrity": "sha512-B3bfloI/2UBQWIATRN6qmlORrvx3Mp0kkNjmXLv0b+DtbtR+pP4/I5kQA/rDUv+OReLywCCldf6co4LdDmh8JA=="
|
||||||
|
},
|
||||||
"node_modules/@clickhouse/client": {
|
"node_modules/@clickhouse/client": {
|
||||||
"version": "1.10.1",
|
"version": "1.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@clickhouse/client/-/client-1.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@clickhouse/client/-/client-1.10.1.tgz",
|
||||||
|
@ -932,6 +947,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
||||||
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="
|
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@hexagon/base64": {
|
||||||
|
"version": "1.1.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz",
|
||||||
|
"integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw=="
|
||||||
|
},
|
||||||
"node_modules/@img/sharp-darwin-arm64": {
|
"node_modules/@img/sharp-darwin-arm64": {
|
||||||
"version": "0.33.5",
|
"version": "0.33.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
||||||
|
@ -1339,6 +1359,11 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@levischuck/tiny-cbor": {
|
||||||
|
"version": "0.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz",
|
||||||
|
"integrity": "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow=="
|
||||||
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "15.1.6",
|
"version": "15.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.6.tgz",
|
||||||
|
@ -1622,6 +1647,25 @@
|
||||||
"react": ">= 16.14.0 < 19.0.0"
|
"react": ">= 16.14.0 < 19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@noble/ciphers": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@noble/hashes": {
|
||||||
|
"version": "1.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz",
|
||||||
|
"integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.21.3 || >=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
|
@ -1657,6 +1701,59 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@peculiar/asn1-android": {
|
||||||
|
"version": "2.3.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.3.15.tgz",
|
||||||
|
"integrity": "sha512-8U2TIj59cRlSXTX2d0mzUKP7whfWGFMzTeC3qPgAbccXFrPNZLaDhpNEdG5U2QZ/tBv/IHlCJ8s+KYXpJeop6w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.3.15",
|
||||||
|
"asn1js": "^3.0.5",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-ecc": {
|
||||||
|
"version": "2.3.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.3.15.tgz",
|
||||||
|
"integrity": "sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.3.15",
|
||||||
|
"@peculiar/asn1-x509": "^2.3.15",
|
||||||
|
"asn1js": "^3.0.5",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-rsa": {
|
||||||
|
"version": "2.3.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.3.15.tgz",
|
||||||
|
"integrity": "sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.3.15",
|
||||||
|
"@peculiar/asn1-x509": "^2.3.15",
|
||||||
|
"asn1js": "^3.0.5",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-schema": {
|
||||||
|
"version": "2.3.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.15.tgz",
|
||||||
|
"integrity": "sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==",
|
||||||
|
"dependencies": {
|
||||||
|
"asn1js": "^3.0.5",
|
||||||
|
"pvtsutils": "^1.3.6",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-x509": {
|
||||||
|
"version": "2.3.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.3.15.tgz",
|
||||||
|
"integrity": "sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.3.15",
|
||||||
|
"asn1js": "^3.0.5",
|
||||||
|
"pvtsutils": "^1.3.6",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@phosphor-icons/react": {
|
"node_modules/@phosphor-icons/react": {
|
||||||
"version": "2.1.7",
|
"version": "2.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.7.tgz",
|
||||||
|
@ -1736,6 +1833,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-compose-refs": {
|
"node_modules/@radix-ui/react-compose-refs": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
|
||||||
|
@ -1799,6 +1913,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-direction": {
|
"node_modules/@radix-ui/react-direction": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
|
||||||
|
@ -1922,6 +2053,50 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-label": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-primitive": "2.0.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-menu": {
|
"node_modules/@radix-ui/react-menu": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.5.tgz",
|
||||||
|
@ -1961,6 +2136,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popover": {
|
"node_modules/@radix-ui/react-popover": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.5.tgz",
|
||||||
|
@ -1997,6 +2189,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popper": {
|
"node_modules/@radix-ui/react-popper": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz",
|
||||||
|
@ -2096,6 +2305,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-roving-focus": {
|
"node_modules/@radix-ui/react-roving-focus": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz",
|
||||||
|
@ -2168,7 +2394,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-slot": {
|
"node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
|
||||||
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
|
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
|
||||||
|
@ -2185,6 +2411,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-tooltip": {
|
"node_modules/@radix-ui/react-tooltip": {
|
||||||
"version": "1.1.8",
|
"version": "1.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz",
|
||||||
|
@ -2342,23 +2585,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-compose-refs": "1.1.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-visually-hidden": {
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-visually-hidden": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz",
|
||||||
|
@ -2594,6 +2820,28 @@
|
||||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@simplewebauthn/browser": {
|
||||||
|
"version": "13.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.1.0.tgz",
|
||||||
|
"integrity": "sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg=="
|
||||||
|
},
|
||||||
|
"node_modules/@simplewebauthn/server": {
|
||||||
|
"version": "13.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-13.1.1.tgz",
|
||||||
|
"integrity": "sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@hexagon/base64": "^1.1.27",
|
||||||
|
"@levischuck/tiny-cbor": "^0.2.2",
|
||||||
|
"@peculiar/asn1-android": "^2.3.10",
|
||||||
|
"@peculiar/asn1-ecc": "^2.3.8",
|
||||||
|
"@peculiar/asn1-rsa": "^2.3.8",
|
||||||
|
"@peculiar/asn1-schema": "^2.3.8",
|
||||||
|
"@peculiar/asn1-x509": "^2.3.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@swc/counter": {
|
"node_modules/@swc/counter": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||||
|
@ -2925,12 +3173,55 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asn1js": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"pvtsutils": "^1.3.2",
|
||||||
|
"pvutils": "^1.1.3",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/better-auth": {
|
||||||
|
"version": "1.1.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.1.16.tgz",
|
||||||
|
"integrity": "sha512-Xc5pxafKZw4QVU8WYfkV2z4Hd8KCXXbphrgOpe2gA/EfanysLBhE1G/F7cEi5e0bW2pGR+vw6gf0ARHA7VFihg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@better-auth/utils": "0.2.3",
|
||||||
|
"@better-fetch/fetch": "1.1.12",
|
||||||
|
"@noble/ciphers": "^0.6.0",
|
||||||
|
"@noble/hashes": "^1.6.1",
|
||||||
|
"@simplewebauthn/browser": "^13.0.0",
|
||||||
|
"@simplewebauthn/server": "^13.0.0",
|
||||||
|
"better-call": "0.3.3",
|
||||||
|
"defu": "^6.1.4",
|
||||||
|
"jose": "^5.9.6",
|
||||||
|
"kysely": "^0.27.4",
|
||||||
|
"nanostores": "^0.11.3",
|
||||||
|
"zod": "^3.24.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/better-call": {
|
||||||
|
"version": "0.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/better-call/-/better-call-0.3.3.tgz",
|
||||||
|
"integrity": "sha512-N4lDVm0NGmFfDJ0XMQ4O83Zm/3dPlvIQdxvwvgSLSkjFX5PM4GUYSVAuxNzXN27QZMHDkrJTWUqxBrm4tPC3eA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@better-fetch/fetch": "^1.1.4",
|
||||||
|
"rou3": "^0.5.1",
|
||||||
|
"uncrypto": "^0.1.3",
|
||||||
|
"zod": "^3.24.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
|
@ -3431,6 +3722,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||||
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
|
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/defu": {
|
||||||
|
"version": "6.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||||
|
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="
|
||||||
|
},
|
||||||
"node_modules/delaunator": {
|
"node_modules/delaunator": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
|
||||||
|
@ -3958,11 +4254,27 @@
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jose": {
|
||||||
|
"version": "5.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz",
|
||||||
|
"integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/kysely": {
|
||||||
|
"version": "0.27.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.5.tgz",
|
||||||
|
"integrity": "sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ldrs": {
|
"node_modules/ldrs": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/ldrs/-/ldrs-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/ldrs/-/ldrs-1.0.2.tgz",
|
||||||
|
@ -4109,6 +4421,20 @@
|
||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nanostores": {
|
||||||
|
"version": "0.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanostores/-/nanostores-0.11.3.tgz",
|
||||||
|
"integrity": "sha512-TUes3xKIX33re4QzdxwZ6tdbodjmn3tWXCEc1uokiEmo14sI1EaGYNs2k3bU2pyyGNmBqFGAVl6jAGWd06AVIg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "15.1.6",
|
"version": "15.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-15.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-15.1.6.tgz",
|
||||||
|
@ -4595,6 +4921,22 @@
|
||||||
"react-is": "^16.13.1"
|
"react-is": "^16.13.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pvtsutils": {
|
||||||
|
"version": "1.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz",
|
||||||
|
"integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pvutils": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
|
@ -4892,6 +5234,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
|
||||||
"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
|
"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/rou3": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rou3/-/rou3-0.5.1.tgz",
|
||||||
|
"integrity": "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="
|
||||||
|
},
|
||||||
"node_modules/run-parallel": {
|
"node_modules/run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
|
@ -5354,6 +5701,11 @@
|
||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uncrypto": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="
|
||||||
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.19.8",
|
"version": "6.19.8",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||||
|
@ -5569,6 +5921,14 @@
|
||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "3.24.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
|
||||||
|
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/zustand": {
|
"node_modules/zustand": {
|
||||||
"version": "5.0.3",
|
"version": "5.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz",
|
||||||
|
|
|
@ -15,9 +15,10 @@
|
||||||
"@phosphor-icons/react": "^2.1.7",
|
"@phosphor-icons/react": "^2.1.7",
|
||||||
"@radix-ui/react-dialog": "^1.1.5",
|
"@radix-ui/react-dialog": "^1.1.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.5",
|
"@radix-ui/react-dropdown-menu": "^2.1.5",
|
||||||
|
"@radix-ui/react-label": "^2.1.2",
|
||||||
"@radix-ui/react-popover": "^1.1.5",
|
"@radix-ui/react-popover": "^1.1.5",
|
||||||
"@radix-ui/react-select": "^2.1.5",
|
"@radix-ui/react-select": "^2.1.5",
|
||||||
"@radix-ui/react-slot": "^1.1.1",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"@tanstack/react-query": "^5.64.2",
|
"@tanstack/react-query": "^5.64.2",
|
||||||
"@tanstack/react-query-devtools": "^5.64.2",
|
"@tanstack/react-query-devtools": "^5.64.2",
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/pg": "^8.11.11",
|
"@types/pg": "^8.11.11",
|
||||||
"@uidotdev/usehooks": "^2.4.1",
|
"@uidotdev/usehooks": "^2.4.1",
|
||||||
|
"better-auth": "^1.1.16",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"countries-list": "^3.1.1",
|
"countries-list": "^3.1.1",
|
||||||
|
|
|
@ -33,9 +33,7 @@ export function DateSelector() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>{getLabel(time)}</DropdownMenuTrigger>
|
||||||
<Button variant="default">{getLabel(time)}</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
import { DateSelector } from "./DateSelector";
|
import { DateSelector } from "./DateSelector";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
|
import { authedFetch } from "../../../hooks/utils";
|
||||||
|
|
||||||
const canGoForward = (time: Time) => {
|
const canGoForward = (time: Time) => {
|
||||||
const currentDay = DateTime.now().startOf("day");
|
const currentDay = DateTime.now().startOf("day");
|
||||||
|
@ -42,9 +43,7 @@ export function Header() {
|
||||||
const { data } = useQuery<{ count: number }>({
|
const { data } = useQuery<{ count: number }>({
|
||||||
queryKey: ["active-sessions"],
|
queryKey: ["active-sessions"],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}/live-user-count`).then(
|
authedFetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}/live-user-count`),
|
||||||
(res) => res.json()
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { time } = useTimeSelection();
|
const { time } = useTimeSelection();
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
import { Duration } from "luxon";
|
import { Duration } from "luxon";
|
||||||
import { Badge } from "../../../components/ui/badge";
|
import { Badge } from "../../../components/ui/badge";
|
||||||
import { Skeleton } from "../../../components/ui/skeleton";
|
import { Skeleton } from "../../../components/ui/skeleton";
|
||||||
|
|
|
@ -1,22 +1,40 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import QueryProvider from "@/providers/QueryProvider";
|
import QueryProvider from "@/providers/QueryProvider";
|
||||||
import { ThemeProvider } from "@/providers/ThemeProvider";
|
import { ThemeProvider } from "@/providers/ThemeProvider";
|
||||||
import { TopBar } from "@/components/TopBar";
|
import { TopBar } from "@/components/TopBar";
|
||||||
|
import { authClient } from "../lib/auth";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { userStore } from "../lib/useStore";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
const metadata: Metadata = {
|
||||||
title: "Frogstats Analytics",
|
title: "Frogstats Analytics",
|
||||||
description: "Analytics dashboard for your web applications",
|
description: "Analytics dashboard for your web applications",
|
||||||
};
|
};
|
||||||
|
const publicRoutes = ["/login"];
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
const { user, isPending } = userStore();
|
||||||
|
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPending && !user && !publicRoutes.includes(pathname)) {
|
||||||
|
redirect("/login");
|
||||||
|
}
|
||||||
|
}, [isPending, user, pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en" className="h-full dark" suppressHydrationWarning>
|
<html lang="en" className="h-full dark" suppressHydrationWarning>
|
||||||
<body
|
<body
|
||||||
|
|
98
client/src/app/login/page.tsx
Normal file
98
client/src/app/login/page.tsx
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { authClient } from "../../lib/auth";
|
||||||
|
import { userStore } from "../../lib/useStore";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data, error } = await authClient.signIn.username({
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
if (data?.user) {
|
||||||
|
userStore.setState({
|
||||||
|
user: data.user,
|
||||||
|
});
|
||||||
|
router.push("/");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Login error:", error);
|
||||||
|
alert("Failed to login. Please try again.");
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn("flex flex-col gap-6 items-center")}>
|
||||||
|
<Card className="w-[500px]">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-2xl">Login</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Enter your email below to login to your account
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="username">Username</Label>
|
||||||
|
<Input
|
||||||
|
id="username"
|
||||||
|
type="username"
|
||||||
|
placeholder="username"
|
||||||
|
required
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Label htmlFor="password">Password</Label>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
|
||||||
|
>
|
||||||
|
Forgot your password?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button type="submit" className="w-full" disabled={isLoading}>
|
||||||
|
{isLoading ? "Logging in..." : "Login"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,4 +1,13 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { authClient } from "../../lib/auth";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
|
const session = authClient.useSession();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container max-w-6xl py-6">
|
<div className="container max-w-6xl py-6">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
@ -8,16 +17,14 @@ export default function SettingsPage() {
|
||||||
Manage your analytics preferences and configurations.
|
Manage your analytics preferences and configurations.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border rounded-lg p-4">
|
<Button
|
||||||
<div className="space-y-4">
|
onClick={async () => {
|
||||||
<div>
|
await authClient.signOut();
|
||||||
<h4 className="text-sm font-medium">Coming Soon</h4>
|
router.push("/login");
|
||||||
<p className="text-sm text-muted-foreground">
|
}}
|
||||||
Settings configuration options will be available here.
|
>
|
||||||
</p>
|
Signout
|
||||||
</div>
|
</Button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
64
client/src/app/signup/page.tsx
Normal file
64
client/src/app/signup/page.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<div className={cn("flex flex-col gap-6")}>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-2xl">Login</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Enter your email below to login to your account
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form>
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="username">Username</Label>
|
||||||
|
<Input
|
||||||
|
id="username"
|
||||||
|
type="username"
|
||||||
|
placeholder="username"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Label htmlFor="password">Password</Label>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
|
||||||
|
>
|
||||||
|
Forgot your password?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<Input id="password" type="password" required />
|
||||||
|
</div>
|
||||||
|
<Button type="submit" className="w-full">
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 text-center text-sm">
|
||||||
|
Don't have an account?{""}
|
||||||
|
<a href="/signup" className="underline underline-offset-4">
|
||||||
|
Sign up
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
68
client/src/components/login-form.tsx
Normal file
68
client/src/components/login-form.tsx
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
|
||||||
|
export function LoginForm({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentPropsWithoutRef<"div">) {
|
||||||
|
return (
|
||||||
|
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-2xl">Login</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Enter your email below to login to your account
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form>
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="m@example.com"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Label htmlFor="password">Password</Label>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
|
||||||
|
>
|
||||||
|
Forgot your password?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<Input id="password" type="password" required />
|
||||||
|
</div>
|
||||||
|
<Button type="submit" className="w-full">
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" className="w-full">
|
||||||
|
Login with Google
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 text-center text-sm">
|
||||||
|
Don't have an account?{""}
|
||||||
|
<a href="#" className="underline underline-offset-4">
|
||||||
|
Sign up
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useMeasure } from "@uidotdev/usehooks";
|
import { useMeasure } from "@uidotdev/usehooks";
|
||||||
|
@ -27,7 +29,7 @@ const CardLoader = React.forwardRef<
|
||||||
<div ref={ref2} className="mt-[-15px] absolute top-0 left-0 w-full">
|
<div ref={ref2} className="mt-[-15px] absolute top-0 left-0 w-full">
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
<l-zoomies
|
<l-zoomies
|
||||||
size={width}
|
size={1000}
|
||||||
stroke="3"
|
stroke="3"
|
||||||
bg-opacity="0.1"
|
bg-opacity="0.1"
|
||||||
speed="1.4"
|
speed="1.4"
|
||||||
|
|
|
@ -5,10 +5,23 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||||
import { Check, ChevronRight, Circle } from "lucide-react";
|
import { Check, ChevronRight, Circle } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { ButtonProps, buttonVariants } from "./button";
|
||||||
|
|
||||||
const DropdownMenu = DropdownMenuPrimitive.Root;
|
const DropdownMenu = DropdownMenuPrimitive.Root;
|
||||||
|
|
||||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
const DropdownMenuTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger> &
|
||||||
|
ButtonProps
|
||||||
|
>(({ className, variant, size, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
// className={cn(className)}
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DropdownMenuTrigger.displayName = DropdownMenuPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
||||||
|
|
||||||
|
|
22
client/src/components/ui/input.tsx
Normal file
22
client/src/components/ui/input.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
||||||
|
({ className, type, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
className={cn(
|
||||||
|
"flex h-9 w-full rounded-md border border-neutral-200 bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-neutral-950 placeholder:text-neutral-500 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:border-neutral-800 dark:file:text-neutral-50 dark:placeholder:text-neutral-400 dark:focus-visible:ring-neutral-300",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Input.displayName = "Input"
|
||||||
|
|
||||||
|
export { Input }
|
26
client/src/components/ui/label.tsx
Normal file
26
client/src/components/ui/label.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const labelVariants = cva(
|
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Label = React.forwardRef<
|
||||||
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
|
VariantProps<typeof labelVariants>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(labelVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Label.displayName = LabelPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Label }
|
|
@ -4,7 +4,7 @@ import {
|
||||||
UseQueryResult,
|
UseQueryResult,
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
import { useTimeSelection } from "../lib/timeSelectionStore";
|
import { useTimeSelection } from "../lib/timeSelectionStore";
|
||||||
import { getStartAndEndDate } from "./utils";
|
import { authedFetch, getStartAndEndDate } from "./utils";
|
||||||
|
|
||||||
export type APIResponse<T> = {
|
export type APIResponse<T> = {
|
||||||
data: T;
|
data: T;
|
||||||
|
@ -25,9 +25,9 @@ export function useGenericQuery<T>(
|
||||||
queryKey: [endpoint, timeToUse],
|
queryKey: [endpoint, timeToUse],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
return fetch(
|
return authedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_BACKEND_URL}/${endpoint}?startDate=${startDate}&endDate=${endDate}&timezone=${timezone}`
|
`${process.env.NEXT_PUBLIC_BACKEND_URL}/${endpoint}?startDate=${startDate}&endDate=${endDate}&timezone=${timezone}`
|
||||||
).then((res) => res.json());
|
);
|
||||||
},
|
},
|
||||||
staleTime: Infinity,
|
staleTime: Infinity,
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
|
@ -111,9 +111,9 @@ export function useGetPageviews(
|
||||||
queryKey: ["pageviews", timeToUse, bucket],
|
queryKey: ["pageviews", timeToUse, bucket],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
return fetch(
|
return authedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_BACKEND_URL}/pageviews?startDate=${startDate}&endDate=${endDate}&timezone=${timezone}&bucket=${bucket}`
|
`${process.env.NEXT_PUBLIC_BACKEND_URL}/pageviews?startDate=${startDate}&endDate=${endDate}&timezone=${timezone}&bucket=${bucket}`
|
||||||
).then((res) => res.json());
|
);
|
||||||
},
|
},
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
staleTime: Infinity,
|
staleTime: Infinity,
|
||||||
|
|
|
@ -15,3 +15,13 @@ export function getStartAndEndDate(time: Time) {
|
||||||
}
|
}
|
||||||
return { startDate: time.day, endDate: time.day };
|
return { startDate: time.day, endDate: time.day };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function authedFetch(url: string) {
|
||||||
|
return fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}).then((res) => res.json());
|
||||||
|
}
|
||||||
|
|
11
client/src/lib/auth.ts
Normal file
11
client/src/lib/auth.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { createAuthClient } from "better-auth/react";
|
||||||
|
import { usernameClient } from "better-auth/client/plugins";
|
||||||
|
|
||||||
|
export const authClient = createAuthClient({
|
||||||
|
// baseURL: process.env.NEXT_PUBLIC_BACKEND_URL,
|
||||||
|
baseURL: process.env.NEXT_PUBLIC_BACKEND_URL?.replace("/api", ""), // the base url of your auth server
|
||||||
|
plugins: [usernameClient()],
|
||||||
|
fetchOptions: {
|
||||||
|
credentials: "include",
|
||||||
|
},
|
||||||
|
});
|
22
client/src/lib/useStore.ts
Normal file
22
client/src/lib/useStore.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { User } from "better-auth";
|
||||||
|
import { create } from "zustand";
|
||||||
|
import { authClient } from "./auth";
|
||||||
|
|
||||||
|
export const userStore = create<{
|
||||||
|
user: User | null;
|
||||||
|
isPending: boolean;
|
||||||
|
setSession: (user: User) => void;
|
||||||
|
setIsPending: (isPending: boolean) => void;
|
||||||
|
}>((set) => ({
|
||||||
|
user: null,
|
||||||
|
isPending: true,
|
||||||
|
setSession: (user) => set({ user }),
|
||||||
|
setIsPending: (isPending) => set({ isPending }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
authClient.getSession().then(({ data: session }) => {
|
||||||
|
userStore.setState({
|
||||||
|
user: session?.user,
|
||||||
|
isPending: false,
|
||||||
|
});
|
||||||
|
});
|
|
@ -41,6 +41,7 @@ services:
|
||||||
- POSTGRES_DB=analytics
|
- POSTGRES_DB=analytics
|
||||||
- POSTGRES_USER=frog
|
- POSTGRES_USER=frog
|
||||||
- POSTGRES_PASSWORD=admin
|
- POSTGRES_PASSWORD=admin
|
||||||
|
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
|
||||||
depends_on:
|
depends_on:
|
||||||
- clickhouse
|
- clickhouse
|
||||||
- postgres
|
- postgres
|
||||||
|
@ -59,9 +60,13 @@ services:
|
||||||
- "3002:3002"
|
- "3002:3002"
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- CLICKHOUSE_HOST=http://clickhouse:8123
|
- POSTGRES_HOST=postgres
|
||||||
- CLICKHOUSE_DB=analytics
|
- POSTGRES_PORT=5432
|
||||||
|
- POSTGRES_DB=analytics
|
||||||
|
- POSTGRES_USER=frog
|
||||||
|
- POSTGRES_PASSWORD=admin
|
||||||
- NEXT_PUBLIC_BACKEND_URL=${BASE_URL}
|
- NEXT_PUBLIC_BACKEND_URL=${BASE_URL}
|
||||||
|
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
create table "user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "emailVerified" boolean not null, "image" text, "createdAt" timestamp not null, "updatedAt" timestamp not null);
|
||||||
|
|
||||||
|
create table "session" ("id" text not null primary key, "expiresAt" timestamp not null, "token" text not null unique, "createdAt" timestamp not null, "updatedAt" timestamp not null, "ipAddress" text, "userAgent" text, "userId" text not null references "user" ("id"));
|
||||||
|
|
||||||
|
create table "account" ("id" text not null primary key, "accountId" text not null, "providerId" text not null, "userId" text not null references "user" ("id"), "accessToken" text, "refreshToken" text, "idToken" text, "accessTokenExpiresAt" timestamp, "refreshTokenExpiresAt" timestamp, "scope" text, "password" text, "createdAt" timestamp not null, "updatedAt" timestamp not null);
|
||||||
|
|
||||||
|
create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" timestamp not null, "createdAt" timestamp, "updatedAt" timestamp)
|
465
server/package-lock.json
generated
465
server/package-lock.json
generated
|
@ -12,12 +12,17 @@
|
||||||
"@fastify/cors": "^10.0.2",
|
"@fastify/cors": "^10.0.2",
|
||||||
"@fastify/one-line-logger": "^1.4.0",
|
"@fastify/one-line-logger": "^1.4.0",
|
||||||
"@fastify/static": "^8.0.4",
|
"@fastify/static": "^8.0.4",
|
||||||
|
"@types/pg": "^8.11.11",
|
||||||
|
"better-auth": "^1.1.16",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"fastify": "^5.1.0",
|
"fastify": "^5.1.0",
|
||||||
|
"fastify-better-auth": "^1.0.0",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
|
"pg": "^8.13.1",
|
||||||
"postgres": "^3.4.5",
|
"postgres": "^3.4.5",
|
||||||
"ua-parser-js": "^2.0.0"
|
"ua-parser-js": "^2.0.0",
|
||||||
|
"undici": "^7.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
|
@ -27,6 +32,19 @@
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@better-auth/utils": {
|
||||||
|
"version": "0.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.2.3.tgz",
|
||||||
|
"integrity": "sha512-Ap1GaSmo6JYhJhxJOpUB0HobkKPTNzfta+bLV89HfpyCAHN7p8ntCrmNFHNAVD0F6v0mywFVEUg1FUhNCc81Rw==",
|
||||||
|
"dependencies": {
|
||||||
|
"uncrypto": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@better-fetch/fetch": {
|
||||||
|
"version": "1.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.12.tgz",
|
||||||
|
"integrity": "sha512-B3bfloI/2UBQWIATRN6qmlORrvx3Mp0kkNjmXLv0b+DtbtR+pP4/I5kQA/rDUv+OReLywCCldf6co4LdDmh8JA=="
|
||||||
|
},
|
||||||
"node_modules/@clickhouse/client": {
|
"node_modules/@clickhouse/client": {
|
||||||
"version": "1.10.1",
|
"version": "1.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@clickhouse/client/-/client-1.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@clickhouse/client/-/client-1.10.1.tgz",
|
||||||
|
@ -217,6 +235,11 @@
|
||||||
"glob": "^11.0.0"
|
"glob": "^11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@hexagon/base64": {
|
||||||
|
"version": "1.1.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz",
|
||||||
|
"integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw=="
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
|
@ -258,6 +281,11 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@levischuck/tiny-cbor": {
|
||||||
|
"version": "0.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz",
|
||||||
|
"integrity": "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow=="
|
||||||
|
},
|
||||||
"node_modules/@lukeed/ms": {
|
"node_modules/@lukeed/ms": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz",
|
||||||
|
@ -266,6 +294,100 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@noble/ciphers": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@noble/hashes": {
|
||||||
|
"version": "1.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz",
|
||||||
|
"integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.21.3 || >=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-android": {
|
||||||
|
"version": "2.3.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.3.15.tgz",
|
||||||
|
"integrity": "sha512-8U2TIj59cRlSXTX2d0mzUKP7whfWGFMzTeC3qPgAbccXFrPNZLaDhpNEdG5U2QZ/tBv/IHlCJ8s+KYXpJeop6w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.3.15",
|
||||||
|
"asn1js": "^3.0.5",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-ecc": {
|
||||||
|
"version": "2.3.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.3.15.tgz",
|
||||||
|
"integrity": "sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.3.15",
|
||||||
|
"@peculiar/asn1-x509": "^2.3.15",
|
||||||
|
"asn1js": "^3.0.5",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-rsa": {
|
||||||
|
"version": "2.3.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.3.15.tgz",
|
||||||
|
"integrity": "sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.3.15",
|
||||||
|
"@peculiar/asn1-x509": "^2.3.15",
|
||||||
|
"asn1js": "^3.0.5",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-schema": {
|
||||||
|
"version": "2.3.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.15.tgz",
|
||||||
|
"integrity": "sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==",
|
||||||
|
"dependencies": {
|
||||||
|
"asn1js": "^3.0.5",
|
||||||
|
"pvtsutils": "^1.3.6",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-x509": {
|
||||||
|
"version": "2.3.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.3.15.tgz",
|
||||||
|
"integrity": "sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.3.15",
|
||||||
|
"asn1js": "^3.0.5",
|
||||||
|
"pvtsutils": "^1.3.6",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@simplewebauthn/browser": {
|
||||||
|
"version": "13.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.1.0.tgz",
|
||||||
|
"integrity": "sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg=="
|
||||||
|
},
|
||||||
|
"node_modules/@simplewebauthn/server": {
|
||||||
|
"version": "13.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-13.1.1.tgz",
|
||||||
|
"integrity": "sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@hexagon/base64": "^1.1.27",
|
||||||
|
"@levischuck/tiny-cbor": "^0.2.2",
|
||||||
|
"@peculiar/asn1-android": "^2.3.10",
|
||||||
|
"@peculiar/asn1-ecc": "^2.3.8",
|
||||||
|
"@peculiar/asn1-rsa": "^2.3.8",
|
||||||
|
"@peculiar/asn1-schema": "^2.3.8",
|
||||||
|
"@peculiar/asn1-x509": "^2.3.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tsconfig/node10": {
|
"node_modules/@tsconfig/node10": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||||
|
@ -300,7 +422,6 @@
|
||||||
"version": "20.10.0",
|
"version": "20.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz",
|
||||||
"integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==",
|
"integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
|
@ -311,6 +432,68 @@
|
||||||
"integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==",
|
"integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/pg": {
|
||||||
|
"version": "8.11.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.11.tgz",
|
||||||
|
"integrity": "sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"pg-protocol": "*",
|
||||||
|
"pg-types": "^4.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/pg/node_modules/pg-types": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"pg-numeric": "1.0.2",
|
||||||
|
"postgres-array": "~3.0.1",
|
||||||
|
"postgres-bytea": "~3.0.0",
|
||||||
|
"postgres-date": "~2.1.0",
|
||||||
|
"postgres-interval": "^3.0.0",
|
||||||
|
"postgres-range": "^1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/pg/node_modules/postgres-array": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/pg/node_modules/postgres-bytea": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==",
|
||||||
|
"dependencies": {
|
||||||
|
"obuf": "~1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/pg/node_modules/postgres-date": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/pg/node_modules/postgres-interval": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/strip-bom": {
|
"node_modules/@types/strip-bom": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||||
|
@ -435,6 +618,19 @@
|
||||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/asn1js": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"pvtsutils": "^1.3.2",
|
||||||
|
"pvutils": "^1.1.3",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/atomic-sleep": {
|
"node_modules/atomic-sleep": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||||
|
@ -476,6 +672,36 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/better-auth": {
|
||||||
|
"version": "1.1.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.1.16.tgz",
|
||||||
|
"integrity": "sha512-Xc5pxafKZw4QVU8WYfkV2z4Hd8KCXXbphrgOpe2gA/EfanysLBhE1G/F7cEi5e0bW2pGR+vw6gf0ARHA7VFihg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@better-auth/utils": "0.2.3",
|
||||||
|
"@better-fetch/fetch": "1.1.12",
|
||||||
|
"@noble/ciphers": "^0.6.0",
|
||||||
|
"@noble/hashes": "^1.6.1",
|
||||||
|
"@simplewebauthn/browser": "^13.0.0",
|
||||||
|
"@simplewebauthn/server": "^13.0.0",
|
||||||
|
"better-call": "0.3.3",
|
||||||
|
"defu": "^6.1.4",
|
||||||
|
"jose": "^5.9.6",
|
||||||
|
"kysely": "^0.27.4",
|
||||||
|
"nanostores": "^0.11.3",
|
||||||
|
"zod": "^3.24.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/better-call": {
|
||||||
|
"version": "0.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/better-call/-/better-call-0.3.3.tgz",
|
||||||
|
"integrity": "sha512-N4lDVm0NGmFfDJ0XMQ4O83Zm/3dPlvIQdxvwvgSLSkjFX5PM4GUYSVAuxNzXN27QZMHDkrJTWUqxBrm4tPC3eA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@better-fetch/fetch": "^1.1.4",
|
||||||
|
"rou3": "^0.5.1",
|
||||||
|
"uncrypto": "^0.1.3",
|
||||||
|
"zod": "^3.24.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
|
@ -634,6 +860,11 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/defu": {
|
||||||
|
"version": "6.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||||
|
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
|
@ -843,6 +1074,21 @@
|
||||||
"toad-cache": "^3.7.0"
|
"toad-cache": "^3.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fastify-better-auth": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fastify-better-auth/-/fastify-better-auth-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-3sSPlcwOVp9tZAQaniu7LeAX8U237jBjHoPs5cAVMBRCdSHRveb20dGf762mtFSxUygMYciAd431sRpRuJFc1Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"fastify-plugin": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 22"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"better-auth": "1.x",
|
||||||
|
"fastify": "5.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fastify-plugin": {
|
"node_modules/fastify-plugin": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.0.1.tgz",
|
||||||
|
@ -1137,6 +1383,14 @@
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jose": {
|
||||||
|
"version": "5.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz",
|
||||||
|
"integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/joycon": {
|
"node_modules/joycon": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
||||||
|
@ -1168,6 +1422,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||||
},
|
},
|
||||||
|
"node_modules/kysely": {
|
||||||
|
"version": "0.27.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.5.tgz",
|
||||||
|
"integrity": "sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/light-my-request": {
|
"node_modules/light-my-request": {
|
||||||
"version": "6.5.1",
|
"version": "6.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.5.1.tgz",
|
||||||
|
@ -1271,6 +1533,20 @@
|
||||||
"obliterator": "^2.0.1"
|
"obliterator": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nanostores": {
|
||||||
|
"version": "0.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanostores/-/nanostores-0.11.3.tgz",
|
||||||
|
"integrity": "sha512-TUes3xKIX33re4QzdxwZ6tdbodjmn3tWXCEc1uokiEmo14sI1EaGYNs2k3bU2pyyGNmBqFGAVl6jAGWd06AVIg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-cron": {
|
"node_modules/node-cron": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
|
||||||
|
@ -1296,6 +1572,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz",
|
||||||
"integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw=="
|
"integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/obuf": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="
|
||||||
|
},
|
||||||
"node_modules/on-exit-leak-free": {
|
"node_modules/on-exit-leak-free": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
|
||||||
|
@ -1355,6 +1636,95 @@
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pg": {
|
||||||
|
"version": "8.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz",
|
||||||
|
"integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-connection-string": "^2.7.0",
|
||||||
|
"pg-pool": "^3.7.0",
|
||||||
|
"pg-protocol": "^1.7.0",
|
||||||
|
"pg-types": "^2.1.0",
|
||||||
|
"pgpass": "1.x"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"pg-cloudflare": "^1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg-native": ">=3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"pg-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-cloudflare": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/pg-connection-string": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA=="
|
||||||
|
},
|
||||||
|
"node_modules/pg-int8": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-numeric": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-pool": {
|
||||||
|
"version": "3.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz",
|
||||||
|
"integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-protocol": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ=="
|
||||||
|
},
|
||||||
|
"node_modules/pg-types": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"postgres-array": "~2.0.0",
|
||||||
|
"postgres-bytea": "~1.0.0",
|
||||||
|
"postgres-date": "~1.0.4",
|
||||||
|
"postgres-interval": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pgpass": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"dependencies": {
|
||||||
|
"split2": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
|
@ -1453,6 +1823,46 @@
|
||||||
"url": "https://github.com/sponsors/porsager"
|
"url": "https://github.com/sponsors/porsager"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postgres-array": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-bytea": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-date": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-interval": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-range": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w=="
|
||||||
|
},
|
||||||
"node_modules/process": {
|
"node_modules/process": {
|
||||||
"version": "0.11.10",
|
"version": "0.11.10",
|
||||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||||
|
@ -1485,6 +1895,22 @@
|
||||||
"once": "^1.3.1"
|
"once": "^1.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pvtsutils": {
|
||||||
|
"version": "1.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz",
|
||||||
|
"integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pvutils": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/quick-format-unescaped": {
|
"node_modules/quick-format-unescaped": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
||||||
|
@ -1631,6 +2057,11 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rou3": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rou3/-/rou3-0.5.1.tgz",
|
||||||
|
"integrity": "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="
|
||||||
|
},
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
@ -2045,6 +2476,11 @@
|
||||||
"strip-json-comments": "^2.0.0"
|
"strip-json-comments": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.7.3",
|
"version": "5.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||||
|
@ -2107,11 +2543,23 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uncrypto": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="
|
||||||
|
},
|
||||||
|
"node_modules/undici": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-Qy96NND4Dou5jKoSJ2gm8ax8AJM/Ey9o9mz7KN1bb9GP+G0l20Zw8afxTnY2f4b7hmhn/z8aC2kfArVQlAhFBw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.18.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "5.26.5",
|
"version": "5.26.5",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
"node_modules/uuid": {
|
||||||
"version": "8.3.2",
|
"version": "8.3.2",
|
||||||
|
@ -2234,7 +2682,6 @@
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4"
|
"node": ">=0.4"
|
||||||
}
|
}
|
||||||
|
@ -2247,6 +2694,14 @@
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "3.24.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
|
||||||
|
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Self-hosted analytics backend using ClickHouse",
|
"description": "Self-hosted analytics backend using ClickHouse",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsc && node dist/index.js",
|
"dev": "tsc && node dist/index.js",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
|
@ -13,12 +14,17 @@
|
||||||
"@fastify/cors": "^10.0.2",
|
"@fastify/cors": "^10.0.2",
|
||||||
"@fastify/one-line-logger": "^1.4.0",
|
"@fastify/one-line-logger": "^1.4.0",
|
||||||
"@fastify/static": "^8.0.4",
|
"@fastify/static": "^8.0.4",
|
||||||
|
"@types/pg": "^8.11.11",
|
||||||
|
"better-auth": "^1.1.16",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"fastify": "^5.1.0",
|
"fastify": "^5.1.0",
|
||||||
|
"fastify-better-auth": "^1.0.0",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
|
"pg": "^8.13.1",
|
||||||
"postgres": "^3.4.5",
|
"postgres": "^3.4.5",
|
||||||
"ua-parser-js": "^2.0.0"
|
"ua-parser-js": "^2.0.0",
|
||||||
|
"undici": "^7.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import clickhouse from "../db/clickhouse/clickhouse";
|
import clickhouse from "../db/clickhouse/clickhouse.js";
|
||||||
import { TrackingPayload } from "../types";
|
import { TrackingPayload } from "../types.js";
|
||||||
import { getDeviceType } from "../utils";
|
import { getDeviceType } from "../utils.js";
|
||||||
|
|
||||||
type TotalPayload = TrackingPayload & {
|
type TotalPayload = TrackingPayload & {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { FastifyRequest } from "fastify";
|
import { FastifyRequest } from "fastify";
|
||||||
import { TrackingPayload } from "../types";
|
import { TrackingPayload } from "../types.js";
|
||||||
import { getUserId, getDeviceType, getIpAddress } from "../utils";
|
import { getUserId, getDeviceType, getIpAddress } from "../utils.js";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { sql } from "../db/postgres/postgres";
|
import { sql } from "../db/postgres/postgres.js";
|
||||||
import UAParser, { UAParser as userAgentParser } from "ua-parser-js";
|
import UAParser, { UAParser as userAgentParser } from "ua-parser-js";
|
||||||
|
|
||||||
import { Pageview } from "../db/clickhouse/types";
|
import { Pageview } from "../db/clickhouse/types.js";
|
||||||
import { pageviewQueue } from "./pageviewQueue";
|
import { pageviewQueue } from "./pageviewQueue.js";
|
||||||
|
|
||||||
type TotalPayload = TrackingPayload & {
|
type TotalPayload = TrackingPayload & {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { FastifyReply, FastifyRequest } from "fastify";
|
import { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import clickhouse from "../db/clickhouse/clickhouse";
|
import clickhouse from "../db/clickhouse/clickhouse.js";
|
||||||
import { GenericRequest } from "./types";
|
import { GenericRequest } from "./types.js";
|
||||||
import { getTimeStatement, processResults } from "./utils";
|
import { getTimeStatement, processResults } from "./utils.js";
|
||||||
|
|
||||||
type GetBrowsersResponse = {
|
type GetBrowsersResponse = {
|
||||||
browser: string;
|
browser: string;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { FastifyReply, FastifyRequest } from "fastify";
|
import { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import clickhouse from "../db/clickhouse/clickhouse";
|
import clickhouse from "../db/clickhouse/clickhouse.js";
|
||||||
import { GenericRequest } from "./types";
|
import { GenericRequest } from "./types.js";
|
||||||
import { getTimeStatement, processResults } from "./utils";
|
import { getTimeStatement, processResults } from "./utils.js";
|
||||||
|
|
||||||
type GetCountriesResponse = {
|
type GetCountriesResponse = {
|
||||||
country: string;
|
country: string;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { FastifyReply, FastifyRequest } from "fastify";
|
import { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import clickhouse from "../db/clickhouse/clickhouse";
|
import clickhouse from "../db/clickhouse/clickhouse.js";
|
||||||
import { GenericRequest } from "./types";
|
import { GenericRequest } from "./types.js";
|
||||||
import { getTimeStatement, processResults } from "./utils";
|
import { getTimeStatement, processResults } from "./utils.js";
|
||||||
|
|
||||||
type GetDevicesResponse = {
|
type GetDevicesResponse = {
|
||||||
device_type: string;
|
device_type: string;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { sql } from "../db/postgres/postgres";
|
import { sql } from "../db/postgres/postgres.js";
|
||||||
|
|
||||||
export const getLiveUsercount = async () => {
|
export const getLiveUsercount = async () => {
|
||||||
const result = await sql`SELECT COUNT(*) FROM active_sessions`;
|
const result = await sql`SELECT COUNT(*) FROM active_sessions`;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { FastifyReply, FastifyRequest } from "fastify";
|
import { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import clickhouse from "../db/clickhouse/clickhouse";
|
import clickhouse from "../db/clickhouse/clickhouse.js";
|
||||||
import { GenericRequest } from "./types";
|
import { GenericRequest } from "./types.js";
|
||||||
import { getTimeStatement, processResults } from "./utils";
|
import { getTimeStatement, processResults } from "./utils.js";
|
||||||
|
|
||||||
type GetOperatingSystemsResponse = {
|
type GetOperatingSystemsResponse = {
|
||||||
operating_system: string;
|
operating_system: string;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { FastifyReply, FastifyRequest } from "fastify";
|
import { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import clickhouse from "../db/clickhouse/clickhouse";
|
import clickhouse from "../db/clickhouse/clickhouse.js";
|
||||||
import { GenericRequest } from "./types";
|
import { GenericRequest } from "./types.js";
|
||||||
import { getTimeStatement, processResults } from "./utils";
|
import { getTimeStatement, processResults } from "./utils.js";
|
||||||
|
|
||||||
type GetOverviewResponse = {
|
type GetOverviewResponse = {
|
||||||
sessions: number;
|
sessions: number;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { FastifyReply, FastifyRequest } from "fastify";
|
import { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import clickhouse from "../db/clickhouse/clickhouse";
|
import clickhouse from "../db/clickhouse/clickhouse.js";
|
||||||
import { getTimeStatement, processResults } from "./utils";
|
import { getTimeStatement, processResults } from "./utils.js";
|
||||||
|
|
||||||
const TimeBucketToFn = {
|
const TimeBucketToFn = {
|
||||||
hour: "toStartOfHour",
|
hour: "toStartOfHour",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { FastifyReply, FastifyRequest } from "fastify";
|
import { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import { getTimeStatement, processResults } from "./utils";
|
import { getTimeStatement, processResults } from "./utils.js";
|
||||||
import clickhouse from "../db/clickhouse/clickhouse";
|
import clickhouse from "../db/clickhouse/clickhouse.js";
|
||||||
import { GenericRequest } from "./types";
|
import { GenericRequest } from "./types.js";
|
||||||
|
|
||||||
type GetPagesResponse = {
|
type GetPagesResponse = {
|
||||||
pathname: string;
|
pathname: string;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { FastifyReply, FastifyRequest } from "fastify";
|
import { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import { getTimeStatement, processResults } from "./utils";
|
import { getTimeStatement, processResults } from "./utils.js";
|
||||||
import clickhouse from "../db/clickhouse/clickhouse";
|
import clickhouse from "../db/clickhouse/clickhouse.js";
|
||||||
import { GenericRequest } from "./types";
|
import { GenericRequest } from "./types.js";
|
||||||
|
|
||||||
type GetReferrersResponse = {
|
type GetReferrersResponse = {
|
||||||
referrer: string;
|
referrer: string;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { createClient } from "@clickhouse/client";
|
import { createClient } from "@clickhouse/client";
|
||||||
import { Session } from "../postgres/types";
|
import { Session } from "../postgres/types.js";
|
||||||
|
|
||||||
export const clickhouse = createClient({
|
export const clickhouse = createClient({
|
||||||
host: process.env.CLICKHOUSE_HOST,
|
host: process.env.CLICKHOUSE_HOST,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import postgres from "postgres";
|
import postgres from "postgres";
|
||||||
import { Session } from "./types";
|
import { Session } from "./types.js";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
|
import { auth } from "../../lib/auth.js";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
|
@ -14,25 +15,99 @@ export const sql = postgres({
|
||||||
|
|
||||||
export async function initializePostgres() {
|
export async function initializePostgres() {
|
||||||
try {
|
try {
|
||||||
await sql<Session[]>`
|
// Phase 1: Create tables with no dependencies
|
||||||
CREATE TABLE IF NOT EXISTS active_sessions (
|
await Promise.all([
|
||||||
session_id TEXT PRIMARY KEY,
|
sql`
|
||||||
user_id TEXT,
|
CREATE TABLE IF NOT EXISTS "user" (
|
||||||
hostname TEXT,
|
"id" text not null primary key,
|
||||||
start_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
"name" text not null,
|
||||||
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
"email" text not null unique,
|
||||||
pageviews INT DEFAULT 0,
|
"emailVerified" boolean not null,
|
||||||
entry_page TEXT,
|
"image" text,
|
||||||
exit_page TEXT,
|
"createdAt" timestamp not null,
|
||||||
device_type TEXT,
|
"updatedAt" timestamp not null
|
||||||
screen_width INT,
|
);
|
||||||
screen_height INT,
|
`,
|
||||||
browser TEXT,
|
|
||||||
operating_system TEXT,
|
sql`
|
||||||
language TEXT,
|
CREATE TABLE IF NOT EXISTS "verification" (
|
||||||
referrer TEXT
|
"id" text not null primary key,
|
||||||
);
|
"identifier" text not null,
|
||||||
`;
|
"value" text not null,
|
||||||
|
"expiresAt" timestamp not null,
|
||||||
|
"createdAt" timestamp,
|
||||||
|
"updatedAt" timestamp
|
||||||
|
);
|
||||||
|
`,
|
||||||
|
|
||||||
|
sql<Session[]>`
|
||||||
|
CREATE TABLE IF NOT EXISTS active_sessions (
|
||||||
|
session_id TEXT PRIMARY KEY,
|
||||||
|
user_id TEXT,
|
||||||
|
hostname TEXT,
|
||||||
|
start_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
pageviews INT DEFAULT 0,
|
||||||
|
entry_page TEXT,
|
||||||
|
exit_page TEXT,
|
||||||
|
device_type TEXT,
|
||||||
|
screen_width INT,
|
||||||
|
screen_height INT,
|
||||||
|
browser TEXT,
|
||||||
|
operating_system TEXT,
|
||||||
|
language TEXT,
|
||||||
|
referrer TEXT
|
||||||
|
);
|
||||||
|
`,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Phase 2: Create tables with foreign key dependencies
|
||||||
|
await Promise.all([
|
||||||
|
sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS "session" (
|
||||||
|
"id" text not null primary key,
|
||||||
|
"expiresAt" timestamp not null,
|
||||||
|
"token" text not null unique,
|
||||||
|
"createdAt" timestamp not null,
|
||||||
|
"updatedAt" timestamp not null,
|
||||||
|
"ipAddress" text,
|
||||||
|
"userAgent" text,
|
||||||
|
"userId" text not null references "user" ("id")
|
||||||
|
);
|
||||||
|
`,
|
||||||
|
|
||||||
|
sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS "account" (
|
||||||
|
"id" text not null primary key,
|
||||||
|
"accountId" text not null,
|
||||||
|
"providerId" text not null,
|
||||||
|
"userId" text not null references "user" ("id"),
|
||||||
|
"accessToken" text,
|
||||||
|
"refreshToken" text,
|
||||||
|
"idToken" text,
|
||||||
|
"accessTokenExpiresAt" timestamp,
|
||||||
|
"refreshTokenExpiresAt" timestamp,
|
||||||
|
"scope" text,
|
||||||
|
"password" text,
|
||||||
|
"createdAt" timestamp not null,
|
||||||
|
"updatedAt" timestamp not null
|
||||||
|
);
|
||||||
|
`,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const user =
|
||||||
|
await sql`SELECT count(*) FROM "user" WHERE username = 'admin'`;
|
||||||
|
if (user.length === 0) {
|
||||||
|
auth.api.signUpEmail({
|
||||||
|
body: {
|
||||||
|
email: "test@test.com",
|
||||||
|
username: "admin",
|
||||||
|
name: "admin",
|
||||||
|
password: "admin123",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Tables created successfully.");
|
console.log("Tables created successfully.");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error creating tables:", err);
|
console.error("Error creating tables:", err);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { insertSessions } from "../clickhouse/clickhouse";
|
import { insertSessions } from "../clickhouse/clickhouse.js";
|
||||||
import { sql } from "./postgres";
|
import { sql } from "./postgres.js";
|
||||||
import { Session } from "./types";
|
import { Session } from "./types.js";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
// function convertPostgresToClickhouse(postgresTimestamp: string): string {
|
// function convertPostgresToClickhouse(postgresTimestamp: string): string {
|
||||||
|
|
|
@ -1,22 +1,32 @@
|
||||||
import cors from "@fastify/cors";
|
import cors from "@fastify/cors";
|
||||||
import fastifyStatic from "@fastify/static";
|
import fastifyStatic from "@fastify/static";
|
||||||
import Fastify, { FastifyReply, FastifyRequest } from "fastify";
|
import Fastify, { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import path from "path";
|
import FastifyBetterAuth from "fastify-better-auth";
|
||||||
import { trackPageView } from "./actions/trackPageView";
|
|
||||||
import { initializeClickhouse } from "./db/clickhouse/clickhouse";
|
|
||||||
import { TrackingPayload } from "./types";
|
|
||||||
import { initializePostgres } from "./db/postgres/postgres";
|
|
||||||
import cron from "node-cron";
|
import cron from "node-cron";
|
||||||
import { cleanupOldSessions } from "./db/postgres/session-cleanup";
|
import { dirname, join } from "path";
|
||||||
import { getLiveUsercount } from "./api/getLiveUsercount";
|
import { fileURLToPath } from "url";
|
||||||
import { getCountries } from "./api/getCountries";
|
import { Headers, HeadersInit } from "undici"; // Ensure Undici is used for Headers
|
||||||
import { getOperatingSystems } from "./api/getOperatingSystems";
|
import { trackPageView } from "./actions/trackPageView.js";
|
||||||
import { getBrowsers } from "./api/getBrowsers";
|
import { getBrowsers } from "./api/getBrowsers.js";
|
||||||
import { getDevices } from "./api/getDevices";
|
import { getCountries } from "./api/getCountries.js";
|
||||||
import { getReferrers } from "./api/getReferrers";
|
import { getDevices } from "./api/getDevices.js";
|
||||||
import { getPages } from "./api/getPages";
|
import { getLiveUsercount } from "./api/getLiveUsercount.js";
|
||||||
import { getPageViews } from "./api/getPageViews";
|
import { getOperatingSystems } from "./api/getOperatingSystems.js";
|
||||||
import { getOverview } from "./api/getOverview";
|
import { getOverview } from "./api/getOverview.js";
|
||||||
|
import { getPages } from "./api/getPages.js";
|
||||||
|
import { getPageViews } from "./api/getPageViews.js";
|
||||||
|
import { getReferrers } from "./api/getReferrers.js";
|
||||||
|
import { initializeClickhouse } from "./db/clickhouse/clickhouse.js";
|
||||||
|
import { initializePostgres } from "./db/postgres/postgres.js";
|
||||||
|
import { cleanupOldSessions } from "./db/postgres/session-cleanup.js";
|
||||||
|
import { auth } from "./lib/auth.js";
|
||||||
|
import { TrackingPayload } from "./types.js";
|
||||||
|
import { toNodeHandler } from "better-auth/node";
|
||||||
|
import { mapHeaders } from "./lib/betterAuth.js";
|
||||||
|
|
||||||
|
// ESM replacement for __dirname:
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
const server = Fastify({
|
const server = Fastify({
|
||||||
logger: {
|
logger: {
|
||||||
|
@ -30,13 +40,77 @@ const server = Fastify({
|
||||||
|
|
||||||
// Register CORS
|
// Register CORS
|
||||||
server.register(cors, {
|
server.register(cors, {
|
||||||
origin: true, // In production, you should specify your frontend domain
|
origin: [
|
||||||
|
"http://localhost:3002",
|
||||||
|
"https://tracking.tomato.gg",
|
||||||
|
"https://tomato.gg",
|
||||||
|
], // In production, you should specify your frontend domain
|
||||||
|
credentials: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve static files
|
// Serve static files
|
||||||
server.register(fastifyStatic, {
|
server.register(fastifyStatic, {
|
||||||
root: path.join(__dirname, "../public"),
|
root: join(__dirname, "../public"),
|
||||||
prefix: "/", // optional: default '/'
|
prefix: "/", // or whatever prefix you need
|
||||||
|
});
|
||||||
|
|
||||||
|
server.register(
|
||||||
|
async (fastify, options) => {
|
||||||
|
await fastify.register((fastify) => {
|
||||||
|
const authHandler = toNodeHandler(options.auth);
|
||||||
|
|
||||||
|
fastify.addContentTypeParser(
|
||||||
|
"application/json",
|
||||||
|
/* c8 ignore next 3 */
|
||||||
|
(_request, _payload, done) => {
|
||||||
|
done(null, null);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
fastify.all("/api/auth/*", async (request, reply: any) => {
|
||||||
|
reply.raw.setHeaders(mapHeaders(reply.getHeaders()));
|
||||||
|
await authHandler(request.raw, reply.raw);
|
||||||
|
});
|
||||||
|
fastify.all("/auth/*", async (request, reply: any) => {
|
||||||
|
reply.raw.setHeaders(mapHeaders(reply.getHeaders()));
|
||||||
|
await authHandler(request.raw, reply.raw);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ auth }
|
||||||
|
);
|
||||||
|
|
||||||
|
server.addHook("onRequest", async (request, reply) => {
|
||||||
|
const { url } = request.raw;
|
||||||
|
|
||||||
|
// Bypass auth for health check and tracking
|
||||||
|
if (
|
||||||
|
url?.startsWith("/health") ||
|
||||||
|
url?.startsWith("/track/pageview") ||
|
||||||
|
url?.startsWith("/analytics") ||
|
||||||
|
url?.startsWith("/auth") ||
|
||||||
|
url?.startsWith("/api/auth")
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Convert Fastify headers object into Fetch-compatible Headers
|
||||||
|
const headers = new Headers(request.headers as HeadersInit);
|
||||||
|
|
||||||
|
// Get session from BetterAuth
|
||||||
|
const session = await auth.api.getSession({ headers });
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
return reply.status(401).send({ error: "Unauthorized" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach session user info to request
|
||||||
|
request.user = session.user;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Auth Error:", err);
|
||||||
|
return reply.status(500).send({ error: "Auth check failed" });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Health check endpoint
|
// Health check endpoint
|
||||||
|
@ -95,3 +169,9 @@ const start = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
start();
|
start();
|
||||||
|
|
||||||
|
declare module "fastify" {
|
||||||
|
interface FastifyRequest {
|
||||||
|
user?: any; // Or define a more specific user type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
27
server/src/lib/auth.ts
Normal file
27
server/src/lib/auth.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { betterAuth } from "better-auth";
|
||||||
|
import pg from "pg";
|
||||||
|
import { username } from "better-auth/plugins";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
basePath: "/auth",
|
||||||
|
database: new pg.Pool({
|
||||||
|
host: process.env.POSTGRES_HOST || "postgres",
|
||||||
|
port: parseInt(process.env.POSTGRES_PORT || "5432", 10),
|
||||||
|
database: process.env.POSTGRES_DB,
|
||||||
|
user: process.env.POSTGRES_USER,
|
||||||
|
password: process.env.POSTGRES_PASSWORD,
|
||||||
|
}),
|
||||||
|
emailAndPassword: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
plugins: [username()],
|
||||||
|
trustedOrigins: [
|
||||||
|
"http://localhost:3002",
|
||||||
|
"http://localhost:3001",
|
||||||
|
"https://tracking.tomato.gg",
|
||||||
|
"https://tomato.gg",
|
||||||
|
],
|
||||||
|
});
|
10
server/src/lib/betterAuth.ts
Normal file
10
server/src/lib/betterAuth.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export function mapHeaders(headers: any) {
|
||||||
|
const entries = Object.entries(headers);
|
||||||
|
const map = new Map();
|
||||||
|
for (const [headerKey, headerValue] of entries) {
|
||||||
|
if (headerValue != null) {
|
||||||
|
map.set(headerKey, headerValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"module": "commonjs",
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
"lib": [
|
"lib": [
|
||||||
"ES2020"
|
"ES2020"
|
||||||
],
|
],
|
||||||
|
@ -11,7 +12,6 @@
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue