Skip to content

feat: add POST /api/routes-b/contacts endpoint#456

Open
tranhoangtu-it wants to merge 1 commit intodavedumto:mainfrom
tranhoangtu-it:feat/post-contacts-endpoint
Open

feat: add POST /api/routes-b/contacts endpoint#456
tranhoangtu-it wants to merge 1 commit intodavedumto:mainfrom
tranhoangtu-it:feat/post-contacts-endpoint

Conversation

@tranhoangtu-it
Copy link
Copy Markdown

Summary

  • Add POST /api/routes-b/contacts endpoint in app/api/routes-b/contacts/route.ts
  • Follows existing patterns from bank-accounts/route.ts
  • Contact model already exists in Prisma schema — no migration needed

Validation

  • name: required, max 100 chars
  • email: required, valid format (regex)
  • company, notes: optional strings
  • Duplicate [userId, email]409 Conflict
  • Unauthenticated → 401

Test plan

  • POST with valid body → 201 with created contact
  • Duplicate email → 409
  • Invalid email → 400
  • Missing name → 400
  • No auth token → 401

Closes #454

Create a new contact for the authenticated user with validation:
- name: required, max 100 chars
- email: required, valid format
- company/notes: optional
- Duplicate email per user returns 409 Conflict
- Unauthenticated requests return 401

Contact model already exists in schema — no migration needed.

Closes davedumto#454
Copilot AI review requested due to automatic review settings March 28, 2026 08:02
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 28, 2026

@tranhoangtu-it is attempting to deploy a commit to the david's projects Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new API endpoint under the routes-b API surface to create Contact records for the authenticated user, using Prisma and the existing Privy token auth helper.

Changes:

  • Introduces POST /api/routes-b/contacts route handler with basic input validation (name, email)
  • Implements per-user duplicate email detection and returns 409 Conflict
  • Creates the contact record and returns a 201 with a selected response payload

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +7 to +8
export async function POST(request: NextRequest) {
const authToken = request.headers.get('authorization')?.replace('Bearer ', '')
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR/issue description mentions adding a POST handler alongside an existing GET contacts handler in this file, but this route currently only exports POST. If GET is expected for /api/routes-b/contacts, it should be included here; otherwise the PR description/issue scope should be updated to match what’s actually being shipped.

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +48
if (
typeof email !== 'string' ||
email.trim() === '' ||
!EMAIL_REGEX.test(email.trim())
) {
return NextResponse.json(
{ error: 'A valid email address is required.' },
{ status: 400 },
)
}

// Check duplicate email for this user
const existing = await prisma.contact.findUnique({
where: { userId_email: { userId: user.id, email: email.trim() } },
})
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Email is only trimmed, not normalized (e.g., lowercased). This allows duplicates that differ only by case ("Alice@acme.com" vs "alice@acme.com") to bypass the duplicate check/unique constraint expectations and also diverges from other endpoints that persist emails lowercased (e.g., routes-d contacts/invoices). Consider normalizing to email.trim().toLowerCase() for validation, the duplicate lookup, and the create call.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +70
// Check duplicate email for this user
const existing = await prisma.contact.findUnique({
where: { userId_email: { userId: user.id, email: email.trim() } },
})
if (existing) {
return NextResponse.json(
{ error: 'A contact with this email already exists.' },
{ status: 409 },
)
}

const contact = await prisma.contact.create({
data: {
userId: user.id,
name: name.trim(),
email: email.trim(),
company: typeof company === 'string' ? company.trim() || null : null,
notes: typeof notes === 'string' ? notes.trim() || null : null,
},
select: {
id: true,
name: true,
email: true,
company: true,
},
})
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The duplicate check + create is subject to a race condition: two concurrent requests can both pass findUnique and then one will fail on the DB unique constraint during create, likely returning a 500 instead of the required 409. Wrap the prisma.contact.create in a try/catch and map Prisma unique constraint errors (P2002) to a 409 response (or rely solely on catching P2002 without the pre-check).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[routes-b] POST /api/routes-b/contacts — create a new contact

2 participants