import { marked } from 'marked'; import { getEmojiFlag, countries } from 'countries-list'; export interface Env { LINEAR_API_KEY: string; LINEAR_TEAM_ID: string; } // --------------------------------------------------------------------------- // Job postings — add a new markdown file in src/postings/ and import it here // --------------------------------------------------------------------------- // import softwareEngineer from './postings/software-engineer.md'; interface Posting { slug: string; title: string; location: string; type: string; salary: string; body: string; // raw markdown (frontmatter stripped) } function parsePosting(slug: string, raw: string): Posting { const fm = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); if (!fm) throw new Error(`Posting ${slug} is missing frontmatter`); const meta: Record = {}; for (const line of fm[1].split('\n')) { const [k, ...rest] = line.split(':'); if (k && rest.length) meta[k.trim()] = rest.join(':').trim(); } return { slug, title: meta['title'] ?? slug, location: meta['location'] ?? 'Remote', type: meta['type'] ?? 'Full-time', salary: meta['salary'] ?? '', body: fm[2].trim(), }; } const POSTINGS: Posting[] = [ // parsePosting('software-engineer', softwareEngineer as string), // add more here as you create markdown files ]; const POSTINGS_BY_SLUG = new Map(POSTINGS.map((p) => [p.slug, p])); // --------------------------------------------------------------------------- // Country list (sorted alphabetically by name, from countries-list) // --------------------------------------------------------------------------- const COUNTRIES: { code: string; name: string }[] = Object.entries(countries) .map(([code, c]) => ({ code, name: c.name })) .sort((a, b) => a.name.localeCompare(b.name)); // --------------------------------------------------------------------------- // SVG logo // --------------------------------------------------------------------------- const DOLLY_SVG = ` `; // --------------------------------------------------------------------------- // HTML shell // --------------------------------------------------------------------------- function page(title: string, body: string): string { return ` ${escapeHtml(title)} · jobs at tangled
${body} `; } // --------------------------------------------------------------------------- // Pages // --------------------------------------------------------------------------- function listingsPage(): string { const rows = POSTINGS.map( (p) => `
${escapeHtml(p.title)}
${escapeHtml(p.location)} · ${escapeHtml(p.type)}${p.salary ? ` · ${escapeHtml(p.salary)}` : ''}
`, ).join(''); const body = `

Open positions

We're a lean, globally distributed team working on building the next-generation of social coding. We work remotely and try to meet once a year in-person.

${ POSTINGS.length === 0 ? `

No open positions right now; check back soon.

` : `
${rows}
` }
`; return page('open positions', body); } function jobPage(posting: Posting): string { const bodyHtml = marked.parse(posting.body) as string; const body = `

${escapeHtml(posting.location)} · ${escapeHtml(posting.type)}${posting.salary ? ` · ${escapeHtml(posting.salary)}` : ''}

${escapeHtml(posting.title)}

${bodyHtml}

Apply for this role

We read every application; if there's a fit, we'll be in touch via email.

${applyForm(posting.slug)}
`; return page(posting.title, body); } function applyForm(slug: string, error?: string): string { const errorHtml = error ? `
${escapeHtml(error)}
` : ''; const inputClass = 'block w-full rounded p-3 bg-gray-50 dark:bg-gray-800 dark:text-white border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-1 focus:ring-gray-400 dark:focus:ring-gray-500'; const selectClass = `${inputClass} appearance-none`; return ` ${errorHtml}

By submitting this form you agree to your data being processed by our subprocessors: Cloudflare and Linear.

`; } function successPage(firstName: string, posting: Posting): string { const body = `
🎉

Application received

Thanks, ${escapeHtml(firstName)}! We've received your application for ${escapeHtml(posting.title)} and will be in touch soon.

