diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5c2265a..12e63d2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,27 +8,29 @@ generator client { } 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[] - courses Course[] - resources Resource[] - courseDrafts CourseDraft[] - drafts Draft[] - role Role? - accounts Account[] - sessions Session[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - userLessons UserLesson[] - userCourses UserCourse[] + 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[] + courses Course[] + resources Resource[] + courseDrafts CourseDraft[] + drafts Draft[] + role Role? + accounts Account[] + sessions Session[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + userLessons UserLesson[] + userCourses UserCourse[] + nip05 Nip05? + lightningAddress LightningAddress? } model Session { @@ -69,32 +71,31 @@ model Account { } model Role { - id String @id @default(uuid()) - user User @relation(fields: [userId], references: [id]) - userId String @unique - subscribed Boolean @default(false) - admin Boolean @default(false) + id String @id @default(uuid()) + user User @relation(fields: [userId], references: [id]) + userId String @unique + subscribed Boolean @default(false) + admin Boolean @default(false) subscriptionStartDate DateTime? lastPaymentAt DateTime? subscriptionExpiredAt DateTime? - nwc String? + nwc String? } model Resource { - id String @id // Client generates UUID - userId String - user User @relation(fields: [userId], references: [id]) - lessons Lesson[] + id String @id // Client generates UUID + userId String + user User @relation(fields: [userId], references: [id]) + lessons Lesson[] draftLessons DraftLesson[] - price Int @default(0) - purchases Purchase[] - noteId String? @unique - videoId String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + price Int @default(0) + purchases Purchase[] + noteId String? @unique + videoId String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } - model Draft { id String @id @default(uuid()) userId String @@ -114,15 +115,15 @@ model Draft { } model Course { - id String @id - userId String - user User @relation(fields: [userId], references: [id]) - price Int @default(0) - lessons Lesson[] - purchases Purchase[] - noteId String? @unique - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id + userId String + user User @relation(fields: [userId], references: [id]) + price Int @default(0) + lessons Lesson[] + purchases Purchase[] + noteId String? @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt userCourses UserCourse[] } @@ -141,16 +142,16 @@ model CourseDraft { } model Lesson { - id String @id @default(uuid()) - courseId String? - course Course? @relation(fields: [courseId], references: [id]) - resourceId String? - resource Resource? @relation(fields: [resourceId], references: [id]) - draftId String? - draft Draft? @relation(fields: [draftId], references: [id]) - index Int - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(uuid()) + courseId String? + course Course? @relation(fields: [courseId], references: [id]) + resourceId String? + resource Resource? @relation(fields: [resourceId], references: [id]) + draftId String? + draft Draft? @relation(fields: [draftId], references: [id]) + index Int + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt userLessons UserLesson[] } @@ -168,48 +169,74 @@ model DraftLesson { } model UserLesson { - id String @id @default(uuid()) - userId String - user User @relation(fields: [userId], references: [id]) - lessonId String - lesson Lesson @relation(fields: [lessonId], references: [id]) - opened Boolean @default(false) - completed Boolean @default(false) - openedAt DateTime? + id String @id @default(uuid()) + userId String + user User @relation(fields: [userId], references: [id]) + lessonId String + lesson Lesson @relation(fields: [lessonId], references: [id]) + opened Boolean @default(false) + completed Boolean @default(false) + openedAt DateTime? completedAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @@unique([userId, lessonId]) } model Purchase { - id String @id @default(uuid()) + id String @id @default(uuid()) userId String courseId String? resourceId String? amountPaid Int - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - user User @relation(fields: [userId], references: [id]) - course Course? @relation(fields: [courseId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) + course Course? @relation(fields: [courseId], references: [id]) resource Resource? @relation(fields: [resourceId], references: [id]) @@unique([userId, courseId]) } model UserCourse { - id String @id @default(uuid()) - userId String - user User @relation(fields: [userId], references: [id]) - courseId String - course Course @relation(fields: [courseId], references: [id]) - started Boolean @default(false) - completed Boolean @default(false) - startedAt DateTime? + id String @id @default(uuid()) + userId String + user User @relation(fields: [userId], references: [id]) + courseId String + course Course @relation(fields: [courseId], references: [id]) + started Boolean @default(false) + completed Boolean @default(false) + startedAt DateTime? completedAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @@unique([userId, courseId]) -} \ No newline at end of file +} + +model Nip05 { + id String @id @default(uuid()) + userId String @unique + user User @relation(fields: [userId], references: [id]) + pubkey String + name String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model LightningAddress { + 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) + invoiceMacaroon String + lndCert String? + lndHost String + lndPort String @default("8080") +} diff --git a/src/components/profile/UserProfile.js b/src/components/profile/UserProfile.js index ec4c0cd..100aa3a 100644 --- a/src/components/profile/UserProfile.js +++ b/src/components/profile/UserProfile.js @@ -94,6 +94,12 @@ const UserProfile = () => { {nip19.npubEncode(user.pubkey)} +

+ Lightning Address: {user.lightningAddress.name}@plebdevs.com +

+

+ NIP-05: {user.nip05.name}@plebdevs.com +

{/* */} diff --git a/src/components/profile/UserSettings.js b/src/components/profile/UserSettings.js index 49718ba..044c881 100644 --- a/src/components/profile/UserSettings.js +++ b/src/components/profile/UserSettings.js @@ -72,7 +72,7 @@ const UserSettings = () => { useEffect(() => { const intervalId = setInterval(() => { setUpdateTrigger(prev => prev + 1); - }, 3000); // Poll every 3 seconds + }, 7000); // Poll every 7 seconds return () => clearInterval(intervalId); // Cleanup on unmount }, []); @@ -181,6 +181,12 @@ const UserSettings = () => { {nip19.npubEncode(user.pubkey)} +

+ Lightning Address: {user.lightningAddress.name}@plebdevs.com +

+

+ NIP-05: {user.nip05.name}@plebdevs.com +

Connect Your Lightning Wallet

diff --git a/src/components/profile/subscription/LightningAddressForm.js b/src/components/profile/subscription/LightningAddressForm.js new file mode 100644 index 0000000..3259159 --- /dev/null +++ b/src/components/profile/subscription/LightningAddressForm.js @@ -0,0 +1,127 @@ +import React, { useState, useEffect } from 'react'; +import { Dialog } from 'primereact/dialog'; +import axios from 'axios'; +import { useSession } from 'next-auth/react'; +import { useToast } from '@/hooks/useToast'; +import { InputText } from 'primereact/inputtext'; +import { ProgressSpinner } from 'primereact/progressspinner'; +import GenericButton from '@/components/buttons/GenericButton'; + +const LightningAddressForm = ({ visible, onHide }) => { + const [isProcessing, setIsProcessing] = useState(false); + const [existingLightningAddress, setExistingLightningAddress] = useState(null); + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const [maxSendable, setMaxSendable] = useState(10000000); + const [minSendable, setMinSendable] = useState(1); + const [invoiceMacaroon, setInvoiceMacaroon] = useState(''); + const [lndCert, setLndCert] = useState(''); + const [lndHost, setLndHost] = useState(''); + const [lndPort, setLndPort] = useState('8080'); + + const { data: session, update } = useSession(); + 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'); + } + }, [session]); + + const handleLightningAddress = async () => { + setIsProcessing(true); + try { + let response; + const lowercaseName = name.toLowerCase(); + if (existingLightningAddress) { + response = await axios.put(`/api/users/${session.user.id}/lightning-address`, { name: lowercaseName, description, maxSendable, minSendable, invoiceMacaroon, lndCert, lndHost, lndPort }); + } else { + response = await axios.post(`/api/users/${session.user.id}/lightning-address`, { name: lowercaseName, description, maxSendable, minSendable, invoiceMacaroon, lndCert, lndHost, lndPort }); + } + if (!existingLightningAddress && response.status === 201) { + showToast('success', 'Lightning Address Claimed', 'Your Lightning Address has been claimed'); + update(); + onHide(); + } else if (existingLightningAddress && response.status === 200) { + showToast('success', 'Lightning Address updated', 'Your Lightning Address has been updated'); + update(); + onHide(); + } else { + showToast('error', 'Error updating Lightning Address', response.data.error); + } + } catch (error) { + console.error('Error claiming Lightning Address:', error); + showToast('error', 'Error claiming Lightning Address', error.message); + setIsProcessing(false); + } + }; + + const deleteLightningAddress = async () => { + setIsProcessing(true); + try { + const response = await axios.delete(`/api/users/${session.user.id}/lightning-address`); + if (response.status === 204) { + showToast('success', 'Lightning Address deleted', 'Your Lightning Address has been deleted'); + update(); + onHide(); + } else { + showToast('error', 'Error deleting Lightning Address', response.data.error); + } + } catch (error) { + console.error('Error deleting Lightning Address:', error); + showToast('error', 'Error deleting Lightning Address', error.message); + setIsProcessing(false); + } + }; + + return ( + + {existingLightningAddress ? ( +

Update your Lightning Address details

+ ) : ( +

Confirm your Lightning Address details

+ )} +
+ + setName(e.target.value)} /> + + setDescription(e.target.value)} /> + + setMaxSendable(e.target.value)} /> + + setMinSendable(e.target.value)} /> + + setInvoiceMacaroon(e.target.value)} /> + + setLndCert(e.target.value)} /> + + setLndHost(e.target.value)} /> + + setLndPort(e.target.value)} /> +
+ {!existingLightningAddress && ( +
+ {isProcessing ? : } +
+ )} + {existingLightningAddress && ( +
+ + +
+ )} +
+ ); +}; + +export default LightningAddressForm; diff --git a/src/components/profile/subscription/Nip05Form.js b/src/components/profile/subscription/Nip05Form.js new file mode 100644 index 0000000..c96be9f --- /dev/null +++ b/src/components/profile/subscription/Nip05Form.js @@ -0,0 +1,102 @@ +import React, { useState, useEffect } from 'react'; +import { Dialog } from 'primereact/dialog'; +import axios from 'axios'; +import { useSession } from 'next-auth/react'; +import { useToast } from '@/hooks/useToast'; +import { InputText } from 'primereact/inputtext'; +import { ProgressSpinner } from 'primereact/progressspinner'; +import GenericButton from '@/components/buttons/GenericButton'; + +const Nip05Form = ({ visible, onHide }) => { + const [isProcessing, setIsProcessing] = useState(false); + const [existingNip05, setExistingNip05] = useState(null); + const [pubkey, setPubkey] = useState(''); + const [name, setName] = useState(''); + + const { data: session, update } = useSession(); + const { showToast } = useToast(); + + useEffect(() => { + if (session && session?.user && !session?.user?.nip05) { + 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 || ''); + } + }, [session]); + + const handleNip05 = async () => { + setIsProcessing(true); + try { + let response; + const lowercaseName = name.toLowerCase(); + if (existingNip05) { + response = await axios.put(`/api/users/${session.user.id}/nip05`, { pubkey, name: lowercaseName }); + } else { + response = await axios.post(`/api/users/${session.user.id}/nip05`, { pubkey, name: lowercaseName }); + } + if (existingNip05 && response.status === 201) { + showToast('success', 'NIP-05 Claimed', 'Your NIP-05 has been claimed'); + update(); + onHide(); + } else if (response.status === 200) { + showToast('success', 'NIP-05 updated', 'Your NIP-05 has been updated'); + update(); + onHide(); + } else { + showToast('error', 'Error updating NIP-05', response.data.error); + } + } catch (error) { + console.error('Error claiming NIP-05:', error); + showToast('error', 'Error claiming NIP-05', error.message); + setIsProcessing(false); + } + }; + + const deleteNip05 = async () => { + setIsProcessing(true); + try { + const response = await axios.delete(`/api/users/${session.user.id}/nip05`); + if (response.status === 204) { + showToast('success', 'NIP-05 deleted', 'Your NIP-05 has been deleted'); + update(); + onHide(); + } else { + showToast('error', 'Error deleting NIP-05', response.data.error); + } + } catch (error) { + console.error('Error deleting NIP-05:', error); + showToast('error', 'Error deleting NIP-05', error.message); + setIsProcessing(false); + } + }; + + return ( + + {existingNip05 ? ( +

Update your Pubkey and Name

+ ) : ( +

Confirm your Pubkey and Name

+ )} +
+ setPubkey(e.target.value)} /> + setName(e.target.value)} /> +
+ {!existingNip05 && ( +
+ {isProcessing ? : } +
+ )} + {existingNip05 && ( +
+ + +
+ )} +
+ ); +}; + +export default Nip05Form; \ No newline at end of file diff --git a/src/components/profile/subscription/SubscribeModal.js b/src/components/profile/subscription/SubscribeModal.js index 377781a..11e8e21 100644 --- a/src/components/profile/subscription/SubscribeModal.js +++ b/src/components/profile/subscription/SubscribeModal.js @@ -13,6 +13,8 @@ import { Menu } from "primereact/menu"; import { Message } from "primereact/message"; import CancelSubscription from '@/components/profile/subscription/CancelSubscription'; import CalendlyEmbed from '@/components/profile/subscription/CalendlyEmbed'; +import Nip05Form from '@/components/profile/subscription/Nip05Form'; +import LightningAddressForm from '@/components/profile/subscription/LightningAddressForm'; import NostrIcon from '../../../../public/images/nostr.png'; import Image from 'next/image'; import RenewSubscription from '@/components/profile/subscription/RenewSubscription'; @@ -27,6 +29,8 @@ const SubscribeModal = ({ user }) => { const [subscribedUntil, setSubscribedUntil] = useState(null); const [subscriptionExpiredAt, setSubscriptionExpiredAt] = useState(null); const [calendlyVisible, setCalendlyVisible] = useState(false); + const [lightningAddressVisible, setLightningAddressVisible] = useState(false); + const [nip05Visible, setNip05Visible] = useState(false); const menu = useRef(null); const [cancelSubscriptionVisible, setCancelSubscriptionVisible] = useState(false); const [renewSubscriptionVisible, setRenewSubscriptionVisible] = useState(false); @@ -92,13 +96,6 @@ const SubscribeModal = ({ user }) => { } const menuItems = [ - { - label: "Renew Subscription", - icon: "pi pi-bolt", - command: () => { - setRenewSubscriptionVisible(true); - }, - }, { label: "Schedule 1:1", icon: "pi pi-calendar", @@ -106,6 +103,27 @@ const SubscribeModal = ({ user }) => { setCalendlyVisible(true); }, }, + { + label: session?.user?.lightningAddress ? "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", + icon: "pi pi-at", + command: () => { + setNip05Visible(true); + }, + }, + { + label: "Renew Subscription", + icon: "pi pi-sync", + command: () => { + setRenewSubscriptionVisible(true); + }, + }, { label: "Cancel Subscription", icon: "pi pi-trash", @@ -121,10 +139,10 @@ const SubscribeModal = ({ user }) => { {subscribed && ( menu.current.toggle(e)} + onClick={(e) => menu.current.toggle(e)} > )} - +
); @@ -190,7 +208,7 @@ const SubscribeModal = ({ user }) => { Claim your own personal plebdevs.com Lightning Address
- Nostr + Nostr Claim your own personal plebdevs.com Nostr NIP-05 identity
@@ -207,12 +225,10 @@ const SubscribeModal = ({ user }) => { )} - {calendlyVisible && ( - setCalendlyVisible(false)} - /> - )} + setCalendlyVisible(false)} + /> setCancelSubscriptionVisible(false)} @@ -222,6 +238,14 @@ const SubscribeModal = ({ user }) => { onHide={() => setRenewSubscriptionVisible(false)} subscribedUntil={subscribedUntil} /> + setNip05Visible(false)} + /> + setLightningAddressVisible(false)} + /> ); }; diff --git a/src/db/models/lightningAddressModels.js b/src/db/models/lightningAddressModels.js new file mode 100644 index 0000000..5f5231d --- /dev/null +++ b/src/db/models/lightningAddressModels.js @@ -0,0 +1,51 @@ +import prisma from "@/db/prisma"; + +export const getAllLightningAddresses = async () => { + return await prisma.lightningAddress.findMany(); +}; + +export const getLightningAddressByName = async (name) => { + return await prisma.lightningAddress.findFirst({ + where: { name }, + }); +}; + +export const getLightningAddress = async (userId) => { + return await prisma.lightningAddress.findUnique({ + where: { userId }, + }); +}; + +export const createLightningAddress = async (userId, name, description, maxSendable, minSendable, invoiceMacaroon, lndCert, lndHost, lndPort) => { + try { + return await prisma.lightningAddress.create({ + data: { + userId, + name, + description, + maxSendable: maxSendable, + minSendable: minSendable, + invoiceMacaroon, + lndCert, + lndHost, + lndPort + }, + }); + } catch (error) { + console.error('Error in createLightningAddress:', error); + throw error; + } +}; + +export const updateLightningAddress = async (userId, data) => { + return await prisma.lightningAddress.update({ + where: { userId }, + data, + }); +}; + +export const deleteLightningAddress = async (userId) => { + return await prisma.lightningAddress.delete({ + where: { userId }, + }); +}; diff --git a/src/db/models/nip05Models.js b/src/db/models/nip05Models.js new file mode 100644 index 0000000..df738d9 --- /dev/null +++ b/src/db/models/nip05Models.js @@ -0,0 +1,37 @@ +import prisma from "@/db/prisma"; + +export const getAllNip05s = async () => { + return await prisma.nip05.findMany(); +}; + +export const getNip05ByName = async (name) => { + return await prisma.nip05.findFirst({ + where: { name }, + }); +}; + +export const getNip05 = async (userId) => { + return await prisma.nip05.findUnique({ + where: { userId }, + }); +}; + +export const createNip05 = async (userId, pubkey, name) => { + return await prisma.nip05.create({ + data: { userId, pubkey, name }, + }); +}; + +export const updateNip05 = async (userId, data) => { + return await prisma.nip05.update({ + where: { userId }, + data, + }); +}; + +export const deleteNip05 = async (userId) => { + return await prisma.nip05.delete({ + where: { userId }, + }); +}; + diff --git a/src/db/models/userModels.js b/src/db/models/userModels.js index 73aabab..9e1f2b9 100644 --- a/src/db/models/userModels.js +++ b/src/db/models/userModels.js @@ -49,6 +49,8 @@ export const getUserById = async (id) => { lesson: true, }, }, + nip05: true, + lightningAddress: true, }, }); }; @@ -74,6 +76,8 @@ export const getUserByPubkey = async (pubkey) => { lesson: true, }, }, + nip05: true, + lightningAddress: true, }, }); } diff --git a/src/pages/api/nip05.js b/src/pages/api/nip05.js index 0a7b071..5c3996e 100644 --- a/src/pages/api/nip05.js +++ b/src/pages/api/nip05.js @@ -1,10 +1,20 @@ -const nostrData = { - names: { - plebdevs: - "f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741", - }, -}; +import { getNip05ByName } from "@/db/models/nip05Models"; export default async function Nip05(req, res) { - return res.status(200).json(nostrData); -} \ No newline at end of file + const name = req.query.name; + if (!name) { + return res.status(400).json({ error: "Name is required" }); + } + + const nip05 = await getNip05ByName(name); + + if (!nip05) { + return res.status(404).json({ error: "NIP-05 not found" }); + } + + return res.status(200).json({ + names: { + [nip05.name.toLowerCase()]: nip05.pubkey, + }, + }); +} diff --git a/src/pages/api/users/[slug]/lightning-address.js b/src/pages/api/users/[slug]/lightning-address.js new file mode 100644 index 0000000..1373cd5 --- /dev/null +++ b/src/pages/api/users/[slug]/lightning-address.js @@ -0,0 +1,56 @@ +import { getLightningAddress, createLightningAddress, updateLightningAddress, deleteLightningAddress } from "@/db/models/lightningAddressModels" + +export default async function handler(req, res) { + const { slug } = req.query; + const userId = slug; + + switch (req.method) { + case 'GET': + try { + const lightningAddress = await getLightningAddress(userId); + if (lightningAddress) { + res.status(200).json(lightningAddress); + } else { + res.status(404).json({ error: 'Lightning Address not found' }); + } + } catch (error) { + res.status(500).json({ error: 'Error fetching Lightning Address' }); + } + break; + + 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); + + res.status(201).json(lightningAddress); + } catch (error) { + console.error('Error creating Lightning Address:', error); + res.status(500).json({ error: 'Error creating Lightning Address', errorMessage: error.message }); + } + break; + + case 'PUT': + try { + const data = req.body; + const lightningAddress = await updateLightningAddress(userId, data); + res.status(200).json(lightningAddress); + } catch (error) { + res.status(500).json({ error: 'Error updating Lightning Address' }); + } + break; + + case 'DELETE': + try { + await deleteLightningAddress(userId); + res.status(204).end(); + } catch (error) { + res.status(500).json({ error: 'Error deleting Lightning Address' }); + } + break; + + default: + res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE']); + res.status(405).end(`Method ${req.method} Not Allowed`); + } +} \ No newline at end of file diff --git a/src/pages/api/users/[slug]/nip05.js b/src/pages/api/users/[slug]/nip05.js new file mode 100644 index 0000000..f074f70 --- /dev/null +++ b/src/pages/api/users/[slug]/nip05.js @@ -0,0 +1,54 @@ +import { getNip05, createNip05, updateNip05, deleteNip05 } from '@/db/models/nip05Models'; + +export default async function handler(req, res) { + const { slug } = req.query; + const userId = slug; + + switch (req.method) { + case 'GET': + try { + const nip05 = await getNip05(userId); + if (nip05) { + res.status(200).json(nip05); + } else { + res.status(404).json({ error: 'NIP-05 not found' }); + } + } catch (error) { + res.status(500).json({ error: 'Error fetching NIP-05' }); + } + break; + + case 'POST': + try { + const { pubkey, name } = req.body; + const nip05 = await createNip05(userId, pubkey, name.toLowerCase()); + res.status(201).json(nip05); + } catch (error) { + res.status(500).json({ error: 'Error creating NIP-05' }); + } + break; + + case 'PUT': + try { + const { pubkey, name } = req.body; + const nip05 = await updateNip05(userId, { pubkey, name: name.toLowerCase() }); + res.status(200).json(nip05); + } catch (error) { + res.status(500).json({ error: 'Error updating NIP-05' }); + } + break; + + case 'DELETE': + try { + await deleteNip05(userId); + res.status(204).end(); + } catch (error) { + res.status(500).json({ error: 'Error deleting NIP-05' }); + } + break; + + default: + res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE']); + res.status(405).end(`Method ${req.method} Not Allowed`); + } +}