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 }