All files / src/app/api/push/subscribe route.ts

0% Statements 0/93
100% Branches 0/0
0% Functions 0/1
0% Lines 0/93

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94                                                                                                                                                                                           
/**
 * Push Notification Subscribe API
 *
 * Stores push notification subscriptions for users.
 * POST /api/push/subscribe
 */

import { NextRequest, NextResponse } from 'next/server';
import { withAuth } from '@/lib/api/middleware';
import {
  successResponse,
  errorResponse,
  validationErrorResponse,
  type ApiResponse,
} from '@/lib/api/responses';
import { prisma } from '@/lib/prisma';
import { z } from 'zod';
import type { Session } from 'next-auth';

// Subscription schema matching Web Push subscription format
const subscriptionSchema = z.object({
  endpoint: z.string().url(),
  expirationTime: z.number().nullable().optional(),
  keys: z.object({
    p256dh: z.string(),
    auth: z.string(),
  }),
});

export const POST = withAuth<ApiResponse<{ message: string }>>(
  async (request: NextRequest, _context, session: Session) => {
    try {
      const userId = session.user?.id;
      if (!userId || typeof userId !== 'number') {
        return errorResponse('INVALID_USER', 'User ID not found', { status: 401 });
      }

      const body = await request.json();
      const parseResult = subscriptionSchema.safeParse(body);

      if (!parseResult.success) {
        const errorMessages = parseResult.error.issues
          .map((issue) => issue.message)
          .join(', ');
        return validationErrorResponse(errorMessages, parseResult.error.issues);
      }

      const { endpoint, expirationTime, keys } = parseResult.data;

      // Check if subscription already exists for this user/endpoint
      const existingSubscription = await prisma.pushSubscription.findFirst({
        where: {
          userId,
          endpoint,
        },
      });

      if (existingSubscription) {
        // Update existing subscription
        await prisma.pushSubscription.update({
          where: { id: existingSubscription.id },
          data: {
            p256dh: keys.p256dh,
            auth: keys.auth,
            expirationTime: expirationTime ? new Date(expirationTime) : null,
            updatedAt: new Date(),
          },
        });

        return successResponse({ message: 'Subscription updated' });
      }

      // Create new subscription
      await prisma.pushSubscription.create({
        data: {
          userId,
          endpoint,
          p256dh: keys.p256dh,
          auth: keys.auth,
          expirationTime: expirationTime ? new Date(expirationTime) : null,
        },
      });

      return successResponse({ message: 'Subscribed' }, { status: 201 });
    } catch (error) {
      console.error('Push subscribe error:', error);
      return errorResponse('SUBSCRIBE_ERROR', 'Failed to save push subscription');
    }
  }
) as (
  request: Request,
  context?: { params?: Promise<Record<string, string>> }
) => Promise<NextResponse>;