diff --git a/src/adapters/abstract.ts b/src/adapters/abstract.ts index d4b351a..5b3d10f 100644 --- a/src/adapters/abstract.ts +++ b/src/adapters/abstract.ts @@ -12,10 +12,9 @@ export abstract class Adapter { } #validateRequiredFields(config: Partial): C { - for(const field of this.requiredFields) { - if (!config[field]) { - throw new Error(`Required config field '${field.toString()}' is missing in ${this.name} profile`); - } + const missingField = this.requiredFields.find(field => !config.hasOwnProperty(field)); + if (missingField) { + throw new Error(`The required '${missingField.toString()}' is missing in '${this.name}' source config`); } return config as C; diff --git a/src/cli/index.ts b/src/cli/index.ts index 29cbde9..60b978e 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -1,6 +1,6 @@ import { program } from "commander"; -import { CLIOptions } from "@types"; +import { CLIOptions, ImportOptions } from "@types"; import { parseConfig } from "../config"; import { importThings } from "../importer"; @@ -10,6 +10,9 @@ const getOptions = () => program .requiredOption("-c, --config ", "sets the path to the YAML file with configuration") .option("-x, --set ", "overrides the config option for this specific run (arg: =, i.e. mqtt.brokerURL=mqtt://localhost:1883", (v: string, prev: string[]) => prev.concat([v]), []) .option("-s, --sources [source...]", "forces the given sources to be queried for new things") + .option("-f, --force", "overrides the existing things with new schemas", false) + .option("-n, --dry-run", "disables all mutations against OpenHAB (data creating, updating or deletion), only data retrieval will be allowed", false) + .option("-j, --json", "prints things to be imported as a JSON", false) .parse() .opts(); @@ -36,6 +39,9 @@ export const run = async () => { }); } - const things = await importThings(config, options.sources ?? []); - console.log(things); + const things = await importThings(config, options); + + if(options.json) { + console.log(JSON.stringify(things, undefined, 3)); + } } \ No newline at end of file diff --git a/src/config/index.ts b/src/config/index.ts index 4a5fbb8..845394f 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -2,4 +2,13 @@ import { Config } from '@types'; import { readFileSync } from 'fs'; import { parse } from 'yaml'; -export const parseConfig = (filePath: string) => parse(readFileSync(filePath, 'utf8')) as Config; +export const parseConfig = (filePath: string) => { + const config = parse(readFileSync(filePath, 'utf8')) as Partial; + const missingField = ['openHAB', 'sources'].find(field => !config.hasOwnProperty(field)); + + if (missingField) { + throw new Error(`The required '${missingField}' is missing in global config`); + } + + return config as Config; +}; diff --git a/src/importer/index.ts b/src/importer/index.ts index 97ca08c..a32991c 100644 --- a/src/importer/index.ts +++ b/src/importer/index.ts @@ -1,15 +1,13 @@ -import axios from "axios"; -import { Thing, Config } from "@types"; +import { Config, ImportOptions } from "@types"; import { adapters } from "../adapters"; +import { DummyOpenHAB, OpenHAB } from "../openhab"; -export const importThings = async (config: Config, sources: string[]) => { - const openhab = axios.create({ - baseURL: config.baseURL, - headers: { - Authorization: `Bearer ${config.token}` - } - }); +export const importThings = async (config: Config, options: ImportOptions) => { + const OH = options.dryRun ? DummyOpenHAB : OpenHAB; + const openhab = new OH(config.openHAB); + + const sources = options.sources ?? Object.keys(config.sources); const things = (await Promise.all(sources.map(source => { const { type, config: cfg } = config.sources[source]; @@ -23,17 +21,13 @@ export const importThings = async (config: Config, sources: string[]) => { return new constructor(source, cfg).loadThings(); }))).flat(); - if (config.override) { - // things.forEach(t => openhab.delete(`/things/${t.UID}`)); + if (options.force) { + await openhab.deleteThings(...things.map(t => t.UID)); } - // const getThingsResponse = await openhab.get('/things'); - // const existingThingsUIDs = getThingsResponse.data.map(t => t.UID); - - // const thingsToImport = things.filter(t => !existingThingsUIDs.includes(t.UID)); + const existingThings = await openhab.getThings(); + const existingThingsUIDs = existingThings.map(t => t.UID); + const thingsToImport = things.filter(t => !existingThingsUIDs.includes(t.UID)); - // openhab.post('/things', thingsToImport); - - // return thingsToImport; - return things; + return thingsToImport; } \ No newline at end of file diff --git a/src/openhab/index.ts b/src/openhab/index.ts new file mode 100644 index 0000000..8a87128 --- /dev/null +++ b/src/openhab/index.ts @@ -0,0 +1,42 @@ +import { OpenHABConfig, Thing } from "@types"; +import axios, { AxiosInstance } from "axios"; + +export class OpenHAB { + #api: AxiosInstance; + + constructor({ baseURL, token }: Partial) { + if (!baseURL || !token) { + throw new Error(`Both 'baseURL' and 'token' properties are required on OpenHAB configuration.`); + } + + this.#api = axios.create({ + baseURL: `${baseURL}/rest`, + headers: { + Authorization: `Bearer ${token}` + } + }); + } + + async getThings(): Promise { + const response = await this.#api.get('/things'); + return response.data; + } + + async deleteThings(...UIDs: string[]): Promise { + await Promise.all(UIDs.map(uid => this.#api.delete(`/things/${uid}`))); + } + + async createThings(...things: Thing[]): Promise { + const response = await this.#api.post('/things'); + return response.data; + } +} + +export class DummyOpenHAB extends OpenHAB { + override async createThings(...things: Thing[]): Promise { + return []; + } + + override async deleteThings(...UIDs: string[]): Promise { + } +} \ No newline at end of file diff --git a/src/types/cli.ts b/src/types/cli.ts index 81a868b..bc6bd0d 100644 --- a/src/types/cli.ts +++ b/src/types/cli.ts @@ -1,5 +1,7 @@ -export type CLIOptions = { +import { ImportOptions } from "./importer"; + +export type CLIOptions = ImportOptions & { config: string; set: string[]; - sources?: string[]; + json: boolean; }; \ No newline at end of file diff --git a/src/types/config.ts b/src/types/config.ts index 2d3ea11..0db90ff 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -1,8 +1,10 @@ -export type Config = { - baseURL: string; - token: string; +import { OpenHABConfig } from "./openhab"; + +export type Config = { override?: boolean; + openHAB: OpenHABConfig; + sources: Record; }; diff --git a/src/types/importer.ts b/src/types/importer.ts new file mode 100644 index 0000000..159d78b --- /dev/null +++ b/src/types/importer.ts @@ -0,0 +1,5 @@ +export type ImportOptions = { + force: boolean; + dryRun: boolean; + sources?: string[]; +}; \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 8f9116e..c791403 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,5 @@ export * from "./openhab"; export * from "./z2m"; export * from "./config"; -export * from "./cli"; \ No newline at end of file +export * from "./cli"; +export * from "./importer"; \ No newline at end of file diff --git a/src/types/openhab.ts b/src/types/openhab.ts index e31694d..57eea43 100644 --- a/src/types/openhab.ts +++ b/src/types/openhab.ts @@ -1,3 +1,8 @@ +export type OpenHABConfig = { + baseURL: string; + token: string; +}; + export type Thing = { label: string; bridgeUID: string;