use sqids

This commit is contained in:
2025-09-17 22:45:50 -05:00
parent 4435c0baee
commit b5d57228f0
6 changed files with 42 additions and 23 deletions

13
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "archery", "name": "archery",
"version": "0.1.7", "version": "0.1.8",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "archery", "name": "archery",
"version": "0.1.7", "version": "0.1.8",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"body-parser": "^1.20.3", "body-parser": "^1.20.3",
@@ -15,7 +15,8 @@
"express-ws": "^5.0.2", "express-ws": "^5.0.2",
"pg": "^8.15.6", "pg": "^8.15.6",
"pg-hstore": "^2.3.4", "pg-hstore": "^2.3.4",
"sequelize": "^6.37.7" "sequelize": "^6.37.7",
"sqids": "0.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/csso": "5.0.4", "@types/csso": "5.0.4",
@@ -1998,6 +1999,12 @@
"node": ">= 10.x" "node": ">= 10.x"
} }
}, },
"node_modules/sqids": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/sqids/-/sqids-0.3.0.tgz",
"integrity": "sha512-lOQK1ucVg+W6n3FhRwwSeUijxe93b51Bfz5PMRMihVf1iVkl82ePQG7V5vwrhzB11v0NtsR25PSZRGiSomJaJw==",
"license": "MIT"
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",

View File

