|
|
|
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 (date, did, local_file, size, aws_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);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|