operational fixes
Build and Push Docker Image / build (push) Successful in 26s

This commit is contained in:
jason
2026-04-22 17:00:42 -05:00
parent b5318abe0a
commit 0a47b90e21
5 changed files with 341 additions and 41 deletions
+43 -11
View File
@@ -2,10 +2,12 @@ import { Router, Request, Response } from 'express'
import multer from 'multer'
import path from 'path'
import fs from 'fs'
import crypto from 'crypto'
import slugify from 'slugify'
import { db, q, Model, Category, ModelPdf } from '../db/index'
import { requireAdmin } from '../middleware/requireAdmin'
import { convertStepFile, geometryOutputPath } from '../services/stepConverter'
import { convertStepFile, geometryOutputPath, GeometryFile } from '../services/stepConverter'
import { thumbnailOutputPath, geometryToTriangles, stlBufferToTriangles, renderThumbnail } from '../services/thumbnailGenerator'
const UPLOADS_DIR = process.env.UPLOADS_DIR ?? path.join(process.cwd(), 'uploads')
const MAX_FILE_BYTES = parseInt(process.env.MAX_FILE_MB ?? '500', 10) * 1024 * 1024
@@ -112,28 +114,31 @@ router.post('/admin/models',
name: string; description?: string; category_id?: string; is_public?: string
}
const ext = path.extname(req.file.originalname).toLowerCase().replace('.', '') as 'step' | 'stp' | 'stl'
const base = slugify(name, { lower: true, strict: true })
let slug = base
const ext = path.extname(req.file.originalname).toLowerCase().replace('.', '') as 'step' | 'stp' | 'stl'
let attempt = 0
// Generate a random, unguessable 12-character hex slug (48 bits of entropy).
// The model name is stored separately for display — the URL reveals nothing
// about the content, making enumeration infeasible without an admin link.
let slug = crypto.randomBytes(6).toString('hex')
while (q<{ id: number }>(`SELECT id FROM models WHERE slug = ?`).get(slug)) {
attempt++
slug = `${base}-${attempt}`
slug = crypto.randomBytes(6).toString('hex') // collision is astronomically unlikely
}
const relPath = path.relative(UPLOADS_DIR, req.file.path).replace(/\\/g, '/')
db.prepare(`
const insertResult = db.prepare(`
INSERT INTO models (slug, name, description, category_id, file_path, file_type, is_public)
VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(slug, name.trim(), description?.trim() || null, category_id ? parseInt(category_id, 10) : null, relPath, ext, is_public === 'on' ? 1 : 0)
const modelId = Number(insertResult.lastInsertRowid)
const absModelPath = path.join(UPLOADS_DIR, relPath)
// Convert STEP/STP to pre-processed geometry JSON so the browser never
// needs to download the 22 MB WASM parser.
// needs to download the 22 MB WASM parser, then generate a thumbnail
// from the resulting geometry data.
if (ext === 'step' || ext === 'stp') {
const absModelPath = path.join(UPLOADS_DIR, relPath)
const geoOutPath = geometryOutputPath(absModelPath)
const geoOutPath = geometryOutputPath(absModelPath)
try {
await convertStepFile(absModelPath, geoOutPath)
} catch (convErr) {
@@ -141,6 +146,33 @@ router.post('/admin/models',
// will show a friendly error instead of crashing the upload.
console.error('[stepConverter] conversion failed:', (convErr as Error).message)
}
// Generate thumbnail from the geometry JSON (only if conversion succeeded)
if (fs.existsSync(geoOutPath)) {
try {
const geo = JSON.parse(fs.readFileSync(geoOutPath, 'utf8')) as GeometryFile
const tris = geometryToTriangles(geo)
const thumbPath = thumbnailOutputPath(UPLOADS_DIR, modelId)
await renderThumbnail(tris, thumbPath)
const thumbRel = path.relative(UPLOADS_DIR, thumbPath).replace(/\\/g, '/')
db.prepare(`UPDATE models SET thumbnail_path = ? WHERE id = ?`).run(thumbRel, modelId)
} catch (thumbErr) {
console.error('[thumbnail] STEP generation failed:', (thumbErr as Error).message)
}
}
} else if (ext === 'stl') {
// Parse the STL directly on the server — no intermediate JSON needed
try {
const stlBuffer = fs.readFileSync(absModelPath)
const tris = stlBufferToTriangles(stlBuffer)
const thumbPath = thumbnailOutputPath(UPLOADS_DIR, modelId)
await renderThumbnail(tris, thumbPath)
const thumbRel = path.relative(UPLOADS_DIR, thumbPath).replace(/\\/g, '/')
db.prepare(`UPDATE models SET thumbnail_path = ? WHERE id = ?`).run(thumbRel, modelId)
} catch (thumbErr) {
console.error('[thumbnail] STL generation failed:', (thumbErr as Error).message)
}
}
res.redirect('/admin')