feat(LOY-232): DTMC API Integration * feat(LOY-232): DTMC API Integration * feat(LOY-232): use employment data in team member card * refactor(LOY-232): remove static data, return employment details in parsed response & fix tests * refactor(LOY-232): improve DTMC API Linking error control flow + make res type safe * fix(LOY-232): remove unused utils * fix(LOY-232): error vars Approved-by: Christian Andolf Approved-by: Erik Tiekstra
203 lines
6.1 KiB
TypeScript
203 lines
6.1 KiB
TypeScript
import { type NextRequest, NextResponse } from "next/server"
|
|
|
|
import { overview } from "@scandic-hotels/common/constants/routes/myPages"
|
|
import * as api from "@scandic-hotels/trpc/api"
|
|
import { isValidSession } from "@scandic-hotels/trpc/utils/session"
|
|
|
|
import { DTMC_SUCCESS_BANNER_KEY } from "@/constants/dtmc"
|
|
import { linkEmploymentError } from "@/constants/routes/dtmc"
|
|
import { internalServerError } from "@/server/errors/next"
|
|
import { getPublicURL } from "@/server/utils"
|
|
|
|
import { auth } from "@/auth"
|
|
import { auth as dtmcAuth } from "@/auth.dtmc"
|
|
import { getLang } from "@/i18n/serverContext"
|
|
|
|
interface LinkEmployeeSuccessResult {
|
|
success: true
|
|
}
|
|
|
|
interface LinkEmployeeErrorResult {
|
|
success: false
|
|
statusCode: number
|
|
queryParam?: string | null
|
|
}
|
|
|
|
type LinkEmployeeResult = LinkEmployeeSuccessResult | LinkEmployeeErrorResult
|
|
|
|
/**
|
|
* Links an employee to a user account via API call.
|
|
*/
|
|
async function linkEmployeeToUser(
|
|
employeeId: string,
|
|
accessToken: string
|
|
): Promise<LinkEmployeeResult> {
|
|
console.log(`[dtmc] Linking employee ID ${employeeId}`)
|
|
let response: Response
|
|
try {
|
|
response = await api.post(
|
|
api.endpoints.v2.Profile.teamMemberCard(employeeId),
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
}
|
|
)
|
|
} catch (networkError) {
|
|
console.error("[dtmc] Network error during API request:", networkError)
|
|
return {
|
|
success: false,
|
|
statusCode: 0,
|
|
}
|
|
}
|
|
|
|
if (!response.ok) {
|
|
console.error(`[dtmc] API returned error status ${response.status}`)
|
|
try {
|
|
const errorResponse = await response.json()
|
|
console.error(`[dtmc] API error response:`, errorResponse)
|
|
} catch (parseError) {
|
|
console.warn(`[dtmc] Could not parse API error response:`, parseError)
|
|
try {
|
|
const errorText = await response.text()
|
|
console.error(`[dtmc] Raw error response:`, errorText)
|
|
} catch {
|
|
console.error(`[dtmc] Could not read error response body`)
|
|
}
|
|
}
|
|
|
|
let queryParam: string | null = null
|
|
switch (response.status) {
|
|
case 400:
|
|
case 404:
|
|
queryParam = "unable_to_verify_employee_id"
|
|
break
|
|
case 401:
|
|
queryParam = "unauthorized"
|
|
break
|
|
case 403:
|
|
queryParam = "forbidden"
|
|
break
|
|
}
|
|
return {
|
|
success: false,
|
|
statusCode: response.status,
|
|
queryParam,
|
|
}
|
|
}
|
|
|
|
console.log(`[dtmc] API call successful - Status: ${response.status}`)
|
|
console.log(
|
|
`[dtmc] Response headers:`,
|
|
Object.fromEntries(response.headers.entries())
|
|
)
|
|
try {
|
|
const responseBody = await response.json()
|
|
console.log(`[dtmc] Response body:`, responseBody)
|
|
} catch (parseError) {
|
|
console.warn(`[dtmc] Could not parse success response body:`, parseError)
|
|
}
|
|
|
|
console.log(`[dtmc] Successfully linked employee ID ${employeeId}`)
|
|
return { success: true }
|
|
}
|
|
|
|
/**
|
|
* This is the route that the NextAuth callback for Microsoft Entra ID provider
|
|
* will redirect too once it has created the session. Since it is its own cookie,
|
|
* here we can check both sessions, the Scandic Friends one and the Azure one.
|
|
*/
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const lang = await getLang()
|
|
const dtmcSession = await dtmcAuth()
|
|
const session = await auth()
|
|
const baseUrl = getPublicURL(request)
|
|
console.log("[dtmc] DTMC Callback handler - using baseUrl:", baseUrl)
|
|
|
|
if (!isValidSession(session)) {
|
|
console.error(
|
|
"[dtmc] DTMC Callback handler - No valid user session found"
|
|
)
|
|
const errorUrl = new URL(linkEmploymentError[lang], baseUrl)
|
|
errorUrl.searchParams.set("error", "no_session")
|
|
return NextResponse.redirect(errorUrl)
|
|
}
|
|
|
|
if (!isValidSession(dtmcSession)) {
|
|
console.error(
|
|
"[dtmc] DTMC Callback handler - No valid entra id session found"
|
|
)
|
|
const errorUrl = new URL(linkEmploymentError[lang], baseUrl)
|
|
errorUrl.searchParams.set("error", "no_entra_id_session")
|
|
return NextResponse.redirect(errorUrl)
|
|
}
|
|
|
|
const employeeId = dtmcSession.employeeId
|
|
|
|
console.log(
|
|
"[dtmc] DTMC Callback handler - Extracted employeeId:",
|
|
employeeId
|
|
)
|
|
|
|
if (!employeeId) {
|
|
console.error("[dtmc] DTMC Callback handler - No employeeId in session")
|
|
const errorUrl = new URL(linkEmploymentError[lang], baseUrl)
|
|
errorUrl.searchParams.set("error", "missing_employee_id")
|
|
return NextResponse.redirect(errorUrl)
|
|
}
|
|
|
|
console.log(
|
|
"[dtmc] DTMC Callback handler - Calling linkEmployeeToUser with ID:",
|
|
employeeId
|
|
)
|
|
|
|
const accessToken = session.token.access_token
|
|
if (!accessToken) {
|
|
console.error("[dtmc] DTMC Callback handler - No access token in session")
|
|
const errorUrl = new URL(linkEmploymentError[lang], baseUrl)
|
|
errorUrl.searchParams.set("error", "missing_access_token")
|
|
return NextResponse.redirect(errorUrl)
|
|
}
|
|
|
|
const result = await linkEmployeeToUser(employeeId, accessToken)
|
|
console.log(
|
|
"[dtmc] DTMC Callback handler - linkEmployeeToUser result:",
|
|
result
|
|
)
|
|
|
|
if (!result.success) {
|
|
console.error(
|
|
"[dtmc] DTMC Callback handler - Failed to verify employment:",
|
|
`Status: ${result.statusCode}, Error: ${result.queryParam}`
|
|
)
|
|
|
|
const errorUrl = new URL(linkEmploymentError[lang], baseUrl)
|
|
|
|
if (result.queryParam) {
|
|
errorUrl.searchParams.set("error", result.queryParam)
|
|
}
|
|
// For 500 errors and network errors, no query param = default error message.
|
|
return NextResponse.redirect(errorUrl)
|
|
}
|
|
|
|
console.log(
|
|
"[dtmc] DTMC Callback handler - Success! Employee linked with ID:",
|
|
employeeId
|
|
)
|
|
|
|
console.log("[dtmc] overview[lang]:", overview[lang])
|
|
const successUrl = new URL(overview[lang], baseUrl)
|
|
successUrl.searchParams.set(DTMC_SUCCESS_BANNER_KEY, "true")
|
|
console.log(
|
|
"[dtmc] DTMC Callback handler - Redirecting to success URL:",
|
|
successUrl.toString()
|
|
)
|
|
return NextResponse.redirect(successUrl)
|
|
} catch (error) {
|
|
console.error("[dtmc] DTMC Callback handler - Error in handler:", error)
|
|
return internalServerError()
|
|
}
|
|
}
|