This commit is contained in:
m5r 2021-08-01 22:03:49 +08:00
parent 7d34fcd48f
commit 1489f97c14
33 changed files with 147 additions and 313 deletions

View File

@ -13,10 +13,7 @@ const bodySchema = zod.object({
email: zod.string().email(),
});
export default async function subscribeToNewsletter(
req: BlitzApiRequest,
res: BlitzApiResponse<Response>,
) {
export default async function subscribeToNewsletter(req: BlitzApiRequest, res: BlitzApiResponse<Response>) {
if (req.method !== "POST") {
const statusCode = 405;
const apiError: ApiError = {

View File

@ -30,20 +30,14 @@ export const LoginForm = (props: LoginFormProps) => {
} else {
return {
[FORM_ERROR]:
"Sorry, we had an unexpected error. Please try again. - " +
error.toString(),
"Sorry, we had an unexpected error. Please try again. - " + error.toString(),
};
}
}
}}
>
<LabeledTextField name="email" label="Email" placeholder="Email" />
<LabeledTextField
name="password"
label="Password"
placeholder="Password"
type="password"
/>
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
<div>
<Link href={Routes.ForgotPasswordPage()}>
<a>Forgot your password?</a>

View File

@ -35,12 +35,7 @@ export const SignupForm = (props: SignupFormProps) => {
}}
>
<LabeledTextField name="email" label="Email" placeholder="Email" />
<LabeledTextField
name="password"
label="Password"
placeholder="Password"
type="password"
/>
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
</Form>
</div>
);

View File

@ -17,9 +17,7 @@ jest.mock("preview-email", () => jest.fn());
describe.skip("forgotPassword mutation", () => {
it("does not throw error if user doesn't exist", async () => {
await expect(
forgotPassword({ email: "no-user@email.com" }, {} as Ctx),
).resolves.not.toThrow();
await expect(forgotPassword({ email: "no-user@email.com" }, {} as Ctx)).resolves.not.toThrow();
});
it("works correctly", async () => {

View File

@ -58,17 +58,11 @@ describe.skip("resetPassword mutation", () => {
// Expired token
await expect(
resetPassword(
{ token: expiredToken, password: newPassword, passwordConfirmation: newPassword },
mockCtx,
),
resetPassword({ token: expiredToken, password: newPassword, passwordConfirmation: newPassword }, mockCtx),
).rejects.toThrowError();
// Good token
await resetPassword(
{ token: goodToken, password: newPassword, passwordConfirmation: newPassword },
mockCtx,
);
await resetPassword({ token: goodToken, password: newPassword, passwordConfirmation: newPassword }, mockCtx);
// Delete's the token
const numberOfTokens = await db.token.count({ where: { userId: user.id } });
@ -76,8 +70,6 @@ describe.skip("resetPassword mutation", () => {
// Updates user's password
const updatedUser = await db.user.findFirst({ where: { id: user.id } });
expect(await SecurePassword.verify(updatedUser!.hashedPassword, newPassword)).toBe(
SecurePassword.VALID,
);
expect(await SecurePassword.verify(updatedUser!.hashedPassword, newPassword)).toBe(SecurePassword.VALID);
});
});

View File

@ -17,10 +17,7 @@ const ForgotPasswordPage: BlitzPage = () => {
{isSuccess ? (
<div>
<h2>Request Submitted</h2>
<p>
If your email is in our system, you will receive instructions to reset your
password shortly.
</p>
<p>If your email is in our system, you will receive instructions to reset your password shortly.</p>
</div>
) : (
<Form
@ -32,8 +29,7 @@ const ForgotPasswordPage: BlitzPage = () => {
await forgotPasswordMutation(values);
} catch (error) {
return {
[FORM_ERROR]:
"Sorry, we had an unexpected error. Please try again.",
[FORM_ERROR]: "Sorry, we had an unexpected error. Please try again.",
};
}
}}
@ -47,8 +43,6 @@ const ForgotPasswordPage: BlitzPage = () => {
ForgotPasswordPage.redirectAuthenticatedTo = Routes.Messages();
ForgotPasswordPage.getLayout = (page) => (
<BaseLayout title="Forgot Your Password?">{page}</BaseLayout>
);
ForgotPasswordPage.getLayout = (page) => <BaseLayout title="Forgot Your Password?">{page}</BaseLayout>;
export default ForgotPasswordPage;

View File

@ -41,19 +41,14 @@ const ResetPasswordPage: BlitzPage = () => {
};
} else {
return {
[FORM_ERROR]:
"Sorry, we had an unexpected error. Please try again.",
[FORM_ERROR]: "Sorry, we had an unexpected error. Please try again.",
};
}
}
}}
>
<LabeledTextField name="password" label="New Password" type="password" />
<LabeledTextField
name="passwordConfirmation"
label="Confirm New Password"
type="password"
/>
<LabeledTextField name="passwordConfirmation" label="Confirm New Password" type="password" />
</Form>
)}
</div>

