mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 07:25:19 +02:00
Merge pull request #35 from MichaelDeBoey/remove-convertKit-usage
feat: remove ConvertKit usage
This commit is contained in:
commit
f346ecb3a0
13 changed files with 0 additions and 247 deletions
|
@ -22,4 +22,3 @@ AWS_SESSION_TOKEN=
|
||||||
NX_PLAID_SECRET=
|
NX_PLAID_SECRET=
|
||||||
NX_FINICITY_APP_KEY=
|
NX_FINICITY_APP_KEY=
|
||||||
NX_FINICITY_PARTNER_SECRET=
|
NX_FINICITY_PARTNER_SECRET=
|
||||||
NX_CONVERTKIT_SECRET=
|
|
1
.github/workflows/validate-pull-request.yml
vendored
1
.github/workflows/validate-pull-request.yml
vendored
|
@ -75,7 +75,6 @@ jobs:
|
||||||
NX_STRIPE_SECRET_KEY=${{ secrets.NX_STRIPE_SECRET_KEY }}
|
NX_STRIPE_SECRET_KEY=${{ secrets.NX_STRIPE_SECRET_KEY }}
|
||||||
NX_STRIPE_WEBHOOK_SECRET=${{ secrets.NX_STRIPE_WEBHOOK_SECRET }}
|
NX_STRIPE_WEBHOOK_SECRET=${{ secrets.NX_STRIPE_WEBHOOK_SECRET }}
|
||||||
NX_PLAID_WEBHOOK_URL=none
|
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_DATABASE_URL=postgresql://maybe:maybe@localhost:5432/maybe_local?connection_limit=32&pool_timeout=20
|
||||||
NX_REDIS_URL=redis://localhost:6379
|
NX_REDIS_URL=redis://localhost:6379
|
||||||
EOF
|
EOF
|
||||||
|
|
|
@ -42,7 +42,6 @@ import {
|
||||||
securitiesRouter,
|
securitiesRouter,
|
||||||
plansRouter,
|
plansRouter,
|
||||||
toolsRouter,
|
toolsRouter,
|
||||||
notificationsRouter,
|
|
||||||
publicRouter,
|
publicRouter,
|
||||||
e2eRouter,
|
e2eRouter,
|
||||||
} from './routes'
|
} from './routes'
|
||||||
|
@ -167,7 +166,6 @@ app.use('/v1/transactions', transactionsRouter)
|
||||||
app.use('/v1/holdings', holdingsRouter)
|
app.use('/v1/holdings', holdingsRouter)
|
||||||
app.use('/v1/securities', securitiesRouter)
|
app.use('/v1/securities', securitiesRouter)
|
||||||
app.use('/v1/plans', plansRouter)
|
app.use('/v1/plans', plansRouter)
|
||||||
app.use('/v1/notifications', notificationsRouter)
|
|
||||||
|
|
||||||
// Sentry must be the *first* handler
|
// Sentry must be the *first* handler
|
||||||
app.use(identifySentryUser)
|
app.use(identifySentryUser)
|
||||||
|
|
|
@ -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
|
|
|
@ -55,7 +55,6 @@ import prisma from './prisma'
|
||||||
import plaid, { getPlaidWebhookUrl } from './plaid'
|
import plaid, { getPlaidWebhookUrl } from './plaid'
|
||||||
import finicity, { getFinicityTxPushUrl, getFinicityWebhookUrl } from './finicity'
|
import finicity, { getFinicityTxPushUrl, getFinicityWebhookUrl } from './finicity'
|
||||||
import stripe from './stripe'
|
import stripe from './stripe'
|
||||||
import convertKit from './convertKit'
|
|
||||||
import postmark from './postmark'
|
import postmark from './postmark'
|
||||||
import { managementClient } from './auth0'
|
import { managementClient } from './auth0'
|
||||||
import s3 from './s3'
|
import s3 from './s3'
|
||||||
|
@ -315,7 +314,6 @@ export async function createContext(req: Request) {
|
||||||
prisma,
|
prisma,
|
||||||
plaid,
|
plaid,
|
||||||
stripe,
|
stripe,
|
||||||
convertKit,
|
|
||||||
s3,
|
s3,
|
||||||
secretsClient,
|
secretsClient,
|
||||||
managementClient,
|
managementClient,
|
||||||
|
|
|
@ -13,6 +13,5 @@ export { default as holdingsRouter } from './holdings.router'
|
||||||
export { default as securitiesRouter } from './securities.router'
|
export { default as securitiesRouter } from './securities.router'
|
||||||
export { default as plansRouter } from './plans.router'
|
export { default as plansRouter } from './plans.router'
|
||||||
export { default as toolsRouter } from './tools.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 publicRouter } from './public.router'
|
||||||
export { default as e2eRouter } from './e2e.router'
|
export { default as e2eRouter } from './e2e.router'
|
||||||
|
|
|
@ -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
|
|
|
@ -79,7 +79,6 @@ const envSchema = z.object({
|
||||||
// Key to Cloudfront pub key
|
// Key to Cloudfront pub key
|
||||||
NX_CDN_SIGNER_PUBKEY_ID: z.string().default('REPLACE_THIS'),
|
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_FROM_ADDRESS: z.string().default('account@maybe.co'),
|
||||||
NX_POSTMARK_REPLY_TO_ADDRESS: z.string().default('support@maybe.co'),
|
NX_POSTMARK_REPLY_TO_ADDRESS: z.string().default('support@maybe.co'),
|
||||||
NX_POSTMARK_API_TOKEN: z.string().default('REPLACE_THIS'),
|
NX_POSTMARK_API_TOKEN: z.string().default('REPLACE_THIS'),
|
||||||
|
|
|
@ -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(
|
NX_POSTMARK_API_TOKEN: ECSSecret.fromSsmParameter(
|
||||||
StringParameter.fromSecureStringParameterAttributes(
|
StringParameter.fromSecureStringParameterAttributes(
|
||||||
this,
|
this,
|
||||||
|
|
|
@ -9,4 +9,3 @@ export * from './useTransactionApi'
|
||||||
export * from './useHoldingApi'
|
export * from './useHoldingApi'
|
||||||
export * from './useSecurityApi'
|
export * from './useSecurityApi'
|
||||||
export * from './usePlanApi'
|
export * from './usePlanApi'
|
||||||
export * from './useNotificationsApi'
|
|
||||||
|
|
|
@ -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<SharedType.ConvertKitSubscription>(
|
|
||||||
`/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<SubscriptionState>([
|
|
||||||
'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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -295,23 +295,3 @@ export interface LinkConfig {
|
||||||
export type PublicTokenExchange = LinkConfig & {
|
export type PublicTokenExchange = LinkConfig & {
|
||||||
institution: Institution
|
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<ConvertKitSubscriber, 'id'>
|
|
||||||
}
|
|
||||||
|
|
|
@ -409,9 +409,6 @@ model User {
|
||||||
|
|
||||||
isoCurrencyCode String @default("USD") @map("iso_currency_code")
|
isoCurrencyCode String @default("USD") @map("iso_currency_code")
|
||||||
|
|
||||||
// Notification preferences
|
|
||||||
convertKitId Int? @map("convert_kit_id")
|
|
||||||
|
|
||||||
// Financial preferences and info
|
// Financial preferences and info
|
||||||
monthlyIncomeUser Decimal? @map("monthly_income_user") @db.Decimal(19, 4)
|
monthlyIncomeUser Decimal? @map("monthly_income_user") @db.Decimal(19, 4)
|
||||||
monthlyExpensesUser Decimal? @map("monthly_expenses_user") @db.Decimal(19, 4)
|
monthlyExpensesUser Decimal? @map("monthly_expenses_user") @db.Decimal(19, 4)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue