import { Sequelize, DataTypes, Op, } from 'sequelize'; import type { ModelStatic, Filterable } from 'sequelize'; import type { LogType } from './BuildController.ts'; type Status = 'queued' | 'running' | 'cancelled' | 'success' | 'error'; type Dependencies = 'stable' | 'testing' | 'staging'; interface DBConfig { db?: string; user?: string; password?: string; host?: string; port?: number; } interface Build { id: number; repo: string; commit?: string; patch?: string; distro: string; dependencies: Dependencies; startTime?: Date; endTime?: Date; status: Status; pid?: number; } interface LogChunk { id: number buildId: number type: LogType, chunk: string } const MONTH = 1000 * 60 * 60 * 24 * 24; const FRESH = { [Op.or]: [ { startTime: { [Op.gt]: new Date(Date.now() - MONTH) } }, { startTime: { [Op.is]: null } } ] } const SELECT = ['id', 'repo', 'commit', 'distro', 'dependencies', 'startTime', 'endTime', 'status']; class DB { private build: ModelStatic; private logChunk: ModelStatic; private sequelize: Sequelize; constructor(config: DBConfig = {}) { this.sequelize = new Sequelize(config.db || 'archery', config.user || 'archery', process.env.PASSWORD || config.password || '', { host: config.host || 'localhost', port: config.port || 5432, dialect: 'postgres' }); this.build = this.sequelize.define('builds', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, repo: { type: DataTypes.STRING, allowNull: false }, commit: { type: DataTypes.STRING, allowNull: true }, patch: { type: DataTypes.TEXT, allowNull: true }, distro: { type: DataTypes.STRING, allowNull: false }, dependencies: { type: DataTypes.ENUM('stable', 'testing', 'staging'), allowNull: false, defaultValue: 'stable' }, startTime: { type: DataTypes.DATE, allowNull: true }, endTime: { type: DataTypes.DATE, allowNull: true }, status: { type: DataTypes.ENUM('queued', 'running', 'cancelled', 'success', 'error'), allowNull: false, defaultValue: 'queued' }, pid: { type: DataTypes.INTEGER, allowNull: true } }); this.logChunk = this.sequelize.define('logChunk', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, buildId: { type: DataTypes.INTEGER, allowNull: false, references: { model: 'builds', key: 'id' }, onUpdate: 'CASCADE', onDelete: 'CASCADE' }, type: { type: DataTypes.ENUM('std', 'err'), allowNull: false, defaultValue: 'std' }, chunk: { type: DataTypes.TEXT, allowNull: false } }); this.sync(); } private async sync(): Promise { await this.build.sync(); await this.logChunk.sync(); } public async createBuild(repo: string, commit: string, patch: string, distro: string, dependencies: string): Promise { const buildRec = await this.build.create({ repo, commit: commit || null, patch: patch || null, distro, dependencies }); return buildRec.id; } public async startBuild(id: number, pid: number): Promise { await this.build.update({ startTime: new Date(), status: 'running', pid, log: '' }, { where: { id } }); } public async finishBuild(id: number, status: Status): Promise { await this.build.update({ endTime: new Date(), status }, { where: { id } }); } public async appendLog(buildId: number, type: LogType, chunk: string): Promise { await this.logChunk.create({ buildId, type, chunk }); } public async getLog(buildId: number): Promise { return await this.logChunk.findAll({ order: [['id', 'ASC']], where: { buildId } }); } public async getBuild(id: number): Promise { return await this.build.findByPk(id); } public async getBuilds(): Promise { return await this.build.findAll({ attributes: SELECT, order: [['id', 'DESC']], where: FRESH }); } public async getBuildsByStatus(status: Status): Promise { return await this.build.findAll({ attributes: SELECT, order: [['id', 'DESC']], where: { ...FRESH, status } }); } public async getBuildsByDistro(distro: string): Promise { return await this.build.findAll({ attributes: SELECT, order: [['id', 'DESC']], where: { ...FRESH, distro } }); } public async getBuildsBy(filterable: Filterable): Promise { return await this.build.findAll({ attributes: SELECT, order: [['id', 'DESC']], where: { ...FRESH, ...filterable } }); } public async dequeue(): Promise { return await this.build.findOne({ order: [['id', 'ASC']], where: { status: 'queued' }, limit: 1 }); } public async searchBuilds(query: string): Promise { return await this.build.findAll({ attributes: SELECT, order: [['id', 'DESC']], where: { [Op.or]: [ { repo: { [Op.iLike]: `%${query}%` } } ] }, limit: 100 }); } public async cleanup(): Promise { await this.build.destroy({ where: { startTime: { [Op.lt]: new Date(Date.now() - MONTH * 6) } } }); } public async close(): Promise { await this.sequelize.close(); } } export default DB; export { DB }; export type { DBConfig, Status, Build, LogChunk };