From bde5e117cfba9fa99601aa0f8a981d427988c781 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Mon, 17 Feb 2025 12:50:32 -0600 Subject: [PATCH] Consolidate user fields, differentiate platform lightning address and nip05, fix millisats on lightning address, small user management and signup ux fixes --- .../migration.sql | 63 +++++++++++++++++ .../migration.sql | 10 +++ .../migration.sql | 5 ++ prisma/schema.prisma | 18 +++-- .../content/courses/CombinedLesson.js | 2 +- .../content/courses/CourseLesson.js | 2 +- .../content/courses/DocumentLesson.js | 2 +- .../content/courses/DraftCourseLesson.js | 2 +- src/components/content/courses/VideoLesson.js | 2 +- src/components/navbar/user/UserAvatar.js | 2 +- src/components/profile/UserAccountLinking.js | 9 ++- src/components/profile/UserProfileCard.js | 68 ++++++++++++++----- .../subscription/LightningAddressForm.js | 48 +++++++------ .../profile/subscription/Nip05Form.js | 12 ++-- .../profile/subscription/SubscribeModal.js | 13 ++-- .../profile/subscription/UserSubscription.js | 11 ++- src/db/models/lightningAddressModels.js | 12 ++-- src/db/models/nip05Models.js | 12 ++-- src/db/models/userModels.js | 12 ++-- src/pages/api/auth/[...nextauth].js | 58 ++++++++++------ .../api/lightning-address/callback/[slug].js | 13 ++-- .../api/lightning-address/lnurlp/[slug].js | 4 +- .../api/users/[slug]/lightning-address.js | 18 ++++- src/pages/auth/signin.js | 14 ++-- src/pages/draft/[slug]/index.js | 2 +- src/pages/subscribe.js | 27 ++------ src/utils/nostr.js | 6 ++ 27 files changed, 298 insertions(+), 149 deletions(-) create mode 100644 prisma/migrations/20250215184313_add_platform_lnaddress_and_nip05/migration.sql create mode 100644 prisma/migrations/20250215194519_consolidate_user_fields/migration.sql create mode 100644 prisma/migrations/20250217173901_millisat_bigint/migration.sql diff --git a/prisma/migrations/20250215184313_add_platform_lnaddress_and_nip05/migration.sql b/prisma/migrations/20250215184313_add_platform_lnaddress_and_nip05/migration.sql new file mode 100644 index 0000000..97a7ce4 --- /dev/null +++ b/prisma/migrations/20250215184313_add_platform_lnaddress_and_nip05/migration.sql @@ -0,0 +1,63 @@ +/* + Warnings: + + - You are about to drop the `LightningAddress` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `Nip05` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "LightningAddress" DROP CONSTRAINT "LightningAddress_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "Nip05" DROP CONSTRAINT "Nip05_userId_fkey"; + +-- AlterTable +ALTER TABLE "User" ADD COLUMN "lud16" TEXT, +ADD COLUMN "nip05" TEXT; + +-- DropTable +DROP TABLE "LightningAddress"; + +-- DropTable +DROP TABLE "Nip05"; + +-- CreateTable +CREATE TABLE "PlatformNip05" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "pubkey" TEXT NOT NULL, + "name" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "PlatformNip05_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "PlatformLightningAddress" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "allowsNostr" BOOLEAN NOT NULL DEFAULT true, + "description" TEXT, + "maxSendable" INTEGER NOT NULL DEFAULT 10000000, + "minSendable" INTEGER NOT NULL DEFAULT 1, + "invoiceMacaroon" TEXT NOT NULL, + "lndCert" TEXT, + "lndHost" TEXT NOT NULL, + "lndPort" TEXT NOT NULL DEFAULT '8080', + + CONSTRAINT "PlatformLightningAddress_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "PlatformNip05_userId_key" ON "PlatformNip05"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "PlatformLightningAddress_userId_key" ON "PlatformLightningAddress"("userId"); + +-- AddForeignKey +ALTER TABLE "PlatformNip05" ADD CONSTRAINT "PlatformNip05_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PlatformLightningAddress" ADD CONSTRAINT "PlatformLightningAddress_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20250215194519_consolidate_user_fields/migration.sql b/prisma/migrations/20250215194519_consolidate_user_fields/migration.sql new file mode 100644 index 0000000..d9be833 --- /dev/null +++ b/prisma/migrations/20250215194519_consolidate_user_fields/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - You are about to drop the column `image` on the `User` table. All the data in the column will be lost. + - You are about to drop the column `name` on the `User` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "User" DROP COLUMN "image", +DROP COLUMN "name"; diff --git a/prisma/migrations/20250217173901_millisat_bigint/migration.sql b/prisma/migrations/20250217173901_millisat_bigint/migration.sql new file mode 100644 index 0000000..94cde92 --- /dev/null +++ b/prisma/migrations/20250217173901_millisat_bigint/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "PlatformLightningAddress" ALTER COLUMN "maxSendable" SET DEFAULT 10000000000, +ALTER COLUMN "maxSendable" SET DATA TYPE BIGINT, +ALTER COLUMN "minSendable" SET DEFAULT 1000, +ALTER COLUMN "minSendable" SET DATA TYPE BIGINT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8e917db..23486b9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -13,15 +13,12 @@ generator client { provider = "prisma-client-js" } -// todo name and username? model User { id String @id @default(uuid()) pubkey String? @unique privkey String? - name String? email String? @unique emailVerified DateTime? - image String? username String? @unique avatar String? purchased Purchase[] @@ -36,8 +33,10 @@ model User { updatedAt DateTime @updatedAt userLessons UserLesson[] userCourses UserCourse[] - nip05 Nip05? - lightningAddress LightningAddress? + nip05 String? + lud16 String? + platformNip05 PlatformNip05? + platformLightningAddress PlatformLightningAddress? userBadges UserBadge[] } @@ -226,7 +225,7 @@ model UserCourse { @@unique([userId, courseId]) } -model Nip05 { +model PlatformNip05 { id String @id @default(uuid()) userId String @unique user User @relation(fields: [userId], references: [id]) @@ -236,16 +235,15 @@ model Nip05 { updatedAt DateTime @updatedAt } -model LightningAddress { +model PlatformLightningAddress { id String @id @default(uuid()) userId String @unique user User @relation(fields: [userId], references: [id]) name String allowsNostr Boolean @default(true) description String? - // todo: change to BigInt to support native millisats - maxSendable Int @default(10000000) - minSendable Int @default(1) + maxSendable BigInt @default(10000000000) + minSendable BigInt @default(1000) invoiceMacaroon String lndCert String? lndHost String diff --git a/src/components/content/courses/CombinedLesson.js b/src/components/content/courses/CombinedLesson.js index 96149a2..ce06358 100644 --- a/src/components/content/courses/CombinedLesson.js +++ b/src/components/content/courses/CombinedLesson.js @@ -213,7 +213,7 @@ const CombinedLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple

By{' '} - {lesson.author?.username || lesson.author?.name || lesson.author?.pubkey} + {lesson.author?.username || lesson.author?.pubkey}

diff --git a/src/components/content/courses/CourseLesson.js b/src/components/content/courses/CourseLesson.js index a0a1c1f..2b04dd8 100644 --- a/src/components/content/courses/CourseLesson.js +++ b/src/components/content/courses/CourseLesson.js @@ -86,7 +86,7 @@ const CourseLesson = ({ lesson, course, decryptionPerformed, isPaid }) => {

Created by{' '} - {lesson.author?.username || lesson.author?.name || lesson.author?.pubkey} + {lesson.author?.username || lesson.author?.pubkey}

diff --git a/src/components/content/courses/DocumentLesson.js b/src/components/content/courses/DocumentLesson.js index 4c96b4d..bd98df4 100644 --- a/src/components/content/courses/DocumentLesson.js +++ b/src/components/content/courses/DocumentLesson.js @@ -127,7 +127,7 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple

By{' '} - {lesson.author?.username || lesson.author?.name || lesson.author?.pubkey} + {lesson.author?.username || lesson.author?.pubkey}

diff --git a/src/components/content/courses/DraftCourseLesson.js b/src/components/content/courses/DraftCourseLesson.js index 970f356..8638837 100644 --- a/src/components/content/courses/DraftCourseLesson.js +++ b/src/components/content/courses/DraftCourseLesson.js @@ -78,7 +78,7 @@ const DraftCourseLesson = ({ lesson, course }) => {

Created by{' '} - {lesson.author?.username || lesson.author?.name || lesson.author?.pubkey} + {lesson.author?.username || lesson.author?.pubkey}

diff --git a/src/components/content/courses/VideoLesson.js b/src/components/content/courses/VideoLesson.js index 455126e..d2b9fb8 100644 --- a/src/components/content/courses/VideoLesson.js +++ b/src/components/content/courses/VideoLesson.js @@ -189,7 +189,7 @@ const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted

Created by{' '} - {lesson.author?.username || lesson.author?.name || lesson.author?.pubkey} + {lesson.author?.username || lesson.author?.pubkey}

diff --git a/src/components/navbar/user/UserAvatar.js b/src/components/navbar/user/UserAvatar.js index f25c1fb..4d056f7 100644 --- a/src/components/navbar/user/UserAvatar.js +++ b/src/components/navbar/user/UserAvatar.js @@ -56,7 +56,7 @@ const UserAvatar = () => { return null; // Or return a loader/spinner/placeholder } else if (user && Object.keys(user).length > 0) { // User exists, show username or pubkey - const displayName = user.username || user?.name || user?.email || user?.pubkey.slice(0, 10) + '...' || "Anon"; + const displayName = user?.username || user?.email || user?.pubkey?.slice(0, 10) + '...' || "Anon"; const items = [ { diff --git a/src/components/profile/UserAccountLinking.js b/src/components/profile/UserAccountLinking.js index 13339e6..398b044 100644 --- a/src/components/profile/UserAccountLinking.js +++ b/src/components/profile/UserAccountLinking.js @@ -79,6 +79,9 @@ const LinkAccountsCard = ({ session }) => { }); if (response.ok) { + // clear the local storage of the users ephemeral keys + localStorage.removeItem('anonymousPrivkey'); + localStorage.removeItem('anonymousPubkey'); showToast('success', 'Success', 'Nostr account linked successfully'); // Refresh the session to get updated user data await update(); @@ -181,7 +184,7 @@ const LinkAccountsCard = ({ session }) => { icon="pi pi-github" onClick={handleGithubLink} disabled={isGithubLinked} - className={`text-[#f8f8ff] w-[250px] mx-auto`} + className={`text-[#f8f8ff] w-[250px]`} rounded /> @@ -190,7 +193,7 @@ const LinkAccountsCard = ({ session }) => { icon={Nostr} onClick={handleNostrLink} disabled={isNostrLinked} - className={`text-[#f8f8ff] w-[250px] mx-auto flex items-center justify-center`} + className={`text-[#f8f8ff] w-[250px]`} rounded /> @@ -199,7 +202,7 @@ const LinkAccountsCard = ({ session }) => { icon="pi pi-envelope" onClick={handleEmailLink} disabled={isEmailLinked} - className={`text-[#f8f8ff] w-[250px] mx-auto`} + className={`text-[#f8f8ff] w-[250px]`} rounded /> diff --git a/src/components/profile/UserProfileCard.js b/src/components/profile/UserProfileCard.js index 386046a..af37210 100644 --- a/src/components/profile/UserProfileCard.js +++ b/src/components/profile/UserProfileCard.js @@ -70,7 +70,7 @@ const UserProfileCard = ({ user }) => { className="rounded-full m-2 mt-0 object-cover max-w-[100px] max-h-[100px]" />

- {user.username || user?.name || user?.email || "Anon"} + {user.username || user?.email || "Anon"}

{ @@ -98,13 +98,17 @@ const UserProfileCard = ({ user }) => {
- {user?.lightningAddress ? ( + {user?.platformLightningAddress ? (

- Lightning Address: {user.lightningAddress.name}@plebdevs.com copyToClipboard(user.lightningAddress.name + "@plebdevs.com")} /> + Lightning Address: {user.platformLightningAddress.name}@plebdevs.com copyToClipboard(user.platformLightningAddress.name + "@plebdevs.com")} /> +

+ ) : user?.lud16 ? ( +

+ Lightning Address: {user.lud16} copyToClipboard(user.lud16)} />

) : (
-

+

Lightning Address: None

{ />
)} - {user?.nip05 ? ( + {user?.platformNip05 ? (

- NIP-05: {user.nip05.name}@plebdevs.com copyToClipboard(user.nip05.name + "@plebdevs.com")} /> + NIP-05:{' '} + {user.platformNip05.name}@plebdevs.com{' '} + copyToClipboard(`${user.platformNip05.name}@plebdevs.com`)} + /> +

+ ) : user?.nip05 ? ( +

+ NIP-05:{' '} + {user.nip05}{' '} + copyToClipboard(user.nip05)} + />

) : (
@@ -125,9 +143,9 @@ const UserProfileCard = ({ user }) => { NIP-05: None
@@ -169,7 +187,7 @@ const UserProfileCard = ({ user }) => { />

- {user.username || user?.name || user?.email || "Anon"} + {user.username || user?.email || "Anon"}

{ user?.pubkey && ( @@ -193,10 +211,14 @@ const UserProfileCard = ({ user }) => { )}
-
- {user?.lightningAddress ? ( -

- Lightning Address: {user.lightningAddress.name}@plebdevs.com copyToClipboard(user.lightningAddress.name + "@plebdevs.com")} /> +
+ {user?.platformLightningAddress ? ( +

+ Lightning Address: {user.platformLightningAddress.name}@plebdevs.com copyToClipboard(user.platformLightningAddress.name + "@plebdevs.com")} /> +

+ ) : user?.lud16 ? ( +

+ Lightning Address: {user.lud16} copyToClipboard(user.lud16)} />

) : (
@@ -211,9 +233,23 @@ const UserProfileCard = ({ user }) => { />
)} - {user?.nip05 ? ( + {user?.platformNip05 ? (

- NIP-05: {user.nip05.name}@plebdevs.com copyToClipboard(user.nip05.name + "@plebdevs.com")} /> + NIP-05:{' '} + {user.platformNip05.name}@plebdevs.com{' '} + copyToClipboard(`${user.platformNip05.name}@plebdevs.com`)} + /> +

+ ) : user?.nip05 ? ( +

+ NIP-05:{' '} + {user.nip05}{' '} + copyToClipboard(user.nip05)} + />

) : (
diff --git a/src/components/profile/subscription/LightningAddressForm.js b/src/components/profile/subscription/LightningAddressForm.js index 6dca2a9..8224dc0 100644 --- a/src/components/profile/subscription/LightningAddressForm.js +++ b/src/components/profile/subscription/LightningAddressForm.js @@ -14,8 +14,8 @@ const LightningAddressForm = ({ visible, onHide }) => { const [existingLightningAddress, setExistingLightningAddress] = useState(null); const [name, setName] = useState(''); const [description, setDescription] = useState(''); - const [maxSendable, setMaxSendable] = useState(10000000); - const [minSendable, setMinSendable] = useState(1); + const [maxSendable, setMaxSendable] = useState(10000000000); + const [minSendable, setMinSendable] = useState(1000); const [invoiceMacaroon, setInvoiceMacaroon] = useState(''); const [lndCert, setLndCert] = useState(''); const [lndHost, setLndHost] = useState(''); @@ -26,18 +26,18 @@ const LightningAddressForm = ({ visible, onHide }) => { const { showToast } = useToast(); useEffect(() => { - if (session && session?.user && !session?.user?.lightningAddress) { - setName(session.user.name || ''); - } else if (session && session?.user && session?.user?.lightningAddress) { - setExistingLightningAddress(session.user.lightningAddress); - setName(session.user.lightningAddress.name || ''); - setDescription(session.user.lightningAddress.description || ''); - setMaxSendable(session.user.lightningAddress.maxSendable || 10000000); - setMinSendable(session.user.lightningAddress.minSendable || 1); - setInvoiceMacaroon(session.user.lightningAddress.invoiceMacaroon || ''); - setLndCert(session.user.lightningAddress.lndCert || ''); - setLndHost(session.user.lightningAddress.lndHost || ''); - setLndPort(session.user.lightningAddress.lndPort || '8080'); + if (session && session?.user && !session?.user?.platformLightningAddress) { + setName(session.user.username || ''); + } else if (session && session?.user && session?.user?.platformLightningAddress) { + setExistingLightningAddress(session.user.platformLightningAddress); + setName(session.user.platformLightningAddress.name || ''); + setDescription(session.user.platformLightningAddress.description || ''); + setMaxSendable(Number(session.user.platformLightningAddress.maxSendable) || 10000000000); + setMinSendable(Number(session.user.platformLightningAddress.minSendable) || 1000); + setInvoiceMacaroon(session.user.platformLightningAddress.invoiceMacaroon || ''); + setLndCert(session.user.platformLightningAddress.lndCert || ''); + setLndHost(session.user.platformLightningAddress.lndHost || ''); + setLndPort(session.user.platformLightningAddress.lndPort || '8080'); } }, [session]); @@ -46,10 +46,21 @@ const LightningAddressForm = ({ visible, onHide }) => { try { let response; const lowercaseName = name.toLowerCase(); + const data = { + name: lowercaseName, + description, + maxSendable: BigInt(maxSendable).toString(), + minSendable: BigInt(minSendable).toString(), + invoiceMacaroon, + lndCert, + lndHost, + lndPort + }; + if (existingLightningAddress) { - response = await axios.put(`/api/users/${session.user.id}/lightning-address`, { name: lowercaseName, description, maxSendable, minSendable, invoiceMacaroon, lndCert, lndHost, lndPort }); + response = await axios.put(`/api/users/${session.user.id}/lightning-address`, data); } else { - response = await axios.post(`/api/users/${session.user.id}/lightning-address`, { name: lowercaseName, description, maxSendable, minSendable, invoiceMacaroon, lndCert, lndHost, lndPort }); + response = await axios.post(`/api/users/${session.user.id}/lightning-address`, data); } if (!existingLightningAddress && response.status === 201) { showToast('success', 'Lightning Address Claimed', 'Your Lightning Address has been claimed'); @@ -101,10 +112,9 @@ const LightningAddressForm = ({ visible, onHide }) => { setDescription(e.target.value)} tooltip='This is your Lightning Address description, it will be displayed as the description LUD16 lnurlp endpoint' /> - {/* Todo: max is 2,147,483 sats until i imlement bigInt for sat amounts */} - setMaxSendable(e.target.value)} max={2147483647} min={1000} tooltip='This is the maximum amount of sats that can be sent to your Lightning Address (currently denominated in sats NOT msat)' /> + setMaxSendable(e.value)} min={1000} tooltip='Maximum amount in millisats (1000 millisats = 1 sat)' /> - setMinSendable(e.target.value)} min={1} max={2147483647} tooltip='This is the minimum amount of sats that can be sent to your Lightning Address (currently denominated in sats NOT msat)' /> + setMinSendable(e.value)} min={1} tooltip='Minimum amount in millisats (1000 millisats = 1 sat)' /> setInvoiceMacaroon(e.target.value)} tooltip='This is your LND Invoice Macaroon, it is used to create invoices for your Lightning Address but DOES NOT grant access to move funds from your LND node' /> diff --git a/src/components/profile/subscription/Nip05Form.js b/src/components/profile/subscription/Nip05Form.js index bd6c92d..cf6831a 100644 --- a/src/components/profile/subscription/Nip05Form.js +++ b/src/components/profile/subscription/Nip05Form.js @@ -19,13 +19,13 @@ const Nip05Form = ({ visible, onHide }) => { const { showToast } = useToast(); useEffect(() => { - if (session && session?.user && !session?.user?.nip05) { + if (session && session?.user && !session?.user?.platformNip05) { setPubkey(session.user.pubkey || ''); - setName(session.user.name || ''); - } else if (session && session?.user && session?.user?.nip05) { - setExistingNip05(session.user.nip05); - setPubkey(session.user.nip05.pubkey || ''); - setName(session.user.nip05.name || ''); + setName(session.user.username || ''); + } else if (session && session?.user && session?.user?.platformNip05) { + setExistingNip05(session.user.platformNip05); + setPubkey(session.user.platformNip05.pubkey || ''); + setName(session.user.platformNip05.name || ''); } }, [session]); diff --git a/src/components/profile/subscription/SubscribeModal.js b/src/components/profile/subscription/SubscribeModal.js index 1ec752b..97bf3ac 100644 --- a/src/components/profile/subscription/SubscribeModal.js +++ b/src/components/profile/subscription/SubscribeModal.js @@ -7,7 +7,6 @@ import { useSession } from 'next-auth/react'; import { useRouter } from 'next/router'; import { useToast } from '@/hooks/useToast'; import { Card } from 'primereact/card'; -import { Badge } from 'primereact/badge'; import GenericButton from '@/components/buttons/GenericButton'; import { Menu } from "primereact/menu"; import { Message } from "primereact/message"; @@ -104,14 +103,14 @@ const SubscribeModal = ({ user }) => { }, }, { - label: session?.user?.lightningAddress ? "Update PlebDevs Lightning Address" : "Claim PlebDevs Lightning Address", + label: session?.user?.platformLightningAddress ? "Update PlebDevs Lightning Address" : "Claim PlebDevs Lightning Address", icon: "pi pi-bolt", command: () => { setLightningAddressVisible(true); }, }, { - label: session?.user?.nip05 ? "Update PlebDevs Nostr NIP-05" : "Claim PlebDevs Nostr NIP-05", + label: session?.user?.platformNip05?.name ? "Update PlebDevs Nostr NIP-05" : "Claim PlebDevs Nostr NIP-05", icon: "pi pi-at", command: () => { setNip05Visible(true); @@ -152,14 +151,14 @@ const SubscribeModal = ({ user }) => { {subscribed && !user?.role?.nwc && (
-

Thank you for your support 🎉

+

Thank you for your support 🎉

Pay-as-you-go subscription will renew on {subscribedUntil.toLocaleDateString()}

)} {subscribed && user?.role?.nwc && (
-

Thank you for your support 🎉

+

Thank you for your support 🎉

Recurring subscription will AUTO renew on {subscribedUntil.toLocaleDateString()}

)} @@ -168,7 +167,7 @@ const SubscribeModal = ({ user }) => { setVisible(true)} />
@@ -232,7 +231,7 @@ const SubscribeModal = ({ user }) => { visible={calendlyVisible} onHide={() => setCalendlyVisible(false)} userId={session?.user?.id} - userName={session?.user?.name || user?.kind0?.username} + userName={session?.user?.username || user?.kind0?.username} userEmail={session?.user?.email} /> { const { data: session, update } = useSession(); const { showToast } = useToast(); - const router = useRouter(); const windowWidth = useWindowWidth(); - const menu = useRef(null); const [user, setUser] = useState(null); const [isProcessing, setIsProcessing] = useState(false); const [subscribed, setSubscribed] = useState(false); @@ -178,8 +175,8 @@ const UserSubscription = () => {
setCalendlyVisible(true)} /> - setNip05Visible(true)} /> - } onClick={() => setLightningAddressVisible(true)} /> + setNip05Visible(true)} /> + } onClick={() => setLightningAddressVisible(true)} />
)} @@ -225,7 +222,7 @@ const UserSubscription = () => { visible={calendlyVisible} onHide={() => setCalendlyVisible(false)} userId={session?.user?.id} - userName={session?.user?.name || user?.kind0?.username} + userName={session?.user?.username || user?.kind0?.username} userEmail={session?.user?.email} /> { - return await prisma.lightningAddress.findMany(); + return await prisma.platformLightningAddress.findMany(); }; export const getLightningAddressByName = async (name) => { - return await prisma.lightningAddress.findFirst({ + return await prisma.platformLightningAddress.findFirst({ where: { name }, }); }; export const getLightningAddress = async (userId) => { - return await prisma.lightningAddress.findUnique({ + return await prisma.platformLightningAddress.findUnique({ where: { userId }, }); }; export const createLightningAddress = async (userId, name, description, maxSendable, minSendable, invoiceMacaroon, lndCert, lndHost, lndPort) => { try { - return await prisma.lightningAddress.create({ + return await prisma.platformLightningAddress.create({ data: { userId, name, @@ -38,14 +38,14 @@ export const createLightningAddress = async (userId, name, description, maxSenda }; export const updateLightningAddress = async (userId, data) => { - return await prisma.lightningAddress.update({ + return await prisma.platformLightningAddress.update({ where: { userId }, data, }); }; export const deleteLightningAddress = async (userId) => { - return await prisma.lightningAddress.delete({ + return await prisma.platformLightningAddress.delete({ where: { userId }, }); }; diff --git a/src/db/models/nip05Models.js b/src/db/models/nip05Models.js index df738d9..1e1a549 100644 --- a/src/db/models/nip05Models.js +++ b/src/db/models/nip05Models.js @@ -1,36 +1,36 @@ import prisma from "@/db/prisma"; export const getAllNip05s = async () => { - return await prisma.nip05.findMany(); + return await prisma.platformNip05.findMany(); }; export const getNip05ByName = async (name) => { - return await prisma.nip05.findFirst({ + return await prisma.platformNip05.findFirst({ where: { name }, }); }; export const getNip05 = async (userId) => { - return await prisma.nip05.findUnique({ + return await prisma.platformNip05.findUnique({ where: { userId }, }); }; export const createNip05 = async (userId, pubkey, name) => { - return await prisma.nip05.create({ + return await prisma.platformNip05.create({ data: { userId, pubkey, name }, }); }; export const updateNip05 = async (userId, data) => { - return await prisma.nip05.update({ + return await prisma.platformNip05.update({ where: { userId }, data, }); }; export const deleteNip05 = async (userId) => { - return await prisma.nip05.delete({ + return await prisma.platformNip05.delete({ where: { userId }, }); }; diff --git a/src/db/models/userModels.js b/src/db/models/userModels.js index 7ec5328..12b00be 100644 --- a/src/db/models/userModels.js +++ b/src/db/models/userModels.js @@ -50,8 +50,8 @@ export const getUserById = async (id) => { lesson: true, }, }, - nip05: true, - lightningAddress: true, + platformNip05: true, + platformLightningAddress: true, userBadges: { include: { badge: true @@ -82,8 +82,8 @@ export const getUserByPubkey = async (pubkey) => { lesson: true, }, }, - nip05: true, - lightningAddress: true, + platformNip05: true, + platformLightningAddress: true, userBadges: { include: { badge: true @@ -278,8 +278,8 @@ export const getUserByEmail = async (email) => { lesson: true, }, }, - nip05: true, - lightningAddress: true, + platformNip05: true, + platformLightningAddress: true, userBadges: { include: { badge: true diff --git a/src/pages/api/auth/[...nextauth].js b/src/pages/api/auth/[...nextauth].js index 2f99aff..abaace9 100644 --- a/src/pages/api/auth/[...nextauth].js +++ b/src/pages/api/auth/[...nextauth].js @@ -33,14 +33,19 @@ const syncNostrProfile = async (pubkey) => { let dbUser = await getUserByPubkey(pubkey); if (dbUser) { - // Update existing user if kind0 fields differ - if (fields.avatar !== dbUser.avatar || fields.username !== dbUser.username) { + // Update existing user if any of the kind0 fields differ + if (fields.avatar !== dbUser.avatar || + fields.username !== dbUser.username || + fields.lud16 !== dbUser.lud16 || + fields.nip05 !== dbUser.nip05) { + const updates = { ...(fields.avatar !== dbUser.avatar && { avatar: fields.avatar }), ...(fields.username !== dbUser.username && { - username: fields.username, - name: fields.username - }) + username: fields.username + }), + ...(fields.lud16 !== dbUser.lud16 && { lud16: fields.lud16 }), + ...(fields.nip05 !== dbUser.nip05 && { nip05: fields.nip05 }) }; await updateUser(dbUser.id, updates); dbUser = await getUserByPubkey(pubkey); @@ -48,11 +53,14 @@ const syncNostrProfile = async (pubkey) => { } else { // Create new user const username = fields.username || pubkey.slice(0, 8); + const lud16 = fields.lud16 || null; + const nip05 = fields.nip05 || null; const payload = { pubkey, username, avatar: fields.avatar, - name: username + lud16, + nip05 }; dbUser = await createUser(payload); @@ -126,8 +134,7 @@ export const authOptions = { if (!user) { user = await createUser({ ...keys, - username: `anon-${keys.pubkey.slice(0, 8)}`, - name: `anon-${keys.pubkey.slice(0, 8)}` + username: `anon-${keys.pubkey.slice(0, 8)}` }); } return { ...user, privkey: keys.privkey }; @@ -157,7 +164,7 @@ export const authOptions = { id: profile.id.toString(), pubkey: keys.pubkey, privkey: keys.privkey, - name: profile.login, + username: profile.login, email: profile.email, avatar: profile.avatar_url }; @@ -194,8 +201,8 @@ export const authOptions = { purchased: true, userCourses: true, userLessons: true, - nip05: true, - lightningAddress: true, + platformNip05: true, + platformLightningAddress: true, userBadges: true } }); @@ -232,7 +239,6 @@ export const authOptions = { username: user.email.split('@')[0], email: user.email, avatar: user.image, - name: user.email.split('@')[0], } // Update the user with the new keypair @@ -256,7 +262,15 @@ export const authOptions = { if (userData) { const fullUser = await getUserById(userData.id); - + // Convert BigInt values to strings if they exist + if (fullUser.platformLightningAddress) { + fullUser.platformLightningAddress = { + ...fullUser.platformLightningAddress, + maxSendable: fullUser.platformLightningAddress.maxSendable?.toString(), + minSendable: fullUser.platformLightningAddress.minSendable?.toString() + }; + } + // Get the user's GitHub account if it exists const githubAccount = await prisma.account.findFirst({ where: { @@ -273,13 +287,14 @@ export const authOptions = { role: fullUser.role, username: fullUser.username, avatar: fullUser.avatar, - name: fullUser.name, email: fullUser.email, userCourses: fullUser.userCourses, userLessons: fullUser.userLessons, purchased: fullUser.purchased, nip05: fullUser.nip05, - lightningAddress: fullUser.lightningAddress, + lud16: fullUser.lud16, + platformNip05: fullUser.platformNip05, + platformLightningAddress: fullUser.platformLightningAddress, githubUsername: token.githubUsername, createdAt: fullUser.createdAt, userBadges: fullUser.userBadges @@ -300,15 +315,22 @@ export const authOptions = { return session; }, async jwt({ token, user, account, profile, session }) { + // Convert BigInt values to strings if they exist + if (user?.platformLightningAddress) { + user.platformLightningAddress = { + ...user.platformLightningAddress, + maxSendable: user.platformLightningAddress.maxSendable?.toString(), + minSendable: user.platformLightningAddress.minSendable?.toString() + }; + } + // If we are linking a github account to an existing email or anon account (we have privkey) if (account?.provider === "github" && user?.id && user?.pubkey && user?.privkey) { try { // First update the user's profile with GitHub info const updatedUser = await updateUser(user.id, { - name: profile?.login || profile?.name, username: profile?.login || profile?.name, avatar: profile?.avatar_url, - image: profile?.avatar_url, }); // Get the updated user @@ -341,10 +363,8 @@ export const authOptions = { if (!existingGithubAccount) { // Update user profile with GitHub info const updatedUser = await updateUser(user.id, { - name: profile?.login || profile?.name, username: profile?.login || profile?.name, avatar: profile?.avatar_url, - image: profile?.avatar_url, email: profile?.email // Add email if user wants it }); diff --git a/src/pages/api/lightning-address/callback/[slug].js b/src/pages/api/lightning-address/callback/[slug].js index 1e3116a..b434544 100644 --- a/src/pages/api/lightning-address/callback/[slug].js +++ b/src/pages/api/lightning-address/callback/[slug].js @@ -60,16 +60,21 @@ export default async function handler(req, res) { descriptionHash = Buffer.from(hash, 'hex').toString('base64'); } - // Convert amount from millisatoshis to satoshis - if (amount < (foundAddress.minSendable)) { + // Check amount against BigInt min/max values + if (amount < foundAddress.minSendable) { res.status(400).json({ error: 'Amount too low' }); return; - } else if (amount > (foundAddress.maxSendable || Number.MAX_SAFE_INTEGER)) { + } else if (amount > foundAddress.maxSendable) { res.status(400).json({ error: 'Amount too high' }); return; } else { try { - const response = await axios.post(`${BACKEND_URL}/api/lightning-address/lnd`, { amount: amount, description_hash: descriptionHash, name: slug, zap_request: queryParams?.nostr ? queryParams.nostr : null }, { + const response = await axios.post(`${BACKEND_URL}/api/lightning-address/lnd`, { + amount: amount, + description_hash: descriptionHash, + name: slug, + zap_request: queryParams?.nostr ? queryParams.nostr : null + }, { headers: { 'Authorization': PLEBDEVS_API_KEY } diff --git a/src/pages/api/lightning-address/lnurlp/[slug].js b/src/pages/api/lightning-address/lnurlp/[slug].js index f084a18..172f6a0 100644 --- a/src/pages/api/lightning-address/lnurlp/[slug].js +++ b/src/pages/api/lightning-address/lnurlp/[slug].js @@ -35,8 +35,8 @@ export default async function handler(req, res) { res.status(200).json({ callback: `${BACKEND_URL}/api/lightning-address/callback/${foundAddress.name}`, - maxSendable: foundAddress.maxSendable || 10000000000, - minSendable: foundAddress.minSendable || 1000, + maxSendable: parseInt(foundAddress.maxSendable), + minSendable: parseInt(foundAddress.minSendable), metadata: JSON.stringify(metadata), tag: 'payRequest', allowsNostr: true, diff --git a/src/pages/api/users/[slug]/lightning-address.js b/src/pages/api/users/[slug]/lightning-address.js index 8c55657..a5fe4cb 100644 --- a/src/pages/api/users/[slug]/lightning-address.js +++ b/src/pages/api/users/[slug]/lightning-address.js @@ -30,9 +30,23 @@ export default async function handler(req, res) { case 'POST': try { const { name, description, maxSendable, minSendable, invoiceMacaroon, lndCert, lndHost, lndPort } = req.body; - const lightningAddress = await createLightningAddress(userId, name, description, maxSendable, minSendable, invoiceMacaroon, lndCert, lndHost, lndPort); + const lightningAddress = await createLightningAddress( + userId, + name, + description, + BigInt(maxSendable), + BigInt(minSendable), + invoiceMacaroon, + lndCert, + lndHost, + lndPort + ); - res.status(201).json(lightningAddress); + res.status(201).json({ + ...lightningAddress, + maxSendable: lightningAddress.maxSendable.toString(), + minSendable: lightningAddress.minSendable.toString() + }); } catch (error) { console.error('Error creating Lightning Address:', error); res.status(500).json({ error: 'Error creating Lightning Address', errorMessage: error.message }); diff --git a/src/pages/auth/signin.js b/src/pages/auth/signin.js index b6761b6..bc422f0 100644 --- a/src/pages/auth/signin.js +++ b/src/pages/auth/signin.js @@ -17,7 +17,7 @@ export default function SignIn() { const handleEmailSignIn = async (e) => { e.preventDefault() - await signIn("email", { email, callbackUrl: '/' }) + await signIn("email", { email, callbackUrl: '/profile' }) } const handleNostrSignIn = async (e) => { @@ -28,7 +28,7 @@ export default function SignIn() { try { const user = await ndk.signer.user() const pubkey = user?._pubkey - signIn("nostr", { pubkey }) + signIn("nostr", { pubkey, callbackUrl: '/profile' }) } catch (error) { console.error("Error signing Nostr event:", error) } @@ -46,7 +46,7 @@ export default function SignIn() { pubkey: storedPubkey, privkey: storedPrivkey, redirect: false, - callbackUrl: '/' + callbackUrl: '/profile' }); if (result?.ok) { @@ -59,7 +59,7 @@ export default function SignIn() { if (session?.user?.pubkey && session?.user?.privkey) { localStorage.setItem('anonymousPubkey', session.user.pubkey); localStorage.setItem('anonymousPrivkey', session.user.privkey); - router.push('/'); + router.push('/profile'); } else { console.error("Session data incomplete:", session); } @@ -77,11 +77,11 @@ export default function SignIn() { const result = await signIn("recovery", { nsec, redirect: false, - callbackUrl: '/' + callbackUrl: '/profile' }); if (result?.ok) { - router.push('/'); + router.push('/profile'); } else { console.error("Recovery login failed:", result?.error); } @@ -93,7 +93,7 @@ export default function SignIn() { useEffect(() => { // Redirect if already signed in if (session?.user) { - router.push('/'); + router.push('/profile'); } }, [session, router]); diff --git a/src/pages/draft/[slug]/index.js b/src/pages/draft/[slug]/index.js index df9f0f3..b189c18 100644 --- a/src/pages/draft/[slug]/index.js +++ b/src/pages/draft/[slug]/index.js @@ -327,7 +327,7 @@ export default function Draft() {

Created by{' '} - {user?.username || user?.name || user?.pubkey.slice(0, 10)}{'... '} + {user?.username || user?.pubkey.slice(0, 10)}{'... '}

)} diff --git a/src/pages/subscribe.js b/src/pages/subscribe.js index bb72f5e..56949bf 100644 --- a/src/pages/subscribe.js +++ b/src/pages/subscribe.js @@ -101,12 +101,12 @@ const Subscribe = () => { command: () => setCalendlyVisible(true), }, { - label: session?.user?.lightningAddress ? "Update PlebDevs Lightning Address" : "Claim PlebDevs Lightning Address", + label: session?.user?.platformLightningAddress ? "Update PlebDevs Lightning Address" : "Claim PlebDevs Lightning Address", icon: "pi pi-bolt", command: () => setLightningAddressVisible(true), }, { - label: session?.user?.nip05 ? "Update PlebDevs Nostr NIP-05" : "Claim PlebDevs Nostr NIP-05", + label: session?.user?.platformNip05?.name ? "Update PlebDevs Nostr NIP-05" : "Claim PlebDevs Nostr NIP-05", icon: "pi pi-at", command: () => setNip05Visible(true), }, @@ -122,23 +122,6 @@ const Subscribe = () => { }, ]; - const subscriptionCardTitleAndButton = ( -
- Plebdevs Subscription - menu.current.toggle(e)} - > - -
- ); - - const subscriptionCardTitle = ( -
- Plebdevs Subscription -
- ); - return (
{windowWidth < 768 && ( @@ -242,8 +225,8 @@ const Subscribe = () => {
setCalendlyVisible(true)} /> - setNip05Visible(true)} /> - } onClick={() => setLightningAddressVisible(true)} /> + setNip05Visible(true)} /> + } onClick={() => setLightningAddressVisible(true)} />
@@ -296,7 +279,7 @@ const Subscribe = () => { visible={calendlyVisible} onHide={() => setCalendlyVisible(false)} userId={session?.user?.id} - userName={session?.user?.name || user?.kind0?.username} + userName={session?.user?.username || user?.kind0?.username} userEmail={session?.user?.email} /> { fields.lud16 = lud16; } + const nip05 = findTruthyPropertyValue(kind0, ['nip05']); + + if (nip05) { + fields.nip05 = nip05; + } + return fields; }