2024-10-02 10:54:35 -04:00
|
|
|
import { Injectable } from '@nestjs/common';
|
2024-05-17 12:22:39 -04:00
|
|
|
import { DateTime } from 'luxon';
|
2024-05-20 20:31:36 -04:00
|
|
|
import semver, { SemVer } from 'semver';
|
2024-10-02 08:37:26 -04:00
|
|
|
import { serverVersion } from 'src/constants';
|
2024-09-30 10:35:11 -04:00
|
|
|
import { OnEvent } from 'src/decorators';
|
2024-06-28 17:08:19 +01:00
|
|
|
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
|
2024-08-15 06:57:01 -04:00
|
|
|
import { VersionCheckMetadata } from 'src/entities/system-metadata.entity';
|
2024-10-02 08:37:26 -04:00
|
|
|
import { ImmichEnvironment, SystemMetadataKey } from 'src/enum';
|
2024-10-02 10:54:35 -04:00
|
|
|
import { DatabaseLock } from 'src/interfaces/database.interface';
|
|
|
|
|
import { ArgOf } from 'src/interfaces/event.interface';
|
|
|
|
|
import { JobName, JobStatus } from 'src/interfaces/job.interface';
|
2024-09-30 17:31:21 -04:00
|
|
|
import { BaseService } from 'src/services/base.service';
|
2024-05-17 12:22:39 -04:00
|
|
|
|
2024-05-20 20:31:36 -04:00
|
|
|
const asNotification = ({ checkedAt, releaseVersion }: VersionCheckMetadata): ReleaseNotification => {
|
2024-05-17 12:22:39 -04:00
|
|
|
return {
|
2024-05-20 20:31:36 -04:00
|
|
|
isAvailable: semver.gt(releaseVersion, serverVersion),
|
2024-05-17 12:22:39 -04:00
|
|
|
checkedAt,
|
2024-05-20 20:31:36 -04:00
|
|
|
serverVersion: ServerVersionResponseDto.fromSemVer(serverVersion),
|
|
|
|
|
releaseVersion: ServerVersionResponseDto.fromSemVer(new SemVer(releaseVersion)),
|
2024-05-17 12:22:39 -04:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@Injectable()
|
2024-09-30 17:31:21 -04:00
|
|
|
export class VersionService extends BaseService {
|
2024-09-30 10:35:11 -04:00
|
|
|
@OnEvent({ name: 'app.bootstrap' })
|
2024-08-15 16:12:41 -04:00
|
|
|
async onBootstrap(): Promise<void> {
|
2024-05-17 12:22:39 -04:00
|
|
|
await this.handleVersionCheck();
|
2024-10-01 13:33:58 -04:00
|
|
|
|
|
|
|
|
await this.databaseRepository.withLock(DatabaseLock.VersionHistory, async () => {
|
|
|
|
|
const latest = await this.versionRepository.getLatest();
|
|
|
|
|
const current = serverVersion.toString();
|
|
|
|
|
if (!latest || latest.version !== current) {
|
|
|
|
|
this.logger.log(`Version has changed, adding ${current} to history`);
|
|
|
|
|
await this.versionRepository.create({ version: current });
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-05-17 12:22:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getVersion() {
|
2024-05-20 20:31:36 -04:00
|
|
|
return ServerVersionResponseDto.fromSemVer(serverVersion);
|
2024-10-01 13:33:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getVersionHistory() {
|
|
|
|
|
return this.versionRepository.getAll();
|
2024-05-17 12:22:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async handleQueueVersionCheck() {
|
|
|
|
|
await this.jobRepository.queue({ name: JobName.VERSION_CHECK, data: {} });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async handleVersionCheck(): Promise<JobStatus> {
|
|
|
|
|
try {
|
|
|
|
|
this.logger.debug('Running version check');
|
|
|
|
|
|
2024-10-02 08:37:26 -04:00
|
|
|
const { environment } = this.configRepository.getEnv();
|
|
|
|
|
if (environment === ImmichEnvironment.DEVELOPMENT) {
|
2024-05-17 12:22:39 -04:00
|
|
|
return JobStatus.SKIPPED;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-30 17:31:21 -04:00
|
|
|
const { newVersionCheck } = await this.getConfig({ withCache: true });
|
2024-05-17 12:22:39 -04:00
|
|
|
if (!newVersionCheck.enabled) {
|
|
|
|
|
return JobStatus.SKIPPED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const versionCheck = await this.systemMetadataRepository.get(SystemMetadataKey.VERSION_CHECK_STATE);
|
|
|
|
|
if (versionCheck?.checkedAt) {
|
|
|
|
|
const lastUpdate = DateTime.fromISO(versionCheck.checkedAt);
|
|
|
|
|
const elapsedTime = DateTime.now().diff(lastUpdate).as('minutes');
|
|
|
|
|
// check once per hour (max)
|
|
|
|
|
if (elapsedTime < 60) {
|
|
|
|
|
return JobStatus.SKIPPED;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-02 10:54:35 -04:00
|
|
|
const { tag_name: releaseVersion, published_at: publishedAt } =
|
|
|
|
|
await this.serverInfoRepository.getGitHubRelease();
|
2024-05-20 20:31:36 -04:00
|
|
|
const metadata: VersionCheckMetadata = { checkedAt: DateTime.utc().toISO(), releaseVersion };
|
2024-05-17 12:22:39 -04:00
|
|
|
|
|
|
|
|
await this.systemMetadataRepository.set(SystemMetadataKey.VERSION_CHECK_STATE, metadata);
|
|
|
|
|
|
2024-05-20 20:31:36 -04:00
|
|
|
if (semver.gt(releaseVersion, serverVersion)) {
|
|
|
|
|
this.logger.log(`Found ${releaseVersion}, released at ${new Date(publishedAt).toLocaleString()}`);
|
2024-09-30 15:50:34 -04:00
|
|
|
this.eventRepository.clientBroadcast('on_new_release', asNotification(metadata));
|
2024-05-17 12:22:39 -04:00
|
|
|
}
|
|
|
|
|
} catch (error: Error | any) {
|
|
|
|
|
this.logger.warn(`Unable to run version check: ${error}`, error?.stack);
|
|
|
|
|
return JobStatus.FAILED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return JobStatus.SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-30 10:35:11 -04:00
|
|
|
@OnEvent({ name: 'websocket.connect' })
|
|
|
|
|
async onWebsocketConnection({ userId }: ArgOf<'websocket.connect'>) {
|
2024-09-30 15:50:34 -04:00
|
|
|
this.eventRepository.clientSend('on_server_version', userId, serverVersion);
|
2024-05-17 12:22:39 -04:00
|
|
|
const metadata = await this.systemMetadataRepository.get(SystemMetadataKey.VERSION_CHECK_STATE);
|
|
|
|
|
if (metadata) {
|
2024-09-30 15:50:34 -04:00
|
|
|
this.eventRepository.clientSend('on_new_release', userId, asNotification(metadata));
|
2024-05-17 12:22:39 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|