← View all positions
`; return page('application received', body); } // --------------------------------------------------------------------------- // Linear // --------------------------------------------------------------------------- // Upload a file to Linear's asset storage and return the public asset URL. async function uploadToLinear(env: Env, file: File): Promise { const query = ` mutation FileUpload($contentType: String!, $filename: String!, $size: Int!) { fileUpload(contentType: $contentType, filename: $filename, size: $size) { uploadFile { uploadUrl assetUrl headers { key value } } } }`; const metaResp = await fetch('https://api.linear.app/graphql', { method: 'POST', headers: { Authorization: env.LINEAR_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables: { contentType: file.type, filename: file.name, size: file.size }, }), }); const meta = (await metaResp.json()) as { data?: { fileUpload?: { uploadFile?: { uploadUrl: string; assetUrl: string; headers: { key: string; value: string }[] } } }; errors?: unknown[]; }; const uploadFile = meta.data?.fileUpload?.uploadFile; if (!uploadFile || meta.errors) { throw new Error(`Linear fileUpload error: ${JSON.stringify(meta.errors ?? meta)}`); } const uploadHeaders: Record = { 'Content-Type': file.type }; for (const { key, value } of uploadFile.headers) uploadHeaders[key] = value; const uploadResp = await fetch(uploadFile.uploadUrl, { method: 'PUT', headers: uploadHeaders, body: await file.arrayBuffer(), }); if (!uploadResp.ok) { throw new Error(`Resume upload failed: ${uploadResp.status} ${uploadResp.statusText}`); } return uploadFile.assetUrl; } async function getOrCreateLabel(env: Env, name: string): Promise { try { const searchQuery = ` query Labels($teamId: ID!) { issueLabels(filter: { team: { id: { eq: $teamId } } }) { nodes { id name } } }`; const searchResp = await fetch('https://api.linear.app/graphql', { method: 'POST', headers: { Authorization: env.LINEAR_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ query: searchQuery, variables: { teamId: env.LINEAR_TEAM_ID } }), }); const searchResult = (await searchResp.json()) as { data?: { issueLabels?: { nodes: { id: string; name: string }[] } }; }; const existing = searchResult.data?.issueLabels?.nodes.find((l) => l.name === name); if (existing) return existing.id; const createQuery = ` mutation LabelCreate($input: IssueLabelCreateInput!) { issueLabelCreate(input: $input) { success issueLabel { id } } }`; const createResp = await fetch('https://api.linear.app/graphql', { method: 'POST', headers: { Authorization: env.LINEAR_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ query: createQuery, variables: { input: { name, teamId: env.LINEAR_TEAM_ID } } }), }); const created = (await createResp.json()) as { data?: { issueLabelCreate?: { success: boolean; issueLabel: { id: string } } }; }; return created.data?.issueLabelCreate?.issueLabel?.id ?? null; } catch (err) { console.error('Failed to get/create label:', err); return null; } } async function createLinearIssue( env: Env, posting: Posting, data: { firstName: string; lastName: string; email: string; country: string; city: string; portfolio: string; linkedin: string; cover: string; resume: File | null; }, ): Promise { const fullName = `${data.firstName} ${data.lastName}`; const title = fullName; const [resumeUrl, labelId] = await Promise.all([ data.resume ? uploadToLinear(env, data.resume) : Promise.resolve(null), getOrCreateLabel(env, posting.title), ]); const description = [ `**Name:** ${fullName}`, `**Email:** ${data.email}`, `**Location:** ${data.city}, ${data.country}`, `**Role:** ${posting.title}`, data.portfolio ? `**Portfolio:** ${data.portfolio}` : null, data.linkedin ? `**LinkedIn:** ${data.linkedin}` : null, resumeUrl ? `**Résumé:** [${data.resume!.name}](${resumeUrl})` : null, '', '---', '', '## Cover letter', '', data.cover, ] .filter((l): l is string => l !== null) .join('\n'); const query = ` mutation CreateIssue($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier } } }`; const input: Record = { title, description, teamId: env.LINEAR_TEAM_ID }; if (labelId) input.labelIds = [labelId]; const resp = await fetch('https://api.linear.app/graphql', { method: 'POST', headers: { Authorization: env.LINEAR_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables: { input } }), }); const result = (await resp.json()) as { data?: { issueCreate?: { success: boolean } }; errors?: unknown[] }; if (result.errors || !result.data?.issueCreate?.success) { throw new Error(`Linear error: ${JSON.stringify(result.errors ?? result)}`); } } // --------------------------------------------------------------------------- // Router // --------------------------------------------------------------------------- export default { async fetch(request: Request, env: Env): Promise { const url = new URL(request.url); const parts = url.pathname.replace(/^\//, '').split('/'); // GET /favicon.svg if (request.method === 'GET' && url.pathname === '/favicon.svg') { return new Response(DOLLY_SVG, { headers: { 'Content-Type': 'image/svg+xml' } }); } // GET / — listings if (request.method === 'GET' && url.pathname === '/') { return html(listingsPage()); } // GET /:slug — job posting if (request.method === 'GET' && parts.length === 1 && parts[0]) { const posting = POSTINGS_BY_SLUG.get(parts[0]); if (!posting) return notFound(); return html(jobPage(posting)); } // POST /:slug/apply — submit application if (request.method === 'POST' && parts.length === 2 && parts[1] === 'apply') { const posting = POSTINGS_BY_SLUG.get(parts[0]); if (!posting) return notFound(); let formData: FormData; try { formData = await request.formData(); } catch { return html(jobPage(posting), 400); } const firstName = (formData.get('first_name') as string | null)?.trim() ?? ''; const lastName = (formData.get('last_name') as string | null)?.trim() ?? ''; const email = (formData.get('email') as string | null)?.trim() ?? ''; const country = (formData.get('country') as string | null)?.trim() ?? ''; const city = (formData.get('city') as string | null)?.trim() ?? ''; const portfolio = (formData.get('portfolio') as string | null)?.trim() ?? ''; const linkedin = (formData.get('linkedin') as string | null)?.trim() ?? ''; const cover = (formData.get('cover') as string | null)?.trim() ?? ''; const resumeEntry = formData.get('resume'); const resume = resumeEntry instanceof File && resumeEntry.size > 0 && resumeEntry.type === 'application/pdf' ? resumeEntry : null; if (!firstName || !lastName || !email || !country || !city || !cover || !resume) { const bodyHtml = marked.parse(posting.body) as string; const body = `

