This commit is contained in:
Primakov Alexandr Alexandrovich 2024-11-04 18:47:17 +03:00
parent 10dc3e5ffe
commit ef35a8aa6c
12 changed files with 3819 additions and 0 deletions

26
.eslintrc.js Normal file
View File

@ -0,0 +1,26 @@
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
},
extends: 'eslint:recommended',
parserOptions: {
ecmaVersion: 12,
},
rules: {
'no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
'no-useless-escape': 0,
'no-empty': ['warn'],
},
globals: {
process: true,
},
}

3542
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

37
package.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "manager-bh",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "commonjs",
"scripts": {
"eslint": "eslint .",
"start": "cross-env NODE_ENV=\"development\" nodemon ./server/index.js",
"start:prod": "cross-env NODE_ENV=\"production\" nodemon ./server/index.js",
"up:prod": "cross-env NODE_ENV=\"production\" node ./server/index.js",
"deploy:d:stop": "docker compose down",
"deploy:d:build": "docker compose build",
"deploy:d:up": "docker compose up -d",
"redeploy": "npm run deploy:d:build && npm run deploy:d:stop && npm run deploy:d:up"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^20.17.6",
"eslint": "^7.30.0",
"nodemon": "^2.0.2"
},
"dependencies": {
"@brojs/mailer": "^1.4.0-alpha.0",
"@keycloak/keycloak-admin-client": "^26.0.5",
"cookie-parser": "^1.4.7",
"cross-env": "^7.0.3",
"dotenv": "^16.4.5",
"express": "^5.0.1",
"express-session": "^1.18.1",
"js-sha256": "^0.11.0",
"keycloak-connect": "^26.0.5",
"uuid": "^11.0.2"
}
}

View File

@ -0,0 +1,27 @@
{
"info": {
"_postman_id": "64d9b604-a7c8-46b3-bd31-8249649ec269",
"name": "manager",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "26924638"
},
"item": [
{
"name": "healthcheck",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{host}}/healthcheck",
"host": [
"{{host}}"
],
"path": [
"healthcheck"
]
}
},
"response": []
}
]
}

8
server/.serverrc.js Normal file
View File

@ -0,0 +1,8 @@
const path = require('path');
module.exports = {
dataPath: path.resolve(__dirname, 'data'),
maxLogCount: 3,
mailStepTimer: 3000,
protocol: 'http',
}

View File

@ -0,0 +1,40 @@
const assignParam = (dev, prod) =>
process.env.NODE_ENV !== 'production' ? dev : prod
const parseToken = (req, res, next) => {
req.isAdmin = assignParam(
true,
Boolean(
req?.kauth?.grant?.access_token?.content?.resource_access?.[
'manager-admin'
]?.roles?.includes('manager_admin'),
),
)
req.userId = assignParam(
process.env['KC.DEV.ID'],
req.kauth?.grant?.access_token?.content?.sub,
)
req.user = assignParam(
{ sub: '123', name: 'dev' },
req.kauth?.grant?.access_token?.content,
)
next()
}
const adminOnly = [
parseToken,
(req, res, next) => {
if (!req.isAdmin) {
// user's role is not authorized
return res.status(403).send({ code: 4, error: 'Access denied' })
}
next()
},
]
module.exports = {
adminOnly,
parseToken,
}

View File

@ -0,0 +1,18 @@
const { getAnswer } = require('../utils/common');
function errorHandler(err, req, res, _next) {
console.error(err);
if (typeof (err) === 'string') {
return res.status(400).json(getAnswer([{ message: err }]));
}
if (err.name === 'UnauthorizedError') {
// jwt authentication error
return res.status(401).json(getAnswer([{ message: 'Invalid Token' }]));
}
// default to 500 server error
return res.status(500).json(getAnswer([{ message: err?.message || 'Invalid Token' }]));
}
module.exports = errorHandler;

36
server/index.js Normal file
View File

@ -0,0 +1,36 @@
const express = require("express");
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
const session = require("express-session");
const app = express();
require("dotenv").config();
require("./mailer");
const errorHandler = require("./_helpers/error-handler");
const { keycloak } = require("./kc");
app.use(
process.env.NODE_ENV !== "production"
? (_, __, next) => next()
: keycloak.middleware()
);
app.use(cookieParser());
app.use(
session({ secret: "so secret", resave: true, saveUninitialized: true })
);
app.use('/api', require('./routes'));
app.use(bodyParser.json({ limit: "50mb" }));
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
app.use(errorHandler);
app.listen(process.env.MANAGER_PORT, () =>
console.log(`Listening on http://localhost:${process.env.MANAGER_PORT}`)
);
module.exports = app;

39
server/kc.js Normal file
View File

@ -0,0 +1,39 @@
const Keycloak = require("keycloak-connect");
const keycloak = new Keycloak(
{},
{
clientId: "microfrontend-admin",
bearerOnly: true,
serverUrl: "https://kc.bro-js.ru",
realm: "bro-js",
}
);
const kcAdminClientPromise = import("@keycloak/keycloak-admin-client")
.then(
async ({ default: KcAdminClient }) =>
new KcAdminClient({
baseUrl: "https://kc.bro-js.ru",
realmName: "bro-js",
})
)
.then(async (kcAdminClient) => {
const credentials = {
username: process.env['KC.TUZ.USERNAME'],
password: process.env['KC.TUZ.PASSWORD'],
grant_type: "password",
grantType: "password",
clientId: "microfrontend-admin",
}
await kcAdminClient.auth(credentials);
setInterval(() => kcAdminClient.auth(credentials), 3 * 24 * 60 *60 * 1000);
return kcAdminClient;
});
module.exports = {
keycloak,
kcAdminClientPromise,
}

16
server/mailer.js Normal file
View File

@ -0,0 +1,16 @@
const { AdminNotificationRequest, addToQueue, configs, init } = require('@brojs/mailer');
const rc = require('./.serverrc');
const pkg = require('../package');
init({
userName: process.env.SMTP_MAIL_LOGIN,
password: process.env.SMTP_MAIL_PASSWORD,
adminMails: process.env.MAIL_TO_1,
smtpConfig: configs.yandex,
queTimer: rc.mailStepTimer,
});
if (process.env.MAIL_TO_1 && process.env.NODE_ENV === 'production') {
addToQueue(new AdminNotificationRequest(`Деплой kc админки v${pkg.version} прошёл успешно (${process.env.ADMIN_FRONT_BASE_NAME}) (Время ${new Date().toLocaleString()})`));
}

8
server/routes/index.js Normal file
View File

@ -0,0 +1,8 @@
const router = require('express').Router();
const pkg = require('../../package.json')
router.get('/healthcheck', (req, res) => {
res.send({ ok: true, version: pkg.version })
})
module.exports = router;

22
server/utils/common.js Normal file
View File

@ -0,0 +1,22 @@
const getAnswer = (error, body = null, success = true) => {
if (error) {
return { success: false, error, body }
} else {
return { success, body }
}
}
function cleanId(entity) {
if (Array.isArray(entity)) {
return entity.map(cleanId)
}
const { _id, ...other } = entity;
return { ...other, id: _id };
}
module.exports = {
getAnswer,
cleanId,
}