beavuck-time 2.1.21

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.
Files changed (53) hide show
  1. package/.env +21 -0
  2. package/.env.test +30 -0
  3. package/.gitlab/issue_templates/bug.md +53 -0
  4. package/.gitlab/issue_templates/enhancement.md +53 -0
  5. package/.gitlab/merge_request_templates/bug_fix.md +9 -0
  6. package/.gitlab/merge_request_templates/enhancement.md +9 -0
  7. package/.gitlab-ci.yml +149 -0
  8. package/.nvmrc +1 -0
  9. package/.sonarlint/connectedMode.json +4 -0
  10. package/CONTRIBUTING.md +57 -0
  11. package/DOCKERHUB_OVERVIEW.md +185 -0
  12. package/Dockerfile +25 -0
  13. package/HELP.md +53 -0
  14. package/README.md +215 -0
  15. package/UNLICENSE +24 -0
  16. package/api-tests/bruno/bruno.json +9 -0
  17. package/api-tests/bruno/collection.bru +7 -0
  18. package/api-tests/bruno/environments/local.bru +8 -0
  19. package/api-tests/bruno/now/nok/nok_no_origin.bru +19 -0
  20. package/api-tests/bruno/now/nok/nok_non_recognized_referrer.bru +23 -0
  21. package/api-tests/bruno/now/nok/nok_non_trusted_origin.bru +23 -0
  22. package/api-tests/bruno/now/nok/nok_non_truted_origin_nor_referrer.bru +24 -0
  23. package/api-tests/bruno/now/ok/ok_same_origin.bru +23 -0
  24. package/api-tests/bruno/now/ok/ok_same_referrer.bru +23 -0
  25. package/api-tests/bruno/now/ok/ok_trusted_origin.bru +23 -0
  26. package/build/api.js +20 -0
  27. package/eslint.config.mjs +28 -0
  28. package/openapi/swagger.json +75 -0
  29. package/package.json +79 -0
  30. package/sonar-project.properties +9 -0
  31. package/src/__tests__/api.test.ts +98 -0
  32. package/src/__tests__/middlewares/errorHandler.test.ts +93 -0
  33. package/src/__tests__/utils/urlUtil.test.ts +53 -0
  34. package/src/api.ts +19 -0
  35. package/src/config/corsOptions.ts +63 -0
  36. package/src/config/logger.ts +38 -0
  37. package/src/controllers/nowController.ts +19 -0
  38. package/src/errors/BeavuckTimeClientError.ts +12 -0
  39. package/src/errors/BeavuckTimeServerError.ts +18 -0
  40. package/src/errors/CorsError.ts +12 -0
  41. package/src/errors/base/BeavuckTimeError.ts +12 -0
  42. package/src/middlewares/corsMiddleware.ts +32 -0
  43. package/src/middlewares/errorHandler.ts +57 -0
  44. package/src/middlewares/notFoundHandler.ts +8 -0
  45. package/src/middlewares/rateLimiter.ts +15 -0
  46. package/src/models/now.ts +14 -0
  47. package/src/routes/routes.ts +84 -0
  48. package/src/server.ts +53 -0
  49. package/src/services/nowService.ts +9 -0
  50. package/src/types/isoTimestamp.ts +8 -0
  51. package/src/utils/urlUtil.ts +29 -0
  52. package/tsconfig.json +38 -0
  53. package/tsoa.json +12 -0
