chore: fix eslint rules
This commit is contained in:
parent
ef93cdd669
commit
35ebb1069a
30 changed files with 241 additions and 251 deletions
|
@ -1,6 +1,6 @@
|
|||
module.exports = {
|
||||
env: { node: true, jest: true },
|
||||
extends: ['airbnb-typescript', 'eslint:recommended', 'plugin:import/typescript'],
|
||||
plugins: ['@typescript-eslint', 'import', 'react'],
|
||||
extends: ['airbnb-base', 'airbnb-typescript/base', 'eslint:recommended', 'plugin:import/typescript', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
|
@ -8,18 +8,19 @@ module.exports = {
|
|||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'import', 'react'],
|
||||
rules: {
|
||||
'arrow-body-style': 0,
|
||||
'no-restricted-exports': 0,
|
||||
'max-len': [1, { code: 200 }],
|
||||
'import/extensions': ['error', 'ignorePackages', { js: 'never', jsx: 'never', ts: 'never', tsx: 'never' }],
|
||||
indent: 'off',
|
||||
'@typescript-eslint/indent': 0,
|
||||
'no-unused-vars': [1, { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-unused-vars': [1, { argsIgnorePattern: '^_' }],
|
||||
'max-classes-per-file': 0,
|
||||
'class-methods-use-this': 0,
|
||||
'import/prefer-default-export': 0,
|
||||
'no-underscore-dangle': 0,
|
||||
'@typescript-eslint/ban-ts-comment': 0,
|
||||
},
|
||||
globals: {
|
||||
NodeJS: true,
|
||||
},
|
||||
env: { node: true, jest: true },
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const childProcess: { execFile: typeof execFile } = jest.genMockFromModule('child_process');
|
||||
|
||||
const execFile = (_path: string, _args: string[], _thing: any, callback: Function) => {
|
||||
const execFile = (_path: string, _args: string[], _thing: any, callback: any) => {
|
||||
callback();
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import path from 'path';
|
||||
|
||||
const fs: {
|
||||
__createMockFiles: typeof createMockFiles;
|
||||
__resetAllMocks: typeof resetAllMocks;
|
||||
|
@ -20,7 +21,7 @@ const createMockFiles = (newMockFiles: Record<string, string>) => {
|
|||
mockFiles = Object.create(null);
|
||||
|
||||
// Create folder tree
|
||||
for (const file in newMockFiles) {
|
||||
Object.keys(newMockFiles).forEach((file) => {
|
||||
const dir = path.dirname(file);
|
||||
|
||||
if (!mockFiles[dir]) {
|
||||
|
@ -29,16 +30,12 @@ const createMockFiles = (newMockFiles: Record<string, string>) => {
|
|||
|
||||
mockFiles[dir].push(path.basename(file));
|
||||
mockFiles[file] = newMockFiles[file];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const readFileSync = (p: string) => {
|
||||
return mockFiles[p];
|
||||
};
|
||||
const readFileSync = (p: string) => mockFiles[p];
|
||||
|
||||
const existsSync = (p: string) => {
|
||||
return mockFiles[p] !== undefined;
|
||||
};
|
||||
const existsSync = (p: string) => mockFiles[p] !== undefined;
|
||||
|
||||
const writeFileSync = (p: string, data: any) => {
|
||||
mockFiles[p] = data;
|
||||
|
@ -85,7 +82,7 @@ const copySync = (source: string, destination: string) => {
|
|||
|
||||
if (mockFiles[source] instanceof Array) {
|
||||
mockFiles[source].forEach((file: string) => {
|
||||
mockFiles[destination + '/' + file] = mockFiles[source + '/' + file];
|
||||
mockFiles[`${destination}/${file}`] = mockFiles[`${source}/${file}`];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -120,4 +117,5 @@ fs.createFileSync = createFileSync;
|
|||
fs.__createMockFiles = createMockFiles;
|
||||
fs.__resetAllMocks = resetAllMocks;
|
||||
|
||||
module.exports = fs;
|
||||
export default fs;
|
||||
// module.exports = fs;
|
||||
|
|
|
@ -9,16 +9,10 @@ module.exports = {
|
|||
values.set(key, value);
|
||||
expirations.set(key, exp);
|
||||
},
|
||||
get: (key: string) => {
|
||||
return values.get(key);
|
||||
},
|
||||
get: (key: string) => values.get(key),
|
||||
quit: jest.fn(),
|
||||
del: (key: string) => {
|
||||
return values.delete(key);
|
||||
},
|
||||
ttl: (key: string) => {
|
||||
return expirations.get(key);
|
||||
},
|
||||
del: (key: string) => values.delete(key),
|
||||
ttl: (key: string) => expirations.get(key),
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -22,5 +22,5 @@ export default new DataSource({
|
|||
logging: !__prod__,
|
||||
synchronize: false,
|
||||
entities: [App, User, Update],
|
||||
migrations: [process.cwd() + '/dist/config/migrations/*.js'],
|
||||
migrations: [`${process.cwd()}/dist/config/migrations/*.js`],
|
||||
});
|
||||
|
|
|
@ -4,15 +4,13 @@ import { __prod__ } from '../constants/constants';
|
|||
import logger from './logger';
|
||||
|
||||
const ApolloLogs: PluginDefinition = {
|
||||
requestDidStart: async () => {
|
||||
return {
|
||||
requestDidStart: async () => ({
|
||||
async didEncounterErrors(errors) {
|
||||
if (!__prod__) {
|
||||
logger.error(JSON.stringify(errors.errors));
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export { ApolloLogs };
|
||||
|
|
|
@ -105,7 +105,7 @@ class Config {
|
|||
this.config = parsed;
|
||||
}
|
||||
|
||||
public setConfig<T extends keyof typeof configSchema.shape>(key: T, value: z.infer<typeof configSchema>[T], writeFile: boolean = false) {
|
||||
public setConfig<T extends keyof typeof configSchema.shape>(key: T, value: z.infer<typeof configSchema>[T], writeFile = false) {
|
||||
const newConf: z.infer<typeof configSchema> = { ...this.getConfig() };
|
||||
newConf[key] = value;
|
||||
|
||||
|
@ -122,7 +122,7 @@ class Config {
|
|||
}
|
||||
}
|
||||
|
||||
export const setConfig = <T extends keyof typeof configSchema.shape>(key: T, value: z.infer<typeof configSchema>[T], writeFile: boolean = false) => {
|
||||
export const setConfig = <T extends keyof typeof configSchema.shape>(key: T, value: z.infer<typeof configSchema>[T], writeFile = false) => {
|
||||
Config.getInstance().setConfig(key, value, writeFile);
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ const WATCH_FILE = '/runtipi/state/events';
|
|||
|
||||
jest.mock('fs-extra');
|
||||
|
||||
// eslint-disable-next-line no-promise-executor-return
|
||||
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -29,7 +30,7 @@ describe('EventDispatcher - dispatchEvent', () => {
|
|||
eventDispatcher.dispatchEvent(EventTypes.APP, ['--help']);
|
||||
|
||||
// @ts-ignore
|
||||
const queue = eventDispatcher.queue;
|
||||
const { queue } = eventDispatcher;
|
||||
|
||||
expect(queue.length).toBe(2);
|
||||
});
|
||||
|
@ -39,12 +40,12 @@ describe('EventDispatcher - dispatchEvent', () => {
|
|||
eventDispatcher.dispatchEvent(EventTypes.UPDATE, ['--help']);
|
||||
|
||||
// @ts-ignore
|
||||
const queue = eventDispatcher.queue;
|
||||
const { queue } = eventDispatcher;
|
||||
|
||||
await wait(1050);
|
||||
|
||||
// @ts-ignore
|
||||
const lock = eventDispatcher.lock;
|
||||
const { lock } = eventDispatcher;
|
||||
|
||||
expect(queue.length).toBe(2);
|
||||
expect(lock).toBeDefined();
|
||||
|
@ -59,7 +60,7 @@ describe('EventDispatcher - dispatchEvent', () => {
|
|||
await wait(1050);
|
||||
|
||||
// @ts-ignore
|
||||
const queue = eventDispatcher.queue;
|
||||
const { queue } = eventDispatcher;
|
||||
|
||||
expect(queue.length).toBe(0);
|
||||
});
|
||||
|
@ -72,7 +73,7 @@ describe('EventDispatcher - dispatchEvent', () => {
|
|||
await wait(1050);
|
||||
|
||||
// @ts-ignore
|
||||
const queue = eventDispatcher.queue;
|
||||
const { queue } = eventDispatcher;
|
||||
|
||||
expect(queue.length).toBe(0);
|
||||
});
|
||||
|
@ -161,7 +162,7 @@ describe('EventDispatcher - clearEvent', () => {
|
|||
eventDispatcher.clearEvent(event);
|
||||
|
||||
// @ts-ignore
|
||||
const queue = eventDispatcher.queue;
|
||||
const { queue } = eventDispatcher;
|
||||
|
||||
expect(queue.length).toBe(0);
|
||||
});
|
||||
|
@ -174,7 +175,7 @@ describe('EventDispatcher - pollQueue', () => {
|
|||
// @ts-ignore
|
||||
const id = eventDispatcher.pollQueue();
|
||||
// @ts-ignore
|
||||
const interval = eventDispatcher.interval;
|
||||
const { interval } = eventDispatcher;
|
||||
|
||||
expect(interval).toBe(123);
|
||||
expect(id).toBe(123);
|
||||
|
@ -192,7 +193,7 @@ describe('EventDispatcher - collectLockStatusAndClean', () => {
|
|||
eventDispatcher.collectLockStatusAndClean();
|
||||
|
||||
// @ts-ignore
|
||||
const lock = eventDispatcher.lock;
|
||||
const { lock } = eventDispatcher;
|
||||
|
||||
expect(lock).toBeNull();
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import cron from 'node-cron';
|
||||
import { getConfig } from '../../config/TipiConfig';
|
||||
import startJobs from '../jobs';
|
||||
import { eventDispatcher, EventTypes } from '../../../core/config/EventDispatcher';
|
||||
import { eventDispatcher, EventTypes } from '../../config/EventDispatcher';
|
||||
|
||||
jest.mock('node-cron');
|
||||
jest.mock('child_process');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import cron from 'node-cron';
|
||||
import logger from '../../config/logger/logger';
|
||||
import { getConfig } from '../../core/config/TipiConfig';
|
||||
import { getConfig } from '../config/TipiConfig';
|
||||
import { eventDispatcher, EventTypes } from '../config/EventDispatcher';
|
||||
|
||||
const startJobs = () => {
|
||||
|
|
|
@ -30,9 +30,7 @@ afterAll(async () => {
|
|||
await teardownConnection(TEST_SUITE);
|
||||
});
|
||||
|
||||
const createState = (apps: string[]) => {
|
||||
return JSON.stringify({ installed: apps.join(' ') });
|
||||
};
|
||||
const createState = (apps: string[]) => JSON.stringify({ installed: apps.join(' ') });
|
||||
|
||||
describe('No state/apps.json', () => {
|
||||
it('Should do nothing and create the update with status SUCCES', async () => {
|
||||
|
|
|
@ -1,9 +1,21 @@
|
|||
import { DataSource } from 'typeorm';
|
||||
import { BaseEntity, DataSource, DeepPartial } from 'typeorm';
|
||||
import logger from '../../config/logger/logger';
|
||||
import App from '../../modules/apps/app.entity';
|
||||
import User from '../../modules/auth/user.entity';
|
||||
import Update from '../../modules/system/update.entity';
|
||||
|
||||
const createUser = async (user: DeepPartial<BaseEntity>): Promise<void> => {
|
||||
await User.create(user).save();
|
||||
};
|
||||
|
||||
const createApp = async (app: DeepPartial<BaseEntity>): Promise<void> => {
|
||||
await App.create(app).save();
|
||||
};
|
||||
|
||||
const createUpdate = async (update: DeepPartial<BaseEntity>): Promise<void> => {
|
||||
await Update.create(update).save();
|
||||
};
|
||||
|
||||
const recover = async (datasource: DataSource) => {
|
||||
logger.info('Recovering broken database');
|
||||
|
||||
|
@ -18,20 +30,14 @@ const recover = async (datasource: DataSource) => {
|
|||
logger.info('running migrations');
|
||||
await datasource.runMigrations();
|
||||
|
||||
// create users
|
||||
for (const user of users) {
|
||||
await User.create(user).save();
|
||||
}
|
||||
// recreate users
|
||||
await Promise.all(users.map(createUser));
|
||||
|
||||
// create apps
|
||||
for (const app of apps) {
|
||||
await App.create(app).save();
|
||||
}
|
||||
await Promise.all(apps.map(createApp));
|
||||
|
||||
// create updates
|
||||
for (const update of updates) {
|
||||
await Update.create(update).save();
|
||||
}
|
||||
await Promise.all(updates.map(createUpdate));
|
||||
|
||||
logger.info(`Users recovered ${users.length}`);
|
||||
logger.info(`Apps recovered ${apps.length}`);
|
||||
|
|
|
@ -10,6 +10,41 @@ type AppsState = { installed: string };
|
|||
|
||||
const UPDATE_NAME = 'v040';
|
||||
|
||||
const migrateApp = async (appId: string): Promise<void> => {
|
||||
const app = await App.findOne({ where: { id: appId } });
|
||||
|
||||
if (!app) {
|
||||
const envFile = readFile(`/app/storage/app-data/${appId}/app.env`).toString();
|
||||
const envVars = envFile.split('\n');
|
||||
const envVarsMap = new Map<string, string>();
|
||||
|
||||
envVars.forEach((envVar) => {
|
||||
const [key, value] = envVar.split('=');
|
||||
envVarsMap.set(key, value);
|
||||
});
|
||||
|
||||
const form: Record<string, string> = {};
|
||||
|
||||
const configFile: AppInfo | null = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appId}/config.json`);
|
||||
configFile?.form_fields?.forEach((field) => {
|
||||
const envVar = field.env_variable;
|
||||
const envVarValue = envVarsMap.get(envVar);
|
||||
|
||||
if (envVarValue) {
|
||||
form[field.env_variable] = envVarValue;
|
||||
}
|
||||
});
|
||||
|
||||
await App.create({ id: appId, status: AppStatusEnum.STOPPED, config: form }).save();
|
||||
} else {
|
||||
logger.info('App already migrated');
|
||||
}
|
||||
};
|
||||
|
||||
const migrateUser = async (user: { email: string; password: string }): Promise<void> => {
|
||||
await User.create({ username: user.email.trim().toLowerCase(), password: user.password }).save();
|
||||
};
|
||||
|
||||
export const updateV040 = async (): Promise<void> => {
|
||||
try {
|
||||
const update = await Update.findOne({ where: { name: UPDATE_NAME } });
|
||||
|
@ -24,36 +59,7 @@ export const updateV040 = async (): Promise<void> => {
|
|||
const state: AppsState = await readJsonFile('/runtipi/state/apps.json');
|
||||
const installed: string[] = state.installed.split(' ').filter(Boolean);
|
||||
|
||||
for (const appId of installed) {
|
||||
const app = await App.findOne({ where: { id: appId } });
|
||||
|
||||
if (!app) {
|
||||
const envFile = readFile(`/app/storage/app-data/${appId}/app.env`).toString();
|
||||
const envVars = envFile.split('\n');
|
||||
const envVarsMap = new Map<string, string>();
|
||||
|
||||
envVars.forEach((envVar) => {
|
||||
const [key, value] = envVar.split('=');
|
||||
envVarsMap.set(key, value);
|
||||
});
|
||||
|
||||
const form: Record<string, string> = {};
|
||||
|
||||
const configFile: AppInfo | null = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appId}/config.json`);
|
||||
configFile?.form_fields?.forEach((field) => {
|
||||
const envVar = field.env_variable;
|
||||
const envVarValue = envVarsMap.get(envVar);
|
||||
|
||||
if (envVarValue) {
|
||||
form[field.env_variable] = envVarValue;
|
||||
}
|
||||
});
|
||||
|
||||
await App.create({ id: appId, status: AppStatusEnum.STOPPED, config: form }).save();
|
||||
} else {
|
||||
logger.info('App already migrated');
|
||||
}
|
||||
}
|
||||
await Promise.all(installed.map((appId) => migrateApp(appId)));
|
||||
deleteFolder('/runtipi/state/apps.json');
|
||||
}
|
||||
|
||||
|
@ -61,9 +67,7 @@ export const updateV040 = async (): Promise<void> => {
|
|||
if (fileExists('/state/users.json')) {
|
||||
const state: { email: string; password: string }[] = await readJsonFile('/runtipi/state/users.json');
|
||||
|
||||
for (const user of state) {
|
||||
await User.create({ username: user.email.trim().toLowerCase(), password: user.password }).save();
|
||||
}
|
||||
await Promise.all(state.map((user) => migrateUser(user)));
|
||||
deleteFolder('/runtipi/state/users.json');
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ const createApp = async (props: IProps) => {
|
|||
};
|
||||
}
|
||||
|
||||
let MockFiles: any = {};
|
||||
const MockFiles: any = {};
|
||||
MockFiles['/runtipi/.env'] = 'TEST=test';
|
||||
MockFiles['/runtipi/repos/repo-id'] = '';
|
||||
MockFiles[`/runtipi/repos/repo-id/apps/${appInfo.id}/config.json`] = JSON.stringify(appInfo);
|
||||
|
@ -71,6 +71,7 @@ const createApp = async (props: IProps) => {
|
|||
status,
|
||||
exposed,
|
||||
domain,
|
||||
version: 1,
|
||||
}).save();
|
||||
|
||||
MockFiles[`/app/storage/app-data/${appInfo.id}`] = '';
|
||||
|
|
|
@ -4,7 +4,7 @@ import { DataSource } from 'typeorm';
|
|||
import logger from '../../../config/logger/logger';
|
||||
import { setupConnection, teardownConnection } from '../../../test/connection';
|
||||
import App from '../app.entity';
|
||||
import { checkAppRequirements, checkEnvFile, generateEnvFile, getAppInfo, getAvailableApps, getEnvMap, getUpdateInfo } from '../apps.helpers';
|
||||
import { checkAppRequirements, checkEnvFile, ensureAppFolder, generateEnvFile, getAppInfo, getAvailableApps, getEnvMap, getUpdateInfo } from '../apps.helpers';
|
||||
import { AppInfo } from '../apps.types';
|
||||
import { createApp } from './apps.factory';
|
||||
|
||||
|
@ -336,15 +336,89 @@ describe('getUpdateInfo', () => {
|
|||
});
|
||||
|
||||
it('Should return update info', async () => {
|
||||
const updateInfo = await getUpdateInfo(app1.id);
|
||||
const updateInfo = await getUpdateInfo(app1.id, 1);
|
||||
|
||||
expect(updateInfo?.latest).toBe(app1.tipi_version);
|
||||
expect(updateInfo?.current).toBe(1);
|
||||
});
|
||||
|
||||
it('Should return null if app is not installed', async () => {
|
||||
const updateInfo = await getUpdateInfo(faker.random.word());
|
||||
const updateInfo = await getUpdateInfo(faker.random.word(), 1);
|
||||
|
||||
expect(updateInfo).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: ensureAppFolder', () => {
|
||||
beforeEach(() => {
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/repo-id/apps/test`]: ['test.yml'],
|
||||
};
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
});
|
||||
|
||||
it('should copy the folder from repo', () => {
|
||||
// Act
|
||||
ensureAppFolder('test');
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual(['test.yml']);
|
||||
});
|
||||
|
||||
it('should not copy the folder if it already exists', () => {
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/repo-id/apps/test`]: ['test.yml'],
|
||||
'/runtipi/apps/test': ['docker-compose.yml'],
|
||||
'/runtipi/apps/test/docker-compose.yml': 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
||||
// Act
|
||||
ensureAppFolder('test');
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual(['docker-compose.yml']);
|
||||
});
|
||||
|
||||
it('Should overwrite the folder if clean up is true', () => {
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/repo-id/apps/test`]: ['test.yml'],
|
||||
'/runtipi/apps/test': ['docker-compose.yml'],
|
||||
'/runtipi/apps/test/docker-compose.yml': 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
||||
// Act
|
||||
ensureAppFolder('test', true);
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual(['test.yml']);
|
||||
});
|
||||
|
||||
it('Should delete folder if it exists but has no docker-compose.yml file', () => {
|
||||
// Arrange
|
||||
const randomFileName = `${faker.random.word()}.yml`;
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/repo-id/apps/test`]: [randomFileName],
|
||||
'/runtipi/apps/test': ['test.yml'],
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
||||
// Act
|
||||
ensureAppFolder('test');
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual([randomFileName]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { DataSource } from 'typeorm';
|
||||
import { setupConnection, teardownConnection } from '../../../test/connection';
|
||||
import fs from 'fs-extra';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { setupConnection, teardownConnection } from '../../../test/connection';
|
||||
import { gcall } from '../../../test/gcall';
|
||||
import App from '../app.entity';
|
||||
import { getAppQuery, InstalledAppsQuery, listAppInfosQuery } from '../../../test/queries';
|
||||
|
@ -9,7 +10,6 @@ import { AppInfo, AppStatusEnum, ListAppsResonse } from '../apps.types';
|
|||
import { createUser } from '../../auth/__tests__/user.factory';
|
||||
import User from '../../auth/user.entity';
|
||||
import { installAppMutation, startAppMutation, stopAppMutation, uninstallAppMutation, updateAppConfigMutation, updateAppMutation } from '../../../test/mutations';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import EventDispatcher from '../../../core/config/EventDispatcher';
|
||||
|
||||
jest.mock('fs');
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import AppsService from '../apps.service';
|
||||
import fs from 'fs-extra';
|
||||
import { DataSource } from 'typeorm';
|
||||
import AppsService from '../apps.service';
|
||||
import { AppInfo, AppStatusEnum, AppSupportedArchitecturesEnum } from '../apps.types';
|
||||
import App from '../app.entity';
|
||||
import { createApp } from './apps.factory';
|
||||
import { setupConnection, teardownConnection } from '../../../test/connection';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { getEnvMap } from '../apps.helpers';
|
||||
import EventDispatcher, { eventDispatcher, EventTypes } from '../../../core/config/EventDispatcher';
|
||||
import { setConfig } from '../../../core/config/TipiConfig';
|
||||
|
@ -56,9 +56,9 @@ describe('Install app', () => {
|
|||
const app = await App.findOne({ where: { id: app1.id } });
|
||||
|
||||
expect(app).toBeDefined();
|
||||
expect(app!.id).toBe(app1.id);
|
||||
expect(app!.config).toStrictEqual({ TEST_FIELD: 'test' });
|
||||
expect(app!.status).toBe(AppStatusEnum.RUNNING);
|
||||
expect(app?.id).toBe(app1.id);
|
||||
expect(app?.config).toStrictEqual({ TEST_FIELD: 'test' });
|
||||
expect(app?.status).toBe(AppStatusEnum.RUNNING);
|
||||
});
|
||||
|
||||
it('Should start app if already installed', async () => {
|
||||
|
@ -147,7 +147,7 @@ describe('Install app', () => {
|
|||
const app2 = await createApp({ exposable: true });
|
||||
const app3 = await createApp({ exposable: true });
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(Object.assign({}, app2.MockFiles, app3.MockFiles));
|
||||
fs.__createMockFiles({ ...app2.MockFiles, ...app3.MockFiles });
|
||||
|
||||
await AppsService.installApp(app2.appInfo.id, { TEST_FIELD: 'test' }, true, 'test.com');
|
||||
|
||||
|
@ -203,8 +203,8 @@ describe('Uninstall app', () => {
|
|||
|
||||
// Assert
|
||||
expect(app).toBeDefined();
|
||||
expect(app!.id).toBe(app1.id);
|
||||
expect(app!.status).toBe(AppStatusEnum.RUNNING);
|
||||
expect(app?.id).toBe(app1.id);
|
||||
expect(app?.status).toBe(AppStatusEnum.RUNNING);
|
||||
});
|
||||
|
||||
it('Should correctly remove app from database', async () => {
|
||||
|
@ -244,7 +244,7 @@ describe('Uninstall app', () => {
|
|||
// Act & Assert
|
||||
await expect(AppsService.uninstallApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to uninstall\nstdout: test`);
|
||||
const app = await App.findOne({ where: { id: app1.id } });
|
||||
expect(app!.status).toBe(AppStatusEnum.STOPPED);
|
||||
expect(app?.status).toBe(AppStatusEnum.STOPPED);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -300,7 +300,7 @@ describe('Start app', () => {
|
|||
// Act & Assert
|
||||
await expect(AppsService.startApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to start\nstdout: test`);
|
||||
const app = await App.findOne({ where: { id: app1.id } });
|
||||
expect(app!.status).toBe(AppStatusEnum.STOPPED);
|
||||
expect(app?.status).toBe(AppStatusEnum.STOPPED);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -333,7 +333,7 @@ describe('Stop app', () => {
|
|||
// Act & Assert
|
||||
await expect(AppsService.stopApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to stop\nstdout: test`);
|
||||
const app = await App.findOne({ where: { id: app1.id } });
|
||||
expect(app!.status).toBe(AppStatusEnum.RUNNING);
|
||||
expect(app?.status).toBe(AppStatusEnum.RUNNING);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -378,17 +378,13 @@ describe('Update app config', () => {
|
|||
expect(envMap.get('RANDOM_FIELD')).toBe('test');
|
||||
});
|
||||
|
||||
it('Should throw if app is exposed and domain is not provided', () => {
|
||||
return expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true)).rejects.toThrowError('Domain is required');
|
||||
});
|
||||
it('Should throw if app is exposed and domain is not provided', () => expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true)).rejects.toThrowError('Domain is required'));
|
||||
|
||||
it('Should throw if app is exposed and domain is not valid', () => {
|
||||
return expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true, 'test')).rejects.toThrowError('Domain test is not valid');
|
||||
});
|
||||
it('Should throw if app is exposed and domain is not valid', () =>
|
||||
expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true, 'test')).rejects.toThrowError('Domain test is not valid'));
|
||||
|
||||
it('Should throw if app is exposed and config does not allow it', () => {
|
||||
return expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true, 'test.com')).rejects.toThrowError(`App ${app1.id} is not exposable`);
|
||||
});
|
||||
it('Should throw if app is exposed and config does not allow it', () =>
|
||||
expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true, 'test.com')).rejects.toThrowError(`App ${app1.id} is not exposable`));
|
||||
|
||||
it('Should throw if app is exposed and domain is already used', async () => {
|
||||
const app2 = await createApp({ exposable: true, installed: true });
|
||||
|
|
|
@ -70,7 +70,7 @@ class App extends BaseEntity {
|
|||
|
||||
@Field(() => UpdateInfo, { nullable: true })
|
||||
updateInfo(): Promise<UpdateInfo | null> {
|
||||
return getUpdateInfo(this.id);
|
||||
return getUpdateInfo(this.id, this.version);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
6
packages/system-api/src/modules/apps/app.types.ts
Normal file
6
packages/system-api/src/modules/apps/app.types.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export interface AppEntityType {
|
||||
id: string;
|
||||
config: Record<string, string>;
|
||||
exposed: boolean;
|
||||
domain?: string;
|
||||
}
|
|
@ -1,14 +1,12 @@
|
|||
import { fileExists, getSeed, readdirSync, readFile, readJsonFile, writeFile } from '../fs/fs.helpers';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs-extra';
|
||||
import { deleteFolder, fileExists, getSeed, readdirSync, readFile, readJsonFile, writeFile } from '../fs/fs.helpers';
|
||||
import { AppInfo, AppStatusEnum } from './apps.types';
|
||||
import logger from '../../config/logger/logger';
|
||||
import App from './app.entity';
|
||||
import { getConfig } from '../../core/config/TipiConfig';
|
||||
import fs from 'fs-extra';
|
||||
import { AppEntityType } from './app.types';
|
||||
|
||||
export const checkAppRequirements = async (appName: string) => {
|
||||
let valid = true;
|
||||
|
||||
const configFile: AppInfo | null = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appName}/config.json`);
|
||||
|
||||
if (!configFile) {
|
||||
|
@ -19,7 +17,7 @@ export const checkAppRequirements = async (appName: string) => {
|
|||
throw new Error(`App ${appName} is not supported on this architecture`);
|
||||
}
|
||||
|
||||
return valid;
|
||||
return true;
|
||||
};
|
||||
|
||||
export const getEnvMap = (appName: string): Map<string, string> => {
|
||||
|
@ -55,7 +53,7 @@ const getEntropy = (name: string, length: number) => {
|
|||
return hash.digest('hex').substring(0, length);
|
||||
};
|
||||
|
||||
export const generateEnvFile = (app: App) => {
|
||||
export const generateEnvFile = (app: AppEntityType) => {
|
||||
const configFile: AppInfo | null = readJsonFile(`/runtipi/apps/${app.id}/config.json`);
|
||||
|
||||
if (!configFile) {
|
||||
|
@ -129,7 +127,8 @@ export const getAppInfo = (id: string, status?: AppStatusEnum): AppInfo | null =
|
|||
const configFile: AppInfo = readJsonFile(`/runtipi/apps/${id}/config.json`);
|
||||
configFile.description = readFile(`/runtipi/apps/${id}/metadata/description.md`).toString();
|
||||
return configFile;
|
||||
} else if (fileExists(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`)) {
|
||||
}
|
||||
if (fileExists(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`)) {
|
||||
const configFile: AppInfo = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`);
|
||||
configFile.description = readFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/metadata/description.md`);
|
||||
|
||||
|
@ -145,20 +144,32 @@ export const getAppInfo = (id: string, status?: AppStatusEnum): AppInfo | null =
|
|||
}
|
||||
};
|
||||
|
||||
export const getUpdateInfo = async (id: string) => {
|
||||
const app = await App.findOne({ where: { id } });
|
||||
|
||||
export const getUpdateInfo = async (id: string, version: number) => {
|
||||
const doesFileExist = fileExists(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}`);
|
||||
|
||||
if (!app || !doesFileExist) {
|
||||
if (!doesFileExist) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const repoConfig: AppInfo = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`);
|
||||
|
||||
return {
|
||||
current: app.version,
|
||||
current: version,
|
||||
latest: repoConfig.tipi_version,
|
||||
dockerVersion: repoConfig.version,
|
||||
};
|
||||
};
|
||||
|
||||
export const ensureAppFolder = (appName: string, cleanup = false) => {
|
||||
if (cleanup && fileExists(`/runtipi/apps/${appName}`)) {
|
||||
deleteFolder(`/runtipi/apps/${appName}`);
|
||||
}
|
||||
|
||||
if (!fileExists(`/runtipi/apps/${appName}/docker-compose.yml`)) {
|
||||
if (fileExists(`/runtipi/apps/${appName}`)) {
|
||||
deleteFolder(`/runtipi/apps/${appName}`);
|
||||
}
|
||||
// Copy from apps repo
|
||||
fs.copySync(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appName}`, `/runtipi/apps/${appName}`);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import validator from 'validator';
|
||||
import { createFolder, ensureAppFolder, readFile, readJsonFile } from '../fs/fs.helpers';
|
||||
import { checkAppRequirements, checkEnvFile, generateEnvFile, getAvailableApps } from './apps.helpers';
|
||||
import { Not } from 'typeorm';
|
||||
import { createFolder, readFile, readJsonFile } from '../fs/fs.helpers';
|
||||
import { checkAppRequirements, checkEnvFile, generateEnvFile, getAvailableApps, ensureAppFolder } from './apps.helpers';
|
||||
import { AppInfo, AppStatusEnum, ListAppsResonse } from './apps.types';
|
||||
import App from './app.entity';
|
||||
import logger from '../../config/logger/logger';
|
||||
import { Not } from 'typeorm';
|
||||
import { getConfig } from '../../core/config/TipiConfig';
|
||||
import { eventDispatcher, EventTypes } from '../../core/config/EventDispatcher';
|
||||
|
||||
|
@ -18,9 +18,7 @@ const filterApp = (app: AppInfo): boolean => {
|
|||
return app.supported_architectures.includes(arch);
|
||||
};
|
||||
|
||||
const filterApps = (apps: AppInfo[]): AppInfo[] => {
|
||||
return apps.sort(sortApps).filter(filterApp);
|
||||
};
|
||||
const filterApps = (apps: AppInfo[]): AppInfo[] => apps.sort(sortApps).filter(filterApp);
|
||||
|
||||
/**
|
||||
* Start all apps which had the status RUNNING in the database
|
||||
|
@ -157,11 +155,7 @@ const installApp = async (id: string, form: Record<string, string>, exposed?: bo
|
|||
const listApps = async (): Promise<ListAppsResonse> => {
|
||||
const folders: string[] = await getAvailableApps();
|
||||
|
||||
const apps: AppInfo[] = folders
|
||||
.map((app) => {
|
||||
return readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app}/config.json`);
|
||||
})
|
||||
.filter(Boolean);
|
||||
const apps: AppInfo[] = folders.map((app) => readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app}/config.json`)).filter(Boolean);
|
||||
|
||||
const filteredApps = filterApps(apps).map((app) => {
|
||||
const description = readFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app.id}/metadata/description.md`);
|
||||
|
@ -254,7 +248,7 @@ const stopApp = async (id: string): Promise<App> => {
|
|||
* @returns - the app entity
|
||||
*/
|
||||
const uninstallApp = async (id: string): Promise<App> => {
|
||||
let app = await App.findOne({ where: { id } });
|
||||
const app = await App.findOne({ where: { id } });
|
||||
|
||||
if (!app) {
|
||||
throw new Error(`App ${id} not found`);
|
||||
|
|
|
@ -7,7 +7,7 @@ import { setupConnection, teardownConnection } from '../../../test/connection';
|
|||
import { gcall } from '../../../test/gcall';
|
||||
import { loginMutation, registerMutation } from '../../../test/mutations';
|
||||
import { isConfiguredQuery, MeQuery, refreshTokenQuery } from '../../../test/queries';
|
||||
import User from '../../auth/user.entity';
|
||||
import User from '../user.entity';
|
||||
import { TokenResponse } from '../auth.types';
|
||||
import { createUser } from './user.factory';
|
||||
|
||||
|
@ -214,7 +214,7 @@ describe('Test: refreshToken', () => {
|
|||
const { data } = await gcall<{ refreshToken: TokenResponse }>({
|
||||
source: refreshTokenQuery,
|
||||
userId: user1.id,
|
||||
session: session,
|
||||
session,
|
||||
});
|
||||
const decoded = jwt.verify(data?.refreshToken?.token || '', getConfig().jwtSecret) as jwt.JwtPayload;
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import * as argon2 from 'argon2';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { DataSource } from 'typeorm';
|
||||
import AuthService from '../auth.service';
|
||||
import { createUser } from './user.factory';
|
||||
import User from '../user.entity';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { setupConnection, teardownConnection } from '../../../test/connection';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { setConfig } from '../../../core/config/TipiConfig';
|
||||
import TipiCache from '../../../config/TipiCache';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import User from '../user.entity';
|
||||
import * as argon2 from 'argon2';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import User from '../user.entity';
|
||||
|
||||
const createUser = async (email?: string) => {
|
||||
const hash = await argon2.hash('password');
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Field, InputType, ObjectType } from 'type-graphql';
|
||||
import User from './user.entity';
|
||||
|
||||
@InputType()
|
||||
class UsernamePasswordInput {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { readJsonFile, readFile, readdirSync, fileExists, writeFile, createFolder, deleteFolder, getSeed, ensureAppFolder } from '../fs.helpers';
|
||||
import fs from 'fs-extra';
|
||||
import { getConfig } from '../../../core/config/TipiConfig';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { readJsonFile, readFile, readdirSync, fileExists, writeFile, createFolder, deleteFolder, getSeed } from '../fs.helpers';
|
||||
|
||||
jest.mock('fs-extra');
|
||||
|
||||
|
@ -15,7 +13,7 @@ describe('Test: readJsonFile', () => {
|
|||
// Arrange
|
||||
const rawFile = '{"test": "test"}';
|
||||
const mockFiles = {
|
||||
['/runtipi/test-file.json']: rawFile,
|
||||
'/runtipi/test-file.json': rawFile,
|
||||
};
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
@ -52,7 +50,7 @@ describe('Test: readFile', () => {
|
|||
it('should return the file', () => {
|
||||
const rawFile = 'test';
|
||||
const mockFiles = {
|
||||
['/runtipi/test-file.txt']: rawFile,
|
||||
'/runtipi/test-file.txt': rawFile,
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -69,7 +67,7 @@ describe('Test: readFile', () => {
|
|||
describe('Test: readdirSync', () => {
|
||||
it('should return the files', () => {
|
||||
const mockFiles = {
|
||||
['/runtipi/test/test-file.txt']: 'test',
|
||||
'/runtipi/test/test-file.txt': 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -86,7 +84,7 @@ describe('Test: readdirSync', () => {
|
|||
describe('Test: fileExists', () => {
|
||||
it('should return true if the file exists', () => {
|
||||
const mockFiles = {
|
||||
['/runtipi/test-file.txt']: 'test',
|
||||
'/runtipi/test-file.txt': 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -133,7 +131,7 @@ describe('Test: deleteFolder', () => {
|
|||
describe('Test: getSeed', () => {
|
||||
it('should return the seed', () => {
|
||||
const mockFiles = {
|
||||
['/runtipi/state/seed']: 'test',
|
||||
'/runtipi/state/seed': 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -142,77 +140,3 @@ describe('Test: getSeed', () => {
|
|||
expect(getSeed()).toEqual('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: ensureAppFolder', () => {
|
||||
beforeEach(() => {
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/${getConfig().appsRepoId}/apps/test`]: ['test.yml'],
|
||||
};
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
});
|
||||
|
||||
it('should copy the folder from repo', () => {
|
||||
// Act
|
||||
ensureAppFolder('test');
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual(['test.yml']);
|
||||
});
|
||||
|
||||
it('should not copy the folder if it already exists', () => {
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/${getConfig().appsRepoId}/apps/test`]: ['test.yml'],
|
||||
['/runtipi/apps/test']: ['docker-compose.yml'],
|
||||
['/runtipi/apps/test/docker-compose.yml']: 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
||||
// Act
|
||||
ensureAppFolder('test');
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual(['docker-compose.yml']);
|
||||
});
|
||||
|
||||
it('Should overwrite the folder if clean up is true', () => {
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/${getConfig().appsRepoId}/apps/test`]: ['test.yml'],
|
||||
['/runtipi/apps/test']: ['docker-compose.yml'],
|
||||
['/runtipi/apps/test/docker-compose.yml']: 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
||||
// Act
|
||||
ensureAppFolder('test', true);
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual(['test.yml']);
|
||||
});
|
||||
|
||||
it('Should delete folder if it exists but has no docker-compose.yml file', () => {
|
||||
// Arrange
|
||||
const randomFileName = `${faker.random.word()}.yml`;
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/${getConfig().appsRepoId}/apps/test`]: [randomFileName],
|
||||
['/runtipi/apps/test']: ['test.yml'],
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
||||
// Act
|
||||
ensureAppFolder('test');
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual([randomFileName]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import fs from 'fs-extra';
|
||||
import { getConfig } from '../../core/config/TipiConfig';
|
||||
|
||||
export const readJsonFile = (path: string): any => {
|
||||
try {
|
||||
|
@ -36,17 +35,3 @@ export const getSeed = () => {
|
|||
const seed = readFile('/runtipi/state/seed');
|
||||
return seed.toString();
|
||||
};
|
||||
|
||||
export const ensureAppFolder = (appName: string, cleanup = false) => {
|
||||
if (cleanup && fileExists(`/runtipi/apps/${appName}`)) {
|
||||
deleteFolder(`/runtipi/apps/${appName}`);
|
||||
}
|
||||
|
||||
if (!fileExists(`/runtipi/apps/${appName}/docker-compose.yml`)) {
|
||||
if (fileExists(`/runtipi/apps/${appName}`)) {
|
||||
deleteFolder(`/runtipi/apps/${appName}`);
|
||||
}
|
||||
// Copy from apps repo
|
||||
fs.copySync(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appName}`, `/runtipi/apps/${appName}`);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import fs from 'fs-extra';
|
||||
import semver from 'semver';
|
||||
import axios from 'axios';
|
||||
import SystemService from '../system.service';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import SystemService from '../system.service';
|
||||
import TipiCache from '../../../config/TipiCache';
|
||||
import { setConfig } from '../../../core/config/TipiConfig';
|
||||
import logger from '../../../config/logger/logger';
|
||||
|
|
|
@ -2,9 +2,11 @@ import 'reflect-metadata';
|
|||
import express from 'express';
|
||||
import { ApolloServerPluginLandingPageGraphQLPlayground as Playground } from 'apollo-server-core';
|
||||
import { ApolloServer } from 'apollo-server-express';
|
||||
import { createServer } from 'http';
|
||||
import { ZodError } from 'zod';
|
||||
import cors from 'cors';
|
||||
import { createSchema } from './schema';
|
||||
import { ApolloLogs } from './config/logger/apollo.logger';
|
||||
import { createServer } from 'http';
|
||||
import logger from './config/logger/logger';
|
||||
import getSessionMiddleware from './core/middlewares/sessionMiddleware';
|
||||
import { MyContext } from './types';
|
||||
|
@ -15,10 +17,8 @@ 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 { ZodError } from 'zod';
|
||||
import systemController from './modules/system/system.controller';
|
||||
import { eventDispatcher, EventTypes } from './core/config/EventDispatcher';
|
||||
import cors from 'cors';
|
||||
|
||||
const applyCustomConfig = () => {
|
||||
try {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { DataSource } from 'typeorm';
|
||||
import pg from 'pg';
|
||||
import App from '../modules/apps/app.entity';
|
||||
import User from '../modules/auth/user.entity';
|
||||
import pg from 'pg';
|
||||
import Update from '../modules/system/update.entity';
|
||||
|
||||
const HOST = 'localhost';
|
||||
|
|
Loading…
Add table
Reference in a new issue