Migrate to fly.io, add Genre type

This commit is contained in:
2024-09-01 11:28:37 -06:00
parent c25a5230d9
commit 758cf386b4
8 changed files with 192 additions and 117 deletions

View File

@@ -37,16 +37,17 @@ import { RedisModule, RedisModuleOptions } from '@liaoliaots/nestjs-redis';
RedisModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService): Promise<RedisModuleOptions> => {
useFactory: async (
configService: ConfigService,
): Promise<RedisModuleOptions> => {
return {
config: {
host: configService.get<string>('redis.host'),
port: configService.get<number>('redis.port'),
password: configService.get<string>('redis.password'),
url: configService.get<string>('redis.url'),
db: configService.get<number>('redis.db'),
}
family: 6,
},
};
}
},
}),
CacheModule.registerAsync<RedisClientOptions>({
isGlobal: true,
@@ -54,12 +55,9 @@ import { RedisModule, RedisModuleOptions } from '@liaoliaots/nestjs-redis';
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
store: redisStore,
socket: {
host: configService.get<string>('redis.host'),
port: configService.get<number>('redis.port'),
},
url: configService.get<string>('redis.url'),
family: 6,
database: configService.get<number>('redis.db'),
password: configService.get<string>('redis.password'),
}),
}),
BullModule.forRootAsync({
@@ -68,10 +66,9 @@ import { RedisModule, RedisModuleOptions } from '@liaoliaots/nestjs-redis';
useFactory: async (configService: ConfigService) => {
const config = {
redis: {
password: configService.get<string>('redis.password'),
host: configService.get<string>('redis.host'),
port: configService.get<number>('redis.port'),
url: configService.get<string>('redis.url'),
db: configService.get<number>('redis.db'),
family: 6,
},
};
return config;
@@ -103,4 +100,4 @@ import { RedisModule, RedisModuleOptions } from '@liaoliaots/nestjs-redis';
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }
export class AppModule {}

View File

@@ -7,7 +7,7 @@ export default () => ({
userAgent: 'api.us.dev/@chip@talking.dev',
rapidApiKey: process.env.RAPID_API_KEY || '',
irc: {
enabled: process.env.IRC_SERVER === "" ? false : true,
enabled: process.env.IRC_SERVER === '' ? false : true,
server: process.env.IRC_SERVER,
tls: process.env.IRC_TLS === 'true',
port: parseInt(process.env.IRC_PORT ?? '6697'),
@@ -19,19 +19,22 @@ export default () => ({
: 'us-dev',
},
redis: {
host: process.env.REDIS_HOST ?? 'redis-master',
port: parseInt(process.env.REDIS_PORT ?? '6379'),
password: process.env.REDIS_PASS ?? '',
url: process.env.REDIS_URL ?? '',
db: parseInt(process.env.REDIS_DB ?? '1'),
},
file: {
bucketName: process.env.FILE_BUCKET_NAME ?? process.env.S3_BUCKET ?? 'api.us.dev-files',
bucketName:
process.env.FILE_BUCKET_NAME ??
process.env.S3_BUCKET ??
'api.us.dev-files',
// default file ttl in seconds
defaultTtl: parseInt(process.env.FILE_DEFAULT_TTL ?? (30 * 24 * 60 * 60).toString()),
defaultTtl: parseInt(
process.env.FILE_DEFAULT_TTL ?? (30 * 24 * 60 * 60).toString(),
),
},
focoLive: {
airtable: {
apiKey: process.env.FOCO_LIVE_AIRTABLE_APIKEY ?? '',
}
}
},
},
});

View File

@@ -11,116 +11,142 @@ import { Gauge } from 'prom-client';
import { filter, pipe } from 'ramda';
const tables = {
venues: 'tblRi4wDorKqNJJbs',
events: 'tbl4RZ75QF5WefE7L',
}
venues: 'tblRi4wDorKqNJJbs',
events: 'tbl4RZ75QF5WefE7L',
};
const compareDates = (a: any, b: any) => new Date(a.Date).getTime() - new Date(b.Date).getTime();
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;
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 Select": string;
Cost: string,
"Date Select": string
'"Specials" at Venue': string,
id: string;
Date: string;
'Music Start Time': string;
'Bar or Venue Name': string;
'Band/DJ/Musician Name': string;
'Cost Select': string;
Cost: string;
'Date Select': string;
'"Specials" at Venue': string;
Genre?: string;
id: string;
}
export interface Venue {
id: string;
"Bar or Venue Name": string,
"Street Address": string,
"City": string,
"Zip Code": number,
State: string,
"Phone Number": string,
Website: string,
"Has Calendar Of Events": string,
"Facebook Page": string,
"Instagram": string,
"Twitter Account": string,
id: string;
'Bar or Venue Name': string;
'Street Address': string;
City: string;
'Zip Code': number;
State: string;
'Phone Number': string;
Website: string;
'Has Calendar Of Events': string;
'Facebook Page': string;
Instagram: string;
'Twitter Account': string;
}
const cacheKeys = {
allEvents: 'foco_live_events',
allVenues: 'foco_live_venues',
}
allEvents: 'foco_live_events',
allVenues: 'foco_live_venues',
};
@Injectable()
export class FocoLiveService {
private readonly airtableBase: AirtableBase;
private readonly logger = new Logger(FocoLiveService.name);
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');
}
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 getAllEvents(): Promise<Event[]> {
return ((await this.airtableBase('Events').select({
view: "Grid view",
}).all())
.map(record => ({
id: record.id,
...record.fields
})) as any as Event[])
.sort(compareDates)
.reverse();
}
async getAllEvents(): Promise<Event[]> {
return (
(
await this.airtableBase('Events')
.select({
view: 'Grid view',
})
.all()
).map((record) => ({
id: record.id,
...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, 10 * 60 * 1000);
}
this.eventCount.set(events.length);
return events;
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, 10 * 60 * 1000);
}
this.eventCount.set(events.length);
return events;
}
async getAllVenues(): Promise<Venue[]> {
return ((await this.airtableBase('Venues').select({
view: "Grid view",
}).all())
.map(record => ({
id: record.id,
...record.fields
})) as any as Venue[])
.sort(compareDates)
.reverse();
}
async getAllVenues(): Promise<Venue[]> {
return (
(
await this.airtableBase('Venues')
.select({
view: 'Grid view',
})
.all()
).map((record) => ({
id: record.id,
...record.fields,
})) as any as Venue[]
)
.sort(compareDates)
.reverse();
}
async getAllVenuesCached(): Promise<Venue[]> {
return await this.cacheManager.get(cacheKeys.allVenues) || await this.getAllVenues();
}
async getAllVenuesCached(): Promise<Venue[]> {
return (
(await this.cacheManager.get(cacheKeys.allVenues)) ||
(await this.getAllVenues())
);
}
@Cron("0 */5 * * * *")
async refreshEvents() {
this.logger.verbose("Refreshing events cache.");
await this.getAllEventsCached();
}
@Cron('0 */5 * * * *')
async refreshEvents() {
this.logger.verbose('Refreshing events cache.');
await this.getAllEventsCached();
}
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
}
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;
}
}

View File

@@ -19,6 +19,7 @@ export class MinioService {
useSSL: this.configService.get<string>('S3_USE_SSL', 'true') === 'true',
accessKey: this.configService.get<string>('S3_ACCESS_KEY', ''),
secretKey: this.configService.get<string>('S3_SECRET_KEY', ''),
region: this.configService.get<string>('S3_REGION', 'auto'),
});
this.defaultBucketName = this.configService.get<string>('S3_BUCKET', '');
}