Browse Source

Merge remote-tracking branch 'origin/main' into dockerpush

pull/5027/head
Dan 1 year ago
parent
commit
78447380e4
  1. 11
      CHANGELOG.md
  2. 6
      apps/api/src/app/user/delete-own-user.dto.ts
  3. 31
      apps/api/src/app/user/user.controller.ts
  4. 9
      apps/api/src/app/user/user.service.ts
  5. 48
      apps/api/src/assets/sitemap.xml
  6. 41
      apps/client/src/app/components/user-account-settings/user-account-settings.component.ts
  7. 49
      apps/client/src/app/components/user-account-settings/user-account-settings.html
  8. 7
      apps/client/src/app/components/user-account-settings/user-account-settings.module.ts
  9. 35
      apps/client/src/app/pages/resources/personal-finance-tools/products.ts
  10. 32
      apps/client/src/app/pages/resources/personal-finance-tools/products/portfolio-visualizer-page.component.ts
  11. 32
      apps/client/src/app/pages/resources/personal-finance-tools/products/stock-events-page.component.ts
  12. 32
      apps/client/src/app/pages/resources/personal-finance-tools/products/wallmine-page.component.ts
  13. 5
      apps/client/src/app/services/data.service.ts
  14. 856
      apps/client/src/locales/messages.de.xlf
  15. 856
      apps/client/src/locales/messages.es.xlf
  16. 856
      apps/client/src/locales/messages.fr.xlf
  17. 856
      apps/client/src/locales/messages.it.xlf
  18. 856
      apps/client/src/locales/messages.nl.xlf
  19. 854
      apps/client/src/locales/messages.pl.xlf
  20. 856
      apps/client/src/locales/messages.pt.xlf
  21. 860
      apps/client/src/locales/messages.tr.xlf
  22. 853
      apps/client/src/locales/messages.xlf
  23. 854
      apps/client/src/locales/messages.zh.xlf
  24. 3
      libs/common/src/lib/permissions.ts
  25. 2
      package.json
  26. 160
      yarn.lock

11
CHANGELOG.md

@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Added the ability to close a user account
### Changed
- Improved the language localization for German (`de`)
- Upgraded `ng-extract-i18n-merge` from version `2.10.0` to `2.12.0`
## 2.84.0 - 2024-06-01
### Added

6
apps/api/src/app/user/delete-own-user.dto.ts

@ -0,0 +1,6 @@
import { IsString } from 'class-validator';
export class DeleteOwnUserDto {
@IsString()
accessToken: string;
}

31
apps/api/src/app/user/user.controller.ts

@ -1,5 +1,6 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { User, UserSettings } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
@ -25,6 +26,7 @@ import { User as UserModel } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { size } from 'lodash';
import { DeleteOwnUserDto } from './delete-own-user.dto';
import { UserItem } from './interfaces/user-item.interface';
import { UpdateUserSettingDto } from './update-user-setting.dto';
import { UserService } from './user.service';
@ -32,12 +34,41 @@ import { UserService } from './user.service';
@Controller('user')
export class UserController {
public constructor(
private readonly configurationService: ConfigurationService,
private readonly jwtService: JwtService,
private readonly propertyService: PropertyService,
@Inject(REQUEST) private readonly request: RequestWithUser,
private readonly userService: UserService
) {}
@Delete()
@HasPermission(permissions.deleteOwnUser)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteOwnUser(
@Body() data: DeleteOwnUserDto
): Promise<UserModel> {
const hashedAccessToken = this.userService.createAccessToken(
data.accessToken,
this.configurationService.get('ACCESS_TOKEN_SALT')
);
const [user] = await this.userService.users({
where: { accessToken: hashedAccessToken, id: this.request.user.id }
});
if (!user) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.userService.deleteUser({
accessToken: hashedAccessToken,
id: user.id
});
}
@Delete(':id')
@HasPermission(permissions.deleteUser)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)

9
apps/api/src/app/user/user.service.ts

@ -240,10 +240,13 @@ export class UserService {
// Reset benchmark
user.Settings.settings.benchmark = undefined;
}
if (user.subscription?.type === 'Premium') {
} else if (user.subscription?.type === 'Premium') {
currentPermissions.push(permissions.reportDataGlitch);
currentPermissions = without(
currentPermissions,
permissions.deleteOwnUser
);
}
}

48
apps/api/src/assets/sitemap.xml

@ -182,6 +182,10 @@
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-portfolio-dividend-tracker</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-portfolio-visualizer</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-portseido</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
@ -210,6 +214,10 @@
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-snowball-analytics</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-stock-events</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-stockle</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
@ -234,6 +242,10 @@
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-vyzer</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-wallmine</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-wealthfolio</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
@ -536,6 +548,10 @@
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-portfolio-dividend-tracker</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-portfolio-visualizer</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-portseido</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
@ -564,6 +580,10 @@
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-snowball-analytics</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-stock-events</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-stockle</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
@ -588,6 +608,10 @@
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-vyzer</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-wallmine</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-wealthfolio</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
@ -902,6 +926,10 @@
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-portfolio-dividend-tracker</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-portfolio-visualizer</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-portseido</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
@ -930,6 +958,10 @@
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-snowball-analytics</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-stock-events</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-stockle</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
@ -954,6 +986,10 @@
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-vyzer</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-wallmine</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-wealthfolio</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
@ -1114,6 +1150,10 @@
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-portfolio-dividend-tracker</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-portfolio-visualizer</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-portseido</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
@ -1142,6 +1182,10 @@
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-snowball-analytics</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-stock-events</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-stockle</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
@ -1166,6 +1210,10 @@
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-vyzer</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-wallmine</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-wealthfolio</loc>
<lastmod>${currentDate}T00:00:00+00:00</lastmod>

41
apps/client/src/app/components/user-account-settings/user-account-settings.component.ts

