feat: add version checking

This commit is contained in:
Tom Wheeler
2025-08-26 01:04:32 +12:00
parent 2ec067e650
commit 805c4fabc9
4 changed files with 204 additions and 5 deletions

133
server/api/github.ts Normal file
View File

@@ -0,0 +1,133 @@
import cacheManager from '@server/lib/cache';
import logger from '@server/logger';
import ExternalAPI from './externalapi';
interface GitHubRelease {
url: string;
assets_url: string;
upload_url: string;
html_url: string;
id: number;
node_id: string;
tag_name: string;
target_commitish: string;
name: string;
draft: boolean;
prerelease: boolean;
created_at: string;
published_at: string;
tarball_url: string;
zipball_url: string;
body: string;
}
interface GithubCommit {
sha: string;
node_id: string;
commit: {
author: {
name: string;
email: string;
date: string;
};
committer: {
name: string;
email: string;
date: string;
};
message: string;
tree: {
sha: string;
url: string;
};
url: string;
comment_count: number;
verification: {
verified: boolean;
reason: string;
signature: string;
payload: string;
};
};
url: string;
html_url: string;
comments_url: string;
parents: [
{
sha: string;
url: string;
html_url: string;
}
];
}
class GithubAPI extends ExternalAPI {
constructor() {
super(
'https://api.github.com',
{},
{
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
nodeCache: cacheManager.getCache('github').data,
}
);
}
public async getAgregarrReleases({
take = 20,
}: {
take?: number;
} = {}): Promise<GitHubRelease[]> {
try {
const data = await this.get<GitHubRelease[]>(
'/repos/agregarr/agregarr/releases',
{
params: {
per_page: take,
},
}
);
return data;
} catch (e) {
logger.warn(
"Failed to retrieve GitHub releases. This may be an issue on GitHub's end. Agregarr can't check if it's on the latest version.",
{ label: 'GitHub API', errorMessage: e.message }
);
return [];
}
}
public async getAgregarrCommits({
take = 20,
branch = 'develop',
}: {
take?: number;
branch?: string;
} = {}): Promise<GithubCommit[]> {
try {
const data = await this.get<GithubCommit[]>(
'/repos/agregarr/agregarr/commits',
{
params: {
per_page: take,
branch,
},
}
);
return data;
} catch (e) {
logger.warn(
"Failed to retrieve GitHub commits. This may be an issue on GitHub's end. Agregarr can't check if it's on the latest version.",
{ label: 'GitHub API', errorMessage: e.message }
);
return [];
}
}
}
export default GithubAPI;

View File

@@ -6,6 +6,7 @@ import { startJobs } from '@server/job/schedule';
import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import routes from '@server/routes';
import restartFlag from '@server/utils/restartFlag';
// imageproxy removed - not needed for collections-only app
import { getAppVersion } from '@server/utils/appVersion';
import { getClientIp } from '@supercharge/request-ip';
@@ -44,6 +45,9 @@ app
// Load Settings
const settings = getSettings().load();
// Initialize RestartFlag with current settings
restartFlag.initializeSettings(settings.main);
// Initialize sync status for existing collections (one-time migration)
settings.initializeSyncStatusForExistingCollections();

View File

@@ -1,4 +1,5 @@
// PushoverAPI removed - notification system not needed
import GithubAPI from '@server/api/github';
import TheMovieDb from '@server/api/themoviedb';
import type {
TmdbMovieResult,
@@ -12,6 +13,7 @@ import { mapNetwork } from '@server/models/Tv';
import settingsRoutes from '@server/routes/settings';
import { appDataPath, appDataStatus } from '@server/utils/appDataVolume';
import { getAppVersion, getCommitTag } from '@server/utils/appVersion';
import restartFlag from '@server/utils/restartFlag';
import { isPerson } from '@server/utils/typeHelpers';
import { Router } from 'express';
import authRoutes from './auth';
@@ -39,14 +41,51 @@ const router = Router();
router.use(checkUser);
router.get('/status', (_req, res) => {
router.get('/status', async (_req, res) => {
const githubApi = new GithubAPI();
const currentVersion = getAppVersion();
const commitTag = getCommitTag();
let updateAvailable = false;
let commitsBehind = 0;
if (currentVersion.startsWith('develop-') && commitTag !== 'local') {
const commits = await githubApi.getAgregarrCommits();
if (commits.length) {
const filteredCommits = commits.filter(
(commit) => !commit.commit.message.includes('[skip ci]')
);
if (filteredCommits[0].sha !== commitTag) {
updateAvailable = true;
}
const commitIndex = filteredCommits.findIndex(
(commit) => commit.sha === commitTag
);
if (updateAvailable) {
commitsBehind = commitIndex;
}
}
} else if (commitTag !== 'local') {
const releases = await githubApi.getAgregarrReleases();
if (releases.length) {
const latestVersion = releases[0];
if (!latestVersion.name.includes(currentVersion)) {
updateAvailable = true;
}
}
}
return res.status(200).json({
version: getAppVersion(),
status: 'ok',
commitTag: getCommitTag(),
tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
appData: appDataStatus(),
appDataPath: appDataPath(),
updateAvailable,
commitsBehind,
restartRequired: restartFlag.isSet(),
});
});

View File

@@ -0,0 +1,23 @@
import type { MainSettings } from '@server/lib/settings';
import { getSettings } from '@server/lib/settings';
class RestartFlag {
private settings: MainSettings;
public initializeSettings(settings: MainSettings): void {
this.settings = { ...settings };
}
public isSet(): boolean {
const settings = getSettings().main;
return (
this.settings.csrfProtection !== settings.csrfProtection ||
this.settings.trustProxy !== settings.trustProxy
);
}
}
const restartFlag = new RestartFlag();
export default restartFlag;