beavuck-time 2.3.6 → 2.3.8
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/build/api.js +20 -0
- package/build/config/corsOptions.js +60 -0
- package/build/config/logger.js +38 -0
- package/build/controllers/nowController.js +30 -0
- package/build/errors/BeavuckTimeClientError.js +13 -0
- package/build/errors/BeavuckTimeServerError.js +16 -0
- package/build/errors/CorsError.js +13 -0
- package/build/errors/base/BeavuckTimeError.js +14 -0
- package/build/middlewares/corsMiddleware.js +34 -0
- package/build/middlewares/errorHandler.js +47 -0
- package/build/middlewares/notFoundHandler.js +9 -0
- package/build/middlewares/rateLimiter.js +16 -0
- package/build/models/now.js +14 -0
- package/build/routes/routes.js +54 -0
- package/build/server.js +65 -0
- package/build/services/nowService.js +11 -0
- package/build/types/isoTimestamp.js +4 -0
- package/build/utils/urlUtil.js +31 -0
- package/openapi/swagger.json +1 -1
- package/package.json +13 -3
- package/.env +0 -21
- package/.env.test +0 -30
- package/.gitlab/issue_templates/bug.md +0 -53
- package/.gitlab/issue_templates/enhancement.md +0 -53
- package/.gitlab/merge_request_templates/bug_fix.md +0 -9
- package/.gitlab/merge_request_templates/enhancement.md +0 -9
- package/.gitlab-ci.yml +0 -158
- package/.nvmrc +0 -1
- package/.sonarlint/connectedMode.json +0 -4
- package/DOCKERHUB_OVERVIEW.md +0 -185
- package/Dockerfile +0 -25
- package/HELP.md +0 -53
- package/api-tests/bruno/bruno.json +0 -9
- package/api-tests/bruno/collection.bru +0 -7
- package/api-tests/bruno/environments/local.bru +0 -8
- package/api-tests/bruno/now/nok/nok_no_origin.bru +0 -19
- package/api-tests/bruno/now/nok/nok_non_recognized_referrer.bru +0 -23
- package/api-tests/bruno/now/nok/nok_non_trusted_origin.bru +0 -23
- package/api-tests/bruno/now/nok/nok_non_truted_origin_nor_referrer.bru +0 -24
- package/api-tests/bruno/now/ok/ok_same_origin.bru +0 -23
- package/api-tests/bruno/now/ok/ok_same_referrer.bru +0 -23
- package/api-tests/bruno/now/ok/ok_trusted_origin.bru +0 -23
- package/eslint.config.mjs +0 -28
- package/sonar-project.properties +0 -9
- package/src/__tests__/api.test.ts +0 -98
- package/src/__tests__/middlewares/errorHandler.test.ts +0 -93
- package/src/__tests__/utils/urlUtil.test.ts +0 -53
- package/src/api.ts +0 -19
- package/src/config/corsOptions.ts +0 -63
- package/src/config/logger.ts +0 -38
- package/src/controllers/nowController.ts +0 -19
- package/src/errors/BeavuckTimeClientError.ts +0 -12
- package/src/errors/BeavuckTimeServerError.ts +0 -18
- package/src/errors/CorsError.ts +0 -12
- package/src/errors/base/BeavuckTimeError.ts +0 -12
- package/src/middlewares/corsMiddleware.ts +0 -32
- package/src/middlewares/errorHandler.ts +0 -57
- package/src/middlewares/notFoundHandler.ts +0 -8
- package/src/middlewares/rateLimiter.ts +0 -15
- package/src/models/now.ts +0 -14
- package/src/routes/routes.ts +0 -84
- package/src/server.ts +0 -83
- package/src/services/nowService.ts +0 -9
- package/src/types/isoTimestamp.ts +0 -8
- package/src/utils/urlUtil.ts +0 -29
- package/tsconfig.json +0 -38
- package/tsoa.json +0 -12
package/build/api.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/api.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.api = void 0;
|
|
8
|
+
const express_1 = __importDefault(require("express"));
|
|
9
|
+
const corsMiddleware_1 = require("./middlewares/corsMiddleware");
|
|
10
|
+
const errorHandler_1 = require("./middlewares/errorHandler");
|
|
11
|
+
const rateLimiter_1 = require("./middlewares/rateLimiter");
|
|
12
|
+
const notFoundHandler_1 = require("./middlewares/notFoundHandler");
|
|
13
|
+
const routes_1 = require("./routes/routes");
|
|
14
|
+
exports.api = (0, express_1.default)();
|
|
15
|
+
exports.api.disable('x-powered-by');
|
|
16
|
+
exports.api.use(corsMiddleware_1.corsMiddleware);
|
|
17
|
+
exports.api.use(errorHandler_1.errorHandler);
|
|
18
|
+
exports.api.use(rateLimiter_1.rateLimiter);
|
|
19
|
+
(0, routes_1.RegisterRoutes)(exports.api);
|
|
20
|
+
exports.api.use(notFoundHandler_1.notFoundHandler);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/config/corsOptions.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.corsOptions = void 0;
|
|
8
|
+
exports.getHostUrl = getHostUrl;
|
|
9
|
+
exports.initHostUrl = initHostUrl;
|
|
10
|
+
exports.getTrustedOrigins = getTrustedOrigins;
|
|
11
|
+
exports.initTrustedOrigins = initTrustedOrigins;
|
|
12
|
+
exports.isAllTrusted = isAllTrusted;
|
|
13
|
+
exports.isOriginAbsentOrTrusted = isOriginAbsentOrTrusted;
|
|
14
|
+
const CorsError_1 = require("../errors/CorsError");
|
|
15
|
+
const urlUtil_1 = require("../utils/urlUtil");
|
|
16
|
+
const http_status_codes_1 = require("http-status-codes");
|
|
17
|
+
const dotenvx_1 = __importDefault(require("@dotenvx/dotenvx"));
|
|
18
|
+
dotenvx_1.default.config();
|
|
19
|
+
exports.corsOptions = {
|
|
20
|
+
methods: ['GET', 'OPTIONS'],
|
|
21
|
+
optionsSuccessStatus: http_status_codes_1.StatusCodes.OK,
|
|
22
|
+
origin: (origin, callback) => {
|
|
23
|
+
if (isAllTrusted() ||
|
|
24
|
+
(0, urlUtil_1.isSameOrigin)(getHostUrl(), (0, urlUtil_1.tryParseUrl)(origin)) ||
|
|
25
|
+
isOriginAbsentOrTrusted(origin)) {
|
|
26
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
27
|
+
callback(null, true);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
callback(new CorsError_1.CorsError(origin), false);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
let HOST_URL;
|
|
35
|
+
let TRUSTED_ORIGINS;
|
|
36
|
+
function getHostUrl() {
|
|
37
|
+
if (!HOST_URL) {
|
|
38
|
+
initHostUrl();
|
|
39
|
+
}
|
|
40
|
+
return HOST_URL;
|
|
41
|
+
}
|
|
42
|
+
function initHostUrl() {
|
|
43
|
+
HOST_URL = new URL(process.env.BEAVUCK_TIME_HOST_URL);
|
|
44
|
+
}
|
|
45
|
+
function getTrustedOrigins() {
|
|
46
|
+
if (!TRUSTED_ORIGINS || TRUSTED_ORIGINS.length === 0) {
|
|
47
|
+
initTrustedOrigins();
|
|
48
|
+
}
|
|
49
|
+
return TRUSTED_ORIGINS;
|
|
50
|
+
}
|
|
51
|
+
function initTrustedOrigins() {
|
|
52
|
+
const trustedOrigins = process.env.BEAVUCK_TIME_TRUSTED_ORIGINS;
|
|
53
|
+
TRUSTED_ORIGINS = trustedOrigins.split(',');
|
|
54
|
+
}
|
|
55
|
+
function isAllTrusted() {
|
|
56
|
+
return getTrustedOrigins().includes('*');
|
|
57
|
+
}
|
|
58
|
+
function isOriginAbsentOrTrusted(origin) {
|
|
59
|
+
return !origin || getTrustedOrigins().includes(origin);
|
|
60
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/config/logger.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.logger = void 0;
|
|
8
|
+
const winston_1 = require("winston");
|
|
9
|
+
require("winston-daily-rotate-file");
|
|
10
|
+
const dotenvx_1 = __importDefault(require("@dotenvx/dotenvx"));
|
|
11
|
+
dotenvx_1.default.config({
|
|
12
|
+
ignore: ['MISSING_ENV_FILE'],
|
|
13
|
+
});
|
|
14
|
+
const LOGS_DIR = 'logs';
|
|
15
|
+
const LOG_LEVEL = process.env.BEAVUCK_TIME_LOG_LEVEL ?? 'info';
|
|
16
|
+
const MAX_LOG_FILES = process.env.BEAVUCK_TIME_MAX_LOG_FILES ?? 64;
|
|
17
|
+
const MAX_SIZE_LOG_FILES = process.env.BEAVUCK_TIME_MAX_SIZE_LOG_FILES ?? '1m';
|
|
18
|
+
const dailyRotateFileTransport = new winston_1.transports.DailyRotateFile({
|
|
19
|
+
filename: `${LOGS_DIR}/%DATE%-combined.log`,
|
|
20
|
+
datePattern: 'YYYY-MM-DD',
|
|
21
|
+
zippedArchive: true,
|
|
22
|
+
maxSize: MAX_SIZE_LOG_FILES,
|
|
23
|
+
maxFiles: MAX_LOG_FILES,
|
|
24
|
+
});
|
|
25
|
+
exports.logger = (0, winston_1.createLogger)({
|
|
26
|
+
level: LOG_LEVEL,
|
|
27
|
+
format: winston_1.format.combine(winston_1.format.timestamp(), winston_1.format.printf(({ timestamp, level, message }) => {
|
|
28
|
+
return `${timestamp} [${level.toUpperCase()}]: ${message}`;
|
|
29
|
+
})),
|
|
30
|
+
transports: [
|
|
31
|
+
new winston_1.transports.Console(),
|
|
32
|
+
new winston_1.transports.File({
|
|
33
|
+
filename: `${LOGS_DIR}/error.log`,
|
|
34
|
+
level: 'error',
|
|
35
|
+
}),
|
|
36
|
+
dailyRotateFileTransport,
|
|
37
|
+
],
|
|
38
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/controllers/nowController.ts
|
|
3
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
4
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
5
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
6
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
7
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.NowController = void 0;
|
|
11
|
+
const tsoa_1 = require("tsoa");
|
|
12
|
+
const nowService_1 = require("../services/nowService");
|
|
13
|
+
let NowController = class NowController extends tsoa_1.Controller {
|
|
14
|
+
/**
|
|
15
|
+
* Get the current time in ISO format, in UTC timezone
|
|
16
|
+
* @summary Get current time
|
|
17
|
+
*/
|
|
18
|
+
async getNow() {
|
|
19
|
+
return new nowService_1.NowService().get();
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
exports.NowController = NowController;
|
|
23
|
+
__decorate([
|
|
24
|
+
(0, tsoa_1.Tags)('now'),
|
|
25
|
+
(0, tsoa_1.Get)(),
|
|
26
|
+
(0, tsoa_1.OperationId)('getNow')
|
|
27
|
+
], NowController.prototype, "getNow", null);
|
|
28
|
+
exports.NowController = NowController = __decorate([
|
|
29
|
+
(0, tsoa_1.Route)('now')
|
|
30
|
+
], NowController);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/errors/BeavuckTimeClientError.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.BeavuckTimeClientError = void 0;
|
|
5
|
+
const BeavuckTimeError_1 = require("./base/BeavuckTimeError");
|
|
6
|
+
const http_status_codes_1 = require("http-status-codes");
|
|
7
|
+
class BeavuckTimeClientError extends BeavuckTimeError_1.BeavuckTimeError {
|
|
8
|
+
static baseMessage = 'Client Error';
|
|
9
|
+
constructor(message, code = http_status_codes_1.StatusCodes.BAD_REQUEST) {
|
|
10
|
+
super(`${BeavuckTimeClientError.baseMessage}: ${message}`, code);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.BeavuckTimeClientError = BeavuckTimeClientError;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/errors/BeavuckTimeServerError.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.BeavuckTimeServerError = void 0;
|
|
5
|
+
const BeavuckTimeError_1 = require("./base/BeavuckTimeError");
|
|
6
|
+
const http_status_codes_1 = require("http-status-codes");
|
|
7
|
+
class BeavuckTimeServerError extends BeavuckTimeError_1.BeavuckTimeError {
|
|
8
|
+
static baseMessage = 'Internal Server Error';
|
|
9
|
+
constructor(message, code = http_status_codes_1.StatusCodes.INTERNAL_SERVER_ERROR) {
|
|
10
|
+
super(`${BeavuckTimeServerError.baseMessage}: ${message}`, code);
|
|
11
|
+
}
|
|
12
|
+
static fromError(error) {
|
|
13
|
+
return error instanceof BeavuckTimeServerError ? error : (new BeavuckTimeServerError(error.message));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.BeavuckTimeServerError = BeavuckTimeServerError;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/errors/CorsError.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.CorsError = void 0;
|
|
5
|
+
const http_status_codes_1 = require("http-status-codes");
|
|
6
|
+
const BeavuckTimeClientError_1 = require("./BeavuckTimeClientError");
|
|
7
|
+
class CorsError extends BeavuckTimeClientError_1.BeavuckTimeClientError {
|
|
8
|
+
static baseMessage = 'Origin forbidden by CORS';
|
|
9
|
+
constructor(origin) {
|
|
10
|
+
super(`${CorsError.baseMessage}: ${origin}`, http_status_codes_1.StatusCodes.FORBIDDEN);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.CorsError = CorsError;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/errors/base/BeavuckTimeError.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.BeavuckTimeError = void 0;
|
|
5
|
+
class BeavuckTimeError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
constructor(message, code) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = this.constructor.name;
|
|
10
|
+
this.code = code;
|
|
11
|
+
Error.captureStackTrace(this, this.constructor);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.BeavuckTimeError = BeavuckTimeError;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/middlewares/corsMiddleware.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.corsMiddleware = void 0;
|
|
8
|
+
const cors_1 = __importDefault(require("cors"));
|
|
9
|
+
const urlUtil_1 = require("../utils/urlUtil");
|
|
10
|
+
const CorsError_1 = require("../errors/CorsError");
|
|
11
|
+
const logger_1 = require("../config/logger");
|
|
12
|
+
const BeavuckTimeServerError_1 = require("../errors/BeavuckTimeServerError");
|
|
13
|
+
const corsOptions_1 = require("../config/corsOptions");
|
|
14
|
+
const corsMiddleware = (req, res, next) => {
|
|
15
|
+
const HOST_URL = (0, urlUtil_1.tryParseUrl)(process.env.BEAVUCK_TIME_HOST_URL ?? '');
|
|
16
|
+
// 'referer' is a misspelling that was kept for compatibility: https://en.wikipedia.org/wiki/HTTP_referer
|
|
17
|
+
const referrerHeader = req.headers.referrer || req.headers.referer;
|
|
18
|
+
if (req.headers.origin) {
|
|
19
|
+
logger_1.logger.debug(`CORS request from ${req.headers.origin}`);
|
|
20
|
+
(0, cors_1.default)(corsOptions_1.corsOptions)(req, res, next);
|
|
21
|
+
}
|
|
22
|
+
else if (!HOST_URL) {
|
|
23
|
+
const noHostUrl = 'Host URL not set';
|
|
24
|
+
next(new BeavuckTimeServerError_1.BeavuckTimeServerError(noHostUrl));
|
|
25
|
+
}
|
|
26
|
+
else if ((0, urlUtil_1.isSameOrigin)(HOST_URL, (0, urlUtil_1.tryParseUrl)(referrerHeader ?? ''))) {
|
|
27
|
+
next();
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const badOriginAndOrReferrer = `origin: ${req.headers.origin}, referrer: ${referrerHeader}`;
|
|
31
|
+
next(new CorsError_1.CorsError(badOriginAndOrReferrer));
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
exports.corsMiddleware = corsMiddleware;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/middlewares/errorHandler.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.errorHandler = void 0;
|
|
5
|
+
const logger_1 = require("../config/logger");
|
|
6
|
+
const BeavuckTimeClientError_1 = require("../errors/BeavuckTimeClientError");
|
|
7
|
+
const BeavuckTimeServerError_1 = require("../errors/BeavuckTimeServerError");
|
|
8
|
+
const http_status_codes_1 = require("http-status-codes");
|
|
9
|
+
const tsoa_1 = require("tsoa");
|
|
10
|
+
const errorHandler = (err, req, res, next) => {
|
|
11
|
+
if (err instanceof tsoa_1.ValidateError) {
|
|
12
|
+
const logMsg = `Validation error on ${req.path}: ${err.fields}`;
|
|
13
|
+
const resStatus = http_status_codes_1.StatusCodes.UNPROCESSABLE_ENTITY;
|
|
14
|
+
const resMsgObj = { message: 'Validation Error', details: err.fields };
|
|
15
|
+
handleOtherError(res, logMsg, resStatus, resMsgObj);
|
|
16
|
+
}
|
|
17
|
+
else if (err instanceof BeavuckTimeClientError_1.BeavuckTimeClientError) {
|
|
18
|
+
handleBeavuckClientError(res, err);
|
|
19
|
+
}
|
|
20
|
+
else if (err instanceof BeavuckTimeServerError_1.BeavuckTimeServerError || err instanceof Error) {
|
|
21
|
+
handleBeavuckServerError(res, BeavuckTimeServerError_1.BeavuckTimeServerError.fromError(err));
|
|
22
|
+
}
|
|
23
|
+
else if (err) {
|
|
24
|
+
handleOtherError(res);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
next();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
exports.errorHandler = errorHandler;
|
|
31
|
+
function sendErrorResponse(res, err) {
|
|
32
|
+
res.status(err.code).json({ message: err.message });
|
|
33
|
+
}
|
|
34
|
+
function handleBeavuckClientError(res, err) {
|
|
35
|
+
logger_1.logger.warn(err);
|
|
36
|
+
sendErrorResponse(res, err);
|
|
37
|
+
}
|
|
38
|
+
function handleBeavuckServerError(res, err) {
|
|
39
|
+
logger_1.logger.error(err);
|
|
40
|
+
sendErrorResponse(res, err);
|
|
41
|
+
}
|
|
42
|
+
function handleOtherError(res, logMsg = `Unknown error`, resStatus = http_status_codes_1.StatusCodes.INTERNAL_SERVER_ERROR, resMsg = {
|
|
43
|
+
message: BeavuckTimeServerError_1.BeavuckTimeServerError.baseMessage,
|
|
44
|
+
}) {
|
|
45
|
+
logger_1.logger.error(`${logMsg}: ${JSON.stringify(resMsg)}`);
|
|
46
|
+
res.status(resStatus).json(resMsg);
|
|
47
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/middlewares/notFoundHandler.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.notFoundHandler = void 0;
|
|
5
|
+
const http_status_codes_1 = require("http-status-codes");
|
|
6
|
+
const notFoundHandler = (_req, res) => {
|
|
7
|
+
res.status(http_status_codes_1.StatusCodes.NOT_FOUND).send({ message: 'Not Found' });
|
|
8
|
+
};
|
|
9
|
+
exports.notFoundHandler = notFoundHandler;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/middlewares/rateLimiter.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.rateLimiter = void 0;
|
|
8
|
+
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
|
|
9
|
+
const NO_RATE_LIMIT = '-1';
|
|
10
|
+
const RATE_LIMIT = parseInt(process.env.BEAVUCK_TIME_RATE_LIMIT ?? NO_RATE_LIMIT, 10);
|
|
11
|
+
const SECONDS_IN_ONE_MINUTE = 60;
|
|
12
|
+
const MILLISECONDS_IN_ONE_SECOND = 1000;
|
|
13
|
+
const ONE_MINUTE = SECONDS_IN_ONE_MINUTE * MILLISECONDS_IN_ONE_SECOND;
|
|
14
|
+
exports.rateLimiter = RATE_LIMIT > 0 ?
|
|
15
|
+
(0, express_rate_limit_1.default)({ windowMs: ONE_MINUTE, limit: RATE_LIMIT })
|
|
16
|
+
: (_req, _res, next) => next();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/models/now.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.Now = void 0;
|
|
5
|
+
/**
|
|
6
|
+
* @example {"now": "2019-08-24T14:15:22Z"}
|
|
7
|
+
*/
|
|
8
|
+
class Now {
|
|
9
|
+
now;
|
|
10
|
+
constructor() {
|
|
11
|
+
this.now = new Date().toISOString();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.Now = Now;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RegisterRoutes = RegisterRoutes;
|
|
4
|
+
const runtime_1 = require("@tsoa/runtime");
|
|
5
|
+
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
|
6
|
+
const nowController_1 = require("./../controllers/nowController");
|
|
7
|
+
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
|
8
|
+
const models = {
|
|
9
|
+
"IsoTimestamp": {
|
|
10
|
+
"dataType": "refAlias",
|
|
11
|
+
"type": { "dataType": "string", "validators": {} },
|
|
12
|
+
},
|
|
13
|
+
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
|
14
|
+
"Now": {
|
|
15
|
+
"dataType": "refObject",
|
|
16
|
+
"properties": {
|
|
17
|
+
"now": { "ref": "IsoTimestamp", "required": true },
|
|
18
|
+
},
|
|
19
|
+
"additionalProperties": false,
|
|
20
|
+
},
|
|
21
|
+
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
|
22
|
+
};
|
|
23
|
+
const templateService = new runtime_1.ExpressTemplateService(models, { "noImplicitAdditionalProperties": "throw-on-extras", "bodyCoercion": true });
|
|
24
|
+
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
|
25
|
+
function RegisterRoutes(app) {
|
|
26
|
+
// ###########################################################################################################
|
|
27
|
+
// NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look
|
|
28
|
+
// Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa
|
|
29
|
+
// ###########################################################################################################
|
|
30
|
+
const argsNowController_getNow = {};
|
|
31
|
+
app.get('/now', ...((0, runtime_1.fetchMiddlewares)(nowController_1.NowController)), ...((0, runtime_1.fetchMiddlewares)(nowController_1.NowController.prototype.getNow)), async function NowController_getNow(request, response, next) {
|
|
32
|
+
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
|
33
|
+
let validatedArgs = [];
|
|
34
|
+
try {
|
|
35
|
+
validatedArgs = templateService.getValidatedArgs({ args: argsNowController_getNow, request, response });
|
|
36
|
+
const controller = new nowController_1.NowController();
|
|
37
|
+
await templateService.apiHandler({
|
|
38
|
+
methodName: 'getNow',
|
|
39
|
+
controller,
|
|
40
|
+
response,
|
|
41
|
+
next,
|
|
42
|
+
validatedArgs,
|
|
43
|
+
successStatus: undefined,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
return next(err);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
|
51
|
+
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
|
52
|
+
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
|
53
|
+
}
|
|
54
|
+
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
|
package/build/server.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// src/server.ts
|
|
4
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6
|
+
};
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.startServer = startServer;
|
|
9
|
+
const api_1 = require("./api");
|
|
10
|
+
const logger_1 = require("./config/logger");
|
|
11
|
+
const urlUtil_1 = require("./utils/urlUtil");
|
|
12
|
+
const dotenvx_1 = __importDefault(require("@dotenvx/dotenvx"));
|
|
13
|
+
dotenvx_1.default.config({
|
|
14
|
+
ignore: ['MISSING_ENV_FILE'],
|
|
15
|
+
});
|
|
16
|
+
function validateEnv({ hostUrl, trustedOrigins, }) {
|
|
17
|
+
if (!hostUrl || !(0, urlUtil_1.tryParseUrl)(hostUrl)) {
|
|
18
|
+
logger_1.logger.error('HOST_URL not set or is not a valid URL');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
if (!trustedOrigins) {
|
|
22
|
+
logger_1.logger.error('TRUSTED_ORIGINS not set');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function startServer(options = {}) {
|
|
27
|
+
const API_PORT = options.port ?? process.env.BEAVUCK_TIME_API_PORT ?? 3000;
|
|
28
|
+
const HOST_URL = options.hostUrl ?? process.env.BEAVUCK_TIME_HOST_URL ?? '';
|
|
29
|
+
const TRUSTED_ORIGINS = options.trustedOrigins ?? process.env.BEAVUCK_TIME_TRUSTED_ORIGINS ?? '';
|
|
30
|
+
validateEnv({ hostUrl: HOST_URL, trustedOrigins: TRUSTED_ORIGINS });
|
|
31
|
+
const server = api_1.api.listen(API_PORT, () => {
|
|
32
|
+
logger_1.logger.info(`
|
|
33
|
+
| | | | ,=.
|
|
34
|
+
| |__ ___ __ ___ ___ _ ___| | __ ,=""""==.__.=" o".___
|
|
35
|
+
| '_ \\ / _ \\/ _\` \\ \\ / / | | |/ __| |/ / ,=.==" ___/
|
|
36
|
+
| |_) | __/ (_| |\\ V /| |_| | (__| < ,==.," , , \\,===""
|
|
37
|
+
|_.__/ \\___|\\__,_| \\_/ \\__,_|\\___|_|\\_\\ < ,==) \\"'"=._.==) \\
|
|
38
|
+
\`=='' \`" \`"
|
|
39
|
+
|
|
40
|
+
Beavuck Time microservice started successfully
|
|
41
|
+
|
|
42
|
+
Ready on API port ${API_PORT} (if this is running in a container, this port number is internal to the container)
|
|
43
|
+
`);
|
|
44
|
+
});
|
|
45
|
+
server.on('error', (err) => {
|
|
46
|
+
logger_1.logger.error(`Beavuck Time Server error [${err.code}]: ${err.message}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
});
|
|
49
|
+
const gracefulShutdown = (signal) => {
|
|
50
|
+
logger_1.logger.info(`${signal} signal received: closing HTTP server`);
|
|
51
|
+
server.close(() => {
|
|
52
|
+
logger_1.logger.info('HTTP server closed');
|
|
53
|
+
process.exit(0);
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
const SIGTERM = 'SIGTERM';
|
|
57
|
+
const SIGINT = 'SIGINT';
|
|
58
|
+
process.on(SIGTERM, () => gracefulShutdown(SIGTERM));
|
|
59
|
+
process.on(SIGINT, () => gracefulShutdown(SIGINT));
|
|
60
|
+
return server;
|
|
61
|
+
}
|
|
62
|
+
// Support running as standalone binary
|
|
63
|
+
if (require.main === module) {
|
|
64
|
+
startServer();
|
|
65
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/services/nowService.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.NowService = void 0;
|
|
5
|
+
const now_1 = require("../models/now");
|
|
6
|
+
class NowService {
|
|
7
|
+
get() {
|
|
8
|
+
return new now_1.Now();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.NowService = NowService;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/utils/urlUtil.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.isSameOrigin = isSameOrigin;
|
|
5
|
+
exports.tryParseUrl = tryParseUrl;
|
|
6
|
+
const url_1 = require("url");
|
|
7
|
+
const logger_1 = require("../config/logger");
|
|
8
|
+
function isSameOrigin(thisUrl, thatUrl) {
|
|
9
|
+
logger_1.logger.debug(`Comparing origins: ${thisUrl?.origin} and ${thatUrl?.origin}`);
|
|
10
|
+
if (!thisUrl || !thatUrl)
|
|
11
|
+
return false;
|
|
12
|
+
if (thisUrl.origin === thatUrl.origin)
|
|
13
|
+
return true;
|
|
14
|
+
const isSameProtocol = thisUrl.protocol === thatUrl.protocol;
|
|
15
|
+
const isSameHost = thisUrl.hostname === thatUrl.hostname;
|
|
16
|
+
const isSamePort = thisUrl.port === thatUrl.port;
|
|
17
|
+
return isSameProtocol && isSameHost && isSamePort;
|
|
18
|
+
}
|
|
19
|
+
function tryParseUrl(urlString) {
|
|
20
|
+
if (!urlString) {
|
|
21
|
+
logger_1.logger.debug('Empty URL string');
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return new url_1.URL(urlString);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
logger_1.logger.warn(`Invalid URL string: ${urlString} (${error})`);
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
package/openapi/swagger.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beavuck-time",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.8",
|
|
4
4
|
"description": "Get time in ISO format, in UTC timezone, from a simple, lightweight node server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"time",
|
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
"author": "Alexis Drai",
|
|
22
22
|
"type": "commonjs",
|
|
23
23
|
"main": "build/api.js",
|
|
24
|
+
"files": [
|
|
25
|
+
"build/",
|
|
26
|
+
"openapi/swagger.json",
|
|
27
|
+
"package.json",
|
|
28
|
+
"CONTRIBUTING.md",
|
|
29
|
+
"README.md",
|
|
30
|
+
"UNLICENSE"
|
|
31
|
+
],
|
|
24
32
|
"bin": {
|
|
25
33
|
"beavuck-time": "build/server.js"
|
|
26
34
|
},
|
|
@@ -30,13 +38,15 @@
|
|
|
30
38
|
"up-minor": "npm version minor --no-git-tag-version",
|
|
31
39
|
"up-major": "npm version major --no-git-tag-version",
|
|
32
40
|
"update-dependencies": "ncu --format group && ncu -u && npm install --verbose && npm prune --verbose && npm audit fix --verbose",
|
|
41
|
+
"prepublishOnly": "npm install && npm run build",
|
|
33
42
|
"lint": "eslint --fix src/",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
34
44
|
"format": "prettier --write src/ --log-level warn",
|
|
35
|
-
"clean": "npm run lint && npm run format",
|
|
45
|
+
"clean": "npm run lint && npm run typecheck && npm run format",
|
|
36
46
|
"test": "DOTENV_CONFIG_PATH=./.env.test jest --coverage",
|
|
37
47
|
"build": "tsoa spec-and-routes && tsc",
|
|
38
48
|
"start": "node build/server.js",
|
|
39
|
-
"go": "npm install && npm run build && npm run clean && npm run test && npm start"
|
|
49
|
+
"go": "rm -rf build && npm install && npm run build && npm run clean && npm run test && npm start"
|
|
40
50
|
},
|
|
41
51
|
"dependencies": {
|
|
42
52
|
"@dotenvx/dotenvx": "^1.49.0",
|
package/.env
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# CORS
|
|
2
|
-
## URL of this API
|
|
3
|
-
BEAVUCK_TIME_HOST_URL=http://localhost:3000
|
|
4
|
-
## To allow requests from any origin, include * (not recommended)
|
|
5
|
-
BEAVUCK_TIME_TRUSTED_ORIGINS=http://localhost:8477
|
|
6
|
-
|
|
7
|
-
# Server
|
|
8
|
-
## Port of this API: in a container, this is the internal port -- outside of a container, this is the external port. Default is 3000
|
|
9
|
-
BEAVUCK_TIME_API_PORT=3000
|
|
10
|
-
|
|
11
|
-
# Rate limiting
|
|
12
|
-
## Requests per minute for each IP address. If not a strictly positive, "no limit". Default is -1
|
|
13
|
-
BEAVUCK_TIME_RATE_LIMIT=-1
|
|
14
|
-
|
|
15
|
-
# Logging
|
|
16
|
-
## Level of detail in logs: error, warn, info, http, verbose, debug, silly. Default is info
|
|
17
|
-
BEAVUCK_TIME_LOG_LEVEL=info
|
|
18
|
-
## Maximum number of logs to keep. This can be a number of files or number of days. If using days, add 'd' as the suffix. Default is 64
|
|
19
|
-
BEAVUCK_TIME_MAX_LOG_FILES=64
|
|
20
|
-
## Maximum size of the file after which it will rotate. This can be a number of bytes, or units of kb, mb, and gb. If using the units, add 'k', 'm', or 'g' as the suffix. The units need to directly follow the number. Default is 1m
|
|
21
|
-
BEAVUCK_TIME_MAX_SIZE_LOG_FILES=1m
|
package/.env.test
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
|
|
2
|
-
#/ public-key encryption for .env files /
|
|
3
|
-
#/ [how it works](https://dotenvx.com/encryption) /
|
|
4
|
-
#/----------------------------------------------------------/
|
|
5
|
-
DOTENV_PUBLIC_KEY_TEST="02d5c6b7efe5269d1fd3626adac3338f0971e58e2555e8e27c53a7e70047273a1c"
|
|
6
|
-
|
|
7
|
-
# Just testing dotenvx here. Not used in .env. Not useful either -- nothing secret in this file at any rate.
|
|
8
|
-
# To change any of those values, run `dotenvx set key value` (ex: `dotenvx set HOST_URL http://localhost:3000`)
|
|
9
|
-
|
|
10
|
-
# CORS
|
|
11
|
-
## URL of this API
|
|
12
|
-
BEAVUCK_TIME_HOST_URL=encrypted:BG5D1BHPO/UlcKh8c1s4s2YDqFQ0yIiduOKDHAsc9GNlm4/BRSPwxHNka+jJopZp++w+nfARNbSJwdgBoZvlbjujb/E8uJ2CX4erxmh1j+HBpmri2ocBa04NnJodMojPkcuxJqP3RSKfh61u+Fo1oTpCoZ8yYg==
|
|
13
|
-
## To allow requests from any origin, include * (not recommended)
|
|
14
|
-
BEAVUCK_TIME_TRUSTED_ORIGINS=encrypted:BE1fOSnJ8rnMU+A1orA9/Ujee4ypigUywnZ9g85l74XiNxEDoOdQmAIiSjYut+y/Y5b3VkqF+vBAgP++LYCMGTPoANWHgscZ+DAzsgqeFjsFp9WTKuWXxTeauI3HzLWI3v44o67oSZ1myWk9wsvc9WWwN4plrA==
|
|
15
|
-
|
|
16
|
-
# Server
|
|
17
|
-
## Port of this API: in a container, this is the internal port -- outside of a container, this is the external port. Default is 3000
|
|
18
|
-
BEAVUCK_TIME_API_PORT=encrypted:BGw4+UyXhxyfMzdxNjZBzTN/akEDL77i+XDqNRZA6w9ed7GYrQyYvuvEho+4VYir9T014sgLYRNeS09cR2mJV8pjyYWzHYhdXQyDeihUcNbLvIpfsZu7KH+aIeHaPx5YIRGJDSM=
|
|
19
|
-
|
|
20
|
-
# Rate limiting
|
|
21
|
-
## Requests per minute for each IP address. If not a strictly positive, "no limit". Default is -1
|
|
22
|
-
BEAVUCK_TIME_RATE_LIMIT=encrypted:BD1uTBsjTAjd+GYlZGM0S5DVCkmiOSEouDmLQj9XztlHpP2u8KyHxByIzUed8qkJXzbcf8zvTsANzY44V91m9UZZqXeZDOQlCqmn+ZpzmPDSA3VMUPzIdUpYF9DD7rWIgxvF
|
|
23
|
-
|
|
24
|
-
# Logging
|
|
25
|
-
## Level of detail in logs: error, warn, info, http, verbose, debug, silly. Default is info
|
|
26
|
-
BEAVUCK_TIME_LOG_LEVEL=encrypted:BGfqcxa9XJrMBk7U9QqkbfnXimot5dnz0kRroclEwtsY0JVc8VCw35idcSKpdS3jhCIxg0enNCZEbVMFMt6Q37yvYlIUqCKqk229moGTDSkQ2V/38pwnmzMS1LD/+nUH2yyFpWA=
|
|
27
|
-
## Maximum number of logs to keep. This can be a number of files or number of days. If using days, add 'd' as the suffix. Default is 64
|
|
28
|
-
BEAVUCK_TIME_MAX_LOG_FILES=encrypted:BET35n9Sfhqkdahr+JCwV/1sfX5zCE2FmH4lFFOi/WUhg9Wi+DIm2NH2WqfHcYIiWcRRnqpr3f2gnXwLsmRMcSydk7gxuERgA+mn0F2ygzbjTQUw+6l3snLgHKskKTuuA1rO
|
|
29
|
-
## Maximum size of the file after which it will rotate. This can be a number of bytes, or units of kb, mb, and gb. If using the units, add 'k', 'm', or 'g' as the suffix. The units need to directly follow the number. Default is 1m
|
|
30
|
-
BEAVUCK_TIME_MAX_SIZE_LOG_FILES=encrypted:BJLXlbtMDxs5di0o+FRr6Im9gYs3GayVwjWomavHzCychYr9JZyOyMTrBN8YrusYoHklvEoHuGfIkUB1m8ErDEl8eJlJvaPZr30+NLPujWSrVwb1B9v8lBon2TfrDQulqSX6
|