diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c969c176..be4fb1f68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added API versioning - Added more durations in the coupon system ### Changed diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 5a077910e..cf565a36d 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,4 +1,4 @@ -import { Logger, ValidationPipe } from '@nestjs/common'; +import { Logger, ValidationPipe, VersioningType } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app/app.module'; @@ -7,8 +7,11 @@ import { environment } from './environments/environment'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableCors(); - const globalPrefix = 'api'; - app.setGlobalPrefix(globalPrefix); + app.enableVersioning({ + defaultVersion: '1', + type: VersioningType.URI + }); + app.setGlobalPrefix('api'); app.useGlobalPipes( new ValidationPipe({ forbidNonWhitelisted: true, diff --git a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html index 614c28bfe..2186368ca 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html +++ b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -8,7 +8,7 @@
- Sign in with Google diff --git a/apps/client/src/app/pages/register/register-page.html b/apps/client/src/app/pages/register/register-page.html index 96d49251b..fd8c07f8d 100644 --- a/apps/client/src/app/pages/register/register-page.html +++ b/apps/client/src/app/pages/register/register-page.html @@ -35,7 +35,7 @@ > or
- Continue with Google diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index cf9b36b1f..ecb33c0df 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -19,7 +19,7 @@ export class AdminService { public deleteProfileData({ dataSource, symbol }: UniqueAsset) { return this.http.delete( - `/api/admin/profile-data/${dataSource}/${symbol}` + `/api/v1/admin/profile-data/${dataSource}/${symbol}` ); } @@ -31,7 +31,7 @@ export class AdminService { symbol: string; }): Observable { return this.http - .get(`/api/admin/market-data/${dataSource}/${symbol}`) + .get(`/api/v1/admin/market-data/${dataSource}/${symbol}`) .pipe( map((data) => { for (const item of data.marketData) { @@ -43,16 +43,16 @@ export class AdminService { } public gatherMax() { - return this.http.post(`/api/admin/gather/max`, {}); + return this.http.post(`/api/v1/admin/gather/max`, {}); } public gatherProfileData() { - return this.http.post(`/api/admin/gather/profile-data`, {}); + return this.http.post(`/api/v1/admin/gather/profile-data`, {}); } public gatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) { return this.http.post( - `/api/admin/gather/profile-data/${dataSource}/${symbol}`, + `/api/v1/admin/gather/profile-data/${dataSource}/${symbol}`, {} ); } @@ -64,7 +64,7 @@ export class AdminService { }: UniqueAsset & { date?: Date; }) { - let url = `/api/admin/gather/${dataSource}/${symbol}`; + let url = `/api/v1/admin/gather/${dataSource}/${symbol}`; if (date) { url = `${url}/${format(date, DATE_FORMAT)}`; @@ -82,7 +82,7 @@ export class AdminService { date: Date; symbol: string; }) { - const url = `/api/symbol/${dataSource}/${symbol}/${format( + const url = `/api/v1/symbol/${dataSource}/${symbol}/${format( date, DATE_FORMAT )}`; @@ -101,7 +101,7 @@ export class AdminService { marketData: UpdateMarketDataDto; symbol: string; }) { - const url = `/api/admin/market-data/${dataSource}/${symbol}/${format( + const url = `/api/v1/admin/market-data/${dataSource}/${symbol}/${format( date, DATE_FORMAT )}`; diff --git a/apps/client/src/app/services/cache.service.ts b/apps/client/src/app/services/cache.service.ts index bcf17f574..69a85f926 100644 --- a/apps/client/src/app/services/cache.service.ts +++ b/apps/client/src/app/services/cache.service.ts @@ -8,6 +8,6 @@ export class CacheService { public constructor(private http: HttpClient) {} public flush() { - return this.http.post(`/api/cache/flush`, {}); + return this.http.post(`/api/v1/cache/flush`, {}); } } diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index c61730d3c..b51a00ce8 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -23,12 +23,10 @@ import { PortfolioChart, PortfolioDetails, PortfolioInvestments, - PortfolioPerformance, PortfolioPerformanceResponse, PortfolioPublicDetails, PortfolioReport, PortfolioSummary, - UniqueAsset, User } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; @@ -52,46 +50,46 @@ export class DataService { couponId?: string; priceId: string; }) { - return this.http.post('/api/subscription/stripe/checkout-session', { + return this.http.post('/api/v1/subscription/stripe/checkout-session', { couponId, priceId }); } public fetchAccounts() { - return this.http.get('/api/account'); + return this.http.get('/api/v1/account'); } public fetchAdminData() { - return this.http.get('/api/admin'); + return this.http.get('/api/v1/admin'); } public fetchAdminMarketData() { - return this.http.get('/api/admin/market-data'); + return this.http.get('/api/v1/admin/market-data'); } public deleteAccess(aId: string) { - return this.http.delete(`/api/access/${aId}`); + return this.http.delete(`/api/v1/access/${aId}`); } public deleteAccount(aId: string) { - return this.http.delete(`/api/account/${aId}`); + return this.http.delete(`/api/v1/account/${aId}`); } public deleteOrder(aId: string) { - return this.http.delete(`/api/order/${aId}`); + return this.http.delete(`/api/v1/order/${aId}`); } public deleteUser(aId: string) { - return this.http.delete(`/api/user/${aId}`); + return this.http.delete(`/api/v1/user/${aId}`); } public fetchAccesses() { - return this.http.get('/api/access'); + return this.http.get('/api/v1/access'); } public fetchChart({ range }: { range: DateRange }) { - return this.http.get('/api/portfolio/chart', { + return this.http.get('/api/v1/portfolio/chart', { params: { range } }); } @@ -103,7 +101,7 @@ export class DataService { params = params.append('activityIds', activityIds.join(',')); } - return this.http.get('/api/export', { + return this.http.get('/api/v1/export', { params }); } @@ -121,7 +119,7 @@ export class DataService { } public fetchInvestments(): Observable { - return this.http.get('/api/portfolio/investments').pipe( + return this.http.get('/api/v1/portfolio/investments').pipe( map((response) => { if (response.firstOrderDate) { response.firstOrderDate = parseISO(response.firstOrderDate); @@ -147,7 +145,7 @@ export class DataService { params = params.append('includeHistoricalData', includeHistoricalData); } - return this.http.get(`/api/symbol/${dataSource}/${symbol}`, { + return this.http.get(`/api/v1/symbol/${dataSource}/${symbol}`, { params }); } @@ -157,14 +155,14 @@ export class DataService { }: { range: DateRange; }): Observable { - return this.http.get('/api/portfolio/positions', { + return this.http.get('/api/v1/portfolio/positions', { params: { range } }); } public fetchSymbols(aQuery: string) { return this.http - .get<{ items: LookupItem[] }>(`/api/symbol/lookup?query=${aQuery}`) + .get<{ items: LookupItem[] }>(`/api/v1/symbol/lookup?query=${aQuery}`) .pipe( map((respose) => { return respose.items; @@ -173,7 +171,7 @@ export class DataService { } public fetchOrders(): Observable { - return this.http.get('/api/order').pipe( + return this.http.get('/api/v1/order').pipe( map(({ activities }) => { for (const activity of activities) { activity.createdAt = parseISO(activity.createdAt); @@ -185,14 +183,14 @@ export class DataService { } public fetchPortfolioDetails(aParams: { [param: string]: any }) { - return this.http.get('/api/portfolio/details', { + return this.http.get('/api/v1/portfolio/details', { params: aParams }); } public fetchPortfolioPerformance(params: { [param: string]: any }) { return this.http.get( - '/api/portfolio/performance', + '/api/v1/portfolio/performance', { params } @@ -201,16 +199,16 @@ export class DataService { public fetchPortfolioPublic(aId: string) { return this.http.get( - `/api/portfolio/public/${aId}` + `/api/v1/portfolio/public/${aId}` ); } public fetchPortfolioReport() { - return this.http.get('/api/portfolio/report'); + return this.http.get('/api/v1/portfolio/report'); } public fetchPortfolioSummary(): Observable { - return this.http.get('/api/portfolio/summary').pipe( + return this.http.get('/api/v1/portfolio/summary').pipe( map((summary) => { if (summary.firstOrderDate) { summary.firstOrderDate = parseISO(summary.firstOrderDate); @@ -229,7 +227,7 @@ export class DataService { symbol: string; }) { return this.http - .get(`/api/portfolio/position/${dataSource}/${symbol}`) + .get(`/api/v1/portfolio/position/${dataSource}/${symbol}`) .pipe( map((data) => { if (data.orders) { @@ -245,47 +243,47 @@ export class DataService { } public loginAnonymous(accessToken: string) { - return this.http.get(`/api/auth/anonymous/${accessToken}`); + return this.http.get(`/api/v1/auth/anonymous/${accessToken}`); } public postAccess(aAccess: CreateAccessDto) { - return this.http.post(`/api/access`, aAccess); + return this.http.post(`/api/v1/access`, aAccess); } public postAccount(aAccount: CreateAccountDto) { - return this.http.post(`/api/account`, aAccount); + return this.http.post(`/api/v1/account`, aAccount); } public postOrder(aOrder: CreateOrderDto) { - return this.http.post(`/api/order`, aOrder); + return this.http.post(`/api/v1/order`, aOrder); } public postUser() { - return this.http.post(`/api/user`, {}); + return this.http.post(`/api/v1/user`, {}); } public putAccount(aAccount: UpdateAccountDto) { - return this.http.put(`/api/account/${aAccount.id}`, aAccount); + return this.http.put(`/api/v1/account/${aAccount.id}`, aAccount); } public putAdminSetting(key: string, aData: PropertyDto) { - return this.http.put(`/api/admin/settings/${key}`, aData); + return this.http.put(`/api/v1/admin/settings/${key}`, aData); } public putOrder(aOrder: UpdateOrderDto) { - return this.http.put(`/api/order/${aOrder.id}`, aOrder); + return this.http.put(`/api/v1/order/${aOrder.id}`, aOrder); } public putUserSetting(aData: UpdateUserSettingDto) { - return this.http.put(`/api/user/setting`, aData); + return this.http.put(`/api/v1/user/setting`, aData); } public putUserSettings(aData: UpdateUserSettingsDto) { - return this.http.put(`/api/user/settings`, aData); + return this.http.put(`/api/v1/user/settings`, aData); } public redeemCoupon(couponCode: string) { - return this.http.post('/api/subscription/redeem-coupon', { + return this.http.post('/api/v1/subscription/redeem-coupon', { couponCode }); } diff --git a/apps/client/src/app/services/import-transactions.service.ts b/apps/client/src/app/services/import-transactions.service.ts index 8fe829d55..6b01dad47 100644 --- a/apps/client/src/app/services/import-transactions.service.ts +++ b/apps/client/src/app/services/import-transactions.service.ts @@ -282,6 +282,6 @@ export class ImportTransactionsService { } private postImport(aImportData: { orders: CreateOrderDto[] }) { - return this.http.post('/api/import', aImportData); + return this.http.post('/api/v1/import', aImportData); } } diff --git a/apps/client/src/app/services/user/user.service.ts b/apps/client/src/app/services/user/user.service.ts index 0d2df2b69..2e6652591 100644 --- a/apps/client/src/app/services/user/user.service.ts +++ b/apps/client/src/app/services/user/user.service.ts @@ -36,7 +36,7 @@ export class UserService extends ObservableStore { } private fetchUser() { - return this.http.get('/api/user').pipe( + return this.http.get('/api/v1/user').pipe( map((user) => { this.setState({ user }, UserStoreActions.GetUser); return user; diff --git a/apps/client/src/app/services/web-authn.service.ts b/apps/client/src/app/services/web-authn.service.ts index 154efd9d9..eb033884c 100644 --- a/apps/client/src/app/services/web-authn.service.ts +++ b/apps/client/src/app/services/web-authn.service.ts @@ -35,7 +35,7 @@ export class WebAuthnService { public register() { return this.http .get( - `/api/auth/webauthn/generate-registration-options`, + `/api/v1/auth/webauthn/generate-registration-options`, {} ) .pipe( @@ -48,7 +48,7 @@ export class WebAuthnService { }), switchMap((attResp) => { return this.http.post( - `/api/auth/webauthn/verify-attestation`, + `/api/v1/auth/webauthn/verify-attestation`, { credential: attResp } @@ -65,31 +65,33 @@ export class WebAuthnService { public deregister() { const deviceId = this.getDeviceId(); - return this.http.delete(`/api/auth-device/${deviceId}`).pipe( - catchError((error) => { - console.warn(`Could not deregister device ${deviceId}`, error); - return of(null); - }), - tap(() => - this.settingsStorageService.removeSetting( - WebAuthnService.WEB_AUTH_N_DEVICE_ID + return this.http + .delete(`/api/v1/auth-device/${deviceId}`) + .pipe( + catchError((error) => { + console.warn(`Could not deregister device ${deviceId}`, error); + return of(null); + }), + tap(() => + this.settingsStorageService.removeSetting( + WebAuthnService.WEB_AUTH_N_DEVICE_ID + ) ) - ) - ); + ); } public login() { const deviceId = this.getDeviceId(); return this.http .post( - `/api/auth/webauthn/generate-assertion-options`, + `/api/v1/auth/webauthn/generate-assertion-options`, { deviceId } ) .pipe( switchMap(startAuthentication), switchMap((assertionResponse) => { return this.http.post<{ authToken: string }>( - `/api/auth/webauthn/verify-assertion`, + `/api/v1/auth/webauthn/verify-assertion`, { credential: assertionResponse, deviceId diff --git a/apps/client/src/main.ts b/apps/client/src/main.ts index 33556621d..54e4b175a 100644 --- a/apps/client/src/main.ts +++ b/apps/client/src/main.ts @@ -8,7 +8,7 @@ import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; (async () => { - const response = await fetch('/api/info'); + const response = await fetch('/api/v1/info'); const info: InfoItem = await response.json(); if (window.localStorage.getItem('utm_source') === 'trusted-web-activity') {