package/README.md ADDED
@@ -0,0 +1,215 @@
1
+ # ⏲️ Beavuck Time
2
+
3
+ ## 📊 Status
4
+
5
+ [![Quality gate](https://sonarcloud.io/api/project_badges/quality_gate?project=beavuck-services_time)](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
6
+
7
+ [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=beavuck-services_time&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
8
+ [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=beavuck-services_time&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
9
+
10
+ [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=beavuck-services_time&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
11
+ [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=beavuck-services_time&metric=bugs)](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
12
+
13
+ [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=beavuck-services_time&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
14
+ [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=beavuck-services_time&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
15
+ [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=beavuck-services_time&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
16
+
17
+ [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=beavuck-services_time&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
18
+ [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=beavuck-services_time&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
19
+
20
+ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=beavuck-services_time&metric=coverage)](https://sonarcloud.io/summary/new_code?id=beavuck-services_time)
21
+
22
+ ---
23
+
24
+ ## 💡 Why
25
+
26
+ You can't count on client devices to all be set up with the correct date and time.
27
+
28
+ But keeping track of time is usually busywork, not the business of your core APIs. And you probably don't want to flood
29
+ your own APIs, whenever you want to get an accurate timestamp for entities in your apps.
30
+
31
+ So you can set up this simple microservice, whose only job should be to answer the question: "what time is it right
32
+ now?"
33
+
34
+ ---
35
+
36
+ ## 🎯 What
37
+
38
+ Get the current time in ISO format, in UTC timezone.
39
+
40
+ This lightweight service focuses on one job.
41
+
42
+ It needs no persistence layer, is capable of handling multiple concurrent requests, and is protected by a simple
43
+ CORS config for security and performance reasons.
44
+
45
+ Dockerized for easy deployment and scaling.
46
+
47
+ ---
48
+
49
+ ## 🔍 Where
50
+
51
+ The code lives on [GitLab](https://gitlab.com/beavuck-services/time),
52
+ and the Docker image is hosted on [Docker Hub](https://hub.docker.com/r/beavuck/time)
53
+
54
+ ### 🦊 GitLab
55
+
56
+ You can find the code on GitLab, where, once you have read the [CONTRIBUTING.md](CONTRIBUTING.md) file, you can also
57
+ create issues and merge requests.
58
+
59
+ Feel free to fork the repo and make your own changes at will, as per the [UNLICENSE](UNLICENSE).
60
+
61
+ ### 🐳 Docker Hub
62
+
63
+ Most devs will only use Docker Hub for their purposes with this project, to use it as is as a dependency for their own
64
+ projects. On Docker Hub, while you're developing, you should use the `beavuck/time:latest` tag to always get the latest
65
+ version.
66
+
67
+ When the time comes to go to production, to protect yourself from surprise breaking changes, you should instead point to
68
+ specific minor version tags, such as `beavuck/time:2.0` : those will not get breaking changes, but they will get
69
+ security updates and bug fixes while they're active.
70
+
71
+ ---
72
+
73
+ ## ⚙️ Usage
74
+
75
+ ### 🪧 Set up (docker-compose example)
76
+
77
+ To run the service in a docker-compose environment, add this in your `docker-compose.yml`'s services section:
78
+
79
+ ```yaml
80
+ time:
81
+ image: beavuck/time:latest
82
+ ports:
83
+ - 'SOME_PORT_NUMBER:3000'
84
+ # HOST_PORT:CONTAINER_PORT (Since we are in a container, CONTAINER_PORT corresponds to the API_PORT variable below)
85
+ environment:
86
+ - HOST_URL: https://time-api.example.com
87
+ # HOST_URL: That API's URL. Essential for CORS config.
88
+ - TRUSTED_ORIGINS: https://my.app.com,https://my-other.app.com
89
+ # TRUSTED_ORIGINS: To allow requests from any origin, include * (not recommended). If empty, will only allow requests from the HOST_URL's origin. Defaults to the HOST_URL's origin
90
+ - API_PORT: 3000
91
+ # API_PORT: Optional. Internal port when in a container. Defaults to 3000
92
+ - RATE_LIMIT: 100
93
+ # RATE_LIMIT: Optional. Max allowed number of requests per minute for each IP address. If negative or 0, no limit. Defaults to no limit
94
+ - LOG_LEVEL: info
95
+ # LOG_LEVEL: Optional. Logging levels include error, warn, info, http, verbose, debug, silly. Defaults to info
96
+ - MAX_LOG_FILES: 64
97
+ # MAX_LOG_FILES: Optional. 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
98
+ - MAX_SIZE_LOG_FILES: 1m
99
+ # MAX_SIZE_LOG_FILES: Optional. 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
100
+ ```
101
+
102
+ Here's the simple docker compose file I used to test this service locally:
103
+
104
+ ```yaml
105
+ services:
106
+ time:
107
+ image: beavuck/time:latest
108
+ ports:
109
+ - '3000:3000'
110
+ environment:
111
+ HOST_URL: http://127.0.0.1:3000
112
+ TRUSTED_ORIGINS: http://127.0.0.1:8000,http://localhost:8000
113
+ LOG_LEVEL: debug
114
+ ```
115
+
116
+ When you're ready, just run your services with:
117
+
118
+ ```shell
119
+ docker compose up -d
120
+ ```
121
+
122
+ ### ✨ Using the service
123
+
124
+ Now, when you run:
125
+
126
+ ```shell
127
+ curl --location 'http://localhost:{{SOME_PORT_NUMBER}}/now' \
128
+ --header 'Origin: {{SOME_TRUSTED_ORIGIN}}'
129
+ ```
130
+
131
+ you should expect an answer such as:
132
+
133
+ ```json
134
+ {
135
+ "now": "2024-06-15T12:35:48.022Z"
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ ## 🛡️ CORS
142
+
143
+ This service is protected by a CORS policy, which you can configure by setting the `TRUSTED_ORIGINS` environment
144
+ variable.
145
+
146
+ When you use the API, keep in mind what roles these headers play:
147
+
148
+ | `"Origin:"` | `"Referrer:"`* | Result |
149
+ | -------------------------------------------- | ------------------------------ | ------ |
150
+ | Defined and API `TRUSTED_ORIGINS` set to `*` | Whatever | ✅ |
151
+ | Trusted | Whatever | ✅ |
152
+ | Same as this API's host | Whatever | ✅ |
153
+ | Not defined | Same as this API's host | ✅ |
154
+ | Defined and not trusted | Whatever | 🛑 |
155
+ | Not defined | Not defined | 🛑 |
156
+ | Not defined or not trusted | Different from this API's host | 🛑 |
157
+
158
+ *AKA `Referer` (sic).
159
+
160
+ ---
161
+
162
+ ## 📚 Use cases
163
+
164
+ ### Batch `POST`s
165
+
166
+ Suppose you're creating timestamped entities in your app, and you want to send them in a batch to your API. Your API
167
+ knows the current time when it gets the request, but not the creation time of each entity.
168
+
169
+ ```mermaid
170
+ sequenceDiagram
171
+ participant 📱 Client
172
+ participant 🖥 Core API
173
+ 📱 Client->>🖥 Core API: POST /entities [{...}, {...}, {...}]
174
+ ```
175
+
176
+ And querying your core API for the current time for each entity is a waste of resources -- that's why you're batching
177
+ the operation in the first place.
178
+
179
+ ```mermaid
180
+ sequenceDiagram
181
+ participant 📱 Client
182
+ participant 🖥 Core API
183
+ 📱 Client->>🖥 Core API: GET /now
184
+ 🖥 Core API->>📱 Client: {"now": "2024-06-15T12:35:48.022Z"}
185
+ 📱 Client->>🖥 Core API: GET /now
186
+ 🖥 Core API->>📱 Client: {"now": "2024-06-15T12:35:49.071Z"}
187
+ 📱 Client->>🖥 Core API: GET /now
188
+ 🖥 Core API->>📱 Client: {"now": "2024-06-15T12:35:49.243Z"}
189
+ 📱 Client->>🖥 Core API: POST /entities [{...}, {...}, {...}]
190
+ ```
191
+
192
+ So you can use this service to get the current time whenever you need it, and use that as the creation time for each
193
+ entity.
194
+
195
+ ```mermaid
196
+ sequenceDiagram
197
+ participant 📱 Client
198
+ participant 🕒 Time
199
+ participant 🖥 Core API
200
+ 📱 Client->>🕒 Time: GET /now
201
+ 🕒 Time->>📱 Client: {"now": "2024-06-15T12:35:48.022Z"}
202
+ 📱 Client->>🕒 Time: GET /now
203
+ 🕒 Time->>📱 Client: {"now": "2024-06-15T12:35:49.071Z"}
204
+ 📱 Client->>🕒 Time: GET /now
205
+ 🕒 Time->>📱 Client: {"now": "2024-06-15T12:35:49.243Z"}
206
+ 📱 Client->>🖥 Core API: POST /entities [{...}, {...}, {...}]
207
+ ```
208
+
209
+ ---
210
+
211
+ ## 📜 License
212
+
213
+ Have at it.
214
+
215
+ This project uses the Unlicense. See the [UNLICENSE](UNLICENSE) file for details.
package/UNLICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org>
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": "1",
3
+ "name": "Time",
4
+ "type": "collection",
5
+ "ignore": [
6
+ "node_modules",
7
+ ".git"
8
+ ]
9
+ }
@@ -0,0 +1,7 @@
1
+ meta {
2
+ name: Time
3
+ }
4
+
5
+ auth {
6
+ mode: none
7
+ }
@@ -0,0 +1,8 @@
1
+ vars {
2
+ BASE_URL: http://localhost:3000
3
+ TRUSTED_ORIGIN: http://localhost:8477
4
+ NON_TRUSTED_ORIGIN: http://localhost:9632
5
+ NON_RECOGNIZED_REFERRER: http://localhost:9236
6
+ RFC_3339_REGEX: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$
7
+ CORS_REGEX: [cC][oO][rR][sS]
8
+ }
@@ -0,0 +1,19 @@
1
+ meta {
2
+ name: nok_no_origin
3
+ type: http
4
+ seq: 2
5
+ }
6
+
7
+ get {
8
+ url: {{BASE_URL}}/now
9
+ body: none
10
+ auth: inherit
11
+ }
12
+
13
+ assert {
14
+ res.status: eq 403
15
+ res.body: isDefined
16
+ res.body.message: isDefined
17
+ res.body.message: matches {{CORS_REGEX}}
18
+ res.responseTime: lt 200
19
+ }
@@ -0,0 +1,23 @@
1
+ meta {
2
+ name: nok_non_recognized_referrer
3
+ type: http
4
+ seq: 4
5
+ }
6
+
7
+ get {
8
+ url: {{BASE_URL}}/now
9
+ body: none
10
+ auth: inherit
11
+ }
12
+
13
+ headers {
14
+ Referrer: {{NON_RECOGNIZED_REFERRER}}
15
+ }
16
+
17
+ assert {
18
+ res.status: eq 403
19
+ res.body: isDefined
20
+ res.body.message: isDefined
21
+ res.body.message: matches {{CORS_REGEX}}
22
+ res.responseTime: lt 200
23
+ }
@@ -0,0 +1,23 @@
1
+ meta {
2
+ name: nok_non_trusted_origin
3
+ type: http
4
+ seq: 1
5
+ }
6
+
7
+ get {
8
+ url: {{BASE_URL}}/now
9
+ body: none
10
+ auth: inherit
11
+ }
12
+
13
+ headers {
14
+ Origin: {{NON_TRUSTED_ORIGIN}}
15
+ }
16
+
17
+ assert {
18
+ res.status: eq 403
19
+ res.body: isDefined
20
+ res.body.message: isDefined
21
+ res.body.message: matches {{CORS_REGEX}}
22
+ res.responseTime: lt 200
23
+ }
@@ -0,0 +1,24 @@
1
+ meta {
2
+ name: nok_non_truted_origin_nor_referrer
3
+ type: http
4
+ seq: 3
5
+ }
6
+
7
+ get {
8
+ url: {{BASE_URL}}/now
9
+ body: none
10
+ auth: inherit
11
+ }
12
+
13
+ headers {
14
+ Origin: {{NON_TRUSTED_ORIGIN}}
15
+ Referrer: {{NON_RECOGNIZED_REFERRER}}
16
+ }
17
+
18
+ assert {
19
+ res.status: eq 403
20
+ res.body: isDefined
21
+ res.body.message: isDefined
22
+ res.body.message: matches {{CORS_REGEX}}
23
+ res.responseTime: lt 200
24
+ }
@@ -0,0 +1,23 @@
1
+ meta {
2
+ name: ok_same_origin
3
+ type: http
4
+ seq: 2
5
+ }
6
+
7
+ get {
8
+ url: {{BASE_URL}}/now
9
+ body: none
10
+ auth: inherit
11
+ }
12
+
13
+ headers {
14
+ Origin: {{BASE_URL}}
15
+ }
16
+
17
+ assert {
18
+ res.status: eq 200
19
+ res.body: isDefined
20
+ res.body.now: isDefined
21
+ res.body.now: matches {{RFC_3339_REGEX}}
22
+ res.responseTime: lt 200
23
+ }
@@ -0,0 +1,23 @@
1
+ meta {
2
+ name: ok_same_referrer
3
+ type: http
4
+ seq: 3
5
+ }
6
+
7
+ get {
8
+ url: {{BASE_URL}}/now
9
+ body: none
10
+ auth: inherit
11
+ }
12
+
13
+ headers {
14
+ Referrer: {{BASE_URL}}
15
+ }
16
+
17
+ assert {
18
+ res.status: eq 200
19
+ res.body: isDefined
20
+ res.body.now: isDefined
21
+ res.body.now: matches {{RFC_3339_REGEX}}
22
+ res.responseTime: lt 200
23
+ }
@@ -0,0 +1,23 @@
1
+ meta {
2
+ name: ok_trusted_origin
3
+ type: http
4
+ seq: 1
5
+ }
6
+
7
+ get {
8
+ url: {{BASE_URL}}/now
9
+ body: none
10
+ auth: inherit
11
+ }
12
+
13
+ headers {
14
+ Origin: {{TRUSTED_ORIGIN}}
15
+ }
16
+
17
+ assert {
18
+ res.status: eq 200
19
+ res.body: isDefined
20
+ res.body.now: isDefined
21
+ res.body.now: matches {{RFC_3339_REGEX}}
22
+ res.responseTime: lt 200
23
+ }
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,28 @@
1
+ // eslint.config.mjs
2
+
3
+ import globals from 'globals'
4
+ import pluginJs from '@eslint/js'
5
+ import tseslint from 'typescript-eslint'
6
+
7
+ /** @type {import('eslint').Linter.Config[]} */
8
+ export default [
9
+ {files: ['**/*.{js,mjs,cjs,ts}']},
10
+ {languageOptions: {globals: globals.browser}},
11
+ pluginJs.configs.recommended,
12
+ ...tseslint.configs.recommended,
13
+ {
14
+ rules: {
15
+ '@typescript-eslint/no-explicit-any': 'off',
16
+ 'no-restricted-syntax': [
17
+ 'error', {
18
+ selector: 'CallExpression[callee.object.name="console"]',
19
+ message: 'Don\'t use console -- use the configured logger instead',
20
+ },
21
+ 'error', {
22
+ selector: ':not(BinaryExpression:matches([operator="!=="], [operator="==="])) > Literal[value="null"]',
23
+ message: 'Usage of "null" is deprecated except when received from legacy APIs. Use "undefined" instead',
24
+ },
25
+ ],
26
+ },
27
+ },
28
+ ]
@@ -0,0 +1,75 @@
1
+ {
2
+ "openapi": "3.0.0",
3
+ "components": {
4
+ "examples": {},
5
+ "headers": {},
6
+ "parameters": {},
7
+ "requestBodies": {},
8
+ "responses": {},
9
+ "schemas": {
10
+ "IsoTimestamp": {
11
+ "type": "string",
12
+ "format": "date-time",
13
+ "description": "Stringified date-time in RFC 3339 format (often referred to as ISO 8601 format)\nSee [RFC 3339 section 5.6](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6)"
14
+ },
15
+ "Now": {
16
+ "properties": {
17
+ "now": {
18
+ "$ref": "#/components/schemas/IsoTimestamp"
19
+ }
20
+ },
21
+ "required": [
22
+ "now"
23
+ ],
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "example": {
27
+ "now": "2019-08-24T14:15:22Z"
28
+ }
29
+ }
30
+ },
31
+ "securitySchemes": {}
32
+ },
33
+ "info": {
34
+ "title": "beavuck-time",
35
+ "version": "2.1.21",
36
+ "description": "Get time in ISO format, in UTC timezone, from a simple, lightweight node server",
37
+ "license": {
38
+ "name": "Unlicense"
39
+ },
40
+ "contact": {
41
+ "name": "Alexis Drai"
42
+ }
43
+ },
44
+ "paths": {
45
+ "/now": {
46
+ "get": {
47
+ "operationId": "getNow",
48
+ "responses": {
49
+ "200": {
50
+ "description": "Ok",
51
+ "content": {
52
+ "application/json": {
53
+ "schema": {
54
+ "$ref": "#/components/schemas/Now"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ },
60
+ "description": "Get the current time in ISO format, in UTC timezone",
61
+ "summary": "Get current time",
62
+ "tags": [
63
+ "now"
64
+ ],
65
+ "security": [],
66
+ "parameters": []
67
+ }
68
+ }
69
+ },
70
+ "servers": [
71
+ {
72
+ "url": "/"
73
+ }
74
+ ]
75
+ }
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "beavuck-time",
3
+ "version": "2.1.21",
4
+ "description": "Get time in ISO format, in UTC timezone, from a simple, lightweight node server",
5
+ "keywords": [],
6
+ "homepage": "https://gitlab.com/beavuck-services/time",
7
+ "bugs": {
8
+ "url": "https://gitlab.com/beavuck-services/time/-/issues"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://gitlab.com/beavuck-services/time.git"
13
+ },
14
+ "license": "Unlicense",
15
+ "author": "Alexis Drai",
16
+ "type": "commonjs",
17
+ "main": "build/api.js",
18
+ "scripts": {
19
+ "run-integration-tests": "cd api-tests/bruno && npm install -g @usebruno/cli && bru run --env local --bail",
20
+ "up-patch": "npm version patch --no-git-tag-version",
21
+ "up-minor": "npm version minor --no-git-tag-version",
22
+ "up-major": "npm version major --no-git-tag-version",
23
+ "update-dependencies": "ncu --format group && ncu -u && npm install --verbose && npm prune --verbose && npm audit fix --verbose",
24
+ "lint": "eslint --fix src/",
25
+ "format": "prettier --write src/ --log-level warn",
26
+ "clean": "npm run lint && npm run format",
27
+ "test": "DOTENV_CONFIG_PATH=./.env.test jest --coverage",
28
+ "build": "tsoa spec-and-routes && tsc",
29
+ "start": "node build/server.js",
30
+ "go": "npm install && npm run build && npm run clean && npm run test && npm start"
31
+ },
32
+ "dependencies": {
33
+ "@dotenvx/dotenvx": "^1.48.0",
34
+ "cors": "^2.8.5",
35
+ "express": "^5.1.0",
36
+ "express-rate-limit": "^8.0.1",
37
+ "http-status-codes": "^2.3.0",
38
+ "joi": "^17.13.3",
39
+ "tsoa": "^6.6.0",
40
+ "winston": "^3.17.0",
41
+ "winston-daily-rotate-file": "^5.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@eslint/js": "^9.31.0",
45
+ "@types/cors": "^2.8.19",
46
+ "@types/express": "^5.0.3",
47
+ "@types/jest": "^30.0.0",
48
+ "@types/node": "^24.0.15",
49
+ "@types/supertest": "^6.0.3",
50
+ "eslint": "^9.31.0",
51
+ "globals": "^16.3.0",
52
+ "jest": "^30.0.4",
53
+ "nodemon": "^3.1.10",
54
+ "npm-check-updates": "^18.0.1",
55
+ "prettier": "3.6.2",
56
+ "supertest": "^7.1.3",
57
+ "ts-jest": "^29.4.0",
58
+ "ts-node": "^10.9.2",
59
+ "typescript": "^5.8.3",
60
+ "typescript-eslint": "^8.37.0"
61
+ },
62
+ "engines": {
63
+ "node": ">=18.20.8"
64
+ },
65
+ "prettier": {
66
+ "experimentalTernaries": true,
67
+ "semi": false,
68
+ "printWidth": 96,
69
+ "tabWidth": 2,
70
+ "useTabs": false,
71
+ "singleQuote": true,
72
+ "jsxSingleQuote": false,
73
+ "bracketSameLine": false,
74
+ "arrowParens": "avoid",
75
+ "endOfLine": "lf",
76
+ "bracketSpacing": false,
77
+ "trailingComma": "all"
78
+ }
79
+ }
@@ -0,0 +1,9 @@
1
+ sonar.projectKey=beavuck-services_time
2
+ sonar.organization=beavuck-services
3
+ sonar.sources=.
4
+ sonar.exclusions=**/node_modules/**, **/coverage/**, **/build/**, **/build/**, **/api-tests/**, *package*.json, src/routes/routes.ts
5
+ sonar.coverage.exclusions=src/__tests__/utils/urlUtil.test.ts, src/__tests__/middlewares/errorHandler.test.ts, src/__tests__/config/corsOptions.test.ts, src/__tests__/api.test.ts, src/config/logger.ts, src/server.ts, src/middlewares/rateLimiter.ts, src/routes/routes.ts
6
+ sonar.sourceEncoding=UTF-8
7
+ sonar.typescript.tsconfigPaths=tsconfig.json
8
+ sonar.coverage.inclusions=src/**/*.*
9
+ sonar.javascript.lcov.reportPaths=coverage/lcov.info