diff --git a/api/resolvers/invite.js b/api/resolvers/invite.js index 347d71523..af3a7c9c3 100644 --- a/api/resolvers/invite.js +++ b/api/resolvers/invite.js @@ -1,7 +1,8 @@ import { inviteSchema, validateSchema } from '@/lib/validate' import { msatsToSats } from '@/lib/format' import assertApiKeyNotPermitted from './apiKey' -import { GqlAuthenticationError } from '@/lib/error' +import { GqlAuthenticationError, GqlInputError } from '@/lib/error' +import { Prisma } from '@prisma/client' export default { Query: { @@ -9,7 +10,6 @@ export default { if (!me) { throw new GqlAuthenticationError() } - return await models.invite.findMany({ where: { userId: me.id @@ -29,17 +29,31 @@ export default { }, Mutation: { - createInvite: async (parent, { gift, limit }, { me, models }) => { + createInvite: async (parent, { id, gift, limit, description }, { me, models }) => { if (!me) { throw new GqlAuthenticationError() } assertApiKeyNotPermitted({ me }) - await validateSchema(inviteSchema, { gift, limit }) - - return await models.invite.create({ - data: { gift, limit, userId: me.id } - }) + await validateSchema(inviteSchema, { id, gift, limit, description }) + try { + return await models.invite.create({ + data: { + id, + gift, + limit, + userId: me.id, + description + } + }) + } catch (error) { + if (error instanceof Prisma.PrismaClientKnownRequestError) { + if (error.code === 'P2002' && error.meta.target.includes('id')) { + throw new GqlInputError('an invite with this code already exists') + } + } + throw error + } }, revokeInvite: async (parent, { id }, { me, models }) => { if (!me) { diff --git a/api/typeDefs/invite.js b/api/typeDefs/invite.js index 93e8f964a..ef889f943 100644 --- a/api/typeDefs/invite.js +++ b/api/typeDefs/invite.js @@ -7,7 +7,7 @@ export default gql` } extend type Mutation { - createInvite(gift: Int!, limit: Int): Invite + createInvite(id:String, gift: Int!, limit: Int, description: String): Invite revokeInvite(id: ID!): Invite } @@ -20,5 +20,6 @@ export default gql` user: User! revoked: Boolean! poor: Boolean! + description: String } ` diff --git a/components/invite.js b/components/invite.js index 3f1fc8e2b..fc334a013 100644 --- a/components/invite.js +++ b/components/invite.js @@ -23,6 +23,7 @@ export default function Invite ({ invite, active }) { size='sm' type='text' placeholder={`${process.env.NEXT_PUBLIC_URL}/invites/${invite.id}`} readOnly noForm /> + {invite.description && {invite.description}}
{invite.gift} sat gift \ diff --git a/components/notifications.js b/components/notifications.js index 3567a32c9..57bc62410 100644 --- a/components/notifications.js +++ b/components/notifications.js @@ -283,7 +283,7 @@ function Invitification ({ n }) { <> your invite has been redeemed by - {numWithUnits(n.invite.invitees.length, { + {' ' + numWithUnits(n.invite.invitees.length, { abbreviate: false, unitSingular: 'stacker', unitPlural: 'stackers' diff --git a/fragments/invites.js b/fragments/invites.js index 038ba53f3..1abc5488f 100644 --- a/fragments/invites.js +++ b/fragments/invites.js @@ -19,5 +19,6 @@ export const INVITE_FIELDS = gql` ...StreakFields } poor + description } ` diff --git a/lib/validate.js b/lib/validate.js index 62b62d3b9..788920055 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -478,7 +478,9 @@ export const bioSchema = object({ export const inviteSchema = object({ gift: intValidator.positive('must be greater than 0').required('required'), - limit: intValidator.positive('must be positive') + limit: intValidator.positive('must be positive'), + description: string().trim().max(42, 'must be at most 42 characters'), + id: string().matches(/^[\w-]+$/, 'invalid id').min(4, 'must be at least 4 characters').max(21, 'must be at most 21 characters') }) export const pushSubscriptionSchema = object({ diff --git a/lib/webPush.js b/lib/webPush.js index 5d054aed7..41132f9be 100644 --- a/lib/webPush.js +++ b/lib/webPush.js @@ -300,9 +300,9 @@ export const notifyReferral = async (userId) => { } } -export const notifyInvite = async (userId) => { +export const notifyInvite = async (userId, inviteId, inviteDescription) => { try { - await sendUserNotification(userId, { title: 'your invite has been redeemed', tag: 'INVITE' }) + await sendUserNotification(userId, { title: `your invite with code ${inviteId} has been redeemed${inviteDescription ? ` (${inviteDescription})` : ''}`, tag: 'INVITE' }) } catch (err) { console.error(err) } diff --git a/pages/invites/[id].js b/pages/invites/[id].js index c33076cf0..50e41e0c6 100644 --- a/pages/invites/[id].js +++ b/pages/invites/[id].js @@ -41,7 +41,7 @@ export async function getServerSideProps ({ req, res, query: { id, error = null { models } ) const invite = await models.invite.findUnique({ where: { id } }) - notifyInvite(invite.userId) + notifyInvite(invite.userId, invite.id, invite.description) } catch (e) { console.log(e) } diff --git a/pages/invites/index.js b/pages/invites/index.js index 37c71b136..18b43993e 100644 --- a/pages/invites/index.js +++ b/pages/invites/index.js @@ -17,8 +17,8 @@ function InviteForm () { const [createInvite] = useMutation( gql` ${INVITE_FIELDS} - mutation createInvite($gift: Int!, $limit: Int) { - createInvite(gift: $gift, limit: $limit) { + mutation createInvite($id: String, $gift: Int!, $limit: Int, $description: String) { + createInvite(id:$id, gift: $gift, limit: $limit, description: $description) { ...InviteFields } }`, { @@ -42,19 +42,28 @@ function InviteForm () { return (
{ + onSubmit={async ({ id, gift, limit, description }) => { const { error } = await createInvite({ variables: { - gift: Number(gift), limit: limit ? Number(limit) : limit + id, + gift: Number(gift), + limit: limit ? Number(limit) : limit, + description } }) if (error) throw error }} > + invite code optional} + name='id' + /> invitee limit optional} name='limit' /> - + note optional} + name='description' + /> create
) diff --git a/prisma/migrations/20241125195641_custom_invites/migration.sql b/prisma/migrations/20241125195641_custom_invites/migration.sql new file mode 100644 index 000000000..aa7e66a5f --- /dev/null +++ b/prisma/migrations/20241125195641_custom_invites/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Invite" ADD COLUMN "description" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5fd081924..9a3505538 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -473,6 +473,8 @@ model Invite { user User @relation("Invites", fields: [userId], references: [id], onDelete: Cascade) invitees User[] + description String? + @@index([createdAt], map: "Invite.created_at_index") @@index([userId], map: "Invite.userId_index") }