diff --git a/.env.example b/.env.example index 6b50f4a43e9..30aa114d981 100644 --- a/.env.example +++ b/.env.example @@ -22,4 +22,3 @@ AWS_SESSION_TOKEN= NX_PLAID_SECRET= NX_FINICITY_APP_KEY= NX_FINICITY_PARTNER_SECRET= -NX_CONVERTKIT_SECRET= \ No newline at end of file diff --git a/.github/workflows/validate-pull-request.yml b/.github/workflows/validate-pull-request.yml index 86a8648ad59..989f6d977fa 100644 --- a/.github/workflows/validate-pull-request.yml +++ b/.github/workflows/validate-pull-request.yml @@ -75,7 +75,6 @@ jobs: NX_STRIPE_SECRET_KEY=${{ secrets.NX_STRIPE_SECRET_KEY }} NX_STRIPE_WEBHOOK_SECRET=${{ secrets.NX_STRIPE_WEBHOOK_SECRET }} NX_PLAID_WEBHOOK_URL=none - NX_CONVERTKIT_SECRET=none NX_DATABASE_URL=postgresql://maybe:maybe@localhost:5432/maybe_local?connection_limit=32&pool_timeout=20 NX_REDIS_URL=redis://localhost:6379 EOF diff --git a/apps/server/src/app/app.ts b/apps/server/src/app/app.ts index e50f0e16729..0d77d66406b 100644 --- a/apps/server/src/app/app.ts +++ b/apps/server/src/app/app.ts @@ -42,7 +42,6 @@ import { securitiesRouter, plansRouter, toolsRouter, - notificationsRouter, publicRouter, e2eRouter, } from './routes' @@ -167,7 +166,6 @@ app.use('/v1/transactions', transactionsRouter) app.use('/v1/holdings', holdingsRouter) app.use('/v1/securities', securitiesRouter) app.use('/v1/plans', plansRouter) -app.use('/v1/notifications', notificationsRouter) // Sentry must be the *first* handler app.use(identifySentryUser) diff --git a/apps/server/src/app/lib/convertKit.ts b/apps/server/src/app/lib/convertKit.ts deleted file mode 100644 index 7bdac90a93d..00000000000 --- a/apps/server/src/app/lib/convertKit.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { SharedType } from '@maybe-finance/shared' -import type { AxiosInstance } from 'axios' -import axios from 'axios' -import env from '../../env' - -class ConvertKitApi { - private axios: AxiosInstance - - constructor(private readonly apiSecret: string) { - this.axios = axios.create({ - baseURL: 'https://api.convertkit.com/v3', - }) - } - - async getSubscription(subscriberId: number | null) { - // Until we have the id stored in DB, assume no subscription - if (!subscriberId) { - return { - isSubscribed: false, - } - } - - const res = await this.axios.get<{ subscriber: SharedType.ConvertKitSubscriber }>( - `/subscribers/${subscriberId}`, - { - params: { - api_secret: this.apiSecret, - }, - } - ) - - return { - isSubscribed: res.data ? res.data.subscriber.state === 'active' : false, - subscriber: res.data.subscriber, - } - } - - async subscribe(email: string) { - const res = await this.axios.post<{ subscription: SharedType.ConvertKitSubscription }>( - '/forms/2279973/subscribe', // The main mailing list ID - { - api_secret: this.apiSecret, - email, - } - ) - - return res.data - } - - async unsubscribe(email: string) { - const res = await this.axios.put<{ subscriber: SharedType.ConvertKitSubscriber }>( - '/unsubscribe', - { - api_secret: this.apiSecret, - email, - } - ) - - return res.data - } -} - -// Prevent multiple instances of S3 client -const convertKit = new ConvertKitApi(env.NX_CONVERTKIT_SECRET) - -export default convertKit diff --git a/apps/server/src/app/lib/endpoint.ts b/apps/server/src/app/lib/endpoint.ts index cb02b1b3cdf..bbb9087fd8f 100644 --- a/apps/server/src/app/lib/endpoint.ts +++ b/apps/server/src/app/lib/endpoint.ts @@ -55,7 +55,6 @@ import prisma from './prisma' import plaid, { getPlaidWebhookUrl } from './plaid' import finicity, { getFinicityTxPushUrl, getFinicityWebhookUrl } from './finicity' import stripe from './stripe' -import convertKit from './convertKit' import postmark from './postmark' import { managementClient } from './auth0' import s3 from './s3' @@ -315,7 +314,6 @@ export async function createContext(req: Request) { prisma, plaid, stripe, - convertKit, s3, secretsClient, managementClient, diff --git a/apps/server/src/app/routes/index.ts b/apps/server/src/app/routes/index.ts index fd988bc3b88..fd09f81cb48 100644 --- a/apps/server/src/app/routes/index.ts +++ b/apps/server/src/app/routes/index.ts @@ -13,6 +13,5 @@ export { default as holdingsRouter } from './holdings.router' export { default as securitiesRouter } from './securities.router' export { default as plansRouter } from './plans.router' export { default as toolsRouter } from './tools.router' -export { default as notificationsRouter } from './notifications.router' export { default as publicRouter } from './public.router' export { default as e2eRouter } from './e2e.router' diff --git a/apps/server/src/app/routes/notifications.router.ts b/apps/server/src/app/routes/notifications.router.ts deleted file mode 100644 index 08a661d9c07..00000000000 --- a/apps/server/src/app/routes/notifications.router.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Router } from 'express' -import endpoint from '../lib/endpoint' - -const router = Router() - -router.get( - '/convertkit/subscription', - endpoint.create({ - resolve: async ({ ctx }) => { - return ctx.convertKit.getSubscription(ctx.user!.convertKitId) - }, - }) -) - -router.post( - '/convertkit/subscribe', - endpoint.create({ - resolve: async ({ ctx }) => { - const auth0User = await ctx.managementClient.getUser({ id: ctx.user!.auth0Id }) - - const { subscription } = await ctx.convertKit.subscribe(auth0User.email!) - - await ctx.userService.update(ctx.user!.id, { - convertKitId: subscription.subscriber.id, - }) - - return subscription - }, - }) -) - -router.post( - '/convertkit/unsubscribe', - endpoint.create({ - resolve: async ({ ctx }) => { - const auth0User = await ctx.managementClient.getUser({ id: ctx.user!.auth0Id }) - const { subscriber } = await ctx.convertKit.unsubscribe(auth0User.email!) - return subscriber - }, - }) -) - -export default router diff --git a/apps/server/src/env.ts b/apps/server/src/env.ts index 0310d64b662..05a0c763561 100644 --- a/apps/server/src/env.ts +++ b/apps/server/src/env.ts @@ -79,7 +79,6 @@ const envSchema = z.object({ // Key to Cloudfront pub key NX_CDN_SIGNER_PUBKEY_ID: z.string().default('REPLACE_THIS'), - NX_CONVERTKIT_SECRET: z.string(), NX_POSTMARK_FROM_ADDRESS: z.string().default('account@maybe.co'), NX_POSTMARK_REPLY_TO_ADDRESS: z.string().default('support@maybe.co'), NX_POSTMARK_API_TOKEN: z.string().default('REPLACE_THIS'), diff --git a/aws/maybe-app/lib/stacks/server-stack.ts b/aws/maybe-app/lib/stacks/server-stack.ts index 1a45d955d2d..4b292e0ba7b 100644 --- a/aws/maybe-app/lib/stacks/server-stack.ts +++ b/aws/maybe-app/lib/stacks/server-stack.ts @@ -197,24 +197,6 @@ export class ServerStack extends Stack { } ) ), - NX_CONVERTKIT_KEY: ECSSecret.fromSsmParameter( - StringParameter.fromSecureStringParameterAttributes( - this, - 'ConvertKitKeyParam', - { - parameterName: '/providers/NX_CONVERTKIT_KEY', - } - ) - ), - NX_CONVERTKIT_SECRET: ECSSecret.fromSsmParameter( - StringParameter.fromSecureStringParameterAttributes( - this, - 'ConvertKitSecretParam', - { - parameterName: '/providers/NX_CONVERTKIT_SECRET', - } - ) - ), NX_POSTMARK_API_TOKEN: ECSSecret.fromSsmParameter( StringParameter.fromSecureStringParameterAttributes( this, diff --git a/libs/client/shared/src/api/index.ts b/libs/client/shared/src/api/index.ts index 2601e1e1f37..bb7a811f180 100644 --- a/libs/client/shared/src/api/index.ts +++ b/libs/client/shared/src/api/index.ts @@ -9,4 +9,3 @@ export * from './useTransactionApi' export * from './useHoldingApi' export * from './useSecurityApi' export * from './usePlanApi' -export * from './useNotificationsApi' diff --git a/libs/client/shared/src/api/useNotificationsApi.ts b/libs/client/shared/src/api/useNotificationsApi.ts deleted file mode 100644 index f3701a63fe9..00000000000 --- a/libs/client/shared/src/api/useNotificationsApi.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { AxiosInstance } from 'axios' -import type { UseQueryOptions } from '@tanstack/react-query' -import type { SharedType } from '@maybe-finance/shared' -import { useMemo } from 'react' -import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query' -import { useAxiosWithAuth } from '..' -import toast from 'react-hot-toast' - -const NotificationsApi = (axios: AxiosInstance) => ({ - async getConvertKitSubscription() { - const { data } = await axios.get<{ - isSubscribed: boolean - subscriber?: SharedType.ConvertKitSubscriber - }>(`/notifications/convertkit/subscription`) - - return data - }, - - async manageSubscription(action: 'subscribe' | 'unsubscribe') { - const { data } = await axios.post( - `/notifications/convertkit/${action}` - ) - return data - }, -}) - -type SubscriptionState = { - isSubscribed: boolean - subscriber?: SharedType.ConvertKitSubscriber -} - -export function useNotificationsApi() { - const queryClient = useQueryClient() - const { axios } = useAxiosWithAuth() - const api = useMemo(() => NotificationsApi(axios), [axios]) - - const useConvertKitSubscriber = ( - options?: Omit< - UseQueryOptions< - { isSubscribed: boolean; subscriber?: SharedType.ConvertKitSubscriber }, - unknown, - { isSubscribed: boolean; subscriber?: SharedType.ConvertKitSubscriber }, - any[] - >, - 'queryKey' | 'queryFn' | 'staleTime' - > - ) => { - return useQuery(['notifications', 'convertkit'], api.getConvertKitSubscription, { - staleTime: 30_000, - ...options, - }) - } - - const useConvertKit = () => - useMutation(api.manageSubscription, { - onMutate: async (action) => { - await queryClient.cancelQueries({ queryKey: ['notifications'] }) - const previousSubscription = queryClient.getQueryData([ - 'notifications', - 'convertkit', - ]) - - // Optimistic update to new state - queryClient.setQueryData(['notifications', 'convertkit'], () => ({ - ...previousSubscription, - isSubscribed: action === 'subscribe', - })) - - return { previousSubscription } - }, - onSuccess: () => { - toast.success('Newsletter preferences updated!') - }, - onError: (err, action, ctx) => { - queryClient.setQueryData(['notifications', 'convertkit'], ctx?.previousSubscription) - - toast.error('Error updating newsletter preferences') - }, - onSettled: () => { - queryClient.invalidateQueries(['notifications', 'convertkit']) - }, - }) - - return { - useConvertKitSubscriber, - useConvertKit, - } -} diff --git a/libs/shared/src/types/user-types.ts b/libs/shared/src/types/user-types.ts index ed291293030..c833224c111 100644 --- a/libs/shared/src/types/user-types.ts +++ b/libs/shared/src/types/user-types.ts @@ -295,23 +295,3 @@ export interface LinkConfig { export type PublicTokenExchange = LinkConfig & { institution: Institution } - -/** - * ================================================================ - * ====== ConvertKitApi ====== - * ================================================================ - */ - -export type ConvertKitSubscriber = { - id: number - first_name: string - email_address: string - state: 'cancelled' | 'active' - created_at: string -} - -export type ConvertKitSubscription = { - id: number - state: 'cancelled' | 'active' - subscriber: Pick -} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 04d7d4787f7..e7402b75544 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -409,9 +409,6 @@ model User { isoCurrencyCode String @default("USD") @map("iso_currency_code") - // Notification preferences - convertKitId Int? @map("convert_kit_id") - // Financial preferences and info monthlyIncomeUser Decimal? @map("monthly_income_user") @db.Decimal(19, 4) monthlyExpensesUser Decimal? @map("monthly_expenses_user") @db.Decimal(19, 4)