|
|
@ -6,27 +6,39 @@ import { |
|
|
import { PropertyKey } from '@ghostfolio/common/types'; |
|
|
import { PropertyKey } from '@ghostfolio/common/types'; |
|
|
|
|
|
|
|
|
import { Injectable } from '@nestjs/common'; |
|
|
import { Injectable } from '@nestjs/common'; |
|
|
|
|
|
import { Property } from '@prisma/client'; |
|
|
|
|
|
import { addMilliseconds, isBefore } from 'date-fns'; |
|
|
|
|
|
import ms from 'ms'; |
|
|
|
|
|
|
|
|
import { PropertyValue } from './interfaces/interfaces'; |
|
|
import { PropertyValue } from './interfaces/interfaces'; |
|
|
|
|
|
|
|
|
@Injectable() |
|
|
@Injectable() |
|
|
export class PropertyService { |
|
|
export class PropertyService { |
|
|
|
|
|
private static readonly CACHE_TTL = ms('1 minute'); |
|
|
|
|
|
|
|
|
|
|
|
private cachedProperties: Promise<Property[]>; |
|
|
|
|
|
private cachedPropertiesExpiresAt: Date; |
|
|
|
|
|
|
|
|
public constructor(private readonly prismaService: PrismaService) {} |
|
|
public constructor(private readonly prismaService: PrismaService) {} |
|
|
|
|
|
|
|
|
public async delete({ key }: { key: PropertyKey }) { |
|
|
public async delete({ key }: { key: PropertyKey }) { |
|
|
return this.prismaService.property.delete({ |
|
|
const property = await this.prismaService.property.delete({ |
|
|
where: { key } |
|
|
where: { key } |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
this.invalidateCache(); |
|
|
|
|
|
|
|
|
|
|
|
return property; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public async get() { |
|
|
public async get({ skipCache = false } = {}) { |
|
|
const response: { |
|
|
const response: { |
|
|
[key: string]: PropertyValue; |
|
|
[key: string]: PropertyValue; |
|
|
} = { |
|
|
} = { |
|
|
[PROPERTY_CURRENCIES]: [] |
|
|
[PROPERTY_CURRENCIES]: [] |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const properties = await this.prismaService.property.findMany(); |
|
|
const properties = await this.getProperties({ skipCache }); |
|
|
|
|
|
|
|
|
for (const property of properties) { |
|
|
for (const property of properties) { |
|
|
let value = property.value; |
|
|
let value = property.value; |
|
|
@ -41,8 +53,11 @@ export class PropertyService { |
|
|
return response; |
|
|
return response; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public async getByKey<TValue extends PropertyValue>(aKey: PropertyKey) { |
|
|
public async getByKey<TValue extends PropertyValue>( |
|
|
const properties = await this.get(); |
|
|
aKey: PropertyKey, |
|
|
|
|
|
{ skipCache = false } = {} |
|
|
|
|
|
) { |
|
|
|
|
|
const properties = await this.get({ skipCache }); |
|
|
return properties[aKey] as TValue; |
|
|
return properties[aKey] as TValue; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -53,10 +68,53 @@ export class PropertyService { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public async put({ key, value }: { key: PropertyKey; value: string }) { |
|
|
public async put({ key, value }: { key: PropertyKey; value: string }) { |
|
|
return this.prismaService.property.upsert({ |
|
|
const property = await this.prismaService.property.upsert({ |
|
|
create: { key, value }, |
|
|
create: { key, value }, |
|
|
update: { value }, |
|
|
update: { value }, |
|
|
where: { key } |
|
|
where: { key } |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
this.invalidateCache(); |
|
|
|
|
|
|
|
|
|
|
|
return property; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Returns the properties from the in-memory cache, falling back to the |
|
|
|
|
|
* database. Callers which write back a modified property must set |
|
|
|
|
|
* skipCache to avoid basing the write on a stale read. |
|
|
|
|
|
*/ |
|
|
|
|
|
private async getProperties({ skipCache = false } = {}) { |
|
|
|
|
|
if (skipCache) { |
|
|
|
|
|
return this.prismaService.property.findMany(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
|
this.cachedProperties && |
|
|
|
|
|
isBefore(new Date(), this.cachedPropertiesExpiresAt) |
|
|
|
|
|
) { |
|
|
|
|
|
return this.cachedProperties; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const properties = this.prismaService.property.findMany().catch((error) => { |
|
|
|
|
|
if (this.cachedProperties === properties) { |
|
|
|
|
|
this.invalidateCache(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
throw error; |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
this.cachedProperties = properties; |
|
|
|
|
|
this.cachedPropertiesExpiresAt = addMilliseconds( |
|
|
|
|
|
new Date(), |
|
|
|
|
|
PropertyService.CACHE_TTL |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
return this.cachedProperties; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private invalidateCache() { |
|
|
|
|
|
this.cachedProperties = undefined; |
|
|
|
|
|
this.cachedPropertiesExpiresAt = undefined; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|