import { Post, NewsArticle, ResearchPaper, BusinessStory, ServiceItemData, ClientLogo, BusinessCourse, StudentProgram, AcceleratorProject, InvestmentProject, Vacancy, GalleryItem } from './types'; const STRAPI_URL = 'https://next.iieasy.ru'; const API_TOKEN = '91344ae88ae3e496f72d6ae9c157a3e929c3078b6269b57d3751d81026880cca6e7dcec074bffd77c8fe853dce9308f8d7dd494cf7b8c804a2875f0f4c24c0419cee196adf247d01cd5ddd5893d325bff34ad236febe0b508114ec906b12423e658d7210be94f2fcc37184334e11065538531c6d935de5becf81f2a48fdd6318'; interface StrapiResponse { data: StrapiDataItem[]; meta: { pagination: { page: number; pageSize: number; pageCount: number; total: number; }; }; } interface StrapiDataItem { id: number; attributes: T_Attributes; } interface StrapiImageAttributes { id?: number; url: string; name?: string; alternativeText?: string | null; caption?: string | null; width?: number; height?: number; formats?: { thumbnail?: StrapiImageFormat; small?: StrapiImageFormat; medium?: StrapiImageFormat; large?: StrapiImageFormat; }; hash?: string; ext?: string; mime?: string; size?: number; provider?: string; provider_metadata?: any; createdAt?: string; updatedAt?: string; publishedAt?: string; } interface StrapiImageFormat { name: string; hash: string; ext: string; mime: string; path: string | null; width: number; height: number; size: number; sizeInBytes?: number; url: string; } interface StrapiMediaPopulated { data: { id: number; attributes: StrapiImageAttributes; } | null; } interface StrapiMultipleMediaPopulated { data: { id: number; attributes: StrapiImageAttributes; }[] | null; } const getFullImageUrl = (mediaFieldData?: StrapiImageAttributes | StrapiMediaPopulated): string => { const placeholderBase = 'https://picsum.photos/seed/'; const placeholderFallback = `${placeholderBase}placeholder-fallback/600/400`; if (!mediaFieldData) { console.warn('getFullImageUrl called with undefined mediaFieldData'); return placeholderFallback; } // Handle case where mediaFieldData is already attributes if ('url' in mediaFieldData && typeof mediaFieldData.url === 'string' && !('data' in mediaFieldData)) { if (mediaFieldData.url.startsWith('http://') || mediaFieldData.url.startsWith('https://')) { return mediaFieldData.url; } return `${STRAPI_URL}${mediaFieldData.url}`; } // Handle case where mediaFieldData has a 'data' property (populated media field) if ('data' in mediaFieldData && mediaFieldData.data?.attributes?.url) { const url = mediaFieldData.data.attributes.url; if (url.startsWith('http://') || url.startsWith('https://')) { return url; } return `${STRAPI_URL}${url}`; } console.warn('getFullImageUrl could not extract URL from mediaFieldData:', mediaFieldData); return `${placeholderBase}no-url-extracted/600/400`; }; // Updated rich text renderer to allow raw HTML const transformRichTextToString = (nodes: any[] | undefined): string => { if (!nodes || !Array.isArray(nodes)) { return ''; } // Helper to escape attributes to prevent some forms of XSS. const escapeAttribute = (text: string) => (text || '').replace(/"/g, '"'); // Helper to escape HTML for code blocks intended for display. const escapeHtmlForCodeDisplay = (text: string) => (text || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); const serializeNodeToHtml = (node: any): string => { // For text nodes, we now trust the content and do not escape it. // This allows users to write raw HTML directly in the editor. if (node.type === 'text') { let text = node.text || ''; // Raw text, not escaped. if (node.bold) text = `${text}`; if (node.italic) text = `${text}`; if (node.underline) text = `${text}`; if (node.strikethrough) text = `${text}`; if (node.code) text = `${text}`; return text; } const childrenHtml = node.children?.map(serializeNodeToHtml).join('') || ''; switch (node.type) { case 'heading': return `${childrenHtml}`; case 'paragraph': return `

${childrenHtml || ''}

`; case 'quote': return `
${childrenHtml}
`; case 'list': const listTag = node.format === 'ordered' ? 'ol' : 'ul'; return `<${listTag}>${childrenHtml}`; case 'list-item': return `
  • ${childrenHtml}
  • `; case 'link': return `${childrenHtml}`; case 'image': if (!node.image?.url) return ''; const imageUrl = getFullImageUrl(node.image); const altText = node.image.alternativeText || ''; const caption = node.image.caption ? `
    ${node.image.caption}
    ` : ''; // Caption is not escaped return `
    ${escapeAttribute(altText)}${caption}
    `; case 'code': // For code blocks, we explicitly escape the content for display. const codeContent = node.children?.[0]?.text || ''; return `
    ${escapeHtmlForCodeDisplay(codeContent)}
    `; default: // For any other block types that just wrap children return childrenHtml; } }; return nodes.map(serializeNodeToHtml).join(''); }; const transformRichTextToListArray = (richTextField: any[] | undefined): string[] => { if (!richTextField || !Array.isArray(richTextField)) { return []; } const result: string[] = []; richTextField.forEach(block => { if (block.type === 'paragraph' && Array.isArray(block.children)) { const paragraphText = block.children .filter((child: any) => child.type === 'text' && typeof child.text === 'string') .map((child: any) => child.text) .join(''); if (paragraphText) { paragraphText.split('\n').forEach(line => { const trimmedLine = line.trim(); if (trimmedLine) result.push(trimmedLine); }); } } else if (block.type === 'list' && Array.isArray(block.children)) { block.children.forEach((listItem: any) => { if (listItem.type === 'list-item' && Array.isArray(listItem.children)) { const listItemText = listItem.children .filter((child: any) => child.type === 'text' && typeof child.text === 'string') .map((child: any) => child.text) .join('') .trim(); if (listItemText) result.push(listItemText); } }); } }); return result; }; const transformGalleryItems = (galleryData: StrapiMultipleMediaPopulated | undefined): GalleryItem[] => { if (!galleryData || !Array.isArray(galleryData.data)) { return []; } return galleryData.data.map((mediaItem: StrapiDataItem) => { const attributes = mediaItem.attributes; const type = attributes.mime && attributes.mime.startsWith('video') ? 'video' : 'image'; return { id: mediaItem.id.toString(), type: type, url: getFullImageUrl(attributes), altText: attributes.alternativeText || undefined, caption: attributes.caption || undefined, }; }); }; const transformPost = (item: any): Post => { return { id: item.id.toString(), title: item.title || 'Untitled Post', category: item.category || 'Uncategorized', date: item.date || (item.publishedAt ? new Date(item.publishedAt).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }) : undefined), publishedAt: item.publishedAt, readTime: item.readTime, imageUrl: getFullImageUrl(item.image), videoUrl: item.videoUrl, isFeatured: item.isFeatured || false, description: item.description, fullContent: Array.isArray(item.fullContent) ? transformRichTextToString(item.fullContent) : (item.fullContent || ''), gallery: transformGalleryItems(item.gallery as StrapiMultipleMediaPopulated | undefined), href: item.slug || `post-${item.id}`, }; }; const transformNewsArticle = (item: any): NewsArticle => { return { id: item.id.toString(), title: item.title || 'Untitled News Article', category: item.category || 'General News', date: item.date || (item.publishedAt ? new Date(item.publishedAt).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }) : undefined), publishedAt: item.publishedAt, imageUrl: getFullImageUrl(item.image), href: item.slug || `news-${item.id}`, description: item.description, fullContent: Array.isArray(item.fullContent) ? transformRichTextToString(item.fullContent) : (item.fullContent || ''), gallery: transformGalleryItems(item.gallery as StrapiMultipleMediaPopulated | undefined), }; }; const transformResearchPaper = (item: any): ResearchPaper => { let authorsArray: string[] | undefined = undefined; if (Array.isArray(item.authors)) { authorsArray = item.authors.map((author: any) => { if (typeof author === 'string') return author; if (typeof author === 'object' && author !== null && typeof author.name === 'string') return author.name; return 'Unknown Author'; }); } else if (typeof item.authors === 'string') { authorsArray = [item.authors]; } return { id: item.id.toString(), title: item.title || 'Untitled Research Paper', category: item.category || 'Research', date: item.date || (item.publishedAt ? new Date(item.publishedAt).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }) : undefined), publishedAt: item.publishedAt, imageUrl: getFullImageUrl(item.image), href: item.slug || `research-${item.id}`, authors: authorsArray, abstract: item.abstract, fullContent: Array.isArray(item.fullContent) ? transformRichTextToString(item.fullContent) : (item.fullContent || ''), gallery: transformGalleryItems(item.gallery as StrapiMultipleMediaPopulated | undefined), }; }; const transformBusinessStory = (item: any): BusinessStory => { return { id: item.id.toString(), title: item.title || 'Untitled Business Story', category: item.category || 'Case Study', imageUrl: getFullImageUrl(item.image), href: item.slug || `biz-${item.id}`, description: item.description, fullContent: Array.isArray(item.fullContent) ? transformRichTextToString(item.fullContent) : (item.fullContent || ''), gallery: transformGalleryItems(item.gallery as StrapiMultipleMediaPopulated | undefined), }; }; const transformServiceItem = (item: any): ServiceItemData => { return { icon: item.icon || 'CpuChipIcon', title: item.title || 'Untitled Service', description: item.description || '', }; }; const transformClientLogo = (item: any): ClientLogo => { return { id: item.id.toString(), name: item.name || 'Unnamed Client', imageUrl: getFullImageUrl(item.image), }; }; const transformBusinessCourse = (item: any): BusinessCourse => { return { icon: item.icon || 'AcademicCapIcon', title: item.title || 'Untitled Business Course', description: item.description || '', }; }; const transformStudentProgram = (item: any): StudentProgram => { return { icon: item.icon || 'AcademicCapIcon', title: item.title || 'Untitled Student Program', targetAudience: item.targetAudience || '', description: item.description || '', }; }; const transformAcceleratorProject = (item: any): AcceleratorProject => { return { id: item.id.toString(), name: item.name || 'Untitled Accelerator Project', tagline: item.tagline || '', description: Array.isArray(item.description_rt) ? transformRichTextToString(item.description_rt) : (typeof item.description === 'string' ? item.description : ''), imageUrl: getFullImageUrl(item.image), category: item.category || 'General AI', websiteUrl: item.websiteUrl, statuspro: item.statuspro || 'Active', }; }; const transformInvestmentProject = (item: any): InvestmentProject => { return { id: item.id.toString(), name: item.name || 'Untitled Investment Project', industry: item.industry || 'AI Sector', problem: Array.isArray(item.problem_rt) ? transformRichTextToString(item.problem_rt) : (typeof item.problem === 'string' ? item.problem : ''), solution: Array.isArray(item.solution_rt) ? transformRichTextToString(item.solution_rt) : (typeof item.solution === 'string' ? item.solution : ''), teamSize: item.teamSize || 1, fundingSought: item.fundingSought || 'N/A', imageUrl: getFullImageUrl(item.image), contactEmail: item.contactEmail, }; }; const transformVacancy = (item: any): Vacancy => { return { id: item.id.toString(), title: item.title || 'Untitled Vacancy', department: item.department || 'N/A', location: item.location || 'N/A', type: item.jobType || 'Full-time', description: item.shortDescription || '', fullDescription: transformRichTextToString(item.fullDescription_rt), responsibilities: transformRichTextToListArray(item.responsibilities_rt), qualifications: transformRichTextToListArray(item.qualifications_rt), offer: transformRichTextToListArray(item.offer_rt), href: item.slug || item.id.toString(), }; }; async function fetchData( endpoint: string, transformFn: (item: any) => T_Transformed ): Promise { try { const response = await fetch(`${STRAPI_URL}/api/${endpoint}?populate=*`, { headers: { 'Authorization': `Bearer ${API_TOKEN}`, }, }); if (!response.ok) { let errorBody = "Could not read error body."; try { errorBody = await response.text(); } catch (e) { /* ignore */ } console.error(`Strapi fetch error for ${endpoint}: ${response.status} ${response.statusText}. Body: ${errorBody}`); throw new Error(`Failed to fetch ${endpoint}: ${response.status}`); } let jsonResponse; try { jsonResponse = await response.json(); } catch (e: any) { console.error(`Error parsing JSON for ${endpoint}:`, e.message); try { const textResponse = await response.text(); console.error(`Raw response text for ${endpoint}: ${textResponse.substring(0, 500)}...`); } catch { /* ignore if reading text also fails */ } throw new Error(`Failed to parse JSON for ${endpoint}`); } if (!jsonResponse.data) { console.warn(`No 'data' field in Strapi response for ${endpoint}:`, jsonResponse); return []; } if (!Array.isArray(jsonResponse.data)) { console.warn(`Data for ${endpoint} is not an array:`, jsonResponse.data); return []; } const transformedData: T_Transformed[] = []; for (const item of jsonResponse.data) { try { transformedData.push(transformFn(item.attributes ? { id: item.id, ...item.attributes } : item)); } catch (transformError: any) { console.error(`Error transforming item with id ${item?.id || 'unknown'} from ${endpoint}: ${transformError.message}`, item, transformError.stack); } } return transformedData; } catch (error: any) { console.error(`Critical error fetching or transforming data for ${endpoint}: ${error.message}`, error.stack); return []; } } export const fetchPosts = (): Promise => fetchData('posts', transformPost); export const fetchNewsArticles = (): Promise => fetchData('news-articles', transformNewsArticle); export const fetchResearchPapers = (): Promise => fetchData('research-papers', transformResearchPaper); export const fetchBusinessStories = (): Promise => fetchData('business-stories', transformBusinessStory); export const fetchServiceItems = (): Promise => fetchData('services', transformServiceItem); export const fetchClientLogos = (): Promise => fetchData('client-logos', transformClientLogo); export const fetchBusinessCourses = (): Promise => fetchData('business-courses', transformBusinessCourse); export const fetchStudentPrograms = (): Promise => fetchData('student-programs', transformStudentProgram); export const fetchAcceleratorProjects = (): Promise => fetchData('accelerator-projects', transformAcceleratorProject); export const fetchInvestmentProjects = (): Promise => fetchData('investment-opportunities', transformInvestmentProject); export const fetchVacancies = (): Promise => fetchData('vacancies', transformVacancy);