244 lines
6.6 KiB
TypeScript
244 lines
6.6 KiB
TypeScript
import { ABC, Path, Compress, FS, Colors, S3Error } from "../deps.ts";
|
|
|
|
import bucket from "../s3.ts";
|
|
import {
|
|
isValidPackageName,
|
|
basicauth,
|
|
isValidFullVersion,
|
|
getAbsolutePackageVersion,
|
|
getBucketFilePath,
|
|
} from "../utils.ts";
|
|
|
|
import db, { IPackage } from "../db.ts";
|
|
|
|
import { v4 } from "https://deno.land/std/uuid/mod.ts";
|
|
|
|
export default function api(g: ABC.Group) {
|
|
const cacheControl = (next: ABC.HandlerFunc) => (ctx: ABC.Context) => {
|
|
ctx.response.headers.set("cache-control", "private");
|
|
return next(ctx);
|
|
};
|
|
|
|
g.get(
|
|
"/",
|
|
(ctx) => {
|
|
return { version: "1" };
|
|
},
|
|
cacheControl
|
|
);
|
|
|
|
g.get("/module", async (ctx) => {
|
|
return db.package.find({}).then((res) => res.map((e) => e.name));
|
|
});
|
|
|
|
g.get("/module/:module", async (ctx) => {
|
|
const module = await db.package.findOne({ name: ctx.params.module });
|
|
if (!module) {
|
|
ctx.response.status = 404;
|
|
return "// Not found";
|
|
} else {
|
|
return module.versions;
|
|
}
|
|
});
|
|
|
|
g.get("/module/:module/v/:version", async (ctx) => {
|
|
const module = await db.package.findOne({ name: ctx.params.module });
|
|
if (!module) {
|
|
ctx.response.status = 404;
|
|
return "// Not found";
|
|
} else {
|
|
let version = getAbsolutePackageVersion(
|
|
module,
|
|
ctx.params.version
|
|
) as string;
|
|
|
|
if (!version) {
|
|
ctx.response.status = 404;
|
|
return "// Not found";
|
|
}
|
|
|
|
const bucketPath = await getBucketFilePath(module.name, version, "/");
|
|
|
|
const filesItr = bucket.listAllObjects({
|
|
batchSize: 100,
|
|
prefix: bucketPath,
|
|
});
|
|
const allowedExts = new Set(
|
|
(ctx.queryParams.ext || "js|ts").split("|").map((e) => "." + e)
|
|
);
|
|
|
|
let files: string[] = [];
|
|
for await (let file of filesItr) {
|
|
const relPath = Path.posix.relative(bucketPath, file.key || "");
|
|
const ext = Path.extname(relPath);
|
|
if (allowedExts.has(ext)) files.push(relPath);
|
|
}
|
|
|
|
return files;
|
|
}
|
|
});
|
|
|
|
// g.post("/getapikey", getApiKey, basicauth("api"));
|
|
g.post("/package/:name", uploadPackage, cacheControl, basicauth("api"));
|
|
g.post("/module/:name", uploadPackage, cacheControl, basicauth("api")); //Switch no module instead of package
|
|
}
|
|
|
|
// async function getApiKey(ctx: ABC.Context) {
|
|
// const key = v4.generate();
|
|
|
|
// await db.api_key.insert({
|
|
// user: ctx.customContext.user,
|
|
// key,
|
|
// createdAt: new Date(),
|
|
// lastAccess: undefined,
|
|
// lastIP: undefined,
|
|
// });
|
|
|
|
// return {
|
|
// key,
|
|
// };
|
|
// }
|
|
|
|
async function uploadPackage(ctx: ABC.Context) {
|
|
const reqId = v4.generate();
|
|
const filename = "./tmp/" + reqId + ".tar";
|
|
const folder = "./tmp/" + reqId;
|
|
|
|
try {
|
|
const packageName = ctx.params.name.toLowerCase();
|
|
|
|
if (!isValidPackageName(packageName)) {
|
|
return {
|
|
success: false,
|
|
message: "Invalid package name",
|
|
};
|
|
}
|
|
|
|
console.log("Writing body to tmp file:", filename);
|
|
const file = await Deno.open(filename, {
|
|
write: true,
|
|
create: true,
|
|
});
|
|
await Deno.copy(ctx.request.body, file);
|
|
file.close();
|
|
|
|
console.log("Create unpacked folder");
|
|
|
|
await Deno.mkdir(folder);
|
|
|
|
console.log("Uncompressing tar");
|
|
|
|
await Compress.Tar.uncompress(filename, folder);
|
|
|
|
console.log("Reading meta.json");
|
|
|
|
const fileContent = await Deno.readFile(Path.join(folder, "meta.json"));
|
|
|
|
console.log("Parsing meta.json");
|
|
|
|
const meta = JSON.parse(new TextDecoder().decode(fileContent));
|
|
|
|
console.log("Checking meta.json");
|
|
|
|
if (!meta?.version) {
|
|
return {
|
|
success: false,
|
|
message: "No version available in meta.json",
|
|
};
|
|
}
|
|
|
|
const packageVersion = meta.version;
|
|
|
|
console.log("Checking correct version");
|
|
|
|
if (!isValidFullVersion(packageVersion)) {
|
|
return {
|
|
success: false,
|
|
message: "Invalid version. Version must be in format: 0.0.0",
|
|
};
|
|
return;
|
|
}
|
|
|
|
console.log("Checking for previous uploads");
|
|
|
|
let packageMeta = await db.package.findOne({ name: packageName });
|
|
|
|
console.log(meta, packageMeta);
|
|
|
|
if (!packageMeta) {
|
|
packageMeta = {
|
|
name: packageName,
|
|
owner: ctx.customContext.user,
|
|
description: meta.description,
|
|
deprecated: false,
|
|
readme: meta.readme,
|
|
versions: [],
|
|
};
|
|
|
|
await db.package.insert(packageMeta);
|
|
}
|
|
|
|
console.log("Check if version was uploaded before");
|
|
|
|
if (packageMeta.versions.find((e) => e === meta.version)) {
|
|
return {
|
|
success: false,
|
|
message: "Version was already uploaded!",
|
|
};
|
|
}
|
|
|
|
const bucketBase = "packages/" + packageName + "/" + packageVersion + "/";
|
|
|
|
console.log("Uploading files to S3");
|
|
|
|
const walker = FS.walk(folder, {
|
|
includeFiles: true,
|
|
includeDirs: false,
|
|
});
|
|
|
|
for await (const file of walker) {
|
|
const relative = Path.relative(folder, file.path).replace("\\", "/");
|
|
|
|
console.log("Normalised path:", relative.replace("\\", "/"));
|
|
|
|
const bucketPath = (bucketBase + relative).replace(/@/g, "§");
|
|
|
|
const body = await Deno.readAll(await Deno.open(file.path));
|
|
console.log("Put Object", bucketPath, body.byteLength);
|
|
await bucket.putObject(bucketPath, body, {});
|
|
}
|
|
console.log("Setting new live version");
|
|
|
|
//TODO: Better option, since this could error whith multiple uploads to the same package at the same time
|
|
await db.package.update(
|
|
{ name: packageName },
|
|
{
|
|
$set: {
|
|
versions: [...packageMeta.versions, packageVersion],
|
|
description: meta.description || packageMeta.description,
|
|
deprecated: meta.deprecated === true,
|
|
},
|
|
}
|
|
);
|
|
|
|
console.log("Finished successfully");
|
|
return {
|
|
success: true,
|
|
};
|
|
} catch (err) {
|
|
console.error(
|
|
Colors.red("Error while processing newly uploaded package")
|
|
);
|
|
console.error(err);
|
|
if (err instanceof S3Error) console.log(err.response);
|
|
return {
|
|
success: false,
|
|
message: err.message,
|
|
};
|
|
} finally {
|
|
console.log("Cleanup");
|
|
await Deno.remove(filename).catch(console.error);
|
|
await Deno.remove(folder, { recursive: true }).catch(console.error);
|
|
}
|
|
}
|