From 0f69388e1ab06040a6ceda25cc96bcf7f475756b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Rialland?= Date: Wed, 7 Sep 2022 12:14:34 +0200 Subject: [PATCH] Add Plausible analytics on REST API --- api/package.json | 1 + api/source/index.ts | 5 +- api/source/plausible.ts | 69 +++++++++++++++++++++ api/source/rate-limiter.ts | 2 +- api/source/route/doc.ts | 2 + api/source/route/openapi.ts | 5 +- api/tsconfig.json | 5 +- yarn.lock | 120 +++++++++++++++++++++++++++++++++++- 8 files changed, 199 insertions(+), 10 deletions(-) create mode 100644 api/source/plausible.ts diff --git a/api/package.json b/api/package.json index d16bbfdeb..5b663d732 100644 --- a/api/package.json +++ b/api/package.json @@ -33,6 +33,7 @@ "@publicodes/api": "^1.0.0-beta.52", "@sentry/node": "^7.1.1", "@sentry/tracing": "^7.1.1", + "got": "^12.4.1", "ioredis": "^5.2.3", "koa": "^2.13.4", "koa-body": "^5.0.0", diff --git a/api/source/index.ts b/api/source/index.ts index e591a8406..808d99f8d 100644 --- a/api/source/index.ts +++ b/api/source/index.ts @@ -6,6 +6,7 @@ import rules from 'modele-social' import Engine from 'publicodes' import { catchErrors } from './errors.js' import openapi from './openapi.json' assert { type: 'json' } +import { plausibleMiddleware } from './plausible.js' import { rateLimiterMiddleware } from './rate-limiter.js' import { docRoutes } from './route/doc.js' import { openapiRoutes } from './route/openapi.js' @@ -39,11 +40,9 @@ app.use(cors()) router.use('/api/v1', docRoutes(), openapiRoutes(openapi)) -router.use(rateLimiterMiddleware) - const apiRoutes = publicodesAPI(new Engine(rules)) -router.use('/api/v1', apiRoutes) +router.use('/api/v1', plausibleMiddleware, rateLimiterMiddleware, apiRoutes) app.use(router.routes()) app.use(router.allowedMethods()) diff --git a/api/source/plausible.ts b/api/source/plausible.ts new file mode 100644 index 000000000..183236309 --- /dev/null +++ b/api/source/plausible.ts @@ -0,0 +1,69 @@ +import got, { RequestError } from 'got' +import { BaseContext, Next } from 'koa' + +interface PlausibleEvent { + eventName: string + props?: Record +} + +export const plausibleEvent = ( + ctx: BaseContext, + { eventName, props }: PlausibleEvent +) => { + const userAgent = ctx.headers['user-agent'] ?? '' + const xForwardedFor = + (Array.isArray(ctx.headers['x-forwarded-for']) + ? ctx.headers['x-forwarded-for'][0] + : ctx.headers['x-forwarded-for']) ?? '' + const referer = ctx.headers.referer ?? '' + const url = ctx.href + + return got('https://plausible.io/api/event', { + method: 'POST', + headers: { + 'user-agent': userAgent, + 'x-forwarded-for': xForwardedFor, + }, + json: { + domain: 'mon-entreprise.urssaf.fr/api', + name: eventName, + referer, + url, + props, + }, + }) +} + +export const plausibleMiddleware = async (ctx: BaseContext, next: Next) => { + const xxx = Date.now().toString() + console.time('requestA-' + xxx) + void plausibleEvent(ctx, { eventName: 'pageview' }) + .catch((err) => { + const error = err as RequestError + console.error(error.code, error.message) + }) + .then(() => { + console.timeEnd('requestA-' + xxx) + }) + console.timeLog('requestA-' + xxx) + + const result = (await next()) as unknown + + console.time('requestB-' + xxx) + void plausibleEvent(ctx, { + eventName: 'status', + props: { + status: ctx.status, + }, + }) + .catch((err) => { + const error = err as RequestError + console.error(error.code, error.message) + }) + .then(() => { + console.timeEnd('requestB-' + xxx) + }) + console.timeLog('requestB-' + xxx) + + return result +} diff --git a/api/source/rate-limiter.ts b/api/source/rate-limiter.ts index fbb1ca676..f6a2a06cb 100644 --- a/api/source/rate-limiter.ts +++ b/api/source/rate-limiter.ts @@ -44,7 +44,7 @@ export const rateLimiterMiddleware = async (ctx: BaseContext, next: Next) => { return } - await next() + return (await next()) as unknown } const isRateLimiterRes = (val: unknown): val is RateLimiterRes => { diff --git a/api/source/route/doc.ts b/api/source/route/doc.ts index 4afc7bae2..293bf9345 100644 --- a/api/source/route/doc.ts +++ b/api/source/route/doc.ts @@ -1,12 +1,14 @@ import Router from '@koa/router' import koaStatic from 'koa-static' import { absolutePath } from 'swagger-ui-dist' +import { plausibleMiddleware } from '../plausible.js' export const docRoutes = () => { const router = new Router() router.all( '/doc/(.*)', + plausibleMiddleware, async (ctx, next) => { const rewriteURL = (typeof ctx.url === 'string' && ctx.url.replace(/.*\/doc\//, '/')) || diff --git a/api/source/route/openapi.ts b/api/source/route/openapi.ts index 16fa30b77..366c0c41e 100644 --- a/api/source/route/openapi.ts +++ b/api/source/route/openapi.ts @@ -1,6 +1,7 @@ import Router from '@koa/router' -import { Context } from 'koa' import { openapi as publicodesOpenapi } from '@publicodes/api' +import { Context } from 'koa' +import { plausibleMiddleware } from '../plausible.js' import { mergeDeep } from '../utils.js' /** @@ -13,7 +14,7 @@ export const openapiRoutes = (customOpenapi: Record) => { const mergedOpenapi = mergeDeep(publicodesOpenapi, customOpenapi) - router.get('/openapi.json', (ctx: Context) => { + router.get('/openapi.json', plausibleMiddleware, (ctx: Context) => { ctx.type = 'application/json' ctx.body = mergedOpenapi }) diff --git a/api/tsconfig.json b/api/tsconfig.json index 2c873b5d5..4026611b5 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -30,7 +30,10 @@ "resolveJsonModule": true, /* Advanced Options */ - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + + // temporary disable lib check waiting a fix for https://github.com/sindresorhus/got/issues/2051 + "skipLibCheck": true }, "ts-node": { "esm": true diff --git a/yarn.lock b/yarn.lock index 00c901874..28de30b30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6392,6 +6392,13 @@ __metadata: languageName: node linkType: hard +"@sindresorhus/is@npm:^5.2.0": + version: 5.3.0 + resolution: "@sindresorhus/is@npm:5.3.0" + checksum: b31cebabcdece3d5322de2a4dbc8c0f004e04147a00f2606787bcaf5655ad4b1954f6727fc6914c524009b2b9a2cc01c42835b55f651ce69fd2a0083b60bb852 + languageName: node + linkType: hard + "@sindresorhus/slugify@npm:^1.1.0": version: 1.1.2 resolution: "@sindresorhus/slugify@npm:1.1.2" @@ -7926,6 +7933,15 @@ __metadata: languageName: node linkType: hard +"@szmarczak/http-timer@npm:^5.0.1": + version: 5.0.1 + resolution: "@szmarczak/http-timer@npm:5.0.1" + dependencies: + defer-to-connect: ^2.0.1 + checksum: fc9cb993e808806692e4a3337c90ece0ec00c89f4b67e3652a356b89730da98bc824273a6d67ca84d5f33cd85f317dcd5ce39d8cc0a2f060145a608a7cb8ce92 + languageName: node + linkType: hard + "@testing-library/dom@npm:^8.3.0": version: 8.12.0 resolution: "@testing-library/dom@npm:8.12.0" @@ -8026,7 +8042,7 @@ __metadata: languageName: node linkType: hard -"@types/cacheable-request@npm:^6.0.1": +"@types/cacheable-request@npm:^6.0.1, @types/cacheable-request@npm:^6.0.2": version: 6.0.2 resolution: "@types/cacheable-request@npm:6.0.2" dependencies: @@ -10007,6 +10023,7 @@ __metadata: "@types/node": ^17.0.35 "@types/swagger-ui-dist": ^3.30.1 chai-http: ^4.3.0 + got: ^12.4.1 ioredis: ^5.2.3 koa: ^2.13.4 koa-body: ^5.0.0 @@ -11448,6 +11465,13 @@ __metadata: languageName: node linkType: hard +"cacheable-lookup@npm:^6.0.4": + version: 6.1.0 + resolution: "cacheable-lookup@npm:6.1.0" + checksum: 4e37afe897219b1035335b0765106a2c970ffa930497b43cac5000b860f3b17f48d004187279fae97e2e4cbf6a3693709b6d64af65279c7d6c8453321d36d118 + languageName: node + linkType: hard + "cacheable-request@npm:^2.1.1": version: 2.1.4 resolution: "cacheable-request@npm:2.1.4" @@ -11478,7 +11502,7 @@ __metadata: languageName: node linkType: hard -"cacheable-request@npm:^7.0.1": +"cacheable-request@npm:^7.0.1, cacheable-request@npm:^7.0.2": version: 7.0.2 resolution: "cacheable-request@npm:7.0.2" dependencies: @@ -13281,6 +13305,15 @@ __metadata: languageName: node linkType: hard +"decompress-response@npm:^6.0.0": + version: 6.0.0 + resolution: "decompress-response@npm:6.0.0" + dependencies: + mimic-response: ^3.1.0 + checksum: d377cf47e02d805e283866c3f50d3d21578b779731e8c5072d6ce8c13cc31493db1c2f6784da9d1d5250822120cefa44f1deab112d5981015f2e17444b763812 + languageName: node + linkType: hard + "decompress-tar@npm:^4.0.0, decompress-tar@npm:^4.1.0, decompress-tar@npm:^4.1.1": version: 4.1.1 resolution: "decompress-tar@npm:4.1.1" @@ -13438,7 +13471,7 @@ __metadata: languageName: node linkType: hard -"defer-to-connect@npm:^2.0.0": +"defer-to-connect@npm:^2.0.0, defer-to-connect@npm:^2.0.1": version: 2.0.1 resolution: "defer-to-connect@npm:2.0.1" checksum: 8a9b50d2f25446c0bfefb55a48e90afd58f85b21bcf78e9207cd7b804354f6409032a1705c2491686e202e64fc05f147aa5aa45f9aa82627563f045937f5791b @@ -16138,6 +16171,13 @@ __metadata: languageName: node linkType: hard +"form-data-encoder@npm:^2.1.0": + version: 2.1.2 + resolution: "form-data-encoder@npm:2.1.2" + checksum: 5c6401e3ebd2ba2adfa151c9fbd1df5adae8b1dd7bca5b18ab25ad1f9b83e20ddffb6b9ff77f5006b93d7ec2032e22828c9543c0bd4b2bc5b05335c31a3ba5b9 + languageName: node + linkType: hard + "form-data@npm:^2.3.1, form-data@npm:^2.5.0": version: 2.5.1 resolution: "form-data@npm:2.5.1" @@ -16915,6 +16955,26 @@ __metadata: languageName: node linkType: hard +"got@npm:^12.4.1": + version: 12.4.1 + resolution: "got@npm:12.4.1" + dependencies: + "@sindresorhus/is": ^5.2.0 + "@szmarczak/http-timer": ^5.0.1 + "@types/cacheable-request": ^6.0.2 + cacheable-lookup: ^6.0.4 + cacheable-request: ^7.0.2 + decompress-response: ^6.0.0 + form-data-encoder: ^2.1.0 + get-stream: ^6.0.1 + http2-wrapper: ^2.1.10 + lowercase-keys: ^3.0.0 + p-cancelable: ^3.0.0 + responselike: ^3.0.0 + checksum: 56a150ea144c951868d0fb3d288bbcbc2c0d3f8f9c200c6fd60ffdc921f6aeb26d140f57fb973ab89e1aeb03a92c4b6422743e5a74d3cb081715901e249f2640 + languageName: node + linkType: hard + "got@npm:^8.3.1": version: 8.3.2 resolution: "got@npm:8.3.2" @@ -17588,6 +17648,16 @@ __metadata: languageName: node linkType: hard +"http2-wrapper@npm:^2.1.10": + version: 2.1.11 + resolution: "http2-wrapper@npm:2.1.11" + dependencies: + quick-lru: ^5.1.1 + resolve-alpn: ^1.2.0 + checksum: 5da05aa2c77226ac9cc82c616383f59c8f31b79897b02ecbe44b09714be1fca1f21bb184e672a669ca2830eefea4edac5f07e71c00cb5a8c5afec8e5a20cfaf7 + languageName: node + linkType: hard + "https-browserify@npm:^1.0.0": version: 1.0.0 resolution: "https-browserify@npm:1.0.0" @@ -20208,6 +20278,13 @@ __metadata: languageName: node linkType: hard +"lowercase-keys@npm:^3.0.0": + version: 3.0.0 + resolution: "lowercase-keys@npm:3.0.0" + checksum: 67a3f81409af969bc0c4ca0e76cd7d16adb1e25aa1c197229587eaf8671275c8c067cd421795dbca4c81be0098e4c426a086a05e30de8a9c587b7a13c0c7ccc5 + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -20764,6 +20841,13 @@ __metadata: languageName: node linkType: hard +"mimic-response@npm:^3.1.0": + version: 3.1.0 + resolution: "mimic-response@npm:3.1.0" + checksum: 25739fee32c17f433626bf19f016df9036b75b3d84a3046c7d156e72ec963dd29d7fc8a302f55a3d6c5a4ff24259676b15d915aad6480815a969ff2ec0836867 + languageName: node + linkType: hard + "min-document@npm:^2.19.0": version: 2.19.0 resolution: "min-document@npm:2.19.0" @@ -22161,6 +22245,13 @@ __metadata: languageName: node linkType: hard +"p-cancelable@npm:^3.0.0": + version: 3.0.0 + resolution: "p-cancelable@npm:3.0.0" + checksum: 2b5ae34218f9c2cf7a7c18e5d9a726ef9b165ef07e6c959f6738371509e747334b5f78f3bcdeb03d8a12dcb978faf641fd87eb21486ed7d36fb823b8ddef3219 + languageName: node + linkType: hard + "p-event@npm:^2.1.0": version: 2.3.1 resolution: "p-event@npm:2.3.1" @@ -23495,6 +23586,13 @@ __metadata: languageName: node linkType: hard +"quick-lru@npm:^5.1.1": + version: 5.1.1 + resolution: "quick-lru@npm:5.1.1" + checksum: a516faa25574be7947969883e6068dbe4aa19e8ef8e8e0fd96cddd6d36485e9106d85c0041a27153286b0770b381328f4072aa40d3b18a19f5f7d2b78b94b5ed + languageName: node + linkType: hard + "quick-temp@npm:^0.1.8": version: 0.1.8 resolution: "quick-temp@npm:0.1.8" @@ -24549,6 +24647,13 @@ __metadata: languageName: node linkType: hard +"resolve-alpn@npm:^1.2.0": + version: 1.2.1 + resolution: "resolve-alpn@npm:1.2.1" + checksum: f558071fcb2c60b04054c99aebd572a2af97ef64128d59bef7ab73bd50d896a222a056de40ffc545b633d99b304c259ea9d0c06830d5c867c34f0bfa60b8eae0 + languageName: node + linkType: hard + "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -24691,6 +24796,15 @@ __metadata: languageName: node linkType: hard +"responselike@npm:^3.0.0": + version: 3.0.0 + resolution: "responselike@npm:3.0.0" + dependencies: + lowercase-keys: ^3.0.0 + checksum: e0cc9be30df4f415d6d83cdede3c5c887cd4a73e7cc1708bcaab1d50a28d15acb68460ac5b02bcc55a42f3d493729c8856427dcf6e57e6e128ad05cba4cfb95e + languageName: node + linkType: hard + "restore-cursor@npm:^2.0.0": version: 2.0.0 resolution: "restore-cursor@npm:2.0.0"