diff --git a/export-gridfs.js b/export-gridfs.js deleted file mode 100644 index f264a31..0000000 --- a/export-gridfs.js +++ /dev/null @@ -1,57 +0,0 @@ -const { MongoClient, GridFSBucket } = require('mongodb'); -const fs = require('fs'); -const path = require('path'); - -async function exportGridFS() { - const client = new MongoClient('mongodb://localhost:27017/'); - - try { - await client.connect(); - const db = client.db('portfolio'); - - const buckets = ['images', 'album_art', 'scores', 'pubs']; - - for (const bucketName of buckets) { - const bucket = new GridFSBucket(db, { bucketName }); - const outputDir = path.join(__dirname, 'public', bucketName); - - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); - } - - const cursor = bucket.find({}); - const files = await cursor.toArray(); - - console.log(`Exporting ${files.length} files from ${bucketName}...`); - - const promises = files.map(file => { - return new Promise((resolve, reject) => { - const outputPath = path.join(outputDir, file.filename); - const stream = bucket.openDownloadStreamByName(file.filename); - const writeStream = fs.createWriteStream(outputPath); - - stream.pipe(writeStream); - - stream.on('end', () => { - console.log(` Saved: ${file.filename}`); - resolve(); - }); - - stream.on('error', (err) => { - console.error(` Error saving ${file.filename}:`, err.message); - reject(err); - }); - }); - }); - - await Promise.all(promises); - console.log(`Finished ${bucketName}`); - } - - console.log('Done!'); - } finally { - await client.close(); - } -} - -exportGridFS(); diff --git a/nuxt.config.ts b/nuxt.config.ts index 5c7fa0d..de034bc 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,5 +1,3 @@ -//import { defineNuxtConfig } from 'nuxt3' - // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ modules: ['@nuxtjs/tailwindcss', '@nuxt/image', '@nuxt/icon', '@pinia/nuxt', 'nuxt-headlessui', 'nuxt-swiper', 'nuxt-umami'], diff --git a/package.json b/package.json index 70e3b3b..b84f128 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "@formkit/themes": "^1.7.2", "@formkit/vue": "^1.7.2", "@pinia/nuxt": "^0.11.3", - "mongodb": "^7.1.0", "nuxt": "^4.3.1", "nuxt-swiper": "^1.2.2", "nuxt-umami": "^3.2.1", diff --git a/public/debug.aux b/public/debug.aux deleted file mode 100644 index f23e546..0000000 --- a/public/debug.aux +++ /dev/null @@ -1 +0,0 @@ -\relax diff --git a/scripts/generate-pdfs.js b/scripts/generate-pdfs.js deleted file mode 100644 index 858ba1e..0000000 --- a/scripts/generate-pdfs.js +++ /dev/null @@ -1,242 +0,0 @@ -const fs = require('fs') -const path = require('path') -const { execSync } = require('child_process') - -const DATA_DIR = path.join(__dirname, '../server/data') -const PUBLIC_DIR = path.join(__dirname, '../public') - -function readJson(filename) { - const data = fs.readFileSync(path.join(DATA_DIR, filename), 'utf-8') - const parsed = JSON.parse(data) - - function cleanDates(obj) { - if (Array.isArray(obj)) return obj.map(cleanDates) - if (obj && typeof obj === 'object') { - if (obj.$date?.$numberLong) return new Date(parseInt(obj.$date.$numberLong)).toISOString() - const cleaned = {} - for (const [k, v] of Object.entries(obj)) cleaned[k] = cleanDates(v) - return cleaned - } - return obj - } - - return cleanDates(parsed) -} - -function formatMonth(dateStr) { - if (!dateStr) return 'Present' - const date = new Date(dateStr) - if (isNaN(date)) return dateStr - return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }) -} - -function formatYear(dateStr) { - if (!dateStr) return '' - const date = new Date(dateStr) - if (isNaN(date)) return dateStr - return date.getFullYear() -} - -function formatShortDate(dateStr) { - if (!dateStr) return '' - const date = new Date(dateStr) - if (isNaN(date)) return dateStr - return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) -} - -function escapeLatex(str) { - if (!str) return '' - str = str.replace(/[\u0300-\u036f]/g, '') - return str.replace(/[&%$#_{}]/g, '\\$&').replace(/~/g, '\\textasciitilde{}').replace(/\^/g, '\\textasciicircum{}').replace(/ä/g, '{\\"a}').replace(/ö/g, '{\\"o}').replace(/ü/g, '{\\"u}').replace(/Ä/g, '{\\"A}').replace(/Ö/g, '{\\"O}').replace(/Ü/g, '{\\"U}').replace(/á/g, "\\'a").replace(/é/g, "\\'e").replace(/í/g, "\\'i").replace(/ó/g, "\\'o").replace(/ú/g, "\\'u").replace(/à/g, "\\`a").replace(/è/g, "\\`e").replace(/ì/g, "\\`i").replace(/ò/g, "\\`o").replace(/ù/g, "\\`u").replace(/ã/g, '{\\~a}').replace(/ñ/g, '{\\~n}').replace(/–/g, '--').replace(/—/g, '---').replace(/ç/g, '\\c c') -} - -function generateCvLatex(resume, talks) { - const basics = resume.basics || {} - - const talksByYear = {} - for (const talk of talks || []) { - if (!talk.date) continue - const year = new Date(talk.date).getFullYear() - if (!talksByYear[year]) talksByYear[year] = [] - talksByYear[year].push(talk) - } - const sortedYears = Object.keys(talksByYear).sort((a, b) => b - a) - - let latex = '\\documentclass[11pt]{article}\n' - latex += '\\usepackage[utf8]{inputenc}\n' - latex += '\\usepackage[T1]{fontenc}\n' - latex += '\\usepackage{geometry}\n' - latex += '\\geometry{letterpaper, top=1in, bottom=1in, left=1in, right=1in}\n' - latex += '\\usepackage{enumitem}\n' - latex += '\\usepackage{titlesec}\n' - latex += '\\pagestyle{empty}\n' - latex += '\\titleformat{\\section}{\\bfseries\\Large}{\\thesection}{1em}{}\n' - latex += '\\titleformat{\\subsection}{\\bfseries\\large}{\\thesubsection}{1em}{}\n' - latex += '\\titlespacing{\\section}{0pt}{12pt}{6pt}\n' - latex += '\\titlespacing{\\subsection}{0pt}{8pt}{4pt}\n' - latex += '\\begin{document}\n' - - // Header - latex += '\\begin{center}\n' - latex += '{\\LARGE\\bfseries ' + escapeLatex(basics.name || '') + '}\\\\[4pt]\n' - latex += '{\\large Curriculum Vitae}\\\\[8pt]\n' - latex += escapeLatex(basics.email || '') + ' \\quad ' + escapeLatex(basics.phone || '') + ' \\quad ' + escapeLatex(basics.website || '') + '\n' - latex += '\\end{center}\n' - latex += '\\hrulefill\\\\[12pt]\n' - - // Education - latex += '\\section{Education}\n' - for (const edu of resume.education || []) { - latex += '{\\bfseries ' + escapeLatex(edu.studyType) + ' in ' + escapeLatex(edu.area) + '}, ' + escapeLatex(edu.institution) + ', ' + formatYear(edu.endDate) + '\\\\[4pt]\n' - } - - // Teaching - latex += '\\section{Teaching}\n' - for (const teach of resume.teaching || []) { - latex += '{\\bfseries ' + escapeLatex(teach.company) + '}, \\textit{' + escapeLatex(teach.position) + '}\\\\[2pt]\n' - latex += escapeLatex(formatMonth(teach.startDate)) + ' -- ' + escapeLatex(formatMonth(teach.endDate)) + '\\\\[8pt]\n' - } - - // Lectures - latex += '\\section{Lectures}\n' - for (const year of sortedYears) { - latex += '\\subsection{' + year + '}\n' - for (const talk of talksByYear[year]) { - latex += '{\\bfseries ' + escapeLatex(talk.location) + '}\\\\\n' - const titles = Array.isArray(talk.title) ? talk.title : [talk.title] - for (const t of titles) latex += '\\hspace{8pt}\\textit{' + escapeLatex(t) + '}\\\\\n' - } - latex += '\\\\[4pt]\n' - } - - // Relevant Work - latex += '\\section{Relevant Work}\n' - for (const w of resume.work || []) { - latex += '{\\bfseries ' + escapeLatex(w.company) + '}, \\textit{' + escapeLatex(w.position) + '}\\\\[2pt]\n' - latex += escapeLatex(formatMonth(w.startDate)) + ' -- ' + escapeLatex(formatMonth(w.endDate)) + '\\\\[8pt]\n' - } - - // Skills - latex += '\\section{Skills}\n' - latex += (resume.skills?.[0]?.keywords?.map(s => escapeLatex(s)).join(', ') || '') + '\\\\[8pt]\n' - - // Languages - latex += '\\section{Languages}\n' - latex += (resume.languages || []).map(l => escapeLatex(l.language) + ' (' + escapeLatex(l.fluency) + ')').join(', ') + '\\\\[8pt]\n' - - // Recordings - latex += '\\section{Recordings}\n' - latex += '\\subsection{Solo Albums}\n' - for (const rel of resume.solo_releases || []) { - latex += '{\\bfseries ' + escapeLatex(rel.title) + '}. ' + escapeLatex(rel.publisher) + '. ' + escapeLatex(rel.media_type) + '. ' + escapeLatex(rel.date) + '.\\\\[4pt]\n' - } - latex += '\\subsection{Compilation Albums}\n' - for (const rel of resume.compilation_releases || []) { - latex += '{\\bfseries ' + escapeLatex(rel.title) + '}. ' + escapeLatex(rel.publisher) + '. ' + escapeLatex(rel.media_type) + '. ' + escapeLatex(rel.date) + '. \\textit{featuring ' + escapeLatex(rel.work) + '}.\\\\[4pt]\n' - } - - // Residencies - latex += '\\section{Residencies and Awards}\n' - for (const res of resume.residencies || []) { - latex += '{\\bfseries ' + escapeLatex(res.org) + '}, ' + escapeLatex(res.date) + '\\\\[4pt]\n' - } - - // References - latex += '\\section{References}\n' - for (const ref of resume.references || []) { - latex += '{\\bfseries ' + escapeLatex(ref.name) + '}\\\\[2pt]\n' - latex += escapeLatex(ref.position) + '\\\\[2pt]\n' - latex += escapeLatex(ref.email) + '\\\\[8pt]\n' - } - - latex += '\\end{document}\n' - return latex -} - -function generateWorksListLatex(resume, works, events) { - const basics = resume.basics || {} - - const worksByYear = {} - for (const work of works || []) { - const year = work.date ? new Date(work.date).getFullYear() : 'Unknown' - if (!worksByYear[year]) worksByYear[year] = [] - const workEvents = (events || []).filter(e => e.program && e.program.some(p => p.work?.toLowerCase().includes(work.title.toLowerCase()))) - worksByYear[year].push({ ...work, events: workEvents }) - } - const sortedYears = Object.keys(worksByYear).sort((a, b) => b - a) - - let latex = '\\documentclass[11pt]{article}\n' - latex += '\\usepackage[utf8]{inputenc}\n' - latex += '\\usepackage[T1]{fontenc}\n' - latex += '\\usepackage{geometry}\n' - latex += '\\geometry{letterpaper, top=1in, bottom=1in, left=1in, right=1in}\n' - latex += '\\usepackage{enumitem}\n' - latex += '\\usepackage{titlesec}\n' - latex += '\\pagestyle{empty}\n' - latex += '\\titleformat{\\section}{\\bfseries\\Large}{\\thesection}{1em}{}\n' - latex += '\\titlespacing{\\section}{0pt}{12pt}{6pt}\n' - latex += '\\begin{document}\n' - - // Header - latex += '\\begin{center}\n' - latex += '{\\LARGE\\bfseries ' + escapeLatex(basics.name || '') + '}\\\\[4pt]\n' - latex += '{\\large Works List with Presentation History}\\\\[8pt]\n' - latex += escapeLatex(basics.email || '') + ' \\quad ' + escapeLatex(basics.phone || '') + ' \\quad ' + escapeLatex(basics.website || '') + '\n' - latex += '\\end{center}\n' - latex += '\\hrulefill\\\\[12pt]\n' - - latex += 'A chronological performance / exhibition history, scores, and recordings are available at\\\\\n' - latex += 'www.unboundedpress.org.\\\\\n' - latex += 'All scores are also published or forthcoming through Frog Peak at\\\\\n' - latex += 'www.frogpeak.org/fpartists/fpwinter.html.\\\\[12pt]\n' - latex += '\\hrulefill\\\\[12pt]\n' - - for (const year of sortedYears) { - latex += '\\section{' + year + '}\n' - for (const work of worksByYear[year]) { - latex += '{\\bfseries\\textit{' + escapeLatex(work.title) + '}}\\\\[-4pt]\n' - if (work.instrument_tags) latex += '\\hspace{8pt}' + escapeLatex(work.instrument_tags.join(', ')) + '\\\\[-4pt]\n' - for (const event of work.events || []) { - const venue = event.venue || {} - latex += '\\hspace{8pt}' + escapeLatex(venue.name) + '; ' + escapeLatex(venue.city) + ', ' + escapeLatex(venue.state) + ' -- ' + escapeLatex(formatShortDate(event.start_date)) + '\\\\\n' - } - latex += '\\\\[4pt]\n' - } - } - - latex += '\\end{document}\n' - return latex -} - -function compileLatex(latexContent, outputPath) { - const tempDir = path.join(__dirname, 'temp') - if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true }) - - const texPath = path.join(tempDir, 'temp.tex') - fs.writeFileSync(texPath, latexContent) - - try { - execSync('cd ' + tempDir + ' && pdflatex temp.tex', { stdio: 'pipe' }) - const pdfPath = path.join(tempDir, 'temp.pdf') - if (fs.existsSync(pdfPath)) { - fs.copyFileSync(pdfPath, outputPath) - console.log('Generated:', outputPath) - } - } catch (e) { - fs.writeFileSync(path.join(PUBLIC_DIR, 'debug.tex'), latexContent) - console.error('Error generating PDF') - } -} - -const resume = readJson('resume.json')[0] -const talks = readJson('talks.json') -const works = readJson('works.json') -const events = readJson('events.json') - -console.log('Generating CV...') -compileLatex(generateCvLatex(resume, talks), path.join(PUBLIC_DIR, 'cv.pdf')) - -console.log('Generating Works List...') -compileLatex(generateWorksListLatex(resume, works, events), path.join(PUBLIC_DIR, 'works_list.pdf')) - -console.log('Done!')