refactor: remove now un-used system queries/mutations/resolvers from both client and server

This commit is contained in:
Nicolas Meienberger 2022-12-26 06:18:33 +01:00 committed by Nicolas Meienberger
parent 3cc3c9011e
commit 7d9c8a75a0
19 changed files with 11 additions and 869 deletions

View file

@ -98,18 +98,6 @@ export enum AppSupportedArchitecturesEnum {
Arm64 = 'ARM64',
}
export type Cpu = {
__typename?: 'Cpu';
load: Scalars['Float'];
};
export type DiskMemory = {
__typename?: 'DiskMemory';
available: Scalars['Float'];
total: Scalars['Float'];
used: Scalars['Float'];
};
export enum FieldTypesEnum {
Email = 'email',
Fqdn = 'fqdn',
@ -146,11 +134,9 @@ export type Mutation = {
login: TokenResponse;
logout: Scalars['Boolean'];
register: TokenResponse;
restart: Scalars['Boolean'];
startApp: App;
stopApp: App;
uninstallApp: App;
update: Scalars['Boolean'];
updateApp: App;
updateAppConfig: App;
};
@ -195,21 +181,12 @@ export type Query = {
listAppsInfo: ListAppsResonse;
me?: Maybe<User>;
refreshToken?: Maybe<TokenResponse>;
systemInfo?: Maybe<SystemInfoResponse>;
version: VersionResponse;
};
export type QueryGetAppArgs = {
id: Scalars['String'];
};
export type SystemInfoResponse = {
__typename?: 'SystemInfoResponse';
cpu: Cpu;
disk: DiskMemory;
memory: DiskMemory;
};
export type TokenResponse = {
__typename?: 'TokenResponse';
token: Scalars['String'];
@ -235,12 +212,6 @@ export type UsernamePasswordInput = {
username: Scalars['String'];
};
export type VersionResponse = {
__typename?: 'VersionResponse';
current: Scalars['String'];
latest?: Maybe<Scalars['String']>;
};
export type InstallAppMutationVariables = Exact<{
input: AppInputType;
}>;
@ -263,10 +234,6 @@ export type RegisterMutationVariables = Exact<{
export type RegisterMutation = { __typename?: 'Mutation'; register: { __typename?: 'TokenResponse'; token: string } };
export type RestartMutationVariables = Exact<{ [key: string]: never }>;
export type RestartMutation = { __typename?: 'Mutation'; restart: boolean };
export type StartAppMutationVariables = Exact<{
id: Scalars['String'];
}>;
@ -285,10 +252,6 @@ export type UninstallAppMutationVariables = Exact<{
export type UninstallAppMutation = { __typename?: 'Mutation'; uninstallApp: { __typename: 'App'; id: string; status: AppStatusEnum } };
export type UpdateMutationVariables = Exact<{ [key: string]: never }>;
export type UpdateMutation = { __typename?: 'Mutation'; update: boolean };
export type UpdateAppMutationVariables = Exact<{
id: Scalars['String'];
}>;
@ -398,22 +361,6 @@ export type RefreshTokenQueryVariables = Exact<{ [key: string]: never }>;
export type RefreshTokenQuery = { __typename?: 'Query'; refreshToken?: { __typename?: 'TokenResponse'; token: string } | null };
export type SystemInfoQueryVariables = Exact<{ [key: string]: never }>;
export type SystemInfoQuery = {
__typename?: 'Query';
systemInfo?: {
__typename?: 'SystemInfoResponse';
cpu: { __typename?: 'Cpu'; load: number };
disk: { __typename?: 'DiskMemory'; available: number; used: number; total: number };
memory: { __typename?: 'DiskMemory'; available: number; used: number; total: number };
} | null;
};
export type VersionQueryVariables = Exact<{ [key: string]: never }>;
export type VersionQuery = { __typename?: 'Query'; version: { __typename?: 'VersionResponse'; current: string; latest?: string | null } };
export const InstallAppDocument = gql`
mutation InstallApp($input: AppInputType!) {
installApp(input: $input) {
@ -545,36 +492,6 @@ export function useRegisterMutation(baseOptions?: Apollo.MutationHookOptions<Reg
export type RegisterMutationHookResult = ReturnType<typeof useRegisterMutation>;
export type RegisterMutationResult = Apollo.MutationResult<RegisterMutation>;
export type RegisterMutationOptions = Apollo.BaseMutationOptions<RegisterMutation, RegisterMutationVariables>;
export const RestartDocument = gql`
mutation Restart {
restart
}
`;
export type RestartMutationFn = Apollo.MutationFunction<RestartMutation, RestartMutationVariables>;
/**
* __useRestartMutation__
*
* To run a mutation, you first call `useRestartMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useRestartMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [restartMutation, { data, loading, error }] = useRestartMutation({
* variables: {
* },
* });
*/
export function useRestartMutation(baseOptions?: Apollo.MutationHookOptions<RestartMutation, RestartMutationVariables>) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<RestartMutation, RestartMutationVariables>(RestartDocument, options);
}
export type RestartMutationHookResult = ReturnType<typeof useRestartMutation>;
export type RestartMutationResult = Apollo.MutationResult<RestartMutation>;
export type RestartMutationOptions = Apollo.BaseMutationOptions<RestartMutation, RestartMutationVariables>;
export const StartAppDocument = gql`
mutation StartApp($id: String!) {
startApp(id: $id) {
@ -680,36 +597,6 @@ export function useUninstallAppMutation(baseOptions?: Apollo.MutationHookOptions
export type UninstallAppMutationHookResult = ReturnType<typeof useUninstallAppMutation>;
export type UninstallAppMutationResult = Apollo.MutationResult<UninstallAppMutation>;
export type UninstallAppMutationOptions = Apollo.BaseMutationOptions<UninstallAppMutation, UninstallAppMutationVariables>;
export const UpdateDocument = gql`
mutation Update {
update
}
`;
export type UpdateMutationFn = Apollo.MutationFunction<UpdateMutation, UpdateMutationVariables>;
/**
* __useUpdateMutation__
*
* To run a mutation, you first call `useUpdateMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [updateMutation, { data, loading, error }] = useUpdateMutation({
* variables: {
* },
* });
*/
export function useUpdateMutation(baseOptions?: Apollo.MutationHookOptions<UpdateMutation, UpdateMutationVariables>) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<UpdateMutation, UpdateMutationVariables>(UpdateDocument, options);
}
export type UpdateMutationHookResult = ReturnType<typeof useUpdateMutation>;
export type UpdateMutationResult = Apollo.MutationResult<UpdateMutation>;
export type UpdateMutationOptions = Apollo.BaseMutationOptions<UpdateMutation, UpdateMutationVariables>;
export const UpdateAppDocument = gql`
mutation UpdateApp($id: String!) {
updateApp(id: $id) {
@ -1048,84 +935,3 @@ export function useRefreshTokenLazyQuery(baseOptions?: Apollo.LazyQueryHookOptio
export type RefreshTokenQueryHookResult = ReturnType<typeof useRefreshTokenQuery>;
export type RefreshTokenLazyQueryHookResult = ReturnType<typeof useRefreshTokenLazyQuery>;
export type RefreshTokenQueryResult = Apollo.QueryResult<RefreshTokenQuery, RefreshTokenQueryVariables>;
export const SystemInfoDocument = gql`
query SystemInfo {
systemInfo {
cpu {
load
}
disk {
available
used
total
}
memory {
available
used
total
}
}
}
`;
/**
* __useSystemInfoQuery__
*
* To run a query within a React component, call `useSystemInfoQuery` and pass it any options that fit your needs.
* When your component renders, `useSystemInfoQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useSystemInfoQuery({
* variables: {
* },
* });
*/
export function useSystemInfoQuery(baseOptions?: Apollo.QueryHookOptions<SystemInfoQuery, SystemInfoQueryVariables>) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<SystemInfoQuery, SystemInfoQueryVariables>(SystemInfoDocument, options);
}
export function useSystemInfoLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<SystemInfoQuery, SystemInfoQueryVariables>) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<SystemInfoQuery, SystemInfoQueryVariables>(SystemInfoDocument, options);
}
export type SystemInfoQueryHookResult = ReturnType<typeof useSystemInfoQuery>;
export type SystemInfoLazyQueryHookResult = ReturnType<typeof useSystemInfoLazyQuery>;
export type SystemInfoQueryResult = Apollo.QueryResult<SystemInfoQuery, SystemInfoQueryVariables>;
export const VersionDocument = gql`
query Version {
version {
current
latest
}
}
`;
/**
* __useVersionQuery__
*
* To run a query within a React component, call `useVersionQuery` and pass it any options that fit your needs.
* When your component renders, `useVersionQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useVersionQuery({
* variables: {
* },
* });
*/
export function useVersionQuery(baseOptions?: Apollo.QueryHookOptions<VersionQuery, VersionQueryVariables>) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<VersionQuery, VersionQueryVariables>(VersionDocument, options);
}
export function useVersionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<VersionQuery, VersionQueryVariables>) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<VersionQuery, VersionQueryVariables>(VersionDocument, options);
}
export type VersionQueryHookResult = ReturnType<typeof useVersionQuery>;
export type VersionLazyQueryHookResult = ReturnType<typeof useVersionLazyQuery>;
export type VersionQueryResult = Apollo.QueryResult<VersionQuery, VersionQueryVariables>;

