@accounter/server
Version:
Accounter GraphQL server
141 lines (124 loc) • 4.48 kB
text/typescript
import { GraphQLError } from 'graphql';
import type { BusinessEmailConfig } from '../../../__generated__/types.js';
import { hashStringToInt } from '../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js';
import { ChargesProvider } from '../../charges/providers/charges.provider.js';
import { getDocumentFromFile } from '../../documents/helpers/upload.helper.js';
import { DocumentsProvider } from '../../documents/providers/documents.provider.js';
import { IInsertDocumentsParams } from '../../documents/types.js';
import { suggestionDataSchema } from '../../financial-entities/helpers/business-suggestion-data-schema.helper.js';
import { BusinessesProvider } from '../../financial-entities/providers/businesses.provider.js';
import { EmailListenerConfig } from '../../financial-entities/types.js';
import type { EmailIngestionModule } from '../types.js';
const businessEmailConfig: EmailIngestionModule.QueryResolvers['businessEmailConfig'] = async (
_,
{ email },
{ injector },
) => {
const business = await injector.get(BusinessesProvider).getBusinessByEmail(email);
if (!business) {
return null;
}
let listenerConfig: EmailListenerConfig = {};
if (business?.suggestion_data) {
const { data, success, error } = suggestionDataSchema.safeParse(business.suggestion_data);
if (success) {
if (data.emailListener) {
listenerConfig = data.emailListener;
}
} else {
console.error(
`Invalid suggestion_data schema for business "${business.name}" [${business.id}]: ${JSON.stringify(error.issues)}`,
);
}
}
const listenerConfigRes: Omit<BusinessEmailConfig, 'businessId'> = {
internalEmailLinks: listenerConfig.internalEmailLinks,
emailBody: listenerConfig.emailBody,
attachments: listenerConfig.attachments,
};
return {
...listenerConfigRes,
businessId: business.id,
};
};
const insertEmailDocuments: EmailIngestionModule.MutationResolvers['insertEmailDocuments'] = async (
_,
{ documents, userDescription, messageId, businessId },
{ injector },
) => {
if (documents.length === 0) {
return true;
}
try {
const { ownerId } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
const newDocuments: Array<IInsertDocumentsParams['documents'][number]> = [];
await Promise.all(
documents.map(async doc => {
const hash = hashStringToInt(await doc.text());
const existingDocument = await injector.get(DocumentsProvider).getDocumentByHash.load(hash);
if (existingDocument) {
console.log(
`Document ${'name' in doc ? doc.name : 'unknown'} already exists in the database with id=${existingDocument.id}, skipping upload.`,
);
return;
}
const newDocument = await getDocumentFromFile(
injector,
doc,
null,
false,
businessId ?? undefined,
hash,
);
newDocuments.push({ ...newDocument, remarks: userDescription });
}),
);
if (newDocuments.length > 0) {
await injector.get(TenantAwareDBClient).transaction(async client => {
const charge = await injector
.get(ChargesProvider)
.generateCharge(
{
ownerId,
userDescription,
},
client,
)
.catch(e => {
throw new Error(`Error generating charge for email id=${messageId}: ${e}`);
});
if (!charge?.id) {
throw new Error(`Charge creation failed for email id=${messageId}`);
}
const documentsWithChargeId = newDocuments.map(doc => ({
...doc,
chargeId: charge.id,
}));
return await injector
.get(DocumentsProvider)
.insertDocuments({ documents: documentsWithChargeId }, client);
});
}
return true;
} catch (error) {
console.error('Failed to insert email documents:', error);
if (error instanceof GraphQLError) {
throw error;
}
throw new GraphQLError(`Failed to insert email documents`, {
extensions: {
code: 'INTERNAL_SERVER_ERROR',
},
});
}
};
export const emailIngestionResolvers = {
Query: {
businessEmailConfig,
},
Mutation: {
insertEmailDocuments,
},
};