View File

@ -17,9 +17,7 @@ export const LabeledTextField = forwardRef<HTMLInputElement, LabeledTextFieldPro
register,
formState: { isSubmitting, errors },
} = useFormContext();
const error = Array.isArray(errors[name])
? errors[name].join(", ")
: errors[name]?.message || errors[name];
const error = Array.isArray(errors[name]) ? errors[name].join(", ") : errors[name]?.message || errors[name];
return (
<div {...outerProps}>

View File

@ -23,12 +23,7 @@ type Props = {
const logger = appLogger.child({ module: "Layout" });
const Layout: FunctionComponent<Props> = ({
children,
title,
pageTitle = title,
hideFooter = false,
}) => {
const Layout: FunctionComponent<Props> = ({ children, title, pageTitle = title, hideFooter = false }) => {
return (
<>
{pageTitle ? (
@ -60,13 +55,7 @@ type ErrorBoundaryState =
errorMessage: string;
};
const blitzErrors = [
RedirectError,
AuthenticationError,
AuthorizationError,
CSRFTokenMismatchError,
NotFoundError,
];
const blitzErrors = [RedirectError, AuthenticationError, AuthorizationError, CSRFTokenMismatchError, NotFoundError];
const ErrorBoundary = withRouter(
class ErrorBoundary extends Component<WithRouterProps, ErrorBoundaryState> {

View File

@ -19,9 +19,7 @@ const insertIncomingMessageQueue = Queue<Payload>(
}
const encryptionKey = customer.encryptionKey;
const message = await twilio(customer.accountSid, customer.authToken)
.messages.get(messageSid)
.fetch();
const message = await twilio(customer.accountSid, customer.authToken).messages.get(messageSid).fetch();
await db.message.create({
data: {
customerId,

View File

@ -9,28 +9,25 @@ type Payload = {
messages: MessageInstance[];
};
const insertMessagesQueue = Queue<Payload>(
"api/queue/insert-messages",
async ({ messages, customerId }) => {
const customer = await db.customer.findFirst({ where: { id: customerId } });
const encryptionKey = customer!.encryptionKey;
const insertMessagesQueue = Queue<Payload>("api/queue/insert-messages", async ({ messages, customerId }) => {
const customer = await db.customer.findFirst({ where: { id: customerId } });
const encryptionKey = customer!.encryptionKey;
const sms = messages
.map<Omit<Message, "id">>((message) => ({
customerId,
content: encrypt(message.body, encryptionKey),
from: message.from,
to: message.to,
status: translateStatus(message.status),
direction: translateDirection(message.direction),
twilioSid: message.sid,
sentAt: new Date(message.dateCreated),
}))
.sort((a, b) => a.sentAt.getTime() - b.sentAt.getTime());
const sms = messages
.map<Omit<Message, "id">>((message) => ({
customerId,
content: encrypt(message.body, encryptionKey),
from: message.from,
to: message.to,
status: translateStatus(message.status),
direction: translateDirection(message.direction),
twilioSid: message.sid,
sentAt: new Date(message.dateCreated),
}))
.sort((a, b) => a.sentAt.getTime() - b.sentAt.getTime());
await db.message.createMany({ data: sms });
},
);
await db.message.createMany({ data: sms });
});
export default insertMessagesQueue;

View File

@ -17,10 +17,7 @@ const sendMessageQueue = Queue<Payload>(
const phoneNumber = await db.phoneNumber.findFirst({ where: { customerId } });
try {
const message = await twilio(
customer!.accountSid!,
customer!.authToken!,
).messages.create({
const message = await twilio(customer!.accountSid!, customer!.authToken!).messages.create({
body: content,
to,
from: phoneNumber!.phoneNumber,

View File

@ -57,12 +57,7 @@ export default async function incomingMessageHandler(req: BlitzApiRequest, res:
}
const url = `https://${serverRuntimeConfig.app.baseUrl}/api/webhook/incoming-message`;
const isRequestValid = twilio.validateRequest(
customer.authToken,
twilioSignature,
url,
req.body,
);
const isRequestValid = twilio.validateRequest(customer.authToken, twilioSignature, url, req.body);
if (!isRequestValid) {
const statusCode = 400;
const apiError: ApiError = {

View File

@ -28,8 +28,7 @@ export default function Conversation() {
const isSameNext = message.from === nextMessage?.from;
const isSamePrevious = message.from === previousMessage?.from;
const differenceInMinutes = previousMessage
? (new Date(message.sentAt).getTime() -
new Date(previousMessage.sentAt).getTime()) /
? (new Date(message.sentAt).getTime() - new Date(previousMessage.sentAt).getTime()) /
1000 /
60
: 0;
@ -63,9 +62,7 @@ export default function Conversation() {
<span
className={clsx(
"inline-block text-left w-[fit-content] p-2 rounded-lg text-white",
isOutbound
? "bg-[#3194ff] rounded-br-none"
: "bg-black rounded-bl-none",
isOutbound ? "bg-[#3194ff] rounded-br-none" : "bg-black rounded-bl-none",
)}
>
{message.content}

View File

@ -23,9 +23,7 @@ export default function ConversationsList() {
<a className="flex flex-col">
<div className="flex flex-row justify-between">
<strong>{recipient}</strong>
<div>
{new Date(lastMessage.sentAt).toLocaleString("fr-FR")}
</div>
<div>{new Date(lastMessage.sentAt).toLocaleString("fr-FR")}</div>
</div>
<div>{lastMessage.content}</div>
</a>

View File

@ -41,9 +41,7 @@ export default function NewMessageBottomSheet() {
<NewMessageArea
recipient={recipient}
onSend={() => {
router
.push(Routes.ConversationPage({ recipient }))
.then(() => setIsOpen(false));
router.push(Routes.ConversationPage({ recipient })).then(() => setIsOpen(false));
}}
/>
</Suspense>

View File

@ -16,45 +16,39 @@ const Body = z.object({
to: z.string(),
});
export default resolver.pipe(
resolver.zod(Body),
resolver.authorize(),
async ({ content, to }, context) => {
const customer = await getCurrentCustomer(null, context);
try {
await twilio(customer!.accountSid!, customer!.authToken!)
.lookups.v1.phoneNumbers(to)
.fetch();
} catch (error) {
logger.error(error);
return;
}
export default resolver.pipe(resolver.zod(Body), resolver.authorize(), async ({ content, to }, context) => {
const customer = await getCurrentCustomer(null, context);
try {
await twilio(customer!.accountSid!, customer!.authToken!).lookups.v1.phoneNumbers(to).fetch();
} catch (error) {
logger.error(error);
return;
}
const customerId = customer!.id;
const customerPhoneNumber = await getCustomerPhoneNumber({ customerId }, context);
const customerId = customer!.id;
const customerPhoneNumber = await getCustomerPhoneNumber({ customerId }, context);
const message = await db.message.create({
data: {
customerId,
to,
from: customerPhoneNumber!.phoneNumber,
direction: Direction.Outbound,
status: MessageStatus.Queued,
content: encrypt(content, customer!.encryptionKey),
sentAt: new Date(),
},
});
const message = await db.message.create({
data: {
customerId,
to,
from: customerPhoneNumber!.phoneNumber,
direction: Direction.Outbound,
status: MessageStatus.Queued,
content: encrypt(content, customer!.encryptionKey),
sentAt: new Date(),
},
});
await sendMessageQueue.enqueue(
{
id: message.id,
customerId,
to,
content,
},
{
id: message.id,
},
);
},
);
await sendMessageQueue.enqueue(
{
id: message.id,
customerId,
to,
content,
},
{
id: message.id,
},
);
});

View File

@ -2,11 +2,7 @@ import { Suspense } from "react";
import type { BlitzPage } from "blitz";
import { Routes, useRouter } from "blitz";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faLongArrowLeft,
faInfoCircle,
faPhoneAlt as faPhone,
} from "@fortawesome/pro-regular-svg-icons";
import { faLongArrowLeft, faInfoCircle, faPhoneAlt as faPhone } from "@fortawesome/pro-regular-svg-icons";
import Layout from "../../../core/layouts/layout";
import Conversation from "../../components/conversation";

View File

@ -9,23 +9,19 @@ const GetConversations = z.object({
recipient: z.string(),
});
export default resolver.pipe(
resolver.zod(GetConversations),
resolver.authorize(),
async ({ recipient }, context) => {
const customer = await getCurrentCustomer(null, context);
const conversation = await db.message.findMany({
where: {
OR: [{ from: recipient }, { to: recipient }],
},
orderBy: { sentAt: Prisma.SortOrder.asc },
});
export default resolver.pipe(resolver.zod(GetConversations), resolver.authorize(), async ({ recipient }, context) => {
const customer = await getCurrentCustomer(null, context);
const conversation = await db.message.findMany({
where: {
OR: [{ from: recipient }, { to: recipient }],
},
orderBy: { sentAt: Prisma.SortOrder.asc },
});
return conversation.map((message) => {
return {
...message,
content: decrypt(message.content, customer!.encryptionKey),
};
});
},
);
return conversation.map((message) => {
return {
...message,
content: decrypt(message.content, customer!.encryptionKey),
};
});
});

View File

@ -7,37 +7,32 @@ type Payload = {
customerId: string;
};
const setTwilioWebhooks = Queue<Payload>(
"api/queue/set-twilio-webhooks",
async ({ customerId }) => {
const customer = await db.customer.findFirst({ where: { id: customerId } });
const twimlApp = customer!.twimlAppSid
? await twilio(customer!.accountSid!, customer!.authToken!)
.applications.get(customer!.twimlAppSid)
.fetch()
: await twilio(customer!.accountSid!, customer!.authToken!).applications.create({
friendlyName: "Virtual Phone",
smsUrl: "https://phone.mokhtar.dev/api/webhook/incoming-message",
smsMethod: "POST",
voiceUrl: "https://phone.mokhtar.dev/api/webhook/incoming-call",
voiceMethod: "POST",
});
const twimlAppSid = twimlApp.sid;
const phoneNumber = await db.phoneNumber.findFirst({ where: { customerId } });
const setTwilioWebhooks = Queue<Payload>("api/queue/set-twilio-webhooks", async ({ customerId }) => {
const customer = await db.customer.findFirst({ where: { id: customerId } });
const twimlApp = customer!.twimlAppSid
? await twilio(customer!.accountSid!, customer!.authToken!).applications.get(customer!.twimlAppSid).fetch()
: await twilio(customer!.accountSid!, customer!.authToken!).applications.create({
friendlyName: "Virtual Phone",
smsUrl: "https://phone.mokhtar.dev/api/webhook/incoming-message",
smsMethod: "POST",
voiceUrl: "https://phone.mokhtar.dev/api/webhook/incoming-call",
voiceMethod: "POST",
});
const twimlAppSid = twimlApp.sid;
const phoneNumber = await db.phoneNumber.findFirst({ where: { customerId } });
await Promise.all([
db.customer.update({
where: { id: customerId },
data: { twimlAppSid },
await Promise.all([
db.customer.update({
where: { id: customerId },
data: { twimlAppSid },
}),
twilio(customer!.accountSid!, customer!.authToken!)
.incomingPhoneNumbers.get(phoneNumber!.phoneNumberSid)
.update({
smsApplicationSid: twimlAppSid,
voiceApplicationSid: twimlAppSid,
}),
twilio(customer!.accountSid!, customer!.authToken!)
.incomingPhoneNumbers.get(phoneNumber!.phoneNumberSid)
.update({
smsApplicationSid: twimlAppSid,
voiceApplicationSid: twimlAppSid,
}),
]);
},
);
]);
});
export default setTwilioWebhooks;

View File

@ -17,13 +17,8 @@ export default function App({ Component, pageProps }: AppProps) {
const getLayout = Component.getLayout || ((page) => page);
return (
<ErrorBoundary
FallbackComponent={RootErrorFallback}
onReset={useQueryErrorResetBoundary().reset}
>
<Suspense fallback="Silence, ca pousse">
{getLayout(<Component {...pageProps} />)}
</Suspense>
<ErrorBoundary FallbackComponent={RootErrorFallback} onReset={useQueryErrorResetBoundary().reset}>
<Suspense fallback="Silence, ca pousse">{getLayout(<Component {...pageProps} />)}</Suspense>
</ErrorBoundary>
);
}
@ -32,18 +27,8 @@ function RootErrorFallback({ error, resetErrorBoundary }: ErrorFallbackProps) {
if (error instanceof AuthenticationError) {
return <LoginForm onSuccess={resetErrorBoundary} />;
} else if (error instanceof AuthorizationError) {
return (
<ErrorComponent
statusCode={error.statusCode}
title="Sorry, you are not authorized to access this"
/>
);
return <ErrorComponent statusCode={error.statusCode} title="Sorry, you are not authorized to access this" />;
} else {
return (
<ErrorComponent
statusCode={error.statusCode || 400}
title={error.message || error.name}
/>
);
return <ErrorComponent statusCode={error.statusCode || 400} title={error.message || error.name} />;
}
}

View File

@ -138,9 +138,8 @@ const Home: BlitzPage = () => {
body {
padding: 0;
margin: 0;
font-family: "Libre Franklin", -apple-system, BlinkMacSystemFont, Segoe UI,
Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,
sans-serif;
font-family: "Libre Franklin", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
* {

View File

@ -20,9 +20,7 @@ const fetchCallsQueue = Queue<Payload>("api/queue/fetch-calls", async ({ custome
to: phoneNumber!.phoneNumber,
}),
]);
const calls = [...callsSent, ...callsReceived].sort(
(a, b) => a.dateCreated.getTime() - b.dateCreated.getTime(),
);
const calls = [...callsSent, ...callsReceived].sort((a, b) => a.dateCreated.getTime() - b.dateCreated.getTime());
await insertCallsQueue.enqueue(
{

View File

@ -1,8 +1,7 @@
import { paginate, resolver } from "blitz";
import db, { Prisma, Customer } from "db";
interface GetPhoneCallsInput
extends Pick<Prisma.PhoneCallFindManyArgs, "where" | "orderBy" | "skip" | "take"> {
interface GetPhoneCallsInput extends Pick<Prisma.PhoneCallFindManyArgs, "where" | "orderBy" | "skip" | "take"> {
customerId: Customer["id"];
}

View File

@ -82,14 +82,8 @@ export default function Alert({ title, message, variant }: Props) {
<div className="flex">
<div className="flex-shrink-0">{variantProperties.icon}</div>
<div className="ml-3">
<h3
className={`text-sm leading-5 font-medium ${variantProperties.titleTextColor}`}
>
{title}
</h3>
<div className={`mt-2 text-sm leading-5 ${variantProperties.messageTextColor}`}>
{message}
</div>
<h3 className={`text-sm leading-5 font-medium ${variantProperties.titleTextColor}`}>{title}</h3>
<div className={`mt-2 text-sm leading-5 ${variantProperties.messageTextColor}`}>{message}</div>
</div>
</div>
</div>

View File

@ -28,38 +28,28 @@ export default function DangerZone() {
<SettingsSection title="Danger Zone" description="Highway to the Danger Zone 𝅘𝅥𝅮">
<div className="shadow border border-red-300 sm:rounded-md sm:overflow-hidden">
<div className="flex justify-between items-center flex-row px-4 py-5 bg-white sm:p-6">
<p>
Once you delete your account, all of its data will be permanently deleted.
</p>
<p>Once you delete your account, all of its data will be permanently deleted.</p>
<span className="text-base font-medium">
<Button
variant="error"
type="button"
onClick={() => setIsConfirmationModalOpen(true)}
>
<Button variant="error" type="button" onClick={() => setIsConfirmationModalOpen(true)}>
Delete my account
</Button>
</span>
</div>
</div>
<Modal
initialFocus={modalCancelButtonRef}
isOpen={isConfirmationModalOpen}
onClose={closeModal}
>
<Modal initialFocus={modalCancelButtonRef} isOpen={isConfirmationModalOpen} onClose={closeModal}>
<div className="md:flex md:items-start">
<div className="mt-3 text-center md:mt-0 md:ml-4 md:text-left">
<ModalTitle>Delete my account</ModalTitle>
<div className="mt-2 text-sm text-gray-500">
<p>
Are you sure you want to delete your account? Your subscription will
be cancelled and your data permanently deleted.
Are you sure you want to delete your account? Your subscription will be cancelled and
your data permanently deleted.
</p>
<p>
You are free to create a new account with the same email address if
you ever wish to come back.
You are free to create a new account with the same email address if you ever wish to
come back.
</p>
</div>
</div>

View File

@ -32,9 +32,7 @@ const Modal: FunctionComponent<Props> = ({ children, initialFocus, isOpen, onClo
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span className="hidden md:inline-block md:align-middle md:h-screen">
&#8203;
</span>
<span className="hidden md:inline-block md:align-middle md:h-screen">&#8203;</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"

View File

@ -61,31 +61,20 @@ const ProfileInformations: FunctionComponent = () => {
<form onSubmit={onSubmit}>
{errorMessage ? (
<div className="mb-8">
<Alert
title="Oops, there was an issue"
message={errorMessage}
variant="error"
/>
<Alert title="Oops, there was an issue" message={errorMessage} variant="error" />
</div>
) : null}
{isSubmitSuccessful ? (
<div className="mb-8">
<Alert
title="Saved successfully"
message="Your changes have been saved."
variant="success"
/>
<Alert title="Saved successfully" message="Your changes have been saved." variant="success" />
</div>
) : null}
<div className="shadow sm:rounded-md sm:overflow-hidden">
<div className="px-4 py-5 bg-white space-y-6 sm:p-6">
<div className="col-span-3 sm:col-span-2">
<label
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-700"
>
<label htmlFor="name" className="block text-sm font-medium leading-5 text-gray-700">
Name
</label>
<div className="mt-1 rounded-md shadow-sm">
@ -101,10 +90,7 @@ const ProfileInformations: FunctionComponent = () => {
</div>
<div>
<label
htmlFor="email"
className="block text-sm font-medium leading-5 text-gray-700"
>
<label htmlFor="email" className="block text-sm font-medium leading-5 text-gray-700">
Email address
</label>
<div className="mt-1 rounded-md shadow-sm">

View File

@ -60,21 +60,13 @@ const UpdatePassword: FunctionComponent = () => {
<form onSubmit={onSubmit}>
{errorMessage ? (
<div className="mb-8">
<Alert
title="Oops, there was an issue"
message={errorMessage}
variant="error"
/>
<Alert title="Oops, there was an issue" message={errorMessage} variant="error" />
</div>
) : null}
{isSubmitSuccessful ? (
<div className="mb-8">
<Alert
title="Saved successfully"
message="Your changes have been saved."
variant="success"
/>
<Alert title="Saved successfully" message="Your changes have been saved." variant="success" />
</div>
) : null}

View File

@ -15,16 +15,12 @@ const navigation = [
{
name: "Account",
href: "/settings/account",
icon: ({ className = "w-8 h-8" }) => (
<FontAwesomeIcon size="lg" className={className} icon={faUserCircle} />
),
icon: ({ className = "w-8 h-8" }) => <FontAwesomeIcon size="lg" className={className} icon={faUserCircle} />,
},
{
name: "Billing",
href: "/settings/billing",
icon: ({ className = "w-8 h-8" }) => (
<FontAwesomeIcon size="lg" className={className} icon={faCreditCard} />
),
icon: ({ className = "w-8 h-8" }) => <FontAwesomeIcon size="lg" className={className} icon={faCreditCard} />,
},
];
/* eslint-enable react/display-name */

View File

@ -7,9 +7,7 @@ const IV_LENGTH = 16;
const ALGORITHM = "aes-256-cbc";
export function encrypt(text: string, encryptionKey: Buffer | string) {
const encryptionKeyAsBuffer = Buffer.isBuffer(encryptionKey)
? encryptionKey
: Buffer.from(encryptionKey, "hex");
const encryptionKeyAsBuffer = Buffer.isBuffer(encryptionKey) ? encryptionKey : Buffer.from(encryptionKey, "hex");
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, encryptionKeyAsBuffer, iv);
const encrypted = cipher.update(text);
@ -19,9 +17,7 @@ export function encrypt(text: string, encryptionKey: Buffer | string) {
}
export function decrypt(encryptedHexText: string, encryptionKey: Buffer | string) {
const encryptionKeyAsBuffer = Buffer.isBuffer(encryptionKey)
? encryptionKey
: Buffer.from(encryptionKey, "hex");
const encryptionKeyAsBuffer = Buffer.isBuffer(encryptionKey) ? encryptionKey : Buffer.from(encryptionKey, "hex");
const [hexIv, hexText] = encryptedHexText.split(":");
const iv = Buffer.from(hexIv!, "hex");
const encryptedText = Buffer.from(hexText!, "hex");

View File

@ -35,9 +35,7 @@ export function forgotPasswordMailer({ to, token }: ResetPasswordMailer) {
if (process.env.NODE_ENV === "production") {
// TODO - send the production email, like this:
// await postmark.sendEmail(msg)
throw new Error(
"No production email implementation in mailers/forgotPasswordMailer",
);
throw new Error("No production email implementation in mailers/forgotPasswordMailer");
} else {
// Preview email in the browser
await previewEmail(msg);

View File

@ -24,17 +24,12 @@ export * from "@testing-library/react";
// router: { pathname: '/my-custom-pathname' },
// });
// --------------------------------------------------
export function render(
ui: RenderUI,
{ wrapper, router, dehydratedState, ...options }: RenderOptions = {},
) {
export function render(ui: RenderUI, { wrapper, router, dehydratedState, ...options }: RenderOptions = {}) {
if (!wrapper) {
// Add a default context wrapper if one isn't supplied from the test
wrapper = ({ children }) => (
<BlitzProvider dehydratedState={dehydratedState}>
<RouterContext.Provider value={{ ...mockRouter, ...router }}>
{children}
</RouterContext.Provider>
<RouterContext.Provider value={{ ...mockRouter, ...router }}>{children}</RouterContext.Provider>
</BlitzProvider>
);
}
@ -52,17 +47,12 @@ export function render(
// router: { pathname: '/my-custom-pathname' },
// });
// --------------------------------------------------
export function renderHook(
hook: RenderHook,
{ wrapper, router, dehydratedState, ...options }: RenderHookOptions = {},
) {
export function renderHook(hook: RenderHook, { wrapper, router, dehydratedState, ...options }: RenderHookOptions = {}) {
if (!wrapper) {
// Add a default context wrapper if one isn't supplied from the test
wrapper = ({ children }) => (
<BlitzProvider dehydratedState={dehydratedState}>
<RouterContext.Provider value={{ ...mockRouter, ...router }}>
{children}
</RouterContext.Provider>
<RouterContext.Provider value={{ ...mockRouter, ...router }}>{children}</RouterContext.Provider>
</BlitzProvider>
);
}