diff --git a/packages/server/src/modules/Attachments/Attachment.module.ts b/packages/server/src/modules/Attachments/Attachment.module.ts index dc9c6f9d4..08d3fad58 100644 --- a/packages/server/src/modules/Attachments/Attachment.module.ts +++ b/packages/server/src/modules/Attachments/Attachment.module.ts @@ -3,7 +3,7 @@ import * as multerS3 from 'multer-s3'; import { S3_CLIENT, S3Module } from "../S3/S3.module"; import { DeleteAttachment } from "./DeleteAttachment"; import { GetAttachment } from "./GetAttachment"; -import { getAttachmentPresignedUrl } from "./GetAttachmentPresignedUrl"; +import { GetAttachmentPresignedUrl } from "./GetAttachmentPresignedUrl"; import { LinkAttachment } from "./LinkAttachment"; import { UnlinkAttachment } from "./UnlinkAttachment"; import { ValidateAttachments } from "./ValidateAttachments"; @@ -35,12 +35,12 @@ const models = [ @Module({ imports: [S3Module, ...models], - exports: [...models], + exports: [...models, GetAttachmentPresignedUrl], controllers: [AttachmentsController], providers: [ DeleteAttachment, GetAttachment, - getAttachmentPresignedUrl, + GetAttachmentPresignedUrl, LinkAttachment, UnlinkAttachment, ValidateAttachments, diff --git a/packages/server/src/modules/Attachments/AttachmentsApplication.ts b/packages/server/src/modules/Attachments/AttachmentsApplication.ts index baee3775e..bb52ec564 100644 --- a/packages/server/src/modules/Attachments/AttachmentsApplication.ts +++ b/packages/server/src/modules/Attachments/AttachmentsApplication.ts @@ -4,7 +4,7 @@ import { DeleteAttachment } from './DeleteAttachment'; import { GetAttachment } from './GetAttachment'; import { LinkAttachment } from './LinkAttachment'; import { UnlinkAttachment } from './UnlinkAttachment'; -import { getAttachmentPresignedUrl } from './GetAttachmentPresignedUrl'; +import { GetAttachmentPresignedUrl } from './GetAttachmentPresignedUrl'; @Injectable() export class AttachmentsApplication { @@ -14,7 +14,7 @@ export class AttachmentsApplication { private readonly getDocumentService: GetAttachment, private readonly linkDocumentService: LinkAttachment, private readonly unlinkDocumentService: UnlinkAttachment, - private readonly getPresignedUrlService: getAttachmentPresignedUrl, + private readonly getPresignedUrlService: GetAttachmentPresignedUrl, ) {} /** diff --git a/packages/server/src/modules/Attachments/GetAttachmentPresignedUrl.ts b/packages/server/src/modules/Attachments/GetAttachmentPresignedUrl.ts index 3d30bbd24..10536ca77 100644 --- a/packages/server/src/modules/Attachments/GetAttachmentPresignedUrl.ts +++ b/packages/server/src/modules/Attachments/GetAttachmentPresignedUrl.ts @@ -1,13 +1,13 @@ +import { Inject, Injectable } from '@nestjs/common'; import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; -import { Inject, Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { TenantModelProxy } from '../System/models/TenantBaseModel'; import { DocumentModel } from './models/Document.model'; -import { ConfigService } from '@nestjs/config'; import { S3_CLIENT } from '../S3/S3.module'; @Injectable() -export class getAttachmentPresignedUrl { +export class GetAttachmentPresignedUrl { constructor( private readonly configService: ConfigService, diff --git a/packages/server/src/modules/Attachments/utils.ts b/packages/server/src/modules/Attachments/utils.ts deleted file mode 100644 index ee5a34740..000000000 --- a/packages/server/src/modules/Attachments/utils.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as path from 'path'; -// import config from '@/config'; - -export const getUploadedObjectUri = (objectKey: string) => { - return ''; - // return new URL( - // path.join(config.s3.bucket, objectKey), - // config.s3.endpoint - // ).toString(); -}; diff --git a/packages/server/src/modules/Organization/Organization.module.ts b/packages/server/src/modules/Organization/Organization.module.ts index 9da36b41b..0973ed5e5 100644 --- a/packages/server/src/modules/Organization/Organization.module.ts +++ b/packages/server/src/modules/Organization/Organization.module.ts @@ -15,6 +15,8 @@ import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBase import { SyncSystemUserToTenantService } from './commands/SyncSystemUserToTenant.service'; import { SyncSystemUserToTenantSubscriber } from './subscribers/SyncSystemUserToTenant.subscriber'; import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service'; +import { AttachmentsModule } from '../Attachments/Attachment.module'; +import { TransformerModule } from '../Transformer/Transformer.module'; @Module({ providers: [ @@ -36,6 +38,8 @@ import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob adapter: BullMQAdapter, }), TenantDBManagerModule, + AttachmentsModule, + TransformerModule, ], controllers: [OrganizationController], }) diff --git a/packages/server/src/modules/Organization/dtos/GetCurrentOrganizationResponse.dto.ts b/packages/server/src/modules/Organization/dtos/GetCurrentOrganizationResponse.dto.ts index e30b42eba..affff45db 100644 --- a/packages/server/src/modules/Organization/dtos/GetCurrentOrganizationResponse.dto.ts +++ b/packages/server/src/modules/Organization/dtos/GetCurrentOrganizationResponse.dto.ts @@ -79,6 +79,13 @@ export class OrganizationMetadataResponseDto { }) logoKey: string; + @ApiPropertyOptional({ + description: 'Logo URL (presigned or public) for display', + example: 'https://...', + nullable: true, + }) + logoUri: string; + @ApiPropertyOptional({ description: 'Organization address details', example: '123 Main St, New York, NY', diff --git a/packages/server/src/modules/Organization/queries/GetCurrentOrganization.service.ts b/packages/server/src/modules/Organization/queries/GetCurrentOrganization.service.ts index f23909dd2..1cc3fb99f 100644 --- a/packages/server/src/modules/Organization/queries/GetCurrentOrganization.service.ts +++ b/packages/server/src/modules/Organization/queries/GetCurrentOrganization.service.ts @@ -3,14 +3,20 @@ import { throwIfTenantNotExists } from '../Organization/_utils'; import { TenantModel } from '@/modules/System/models/TenantModel'; import { Injectable } from '@nestjs/common'; import { ModelObject } from 'objection'; +import { GetAttachmentPresignedUrl } from '@/modules/Attachments/GetAttachmentPresignedUrl'; +import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; +import { GetCurrentOrganizationTransformer } from './GetCurrentOrganization.transformer'; @Injectable() export class GetCurrentOrganizationService { - constructor(private readonly tenancyContext: TenancyContext) {} + constructor( + private readonly tenancyContext: TenancyContext, + private readonly getPresignedUrlService: GetAttachmentPresignedUrl, + private readonly transformer: TransformerInjectable, + ) {} /** * Retrieve the current organization metadata. - * @param {number} tenantId * @returns {Promise} */ async getCurrentOrganization(): Promise> { @@ -21,6 +27,13 @@ export class GetCurrentOrganizationService { throwIfTenantNotExists(tenant); - return tenant; + const logoUri = tenant.metadata?.logoKey ? + await this.getPresignedUrlService.getPresignedUrl(tenant.metadata.logoKey) : null; + + return await this.transformer.transform( + tenant, + new GetCurrentOrganizationTransformer(), + { logoUri }, + ); } } diff --git a/packages/server/src/modules/Organization/queries/GetCurrentOrganization.transformer.ts b/packages/server/src/modules/Organization/queries/GetCurrentOrganization.transformer.ts new file mode 100644 index 000000000..67771d1cc --- /dev/null +++ b/packages/server/src/modules/Organization/queries/GetCurrentOrganization.transformer.ts @@ -0,0 +1,21 @@ +import { Transformer } from '@/modules/Transformer/Transformer'; +import { GetCurrentOrganizationMetadataTransformer } from './GetCurrentOrganizationMetadata.transformer'; + +export class GetCurrentOrganizationTransformer extends Transformer { + /** + * Transforms the tenant/organization for the current-organization response. + * Delegates metadata transformation to GetCurrentOrganizationMetadataTransformer + * and injects the pre-computed logoUri from options. + */ + transform = (tenant: Record) => { + const metadataTransformer = new GetCurrentOrganizationMetadataTransformer(); + const transformedMetadata = this.item(tenant.metadata, metadataTransformer, { + logoUri: this.options?.logoUri, + }); + + return { + ...tenant, + metadata: transformedMetadata, + }; + }; +} diff --git a/packages/server/src/modules/Organization/queries/GetCurrentOrganizationMetadata.transformer.ts b/packages/server/src/modules/Organization/queries/GetCurrentOrganizationMetadata.transformer.ts new file mode 100644 index 000000000..77b5f2ade --- /dev/null +++ b/packages/server/src/modules/Organization/queries/GetCurrentOrganizationMetadata.transformer.ts @@ -0,0 +1,21 @@ +import { Transformer } from '@/modules/Transformer/Transformer'; + +export class GetCurrentOrganizationMetadataTransformer extends Transformer { + /** + * Include these attributes in the metadata response. + * @returns {string[]} + */ + public includeAttributes = (): string[] => { + return ['logoUri']; + }; + + /** + * Logo URI (presigned or public URL) for display. + * Provided via options from the service after resolving logoKey. + * @param metadata + * @returns {string | null} + */ + public logoUri = (metadata: Record): string | null => { + return this.options?.logoUri ?? null; + }; +} diff --git a/packages/server/src/modules/PdfTemplate/PdfTemplates.module.ts b/packages/server/src/modules/PdfTemplate/PdfTemplates.module.ts index 698daa646..81df06a8a 100644 --- a/packages/server/src/modules/PdfTemplate/PdfTemplates.module.ts +++ b/packages/server/src/modules/PdfTemplate/PdfTemplates.module.ts @@ -13,6 +13,7 @@ import { BrandingTemplateDTOTransformer } from './BrandingTemplateDTOTransformer import { GetOrganizationBrandingAttributesService } from './queries/GetOrganizationBrandingAttributes.service'; import { GetPdfTemplates } from './queries/GetPdfTemplates.service'; import { GetPdfTemplateBrandingState } from './queries/GetPdfTemplateBrandingState.service'; +import { AttachmentsModule } from '../Attachments/Attachment.module'; @Module({ exports: [ @@ -20,7 +21,7 @@ import { GetPdfTemplateBrandingState } from './queries/GetPdfTemplateBrandingSta BrandingTemplateDTOTransformer, GetOrganizationBrandingAttributesService, ], - imports: [TenancyDatabaseModule], + imports: [TenancyDatabaseModule, AttachmentsModule], controllers: [PdfTemplatesController], providers: [ PdfTemplateApplication, diff --git a/packages/server/src/modules/PdfTemplate/models/PdfTemplate.ts b/packages/server/src/modules/PdfTemplate/models/PdfTemplate.ts index 718208934..1478cf8df 100644 --- a/packages/server/src/modules/PdfTemplate/models/PdfTemplate.ts +++ b/packages/server/src/modules/PdfTemplate/models/PdfTemplate.ts @@ -56,16 +56,6 @@ export class PdfTemplateModel extends TenantBaseModel { return ['companyLogoUri']; } - /** - * Retrieves the company logo uri. - * @returns {string} - */ - // get companyLogoUri() { - // return this.attributes?.companyLogoKey - // ? getUploadedObjectUri(this.attributes.companyLogoKey) - // : ''; - // } - /** * Relationship mapping. */ diff --git a/packages/server/src/modules/PdfTemplate/queries/GetOrganizationBrandingAttributes.service.ts b/packages/server/src/modules/PdfTemplate/queries/GetOrganizationBrandingAttributes.service.ts index 36a282a2b..21895e2cd 100644 --- a/packages/server/src/modules/PdfTemplate/queries/GetOrganizationBrandingAttributes.service.ts +++ b/packages/server/src/modules/PdfTemplate/queries/GetOrganizationBrandingAttributes.service.ts @@ -1,10 +1,14 @@ import { Injectable } from '@nestjs/common'; import { CommonOrganizationBrandingAttributes } from '../types'; import { TenancyContext } from '../../Tenancy/TenancyContext.service'; +import { GetAttachmentPresignedUrl } from '@/modules/Attachments/GetAttachmentPresignedUrl'; @Injectable() export class GetOrganizationBrandingAttributesService { - constructor(private readonly tenancyContext: TenancyContext) {} + constructor( + private readonly tenancyContext: TenancyContext, + private readonly getPresignedUrlService: GetAttachmentPresignedUrl, + ) {} /** * Retrieves the given organization branding attributes initial state. @@ -17,13 +21,22 @@ export class GetOrganizationBrandingAttributesService { const companyName = tenantMetadata?.name; const primaryColor = tenantMetadata?.primaryColor; const companyLogoKey = tenantMetadata?.logoKey; - const companyLogoUri = tenantMetadata?.logoUri; const companyAddress = tenantMetadata?.addressTextFormatted; + let companyLogoUri: string | null = null; + if (companyLogoKey) { + try { + companyLogoUri = + await this.getPresignedUrlService.getPresignedUrl(companyLogoKey); + } catch { + companyLogoUri = null; + } + } + return { companyName, companyAddress, - companyLogoUri, + companyLogoUri: companyLogoUri ?? undefined, companyLogoKey, primaryColor, }; diff --git a/packages/server/src/modules/PdfTemplate/queries/GetPdfTemplate.service.ts b/packages/server/src/modules/PdfTemplate/queries/GetPdfTemplate.service.ts index 990a04d52..b0042ea59 100644 --- a/packages/server/src/modules/PdfTemplate/queries/GetPdfTemplate.service.ts +++ b/packages/server/src/modules/PdfTemplate/queries/GetPdfTemplate.service.ts @@ -4,6 +4,7 @@ import { GetPdfTemplateTransformer } from './GetPdfTemplate.transformer'; import { PdfTemplateModel } from '../models/PdfTemplate'; import { TransformerInjectable } from '../../Transformer/TransformerInjectable.service'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { GetAttachmentPresignedUrl } from '@/modules/Attachments/GetAttachmentPresignedUrl'; @Injectable() export class GetPdfTemplateService { @@ -13,6 +14,7 @@ export class GetPdfTemplateService { typeof PdfTemplateModel >, private readonly transformer: TransformerInjectable, + private readonly getPresignedUrlService: GetAttachmentPresignedUrl, ) {} /** @@ -29,8 +31,19 @@ export class GetPdfTemplateService { .findById(templateId) .throwIfNotFound(); + const companyLogoKey = template.attributes?.companyLogoKey; + let companyLogoUri: string | null = null; + + if (companyLogoKey) { + try { + companyLogoUri = + await this.getPresignedUrlService.getPresignedUrl(companyLogoKey); + } catch { + companyLogoUri = null; + } + } return this.transformer.transform( - template, + { ...template, companyLogoUri }, new GetPdfTemplateTransformer(), ); } diff --git a/packages/server/src/modules/PdfTemplate/queries/GetPdfTemplate.transformer.ts b/packages/server/src/modules/PdfTemplate/queries/GetPdfTemplate.transformer.ts index 986c0d496..6d3ef895f 100644 --- a/packages/server/src/modules/PdfTemplate/queries/GetPdfTemplate.transformer.ts +++ b/packages/server/src/modules/PdfTemplate/queries/GetPdfTemplate.transformer.ts @@ -7,7 +7,7 @@ export class GetPdfTemplateTransformer extends Transformer { * @returns {string[]} */ public includeAttributes = (): string[] => { - return ['createdAtFormatted', 'resourceFormatted', 'attributes']; + return ['createdAtFormatted', 'resourceFormatted', 'attributes', 'companyLogoUri']; }; /** @@ -28,6 +28,15 @@ export class GetPdfTemplateTransformer extends Transformer { // return getTransactionTypeLabel(template.resource); }; + /** + * Retrieves the company logo URI. + * @param {Object} template + * @returns {string | null} + */ + protected companyLogoUri = (template) => { + return template.companyLogoUri; + }; + /** * Retrieves transformed brand attributes. * @param {} template diff --git a/packages/server/src/modules/System/models/TenantMetadataModel.ts b/packages/server/src/modules/System/models/TenantMetadataModel.ts index b16e6b61f..2886113c6 100644 --- a/packages/server/src/modules/System/models/TenantMetadataModel.ts +++ b/packages/server/src/modules/System/models/TenantMetadataModel.ts @@ -4,7 +4,6 @@ import { organizationAddressTextFormat, } from '@/utils/address-text-format'; import { findByIsoCountryCode } from '@bigcapital/utils'; -// import { getUploadedObjectUri } from '../../services/Attachments/utils'; export class TenantMetadata extends BaseModel { public baseCurrency!: string; @@ -65,17 +64,9 @@ export class TenantMetadata extends BaseModel { } /** - * Organization logo url. - * @returns {string | null} + * Retrieves the organization address formatted text. + * @returns {string} */ - // public get logoUri() { - // return this.logoKey ? getUploadedObjectUri(this.logoKey) : null; - // } - - // /** - // * Retrieves the organization address formatted text. - // * @returns {string} - // */ public get addressTextFormatted() { const addressCountry = findByIsoCountryCode(this.location);