diff --git a/package.json b/package.json index 46b2419..1808889 100644 --- a/package.json +++ b/package.json @@ -27,12 +27,14 @@ "@nestjs/jwt": "^10.1.1", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.1.11", + "@willsoto/nestjs-prometheus": "^6.0.0", "axios": "^1.5.0", "bcrypt": "^5.1.1", "cache-manager": "^5.2.3", "hbs": "^4.2.0", "minio": "^7.1.3", "open-graph-scraper": "^6.3.0", + "prom-client": "^15.0.0", "ramda": "^0.29.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", diff --git a/src/app.module.ts b/src/app.module.ts index 1690271..34e13ca 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -13,6 +13,7 @@ import { IrcbotModule } from './ircbot/ircbot.module'; import { IrcbotService } from './ircbot/ircbot.service'; import { DinosaurwetModule } from './dinosaurwet/dinosaurwet.module'; import { OgScraperModule } from './ogscraper/ogscraper.module'; +import { PrometheusModule } from '@willsoto/nestjs-prometheus'; @Module({ imports: [ @@ -20,6 +21,11 @@ import { OgScraperModule } from './ogscraper/ogscraper.module'; isGlobal: true, load: [configuration], }), + PrometheusModule.register({ + defaultLabels: { + app: 'us.dev api', + }, + }), CacheModule.register({ isGlobal: true }), ParkioModule, IswordModule, diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 8183d69..d97aa63 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -10,8 +10,9 @@ import { } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthGuard } from './auth.guard'; -import { ApiBearerAuth } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +@ApiTags('auth') @Controller('auth') export class AuthController { constructor(private authService: AuthService) {} @@ -24,8 +25,8 @@ export class AuthController { @UseGuards(AuthGuard) @Post('hash') - hash(@Body() hashDto: {pass: string, rounds?: number}) { - return this.authService.hash(hashDto.pass, hashDto.rounds) + hash(@Body() hashDto: { pass: string; rounds?: number }) { + return this.authService.hash(hashDto.pass, hashDto.rounds); } @UseGuards(AuthGuard) diff --git a/src/dinosaurwet/dinosaurwet.controller.ts b/src/dinosaurwet/dinosaurwet.controller.ts index 9a74729..178cd08 100644 --- a/src/dinosaurwet/dinosaurwet.controller.ts +++ b/src/dinosaurwet/dinosaurwet.controller.ts @@ -2,6 +2,9 @@ import { Controller, Get, Res } from '@nestjs/common'; import { Response } from 'express'; import { identity, prop } from 'ramda'; import * as xml from 'xml'; +import { InjectMetric } from '@willsoto/nestjs-prometheus'; +import { Gauge } from 'prom-client'; +import { ApiTags } from '@nestjs/swagger'; const episodeFiles: [number, string][] = [ [27544345, '01-Into-the-Big-Wet.mp3'], @@ -60,6 +63,34 @@ const buildFeeds = ( episodes: Episode[], pubDateFn: (index: number) => Date = () => startDate, ) => { + const episodeItems = episodes + .filter((episode: Episode, index: number) => pubDateFn(index) <= new Date()) + .map(({ title, url, length, fakeReleaseDate }, index) => ({ + item: [ + { title }, + { guid: url }, + { pubDate: pubDateFn(index).toUTCString() }, + { + 'itunes:image': { + _attr: { + href: dinosaurImage, + }, + }, + }, + { 'itunes:episode': index + 1 }, + { + enclosure: [ + { + _attr: { + url, + type: 'audio/mpeg', + length, + }, + }, + ], + }, + ], + })); const feedObject = { rss: [ { @@ -100,48 +131,24 @@ const buildFeeds = ( 'A recreation of Dinosaur Wet as an RSS feed for listeners-with-licenses enjoyment.', }, { language: 'en-US' }, - ...episodes - .filter( - (episode: Episode, index: number) => - pubDateFn(index) <= new Date(), - ) - .map(({ title, url, length, fakeReleaseDate }, index) => ({ - item: [ - { title }, - { guid: url }, - { pubDate: pubDateFn(index).toUTCString() }, - { - 'itunes:image': { - _attr: { - href: dinosaurImage, - }, - }, - }, - { 'itunes:episode': index + 1 }, - { - enclosure: [ - { - _attr: { - url, - type: 'audio/mpeg', - length, - }, - }, - ], - }, - ], - })), + ...episodeItems, ], }, ], }; const feed = '' + xml(feedObject); - return feed; + return { feed, episodeItems }; }; +@ApiTags('dinosaurwet') @Controller('dinosaurwet') export class DinosaurwetController { + constructor( + @InjectMetric('weekly_count') public weeklyCount: Gauge, + @InjectMetric('daily_count') public dailyCount: Gauge, + ) {} + @Get('') getAllAtOnce(@Res() response: Response) { response.header('Content-Type', 'application/xml'); @@ -158,7 +165,8 @@ export class DinosaurwetController { (index: number) => new Date(new Date(startDate).setDate(startDate.getDate() + index)), ); - return response.send(feed); + this.dailyCount.set(feed.episodeItems.length); + return response.send(feed.feed); } @Get('weekly') @@ -170,6 +178,7 @@ export class DinosaurwetController { (index: number) => new Date(new Date(startDate).setDate(startDate.getDate() + index * 7)), ); - return response.send(feed); + this.weeklyCount.set(feed.episodeItems.length); + return response.send(feed.feed); } } diff --git a/src/dinosaurwet/dinosaurwet.module.ts b/src/dinosaurwet/dinosaurwet.module.ts index e487cd1..e2bacd0 100644 --- a/src/dinosaurwet/dinosaurwet.module.ts +++ b/src/dinosaurwet/dinosaurwet.module.ts @@ -1,7 +1,29 @@ import { Module } from '@nestjs/common'; import { DinosaurwetController } from './dinosaurwet.controller'; +import { + PrometheusModule, + makeGaugeProvider, +} from '@willsoto/nestjs-prometheus'; @Module({ - controllers: [DinosaurwetController] + imports: [ + PrometheusModule.register({ + customMetricPrefix: 'dinosaurwet', + defaultMetrics: { + enabled: false, + }, + }), + ], + controllers: [DinosaurwetController], + providers: [ + makeGaugeProvider({ + name: 'daily_count', + help: 'The current daily Dinosaur Wet episode count', + }), + makeGaugeProvider({ + name: 'weekly_count', + help: 'The current weekly Dinosaur Wet episode count', + }), + ], }) export class DinosaurwetModule {} diff --git a/yarn.lock b/yarn.lock index d9a10a8..c6818f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -849,6 +849,11 @@ consola "^2.15.0" node-fetch "^2.6.1" +"@opentelemetry/api@^1.4.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.6.0.tgz#de2c6823203d6f319511898bb5de7e70f5267e19" + integrity sha512-OWlrQAnWn9577PhVgqjUvMr1pg57Bc4jv0iL4w0PRuOSRvq67rvHW9Ie/dZVMvCzhSCB+UxhcY/PmCmFj33Q+g== + "@pkgr/utils@^2.3.1": version "2.4.2" resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" @@ -1370,6 +1375,11 @@ "@webassemblyjs/ast" "1.11.6" "@xtuc/long" "4.2.2" +"@willsoto/nestjs-prometheus@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@willsoto/nestjs-prometheus/-/nestjs-prometheus-6.0.0.tgz#6ef4d5d5dfb04ebe982aab6f3a7893974e89a669" + integrity sha512-Krmda5CT9xDPjab8Eqdqiwi7xkZSX60A5rEGVLEDjUG6J6Rw5SCZ/BPaRk+MxNGWzUrRkM7K5FtTg38vWIOt1Q== + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -1668,6 +1678,11 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bintrees@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" + integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw== + bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -4621,6 +4636,14 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +prom-client@^15.0.0: + version "15.0.0" + resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-15.0.0.tgz#067da874a2aa5d2e21bd5cdba9f24a8178bdab6a" + integrity sha512-UocpgIrKyA2TKLVZDSfm8rGkL13C19YrQBAiG3xo3aDFWcHedxRxI3z+cIcucoxpSO0h5lff5iv/SXoxyeopeA== + dependencies: + "@opentelemetry/api" "^1.4.0" + tdigest "^0.1.1" + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -5230,6 +5253,13 @@ tar@^6.1.11: mkdirp "^1.0.3" yallist "^4.0.0" +tdigest@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.2.tgz#96c64bac4ff10746b910b0e23b515794e12faced" + integrity sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA== + dependencies: + bintrees "1.0.2" + terser-webpack-plugin@^5.3.7: version "5.3.9" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1"