View file

@ -1,3 +0,0 @@
mutation Restart {
restart
}

View file

@ -1,3 +0,0 @@
mutation Update {
update
}

View file

@ -1,17 +0,0 @@
query SystemInfo {
systemInfo {
cpu {
load
}
disk {
available
used
total
}
memory {
available
used
total
}
}
}

View file

@ -1,6 +0,0 @@
query Version {
version {
current
latest
}
}

View file

@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect, useState, useCallback } from 'react';
import { ApolloClient } from '@apollo/client';
import { createApolloClient } from '../core/apollo/client';
@ -11,7 +11,7 @@ export default function useCachedResources(): IReturnProps {
const [isLoadingComplete, setLoadingComplete] = useState(false);
const [client, setClient] = useState<ApolloClient<unknown>>();
async function loadResourcesAndDataAsync() {
const loadResourcesAndDataAsync = useCallback(() => {
try {
const restoredClient = createApolloClient();
@ -19,14 +19,18 @@ export default function useCachedResources(): IReturnProps {
} catch (error) {
// We might want to provide this error information to an error reporting service
console.error(error);
} finally {
setLoadingComplete(true);
}
}
}, []);
useEffect(() => {
loadResourcesAndDataAsync();
}, []);
}, [loadResourcesAndDataAsync]);
useEffect(() => {
if (client) {
setLoadingComplete(true);
}
}, [client]);
return { client, isLoadingComplete };
}

