-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: add some helpers & update metadata
- Loading branch information
Showing
10 changed files
with
444 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,12 @@ | ||
NEXT_PUBLIC_SITE_URL= | ||
|
||
ACCESS_TOKEN_KEY='user_access_token' | ||
REFRESH_TOKEN_KEY='user_refresh_token' | ||
|
||
NEXT_PUBLIC_MEASUREMENT_ID= | ||
NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID= | ||
SENTRY_DSN= | ||
SENTRY_AUTH_TOKEN= | ||
|
||
NEXT_PUBLIC_SUPABASE_URL=https://odwqofjfevmgoakhmaav.supabase.co | ||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9kd3FvZmpmZXZtZ29ha2htYWF2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDk1NDY3NDYsImV4cCI6MjAyNTEyMjc0Nn0.m0sa2EhmHo0OKBwHJOhuO-YkHA-vtbyH-ah6eBppVpc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,12 @@ | ||
NEXT_PUBLIC_SITE_URL= | ||
|
||
ACCESS_TOKEN_KEY= | ||
REFRESH_TOKEN_KEY= | ||
|
||
NEXT_PUBLIC_MEASUREMENT_ID= | ||
NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID= | ||
SENTRY_DSN= | ||
SENTRY_AUTH_TOKEN= | ||
|
||
NEXT_PUBLIC_SUPABASE_URL= | ||
NEXT_PUBLIC_SUPABASE_ANON_KEY= |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import type { NextRequest } from 'next/server'; | ||
|
||
import { updateSession } from '@/utils/supabase/middleware'; | ||
|
||
export async function middleware(request: NextRequest) { | ||
return await updateSession(request); | ||
} | ||
|
||
export const config = { | ||
matcher: [ | ||
/* | ||
* Match all request paths except: | ||
* - _next/static (static files) | ||
* - _next/image (image optimization files) | ||
* - favicon.ico (favicon file) | ||
* - images - .svg, .png, .jpg, .jpeg, .gif, .webp | ||
* Feel free to modify this pattern to include more paths. | ||
*/ | ||
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export const getURL = (path: string = '') => { | ||
// Check if NEXT_PUBLIC_SITE_URL is set and non-empty. Set this to your site URL in production env. | ||
let url = | ||
process?.env?.NEXT_PUBLIC_SITE_URL && process.env.NEXT_PUBLIC_SITE_URL.trim() !== '' | ||
? process.env.NEXT_PUBLIC_SITE_URL | ||
: // If not set, check for NEXT_PUBLIC_VERCEL_URL, which is automatically set by Vercel. | ||
process?.env?.NEXT_PUBLIC_VERCEL_URL && process.env.NEXT_PUBLIC_VERCEL_URL.trim() !== '' | ||
? process.env.NEXT_PUBLIC_VERCEL_URL | ||
: // If neither is set, default to localhost for local development. | ||
'http://localhost:3000/'; | ||
|
||
// Trim the URL and remove trailing slash if exists. | ||
url = url.replace(/\/+$/, ''); | ||
// Make sure to include `https://` when not localhost. | ||
url = url.includes('http') ? url : `https://${url}`; | ||
// Ensure path starts without a slash to avoid double slashes in the final URL. | ||
path = path.replace(/^\/+/, ''); | ||
|
||
// Concatenate the URL and the path. | ||
return path ? `${url}/${path}` : url; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
// import { createClient } from '@supabase/supabase-js'; | ||
// import type Stripe from 'stripe'; | ||
// import type { Database, Tables, TablesInsert } from 'types_db'; | ||
|
||
// import { toDateTime } from '@/utils/helpers'; | ||
// import { stripe } from '@/utils/stripe/config'; | ||
|
||
// type Product = Tables<'products'>; | ||
// type Price = Tables<'prices'>; | ||
|
||
// // Change to control trial period length | ||
// const TRIAL_PERIOD_DAYS = 0; | ||
|
||
// // Note: supabaseAdmin uses the SERVICE_ROLE_KEY which you must only use in a secure server-side context | ||
// // as it has admin privileges and overwrites RLS policies! | ||
// const supabaseAdmin = createClient<Database>( | ||
// process.env.NEXT_PUBLIC_SUPABASE_URL || '', | ||
// process.env.SUPABASE_SERVICE_ROLE_KEY || '' | ||
// ); | ||
|
||
// const upsertProductRecord = async (product: Stripe.Product) => { | ||
// const productData: Product = { | ||
// id: product.id, | ||
// active: product.active, | ||
// name: product.name, | ||
// description: product.description ?? null, | ||
// image: product.images?.[0] ?? null, | ||
// metadata: product.metadata, | ||
// }; | ||
|
||
// const { error: upsertError } = await supabaseAdmin.from('products').upsert([productData]); | ||
// if (upsertError) throw new Error(`Product insert/update failed: ${upsertError.message}`); | ||
// console.log(`Product inserted/updated: ${product.id}`); | ||
// }; | ||
|
||
// const upsertPriceRecord = async (price: Stripe.Price, retryCount = 0, maxRetries = 3) => { | ||
// const priceData: Price = { | ||
// id: price.id, | ||
// product_id: typeof price.product === 'string' ? price.product : '', | ||
// active: price.active, | ||
// currency: price.currency, | ||
// type: price.type, | ||
// unit_amount: price.unit_amount ?? null, | ||
// interval: price.recurring?.interval ?? null, | ||
// interval_count: price.recurring?.interval_count ?? null, | ||
// trial_period_days: price.recurring?.trial_period_days ?? TRIAL_PERIOD_DAYS, | ||
// }; | ||
|
||
// const { error: upsertError } = await supabaseAdmin.from('prices').upsert([priceData]); | ||
|
||
// if (upsertError?.message.includes('foreign key constraint')) { | ||
// if (retryCount < maxRetries) { | ||
// console.log(`Retry attempt ${retryCount + 1} for price ID: ${price.id}`); | ||
// await new Promise((resolve) => setTimeout(resolve, 2000)); | ||
// await upsertPriceRecord(price, retryCount + 1, maxRetries); | ||
// } else { | ||
// throw new Error( | ||
// `Price insert/update failed after ${maxRetries} retries: ${upsertError.message}` | ||
// ); | ||
// } | ||
// } else if (upsertError) { | ||
// throw new Error(`Price insert/update failed: ${upsertError.message}`); | ||
// } else { | ||
// console.log(`Price inserted/updated: ${price.id}`); | ||
// } | ||
// }; | ||
|
||
// const deleteProductRecord = async (product: Stripe.Product) => { | ||
// const { error: deletionError } = await supabaseAdmin | ||
// .from('products') | ||
// .delete() | ||
// .eq('id', product.id); | ||
// if (deletionError) throw new Error(`Product deletion failed: ${deletionError.message}`); | ||
// console.log(`Product deleted: ${product.id}`); | ||
// }; | ||
|
||
// const deletePriceRecord = async (price: Stripe.Price) => { | ||
// const { error: deletionError } = await supabaseAdmin.from('prices').delete().eq('id', price.id); | ||
// if (deletionError) throw new Error(`Price deletion failed: ${deletionError.message}`); | ||
// console.log(`Price deleted: ${price.id}`); | ||
// }; | ||
|
||
// const upsertCustomerToSupabase = async (uuid: string, customerId: string) => { | ||
// const { error: upsertError } = await supabaseAdmin | ||
// .from('customers') | ||
// .upsert([{ id: uuid, stripe_customer_id: customerId }]); | ||
|
||
// if (upsertError) | ||
// throw new Error(`Supabase customer record creation failed: ${upsertError.message}`); | ||
|
||
// return customerId; | ||
// }; | ||
|
||
// const createCustomerInStripe = async (uuid: string, email: string) => { | ||
// const customerData = { metadata: { supabaseUUID: uuid }, email: email }; | ||
// const newCustomer = await stripe.customers.create(customerData); | ||
// if (!newCustomer) throw new Error('Stripe customer creation failed.'); | ||
|
||
// return newCustomer.id; | ||
// }; | ||
|
||
// const createOrRetrieveCustomer = async ({ email, uuid }: { email: string; uuid: string }) => { | ||
// // Check if the customer already exists in Supabase | ||
// const { data: existingSupabaseCustomer, error: queryError } = await supabaseAdmin | ||
// .from('customers') | ||
// .select('*') | ||
// .eq('id', uuid) | ||
// .maybeSingle(); | ||
|
||
// if (queryError) { | ||
// throw new Error(`Supabase customer lookup failed: ${queryError.message}`); | ||
// } | ||
|
||
// // Retrieve the Stripe customer ID using the Supabase customer ID, with email fallback | ||
// let stripeCustomerId: string | undefined; | ||
// if (existingSupabaseCustomer?.stripe_customer_id) { | ||
// const existingStripeCustomer = await stripe.customers.retrieve( | ||
// existingSupabaseCustomer.stripe_customer_id | ||
// ); | ||
// stripeCustomerId = existingStripeCustomer.id; | ||
// } else { | ||
// // If Stripe ID is missing from Supabase, try to retrieve Stripe customer ID by email | ||
// const stripeCustomers = await stripe.customers.list({ email: email }); | ||
// stripeCustomerId = stripeCustomers.data.length > 0 ? stripeCustomers.data[0].id : undefined; | ||
// } | ||
|
||
// // If still no stripeCustomerId, create a new customer in Stripe | ||
// const stripeIdToInsert = stripeCustomerId | ||
// ? stripeCustomerId | ||
// : await createCustomerInStripe(uuid, email); | ||
// if (!stripeIdToInsert) throw new Error('Stripe customer creation failed.'); | ||
|
||
// if (existingSupabaseCustomer && stripeCustomerId) { | ||
// // If Supabase has a record but doesn't match Stripe, update Supabase record | ||
// if (existingSupabaseCustomer.stripe_customer_id !== stripeCustomerId) { | ||
// const { error: updateError } = await supabaseAdmin | ||
// .from('customers') | ||
// .update({ stripe_customer_id: stripeCustomerId }) | ||
// .eq('id', uuid); | ||
|
||
// if (updateError) | ||
// throw new Error(`Supabase customer record update failed: ${updateError.message}`); | ||
// console.warn(`Supabase customer record mismatched Stripe ID. Supabase record updated.`); | ||
// } | ||
// // If Supabase has a record and matches Stripe, return Stripe customer ID | ||
// return stripeCustomerId; | ||
// } else { | ||
// console.warn(`Supabase customer record was missing. A new record was created.`); | ||
|
||
// // If Supabase has no record, create a new record and return Stripe customer ID | ||
// const upsertedStripeCustomer = await upsertCustomerToSupabase(uuid, stripeIdToInsert); | ||
// if (!upsertedStripeCustomer) throw new Error('Supabase customer record creation failed.'); | ||
|
||
// return upsertedStripeCustomer; | ||
// } | ||
// }; | ||
|
||
// /** | ||
// * Copies the billing details from the payment method to the customer object. | ||
// */ | ||
// const copyBillingDetailsToCustomer = async (uuid: string, payment_method: Stripe.PaymentMethod) => { | ||
// //Todo: check this assertion | ||
// const customer = payment_method.customer as string; | ||
// const { name, phone, address } = payment_method.billing_details; | ||
// if (!name || !phone || !address) return; | ||
|
||
// await stripe.customers.update(customer, { name, phone, address }); | ||
// const { error: updateError } = await supabaseAdmin | ||
// .from('users') | ||
// .update({ | ||
// billing_address: { ...address }, | ||
// payment_method: { ...payment_method[payment_method.type] }, | ||
// }) | ||
// .eq('id', uuid); | ||
// if (updateError) throw new Error(`Customer update failed: ${updateError.message}`); | ||
// }; | ||
|
||
// const manageSubscriptionStatusChange = async ( | ||
// subscriptionId: string, | ||
// customerId: string, | ||
// createAction = false | ||
// ) => { | ||
// // Get customer's UUID from mapping table. | ||
// const { data: customerData, error: noCustomerError } = await supabaseAdmin | ||
// .from('customers') | ||
// .select('id') | ||
// .eq('stripe_customer_id', customerId) | ||
// .single(); | ||
|
||
// if (noCustomerError) throw new Error(`Customer lookup failed: ${noCustomerError.message}`); | ||
|
||
// const { id: uuid } = customerData!; | ||
|
||
// const subscription = await stripe.subscriptions.retrieve(subscriptionId, { | ||
// expand: ['default_payment_method'], | ||
// }); | ||
// // Upsert the latest status of the subscription object. | ||
// const subscriptionData: TablesInsert<'subscriptions'> = { | ||
// id: subscription.id, | ||
// user_id: uuid, | ||
// metadata: subscription.metadata, | ||
// status: subscription.status, | ||
// price_id: subscription.items.data[0].price.id, | ||
// //TODO check quantity on subscription | ||
|
||
// quantity: subscription.quantity, | ||
// cancel_at_period_end: subscription.cancel_at_period_end, | ||
// cancel_at: subscription.cancel_at ? toDateTime(subscription.cancel_at).toISOString() : null, | ||
// canceled_at: subscription.canceled_at | ||
// ? toDateTime(subscription.canceled_at).toISOString() | ||
// : null, | ||
// current_period_start: toDateTime(subscription.current_period_start).toISOString(), | ||
// current_period_end: toDateTime(subscription.current_period_end).toISOString(), | ||
// created: toDateTime(subscription.created).toISOString(), | ||
// ended_at: subscription.ended_at ? toDateTime(subscription.ended_at).toISOString() : null, | ||
// trial_start: subscription.trial_start | ||
// ? toDateTime(subscription.trial_start).toISOString() | ||
// : null, | ||
// trial_end: subscription.trial_end ? toDateTime(subscription.trial_end).toISOString() : null, | ||
// }; | ||
|
||
// const { error: upsertError } = await supabaseAdmin | ||
// .from('subscriptions') | ||
// .upsert([subscriptionData]); | ||
// if (upsertError) throw new Error(`Subscription insert/update failed: ${upsertError.message}`); | ||
// console.log(`Inserted/updated subscription [${subscription.id}] for user [${uuid}]`); | ||
|
||
// // For a new subscription copy the billing details to the customer object. | ||
// // NOTE: This is a costly operation and should happen at the very end. | ||
// if (createAction && subscription.default_payment_method && uuid) | ||
// //@ts-ignore | ||
// await copyBillingDetailsToCustomer( | ||
// uuid, | ||
// subscription.default_payment_method as Stripe.PaymentMethod | ||
// ); | ||
// }; | ||
|
||
// export { | ||
// createOrRetrieveCustomer, | ||
// deletePriceRecord, | ||
// deleteProductRecord, | ||
// manageSubscriptionStatusChange, | ||
// upsertPriceRecord, | ||
// upsertProductRecord, | ||
// }; |
Oops, something went wrong.