From d6a92358d31768447e58ccbffc53da66d19f6eb6 Mon Sep 17 00:00:00 2001 From: "lucas.favaro" Date: Wed, 13 May 2026 14:13:09 -0300 Subject: [PATCH] =?UTF-8?q?Atualiza=C3=A7=C3=B5es=20de=20objetos=20a=20ser?= =?UTF-8?q?em=20criados=20para=20contrinui=C3=A7=C3=A3o.Aplicativo=20de=20?= =?UTF-8?q?testes=20do=20manifesto.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...7000001_seq_tb_flash_fato_contribuicao.sql | 0 ...60507000002_tb_flash_fato_contribuicao.sql | 0 ..._seq_tb_flash_nodo_resumo_contribuicao.sql | 0 ...0010_tb_flash_nodo_resumo_contribuicao.sql | 0 ...11_prc_flash_carga_resumo_contribuicao.sql | 0 ...2_prc_flash_atualiza_contribuicao_dono.sql | 0 ...013_prc_flash_carga_dados_contribuicao.sql | 0 ...7000014_tb_flash_meta_contribuicao_stg.sql | 0 ...7000015_vw_flash_meta_contribuicao_stg.sql | 0 package.json | 1 + scripts/assets/logo-davinti.jpg | Bin 0 -> 21880 bytes scripts/module-test-app.md | 123 +++ scripts/module-test-app.ts | 885 ++++++++++++++++++ 13 files changed, 1009 insertions(+) rename migrations/{ => C5_big}/20260507000001_seq_tb_flash_fato_contribuicao.sql (100%) rename migrations/{ => C5_big}/20260507000002_tb_flash_fato_contribuicao.sql (100%) rename migrations/{ => C5_big}/20260507000009_seq_tb_flash_nodo_resumo_contribuicao.sql (100%) rename migrations/{ => C5_big}/20260507000010_tb_flash_nodo_resumo_contribuicao.sql (100%) rename migrations/{ => C5_big}/20260507000011_prc_flash_carga_resumo_contribuicao.sql (100%) rename migrations/{ => C5_big}/20260507000012_prc_flash_atualiza_contribuicao_dono.sql (100%) rename migrations/{ => C5_big}/20260507000013_prc_flash_carga_dados_contribuicao.sql (100%) rename migrations/{ => C5_big}/20260507000014_tb_flash_meta_contribuicao_stg.sql (100%) rename migrations/{ => C5_big}/20260507000015_vw_flash_meta_contribuicao_stg.sql (100%) create mode 100644 scripts/assets/logo-davinti.jpg create mode 100644 scripts/module-test-app.md create mode 100644 scripts/module-test-app.ts diff --git a/migrations/20260507000001_seq_tb_flash_fato_contribuicao.sql b/migrations/C5_big/20260507000001_seq_tb_flash_fato_contribuicao.sql similarity index 100% rename from migrations/20260507000001_seq_tb_flash_fato_contribuicao.sql rename to migrations/C5_big/20260507000001_seq_tb_flash_fato_contribuicao.sql diff --git a/migrations/20260507000002_tb_flash_fato_contribuicao.sql b/migrations/C5_big/20260507000002_tb_flash_fato_contribuicao.sql similarity index 100% rename from migrations/20260507000002_tb_flash_fato_contribuicao.sql rename to migrations/C5_big/20260507000002_tb_flash_fato_contribuicao.sql diff --git a/migrations/20260507000009_seq_tb_flash_nodo_resumo_contribuicao.sql b/migrations/C5_big/20260507000009_seq_tb_flash_nodo_resumo_contribuicao.sql similarity index 100% rename from migrations/20260507000009_seq_tb_flash_nodo_resumo_contribuicao.sql rename to migrations/C5_big/20260507000009_seq_tb_flash_nodo_resumo_contribuicao.sql diff --git a/migrations/20260507000010_tb_flash_nodo_resumo_contribuicao.sql b/migrations/C5_big/20260507000010_tb_flash_nodo_resumo_contribuicao.sql similarity index 100% rename from migrations/20260507000010_tb_flash_nodo_resumo_contribuicao.sql rename to migrations/C5_big/20260507000010_tb_flash_nodo_resumo_contribuicao.sql diff --git a/migrations/20260507000011_prc_flash_carga_resumo_contribuicao.sql b/migrations/C5_big/20260507000011_prc_flash_carga_resumo_contribuicao.sql similarity index 100% rename from migrations/20260507000011_prc_flash_carga_resumo_contribuicao.sql rename to migrations/C5_big/20260507000011_prc_flash_carga_resumo_contribuicao.sql diff --git a/migrations/20260507000012_prc_flash_atualiza_contribuicao_dono.sql b/migrations/C5_big/20260507000012_prc_flash_atualiza_contribuicao_dono.sql similarity index 100% rename from migrations/20260507000012_prc_flash_atualiza_contribuicao_dono.sql rename to migrations/C5_big/20260507000012_prc_flash_atualiza_contribuicao_dono.sql diff --git a/migrations/20260507000013_prc_flash_carga_dados_contribuicao.sql b/migrations/C5_big/20260507000013_prc_flash_carga_dados_contribuicao.sql similarity index 100% rename from migrations/20260507000013_prc_flash_carga_dados_contribuicao.sql rename to migrations/C5_big/20260507000013_prc_flash_carga_dados_contribuicao.sql diff --git a/migrations/20260507000014_tb_flash_meta_contribuicao_stg.sql b/migrations/C5_big/20260507000014_tb_flash_meta_contribuicao_stg.sql similarity index 100% rename from migrations/20260507000014_tb_flash_meta_contribuicao_stg.sql rename to migrations/C5_big/20260507000014_tb_flash_meta_contribuicao_stg.sql diff --git a/migrations/20260507000015_vw_flash_meta_contribuicao_stg.sql b/migrations/C5_big/20260507000015_vw_flash_meta_contribuicao_stg.sql similarity index 100% rename from migrations/20260507000015_vw_flash_meta_contribuicao_stg.sql rename to migrations/C5_big/20260507000015_vw_flash_meta_contribuicao_stg.sql diff --git a/package.json b/package.json index 6735d68..8ca8299 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "generate": "jeff generate -i \"src/**/*.module.ts\"", "generate:output": "jeff generate -i \"src/**/*.module.ts\" -o dist/manifest.json", "vet": "jeff vet -i \"src/**/*.module.ts\"", + "test:app": "tsx scripts/module-test-app.ts", "jeff": "jeff" }, "author": "", diff --git a/scripts/assets/logo-davinti.jpg b/scripts/assets/logo-davinti.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2701cb781d731b6afa7b3119dfb069f291e58bfe GIT binary patch literal 21880 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 + implementations: Record) => { sql: string }>>; + schedules?: Record; +}; + +const root = process.cwd(); +const srcDir = path.join(root, "src"); +const portArg = Number(process.argv.find((arg) => arg.startsWith("--port="))?.split("=")[1]); +const startPort = Number.isFinite(portArg) && portArg > 0 ? portArg : 4317; + +async function findModuleFiles(dir: string): Promise { + const entries = await readdir(dir, { withFileTypes: true }); + const files = await Promise.all( + entries.map(async (entry) => { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) return findModuleFiles(fullPath); + if (entry.isFile() && entry.name.endsWith(".module.ts")) return [fullPath]; + return []; + }), + ); + return files.flat(); +} + +async function loadModules() { + const files = await findModuleFiles(srcDir); + const modules = await Promise.all( + files.map(async (file) => { + const imported = await import(pathToFileURL(file).href + `?t=${Date.now()}`); + const module = imported.default as ModuleDefinition; + return { + file: path.relative(root, file).replace(/\\/g, "/"), + module, + }; + }), + ); + return modules.sort((a, b) => a.module.id.localeCompare(b.module.id)); +} + +function json(res: http.ServerResponse, status: number, data: unknown) { + res.writeHead(status, { "content-type": "application/json; charset=utf-8" }); + res.end(JSON.stringify(data, null, 2)); +} + +function text(res: http.ServerResponse, status: number, data: string) { + res.writeHead(status, { "content-type": "text/plain; charset=utf-8" }); + res.end(data); +} + +function html(res: http.ServerResponse, status: number, data: string) { + res.writeHead(status, { "content-type": "text/html; charset=utf-8" }); + res.end(data); +} + +function jpg(res: http.ServerResponse, status: number, data: Buffer) { + res.writeHead(status, { + "content-type": "image/jpeg", + "cache-control": "public, max-age=3600", + }); + res.end(data); +} + +async function readJsonBody(req: http.IncomingMessage) { + const chunks: Buffer[] = []; + for await (const chunk of req) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + const raw = Buffer.concat(chunks).toString("utf8"); + return raw ? JSON.parse(raw) : {}; +} + +function defaultArgs(params: string[]) { + const args: Record = { + ctx_user_companies: "1,2,3", + ctx_user_companies_for_module: "1,2,3", + }; + for (const param of params) args[param] = `:${param}`; + return args; +} + +function vetProject() { + return new Promise<{ code: number | null; output: string }>((resolve) => { + const child = spawn(process.platform === "win32" ? "npm.cmd" : "npm", ["run", "vet"], { + cwd: root, + shell: false, + }); + let output = ""; + child.stdout.on("data", (chunk) => (output += chunk.toString())); + child.stderr.on("data", (chunk) => (output += chunk.toString())); + child.on("close", (code) => resolve({ code, output })); + }); +} + +function parseManifestValue(value: string) { + const trimmed = value.trim(); + if (trimmed.toLowerCase() === "null") return null; + if (trimmed.toLowerCase() === "true") return true; + if (trimmed.toLowerCase() === "false") return false; + if (/^-?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed); + if ( + (trimmed.startsWith("'") && trimmed.endsWith("'")) || + (trimmed.startsWith('"') && trimmed.endsWith('"')) + ) { + return trimmed.slice(1, -1); + } + return value; +} + +function findBearerToken(value: unknown): string | null { + if (!value || typeof value !== "object") return null; + + if ( + "token" in value && + typeof (value as Record).token === "string" && + (value as Record).token.trim() + ) { + return (value as Record).token.replace(/^Bearer\s+/i, "").trim(); + } + + const preferredKeys = new Set([ + "access_token", + "accessToken", + "bearer", + "bearerToken", + "token", + "jwt", + ]); + const preferredLowerKeys = new Set([...preferredKeys].map((key) => key.toLowerCase())); + + const queue: unknown[] = [value]; + while (queue.length) { + const current = queue.shift(); + if (!current || typeof current !== "object") continue; + + for (const [key, nested] of Object.entries(current)) { + if (typeof nested === "string" && preferredKeys.has(key)) { + return nested.replace(/^Bearer\s+/i, "").trim(); + } + if (typeof nested === "string" && preferredLowerKeys.has(key.toLowerCase())) { + return nested.replace(/^Bearer\s+/i, "").trim(); + } + if (nested && typeof nested === "object") queue.push(nested); + } + } + + return null; +} + +async function postLogin(loginUrl: string, payload: Record) { + const response = await fetch(loginUrl, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(payload), + }); + + const textBody = await response.text(); + let parsedBody: unknown = textBody; + try { + parsedBody = textBody ? JSON.parse(textBody) : null; + } catch { + parsedBody = textBody; + } + + return { + ok: response.ok, + status: response.status, + bearerToken: findBearerToken(parsedBody), + responseBody: parsedBody, + }; +} + +async function handleApi(req: http.IncomingMessage, res: http.ServerResponse) { + const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`); + + if (req.method === "GET" && url.pathname === "/api/modules") { + const modules = await loadModules(); + return json( + res, + 200, + modules.map(({ file, module }) => ({ + file, + id: module.id, + label: module.label, + entrypoint: module.entrypoint, + queries: Object.entries(module.queries).map(([key, query]) => ({ + key, + name: query.name, + params: query.params, + })), + systems: Object.keys(module.implementations || {}), + })), + ); + } + + if (req.method === "GET" && url.pathname === "/assets/logo-davinti.jpg") { + const image = await readFile(path.join(root, "scripts", "assets", "logo-davinti.jpg")); + return jpg(res, 200, image); + } + + if (req.method === "GET" && url.pathname === "/api/docs") { + const markdown = await readFile(path.join(root, "scripts", "module-test-app.md"), "utf8"); + return json(res, 200, { markdown }); + } + + if (req.method === "POST" && url.pathname === "/api/render") { + const body = await readJsonBody(req); + const modules = await loadModules(); + const found = modules.find(({ module }) => module.id === body.moduleId); + if (!found) return json(res, 404, { error: "Module not found" }); + + const query = found.module.queries[body.queryKey]; + const implementation = found.module.implementations?.[body.system]?.[body.queryKey]; + if (!query || !implementation) return json(res, 404, { error: "Query implementation not found" }); + + const args = { ...defaultArgs(query.params), ...(body.args || {}) }; + const result = implementation(args); + return json(res, 200, { sql: result.sql, args }); + } + + if (req.method === "POST" && url.pathname === "/api/login") { + const body = await readJsonBody(req); + const loginUrl = String(body.loginUrl || ""); + const email = String(body.email || ""); + const senha = String(body.senha || ""); + + if (!loginUrl || !email || !senha) { + return json(res, 400, { error: "loginUrl, email e senha sao obrigatorios" }); + } + + const attempts = [ + { email, senha }, + { email, password: senha }, + ]; + const results = []; + + for (const payload of attempts) { + const result = await postLogin(loginUrl, payload); + results.push(result); + if (result.ok && result.bearerToken) { + return json(res, 200, { + status: result.status, + bearerToken: result.bearerToken, + responseBody: result.responseBody, + }); + } + } + + const lastResult = results[results.length - 1]; + const successfulWithoutToken = results.find((result) => result.ok); + if (successfulWithoutToken) { + return json(res, 502, { + error: "Login realizado, mas nao encontrei token no retorno", + status: successfulWithoutToken.status, + responseBody: successfulWithoutToken.responseBody, + }); + } + + return json(res, lastResult.status, { + error: "Login retornou erro HTTP " + lastResult.status, + status: lastResult.status, + responseBody: lastResult.responseBody, + }); + } + + if (req.method === "POST" && url.pathname === "/api/manifest-execute") { + const body = await readJsonBody(req); + const baseUrl = String(body.baseUrl || "").replace(/\/+$/, ""); + const moduleId = String(body.moduleId || ""); + const queryKey = String(body.queryKey || ""); + const clientId = String(body.clientId || ""); + const bearerToken = String(body.bearerToken || ""); + const params = body.params || {}; + + if (!baseUrl || !moduleId || !queryKey) { + return json(res, 400, { error: "baseUrl, moduleId e queryKey sao obrigatorios" }); + } + + const endpoint = `${baseUrl}/api/manifest/modules/${encodeURIComponent(moduleId)}/queries/${encodeURIComponent(queryKey)}/execute`; + const headers: Record = { + "content-type": "application/json", + }; + if (clientId) headers["x-client-id"] = clientId; + if (bearerToken) headers.authorization = `Bearer ${bearerToken}`; + + const response = await fetch(endpoint, { + method: "POST", + headers, + body: JSON.stringify(params), + }); + + const textBody = await response.text(); + let parsedBody: unknown = textBody; + try { + parsedBody = textBody ? JSON.parse(textBody) : null; + } catch { + parsedBody = textBody; + } + + return json(res, 200, { + status: response.status, + ok: response.ok, + url: endpoint, + requestBody: params, + responseBody: parsedBody, + }); + } + + if (req.method === "POST" && url.pathname === "/api/vet") { + const result = await vetProject(); + return json(res, result.code === 0 ? 200 : 500, result); + } + + return false; +} + +const page = String.raw` + + + + + App Dono Modulos - Teste Local + + + +
+
+
+ +
+ App Dono Modulos + Teste local de queries e manifesto remoto +
+
+
Vitruvio manifest tools
+
+
+
+
+
+

Modulo de teste

+ Local + remoto +
+
+

Selecao

+ + + + + + +
Os valores sao expressoes SQL cruas. Para testar literal de data, use aspas: '2026-05-08'.
+
+
+

Autenticacao

+ + +
+ + +
+ + + + + +
+
+

Parametros

+
+
+
+

Manifesto

+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+

+    
+
+ + +`; + +const server = http.createServer(async (req, res) => { + try { + const handled = await handleApi(req, res); + if (handled !== false) return; + if (req.method === "GET" && (req.url === "/" || req.url?.startsWith("/?"))) { + return html(res, 200, page); + } + return text(res, 404, "Not found"); + } catch (error) { + return json(res, 500, { error: error instanceof Error ? error.message : String(error) }); + } +}); + +function listen(port: number) { + server.once("error", (error: NodeJS.ErrnoException) => { + if (error.code === "EADDRINUSE") listen(port + 1); + else throw error; + }); + server.listen(port, () => { + const address = server.address(); + const actualPort = typeof address === "object" && address ? address.port : port; + console.log(`Module test app: http://localhost:${actualPort}`); + }); +} + +listen(startPort);