View file

@ -1,239 +0,0 @@
import { faker } from '@faker-js/faker';
import axios from 'axios';
import fs from 'fs-extra';
import { DataSource } from 'typeorm';
import TipiCache from '../../../config/TipiCache';
import * as TipiConfig from '../../../core/config/TipiConfig';
import { setConfig } from '../../../core/config/TipiConfig';
import { setupConnection, teardownConnection } from '../../../test/connection';
import { gcall } from '../../../test/gcall';
import { restartMutation, updateMutation } from '../../../test/mutations';
import { systemInfoQuery, versionQuery } from '../../../test/queries';
import User from '../../auth/user.entity';
import { createUser } from '../../auth/__tests__/user.factory';
import { SystemInfoResponse } from '../system.types';
import EventDispatcher from '../../../core/config/EventDispatcher';
jest.mock('fs-extra');
jest.mock('axios');
jest.mock('redis');
beforeEach(async () => {
jest.resetModules();
jest.resetAllMocks();
jest.restoreAllMocks();
});
let db: DataSource | null = null;
const TEST_SUITE = 'systemresolver';
beforeAll(async () => {
db = await setupConnection(TEST_SUITE);
});
afterAll(async () => {
await db?.destroy();
await teardownConnection(TEST_SUITE);
});
beforeEach(async () => {
jest.resetModules();
jest.resetAllMocks();
jest.restoreAllMocks();
await User.clear();
});
describe('Test: systemInfo', () => {
it('Should return correct system info from file', async () => {
const systemInfo = {
cpu: { load: 10 },
memory: { available: 100, total: 1000, used: 900 },
disk: { available: 100, total: 1000, used: 900 },
};
const MockFiles = {
'/runtipi/state/system-info.json': JSON.stringify(systemInfo),
};
// @ts-ignore
fs.__createMockFiles(MockFiles);
const { data } = await gcall<{ systemInfo: SystemInfoResponse }>({ source: systemInfoQuery });
expect(data?.systemInfo).toBeDefined();
expect(data?.systemInfo.cpu).toBeDefined();
expect(data?.systemInfo.cpu.load).toBe(systemInfo.cpu.load);
expect(data?.systemInfo.memory).toBeDefined();
expect(data?.systemInfo.memory.available).toBe(systemInfo.memory.available);
expect(data?.systemInfo.memory.total).toBe(systemInfo.memory.total);
expect(data?.systemInfo.memory.used).toBe(systemInfo.memory.used);
expect(data?.systemInfo.disk).toBeDefined();
expect(data?.systemInfo.disk.available).toBe(systemInfo.disk.available);
expect(data?.systemInfo.disk.total).toBe(systemInfo.disk.total);
expect(data?.systemInfo.disk.used).toBe(systemInfo.disk.used);
});
it('Should return 0 for missing values', async () => {
const systemInfo = {
cpu: {},
memory: {},
disk: {},
};
const MockFiles = {
'/runtipi/state/system-info.json': JSON.stringify(systemInfo),
};
// @ts-ignore
fs.__createMockFiles(MockFiles);
const { data } = await gcall<{ systemInfo: SystemInfoResponse }>({ source: systemInfoQuery });
expect(data?.systemInfo).toBeDefined();
expect(data?.systemInfo.cpu).toBeDefined();
expect(data?.systemInfo.cpu.load).toBe(0);
expect(data?.systemInfo.memory).toBeDefined();
expect(data?.systemInfo.memory.available).toBe(0);
expect(data?.systemInfo.memory.total).toBe(0);
expect(data?.systemInfo.memory.used).toBe(0);
expect(data?.systemInfo.disk).toBeDefined();
expect(data?.systemInfo.disk.available).toBe(0);
expect(data?.systemInfo.disk.total).toBe(0);
expect(data?.systemInfo.disk.used).toBe(0);
});
});
describe('Test: getVersion', () => {
const current = `${faker.random.numeric(1)}.${faker.random.numeric(1)}.${faker.random.numeric()}`;
const latest = `${faker.random.numeric(1)}.${faker.random.numeric(1)}.${faker.random.numeric()}`;
beforeEach(async () => {
jest.spyOn(axios, 'get').mockResolvedValue({
data: { name: `v${latest}` },
});
setConfig('version', current);
});
it('Should return correct version', async () => {
const { data } = await gcall<{ version: { current: string; latest?: string } }>({
source: versionQuery,
});
expect(data?.version).toBeDefined();
expect(data?.version.current).toBeDefined();
expect(data?.version.latest).toBeDefined();
expect(data?.version.current).toBe(current);
expect(data?.version.latest).toBe(latest);
});
});
describe('Test: restart', () => {
beforeEach(async () => {
setConfig('status', 'RUNNING');
setConfig('version', '1.0.0');
TipiCache.set('latestVersion', '1.0.1');
});
it('Should return true', async () => {
// Arrange
EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
// Act
const user = await createUser();
const { data } = await gcall<{ restart: boolean }>({ source: restartMutation, userId: user.id });
// Assert
expect(data?.restart).toBeDefined();
expect(data?.restart).toBe(true);
});
it("Should return an error if user doesn't exist", async () => {
// Arrange
const { data, errors } = await gcall<{ restart: boolean }>({
source: restartMutation,
userId: 1,
});
// Assert
expect(errors?.[0].message).toBe('Access denied! You need to be authorized to perform this action!');
expect(data?.restart).toBeUndefined();
});
it('Should throw an error if no userId is not provided', async () => {
// Arrange
const { data, errors } = await gcall<{ restart: boolean }>({ source: restartMutation });
// Assert
expect(errors?.[0].message).toBe('Access denied! You need to be authorized to perform this action!');
expect(data?.restart).toBeUndefined();
});
it('Should set app status to restarting', async () => {
// Arrange
EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
const spy = jest.spyOn(TipiConfig, 'setConfig');
const user = await createUser();
// Act
await gcall<{ restart: boolean }>({ source: restartMutation, userId: user.id });
// Assert
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenNthCalledWith(1, 'status', 'RESTARTING');
spy.mockRestore();
});
});
describe('Test: update', () => {
beforeEach(async () => {
setConfig('status', 'RUNNING');
setConfig('version', '1.0.0');
TipiCache.set('latestVersion', '1.0.1');
});
it('Should return true', async () => {
// Arrange
EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
const user = await createUser();
// Act
const { data } = await gcall<{ update: boolean }>({ source: updateMutation, userId: user.id });
// Assert
expect(data?.update).toBeDefined();
expect(data?.update).toBe(true);
});
it("Should return an error if user doesn't exist", async () => {
// Act
const { data, errors } = await gcall<{ update: boolean }>({ source: updateMutation, userId: 1 });
// Assert
expect(errors?.[0].message).toBe('Access denied! You need to be authorized to perform this action!');
expect(data?.update).toBeUndefined();
});
it('Should throw an error if no userId is not provided', async () => {
// Act
const { data, errors } = await gcall<{ update: boolean }>({ source: updateMutation });
// Assert
expect(errors?.[0].message).toBe('Access denied! You need to be authorized to perform this action!');
expect(data?.update).toBeUndefined();
});
it('Should set app status to updating', async () => {
// Arrange
EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
const spy = jest.spyOn(TipiConfig, 'setConfig');
const user = await createUser();
// Act
await gcall<{ update: boolean }>({ source: updateMutation, userId: user.id });
// Assert
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenNthCalledWith(1, 'status', 'UPDATING');
spy.mockRestore();
});
});

