import { objectMap, objectOmit } from '@dzeio/object-util' import URLManager from '@dzeio/url-manager' import type { APIRoute } from 'astro' import { evaluate } from 'mathjs' import { exec as execSync } from 'node:child_process' import fs from 'node:fs/promises' import os from 'node:os' import path from 'node:path/posix' import { promisify } from 'node:util' import FilesUtils from '../../../libs/FilesUtils' import { buildRFC7807 } from '../../../libs/RFCs/RFC7807' import { getParams } from '../../../libs/gcodeUtils' const exec = promisify(execSync) let tmpDir: string /** * body is the stl * query * price: algorithm from settings * adionnal settings from https://manual.slic3r.org/advanced/command-line */ export const post: APIRoute = async ({ request }) => { if (!tmpDir) { tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'paas-')) } const query = new URLManager(request.url).query() const file = (Math.random() * 1000000).toFixed(0) console.log('started processing new request', file) await fs.mkdir(`${tmpDir}/files`, { recursive: true }) const overrides = objectOmit(query, 'algo', 'config') const configName = query?.config ?? 'config' let config = `${import.meta.env.CONFIGS_PATH}/` + configName + '.ini' if (!await FilesUtils.exists(config)) { console.log('request finished in error :(', file) return buildRFC7807({ type: '/missing-config-file', status: 404, title: 'Configuration file is missing', details: `the configuration file "${configName}" is not available on the remote server` }) } const stlPath = `${tmpDir}/files/${file}.stl` const gcodePath = `${tmpDir}/files/${file}.gcode` // write file await fs.writeFile(stlPath, new Uint8Array(Buffer.from(await request.arrayBuffer())), { encoding: null }) // console.log(fs.statSync(stlPath).size, req.body.length) let additionnalParams = objectMap(overrides, (value, key) => `--${(key as string).replace(/_/g, '-')} ${value}`).join(' ') const slicer = import.meta.env.PRUSASLICER_PATH ?? 'prusa-slicer' if (slicer.includes('prusa')) { additionnalParams += ' --export-gcode' } const cmd = `${slicer} ${stlPath} --load ${config} --output ${gcodePath} ${additionnalParams}` try { await exec(cmd, { timeout: 60000 }) } catch (e: any) { console.log('request finished in error :(', file) const line = e.toString() console.error(e) if (line.includes('Objects could not fit on the bed')) { await fs.rm(stlPath) return buildRFC7807({ type: '/object-too-large', status: 413, title: 'Object is too large' }) } else if (line.includes('No such file')) { await fs.rm(stlPath) return buildRFC7807({ type: '/missing-config-file', status: 404, title: 'Configuration file is missing', details: `the configuration file "${configName}" is not available on the remote server` }) } else if (line.includes('Unknown option')) { await fs.rm(stlPath) return buildRFC7807({ type: '/slicer-option-unknown', status: 400, title: ' config override doew not exists', details: 'an override does not exists, please contact an administrator or refer to the documentation' }) } else if ( line.includes('is not recognized as an internal or external command') || line.includes('.dll was not loaded') ) { await fs.rm(stlPath) return buildRFC7807({ type: '/slicer-not-found', status: 408, title: 'the slicer used to process this file has not been found', details: 'the server has a misconfiguration meaning that we can\'t process the request, please contact an administrator', additionnalInfo: line.includes('dll') ? 'Missing DLL' : 'Slicer not found ' }) } else if (line.includes('ETIMEDOUT')) { await fs.rm(stlPath) return buildRFC7807({ type: '/timed-out-slicing', status: 408, title: 'Timed out slicing file', detail: `The file you are trying to process takes too long to be processed`, processingTimeoutMillis: 60000 }) } return buildRFC7807({ type: '/general-input-output-error', status: 500, title: 'General I/O error', details: 'A server error make it impossible to slice the file, please contact an administrator with the json error', fileId: file, config: configName, // fileSize: req.body.length, overrides: overrides, serverMessage: e.toString().replace(cmd, '***SLICER***').replace(stlPath, configName ?? `***FILE***`).replace(`${path}/configs/`, '').replace('.ini', '') }) } const gcode = await fs.readFile(gcodePath, 'utf-8') await fs.rm(stlPath) await fs.rm(gcodePath) const params = getParams(gcode) let price: string | undefined if (query?.algo) { let algo = decodeURI(query.algo as string) // objectLoop(params, (value, key) => { // if (typeof value !== 'number') { // return // } // while (algo.includes(key)) { // algo = algo.replace(key, value.toString()) // } // }) try { const tmp = evaluate(algo, params) if (typeof tmp === 'number') { price = tmp.toFixed(2) } else { return buildRFC7807({ type: '/algorithm-error', status: 500, title: 'Algorithm compilation error', details: 'It seems the algorithm resolution failed', algorithm: algo, algorithmError: 'Algorithm return a Unit', parameters: params }) } } catch (e) { console.dir(e) return buildRFC7807({ type: '/algorithm-error', status: 500, title: 'Algorithm compilation error', details: 'It seems the algorithm resolution failed', algorithm: algo, algorithmError: e, parameters: params }) } } console.log('request successfull :)', file) return { status: 200, body: JSON.stringify({ price: price ? parseFloat(price) : undefined, ...getParams(gcode), gcode }) } }