import { createWriteStream } from "node:fs"; import { constants, access, mkdir } from "node:fs/promises"; import { pipeline } from "node:stream/promises"; import { fileURLToPath } from "node:url"; import { DataTypes, Sequelize } from "sequelize"; import { parsedDoc } from "./config.js"; const db = new Sequelize(parsedDoc.db.name, parsedDoc.db.username, parsedDoc.db.password, { host: parsedDoc.db.postgres ? `${parsedDoc.postgres.domain}` : "localhost", port: parsedDoc.db.postgres ? parsedDoc.postgres.port : undefined, dialect: parsedDoc.db.postgres ? "postgres" : "sqlite", logging: parsedDoc.server.server.logging, storage: "database.sqlite" //this is sqlite only }); const catalogAssets = db.define("catalog_assets", { package_name: { type: DataTypes.STRING, unique: true }, title: { type: DataTypes.TEXT }, description: { type: DataTypes.TEXT }, author: { type: DataTypes.TEXT }, image: { type: DataTypes.TEXT }, tags: { type: DataTypes.JSON, allowNull: true }, version: { type: DataTypes.TEXT }, background_image: { type: DataTypes.TEXT, allowNull: true }, background_video: { type: DataTypes.TEXT, allowNull: true }, payload: { type: DataTypes.TEXT }, type: { type: DataTypes.TEXT } }); function marketplaceAPI(app) { app.get("/api/catalog-stats/", (request, reply) => { reply.send({ version: "1.0.0", spec: "Nebula Services", enabled: true }); }); app.get("/api/catalog-assets/", async (request, reply) => { try { const { page } = request.query; const pageNum = parseInt(page, 10) || 1; if (pageNum < 1) { reply.status(400).send({ error: "Page must be a positive number!" }); } const offset = (pageNum - 1) * 20; const totalItems = await catalogAssets.count(); const dbAssets = await catalogAssets.findAll({ offset: offset, limit: 20 }); const assets = dbAssets.reduce((acc, asset) => { acc[asset.package_name] = { title: asset.title, description: asset.description, author: asset.author, image: asset.image, tags: asset.tags, version: asset.version, background_image: asset.background_image, background_video: asset.background_video, payload: asset.payload, type: asset.type }; return acc; }, {}); return reply.send({ assets, pages: Math.ceil(totalItems / 20) }); } catch (error) { return reply.status(500).send({ error: "An error occured" }); } }); app.get("/api/packages/:package", async (request, reply) => { try { const packageRow = await catalogAssets.findOne({ where: { package_name: request.params.package } }); if (!packageRow) return reply.status(404).send({ error: "Package not found!" }); const details = { title: packageRow.get("title"), description: packageRow.get("description"), image: packageRow.get("image"), author: packageRow.get("author"), tags: packageRow.get("tags"), version: packageRow.get("version"), background_image: packageRow.get("background_image"), background_video: packageRow.get("background_video"), payload: packageRow.get("payload"), type: packageRow.get("type") }; reply.send(details); } catch (error) { reply.status(500).send({ error: "An unexpected error occured" }); } }); async function verifyReq(request, upload, data) { if (request.headers.psk !== parsedDoc.marketplace.psk) { return { status: 403, error: new Error("PSK isn't correct!") }; } else if (upload && !request.headers.packagename) { return { status: 500, error: new Error("No packagename defined!") }; } else if (upload && !data) { return { status: 400, error: new Error("No file uploaded!") }; } else { return { status: 200 }; } } app.post("/api/upload-asset", async (request, reply) => { const data = await request.file(); const verify = await verifyReq(request, true, data); if (verify.error !== undefined) { reply.status(verify.status).send({ status: verify.error.message }); } else { try { await pipeline(data.file, createWriteStream(fileURLToPath(new URL(`../database_assets/${request.headers.packagename}/${data.filename}`, import.meta.url)))); } catch (error) { return reply.status(500).send({ status: `File couldn't be uploaded! (Package most likely doesn't exist)` }); } return reply.status(verify.status).send({ status: "File uploaded successfully!" }); } }); app.post("/api/create-package", async (request, reply) => { const verify = await verifyReq(request, false, undefined); if (verify.error !== undefined) { reply.status(verify.status).send({ status: verify.error.message }); } else { const body = { package_name: request.body.uuid, title: request.body.title, image: request.body.image, author: request.body.author, version: request.body.version, description: request.body.description, tags: request.body.tags, payload: request.body.payload, background_video: request.body.background_video, background_image: request.body.background_image, type: request.body.type }; await catalogAssets.create({ package_name: body.package_name, title: body.title, image: body.image, author: body.author, version: body.version, description: body.description, tags: body.tags, payload: body.payload, background_video: body.background_video, background_image: body.background_image, type: body.type }); const assets = fileURLToPath(new URL("../database_assets", import.meta.url)); try { await access(`${assets}/${body.package_name}/`, constants.F_OK); return reply.status(500).send({ status: "Package already exists!" }); } catch (err) { await mkdir(`${assets}/${body.package_name}/`); return reply .status(verify.status) .send({ status: "Package created successfully!" }); } } }); } export { marketplaceAPI, db, catalogAssets };