diff --git a/src/commands/add-cert.ts b/src/commands/add-cert.ts index da02f2d..0537099 100644 --- a/src/commands/add-cert.ts +++ b/src/commands/add-cert.ts @@ -1,9 +1,9 @@ import { Command } from '@oclif/core'; import { exec } from 'child_process'; -import { ConfigService } from '../services/config.service'; import os from 'node:os'; import path from 'node:path'; import { CLIUtils } from '../utils/cli.utils'; +import { WEBDAV_SSL_CERTS_DIR } from '../constants/configs'; export default class AddCert extends Command { static readonly args = {}; @@ -14,7 +14,7 @@ export default class AddCert extends Command { static readonly enableJsonFlag = true; public run = async () => { - const certPath = path.join(ConfigService.WEBDAV_SSL_CERTS_DIR, 'cert.crt'); + const certPath = path.join(WEBDAV_SSL_CERTS_DIR, 'cert.crt'); const platform = os.platform(); const scriptBasePath = path.join(__dirname, '../../scripts'); diff --git a/src/commands/logs.ts b/src/commands/logs.ts index fd7b020..e8ec94e 100644 --- a/src/commands/logs.ts +++ b/src/commands/logs.ts @@ -1,6 +1,6 @@ import { Command } from '@oclif/core'; -import { ConfigService } from '../services/config.service'; import { CLIUtils } from '../utils/cli.utils'; +import { INTERNXT_CLI_LOGS_DIR } from '../constants/configs'; export default class Logs extends Command { static readonly args = {}; @@ -11,9 +11,9 @@ export default class Logs extends Command { static readonly enableJsonFlag = true; public run = async () => { - const message = `Internxt CLI logs are located at ${ConfigService.INTERNXT_CLI_LOGS_DIR}`; + const message = `Internxt CLI logs are located at ${INTERNXT_CLI_LOGS_DIR}`; CLIUtils.log(this.log.bind(this), message); - return { success: true, message, path: ConfigService.INTERNXT_CLI_LOGS_DIR }; + return { success: true, message, path: INTERNXT_CLI_LOGS_DIR }; }; public catch = async (error: Error) => { diff --git a/src/constants/configs.ts b/src/constants/configs.ts new file mode 100644 index 0000000..a66061a --- /dev/null +++ b/src/constants/configs.ts @@ -0,0 +1,15 @@ +import path from 'node:path'; +import os from 'node:os'; + +export const INTERNXT_CLI_DATA_DIR = path.join(os.homedir(), '.internxt-cli'); +export const INTERNXT_CLI_LOGS_DIR = path.join(INTERNXT_CLI_DATA_DIR, 'logs'); +export const INTERNXT_TMP_DIR = os.tmpdir(); +export const CREDENTIALS_FILE = path.join(INTERNXT_CLI_DATA_DIR, '.inxtcli'); +export const DRIVE_SQLITE_FILE = path.join(INTERNXT_CLI_DATA_DIR, 'internxt-cli-drive.sqlite'); +export const WEBDAV_SSL_CERTS_DIR = path.join(INTERNXT_CLI_DATA_DIR, 'certs'); +export const WEBDAV_CONFIGS_FILE = path.join(INTERNXT_CLI_DATA_DIR, 'config.webdav.inxt'); +export const WEBDAV_DEFAULT_HOST = '127.0.0.1'; +export const WEBDAV_DEFAULT_PORT = '3005'; +export const WEBDAV_DEFAULT_PROTOCOL = 'https'; +export const WEBDAV_DEFAULT_TIMEOUT = 0; +export const WEBDAV_DEFAULT_CREATE_FULL_PATH = true; diff --git a/src/services/config.service.ts b/src/services/config.service.ts index d3d7ce5..136e583 100644 --- a/src/services/config.service.ts +++ b/src/services/config.service.ts @@ -1,24 +1,22 @@ -import path from 'node:path'; -import os from 'node:os'; import fs from 'node:fs/promises'; import { ConfigKeys } from '../types/config.types'; import { LoginCredentials, WebdavConfig } from '../types/command.types'; import { CryptoService } from './crypto.service'; import { isFileNotFoundError } from '../utils/errors.utils'; +import { + CREDENTIALS_FILE, + INTERNXT_CLI_DATA_DIR, + INTERNXT_CLI_LOGS_DIR, + WEBDAV_CONFIGS_FILE, + WEBDAV_DEFAULT_CREATE_FULL_PATH, + WEBDAV_DEFAULT_HOST, + WEBDAV_DEFAULT_PORT, + WEBDAV_DEFAULT_PROTOCOL, + WEBDAV_DEFAULT_TIMEOUT, + WEBDAV_SSL_CERTS_DIR, +} from '../constants/configs'; export class ConfigService { - static readonly INTERNXT_CLI_DATA_DIR = path.join(os.homedir(), '.internxt-cli'); - static readonly INTERNXT_CLI_LOGS_DIR = path.join(this.INTERNXT_CLI_DATA_DIR, 'logs'); - static readonly INTERNXT_TMP_DIR = os.tmpdir(); - static readonly CREDENTIALS_FILE = path.join(this.INTERNXT_CLI_DATA_DIR, '.inxtcli'); - static readonly DRIVE_SQLITE_FILE = path.join(this.INTERNXT_CLI_DATA_DIR, 'internxt-cli-drive.sqlite'); - static readonly WEBDAV_SSL_CERTS_DIR = path.join(this.INTERNXT_CLI_DATA_DIR, 'certs'); - static readonly WEBDAV_CONFIGS_FILE = path.join(this.INTERNXT_CLI_DATA_DIR, 'config.webdav.inxt'); - static readonly WEBDAV_DEFAULT_HOST = '127.0.0.1'; - static readonly WEBDAV_DEFAULT_PORT = '3005'; - static readonly WEBDAV_DEFAULT_PROTOCOL = 'https'; - static readonly WEBDAV_DEFAULT_TIMEOUT = 0; - static readonly WEBDAV_DEFAULT_CREATE_FULL_PATH = true; public static readonly instance: ConfigService = new ConfigService(); /** @@ -42,7 +40,7 @@ export class ConfigService { await this.ensureInternxtCliDataDirExists(); const credentialsString = JSON.stringify(loginCredentials); const encryptedCredentials = CryptoService.instance.encryptText(credentialsString); - await fs.writeFile(ConfigService.CREDENTIALS_FILE, encryptedCredentials, 'utf8'); + await fs.writeFile(CREDENTIALS_FILE, encryptedCredentials, 'utf8'); }; /** @@ -51,9 +49,9 @@ export class ConfigService { **/ public clearUser = async (): Promise => { try { - const stat = await fs.stat(ConfigService.CREDENTIALS_FILE); + const stat = await fs.stat(CREDENTIALS_FILE); if (stat.size === 0) return; - await fs.writeFile(ConfigService.CREDENTIALS_FILE, '', 'utf8'); + await fs.writeFile(CREDENTIALS_FILE, '', 'utf8'); } catch (error) { if (!isFileNotFoundError(error)) { throw error; @@ -67,7 +65,7 @@ export class ConfigService { **/ public readUser = async (): Promise => { try { - const encryptedCredentials = await fs.readFile(ConfigService.CREDENTIALS_FILE, 'utf8'); + const encryptedCredentials = await fs.readFile(CREDENTIALS_FILE, 'utf8'); const credentialsString = CryptoService.instance.decryptText(encryptedCredentials); const loginCredentials = JSON.parse(credentialsString) as LoginCredentials; return loginCredentials; @@ -79,52 +77,52 @@ export class ConfigService { public saveWebdavConfig = async (webdavConfig: WebdavConfig): Promise => { await this.ensureInternxtCliDataDirExists(); const configs = JSON.stringify(webdavConfig); - await fs.writeFile(ConfigService.WEBDAV_CONFIGS_FILE, configs, 'utf8'); + await fs.writeFile(WEBDAV_CONFIGS_FILE, configs, 'utf8'); }; public readWebdavConfig = async (): Promise => { try { - const configsData = await fs.readFile(ConfigService.WEBDAV_CONFIGS_FILE, 'utf8'); + const configsData = await fs.readFile(WEBDAV_CONFIGS_FILE, 'utf8'); const configs = JSON.parse(configsData); return { - host: configs?.host ?? ConfigService.WEBDAV_DEFAULT_HOST, - port: configs?.port ?? ConfigService.WEBDAV_DEFAULT_PORT, - protocol: configs?.protocol ?? ConfigService.WEBDAV_DEFAULT_PROTOCOL, - timeoutMinutes: configs?.timeoutMinutes ?? ConfigService.WEBDAV_DEFAULT_TIMEOUT, - createFullPath: configs?.createFullPath ?? ConfigService.WEBDAV_DEFAULT_CREATE_FULL_PATH, + host: configs?.host ?? WEBDAV_DEFAULT_HOST, + port: configs?.port ?? WEBDAV_DEFAULT_PORT, + protocol: configs?.protocol ?? WEBDAV_DEFAULT_PROTOCOL, + timeoutMinutes: configs?.timeoutMinutes ?? WEBDAV_DEFAULT_TIMEOUT, + createFullPath: configs?.createFullPath ?? WEBDAV_DEFAULT_CREATE_FULL_PATH, }; } catch { return { - host: ConfigService.WEBDAV_DEFAULT_HOST, - port: ConfigService.WEBDAV_DEFAULT_PORT, - protocol: ConfigService.WEBDAV_DEFAULT_PROTOCOL, - timeoutMinutes: ConfigService.WEBDAV_DEFAULT_TIMEOUT, - createFullPath: ConfigService.WEBDAV_DEFAULT_CREATE_FULL_PATH, + host: WEBDAV_DEFAULT_HOST, + port: WEBDAV_DEFAULT_PORT, + protocol: WEBDAV_DEFAULT_PROTOCOL, + timeoutMinutes: WEBDAV_DEFAULT_TIMEOUT, + createFullPath: WEBDAV_DEFAULT_CREATE_FULL_PATH, }; } }; ensureInternxtCliDataDirExists = async () => { try { - await fs.access(ConfigService.INTERNXT_CLI_DATA_DIR); + await fs.access(INTERNXT_CLI_DATA_DIR); } catch { - await fs.mkdir(ConfigService.INTERNXT_CLI_DATA_DIR); + await fs.mkdir(INTERNXT_CLI_DATA_DIR); } }; ensureWebdavCertsDirExists = async () => { try { - await fs.access(ConfigService.WEBDAV_SSL_CERTS_DIR); + await fs.access(WEBDAV_SSL_CERTS_DIR); } catch { - await fs.mkdir(ConfigService.WEBDAV_SSL_CERTS_DIR); + await fs.mkdir(WEBDAV_SSL_CERTS_DIR); } }; ensureInternxtLogsDirExists = async () => { try { - await fs.access(ConfigService.INTERNXT_CLI_LOGS_DIR); + await fs.access(INTERNXT_CLI_LOGS_DIR); } catch { - await fs.mkdir(ConfigService.INTERNXT_CLI_LOGS_DIR); + await fs.mkdir(INTERNXT_CLI_LOGS_DIR); } }; } diff --git a/src/utils/logger.utils.ts b/src/utils/logger.utils.ts index 78f249a..47cc98b 100644 --- a/src/utils/logger.utils.ts +++ b/src/utils/logger.utils.ts @@ -1,5 +1,5 @@ import winston from 'winston'; -import { ConfigService } from '../services/config.service'; +import { INTERNXT_CLI_LOGS_DIR } from '../constants/configs'; const maxLogSize = 40 * 1024 * 1024; const maxLogsFiles = 5; @@ -12,14 +12,14 @@ export const logger = winston.createLogger({ new winston.transports.File({ filename: 'internxt-cli-error.log', level: 'error', - dirname: ConfigService.INTERNXT_CLI_LOGS_DIR, + dirname: INTERNXT_CLI_LOGS_DIR, maxsize: maxLogSize, maxFiles: maxLogsFiles, tailable: true, }), new winston.transports.File({ filename: 'internxt-cli-combined.log', - dirname: ConfigService.INTERNXT_CLI_LOGS_DIR, + dirname: INTERNXT_CLI_LOGS_DIR, maxsize: maxLogSize, maxFiles: maxLogsFiles, tailable: true, @@ -35,14 +35,14 @@ export const webdavLogger = winston.createLogger({ new winston.transports.File({ filename: 'internxt-webdav-error.log', level: 'error', - dirname: ConfigService.INTERNXT_CLI_LOGS_DIR, + dirname: INTERNXT_CLI_LOGS_DIR, maxsize: maxLogSize, maxFiles: maxLogsFiles, tailable: true, }), new winston.transports.File({ filename: 'internxt-webdav-combined.log', - dirname: ConfigService.INTERNXT_CLI_LOGS_DIR, + dirname: INTERNXT_CLI_LOGS_DIR, maxsize: maxLogSize, maxFiles: maxLogsFiles, tailable: true, diff --git a/src/utils/network.utils.ts b/src/utils/network.utils.ts index 054d8a8..0edcc76 100644 --- a/src/utils/network.utils.ts +++ b/src/utils/network.utils.ts @@ -4,8 +4,8 @@ import { readFile, stat, writeFile } from 'node:fs/promises'; import path from 'node:path'; import selfsigned from 'selfsigned'; import parseRange from 'range-parser'; -import { ConfigService } from '../services/config.service'; import { WebdavConfig } from '../types/command.types'; +import { WEBDAV_SSL_CERTS_DIR } from '../constants/configs'; export class NetworkUtils { static getAuthFromCredentials(creds: NetworkCredentials): { username: string; password: string } { @@ -16,8 +16,8 @@ export class NetworkUtils { } static readonly WEBDAV_SSL_CERTS_PATH = { - cert: path.join(ConfigService.WEBDAV_SSL_CERTS_DIR, 'cert.crt'), - privateKey: path.join(ConfigService.WEBDAV_SSL_CERTS_DIR, 'priv.key'), + cert: path.join(WEBDAV_SSL_CERTS_DIR, 'cert.crt'), + privateKey: path.join(WEBDAV_SSL_CERTS_DIR, 'priv.key'), }; static async generateNewSelfsignedCerts(configs: WebdavConfig): Promise { diff --git a/test/services/config.service.test.ts b/test/services/config.service.test.ts index c3b64a8..170c303 100644 --- a/test/services/config.service.test.ts +++ b/test/services/config.service.test.ts @@ -6,6 +6,16 @@ import { CryptoService } from '../../src/services/crypto.service'; import { LoginCredentials, WebdavConfig } from '../../src/types/command.types'; import { UserCredentialsFixture } from '../fixtures/login.fixture'; import { fail } from 'node:assert'; +import { + CREDENTIALS_FILE, + WEBDAV_CONFIGS_FILE, + WEBDAV_DEFAULT_CREATE_FULL_PATH, + WEBDAV_DEFAULT_HOST, + WEBDAV_DEFAULT_PORT, + WEBDAV_DEFAULT_PROTOCOL, + WEBDAV_DEFAULT_TIMEOUT, + WEBDAV_SSL_CERTS_DIR, +} from '../../src/constants/configs'; const env = Object.assign({}, process.env); @@ -47,7 +57,7 @@ describe('Config service', () => { await ConfigService.instance.saveUser(userCredentials); expect(configServiceStub).toHaveBeenCalledWith(stringCredentials); - expect(fsStub).toHaveBeenCalledWith(ConfigService.CREDENTIALS_FILE, encryptedUserCredentials, 'utf8'); + expect(fsStub).toHaveBeenCalledWith(CREDENTIALS_FILE, encryptedUserCredentials, 'utf8'); }); it('When user credentials are read, then they are read and decrypted from a file', async () => { @@ -60,7 +70,7 @@ describe('Config service', () => { const loginCredentials = await ConfigService.instance.readUser(); expect(userCredentials).to.be.deep.equal(loginCredentials); - expect(fsStub).toHaveBeenCalledWith(ConfigService.CREDENTIALS_FILE, 'utf8'); + expect(fsStub).toHaveBeenCalledWith(CREDENTIALS_FILE, 'utf8'); expect(configServiceStub).toHaveBeenCalledWith(encryptedUserCredentials); }); @@ -70,7 +80,7 @@ describe('Config service', () => { const loginCredentials = await ConfigService.instance.readUser(); expect(loginCredentials).to.be.equal(undefined); - expect(fsStub).toHaveBeenCalledWith(ConfigService.CREDENTIALS_FILE, 'utf8'); + expect(fsStub).toHaveBeenCalledWith(CREDENTIALS_FILE, 'utf8'); expect(configServiceStub).not.toHaveBeenCalled(); }); @@ -83,11 +93,11 @@ describe('Config service', () => { .mockResolvedValue({ size: BigInt(crypto.randomInt(1, 100000)) }); await ConfigService.instance.clearUser(); - const credentialsFileContent = await fs.readFile(ConfigService.CREDENTIALS_FILE, 'utf8'); + const credentialsFileContent = await fs.readFile(CREDENTIALS_FILE, 'utf8'); - expect(writeFileStub).toHaveBeenCalledWith(ConfigService.CREDENTIALS_FILE, '', 'utf8'); - expect(readFileStub).toHaveBeenCalledWith(ConfigService.CREDENTIALS_FILE, 'utf8'); - expect(existFileStub).toHaveBeenCalledWith(ConfigService.CREDENTIALS_FILE); + expect(writeFileStub).toHaveBeenCalledWith(CREDENTIALS_FILE, '', 'utf8'); + expect(readFileStub).toHaveBeenCalledWith(CREDENTIALS_FILE, 'utf8'); + expect(existFileStub).toHaveBeenCalledWith(CREDENTIALS_FILE); expect(credentialsFileContent).to.be.equal(''); }); @@ -100,7 +110,7 @@ describe('Config service', () => { await ConfigService.instance.clearUser(); - expect(statStub).toHaveBeenCalledWith(ConfigService.CREDENTIALS_FILE); + expect(statStub).toHaveBeenCalledWith(CREDENTIALS_FILE); expect(writeFileStub).not.toHaveBeenCalled(); }); @@ -113,7 +123,7 @@ describe('Config service', () => { await ConfigService.instance.clearUser(); - expect(statStub).toHaveBeenCalledWith(ConfigService.CREDENTIALS_FILE); + expect(statStub).toHaveBeenCalledWith(CREDENTIALS_FILE); expect(writeFileStub).not.toHaveBeenCalled(); }); @@ -124,7 +134,7 @@ describe('Config service', () => { await ConfigService.instance.ensureWebdavCertsDirExists(); - expect(stubMkdir).toHaveBeenCalledWith(ConfigService.WEBDAV_SSL_CERTS_DIR); + expect(stubMkdir).toHaveBeenCalledWith(WEBDAV_SSL_CERTS_DIR); }); it('When webdav config options are saved, then they are written to a file', async () => { @@ -140,7 +150,7 @@ describe('Config service', () => { const fsStub = vi.spyOn(fs, 'writeFile').mockResolvedValue(); await ConfigService.instance.saveWebdavConfig(webdavConfig); - expect(fsStub).toHaveBeenCalledWith(ConfigService.WEBDAV_CONFIGS_FILE, stringConfig, 'utf8'); + expect(fsStub).toHaveBeenCalledWith(WEBDAV_CONFIGS_FILE, stringConfig, 'utf8'); }); it('When webdav config options are read and exist, then they are read from a file', async () => { @@ -157,39 +167,39 @@ describe('Config service', () => { const webdavConfigResult = await ConfigService.instance.readWebdavConfig(); expect(webdavConfigResult).to.be.deep.equal(webdavConfig); - expect(fsStub).toHaveBeenCalledWith(ConfigService.WEBDAV_CONFIGS_FILE, 'utf8'); + expect(fsStub).toHaveBeenCalledWith(WEBDAV_CONFIGS_FILE, 'utf8'); }); it('When webdav config options are read but not exist, then they are returned from defaults', async () => { const defaultWebdavConfig: WebdavConfig = { - host: ConfigService.WEBDAV_DEFAULT_HOST, - port: ConfigService.WEBDAV_DEFAULT_PORT, - protocol: ConfigService.WEBDAV_DEFAULT_PROTOCOL, - timeoutMinutes: ConfigService.WEBDAV_DEFAULT_TIMEOUT, - createFullPath: ConfigService.WEBDAV_DEFAULT_CREATE_FULL_PATH, + host: WEBDAV_DEFAULT_HOST, + port: WEBDAV_DEFAULT_PORT, + protocol: WEBDAV_DEFAULT_PROTOCOL, + timeoutMinutes: WEBDAV_DEFAULT_TIMEOUT, + createFullPath: WEBDAV_DEFAULT_CREATE_FULL_PATH, }; const fsStub = vi.spyOn(fs, 'readFile').mockResolvedValue(''); const webdavConfigResult = await ConfigService.instance.readWebdavConfig(); expect(webdavConfigResult).to.be.deep.equal(defaultWebdavConfig); - expect(fsStub).toHaveBeenCalledWith(ConfigService.WEBDAV_CONFIGS_FILE, 'utf8'); + expect(fsStub).toHaveBeenCalledWith(WEBDAV_CONFIGS_FILE, 'utf8'); }); it('When webdav config options are read but an error is thrown, then they are returned from defaults', async () => { const defaultWebdavConfig: WebdavConfig = { - host: ConfigService.WEBDAV_DEFAULT_HOST, - port: ConfigService.WEBDAV_DEFAULT_PORT, - protocol: ConfigService.WEBDAV_DEFAULT_PROTOCOL, - timeoutMinutes: ConfigService.WEBDAV_DEFAULT_TIMEOUT, - createFullPath: ConfigService.WEBDAV_DEFAULT_CREATE_FULL_PATH, + host: WEBDAV_DEFAULT_HOST, + port: WEBDAV_DEFAULT_PORT, + protocol: WEBDAV_DEFAULT_PROTOCOL, + timeoutMinutes: WEBDAV_DEFAULT_TIMEOUT, + createFullPath: WEBDAV_DEFAULT_CREATE_FULL_PATH, }; const fsStub = vi.spyOn(fs, 'readFile').mockRejectedValue(new Error()); const webdavConfigResult = await ConfigService.instance.readWebdavConfig(); expect(webdavConfigResult).to.be.deep.equal(defaultWebdavConfig); - expect(fsStub).toHaveBeenCalledWith(ConfigService.WEBDAV_CONFIGS_FILE, 'utf8'); + expect(fsStub).toHaveBeenCalledWith(WEBDAV_CONFIGS_FILE, 'utf8'); }); it('should default to true when webdav config exists but createFullPath property is missing', async () => { @@ -207,7 +217,7 @@ describe('Config service', () => { expect(webdavConfigResult.createFullPath).to.be.equal(true); expect(webdavConfigResult.host).to.be.equal(partialWebdavConfig.host); expect(webdavConfigResult.port).to.be.equal(partialWebdavConfig.port); - expect(fsStub).toHaveBeenCalledWith(ConfigService.WEBDAV_CONFIGS_FILE, 'utf8'); + expect(fsStub).toHaveBeenCalledWith(WEBDAV_CONFIGS_FILE, 'utf8'); }); it('shoud return false when webdav config has createFullPath explicitly set to false', async () => { @@ -224,7 +234,7 @@ describe('Config service', () => { const webdavConfigResult = await ConfigService.instance.readWebdavConfig(); expect(webdavConfigResult.createFullPath).to.be.equal(false); - expect(fsStub).toHaveBeenCalledWith(ConfigService.WEBDAV_CONFIGS_FILE, 'utf8'); + expect(fsStub).toHaveBeenCalledWith(WEBDAV_CONFIGS_FILE, 'utf8'); }); it('should return true when webdav config has createFullPath explicitly set to true', async () => { @@ -241,6 +251,6 @@ describe('Config service', () => { const webdavConfigResult = await ConfigService.instance.readWebdavConfig(); expect(webdavConfigResult.createFullPath).to.be.equal(true); - expect(fsStub).toHaveBeenCalledWith(ConfigService.WEBDAV_CONFIGS_FILE, 'utf8'); + expect(fsStub).toHaveBeenCalledWith(WEBDAV_CONFIGS_FILE, 'utf8'); }); });