@oondemand/oon-core-back 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +91 -0
- package/README.md +130 -0
- package/bin/cli.js +95 -0
- package/bin/dev-runner.js +8 -0
- package/package.json +53 -0
- package/src/api/auth.routes.js +20 -0
- package/src/api/core.routes.js +75 -0
- package/src/api/status.routes.js +21 -0
- package/src/boot/activation.js +31 -0
- package/src/boot/createApp.js +56 -0
- package/src/boot/database.js +35 -0
- package/src/boot/loadCentral.js +51 -0
- package/src/boot/startServer.js +31 -0
- package/src/core/context.js +35 -0
- package/src/core/define.js +171 -0
- package/src/core/errors/GenericError.js +14 -0
- package/src/core/factories/crudController.js +66 -0
- package/src/core/factories/crudService.js +96 -0
- package/src/core/factories/modelRouter.js +67 -0
- package/src/core/fields.js +92 -0
- package/src/core/middlewares/auth.js +65 -0
- package/src/core/middlewares/correlationId.js +12 -0
- package/src/core/middlewares/error.js +46 -0
- package/src/core/middlewares/mutationAudit.js +40 -0
- package/src/core/middlewares/rbac.js +22 -0
- package/src/core/middlewares/requestLog.js +28 -0
- package/src/core/models/ControleAlteracao.js +28 -0
- package/src/core/models/Log.js +22 -0
- package/src/core/models/Sistema.js +27 -0
- package/src/core/registry.js +117 -0
- package/src/core/utils/helpers.js +44 -0
- package/src/deploy/render.js +88 -0
- package/src/deploy/templates/Dockerfile +19 -0
- package/src/deploy/templates/certificate.yaml +12 -0
- package/src/deploy/templates/configmap.yaml +16 -0
- package/src/deploy/templates/deployment.yaml +32 -0
- package/src/deploy/templates/hpa.yaml +19 -0
- package/src/deploy/templates/ingress.yaml +24 -0
- package/src/deploy/templates/secret.yaml +12 -0
- package/src/deploy/templates/service.yaml +14 -0
- package/src/index.js +43 -0
- package/src/modules/assistants/index.js +33 -0
- package/src/modules/currencies/index.js +31 -0
- package/src/modules/documents/index.js +19 -0
- package/src/modules/omie/index.js +69 -0
- package/src/modules/pipelines/index.js +19 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const ControleAlteracao = require("../models/ControleAlteracao");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Trilha de auditoria de mutações. Em respostas de sucesso (status < 400)
|
|
5
|
+
* grava um registro em ControleAlteracao com o usuário, a entidade e o
|
|
6
|
+
* registro afetado. Usado pelas factories de CRUD e por `defineRoutes`.
|
|
7
|
+
*
|
|
8
|
+
* @param {{ entidade: string, acao: string }} opts
|
|
9
|
+
*/
|
|
10
|
+
function mutationAudit({ entidade, acao }) {
|
|
11
|
+
return (req, res, next) => {
|
|
12
|
+
const origem = req.headers["x-origem"] || "api";
|
|
13
|
+
|
|
14
|
+
const originalJson = res.json.bind(res);
|
|
15
|
+
let responseBody;
|
|
16
|
+
res.json = (body) => {
|
|
17
|
+
responseBody = body;
|
|
18
|
+
return originalJson(body);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
res.on("finish", () => {
|
|
22
|
+
if (res.statusCode >= 400) return;
|
|
23
|
+
const { message, ...rest } = responseBody || {};
|
|
24
|
+
const registro = rest[Object.keys(rest)[0]];
|
|
25
|
+
|
|
26
|
+
ControleAlteracao.create({
|
|
27
|
+
entidade,
|
|
28
|
+
acao,
|
|
29
|
+
origem,
|
|
30
|
+
usuario: { nome: req?.usuario?.nome, email: req?.usuario?.email },
|
|
31
|
+
idRegistro: registro?._id,
|
|
32
|
+
dadosAtualizados: registro,
|
|
33
|
+
}).catch((e) => console.log("ERRO AO REGISTRAR AÇÃO", e?.message));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
next();
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { mutationAudit };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const Helpers = require("../utils/helpers");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RBAC simples baseado em `req.usuario.tipo`. `roles` vazio => qualquer
|
|
5
|
+
* usuário autenticado. `admin` sempre passa.
|
|
6
|
+
*
|
|
7
|
+
* @param {string[]} roles papéis permitidos
|
|
8
|
+
*/
|
|
9
|
+
function rbac(roles = []) {
|
|
10
|
+
return (req, res, next) => {
|
|
11
|
+
if (!roles.length) return next();
|
|
12
|
+
const tipo = req.usuario?.tipo;
|
|
13
|
+
if (tipo === "admin" || roles.includes(tipo)) return next();
|
|
14
|
+
return Helpers.sendErrorResponse({
|
|
15
|
+
res,
|
|
16
|
+
statusCode: 403,
|
|
17
|
+
message: "Acesso negado para o seu perfil.",
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { rbac };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const Log = require("../models/Log");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Registra requisições mutantes (não-GET) na model interna Log.
|
|
5
|
+
* Captura status e corpo da resposta no `finish`.
|
|
6
|
+
*/
|
|
7
|
+
const requestLog = async (req, res, next) => {
|
|
8
|
+
if (req.method === "GET") return next();
|
|
9
|
+
|
|
10
|
+
const log = new Log({
|
|
11
|
+
usuario: req.usuario || null,
|
|
12
|
+
endpoint: req.originalUrl,
|
|
13
|
+
metodo: req.method,
|
|
14
|
+
ip: req.ip || req.headers["x-forwarded-for"] || req.connection?.remoteAddress,
|
|
15
|
+
correlationId: req.correlationId,
|
|
16
|
+
dadosRequisicao: req.body,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
res.on("finish", () => {
|
|
20
|
+
log.statusResposta = res.statusCode;
|
|
21
|
+
log.dadosResposta = res.locals.body || null;
|
|
22
|
+
log.save().catch(() => {});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
next();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
module.exports = requestLog;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const mongoose = require("mongoose");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Trilha de auditoria de mutações. Diferente da versão do CST, no Core
|
|
5
|
+
* `entidade` é uma string livre (o registry conhece o nome da model em
|
|
6
|
+
* runtime) e `acao`/`origem` aceitam os valores padrão sem enum rígido,
|
|
7
|
+
* porque o Core não pode enumerar o domínio do cliente.
|
|
8
|
+
*/
|
|
9
|
+
const ACOES_PADRAO = ["adicionado", "alterado", "excluido"];
|
|
10
|
+
const ORIGENS_PADRAO = ["form", "datagrid", "importacao", "api"];
|
|
11
|
+
|
|
12
|
+
const controleAlteracaoSchema = new mongoose.Schema({
|
|
13
|
+
dataHora: { type: Date, default: Date.now, required: true },
|
|
14
|
+
usuario: { nome: String, email: String },
|
|
15
|
+
entidade: { type: String, required: true },
|
|
16
|
+
acao: { type: String, required: true },
|
|
17
|
+
origem: { type: String, default: "api" },
|
|
18
|
+
idRegistro: { type: String },
|
|
19
|
+
dadosAtualizados: { type: Object },
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const ControleAlteracao =
|
|
23
|
+
mongoose.models.ControleAlteracao ||
|
|
24
|
+
mongoose.model("ControleAlteracao", controleAlteracaoSchema);
|
|
25
|
+
|
|
26
|
+
module.exports = ControleAlteracao;
|
|
27
|
+
module.exports.ACOES_PADRAO = ACOES_PADRAO;
|
|
28
|
+
module.exports.ORIGENS_PADRAO = ORIGENS_PADRAO;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const mongoose = require("mongoose");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Log de requisições mutantes. Model interna do Core — registrada
|
|
5
|
+
* automaticamente pelo requestLog middleware. Não é domínio do cliente.
|
|
6
|
+
*/
|
|
7
|
+
const logSchema = new mongoose.Schema(
|
|
8
|
+
{
|
|
9
|
+
usuario: { type: Object, required: false },
|
|
10
|
+
endpoint: { type: String, required: true },
|
|
11
|
+
metodo: { type: String, required: true },
|
|
12
|
+
ip: { type: String },
|
|
13
|
+
correlationId: { type: String },
|
|
14
|
+
dadosRequisicao: { type: Object },
|
|
15
|
+
dadosResposta: { type: Object },
|
|
16
|
+
statusResposta: { type: Number },
|
|
17
|
+
},
|
|
18
|
+
{ timestamps: true }
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
module.exports =
|
|
22
|
+
mongoose.models.Log || mongoose.model("Log", logSchema);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const mongoose = require("mongoose");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuração singleton da Central (chaves de integração, remetente, etc.).
|
|
5
|
+
* Model interna do Core. O cliente estende via `central.config.js`.
|
|
6
|
+
*/
|
|
7
|
+
const omieSchema = new mongoose.Schema(
|
|
8
|
+
{
|
|
9
|
+
id_conta_corrente: { type: Number },
|
|
10
|
+
codigo_categoria: { type: String },
|
|
11
|
+
},
|
|
12
|
+
{ _id: false }
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const sistemaSchema = new mongoose.Schema(
|
|
16
|
+
{
|
|
17
|
+
omie: omieSchema,
|
|
18
|
+
openIaKey: String,
|
|
19
|
+
appKey: String,
|
|
20
|
+
sendgrid_api_key: { type: String },
|
|
21
|
+
remetente: { type: { nome: String, email: String } },
|
|
22
|
+
},
|
|
23
|
+
{ timestamps: true }
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
module.exports =
|
|
27
|
+
mongoose.models.Sistema || mongoose.model("Sistema", sistemaSchema);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Registry — coração técnico do Core (Seção 17).
|
|
3
|
+
*
|
|
4
|
+
* Tudo que o dev declara via `define*` é registrado aqui. A partir do
|
|
5
|
+
* registry, o Core deriva: schemas Mongoose, CRUD, RBAC, OpenAPI/metadata,
|
|
6
|
+
* rotas /core/* e os hooks dos módulos opinativos.
|
|
7
|
+
*
|
|
8
|
+
* É um singleton de processo. `reset()` existe para testes.
|
|
9
|
+
*/
|
|
10
|
+
const store = {
|
|
11
|
+
models: new Map(), // name -> { name, mongooseModel, definition }
|
|
12
|
+
validations: new Map(), // modelName -> validateFn
|
|
13
|
+
triggers: new Map(), // modelName -> { before, after }
|
|
14
|
+
routes: [], // [{ basePath, build }]
|
|
15
|
+
collections: new Map(),
|
|
16
|
+
documents: new Map(),
|
|
17
|
+
pipelines: new Map(),
|
|
18
|
+
omieMappings: new Map(),
|
|
19
|
+
integrations: new Map(),
|
|
20
|
+
menus: [],
|
|
21
|
+
features: new Map(),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function registerModel(entry) {
|
|
25
|
+
if (store.models.has(entry.name)) {
|
|
26
|
+
throw new Error(`Model "${entry.name}" já registrada no Core.`);
|
|
27
|
+
}
|
|
28
|
+
store.models.set(entry.name, entry);
|
|
29
|
+
return entry;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getModel(name) {
|
|
33
|
+
return store.models.get(name);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function listModels() {
|
|
37
|
+
return [...store.models.values()];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function registerValidation(modelName, fn) {
|
|
41
|
+
store.validations.set(modelName, fn);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getValidation(modelName) {
|
|
45
|
+
return store.validations.get(modelName);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function registerTrigger(modelName, hooks) {
|
|
49
|
+
const current = store.triggers.get(modelName) || { before: [], after: [] };
|
|
50
|
+
if (hooks.before) current.before.push(hooks.before);
|
|
51
|
+
if (hooks.after) current.after.push(hooks.after);
|
|
52
|
+
store.triggers.set(modelName, current);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getTriggers(modelName) {
|
|
56
|
+
return store.triggers.get(modelName) || { before: [], after: [] };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function registerRoutes(entry) {
|
|
60
|
+
store.routes.push(entry);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function listRoutes() {
|
|
64
|
+
return store.routes;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const simpleRegister = (mapName) => (key, value) => {
|
|
68
|
+
store[mapName].set(key, value);
|
|
69
|
+
return value;
|
|
70
|
+
};
|
|
71
|
+
const simpleList = (mapName) => () => [...store[mapName].values()];
|
|
72
|
+
|
|
73
|
+
function registerMenu(item) {
|
|
74
|
+
store.menus.push(item);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function reset() {
|
|
78
|
+
store.models.clear();
|
|
79
|
+
store.validations.clear();
|
|
80
|
+
store.triggers.clear();
|
|
81
|
+
store.routes.length = 0;
|
|
82
|
+
store.collections.clear();
|
|
83
|
+
store.documents.clear();
|
|
84
|
+
store.pipelines.clear();
|
|
85
|
+
store.omieMappings.clear();
|
|
86
|
+
store.integrations.clear();
|
|
87
|
+
store.menus.length = 0;
|
|
88
|
+
store.features.clear();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
store,
|
|
93
|
+
registerModel,
|
|
94
|
+
getModel,
|
|
95
|
+
listModels,
|
|
96
|
+
registerValidation,
|
|
97
|
+
getValidation,
|
|
98
|
+
registerTrigger,
|
|
99
|
+
getTriggers,
|
|
100
|
+
registerRoutes,
|
|
101
|
+
listRoutes,
|
|
102
|
+
registerCollection: simpleRegister("collections"),
|
|
103
|
+
listCollections: simpleList("collections"),
|
|
104
|
+
registerDocument: simpleRegister("documents"),
|
|
105
|
+
listDocuments: simpleList("documents"),
|
|
106
|
+
registerPipeline: simpleRegister("pipelines"),
|
|
107
|
+
listPipelines: simpleList("pipelines"),
|
|
108
|
+
registerOmieMapping: simpleRegister("omieMappings"),
|
|
109
|
+
listOmieMappings: simpleList("omieMappings"),
|
|
110
|
+
registerIntegration: simpleRegister("integrations"),
|
|
111
|
+
listIntegrations: simpleList("integrations"),
|
|
112
|
+
registerFeature: simpleRegister("features"),
|
|
113
|
+
listFeatures: simpleList("features"),
|
|
114
|
+
registerMenu,
|
|
115
|
+
listMenus: () => store.menus,
|
|
116
|
+
reset,
|
|
117
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
exports.sendResponse = ({ res, statusCode = 200, message = "OK", ...rest }) => {
|
|
2
|
+
res.status(statusCode).send({ message, ...rest });
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
exports.sendErrorResponse = ({ res, statusCode = 500, message, error }) => {
|
|
6
|
+
res.status(statusCode).json({
|
|
7
|
+
message,
|
|
8
|
+
...(error ? { error } : {}),
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
exports.sendPaginatedResponse = ({
|
|
13
|
+
res,
|
|
14
|
+
statusCode = 200,
|
|
15
|
+
message = "OK",
|
|
16
|
+
results,
|
|
17
|
+
pagination: { currentPage, totalPages, totalItems, itemsPerPage },
|
|
18
|
+
}) => {
|
|
19
|
+
res.status(statusCode).json({
|
|
20
|
+
message,
|
|
21
|
+
results,
|
|
22
|
+
pagination: { currentPage, totalPages, totalItems, itemsPerPage },
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
exports.asyncHandler = (callback) => {
|
|
27
|
+
return (req, res, next) => {
|
|
28
|
+
Promise.resolve(callback(req, res, next)).catch((error) => {
|
|
29
|
+
// Erros de domínio (4xx) são esperados; só logamos os inesperados.
|
|
30
|
+
const status = error?.statusCode || 500;
|
|
31
|
+
if (status >= 500) console.log("🆘 [ERROR]:", error?.message || error);
|
|
32
|
+
next(error);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
exports.attempt = async (callback) => {
|
|
38
|
+
try {
|
|
39
|
+
const result = await callback();
|
|
40
|
+
return [result, null];
|
|
41
|
+
} catch (err) {
|
|
42
|
+
return [null, err];
|
|
43
|
+
}
|
|
44
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
|
|
4
|
+
const TEMPLATES_DIR = path.join(__dirname, "templates");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Defaults dos manifests. São sobrescritos pelo `central.manifest.json`
|
|
8
|
+
* do consumidor. O dev não escreve YAML — só ajusta esses valores.
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULTS = {
|
|
11
|
+
SERVICE_NAME: "central-oon",
|
|
12
|
+
NAMESPACE: "default",
|
|
13
|
+
IMAGE: "ghcr.io/oondemand/central-oon",
|
|
14
|
+
IMAGE_TAG: "latest",
|
|
15
|
+
PORT: "4000",
|
|
16
|
+
REPLICAS: "1",
|
|
17
|
+
NODE_ENV: "production",
|
|
18
|
+
MEM_REQUEST: "128Mi",
|
|
19
|
+
MEM_LIMIT: "256Mi",
|
|
20
|
+
CPU_REQUEST: "0.1",
|
|
21
|
+
CPU_LIMIT: "0.2",
|
|
22
|
+
HPA_MIN: "1",
|
|
23
|
+
HPA_MAX: "3",
|
|
24
|
+
HPA_CPU_TARGET: "70",
|
|
25
|
+
HOST: "central.example.com",
|
|
26
|
+
DB_SERVER: "",
|
|
27
|
+
DB_NAME: "",
|
|
28
|
+
DB_AUTH_SOURCE: "",
|
|
29
|
+
DB_REPLICA_SET: "",
|
|
30
|
+
DB_TSL: "",
|
|
31
|
+
DB_USER: "",
|
|
32
|
+
DB_PASSWORD: "",
|
|
33
|
+
JWT_SECRET: "",
|
|
34
|
+
OMIE_APP_KEY: "",
|
|
35
|
+
OMIE_APP_SECRET: "",
|
|
36
|
+
MEUS_APPS_BACKEND_URL: "",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const K8S_TEMPLATES = [
|
|
40
|
+
"configmap.yaml",
|
|
41
|
+
"secret.yaml",
|
|
42
|
+
"deployment.yaml",
|
|
43
|
+
"service.yaml",
|
|
44
|
+
"ingress.yaml",
|
|
45
|
+
"hpa.yaml",
|
|
46
|
+
"certificate.yaml",
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
function interpolate(content, vars) {
|
|
50
|
+
return content.replace(/\$\{(\w+)\}/g, (_, key) =>
|
|
51
|
+
vars[key] !== undefined ? String(vars[key]) : ""
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function loadManifest(cwd = process.cwd()) {
|
|
56
|
+
const file = path.join(cwd, "central.manifest.json");
|
|
57
|
+
const user = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, "utf8")) : {};
|
|
58
|
+
return { ...DEFAULTS, ...user };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Renderiza os manifests k8s para `outDir` (default: ./k8s) a partir do
|
|
63
|
+
* central.manifest.json. Retorna a lista de arquivos gerados.
|
|
64
|
+
*/
|
|
65
|
+
function renderK8s({ cwd = process.cwd(), outDir } = {}) {
|
|
66
|
+
const vars = loadManifest(cwd);
|
|
67
|
+
const target = outDir || path.join(cwd, "k8s");
|
|
68
|
+
fs.mkdirSync(target, { recursive: true });
|
|
69
|
+
|
|
70
|
+
const written = [];
|
|
71
|
+
for (const tpl of K8S_TEMPLATES) {
|
|
72
|
+
const raw = fs.readFileSync(path.join(TEMPLATES_DIR, tpl), "utf8");
|
|
73
|
+
const out = interpolate(raw, vars);
|
|
74
|
+
const dest = path.join(target, tpl);
|
|
75
|
+
fs.writeFileSync(dest, out);
|
|
76
|
+
written.push(dest);
|
|
77
|
+
}
|
|
78
|
+
return written;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function renderDockerfile({ cwd = process.cwd() } = {}) {
|
|
82
|
+
const raw = fs.readFileSync(path.join(TEMPLATES_DIR, "Dockerfile"), "utf8");
|
|
83
|
+
const dest = path.join(cwd, "Dockerfile");
|
|
84
|
+
fs.writeFileSync(dest, raw);
|
|
85
|
+
return dest;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { renderK8s, renderDockerfile, loadManifest, DEFAULTS, TEMPLATES_DIR };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Dockerfile padrão de uma Central Oon (gerado pelo oonCore-back).
|
|
2
|
+
# Estágio 1: dependências de produção
|
|
3
|
+
FROM node:18 AS builder
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
COPY package*.json ./
|
|
6
|
+
RUN npm install --production
|
|
7
|
+
COPY src ./src
|
|
8
|
+
COPY central.config.js ./central.config.js
|
|
9
|
+
|
|
10
|
+
# Estágio 2: runtime
|
|
11
|
+
FROM node:18-alpine
|
|
12
|
+
ARG SERVICE_VERSION
|
|
13
|
+
ENV SERVICE_VERSION=${SERVICE_VERSION}
|
|
14
|
+
WORKDIR /app
|
|
15
|
+
COPY --from=builder /app .
|
|
16
|
+
RUN mkdir -p uploads
|
|
17
|
+
EXPOSE 4000
|
|
18
|
+
# Sobe a Central via CLI do Core.
|
|
19
|
+
CMD ["npx", "oonCore-back", "start"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
apiVersion: v1
|
|
2
|
+
kind: ConfigMap
|
|
3
|
+
metadata:
|
|
4
|
+
name: ${SERVICE_NAME}-config
|
|
5
|
+
namespace: ${NAMESPACE}
|
|
6
|
+
data:
|
|
7
|
+
NODE_ENV: "${NODE_ENV}"
|
|
8
|
+
SERVICE_NAME: "${SERVICE_NAME}"
|
|
9
|
+
SERVICE_VERSION: "${IMAGE_TAG}"
|
|
10
|
+
PORT: "${PORT}"
|
|
11
|
+
DB_SERVER: "${DB_SERVER}"
|
|
12
|
+
DB_NAME: "${DB_NAME}"
|
|
13
|
+
DB_AUTH_SOURCE: "${DB_AUTH_SOURCE}"
|
|
14
|
+
DB_REPLICA_SET: "${DB_REPLICA_SET}"
|
|
15
|
+
DB_TSL: "${DB_TSL}"
|
|
16
|
+
MEUS_APPS_BACKEND_URL: "${MEUS_APPS_BACKEND_URL}"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
apiVersion: apps/v1
|
|
2
|
+
kind: Deployment
|
|
3
|
+
metadata:
|
|
4
|
+
name: ${SERVICE_NAME}
|
|
5
|
+
namespace: ${NAMESPACE}
|
|
6
|
+
spec:
|
|
7
|
+
replicas: ${REPLICAS}
|
|
8
|
+
selector:
|
|
9
|
+
matchLabels:
|
|
10
|
+
app: ${SERVICE_NAME}
|
|
11
|
+
template:
|
|
12
|
+
metadata:
|
|
13
|
+
labels:
|
|
14
|
+
app: ${SERVICE_NAME}
|
|
15
|
+
spec:
|
|
16
|
+
containers:
|
|
17
|
+
- name: ${SERVICE_NAME}-container
|
|
18
|
+
image: ${IMAGE}:${IMAGE_TAG}
|
|
19
|
+
ports:
|
|
20
|
+
- containerPort: ${PORT}
|
|
21
|
+
resources:
|
|
22
|
+
requests:
|
|
23
|
+
memory: "${MEM_REQUEST}"
|
|
24
|
+
cpu: "${CPU_REQUEST}"
|
|
25
|
+
limits:
|
|
26
|
+
memory: "${MEM_LIMIT}"
|
|
27
|
+
cpu: "${CPU_LIMIT}"
|
|
28
|
+
envFrom:
|
|
29
|
+
- configMapRef:
|
|
30
|
+
name: ${SERVICE_NAME}-config
|
|
31
|
+
- secretRef:
|
|
32
|
+
name: ${SERVICE_NAME}-secret
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
apiVersion: autoscaling/v2
|
|
2
|
+
kind: HorizontalPodAutoscaler
|
|
3
|
+
metadata:
|
|
4
|
+
name: ${SERVICE_NAME}-hpa
|
|
5
|
+
namespace: ${NAMESPACE}
|
|
6
|
+
spec:
|
|
7
|
+
scaleTargetRef:
|
|
8
|
+
apiVersion: apps/v1
|
|
9
|
+
kind: Deployment
|
|
10
|
+
name: ${SERVICE_NAME}
|
|
11
|
+
minReplicas: ${HPA_MIN}
|
|
12
|
+
maxReplicas: ${HPA_MAX}
|
|
13
|
+
metrics:
|
|
14
|
+
- type: Resource
|
|
15
|
+
resource:
|
|
16
|
+
name: cpu
|
|
17
|
+
target:
|
|
18
|
+
type: Utilization
|
|
19
|
+
averageUtilization: ${HPA_CPU_TARGET}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
apiVersion: networking.k8s.io/v1
|
|
2
|
+
kind: Ingress
|
|
3
|
+
metadata:
|
|
4
|
+
name: ${SERVICE_NAME}-ingress
|
|
5
|
+
namespace: ${NAMESPACE}
|
|
6
|
+
annotations:
|
|
7
|
+
kubernetes.io/ingress.class: "nginx"
|
|
8
|
+
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
|
9
|
+
spec:
|
|
10
|
+
tls:
|
|
11
|
+
- hosts:
|
|
12
|
+
- ${HOST}
|
|
13
|
+
secretName: ${SERVICE_NAME}-tls
|
|
14
|
+
rules:
|
|
15
|
+
- host: ${HOST}
|
|
16
|
+
http:
|
|
17
|
+
paths:
|
|
18
|
+
- path: /
|
|
19
|
+
pathType: Prefix
|
|
20
|
+
backend:
|
|
21
|
+
service:
|
|
22
|
+
name: ${SERVICE_NAME}-service
|
|
23
|
+
port:
|
|
24
|
+
number: 80
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
apiVersion: v1
|
|
2
|
+
kind: Secret
|
|
3
|
+
metadata:
|
|
4
|
+
name: ${SERVICE_NAME}-secret
|
|
5
|
+
namespace: ${NAMESPACE}
|
|
6
|
+
type: Opaque
|
|
7
|
+
stringData:
|
|
8
|
+
DB_USER: "${DB_USER}"
|
|
9
|
+
DB_PASSWORD: "${DB_PASSWORD}"
|
|
10
|
+
JWT_SECRET: "${JWT_SECRET}"
|
|
11
|
+
OMIE_APP_KEY: "${OMIE_APP_KEY}"
|
|
12
|
+
OMIE_APP_SECRET: "${OMIE_APP_SECRET}"
|
package/src/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require("dotenv").config();
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* API pública do @oondemand/oon-core-back (Seção 2.4).
|
|
5
|
+
*
|
|
6
|
+
* O consumidor escreve apenas models/validations/triggers/hooks/mappings/
|
|
7
|
+
* pipelines/documents/integrations/routes e sobe com `start()` ou
|
|
8
|
+
* `oonCore-back start`.
|
|
9
|
+
*/
|
|
10
|
+
const { start } = require("./boot/startServer");
|
|
11
|
+
const { createApp } = require("./boot/createApp");
|
|
12
|
+
const { activate } = require("./boot/activation");
|
|
13
|
+
const define = require("./core/define");
|
|
14
|
+
const fields = require("./core/fields");
|
|
15
|
+
const registry = require("./core/registry");
|
|
16
|
+
const GenericError = require("./core/errors/GenericError");
|
|
17
|
+
const { omie } = require("./modules/omie");
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
// boot
|
|
21
|
+
start,
|
|
22
|
+
createApp,
|
|
23
|
+
activate,
|
|
24
|
+
|
|
25
|
+
// declaração de domínio
|
|
26
|
+
defineCentral: define.defineCentral,
|
|
27
|
+
defineModel: define.defineModel,
|
|
28
|
+
defineCollection: define.defineCollection,
|
|
29
|
+
defineDocument: define.defineDocument,
|
|
30
|
+
definePipeline: define.definePipeline,
|
|
31
|
+
defineOmieMapping: define.defineOmieMapping,
|
|
32
|
+
defineRoutes: define.defineRoutes,
|
|
33
|
+
defineValidation: define.defineValidation,
|
|
34
|
+
defineTrigger: define.defineTrigger,
|
|
35
|
+
|
|
36
|
+
// helpers
|
|
37
|
+
fields,
|
|
38
|
+
omie,
|
|
39
|
+
GenericError,
|
|
40
|
+
|
|
41
|
+
// avançado / introspecção
|
|
42
|
+
registry,
|
|
43
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const mongoose = require("mongoose");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Módulo Assistentes IA (Seção 13) — catálogo de assistentes, prompts e
|
|
5
|
+
* provedores. O dev só declara `ai: { ... }` na model/pipeline; o
|
|
6
|
+
* mascaramento e os guardrails ficam no Core.
|
|
7
|
+
*/
|
|
8
|
+
const assistenteSchema = new mongoose.Schema(
|
|
9
|
+
{
|
|
10
|
+
nome: { type: String, required: true },
|
|
11
|
+
provedor: { type: String, default: "anthropic" },
|
|
12
|
+
modelo: { type: String, default: "claude-haiku-4-5-20251001" },
|
|
13
|
+
prompt: String,
|
|
14
|
+
ativo: { type: Boolean, default: true },
|
|
15
|
+
},
|
|
16
|
+
{ timestamps: true }
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const Assistente =
|
|
20
|
+
mongoose.models.Assistente || mongoose.model("Assistente", assistenteSchema);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Mascara campos sensíveis antes de enviar a um provedor de IA.
|
|
24
|
+
*/
|
|
25
|
+
function mascarar(texto, segredos = []) {
|
|
26
|
+
let out = String(texto || "");
|
|
27
|
+
for (const s of segredos) {
|
|
28
|
+
if (s) out = out.split(s).join("«redacted»");
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { Assistente, mascarar };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const mongoose = require("mongoose");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Módulo Multi-moedas (Seção 14) — cadastro/cotação/conversão. As models
|
|
5
|
+
* usam os helpers `fields.currency*`. Aqui ficam a model interna de Moeda
|
|
6
|
+
* e o serviço de conversão padrão.
|
|
7
|
+
*/
|
|
8
|
+
const moedaSchema = new mongoose.Schema(
|
|
9
|
+
{
|
|
10
|
+
codigo: { type: String, uppercase: true, minlength: 3, maxlength: 3, unique: true },
|
|
11
|
+
nome: String,
|
|
12
|
+
cotacao: { type: Number, default: 1 },
|
|
13
|
+
ativa: { type: Boolean, default: true },
|
|
14
|
+
},
|
|
15
|
+
{ timestamps: true }
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const Moeda = mongoose.models.Moeda || mongoose.model("Moeda", moedaSchema);
|
|
19
|
+
|
|
20
|
+
async function converter({ valor, de = "BRL", para = "BRL" }) {
|
|
21
|
+
if (de === para) return valor;
|
|
22
|
+
const [origem, destino] = await Promise.all([
|
|
23
|
+
Moeda.findOne({ codigo: de }),
|
|
24
|
+
Moeda.findOne({ codigo: para }),
|
|
25
|
+
]);
|
|
26
|
+
const cotacaoDe = origem?.cotacao || 1;
|
|
27
|
+
const cotacaoPara = destino?.cotacao || 1;
|
|
28
|
+
return (valor * cotacaoDe) / cotacaoPara;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { Moeda, converter };
|