Initial commit from monorepo
This commit is contained in:
40
server/api/auth/debug-session.get.ts
Normal file
40
server/api/auth/debug-session.get.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { defineEventHandler, createError } from 'h3'
|
||||
import type LogtoClient from '@logto/node'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const client = event.context.logtoClient as LogtoClient | undefined
|
||||
const user = event.context.logtoUser
|
||||
|
||||
if (!client) {
|
||||
return {
|
||||
authenticated: false,
|
||||
user: null,
|
||||
idToken: null,
|
||||
refreshToken: null
|
||||
}
|
||||
}
|
||||
|
||||
let idToken = null
|
||||
let refreshToken = null
|
||||
|
||||
try {
|
||||
idToken = await client.getIdToken()
|
||||
}
|
||||
catch {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
try {
|
||||
refreshToken = await client.getRefreshToken()
|
||||
}
|
||||
catch {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
return {
|
||||
authenticated: true,
|
||||
user,
|
||||
idToken,
|
||||
refreshToken: refreshToken ? `${refreshToken.substring(0, 20)}...` : null
|
||||
}
|
||||
})
|
||||
18
server/api/auth/id-token-claims.get.ts
Normal file
18
server/api/auth/id-token-claims.get.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineEventHandler, createError } from 'h3'
|
||||
import type LogtoClient from '@logto/node'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const client = event.context.logtoClient as LogtoClient | undefined
|
||||
|
||||
if (!client) {
|
||||
throw createError({ statusCode: 401, message: 'Not authenticated' })
|
||||
}
|
||||
|
||||
try {
|
||||
const claims = await client.getIdTokenClaims()
|
||||
return { claims }
|
||||
}
|
||||
catch {
|
||||
return { claims: null }
|
||||
}
|
||||
})
|
||||
18
server/api/auth/id-token.get.ts
Normal file
18
server/api/auth/id-token.get.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineEventHandler, createError } from 'h3'
|
||||
import type LogtoClient from '@logto/node'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const client = event.context.logtoClient as LogtoClient | undefined
|
||||
|
||||
if (!client) {
|
||||
throw createError({ statusCode: 401, message: 'Not authenticated' })
|
||||
}
|
||||
|
||||
try {
|
||||
const idToken = await client.getIdToken()
|
||||
return { id_token: idToken }
|
||||
}
|
||||
catch {
|
||||
return { id_token: null }
|
||||
}
|
||||
})
|
||||
76
server/api/auth/refresh.post.ts
Normal file
76
server/api/auth/refresh.post.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { defineEventHandler, createError } from 'h3'
|
||||
import type LogtoClient from '@logto/node'
|
||||
|
||||
const RESOURCES = {
|
||||
teams: 'https://teams.optovia.ru',
|
||||
exchange: 'https://exchange.optovia.ru',
|
||||
orders: 'https://orders.optovia.ru',
|
||||
kyc: 'https://kyc.optovia.ru',
|
||||
billing: 'https://billing.optovia.ru'
|
||||
} as const
|
||||
|
||||
export type ResourceKey = keyof typeof RESOURCES
|
||||
|
||||
export interface TokenInfo {
|
||||
token: string
|
||||
expiresAt: number
|
||||
}
|
||||
|
||||
export interface RefreshResponse {
|
||||
tokens: Partial<Record<ResourceKey, TokenInfo>>
|
||||
}
|
||||
|
||||
function decodeTokenExpiry(token: string): number {
|
||||
try {
|
||||
const payload = token.split('.')[1]
|
||||
if (payload) {
|
||||
const decoded = JSON.parse(Buffer.from(payload, 'base64').toString('utf8'))
|
||||
if (decoded.exp) {
|
||||
return decoded.exp * 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// ignore
|
||||
}
|
||||
return Date.now() + 3600 * 1000 // default 1 hour
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh all access tokens for all resources.
|
||||
* Gets organizationId from logtoUser (first organization).
|
||||
* Returns tokens with expiry timestamps.
|
||||
*/
|
||||
export default defineEventHandler(async (event): Promise<RefreshResponse> => {
|
||||
const client = event.context.logtoClient as LogtoClient | undefined
|
||||
const logtoUser = event.context.logtoUser as { organizations?: string[] } | undefined
|
||||
|
||||
if (!client) {
|
||||
throw createError({ statusCode: 401, message: 'Not authenticated' })
|
||||
}
|
||||
|
||||
// Get first organization from Logto user
|
||||
const organizationId = logtoUser?.organizations?.[0]
|
||||
|
||||
const tokens: Partial<Record<ResourceKey, TokenInfo>> = {}
|
||||
|
||||
// Fetch all tokens in parallel with organization context
|
||||
await Promise.all(
|
||||
(Object.entries(RESOURCES) as [ResourceKey, string][]).map(async ([key, resource]) => {
|
||||
try {
|
||||
const token = await client.getAccessToken(resource, organizationId)
|
||||
if (token) {
|
||||
tokens[key] = {
|
||||
token,
|
||||
expiresAt: decodeTokenExpiry(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Token not available for this resource, skip
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return { tokens }
|
||||
})
|
||||
148
server/api/companies.get.ts
Normal file
148
server/api/companies.get.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = getQuery(event)
|
||||
const locale = query.locale || 'ru'
|
||||
|
||||
// Service companies data with translations
|
||||
const companiesData = {
|
||||
ru: {
|
||||
logistics: [
|
||||
{
|
||||
id: 1,
|
||||
name: "RosLogistic",
|
||||
price: 15000,
|
||||
description: "Federal transportation network",
|
||||
rating: 4.8,
|
||||
reviews: 156,
|
||||
experience: "15 years experience",
|
||||
verified: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "TransService",
|
||||
price: 12000,
|
||||
description: "Regional carrier",
|
||||
rating: 4.5,
|
||||
reviews: 89,
|
||||
experience: "8 years experience",
|
||||
verified: false
|
||||
}
|
||||
],
|
||||
banks: [
|
||||
{
|
||||
id: 1,
|
||||
name: "VTB",
|
||||
rate: 12.5,
|
||||
terms: "Raw materials purchase loan",
|
||||
period: "up to 24 months",
|
||||
rating: 4.7,
|
||||
reviews: 89,
|
||||
verified: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Sberbank",
|
||||
rate: 13.2,
|
||||
terms: "Trade financing",
|
||||
period: "up to 18 months",
|
||||
rating: 4.9,
|
||||
reviews: 145,
|
||||
verified: true
|
||||
}
|
||||
],
|
||||
insurance: [
|
||||
{
|
||||
id: 1,
|
||||
name: "RESO",
|
||||
cost: 3500,
|
||||
coverage: "Cargo insurance up to 500M₽",
|
||||
rating: 4.6,
|
||||
reviews: 67,
|
||||
verified: true
|
||||
}
|
||||
],
|
||||
laboratories: [
|
||||
{
|
||||
id: 1,
|
||||
name: "MetalTest",
|
||||
price: 8000,
|
||||
tests: "Chemical analysis, mechanical properties",
|
||||
certification: "ISO 9001",
|
||||
rating: 4.7,
|
||||
reviews: 45,
|
||||
verified: true
|
||||
}
|
||||
]
|
||||
},
|
||||
en: {
|
||||
logistics: [
|
||||
{
|
||||
id: 1,
|
||||
name: "RusLogistics",
|
||||
price: 15000,
|
||||
description: "Federal transportation network",
|
||||
rating: 4.8,
|
||||
reviews: 156,
|
||||
experience: "15 years experience",
|
||||
verified: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "TransService",
|
||||
price: 12000,
|
||||
description: "Regional carrier",
|
||||
rating: 4.5,
|
||||
reviews: 89,
|
||||
experience: "8 years experience",
|
||||
verified: false
|
||||
}
|
||||
],
|
||||
banks: [
|
||||
{
|
||||
id: 1,
|
||||
name: "VTB Bank",
|
||||
rate: 12.5,
|
||||
terms: "Raw materials purchase loan",
|
||||
period: "up to 24 months",
|
||||
rating: 4.7,
|
||||
reviews: 89,
|
||||
verified: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Sberbank",
|
||||
rate: 13.2,
|
||||
terms: "Trade financing",
|
||||
period: "up to 18 months",
|
||||
rating: 4.9,
|
||||
reviews: 145,
|
||||
verified: true
|
||||
}
|
||||
],
|
||||
insurance: [
|
||||
{
|
||||
id: 1,
|
||||
name: "RESO Insurance",
|
||||
cost: 3500,
|
||||
coverage: "Cargo insurance up to 500M₽",
|
||||
rating: 4.6,
|
||||
reviews: 67,
|
||||
verified: true
|
||||
}
|
||||
],
|
||||
laboratories: [
|
||||
{
|
||||
id: 1,
|
||||
name: "MetalTest Lab",
|
||||
price: 8000,
|
||||
tests: "Chemical analysis, mechanical properties",
|
||||
certification: "ISO 9001",
|
||||
rating: 4.7,
|
||||
reviews: 45,
|
||||
verified: true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return companiesData[locale as 'ru' | 'en'] || companiesData.ru
|
||||
})
|
||||
34
server/api/locations.get.ts
Normal file
34
server/api/locations.get.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = getQuery(event)
|
||||
const locale = query.locale || 'ru'
|
||||
|
||||
// Cities data with translations
|
||||
const locationsData = {
|
||||
ru: [
|
||||
{ name: 'Moscow', region: 'Central Federal District', distance: '0 km', coords: [55.76, 37.64] },
|
||||
{ name: 'St. Petersburg', region: 'Northwestern Federal District', distance: '635 km', coords: [59.93, 30.34] },
|
||||
{ name: 'Novosibirsk', region: 'Siberian Federal District', distance: '3354 km', coords: [55.03, 82.92] },
|
||||
{ name: 'Yekaterinburg', region: 'Ural Federal District', distance: '1416 km', coords: [56.84, 60.65] },
|
||||
{ name: 'Kazan', region: 'Volga Federal District', distance: '719 km', coords: [55.83, 49.09] },
|
||||
{ name: 'Nizhny Novgorod', region: 'Volga Federal District', distance: '411 km', coords: [56.33, 44.00] },
|
||||
{ name: 'Chelyabinsk', region: 'Ural Federal District', distance: '1777 km', coords: [55.16, 61.43] },
|
||||
{ name: 'Samara', region: 'Volga Federal District', distance: '862 km', coords: [53.20, 50.15] },
|
||||
{ name: 'Omsk', region: 'Siberian Federal District', distance: '2555 km', coords: [54.97, 73.37] },
|
||||
{ name: 'Rostov-on-Don', region: 'Southern Federal District', distance: '925 km', coords: [47.23, 39.72] }
|
||||
],
|
||||
en: [
|
||||
{ name: 'Moscow', region: 'Central Federal District', distance: '0 km', coords: [55.76, 37.64] },
|
||||
{ name: 'St. Petersburg', region: 'Northwestern Federal District', distance: '635 km', coords: [59.93, 30.34] },
|
||||
{ name: 'Novosibirsk', region: 'Siberian Federal District', distance: '3354 km', coords: [55.03, 82.92] },
|
||||
{ name: 'Yekaterinburg', region: 'Ural Federal District', distance: '1416 km', coords: [56.84, 60.65] },
|
||||
{ name: 'Kazan', region: 'Volga Federal District', distance: '719 km', coords: [55.83, 49.09] },
|
||||
{ name: 'Nizhny Novgorod', region: 'Volga Federal District', distance: '411 km', coords: [56.33, 44.00] },
|
||||
{ name: 'Chelyabinsk', region: 'Ural Federal District', distance: '1777 km', coords: [55.16, 61.43] },
|
||||
{ name: 'Samara', region: 'Volga Federal District', distance: '862 km', coords: [53.20, 50.15] },
|
||||
{ name: 'Omsk', region: 'Siberian Federal District', distance: '2555 km', coords: [54.97, 73.37] },
|
||||
{ name: 'Rostov-on-Don', region: 'Southern Federal District', distance: '925 km', coords: [47.23, 39.72] }
|
||||
]
|
||||
}
|
||||
|
||||
return locationsData[locale as 'ru' | 'en'] || locationsData.ru
|
||||
})
|
||||
126
server/api/suppliers.get.ts
Normal file
126
server/api/suppliers.get.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = getQuery(event)
|
||||
const locale = query.locale || 'ru'
|
||||
|
||||
// Suppliers data with translations
|
||||
const suppliersData = {
|
||||
ru: [
|
||||
{
|
||||
id: 1,
|
||||
name: "UralMetal",
|
||||
location: "Yekaterinburg",
|
||||
coords: [56.84, 60.65],
|
||||
totalPrice: 180000,
|
||||
rating: 4.8,
|
||||
reviews: 127,
|
||||
experience: "15 years in business",
|
||||
verified: true,
|
||||
logistics: [
|
||||
{
|
||||
type: 'auto',
|
||||
route: 'Auto delivery',
|
||||
time: '5-7 days',
|
||||
cost: 15000
|
||||
},
|
||||
{
|
||||
type: 'multimodal',
|
||||
route: 'Auto + Sea + Auto',
|
||||
time: '12-15 days',
|
||||
cost: 8000
|
||||
},
|
||||
{
|
||||
type: 'rail',
|
||||
route: 'Auto + Rail + Auto',
|
||||
time: '8-10 days',
|
||||
cost: 12000
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "SiberiaSteel",
|
||||
location: "Novosibirsk",
|
||||
coords: [55.03, 82.92],
|
||||
totalPrice: 175000,
|
||||
rating: 4.6,
|
||||
reviews: 89,
|
||||
experience: "12 years in business",
|
||||
verified: true,
|
||||
logistics: [
|
||||
{
|
||||
type: 'auto',
|
||||
route: 'Auto delivery',
|
||||
time: '6-8 days',
|
||||
cost: 18000
|
||||
},
|
||||
{
|
||||
type: 'rail',
|
||||
route: 'Auto + Rail + Auto',
|
||||
time: '10-12 days',
|
||||
cost: 10000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
en: [
|
||||
{
|
||||
id: 1,
|
||||
name: "UralMetal",
|
||||
location: "Yekaterinburg",
|
||||
coords: [56.84, 60.65],
|
||||
totalPrice: 180000,
|
||||
rating: 4.8,
|
||||
reviews: 127,
|
||||
experience: "15 years in business",
|
||||
verified: true,
|
||||
logistics: [
|
||||
{
|
||||
type: 'auto',
|
||||
route: 'Auto delivery',
|
||||
time: '5-7 days',
|
||||
cost: 15000
|
||||
},
|
||||
{
|
||||
type: 'multimodal',
|
||||
route: 'Auto + Sea + Auto',
|
||||
time: '12-15 days',
|
||||
cost: 8000
|
||||
},
|
||||
{
|
||||
type: 'rail',
|
||||
route: 'Auto + Rail + Auto',
|
||||
time: '8-10 days',
|
||||
cost: 12000
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "SiberiaSteel",
|
||||
location: "Novosibirsk",
|
||||
coords: [55.03, 82.92],
|
||||
totalPrice: 175000,
|
||||
rating: 4.6,
|
||||
reviews: 89,
|
||||
experience: "12 years in business",
|
||||
verified: true,
|
||||
logistics: [
|
||||
{
|
||||
type: 'auto',
|
||||
route: 'Auto delivery',
|
||||
time: '6-8 days',
|
||||
cost: 18000
|
||||
},
|
||||
{
|
||||
type: 'rail',
|
||||
route: 'Auto + Rail + Auto',
|
||||
time: '10-12 days',
|
||||
cost: 10000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return suppliersData[locale as 'ru' | 'en'] || suppliersData.ru
|
||||
})
|
||||
152
server/middleware/00-logto.ts
Normal file
152
server/middleware/00-logto.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import LogtoClient, { CookieStorage } from '@logto/node'
|
||||
import { getRequestURL, getCookie, setCookie, sendRedirect, type H3Event } from 'h3'
|
||||
import { randomUUID } from 'crypto'
|
||||
|
||||
const SESSION_COOKIE_NAME = 'logtoSession'
|
||||
const LEGACY_COOKIE_NAME = 'logtoCookies'
|
||||
|
||||
const SCOPES = [
|
||||
'openid',
|
||||
'profile',
|
||||
'email',
|
||||
'offline_access',
|
||||
'urn:logto:scope:organizations',
|
||||
'urn:logto:scope:organization_token',
|
||||
'teams:member'
|
||||
]
|
||||
|
||||
const RESOURCES = [
|
||||
'https://teams.optovia.ru',
|
||||
'https://orders.optovia.ru',
|
||||
'https://kyc.optovia.ru',
|
||||
'https://exchange.optovia.ru',
|
||||
'https://billing.optovia.ru'
|
||||
]
|
||||
|
||||
const createSessionWrapper = (event: H3Event) => {
|
||||
const storage = useStorage('logto')
|
||||
let currentSessionId = ''
|
||||
|
||||
return {
|
||||
wrap: async (data: Record<string, unknown>) => {
|
||||
if (!data || Object.keys(data).length === 0) {
|
||||
if (currentSessionId) {
|
||||
await storage.removeItem(`session:${currentSessionId}`)
|
||||
}
|
||||
currentSessionId = ''
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!currentSessionId) {
|
||||
currentSessionId = randomUUID()
|
||||
}
|
||||
|
||||
await storage.setItem(`session:${currentSessionId}`, data)
|
||||
return currentSessionId
|
||||
},
|
||||
unwrap: async (value: string) => {
|
||||
currentSessionId = value || ''
|
||||
if (!currentSessionId) return {}
|
||||
const stored = await storage.getItem<Record<string, unknown>>(`session:${currentSessionId}`)
|
||||
if (!stored) {
|
||||
// Session ID exists in cookie but data not found in storage - clear the stale cookie
|
||||
setCookie(event, SESSION_COOKIE_NAME, '', { path: '/', maxAge: 0 })
|
||||
currentSessionId = ''
|
||||
return {}
|
||||
}
|
||||
return stored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const endpoint = process.env.NUXT_LOGTO_ENDPOINT || 'https://auth.optovia.ru'
|
||||
const appId = process.env.NUXT_LOGTO_APP_ID || ''
|
||||
const appSecret = process.env.NUXT_LOGTO_APP_SECRET || ''
|
||||
|
||||
if (!appId || !appSecret) {
|
||||
return
|
||||
}
|
||||
|
||||
const url = getRequestURL(event)
|
||||
|
||||
if (getCookie(event, LEGACY_COOKIE_NAME)) {
|
||||
setCookie(event, LEGACY_COOKIE_NAME, '', { path: '/', maxAge: 0 })
|
||||
}
|
||||
|
||||
// Check for stale session cookie BEFORE initializing CookieStorage
|
||||
const existingSessionId = getCookie(event, SESSION_COOKIE_NAME)
|
||||
if (existingSessionId) {
|
||||
const nitroStorage = useStorage('logto')
|
||||
const sessionData = await nitroStorage.getItem(`session:${existingSessionId}`)
|
||||
if (!sessionData) {
|
||||
// Session cookie exists but no data in storage - clear it
|
||||
setCookie(event, SESSION_COOKIE_NAME, '', { path: '/', maxAge: 0 })
|
||||
}
|
||||
}
|
||||
|
||||
const storage = new CookieStorage({
|
||||
cookieKey: SESSION_COOKIE_NAME,
|
||||
isSecure: process.env.NODE_ENV === 'production',
|
||||
sessionWrapper: createSessionWrapper(event),
|
||||
getCookie: async (name) => getCookie(event, name),
|
||||
setCookie: async (name, value, options) => {
|
||||
setCookie(event, name, value, options)
|
||||
}
|
||||
})
|
||||
|
||||
await storage.init()
|
||||
|
||||
const logto = new LogtoClient(
|
||||
{
|
||||
endpoint,
|
||||
appId,
|
||||
appSecret,
|
||||
resources: RESOURCES,
|
||||
scopes: SCOPES
|
||||
},
|
||||
{
|
||||
navigate: async (target) => {
|
||||
await sendRedirect(event, target, 302)
|
||||
},
|
||||
storage
|
||||
}
|
||||
)
|
||||
|
||||
if (url.pathname === '/sign-in') {
|
||||
await logto.signIn({
|
||||
redirectUri: new URL('/callback', url).href
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (url.pathname === '/sign-out') {
|
||||
await logto.signOut(new URL('/', url).href)
|
||||
return
|
||||
}
|
||||
|
||||
if (url.pathname === '/callback') {
|
||||
await logto.handleSignInCallback(url.href)
|
||||
await sendRedirect(event, '/', 302)
|
||||
return
|
||||
}
|
||||
|
||||
event.context.logtoClient = logto
|
||||
|
||||
if (await logto.isAuthenticated()) {
|
||||
try {
|
||||
event.context.logtoUser = await logto.fetchUserInfo()
|
||||
} catch {
|
||||
try {
|
||||
event.context.logtoUser = await logto.getIdTokenClaims()
|
||||
} catch {
|
||||
event.context.logtoUser = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const orgId = (event.context.logtoUser as { organizations?: string[] } | undefined)?.organizations?.[0]
|
||||
if (orgId) {
|
||||
event.context.logtoOrgId = orgId
|
||||
}
|
||||
})
|
||||
74
server/middleware/me.ts
Normal file
74
server/middleware/me.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type LogtoClient from '@logto/node'
|
||||
import { print } from 'graphql'
|
||||
|
||||
type SelectedLocation = {
|
||||
type: string
|
||||
uuid: string
|
||||
name: string
|
||||
latitude: number
|
||||
longitude: number
|
||||
} | null
|
||||
|
||||
type MePayload = {
|
||||
id?: string | null
|
||||
firstName?: string | null
|
||||
lastName?: string | null
|
||||
phone?: string | null
|
||||
avatarId?: string | null
|
||||
activeTeamId?: string | null
|
||||
activeTeam?: {
|
||||
name?: string | null
|
||||
teamType?: string | null
|
||||
logtoOrgId?: string | null
|
||||
selectedLocation?: SelectedLocation
|
||||
} | null
|
||||
teams?: Array<{ id?: string | null; name?: string | null; logtoOrgId?: string | null; teamType?: string | null } | null> | null
|
||||
} | null
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
if (event.context.meLoaded) return
|
||||
event.context.meLoaded = true
|
||||
|
||||
const client = event.context.logtoClient as LogtoClient | undefined
|
||||
if (!client) return
|
||||
|
||||
let idToken: string | null = null
|
||||
try {
|
||||
idToken = await client.getIdToken()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
if (!idToken) return
|
||||
|
||||
try {
|
||||
const { GetMeDocument, GetMeProfileDocument } = await import('~/composables/graphql/user/teams-generated')
|
||||
const endpoint = process.env.NUXT_PUBLIC_TEAMS_GRAPHQL_USER || 'https://teams.optovia.ru/graphql/user/'
|
||||
|
||||
const [meResponse, profileResponse] = await Promise.all([
|
||||
$fetch<{ data?: { me?: MePayload } }>(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${idToken}` },
|
||||
body: { query: print(GetMeDocument) }
|
||||
}),
|
||||
$fetch<{ data?: { me?: MePayload } }>(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${idToken}` },
|
||||
body: { query: print(GetMeProfileDocument) }
|
||||
})
|
||||
])
|
||||
|
||||
const baseMe = meResponse?.data?.me ?? null
|
||||
const profileMe = profileResponse?.data?.me ?? null
|
||||
if (baseMe || profileMe) {
|
||||
event.context.me = {
|
||||
...(baseMe || {}),
|
||||
...(profileMe || {}),
|
||||
activeTeam: baseMe?.activeTeam || profileMe?.activeTeam || null,
|
||||
teams: baseMe?.teams || null
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore if user context can't be fetched
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user