mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
refactor(api): collapse single-impl interfaces and facades (#3382)
This commit is contained in:
1 parent
e7b13fd19f
commit
c16d414d93
426 files changed
+2085
-6445
No files matched your search
@@ -17,7 +17,6 @@ import {
|
||||
DonorsByStripeCustomerId,
|
||||
DonorsByStripeSubscriptionId,
|
||||
} from '@app/api/donation/DonationTables';
|
||||
import {IDonationRepository} from '@app/api/donation/IDonationRepository';
|
||||
import {Donor} from '@app/api/donation/models/Donor';
|
||||
import {DonorMagicLinkToken} from '@app/api/donation/models/DonorMagicLinkToken';
|
||||
|
||||
@@ -44,7 +43,7 @@ const FETCH_MAGIC_LINK_TOKENS_BY_EMAIL_QUERY = DonorMagicLinkTokensByEmail.selec
|
||||
where: DonorMagicLinkTokensByEmail.where.eq('donor_email'),
|
||||
});
|
||||
|
||||
export class DonationRepository extends IDonationRepository {
|
||||
export class DonationRepository {
|
||||
async findDonorByEmail(email: string): Promise<Donor | null> {
|
||||
const row = await fetchOne<DonorRow>(FETCH_DONOR_BY_EMAIL_QUERY, {email});
|
||||
return row ? new Donor(row) : null;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {IDonationService} from '@app/api/donation/IDonationService';
|
||||
import type {DonationCheckoutService} from '@app/api/donation/services/DonationCheckoutService';
|
||||
import type {DonationMagicLinkService} from '@app/api/donation/services/DonationMagicLinkService';
|
||||
import type {DonationCurrency} from '@fluxer/schema/src/domains/donation/DonationSchemas';
|
||||
|
||||
export class DonationService implements IDonationService {
|
||||
export class DonationService {
|
||||
constructor(
|
||||
private magicLinkService: DonationMagicLinkService,
|
||||
private checkoutService: DonationCheckoutService,
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {Donor} from '@app/api/donation/models/Donor';
|
||||
import type {DonorMagicLinkToken} from '@app/api/donation/models/DonorMagicLinkToken';
|
||||
|
||||
export abstract class IDonationRepository {
|
||||
abstract findDonorByEmail(email: string): Promise<Donor | null>;
|
||||
|
||||
abstract findDonorByStripeCustomerId(customerId: string): Promise<Donor | null>;
|
||||
|
||||
abstract findDonorByStripeSubscriptionId(subscriptionId: string): Promise<Donor | null>;
|
||||
|
||||
abstract createDonor(data: {
|
||||
email: string;
|
||||
stripeCustomerId: string | null;
|
||||
businessName?: string | null;
|
||||
taxId?: string | null;
|
||||
taxIdType?: string | null;
|
||||
stripeSubscriptionId: string | null;
|
||||
subscriptionAmountCents: number | null;
|
||||
subscriptionCurrency: string | null;
|
||||
subscriptionInterval: string | null;
|
||||
subscriptionCurrentPeriodEnd: Date | null;
|
||||
subscriptionCancelAt?: Date | null;
|
||||
subscriptionStatus?: string | null;
|
||||
}): Promise<Donor>;
|
||||
|
||||
abstract updateDonorSubscription(
|
||||
email: string,
|
||||
data: {
|
||||
stripeCustomerId: string | null;
|
||||
businessName?: string | null;
|
||||
taxId?: string | null;
|
||||
taxIdType?: string | null;
|
||||
stripeSubscriptionId: string | null;
|
||||
subscriptionAmountCents: number | null;
|
||||
subscriptionCurrency: string | null;
|
||||
subscriptionInterval: string | null;
|
||||
subscriptionCurrentPeriodEnd: Date | null;
|
||||
subscriptionCancelAt?: Date | null;
|
||||
subscriptionStatus?: string | null;
|
||||
},
|
||||
): Promise<Donor | null>;
|
||||
|
||||
abstract updateDonorCustomerDetails(
|
||||
email: string,
|
||||
data: {
|
||||
stripeCustomerId: string | null;
|
||||
businessName?: string | null;
|
||||
taxId?: string | null;
|
||||
taxIdType?: string | null;
|
||||
},
|
||||
): Promise<Donor | null>;
|
||||
|
||||
abstract linkDonorStripeCustomer(email: string, stripeCustomerId: string): Promise<void>;
|
||||
|
||||
abstract clearDonorStripeCustomer(stripeCustomerId: string): Promise<void>;
|
||||
|
||||
abstract cancelDonorSubscription(email: string): Promise<void>;
|
||||
|
||||
abstract deleteDonorSubscriptionMapping(stripeSubscriptionId: string, email: string): Promise<void>;
|
||||
|
||||
abstract createMagicLinkToken(token: DonorMagicLinkToken): Promise<void>;
|
||||
|
||||
abstract findMagicLinkToken(token: string): Promise<DonorMagicLinkToken | null>;
|
||||
|
||||
abstract markMagicLinkTokenUsed(token: string, usedAt: Date): Promise<void>;
|
||||
|
||||
abstract invalidateTokensForEmail(email: string): Promise<void>;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {DonationCurrency} from '@fluxer/schema/src/domains/donation/DonationSchemas';
|
||||
|
||||
export interface IDonationService {
|
||||
requestMagicLink(email: string, locale?: string | null): Promise<void>;
|
||||
validateMagicLinkToken(token: string): Promise<{
|
||||
email: string;
|
||||
stripeCustomerId: string | null;
|
||||
}>;
|
||||
redeemMagicLinkToken(token: string): Promise<string | null>;
|
||||
createDonationCheckout(params: {
|
||||
email: string;
|
||||
amountCents: number;
|
||||
currency: DonationCurrency;
|
||||
interval: 'month' | 'year' | null;
|
||||
isBusiness?: boolean;
|
||||
locale?: string | null;
|
||||
}): Promise<string>;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import {Config} from '@app/api/Config';
|
||||
import {getContentMessage} from '@app/api/content_i18n/ContentI18n';
|
||||
import type {IDonationRepository} from '@app/api/donation/IDonationRepository';
|
||||
import type {DonationRepository} from '@app/api/donation/DonationRepository';
|
||||
import type {IEmailDnsValidationService} from '@app/api/infrastructure/IEmailDnsValidationService';
|
||||
import {getInstanceProductName} from '@app/api/instance/ProductName';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
@@ -97,7 +97,7 @@ function getDonationProductData(interval: 'month' | 'year' | null, locale: strin
|
||||
export class DonationCheckoutService {
|
||||
constructor(
|
||||
private stripe: Stripe | null,
|
||||
private donationRepository: IDonationRepository,
|
||||
private donationRepository: DonationRepository,
|
||||
private emailDnsValidationService: IEmailDnsValidationService,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import {randomBytes} from 'node:crypto';
|
||||
import {Config} from '@app/api/Config';
|
||||
import type {IDonationRepository} from '@app/api/donation/IDonationRepository';
|
||||
import type {DonationRepository} from '@app/api/donation/DonationRepository';
|
||||
import {DonorMagicLinkToken} from '@app/api/donation/models/DonorMagicLinkToken';
|
||||
import type {IEmailDnsValidationService} from '@app/api/infrastructure/IEmailDnsValidationService';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
@@ -16,7 +16,7 @@ import type {IEmailService} from '@pkgs/email/src/IEmailService';
|
||||
|
||||
export class DonationMagicLinkService {
|
||||
constructor(
|
||||
private donationRepository: IDonationRepository,
|
||||
private donationRepository: DonationRepository,
|
||||
private emailService: IEmailService,
|
||||
private emailDnsValidationService: IEmailDnsValidationService,
|
||||
) {}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import {clearTestEmails, createUniqueEmail, listTestEmails} from '@app/api/auth/tests/AuthTestUtils';
|
||||
import {DonationRepository} from '@app/api/donation/DonationRepository';
|
||||
import {
|
||||
clearDonationTestEmails,
|
||||
createDonationRequestLinkBuilder,
|
||||
createUniqueEmail,
|
||||
listDonationTestEmails,
|
||||
TEST_DONOR_EMAIL,
|
||||
} from '@app/api/donation/tests/DonationTestUtils';
|
||||
import {createDonationRequestLinkBuilder, TEST_DONOR_EMAIL} from '@app/api/donation/tests/DonationTestUtils';
|
||||
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
|
||||
import {afterAll, beforeAll, beforeEach, describe, expect, test} from 'vitest';
|
||||
|
||||
@@ -21,7 +16,7 @@ describe('POST /donations/request-link', () => {
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await harness.reset();
|
||||
await clearDonationTestEmails(harness);
|
||||
await clearTestEmails(harness);
|
||||
});
|
||||
async function createDonor(email: string): Promise<void> {
|
||||
await donationRepository.createDonor({
|
||||
@@ -41,20 +36,20 @@ describe('POST /donations/request-link', () => {
|
||||
test('sends magic link email when donor exists', async () => {
|
||||
await createDonor(TEST_DONOR_EMAIL);
|
||||
await createDonationRequestLinkBuilder(harness).body({email: TEST_DONOR_EMAIL}).expect(204).execute();
|
||||
const emails = await listDonationTestEmails(harness, {recipient: TEST_DONOR_EMAIL});
|
||||
const emails = await listTestEmails(harness, {recipient: TEST_DONOR_EMAIL});
|
||||
expect(emails).toHaveLength(1);
|
||||
expect(emails[0]?.type).toBe('donation_magic_link');
|
||||
});
|
||||
test('does not send email when donor does not exist', async () => {
|
||||
const unknownEmail = createUniqueEmail('unknown');
|
||||
await createDonationRequestLinkBuilder(harness).body({email: unknownEmail}).expect(204).execute();
|
||||
const emails = await listDonationTestEmails(harness, {recipient: unknownEmail});
|
||||
const emails = await listTestEmails(harness, {recipient: unknownEmail});
|
||||
expect(emails).toHaveLength(0);
|
||||
});
|
||||
test('magic link token is 64-character hex string', async () => {
|
||||
await createDonor(TEST_DONOR_EMAIL);
|
||||
await createDonationRequestLinkBuilder(harness).body({email: TEST_DONOR_EMAIL}).expect(204).execute();
|
||||
const emails = await listDonationTestEmails(harness, {recipient: TEST_DONOR_EMAIL});
|
||||
const emails = await listTestEmails(harness, {recipient: TEST_DONOR_EMAIL});
|
||||
const token = emails[0]?.metadata.token;
|
||||
expect(token).toBeDefined();
|
||||
expect(token).toHaveLength(64);
|
||||
@@ -66,7 +61,7 @@ describe('POST /donations/request-link', () => {
|
||||
await createDonor(TEST_DONOR_EMAIL);
|
||||
await createDonationRequestLinkBuilder(harness).body({email: TEST_DONOR_EMAIL}).expect(204).execute();
|
||||
await createDonationRequestLinkBuilder(harness).body({email: TEST_DONOR_EMAIL}).expect(204).execute();
|
||||
const emails = await listDonationTestEmails(harness, {recipient: TEST_DONOR_EMAIL});
|
||||
const emails = await listTestEmails(harness, {recipient: TEST_DONOR_EMAIL});
|
||||
expect(emails).toHaveLength(2);
|
||||
const token1 = emails[0]?.metadata.token;
|
||||
const token2 = emails[1]?.metadata.token;
|
||||
|
||||
@@ -1,36 +1,9 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {randomUUID} from 'node:crypto';
|
||||
import type {ApiTestHarness} from '@app/api/test/ApiTestHarness';
|
||||
import {createBuilderWithoutAuth, type TestRequestBuilder} from '@app/api/test/TestRequestBuilder';
|
||||
import {DONATION_CURRENCIES, type DonationCurrency} from '@fluxer/schema/src/domains/donation/DonationAmountUtils';
|
||||
|
||||
interface DonationTestEmailRecord {
|
||||
to: string;
|
||||
type: string;
|
||||
timestamp: string;
|
||||
metadata: Record<string, string>;
|
||||
}
|
||||
|
||||
export async function listDonationTestEmails(
|
||||
harness: ApiTestHarness,
|
||||
params?: {
|
||||
recipient?: string;
|
||||
},
|
||||
): Promise<Array<DonationTestEmailRecord>> {
|
||||
const query = params?.recipient ? `?recipient=${encodeURIComponent(params.recipient)}` : '';
|
||||
const response = await createBuilderWithoutAuth<{
|
||||
emails: Array<DonationTestEmailRecord>;
|
||||
}>(harness)
|
||||
.get(`/test/emails${query}`)
|
||||
.execute();
|
||||
return response.emails;
|
||||
}
|
||||
|
||||
export async function clearDonationTestEmails(harness: ApiTestHarness): Promise<void> {
|
||||
await createBuilderWithoutAuth(harness).delete('/test/emails').expect(204).execute();
|
||||
}
|
||||
|
||||
export const TEST_DONOR_EMAIL = '[email protected]';
|
||||
export const TEST_MAGIC_LINK_TOKEN = 'a'.repeat(64);
|
||||
export const DONATION_AMOUNTS = {
|
||||
@@ -84,7 +57,3 @@ export function createValidCheckoutBody(overrides?: Partial<DonationCheckoutRequ
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function createUniqueEmail(prefix = 'donation'): string {
|
||||
return `${prefix}-${randomUUID()}@test.com`;
|
||||
}
|
||||
Reference in new issue
Block a user