Started app update implementation.
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good
This commit is contained in:
parent
cc1c827c0b
commit
de40e0c3ff
@ -17,7 +17,8 @@
|
|||||||
"urlClient": "https://dev.tombutcher.work",
|
"urlClient": "https://dev.tombutcher.work",
|
||||||
"urlElectronClient": "http://localhost:5780",
|
"urlElectronClient": "http://localhost:5780",
|
||||||
"urlApi": "https://dev.tombutcher.work/api",
|
"urlApi": "https://dev.tombutcher.work/api",
|
||||||
"devAuthClient": "http://localhost:3500"
|
"devAuthClient": "http://localhost:3500",
|
||||||
|
"jenkinsProject": "https://ci.tombutcher.work/job/farmcontrol/job/farmcontrol-ui"
|
||||||
},
|
},
|
||||||
"database": {
|
"database": {
|
||||||
"mongo": {
|
"mongo": {
|
||||||
@ -77,7 +78,8 @@
|
|||||||
"urlClient": "http://localhost:3000",
|
"urlClient": "http://localhost:3000",
|
||||||
"urlElectronClient": "http://localhost:5780",
|
"urlElectronClient": "http://localhost:5780",
|
||||||
"urlApi": "http://localhost:8788/api",
|
"urlApi": "http://localhost:8788/api",
|
||||||
"devAuthClient": "http://localhost:3500"
|
"devAuthClient": "http://localhost:3500",
|
||||||
|
"jenkinsProject": "https://ci.tombutcher.work/job/farmcontrol/job/farmcontrol-ui"
|
||||||
},
|
},
|
||||||
"database": {
|
"database": {
|
||||||
"mongo": {
|
"mongo": {
|
||||||
@ -135,7 +137,8 @@
|
|||||||
"urlClient": "https://web.farmcontrol.app",
|
"urlClient": "https://web.farmcontrol.app",
|
||||||
"urlElectronClient": "http://localhost:3000",
|
"urlElectronClient": "http://localhost:3000",
|
||||||
"urlApi": "https://api.farmcontrol.app",
|
"urlApi": "https://api.farmcontrol.app",
|
||||||
"devAuthClient": "http://localhost:3500"
|
"devAuthClient": "http://localhost:3500",
|
||||||
|
"jenkinsProject": "https://ci.tombutcher.work/job/farmcontrol/job/farmcontrol-ui"
|
||||||
},
|
},
|
||||||
"database": {
|
"database": {
|
||||||
"mongo": {
|
"mongo": {
|
||||||
|
|||||||
@ -59,6 +59,7 @@ import {
|
|||||||
excelRoutes,
|
excelRoutes,
|
||||||
csvRoutes,
|
csvRoutes,
|
||||||
appLaunchRoutes,
|
appLaunchRoutes,
|
||||||
|
appUpdateRoutes,
|
||||||
} from './routes/index.js';
|
} from './routes/index.js';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
@ -185,6 +186,7 @@ app.use('/rss', rssRoutes);
|
|||||||
app.use('/excel', excelRoutes);
|
app.use('/excel', excelRoutes);
|
||||||
app.use('/csv', csvRoutes);
|
app.use('/csv', csvRoutes);
|
||||||
app.use('/applaunch', appLaunchRoutes);
|
app.use('/applaunch', appLaunchRoutes);
|
||||||
|
app.use('/appupdate', appUpdateRoutes);
|
||||||
|
|
||||||
// Start the application
|
// Start the application
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
|
|||||||
@ -52,6 +52,7 @@ import rssRoutes from './misc/rss.js';
|
|||||||
import excelRoutes from './misc/excel.js';
|
import excelRoutes from './misc/excel.js';
|
||||||
import csvRoutes from './misc/csv.js';
|
import csvRoutes from './misc/csv.js';
|
||||||
import appLaunchRoutes from './misc/applaunch.js';
|
import appLaunchRoutes from './misc/applaunch.js';
|
||||||
|
import appUpdateRoutes from './misc/appupdate.js';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
userRoutes,
|
userRoutes,
|
||||||
@ -108,4 +109,5 @@ export {
|
|||||||
excelRoutes,
|
excelRoutes,
|
||||||
csvRoutes,
|
csvRoutes,
|
||||||
appLaunchRoutes,
|
appLaunchRoutes,
|
||||||
|
appUpdateRoutes,
|
||||||
};
|
};
|
||||||
|
|||||||
13
src/routes/misc/appupdate.js
Normal file
13
src/routes/misc/appupdate.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import { isAuthenticated } from '../../keycloak.js';
|
||||||
|
import {
|
||||||
|
appUpdateBranchesRouteHandler,
|
||||||
|
appUpdateCurrentRouteHandler,
|
||||||
|
} from '../../services/misc/appupdate.js';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.get('/branches', isAuthenticated, appUpdateBranchesRouteHandler);
|
||||||
|
router.get('/current', isAuthenticated, appUpdateCurrentRouteHandler);
|
||||||
|
|
||||||
|
export default router;
|
||||||
122
src/services/misc/appupdate.js
Normal file
122
src/services/misc/appupdate.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import config from '../../config.js';
|
||||||
|
import log4js from 'log4js';
|
||||||
|
|
||||||
|
const logger = log4js.getLogger('AppUpdate');
|
||||||
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
|
const normalizeProjectUrl = (projectUrl) => {
|
||||||
|
if (typeof projectUrl !== 'string') return '';
|
||||||
|
return projectUrl.replace(/\/+$/, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getProjectUrl = () => normalizeProjectUrl(config.app?.jenkinsProject);
|
||||||
|
|
||||||
|
const buildApiUrl = (baseUrl, query = '') => {
|
||||||
|
const cleanBaseUrl = normalizeProjectUrl(baseUrl);
|
||||||
|
if (!cleanBaseUrl) return '';
|
||||||
|
return `${cleanBaseUrl}/api/json${query}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBranchBuildApiUrl = (branchUrl, buildType = 'lastSuccessfulBuild') =>
|
||||||
|
`${normalizeProjectUrl(branchUrl)}/${buildType}/api/json?tree=number,url,result,timestamp,artifacts[fileName,relativePath]`;
|
||||||
|
|
||||||
|
const mapArtifacts = (build) => {
|
||||||
|
const buildUrl = normalizeProjectUrl(build?.url);
|
||||||
|
const artifacts = Array.isArray(build?.artifacts) ? build.artifacts : [];
|
||||||
|
console.log(artifacts);
|
||||||
|
return artifacts.map((artifact) => ({
|
||||||
|
fileName: artifact.fileName,
|
||||||
|
relativePath: artifact.relativePath,
|
||||||
|
url: `${buildUrl}/artifact/${artifact.relativePath}`,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBranchesFromJenkins = async () => {
|
||||||
|
const projectUrl = getProjectUrl();
|
||||||
|
if (!projectUrl) {
|
||||||
|
throw new Error('Missing config.app.jenkinsProject');
|
||||||
|
}
|
||||||
|
|
||||||
|
const jenkinsUrl = buildApiUrl(projectUrl, '?tree=jobs[name,url,color]');
|
||||||
|
const response = await axios.get(jenkinsUrl, { timeout: 10000 });
|
||||||
|
const jobs = Array.isArray(response.data?.jobs) ? response.data.jobs : [];
|
||||||
|
|
||||||
|
return jobs
|
||||||
|
.map((job) => ({
|
||||||
|
name: job.name,
|
||||||
|
url: job.url,
|
||||||
|
color: job.color,
|
||||||
|
}))
|
||||||
|
.filter((job) => typeof job.name === 'string' && typeof job.url === 'string');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLatestBuildForBranch = async (branchUrl) => {
|
||||||
|
const buildTypes = ['lastSuccessfulBuild', 'lastBuild'];
|
||||||
|
|
||||||
|
for (const buildType of buildTypes) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(getBranchBuildApiUrl(branchUrl, buildType), {
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
if (response?.data?.number) {
|
||||||
|
return { build: response.data, source: buildType };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug(`Failed ${buildType} lookup for ${branchUrl}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { build: null, source: null };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const appUpdateBranchesRouteHandler = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const branches = await getBranchesFromJenkins();
|
||||||
|
return res.send({
|
||||||
|
branches: branches.map((branch) => branch.name),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to fetch Jenkins branches:', error);
|
||||||
|
return res.status(500).send({
|
||||||
|
error: 'Failed to fetch app update branches',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const appUpdateCurrentRouteHandler = async (req, res) => {
|
||||||
|
const requestedBranch = typeof req.query.branch === 'string' ? req.query.branch.trim() : '';
|
||||||
|
|
||||||
|
if (!requestedBranch) {
|
||||||
|
return res.status(400).send({ error: 'branch query parameter is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const branches = await getBranchesFromJenkins();
|
||||||
|
const branch = branches.find((item) => item.name === requestedBranch);
|
||||||
|
|
||||||
|
if (!branch) {
|
||||||
|
return res.status(404).send({ error: `Branch "${requestedBranch}" not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { build, source } = await getLatestBuildForBranch(branch.url);
|
||||||
|
if (!build) {
|
||||||
|
return res.status(404).send({ error: `No build found for branch "${requestedBranch}"` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.send({
|
||||||
|
branch: requestedBranch,
|
||||||
|
buildNumber: build.number,
|
||||||
|
buildUrl: build.url,
|
||||||
|
buildResult: build.result,
|
||||||
|
buildTimestamp: build.timestamp,
|
||||||
|
buildSource: source,
|
||||||
|
artifacts: mapArtifacts(build),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to fetch Jenkins build info:', error);
|
||||||
|
return res.status(500).send({
|
||||||
|
error: 'Failed to fetch current app update details',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user