View file

@ -1,187 +0,0 @@
import fs from 'fs-extra';
import semver from 'semver';
import axios from 'axios';
import { faker } from '@faker-js/faker';
import SystemService from '../system.service';
import TipiCache from '../../../config/TipiCache';
import { setConfig } from '../../../core/config/TipiConfig';
import EventDispatcher from '../../../core/config/EventDispatcher';
jest.mock('fs-extra');
jest.mock('axios');
jest.mock('redis');
beforeEach(async () => {
jest.resetModules();
jest.resetAllMocks();
});
describe('Test: systemInfo', () => {
it('Should throw if system-info.json does not exist', () => {
try {
SystemService.systemInfo();
} catch (e: unknown) {
if (e instanceof Error) {
expect(e).toBeDefined();
expect(e.message).toBe('Error parsing system info');
} else {
fail('Should throw an error');
}
}
});
it('It should return system info', async () => {
// Arrange
const info = {
cpu: { load: 0.1 },
memory: { available: 1000, total: 2000, used: 1000 },
disk: { available: 1000, total: 2000, used: 1000 },
};
const MockFiles = {
'/runtipi/state/system-info.json': JSON.stringify(info),
};
// @ts-ignore
fs.__createMockFiles(MockFiles);
// Act
const systemInfo = SystemService.systemInfo();
// Assert
expect(systemInfo).toBeDefined();
expect(systemInfo.cpu).toBeDefined();
expect(systemInfo.memory).toBeDefined();
});
});
describe('Test: getVersion', () => {
beforeEach(() => {
TipiCache.del('latestVersion');
});
afterAll(() => {
jest.restoreAllMocks();
});
it('It should return version', async () => {
// Arrange
const spy = jest.spyOn(axios, 'get').mockResolvedValue({
data: { name: `v${faker.random.numeric(1)}.${faker.random.numeric(1)}.${faker.random.numeric()}` },
});
// Act
const version = await SystemService.getVersion();
// Assert
expect(version).toBeDefined();
expect(version.current).toBeDefined();
expect(semver.valid(version.latest)).toBeTruthy();
spy.mockRestore();
});
it('Should return undefined for latest if request fails', async () => {
jest.spyOn(axios, 'get').mockImplementation(() => {
throw new Error('Error');
});
const version = await SystemService.getVersion();
expect(version).toBeDefined();
expect(version.current).toBeDefined();
expect(version.latest).toBeUndefined();
});
it('Should return cached version', async () => {
// Arrange
const spy = jest.spyOn(axios, 'get').mockResolvedValue({
data: { name: `v${faker.random.numeric(1)}.${faker.random.numeric(1)}.${faker.random.numeric()}` },
});
// Act
const version = await SystemService.getVersion();
const version2 = await SystemService.getVersion();
// Assert
expect(version).toBeDefined();
expect(version.current).toBeDefined();
expect(semver.valid(version.latest)).toBeTruthy();
expect(version2.latest).toBe(version.latest);
expect(version2.current).toBeDefined();
expect(semver.valid(version2.latest)).toBeTruthy();
expect(spy).toHaveBeenCalledTimes(1);
spy.mockRestore();
});
});
describe('Test: restart', () => {
it('Should return true', async () => {
// Arrange
EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
// Act
const restart = await SystemService.restart();
// Assert
expect(restart).toBeTruthy();
});
});
describe('Test: update', () => {
it('Should return true', async () => {
// Arrange
EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
setConfig('version', '0.0.1');
TipiCache.set('latestVersion', '0.0.2');
// Act
const update = await SystemService.update();
// Assert
expect(update).toBeTruthy();
});
it('Should throw an error if latest version is not set', async () => {
// Arrange
TipiCache.del('latestVersion');
const spy = jest.spyOn(axios, 'get').mockResolvedValue({
data: { name: null },
});
setConfig('version', '0.0.1');
// Act & Assert
await expect(SystemService.update()).rejects.toThrow('Could not get latest version');
spy.mockRestore();
});
it('Should throw if current version is higher than latest', async () => {
// Arrange
setConfig('version', '0.0.2');
TipiCache.set('latestVersion', '0.0.1');
// Act & Assert
await expect(SystemService.update()).rejects.toThrow('Current version is newer than latest version');
});
it('Should throw if current version is equal to latest', async () => {
// Arrange
setConfig('version', '0.0.1');
TipiCache.set('latestVersion', '0.0.1');
// Act & Assert
await expect(SystemService.update()).rejects.toThrow('Current version is already up to date');
});
it('Should throw an error if there is a major version difference', async () => {
// Arrange
setConfig('version', '0.0.1');
TipiCache.set('latestVersion', '1.0.0');
// Act & Assert
await expect(SystemService.update()).rejects.toThrow('The major version has changed. Please update manually');
});
});

