@ -198,6 +198,20 @@ export class ImportService {
const tagIdMapping : { [ oldTagId : string ] : string } = { } ;
const tagIdMapping : { [ oldTagId : string ] : string } = { } ;
const userCurrency = user . settings . settings . baseCurrency ;
const userCurrency = user . settings . settings . baseCurrency ;
// Validate the symbols before any data is persisted
for ( const [ index , assetProfileWithMarketData ] of (
assetProfilesWithMarketDataDto ? ? [ ]
) . entries ( ) ) {
if (
assetProfileWithMarketData . dataSource === DataSource . MANUAL &&
! isValidCustomAssetProfileSymbol ( assetProfileWithMarketData . symbol )
) {
throw new Error (
` assetProfiles. ${ index } .symbol (" ${ assetProfileWithMarketData . symbol } ") must be a UUID or start with the prefix " ${ ghostfolioPrefix } _" for the data source (" ${ DataSource . MANUAL } ") `
) ;
}
}
if ( platformsDto ? . length ) {
if ( platformsDto ? . length ) {
const canCreatePlatform = hasPermission (
const canCreatePlatform = hasPermission (
user . permissions ,
user . permissions ,
@ -236,6 +250,26 @@ export class ImportService {
}
}
}
}
for ( const [ index , activity ] of activitiesDto . entries ( ) ) {
if ( ! activity . dataSource ) {
if ( [ 'FEE' , 'INTEREST' , 'LIABILITY' ] . includes ( activity . type ) ) {
activity . dataSource = DataSource . MANUAL ;
} else {
activity . dataSource =
this . dataProviderService . getDataSourceForImport ( ) ;
}
}
if (
activity . dataSource === DataSource . MANUAL &&
! isValidCustomAssetProfileSymbol ( activity . symbol )
) {
throw new Error (
` activities. ${ index } .symbol (" ${ activity . symbol } ") must be a UUID or start with the prefix " ${ ghostfolioPrefix } _" for the data source (" ${ DataSource . MANUAL } ") `
) ;
}
}
const existingTagsOfUser =
const existingTagsOfUser =
tagsDto ? . length || ( ! isDryRun && accountsWithBalancesDto ? . length )
tagsDto ? . length || ( ! isDryRun && accountsWithBalancesDto ? . length )
? await this . tagService . getTagsForUser ( user . id )
? await this . tagService . getTagsForUser ( user . id )
@ -386,40 +420,56 @@ export class ImportService {
}
}
}
}
for ( const [ index , assetProfileWithMarketData ] of (
if ( assetProfilesWithMarketDataDto ? . length ) {
assetProfilesWithMarketDataDto ? ? [ ]
const customAssetProfileNames = assetProfilesWithMarketDataDto
) . entries ( ) ) {
. filter ( ( { dataSource , name } ) = > {
if (
return dataSource === DataSource . MANUAL && Boolean ( name ) ;
assetProfileWithMarketData . dataSource === DataSource . MANUAL &&
} )
! isValidCustomAssetProfileSymbol ( assetProfileWithMarketData . symbol )
. map ( ( { name } ) = > {
) {
return name ;
throw new Error (
} ) ;
` assetProfiles. ${ index } .symbol (" ${ assetProfileWithMarketData . symbol } ") must be a UUID or start with the prefix " ${ ghostfolioPrefix } _" for the data source (" ${ DataSource . MANUAL } ") `
) ;
}
}
if ( ! isDryRun && assetProfilesWithMarketDataDto ? . length ) {
const [ existingAssetProfiles , existingCustomAssetProfilesOfUser ] =
const existingAssetProfiles =
await Promise . all ( [
await this . symbolProfileService . getSymbolProfiles (
this . symbolProfileService . getSymbolProfiles (
assetProfilesWithMarketDataDto . map ( ( { dataSource , symbol } ) = > {
assetProfilesWithMarketDataDto . map ( ( { dataSource , symbol } ) = > {
return { dataSource , symbol } ;
return { dataSource , symbol } ;
} )
} )
) ;
) ,
this . symbolProfileService . getCustomSymbolProfilesByNames ( {
names : customAssetProfileNames ,
userId : user.id
} )
] ) ;
for ( const assetProfileWithMarketData of assetProfilesWithMarketDataDto ) {
for ( const assetProfileWithMarketData of assetProfilesWithMarketDataDto ) {
let symbol = assetProfileWithMarketData . symbol ;
// Check if there is any existing asset profile
// Check if there is any existing asset profile
const existingAssetProfile = existingAssetProfiles . find (
const existingAssetProfile = existingAssetProfiles . find (
( { dataSource , symbol } ) = > {
( assetProfile ) = > {
return (
return (
dataSource === assetProfileWithMarketData . dataSource &&
assetProfile . dataSource ===
symbol === assetProfileWithMarketData . symbol
assetProfileWithMarketData . dataSource &&
assetProfile . symbol === assetProfileWithMarketData . symbol
) ;
) ;
}
}
) ;
) ;
// If there is no asset profile or if the asset profile belongs to a different user, then create a new asset profile
// If there is no asset profile or if the asset profile belongs to a different user, then reuse the custom asset profile of the user or create a new asset profile
if ( ! existingAssetProfile || existingAssetProfile . userId !== user . id ) {
if ( ! existingAssetProfile || existingAssetProfile . userId !== user . id ) {
// Check if the user has a custom asset profile with the same name
const existingCustomAssetProfileOfUser =
assetProfileWithMarketData . dataSource === DataSource . MANUAL
? existingCustomAssetProfilesOfUser . find ( ( { name } ) = > {
return name === assetProfileWithMarketData . name ;
} )
: undefined ;
if ( existingCustomAssetProfileOfUser ) {
// Reuse the custom asset profile of the user instead of creating a duplicate
symbol = existingCustomAssetProfileOfUser . symbol ;
} else {
const assetProfile : CreateAssetProfileDto = omit (
const assetProfile : CreateAssetProfileDto = omit (
assetProfileWithMarketData ,
assetProfileWithMarketData ,
'marketData'
'marketData'
@ -427,11 +477,12 @@ export class ImportService {
// Asset profile belongs to a different user
// Asset profile belongs to a different user
if ( existingAssetProfile ) {
if ( existingAssetProfile ) {
const symbol = randomUUID ( ) ;
symbol = randomUUID ( ) ;
assetProfileSymbolMapping [ assetProfile . symbol ] = symbol ;
assetProfile . symbol = symbol ;
}
}
assetProfile . symbol = symbol ;
if ( ! isDryRun ) {
// Create a new asset profile
// Create a new asset profile
const assetProfileObject : Prisma.SymbolProfileCreateInput = {
const assetProfileObject : Prisma.SymbolProfileCreateInput = {
. . . assetProfile ,
. . . assetProfile ,
@ -441,13 +492,34 @@ export class ImportService {
await this . symbolProfileService . add ( assetProfileObject ) ;
await this . symbolProfileService . add ( assetProfileObject ) ;
}
}
if (
assetProfile . dataSource === DataSource . MANUAL &&
Boolean ( assetProfile . name )
) {
existingCustomAssetProfilesOfUser . push ( {
name : assetProfile.name ,
symbol : assetProfile . symbol
} ) ;
}
}
if ( symbol !== assetProfileWithMarketData . symbol ) {
assetProfileSymbolMapping [ assetProfileWithMarketData . symbol ] =
symbol ;
// Keep the asset profile in sync with the activities to validate
assetProfileWithMarketData . symbol = symbol ;
}
}
if ( ! isDryRun ) {
// Insert or update market data
// Insert or update market data
const marketDataObjects = assetProfileWithMarketData . marketData . map (
const marketDataObjects = assetProfileWithMarketData . marketData . map (
( marketData ) = > {
( marketData ) = > {
return {
return {
. . . marketData ,
. . . marketData ,
dataSource : assetProfileWithMarketData.dataSource ,
symbol ,
symbol : assetProfileWithMarketData . symbol
dataSource : assetProfileWithMarketData.dataSource
} as Prisma . MarketDataUpdateInput ;
} as Prisma . MarketDataUpdateInput ;
}
}
) ;
) ;
@ -455,24 +527,12 @@ export class ImportService {
await this . marketDataService . updateMany ( { data : marketDataObjects } ) ;
await this . marketDataService . updateMany ( { data : marketDataObjects } ) ;
}
}
}
}
for ( const [ index , activity ] of activitiesDto . entries ( ) ) {
if ( ! activity . dataSource ) {
if ( [ 'FEE' , 'INTEREST' , 'LIABILITY' ] . includes ( activity . type ) ) {
activity . dataSource = DataSource . MANUAL ;
} else {
activity . dataSource =
this . dataProviderService . getDataSourceForImport ( ) ;
}
}
}
if (
for ( const activity of activitiesDto ) {
activity . dataSource === DataSource . MANUAL &&
// If an asset profile is created or reused, then update the symbol in all activities
! isValidCustomAssetProfileSymbol ( activity . symbol )
if ( assetProfileSymbolMapping [ activity . symbol ] ) {
) {
activity . symbol = assetProfileSymbolMapping [ activity . symbol ] ;
throw new Error (
` activities. ${ index } .symbol (" ${ activity . symbol } ") must be a UUID or start with the prefix " ${ ghostfolioPrefix } _" for the data source (" ${ DataSource . MANUAL } ") `
) ;
}
}
if ( ! isDryRun ) {
if ( ! isDryRun ) {
@ -481,11 +541,6 @@ export class ImportService {
activity . accountId = accountIdMapping [ activity . accountId ] ;
activity . accountId = accountIdMapping [ activity . accountId ] ;
}
}
// If a new asset profile is created, then update the symbol in all activities
if ( assetProfileSymbolMapping [ activity . symbol ] ) {
activity . symbol = assetProfileSymbolMapping [ activity . symbol ] ;
}
// If a new tag is created, then update the tag ID in all activities
// If a new tag is created, then update the tag ID in all activities
activity . tags = ( activity . tags ? ? [ ] ) . map ( ( tagId ) = > {
activity . tags = ( activity . tags ? ? [ ] ) . map ( ( tagId ) = > {
return tagIdMapping [ tagId ] ? ? tagId ;
return tagIdMapping [ tagId ] ? ? tagId ;