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 (
)
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")
}