Browse Source

Merge 360d5c397b into ad47bedcb5

pull/4270/merge
Haruka Kishida 1 week ago
committed by GitHub
parent
commit
369f16f35a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 55
      apps/api/src/app/auth/api-key.strategy.ts
  3. 16
      apps/api/src/app/redis-cache/redis-cache.module.ts
  4. 36
      apps/api/src/app/redis-cache/redis-cache.service.ts
  5. 6143
      package-lock.json
  6. 30
      package.json

1
CHANGELOG.md

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Upgraded `nestjs` from version `10.4.15` to `11.0.12`
- Upgraded `eslint` dependencies - Upgraded `eslint` dependencies
## 2.150.0 - 2025-04-05 ## 2.150.0 - 2025-04-05

55
apps/api/src/app/auth/api-key.strategy.ts

@ -21,37 +21,38 @@ export class ApiKeyStrategy extends PassportStrategy(
private readonly prismaService: PrismaService, private readonly prismaService: PrismaService,
private readonly userService: UserService private readonly userService: UserService
) { ) {
super( super({ header: HEADER_KEY_TOKEN, prefix: 'Api-Key ' }, true);
{ header: HEADER_KEY_TOKEN, prefix: 'Api-Key ' }, }
true,
async (apiKey: string, done: (error: any, user?: any) => void) => {
try {
const user = await this.validateApiKey(apiKey);
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
if (hasRole(user, 'INACTIVE')) {
throw new HttpException(
getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS),
StatusCodes.TOO_MANY_REQUESTS
);
}
await this.prismaService.analytics.upsert({ public async validate(
create: { User: { connect: { id: user.id } } }, apiKey: string,
update: { done: (error: any, user?: any) => void
activityCount: { increment: 1 }, ) {
lastRequestAt: new Date() try {
}, const user = await this.validateApiKey(apiKey);
where: { userId: user.id }
});
}
done(null, user); if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
} catch (error) { if (hasRole(user, 'INACTIVE')) {
done(error, null); throw new HttpException(
getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS),
StatusCodes.TOO_MANY_REQUESTS
);
} }
await this.prismaService.analytics.upsert({
create: { User: { connect: { id: user.id } } },
update: {
activityCount: { increment: 1 },
lastRequestAt: new Date()
},
where: { userId: user.id }
});
} }
);
done(null, user);
} catch (error) {
done(error, null);
}
} }
private async validateApiKey(apiKey: string) { private async validateApiKey(apiKey: string) {

16
apps/api/src/app/redis-cache/redis-cache.module.ts

@ -1,17 +1,16 @@
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { createKeyv } from '@keyv/redis';
import { CacheModule } from '@nestjs/cache-manager'; import { CacheModule } from '@nestjs/cache-manager';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { redisStore } from 'cache-manager-redis-yet';
import type { RedisClientOptions } from 'redis';
import { RedisCacheService } from './redis-cache.service'; import { RedisCacheService } from './redis-cache.service';
@Module({ @Module({
exports: [RedisCacheService], exports: [RedisCacheService],
imports: [ imports: [
CacheModule.registerAsync<RedisClientOptions>({ CacheModule.registerAsync({
imports: [ConfigurationModule], imports: [ConfigurationModule],
inject: [ConfigurationService], inject: [ConfigurationService],
useFactory: async (configurationService: ConfigurationService) => { useFactory: async (configurationService: ConfigurationService) => {
@ -20,10 +19,13 @@ import { RedisCacheService } from './redis-cache.service';
); );
return { return {
store: redisStore, stores: [
ttl: configurationService.get('CACHE_TTL'), createKeyv(
url: `redis://${redisPassword ? `:${redisPassword}` : ''}@${configurationService.get('REDIS_HOST')}:${configurationService.get('REDIS_PORT')}/${configurationService.get('REDIS_DB')}` `redis://${redisPassword ? `:${redisPassword}` : ''}@${configurationService.get('REDIS_HOST')}:${configurationService.get('REDIS_PORT')}/${configurationService.get('REDIS_DB')}`
} as RedisClientOptions; )
],
ttl: configurationService.get('CACHE_TTL')
};
} }
}), }),
ConfigurationModule ConfigurationModule

36
apps/api/src/app/redis-cache/redis-cache.service.ts

@ -2,21 +2,20 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/con
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import { AssetProfileIdentifier, Filter } from '@ghostfolio/common/interfaces'; import { AssetProfileIdentifier, Filter } from '@ghostfolio/common/interfaces';
import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager';
import { Inject, Injectable, Logger } from '@nestjs/common'; import { Inject, Injectable, Logger } from '@nestjs/common';
import { Milliseconds } from 'cache-manager';
import { RedisCache } from 'cache-manager-redis-yet';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import ms from 'ms'; import ms from 'ms';
@Injectable() @Injectable()
export class RedisCacheService { export class RedisCacheService {
public constructor( public constructor(
@Inject(CACHE_MANAGER) private readonly cache: RedisCache, @Inject(CACHE_MANAGER) private readonly cache: Cache,
private readonly configurationService: ConfigurationService private readonly configurationService: ConfigurationService
) { ) {
const client = cache.store.client; const client = cache.stores[0];
client.deserialize = undefined;
client.on('error', (error) => { client.on('error', (error) => {
Logger.error(error, 'RedisCacheService'); Logger.error(error, 'RedisCacheService');
}); });
@ -27,13 +26,15 @@ export class RedisCacheService {
} }
public async getKeys(aPrefix?: string): Promise<string[]> { public async getKeys(aPrefix?: string): Promise<string[]> {
let prefix = aPrefix; const keys: string[] = [];
const prefix = aPrefix;
if (prefix) { for await (const [key] of this.cache.stores[0].iterator({})) {
prefix = `${prefix}*`; if ((prefix && key.startsWith(prefix)) || !prefix) {
keys.push(key);
}
} }
return this.cache.store.keys(prefix); return keys;
} }
public getPortfolioSnapshotKey({ public getPortfolioSnapshotKey({
@ -62,10 +63,8 @@ export class RedisCacheService {
public async isHealthy() { public async isHealthy() {
try { try {
const client = this.cache.store.client;
const isHealthy = await Promise.race([ const isHealthy = await Promise.race([
client.ping(), this.getKeys(),
new Promise((_, reject) => new Promise((_, reject) =>
setTimeout( setTimeout(
() => reject(new Error('Redis health check timeout')), () => reject(new Error('Redis health check timeout')),
@ -92,17 +91,16 @@ export class RedisCacheService {
const keys = await this.getKeys( const keys = await this.getKeys(
`${this.getPortfolioSnapshotKey({ userId })}` `${this.getPortfolioSnapshotKey({ userId })}`
); );
console.log('keys is ');
for (const key of keys) { console.log(keys);
await this.remove(key); return this.cache.mdel(keys);
}
} }
public async reset() { public async reset() {
return this.cache.reset(); return this.cache.clear();
} }
public async set(key: string, value: string, ttl?: Milliseconds) { public async set(key: string, value: string, ttl?: number) {
return this.cache.set( return this.cache.set(
key, key,
value, value,

6143
package-lock.json

File diff suppressed because it is too large

30
package.json

@ -76,17 +76,18 @@
"@dfinity/principal": "0.15.7", "@dfinity/principal": "0.15.7",
"@dinero.js/currencies": "2.0.0-alpha.8", "@dinero.js/currencies": "2.0.0-alpha.8",
"@internationalized/number": "3.6.0", "@internationalized/number": "3.6.0",
"@nestjs/bull": "10.2.3", "@keyv/redis": "4.3.2",
"@nestjs/cache-manager": "2.3.0", "@nestjs/bull": "11.0.2",
"@nestjs/common": "10.4.15", "@nestjs/cache-manager": "3.0.1",
"@nestjs/config": "3.3.0", "@nestjs/common": "11.0.12",
"@nestjs/core": "10.4.15", "@nestjs/config": "4.0.2",
"@nestjs/event-emitter": "2.1.1", "@nestjs/core": "11.0.12",
"@nestjs/jwt": "10.2.0", "@nestjs/event-emitter": "3.0.1",
"@nestjs/passport": "10.0.3", "@nestjs/jwt": "11.0.0",
"@nestjs/platform-express": "10.4.15", "@nestjs/passport": "11.0.5",
"@nestjs/schedule": "4.1.2", "@nestjs/platform-express": "11.0.12",
"@nestjs/serve-static": "4.0.2", "@nestjs/schedule": "5.0.1",
"@nestjs/serve-static": "5.0.3",
"@prisma/client": "6.5.0", "@prisma/client": "6.5.0",
"@simplewebauthn/browser": "13.1.0", "@simplewebauthn/browser": "13.1.0",
"@simplewebauthn/server": "13.1.1", "@simplewebauthn/server": "13.1.1",
@ -95,8 +96,6 @@
"big.js": "6.2.2", "big.js": "6.2.2",
"bootstrap": "4.6.0", "bootstrap": "4.6.0",
"bull": "4.16.5", "bull": "4.16.5",
"cache-manager": "5.7.6",
"cache-manager-redis-yet": "5.1.4",
"chart.js": "4.4.7", "chart.js": "4.4.7",
"chartjs-adapter-date-fns": "3.0.0", "chartjs-adapter-date-fns": "3.0.0",
"chartjs-chart-treemap": "3.1.0", "chartjs-chart-treemap": "3.1.0",
@ -153,8 +152,8 @@
"@angular/pwa": "19.2.1", "@angular/pwa": "19.2.1",
"@eslint/eslintrc": "3.3.1", "@eslint/eslintrc": "3.3.1",
"@eslint/js": "9.24.0", "@eslint/js": "9.24.0",
"@nestjs/schematics": "10.2.3", "@nestjs/schematics": "11.0.2",
"@nestjs/testing": "10.4.15", "@nestjs/testing": "11.0.12",
"@nx/angular": "20.6.4", "@nx/angular": "20.6.4",
"@nx/cypress": "20.6.4", "@nx/cypress": "20.6.4",
"@nx/eslint-plugin": "20.6.4", "@nx/eslint-plugin": "20.6.4",
@ -173,7 +172,6 @@
"@storybook/core-server": "8.4.7", "@storybook/core-server": "8.4.7",
"@trivago/prettier-plugin-sort-imports": "5.2.2", "@trivago/prettier-plugin-sort-imports": "5.2.2",
"@types/big.js": "6.2.2", "@types/big.js": "6.2.2",
"@types/cache-manager": "4.0.6",
"@types/google-spreadsheet": "3.1.5", "@types/google-spreadsheet": "3.1.5",
"@types/jest": "29.5.13", "@types/jest": "29.5.13",
"@types/lodash": "4.17.16", "@types/lodash": "4.17.16",

Loading…
Cancel
Save