Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror
This commit is contained in:
commit
0b596b6541
15
CHANGELOG.md
15
CHANGELOG.md
@ -5,16 +5,29 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
## 2.111.0 - 2024-09-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added read `permissions` to the `Platform` model
|
||||||
|
- Added read `permissions` to the `Tag` model
|
||||||
|
- Added `userId` to the `Tag` database schema
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Considered the availability of the date range selector in the assistant per view
|
||||||
|
- Considered the availability of the filters in the assistant per view
|
||||||
- Optimized the portfolio calculations with smarter cloning of activities
|
- Optimized the portfolio calculations with smarter cloning of activities
|
||||||
- Integrated the add currency functionality into the market data section of the admin control panel
|
- Integrated the add currency functionality into the market data section of the admin control panel
|
||||||
- Improved the language localization for German (`de`)
|
- Improved the language localization for German (`de`)
|
||||||
- Upgraded `prisma` from version `5.19.1` to `5.20.0`
|
- Upgraded `prisma` from version `5.19.1` to `5.20.0`
|
||||||
- Upgraded `webpack-bundle-analyzer` from version `4.10.1` to `4.10.2`
|
- Upgraded `webpack-bundle-analyzer` from version `4.10.1` to `4.10.2`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed the content height of the create or update platform dialog in the admin control
|
||||||
|
- Fixed the content height of the create or update tag dialog in the admin control
|
||||||
|
|
||||||
## 2.110.0 - 2024-09-24
|
## 2.110.0 - 2024-09-24
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -114,7 +114,7 @@ export class InfoService {
|
|||||||
}),
|
}),
|
||||||
this.getStatistics(),
|
this.getStatistics(),
|
||||||
this.getSubscriptions(),
|
this.getSubscriptions(),
|
||||||
this.tagService.get()
|
this.tagService.getPublic()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (isUserSignupEnabled) {
|
if (isUserSignupEnabled) {
|
||||||
|
@ -26,6 +26,7 @@ export class PlatformController {
|
|||||||
public constructor(private readonly platformService: PlatformService) {}
|
public constructor(private readonly platformService: PlatformService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@HasPermission(permissions.readPlatforms)
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
public async getPlatforms() {
|
public async getPlatforms() {
|
||||||
return this.platformService.getPlatformsWithAccountCount();
|
return this.platformService.getPlatformsWithAccountCount();
|
||||||
|
@ -26,6 +26,7 @@ export class TagController {
|
|||||||
public constructor(private readonly tagService: TagService) {}
|
public constructor(private readonly tagService: TagService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@HasPermission(permissions.readTags)
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
public async getTags() {
|
public async getTags() {
|
||||||
return this.tagService.getTagsWithActivityCount();
|
return this.tagService.getTagsWithActivityCount();
|
||||||
|
@ -56,10 +56,11 @@ export class TagService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return tagsWithOrderCount.map(({ _count, id, name }) => {
|
return tagsWithOrderCount.map(({ _count, id, name, userId }) => {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
userId,
|
||||||
activityCount: _count.orders
|
activityCount: _count.orders
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -70,7 +70,7 @@ export class UserService {
|
|||||||
},
|
},
|
||||||
where: { userId: id }
|
where: { userId: id }
|
||||||
}),
|
}),
|
||||||
this.tagService.getByUser(id)
|
this.tagService.getInUseByUser(id)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let systemMessage: SystemMessage;
|
let systemMessage: SystemMessage;
|
||||||
|
@ -6,15 +6,18 @@ import { Injectable } from '@nestjs/common';
|
|||||||
export class TagService {
|
export class TagService {
|
||||||
public constructor(private readonly prismaService: PrismaService) {}
|
public constructor(private readonly prismaService: PrismaService) {}
|
||||||
|
|
||||||
public async get() {
|
public async getPublic() {
|
||||||
return this.prismaService.tag.findMany({
|
return this.prismaService.tag.findMany({
|
||||||
orderBy: {
|
orderBy: {
|
||||||
name: 'asc'
|
name: 'asc'
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
userId: null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getByUser(userId: string) {
|
public async getInUseByUser(userId: string) {
|
||||||
return this.prismaService.tag.findMany({
|
return this.prismaService.tag.findMany({
|
||||||
orderBy: {
|
orderBy: {
|
||||||
name: 'asc'
|
name: 'asc'
|
||||||
|
@ -31,6 +31,8 @@
|
|||||||
class="position-fixed w-100"
|
class="position-fixed w-100"
|
||||||
[currentRoute]="currentRoute"
|
[currentRoute]="currentRoute"
|
||||||
[deviceType]="deviceType"
|
[deviceType]="deviceType"
|
||||||
|
[hasPermissionToChangeDateRange]="hasPermissionToChangeDateRange"
|
||||||
|
[hasPermissionToChangeFilters]="hasPermissionToChangeFilters"
|
||||||
[hasTabs]="hasTabs"
|
[hasTabs]="hasTabs"
|
||||||
[info]="info"
|
[info]="info"
|
||||||
[pageTitle]="pageTitle"
|
[pageTitle]="pageTitle"
|
||||||
|
@ -47,6 +47,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
public canCreateAccount: boolean;
|
public canCreateAccount: boolean;
|
||||||
public currentRoute: string;
|
public currentRoute: string;
|
||||||
|
public currentSubRoute: string;
|
||||||
public currentYear = new Date().getFullYear();
|
public currentYear = new Date().getFullYear();
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
@ -54,6 +55,8 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
public hasPermissionForStatistics: boolean;
|
public hasPermissionForStatistics: boolean;
|
||||||
public hasPermissionForSubscription: boolean;
|
public hasPermissionForSubscription: boolean;
|
||||||
public hasPermissionToAccessFearAndGreedIndex: boolean;
|
public hasPermissionToAccessFearAndGreedIndex: boolean;
|
||||||
|
public hasPermissionToChangeDateRange: boolean;
|
||||||
|
public hasPermissionToChangeFilters: boolean;
|
||||||
public hasTabs = false;
|
public hasTabs = false;
|
||||||
public info: InfoItem;
|
public info: InfoItem;
|
||||||
public pageTitle: string;
|
public pageTitle: string;
|
||||||
@ -147,6 +150,35 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
const urlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET];
|
const urlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET];
|
||||||
const urlSegments = urlSegmentGroup.segments;
|
const urlSegments = urlSegmentGroup.segments;
|
||||||
this.currentRoute = urlSegments[0].path;
|
this.currentRoute = urlSegments[0].path;
|
||||||
|
this.currentSubRoute = urlSegments[1]?.path;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(this.currentRoute === 'home' && !this.currentSubRoute) ||
|
||||||
|
(this.currentRoute === 'home' &&
|
||||||
|
this.currentSubRoute === 'holdings') ||
|
||||||
|
(this.currentRoute === 'portfolio' && !this.currentSubRoute) ||
|
||||||
|
(this.currentRoute === 'zen' && !this.currentSubRoute) ||
|
||||||
|
(this.currentRoute === 'zen' && this.currentSubRoute === 'holdings')
|
||||||
|
) {
|
||||||
|
this.hasPermissionToChangeDateRange = true;
|
||||||
|
} else {
|
||||||
|
this.hasPermissionToChangeDateRange = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(this.currentRoute === 'home' &&
|
||||||
|
this.currentSubRoute === 'holdings') ||
|
||||||
|
(this.currentRoute === 'portfolio' && !this.currentSubRoute) ||
|
||||||
|
(this.currentRoute === 'portfolio' &&
|
||||||
|
this.currentSubRoute === 'activities') ||
|
||||||
|
(this.currentRoute === 'portfolio' &&
|
||||||
|
this.currentSubRoute === 'allocations') ||
|
||||||
|
(this.currentRoute === 'zen' && this.currentSubRoute === 'holdings')
|
||||||
|
) {
|
||||||
|
this.hasPermissionToChangeFilters = true;
|
||||||
|
} else {
|
||||||
|
this.hasPermissionToChangeFilters = false;
|
||||||
|
}
|
||||||
|
|
||||||
this.hasTabs =
|
this.hasTabs =
|
||||||
(this.currentRoute === this.routerLinkAbout[0].slice(1) ||
|
(this.currentRoute === this.routerLinkAbout[0].slice(1) ||
|
||||||
@ -182,6 +214,8 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
|
@ -139,7 +139,7 @@ export class AdminPlatformComponent implements OnInit, OnDestroy {
|
|||||||
url: null
|
url: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
|
height: this.deviceType === 'mobile' ? '97.5vh' : undefined,
|
||||||
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ export class AdminPlatformComponent implements OnInit, OnDestroy {
|
|||||||
url
|
url
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
|
height: this.deviceType === 'mobile' ? '97.5vh' : undefined,
|
||||||
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,6 +34,20 @@
|
|||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="userId">
|
||||||
|
<th
|
||||||
|
*matHeaderCellDef
|
||||||
|
class="px-1"
|
||||||
|
mat-header-cell
|
||||||
|
mat-sort-header="userId"
|
||||||
|
>
|
||||||
|
<ng-container i18n>User</ng-container>
|
||||||
|
</th>
|
||||||
|
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||||
|
<span class="text-monospace">{{ element.userId }}</span>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="activities">
|
<ng-container matColumnDef="activities">
|
||||||
<th
|
<th
|
||||||
*matHeaderCellDef
|
*matHeaderCellDef
|
||||||
|
@ -36,7 +36,7 @@ export class AdminTagComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
public dataSource: MatTableDataSource<Tag> = new MatTableDataSource();
|
public dataSource: MatTableDataSource<Tag> = new MatTableDataSource();
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public displayedColumns = ['name', 'activities', 'actions'];
|
public displayedColumns = ['name', 'userId', 'activities', 'actions'];
|
||||||
public tags: Tag[];
|
public tags: Tag[];
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
@ -138,7 +138,7 @@ export class AdminTagComponent implements OnInit, OnDestroy {
|
|||||||
name: null
|
name: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
|
height: this.deviceType === 'mobile' ? '97.5vh' : undefined,
|
||||||
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ export class AdminTagComponent implements OnInit, OnDestroy {
|
|||||||
name
|
name
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
|
height: this.deviceType === 'mobile' ? '97.5vh' : undefined,
|
||||||
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@
|
|||||||
matBadge="✓"
|
matBadge="✓"
|
||||||
matBadgeSize="small"
|
matBadgeSize="small"
|
||||||
[mat-menu-trigger-for]="assistantMenu"
|
[mat-menu-trigger-for]="assistantMenu"
|
||||||
[matBadgeHidden]="!hasFilters"
|
[matBadgeHidden]="!hasFilters || !hasPermissionToChangeFilters"
|
||||||
[matMenuTriggerRestoreFocus]="false"
|
[matMenuTriggerRestoreFocus]="false"
|
||||||
(menuOpened)="onOpenAssistant()"
|
(menuOpened)="onOpenAssistant()"
|
||||||
>
|
>
|
||||||
@ -140,6 +140,8 @@
|
|||||||
[hasPermissionToAccessAdminControl]="
|
[hasPermissionToAccessAdminControl]="
|
||||||
hasPermissionToAccessAdminControl
|
hasPermissionToAccessAdminControl
|
||||||
"
|
"
|
||||||
|
[hasPermissionToChangeDateRange]="hasPermissionToChangeDateRange"
|
||||||
|
[hasPermissionToChangeFilters]="hasPermissionToChangeFilters"
|
||||||
[user]="user"
|
[user]="user"
|
||||||
(closed)="closeAssistant()"
|
(closed)="closeAssistant()"
|
||||||
(dateRangeChanged)="onDateRangeChange($event)"
|
(dateRangeChanged)="onDateRangeChange($event)"
|
||||||
|
@ -56,6 +56,8 @@ export class HeaderComponent implements OnChanges {
|
|||||||
|
|
||||||
@Input() currentRoute: string;
|
@Input() currentRoute: string;
|
||||||
@Input() deviceType: string;
|
@Input() deviceType: string;
|
||||||
|
@Input() hasPermissionToChangeDateRange: boolean;
|
||||||
|
@Input() hasPermissionToChangeFilters: boolean;
|
||||||
@Input() hasTabs: boolean;
|
@Input() hasTabs: boolean;
|
||||||
@Input() info: InfoItem;
|
@Input() info: InfoItem;
|
||||||
@Input() pageTitle: string;
|
@Input() pageTitle: string;
|
||||||
@ -197,7 +199,7 @@ export class HeaderComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onLogoClick() {
|
public onLogoClick() {
|
||||||
if (this.currentRoute === 'home' || this.currentRoute === 'zen') {
|
if (['home', 'zen'].includes(this.currentRoute)) {
|
||||||
this.layoutService.getShouldReloadSubject().next();
|
this.layoutService.getShouldReloadSubject().next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,10 +158,10 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
|
|||||||
{ id: this.data.symbol, type: 'SYMBOL' }
|
{ id: this.data.symbol, type: 'SYMBOL' }
|
||||||
];
|
];
|
||||||
|
|
||||||
this.tagsAvailable = tags.map(({ id, name }) => {
|
this.tagsAvailable = tags.map((tag) => {
|
||||||
return {
|
return {
|
||||||
id,
|
...tag,
|
||||||
name: translate(name)
|
name: translate(tag.name)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -320,10 +320,10 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
|
|||||||
this.sectors = {};
|
this.sectors = {};
|
||||||
this.SymbolProfile = SymbolProfile;
|
this.SymbolProfile = SymbolProfile;
|
||||||
|
|
||||||
this.tags = tags.map(({ id, name }) => {
|
this.tags = tags.map((tag) => {
|
||||||
return {
|
return {
|
||||||
id,
|
...tag,
|
||||||
name: translate(name)
|
name: translate(tag.name)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -81,10 +81,10 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
|
|||||||
this.currencies = currencies;
|
this.currencies = currencies;
|
||||||
this.defaultDateFormat = getDateFormatString(this.locale);
|
this.defaultDateFormat = getDateFormatString(this.locale);
|
||||||
this.platforms = platforms;
|
this.platforms = platforms;
|
||||||
this.tagsAvailable = tags.map(({ id, name }) => {
|
this.tagsAvailable = tags.map((tag) => {
|
||||||
return {
|
return {
|
||||||
id,
|
...tag,
|
||||||
name: translate(name)
|
name: translate(tag.name)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@ export const permissions = {
|
|||||||
enableSubscriptionInterstitial: 'enableSubscriptionInterstitial',
|
enableSubscriptionInterstitial: 'enableSubscriptionInterstitial',
|
||||||
enableSystemMessage: 'enableSystemMessage',
|
enableSystemMessage: 'enableSystemMessage',
|
||||||
impersonateAllUsers: 'impersonateAllUsers',
|
impersonateAllUsers: 'impersonateAllUsers',
|
||||||
|
readPlatforms: 'readPlatforms',
|
||||||
|
readTags: 'readTags',
|
||||||
reportDataGlitch: 'reportDataGlitch',
|
reportDataGlitch: 'reportDataGlitch',
|
||||||
toggleReadOnlyMode: 'toggleReadOnlyMode',
|
toggleReadOnlyMode: 'toggleReadOnlyMode',
|
||||||
updateAccount: 'updateAccount',
|
updateAccount: 'updateAccount',
|
||||||
@ -64,6 +66,8 @@ export function getPermissions(aRole: Role): string[] {
|
|||||||
permissions.deletePlatform,
|
permissions.deletePlatform,
|
||||||
permissions.deleteTag,
|
permissions.deleteTag,
|
||||||
permissions.deleteUser,
|
permissions.deleteUser,
|
||||||
|
permissions.readPlatforms,
|
||||||
|
permissions.readTags,
|
||||||
permissions.updateAccount,
|
permissions.updateAccount,
|
||||||
permissions.updateAuthDevice,
|
permissions.updateAuthDevice,
|
||||||
permissions.updateOrder,
|
permissions.updateOrder,
|
||||||
|
@ -110,6 +110,8 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
|
|
||||||
@Input() deviceType: string;
|
@Input() deviceType: string;
|
||||||
@Input() hasPermissionToAccessAdminControl: boolean;
|
@Input() hasPermissionToAccessAdminControl: boolean;
|
||||||
|
@Input() hasPermissionToChangeDateRange: boolean;
|
||||||
|
@Input() hasPermissionToChangeFilters: boolean;
|
||||||
@Input() user: User;
|
@Input() user: User;
|
||||||
|
|
||||||
@Output() closed = new EventEmitter<void>();
|
@Output() closed = new EventEmitter<void>();
|
||||||
@ -254,8 +256,20 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
{ label: $localize`Max`, value: 'max' }
|
{ label: $localize`Max`, value: 'max' }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
this.dateRangeFormControl.disable({ emitEvent: false });
|
||||||
|
|
||||||
|
if (this.hasPermissionToChangeDateRange) {
|
||||||
|
this.dateRangeFormControl.enable({ emitEvent: false });
|
||||||
|
}
|
||||||
|
|
||||||
this.dateRangeFormControl.setValue(this.user?.settings?.dateRange ?? null);
|
this.dateRangeFormControl.setValue(this.user?.settings?.dateRange ?? null);
|
||||||
|
|
||||||
|
this.filterForm.disable({ emitEvent: false });
|
||||||
|
|
||||||
|
if (this.hasPermissionToChangeFilters) {
|
||||||
|
this.filterForm.enable({ emitEvent: false });
|
||||||
|
}
|
||||||
|
|
||||||
this.filterForm.setValue(
|
this.filterForm.setValue(
|
||||||
{
|
{
|
||||||
account: this.user?.settings?.['filters.accounts']?.[0] ?? null,
|
account: this.user?.settings?.['filters.accounts']?.[0] ?? null,
|
||||||
|
@ -150,7 +150,9 @@
|
|||||||
<button
|
<button
|
||||||
i18n
|
i18n
|
||||||
mat-button
|
mat-button
|
||||||
[disabled]="!hasFilter(filterForm.value)"
|
[disabled]="
|
||||||
|
!hasFilter(filterForm.value) || !hasPermissionToChangeFilters
|
||||||
|
"
|
||||||
(click)="onResetFilters()"
|
(click)="onResetFilters()"
|
||||||
>
|
>
|
||||||
Reset Filters
|
Reset Filters
|
||||||
@ -160,7 +162,7 @@
|
|||||||
color="primary"
|
color="primary"
|
||||||
i18n
|
i18n
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[disabled]="!filterForm.dirty"
|
[disabled]="!filterForm.dirty || !hasPermissionToChangeFilters"
|
||||||
(click)="onApplyFilters()"
|
(click)="onApplyFilters()"
|
||||||
>
|
>
|
||||||
Apply Filters
|
Apply Filters
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ghostfolio",
|
"name": "ghostfolio",
|
||||||
"version": "2.110.0",
|
"version": "2.111.0",
|
||||||
"homepage": "https://ghostfol.io",
|
"homepage": "https://ghostfol.io",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"repository": "https://github.com/ghostfolio/ghostfolio",
|
"repository": "https://github.com/ghostfolio/ghostfolio",
|
||||||
@ -37,6 +37,7 @@
|
|||||||
"ng": "nx",
|
"ng": "nx",
|
||||||
"nx": "nx",
|
"nx": "nx",
|
||||||
"postinstall": "prisma generate",
|
"postinstall": "prisma generate",
|
||||||
|
"prisma": "prisma",
|
||||||
"replace-placeholders-in-build": "node ./replace.build.js",
|
"replace-placeholders-in-build": "node ./replace.build.js",
|
||||||
"start": "node dist/apps/api/main",
|
"start": "node dist/apps/api/main",
|
||||||
"start:client": "nx run client:copy-assets && nx run client:serve --configuration=development-en --hmr -o",
|
"start:client": "nx run client:copy-assets && nx run client:serve --configuration=development-en --hmr -o",
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "Tag_name_key";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Tag" ADD COLUMN "userId" TEXT;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Tag_name_userId_key" ON "Tag"("name", "userId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Tag" ADD CONSTRAINT "Tag_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -213,9 +213,12 @@ model Subscription {
|
|||||||
|
|
||||||
model Tag {
|
model Tag {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
name String @unique
|
name String
|
||||||
orders Order[]
|
orders Order[]
|
||||||
|
userId String?
|
||||||
|
User User? @relation(fields: [userId], onDelete: Cascade, references: [id])
|
||||||
|
|
||||||
|
@@unique([name, userId])
|
||||||
@@index([name])
|
@@index([name])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,6 +239,7 @@ model User {
|
|||||||
Order Order[]
|
Order Order[]
|
||||||
Settings Settings?
|
Settings Settings?
|
||||||
Subscription Subscription[]
|
Subscription Subscription[]
|
||||||
|
Tag Tag[]
|
||||||
|
|
||||||
@@index([accessToken])
|
@@index([accessToken])
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user