const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3'); const cors = require('cors'); const crypto = require('crypto'); const didJwt = require('did-jwt'); const { Resolver } = require('did-resolver'); const express = require('express'); const { getResolver } = require('ethr-did-resolver'); const fs = require('fs'); const multer = require('multer'); const path = require('path'); const sqlite3 = require('sqlite3').verbose(); require('dotenv').config() const app = express(); app.use(cors()); const port = process.env.PORT || 3000; // file name also referenced in flyway.conf and potentially in .env files or in environment variables const dbFile = process.env.SQLITE_FILE || './image-db.sqlite'; const ethrDidResolver = getResolver; const resolver = new Resolver({ ...ethrDidResolver({ infuraProjectId: process.env.INFURA_PROJECT_ID || 'fake-infura-project-id' }) }) // Open a connection to the SQLite database const db = new sqlite3.Database(dbFile, (err) => { if (err) { console.error('Error opening database:', err); } }); // Configure AWS const s3Client = new S3Client({ region: process.env.AWS_REGION, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY, secretAccessKey: process.env.AWS_SECRET_KEY } }); const uploadDir = 'uploads'; const uploadMulter = multer({ dest: uploadDir + '/' }); // POST endpoint to upload an image app.post('/image', uploadMulter.single('image'), async (req, res) => { // Verify the JWT try { const auth = req.headers.authorization; if (!auth || !auth.startsWith('Bearer ')) { return res.status(401).send(JSON.stringify({ success: false, message: 'Missing "Bearer JWT" in Authorization header.'})); } const jwt = auth.substring('Bearer '.length); const verified = await didJwt.verifyJWT(jwt, {resolver}); if (!verified) { return res.status(401).send(JSON.stringify({ success: false, message: 'Got invalid JWT in Authorization header.'})); } const issuerDid = verified.issuer; // Read the file from the temporary location fs.readFile(req.file.path, async (err, data) => { if (err) throw err; // Handle error const hashSum = crypto.createHash('sha256'); hashSum.update(data); const hashHex = hashSum.digest('hex'); const bucketName = 'gifts-image'; const fileName = hashHex; const params = { Body: data, Bucket: bucketName, // S3 Bucket name ContentType: req.file.mimetype, // File content type Key: fileName, // File name to use in S3 }; try { // record the upload in the database const currentDate = new Date().toISOString(); const localFile = req.file.path.startsWith(uploadDir + '/') ? req.file.path.substring(uploadDir.length + 1) : req.file.path; const finalUrl = `https://${bucketName}.s3.amazonaws.com/${fileName}`; await db.run('INSERT INTO image (time, did, local_file, size, final_file, url) VALUES (?, ?, ?, ?, ?, ?)', [ currentDate, issuerDid, localFile, req.file.size, fileName, finalUrl ], (dbErr) => { if (dbErr) { console.error(currentDate, "Error inserting record from", issuerDid, "into database:", dbErr); // don't continue because then we'll have storage we cannot track (and potentially limit) throw dbErr; } }); // send to AWS const command = new PutObjectCommand(params); const response = await s3Client.send(command); if (response.$metadata.httpStatusCode !== 200) { const errorTime = new Date().toISOString(); console.error(errorTime, "Error uploading to S3 with bad HTTP status, with metadata:", response.$metadata); res.status(500).send(JSON.stringify({ success: false, message: "Got bad status of " + response.$metadata.httpStatusCode + " from AWS. See server logs at " + errorTime })); } else { fs.rm(req.file.path, (err) => { if (err) { console.error("Error deleting temp file", req.file.path, "with error:", err); } }); res.send(JSON.stringify({success: true, url: finalUrl})); } } catch (uploadError) { const errorTime = new Date().toISOString(); console.error(errorTime, "Error uploading to S3:", uploadError); res.status(500).send(JSON.stringify({ success: false, message: "Got error uploading file. See server logs at " + errorTime + " Error Details: " + uploadError })); } }) } catch (error) { const errorTime = new Date().toISOString(); console.error(errorTime, "Error processing image upload:", error); res.status(500).send(JSON.stringify({ success: false, message: "Got error processing image upload. See server logs at " + errorTime + " Error Details: " + error })); } }); app.listen(port, () => { console.log(`Server running at http://localhost:${port}`); }); // Close the database connection when the Node.js app ends process.on('SIGINT', () => { db.close((err) => { if (err) { console.error('Error closing DB connection:', err); return; } process.exit(0); }); });