multi tenancy stuff
This commit is contained in:
@ -1,13 +0,0 @@
|
||||
import { useSession, useQuery } from "blitz";
|
||||
|
||||
import getCurrentCustomer from "../../customers/queries/get-current-customer";
|
||||
|
||||
export default function useCurrentCustomer() {
|
||||
const session = useSession();
|
||||
const [customer] = useQuery(getCurrentCustomer, null, { enabled: Boolean(session.userId) });
|
||||
return {
|
||||
customer,
|
||||
hasFilledTwilioCredentials: Boolean(customer && customer.accountSid && customer.authToken),
|
||||
hasCompletedOnboarding: session.hasCompletedOnboarding,
|
||||
};
|
||||
}
|
11
app/core/hooks/use-current-phone-number.ts
Normal file
11
app/core/hooks/use-current-phone-number.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { useQuery } from "blitz";
|
||||
|
||||
import getCurrentPhoneNumber from "../../phone-numbers/queries/get-current-phone-number";
|
||||
import useCurrentUser from "./use-current-user";
|
||||
|
||||
export default function useUserPhoneNumber() {
|
||||
const { hasFilledTwilioCredentials } = useCurrentUser();
|
||||
const [phoneNumber] = useQuery(getCurrentPhoneNumber, {}, { enabled: hasFilledTwilioCredentials });
|
||||
|
||||
return phoneNumber;
|
||||
}
|
15
app/core/hooks/use-current-user.ts
Normal file
15
app/core/hooks/use-current-user.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { useSession, useQuery } from "blitz";
|
||||
|
||||
import getCurrentUser from "../../users/queries/get-current-user";
|
||||
|
||||
export default function useCurrentUser() {
|
||||
const session = useSession();
|
||||
const [user] = useQuery(getCurrentUser, null, { enabled: Boolean(session.userId) });
|
||||
const organization = user?.memberships[0]!.organization;
|
||||
return {
|
||||
user,
|
||||
organization,
|
||||
hasFilledTwilioCredentials: Boolean(user && organization?.twilioAccountSid && organization?.twilioAuthToken),
|
||||
hasCompletedOnboarding: session.hasCompletedOnboarding,
|
||||
};
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { useQuery } from "blitz";
|
||||
|
||||
import getCurrentCustomerPhoneNumber from "../../phone-numbers/queries/get-current-customer-phone-number";
|
||||
import useCurrentCustomer from "./use-current-customer";
|
||||
|
||||
export default function useCustomerPhoneNumber() {
|
||||
const { customer } = useCurrentCustomer();
|
||||
const hasFilledTwilioCredentials = Boolean(customer && customer.accountSid && customer.authToken);
|
||||
const [customerPhoneNumber] = useQuery(getCurrentCustomerPhoneNumber, {}, { enabled: hasFilledTwilioCredentials });
|
||||
|
||||
return customerPhoneNumber;
|
||||
}
|
@ -2,6 +2,7 @@ import { getConfig, useMutation } from "blitz";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import setNotificationSubscription from "../mutations/set-notification-subscription";
|
||||
import useCurrentPhoneNumber from "./use-current-phone-number";
|
||||
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
|
||||
@ -9,6 +10,7 @@ export default function useNotifications() {
|
||||
const isServiceWorkerSupported = "serviceWorker" in navigator;
|
||||
const [subscription, setSubscription] = useState<PushSubscription | null>(null);
|
||||
const [setNotificationSubscriptionMutation] = useMutation(setNotificationSubscription);
|
||||
const phoneNumber = useCurrentPhoneNumber();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@ -23,7 +25,7 @@ export default function useNotifications() {
|
||||
}, [isServiceWorkerSupported]);
|
||||
|
||||
async function subscribe() {
|
||||
if (!isServiceWorkerSupported) {
|
||||
if (!isServiceWorkerSupported || !phoneNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -33,7 +35,10 @@ export default function useNotifications() {
|
||||
applicationServerKey: urlBase64ToUint8Array(publicRuntimeConfig.webPush.publicKey),
|
||||
});
|
||||
setSubscription(subscription);
|
||||
await setNotificationSubscriptionMutation({ subscription: subscription.toJSON() as any }); // TODO remove as any
|
||||
await setNotificationSubscriptionMutation({
|
||||
phoneNumberId: phoneNumber.id,
|
||||
subscription: subscription.toJSON() as any,
|
||||
}); // TODO remove as any
|
||||
}
|
||||
|
||||
async function unsubscribe() {
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Routes, useRouter } from "blitz";
|
||||
|
||||
import useCurrentCustomer from "./use-current-customer";
|
||||
import useCustomerPhoneNumber from "./use-customer-phone-number";
|
||||
import useCurrentUser from "./use-current-user";
|
||||
import useCurrentPhoneNumber from "./use-current-phone-number";
|
||||
|
||||
export default function useRequireOnboarding() {
|
||||
const router = useRouter();
|
||||
const { hasFilledTwilioCredentials, hasCompletedOnboarding } = useCurrentCustomer();
|
||||
const customerPhoneNumber = useCustomerPhoneNumber();
|
||||
const { hasFilledTwilioCredentials, hasCompletedOnboarding } = useCurrentUser();
|
||||
const phoneNumber = useCurrentPhoneNumber();
|
||||
|
||||
if (hasCompletedOnboarding) {
|
||||
return;
|
||||
@ -16,12 +16,12 @@ export default function useRequireOnboarding() {
|
||||
throw router.push(Routes.StepTwo());
|
||||
}
|
||||
|
||||
/*if (!customer.paddleCustomerId || !customer.paddleSubscriptionId) {
|
||||
/*if (!user.paddleCustomerId || !user.paddleSubscriptionId) {
|
||||
throw router.push(Routes.StepTwo());
|
||||
return;
|
||||
}*/
|
||||
|
||||
if (!customerPhoneNumber) {
|
||||
if (!phoneNumber) {
|
||||
throw router.push(Routes.StepThree());
|
||||
}
|
||||
}
|
||||
|
@ -77,6 +77,8 @@ const ErrorBoundary = withRouter(
|
||||
// let Blitz ErrorBoundary handle this one
|
||||
throw error;
|
||||
}
|
||||
|
||||
// if network error and connection lost, display the auto-reload page with countdown
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
@ -3,10 +3,13 @@ import { z } from "zod";
|
||||
|
||||
import db from "../../../db";
|
||||
import appLogger from "../../../integrations/logger";
|
||||
import { enforceSuperAdminIfNotCurrentOrganization, setDefaultOrganizationId } from "../utils";
|
||||
|
||||
const logger = appLogger.child({ mutation: "set-notification-subscription" });
|
||||
|
||||
const Body = z.object({
|
||||
organizationId: z.string().optional(),
|
||||
phoneNumberId: z.string(),
|
||||
subscription: z.object({
|
||||
endpoint: z.string(),
|
||||
expirationTime: z.number().nullable(),
|
||||
@ -17,22 +20,36 @@ const Body = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export default resolver.pipe(resolver.zod(Body), resolver.authorize(), async ({ subscription }, context) => {
|
||||
const customerId = context.session.userId;
|
||||
try {
|
||||
await db.notificationSubscription.create({
|
||||
data: {
|
||||
customerId,
|
||||
endpoint: subscription.endpoint,
|
||||
expirationTime: subscription.expirationTime,
|
||||
keys_p256dh: subscription.keys.p256dh,
|
||||
keys_auth: subscription.keys.auth,
|
||||
},
|
||||
export default resolver.pipe(
|
||||
resolver.zod(Body),
|
||||
resolver.authorize(),
|
||||
setDefaultOrganizationId,
|
||||
enforceSuperAdminIfNotCurrentOrganization,
|
||||
async ({ organizationId, phoneNumberId, subscription }) => {
|
||||
const phoneNumber = await db.phoneNumber.findFirst({
|
||||
where: { id: phoneNumberId, organizationId },
|
||||
include: { organization: true },
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code !== "P2002") {
|
||||
logger.error(error);
|
||||
// we might want to `throw error`;
|
||||
if (!phoneNumber) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await db.notificationSubscription.create({
|
||||
data: {
|
||||
organizationId,
|
||||
phoneNumberId,
|
||||
endpoint: subscription.endpoint,
|
||||
expirationTime: subscription.expirationTime,
|
||||
keys_p256dh: subscription.keys.p256dh,
|
||||
keys_auth: subscription.keys.auth,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code !== "P2002") {
|
||||
logger.error(error);
|
||||
// we might want to `throw error`;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
35
app/core/utils.ts
Normal file
35
app/core/utils.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import type { Ctx } from "blitz";
|
||||
|
||||
import type { Prisma } from "../../db";
|
||||
import { GlobalRole } from "../../db";
|
||||
|
||||
function assert(condition: any, message: string): asserts condition {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
export function setDefaultOrganizationId<T extends Record<any, any>>(
|
||||
input: T,
|
||||
{ session }: Ctx,
|
||||
): T & { organizationId: Prisma.StringNullableFilter | string } {
|
||||
assert(session.orgId, "Missing session.orgId in setDefaultOrganizationId");
|
||||
if (input.organizationId) {
|
||||
// Pass through the input
|
||||
return input as T & { organizationId: string };
|
||||
} else if (session.roles?.includes(GlobalRole.SUPERADMIN)) {
|
||||
// Allow viewing any organization
|
||||
return { ...input, organizationId: { not: "" } };
|
||||
} else {
|
||||
// Set organizationId to session.orgId
|
||||
return { ...input, organizationId: session.orgId };
|
||||
}
|
||||
}
|
||||
|
||||
export function enforceSuperAdminIfNotCurrentOrganization<T extends Record<any, any>>(input: T, ctx: Ctx): T {
|
||||
assert(ctx.session.orgId, "missing session.orgId");
|
||||
assert(input.organizationId, "missing input.organizationId");
|
||||
|
||||
if (input.organizationId !== ctx.session.orgId) {
|
||||
ctx.session.$authorize(GlobalRole.SUPERADMIN);
|
||||
}
|
||||
return input;
|
||||
}
|
Reference in New Issue
Block a user