mirror of https://github.com/ghostfolio/ghostfolio
committed by
GitHub
249 changed files with 48565 additions and 8239 deletions
@ -1,237 +0,0 @@ |
|||
import * as fs from 'fs'; |
|||
import * as path from 'path'; |
|||
|
|||
import { environment } from '@ghostfolio/api/environments/environment'; |
|||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; |
|||
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config'; |
|||
import { DATE_FORMAT } from '@ghostfolio/common/helper'; |
|||
import { Injectable, NestMiddleware } from '@nestjs/common'; |
|||
import { format } from 'date-fns'; |
|||
import { NextFunction, Request, Response } from 'express'; |
|||
|
|||
@Injectable() |
|||
export class FrontendMiddleware implements NestMiddleware { |
|||
public indexHtmlDe = ''; |
|||
public indexHtmlEn = ''; |
|||
public indexHtmlEs = ''; |
|||
public indexHtmlFr = ''; |
|||
public indexHtmlIt = ''; |
|||
public indexHtmlNl = ''; |
|||
public indexHtmlPt = ''; |
|||
|
|||
private static readonly DEFAULT_DESCRIPTION = |
|||
'Ghostfolio is a personal finance dashboard to keep track of your assets like stocks, ETFs or cryptocurrencies across multiple platforms.'; |
|||
|
|||
public constructor( |
|||
private readonly configurationService: ConfigurationService |
|||
) { |
|||
try { |
|||
this.indexHtmlDe = fs.readFileSync( |
|||
this.getPathOfIndexHtmlFile('de'), |
|||
'utf8' |
|||
); |
|||
this.indexHtmlEn = fs.readFileSync( |
|||
this.getPathOfIndexHtmlFile(DEFAULT_LANGUAGE_CODE), |
|||
'utf8' |
|||
); |
|||
this.indexHtmlEs = fs.readFileSync( |
|||
this.getPathOfIndexHtmlFile('es'), |
|||
'utf8' |
|||
); |
|||
this.indexHtmlFr = fs.readFileSync( |
|||
this.getPathOfIndexHtmlFile('fr'), |
|||
'utf8' |
|||
); |
|||
this.indexHtmlIt = fs.readFileSync( |
|||
this.getPathOfIndexHtmlFile('it'), |
|||
'utf8' |
|||
); |
|||
this.indexHtmlNl = fs.readFileSync( |
|||
this.getPathOfIndexHtmlFile('nl'), |
|||
'utf8' |
|||
); |
|||
this.indexHtmlPt = fs.readFileSync( |
|||
this.getPathOfIndexHtmlFile('pt'), |
|||
'utf8' |
|||
); |
|||
} catch {} |
|||
} |
|||
|
|||
public use(request: Request, response: Response, next: NextFunction) { |
|||
const currentDate = format(new Date(), DATE_FORMAT); |
|||
let featureGraphicPath = 'assets/cover.png'; |
|||
let title = 'Ghostfolio – Open Source Wealth Management Software'; |
|||
|
|||
if (request.path.startsWith('/en/blog/2022/08/500-stars-on-github')) { |
|||
featureGraphicPath = 'assets/images/blog/500-stars-on-github.jpg'; |
|||
title = `500 Stars - ${title}`; |
|||
} else if (request.path.startsWith('/en/blog/2022/10/hacktoberfest-2022')) { |
|||
featureGraphicPath = 'assets/images/blog/hacktoberfest-2022.png'; |
|||
title = `Hacktoberfest 2022 - ${title}`; |
|||
} else if (request.path.startsWith('/en/blog/2022/11/black-friday-2022')) { |
|||
featureGraphicPath = 'assets/images/blog/black-friday-2022.jpg'; |
|||
title = `Black Friday 2022 - ${title}`; |
|||
} else if ( |
|||
request.path.startsWith( |
|||
'/en/blog/2022/12/the-importance-of-tracking-your-personal-finances' |
|||
) |
|||
) { |
|||
featureGraphicPath = 'assets/images/blog/20221226.jpg'; |
|||
title = `The importance of tracking your personal finances - ${title}`; |
|||
} else if ( |
|||
request.path.startsWith( |
|||
'/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt' |
|||
) |
|||
) { |
|||
featureGraphicPath = 'assets/images/blog/ghostfolio-x-sackgeld.png'; |
|||
title = `Ghostfolio auf Sackgeld.com vorgestellt - ${title}`; |
|||
} else if ( |
|||
request.path.startsWith('/en/blog/2023/02/ghostfolio-meets-umbrel') |
|||
) { |
|||
featureGraphicPath = 'assets/images/blog/ghostfolio-x-umbrel.png'; |
|||
title = `Ghostfolio meets Umbrel - ${title}`; |
|||
} else if ( |
|||
request.path.startsWith( |
|||
'/en/blog/2023/03/ghostfolio-reaches-1000-stars-on-github' |
|||
) |
|||
) { |
|||
featureGraphicPath = 'assets/images/blog/1000-stars-on-github.jpg'; |
|||
title = `Ghostfolio reaches 1’000 Stars on GitHub - ${title}`; |
|||
} else if ( |
|||
request.path.startsWith( |
|||
'/en/blog/2023/05/unlock-your-financial-potential-with-ghostfolio' |
|||
) |
|||
) { |
|||
featureGraphicPath = 'assets/images/blog/20230520.jpg'; |
|||
title = `Unlock your Financial Potential with Ghostfolio - ${title}`; |
|||
} else if ( |
|||
request.path.startsWith('/en/blog/2023/07/exploring-the-path-to-fire') |
|||
) { |
|||
featureGraphicPath = 'assets/images/blog/20230701.jpg'; |
|||
title = `Exploring the Path to FIRE - ${title}`; |
|||
} |
|||
|
|||
if ( |
|||
request.path.startsWith('/api/') || |
|||
this.isFileRequest(request.url) || |
|||
!environment.production |
|||
) { |
|||
// Skip
|
|||
next(); |
|||
} else if (request.path === '/de' || request.path.startsWith('/de/')) { |
|||
response.send( |
|||
this.interpolate(this.indexHtmlDe, { |
|||
currentDate, |
|||
featureGraphicPath, |
|||
title, |
|||
description: |
|||
'Mit dem Finanz-Dashboard Ghostfolio können Sie Ihr Vermögen in Form von Aktien, ETFs oder Kryptowährungen verteilt über mehrere Finanzinstitute überwachen.', |
|||
languageCode: 'de', |
|||
path: request.path, |
|||
rootUrl: this.configurationService.get('ROOT_URL') |
|||
}) |
|||
); |
|||
} else if (request.path === '/es' || request.path.startsWith('/es/')) { |
|||
response.send( |
|||
this.interpolate(this.indexHtmlEs, { |
|||
currentDate, |
|||
featureGraphicPath, |
|||
title, |
|||
description: |
|||
'Ghostfolio es un dashboard de finanzas personales para hacer un seguimiento de tus activos como acciones, ETFs o criptodivisas a través de múltiples plataformas.', |
|||
languageCode: 'es', |
|||
path: request.path, |
|||
rootUrl: this.configurationService.get('ROOT_URL') |
|||
}) |
|||
); |
|||
} else if (request.path === '/fr' || request.path.startsWith('/fr/')) { |
|||
response.send( |
|||
this.interpolate(this.indexHtmlFr, { |
|||
currentDate, |
|||
featureGraphicPath, |
|||
title, |
|||
description: |
|||
'Ghostfolio est un dashboard de finances personnelles qui permet de suivre vos actifs comme les actions, les ETF ou les crypto-monnaies sur plusieurs plateformes.', |
|||
languageCode: 'fr', |
|||
path: request.path, |
|||
rootUrl: this.configurationService.get('ROOT_URL') |
|||
}) |
|||
); |
|||
} else if (request.path === '/it' || request.path.startsWith('/it/')) { |
|||
response.send( |
|||
this.interpolate(this.indexHtmlIt, { |
|||
currentDate, |
|||
featureGraphicPath, |
|||
title, |
|||
description: |
|||
'Ghostfolio è un dashboard di finanza personale per tenere traccia delle vostre attività come azioni, ETF o criptovalute su più piattaforme.', |
|||
languageCode: 'it', |
|||
path: request.path, |
|||
rootUrl: this.configurationService.get('ROOT_URL') |
|||
}) |
|||
); |
|||
} else if (request.path === '/nl' || request.path.startsWith('/nl/')) { |
|||
response.send( |
|||
this.interpolate(this.indexHtmlNl, { |
|||
currentDate, |
|||
featureGraphicPath, |
|||
title, |
|||
description: |
|||
'Ghostfolio is een persoonlijk financieel dashboard om uw activa zoals aandelen, ETF’s of cryptocurrencies over meerdere platforms bij te houden.', |
|||
languageCode: 'nl', |
|||
path: request.path, |
|||
rootUrl: this.configurationService.get('ROOT_URL') |
|||
}) |
|||
); |
|||
} else if (request.path === '/pt' || request.path.startsWith('/pt/')) { |
|||
response.send( |
|||
this.interpolate(this.indexHtmlPt, { |
|||
currentDate, |
|||
featureGraphicPath, |
|||
title, |
|||
description: |
|||
'Ghostfolio é um dashboard de finanças pessoais para acompanhar os seus activos como acções, ETFs ou criptomoedas em múltiplas plataformas.', |
|||
languageCode: 'pt', |
|||
path: request.path, |
|||
rootUrl: this.configurationService.get('ROOT_URL') |
|||
}) |
|||
); |
|||
} else { |
|||
response.send( |
|||
this.interpolate(this.indexHtmlEn, { |
|||
currentDate, |
|||
featureGraphicPath, |
|||
title, |
|||
description: FrontendMiddleware.DEFAULT_DESCRIPTION, |
|||
languageCode: DEFAULT_LANGUAGE_CODE, |
|||
path: request.path, |
|||
rootUrl: this.configurationService.get('ROOT_URL') |
|||
}) |
|||
); |
|||
} |
|||
} |
|||
|
|||
private getPathOfIndexHtmlFile(aLocale: string) { |
|||
return path.join(__dirname, '..', 'client', aLocale, 'index.html'); |
|||
} |
|||
|
|||
private interpolate(template: string, context: any) { |
|||
return template.replace(/[$]{([^}]+)}/g, (_, objectPath) => { |
|||
const properties = objectPath.split('.'); |
|||
return properties.reduce( |
|||
(previous, current) => previous?.[current], |
|||
context |
|||
); |
|||
}); |
|||
} |
|||
|
|||
private isFileRequest(filename: string) { |
|||
if (filename === '/assets/LICENSE') { |
|||
return true; |
|||
} else if (filename.includes('auth/ey')) { |
|||
return false; |
|||
} |
|||
|
|||
return filename.split('.').pop() !== filename; |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
import { Cache } from 'cache-manager'; |
|||
|
|||
import type { RedisStore } from './redis-store.interface'; |
|||
|
|||
export interface RedisCache extends Cache { |
|||
store: RedisStore; |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
import { Store } from 'cache-manager'; |
|||
import { createClient } from 'redis'; |
|||
|
|||
export interface RedisStore extends Store { |
|||
getClient: () => ReturnType<typeof createClient>; |
|||
isCacheableValue: (value: any) => boolean; |
|||
name: 'redis'; |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
import * as fs from 'fs'; |
|||
import * as path from 'path'; |
|||
|
|||
import { |
|||
DATE_FORMAT, |
|||
getYesterday, |
|||
interpolate |
|||
} from '@ghostfolio/common/helper'; |
|||
import { Controller, Get, Res, VERSION_NEUTRAL, Version } from '@nestjs/common'; |
|||
import { format } from 'date-fns'; |
|||
import { Response } from 'express'; |
|||
|
|||
@Controller('sitemap.xml') |
|||
export class SitemapController { |
|||
public sitemapXml = ''; |
|||
|
|||
public constructor() { |
|||
try { |
|||
this.sitemapXml = fs.readFileSync( |
|||
path.join(__dirname, 'assets', 'sitemap.xml'), |
|||
'utf8' |
|||
); |
|||
} catch {} |
|||
} |
|||
|
|||
@Get() |
|||
@Version(VERSION_NEUTRAL) |
|||
public async flushCache(@Res() response: Response): Promise<void> { |
|||
response.setHeader('content-type', 'application/xml'); |
|||
response.send( |
|||
interpolate(this.sitemapXml, { |
|||
currentDate: format(getYesterday(), DATE_FORMAT) |
|||
}) |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; |
|||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; |
|||
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module'; |
|||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; |
|||
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; |
|||
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; |
|||
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; |
|||
import { Module } from '@nestjs/common'; |
|||
|
|||
import { SitemapController } from './sitemap.controller'; |
|||
|
|||
@Module({ |
|||
controllers: [SitemapController], |
|||
imports: [ |
|||
ConfigurationModule, |
|||
DataGatheringModule, |
|||
DataProviderModule, |
|||
ExchangeRateDataModule, |
|||
PrismaModule, |
|||
RedisCacheModule, |
|||
SymbolProfileModule |
|||
] |
|||
}) |
|||
export class SitemapModule {} |
|||
@ -0,0 +1 @@ |
|||
["AU", "HK", "NZ", "SG"] |
|||
@ -0,0 +1,19 @@ |
|||
[ |
|||
"AT", |
|||
"BE", |
|||
"CH", |
|||
"DE", |
|||
"DK", |
|||
"ES", |
|||
"FI", |
|||
"FR", |
|||
"GB", |
|||
"IE", |
|||
"IL", |
|||
"IT", |
|||
"LU", |
|||
"NL", |
|||
"NO", |
|||
"PT", |
|||
"SE" |
|||
] |
|||
@ -0,0 +1,655 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<urlset |
|||
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" |
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 |
|||
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"> |
|||
<url> |
|||
<loc>https://ghostfol.io/de</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/blog</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/blog/2021/07/hallo-ghostfolio</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/features</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/haeufig-gestellte-fragen</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/maerkte</loc> |
|||
<changefreq>daily</changefreq> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/open</loc> |
|||
<changefreq>daily</changefreq> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/preise</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/registrierung</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-altoo</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-copilot-money</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-delta</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-divvydiary</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-exirio</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-folishare</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-getquin</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-gospatz</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-justetf</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-kubera</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-markets.sh</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-maybe-finance</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-monse</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-parqet</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-plannix</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-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-portseido</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-projectionlab</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-seeking-alpha</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-sharesight</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-simple-portfolio</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<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-sumio</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-utluna</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-yeekatee</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ueber-uns</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ueber-uns/changelog</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ueber-uns/datenschutzbestimmungen</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ueber-uns/lizenz</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/de/ueber-uns/oss-friends</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/about</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/about/changelog</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/about/license</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/about/oss-friends</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog/2021/07/hello-ghostfolio</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog/2022/01/ghostfolio-first-months-in-open-source</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog/2022/07/ghostfolio-meets-internet-identity</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog/2022/07/how-do-i-get-my-finances-in-order</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog/2022/08/500-stars-on-github</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog/2022/10/hacktoberfest-2022</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog/2022/11/black-friday-2022</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog/2022/12/the-importance-of-tracking-your-personal-finances</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog/2023/02/ghostfolio-meets-umbrel</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog/2023/03/ghostfolio-reaches-1000-stars-on-github</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog/2023/05/unlock-your-financial-potential-with-ghostfolio</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog/2023/07/exploring-the-path-to-fire</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/blog/2023/08/ghostfolio-joins-oss-friends</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/faq</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/features</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/markets</loc> |
|||
<changefreq>daily</changefreq> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/open</loc> |
|||
<changefreq>daily</changefreq> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/pricing</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/register</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-altoo</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-copilot-money</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-delta</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-divvydiary</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-exirio</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-folishare</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-getquin</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-gospatz</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-justetf</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-kubera</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-markets.sh</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-maybe-finance</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-monse</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-parqet</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-plannix</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-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-portseido</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-projectionlab</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-seeking-alpha</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-sharesight</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-simple-portfolio</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<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-sumio</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-utluna</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-yeekatee</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/es</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/es/funcionalidades</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/es/mercados</loc> |
|||
<changefreq>daily</changefreq> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/es/open</loc> |
|||
<changefreq>daily</changefreq> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/es/precios</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/es/preguntas-mas-frecuentes</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/es/recursos</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/es/registro</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/es/sobre</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/es/sobre/changelog</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/es/sobre/licencia</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/es/sobre/oss-friends</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/es/sobre/politica-de-privacidad</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/fr</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/fr/a-propos</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/fr/a-propos/changelog</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/fr/a-propos/licence</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/fr/a-propos/oss-friends</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/fr/a-propos/politique-de-confidentialite</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/fr/enregistrement</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/fr/fonctionnalites</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/fr/foire-aux-questions</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/fr/marches</loc> |
|||
<changefreq>daily</changefreq> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/fr/open</loc> |
|||
<changefreq>daily</changefreq> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/fr/prix</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/fr/ressources</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/it</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/it/domande-piu-frequenti</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/it/funzionalita</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/it/informazioni-su</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/it/informazioni-su/changelog</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/it/informazioni-su/informativa-sulla-privacy</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/it/informazioni-su/licenza</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/it/informazioni-su/oss-friends</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/it/iscrizione</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/it/mercati</loc> |
|||
<changefreq>daily</changefreq> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/it/open</loc> |
|||
<changefreq>daily</changefreq> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/it/prezzi</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/it/risorse</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/nl</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/nl/bronnen</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/nl/kenmerken</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/nl/markten</loc> |
|||
<changefreq>daily</changefreq> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/nl/open</loc> |
|||
<changefreq>daily</changefreq> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/nl/over</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/nl/over/changelog</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/nl/over/licentie</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/nl/over/oss-friends</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/nl/over/privacybeleid</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/nl/prijzen</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/nl/registratie</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/nl/vaak-gestelde-vragen</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/pt/blog</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/pt/funcionalidades</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/pt/mercados</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/pt/open</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/pt/perguntas-mais-frequentes</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/pt/precos</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/pt/recursos</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/pt/registo</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/pt/sobre</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/pt/sobre/changelog</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/pt/sobre/licenca</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/pt/sobre/oss-friends</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
<url> |
|||
<loc>https://ghostfol.io/pt/sobre/politica-de-privacidade</loc> |
|||
<lastmod>${currentDate}T00:00:00+00:00</lastmod> |
|||
</url> |
|||
</urlset> |
|||
@ -0,0 +1,132 @@ |
|||
import * as fs from 'fs'; |
|||
import { join } from 'path'; |
|||
|
|||
import { environment } from '@ghostfolio/api/environments/environment'; |
|||
import { |
|||
DEFAULT_LANGUAGE_CODE, |
|||
DEFAULT_ROOT_URL, |
|||
SUPPORTED_LANGUAGE_CODES |
|||
} from '@ghostfolio/common/config'; |
|||
import { DATE_FORMAT, interpolate } from '@ghostfolio/common/helper'; |
|||
import { format } from 'date-fns'; |
|||
import { NextFunction, Request, Response } from 'express'; |
|||
|
|||
const descriptions = { |
|||
de: 'Mit dem Finanz-Dashboard Ghostfolio können Sie Ihr Vermögen in Form von Aktien, ETFs oder Kryptowährungen verteilt über mehrere Finanzinstitute überwachen.', |
|||
en: 'Ghostfolio is a personal finance dashboard to keep track of your assets like stocks, ETFs or cryptocurrencies across multiple platforms.', |
|||
es: 'Ghostfolio es un dashboard de finanzas personales para hacer un seguimiento de tus activos como acciones, ETFs o criptodivisas a través de múltiples plataformas.', |
|||
fr: 'Ghostfolio est un dashboard de finances personnelles qui permet de suivre vos actifs comme les actions, les ETF ou les crypto-monnaies sur plusieurs plateformes.', |
|||
it: 'Ghostfolio è un dashboard di finanza personale per tenere traccia delle vostre attività come azioni, ETF o criptovalute su più piattaforme.', |
|||
nl: 'Ghostfolio is een persoonlijk financieel dashboard om uw activa zoals aandelen, ETF’s of cryptocurrencies over meerdere platforms bij te houden.', |
|||
pt: 'Ghostfolio é um dashboard de finanças pessoais para acompanhar os seus activos como acções, ETFs ou criptomoedas em múltiplas plataformas.' |
|||
}; |
|||
|
|||
const title = 'Ghostfolio – Open Source Wealth Management Software'; |
|||
const titleShort = 'Ghostfolio'; |
|||
|
|||
let indexHtmlMap: { [languageCode: string]: string } = {}; |
|||
|
|||
try { |
|||
indexHtmlMap = SUPPORTED_LANGUAGE_CODES.reduce( |
|||
(map, languageCode) => ({ |
|||
...map, |
|||
[languageCode]: fs.readFileSync( |
|||
join(__dirname, '..', 'client', languageCode, 'index.html'), |
|||
'utf8' |
|||
) |
|||
}), |
|||
{} |
|||
); |
|||
} catch {} |
|||
|
|||
const locales = { |
|||
'/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt': { |
|||
featureGraphicPath: 'assets/images/blog/ghostfolio-x-sackgeld.png', |
|||
title: `Ghostfolio auf Sackgeld.com vorgestellt - ${titleShort}` |
|||
}, |
|||
'/en/blog/2022/08/500-stars-on-github': { |
|||
featureGraphicPath: 'assets/images/blog/500-stars-on-github.jpg', |
|||
title: `500 Stars - ${titleShort}` |
|||
}, |
|||
'/en/blog/2022/10/hacktoberfest-2022': { |
|||
featureGraphicPath: 'assets/images/blog/hacktoberfest-2022.png', |
|||
title: `Hacktoberfest 2022 - ${titleShort}` |
|||
}, |
|||
'/en/blog/2022/12/the-importance-of-tracking-your-personal-finances': { |
|||
featureGraphicPath: 'assets/images/blog/20221226.jpg', |
|||
title: `The importance of tracking your personal finances - ${titleShort}` |
|||
}, |
|||
'/en/blog/2023/02/ghostfolio-meets-umbrel': { |
|||
featureGraphicPath: 'assets/images/blog/ghostfolio-x-umbrel.png', |
|||
title: `Ghostfolio meets Umbrel - ${titleShort}` |
|||
}, |
|||
'/en/blog/2023/03/ghostfolio-reaches-1000-stars-on-github': { |
|||
featureGraphicPath: 'assets/images/blog/1000-stars-on-github.jpg', |
|||
title: `Ghostfolio reaches 1’000 Stars on GitHub - ${titleShort}` |
|||
}, |
|||
'/en/blog/2023/05/unlock-your-financial-potential-with-ghostfolio': { |
|||
featureGraphicPath: 'assets/images/blog/20230520.jpg', |
|||
title: `Unlock your Financial Potential with Ghostfolio - ${titleShort}` |
|||
}, |
|||
'/en/blog/2023/07/exploring-the-path-to-fire': { |
|||
featureGraphicPath: 'assets/images/blog/20230701.jpg', |
|||
title: `Exploring the Path to FIRE - ${titleShort}` |
|||
}, |
|||
'/en/blog/2023/08/ghostfolio-joins-oss-friends': { |
|||
featureGraphicPath: 'assets/images/blog/ghostfolio-joins-oss-friends.png', |
|||
title: `Ghostfolio joins OSS Friends - ${titleShort}` |
|||
} |
|||
}; |
|||
|
|||
const isFileRequest = (filename: string) => { |
|||
if (filename === '/assets/LICENSE') { |
|||
return true; |
|||
} else if ( |
|||
filename.includes('auth/ey') || |
|||
filename.includes( |
|||
'personal-finance-tools/open-source-alternative-to-markets.sh' |
|||
) |
|||
) { |
|||
return false; |
|||
} |
|||
|
|||
return filename.split('.').pop() !== filename; |
|||
}; |
|||
|
|||
export const HtmlTemplateMiddleware = async ( |
|||
request: Request, |
|||
response: Response, |
|||
next: NextFunction |
|||
) => { |
|||
const path = request.originalUrl.replace(/\/$/, ''); |
|||
let languageCode = path.substr(1, 2); |
|||
|
|||
if (!SUPPORTED_LANGUAGE_CODES.includes(languageCode)) { |
|||
languageCode = DEFAULT_LANGUAGE_CODE; |
|||
} |
|||
|
|||
const currentDate = format(new Date(), DATE_FORMAT); |
|||
const rootUrl = process.env.ROOT_URL || DEFAULT_ROOT_URL; |
|||
|
|||
if ( |
|||
path.startsWith('/api/') || |
|||
isFileRequest(path) || |
|||
!environment.production |
|||
) { |
|||
// Skip
|
|||
next(); |
|||
} else { |
|||
const indexHtml = interpolate(indexHtmlMap[languageCode], { |
|||
currentDate, |
|||
languageCode, |
|||
path, |
|||
rootUrl, |
|||
description: descriptions[languageCode], |
|||
featureGraphicPath: |
|||
locales[path]?.featureGraphicPath ?? 'assets/cover.png', |
|||
title: locales[path]?.title ?? title |
|||
}); |
|||
|
|||
return response.send(indexHtml); |
|||
} |
|||
}; |
|||
@ -0,0 +1,10 @@ |
|||
import { AccountBalanceService } from '@ghostfolio/api/services/account-balance/account-balance.service'; |
|||
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; |
|||
import { Module } from '@nestjs/common'; |
|||
|
|||
@Module({ |
|||
exports: [AccountBalanceService], |
|||
imports: [PrismaModule], |
|||
providers: [AccountBalanceService] |
|||
}) |
|||
export class AccountBalanceModule {} |
|||
@ -0,0 +1,16 @@ |
|||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; |
|||
import { Injectable } from '@nestjs/common'; |
|||
import { AccountBalance, Prisma } from '@prisma/client'; |
|||
|
|||
@Injectable() |
|||
export class AccountBalanceService { |
|||
public constructor(private readonly prismaService: PrismaService) {} |
|||
|
|||
public async createAccountBalance( |
|||
data: Prisma.AccountBalanceCreateInput |
|||
): Promise<AccountBalance> { |
|||
return this.prismaService.accountBalance.create({ |
|||
data |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface'; |
|||
import { HttpException, Inject, Injectable } from '@nestjs/common'; |
|||
import { Prisma } from '@prisma/client'; |
|||
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; |
|||
|
|||
@Injectable() |
|||
export class DataEnhancerService { |
|||
public constructor( |
|||
@Inject('DataEnhancers') |
|||
private readonly dataEnhancers: DataEnhancerInterface[] |
|||
) {} |
|||
|
|||
public async enhance(aName: string) { |
|||
const dataEnhancer = this.dataEnhancers.find((dataEnhancer) => { |
|||
return dataEnhancer.getName() === aName; |
|||
}); |
|||
|
|||
if (!dataEnhancer) { |
|||
throw new HttpException( |
|||
getReasonPhrase(StatusCodes.NOT_FOUND), |
|||
StatusCodes.NOT_FOUND |
|||
); |
|||
} |
|||
|
|||
try { |
|||
const assetProfile = await dataEnhancer.enhance({ |
|||
response: { |
|||
assetClass: 'EQUITY', |
|||
assetSubClass: 'ETF' |
|||
}, |
|||
symbol: dataEnhancer.getTestSymbol() |
|||
}); |
|||
|
|||
if ( |
|||
(assetProfile.countries as unknown as Prisma.JsonArray)?.length > 0 && |
|||
(assetProfile.sectors as unknown as Prisma.JsonArray)?.length > 0 |
|||
) { |
|||
return true; |
|||
} |
|||
} catch {} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
@ -1,57 +1,110 @@ |
|||
<div |
|||
class="align-items-center container d-flex flex-column h-100 justify-content-center overview p-0 position-relative" |
|||
> |
|||
<div class="row w-100"> |
|||
<div class="col p-0"> |
|||
<div class="chart-container mx-auto position-relative"> |
|||
<div |
|||
*ngIf="hasPermissionToCreateOrder && historicalDataItems?.length === 0" |
|||
class="align-items-center d-flex h-100 justify-content-center w-100" |
|||
<div |
|||
*ngIf="hasPermissionToCreateOrder && historicalDataItems?.length === 0; else isUserActive" |
|||
class="justify-content-center row w-100" |
|||
> |
|||
<div class="col introduction"> |
|||
<h4 i18n>Welcome to Ghostfolio</h4> |
|||
<p i18n>Ready to take control of your personal finances?</p> |
|||
<ol class="font-weight-bold"> |
|||
<li |
|||
class="mb-2" |
|||
[ngClass]="{ 'text-muted': user?.accounts?.length > 1 }" |
|||
> |
|||
<div class="d-flex justify-content-center"> |
|||
<gf-no-transactions-info-indicator></gf-no-transactions-info-indicator> |
|||
</div> |
|||
</div> |
|||
<gf-line-chart |
|||
class="position-absolute" |
|||
symbol="Performance" |
|||
unit="%" |
|||
[colorScheme]="user?.settings?.colorScheme" |
|||
[hidden]="historicalDataItems?.length === 0" |
|||
[historicalDataItems]="historicalDataItems" |
|||
[isAnimated]="user?.settings?.dateRange === '1d' ? false : true" |
|||
[locale]="user?.settings?.locale" |
|||
[ngClass]="{ 'pr-3': deviceType === 'mobile' }" |
|||
[showGradient]="true" |
|||
[showLoader]="false" |
|||
[showXAxis]="false" |
|||
[showYAxis]="false" |
|||
></gf-line-chart> |
|||
<a class="d-block" [routerLink]="['/accounts']" |
|||
><span i18n>Setup your accounts</span><br /> |
|||
<span class="font-weight-normal" i18n |
|||
>Get a comprehensive financial overview by adding your bank and |
|||
brokerage accounts.</span |
|||
></a |
|||
> |
|||
</li> |
|||
<li class="mb-2"> |
|||
<a class="d-block" [routerLink]="['/portfolio', 'activities']"> |
|||
<span i18n>Capture your activities</span><br /> |
|||
<span class="font-weight-normal" i18n |
|||
>Record your investment activities to keep your portfolio up to |
|||
date.</span |
|||
></a |
|||
> |
|||
</li> |
|||
<li class="mb-2"> |
|||
<a class="d-block" [routerLink]="['/portfolio']"> |
|||
<span i18n>Monitor and analyze your portfolio</span><br /> |
|||
<span class="font-weight-normal" i18n |
|||
>Track your progress in real-time with comprehensive analysis and |
|||
insights.</span |
|||
> |
|||
</a> |
|||
</li> |
|||
</ol> |
|||
<div class="d-flex justify-content-center"> |
|||
<a |
|||
*ngIf="user?.accounts?.length === 1" |
|||
color="primary" |
|||
mat-flat-button |
|||
[routerLink]="['/accounts']" |
|||
> |
|||
<ng-container i18n>Setup accounts</ng-container> |
|||
</a> |
|||
<a |
|||
*ngIf="user?.accounts?.length > 1" |
|||
color="primary" |
|||
mat-flat-button |
|||
[routerLink]="['/portfolio', 'activities']" |
|||
> |
|||
<ng-container i18n>Add activity</ng-container> |
|||
</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="overview-container row mt-1"> |
|||
<div class="col"> |
|||
<gf-portfolio-performance |
|||
class="pb-4" |
|||
[baseCurrency]="user?.settings?.baseCurrency" |
|||
[deviceType]="deviceType" |
|||
[errors]="errors" |
|||
[isAllTimeHigh]="isAllTimeHigh" |
|||
[isAllTimeLow]="isAllTimeLow" |
|||
[isLoading]="isLoadingPerformance" |
|||
[locale]="user?.settings?.locale" |
|||
[performance]="performance" |
|||
[showDetails]="showDetails" |
|||
></gf-portfolio-performance> |
|||
<div *ngIf="showDetails" class="text-center"> |
|||
<gf-toggle |
|||
[defaultValue]="user?.settings?.dateRange" |
|||
<ng-template #isUserActive> |
|||
<div class="row w-100"> |
|||
<div class="col p-0"> |
|||
<div class="chart-container mx-auto position-relative"> |
|||
<gf-line-chart |
|||
class="position-absolute" |
|||
symbol="Performance" |
|||
unit="%" |
|||
[colorScheme]="user?.settings?.colorScheme" |
|||
[hidden]="historicalDataItems?.length === 0" |
|||
[historicalDataItems]="historicalDataItems" |
|||
[isAnimated]="user?.settings?.dateRange === '1d' ? false : true" |
|||
[locale]="user?.settings?.locale" |
|||
[ngClass]="{ 'pr-3': deviceType === 'mobile' }" |
|||
[showGradient]="true" |
|||
[showLoader]="false" |
|||
[showXAxis]="false" |
|||
[showYAxis]="false" |
|||
></gf-line-chart> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="overview-container row mt-1"> |
|||
<div class="col"> |
|||
<gf-portfolio-performance |
|||
class="pb-4" |
|||
[baseCurrency]="user?.settings?.baseCurrency" |
|||
[deviceType]="deviceType" |
|||
[errors]="errors" |
|||
[isAllTimeHigh]="isAllTimeHigh" |
|||
[isAllTimeLow]="isAllTimeLow" |
|||
[isLoading]="isLoadingPerformance" |
|||
[options]="dateRangeOptions" |
|||
(change)="onChangeDateRange($event.value)" |
|||
></gf-toggle> |
|||
[locale]="user?.settings?.locale" |
|||
[performance]="performance" |
|||
[showDetails]="showDetails" |
|||
></gf-portfolio-performance> |
|||
<div *ngIf="showDetails" class="text-center"> |
|||
<gf-toggle |
|||
[defaultValue]="user?.settings?.dateRange" |
|||
[isLoading]="isLoadingPerformance" |
|||
[options]="dateRangeOptions" |
|||
(change)="onChangeDateRange($event.value)" |
|||
></gf-toggle> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</ng-template> |
|||
</div> |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue