learnstore / index.js
ashecribb's picture
Upload 77 files
025f5c0 verified
require('dotenv').config()
var cors = require('cors')
var { I } = require('ipfsio')
var express = require('express')
var multer = require('multer')
const fs = require('fs')
const fsp = require('fs').promises
const { randomUUID } = require('crypto');
const axios = require('axios')
const jose = require('jose')
var app = express()
var i = new I(process.env.NFT_STORAGE_KEY)
let institutionKeyPair;
const KEY_FILE = 'keys.json';
async function initializeKeys() {
try {
if (fs.existsSync(KEY_FILE)) {
const keyFileContent = await fsp.readFile(KEY_FILE, 'utf-8');
const jwk = JSON.parse(keyFileContent);
institutionKeyPair = {
publicKey: await jose.importJWK(jwk.publicKey, 'ES256'),
privateKey: await jose.importJWK(jwk.privateKey, 'ES256'),
};
console.log('Loaded institutional key pair from file.');
} else {
const { publicKey, privateKey } = await jose.generateKeyPair('ES256');
institutionKeyPair = { publicKey, privateKey };
const jwk = {
publicKey: await jose.exportJWK(publicKey),
privateKey: await jose.exportJWK(privateKey),
};
await fsp.writeFile(KEY_FILE, JSON.stringify(jwk, null, 2));
console.log('Generated new institutional key pair and saved to file.');
}
} catch (error) {
console.error('Failed to initialize keys:', error);
}
}
initializeKeys();
const recentUploads = []
const MAX_RECENT_UPLOADS = 10
const UPLOAD_DIR = 'uploads/'
if (!fs.existsSync(UPLOAD_DIR)) {
fs.mkdirSync(UPLOAD_DIR)
}
const allowed = (process.env.ALLOWED ? process.env.ALLOWED.split(",") : [])
const port = (process.env.PORT ? process.env.PORT : 3000)
const issuerDid = process.env.ISSUER_DID || 'did:web:example.com';
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, UPLOAD_DIR)
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
cb(null, file.fieldname + '-' + uniqueSuffix)
}
})
const upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 5, // 5MB
files: 10
}
})
app.use(express.json())
app.use(express.static('public'))
if (allowed && allowed.length > 0) {
app.use(cors({ origin: allowed }))
} else {
app.use(cors())
}
app.use(express.urlencoded({ extended: true }));
app.post('/add', async (req, res) => {
let cid;
if (req.body.url) {
cid = await i.url(req.body.url)
} else if (req.body.object) {
cid = await i.object(req.body.object)
}
console.log("cid", cid)
res.json({ success: cid })
})
app.get('/.well-known/jwks.json', async (req, res) => {
if (!institutionKeyPair || !institutionKeyPair.publicKey) {
return res.status(500).json({ error: 'Key pair not generated yet.' });
}
const jwk = await jose.exportJWK(institutionKeyPair.publicKey);
res.json({ keys: [jwk] });
});
app.post('/issue-credential', async (req, res) => {
if (!institutionKeyPair || !institutionKeyPair.privateKey) {
return res.status(500).json({ error: 'Key pair not generated yet.' });
}
const { studentDid, proofOfWorkCID, claims } = req.body;
if (!studentDid || !claims) {
return res.status(400).json({ error: 'Missing studentDid or claims.' });
}
try {
const vcPayload = {
'@context': [
'https://www.w3.org/2018/credentials/v1',
'https://www.w3.org/2018/credentials/examples/v1'
],
id: `urn:uuid:${randomUUID()}`,
type: ['VerifiableCredential', 'AcademicCredential'],
issuer: issuerDid,
issuanceDate: new Date().toISOString(),
credentialSubject: {
id: studentDid,
proofOfWorkCID: proofOfWorkCID,
...claims
}
};
const jwt = await new jose.SignJWT(vcPayload)
.setProtectedHeader({ alg: 'ES256' })
.setIssuedAt()
.setIssuer(issuerDid)
.setSubject(studentDid)
.sign(institutionKeyPair.privateKey);
res.json({ vcJwt: jwt });
} catch (error) {
console.error('Failed to issue credential:', error);
res.status(500).json({ error: 'Failed to issue credential.' });
}
});
app.post('/batch-archive', async (req, res) => {
const { items } = req.body; // Expects an array of objects with { url, metadata }
if (!items || !Array.isArray(items) || items.length === 0) {
return res.status(400).json({ error: 'Invalid or empty "items" array in request body.' });
}
try {
const processingPromises = items.map(async (item) => {
const cid = await i.url(item.url);
return { cid, metadata: item.metadata };
});
const processedItems = await Promise.all(processingPromises);
const manifest = {
archiveDate: new Date().toISOString(),
items: processedItems,
};
const manifestCid = await i.object(manifest);
res.json({ success: true, manifestCid });
} catch (error) {
console.error('Batch archive failed:', error);
res.status(500).json({ error: 'Failed to process batch archive.' });
}
});
app.get('/recent', (req, res) => {
res.json(recentUploads)
})
app.get('/status/:cid', async (req, res) => {
const { cid } = req.params
try {
const response = await axios.get(`https://api.nft.storage/check/${cid}`, {
headers: {
'Authorization': `Bearer ${process.env.NFT_STORAGE_KEY}`
}
})
res.json(response.data)
} catch (error) {
if (error.response) {
res.status(error.response.status).json({ error: error.response.data })
} else {
res.status(500).json({ error: 'Failed to check CID status' })
}
}
})
app.post('/add-encrypted-object', upload.single('file'), async (req, res) => {
const file = req.file
if (!file) {
return res.status(400).json({ error: 'No file uploaded' })
}
try {
const cid = await i.path(file.path)
res.json({ success: cid })
} catch (error) {
console.error('Failed to pin encrypted object:', error)
res.status(500).json({ error: 'Failed to pin encrypted object' })
} finally {
// Clean up the temporary file
await fsp.unlink(file.path)
}
})
app.post('/add-learning-object', upload.array('files'), async (req, res) => {
const files = req.files
if (!files || files.length === 0) {
return res.status(400).json({ error: 'No files uploaded' })
}
const { title, author, subject, gradeLevel, license, description } = req.body
try {
const cids = []
for (const file of files) {
const cid = await i.path(file.path)
cids.push({
filename: file.originalname,
cid: cid
})
}
const manifest = {
title: title || 'Untitled Learning Object',
author: author || 'Unknown',
subject: subject || 'Uncategorized',
gradeLevel: gradeLevel || 'N/A',
license: license || 'CC-BY-4.0',
description: description || '',
files: cids
}
const manifestCid = await i.object(manifest)
recentUploads.unshift(manifestCid)
if (recentUploads.length > MAX_RECENT_UPLOADS) {
recentUploads.pop()
}
res.json({ success: manifestCid })
} catch (error) {
console.error('Failed to process dataset:', error)
res.status(500).json({ error: 'Failed to process dataset' })
} finally {
const unlinkPromises = files.map(file => fsp.unlink(file.path))
await Promise.all(unlinkPromises)
}
})
app.listen(port)