mirror of
https://github.com/papra-hq/papra.git
synced 2026-01-06 08:59:37 -06:00
feat(intake-emails): when deleting intake email in organization, delete in OwlRelay too (#192)
* feat(intake-emails): delete email in owlrelay too * Update apps/papra-server/src/modules/intake-emails/drivers/random-username/random-username.intake-email-driver.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
979df5dad8
commit
79eafdb3ee
@@ -37,7 +37,7 @@
|
||||
"@crowlog/logger": "^1.1.0",
|
||||
"@hono/node-server": "^1.13.7",
|
||||
"@libsql/client": "^0.14.0",
|
||||
"@owlrelay/api-sdk": "^0.0.1",
|
||||
"@owlrelay/api-sdk": "^0.0.2",
|
||||
"@owlrelay/webhook": "^0.0.3",
|
||||
"@papra/lecture": "^0.0.4",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Config } from '../../config/config.types';
|
||||
export type IntakeEmailsServices = {
|
||||
name: string;
|
||||
generateEmailAddress: () => Promise<{ emailAddress: string }>;
|
||||
deleteEmailAddress: ({ emailAddress }: { emailAddress: string }) => Promise<void>;
|
||||
};
|
||||
|
||||
export type IntakeEmailDriverFactory = (args: { config: Config }) => IntakeEmailsServices;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { buildUrl } from '@corentinth/chisels';
|
||||
import { buildUrl, safely } from '@corentinth/chisels';
|
||||
import { generateId as generateHumanReadableId } from '@corentinth/friendly-ids';
|
||||
import { createClient } from '@owlrelay/api-sdk';
|
||||
import { createLogger } from '../../../shared/logger/logger';
|
||||
import { INTAKE_EMAILS_INGEST_ROUTE } from '../../intake-emails.constants';
|
||||
import { buildEmailAddress } from '../../intake-emails.models';
|
||||
import { defineIntakeEmailDriver } from '../intake-emails.drivers.models';
|
||||
|
||||
export const OWLRELAY_INTAKE_EMAIL_DRIVER_NAME = 'owlrelay';
|
||||
|
||||
const logger = createLogger({ namespace: 'intake-emails.drivers.owlrelay' });
|
||||
|
||||
export const owlrelayIntakeEmailDriverFactory = defineIntakeEmailDriver(({ config }) => {
|
||||
const { baseUrl } = config.server;
|
||||
const { webhookSecret } = config.intakeEmails;
|
||||
@@ -21,7 +24,7 @@ export const owlrelayIntakeEmailDriverFactory = defineIntakeEmailDriver(({ confi
|
||||
return {
|
||||
name: OWLRELAY_INTAKE_EMAIL_DRIVER_NAME,
|
||||
generateEmailAddress: async () => {
|
||||
const { domain, username } = await client.createEmail({
|
||||
const { domain, username, id: owlrelayEmailId } = await client.createEmail({
|
||||
username: generateHumanReadableId(),
|
||||
webhookUrl,
|
||||
webhookSecret,
|
||||
@@ -29,9 +32,21 @@ export const owlrelayIntakeEmailDriverFactory = defineIntakeEmailDriver(({ confi
|
||||
|
||||
const emailAddress = buildEmailAddress({ username, domain });
|
||||
|
||||
logger.info({ emailAddress, owlrelayEmailId }, 'Created email address in OwlRelay');
|
||||
|
||||
return {
|
||||
emailAddress,
|
||||
};
|
||||
},
|
||||
deleteEmailAddress: async ({ emailAddress }) => {
|
||||
const [, error] = await safely(client.deleteEmail({ emailAddress }));
|
||||
|
||||
if (error) {
|
||||
logger.error({ error }, 'Failed to delete email address in OwlRelay');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info({ emailAddress }, 'Deleted email address in OwlRelay');
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -15,5 +15,7 @@ export const randomUsernameIntakeEmailDriverFactory = defineIntakeEmailDriver(({
|
||||
emailAddress: `${randomUsername}@${domain}`,
|
||||
};
|
||||
},
|
||||
// Deletion functionality is not required for this driver
|
||||
deleteEmailAddress: async () => {},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -5,3 +5,9 @@ export const createIntakeEmailLimitReachedError = createErrorFactory({
|
||||
code: 'intake_email.limit_reached',
|
||||
statusCode: 403,
|
||||
});
|
||||
|
||||
export const createIntakeEmailNotFoundError = createErrorFactory({
|
||||
message: 'Intake email not found',
|
||||
code: 'intake_email.not_found',
|
||||
statusCode: 404,
|
||||
});
|
||||
|
||||
@@ -44,6 +44,7 @@ describe('intake-emails repository', () => {
|
||||
|
||||
const { intakeEmail: retrievedIntakeEmail } = await intakeEmailsRepository.getIntakeEmail({
|
||||
intakeEmailId: intakeEmail.id,
|
||||
organizationId: 'organization-1',
|
||||
});
|
||||
|
||||
expect(
|
||||
|
||||
@@ -47,12 +47,15 @@ async function updateIntakeEmail({ intakeEmailId, organizationId, isEnabled, all
|
||||
return { intakeEmail };
|
||||
}
|
||||
|
||||
async function getIntakeEmail({ intakeEmailId, db }: { intakeEmailId: string; db: Database }) {
|
||||
async function getIntakeEmail({ intakeEmailId, organizationId, db }: { intakeEmailId: string; organizationId: string; db: Database }) {
|
||||
const [intakeEmail] = await db
|
||||
.select()
|
||||
.from(intakeEmailsTable)
|
||||
.where(
|
||||
eq(intakeEmailsTable.id, intakeEmailId),
|
||||
and(
|
||||
eq(intakeEmailsTable.id, intakeEmailId),
|
||||
eq(intakeEmailsTable.organizationId, organizationId),
|
||||
),
|
||||
);
|
||||
|
||||
return { intakeEmail };
|
||||
|
||||
@@ -18,7 +18,7 @@ import { INTAKE_EMAILS_INGEST_ROUTE } from './intake-emails.constants';
|
||||
import { createIntakeEmailsRepository } from './intake-emails.repository';
|
||||
import { intakeEmailsIngestionMetaSchema, parseJson } from './intake-emails.schemas';
|
||||
import { createIntakeEmailsServices } from './intake-emails.services';
|
||||
import { createIntakeEmail, processIntakeEmailIngestion } from './intake-emails.usecases';
|
||||
import { createIntakeEmail, deleteIntakeEmail, processIntakeEmailIngestion } from './intake-emails.usecases';
|
||||
|
||||
const logger = createLogger({ namespace: 'intake-emails.routes' });
|
||||
|
||||
@@ -86,7 +86,7 @@ function setupCreateIntakeEmailRoute({ app, db, config }: RouteDefinitionContext
|
||||
);
|
||||
}
|
||||
|
||||
function setupDeleteIntakeEmailRoute({ app, db }: RouteDefinitionContext) {
|
||||
function setupDeleteIntakeEmailRoute({ app, db, config }: RouteDefinitionContext) {
|
||||
app.delete(
|
||||
'/api/organizations/:organizationId/intake-emails/:intakeEmailId',
|
||||
validateParams(z.object({
|
||||
@@ -99,10 +99,11 @@ function setupDeleteIntakeEmailRoute({ app, db }: RouteDefinitionContext) {
|
||||
|
||||
const organizationsRepository = createOrganizationsRepository({ db });
|
||||
const intakeEmailsRepository = createIntakeEmailsRepository({ db });
|
||||
const intakeEmailsServices = createIntakeEmailsServices({ config });
|
||||
|
||||
await ensureUserIsInOrganization({ userId, organizationId, organizationsRepository });
|
||||
|
||||
await intakeEmailsRepository.deleteIntakeEmail({ intakeEmailId, organizationId });
|
||||
await deleteIntakeEmail({ intakeEmailId, organizationId, intakeEmailsRepository, intakeEmailsServices });
|
||||
|
||||
return context.body(null, 204);
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ import { safely } from '@corentinth/chisels';
|
||||
import { createDocument } from '../documents/documents.usecases';
|
||||
import { getOrganizationPlan } from '../plans/plans.usecases';
|
||||
import { addLogContext, createLogger } from '../shared/logger/logger';
|
||||
import { createIntakeEmailLimitReachedError } from './intake-emails.errors';
|
||||
import { createIntakeEmailLimitReachedError, createIntakeEmailNotFoundError } from './intake-emails.errors';
|
||||
import { getIsFromAllowedOrigin } from './intake-emails.models';
|
||||
|
||||
export async function createIntakeEmail({
|
||||
@@ -165,3 +165,24 @@ export async function checkIfOrganizationCanCreateNewIntakeEmail({
|
||||
throw createIntakeEmailLimitReachedError();
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteIntakeEmail({
|
||||
intakeEmailId,
|
||||
organizationId,
|
||||
intakeEmailsRepository,
|
||||
intakeEmailsServices,
|
||||
}: {
|
||||
intakeEmailId: string;
|
||||
organizationId: string;
|
||||
intakeEmailsRepository: IntakeEmailsRepository;
|
||||
intakeEmailsServices: IntakeEmailsServices;
|
||||
}) {
|
||||
const { intakeEmail } = await intakeEmailsRepository.getIntakeEmail({ intakeEmailId, organizationId });
|
||||
|
||||
if (!intakeEmail) {
|
||||
throw createIntakeEmailNotFoundError();
|
||||
}
|
||||
|
||||
await intakeEmailsRepository.deleteIntakeEmail({ organizationId: intakeEmail.organizationId, intakeEmailId });
|
||||
await intakeEmailsServices.deleteEmailAddress({ emailAddress: intakeEmail.emailAddress });
|
||||
}
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -236,8 +236,8 @@ importers:
|
||||
specifier: ^0.14.0
|
||||
version: 0.14.0
|
||||
'@owlrelay/api-sdk':
|
||||
specifier: ^0.0.1
|
||||
version: 0.0.1
|
||||
specifier: ^0.0.2
|
||||
version: 0.0.2
|
||||
'@owlrelay/webhook':
|
||||
specifier: ^0.0.3
|
||||
version: 0.0.3
|
||||
@@ -2064,8 +2064,8 @@ packages:
|
||||
'@oslojs/encoding@1.1.0':
|
||||
resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
|
||||
|
||||
'@owlrelay/api-sdk@0.0.1':
|
||||
resolution: {integrity: sha512-/4x/J4ktb9z3Zf3VfX7tjQYYqgn8bMXk7hbYePoGknijk90QRjsfsVjpPjl2fgUs+ZGFeOQqdsgLmpxq2d1NGA==}
|
||||
'@owlrelay/api-sdk@0.0.2':
|
||||
resolution: {integrity: sha512-7WzTd/IKiT/h9Y90O7aCdrdZVk26Tb4f4EkIGRzzF0hvujmrp98R3TNji/ZD9As0v6OOilMlMYm4xlZGQzu+bQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@owlrelay/webhook@0.0.3':
|
||||
@@ -8551,7 +8551,7 @@ snapshots:
|
||||
|
||||
'@oslojs/encoding@1.1.0': {}
|
||||
|
||||
'@owlrelay/api-sdk@0.0.1':
|
||||
'@owlrelay/api-sdk@0.0.2':
|
||||
dependencies:
|
||||
ofetch: 1.4.1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user