ba-cafe

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

route.ts (3962B)


      1 import { NextResponse } from 'next/server';
      2 import { createClient } from '@supabase/supabase-js';
      3 import { messages } from '@/lib/messages';
      4 import { shouldScheduleNotification } from '@/lib/timeUtils';
      5 
      6 export async function OPTIONS() {
      7   return new NextResponse(null, {
      8     status: 204,
      9     headers: {
     10       'Access-Control-Allow-Origin': 'https://rabbit1.cc',
     11       'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
     12       'Access-Control-Allow-Headers': 'Content-Type, Authorization',
     13     },
     14   });
     15 }
     16 
     17 export async function POST(request: Request) {
     18   try {
     19     const body = await request.json();
     20     const { tapTime, ticket1Time, ticket2Time } = body;
     21     const authHeader = request.headers.get('Authorization');
     22     const supabase = createClient(
     23       process.env.NEXT_PUBLIC_SUPABASE_URL!,
     24       process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
     25       { global: { headers: { Authorization: authHeader ?? '' } } }
     26     );
     27 
     28     if (!authHeader) return NextResponse.json({ error: 'No token' }, { status: 401 });
     29 
     30     const { data: { user }, error: authError } = await supabase.auth.getUser();
     31     if (authError || !user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
     32 
     33     // ミリ秒を切り捨てた基準時刻を作成
     34     const now = new Date();
     35     now.setMilliseconds(0);
     36     const nowIso = now.toISOString();
     37 
     38     // 1. カフェタップの処理
     39     if (tapTime) {
     40       const { data: lastTap } = await supabase
     41         .from('taps')
     42         .select('tap_time')
     43         .eq('user_id', user.id)
     44         .order('tap_time', { ascending: false })
     45         .limit(1)
     46         .maybeSingle();
     47       
     48       const currentTapDate = new Date(tapTime);
     49       currentTapDate.setMilliseconds(0);
     50 
     51       if (lastTap) {
     52         const lastTapDate = new Date(lastTap.tap_time);
     53         const diffMs = currentTapDate.getTime() - lastTapDate.getTime();
     54 
     55         if (diffMs < 3600000) { 
     56           if (shouldScheduleNotification(lastTapDate) === shouldScheduleNotification(currentTapDate)) {
     57             return NextResponse.json({ error: 'Duplicate tap' }, { status: 429 });
     58           }
     59         }
     60       }
     61       await supabase.from('taps').insert([{ user_id: user.id, tap_time: currentTapDate.toISOString() }]);
     62     }
     63 
     64     // 2. プロフィールの更新
     65     const upsertData: any = { 
     66       id: user.id, 
     67       updated_at: nowIso 
     68     };
     69 
     70     if (ticket1Time !== undefined) {
     71       const d1 = ticket1Time ? new Date(ticket1Time) : null;
     72       if (d1) d1.setMilliseconds(0);
     73       upsertData.ticket1_time = d1 ? d1.toISOString() : null;
     74     }
     75     if (ticket2Time !== undefined) {
     76       const d2 = ticket2Time ? new Date(ticket2Time) : null;
     77       if (d2) d2.setMilliseconds(0);
     78       upsertData.ticket2_time = d2 ? d2.toISOString() : null;
     79     }
     80 
     81     await supabase.from('profiles').upsert(upsertData);
     82 
     83     // 3. 通知予約処理
     84     if (tapTime && shouldScheduleNotification(new Date(tapTime))) {
     85       const sendAfter = new Date(tapTime);
     86       sendAfter.setSeconds(0, 0); 
     87       sendAfter.setHours(sendAfter.getHours() + 3);
     88 
     89       const randomMsg = messages[Math.floor(Math.random() * messages.length)];
     90       
     91       await fetch("https://onesignal.com/api/v1/notifications", {
     92         method: "POST",
     93         headers: {
     94           "Content-Type": "application/json",
     95           "Authorization": `Basic ${process.env.ONESIGNAL_REST_API_KEY}`
     96         },
     97         body: JSON.stringify({
     98           app_id: process.env.NEXT_PUBLIC_ONESIGNAL_APP_ID,
     99           include_aliases: { external_id: [user.id] },
    100           target_channel: "push",
    101           contents: { en: randomMsg.body, ja: randomMsg.body },
    102           headings: { en: randomMsg.title, ja: randomMsg.title },
    103           send_after: sendAfter.toISOString(), 
    104         })
    105       });
    106     }
    107 
    108     return NextResponse.json({ success: true });
    109 
    110   } catch (error: any) {
    111     console.error("API Error:", error);
    112     return NextResponse.json({ error: error.message }, { status: 500 });
    113   }
    114 }