@@ -1,6 +1,6 @@
{ {
"name": "archery", "name": "archery",
"version": "0.1.7", "version": "0.1.8",
"description": "Build Arch packages through a web interface", "description": "Build Arch packages through a web interface",
"keywords": [ "keywords": [
"docker", "docker",
@@ -26,7 +26,8 @@
"express-ws": "^5.0.2", "express-ws": "^5.0.2",
"pg": "^8.15.6", "pg": "^8.15.6",
"pg-hstore": "^2.3.4", "pg-hstore": "^2.3.4",
"sequelize": "^6.37.7" "sequelize": "^6.37.7",
"sqids": "0.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/csso": "5.0.4", "@types/csso": "5.0.4",

View File

@@ -24,6 +24,7 @@ interface Build {
endTime?: Date; endTime?: Date;
status: Status; status: Status;
pid?: number; pid?: number;
sqid?: string;
} }
interface LogChunk { interface LogChunk {
@@ -51,7 +52,8 @@ class DB {
this.sequelize = new Sequelize(config.db || 'archery', config.user || 'archery', process.env.PASSWORD || config.password || '', { this.sequelize = new Sequelize(config.db || 'archery', config.user || 'archery', process.env.PASSWORD || config.password || '', {
host: config.host || 'localhost', host: config.host || 'localhost',
port: config.port || 5432, port: config.port || 5432,
dialect: 'postgres' dialect: 'postgres',
logging: false
}); });
this.build = this.sequelize.define('builds', { this.build = this.sequelize.define('builds', {
id: { id: {

View File

@@ -4,6 +4,7 @@ import type { Express } from "express";
import express from 'express'; import express from 'express';
import expressWs from "express-ws"; import expressWs from "express-ws";
import bodyParser from "body-parser"; import bodyParser from "body-parser";
import Sqids from 'sqids';
import type { DB, LogChunk } from "./DB.ts"; import type { DB, LogChunk } from "./DB.ts";
import type { BuildController, BuildEvent } from "./BuildController.ts"; import type { BuildController, BuildEvent } from "./BuildController.ts";
@@ -41,6 +42,10 @@ class Web {
private port: number; private port: number;
constructor(options: WebConfig = {}) { constructor(options: WebConfig = {}) {
const sqids = new Sqids({
minLength: 6,
alphabet: 'abcdefghijkmnprstuvwxyz'
});
const app: Express = express(); const app: Express = express();
const wsApp = this.app = expressWs(app).app; const wsApp = this.app = expressWs(app).app;
this.port = notStupidParseInt(process.env.PORT) || options['port'] as number || 8080; this.port = notStupidParseInt(process.env.PORT) || options['port'] as number || 8080;
@@ -66,6 +71,9 @@ class Web {
app.get('/', async (req, res) => { app.get('/', async (req, res) => {
try { try {
const builds = 'q' in req.query ? await this.db.searchBuilds(req.query.q as string) : await this.db.getBuildsBy(req.query); const builds = 'q' in req.query ? await this.db.searchBuilds(req.query.q as string) : await this.db.getBuildsBy(req.query);
builds.forEach(b => {
b.sqid = sqids.encode([b.id]);
});
res.render('index', { res.render('index', {
page: { page: {
title: 'Archery', title: 'Archery',
@@ -100,22 +108,23 @@ class Web {
req.body.distro || 'arch', req.body.distro || 'arch',
req.body.dependencies || 'stable' req.body.dependencies || 'stable'
); );
res.redirect(`/build/${buildId}`); res.redirect(`/build/${sqids.encode([buildId])}`);
this.buildController.triggerBuild(); this.buildController.triggerBuild();
}); });
app.get('/build/:num/?', async (req, res) => { app.get('/build/:id/?', async (req, res) => {
const build = await this.db.getBuild(parseInt(req.params.num)); const build = await this.db.getBuild(sqids.decode(req.params.id)?.[0]);
if (!build) { if (!build) {
res.sendStatus(404); res.sendStatus(404);
return; return;
} }
build.sqid = sqids.encode([build.id]);
const log = splitLines(await this.db.getLog(build.id)); const log = splitLines(await this.db.getLog(build.id));
res.render('build', { res.render('build', {
page: { page: {
title: 'Archery', title: 'Archery',
titlesuffix: `Build #${req.params.num}`, titlesuffix: `Build #${build.id}`,
description: `Building ${build.repo} on ${build.distro}` description: `Building ${build.repo} on ${build.distro}`
}, },
build, build,
@@ -124,8 +133,8 @@ class Web {
}); });
}); });
app.get('/build/:num/cancel', async (req, res) => { app.get('/build/:id/cancel', async (req, res) => {
const build = await this.db.getBuild(parseInt(req.params.num)); const build = await this.db.getBuild(sqids.decode(req.params.id)?.[0]);
if (!build) { if (!build) {
res.sendStatus(404); res.sendStatus(404);
return; return;
@@ -136,11 +145,11 @@ class Web {
catch (ex) { catch (ex) {
console.error(ex); console.error(ex);
} }
res.redirect(`/build/${build.id}`); res.redirect(`/build/${req.params.id}`);
}); });
app.get('/build/:num/logs/?', async (req, res) => { app.get('/build/:id/logs/?', async (req, res) => {
const build = await this.db.getBuild(parseInt(req.params.num)); const build = await this.db.getBuild(sqids.decode(req.params.id)?.[0]);
if (!build) { if (!build) {
res.sendStatus(404); res.sendStatus(404);
return; return;
@@ -149,8 +158,8 @@ class Web {
res.set('Content-Type', 'text/plain').send(log); res.set('Content-Type', 'text/plain').send(log);
}); });
app.get('/build/:num/patch/?', async (req, res) => { app.get('/build/:id/patch/?', async (req, res) => {
const build = await this.db.getBuild(parseInt(req.params.num)); const build = await this.db.getBuild(sqids.decode(req.params.id)?.[0]);
if (!build || !build.patch) { if (!build || !build.patch) {
res.sendStatus(404); res.sendStatus(404);
return; return;
@@ -162,10 +171,10 @@ class Web {
res.send('Healthy'); res.send('Healthy');
}); });
wsApp.ws('/build/:num/ws', (ws, req) => { wsApp.ws('/build/:id/ws', (ws, req) => {
console.log('WS Opened'); console.log('WS Opened');
const eventListener = (be: BuildEvent) => { const eventListener = (be: BuildEvent) => {
if (be.id === notStupidParseInt(req.params.num)) { if (be.id === sqids.decode(req.params.id)?.[0]) {
ws.send(JSON.stringify(be)); ws.send(JSON.stringify(be));
} }
}; };

View File

@@ -14,7 +14,7 @@
<div class="grid-2col"> <div class="grid-2col">
<label>Repo</label> <span><%= build.repo %></span> <label>Repo</label> <span><%= build.repo %></span>
<label>Commit</label> <span><% if (build.commit) { %><%= build.commit %><% } else { %>latest<% } %></span> <label>Commit</label> <span><% if (build.commit) { %><%= build.commit %><% } else { %>latest<% } %></span>
<label>Patch</label> <span><% if (build.patch) { %><a href="/build/<%= build.id %>/patch">patch file</a><% } else { %>none<% } %></span> <label>Patch</label> <span><% if (build.patch) { %><a href="/build/<%= build.sqid %>/patch">patch file</a><% } else { %>none<% } %></span>
<label>Distro</label> <span><%= build.distro %></span> <label>Distro</label> <span><%= build.distro %></span>
<label>Dependencies</label> <span><%= build.dependencies %></span> <label>Dependencies</label> <span><%= build.dependencies %></span>
<label>Start time</label> <span class="to-local-time"><%= build.startTime %></span> <label>Start time</label> <span class="to-local-time"><%= build.startTime %></span>
@@ -22,10 +22,10 @@
</div> </div>
<% if (!ended) { %> <% if (!ended) { %>
<div> <div>
<a href="/build/<%= build.id %>/cancel" class="button">Cancel build</a> <a href="/build/<%= build.sqid %>/cancel" class="button">Cancel build</a>
</div> </div>
<% } %> <% } %>
<p><a href="/build/<%= build.id %>/logs">Full logs</a></p> <p><a href="/build/<%= build.sqid %>/logs">Full logs</a></p>
<pre class="overflow-x"><div class="logs" id="logs"><% (log || []).forEach(line => { %><p><%= line %></p><% }) %></div></pre> <pre class="overflow-x"><div class="logs" id="logs"><% (log || []).forEach(line => { %><p><%= line %></p><% }) %></div></pre>
<% if (!ended) { %> <% if (!ended) { %>

View File

@@ -20,7 +20,7 @@
</tr> </tr>
<% builds.forEach(build => { %> <% builds.forEach(build => { %>
<tr> <tr>
<td><a href="/build/<%= build.id %>"><%= build.repo %></a></td> <td><a href="/build/<%= build.sqid %>"><%= build.repo %></a></td>
<td><%= build.distro %></td> <td><%= build.distro %></td>
<td class="to-local-time"><%= build.startTime %></td> <td class="to-local-time"><%= build.startTime %></td>
<td><%= timeElapsed(build.startTime, build.endTime) %></td> <td><%= timeElapsed(build.startTime, build.endTime) %></td>