${escapeHtml(posting.location)} · ${escapeHtml(posting.type)}${posting.salary ? ` · ${escapeHtml(posting.salary)}` : ''}

${escapeHtml(posting.title)}

${bodyHtml}

Apply for this role

We read every application; if there's a fit, we'll be in touch via email.

${applyForm(posting.slug, 'Please fill in all required fields.')}
`; return html(page(posting.title, body), 400); } try { await createLinearIssue(env, posting, { firstName, lastName, email, country, city, portfolio, linkedin, cover, resume }); return html(successPage(firstName, posting)); } catch (err) { console.error('Linear issue creation failed:', err); const bodyHtml = marked.parse(posting.body) as string; const body = `

${escapeHtml(posting.location)} · ${escapeHtml(posting.type)}${posting.salary ? ` · ${escapeHtml(posting.salary)}` : ''}

${escapeHtml(posting.title)}

${bodyHtml}

Apply for this role

We read every application; if there's a fit, we'll be in touch via email.

${applyForm(posting.slug, 'Something went wrong submitting your application. Please try again.')}
`; return html(page(posting.title, body), 500); } } return notFound(); }, } satisfies ExportedHandler; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function html(body: string, status = 200): Response { return new Response(body, { status, headers: { 'Content-Type': 'text/html; charset=utf-8' } }); } function notFound(): Response { return new Response('not found', { status: 404 }); } function escapeHtml(str: string): string { return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); }