custom nip05 and lightning address creation, updates, and deletes fully working. Still need to add to lnurlp endpoints

This commit is contained in:
austinkelsay 2024-09-26 14:50:14 -05:00
parent 3a18a41404
commit d13ddfcaa9
12 changed files with 612 additions and 108 deletions

View File

@ -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])
}
}
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")
}

View File

@ -94,6 +94,12 @@ const UserProfile = () => {
<Tooltip target=".pubkey-tooltip" content={"this is your nostr npub"} />
{nip19.npubEncode(user.pubkey)} <i className="pi pi-question-circle text-xl pubkey-tooltip" />
</h2>
<h3 className="text-center text-xl my-2">
<span className="font-bold">Lightning Address:</span> {user.lightningAddress.name}@plebdevs.com
</h3>
<h3 className="text-center text-xl my-2">
<span className="font-bold">NIP-05:</span> {user.nip05.name}@plebdevs.com
</h3>
{/* <GithubContributionChart username={"austinkelsay"} /> */}
<GithubContributionChartDisabled username={"austinkelsay"} />
<UserProgress />

View File

@ -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 = () => {
<Tooltip target=".pubkey-tooltip" content={"this is your nostr npub"} />
{nip19.npubEncode(user.pubkey)} <i className="pi pi-question-circle text-xl pubkey-tooltip" />
</h2>
<h3 className="text-center text-xl my-2">
<span className="font-bold">Lightning Address:</span> {user.lightningAddress.name}@plebdevs.com
</h3>
<h3 className="text-center text-xl my-2">
<span className="font-bold">NIP-05:</span> {user.nip05.name}@plebdevs.com
</h3>
<div className="flex flex-col w-1/2 mx-auto justify-between items-center">
<h2 className="text-xl my-2 max-mob:text-base max-tab:text-base">Connect Your Lightning Wallet</h2>
<BitcoinConnectButton />

View File

@ -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 (
<Dialog header="Lightning Address" visible={visible} onHide={onHide}>
{existingLightningAddress ? (
<p>Update your Lightning Address details</p>
) : (
<p>Confirm your Lightning Address details</p>
)}
<div className="flex flex-col gap-2 max-mob:min-w-[80vw] max-tab:min-w-[60vw] min-w-[40vw]">
<label>Name</label>
<InputText placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
<label>Description</label>
<InputText placeholder="Description" value={description} onChange={(e) => setDescription(e.target.value)} />
<label>Max Sendable</label>
<InputText placeholder="Max Sendable" value={maxSendable} onChange={(e) => setMaxSendable(e.target.value)} />
<label>Min Sendable</label>
<InputText placeholder="Min Sendable" value={minSendable} onChange={(e) => setMinSendable(e.target.value)} />
<label>Invoice Macaroon</label>
<InputText placeholder="Invoice Macaroon" value={invoiceMacaroon} onChange={(e) => setInvoiceMacaroon(e.target.value)} />
<label>LND Cert</label>
<InputText placeholder="LND Cert" value={lndCert} onChange={(e) => setLndCert(e.target.value)} />
<label>LND Host</label>
<InputText placeholder="LND Host" value={lndHost} onChange={(e) => setLndHost(e.target.value)} />
<label>LND Port</label>
<InputText placeholder="LND Port" value={lndPort} onChange={(e) => setLndPort(e.target.value)} />
</div>
{!existingLightningAddress && (
<div className="flex flex-row justify-center mt-6">
{isProcessing ? <ProgressSpinner /> : <GenericButton severity="success" outlined className="mx-auto" label='Confirm' onClick={handleLightningAddress} />}
</div>
)}
{existingLightningAddress && (
<div className="flex flex-row justify-center w-full mt-6 gap-4">
<GenericButton severity="success" outlined className="mx-auto" label="Update" onClick={handleLightningAddress} />
<GenericButton severity="danger" outlined className="mx-auto" label="Delete" onClick={deleteLightningAddress} />
</div>
)}
</Dialog>
);
};
export default LightningAddressForm;

View File

@ -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 (
<Dialog header="NIP-05" visible={visible} onHide={onHide}>
{existingNip05 ? (
<p>Update your Pubkey and Name</p>
) : (
<p>Confirm your Pubkey and Name</p>
)}
<div className="flex flex-col gap-2 max-mob:min-w-[80vw] max-tab:min-w-[60vw] min-w-[40vw]">
<InputText placeholder="Pubkey" value={pubkey} onChange={(e) => setPubkey(e.target.value)} />
<InputText placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
</div>
{!existingNip05 && (
<div className="flex flex-row justify-center mt-6">
{isProcessing ? <ProgressSpinner /> : <GenericButton severity="success" outlined className="mx-auto" label='Confirm' onClick={handleNip05} />}
</div>
)}
{existingNip05 && (
<div className="flex flex-row justify-center w-full mt-6 gap-4">
<GenericButton severity="success" outlined className="mx-auto" label="Update" onClick={handleNip05} />
<GenericButton severity="danger" outlined className="mx-auto" label="Delete" onClick={deleteNip05} />
</div>
)}
</Dialog>
);
};
export default Nip05Form;

View File

@ -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 && (
<i
className="pi pi-ellipsis-h text-2xl cursor-pointer hover:opacity-75"
onClick={(e) => menu.current.toggle(e)}
onClick={(e) => menu.current.toggle(e)}
></i>
)}
<Menu model={menuItems} popup ref={menu} />
<Menu model={menuItems} popup ref={menu} className="w-fit" />
</div>
);
@ -190,7 +208,7 @@ const SubscribeModal = ({ user }) => {
<span>Claim your own personal plebdevs.com Lightning Address</span>
</div>
<div className="flex items-center">
<Image src={NostrIcon} alt="Nostr" width={26 } height={26} className='mr-2' />
<Image src={NostrIcon} alt="Nostr" width={26} height={26} className='mr-2' />
<span>Claim your own personal plebdevs.com Nostr NIP-05 identity</span>
</div>
</div>
@ -207,12 +225,10 @@ const SubscribeModal = ({ user }) => {
</Card>
)}
</Dialog>
{calendlyVisible && (
<CalendlyEmbed
visible={calendlyVisible}
onHide={() => setCalendlyVisible(false)}
/>
)}
<CalendlyEmbed
visible={calendlyVisible}
onHide={() => setCalendlyVisible(false)}
/>
<CancelSubscription
visible={cancelSubscriptionVisible}
onHide={() => setCancelSubscriptionVisible(false)}
@ -222,6 +238,14 @@ const SubscribeModal = ({ user }) => {
onHide={() => setRenewSubscriptionVisible(false)}
subscribedUntil={subscribedUntil}
/>
<Nip05Form
visible={nip05Visible}
onHide={() => setNip05Visible(false)}
/>
<LightningAddressForm
visible={lightningAddressVisible}
onHide={() => setLightningAddressVisible(false)}
/>
</>
);
};

View File

@ -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 },
});
};

View File

@ -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 },
});
};

View File

@ -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,
},
});
}

View File

@ -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);
}
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,
},
});
}

View File

@ -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`);
}
}

View File

@ -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`);
}
}