@ -4,6 +4,7 @@ import {
KEY_TOKEN,
SettingsStorageService
} from '@ghostfolio/client/services/settings-storage.service';
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service';
import { downloadAsFile } from '@ghostfolio/common/helper';
@ -17,6 +18,7 @@ import {
OnDestroy,
OnInit
} from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { format, parseISO } from 'date-fns';
import { uniq } from 'lodash';
@ -33,8 +35,13 @@ export class UserAccountSettingsComponent implements OnDestroy, OnInit {
public appearancePlaceholder = $localize`Auto`;
public baseCurrency: string;
public currencies: string[] = [];
public deleteOwnUserForm = this.formBuilder.group({
accessToken: ['', Validators.required]
});
public hasPermissionToDeleteOwnUser: boolean;
public hasPermissionToUpdateViewMode: boolean;
public hasPermissionToUpdateUserSettings: boolean;
public isAccessTokenHidden = true;
public isWebAuthnEnabled: boolean;
public language = document.documentElement.lang;
public locales = [
@ -58,7 +65,9 @@ export class UserAccountSettingsComponent implements OnDestroy, OnInit {
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private formBuilder: FormBuilder,
private settingsStorageService: SettingsStorageService,
private tokenStorageService: TokenStorageService,
private userService: UserService,
public webAuthnService: WebAuthnService
) {
@ -73,6 +82,11 @@ export class UserAccountSettingsComponent implements OnDestroy, OnInit {
if (state?.user) {
this.user = state.user;
this.hasPermissionToDeleteOwnUser = hasPermission(
this.user.permissions,
permissions.deleteOwnUser
);
this.hasPermissionToUpdateUserSettings = hasPermission(
this.user.permissions,
permissions.updateUserSettings
@ -125,6 +139,33 @@ export class UserAccountSettingsComponent implements OnDestroy, OnInit {
});
}
public onCloseAccount() {
const confirmation = confirm(
$localize`Do you really want to close your Ghostfolio account?`
);
if (confirmation) {
this.dataService
.deleteOwnUser({
accessToken: this.deleteOwnUserForm.get('accessToken').value
})
.pipe(
catchError(() => {
alert($localize`Oops! Incorrect Security Token.`);
return EMPTY;
}),
takeUntil(this.unsubscribeSubject)
)
.subscribe(() => {
this.tokenStorageService.signOut();
this.userService.remove();
document.location.href = `/${document.documentElement.lang}`;
});
}
}
public onExperimentalFeaturesChange(aEvent: MatSlideToggleChange) {
this.dataService
.putUserSetting({ isExperimentalFeatures: aEvent.checked })

49
apps/client/src/app/components/user-account-settings/user-account-settings.html

@ -232,6 +232,55 @@
</button>
</div>
</div>
@if (hasPermissionToDeleteOwnUser) {
<hr class="mt-5" />
<form
class="w-100"
[formGroup]="deleteOwnUserForm"
(ngSubmit)="onCloseAccount()"
>
<div class="d-flex py-1">
<div class="pr-1 text-danger w-50" i18n>Danger Zone</div>
<div class="pl-1 w-50">
<mat-form-field
appearance="outline"
class="without-hint w-100"
[hideRequiredMarker]="true"
>
<mat-label i18n>Security Token</mat-label>
<input
formControlName="accessToken"
matInput
[type]="isAccessTokenHidden ? 'password' : 'text'"
/>
<button
mat-button
matSuffix
type="button"
(click)="isAccessTokenHidden = !isAccessTokenHidden"
>
<ion-icon
[name]="
isAccessTokenHidden ? 'eye-outline' : 'eye-off-outline'
"
/>
</button>
</mat-form-field>
<button
class="mt-2"
color="warn"
mat-flat-button
type="submit"
[disabled]="
!(deleteOwnUserForm.dirty && deleteOwnUserForm.valid)
"
>
<span i18n>Close Account</span>
</button>
</div>
</div>
</form>
}
</div>
</div>
</div>

7
apps/client/src/app/components/user-account-settings/user-account-settings.module.ts

@ -1,11 +1,12 @@
import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { RouterModule } from '@angular/router';
@ -22,10 +23,12 @@ import { UserAccountSettingsComponent } from './user-account-settings.component'
MatButtonModule,
MatCardModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatSlideToggleModule,
ReactiveFormsModule,
RouterModule
]
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfUserAccountSettingsModule {}

35
apps/client/src/app/pages/resources/personal-finance-tools/products.ts

@ -32,6 +32,7 @@ import { MonsePageComponent } from './products/monse-page.component';
import { ParqetPageComponent } from './products/parqet-page.component';
import { PlannixPageComponent } from './products/plannix-page.component';
import { PortfolioDividendTrackerPageComponent } from './products/portfolio-dividend-tracker-page.component';
import { PortfolioVisualizerPageComponent } from './products/portfolio-visualizer-page.component';
import { PortseidoPageComponent } from './products/portseido-page.component';
import { ProjectionLabPageComponent } from './products/projectionlab-page.component';
import { RocketMoneyPageComponent } from './products/rocket-money-page.component';
@ -39,12 +40,14 @@ import { SeekingAlphaPageComponent } from './products/seeking-alpha-page.compone
import { SharesightPageComponent } from './products/sharesight-page.component';
import { SimplePortfolioPageComponent } from './products/simple-portfolio-page.component';
import { SnowballAnalyticsPageComponent } from './products/snowball-analytics-page.component';
import { StockEventsPageComponent } from './products/stock-events-page.component';
import { StocklePageComponent } from './products/stockle-page.component';
import { StockMarketEyePageComponent } from './products/stockmarketeye-page.component';
import { SumioPageComponent } from './products/sumio-page.component';
import { TillerPageComponent } from './products/tiller-page.component';
import { UtlunaPageComponent } from './products/utluna-page.component';
import { VyzerPageComponent } from './products/vyzer-page.component';
import { WallminePageComponent } from './products/wallmine-page.component';
import { WealthfolioPageComponent } from './products/wealthfolio-page.component';
import { WealthicaPageComponent } from './products/wealthica-page.component';
import { WhalPageComponent } from './products/whal-page.component';
@ -67,7 +70,8 @@ export const products: Product[] = [
'Italiano',
'Nederlands',
'Português',
'Türkçe'
'Türkçe',
'简体中文'
],
name: 'Ghostfolio',
origin: $localize`Switzerland`,
@ -408,6 +412,16 @@ export const products: Product[] = [
pricingPerYear: '€60',
slogan: 'Manage all your portfolios'
},
{
component: PortfolioVisualizerPageComponent,
hasFreePlan: true,
hasSelfHostingAbility: false,
key: 'portfolio-visualizer',
languages: ['English'],
name: 'Portfolio Visualizer',
pricingPerYear: '$360',
slogan: 'Tools for Better Investors'
},
{
component: PortseidoPageComponent,
founded: 2021,
@ -484,6 +498,15 @@ export const products: Product[] = [
pricingPerYear: '$80',
slogan: 'Simple and powerful portfolio tracker'
},
{
component: StockEventsPageComponent,
founded: 2019,
hasSelfHostingAbility: false,
key: 'stock-events',
name: 'Stock Events',
origin: $localize`Germany`,
slogan: 'Track all your Investments'
},
{
component: StocklePageComponent,
key: 'stockle',
@ -543,6 +566,16 @@ export const products: Product[] = [
pricingPerYear: '$348',
slogan: 'Virtual Family Office for Smart Wealth Management'
},
{
component: WallminePageComponent,
hasSelfHostingAbility: false,
key: 'wallmine',
languages: ['English'],
name: 'wallmine',
origin: $localize`Czech Republic`,
pricingPerYear: '$600',
slogan: 'Make Smarter Investments'
},
{
component: WealthfolioPageComponent,
hasSelfHostingAbility: true,

32
apps/client/src/app/pages/resources/personal-finance-tools/products/portfolio-visualizer-page.component.ts

@ -0,0 +1,32 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
import { BaseProductPageComponent } from './base-page.component';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-portfolio-visualizer-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class PortfolioVisualizerPageComponent extends BaseProductPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'portfolio-visualizer';
});
public routerLinkAbout = ['/' + $localize`about`];
public routerLinkFeatures = ['/' + $localize`features`];
public routerLinkResourcesPersonalFinanceTools = [
'/' + $localize`resources`,
'personal-finance-tools'
];
}

32
apps/client/src/app/pages/resources/personal-finance-tools/products/stock-events-page.component.ts

@ -0,0 +1,32 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
import { BaseProductPageComponent } from './base-page.component';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-stock-events-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class StockEventsPageComponent extends BaseProductPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'stock-events';
});
public routerLinkAbout = ['/' + $localize`about`];
public routerLinkFeatures = ['/' + $localize`features`];
public routerLinkResourcesPersonalFinanceTools = [
'/' + $localize`resources`,
'personal-finance-tools'
];
}

32
apps/client/src/app/pages/resources/personal-finance-tools/products/wallmine-page.component.ts

@ -0,0 +1,32 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
import { BaseProductPageComponent } from './base-page.component';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-wallmine-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class WallminePageComponent extends BaseProductPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'wallmine';
});
public routerLinkAbout = ['/' + $localize`about`];
public routerLinkFeatures = ['/' + $localize`features`];
public routerLinkResourcesPersonalFinanceTools = [
'/' + $localize`resources`,
'personal-finance-tools'
];
}