View file

@ -1,12 +0,0 @@
import { Request, Response } from 'express';
import { getConfig } from '../../core/config/TipiConfig';
const status = async (req: Request, res: Response) => {
res.status(200).json({
status: getConfig().status,
});
};
export default {
status,
};

View file

@ -1,28 +0,0 @@
import { Authorized, Mutation, Query, Resolver } from 'type-graphql';
import SystemService from './system.service';
import { SystemInfoResponse, VersionResponse } from './system.types';
@Resolver()
export default class AuthResolver {
@Query(() => SystemInfoResponse, { nullable: true })
async systemInfo(): Promise<SystemInfoResponse> {
return SystemService.systemInfo();
}
@Query(() => VersionResponse)
async version(): Promise<VersionResponse> {
return SystemService.getVersion();
}
@Authorized()
@Mutation(() => Boolean)
async restart(): Promise<boolean> {
return SystemService.restart();
}
@Authorized()
@Mutation(() => Boolean)
async update(): Promise<boolean> {
return SystemService.update();
}
}

View file

@ -1,97 +0,0 @@
import axios from 'axios';
import z from 'zod';
import semver from 'semver';
import logger from '../../config/logger/logger';
import TipiCache from '../../config/TipiCache';
import { getConfig, setConfig } from '../../core/config/TipiConfig';
import { readJsonFile } from '../fs/fs.helpers';
import { eventDispatcher, EventTypes } from '../../core/config/EventDispatcher';
const systemInfoSchema = z.object({
cpu: z.object({
load: z.number().default(0),
}),
disk: z.object({
total: z.number().default(0),
used: z.number().default(0),
available: z.number().default(0),
}),
memory: z.object({
total: z.number().default(0),
available: z.number().default(0),
used: z.number().default(0),
}),
});
const systemInfo = (): z.infer<typeof systemInfoSchema> => {
const info = systemInfoSchema.safeParse(readJsonFile('/runtipi/state/system-info.json'));
if (!info.success) {
logger.error('Error parsing system info');
logger.error(info.error);
throw new Error('Error parsing system info');
} else {
return info.data;
}
};
const getVersion = async (): Promise<{ current: string; latest?: string }> => {
try {
let version = await TipiCache.get('latestVersion');
if (!version) {
const { data } = await axios.get('https://api.github.com/repos/meienberger/runtipi/releases/latest');
version = data.name.replace('v', '');
await TipiCache.set('latestVersion', version?.replace('v', '') || '', 60 * 60);
}
return { current: getConfig().version, latest: version?.replace('v', '') };
} catch (e) {
logger.error(e);
return { current: getConfig().version, latest: undefined };
}
};
const restart = async (): Promise<boolean> => {
setConfig('status', 'RESTARTING');
eventDispatcher.dispatchEventAsync(EventTypes.RESTART);
return true;
};
const update = async (): Promise<boolean> => {
const { current, latest } = await getVersion();
if (!latest) {
throw new Error('Could not get latest version');
}
if (semver.gt(current, latest)) {
throw new Error('Current version is newer than latest version');
}
if (semver.eq(current, latest)) {
throw new Error('Current version is already up to date');
}
if (semver.major(current) !== semver.major(latest)) {
throw new Error('The major version has changed. Please update manually');
}
setConfig('status', 'UPDATING');
eventDispatcher.dispatchEventAsync(EventTypes.UPDATE);
return true;
};
const SystemService = {
systemInfo,
getVersion,
restart,
update,
};
export default SystemService;

