diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a43aa44c..752fc2397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Moved the tags from the info to the user service - Switched the `prefer-const` rule from `warn` to `error` in the `eslint` configuration ## 2.113.0 - 2024-10-06 diff --git a/apps/api/src/app/info/info.module.ts b/apps/api/src/app/info/info.module.ts index 9b7854160..7903ac397 100644 --- a/apps/api/src/app/info/info.module.ts +++ b/apps/api/src/app/info/info.module.ts @@ -9,7 +9,6 @@ import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-d import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; -import { TagModule } from '@ghostfolio/api/services/tag/tag.module'; import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; @@ -33,7 +32,6 @@ import { InfoService } from './info.service'; PropertyModule, RedisCacheModule, SymbolProfileModule, - TagModule, TransformDataSourceInResponseModule, UserModule ], diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index ee225e769..bd291c511 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -5,7 +5,6 @@ import { UserService } from '@ghostfolio/api/app/user/user.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; -import { TagService } from '@ghostfolio/api/services/tag/tag.service'; import { DEFAULT_CURRENCY, PROPERTY_BETTER_UPTIME_MONITOR_ID, @@ -47,7 +46,6 @@ export class InfoService { private readonly platformService: PlatformService, private readonly propertyService: PropertyService, private readonly redisCacheService: RedisCacheService, - private readonly tagService: TagService, private readonly userService: UserService ) {} @@ -103,8 +101,7 @@ export class InfoService { isUserSignupEnabled, platforms, statistics, - subscriptions, - tags + subscriptions ] = await Promise.all([ this.benchmarkService.getBenchmarkAssetProfiles(), this.getDemoAuthToken(), @@ -113,8 +110,7 @@ export class InfoService { orderBy: { name: 'asc' } }), this.getStatistics(), - this.getSubscriptions(), - this.tagService.getPublic() + this.getSubscriptions() ]); if (isUserSignupEnabled) { @@ -130,7 +126,6 @@ export class InfoService { platforms, statistics, subscriptions, - tags, baseCurrency: DEFAULT_CURRENCY, currencies: this.exchangeRateDataService.getCurrencies() }; diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index c04af5fbf..0f76b9540 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -56,7 +56,7 @@ export class UserService { { Account, id, permissions, Settings, subscription }: UserWithSettings, aLocale = locale ): Promise { - const accessesResult = await Promise.all([ + const userData = await Promise.all([ this.prismaService.access.findMany({ include: { User: true @@ -70,11 +70,11 @@ export class UserService { }, where: { userId: id } }), - this.tagService.getInUseByUser(id) + this.tagService.getTagsForUser(id) ]); - const access = accessesResult[0]; - const firstActivity = accessesResult[1]; - let tags = accessesResult[2]; + const access = userData[0]; + const firstActivity = userData[1]; + let tags = userData[2]; let systemMessage: SystemMessage; diff --git a/apps/api/src/services/tag/tag.service.ts b/apps/api/src/services/tag/tag.service.ts index 5426a0582..b16f22fbb 100644 --- a/apps/api/src/services/tag/tag.service.ts +++ b/apps/api/src/services/tag/tag.service.ts @@ -6,29 +6,39 @@ import { Injectable } from '@nestjs/common'; export class TagService { public constructor(private readonly prismaService: PrismaService) {} - public async getPublic() { - return this.prismaService.tag.findMany({ - orderBy: { - name: 'asc' + public async getTagsForUser(userId: string) { + const tags = await this.prismaService.tag.findMany({ + include: { + _count: { + select: { + orders: { + where: { + userId + } + } + } + } }, - where: { - userId: null - } - }); - } - - public async getInUseByUser(userId: string) { - return this.prismaService.tag.findMany({ orderBy: { name: 'asc' }, where: { - orders: { - some: { + OR: [ + { userId + }, + { + userId: null } - } + ] } }); + + return tags.map(({ _count, id, name, userId }) => ({ + id, + name, + userId, + isUsed: _count.orders > 0 + })); } } diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index b94ba79d5..792ec6f9c 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -147,8 +147,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { ) {} public ngOnInit() { - const { tags } = this.dataService.fetchInfo(); - this.activityForm = this.formBuilder.group({ tags: [] }); @@ -158,13 +156,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { { id: this.data.symbol, type: 'SYMBOL' } ]; - this.tagsAvailable = tags.map((tag) => { - return { - ...tag, - name: translate(tag.name) - }; - }); - this.activityForm .get('tags') .valueChanges.pipe(takeUntil(this.unsubscribeSubject)) @@ -434,6 +425,14 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { if (state?.user) { this.user = state.user; + this.tagsAvailable = + this.user?.tags?.map((tag) => { + return { + ...tag, + name: translate(tag.name) + }; + }) ?? []; + this.changeDetectorRef.markForCheck(); } }); diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts index 4690ab6a8..cb21c255d 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -76,17 +76,19 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { this.locale = this.data.user?.settings?.locale; this.dateAdapter.setLocale(this.locale); - const { currencies, platforms, tags } = this.dataService.fetchInfo(); + const { currencies, platforms } = this.dataService.fetchInfo(); this.currencies = currencies; this.defaultDateFormat = getDateFormatString(this.locale); this.platforms = platforms; - this.tagsAvailable = tags.map((tag) => { - return { - ...tag, - name: translate(tag.name) - }; - }); + + this.tagsAvailable = + this.data.user?.tags?.map((tag) => { + return { + ...tag, + name: translate(tag.name) + }; + }) ?? []; Object.keys(Type).forEach((type) => { this.typesTranslationMap[Type[type]] = translate(Type[type]); diff --git a/libs/common/src/lib/interfaces/info-item.interface.ts b/libs/common/src/lib/interfaces/info-item.interface.ts index d279c74a4..1b3926331 100644 --- a/libs/common/src/lib/interfaces/info-item.interface.ts +++ b/libs/common/src/lib/interfaces/info-item.interface.ts @@ -1,6 +1,6 @@ import { SubscriptionOffer } from '@ghostfolio/common/types'; -import { Platform, SymbolProfile, Tag } from '@prisma/client'; +import { Platform, SymbolProfile } from '@prisma/client'; import { Statistics } from './statistics.interface'; import { Subscription } from './subscription.interface'; @@ -19,5 +19,4 @@ export interface InfoItem { statistics: Statistics; stripePublicKey?: string; subscriptions: { [offer in SubscriptionOffer]: Subscription }; - tags: Tag[]; } diff --git a/libs/common/src/lib/interfaces/user.interface.ts b/libs/common/src/lib/interfaces/user.interface.ts index 2891314a0..27cd1a610 100644 --- a/libs/common/src/lib/interfaces/user.interface.ts +++ b/libs/common/src/lib/interfaces/user.interface.ts @@ -23,5 +23,5 @@ export interface User { offer: SubscriptionOffer; type: SubscriptionType; }; - tags: Tag[]; + tags: (Tag & { isUsed: boolean })[]; } diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index c60e93d88..c93d04303 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -156,7 +156,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ) {} public ngOnInit() { - this.accounts = this.user?.accounts; this.assetClasses = Object.keys(AssetClass).map((assetClass) => { return { id: assetClass, @@ -164,13 +163,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { type: 'ASSET_CLASS' }; }); - this.tags = this.user?.tags.map(({ id, name }) => { - return { - id, - label: translate(name), - type: 'TAG' - }; - }); this.searchFormControl.valueChanges .pipe( @@ -212,6 +204,8 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { } public ngOnChanges() { + this.accounts = this.user?.accounts ?? []; + this.dateRangeOptions = [ { label: $localize`Today`, value: '1d' }, { @@ -279,6 +273,23 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { emitEvent: false } ); + + this.tags = + this.user?.tags + ?.filter(({ isUsed }) => { + return isUsed; + }) + .map(({ id, name }) => { + return { + id, + label: translate(name), + type: 'TAG' + }; + }) ?? []; + + if (this.tags.length === 0) { + this.filterForm.get('tag').disable({ emitEvent: false }); + } } public hasFilter(aFormValue: { [key: string]: string }) {