From 849bc6e3aab0dbb89145ac4d9c1043cac32e9f66 Mon Sep 17 00:00:00 2001 From: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com> Date: Mon, 22 Jul 2024 15:50:45 +0200 Subject: [PATCH] fix(server): correct openapi response type for getServerLicense() (#11261) * fix(server): correct openapi response type for getServerLicense() * return 404 error when license doesn't exist * update e2e test --- e2e/src/api/specs/server.e2e-spec.ts | 2 +- mobile/openapi/lib/api/server_api.dart | 4 ++-- open-api/immich-openapi-specs.json | 5 ++++- open-api/typescript-sdk/src/fetch-client.ts | 10 ++++++---- server/src/controllers/server.controller.ts | 5 +++-- server/src/services/server.service.ts | 10 +++++++--- .../user-settings-page/license-settings.svelte | 14 +++++++++++++- 7 files changed, 36 insertions(+), 14 deletions(-) diff --git a/e2e/src/api/specs/server.e2e-spec.ts b/e2e/src/api/specs/server.e2e-spec.ts index 0ab53a0cb3..d19744674f 100644 --- a/e2e/src/api/specs/server.e2e-spec.ts +++ b/e2e/src/api/specs/server.e2e-spec.ts @@ -254,7 +254,7 @@ describe('/server', () => { .set('Authorization', `Bearer ${admin.accessToken}`) .send(serverLicense); const { status } = await request(app).get('/server/license').set('Authorization', `Bearer ${admin.accessToken}`); - expect(status).toBe(200); + expect(status).toBe(404); }); }); diff --git a/mobile/openapi/lib/api/server_api.dart b/mobile/openapi/lib/api/server_api.dart index 4987c6cd9c..9cb52514c2 100644 --- a/mobile/openapi/lib/api/server_api.dart +++ b/mobile/openapi/lib/api/server_api.dart @@ -75,7 +75,7 @@ class ServerApi { ); } - Future getServerLicense() async { + Future getServerLicense() async { final response = await getServerLicenseWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); @@ -84,7 +84,7 @@ class ServerApi { // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'Object',) as Object; + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LicenseResponseDto',) as LicenseResponseDto; } return null; diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index a6cd8913d2..de19f99bcf 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -5022,11 +5022,14 @@ "content": { "application/json": { "schema": { - "type": "object" + "$ref": "#/components/schemas/LicenseResponseDto" } } }, "description": "" + }, + "404": { + "description": "" } }, "security": [ diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 84d959a8da..9b6c3f2574 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -880,12 +880,12 @@ export type ServerVersionResponseDto = { minor: number; patch: number; }; -export type LicenseKeyDto = { +export type LicenseResponseDto = { + activatedAt: string; activationKey: string; licenseKey: string; }; -export type LicenseResponseDto = { - activatedAt: string; +export type LicenseKeyDto = { activationKey: string; licenseKey: string; }; @@ -2511,7 +2511,9 @@ export function deleteServerLicense(opts?: Oazapfts.RequestOpts) { export function getServerLicense(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; - data: object; + data: LicenseResponseDto; + } | { + status: 404; }>("/server/license", { ...opts })); diff --git a/server/src/controllers/server.controller.ts b/server/src/controllers/server.controller.ts index 009c36c793..0c615223e2 100644 --- a/server/src/controllers/server.controller.ts +++ b/server/src/controllers/server.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Delete, Get, Put } from '@nestjs/common'; -import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger'; +import { ApiExcludeEndpoint, ApiNotFoundResponse, ApiTags } from '@nestjs/swagger'; import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto'; import { ServerAboutResponseDto, @@ -95,7 +95,8 @@ export class ServerController { @Get('license') @Authenticated({ admin: true }) - getServerLicense(): Promise { + @ApiNotFoundResponse() + getServerLicense(): Promise { return this.service.getLicense(); } } diff --git a/server/src/services/server.service.ts b/server/src/services/server.service.ts index 1aaf85b1ba..22196c4e26 100644 --- a/server/src/services/server.service.ts +++ b/server/src/services/server.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common'; import { getBuildMetadata, getServerLicensePublicKey } from 'src/config'; import { serverVersion } from 'src/constants'; import { StorageCore, StorageFolder } from 'src/cores/storage.core'; @@ -164,8 +164,12 @@ export class ServerService implements OnEvents { await this.systemMetadataRepository.delete(SystemMetadataKey.LICENSE); } - async getLicense(): Promise { - return this.systemMetadataRepository.get(SystemMetadataKey.LICENSE); + async getLicense(): Promise { + const license = await this.systemMetadataRepository.get(SystemMetadataKey.LICENSE); + if (!license) { + throw new NotFoundException(); + } + return license; } async setLicense(dto: LicenseKeyDto): Promise { diff --git a/web/src/lib/components/user-settings-page/license-settings.svelte b/web/src/lib/components/user-settings-page/license-settings.svelte index 6952d8139e..a88a89486f 100644 --- a/web/src/lib/components/user-settings-page/license-settings.svelte +++ b/web/src/lib/components/user-settings-page/license-settings.svelte @@ -10,6 +10,7 @@ getAboutInfo, getMyUser, getServerLicense, + isHttpError, type LicenseResponseDto, } from '@immich/sdk'; import Icon from '$lib/components/elements/icon.svelte'; @@ -36,7 +37,18 @@ } if (isServerLicense && $user.isAdmin) { - serverLicenseInfo = (await getServerLicense()) as LicenseResponseDto | null; + serverLicenseInfo = await getServerLicenseInfo(); + } + }; + + const getServerLicenseInfo = async () => { + try { + return await getServerLicense(); + } catch (error) { + if (isHttpError(error) && error.status === 404) { + return null; + } + throw error; } };