From 2f2ebd1f3983e077bb5e3317fd07133704ff3f49 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 07:41:00 +0000 Subject: [PATCH] fix: address review comments - upload path, validation, env credentials, entrypoint, i18n Co-authored-by: RobertgPatch <5817970+RobertgPatch@users.noreply.github.com> --- .env.dev | 10 ++++----- .../family-office/family-office.controller.ts | 21 ++++++++++++------- apps/api/src/app/upload/upload.controller.ts | 9 ++++++++ apps/api/src/app/upload/upload.module.ts | 6 +++--- apps/api/src/app/upload/upload.service.ts | 2 +- .../record-valuation-dialog.component.ts | 7 ++++--- .../partnerships/partnerships-page.routes.ts | 2 +- docker/entrypoint.sh | 6 ++++-- 8 files changed, 41 insertions(+), 22 deletions(-) diff --git a/.env.dev b/.env.dev index ce282befa..d47448df8 100644 --- a/.env.dev +++ b/.env.dev @@ -3,18 +3,18 @@ COMPOSE_PROJECT_NAME=ghostfolio-development # CACHE REDIS_HOST=localhost REDIS_PORT=6379 -REDIS_PASSWORD=b126659952eb48d08cc9fc9d966903ff +REDIS_PASSWORD= # POSTGRES POSTGRES_DB=ghostfolio-db POSTGRES_USER=user -POSTGRES_PASSWORD=302653e0a3b94dd2942bc283f269fc62 +POSTGRES_PASSWORD= POSTGRES_PORT=5434 # VARIOUS -ACCESS_TOKEN_SALT=34f2558b9a934517a978d69ab501fcfd -DATABASE_URL=postgresql://user:302653e0a3b94dd2942bc283f269fc62@localhost:5434/ghostfolio-db?connect_timeout=300&sslmode=prefer -JWT_SECRET_KEY=12005231ada14bd1919967473625bec4 +ACCESS_TOKEN_SALT= +DATABASE_URL=postgresql://user:@localhost:5434/ghostfolio-db?connect_timeout=300&sslmode=prefer +JWT_SECRET_KEY= # DEVELOPMENT HOST=0.0.0.0 diff --git a/apps/api/src/app/family-office/family-office.controller.ts b/apps/api/src/app/family-office/family-office.controller.ts index 7f5dbd9a7..d39b6881a 100644 --- a/apps/api/src/app/family-office/family-office.controller.ts +++ b/apps/api/src/app/family-office/family-office.controller.ts @@ -3,7 +3,14 @@ import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard' import { permissions } from '@ghostfolio/common/permissions'; import type { RequestWithUser } from '@ghostfolio/common/types'; -import { Controller, Get, Inject, Query, UseGuards } from '@nestjs/common'; +import { + BadRequestException, + Controller, + Get, + Inject, + Query, + UseGuards +} from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; @@ -92,17 +99,17 @@ export class FamilyOfficeController { @Query('year') year?: string ) { if (!period || !year) { - return { - error: 'period and year are required query parameters' - }; + throw new BadRequestException( + 'period and year are required query parameters' + ); } const validPeriods = ['MONTHLY', 'QUARTERLY', 'YEARLY']; if (!validPeriods.includes(period)) { - return { - error: `period must be one of: ${validPeriods.join(', ')}` - }; + throw new BadRequestException( + `period must be one of: ${validPeriods.join(', ')}` + ); } const yearNum = parseInt(year, 10); diff --git a/apps/api/src/app/upload/upload.controller.ts b/apps/api/src/app/upload/upload.controller.ts index b5ad78b3a..30e272897 100644 --- a/apps/api/src/app/upload/upload.controller.ts +++ b/apps/api/src/app/upload/upload.controller.ts @@ -4,6 +4,7 @@ import { permissions } from '@ghostfolio/common/permissions'; import type { RequestWithUser } from '@ghostfolio/common/types'; import { + BadRequestException, Controller, Get, Inject, @@ -36,6 +37,14 @@ export class UploadController { public async uploadDocument(@UploadedFile() file: any) { const body = this.request.body as any; + const validDocumentTypes = Object.values(DocumentType); + + if (!body.type || !validDocumentTypes.includes(body.type as DocumentType)) { + throw new BadRequestException( + `type must be one of: ${validDocumentTypes.join(', ')}` + ); + } + return this.uploadService.createDocument({ file, entityId: body.entityId, diff --git a/apps/api/src/app/upload/upload.module.ts b/apps/api/src/app/upload/upload.module.ts index 9687b388c..6c3cfe57a 100644 --- a/apps/api/src/app/upload/upload.module.ts +++ b/apps/api/src/app/upload/upload.module.ts @@ -4,7 +4,7 @@ import { Module } from '@nestjs/common'; import { MulterModule } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { existsSync, mkdirSync } from 'node:fs'; -import { join } from 'node:path'; +import { extname, join } from 'node:path'; import { v4 as uuidv4 } from 'uuid'; import { UploadController } from './upload.controller'; @@ -38,8 +38,8 @@ if (!existsSync(uploadDir)) { cb(null, subDir); }, filename: (_req, file, cb) => { - const ext = file.originalname.split('.').pop(); - cb(null, `${uuidv4()}.${ext}`); + const ext = extname(file.originalname); + cb(null, ext ? `${uuidv4()}${ext}` : uuidv4()); } }) }), diff --git a/apps/api/src/app/upload/upload.service.ts b/apps/api/src/app/upload/upload.service.ts index 8ff921d35..c95b0aca9 100644 --- a/apps/api/src/app/upload/upload.service.ts +++ b/apps/api/src/app/upload/upload.service.ts @@ -51,7 +51,7 @@ export class UploadService { await mkdir(subDir, { recursive: true }); } - const relativePath = `/${yearDir}/${monthDir}/${file.filename}`; + const relativePath = `${yearDir}/${monthDir}/${file.filename}`; return this.prismaService.document.create({ data: { diff --git a/apps/client/src/app/pages/partnership-detail/record-valuation-dialog/record-valuation-dialog.component.ts b/apps/client/src/app/pages/partnership-detail/record-valuation-dialog/record-valuation-dialog.component.ts index 606888098..b0da939cb 100644 --- a/apps/client/src/app/pages/partnership-detail/record-valuation-dialog/record-valuation-dialog.component.ts +++ b/apps/client/src/app/pages/partnership-detail/record-valuation-dialog/record-valuation-dialog.component.ts @@ -62,10 +62,11 @@ import { MatSelectModule } from '@angular/material/select'; Source - Statement + Statement Manual Entry - Auditor + Appraisal Fund Admin + Market @@ -108,7 +109,7 @@ export class GfRecordValuationDialogComponent implements OnInit { Validators.required ]), nav: new FormControl(null, [Validators.required, Validators.min(0)]), - source: new FormControl('STATEMENT', [Validators.required]), + source: new FormControl('NAV_STATEMENT', [Validators.required]), notes: new FormControl('') }); } diff --git a/apps/client/src/app/pages/partnerships/partnerships-page.routes.ts b/apps/client/src/app/pages/partnerships/partnerships-page.routes.ts index 113aaa90e..06871f392 100644 --- a/apps/client/src/app/pages/partnerships/partnerships-page.routes.ts +++ b/apps/client/src/app/pages/partnerships/partnerships-page.routes.ts @@ -9,6 +9,6 @@ export const routes: Routes = [ canActivate: [AuthGuard], component: GfPartnershipsPageComponent, path: '', - title: 'Partnerships' + title: $localize`Partnerships` } ]; diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 450b28ead..5332704a7 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -5,8 +5,10 @@ set -e echo "==> Running database migrations" npx prisma migrate deploy -echo "==> Ensuring schema is fully synced" -npx prisma db push --skip-generate --accept-data-loss 2>/dev/null || echo " (db push skipped — schema already in sync)" +if [ "${DB_PUSH_ON_STARTUP}" = "true" ]; then + echo "==> Syncing schema (DB_PUSH_ON_STARTUP=true)" + npx prisma db push --skip-generate --accept-data-loss || echo " (db push failed -- check schema and database state)" +fi echo "==> Seeding the database (if applicable)" npx prisma db seed || echo " (seed skipped or already applied)"