5
apps/client/src/app/services/data.service.ts

@ -9,6 +9,7 @@ import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
import { PortfolioHoldingDetail } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-holding-detail.interface';
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface';
import { DeleteOwnUserDto } from '@ghostfolio/api/app/user/delete-own-user.dto';
import { UserItem } from '@ghostfolio/api/app/user/interfaces/user-item.interface';
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
@ -271,6 +272,10 @@ export class DataService {
return this.http.delete<any>(`/api/v1/benchmark/${dataSource}/${symbol}`);
}
public deleteOwnUser(aData: DeleteOwnUserDto) {
return this.http.delete<any>(`/api/v1/user`, { body: aData });
}
public deleteUser(aId: string) {
return this.http.delete<any>(`/api/v1/user/${aId}`);
}

856
apps/client/src/locales/messages.de.xlf

File diff suppressed because it is too large

856
apps/client/src/locales/messages.es.xlf

File diff suppressed because it is too large

856
apps/client/src/locales/messages.fr.xlf

File diff suppressed because it is too large

856
apps/client/src/locales/messages.it.xlf

File diff suppressed because it is too large

856
apps/client/src/locales/messages.nl.xlf

File diff suppressed because it is too large

854
apps/client/src/locales/messages.pl.xlf

File diff suppressed because it is too large

856
apps/client/src/locales/messages.pt.xlf

File diff suppressed because it is too large

