mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-04-19 10:51:20 +00:00
Nostr login flow
This commit is contained in:
parent
2de8b23d07
commit
1f9d76d783
@ -21,6 +21,8 @@ services:
|
||||
- .env
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- .:/app
|
||||
links:
|
||||
- db
|
||||
|
||||
|
292
package-lock.json
generated
292
package-lock.json
generated
@ -8,9 +8,12 @@
|
||||
"name": "plebdevs-new",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.9.1",
|
||||
"@reduxjs/toolkit": "^2.1.0",
|
||||
"axios": "^1.6.7",
|
||||
"next": "14.0.4",
|
||||
"next-auth": "^4.24.5",
|
||||
"nostr-tools": "^2.1.5",
|
||||
"primeicons": "^6.0.1",
|
||||
"primereact": "^10.2.1",
|
||||
"react": "^18",
|
||||
@ -23,6 +26,7 @@
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.0.4",
|
||||
"postcss": "^8",
|
||||
"prisma": "^5.9.1",
|
||||
"tailwindcss": "^3.3.0"
|
||||
}
|
||||
},
|
||||
@ -388,6 +392,47 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/ciphers": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.2.0.tgz",
|
||||
"integrity": "sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/curves": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
|
||||
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.3.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/curves/node_modules/@noble/hashes": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
|
||||
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
|
||||
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@ -441,6 +486,68 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "5.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.9.1.tgz",
|
||||
"integrity": "sha512-caSOnG4kxcSkhqC/2ShV7rEoWwd3XrftokxJqOCMVvia4NYV/TPtJlS9C2os3Igxw/Qyxumj9GBQzcStzECvtQ==",
|
||||
"hasInstallScript": true,
|
||||
"engines": {
|
||||
"node": ">=16.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prisma": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"prisma": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/debug": {
|
||||
"version": "5.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.9.1.tgz",
|
||||
"integrity": "sha512-yAHFSFCg8KVoL0oRUno3m60GAjsUKYUDkQ+9BA2X2JfVR3kRVSJFc/GpQ2fSORi4pSHZR9orfM4UC9OVXIFFTA==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "5.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.9.1.tgz",
|
||||
"integrity": "sha512-gkdXmjxQ5jktxWNdDA5aZZ6R8rH74JkoKq6LD5mACSvxd2vbqWeWIOV0Py5wFC8vofOYShbt6XUeCIUmrOzOnQ==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.9.1",
|
||||
"@prisma/engines-version": "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64",
|
||||
"@prisma/fetch-engine": "5.9.1",
|
||||
"@prisma/get-platform": "5.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines-version": {
|
||||
"version": "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64.tgz",
|
||||
"integrity": "sha512-HFl7275yF0FWbdcNvcSRbbu9JCBSLMcurYwvWc8WGDnpu7APxQo2ONtZrUggU3WxLxUJ2uBX+0GOFIcJeVeOOQ==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@prisma/fetch-engine": {
|
||||
"version": "5.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.9.1.tgz",
|
||||
"integrity": "sha512-l0goQOMcNVOJs1kAcwqpKq3ylvkD9F04Ioe1oJoCqmz05mw22bNAKKGWuDd3zTUoUZr97va0c/UfLNru+PDmNA==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.9.1",
|
||||
"@prisma/engines-version": "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64",
|
||||
"@prisma/get-platform": "5.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/get-platform": {
|
||||
"version": "5.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.9.1.tgz",
|
||||
"integrity": "sha512-6OQsNxTyhvG+T2Ksr8FPFpuPeL4r9u0JF0OZHUBI/Uy9SS43sPyAIutt4ZEAyqWQt104ERh70EZedkHZKsnNbg==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.1.0.tgz",
|
||||
@ -470,6 +577,53 @@
|
||||
"integrity": "sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@scure/base": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
|
||||
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/@scure/bip32": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz",
|
||||
"integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==",
|
||||
"dependencies": {
|
||||
"@noble/curves": "~1.1.0",
|
||||
"@noble/hashes": "~1.3.1",
|
||||
"@scure/base": "~1.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@scure/bip32/node_modules/@noble/curves": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
|
||||
"integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.3.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@scure/bip39": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
|
||||
"integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "~1.3.0",
|
||||
"@scure/base": "~1.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz",
|
||||
@ -871,6 +1025,11 @@
|
||||
"has-symbols": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.16",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",
|
||||
@ -929,6 +1088,16 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
|
||||
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.4",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
|
||||
@ -1147,6 +1316,17 @@
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||
@ -1261,6 +1441,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
@ -2007,6 +2195,25 @@
|
||||
"integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||
@ -2032,6 +2239,19 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||
@ -2985,6 +3205,25 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@ -3179,6 +3418,36 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nostr-tools": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.1.5.tgz",
|
||||
"integrity": "sha512-Gug/j54YGQ0ewB09dZW3mS9qfXWFlcOQMlyb1MmqQsuNO/95mfNOQSBi+jZ61O++Y+jG99SzAUPFLopUsKf0MA==",
|
||||
"dependencies": {
|
||||
"@noble/ciphers": "0.2.0",
|
||||
"@noble/curves": "1.2.0",
|
||||
"@noble/hashes": "1.3.1",
|
||||
"@scure/base": "1.1.1",
|
||||
"@scure/bip32": "1.3.1",
|
||||
"@scure/bip39": "1.2.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"nostr-wasm": "v0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/nostr-wasm": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz",
|
||||
"integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/oauth": {
|
||||
"version": "0.9.15",
|
||||
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
|
||||
@ -3717,6 +3986,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "5.9.1",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.9.1.tgz",
|
||||
"integrity": "sha512-Hy/8KJZz0ELtkw4FnG9MS9rNWlXcJhf98Z2QMqi0QiVMoS8PzsBkpla0/Y5hTlob8F3HeECYphBjqmBxrluUrQ==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/engines": "5.9.1"
|
||||
},
|
||||
"bin": {
|
||||
"prisma": "build/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.13"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
@ -3727,6 +4012,11 @@
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@ -4648,7 +4938,7 @@
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
|
@ -9,9 +9,12 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.9.1",
|
||||
"@reduxjs/toolkit": "^2.1.0",
|
||||
"axios": "^1.6.7",
|
||||
"next": "14.0.4",
|
||||
"next-auth": "^4.24.5",
|
||||
"nostr-tools": "^2.1.5",
|
||||
"primeicons": "^6.0.1",
|
||||
"primereact": "^10.2.1",
|
||||
"react": "^18",
|
||||
@ -24,6 +27,7 @@
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.0.4",
|
||||
"postcss": "^8",
|
||||
"prisma": "^5.9.1",
|
||||
"tailwindcss": "^3.3.0"
|
||||
}
|
||||
}
|
||||
|
87
prisma/migrations/20240211045317_init/migration.sql
Normal file
87
prisma/migrations/20240211045317_init/migration.sql
Normal file
@ -0,0 +1,87 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"pubkey" TEXT NOT NULL,
|
||||
"username" TEXT,
|
||||
"roleId" INTEGER,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Role" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"subscribed" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
CONSTRAINT "Role_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Purchase" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"courseId" INTEGER,
|
||||
"resourceId" INTEGER,
|
||||
"amountPaid" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Purchase_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Course" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"image" TEXT NOT NULL,
|
||||
"isFree" BOOLEAN NOT NULL DEFAULT false,
|
||||
"noteId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Course_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Resource" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"image" TEXT,
|
||||
"courseId" INTEGER,
|
||||
"noteId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Resource_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_pubkey_key" ON "User"("pubkey");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Course_noteId_key" ON "Course"("noteId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Resource_noteId_key" ON "Resource"("noteId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "User" ADD CONSTRAINT "User_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "Role"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Purchase" ADD CONSTRAINT "Purchase_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Purchase" ADD CONSTRAINT "Purchase_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Purchase" ADD CONSTRAINT "Purchase_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "Resource"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Resource" ADD CONSTRAINT "Resource_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
@ -3,9 +3,13 @@ datasource db {
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
npub String @unique
|
||||
pubkey String @unique
|
||||
username String? @unique
|
||||
purchased Purchase[]
|
||||
role Role? @relation(fields: [roleId], references: [id])
|
||||
|
@ -1,19 +1,27 @@
|
||||
import React from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Menubar } from 'primereact/menubar';
|
||||
import { useSelector } from 'react-redux';
|
||||
import 'primereact/resources/primereact.min.css';
|
||||
import 'primeicons/primeicons.css';
|
||||
import styles from './navbar.module.css';
|
||||
|
||||
const Navbar = () => {
|
||||
const router = useRouter();
|
||||
const user = useSelector((state) => state.user);
|
||||
|
||||
const end = (
|
||||
(user && user?.username || user.pubkey) ?
|
||||
<h1>{user.username || user.pubkey}</h1>
|
||||
:
|
||||
<Button
|
||||
label={"Login"}
|
||||
icon="pi pi-user"
|
||||
className="text-[#f8f8ff]"
|
||||
rounded
|
||||
onClick={() => router.push('/login')}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -29,6 +29,21 @@ export const getUserById = async (id) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getUserByPubkey = async (pubkey) => {
|
||||
return await prisma.user.findUnique({
|
||||
where: { pubkey },
|
||||
include: {
|
||||
role: true, // Include related role
|
||||
purchased: {
|
||||
include: {
|
||||
course: true, // Include course details in purchases
|
||||
resource: true, // Include resource details in purchases
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const createUser = async (data) => {
|
||||
return await prisma.user.create({
|
||||
data,
|
||||
|
106
src/hooks/useNostr.js
Normal file
106
src/hooks/useNostr.js
Normal file
@ -0,0 +1,106 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { SimplePool, relayInit, nip19 } from "nostr-tools";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { initialRelays } from "@/redux/reducers/userReducer";
|
||||
|
||||
export const useNostr = () => {
|
||||
const [relays, setRelays] = useState(initialRelays);
|
||||
const [relayStatuses, setRelayStatuses] = useState({});
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const pool = useRef(new SimplePool({ seenOnEnabled: true }));
|
||||
const subscriptions = useRef([]);
|
||||
|
||||
const getRelayStatuses = () => {
|
||||
if (pool.current && pool.current._conn) {
|
||||
const statuses = {};
|
||||
|
||||
for (const url in pool.current._conn) {
|
||||
const relay = pool.current._conn[url];
|
||||
statuses[url] = relay.status; // Assuming 'status' is an accessible field in Relay object
|
||||
}
|
||||
|
||||
setRelayStatuses(statuses);
|
||||
}
|
||||
};
|
||||
|
||||
const updateRelays = async (newRelays) => {
|
||||
// Set new relays
|
||||
setRelays(newRelays);
|
||||
|
||||
// Ensure the relays are connected before using them
|
||||
await Promise.all(newRelays.map(relay => pool.current.ensureRelay(relay)));
|
||||
};
|
||||
|
||||
const fetchKind0 = async (criteria, params) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const events = [];
|
||||
const timeoutDuration = 1000;
|
||||
|
||||
const sub = pool.current.subscribeMany(relays, criteria, {
|
||||
...params,
|
||||
onevent: (event) => {
|
||||
events.push(event);
|
||||
},
|
||||
onerror: (error) => {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Set a timeout to sort and resolve with the most recent event
|
||||
setTimeout(() => {
|
||||
if (events.length === 0) {
|
||||
resolve(null); // or reject based on your needs
|
||||
} else {
|
||||
events.sort((a, b) => b.created_at - a.created_at); // Sort in descending order
|
||||
const mostRecentEventContent = JSON.parse(events[0].content);
|
||||
resolve(mostRecentEventContent);
|
||||
}
|
||||
}, timeoutDuration);
|
||||
});
|
||||
};
|
||||
|
||||
const fetchSingleEvent = async (id) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sub = pool.current.subscribeMany(relays, [{ ids: [id] }]);
|
||||
|
||||
sub.on("event", (event) => {
|
||||
resolve(event);
|
||||
});
|
||||
|
||||
sub.on("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const publishEvent = async (event) => {
|
||||
try {
|
||||
const publishPromises = pool.current.publish(relays, event);
|
||||
await Promise.all(publishPromises);
|
||||
} catch (error) {
|
||||
console.error("Error publishing event:", error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getRelayStatuses(); // Get initial statuses on mount
|
||||
|
||||
// Copy current subscriptions to a local variable inside the effect
|
||||
const currentSubscriptions = subscriptions.current;
|
||||
|
||||
return () => {
|
||||
// Use the local variable in the cleanup function
|
||||
currentSubscriptions.forEach((sub) => sub.unsub());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
updateRelays,
|
||||
fetchSingleEvent,
|
||||
publishEvent,
|
||||
fetchKind0,
|
||||
getRelayStatuses,
|
||||
};
|
||||
};
|
21
src/hooks/useToast.js
Normal file
21
src/hooks/useToast.js
Normal file
@ -0,0 +1,21 @@
|
||||
import React, { createContext, useContext, useRef } from 'react';
|
||||
import { Toast } from 'primereact/toast';
|
||||
|
||||
const ToastContext = createContext();
|
||||
|
||||
export const useToast = () => useContext(ToastContext);
|
||||
|
||||
export const ToastProvider = ({ children }) => {
|
||||
const toast = useRef(null);
|
||||
|
||||
const showToast = (severity, summary, detail) => {
|
||||
toast.current.show({ severity, summary, detail });
|
||||
};
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={{ showToast }}>
|
||||
<Toast ref={toast} />
|
||||
{children}
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
};
|
@ -2,6 +2,7 @@ import { PrimeReactProvider } from 'primereact/api';
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "@/redux/store";
|
||||
import Navbar from '@/components/navbar/Navbar';
|
||||
import { ToastProvider } from '@/hooks/useToast';
|
||||
import '@/styles/globals.css'
|
||||
import 'primereact/resources/themes/lara-dark-indigo/theme.css';
|
||||
|
||||
@ -11,8 +12,10 @@ export default function MyApp({
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<PrimeReactProvider>
|
||||
<Navbar />
|
||||
<Component {...pageProps} />
|
||||
<ToastProvider>
|
||||
<Navbar />
|
||||
<Component {...pageProps} />
|
||||
</ToastProvider>
|
||||
</PrimeReactProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
@ -1,36 +1,59 @@
|
||||
import { getUserById, updateUser, deleteUser } from "@/db/models/userModels";
|
||||
import { getUserById, getUserByPubkey, updateUser, deleteUser } from "@/db/models/userModels";
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { slug } = req.query;
|
||||
|
||||
if (req.method === 'GET') {
|
||||
try {
|
||||
const user = await getUserById(parseInt(slug));
|
||||
if (user) {
|
||||
res.status(200).json(user);
|
||||
} else {
|
||||
res.status(404).json({ error: 'User not found' });
|
||||
// Determine if slug is a pubkey or an ID
|
||||
const isPubkey = /^[0-9a-fA-F]{64}$/.test(slug);
|
||||
|
||||
try {
|
||||
let user;
|
||||
if (isPubkey) {
|
||||
console.log('is pub', slug);
|
||||
// If slug is a pubkey
|
||||
user = await getUserByPubkey(slug);
|
||||
} else {
|
||||
// Assume slug is an ID
|
||||
const id = parseInt(slug);
|
||||
if (isNaN(id)) {
|
||||
return res.status(400).json({ error: "Invalid identifier" });
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
user = await getUserById(id);
|
||||
}
|
||||
} else if (req.method === 'PUT') {
|
||||
try {
|
||||
const user = await updateUser(parseInt(slug), req.body);
|
||||
res.status(200).json(user);
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: error.message });
|
||||
|
||||
if (!user) {
|
||||
return res.status(204).end();
|
||||
}
|
||||
} else if (req.method === 'DELETE') {
|
||||
try {
|
||||
await deleteUser(parseInt(slug));
|
||||
res.status(204).end();
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
|
||||
switch (req.method) {
|
||||
case 'GET':
|
||||
return res.status(200).json(user);
|
||||
|
||||
case 'PUT':
|
||||
if (!isPubkey) {
|
||||
// Update operation should be done with an ID, not a pubkey
|
||||
const updatedUser = await updateUser(parseInt(slug), req.body);
|
||||
return res.status(200).json(updatedUser);
|
||||
} else {
|
||||
// Handle attempt to update user with pubkey
|
||||
return res.status(400).json({ error: "Cannot update user with pubkey. Use ID instead." });
|
||||
}
|
||||
|
||||
case 'DELETE':
|
||||
if (!isPubkey) {
|
||||
// Delete operation should be done with an ID, not a pubkey
|
||||
await deleteUser(parseInt(slug));
|
||||
return res.status(204).end();
|
||||
} else {
|
||||
// Handle attempt to delete user with pubkey
|
||||
return res.status(400).json({ error: "Cannot delete user with pubkey. Use ID instead." });
|
||||
}
|
||||
|
||||
default:
|
||||
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
|
||||
return res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||
}
|
||||
} else {
|
||||
// Handle any other HTTP method
|
||||
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
|
||||
res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
83
src/pages/login.js
Normal file
83
src/pages/login.js
Normal file
@ -0,0 +1,83 @@
|
||||
import React from "react";
|
||||
import axios from "axios";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useRouter } from "next/router";
|
||||
import { Button } from 'primereact/button';
|
||||
import { useToast } from "@/hooks/useToast";
|
||||
import { useNostr } from "@/hooks/useNostr";
|
||||
import { findKind0Username } from "@/utils/nostr";
|
||||
import { setPubkey, setUsername } from "@/redux/reducers/userReducer";
|
||||
|
||||
const Login = () => {
|
||||
const dispatch = useDispatch();
|
||||
const router = useRouter();
|
||||
const { showToast } = useToast();
|
||||
|
||||
const nostrLogin = async () => {
|
||||
try {
|
||||
if (!window || !window.nostr) {
|
||||
throw new Error('Nostr is not available');
|
||||
}
|
||||
|
||||
const publicKey = await window.nostr.getPublicKey();
|
||||
if (!publicKey) {
|
||||
throw new Error('Failed to obtain public key');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get(`/api/users/${publicKey}`);
|
||||
if (response.status === 200 && response.data) {
|
||||
dispatch(setPubkey(publicKey));
|
||||
if (response.data.username) {
|
||||
dispatch(setUsername(response.data.username));
|
||||
}
|
||||
router.push('/');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response?.status !== 204) {
|
||||
throw error; // Rethrow error if it's not the expected 204 status
|
||||
}
|
||||
|
||||
// Handle user creation if status is 204 (No Content)
|
||||
const kind0 = await fetchKind0([{ authors: [publicKey], kinds: [0] }], {});
|
||||
const username = kind0 ? await findKind0Username(kind0) : undefined;
|
||||
const payload = { pubkey: publicKey, ...(username && { username }) };
|
||||
|
||||
const createUserResponse = await axios.post(`/api/users`, payload);
|
||||
if (createUserResponse.status === 201) {
|
||||
dispatch(setPubkey(publicKey));
|
||||
if (username) {
|
||||
dispatch(setUsername(username));
|
||||
}
|
||||
router.push('/');
|
||||
} else {
|
||||
showToast('error', 'Error', 'User not created');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('error', 'Error', error.message || 'An unexpected error occurred');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-fit mx-auto mt-24 flex flex-col justify-center">
|
||||
<h1 className="text-center mb-8">Login</h1>
|
||||
<Button
|
||||
label={"login with nostr"}
|
||||
icon="pi pi-user"
|
||||
className="text-[#f8f8ff] w-[250px] my-4"
|
||||
rounded
|
||||
onClick={nostrLogin}
|
||||
/>
|
||||
<Button
|
||||
label={"login anonymously"}
|
||||
icon="pi pi-user"
|
||||
className="text-[#f8f8ff] w-[250px] my-4"
|
||||
rounded
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Login;
|
@ -1,6 +1,6 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const initialRelays = [
|
||||
export const initialRelays = [
|
||||
"wss://nos.lol/",
|
||||
"wss://relay.damus.io/",
|
||||
"wss://relay.snort.social/",
|
||||
@ -14,6 +14,7 @@ export const userSlice = createSlice({
|
||||
name: "user",
|
||||
initialState: {
|
||||
pubkey: '',
|
||||
username: '',
|
||||
relays: initialRelays,
|
||||
},
|
||||
reducers: {
|
||||
@ -22,10 +23,13 @@ export const userSlice = createSlice({
|
||||
},
|
||||
setPubkey: (state, action) => {
|
||||
state.pubkey = action.payload;
|
||||
}
|
||||
},
|
||||
setUsername: (state, action) => {
|
||||
state.username = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setRelays, setPubkey } = userSlice.actions;
|
||||
export const { setRelays, setPubkey, setUsername } = userSlice.actions;
|
||||
|
||||
export default userSlice.reducer;
|
16
src/utils/nostr.js
Normal file
16
src/utils/nostr.js
Normal file
@ -0,0 +1,16 @@
|
||||
export const findKind0Username = async (kind0) => {
|
||||
const usernameProperties = ['name', 'displayName', 'display_name', 'username', 'handle', 'alias'];
|
||||
|
||||
const findTruthyPropertyValue = (object, properties) => {
|
||||
for (const property of properties) {
|
||||
if (object[property]) {
|
||||
return object[property];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const username = findTruthyPropertyValue(kind0, usernameProperties);
|
||||
|
||||
return username;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user