Files
aerbim-ht-monitor/frontend/app/api/get-zones/route.ts

184 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextResponse, NextRequest } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { getToken } from 'next-auth/jwt'
export async function GET(req: NextRequest) {
try {
const session = await getServerSession(authOptions)
const authHeader = req.headers.get('authorization') || req.headers.get('Authorization')
const bearer = authHeader && authHeader.toLowerCase().startsWith('bearer ') ? authHeader.slice(7) : undefined
const secret = process.env.NEXTAUTH_SECRET
const token = secret ? (await getToken({ req, secret }).catch(() => null)) : null
let accessToken: string | undefined = session?.accessToken || bearer || (token as any)?.accessToken
const refreshToken: string | undefined = session?.refreshToken || (token as any)?.refreshToken
if (!accessToken && refreshToken) {
try {
const refreshRes = await fetch(`${process.env.BACKEND_URL}/auth/refresh/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh: refreshToken }),
})
if (refreshRes.ok) {
const refreshed = await refreshRes.json()
accessToken = refreshed.access
} else {
const errorText = await refreshRes.text()
let errorData: { error?: string; detail?: string; code?: string } = {}
try {
errorData = JSON.parse(errorText)
} catch {
errorData = { error: errorText }
}
const errorMessage = (errorData.error as string) || (errorData.detail as string) || ''
if (typeof errorMessage === 'string' &&
(errorMessage.includes('Token is expired') ||
errorMessage.includes('expired') ||
errorData.code === 'token_not_valid')) {
console.warn('Refresh token expired, user needs to re-authenticate')
} else {
console.error('Token refresh failed:', errorData.error || errorData.detail || 'Unknown error')
}
}
} catch (error) {
console.error('Error during token refresh:', error)
}
}
if (!accessToken) {
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
}
const backendUrl = process.env.BACKEND_URL
if (!backendUrl) {
return NextResponse.json({ success: false, error: 'BACKEND_URL is not configured' }, { status: 500 })
}
const { searchParams } = new URL(req.url)
const objectId = searchParams.get('objectId')
if (!objectId) {
return NextResponse.json({ success: false, error: 'objectId query parameter is required' }, { status: 400 })
}
// Нормализуем objectId с фронтенда вида "object_2" к числовому идентификатору бэкенда "2"
const normalizedObjectId = /^object_(\d+)$/.test(objectId) ? objectId.replace(/^object_/, '') : objectId
const zonesRes = await fetch(`${backendUrl}/account/get-zones/?objectId=${encodeURIComponent(normalizedObjectId)}`, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
cache: 'no-store',
})
const payloadText = await zonesRes.text()
let payload: any
try { payload = JSON.parse(payloadText) } catch { payload = payloadText }
// Отладка: наблюдаем статус ответа и предполагаемую длину
console.log(
'[api/get-zones] objectId=%s normalized=%s status=%d payloadType=%s length=%d',
objectId,
normalizedObjectId,
zonesRes.status,
typeof payload,
Array.isArray((payload as any)?.data)
? (payload as any).data.length
: Array.isArray(payload)
? payload.length
: 0
)
if (!zonesRes.ok) {
if (payload && typeof payload === 'object') {
if (payload.code === 'token_not_valid' ||
(payload.detail && typeof payload.detail === 'string' && (payload.detail.includes('Token is expired') || payload.detail.includes('Given token not valid'))) ||
(payload.messages && Array.isArray(payload.messages) && payload.messages.some((msg: any) =>
msg.message && typeof msg.message === 'string' && msg.message.includes('Token is expired')
))) {
console.warn('Access token expired, user needs to re-authenticate')
return NextResponse.json({
success: false,
error: 'Authentication required - please log in again'
}, { status: 401 })
}
}
// Резервный путь: пробуем получить список объектов и извлечь зоны для указанного objectId
try {
const objectsRes = await fetch(`${backendUrl}/account/get-objects/`, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
cache: 'no-store',
})
const objectsText = await objectsRes.text()
let objectsPayload: any
try { objectsPayload = JSON.parse(objectsText) } catch { objectsPayload = objectsText }
if (objectsRes.ok) {
const objectsList: any[] = Array.isArray(objectsPayload?.data) ? objectsPayload.data
: (Array.isArray(objectsPayload) ? objectsPayload : (Array.isArray(objectsPayload?.objects) ? objectsPayload.objects : []))
const target = objectsList.find((o: any) => (
o?.id === normalizedObjectId || o?.object_id === normalizedObjectId || o?.slug === normalizedObjectId || o?.identifier === normalizedObjectId
))
const rawZones: any[] = target?.zones || target?.zone_list || target?.areas || target?.Зоны || []
const normalized = Array.isArray(rawZones) ? rawZones.map((z: any, idx: number) => ({
id: z?.id ?? z?.zone_id ?? `${normalizedObjectId}_zone_${idx}`,
name: z?.name ?? z?.zone_name ?? `Зона ${idx + 1}`,
floor: typeof z?.floor === 'number' ? z.floor : (typeof z?.level === 'number' ? z.level : 0),
image_path: z?.image_path ?? z?.image ?? z?.preview_image ?? null,
model_path: z?.model_path ?? z?.model ?? z?.modelUrl ?? null,
order: typeof z?.order === 'number' ? z.order : 0,
sensors: Array.isArray(z?.sensors) ? z.sensors : (Array.isArray(z?.detectors) ? z.detectors : []),
})) : []
// Отладка: длина массива в резервном пути
console.log('[api/get-zones:fallback] normalized length=%d', normalized.length)
// Возвращаем успешный ответ с нормализованными зонами (может быть пустой массив)
return NextResponse.json({ success: true, data: normalized }, { status: 200 })
}
} catch (fallbackErr) {
console.warn('Fallback get-objects failed:', fallbackErr)
}
// Если дошли до сюда, возвращаем успешный ответ с пустым списком, чтобы не ломать UI
return NextResponse.json({ success: true, data: [] }, { status: 200 })
}
// Распаковываем массив зон от бэкенда в плоский список в поле data
const zonesData: any[] = Array.isArray((payload as any)?.data)
? (payload as any).data
: Array.isArray(payload)
? (payload as any)
: Array.isArray((payload as any)?.zones)
? (payload as any).zones
: []
return NextResponse.json({ success: true, data: zonesData }, { status: 200 })
// Нормализация: при необходимости используем запасной image_path на стороне клиента
return NextResponse.json({ success: true, data: payload })
} catch (error) {
console.error('Error fetching zones data:', error)
return NextResponse.json(
{
success: false,
error: 'Failed to fetch zones data',
},
{ status: 500 }
)
}
}