Feature/upgrade to angular 16 (#2156)

* Upgrade Angular, NestJS and Nx

* Replace executor to @nx/angular:webpack-browser and @nx/angular:webpack-dev-server

* Add target for copying assets

* Improve redirection of home page

* Update changelog
pull/2202/head^2
Thomas Kaul 10 months ago committed by GitHub
parent c0f130a077
commit 43d0b55004
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -33,4 +33,4 @@ jobs:
run: yarn test
- name: Build application
run: yarn build:all
run: yarn build:production

@ -5,6 +5,19 @@ 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 a `copy-assets` `Nx` target to the client build
### Changed
- Improved the redirection of the home page to the localized home page
- Upgraded `angular` from version `15.2.5` to `16.1.8`
- Upgraded `nestjs` from version `9.1.4` to `10.1.3`
- Upgraded `Nx` from version `16.0.3` to `16.5.5`
## 1.296.0 - 2023-08-01
### Changed

@ -33,7 +33,7 @@ COPY ./tsconfig.base.json tsconfig.base.json
COPY ./libs libs
COPY ./apps apps
RUN yarn build:all
RUN yarn build:production
# Prepare the dist image with additional node_modules
WORKDIR /ghostfolio/dist/apps/api
@ -58,4 +58,4 @@ RUN apt update && apt install -y \
COPY --from=builder /ghostfolio/dist/apps /ghostfolio/apps
WORKDIR /ghostfolio/apps/api
EXPOSE ${PORT:-3333}
CMD [ "yarn", "start:prod" ]
CMD [ "yarn", "start:production" ]

@ -153,7 +153,6 @@ Please follow the instructions of the Ghostfolio [Unraid Community App](https://
### Setup
1. Run `yarn install`
1. Run `yarn build:dev` to build the source code including the assets
1. Run `docker-compose --env-file ./.env -f docker/docker-compose.dev.yml up -d` to start [PostgreSQL](https://www.postgresql.org) and [Redis](https://redis.io)
1. Run `yarn database:setup` to initialize the database schema
1. Start the server and the client (see [_Development_](#Development))

@ -33,7 +33,7 @@
"outputs": ["{options.outputPath}"]
},
"serve": {
"executor": "@nx/node:node",
"executor": "@nx/js:node",
"options": {
"buildTarget": "api:build"
}

@ -35,6 +35,11 @@ import { RedisCacheModule } from './redis-cache/redis-cache.module';
import { SubscriptionModule } from './subscription/subscription.module';
import { SymbolModule } from './symbol/symbol.module';
import { UserModule } from './user/user.module';
import {
DEFAULT_LANGUAGE_CODE,
SUPPORTED_LANGUAGE_CODES
} from '@ghostfolio/common/config';
import { StatusCodes } from 'http-status-codes';
@Module({
imports: [
@ -71,14 +76,24 @@ import { UserModule } from './user/user.module';
ScheduleModule.forRoot(),
ServeStaticModule.forRoot({
serveStaticOptions: {
/*etag: false // Disable etag header to fix PWA
setHeaders: (res, path) => {
if (path.includes('ngsw.json')) {
// Disable cache (https://stackoverflow.com/questions/22632593/how-to-disable-webpage-caching-in-expressjs-nodejs/39775595)
// https://gertjans.home.xs4all.nl/javascript/cache-control.html#no-cache
res.set('Cache-Control', 'no-cache, no-store, must-revalidate');
setHeaders: (res) => {
if (res.req?.path === '/') {
let languageCode = DEFAULT_LANGUAGE_CODE;
try {
const code = res.req.headers['accept-language']
.split(',')[0]
.split('-')[0];
if (SUPPORTED_LANGUAGE_CODES.includes(code)) {
languageCode = code;
}
} catch {}
res.set('Location', `/${languageCode}`);
res.statusCode = StatusCodes.MOVED_PERMANENTLY;
}
}*/
}
},
rootPath: join(__dirname, '..', 'client'),
exclude: ['/api*']

@ -1,8 +1,8 @@
import { Store } from 'cache-manager';
import { RedisClient } from 'redis';
import { createClient } from 'redis';
export interface RedisStore extends Store {
getClient: () => RedisClient;
getClient: () => ReturnType<typeof createClient>;
isCacheableValue: (value: any) => boolean;
name: 'redis';
}

@ -1,7 +1,9 @@
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { CacheManagerOptions, CacheModule, Module } from '@nestjs/common';
import * as redisStore from 'cache-manager-redis-store';
import { CacheModule } from '@nestjs/cache-manager';
import { Module } from '@nestjs/common';
import { redisStore } from 'cache-manager-redis-store';
import type { RedisClientOptions } from 'redis';
import { RedisCacheService } from './redis-cache.service';
@ -11,7 +13,7 @@ import { RedisCacheService } from './redis-cache.service';
imports: [ConfigurationModule],
inject: [ConfigurationService],
useFactory: async (configurationService: ConfigurationService) => {
return <CacheManagerOptions>{
return <RedisClientOptions>{
host: configurationService.get('REDIS_HOST'),
max: configurationService.get('MAX_ITEM_IN_CACHE'),
password: configurationService.get('REDIS_PASSWORD'),

@ -1,7 +1,8 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { CACHE_MANAGER, Inject, Injectable, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject, Injectable, Logger } from '@nestjs/common';
import type { RedisCache } from './interfaces/redis-cache.interface';
@ -35,8 +36,10 @@ export class RedisCacheService {
}
public async set(key: string, value: string, ttlInSeconds?: number) {
await this.cache.set(key, value, {
ttl: ttlInSeconds ?? this.configurationService.get('CACHE_TTL')
});
await this.cache.set(
key,
value,
ttlInSeconds ?? this.configurationService.get('CACHE_TTL')
);
}
}

@ -4,7 +4,7 @@
"outDir": "../../dist/out-tsc",
"types": ["node"],
"emitDecoratorMetadata": true,
"target": "es2015"
"target": "es2021"
},
"exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"],
"include": ["**/*.ts"]

@ -11,60 +11,15 @@
"prefix": "gf",
"targets": {
"build": {
"executor": "@angular-devkit/build-angular:browser",
"executor": "@nx/angular:webpack-browser",
"options": {
"localize": true,
"outputPath": "dist/apps/client",
"index": "apps/client/src/index.html",
"main": "apps/client/src/main.ts",
"polyfills": "apps/client/src/polyfills.ts",
"tsConfig": "apps/client/tsconfig.app.json",
"assets": [
{
"glob": "assetlinks.json",
"input": "apps/client/src/assets",
"output": "./../.well-known"
},
{
"glob": "CHANGELOG.md",
"input": "",
"output": "./../assets"
},
{
"glob": "favicon.ico",
"input": "apps/client/src/assets",
"output": "./../"
},
{
"glob": "LICENSE",
"input": "",
"output": "./../assets"
},
{
"glob": "robots.txt",
"input": "apps/client/src/assets",
"output": "./../"
},
{
"glob": "site.webmanifest",
"input": "apps/client/src/assets",
"output": "./../"
},
{
"glob": "**/*",
"input": "node_modules/ionicons/dist/ionicons",
"output": "./../ionicons"
},
{
"glob": "**/*.js",
"input": "node_modules/ionicons/dist/",
"output": "./../"
},
{
"glob": "**/*",
"input": "apps/client/src/assets",
"output": "./../assets/"
}
],
"assets": [],
"styles": [
"apps/client/src/styles/theme.scss",
"apps/client/src/styles.scss"
@ -139,8 +94,48 @@
"outputs": ["{options.outputPath}"],
"defaultConfiguration": ""
},
"copy-assets": {
"executor": "nx:run-commands",
"options": {
"commands": [
{
"command": "mkdir -p dist/apps/client"
},
{
"command": "cp -r apps/client/src/assets dist/apps/client"
},
{
"command": "cp -r apps/client/src/assets/.well-known dist/apps/client"
},
{
"command": "cp apps/client/src/assets/favicon.ico dist/apps/client"
},
{
"command": "cp apps/client/src/assets/robots.txt dist/apps/client"
},
{
"command": "cp apps/client/src/assets/site.webmanifest dist/apps/client"
},
{
"command": "cp node_modules/ionicons/dist/index.js dist/apps/client"
},
{
"command": "cp node_modules/ionicons/dist/ionicons.js dist/apps/client"
},
{
"command": "cp -r node_modules/ionicons/dist/ionicons dist/apps/client/ionicons"
},
{
"command": "cp CHANGELOG.md dist/apps/client/assets"
},
{
"command": "cp LICENSE dist/apps/client/assets"
}
]
}
},
"serve": {
"executor": "@angular-devkit/build-angular:dev-server",
"executor": "@nx/angular:webpack-dev-server",
"options": {
"browserTarget": "client:build",
"proxyConfig": "apps/client/proxy.conf.json"

@ -1,7 +1,6 @@
import { Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
Router,
RouterStateSnapshot
} from '@angular/router';
@ -12,7 +11,7 @@ import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
export class AuthGuard {
private static PUBLIC_PAGE_ROUTES = [
'/about',
'/about/changelog',

@ -3,7 +3,7 @@
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"],
"typeRoots": ["../node_modules/@types"],
"typeRoots": ["../../node_modules/@types"],
"target": "ES2022",
"useDefineForClassFields": false
},

@ -91,4 +91,14 @@ export const QUEUE_JOB_STATUS_LIST = <JobStatus[]>[
'waiting'
];
export const SUPPORTED_LANGUAGE_CODES = [
'de',
'en',
'es',
'fr',
'it',
'nl',
'pt'
];
export const UNKNOWN_KEY = 'UNKNOWN';

@ -50,7 +50,8 @@
"default",
"^production",
"{workspaceRoot}/.storybook/**/*",
"!{projectRoot}/.storybook/**/*"
"{projectRoot}/.storybook/**/*",
"{projectRoot}/tsconfig.storybook.json"
]
}
},
@ -67,7 +68,9 @@
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.[jt]s",
"!{projectRoot}/.storybook/**/*",
"!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)"
"!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)",
"!{projectRoot}/tsconfig.storybook.json",
"!{projectRoot}/src/test-setup.[jt]s"
]
}
}

@ -13,8 +13,8 @@
"affected:lint": "nx affected:lint",
"affected:test": "nx affected:test",
"angular": "node --max_old_space_size=32768 ./node_modules/@angular/cli/bin/ng",
"build:all": "nx run api:build:production && nx run client:build:production --localize && yarn replace-placeholders-in-build",
"build:dev": "nx run api:build && nx run client:build --localize && yarn replace-placeholders-in-build",
"build:dev": "nx run api:build && nx run client:build && nx run client:copy-assets && yarn replace-placeholders-in-build",
"build:production": "nx run api:build:production && nx run client:build:production && nx run client:copy-assets && yarn replace-placeholders-in-build",
"build:storybook": "nx run ui:build-storybook",
"database:format-schema": "prisma format",
"database:generate-typings": "prisma generate",
@ -36,11 +36,11 @@
"lint": "nx workspace-lint && ng lint",
"ng": "nx",
"nx": "nx",
"postinstall": "prisma generate && ngcc --properties es2020 browser module main",
"postinstall": "prisma generate",
"replace-placeholders-in-build": "node ./replace.build.js",
"start": "node dist/apps/api/main",
"start:client": "nx run client:serve --configuration=development-en --hmr -o",
"start:prod": "yarn database:migrate && yarn database:seed && node main",
"start:client": "nx run client:copy-assets && nx run client:serve --configuration=development-en --hmr -o",
"start:production": "yarn database:migrate && yarn database:seed && node main",
"start:server": "nx run api:serve --watch",
"start:storybook": "nx run ui:storybook",
"test": "npx dotenv-cli -e .env.example -- nx test",
@ -52,17 +52,17 @@
"workspace-generator": "nx workspace-generator"
},
"dependencies": {
"@angular/animations": "15.2.5",
"@angular/cdk": "15.2.6",
"@angular/common": "15.2.5",
"@angular/compiler": "15.2.5",
"@angular/core": "15.2.5",
"@angular/forms": "15.2.5",
"@angular/material": "15.2.6",
"@angular/platform-browser": "15.2.5",
"@angular/platform-browser-dynamic": "15.2.5",
"@angular/router": "15.2.5",
"@angular/service-worker": "15.2.5",
"@angular/animations": "16.1.8",
"@angular/cdk": "16.1.7",
"@angular/common": "16.1.8",
"@angular/compiler": "16.1.8",
"@angular/core": "16.1.8",
"@angular/forms": "16.1.8",
"@angular/material": "16.1.7",
"@angular/platform-browser": "16.1.8",
"@angular/platform-browser-dynamic": "16.1.8",
"@angular/router": "16.1.8",
"@angular/service-worker": "16.1.8",
"@codewithdan/observable-store": "2.2.15",
"@dfinity/agent": "0.15.7",
"@dfinity/auth-client": "0.15.7",
@ -70,15 +70,16 @@
"@dfinity/identity": "0.15.7",
"@dfinity/principal": "0.15.7",
"@dinero.js/currencies": "2.0.0-alpha.8",
"@nestjs/bull": "0.6.3",
"@nestjs/common": "9.1.4",
"@nestjs/config": "2.2.0",
"@nestjs/core": "9.1.4",
"@nestjs/jwt": "9.0.0",
"@nestjs/passport": "9.0.0",
"@nestjs/platform-express": "9.1.4",
"@nestjs/schedule": "2.1.0",
"@nestjs/serve-static": "3.0.0",
"@nestjs/bull": "10.0.1",
"@nestjs/cache-manager": "2.1.0",
"@nestjs/common": "10.1.3",
"@nestjs/config": "3.0.0",
"@nestjs/core": "10.1.3",
"@nestjs/jwt": "10.1.0",
"@nestjs/passport": "10.0.0",
"@nestjs/platform-express": "10.1.3",
"@nestjs/schedule": "3.0.2",
"@nestjs/serve-static": "4.0.0",
"@prisma/client": "4.16.2",
"@simplewebauthn/browser": "5.2.1",
"@simplewebauthn/server": "5.2.1",
@ -89,8 +90,8 @@
"body-parser": "1.20.1",
"bootstrap": "4.6.0",
"bull": "4.10.4",
"cache-manager": "3.4.3",
"cache-manager-redis-store": "2.0.0",
"cache-manager": "5.2.3",
"cache-manager-redis-store": "3.0.1",
"chart.js": "4.2.0",
"chartjs-adapter-date-fns": "3.0.0",
"chartjs-plugin-annotation": "2.1.2",
@ -128,37 +129,37 @@
"twitter-api-v2": "1.14.2",
"uuid": "9.0.0",
"yahoo-finance2": "2.4.3",
"zone.js": "0.12.0"
"zone.js": "0.13.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "15.2.5",
"@angular-devkit/core": "15.2.5",
"@angular-devkit/schematics": "15.2.5",
"@angular-eslint/eslint-plugin": "15.2.0",
"@angular-eslint/eslint-plugin-template": "15.2.0",
"@angular-eslint/template-parser": "15.2.0",
"@angular/cli": "15.2.5",
"@angular/compiler-cli": "15.2.5",
"@angular/language-service": "15.2.5",
"@angular/localize": "15.2.5",
"@angular/pwa": "15.2.5",
"@nestjs/schematics": "9.1.0",
"@nestjs/testing": "9.4.0",
"@nx/angular": "16.0.3",
"@nx/cypress": "16.0.3",
"@nx/eslint-plugin": "16.0.3",
"@nx/jest": "16.0.3",
"@nx/js": "16.0.3",
"@nx/nest": "16.0.3",
"@nx/node": "16.0.3",
"@nx/storybook": "16.0.3",
"@nx/web": "16.0.3",
"@nx/workspace": "16.0.3",
"@schematics/angular": "15.2.5",
"@angular-devkit/build-angular": "16.1.8",
"@angular-devkit/core": "16.1.8",
"@angular-devkit/schematics": "16.1.8",
"@angular-eslint/eslint-plugin": "16.0.3",
"@angular-eslint/eslint-plugin-template": "16.0.3",
"@angular-eslint/template-parser": "16.0.3",
"@angular/cli": "16.1.8",
"@angular/compiler-cli": "16.1.8",
"@angular/language-service": "16.1.8",
"@angular/localize": "16.1.8",
"@angular/pwa": "16.1.8",
"@nestjs/schematics": "10.0.1",
"@nestjs/testing": "10.1.3",
"@nx/angular": "16.5.5",
"@nx/cypress": "16.5.5",
"@nx/eslint-plugin": "16.5.5",
"@nx/jest": "16.5.5",
"@nx/js": "16.5.5",
"@nx/nest": "16.5.5",
"@nx/node": "16.5.5",
"@nx/storybook": "16.5.5",
"@nx/web": "16.5.5",
"@nx/workspace": "16.5.5",
"@schematics/angular": "16.1.8",
"@simplewebauthn/typescript-types": "5.2.1",
"@storybook/addon-essentials": "7.0.9",
"@storybook/angular": "7.0.9",
"@storybook/core-server": "7.0.9",
"@storybook/addon-essentials": "7.0.27",
"@storybook/angular": "7.0.27",
"@storybook/core-server": "7.0.27",
"@types/big.js": "6.1.6",
"@types/body-parser": "1.19.2",
"@types/cache-manager": "3.4.2",
@ -167,7 +168,7 @@
"@types/jest": "29.4.4",
"@types/lodash": "4.14.195",
"@types/marked": "4.0.8",
"@types/node": "18.11.18",
"@types/node": "20.4.2",
"@types/papaparse": "5.3.7",
"@types/passport-google-oauth20": "2.0.11",
"@typescript-eslint/eslint-plugin": "5.51.0",
@ -184,9 +185,9 @@
"import-sort-style-module": "6.0.0",
"jest": "29.4.3",
"jest-environment-jsdom": "29.4.3",
"jest-preset-angular": "13.0.0",
"nx": "16.0.3",
"nx-cloud": "16.0.5",
"jest-preset-angular": "13.1.1",
"nx": "16.5.5",
"nx-cloud": "16.1.1",
"prettier": "2.8.4",
"prettier-plugin-organize-attributes": "0.0.5",
"react": "18.2.0",
@ -195,8 +196,8 @@
"storybook": "7.0.9",
"ts-jest": "29.1.0",
"ts-node": "10.9.1",
"tslib": "2.0.0",
"typescript": "4.9.5"
"tslib": "2.6.0",
"typescript": "5.1.6"
},
"engines": {
"node": ">=18"

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save