Browse Source

Task/deprecate account and account balance endpoints in favor of accounts endpoints (#8332)

* Deprecate account and account balance endpoints in favor of accounts endpoints

* Update changelog
pull/8336/merge
Thomas Kaul 3 hours ago
committed by GitHub
parent
commit
69c1365c69
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 157
      apps/api/src/app/__snapshots__/app.module.spec.ts.snap
  3. 13
      apps/api/src/app/account-balance/account-balance.module.ts
  4. 6
      apps/api/src/app/app.module.ts
  5. 5
      apps/api/src/app/endpoints/accounts/account-balance.controller.ts
  6. 72
      apps/api/src/app/endpoints/accounts/accounts.controller.ts
  7. 11
      apps/api/src/app/endpoints/accounts/accounts.module.ts
  8. 0
      apps/api/src/app/endpoints/accounts/get-all-accounts.dto.ts
  9. 1
      apps/api/src/services/account-balance/account-balance.service.ts
  10. 7
      apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts
  11. 1
      apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html
  12. 9
      libs/common/src/lib/dtos/create-account-balance-with-account-id.dto.ts
  13. 5
      libs/common/src/lib/dtos/create-account-balance.dto.ts
  14. 2
      libs/common/src/lib/dtos/index.ts
  15. 2
      libs/common/src/lib/interfaces/index.ts
  16. 6
      libs/common/src/lib/interfaces/responses/account-balance-response.interface.ts
  17. 4
      libs/common/src/lib/interfaces/responses/account-balances-response.interface.ts
  18. 2
      libs/ui/src/lib/account-balances/account-balances.component.ts
  19. 42
      libs/ui/src/lib/services/data.service.ts

1
CHANGELOG.md

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Restricted the health check endpoint of the AI service (`GET api/v1/health/ai`) to administrators
- Increased the timeout in the health check endpoint of the AI service to 30 seconds
- Deprecated the `api/v1/access` endpoints in favor of the `api/v1/accesses` endpoints
- Deprecated the `api/v1/account` and `api/v1/account-balance` endpoints in favor of the `api/v1/accounts` endpoints
- Improved the performance of the code style check in the build workflow by skipping the warning-level rules of _ESLint_
- Refreshed the cryptocurrencies list

157
apps/api/src/app/__snapshots__/app.module.spec.ts.snap

@ -50,6 +50,47 @@ exports[`AppModule should keep the security of each route 1`] = `
"account:delete",
],
},
"DELETE /account/:id/balances/:balanceId": {
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
"ImpersonationGuard",
"ScopeGuard",
],
"interceptors": [],
"permission": "deleteAccountBalance",
"scopes": [
"account:update",
],
},
"DELETE /accounts/:id": {
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
"ImpersonationGuard",
"ScopeGuard",
],
"interceptors": [
"RedactValuesInResponseInterceptor",
],
"permission": "deleteAccount",
"scopes": [
"account:delete",
],
},
"DELETE /accounts/:id/balances/:balanceId": {
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
"ImpersonationGuard",
"ScopeGuard",
],
"interceptors": [],
"permission": "deleteAccountBalance",
"scopes": [
"account:update",
],
},
"DELETE /activities": {
"guards": [
"AuthGuard('jwt')",
@ -263,6 +304,49 @@ exports[`AppModule should keep the security of each route 1`] = `
"account:read",
],
},
"GET /accounts": {
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
"ImpersonationGuard",
"ScopeGuard",
],
"interceptors": [
"TransformDataSourceInRequestInterceptor",
"RedactValuesInResponseInterceptor",
],
"scopes": [
"account:read",
],
},
"GET /accounts/:id": {
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
"ImpersonationGuard",
"ScopeGuard",
],
"interceptors": [
"RedactValuesInResponseInterceptor",
],
"scopes": [
"account:read",
],
},
"GET /accounts/:id/balances": {
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
"ImpersonationGuard",
"ScopeGuard",
],
"interceptors": [
"RedactValuesInResponseInterceptor",
],
"scopes": [
"account:read",
],
},
"GET /activities": {
"guards": [
"AuthGuard('jwt')",
@ -862,6 +946,21 @@ exports[`AppModule should keep the security of each route 1`] = `
"account:update",
],
},
"POST /account/:id/balances": {
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
"ImpersonationGuard",
"ScopeGuard",
],
"interceptors": [
"RedactValuesInResponseInterceptor",
],
"permission": "createAccountBalance",
"scopes": [
"account:update",
],
},
"POST /account/transfer-balance": {
"guards": [
"AuthGuard('jwt')",
@ -875,6 +974,49 @@ exports[`AppModule should keep the security of each route 1`] = `
"account:update",
],
},
"POST /accounts": {
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
"ImpersonationGuard",
"ScopeGuard",
],
"interceptors": [
"RedactValuesInResponseInterceptor",
],
"permission": "createAccount",
"scopes": [
"account:create",
],
},
"POST /accounts/:id/balances": {
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
"ImpersonationGuard",
"ScopeGuard",
],
"interceptors": [
"RedactValuesInResponseInterceptor",
],
"permission": "createAccountBalance",
"scopes": [
"account:update",
],
},
"POST /accounts/transfer-balance": {
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
"ImpersonationGuard",
"ScopeGuard",
],
"interceptors": [],
"permission": "updateAccount",
"scopes": [
"account:update",
],
},
"POST /activities": {
"guards": [
"AuthGuard('jwt')",
@ -1187,6 +1329,21 @@ exports[`AppModule should keep the security of each route 1`] = `
"account:update",
],
},
"PUT /accounts/:id": {
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
"ImpersonationGuard",
"ScopeGuard",
],
"interceptors": [
"RedactValuesInResponseInterceptor",
],
"permission": "updateAccount",
"scopes": [
"account:update",
],
},
"PUT /activities/:id": {
"guards": [
"AuthGuard('jwt')",

13
apps/api/src/app/account-balance/account-balance.module.ts

@ -1,13 +0,0 @@
import { AccountBalanceModule as AccountBalanceServiceModule } from '@ghostfolio/api/services/account-balance/account-balance.module';
import { AccountModule } from '@ghostfolio/api/services/account/account.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
import { Module } from '@nestjs/common';
import { AccountBalanceController } from './account-balance.controller';
@Module({
controllers: [AccountBalanceController],
imports: [AccountBalanceServiceModule, AccountModule, ImpersonationModule]
})
export class AccountBalanceModule {}

6
apps/api/src/app/app.module.ts

@ -35,12 +35,11 @@ import { ThrottlerModule } from '@nestjs/throttler';
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
import { join } from 'node:path';
import { AccountBalanceModule } from './account-balance/account-balance.module';
import { AccountModule } from './account/account.module';
import { ActivitiesModule } from './activities/activities.module';
import { AdminModule } from './admin/admin.module';
import { AppController } from './app.controller';
import { AccessesModule } from './endpoints/accesses/accesses.module';
import { AccountsModule } from './endpoints/accounts/accounts.module';
import { AiModule } from './endpoints/ai/ai.module';
import { ApiKeysModule } from './endpoints/api-keys/api-keys.module';
import { AssetProfilesModule } from './endpoints/asset-profiles/asset-profiles.module';
@ -73,8 +72,7 @@ import { UserModule } from './user/user.module';
imports: [
AdminModule,
AccessesModule,
AccountBalanceModule,
AccountModule,
AccountsModule,
ActivitiesModule,
AiModule,
ApiKeysModule,

5
apps/api/src/app/account-balance/account-balance.controller.ts → apps/api/src/app/endpoints/accounts/account-balance.controller.ts

@ -4,7 +4,7 @@ import { RequiresScope } from '@ghostfolio/api/decorators/requires-scope.decorat
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor';
import { AccountBalanceService } from '@ghostfolio/api/services/account-balance/account-balance.service';
import { AccountService } from '@ghostfolio/api/services/account/account.service';
import { CreateAccountBalanceDto } from '@ghostfolio/common/dtos';
import { CreateAccountBalanceWithAccountIdDto } from '@ghostfolio/common/dtos';
import { permissions } from '@ghostfolio/common/permissions';
import { scopes } from '@ghostfolio/common/scopes';
import type { ImpersonationContext } from '@ghostfolio/common/types';
@ -21,6 +21,7 @@ import {
import { AccountBalance } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
/** @deprecated */
@Controller('account-balance')
export class AccountBalanceController {
public constructor(
@ -33,7 +34,7 @@ export class AccountBalanceController {
@RequiresScope(scopes.accountUpdate)
@UseInterceptors(RedactValuesInResponseInterceptor)
public async createAccountBalance(
@Body() data: CreateAccountBalanceDto,
@Body() data: CreateAccountBalanceWithAccountIdDto,
@Impersonation() { userId }: ImpersonationContext
): Promise<AccountBalance> {
const account = await this.accountService.account({

72
apps/api/src/app/account/account.controller.ts → apps/api/src/app/endpoints/accounts/accounts.controller.ts

@ -8,11 +8,13 @@ import { AccountBalanceService } from '@ghostfolio/api/services/account-balance/
import { AccountService } from '@ghostfolio/api/services/account/account.service';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import {
CreateAccountBalanceDto,
CreateAccountDto,
TransferBalanceDto,
UpdateAccountDto
} from '@ghostfolio/common/dtos';
import {
AccountBalanceResponse,
AccountBalancesResponse,
AccountResponse,
AccountsResponse
@ -35,11 +37,16 @@ import {
} from '@nestjs/common';
import { Account as AccountModel } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { pick } from 'lodash-es';
import { GetAllAccountsDto } from './get-all-accounts.dto';
@Controller('account')
export class AccountController {
@Controller([
'accounts',
/** @deprecated */
'account'
])
export class AccountsController {
public constructor(
private readonly accountBalanceService: AccountBalanceService,
private readonly accountService: AccountService,
@ -80,6 +87,33 @@ export class AccountController {
});
}
@Delete(':id/balances/:balanceId')
@HasPermission(permissions.deleteAccountBalance)
@RequiresScope(scopes.accountUpdate)
public async deleteAccountBalance(
@Impersonation() { userId }: ImpersonationContext,
@Param('balanceId') balanceId: string,
@Param('id') id: string
): Promise<void> {
const accountBalance = await this.accountBalanceService.accountBalance({
userId,
accountId: id,
id: balanceId
});
if (!accountBalance) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
await this.accountBalanceService.deleteAccountBalance({
id: accountBalance.id,
userId: accountBalance.userId
});
}
@Get()
@RequiresScope(scopes.accountRead)
@UseInterceptors(RedactValuesInResponseInterceptor)
@ -182,6 +216,40 @@ export class AccountController {
}
}
@HasPermission(permissions.createAccountBalance)
@Post(':id/balances')
@RequiresScope(scopes.accountUpdate)
@UseInterceptors(RedactValuesInResponseInterceptor)
public async createAccountBalance(
@Body() { balance, date }: CreateAccountBalanceDto,
@Impersonation() { userId }: ImpersonationContext,
@Param('id') id: string
): Promise<AccountBalanceResponse> {
const account = await this.accountService.account({
id_userId: {
id,
userId
}
});
if (!account) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const accountBalance =
await this.accountBalanceService.createOrUpdateAccountBalance({
balance,
date,
accountId: account.id,
userId: account.userId
});
return pick(accountBalance, ['accountId', 'date', 'id', 'value']);
}
@HasPermission(permissions.updateAccount)
@Post('transfer-balance')
@RequiresScope(scopes.accountUpdate)

11
apps/api/src/app/account/account.module.ts → apps/api/src/app/endpoints/accounts/accounts.module.ts

@ -2,20 +2,21 @@ import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module'
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module';
import { AccountBalanceModule } from '@ghostfolio/api/services/account-balance/account-balance.module';
import { AccountModule as AccountServiceModule } from '@ghostfolio/api/services/account/account.module';
import { AccountModule } from '@ghostfolio/api/services/account/account.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
import { Module } from '@nestjs/common';
import { AccountController } from './account.controller';
import { AccountBalanceController } from './account-balance.controller';
import { AccountsController } from './accounts.controller';
@Module({
controllers: [AccountController],
controllers: [AccountBalanceController, AccountsController],
imports: [
AccountBalanceModule,
AccountServiceModule,
AccountModule,
ApiModule,
ConfigurationModule,
ImpersonationModule,
@ -24,4 +25,4 @@ import { AccountController } from './account.controller';
UserModule
]
})
export class AccountModule {}
export class AccountsModule {}

0
apps/api/src/app/account/get-all-accounts.dto.ts → apps/api/src/app/endpoints/accounts/get-all-accounts.dto.ts

1
apps/api/src/services/account-balance/account-balance.service.ts

@ -49,6 +49,7 @@ export class AccountBalanceService {
date,
userId
}: CreateAccountBalanceDto & {
accountId: string;
userId: string;
}): Promise<AccountBalance> {
const dateOfBalance = resetHours(parseISO(date));

7
apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts

@ -176,7 +176,10 @@ export class GfAccountDetailDialogComponent implements OnInit {
this.isSubmitting.set(true);
this.dataService
.postAccountBalance(accountBalance)
.postAccountBalance({
accountBalance,
accountId: this.data.accountId
})
.pipe(
finalize(() => {
this.isSubmitting.set(false);
@ -202,7 +205,7 @@ export class GfAccountDetailDialogComponent implements OnInit {
protected onDeleteAccountBalance(aId: string) {
this.dataService
.deleteAccountBalance(aId)
.deleteAccountBalance({ accountId: this.data.accountId, id: aId })
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.initialize();

1
apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html

@ -211,7 +211,6 @@
<gf-account-balances
[accountBalances]="accountBalances"
[accountCurrency]="currency"
[accountId]="data.accountId"
[currentBalance]="balance"
[isSubmitting]="isSubmitting()"
[locale]="user?.settings?.locale"

9
libs/common/src/lib/dtos/create-account-balance-with-account-id.dto.ts

@ -0,0 +1,9 @@
import { IsUUID } from 'class-validator';
import { CreateAccountBalanceDto } from './create-account-balance.dto';
/** @deprecated */
export class CreateAccountBalanceWithAccountIdDto extends CreateAccountBalanceDto {
@IsUUID()
accountId: string;
}

5
libs/common/src/lib/dtos/create-account-balance.dto.ts

@ -1,11 +1,8 @@
import { IsValidAccountBalanceDateConstraint } from '@ghostfolio/common/validator-constraints/is-valid-account-balance-date';
import { IsISO8601, IsNumber, IsUUID, Validate } from 'class-validator';
import { IsISO8601, IsNumber, Validate } from 'class-validator';
export class CreateAccountBalanceDto {
@IsUUID()
accountId: string;
@IsNumber()
balance: number;

2
libs/common/src/lib/dtos/index.ts

@ -1,6 +1,7 @@
import { AuthDeviceDto } from './auth-device.dto';
import { CountryDto } from './country.dto';
import { CreateAccessDto } from './create-access.dto';
import { CreateAccountBalanceWithAccountIdDto } from './create-account-balance-with-account-id.dto';
import { CreateAccountBalanceDto } from './create-account-balance.dto';
import { CreateAccountWithBalancesDto } from './create-account-with-balances.dto';
import { CreateAccountDto } from './create-account.dto';
@ -36,6 +37,7 @@ export {
CountryDto,
CreateAccessDto,
CreateAccountBalanceDto,
CreateAccountBalanceWithAccountIdDto,
CreateAccountDto,
CreateAccountWithBalancesDto,
CreateAssetProfileDto,

2
libs/common/src/lib/interfaces/index.ts

@ -41,6 +41,7 @@ import type { ReferralPartner } from './referral-partner.interface';
import type { AccessResponse } from './responses/access-response.interface';
import type { AccessTokenResponse } from './responses/access-token-response.interface';
import type { AccessesResponse } from './responses/accesses-response.interface';
import type { AccountBalanceResponse } from './responses/account-balance-response.interface';
import type { AccountBalancesResponse } from './responses/account-balances-response.interface';
import type { AccountResponse } from './responses/account-response.interface';
import type { AccountsResponse } from './responses/accounts-response.interface';
@ -114,6 +115,7 @@ export {
AccessTokenResponse,
AccessesResponse,
AccountBalance,
AccountBalanceResponse,
AccountBalancesResponse,
AccountResponse,
AccountsResponse,

6
libs/common/src/lib/interfaces/responses/account-balance-response.interface.ts

@ -0,0 +1,6 @@
import type { AccountBalance } from '@ghostfolio/prisma/browser';
export interface AccountBalanceResponse extends Pick<
AccountBalance,
'accountId' | 'date' | 'id' | 'value'
> {}

4
libs/common/src/lib/interfaces/responses/account-balances-response.interface.ts

@ -1,7 +1,7 @@
import type { AccountBalance } from '@ghostfolio/prisma/browser';
import type { AccountBalanceResponse } from './account-balance-response.interface';
export interface AccountBalancesResponse {
balances: (Pick<AccountBalance, 'accountId' | 'date' | 'id' | 'value'> & {
balances: (AccountBalanceResponse & {
valueInBaseCurrency: number;
})[];
}

2
libs/ui/src/lib/account-balances/account-balances.component.ts

@ -69,7 +69,6 @@ export class GfAccountBalancesComponent implements OnChanges {
input.required<AccountBalancesResponse['balances']>();
public readonly accountCurrency = input.required<string | null>();
public readonly accountId = input.required<string>();
public readonly currentBalance = input<number | null>();
public readonly displayedColumns: string[] = ['date', 'value', 'actions'];
public readonly isSubmitting = input(false);
@ -138,7 +137,6 @@ export class GfAccountBalancesComponent implements OnChanges {
const accountBalance: CreateAccountBalanceDto = {
balance,
accountId: this.accountId(),
date: format(date, DATE_FORMAT)
};

42
libs/ui/src/lib/services/data.service.ts

@ -24,6 +24,7 @@ import {
AccessResponse,
AccessTokenResponse,
AccessesResponse,
AccountBalanceResponse,
AccountBalancesResponse,
AccountResponse,
AccountsResponse,
@ -73,7 +74,6 @@ import type {
} from '@ghostfolio/common/types';
import type {
Account,
AccountBalance,
MarketData,
Order,
SymbolProfile,
@ -199,19 +199,19 @@ export class DataService {
}
public fetchAccount(aAccountId: string) {
return this.http.get<AccountResponse>(`/api/v1/account/${aAccountId}`);
return this.http.get<AccountResponse>(`/api/v1/accounts/${aAccountId}`);
}
public fetchAccountBalances(aAccountId: string) {
return this.http.get<AccountBalancesResponse>(
`/api/v1/account/${aAccountId}/balances`
`/api/v1/accounts/${aAccountId}/balances`
);
}
public fetchAccounts({ filters }: { filters?: Filter[] } = {}) {
const params = this.buildFiltersAsQueryParams({ filters });
return this.http.get<AccountsResponse>('/api/v1/account', { params });
return this.http.get<AccountsResponse>('/api/v1/accounts', { params });
}
public fetchActivities({
@ -315,11 +315,19 @@ export class DataService {
}
public deleteAccount(aId: string) {
return this.http.delete<Account>(`/api/v1/account/${aId}`);
return this.http.delete<Account>(`/api/v1/accounts/${aId}`);
}
public deleteAccountBalance(aId: string) {
return this.http.delete<AccountBalance>(`/api/v1/account-balance/${aId}`);
public deleteAccountBalance({
accountId,
id
}: {
accountId: string;
id: string;
}) {
return this.http.delete<void>(
`/api/v1/accounts/${accountId}/balances/${id}`
);
}
public deleteActivities({
@ -867,13 +875,19 @@ export class DataService {
}
public postAccount(aAccount: CreateAccountDto) {
return this.http.post<Account>('/api/v1/account', aAccount);
return this.http.post<Account>('/api/v1/accounts', aAccount);
}
public postAccountBalance(aAccountBalance: CreateAccountBalanceDto) {
return this.http.post<AccountBalance>(
'/api/v1/account-balance',
aAccountBalance
public postAccountBalance({
accountBalance,
accountId
}: {
accountBalance: CreateAccountBalanceDto;
accountId: string;
}) {
return this.http.post<AccountBalanceResponse>(
`/api/v1/accounts/${accountId}/balances`,
accountBalance
);
}
@ -934,7 +948,7 @@ export class DataService {
}
public putAccount(aAccount: UpdateAccountDto) {
return this.http.put<UserItem>(`/api/v1/account/${aAccount.id}`, aAccount);
return this.http.put<UserItem>(`/api/v1/accounts/${aAccount.id}`, aAccount);
}
public putActivity(aOrder: UpdateOrderDto) {
@ -985,7 +999,7 @@ export class DataService {
accountIdTo,
balance
}: TransferBalanceDto) {
return this.http.post('/api/v1/account/transfer-balance', {
return this.http.post('/api/v1/accounts/transfer-balance', {
accountIdFrom,
accountIdTo,
balance

Loading…
Cancel
Save