860
apps/client/src/locales/messages.tr.xlf

File diff suppressed because it is too large

853
apps/client/src/locales/messages.xlf

File diff suppressed because it is too large

854
apps/client/src/locales/messages.zh.xlf

File diff suppressed because it is too large

3
libs/common/src/lib/permissions.ts

@ -17,6 +17,7 @@ export const permissions = {
deleteAccountBalance: 'deleteAcccountBalance',
deleteAuthDevice: 'deleteAuthDevice',
deleteOrder: 'deleteOrder',
deleteOwnUser: 'deleteOwnUser',
deletePlatform: 'deletePlatform',
deleteTag: 'deleteTag',
deleteUser: 'deleteUser',
@ -57,6 +58,7 @@ export function getPermissions(aRole: Role): string[] {
permissions.deleteAccount,
permissions.deleteAuthDevice,
permissions.deleteOrder,
permissions.deleteOwnUser,
permissions.deletePlatform,
permissions.deleteTag,
permissions.deleteUser,
@ -84,6 +86,7 @@ export function getPermissions(aRole: Role): string[] {
permissions.deleteAccountBalance,
permissions.deleteAuthDevice,
permissions.deleteOrder,
permissions.deleteOwnUser,
permissions.updateAccount,
permissions.updateAuthDevice,
permissions.updateOrder,

2
package.json

@ -120,7 +120,7 @@
"lodash": "4.17.21",
"marked": "9.1.6",
"ms": "3.0.0-canary.1",
"ng-extract-i18n-merge": "2.10.0",
"ng-extract-i18n-merge": "2.12.0",
"ngx-device-detector": "5.0.1",
"ngx-markdown": "17.1.1",
"ngx-skeleton-loader": "7.0.0",

160
yarn.lock

@ -31,12 +31,12 @@
"@angular-devkit/core" "17.3.8"
rxjs "7.8.1"
"@angular-devkit/architect@^0.1301.0 || ^0.1401.0 || ^0.1501.0 || ^0.1601.0 || ^0.1700.0":
version "0.1700.10"
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1700.10.tgz#cf3bb2dfca17bb7d78639b7e55e13bb16627fa62"
integrity sha512-JD/3jkdN1jrFMIDEk9grKdbjutIoxUDMRazq1LZooWjTkzlYk09i/s6HwvIPao7zvxJfelD6asTPspgkjOMP5A==
"@angular-devkit/architect@^0.1301.0 || ^0.1401.0 || ^0.1501.0 || ^0.1601.0 || ^0.1700.0 || ^0.1800.0":
version "0.1800.2"
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1800.2.tgz#c4bc51e654558c7e7d27e0558b671d6731d46ccf"
integrity sha512-PX7lCTAqWe9C40+fie+DAc8vhpGA+JgZKWWrMHUTV/iZx8RXx2X4xGQsqYu36p4i3MSfQdbn+0xLWGmjScPVOQ==
dependencies:
"@angular-devkit/core" "17.0.10"
"@angular-devkit/core" "18.0.2"
rxjs "7.8.1"
"@angular-devkit/build-angular@17.3.8":
@ -140,18 +140,6 @@
rxjs "7.8.1"
source-map "0.7.4"
"@angular-devkit/core@17.0.10":
version "17.0.10"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.0.10.tgz#dce8b3cd4b90d694ed5ccf405c8f25e45938b310"
integrity sha512-93N6oHnmtRt0hL3AXxvnk47sN1rHndfj+pqI5haEY41AGWzIWv9cSBsqlM0PWltNpo6VivcExZESvbLJ71wqbQ==
dependencies:
ajv "8.12.0"
ajv-formats "2.1.1"
jsonc-parser "3.2.0"
picomatch "3.0.1"
rxjs "7.8.1"
source-map "0.7.4"
"@angular-devkit/core@17.3.3":
version "17.3.3"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.3.3.tgz#dce2f615355b2ef59c19927d90620670a6c890d0"
@ -164,10 +152,10 @@
rxjs "7.8.1"
source-map "0.7.4"
"@angular-devkit/core@17.3.7":
version "17.3.7"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.3.7.tgz#553b456cf9747eecdb1f7389127057bc5cb9f8e4"
integrity sha512-qpZ7BShyqS/Jqld36E7kL02cyb2pjn1Az1p9439SbP8nsvJgYlsyjwYK2Kmcn/Wi+TZGIKxkqxgBBw9vqGgeJw==
"@angular-devkit/core@17.3.8":
version "17.3.8"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.3.8.tgz#8679cacf84cf79764f027811020e235ab32016d2"
integrity sha512-Q8q0voCGudbdCgJ7lXdnyaxKHbNQBARH68zPQV72WT8NWy+Gw/tys870i6L58NWbBaCJEUcIj/kb6KoakSRu+Q==
dependencies:
ajv "8.12.0"
ajv-formats "2.1.1"
@ -176,15 +164,15 @@
rxjs "7.8.1"
source-map "0.7.4"
"@angular-devkit/core@17.3.8", "@angular-devkit/core@^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0":
version "17.3.8"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.3.8.tgz#8679cacf84cf79764f027811020e235ab32016d2"
integrity sha512-Q8q0voCGudbdCgJ7lXdnyaxKHbNQBARH68zPQV72WT8NWy+Gw/tys870i6L58NWbBaCJEUcIj/kb6KoakSRu+Q==
"@angular-devkit/core@18.0.2", "@angular-devkit/core@^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0":
version "18.0.2"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-18.0.2.tgz#6757af88d6d433b75392e124b50fa990466d02b2"
integrity sha512-QXcEdfmODc0rKblBerk30yw70fypIkFm6gQBLJgsshpwc+TMA+fuMLcPQebOTzKLtD2tNUkk/7SrWPQIGqeXaA==
dependencies:
ajv "8.12.0"
ajv-formats "2.1.1"
ajv "8.13.0"
ajv-formats "3.0.1"
jsonc-parser "3.2.1"
picomatch "4.0.1"
picomatch "4.0.2"
rxjs "7.8.1"
source-map "0.7.4"
@ -243,6 +231,17 @@
ora "5.4.1"
rxjs "7.8.1"
"@angular-devkit/schematics@18.0.2", "@angular-devkit/schematics@^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0":
version "18.0.2"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-18.0.2.tgz#9795a79f4de2f622c388fe074153f8abb0ee22a4"
integrity sha512-G9yGcoB67sH0eRNWoiQWNn2KwiI7sDasVscYPGKf1yo7JRiXmzX/LpfKRPsZTl+Bs0FItnwDInsqgMisK89/6g==
dependencies:
"@angular-devkit/core" "18.0.2"
jsonc-parser "3.2.1"
magic-string "0.30.10"
ora "5.4.1"
rxjs "7.8.1"
"@angular-eslint/bundled-angular-compiler@17.5.1":
version "17.5.1"
resolved "https://registry.yarnpkg.com/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-17.5.1.tgz#21017c29486bf2f0f83501fb034dd0ff0a2c173e"
@ -5050,13 +5049,13 @@
"@angular-devkit/schematics" "17.3.8"
jsonc-parser "3.2.1"
"@schematics/angular@^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0":
version "17.3.7"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-17.3.7.tgz#0e033e9a999ffcfc24cb2ce36529b5b5d5241312"
integrity sha512-HaJroKaberriP4wFefTTSVFrtU9GMvnG3I6ELbOteOyKMH7o2V91FXGJDJ5KnIiLRlBmC30G3r+9Ybc/rtAYkw==
"@schematics/angular@^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0":
version "18.0.2"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-18.0.2.tgz#bc1f863b6f8b6d7a49fef8eccadda545f4fcf91d"
integrity sha512-qkJs1oxHtneJ6QxDKpxNyneXGDM9SKVj+Bgi8xUAU3FEzpsYmE/aW3MfwYHOZl0pDBO8c2raqLvlyl3dGP6/Gg==
dependencies:
"@angular-devkit/core" "17.3.7"
"@angular-devkit/schematics" "17.3.7"
"@angular-devkit/core" "18.0.2"
"@angular-devkit/schematics" "18.0.2"
jsonc-parser "3.2.1"
"@sigstore/bundle@^2.3.0", "@sigstore/bundle@^2.3.1":
@ -7373,6 +7372,13 @@ ajv-formats@2.1.1, ajv-formats@^2.1.1:
dependencies:
ajv "^8.0.0"
ajv-formats@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578"
integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==
dependencies:
ajv "^8.0.0"
ajv-keywords@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
@ -7405,6 +7411,16 @@ ajv@8.12.0:
require-from-string "^2.0.2"
uri-js "^4.2.2"
ajv@8.13.0:
version "8.13.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.13.0.tgz#a3939eaec9fb80d217ddf0c3376948c023f28c91"
integrity sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==
dependencies:
fast-deep-equal "^3.1.3"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.4.1"
ajv@^6.12.4, ajv@^6.12.5:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@ -13869,6 +13885,13 @@ magic-string@0.30.0:
dependencies:
"@jridgewell/sourcemap-codec" "^1.4.13"
magic-string@0.30.10:
version "0.30.10"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e"
integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==
dependencies:
"@jridgewell/sourcemap-codec" "^1.4.15"
magic-string@0.30.8:
version "0.30.8"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.8.tgz#14e8624246d2bedba70d5462aa99ac9681844613"
@ -13876,10 +13899,10 @@ magic-string@0.30.8:
dependencies:
"@jridgewell/sourcemap-codec" "^1.4.15"
magic-string@~0.30.2:
version "0.30.10"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e"
integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==
magic-string@^0.30.5, magic-string@~0.30.2:
version "0.30.5"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9"
integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==
dependencies:
"@jridgewell/sourcemap-codec" "^1.4.15"
@ -14568,16 +14591,16 @@ neo-async@^2.5.0, neo-async@^2.6.2:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
ng-extract-i18n-merge@2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/ng-extract-i18n-merge/-/ng-extract-i18n-merge-2.10.0.tgz#20d2a4a1d21f058773242cbcc8406c4ef0f8fea0"
integrity sha512-mWYRWAUc7kirS3kIQxUR0kGv7Yv5JnV0C05VNvGwHdyMM3vSdJ0WAE/o4RwzW1cRyzXuG9oNOz4gctTzQsTErw==
ng-extract-i18n-merge@2.12.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/ng-extract-i18n-merge/-/ng-extract-i18n-merge-2.12.0.tgz#857db1c035db8b1d29b6d7cd91ef94f824f41bfb"
integrity sha512-ohzt7WLraXS0PVPEohYK0f/TxzGu/1vOYeukncd8r6sJybrSUG/dm/lpxC4Ozkhq4rhKEHRPRTKA5x59qjIfjw==
dependencies:
"@angular-devkit/architect" "^0.1301.0 || ^0.1401.0 || ^0.1501.0 || ^0.1601.0 || ^0.1700.0"
"@angular-devkit/core" "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
"@angular-devkit/schematics" "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
"@schematics/angular" "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
xmldoc "^1.1.2"
"@angular-devkit/architect" "^0.1301.0 || ^0.1401.0 || ^0.1501.0 || ^0.1601.0 || ^0.1700.0 || ^0.1800.0"
"@angular-devkit/core" "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
"@angular-devkit/schematics" "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
"@schematics/angular" "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
xmldoc "^1.1.3"
ngx-device-detector@5.0.1:
version "5.0.1"
@ -15473,16 +15496,16 @@ picocolors@^1.0.0:
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-3.0.1.tgz#817033161def55ec9638567a2f3bbc876b3e7516"
integrity sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==
picomatch@4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.1.tgz#68c26c8837399e5819edce48590412ea07f17a07"
integrity sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==
picomatch@4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
@ -17299,7 +17322,7 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -17317,6 +17340,15 @@ string-width@^1.0.1:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
@ -17376,7 +17408,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -17397,6 +17429,13 @@ strip-ansi@^4.0.0:
dependencies:
ansi-regex "^3.0.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@ -18838,7 +18877,7 @@ wordwrap@^1.0.0:
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -18864,6 +18903,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
@ -18922,7 +18970,7 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
xmldoc@^1.1.2:
xmldoc@^1.1.3:
version "1.3.0"
resolved "https://registry.yarnpkg.com/xmldoc/-/xmldoc-1.3.0.tgz#7823225b096c74036347c9ec5924d06b6a3cebab"
integrity sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==

Loading…
Cancel
Save