File size: 7,784 Bytes
9853b20
 
 
 
dac0489
9853b20
 
 
 
 
dac0489
9853b20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dac0489
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9853b20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
import { NextRequest, NextResponse } from 'next/server'
import { hashPin, generateSessionHash, generateCode, createCodePair } from 'lib/codes'
import { createRoom } from 'lib/roomService'
import { setAnonymousSession } from 'lib/session'
import { getCodeByCode, getCodeById, insertCode, updateCode, updateRoom, updateCodesRoomId } from 'lib/persistence'
import { logEvent } from 'lib/logger'

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const { code, pin, userAgent, codes } = body

    console.log('[codes/setup] Incoming request body:', { code, hasPin: Boolean(pin), userAgent })

    if (!code) {
      console.warn('[codes/setup] Missing code in request body')
      return NextResponse.json(
        { error: 'Chyb¡ k¢d m¡stnosti' },
        { status: 400 }
      )
    }

    let codeData = await getCodeByCode(code)

    // If code is missing or already used (used === 1), create a fresh pair and use that instead.
    if (!codeData || codeData?.used === 1) {
      console.warn('[codes/setup] Code missing or already used - creating new pair for user', { requested: code.toUpperCase(), existing: !!codeData, used: codeData?.used ?? null })
      try {
        const { code1, code2 } = await createCodePair()
        const createdPrimary = await insertCode({
          code: code1,
          linked_to: null,
          used: null,
          used_count: 0
        })

        if (!createdPrimary) {
          throw new Error('Failed to create primary code')
        }

        const createdSecondary = await insertCode({
          code: code2,
          linked_to: createdPrimary.id,
          used: null,
          used_count: 0
        })

        if (!createdSecondary) {
          // Best-effort cleanup marker
          await updateCode(createdPrimary.id, {} as any)
          throw new Error('Failed to create secondary code')
        }

        const linkResult = await updateCode(createdPrimary.id, { linked_to: createdSecondary.id })
        if (!linkResult) {
          throw new Error('Failed to link primary and secondary codes')
        }

        codeData = { ...createdPrimary, linked_to: createdSecondary.id }
      } catch (createErr) {
        console.error('[codes/setup] Failed to create code pair fallback:', createErr)
        return NextResponse.json(
          { error: 'Nepodaýilo se vytvoýit k¢d' },
          { status: 500 }
        )
      }
    }

    // If multiple codes were supplied, create/ensure them and bind to one room
    if (Array.isArray(codes) && codes.length > 0) {
      const normalized = Array.from(new Set(codes.map((c: any) => String(c).toUpperCase().trim()).filter(Boolean)))
      if (normalized.length < 2) {
        console.warn('[codes/setup] multi-code flow requires at least 2 codes')
      } else {
        // Ensure each code exists (create if missing)
        const rows: Array<any> = []
        for (const candidate of normalized) {
          let cd = await getCodeByCode(candidate)
          if (!cd) {
            cd = await insertCode({ code: candidate, linked_to: null, used: null, used_count: 0 })
            if (!cd) {
              console.error('[codes/setup] failed to create code', candidate)
              return NextResponse.json({ error: 'Nepodařilo se vytvořit kódy' }, { status: 500 })
            }
          }
          rows.push(cd)
        }

        // Create room linked to first two codes and attach remaining codes
        const roomId = await createRoom(rows[0].id, rows[1].id)
        const remaining = rows.slice(2).map((r) => r.id)
        if (remaining.length > 0) {
          await updateCodesRoomId(remaining, roomId)
        }

        await logEvent({ module: 'codes', operation: 'setup_multi', data: { codes: normalized, roomId } })

        return NextResponse.json({ success: true, roomId, codes: normalized })
      }
    }

    // Ensure we have a linked code. If missing, create a linked secondary code.
    if (!codeData || !codeData.linked_to) {
      console.warn('[codes/setup] code.linked_to missing, creating secondary code', { codeId: codeData?.id || null })
      let createdSecondary = null
      for (let i = 0; i < 5; i++) {
        const candidate = generateCode()
        const sec = await insertCode({
          code: candidate,
          linked_to: codeData!.id,
          used: null,
          used_count: 0
        })

        if (sec) {
          createdSecondary = sec
          break
        }
      }

      if (!createdSecondary) {
        console.error('[codes/setup] Failed to create a linked secondary code')
        return NextResponse.json({ error: 'Nepodaýilo se vytvoýit p rovì k¢d' }, { status: 500 })
      }

      const linkResult = await updateCode(codeData!.id, { linked_to: createdSecondary.id })

      if (!linkResult) {
        console.error('[codes/setup] Failed to link primary to secondary')
        return NextResponse.json({ error: 'Nepodaýilo se aktualizovat p rov‚ k¢dy' }, { status: 500 })
      }

      codeData!.linked_to = createdSecondary.id
    }

    const linkedCode = await getCodeById(codeData!.linked_to!)

    if (!linkedCode) {
      return NextResponse.json(
        { error: 'P rovì k¢d nenalezen' },
        { status: 400 }
      )
    }

    const roomId = await createRoom(codeData!.id, linkedCode.id)

    const sessionHash = generateSessionHash(code, userAgent || '')
    let pinHash: string | undefined

    if (pin) {
      pinHash = await hashPin(pin)
    }

    const now = new Date().toISOString()

    // Mark code as used with PIN or session - include userId if authenticated
    const updatePayload: Record<string, unknown> = {
      used: 1,
      room_id: roomId,
      pin_hash: pinHash || null,
      session_hash: !pin ? sessionHash : null,
      date_first: now,
      date_last: now,
      used_count: 1
    }
    // Do not set user_id server-side here to avoid relying on cookies/session in this route

    const updated = await updateCode(codeData!.id, updatePayload as any)
    const updateErr = !updated ? { message: 'Update failed' } : null

    if (updateErr) {
      // Attempt cleanup: disable created room to avoid dangling room
      try {
        await updateRoom(roomId, { status: 0 })
      } catch (cleanupErr) {
        console.error('Cleanup failed after codes update error:', cleanupErr)
      }
      console.error('Failed to update code after creating room:', updateErr)
      return NextResponse.json(
        { error: 'Nepodaýilo se oznaŸit k¢d jako pou§itì (RLS nebo db chyba)', detail: updateErr.message || updateErr },
        { status: 500 }
      )
    }

    // Set anonymous session if no PIN
    if (!pin) {
      await setAnonymousSession({
        codeId: codeData!.id,
        sessionHash,
        roomId,
        expiresAt: Date.now() + (10 * 365 * 24 * 60 * 60 * 1000) // 10 let pro bez PIN
      })
    } else {
      // Set session for PIN-protected code
      await setAnonymousSession({
        codeId: codeData!.id,
        sessionHash: Buffer.from(`${codeData!.id}:${Date.now()}`).toString('base64'),
        roomId,
        expiresAt: Date.now() + (30 * 24 * 60 * 60 * 1000) // 30 dn¡ pro s PIN
      })
    }

    await logEvent({
      module: 'codes',
      operation: 'setup',
      data: { codeId: codeData!.id, roomId, pinSet: Boolean(pin) },
      ip: (request as any).headers?.get?.('x-forwarded-for') || null,
      userAgent: (request as any).headers?.get?.('user-agent') || null
    })

    return NextResponse.json({
      success: true,
      roomId,
      codeId: codeData!.id
    })
  } catch (error) {
    console.error('Code setup error:', error)
    return NextResponse.json(
      { error: 'Vnitýn¡ chyba serveru' },
      { status: 500 }
    )
  }
}