Add event caching, filtering, and metrics
This commit is contained in:
@@ -1,16 +1,26 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { Controller, Get, Logger, Query } from '@nestjs/common';
|
||||
import { FocoLiveService } from './foco-live.service';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||
import { InjectMetric } from '@willsoto/nestjs-prometheus';
|
||||
import { Gauge } from 'prom-client';
|
||||
|
||||
@ApiTags('foco-live')
|
||||
@Controller('foco-live')
|
||||
export class FocoLiveController {
|
||||
private readonly logger = new Logger(FocoLiveController.name);
|
||||
|
||||
constructor(
|
||||
private readonly focoLiveService: FocoLiveService,
|
||||
@InjectMetric('query_count') public queryCount: Gauge<string>,
|
||||
) { }
|
||||
|
||||
@Get('events')
|
||||
async getEvents() {
|
||||
return this.focoLiveService.getEvents();
|
||||
@ApiQuery({ name: 'venue', required: false })
|
||||
@ApiQuery({ name: 'before', required: false, description: "Filter events that are before the provided date, any date format" })
|
||||
@ApiQuery({ name: 'after', required: false, description: "Filter events that are after the provided date, any date format" })
|
||||
async getEvents(@Query('venue') venue?: string, @Query('before') before?: string, @Query('after') after?: string) {
|
||||
this.queryCount.inc();
|
||||
this.logger.verbose(`GET /foco-live/events?venue=${venue}&before=${before}&after=${after}`);
|
||||
return this.focoLiveService.getEvents({ venue, before: before ? new Date(before) : undefined, after: after ? new Date(after) : undefined });
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,33 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { FocoLiveController } from './foco-live.controller';
|
||||
import { FocoLiveService } from './foco-live.service';
|
||||
import { PrometheusModule, makeGaugeProvider } from '@willsoto/nestjs-prometheus';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CacheModule.register(),
|
||||
PrometheusModule.register({
|
||||
customMetricPrefix: 'foco_live',
|
||||
defaultMetrics: {
|
||||
enabled: false,
|
||||
},
|
||||
})
|
||||
],
|
||||
controllers: [FocoLiveController],
|
||||
providers: [FocoLiveService]
|
||||
providers: [FocoLiveService,
|
||||
makeGaugeProvider({
|
||||
name: 'event_count',
|
||||
help: 'The current number of events in the Foco Live database at last query time',
|
||||
}),
|
||||
makeGaugeProvider({
|
||||
name: 'event_cache_misses',
|
||||
help: 'The total number of cache misses for the Foco Live events',
|
||||
}),
|
||||
makeGaugeProvider({
|
||||
name: 'query_count',
|
||||
help: 'The total number of queries to the Foco Live API',
|
||||
}),
|
||||
]
|
||||
})
|
||||
export class FocoLiveModule {}
|
||||
export class FocoLiveModule { }
|
||||
|
@@ -1,7 +1,12 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { InjectMetric } from '@willsoto/nestjs-prometheus';
|
||||
import * as Airtable from 'airtable';
|
||||
import { AirtableBase } from 'airtable/lib/airtable_base';
|
||||
import { Cache } from 'cache-manager';
|
||||
import { Gauge } from 'prom-client';
|
||||
import { filter, pipe } from 'ramda';
|
||||
|
||||
const tables = {
|
||||
venues: 'tblRi4wDorKqNJJbs',
|
||||
@@ -10,24 +15,66 @@ const tables = {
|
||||
|
||||
const compareDates = (a: any, b: any) => new Date(a.Date).getTime() - new Date(b.Date).getTime();
|
||||
|
||||
const beforeFilter = (before?: Date) => (a: Event) => before ? new Date(a.Date) <= before : true;
|
||||
const afterFilter = (after?: Date) => (a: Event) => after ? new Date(a.Date) >= after : true;
|
||||
|
||||
export interface Event {
|
||||
"Date": string,
|
||||
"Music Start Time": string,
|
||||
"Bar or Venue Name": string,
|
||||
"Band/DJ/Musician Name": string,
|
||||
Cost: string,
|
||||
"Date Select": string
|
||||
}
|
||||
|
||||
const cacheKeys = {
|
||||
allEvents: 'foco_live_events',
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class FocoLiveService {
|
||||
private readonly airtableBase: AirtableBase;
|
||||
private readonly logger = new Logger(FocoLiveService.name);
|
||||
|
||||
constructor(
|
||||
private readonly config: ConfigService,
|
||||
@InjectMetric('event_count') public eventCount: Gauge<string>,
|
||||
@InjectMetric('event_cache_misses') public cacheMisses: Gauge<string>,
|
||||
@Inject(CACHE_MANAGER) private cacheManager: Cache
|
||||
) {
|
||||
this.airtableBase = new Airtable({
|
||||
apiKey: config.get('focoLive.airtable.apiKey'),
|
||||
}).base('app1SjPrn5qrhr59J');
|
||||
}
|
||||
|
||||
async getEvents() {
|
||||
return (await this.airtableBase('Events').select({
|
||||
async getAllEvents(): Promise<Event[]> {
|
||||
return ((await this.airtableBase('Events').select({
|
||||
view: "Grid view",
|
||||
}).all())
|
||||
.map(record => record.fields)
|
||||
.map(record => record.fields) as any as Event[])
|
||||
.sort(compareDates)
|
||||
.reverse();
|
||||
}
|
||||
|
||||
async getAllEventsCached(): Promise<Event[]> {
|
||||
let events: Event[] | null | undefined = await this.cacheManager.get(cacheKeys.allEvents);
|
||||
if (!events) {
|
||||
events = await this.getAllEvents();
|
||||
this.cacheMisses.inc();
|
||||
this.cacheManager.set(cacheKeys.allEvents, events, 5 * 60 * 1000);
|
||||
}
|
||||
this.eventCount.set(events.length);
|
||||
return events;
|
||||
}
|
||||
|
||||
async getEvents(options: { venue?: string, before?: Date, after?: Date } = {}) {
|
||||
const events = await this.getAllEventsCached();
|
||||
const results = pipe(
|
||||
filter((a: Event) => a["Bar or Venue Name"] === (options.venue ?? a["Bar or Venue Name"])),
|
||||
filter(beforeFilter(options.before)),
|
||||
filter(afterFilter(options.after)),
|
||||
)(events);
|
||||
this.logger.verbose(`Returning ${results.length} events, ${events.length} total events in database.`);
|
||||
return results
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user