diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 0c332f504a..1a6ffd372e 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -40,12 +40,12 @@ import { AccountModule } from './account/account.module'; import { ActivitiesModule } from './activities/activities.module'; import { AdminModule } from './admin/admin.module'; import { AppController } from './app.controller'; -import { AuthModule } from './auth/auth.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'; import { AssetsModule } from './endpoints/assets/assets.module'; import { AuthDevicesModule } from './endpoints/auth-devices/auth-devices.module'; +import { AuthModule } from './endpoints/auth/auth.module'; import { BenchmarksModule } from './endpoints/benchmarks/benchmarks.module'; import { CacheModule } from './endpoints/cache/cache.module'; import { GhostfolioModule } from './endpoints/data-providers/ghostfolio/ghostfolio.module'; @@ -60,10 +60,10 @@ import { McpModule } from './endpoints/mcp/mcp.module'; import { PlatformsModule } from './endpoints/platforms/platforms.module'; import { PublicModule } from './endpoints/public/public.module'; import { SitemapModule } from './endpoints/sitemap/sitemap.module'; +import { SubscriptionModule } from './endpoints/subscription/subscription.module'; import { TagsModule } from './endpoints/tags/tags.module'; import { WatchlistModule } from './endpoints/watchlist/watchlist.module'; import { PortfolioModule } from './portfolio/portfolio.module'; -import { SubscriptionModule } from './subscription/subscription.module'; import { SymbolModule } from './symbol/symbol.module'; import { UserModule } from './user/user.module'; diff --git a/apps/api/src/app/auth/api-key.strategy.ts b/apps/api/src/app/endpoints/auth/api-key.strategy.ts similarity index 100% rename from apps/api/src/app/auth/api-key.strategy.ts rename to apps/api/src/app/endpoints/auth/api-key.strategy.ts diff --git a/apps/api/src/app/auth/auth.controller.ts b/apps/api/src/app/endpoints/auth/auth.controller.ts similarity index 91% rename from apps/api/src/app/auth/auth.controller.ts rename to apps/api/src/app/endpoints/auth/auth.controller.ts index 4cc42b5c16..f0292167c2 100644 --- a/apps/api/src/app/auth/auth.controller.ts +++ b/apps/api/src/app/endpoints/auth/auth.controller.ts @@ -1,10 +1,10 @@ -import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service'; import { AllowDuringImpersonation } from '@ghostfolio/api/decorators/allow-during-impersonation.decorator'; import { CustomThrottlerGuard } from '@ghostfolio/api/guards/custom-throttler.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { OAuthCallbackGuard } from '@ghostfolio/api/guards/oauth-callback.guard'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config'; +import { AuthDeviceDto } from '@ghostfolio/common/dtos'; import { AssertionCredentialJSON, AttestationCredentialJSON, @@ -24,11 +24,16 @@ import { VERSION_NEUTRAL } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; +import type { + PublicKeyCredentialCreationOptionsJSON, + PublicKeyCredentialRequestOptionsJSON +} from '@simplewebauthn/server'; import { Request, Response } from 'express'; import { getReasonPhrase, StatusCodes } from 'http-status-codes'; import { AuthService } from './auth.service'; import { GenerateAuthenticationOptionsDto } from './generate-authentication-options.dto'; +import { WebAuthService } from './web-auth.service'; @AllowDuringImpersonation() @Controller('auth') @@ -124,13 +129,13 @@ export class AuthController { @UseGuards(CustomThrottlerGuard) public async generateAuthenticationOptions( @Body() body: GenerateAuthenticationOptionsDto - ) { + ): Promise { return this.webAuthService.generateAuthenticationOptions(body.deviceId); } @Get('webauthn/generate-registration-options') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) - public async generateRegistrationOptions() { + public async generateRegistrationOptions(): Promise { return this.webAuthService.generateRegistrationOptions(); } @@ -138,7 +143,7 @@ export class AuthController { @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async verifyAttestation( @Body() body: { deviceName: string; credential: AttestationCredentialJSON } - ) { + ): Promise { return this.webAuthService.verifyAttestation(body.credential); } @@ -146,7 +151,7 @@ export class AuthController { @UseGuards(CustomThrottlerGuard) public async verifyAuthentication( @Body() body: { deviceId: string; credential: AssertionCredentialJSON } - ) { + ): Promise { try { const authToken = await this.webAuthService.verifyAuthentication( body.deviceId, diff --git a/apps/api/src/app/auth/auth.module.ts b/apps/api/src/app/endpoints/auth/auth.module.ts similarity index 96% rename from apps/api/src/app/auth/auth.module.ts rename to apps/api/src/app/endpoints/auth/auth.module.ts index afeebe98d0..01951b91c0 100644 --- a/apps/api/src/app/auth/auth.module.ts +++ b/apps/api/src/app/endpoints/auth/auth.module.ts @@ -1,5 +1,3 @@ -import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service'; -import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module'; import { UserModule } from '@ghostfolio/api/app/user/user.module'; import { ApiKeyService } from '@ghostfolio/api/services/api-key/api-key.service'; import { ApiModule } from '@ghostfolio/api/services/api/api.module'; @@ -24,6 +22,7 @@ import { GoogleStrategy } from './google.strategy'; import { JwtStrategy } from './jwt.strategy'; import { getOidcDiscoveryUrl } from './oidc.helper'; import { OidcStrategy } from './oidc.strategy'; +import { WebAuthService } from './web-auth.service'; @Module({ controllers: [AuthController], @@ -40,7 +39,6 @@ import { OidcStrategy } from './oidc.strategy'; PrismaModule, PropertyModule, RedisCacheModule, - SubscriptionModule, UserModule ], providers: [ diff --git a/apps/api/src/app/auth/auth.service.ts b/apps/api/src/app/endpoints/auth/auth.service.ts similarity index 100% rename from apps/api/src/app/auth/auth.service.ts rename to apps/api/src/app/endpoints/auth/auth.service.ts diff --git a/apps/api/src/app/auth/generate-authentication-options.dto.ts b/apps/api/src/app/endpoints/auth/generate-authentication-options.dto.ts similarity index 100% rename from apps/api/src/app/auth/generate-authentication-options.dto.ts rename to apps/api/src/app/endpoints/auth/generate-authentication-options.dto.ts diff --git a/apps/api/src/app/auth/google.strategy.ts b/apps/api/src/app/endpoints/auth/google.strategy.ts similarity index 100% rename from apps/api/src/app/auth/google.strategy.ts rename to apps/api/src/app/endpoints/auth/google.strategy.ts diff --git a/apps/api/src/app/auth/interfaces/interfaces.ts b/apps/api/src/app/endpoints/auth/interfaces/interfaces.ts similarity index 100% rename from apps/api/src/app/auth/interfaces/interfaces.ts rename to apps/api/src/app/endpoints/auth/interfaces/interfaces.ts diff --git a/apps/api/src/app/auth/jwt.strategy.ts b/apps/api/src/app/endpoints/auth/jwt.strategy.ts similarity index 100% rename from apps/api/src/app/auth/jwt.strategy.ts rename to apps/api/src/app/endpoints/auth/jwt.strategy.ts diff --git a/apps/api/src/app/auth/oidc-state.store.ts b/apps/api/src/app/endpoints/auth/oidc-state.store.ts similarity index 100% rename from apps/api/src/app/auth/oidc-state.store.ts rename to apps/api/src/app/endpoints/auth/oidc-state.store.ts diff --git a/apps/api/src/app/auth/oidc.helper.spec.ts b/apps/api/src/app/endpoints/auth/oidc.helper.spec.ts similarity index 100% rename from apps/api/src/app/auth/oidc.helper.spec.ts rename to apps/api/src/app/endpoints/auth/oidc.helper.spec.ts diff --git a/apps/api/src/app/auth/oidc.helper.ts b/apps/api/src/app/endpoints/auth/oidc.helper.ts similarity index 100% rename from apps/api/src/app/auth/oidc.helper.ts rename to apps/api/src/app/endpoints/auth/oidc.helper.ts diff --git a/apps/api/src/app/auth/oidc.strategy.ts b/apps/api/src/app/endpoints/auth/oidc.strategy.ts similarity index 100% rename from apps/api/src/app/auth/oidc.strategy.ts rename to apps/api/src/app/endpoints/auth/oidc.strategy.ts diff --git a/apps/api/src/app/auth/web-auth.service.ts b/apps/api/src/app/endpoints/auth/web-auth.service.ts similarity index 100% rename from apps/api/src/app/auth/web-auth.service.ts rename to apps/api/src/app/endpoints/auth/web-auth.service.ts diff --git a/apps/api/src/app/endpoints/info/info.module.ts b/apps/api/src/app/endpoints/info/info.module.ts index 7d30a7b53f..5211040512 100644 --- a/apps/api/src/app/endpoints/info/info.module.ts +++ b/apps/api/src/app/endpoints/info/info.module.ts @@ -1,4 +1,3 @@ -import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module'; import { UserModule } from '@ghostfolio/api/app/user/user.module'; import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module'; @@ -10,6 +9,7 @@ import { PlatformModule } from '@ghostfolio/api/services/platform/platform.modul import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module'; +import { SubscriptionModule } from '@ghostfolio/api/services/subscription/subscription.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { Module } from '@nestjs/common'; diff --git a/apps/api/src/app/endpoints/info/info.service.ts b/apps/api/src/app/endpoints/info/info.service.ts index ab373055bf..506e4887a0 100644 --- a/apps/api/src/app/endpoints/info/info.service.ts +++ b/apps/api/src/app/endpoints/info/info.service.ts @@ -1,4 +1,3 @@ -import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service'; import { UserService } from '@ghostfolio/api/app/user/user.service'; import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; @@ -7,6 +6,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service'; +import { SubscriptionService } from '@ghostfolio/api/services/subscription/subscription.service'; import { DEFAULT_CURRENCY, ghostfolioFearAndGreedIndexSymbolStocks, diff --git a/apps/api/src/app/subscription/subscription.controller.ts b/apps/api/src/app/endpoints/subscription/subscription.controller.ts similarity index 93% rename from apps/api/src/app/subscription/subscription.controller.ts rename to apps/api/src/app/endpoints/subscription/subscription.controller.ts index 778e322433..0d989265a7 100644 --- a/apps/api/src/app/subscription/subscription.controller.ts +++ b/apps/api/src/app/endpoints/subscription/subscription.controller.ts @@ -3,13 +3,15 @@ import { CustomThrottlerGuard } from '@ghostfolio/api/guards/custom-throttler.gu 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 { SubscriptionService } from '@ghostfolio/api/services/subscription/subscription.service'; import { DEFAULT_LANGUAGE_CODE, PROPERTY_COUPONS } from '@ghostfolio/common/config'; import { Coupon, - CreateStripeCheckoutSessionResponse + CreateStripeCheckoutSessionResponse, + RedeemCouponResponse } from '@ghostfolio/common/interfaces'; import type { RequestWithUser } from '@ghostfolio/common/types'; @@ -31,8 +33,6 @@ import { AuthGuard } from '@nestjs/passport'; import { Request, Response } from 'express'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import { SubscriptionService } from './subscription.service'; - @AllowDuringImpersonation() @Controller('subscription') export class SubscriptionController { @@ -48,7 +48,9 @@ export class SubscriptionController { @Post('redeem-coupon') @HttpCode(StatusCodes.OK) @UseGuards(AuthGuard('jwt'), CustomThrottlerGuard, HasPermissionGuard) - public async redeemCoupon(@Body() { couponCode }: { couponCode: string }) { + public async redeemCoupon( + @Body() { couponCode }: { couponCode: string } + ): Promise { if (!this.request.user) { throw new HttpException( getReasonPhrase(StatusCodes.FORBIDDEN), diff --git a/apps/api/src/app/endpoints/subscription/subscription.module.ts b/apps/api/src/app/endpoints/subscription/subscription.module.ts new file mode 100644 index 0000000000..4128efd84d --- /dev/null +++ b/apps/api/src/app/endpoints/subscription/subscription.module.ts @@ -0,0 +1,13 @@ +import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; +import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; +import { SubscriptionModule as SubscriptionServiceModule } from '@ghostfolio/api/services/subscription/subscription.module'; + +import { Module } from '@nestjs/common'; + +import { SubscriptionController } from './subscription.controller'; + +@Module({ + controllers: [SubscriptionController], + imports: [ConfigurationModule, PropertyModule, SubscriptionServiceModule] +}) +export class SubscriptionModule {} diff --git a/apps/api/src/app/user/user.module.ts b/apps/api/src/app/user/user.module.ts index 1566309647..cb105a9a3c 100644 --- a/apps/api/src/app/user/user.module.ts +++ b/apps/api/src/app/user/user.module.ts @@ -1,5 +1,4 @@ import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; -import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module'; import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module'; import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; @@ -7,6 +6,7 @@ import { I18nModule } from '@ghostfolio/api/services/i18n/i18n.module'; import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; +import { SubscriptionModule } from '@ghostfolio/api/services/subscription/subscription.module'; import { TagModule } from '@ghostfolio/api/services/tag/tag.module'; import { Module } from '@nestjs/common'; diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index c2f5ad5661..92a1a77f6e 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -1,5 +1,4 @@ import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; -import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service'; import { environment } from '@ghostfolio/api/environments/environment'; import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event'; import { getSupportedLanguageCode } from '@ghostfolio/api/helper/language.helper'; @@ -9,6 +8,7 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/con import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; +import { SubscriptionService } from '@ghostfolio/api/services/subscription/subscription.service'; import { TagService } from '@ghostfolio/api/services/tag/tag.service'; import { DEFAULT_CURRENCY, diff --git a/apps/api/src/services/impersonation/impersonation.module.ts b/apps/api/src/services/impersonation/impersonation.module.ts index b0e0567fa6..7e4c5d9270 100644 --- a/apps/api/src/services/impersonation/impersonation.module.ts +++ b/apps/api/src/services/impersonation/impersonation.module.ts @@ -1,8 +1,8 @@ import { AccessModule } from '@ghostfolio/api/app/access/access.module'; -import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; +import { SubscriptionModule } from '@ghostfolio/api/services/subscription/subscription.module'; import { Module } from '@nestjs/common'; diff --git a/apps/api/src/services/impersonation/impersonation.service.spec.ts b/apps/api/src/services/impersonation/impersonation.service.spec.ts index ab8d5fe44e..7e90849414 100644 --- a/apps/api/src/services/impersonation/impersonation.service.spec.ts +++ b/apps/api/src/services/impersonation/impersonation.service.spec.ts @@ -1,7 +1,7 @@ import { AccessService } from '@ghostfolio/api/app/access/access.service'; -import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; +import { SubscriptionService } from '@ghostfolio/api/services/subscription/subscription.service'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { SubscriptionType } from '@ghostfolio/common/enums'; import { permissions } from '@ghostfolio/common/permissions'; diff --git a/apps/api/src/services/impersonation/impersonation.service.ts b/apps/api/src/services/impersonation/impersonation.service.ts index 1cce830b5e..546579c6ac 100644 --- a/apps/api/src/services/impersonation/impersonation.service.ts +++ b/apps/api/src/services/impersonation/impersonation.service.ts @@ -1,7 +1,7 @@ import { AccessService } from '@ghostfolio/api/app/access/access.service'; -import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; +import { SubscriptionService } from '@ghostfolio/api/services/subscription/subscription.service'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { UserSettings } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; diff --git a/apps/api/src/app/subscription/subscription.module.ts b/apps/api/src/services/subscription/subscription.module.ts similarity index 83% rename from apps/api/src/app/subscription/subscription.module.ts rename to apps/api/src/services/subscription/subscription.module.ts index bf4bba7b70..7034f95f77 100644 --- a/apps/api/src/app/subscription/subscription.module.ts +++ b/apps/api/src/services/subscription/subscription.module.ts @@ -4,11 +4,9 @@ import { PropertyModule } from '@ghostfolio/api/services/property/property.modul import { Module } from '@nestjs/common'; -import { SubscriptionController } from './subscription.controller'; import { SubscriptionService } from './subscription.service'; @Module({ - controllers: [SubscriptionController], exports: [SubscriptionService], imports: [ConfigurationModule, PrismaModule, PropertyModule], providers: [SubscriptionService] diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/services/subscription/subscription.service.ts similarity index 100% rename from apps/api/src/app/subscription/subscription.service.ts rename to apps/api/src/services/subscription/subscription.service.ts diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index d17f2377b7..fba80e68cb 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -86,6 +86,7 @@ import type { PortfolioPerformanceResponse } from './responses/portfolio-perform import type { PortfolioReportResponse } from './responses/portfolio-report.interface'; import type { PublicPortfolioResponse } from './responses/public-portfolio-response.interface'; import type { QuotesResponse } from './responses/quotes-response.interface'; +import type { RedeemCouponResponse } from './responses/redeem-coupon-response.interface'; import type { WatchlistResponse } from './responses/watchlist-response.interface'; import type { RuleSettings } from './rule-settings.interface'; import type { ScraperConfiguration } from './scraper-configuration.interface'; @@ -192,6 +193,7 @@ export { PublicKeyCredentialRequestOptionsJSON, PublicPortfolioResponse, QuotesResponse, + RedeemCouponResponse, ReferralPartner, ResponseError, RuleSettings, diff --git a/libs/common/src/lib/interfaces/responses/redeem-coupon-response.interface.ts b/libs/common/src/lib/interfaces/responses/redeem-coupon-response.interface.ts new file mode 100644 index 0000000000..f54c623d75 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/redeem-coupon-response.interface.ts @@ -0,0 +1,4 @@ +export interface RedeemCouponResponse { + message: string; + statusCode: number; +} diff --git a/libs/ui/src/lib/services/data.service.ts b/libs/ui/src/lib/services/data.service.ts index 6292f8770a..c69e0cf0e6 100644 --- a/libs/ui/src/lib/services/data.service.ts +++ b/libs/ui/src/lib/services/data.service.ts @@ -57,6 +57,7 @@ import { PortfolioPerformanceResponse, PortfolioReportResponse, PublicPortfolioResponse, + RedeemCouponResponse, SymbolItem, User, UserItem, @@ -967,9 +968,12 @@ export class DataService { } public redeemCoupon(couponCode: string) { - return this.http.post('/api/v1/subscription/redeem-coupon', { - couponCode - }); + return this.http.post( + '/api/v1/subscription/redeem-coupon', + { + couponCode + } + ); } public transferAccountBalance({