diff --git a/jest/helpers.ts b/jest/helpers.ts index b8d283a..55b15dc 100644 --- a/jest/helpers.ts +++ b/jest/helpers.ts @@ -7,9 +7,6 @@ import listen from "test-listen"; import fetch from "isomorphic-fetch"; import crypto from "crypto"; -import CookieStore from "../lib/cookie-store"; -import Session from "../lib/session"; - type Authentication = | "none" | "auth0" @@ -93,15 +90,13 @@ function writeSessionToCookie( res: ServerResponse, authentication: Authentication, ) { - const cookieStore = new CookieStore(); - const session: Session = new Session({ + const session = { id: `${authentication}|userId`, email: "test@fss.test", name: "Groot", teamId: "teamId", role: "owner", - }); - cookieStore.save(req, res, session); + }; const setCookieHeader = res.getHeader("Set-Cookie") as string[]; // write it to request headers to immediately have access to the user's session diff --git a/src/__tests__/pages/api/auth/sign-up.ts b/src/__tests__/pages/api/auth/sign-up.ts index 324fad1..d455855 100644 --- a/src/__tests__/pages/api/auth/sign-up.ts +++ b/src/__tests__/pages/api/auth/sign-up.ts @@ -1,36 +1,28 @@ -jest.mock("../../../../pages/api/user/_auth0", () => ({ - setAppMetadata: jest.fn(), -})); jest.mock("../../../../pages/api/_send-email", () => ({ sendEmail: jest.fn(), })); -jest.mock("../../../../database/users", () => ({ createUser: jest.fn() })); -jest.mock("../../../../database/teams", () => ({ createTeam: jest.fn() })); +jest.mock("../../../../database/customer", () => ({ createCustomer: jest.fn() })); import { parse } from "set-cookie-parser"; import { callApiHandler } from "../../../../../jest/helpers"; import signUpHandler from "../../../../pages/api/auth/sign-up"; -import { sessionName } from "../../../../../lib/cookie-store"; import { sendEmail } from "../../../../pages/api/_send-email"; -import { createUser } from "../../../../database/users"; -import { createTeam } from "../../../../database/teams"; +import { createCustomer } from "../../../../database/customer"; + +const sessionName = ""; describe("/api/auth/sign-up", () => { const mockedSendEmail = sendEmail as jest.Mock< ReturnType >; - const mockedCreateUser = createUser as jest.Mock< - ReturnType - >; - const mockedCreateTeam = createTeam as jest.Mock< - ReturnType + const mockedCreateCustomer = createCustomer as jest.Mock< + ReturnType >; beforeEach(() => { mockedSendEmail.mockClear(); - mockedCreateUser.mockClear(); - mockedCreateTeam.mockClear(); + mockedCreateCustomer.mockClear(); }); test("responds 405 to GET", async () => { @@ -46,22 +38,11 @@ describe("/api/auth/sign-up", () => { }); test("responds 200 to POST with body from email login", async () => { - mockedCreateUser.mockResolvedValue({ + /*mockedCreateCustomer.mockResolvedValue({ id: "auth0|1234567", - teamId: "98765", - role: "owner", email: "test@fss.dev", name: "Groot", - createdAt: new Date(), - updatedAt: new Date(), - }); - mockedCreateTeam.mockResolvedValue({ - id: "98765", - subscriptionId: null, - teamMembersLimit: 1, - createdAt: new Date(), - updatedAt: new Date(), - }); + });*/ const body = { accessToken: diff --git a/src/components/long-press-handler.tsx b/src/components/long-press-handler.tsx deleted file mode 100644 index dea300b..0000000 --- a/src/components/long-press-handler.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { FunctionComponent } from "react"; -import usePress from "react-gui/use-press"; - -const LongPressHandler: FunctionComponent = ({ children }) => { - const onLongPress = (event: any) => console.log("event", event); - const ref = usePress({ onLongPress }); - - return ( -
e.preventDefault()}> - {children} -
- ); -}; - -export default LongPressHandler; diff --git a/src/components/settings/pricing-plans.old.tsx b/src/components/settings/pricing-plans.old.tsx deleted file mode 100644 index 15955a6..0000000 --- a/src/components/settings/pricing-plans.old.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import type { FunctionComponent } from "react"; -import { useState } from "react"; -import clsx from "clsx"; -import { CheckIcon } from "@heroicons/react/outline"; - -import useUser from "../../hooks/use-user"; -import useSubscription from "../../hooks/use-subscription"; - -import type { Plan, PlanId } from "../../subscription/plans"; -import { - FREE, - MONTHLY, - ANNUALLY, - TEAM_MONTHLY, - TEAM_ANNUALLY, -} from "../../subscription/plans"; - -type Props = { - activePlanId?: PlanId; -}; -const PLANS: Record = { - monthly: [FREE, MONTHLY, TEAM_MONTHLY], - yearly: [FREE, ANNUALLY, TEAM_ANNUALLY], -}; - -const PricingPlans: FunctionComponent = ({ activePlanId }) => { - const [billingSchedule, setBillingSchedule] = useState( - "monthly", - ); - - return ( -
-
-
- - -
-
- -
- {PLANS[billingSchedule].map((plan) => ( - - ))} -
-
- ); -}; - -export default PricingPlans; - -type BillingSchedule = "yearly" | "monthly"; - -type PricingPlanProps = { - plan: Plan; - billingSchedule: BillingSchedule; - activePlanId?: PlanId; -}; - -const PricingPlan: FunctionComponent = ({ - plan, - billingSchedule, - activePlanId, -}) => { - const { subscribe, changePlan } = useSubscription(); - const { userProfile } = useUser(); - const { name, description, features, price, id } = plan; - const isActivePlan = - (typeof activePlanId !== "undefined" ? activePlanId : "free") === id; - - function movePlan() { - const teamId = userProfile!.teamId; - const email = userProfile!.email; - const planId = plan.id; - - if (typeof activePlanId === "undefined" && typeof planId === "number") { - return subscribe({ email, teamId, planId }); - } - - changePlan({ planId }); - } - - return ( -
-
-

- {name} -

-

{description}

-

- -

- -
- -
-
-
-

- What's included -

-
    - {features.map((feature) => ( -
  • - - - {feature} - -
  • - ))} -
-
-
- ); -}; - -type PlanButtonProps = { - name: Plan["name"]; - isActivePlan: boolean; - changePlan: () => void; -}; - -const PlanButton: FunctionComponent = ({ - name, - isActivePlan, - changePlan, -}) => { - return isActivePlan ? ( -
- You're currently on this plan -
- ) : ( - - ); -}; - -type PlanPriceProps = { - price: Plan["price"]; - billingSchedule: BillingSchedule; -}; - -const PlanPrice: FunctionComponent = ({ - price, - billingSchedule, -}) => { - if (price === "free") { - return ( - Free - ); - } - - return ( - <> - - ${price} - - /mo - {billingSchedule === "yearly" ? ( - - billed yearly - - ) : null} - - ); -}; diff --git a/src/components/settings/profile-informations.tsx b/src/components/settings/profile-informations.tsx index de2b38c..be99986 100644 --- a/src/components/settings/profile-informations.tsx +++ b/src/components/settings/profile-informations.tsx @@ -30,7 +30,7 @@ const ProfileInformations: FunctionComponent = () => { const [errorMessage, setErrorMessage] = useState(""); useEffect(() => { - setValue("name", user.userProfile?.name ?? ""); + setValue("name", user.userProfile?.user_metadata.name ?? ""); setValue("email", user.userProfile?.email ?? ""); }, [setValue, user.userProfile]); @@ -40,7 +40,7 @@ const ProfileInformations: FunctionComponent = () => { } try { - await user.updateUser({ name, email }); + await user.updateUser({ email, data: { name } }); } catch (error) { logger.error(error.response, "error updating user infos"); diff --git a/src/database/subscriptions.ts b/src/database/subscriptions.ts index 6c10213..aa84f5a 100644 --- a/src/database/subscriptions.ts +++ b/src/database/subscriptions.ts @@ -1,5 +1,6 @@ import type { PlanId } from "../subscription/plans"; import appLogger from "../../lib/logger"; +import supabase from "../supabase/server"; const logger = appLogger.child({ module: "subscriptions" }); @@ -32,12 +33,6 @@ export type Subscription = { updatedAt: Date; }; -type FirestoreSubscription = FirestoreEntry; - -const subscriptions = firestoreCollection( - "subscriptions", -); - type CreateSubscriptionParams = Pick< Subscription, | "userId" @@ -62,24 +57,25 @@ export async function createSubscription({ cancelUrl, lastEventTime, }: CreateSubscriptionParams): Promise { - const createdAt = FieldValue.serverTimestamp() as Timestamp; - await subscriptions.doc(paddleSubscriptionId).set({ - userId, - planId, - paddleCheckoutId, - paddleSubscriptionId, - nextBillDate, - status, - updateUrl, - cancelUrl, - lastEventTime, - createdAt, - updatedAt: createdAt, - }); + const createdAt = new Date(); + const { data } = await supabase + .from("subscription") + .insert({ + userId, + planId, + paddleCheckoutId, + paddleSubscriptionId, + nextBillDate, + status, + updateUrl, + cancelUrl, + lastEventTime, + createdAt, + updatedAt: createdAt, + }) + .throwOnError(); - const subscription = await findSubscription({ paddleSubscriptionId }); - - return subscription!; + return data![0]; } type GetSubscriptionParams = Pick; @@ -87,14 +83,15 @@ type GetSubscriptionParams = Pick; export async function findSubscription({ paddleSubscriptionId, }: GetSubscriptionParams): Promise { - const subscriptionDocument = await subscriptions - .doc(paddleSubscriptionId) - .get(); - if (!subscriptionDocument.exists) { - return; - } + const { error, data } = await supabase + .from("subscription") + .select("*") + .eq("paddleSubscriptionId", paddleSubscriptionId) + .single(); - return convertFromFirestore(subscriptionDocument.data()!); + if (error) throw error; + + return data!; } type FindUserSubscriptionParams = Pick; @@ -102,18 +99,16 @@ type FindUserSubscriptionParams = Pick; export async function findUserSubscription({ userId, }: FindUserSubscriptionParams): Promise { - const subscriptionDocumentsSnapshot = await subscriptions - .where("userId", "==", userId) - .where("status", "!=", "deleted") - .get(); - if (subscriptionDocumentsSnapshot.docs.length === 0) { - logger.warn(`No subscription found for user ${userId}`); - return null; - } + const { error, data } = await supabase + .from("subscription") + .select("*") + .eq("userId", userId) + .neq("status", "deleted") + .single(); - const subscriptionDocument = subscriptionDocumentsSnapshot.docs[0].data(); + if (error) throw error; - return convertFromFirestore(subscriptionDocument); + return data!; } type UpdateSubscriptionParams = Pick & @@ -135,17 +130,22 @@ export async function updateSubscription( update: UpdateSubscriptionParams, ): Promise { const paddleSubscriptionId = update.paddleSubscriptionId; - await subscriptions.doc(paddleSubscriptionId).set( - { + await supabase + .from("subscription") + .update({ ...update, - updatedAt: FieldValue.serverTimestamp() as Timestamp, - }, - { merge: true }, - ); + updatedAt: new Date(), + }) + .eq("paddleSubscriptionId", paddleSubscriptionId) + .throwOnError(); } export async function deleteSubscription({ paddleSubscriptionId, }: Pick): Promise { - await subscriptions.doc(paddleSubscriptionId).delete(); + await supabase + .from("subscription") + .delete() + .eq("paddleSubscriptionId", paddleSubscriptionId) + .throwOnError(); } diff --git a/src/pages/api/subscription/_subscription-cancelled.ts b/src/pages/api/subscription/_subscription-cancelled.ts index f57d557..cd358b5 100644 --- a/src/pages/api/subscription/_subscription-cancelled.ts +++ b/src/pages/api/subscription/_subscription-cancelled.ts @@ -8,7 +8,6 @@ import { SUBSCRIPTION_STATUSES, updateSubscription, } from "../../../database/subscriptions"; -import { FREE } from "../../../subscription/plans"; import appLogger from "../../../../lib/logger"; const logger = appLogger.child({ module: "subscription-cancelled" }); diff --git a/src/pages/api/subscription/_subscription-created.ts b/src/pages/api/subscription/_subscription-created.ts index dc96739..a567661 100644 --- a/src/pages/api/subscription/_subscription-created.ts +++ b/src/pages/api/subscription/_subscription-created.ts @@ -11,7 +11,7 @@ import { import { sendEmail } from "../_send-email"; import type { ApiError } from "../_types"; import appLogger from "../../../../lib/logger"; -import { PAID_PLANS } from "../../../subscription/plans"; +import { PLANS } from "../../../subscription/plans"; const logger = appLogger.child({ module: "subscription-created" }); @@ -94,7 +94,7 @@ export async function subscriptionCreatedHandler( cancelUrl, }); - const nextPlan = PAID_PLANS[planId]; + const nextPlan = PLANS[planId]; sendEmail({ subject: "Thanks for your purchase", body: `Welcome to ${nextPlan.name} plan`, diff --git a/src/pages/api/subscription/_subscription-updated.ts b/src/pages/api/subscription/_subscription-updated.ts index 0b00132..e149450 100644 --- a/src/pages/api/subscription/_subscription-updated.ts +++ b/src/pages/api/subscription/_subscription-updated.ts @@ -8,9 +8,10 @@ import { SUBSCRIPTION_STATUSES, updateSubscription, } from "../../../database/subscriptions"; -import { PAID_PLANS } from "../../../subscription/plans"; +import { PLANS } from "../../../subscription/plans"; import appLogger from "../../../../lib/logger"; import { sendEmail } from "../_send-email"; +import { findCustomer } from "../../../database/customer"; const logger = appLogger.child({ module: "subscription-updated" }); @@ -69,7 +70,7 @@ export async function subscriptionUpdated( const updateUrl = body.update_url; const cancelUrl = body.cancel_url; const planId = body.subscription_plan_id; - const nextPlan = PAID_PLANS[planId]; + const nextPlan = PLANS[planId]; await updateSubscription({ paddleSubscriptionId, planId, @@ -79,7 +80,7 @@ export async function subscriptionUpdated( cancelUrl, }); - const user = await findUser({ id: subscription.userId }); + const user = await findCustomer(subscription.userId); sendEmail({ subject: "Thanks for your purchase", diff --git a/src/pages/api/subscription/update.ts b/src/pages/api/subscription/update.ts index d4cdb0c..75661c0 100644 --- a/src/pages/api/subscription/update.ts +++ b/src/pages/api/subscription/update.ts @@ -24,7 +24,7 @@ const bodySchema = Joi.object({ export default withApiAuthRequired(async function updateSubscription( req, res, - session, + user, ) { if (req.method !== "POST") { const statusCode = 405; @@ -57,7 +57,7 @@ export default withApiAuthRequired(async function updateSubscription( const { planId }: Body = validationResult.value; const subscription = await findUserSubscription({ - teamId: session.user.teamId, + userId: user.id, }); if (!subscription) { const statusCode = 500; diff --git a/src/pages/api/user/delete-user.ts b/src/pages/api/user/delete-user.ts index 0fba0fb..570cfda 100644 --- a/src/pages/api/user/delete-user.ts +++ b/src/pages/api/user/delete-user.ts @@ -4,6 +4,7 @@ import { deleteSubscription } from "../../../database/subscriptions"; import { cancelPaddleSubscription } from "../../../subscription/_paddle-api"; import appLogger from "../../../../lib/logger"; +import supabase from "../../../supabase/server"; type Response = void | ApiError; @@ -12,7 +13,7 @@ const logger = appLogger.child({ route: "/api/user/delete-user" }); export default withApiAuthRequired(async function deleteUserHandler( req, res, - session, + user, ) { if (req.method !== "POST") { const statusCode = 405; @@ -27,34 +28,12 @@ export default withApiAuthRequired(async function deleteUserHandler( return; } - const { id: userId, role, teamId } = session.user; - const team = await findTeam({ id: teamId }); - const subscriptionId = team!.subscriptionId; - try { let actions: Promise[] = [ - deleteAuth0User({ id: userId }), - deleteUser({ id: userId, teamId }), + supabase.auth.api.deleteUser(user.id, ""), ]; - if (role === "owner") { - const teamMembers = await findUsersByTeam({ teamId }); - - teamMembers.forEach((member) => - actions.push(deleteUser({ id: member.id, teamId })), - ); - actions.push(deleteTeam({ id: teamId })); - - if (subscriptionId) { - actions.push( - cancelPaddleSubscription({ subscriptionId }), - deleteSubscription({ - paddleSubscriptionId: subscriptionId, - }), - ); - } - } - + // TODO: delete user phone number/messages await Promise.all(actions); res.status(200).end(); diff --git a/src/pages/api/user/session.ts b/src/pages/api/user/session.ts deleted file mode 100644 index 1502f45..0000000 --- a/src/pages/api/user/session.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { ApiError } from "../_types"; -import type Session from "../../../../lib/session"; -import { - sessionCache, - withApiAuthRequired, -} from "../../../../lib/session-helpers"; -import appLogger from "../../../../lib/logger"; - -type Response = Session | ApiError; - -const logger = appLogger.child({ route: "/api/user/session" }); - -export default withApiAuthRequired(async function session(req, res) { - if (req.method !== "GET") { - const statusCode = 405; - const apiError: ApiError = { - statusCode, - errorMessage: `Method ${req.method} Not Allowed`, - }; - logger.error(apiError); - - res.setHeader("Allow", ["GET"]); - res.status(statusCode).send(apiError); - return; - } - - const session = sessionCache.get(req, res)!; - - res.status(200).send(session); -});