initial changes
This commit is contained in:
127
src/api.js
Normal file
127
src/api.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
DeleteObjectCommand,
|
||||
GetObjectCommand,
|
||||
ListObjectsCommand,
|
||||
S3Client,
|
||||
} from "@aws-sdk/client-s3";
|
||||
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||
|
||||
const BUCKET = "repertory";
|
||||
|
||||
const oldItems = [];
|
||||
|
||||
const s3 = new S3Client({
|
||||
region: "any",
|
||||
endpoint: "https://gateway.storjshare.io",
|
||||
forcePathStyle: true,
|
||||
credentials: {
|
||||
accessKeyId: process.env.R_AWS_KEY,
|
||||
secretAccessKey: process.env.R_AWS_SECRET,
|
||||
},
|
||||
});
|
||||
|
||||
const cleanOldItems = async () => {
|
||||
console.log("cleanOldItems", oldItems.length);
|
||||
while (oldItems.length > 0) {
|
||||
const key = oldItems.pop();
|
||||
await s3.send(
|
||||
new DeleteObjectCommand({
|
||||
Bucket: BUCKET,
|
||||
Key: key,
|
||||
}),
|
||||
);
|
||||
console.log(key);
|
||||
}
|
||||
};
|
||||
|
||||
const createDownloadLink = async (key) => {
|
||||
let filename = key.split("/");
|
||||
filename = filename[filename.length - 1];
|
||||
return await getSignedUrl(
|
||||
s3,
|
||||
new GetObjectCommand({
|
||||
Bucket: BUCKET,
|
||||
Key: key,
|
||||
ResponseContentDisposition: `attachment; filename="${filename}"`,
|
||||
}),
|
||||
{ expiresIn: 3600 },
|
||||
);
|
||||
};
|
||||
|
||||
const getAllFiles = async () => {
|
||||
const data = await s3.send(
|
||||
new ListObjectsCommand({
|
||||
Bucket: BUCKET,
|
||||
Delimiter: "/",
|
||||
}),
|
||||
);
|
||||
console.log(data);
|
||||
};
|
||||
|
||||
const getBucketFiles = async (folderName) => {
|
||||
try {
|
||||
folderName = folderName.toLowerCase();
|
||||
const folderKey = encodeURIComponent(folderName) + "/";
|
||||
const data = await s3.send(
|
||||
new ListObjectsCommand({
|
||||
Bucket: BUCKET,
|
||||
Prefix: folderKey,
|
||||
}),
|
||||
);
|
||||
const ret = data.Contents.filter((item) => item.Key !== folderKey)
|
||||
.map((item) => {
|
||||
return {
|
||||
date: item.LastModified.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
}).replace(/,/g, ""),
|
||||
sort: item.LastModified.getTime(),
|
||||
name: item.Key.replace(folderKey, ""),
|
||||
key: item.Key,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a.sort > b.sort ? -1 : a.sort < b.sort ? 1 : 0;
|
||||
});
|
||||
const itemCount = {};
|
||||
const filteredItems = ret
|
||||
.filter((item) => item.name.endsWith(".tar.gz"))
|
||||
.filter((item) => {
|
||||
if (folderName === "nightly") {
|
||||
const parts = item.name.split("_");
|
||||
const groupId = `${parts[0]}_${parts[3]}_${parts[4]}`;
|
||||
itemCount[groupId] = itemCount[groupId] || 0;
|
||||
if (++itemCount[groupId] <= 3) {
|
||||
return true;
|
||||
}
|
||||
if (!oldItems.find((key) => key === item.key)) {
|
||||
oldItems.push(item.key);
|
||||
oldItems.push(item.key + ".sha256");
|
||||
oldItems.push(item.key + ".sig");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const totalCount = filteredItems.length * 3;
|
||||
for (let i = 0; i < totalCount && i < filteredItems.length; i += 3) {
|
||||
let item = ret.filter(
|
||||
(item) => item.name === filteredItems[i].name + ".sha256",
|
||||
);
|
||||
filteredItems.splice(i + 1, 0, ...item);
|
||||
item = ret.filter((item) => item.name === filteredItems[i].name + ".sig");
|
||||
filteredItems.splice(i + 2, 0, ...item);
|
||||
}
|
||||
return filteredItems;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export { cleanOldItems, createDownloadLink, getBucketFiles };
|
49
src/app.js
Normal file
49
src/app.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import createHttpError from "http-errors";
|
||||
import express from "express";
|
||||
import path from "path";
|
||||
import cookieParser from "cookie-parser";
|
||||
import logger from "morgan";
|
||||
|
||||
import indexRouter from "./routes/index";
|
||||
|
||||
if (!process.env.R_AWS_KEY) {
|
||||
console.log("FATAL: 'R_AWS_KEY' environment variable is not set");
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
if (!process.env.R_AWS_SECRET) {
|
||||
console.log("FATAL: 'R_AWS_SECRET' environment variable is not set");
|
||||
process.exit(-2);
|
||||
}
|
||||
|
||||
const app = express().set("env", process.env.NODE_ENV || "production");
|
||||
|
||||
const errorHandler = (err, res) => {
|
||||
res.locals.message = err.message || "unknown error";
|
||||
res.locals.error = app.get("env") === "development" ? err : {};
|
||||
res.render("errors", {
|
||||
message: "An error occurred",
|
||||
error: { status: err.status || 500 },
|
||||
});
|
||||
};
|
||||
|
||||
app.set("views", path.join(__dirname, "views"));
|
||||
app.set("view engine", "pug");
|
||||
|
||||
app.use(logger("dev"));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
|
||||
app.use("/", indexRouter);
|
||||
|
||||
app.use((err, req, res, next) => {
|
||||
errorHandler(err, res);
|
||||
});
|
||||
|
||||
app.use((_, res) => {
|
||||
errorHandler(createHttpError(404), res);
|
||||
});
|
||||
|
||||
module.exports = app;
|
6
src/mixins/download.pug
Normal file
6
src/mixins/download.pug
Normal file
@@ -0,0 +1,6 @@
|
||||
mixin download(file)
|
||||
.download
|
||||
|[#{file.date}]
|
||||
|
|
||||
a(href='/'+`download?key=${file.key}`) #{file.name}
|
||||
|
3
src/mixins/stage.pug
Normal file
3
src/mixins/stage.pug
Normal file
@@ -0,0 +1,3 @@
|
||||
mixin stage(name)
|
||||
.stage
|
||||
a(href='/'+`stage?name=${name}`) #{name}
|
29
src/public/stylesheets/style.css
Normal file
29
src/public/stylesheets/style.css
Normal file
@@ -0,0 +1,29 @@
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 10px;
|
||||
font: 16px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
color: #00B7FF;
|
||||
font-size: 18px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.download {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stage {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.public_key > textarea {
|
||||
height: 415px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
resize: none;
|
||||
font-family: monospace;
|
||||
outline: 0;
|
||||
}
|
82
src/routes/index.js
Normal file
82
src/routes/index.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import express from "express";
|
||||
import { cleanOldItems, createDownloadLink, getBucketFiles } from "../api";
|
||||
|
||||
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqXedleDOugdk9sBpgFOA
|
||||
0+MogIbBF7+iXIIHv8CRBbrrf8nxLSgQvbHQIP0EklebDgLZRgyGI3SSQYj7D957
|
||||
uNf1//dpkELNzfuezgAyFer9+iH4Svq46HADp5k+ugaK0mMDZM7OLOgo7415/+z4
|
||||
NIQopv8prMFdxkShr4e4dpR+S6LYMYMVjsi1gnYWaZJMWgzeZouXFSscS1/XDXSE
|
||||
vr1Jfqme+RmB4Q2QqGcDrY2ijumCJYJzQqlwG6liJ4FNg0U3POTCQDhQmuUoEJe0
|
||||
/dyiWlo48WQbBu6gUDHbTCCUSZPs2Lc9l65MqOCpX76+VXPYetZgqpMF4GVzb2y9
|
||||
kETxFNpiMYBlOBZk0I1G33wqVmw46MI5IZMQ2z2F8Mzt1hByUNTgup2IQELCv1a5
|
||||
a2ACs2TBRuAy1REeHhjLgiA/MpoGX7TpoHCGyo8jBChJVpP9ZHltKoChwDC+bIyx
|
||||
rgYH3jYDkl2FFuAUJ8zAZl8U1kjqZb9HGq9ootMk34Dbo3IVkc2azB2orEP9F8QV
|
||||
KxvZZDA9FAFEthSiNf5soJ6mZGLi0es5EWPoKMUEd9tG5bP980DySAWSSRK0AOfE
|
||||
QShT/z7oG79Orxyomwrb8ZJCi7wEfcCuK1NWgqLVUgXhpi2J9WYS6DAbF3Oh3Hhl
|
||||
DYSHlcfFBteqNDlR2uFInIECAwEAAQ==
|
||||
-----END PUBLIC KEY-----`;
|
||||
|
||||
const router = express.Router();
|
||||
const stages = ["release", "nightly", "alpha", "beta", "RC"];
|
||||
|
||||
router.get("/", (_, res, next) => {
|
||||
try {
|
||||
res.render("index", {
|
||||
title: "Fifthgrid Builds",
|
||||
stages,
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/download", async (req, res, next) => {
|
||||
try {
|
||||
res.redirect(await createDownloadLink(req.query.key.toString()));
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/stage", async (req, res, next) => {
|
||||
try {
|
||||
const name = req.query.name.toString();
|
||||
if (!stages.includes(name)) {
|
||||
res.render("errors", {
|
||||
message: "An error occurred",
|
||||
error: { status: 404 },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const files = await getBucketFiles(name);
|
||||
res.render("stage", {
|
||||
title: "Fifthgrid Builds",
|
||||
name: name,
|
||||
public_key: PUBLIC_KEY,
|
||||
files: files,
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
setInterval(
|
||||
async () => {
|
||||
try {
|
||||
for (const stage of stages) {
|
||||
try {
|
||||
await getBucketFiles(stage);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
await cleanOldItems();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
},
|
||||
15 * 60 * 1000,
|
||||
);
|
||||
|
||||
export default router;
|
6
src/views/errors.pug
Normal file
6
src/views/errors.pug
Normal file
@@ -0,0 +1,6 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
h1= message
|
||||
h2= error.status
|
||||
pre #{error.stack}
|
9
src/views/index.pug
Normal file
9
src/views/index.pug
Normal file
@@ -0,0 +1,9 @@
|
||||
extends layout
|
||||
|
||||
include ../mixins/stage.pug
|
||||
|
||||
block content
|
||||
h1= title
|
||||
h3= "Currently only 'nightly' and 'rc' builds are available"
|
||||
each name in stages
|
||||
+stage(name)
|
7
src/views/layout.pug
Normal file
7
src/views/layout.pug
Normal file
@@ -0,0 +1,7 @@
|
||||
doctype html
|
||||
html
|
||||
head
|
||||
title= title
|
||||
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||
body
|
||||
block content
|
15
src/views/stage.pug
Normal file
15
src/views/stage.pug
Normal file
@@ -0,0 +1,15 @@
|
||||
extends layout
|
||||
|
||||
include ../mixins/download.pug
|
||||
|
||||
block content
|
||||
h1= title + ' [' + name + ']'
|
||||
h2= 'Public Key'
|
||||
.public_key
|
||||
textarea(readonly) #{public_key}
|
||||
h2= 'Available Downloads'
|
||||
each file, idx in files
|
||||
if idx % 3 == 0
|
||||
hr
|
||||
+download(file)
|
||||
hr
|
Reference in New Issue
Block a user