From b3880b2a7a2b7d1bcb79490ad21d9e445ac4913c Mon Sep 17 00:00:00 2001 From: nukeop <12746779+nukeop@users.noreply.github.com> Date: Sun, 13 Aug 2023 03:16:32 +0200 Subject: [PATCH] Cache requests for top streams --- package-lock.json | 35 +++++++++++++++++++ package.json | 1 + .../stream-mappings.e2e.spec.ts | 2 +- .../stream-mappings.service.ts | 19 ++++++++-- test/supabase-mock.ts | 2 +- tsconfig.json | 3 +- 6 files changed, 57 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0f58016..dfc5d4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "lodash": "^4.17.21", + "node-cache": "^5.1.2", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0" @@ -6354,6 +6355,25 @@ "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" }, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/node-cache/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -13323,6 +13343,21 @@ "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" }, + "node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "requires": { + "clone": "2.x" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==" + } + } + }, "node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", diff --git a/package.json b/package.json index adb2810..89b2f9d 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "lodash": "^4.17.21", + "node-cache": "^5.1.2", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0" diff --git a/src/stream-mappings/stream-mappings.e2e.spec.ts b/src/stream-mappings/stream-mappings.e2e.spec.ts index 3d2d8a5..6117fe0 100644 --- a/src/stream-mappings/stream-mappings.e2e.spec.ts +++ b/src/stream-mappings/stream-mappings.e2e.spec.ts @@ -1,7 +1,7 @@ import { INestApplication, ValidationPipe } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { Test } from '@nestjs/testing'; -import * as request from 'supertest'; +import request from 'supertest'; import { StreamMappingBuilder, SupabaseMock } from 'test/supabase-mock'; import { TestFixture } from 'test/test-fixture'; diff --git a/src/stream-mappings/stream-mappings.service.ts b/src/stream-mappings/stream-mappings.service.ts index 6c75353..e160b2d 100644 --- a/src/stream-mappings/stream-mappings.service.ts +++ b/src/stream-mappings/stream-mappings.service.ts @@ -1,6 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { SupabaseClient } from '@supabase/supabase-js'; import { groupBy, sortBy } from 'lodash'; +import NodeCache from 'node-cache'; import { CreateStreamMappingDto } from './dto/create-stream-mapping.dto'; import { DeleteStreamMappingDto } from './dto/delete-stream-maping.dto'; @@ -24,12 +25,15 @@ export type StreamIdWithScore = { @Injectable() export class StreamMappingsService { private client: SupabaseClient; + private cache: NodeCache; constructor() { this.client = new SupabaseClient( process.env.SUPABASE_URL ?? '', process.env.SUPABASE_KEY ?? '', ); + + this.cache = new NodeCache({ stdTTL: 60, checkperiod: 60 }); } private async findAllByArtistTitleAndSource( @@ -82,6 +86,13 @@ export class StreamMappingsService { source: 'Youtube', author_id?: string, ): Promise { + const cacheKey = `${artist}:${title}:${source}:`; + const cachedStream: StreamIdWithScore | undefined = + this.cache.get(cacheKey); + if (cachedStream) { + return cachedStream; + } + const streamMappings = await this.findAllByArtistTitleAndSource( artist, title, @@ -105,8 +116,9 @@ export class StreamMappingsService { (streamMapping) => streamMapping.author_id === author_id, ); + let topStream: StreamIdWithScore; if (verifiedByCurrentUser) { - return { + topStream = { ...(streamIdsWithScores.find( (streamIdWithScore) => streamIdWithScore.stream_id === verifiedByCurrentUser.stream_id, @@ -114,8 +126,11 @@ export class StreamMappingsService { self_verified: true, }; } else { - return sortBy(streamIdsWithScores, 'score').reverse()[0]; + topStream = sortBy(streamIdsWithScores, 'score').reverse()[0]; } + + this.cache.set(cacheKey, topStream); + return topStream; } async verifyTrack( diff --git a/test/supabase-mock.ts b/test/supabase-mock.ts index 4b5d350..cc525f0 100644 --- a/test/supabase-mock.ts +++ b/test/supabase-mock.ts @@ -1,5 +1,5 @@ import { matches } from 'lodash'; -import * as nock from 'nock'; +import nock from 'nock'; import { CreateStreamMappingDto } from 'src/stream-mappings/dto/create-stream-mapping.dto'; import { DeleteStreamMappingDto } from 'src/stream-mappings/dto/delete-stream-maping.dto'; diff --git a/tsconfig.json b/tsconfig.json index 1524e44..8b679ce 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,7 @@ "noImplicitAny": false, "strictBindCallApply": true, "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true } }