From 0ef1c1b9136e67033f43d7ee62b4d007f33e297c Mon Sep 17 00:00:00 2001 From: Isac Alves <70695712+isacna@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:08:42 -0300 Subject: [PATCH] ci: add GitHub Actions for build and npm publish --- .github/workflows/ci.yml | 28 +++++++ .github/workflows/publish-npm.yml | 35 ++++++++ README.md | 126 ++++++++++++++++++++++------- dist/@types/types.d.ts | 20 ++++- dist/index.d.ts | 84 ++----------------- dist/index.js | 96 ++++------------------ dist/service/auth.d.ts | 2 +- dist/service/auth.js | 15 ++-- dist/service/concatenatedFilter.js | 32 +++----- dist/service/create.d.ts | 2 + dist/service/create.js | 5 ++ dist/service/delete.d.ts | 2 + dist/service/delete.js | 5 ++ dist/service/list.js | 32 +++----- dist/service/read.d.ts | 2 + dist/service/read.js | 5 ++ dist/service/request.d.ts | 12 +++ dist/service/request.js | 41 ++++++++++ dist/service/update.d.ts | 2 + dist/service/update.js | 5 ++ package-lock.json | 4 +- package.json | 4 +- src/@types/types.ts | 26 ++++-- src/index.ts | 121 ++++++++------------------- src/service/auth.ts | 17 ++-- src/service/concatenatedFilter.ts | 63 +++++++-------- src/service/create.ts | 12 +++ src/service/delete.ts | 12 +++ src/service/list.ts | 39 ++++----- src/service/read.ts | 12 +++ src/service/request.ts | 63 +++++++++++++++ src/service/update.ts | 12 +++ 32 files changed, 543 insertions(+), 393 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/publish-npm.yml create mode 100644 dist/service/create.d.ts create mode 100644 dist/service/create.js create mode 100644 dist/service/delete.d.ts create mode 100644 dist/service/delete.js create mode 100644 dist/service/read.d.ts create mode 100644 dist/service/read.js create mode 100644 dist/service/request.d.ts create mode 100644 dist/service/request.js create mode 100644 dist/service/update.d.ts create mode 100644 dist/service/update.js create mode 100644 src/service/create.ts create mode 100644 src/service/delete.ts create mode 100644 src/service/read.ts create mode 100644 src/service/request.ts create mode 100644 src/service/update.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cef50e5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + - master + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build package + run: npm run build diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 0000000..006b749 --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,35 @@ +name: Deploy and Publish to npmjs + +on: + push: + tags: + - 'v*.*.*' + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + registry-url: https://registry.npmjs.org + + - name: Install dependencies + run: npm ci + + - name: Build package + run: npm run build + + - name: Publish to npmjs + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/README.md b/README.md index 49608d4..d0d5c07 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,110 @@ # API IXC Soft -Este módulo permite que você faz consultas no seu ERP de forma automática, passando apenas alguns parametros de forma simples. +SDK em TypeScript para consumir o WebService do IXC Soft e facilitar publicação/consumo via NPM. -## Uso - -Instalar pacote usando NPM +## Instalação ```bash npm install api-ixc-soft ``` -Instancie a class e exporte. +## Configuração -```javascript -import IXC from "api-ixc-soft" +```ts +import IXC from "api-ixc-soft"; export const ixc = new IXC({ - url: `https://HOST.com.br`, - credentials: {token: `Basic ******`} or {username: "123", password: "41837b8eb82f1e60e148823a..."} - }) -``` - -Exemplo request: - -```javascript - -const row = async () => { - const result = await ixc.list("cliente", { - qtype: "id", - query: "123", - oper: "=", - page: "1", - }); - return result; -}; -row().then((item) => console.log(item)); -``` \ No newline at end of file + url: "https://HOST.com.br", + credentials: { token: "Basic ******" }, + // ou + // credentials: { username: "123", password: "41837b8eb82f1e60e148823a..." } +}); +``` + +## Métodos disponíveis + +### `list(table, body)` + +Consulta registros com `ixcsoft: listar`. + +```ts +const result = await ixc.list("cliente", { + qtype: "id", + query: "123", + oper: "=", + page: "1", + rp: "20", +}); +``` + +### `listFilter(table, filter)` + +Consulta com filtros concatenados (`grid_param`). + +```ts +const result = await ixc.listFilter("su_oss_chamado", [ + { TB: "status", OP: "=", P: "F", C: "AND", G: "status" }, + { TB: "setor", OP: "=", P: "1", C: "AND", G: "setor" }, +]); +``` + +### `read(table, body)` + +Obtém um único registro com `ixcsoft: obter`. + +```ts +const result = await ixc.read("cliente", { id: "123" }); +``` + +### `create(table, body)` + +Insere registro com `ixcsoft: incluir`. + +```ts +const result = await ixc.create("cliente", { + razao: "Cliente Teste", + tipo_pessoa: "F", +}); +``` + +### `update(table, body)` + +Atualiza registro com `ixcsoft: alterar`. + +```ts +const result = await ixc.update("cliente", { + id: "123", + razao: "Cliente Atualizado", +}); +``` + +### `delete(table, body)` + +Remove registro com `ixcsoft: deletar`. + +```ts +const result = await ixc.delete("cliente", { id: "123" }); +``` + +## Observação + +Os endpoints/tabelas e payloads dependem da documentação oficial do IXC Soft: +https://wikiapiprovedor.ixcsoft.com.br/# + +## Publicação automática no npmjs (GitHub Actions) + +Foi adicionado workflow em `.github/workflows/publish-npm.yml`. + +### Como configurar + +1. No GitHub do repositório, configure o secret: + - `NPM_TOKEN`: token de automação do npmjs com permissão de publish. +2. Faça commit com versão atualizada no `package.json`. +3. Crie e envie uma tag semântica: + +```bash +git tag v1.1.1 +git push origin v1.1.1 +``` + +Ao subir a tag `v*.*.*`, o workflow executa `npm ci`, `npm run build` e `npm publish`. diff --git a/dist/@types/types.d.ts b/dist/@types/types.d.ts index 32c1657..634dab9 100644 --- a/dist/@types/types.d.ts +++ b/dist/@types/types.d.ts @@ -1,13 +1,14 @@ interface Config { - url: String; - auth: String; + url: string; + auth: string; } interface Result { - registros: String[]; - total: Number; + registros: string[]; + total: number; } type FilterOperator = '=' | '>=' | '>' | '<=' | '<' | 'L' | '!='; type SortOrder = 'asc' | 'desc'; +export type IxcRequestOperation = 'listar' | 'obter' | 'incluir' | 'alterar' | 'deletar'; export interface BodyRaw { qtype: string; query?: string; @@ -41,6 +42,17 @@ export interface IxcListResponse { registros?: unknown[]; total?: number; } +export interface IxcReadResponse { + status?: string; + message?: string; + registro?: unknown; +} +export interface IxcMutationResponse { + status?: string; + type?: string; + message?: string; + data?: Record; +} export interface Return_Auth { token: string; } diff --git a/dist/index.d.ts b/dist/index.d.ts index ed49a98..327fcbb 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,30 +1,4 @@ -import { BodyRaw, FilterParams, IXC_Auth, IxcListResponse, Return_Auth } from "./@types/types.js"; -/** - * The `IXC` module provides a set of functions for list, update, create and delete - * - * @param url URL of the IXC API - * @param credentials {@link IXC_Auth} Object with the credentials to be used in the requests - * ```json - * { - * "username": "25", - * "password": "41837b8eb82f1e60e148823a..." - * } - * ``` - * OR - * ```json - * { - * "token": "Basic MTYzOjk3NmRmZjlkNGZkNjExODdhNzUyNWQ..." - * } - * ``` - * - * @example - * ```js - * const ixc = new IXC({ - * url: "https://assinante.nome.com.br", - * credentials: {token: "Basic ...."} OR {username: "25", password: "418732...."} - * }) - * ``` - */ +import { BodyRaw, FilterParams, IXC_Auth, IxcListResponse, IxcMutationResponse, IxcReadResponse, Return_Auth } from "./@types/types.js"; declare class IXC { url: string; authenticate: Return_Auth; @@ -32,59 +6,11 @@ declare class IXC { url: string; credentials: IXC_Auth; }); - /** - * The `IXC` module provides a set of functions for list, update, create and delete - * @see [source](https://wikiapiprovedor.ixcsoft.com.br/#) - */ - /** - * - * @param table {@link [tables](https://wikiapiprovedor.ixcsoft.com.br/#)} Path to the table to be queried - * @param body {@link BodyRaw} Object with the parameters to be sent to the API. Example: - * ```json - * { - * "qtype": "id", - * "query": "1", - * "oper": "=", - * "page": "1", - * "rp": "10", - * "sortname": "id", - * "sortorder": "asc", - * } - * ``` - * @returns {@link IxcListResponse} - */ list(table: string, body: BodyRaw): Promise; - /** - * - * @param table {@link [tables](https://wikiapiprovedor.ixcsoft.com.br/#)} Path to the table to be queried - * @param filter {@link FilterParams} Array of objects with the parameters to be sent to the API. Example: - * ```json - * [ - * { - * "TB": "setor", - * "OP": "=", - * "P": "1", - * "C": "AND", - * "G": "setor", - * }, - * { - * "TB": "status", - * "OP": "=", - * "P": "F", - * "C": "AND", - * "G": "status", - * } - * { - * "TB": "data_abertura", - * "OP": ">=", - * "P": "2023-12-01", - * "C": "AND", - * "G": "data_abertura", - * } - * ] - * ``` - * @returns {@link IxcListResponse} - */ listFilter(table: string, filter: FilterParams[]): Promise; + read(table: string, body: Record): Promise; + create(table: string, body: Record): Promise; + update(table: string, body: Record): Promise; + delete(table: string, body: Record): Promise; } export default IXC; diff --git a/dist/index.js b/dist/index.js index 536d0ff..a920c2f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,96 +1,32 @@ -// import fetch from "node-fetch" import { auth } from "./service/auth.js"; import { concatenatedFilter } from "./service/concatenatedFilter.js"; +import { createRecord } from "./service/create.js"; +import { deleteRecord } from "./service/delete.js"; import { listAll } from "./service/list.js"; -/** - * The `IXC` module provides a set of functions for list, update, create and delete - * - * @param url URL of the IXC API - * @param credentials {@link IXC_Auth} Object with the credentials to be used in the requests - * ```json - * { - * "username": "25", - * "password": "41837b8eb82f1e60e148823a..." - * } - * ``` - * OR - * ```json - * { - * "token": "Basic MTYzOjk3NmRmZjlkNGZkNjExODdhNzUyNWQ..." - * } - * ``` - * - * @example - * ```js - * const ixc = new IXC({ - * url: "https://assinante.nome.com.br", - * credentials: {token: "Basic ...."} OR {username: "25", password: "418732...."} - * }) - * ``` - */ +import { readRecord } from "./service/read.js"; +import { updateRecord } from "./service/update.js"; class IXC { constructor({ url, credentials }) { this.url = url; this.authenticate = auth(credentials); } - /** - * The `IXC` module provides a set of functions for list, update, create and delete - * @see [source](https://wikiapiprovedor.ixcsoft.com.br/#) - */ - /** - * - * @param table {@link [tables](https://wikiapiprovedor.ixcsoft.com.br/#)} Path to the table to be queried - * @param body {@link BodyRaw} Object with the parameters to be sent to the API. Example: - * ```json - * { - * "qtype": "id", - * "query": "1", - * "oper": "=", - * "page": "1", - * "rp": "10", - * "sortname": "id", - * "sortorder": "asc", - * } - * ``` - * @returns {@link IxcListResponse} - */ async list(table, body) { return await listAll(this.url, this.authenticate.token, table, body); } - ; - /** - * - * @param table {@link [tables](https://wikiapiprovedor.ixcsoft.com.br/#)} Path to the table to be queried - * @param filter {@link FilterParams} Array of objects with the parameters to be sent to the API. Example: - * ```json - * [ - * { - * "TB": "setor", - * "OP": "=", - * "P": "1", - * "C": "AND", - * "G": "setor", - * }, - * { - * "TB": "status", - * "OP": "=", - * "P": "F", - * "C": "AND", - * "G": "status", - * } - * { - * "TB": "data_abertura", - * "OP": ">=", - * "P": "2023-12-01", - * "C": "AND", - * "G": "data_abertura", - * } - * ] - * ``` - * @returns {@link IxcListResponse} - */ async listFilter(table, filter) { return await concatenatedFilter(this.url, this.authenticate.token, table, filter); } + async read(table, body) { + return await readRecord(this.url, this.authenticate.token, table, body); + } + async create(table, body) { + return await createRecord(this.url, this.authenticate.token, table, body); + } + async update(table, body) { + return await updateRecord(this.url, this.authenticate.token, table, body); + } + async delete(table, body) { + return await deleteRecord(this.url, this.authenticate.token, table, body); + } } export default IXC; diff --git a/dist/service/auth.d.ts b/dist/service/auth.d.ts index fea7ca8..1ddb42c 100644 --- a/dist/service/auth.d.ts +++ b/dist/service/auth.d.ts @@ -1,2 +1,2 @@ import { IXC_Auth, Return_Auth } from "../@types/types.js"; -export declare const auth: (auth: IXC_Auth) => Return_Auth; +export declare const auth: (credentials: IXC_Auth) => Return_Auth; diff --git a/dist/service/auth.js b/dist/service/auth.js index 8ea65ce..d1fa605 100644 --- a/dist/service/auth.js +++ b/dist/service/auth.js @@ -1,7 +1,10 @@ -export const auth = (auth) => { - if (auth.username && auth.password) - auth.token = Buffer.from(`Basic ${auth.username}:${auth.password}`).toString("base64"); - if (auth.token) - auth.token; - return { token: auth.token || "" }; +export const auth = (credentials) => { + if (credentials.username && credentials.password) { + const encoded = Buffer.from(`${credentials.username}:${credentials.password}`).toString("base64"); + return { token: `Basic ${encoded}` }; + } + if (credentials.token) { + return { token: credentials.token }; + } + return { token: "" }; }; diff --git a/dist/service/concatenatedFilter.js b/dist/service/concatenatedFilter.js index fd7afcc..878a226 100644 --- a/dist/service/concatenatedFilter.js +++ b/dist/service/concatenatedFilter.js @@ -1,35 +1,29 @@ -import fetch from "node-fetch"; +import { ixcRequest } from "./request.js"; export async function concatenatedFilter(url, token, table, filter) { const body = { rp: "100000", grid_param: JSON.stringify(filter), }; - const ixcRequest = await fetch(`${url}/webservice/v1/${table}`, { - method: "POST", - body: JSON.stringify(body), - headers: { - ixcsoft: "listar", - "Content-Type": "application/json", - Authorization: token, - }, + const response = await ixcRequest({ + url, + token, + table, + operation: "listar", + body, }); - if (ixcRequest.ok === false) { - throw Error(`Network response was not ok`); - } - const response = await ixcRequest.json(); - if (response.registros && response.registros.length > 0) { + if (Array.isArray(response.registros) && response.registros.length > 0) { return { status: "success", - total: response.total, - registros: response.registros + total: Number(response.total || 0), + registros: response.registros, }; } - if (response.total == 0) { + if (Number(response.total || 0) === 0) { return { status: "error", total: 0, - registros: [] + registros: [], }; } - throw Error(`Unrecognized response from IXC: ${JSON.stringify(response)}`); + throw new Error(`Unrecognized response from IXC: ${JSON.stringify(response)}`); } diff --git a/dist/service/create.d.ts b/dist/service/create.d.ts new file mode 100644 index 0000000..6a63044 --- /dev/null +++ b/dist/service/create.d.ts @@ -0,0 +1,2 @@ +import { IxcMutationResponse } from "../@types/types.js"; +export declare function createRecord(url: string, token: string, table: string, body: Record): Promise; diff --git a/dist/service/create.js b/dist/service/create.js new file mode 100644 index 0000000..5748d51 --- /dev/null +++ b/dist/service/create.js @@ -0,0 +1,5 @@ +import { ixcRequest, parseMutationResponse } from "./request.js"; +export async function createRecord(url, token, table, body) { + const response = await ixcRequest({ url, token, table, operation: "incluir", body }); + return parseMutationResponse(response); +} diff --git a/dist/service/delete.d.ts b/dist/service/delete.d.ts new file mode 100644 index 0000000..5b20eed --- /dev/null +++ b/dist/service/delete.d.ts @@ -0,0 +1,2 @@ +import { IxcMutationResponse } from "../@types/types.js"; +export declare function deleteRecord(url: string, token: string, table: string, body: Record): Promise; diff --git a/dist/service/delete.js b/dist/service/delete.js new file mode 100644 index 0000000..2e28428 --- /dev/null +++ b/dist/service/delete.js @@ -0,0 +1,5 @@ +import { ixcRequest, parseMutationResponse } from "./request.js"; +export async function deleteRecord(url, token, table, body) { + const response = await ixcRequest({ url, token, table, operation: "deletar", body }); + return parseMutationResponse(response); +} diff --git a/dist/service/list.js b/dist/service/list.js index 43d8718..3898c83 100644 --- a/dist/service/list.js +++ b/dist/service/list.js @@ -1,31 +1,25 @@ -import fetch from "node-fetch"; +import { ixcRequest } from "./request.js"; export async function listAll(url, token, table, body) { - const ixcRequest = await fetch(`${url}/webservice/v1/${table}`, { - method: "POST", - body: JSON.stringify(body), - headers: { - ixcsoft: "listar", - "Content-Type": "application/json", - Authorization: token, - }, + const response = await ixcRequest({ + url, + token, + table, + operation: "listar", + body, }); - if (ixcRequest.ok === false) { - throw Error(`Network response was not ok`); - } - const response = await ixcRequest.json(); - if (response.registros && response.registros.length > 0) { + if (Array.isArray(response.registros) && response.registros.length > 0) { return { status: "success", - total: response.total, - registros: response.registros + total: Number(response.total || 0), + registros: response.registros, }; } - if (response.total == 0) { + if (Number(response.total || 0) === 0) { return { status: "error", total: 0, - registros: [] + registros: [], }; } - throw Error(`Unrecognized response from IXC: ${JSON.stringify(response)}`); + throw new Error(`Unrecognized response from IXC: ${JSON.stringify(response)}`); } diff --git a/dist/service/read.d.ts b/dist/service/read.d.ts new file mode 100644 index 0000000..0ca3ae0 --- /dev/null +++ b/dist/service/read.d.ts @@ -0,0 +1,2 @@ +import { IxcReadResponse } from "../@types/types.js"; +export declare function readRecord(url: string, token: string, table: string, body: Record): Promise; diff --git a/dist/service/read.js b/dist/service/read.js new file mode 100644 index 0000000..91639fb --- /dev/null +++ b/dist/service/read.js @@ -0,0 +1,5 @@ +import { ixcRequest, parseReadResponse } from "./request.js"; +export async function readRecord(url, token, table, body) { + const response = await ixcRequest({ url, token, table, operation: "obter", body }); + return parseReadResponse(response); +} diff --git a/dist/service/request.d.ts b/dist/service/request.d.ts new file mode 100644 index 0000000..85e0916 --- /dev/null +++ b/dist/service/request.d.ts @@ -0,0 +1,12 @@ +import { IxcMutationResponse, IxcReadResponse, IxcRequestOperation } from "../@types/types.js"; +interface RequestOptions { + url: string; + token: string; + table: string; + operation: IxcRequestOperation; + body?: unknown; +} +export declare function ixcRequest({ url, token, table, operation, body, }: RequestOptions): Promise; +export declare function parseMutationResponse(response: unknown): IxcMutationResponse; +export declare function parseReadResponse(response: unknown): IxcReadResponse; +export {}; diff --git a/dist/service/request.js b/dist/service/request.js new file mode 100644 index 0000000..95c7b7f --- /dev/null +++ b/dist/service/request.js @@ -0,0 +1,41 @@ +import fetch from "node-fetch"; +export async function ixcRequest({ url, token, table, operation, body = {}, }) { + const response = await fetch(`${url}/webservice/v1/${table}`, { + method: "POST", + body: JSON.stringify(body), + headers: { + ixcsoft: operation, + "Content-Type": "application/json", + Authorization: token, + }, + }); + if (!response.ok) { + throw new Error(`IXC request failed with status ${response.status}`); + } + return (await response.json()); +} +export function parseMutationResponse(response) { + const typed = response; + return { + status: typeof typed.status === "string" ? typed.status : undefined, + type: typeof typed.type === "string" ? typed.type : undefined, + message: typeof typed.message === "string" + ? typed.message + : typeof typed.mensagem === "string" + ? typed.mensagem + : undefined, + data: typed, + }; +} +export function parseReadResponse(response) { + const typed = response; + return { + status: typeof typed.status === "string" ? typed.status : undefined, + message: typeof typed.message === "string" + ? typed.message + : typeof typed.mensagem === "string" + ? typed.mensagem + : undefined, + registro: typed.registro ?? typed, + }; +} diff --git a/dist/service/update.d.ts b/dist/service/update.d.ts new file mode 100644 index 0000000..81c85cd --- /dev/null +++ b/dist/service/update.d.ts @@ -0,0 +1,2 @@ +import { IxcMutationResponse } from "../@types/types.js"; +export declare function updateRecord(url: string, token: string, table: string, body: Record): Promise; diff --git a/dist/service/update.js b/dist/service/update.js new file mode 100644 index 0000000..525ef2c --- /dev/null +++ b/dist/service/update.js @@ -0,0 +1,5 @@ +import { ixcRequest, parseMutationResponse } from "./request.js"; +export async function updateRecord(url, token, table, body) { + const response = await ixcRequest({ url, token, table, operation: "alterar", body }); + return parseMutationResponse(response); +} diff --git a/package-lock.json b/package-lock.json index ff61d90..7f27545 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "api-ixc-soft", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "api-ixc-soft", - "version": "1.0.0", + "version": "1.1.0", "license": "ISC", "dependencies": { "node-fetch": "^2.6.9" diff --git a/package.json b/package.json index 82413b5..f95ba63 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "api-ixc-soft", - "version": "1.0.3", + "version": "1.1.0", "description": "", "main": "dist/index.js", - "types": "./dist/src/types.d.ts", + "types": "dist/index.d.ts", "type": "module", "scripts": { "dev": "ts-node ./src/index.ts", diff --git a/src/@types/types.ts b/src/@types/types.ts index 03e43a6..609d7c9 100644 --- a/src/@types/types.ts +++ b/src/@types/types.ts @@ -1,15 +1,18 @@ interface Config { - url: String, - auth: String + url: string, + auth: string } interface Result { - registros: String[], - total: Number, + registros: string[], + total: number, } + type FilterOperator = '=' | '>=' | '>' | '<=' | '<' | 'L' | '!=' type SortOrder = 'asc' | 'desc' +export type IxcRequestOperation = 'listar' | 'obter' | 'incluir' | 'alterar' | 'deletar'; + export interface BodyRaw { qtype: string, query?: string, @@ -48,6 +51,19 @@ export interface IxcListResponse { total?: number } +export interface IxcReadResponse { + status?: string; + message?: string; + registro?: unknown; +} + +export interface IxcMutationResponse { + status?: string; + type?: string; + message?: string; + data?: Record; +} + export interface Return_Auth { token: string; } @@ -59,4 +75,4 @@ export interface FilterParams { P: string C: ConditionOperator G: string -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 5fabc45..c945c42 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,105 +1,52 @@ -// import fetch from "node-fetch" - -import { BodyRaw, FilterParams, IXC_Auth, IxcListResponse, Return_Auth } from "./@types/types"; +import { + BodyRaw, + FilterParams, + IXC_Auth, + IxcListResponse, + IxcMutationResponse, + IxcReadResponse, + Return_Auth, +} from "./@types/types"; import { auth } from "./service/auth"; import { concatenatedFilter } from "./service/concatenatedFilter"; +import { createRecord } from "./service/create"; +import { deleteRecord } from "./service/delete"; import { listAll } from "./service/list"; +import { readRecord } from "./service/read"; +import { updateRecord } from "./service/update"; -/** - * The `IXC` module provides a set of functions for list, update, create and delete - * - * @param url URL of the IXC API - * @param credentials {@link IXC_Auth} Object with the credentials to be used in the requests - * ```json - * { - * "username": "25", - * "password": "41837b8eb82f1e60e148823a..." - * } - * ``` - * OR - * ```json - * { - * "token": "Basic MTYzOjk3NmRmZjlkNGZkNjExODdhNzUyNWQ..." - * } - * ``` - * - * @example - * ```js - * const ixc = new IXC({ - * url: "https://assinante.nome.com.br", - * credentials: {token: "Basic ...."} OR {username: "25", password: "418732...."} - * }) - * ``` - */ class IXC { public url: string; public authenticate: Return_Auth; + constructor({ url, credentials }: { url: string; credentials: IXC_Auth }) { this.url = url; - this.authenticate = auth(credentials) + this.authenticate = auth(credentials); } - /** - * The `IXC` module provides a set of functions for list, update, create and delete - * @see [source](https://wikiapiprovedor.ixcsoft.com.br/#) - */ - - /** - * - * @param table {@link [tables](https://wikiapiprovedor.ixcsoft.com.br/#)} Path to the table to be queried - * @param body {@link BodyRaw} Object with the parameters to be sent to the API. Example: - * ```json - * { - * "qtype": "id", - * "query": "1", - * "oper": "=", - * "page": "1", - * "rp": "10", - * "sortname": "id", - * "sortorder": "asc", - * } - * ``` - * @returns {@link IxcListResponse} - */ async list(table: string, body: BodyRaw): Promise { - return await listAll(this.url, this.authenticate.token, table, body) - }; + return await listAll(this.url, this.authenticate.token, table, body); + } - /** - * - * @param table {@link [tables](https://wikiapiprovedor.ixcsoft.com.br/#)} Path to the table to be queried - * @param filter {@link FilterParams} Array of objects with the parameters to be sent to the API. Example: - * ```json - * [ - * { - * "TB": "setor", - * "OP": "=", - * "P": "1", - * "C": "AND", - * "G": "setor", - * }, - * { - * "TB": "status", - * "OP": "=", - * "P": "F", - * "C": "AND", - * "G": "status", - * } - * { - * "TB": "data_abertura", - * "OP": ">=", - * "P": "2023-12-01", - * "C": "AND", - * "G": "data_abertura", - * } - * ] - * ``` - * @returns {@link IxcListResponse} - */ async listFilter(table: string, filter: FilterParams[]): Promise { - return await concatenatedFilter(this.url, this.authenticate.token, table, filter) + return await concatenatedFilter(this.url, this.authenticate.token, table, filter); + } + + async read(table: string, body: Record): Promise { + return await readRecord(this.url, this.authenticate.token, table, body); } + async create(table: string, body: Record): Promise { + return await createRecord(this.url, this.authenticate.token, table, body); + } + + async update(table: string, body: Record): Promise { + return await updateRecord(this.url, this.authenticate.token, table, body); + } + + async delete(table: string, body: Record): Promise { + return await deleteRecord(this.url, this.authenticate.token, table, body); + } } -export default IXC; \ No newline at end of file +export default IXC; diff --git a/src/service/auth.ts b/src/service/auth.ts index a1b9857..d438491 100644 --- a/src/service/auth.ts +++ b/src/service/auth.ts @@ -1,7 +1,14 @@ import { IXC_Auth, Return_Auth } from "../@types/types"; -export const auth = (auth: IXC_Auth): Return_Auth => { - if(auth.username && auth.password) auth.token = Buffer.from(`Basic ${auth.username}:${auth.password}`).toString("base64"); - if(auth.token) auth.token - return {token: auth.token || ""} -} \ No newline at end of file +export const auth = (credentials: IXC_Auth): Return_Auth => { + if (credentials.username && credentials.password) { + const encoded = Buffer.from(`${credentials.username}:${credentials.password}`).toString("base64"); + return { token: `Basic ${encoded}` }; + } + + if (credentials.token) { + return { token: credentials.token }; + } + + return { token: "" }; +}; diff --git a/src/service/concatenatedFilter.ts b/src/service/concatenatedFilter.ts index a67575a..4eacbf9 100644 --- a/src/service/concatenatedFilter.ts +++ b/src/service/concatenatedFilter.ts @@ -1,40 +1,35 @@ -import fetch from "node-fetch"; import { FilterParams, IxcListResponse } from "../@types/types"; +import { ixcRequest } from "./request"; export async function concatenatedFilter(url: string, token: string, table: string, filter: FilterParams[]): Promise { + const body = { + rp: "100000", + grid_param: JSON.stringify(filter), + }; - const body = { - rp: "100000", - grid_param: JSON.stringify(filter), - } + const response = await ixcRequest>({ + url, + token, + table, + operation: "listar", + body, + }); - const ixcRequest = await fetch(`${url}/webservice/v1/${table}`, { - method: "POST", - body: JSON.stringify(body), - headers: { - ixcsoft: "listar", - "Content-Type": "application/json", - Authorization: token, - }, - }); - if (ixcRequest.ok === false) { - throw Error(`Network response was not ok`); - } + if (Array.isArray(response.registros) && response.registros.length > 0) { + return { + status: "success", + total: Number(response.total || 0), + registros: response.registros, + }; + } - const response = await ixcRequest.json() - if (response.registros && response.registros.length > 0) { - return { - status: "success", - total: response.total, - registros: response.registros - }; - } - if (response.total == 0) { - return { - status: "error", - total: 0, - registros: [] - }; - } - throw Error(`Unrecognized response from IXC: ${JSON.stringify(response)}`); -} \ No newline at end of file + if (Number(response.total || 0) === 0) { + return { + status: "error", + total: 0, + registros: [], + }; + } + + throw new Error(`Unrecognized response from IXC: ${JSON.stringify(response)}`); +} diff --git a/src/service/create.ts b/src/service/create.ts new file mode 100644 index 0000000..4bd0819 --- /dev/null +++ b/src/service/create.ts @@ -0,0 +1,12 @@ +import { IxcMutationResponse } from "../@types/types"; +import { ixcRequest, parseMutationResponse } from "./request"; + +export async function createRecord( + url: string, + token: string, + table: string, + body: Record +): Promise { + const response = await ixcRequest({ url, token, table, operation: "incluir", body }); + return parseMutationResponse(response); +} diff --git a/src/service/delete.ts b/src/service/delete.ts new file mode 100644 index 0000000..3a40073 --- /dev/null +++ b/src/service/delete.ts @@ -0,0 +1,12 @@ +import { IxcMutationResponse } from "../@types/types"; +import { ixcRequest, parseMutationResponse } from "./request"; + +export async function deleteRecord( + url: string, + token: string, + table: string, + body: Record +): Promise { + const response = await ixcRequest({ url, token, table, operation: "deletar", body }); + return parseMutationResponse(response); +} diff --git a/src/service/list.ts b/src/service/list.ts index e76b2e4..0755c01 100644 --- a/src/service/list.ts +++ b/src/service/list.ts @@ -1,37 +1,30 @@ -import fetch from "node-fetch"; import { BodyRaw, IxcListResponse } from "../@types/types"; - +import { ixcRequest } from "./request"; export async function listAll(url: string, token: string, table: string, body: BodyRaw): Promise { - - const ixcRequest = await fetch(`${url}/webservice/v1/${table}`, { - method: "POST", - body: JSON.stringify(body), - headers: { - ixcsoft: "listar", - "Content-Type": "application/json", - Authorization: token, - }, + const response = await ixcRequest>({ + url, + token, + table, + operation: "listar", + body, }); - if (ixcRequest.ok === false) { - throw Error(`Network response was not ok`); - } - - const response = await ixcRequest.json() - if (response.registros && response.registros.length > 0) { + if (Array.isArray(response.registros) && response.registros.length > 0) { return { status: "success", - total: response.total, - registros: response.registros + total: Number(response.total || 0), + registros: response.registros, }; } - if (response.total == 0) { + + if (Number(response.total || 0) === 0) { return { status: "error", total: 0, - registros: [] + registros: [], }; } - throw Error(`Unrecognized response from IXC: ${JSON.stringify(response)}`); -} \ No newline at end of file + + throw new Error(`Unrecognized response from IXC: ${JSON.stringify(response)}`); +} diff --git a/src/service/read.ts b/src/service/read.ts new file mode 100644 index 0000000..3b590a0 --- /dev/null +++ b/src/service/read.ts @@ -0,0 +1,12 @@ +import { IxcReadResponse } from "../@types/types"; +import { ixcRequest, parseReadResponse } from "./request"; + +export async function readRecord( + url: string, + token: string, + table: string, + body: Record +): Promise { + const response = await ixcRequest({ url, token, table, operation: "obter", body }); + return parseReadResponse(response); +} diff --git a/src/service/request.ts b/src/service/request.ts new file mode 100644 index 0000000..1ba1657 --- /dev/null +++ b/src/service/request.ts @@ -0,0 +1,63 @@ +import fetch from "node-fetch"; +import { IxcMutationResponse, IxcReadResponse, IxcRequestOperation } from "../@types/types"; + +interface RequestOptions { + url: string; + token: string; + table: string; + operation: IxcRequestOperation; + body?: unknown; +} + +export async function ixcRequest({ + url, + token, + table, + operation, + body = {}, +}: RequestOptions): Promise { + const response = await fetch(`${url}/webservice/v1/${table}`, { + method: "POST", + body: JSON.stringify(body), + headers: { + ixcsoft: operation, + "Content-Type": "application/json", + Authorization: token, + }, + }); + + if (!response.ok) { + throw new Error(`IXC request failed with status ${response.status}`); + } + + return (await response.json()) as T; +} + +export function parseMutationResponse(response: unknown): IxcMutationResponse { + const typed = response as Record; + return { + status: typeof typed.status === "string" ? typed.status : undefined, + type: typeof typed.type === "string" ? typed.type : undefined, + message: + typeof typed.message === "string" + ? typed.message + : typeof typed.mensagem === "string" + ? typed.mensagem + : undefined, + data: typed, + }; +} + +export function parseReadResponse(response: unknown): IxcReadResponse { + const typed = response as Record; + return { + status: typeof typed.status === "string" ? typed.status : undefined, + message: + typeof typed.message === "string" + ? typed.message + : typeof typed.mensagem === "string" + ? typed.mensagem + : undefined, + registro: typed.registro ?? typed, + }; +} diff --git a/src/service/update.ts b/src/service/update.ts new file mode 100644 index 0000000..7b28108 --- /dev/null +++ b/src/service/update.ts @@ -0,0 +1,12 @@ +import { IxcMutationResponse } from "../@types/types"; +import { ixcRequest, parseMutationResponse } from "./request"; + +export async function updateRecord( + url: string, + token: string, + table: string, + body: Record +): Promise { + const response = await ixcRequest({ url, token, table, operation: "alterar", body }); + return parseMutationResponse(response); +}