View file

@ -1,42 +0,0 @@
import { Field, ObjectType } from 'type-graphql';
@ObjectType()
class Cpu {
@Field(() => Number, { nullable: false })
load!: number;
}
@ObjectType()
class DiskMemory {
@Field(() => Number, { nullable: false })
total!: number;
@Field(() => Number, { nullable: false })
used!: number;
@Field(() => Number, { nullable: false })
available!: number;
}
@ObjectType()
class SystemInfoResponse {
@Field(() => Cpu, { nullable: false })
cpu!: Cpu;
@Field(() => DiskMemory, { nullable: false })
disk!: DiskMemory;
@Field(() => DiskMemory, { nullable: false })
memory!: DiskMemory;
}
@ObjectType()
class VersionResponse {
@Field(() => String, { nullable: false })
current!: string;
@Field(() => String, { nullable: true })
latest?: string;
}
export { SystemInfoResponse, VersionResponse };

View file

@ -3,11 +3,10 @@ import { buildSchema } from 'type-graphql';
import { customAuthChecker } from './core/middlewares/authChecker';
import AppsResolver from './modules/apps/apps.resolver';
import AuthResolver from './modules/auth/auth.resolver';
import SystemResolver from './modules/system/system.resolver';
const createSchema = (): Promise<GraphQLSchema> =>
buildSchema({
resolvers: [AppsResolver, AuthResolver, SystemResolver],
resolvers: [AppsResolver, AuthResolver],
validate: true,
authChecker: customAuthChecker,
});

