/** * A stand-in for the Pireon public API, with enough content to see every * section of the landing at once. * * The real API answers `{"data": []}` for every collection until the panel has * something published, so the page renders as a hero and a contact form and * nothing else — correct, but impossible to review. Point the app at this * instead: * * npm run mock # this server, on :4599 * npm run start:mock # the app against it * * `mock-api-down.mjs` is the other half: every endpoint answering 503, which is * how the maintenance banner gets exercised. */ import { createServer } from 'node:http'; const tech = (id, name, slug, category, color, ring) => ({ id, name, slug, category, color, ring, projects_count: 3, members_count: 2, }); const technologies = [ tech(1, 'Angular', 'angular', 'framework', '#dd0031', 'adopt'), tech(2, 'TypeScript', 'typescript', 'language', '#3178c6', 'adopt'), tech(3, 'Laravel', 'laravel', 'framework', '#ff2d20', 'trial'), tech(4, 'Postgres', 'postgres', 'datastore', '#336791', 'assess'), tech(5, 'Kubernetes', 'kubernetes', 'infrastructure', '#326ce5', 'hold'), ]; const project = (id, title, slug, status, featured) => ({ id, locale: 'es', title, slug, status, tagline: 'Una herramienta pequeña que resuelve un problema concreto y aburrido.', is_featured: featured, needs_help: id % 2 === 0, gallery: [], services: [], testimonials: [], talks: [], demo_url: 'https://example.com', updated_at: '2026-01-01T00:00:00Z', technologies: technologies.slice(0, 3), members: [], calls: [], repositories: [{ id, provider: 'gitea', full_name: `pireon/${slug}`, url: `https://git.example/${slug}`, languages: {}, topics: [], stars: 42 * id, forks: 3, contributors_count: 4, open_issues_count: 2, is_archived: false, is_dormant: false, good_first_issues: [] }], alternates: {}, path: `/proyectos/${slug}`, }); const member = (id, name, slug, founder) => ({ id, name, slug, headline: 'Backend y cosas que nadie quiere tocar', links: { web: 'https://example.com' }, github_username: slug, is_founder: founder, is_current: id !== 4, technologies: [], projects: [], alternates: {}, path: `/equipo/${slug}`, }); const page = (id, title, slug, featured) => ({ id, locale: 'es', title, slug, status: 'published', is_featured: featured, excerpt: 'Una página publicada desde el panel, con su propio título, extracto y cuerpo.', body: '
Texto de la página, tal como sale del editor del panel.
${topic} empezó como una necesidad concreta de una asociación y acabó siendo una herramienta que usan varias.
` + 'Elegimos renderizar en el servidor porque la mayoría de visitas llegan desde móviles modestos.
', postmortem: 'Subestimamos la importación de datos antiguos: nos llevó el doble de lo previsto.
', meta_title: `${listed.title} · Pireon`, meta_description: listed.tagline, started_at: '2025-03-01T00:00:00Z', ended_at: listed.status === 'completed' ? '2026-06-01T00:00:00Z' : null, client: data['/public/v1/clients'][0], members: team, lead: team[0], calls: data['/public/v1/calls'].slice(0, listed.id % 2 ? 1 : 0).map((c) => ({ ...c, path: `/colabora/${c.slug}` })), alternates: { es: `/proyectos/${listed.slug}`, en: `/en/projects/${listed.slug}`, ca: `/ca/projectes/${listed.slug}` }, }; } // Calls link to their own page, and one of them is closed so the page can // show what a visitor sees when a link to a filled call is still out there. for (const call of data['/public/v1/calls']) { call.path = `/colabora/${call.slug}`; call.alternates = { es: call.path, en: `/en/contribute/${call.slug}`, ca: `/ca/col-labora/${call.slug}` }; data[`/public/v1/calls/${call.slug}`] = { ...call, status: call.id === 2 ? 'filled' : 'open', what_you_get: 'Revisiones de código con alguien del colectivo.\nUn proyecto real en tu portfolio.', total_hours: call.hours_per_week * (call.duration_weeks ?? 4), project: { ...call.project, path: `/proyectos/${call.project.slug}` }, }; } for (const service of data['/public/v1/services']) { service.path = `/servicios/${service.slug}`; data[`/public/v1/services/${service.slug}`] = { ...service, description: 'Empezamos escuchando: qué hace hoy la organización y qué le sobra.\nDespués construimos lo mínimo que resuelve el problema, y lo dejamos documentado para que lo pueda mantener otra persona.', meta_description: service.tagline, projects: data['/public/v1/projects'].slice(0, 2).map((p) => ({ ...p, path: `/proyectos/${p.slug}` })), technologies: technologies.slice(0, 4), testimonials: data['/public/v1/testimonials'].slice(0, 2), alternates: { es: service.path, en: `/en/services/${service.slug}`, ca: `/ca/serveis/${service.slug}` }, }; } for (const listed of data['/public/v1/members']) { data[`/public/v1/members/${listed.slug}`] = { ...listed, bio: 'Programa desde hace años en proyectos de todo tipo.\nAhora dedica unas horas a la semana al colectivo, sobre todo a revisar código y acompañar a quien empieza.', joined_at: '2024-09-01T00:00:00Z', technologies: technologies.slice(0, 4), projects: data['/public/v1/projects'].slice(0, 2).map((p) => ({ ...p, path: `/proyectos/${p.slug}` })), alternates: { es: `/equipo/${listed.slug}`, en: `/en/team/${listed.slug}`, ca: `/ca/equip/${listed.slug}` }, }; } // What the sitemap is built from, and one renamed project to exercise the // redirect middleware with. data['/public/v1/slugs'] = { pages: data['/public/v1/pages'].map((p) => ({ id: p.id, updated_at: p.updated_at, alternates: p.alternates })), projects: data['/public/v1/projects'].map((p) => ({ id: p.id, updated_at: p.updated_at, alternates: data[`/public/v1/projects/${p.slug}`].alternates, })), }; data['/public/v1/redirects'] = [{ from: '/proyectos/catalogo-viejo', to: '/proyectos/catalogo', status: 301, locale: null }]; createServer((req, res) => { const path = new URL(req.url, 'http://x').pathname.replace(/^\/api/, ''); const payload = data[path]; res.setHeader('content-type', 'application/json'); // The two write endpoints: accept whatever arrives, as the real API does // for a valid submission. if (req.method === 'POST') { res.statusCode = 201; res.end(JSON.stringify({ data: { id: 1, kind: 'contribute', status: 'new' } })); return; } if (payload === undefined) { res.statusCode = 404; res.end(JSON.stringify({ message: 'not found' })); return; } res.end(JSON.stringify({ data: payload })); }).listen(4599, () => console.log('Mock API on http://localhost:4599/api'));