Merge pull request #1004 from bigcapitalhq/fix/pdf-template-logo-display
fix(server): PDF template logo not showing on reopen
This commit is contained in:
@@ -3,7 +3,7 @@ import * as multerS3 from 'multer-s3';
|
|||||||
import { S3_CLIENT, S3Module } from "../S3/S3.module";
|
import { S3_CLIENT, S3Module } from "../S3/S3.module";
|
||||||
import { DeleteAttachment } from "./DeleteAttachment";
|
import { DeleteAttachment } from "./DeleteAttachment";
|
||||||
import { GetAttachment } from "./GetAttachment";
|
import { GetAttachment } from "./GetAttachment";
|
||||||
import { getAttachmentPresignedUrl } from "./GetAttachmentPresignedUrl";
|
import { GetAttachmentPresignedUrl } from "./GetAttachmentPresignedUrl";
|
||||||
import { LinkAttachment } from "./LinkAttachment";
|
import { LinkAttachment } from "./LinkAttachment";
|
||||||
import { UnlinkAttachment } from "./UnlinkAttachment";
|
import { UnlinkAttachment } from "./UnlinkAttachment";
|
||||||
import { ValidateAttachments } from "./ValidateAttachments";
|
import { ValidateAttachments } from "./ValidateAttachments";
|
||||||
@@ -35,12 +35,12 @@ const models = [
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [S3Module, ...models],
|
imports: [S3Module, ...models],
|
||||||
exports: [...models],
|
exports: [...models, GetAttachmentPresignedUrl],
|
||||||
controllers: [AttachmentsController],
|
controllers: [AttachmentsController],
|
||||||
providers: [
|
providers: [
|
||||||
DeleteAttachment,
|
DeleteAttachment,
|
||||||
GetAttachment,
|
GetAttachment,
|
||||||
getAttachmentPresignedUrl,
|
GetAttachmentPresignedUrl,
|
||||||
LinkAttachment,
|
LinkAttachment,
|
||||||
UnlinkAttachment,
|
UnlinkAttachment,
|
||||||
ValidateAttachments,
|
ValidateAttachments,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { DeleteAttachment } from './DeleteAttachment';
|
|||||||
import { GetAttachment } from './GetAttachment';
|
import { GetAttachment } from './GetAttachment';
|
||||||
import { LinkAttachment } from './LinkAttachment';
|
import { LinkAttachment } from './LinkAttachment';
|
||||||
import { UnlinkAttachment } from './UnlinkAttachment';
|
import { UnlinkAttachment } from './UnlinkAttachment';
|
||||||
import { getAttachmentPresignedUrl } from './GetAttachmentPresignedUrl';
|
import { GetAttachmentPresignedUrl } from './GetAttachmentPresignedUrl';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AttachmentsApplication {
|
export class AttachmentsApplication {
|
||||||
@@ -14,7 +14,7 @@ export class AttachmentsApplication {
|
|||||||
private readonly getDocumentService: GetAttachment,
|
private readonly getDocumentService: GetAttachment,
|
||||||
private readonly linkDocumentService: LinkAttachment,
|
private readonly linkDocumentService: LinkAttachment,
|
||||||
private readonly unlinkDocumentService: UnlinkAttachment,
|
private readonly unlinkDocumentService: UnlinkAttachment,
|
||||||
private readonly getPresignedUrlService: getAttachmentPresignedUrl,
|
private readonly getPresignedUrlService: GetAttachmentPresignedUrl,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
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 { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||||
import { DocumentModel } from './models/Document.model';
|
import { DocumentModel } from './models/Document.model';
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { S3_CLIENT } from '../S3/S3.module';
|
import { S3_CLIENT } from '../S3/S3.module';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class getAttachmentPresignedUrl {
|
export class GetAttachmentPresignedUrl {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
|
||||||
};
|
|
||||||
@@ -15,6 +15,8 @@ import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBase
|
|||||||
import { SyncSystemUserToTenantService } from './commands/SyncSystemUserToTenant.service';
|
import { SyncSystemUserToTenantService } from './commands/SyncSystemUserToTenant.service';
|
||||||
import { SyncSystemUserToTenantSubscriber } from './subscribers/SyncSystemUserToTenant.subscriber';
|
import { SyncSystemUserToTenantSubscriber } from './subscribers/SyncSystemUserToTenant.subscriber';
|
||||||
import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service';
|
import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service';
|
||||||
|
import { AttachmentsModule } from '../Attachments/Attachment.module';
|
||||||
|
import { TransformerModule } from '../Transformer/Transformer.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [
|
providers: [
|
||||||
@@ -36,6 +38,8 @@ import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob
|
|||||||
adapter: BullMQAdapter,
|
adapter: BullMQAdapter,
|
||||||
}),
|
}),
|
||||||
TenantDBManagerModule,
|
TenantDBManagerModule,
|
||||||
|
AttachmentsModule,
|
||||||
|
TransformerModule,
|
||||||
],
|
],
|
||||||
controllers: [OrganizationController],
|
controllers: [OrganizationController],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -79,6 +79,13 @@ export class OrganizationMetadataResponseDto {
|
|||||||
})
|
})
|
||||||
logoKey: string;
|
logoKey: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Logo URL (presigned or public) for display',
|
||||||
|
example: 'https://...',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
logoUri: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description: 'Organization address details',
|
description: 'Organization address details',
|
||||||
example: '123 Main St, New York, NY',
|
example: '123 Main St, New York, NY',
|
||||||
|
|||||||
@@ -3,14 +3,20 @@ import { throwIfTenantNotExists } from '../Organization/_utils';
|
|||||||
import { TenantModel } from '@/modules/System/models/TenantModel';
|
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ModelObject } from 'objection';
|
import { ModelObject } from 'objection';
|
||||||
|
import { GetAttachmentPresignedUrl } from '@/modules/Attachments/GetAttachmentPresignedUrl';
|
||||||
|
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||||
|
import { GetCurrentOrganizationTransformer } from './GetCurrentOrganization.transformer';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetCurrentOrganizationService {
|
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.
|
* Retrieve the current organization metadata.
|
||||||
* @param {number} tenantId
|
|
||||||
* @returns {Promise<ITenant[]>}
|
* @returns {Promise<ITenant[]>}
|
||||||
*/
|
*/
|
||||||
async getCurrentOrganization(): Promise<ModelObject<TenantModel>> {
|
async getCurrentOrganization(): Promise<ModelObject<TenantModel>> {
|
||||||
@@ -21,6 +27,13 @@ export class GetCurrentOrganizationService {
|
|||||||
|
|
||||||
throwIfTenantNotExists(tenant);
|
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 },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
@@ -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<string, any>) => {
|
||||||
|
const metadataTransformer = new GetCurrentOrganizationMetadataTransformer();
|
||||||
|
const transformedMetadata = this.item(tenant.metadata, metadataTransformer, {
|
||||||
|
logoUri: this.options?.logoUri,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...tenant,
|
||||||
|
metadata: transformedMetadata,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
+21
@@ -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, any>): string | null => {
|
||||||
|
return this.options?.logoUri ?? null;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import { BrandingTemplateDTOTransformer } from './BrandingTemplateDTOTransformer
|
|||||||
import { GetOrganizationBrandingAttributesService } from './queries/GetOrganizationBrandingAttributes.service';
|
import { GetOrganizationBrandingAttributesService } from './queries/GetOrganizationBrandingAttributes.service';
|
||||||
import { GetPdfTemplates } from './queries/GetPdfTemplates.service';
|
import { GetPdfTemplates } from './queries/GetPdfTemplates.service';
|
||||||
import { GetPdfTemplateBrandingState } from './queries/GetPdfTemplateBrandingState.service';
|
import { GetPdfTemplateBrandingState } from './queries/GetPdfTemplateBrandingState.service';
|
||||||
|
import { AttachmentsModule } from '../Attachments/Attachment.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
exports: [
|
exports: [
|
||||||
@@ -20,7 +21,7 @@ import { GetPdfTemplateBrandingState } from './queries/GetPdfTemplateBrandingSta
|
|||||||
BrandingTemplateDTOTransformer,
|
BrandingTemplateDTOTransformer,
|
||||||
GetOrganizationBrandingAttributesService,
|
GetOrganizationBrandingAttributesService,
|
||||||
],
|
],
|
||||||
imports: [TenancyDatabaseModule],
|
imports: [TenancyDatabaseModule, AttachmentsModule],
|
||||||
controllers: [PdfTemplatesController],
|
controllers: [PdfTemplatesController],
|
||||||
providers: [
|
providers: [
|
||||||
PdfTemplateApplication,
|
PdfTemplateApplication,
|
||||||
|
|||||||
@@ -56,16 +56,6 @@ export class PdfTemplateModel extends TenantBaseModel {
|
|||||||
return ['companyLogoUri'];
|
return ['companyLogoUri'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the company logo uri.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
// get companyLogoUri() {
|
|
||||||
// return this.attributes?.companyLogoKey
|
|
||||||
// ? getUploadedObjectUri(this.attributes.companyLogoKey)
|
|
||||||
// : '';
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+16
-3
@@ -1,10 +1,14 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { CommonOrganizationBrandingAttributes } from '../types';
|
import { CommonOrganizationBrandingAttributes } from '../types';
|
||||||
import { TenancyContext } from '../../Tenancy/TenancyContext.service';
|
import { TenancyContext } from '../../Tenancy/TenancyContext.service';
|
||||||
|
import { GetAttachmentPresignedUrl } from '@/modules/Attachments/GetAttachmentPresignedUrl';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetOrganizationBrandingAttributesService {
|
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.
|
* Retrieves the given organization branding attributes initial state.
|
||||||
@@ -17,13 +21,22 @@ export class GetOrganizationBrandingAttributesService {
|
|||||||
const companyName = tenantMetadata?.name;
|
const companyName = tenantMetadata?.name;
|
||||||
const primaryColor = tenantMetadata?.primaryColor;
|
const primaryColor = tenantMetadata?.primaryColor;
|
||||||
const companyLogoKey = tenantMetadata?.logoKey;
|
const companyLogoKey = tenantMetadata?.logoKey;
|
||||||
const companyLogoUri = tenantMetadata?.logoUri;
|
|
||||||
const companyAddress = tenantMetadata?.addressTextFormatted;
|
const companyAddress = tenantMetadata?.addressTextFormatted;
|
||||||
|
|
||||||
|
let companyLogoUri: string | null = null;
|
||||||
|
if (companyLogoKey) {
|
||||||
|
try {
|
||||||
|
companyLogoUri =
|
||||||
|
await this.getPresignedUrlService.getPresignedUrl(companyLogoKey);
|
||||||
|
} catch {
|
||||||
|
companyLogoUri = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
companyName,
|
companyName,
|
||||||
companyAddress,
|
companyAddress,
|
||||||
companyLogoUri,
|
companyLogoUri: companyLogoUri ?? undefined,
|
||||||
companyLogoKey,
|
companyLogoKey,
|
||||||
primaryColor,
|
primaryColor,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { GetPdfTemplateTransformer } from './GetPdfTemplate.transformer';
|
|||||||
import { PdfTemplateModel } from '../models/PdfTemplate';
|
import { PdfTemplateModel } from '../models/PdfTemplate';
|
||||||
import { TransformerInjectable } from '../../Transformer/TransformerInjectable.service';
|
import { TransformerInjectable } from '../../Transformer/TransformerInjectable.service';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { GetAttachmentPresignedUrl } from '@/modules/Attachments/GetAttachmentPresignedUrl';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetPdfTemplateService {
|
export class GetPdfTemplateService {
|
||||||
@@ -13,6 +14,7 @@ export class GetPdfTemplateService {
|
|||||||
typeof PdfTemplateModel
|
typeof PdfTemplateModel
|
||||||
>,
|
>,
|
||||||
private readonly transformer: TransformerInjectable,
|
private readonly transformer: TransformerInjectable,
|
||||||
|
private readonly getPresignedUrlService: GetAttachmentPresignedUrl,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,8 +31,19 @@ export class GetPdfTemplateService {
|
|||||||
.findById(templateId)
|
.findById(templateId)
|
||||||
.throwIfNotFound();
|
.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(
|
return this.transformer.transform(
|
||||||
template,
|
{ ...template, companyLogoUri },
|
||||||
new GetPdfTemplateTransformer(),
|
new GetPdfTemplateTransformer(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export class GetPdfTemplateTransformer extends Transformer {
|
|||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
public includeAttributes = (): 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);
|
// 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.
|
* Retrieves transformed brand attributes.
|
||||||
* @param {} template
|
* @param {} template
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
organizationAddressTextFormat,
|
organizationAddressTextFormat,
|
||||||
} from '@/utils/address-text-format';
|
} from '@/utils/address-text-format';
|
||||||
import { findByIsoCountryCode } from '@bigcapital/utils';
|
import { findByIsoCountryCode } from '@bigcapital/utils';
|
||||||
// import { getUploadedObjectUri } from '../../services/Attachments/utils';
|
|
||||||
|
|
||||||
export class TenantMetadata extends BaseModel {
|
export class TenantMetadata extends BaseModel {
|
||||||
public baseCurrency!: string;
|
public baseCurrency!: string;
|
||||||
@@ -65,17 +64,9 @@ export class TenantMetadata extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Organization logo url.
|
* Retrieves the organization address formatted text.
|
||||||
* @returns {string | null}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
// public get logoUri() {
|
|
||||||
// return this.logoKey ? getUploadedObjectUri(this.logoKey) : null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Retrieves the organization address formatted text.
|
|
||||||
// * @returns {string}
|
|
||||||
// */
|
|
||||||
public get addressTextFormatted() {
|
public get addressTextFormatted() {
|
||||||
const addressCountry = findByIsoCountryCode(this.location);
|
const addressCountry = findByIsoCountryCode(this.location);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user