View file

@ -17,7 +17,6 @@ import { runUpdates } from './core/updates/run';
import recover from './core/updates/recover-migrations';
import startJobs from './core/jobs/jobs';
import { applyJsonConfig, getConfig, setConfig } from './core/config/TipiConfig';
import systemController from './modules/system/system.controller';
import { eventDispatcher, EventTypes } from './core/config/EventDispatcher';
const applyCustomConfig = () => {
@ -50,7 +49,6 @@ const main = async () => {
app.use(cors(corsOptions));
app.use(express.static(`${getConfig().rootFolder}/repos/${getConfig().appsRepoId}`));
app.use('/status', systemController.status);
app.use(getSessionMiddleware);
await datasource.initialize();

View file

@ -10,8 +10,6 @@ import * as updateAppConfig from './updateAppConfig.graphql';
import * as updateApp from './updateApp.graphql';
import * as register from './register.graphql';
import * as login from './login.graphql';
import * as restart from './restart.graphql';
import * as update from './update.graphql';
export const installAppMutation = print(installApp);
export const startAppMutation = print(startApp);
@ -21,5 +19,3 @@ export const updateAppConfigMutation = print(updateAppConfig);
export const updateAppMutation = print(updateApp);
export const registerMutation = print(register);
export const loginMutation = print(login);
export const restartMutation = print(restart);
export const updateMutation = print(update);

View file

@ -1,3 +0,0 @@
mutation {
restart
}

View file

@ -1,3 +0,0 @@
mutation {
update
}

View file

@ -7,8 +7,6 @@ import * as getApp from './getApp.graphql';
import * as InstalledApps from './installedApps.graphql';
import * as Me from './me.graphql';
import * as isConfigured from './isConfigured.graphql';
import * as systemInfo from './systemInfo.graphql';
import * as version from './version.graphql';
import * as refreshToken from './refreshToken.graphql';
export const listAppInfosQuery = print(listAppInfos);
@ -16,6 +14,4 @@ export const getAppQuery = print(getApp);
export const InstalledAppsQuery = print(InstalledApps);
export const MeQuery = print(Me);
export const isConfiguredQuery = print(isConfigured);
export const systemInfoQuery = print(systemInfo);
export const versionQuery = print(version);
export const refreshTokenQuery = print(refreshToken);

View file

@ -1,17 +0,0 @@
query {
systemInfo {
cpu {
load
}
memory {
total
available
used
}
disk {
total
available
used
}
}
}