diff --git a/LogoMenor.png b/LogoMenor.png
new file mode 100644
index 0000000..aa6a647
Binary files /dev/null and b/LogoMenor.png differ
diff --git a/src/routes.js b/src/routes.js
index 4864198..6fc335d 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -64,66 +64,352 @@ if (enableSwagger) {
}
}
+ routes.get('/api-docs/custom-logo.png', (req, res) => {
+ res.sendFile(path.resolve(__dirname, '..', 'LogoMenor.png'))
+ })
+
const customCss = `
- .swagger-ui .topbar { background: #1a1a2e; }
- .swagger-ui .topbar .topbar-wrapper .link { display: grid; grid-template-columns: 1fr; align-items: center; }
- .swagger-ui .topbar .topbar-wrapper .link img { display: none; }
+ /* Traduzir botões e labels do Swagger UI para PT-BR via CSS content */
+ .swagger-ui .btn.try-out__btn { font-size: 0; }
+ .swagger-ui .btn.try-out__btn::before { content: "Experimentar"; font-size: 14px; }
+ .swagger-ui .try-out .btn.cancel { font-size: 0; }
+ .swagger-ui .try-out .btn.cancel::before { content: "Cancelar"; font-size: 14px; }
+ .swagger-ui .btn.execute { font-size: 0; }
+ .swagger-ui .btn.execute::before { content: "Executar"; font-size: 14px; font-weight: bold; }
+ .swagger-ui .btn.btn-clear { font-size: 0; }
+ .swagger-ui .btn.btn-clear::before { content: "Limpar"; font-size: 14px; }
+ .swagger-ui .auth-wrapper .authorize { font-size: 0; }
+ .swagger-ui .auth-wrapper .authorize span { display: none; }
+ .swagger-ui .auth-wrapper .authorize::before { content: "Autorizar"; font-size: 14px; font-weight: bold; }
+ .swagger-ui .auth-btn-wrapper .btn-done { font-size: 0; }
+ .swagger-ui .auth-btn-wrapper .btn-done::before { content: "Fechar"; font-size: 14px; }
+ .swagger-ui .auth-container .auth-btn-wrapper .authorize { font-size: 0; }
+ .swagger-ui .auth-container .auth-btn-wrapper .authorize::before { content: "Autorizar"; font-size: 14px; }
+ .swagger-ui .btn-group .btn { color: inherit; }
+ .swagger-ui .info .title small { display: none; }
+
+ /* Logo customizada da Neuralsys no topbar (substitui a logo do Swagger) */
+ .swagger-ui .topbar .topbar-wrapper .link {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ grid-template-rows: auto auto;
+ column-gap: 14px;
+ align-items: center;
+ }
+ .swagger-ui .topbar .topbar-wrapper .link img {
+ content: url("./custom-logo.png");
+ height: 56px;
+ width: auto;
+ grid-column: 1;
+ grid-row: 1 / span 2;
+ }
.swagger-ui .topbar .topbar-wrapper .link span { display: none; }
.swagger-ui .topbar .topbar-wrapper .link::before {
- content: "CoreGuard • WhatsAPI Oficial";
- color: #fff; font-family: sans-serif; font-weight: 700; font-size: 20px; padding: 8px 0;
+ content: "CoreGuard";
+ grid-column: 2;
+ grid-row: 1;
+ align-self: end;
+ color: #fff;
+ font-family: sans-serif;
+ font-weight: 700;
+ font-size: 22px;
+ line-height: 1;
+ letter-spacing: 0.3px;
}
- .swagger-ui section.models, .swagger-ui .models { display: none !important; }
- body:not(.neuralsys-authorized) .swagger-ui .opblock-tag { pointer-events: none; opacity: 0.55; }
+ .swagger-ui .topbar .topbar-wrapper .link::after {
+ content: "WhatsAPI Oficial";
+ grid-column: 2;
+ grid-row: 2;
+ align-self: start;
+ color: #fff;
+ font-family: sans-serif;
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 1.1;
+ letter-spacing: 0.4px;
+ margin-top: 3px;
+ }
+
+ /* Classe usada pelo filtro customizado para esconder endpoints/seções */
+ .swagger-ui .neuralsys-hidden { display: none !important; }
+
+ /* Container do filtro customizado (substitui o filtro nativo) */
+ .swagger-ui .neuralsys-filter-container {
+ margin: 20px 0;
+ padding: 0 20px;
+ max-width: 1460px;
+ box-sizing: border-box;
+ }
+ .swagger-ui .neuralsys-filter-container input {
+ width: 100%;
+ padding: 10px 14px;
+ border: 2px solid #41444e;
+ border-radius: 4px;
+ font-size: 14px;
+ box-sizing: border-box;
+ outline: none;
+ transition: border-color .15s;
+ }
+ .swagger-ui .neuralsys-filter-container input:focus {
+ border-color: #4990e2;
+ }
+
+ /* Labels de "Parameters" / "Responses" / "No parameters" etc. */
+ .swagger-ui .opblock-section-header h4 span[data-name="parameters"]::after,
+ .swagger-ui .opblock-section-header h4.parameters-header::after { content: ""; }
+
+ /* Botão "Copy to clipboard" sempre visível nos endpoints da lista */
+ .swagger-ui .opblock .opblock-summary .view-line-link.copy-to-clipboard {
+ width: 24px !important;
+ }
+
+ /* Ocultar seção "Schemas" no final da página */
+ .swagger-ui section.models,
+ .swagger-ui .models { display: none !important; }
+
+ /* Gate: bloqueia expansão e filtragem até autorizar com apiKey válida */
+ body:not(.neuralsys-authorized) .swagger-ui .opblock-tag { pointer-events: none; opacity: 0.55; cursor: not-allowed; }
body:not(.neuralsys-authorized) .swagger-ui .opblock-tag-section .no-margin { display: none !important; }
+ body:not(.neuralsys-authorized) .swagger-ui .neuralsys-filter-container { display: none !important; }
+ /* Rodapé minimalista */
+ body { margin-bottom: 0; }
.neuralsys-footer {
- background: #000; color: #fff; padding: 16px 32px; font-size: 13px;
- display: flex; justify-content: space-between; font-family: sans-serif; margin-top: 40px;
+ background: #000;
+ color: #fff;
+ padding: 16px 32px;
+ font-size: 13px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-family: sans-serif;
+ margin-top: 40px;
}
+ .neuralsys-footer a { color: #fff; text-decoration: none; display: inline-flex; align-items: center; gap: 8px; }
+ .neuralsys-footer a:hover { text-decoration: underline; }
+ .neuralsys-footer svg { width: 16px; height: 16px; fill: currentColor; }
`
const customJsStr = `
(function () {
- var authorized = false, lastKey = null, validating = false;
+ var HIDDEN = 'neuralsys-hidden'
+
+ function buildCorpus(op, rawPath) {
+ var paramNames = (op.parameters || []).map(function (p) { return p.name || '' }).join(' ')
+ var props = op && op.requestBody && op.requestBody.content &&
+ op.requestBody.content['application/json'] &&
+ op.requestBody.content['application/json'].schema &&
+ op.requestBody.content['application/json'].schema.properties
+ var reqBodyProps = props ? Object.keys(props).join(' ') : ''
+ return (rawPath + ' ' + (op.summary || '') + ' ' + (op.description || '') +
+ ' ' + paramNames + ' ' + reqBodyProps).toLowerCase()
+ }
+
+ function resetAll() {
+ document.querySelectorAll('.' + HIDDEN).forEach(function (el) {
+ el.classList.remove(HIDDEN)
+ })
+ }
+
+ function installFilter() {
+ if (!window.ui) { return setTimeout(installFilter, 250) }
+ if (document.querySelector('.neuralsys-filter-container')) return
+
+ var wrapper = document.querySelector('.swagger-ui .wrapper')
+ var firstSection = document.querySelector('.opblock-tag-section')
+ if (!wrapper || !firstSection) { return setTimeout(installFilter, 250) }
+
+ var container = document.createElement('div')
+ container.className = 'neuralsys-filter-container'
+ var input = document.createElement('input')
+ input.type = 'text'
+ input.placeholder = 'Filtrar...'
+ input.setAttribute('aria-label', 'Filtrar endpoints')
+ container.appendChild(input)
+
+ firstSection.parentNode.insertBefore(container, firstSection)
+
+ var specJson = window.ui.specSelectors.specJson().toJS()
+ var paths = specJson.paths || {}
+
+ var tagIndex = null
+ function buildTagIndex() {
+ if (tagIndex) return tagIndex
+ tagIndex = {}
+ var httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head']
+ Object.keys(paths).forEach(function (p) {
+ Object.keys(paths[p] || {}).forEach(function (m) {
+ var ml = m.toLowerCase()
+ if (httpMethods.indexOf(ml) === -1) return
+ var op = paths[p][m]
+ if (!op || typeof op !== 'object') return
+ var tags = (op.tags && op.tags.length) ? op.tags : ['default']
+ tags.forEach(function (t) {
+ if (!tagIndex[t]) tagIndex[t] = []
+ tagIndex[t].push({ path: p, method: ml, corpus: buildCorpus(op, p) })
+ })
+ })
+ })
+ return tagIndex
+ }
+
+ function getTagName(sec) {
+ var tagEl = sec.querySelector('.opblock-tag')
+ if (!tagEl) return ''
+ var name = tagEl.getAttribute('data-tag') || tagEl.getAttribute('data-name')
+ if (name) return name
+ var span = tagEl.querySelector('span') || tagEl
+ return ((span.textContent || '').replace(/\\s+/g, ' ') || '').trim()
+ }
+
+ function hideNonMatchingBlocks(q) {
+ document.querySelectorAll('.opblock').forEach(function (block) {
+ var pathEl = block.querySelector('.opblock-summary-path')
+ var methodEl = block.querySelector('.opblock-summary-method')
+ if (!pathEl || !methodEl) {
+ block.classList.remove(HIDDEN)
+ return
+ }
+ var rawPath = pathEl.getAttribute('data-path') ||
+ (pathEl.querySelector('a') && pathEl.querySelector('a').textContent) ||
+ pathEl.textContent
+ rawPath = (rawPath || '').trim()
+ var method = methodEl.textContent.trim().toLowerCase()
+ var op = (paths[rawPath] && paths[rawPath][method]) || {}
+ var corpus = buildCorpus(op, rawPath)
+ if (corpus.indexOf(q) !== -1) {
+ block.classList.remove(HIDDEN)
+ } else {
+ block.classList.add(HIDDEN)
+ }
+ })
+ }
+
+ function applyFilter() {
+ var q = input.value.trim().toLowerCase()
+ if (!q) { resetAll(); return }
+
+ var idx = buildTagIndex()
+ var needsExpansion = false
+
+ document.querySelectorAll('.opblock-tag-section').forEach(function (sec) {
+ var tagName = getTagName(sec)
+ var ops = idx[tagName] || []
+ var hasMatch = ops.some(function (o) { return o.corpus.indexOf(q) !== -1 })
+ if (!hasMatch) {
+ sec.classList.add(HIDDEN)
+ return
+ }
+ sec.classList.remove(HIDDEN)
+ if (!sec.classList.contains('is-open')) {
+ var header = sec.querySelector('.opblock-tag')
+ if (header) {
+ header.click()
+ needsExpansion = true
+ }
+ }
+ })
+
+ if (needsExpansion) {
+ setTimeout(function () { hideNonMatchingBlocks(q) }, 80)
+ } else {
+ hideNonMatchingBlocks(q)
+ }
+ }
+
+ input.addEventListener('input', applyFilter)
+ }
+
+ var observer = new MutationObserver(function () {
+ if (!document.querySelector('.neuralsys-filter-container') &&
+ document.querySelector('.opblock-tag-section')) {
+ installFilter()
+ }
+ translateCopyButtons()
+ if (!document.querySelector('.neuralsys-footer')) installFooter()
+ })
+
+ function translateCopyButtons() {
+ var label = 'Copiar para área de transferência'
+ document.querySelectorAll('.view-line-link.copy-to-clipboard, .copy-to-clipboard').forEach(function (el) {
+ if (el.getAttribute('title') !== label) el.setAttribute('title', label)
+ if (el.getAttribute('aria-label') !== label) el.setAttribute('aria-label', label)
+ })
+ }
+
+ var authorized = false
+ var lastKey = null
+ var validating = false
+
function currentKey() {
try {
- var auth = window.ui && window.ui.authSelectors.authorized();
- if (!auth) return null;
- var js = auth.toJS ? auth.toJS() : auth;
- if (js && js.apiKeyAuth && js.apiKeyAuth.value) return js.apiKeyAuth.value;
- } catch(e) {}
- return null;
+ var auth = window.ui.authSelectors.authorized()
+ if (!auth) return null
+ var js = auth.toJS ? auth.toJS() : auth
+ if (js && js.apiKeyAuth && js.apiKeyAuth.value) return js.apiKeyAuth.value
+ } catch (e) {}
+ return null
}
+
+ function collapseAll() {
+ document.querySelectorAll('.opblock-tag-section.is-open').forEach(function (sec) {
+ var header = sec.querySelector('.opblock-tag')
+ if (header) header.click()
+ })
+ }
+
function setAuthorized(ok) {
- authorized = ok;
- document.body.classList.toggle('neuralsys-authorized', ok);
- if (!ok) {
- document.querySelectorAll('.opblock-tag-section.is-open').forEach(function(s) {
- var h = s.querySelector('.opblock-tag'); if (h) h.click();
- });
+ authorized = ok
+ document.body.classList.toggle('neuralsys-authorized', ok)
+ if (!ok) collapseAll()
+ }
+
+ function validateKey(key) {
+ if (validating) return
+ validating = true
+ fetch('/validateApiKey', { headers: { 'x-api-key': key } })
+ .then(function (r) { setAuthorized(r.status === 200) })
+ .catch(function () { setAuthorized(false) })
+ .then(function () { validating = false })
+ }
+
+ function pollAuth() {
+ var key = currentKey()
+ if (key !== lastKey) {
+ lastKey = key
+ if (key) { validateKey(key) } else { setAuthorized(false) }
}
}
- function validateKey(key) {
- if (validating) return; validating = true;
- fetch('/validateApiKey', { headers: { 'x-api-key': key } })
- .then(function(r) { setAuthorized(r.status === 200); })
- .catch(function() { setAuthorized(false); })
- .then(function() { validating = false; });
- }
- function pollAuth() {
- var key = currentKey();
- if (key !== lastKey) { lastKey = key; if (key) validateKey(key); else setAuthorized(false); }
- }
+
function installFooter() {
- if (document.querySelector('.neuralsys-footer')) return;
- var f = document.createElement('footer');
- f.className = 'neuralsys-footer';
- f.innerHTML = 'Copyright © ' + new Date().getFullYear() + ' — CoreGuardcontato@neuralsys.com.br';
- document.body.appendChild(f);
+ if (document.querySelector('.neuralsys-footer')) return
+ var footer = document.createElement('footer')
+ footer.className = 'neuralsys-footer'
+ var year = new Date().getFullYear()
+ footer.innerHTML =
+ 'Copyright \\u00A9 ' + year + ' - CoreGuard' +
+ '' +
+ '' +
+ 'contato@neuralsys.com.br' +
+ ''
+ document.body.appendChild(footer)
+ }
+
+ function boot() {
+ installFilter()
+ translateCopyButtons()
+ installFooter()
+ setAuthorized(false)
+ setInterval(pollAuth, 500)
+ observer.observe(document.body, { childList: true, subtree: true })
+ }
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', function () { setTimeout(boot, 400) })
+ } else {
+ setTimeout(boot, 400)
}
- function boot() { setAuthorized(false); setInterval(pollAuth, 500); installFooter(); }
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function() { setTimeout(boot, 400); });
- else setTimeout(boot, 400);
})();
`
@@ -132,6 +418,7 @@ if (enableSwagger) {
customCss,
customJsStr,
customSiteTitle: 'WhatsApp Oficial API — Documentação',
+ customfavIcon: '/api-docs/custom-logo.png',
swaggerOptions: { docExpansion: 'none', persistAuthorization: true },
}))
}