mirror of
https://github.com/lukevella/rallly.git
synced 2025-08-06 09:59:00 +02:00
♻️ Refactor safe action middleware (#1809)
This commit is contained in:
parent
6b4e5b3540
commit
c2701a4d4f
7 changed files with 89 additions and 78 deletions
|
@ -5,8 +5,9 @@ import { signOut } from "@/next-auth";
|
|||
import { subject } from "@casl/ability";
|
||||
import { prisma } from "@rallly/database";
|
||||
|
||||
export const deleteCurrentUserAction = authActionClient.action(
|
||||
async ({ ctx }) => {
|
||||
export const deleteCurrentUserAction = authActionClient
|
||||
.metadata({ actionName: "delete_current_user" })
|
||||
.action(async ({ ctx }) => {
|
||||
const userId = ctx.user.id;
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
|
@ -34,18 +35,9 @@ export const deleteCurrentUserAction = authActionClient.action(
|
|||
},
|
||||
});
|
||||
|
||||
ctx.posthog?.capture({
|
||||
event: "delete_account",
|
||||
distinctId: ctx.user.id,
|
||||
properties: {
|
||||
email: ctx.user.email,
|
||||
},
|
||||
});
|
||||
|
||||
await signOut();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
@ -5,7 +5,9 @@ import { subject } from "@casl/ability";
|
|||
import { prisma } from "@rallly/database";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const makeMeAdminAction = authActionClient.action(async ({ ctx }) => {
|
||||
export const makeMeAdminAction = authActionClient
|
||||
.metadata({ actionName: "make_admin" })
|
||||
.action(async ({ ctx }) => {
|
||||
if (ctx.ability.cannot("update", subject("User", ctx.user), "role")) {
|
||||
throw new ActionError({
|
||||
code: "UNAUTHORIZED",
|
||||
|
@ -23,4 +25,4 @@ export const makeMeAdminAction = authActionClient.action(async ({ ctx }) => {
|
|||
});
|
||||
|
||||
redirect("/control-panel");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
import { requireUserAbility } from "@/auth/queries";
|
||||
import { posthog } from "@rallly/posthog/server";
|
||||
import { waitUntil } from "@vercel/functions";
|
||||
import { createSafeActionClient } from "next-safe-action";
|
||||
import { createMiddleware, createSafeActionClient } from "next-safe-action";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import z from "zod";
|
||||
|
||||
type ActionErrorCode =
|
||||
| "UNAUTHORIZED"
|
||||
|
@ -28,7 +30,45 @@ export class ActionError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
const autoRevalidateMiddleware = createMiddleware().define(async ({ next }) => {
|
||||
const result = await next();
|
||||
revalidatePath("/", "layout");
|
||||
return result;
|
||||
});
|
||||
|
||||
const posthogMiddleware = createMiddleware<{
|
||||
ctx: { user: { id: string } };
|
||||
metadata: { actionName: string };
|
||||
}>().define(async ({ ctx, next, metadata }) => {
|
||||
let properties: Record<string, unknown> | undefined;
|
||||
|
||||
const result = await next({
|
||||
ctx: {
|
||||
posthog,
|
||||
captureProperties: (props?: Record<string, unknown>) => {
|
||||
properties = props;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
posthog?.capture({
|
||||
distinctId: ctx.user.id,
|
||||
event: metadata.actionName,
|
||||
properties,
|
||||
});
|
||||
|
||||
if (posthog) {
|
||||
waitUntil(posthog.shutdown());
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
export const actionClient = createSafeActionClient({
|
||||
defineMetadataSchema: () =>
|
||||
z.object({
|
||||
actionName: z.string(),
|
||||
}),
|
||||
handleServerError: async (error) => {
|
||||
if (error instanceof ActionError) {
|
||||
return error.code;
|
||||
|
@ -36,22 +76,14 @@ export const actionClient = createSafeActionClient({
|
|||
|
||||
return "INTERNAL_SERVER_ERROR";
|
||||
},
|
||||
}).use(({ next }) => {
|
||||
const result = next({
|
||||
ctx: {
|
||||
posthog,
|
||||
},
|
||||
});
|
||||
}).use(autoRevalidateMiddleware);
|
||||
|
||||
waitUntil(Promise.all([posthog?.shutdown()]));
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
export const authActionClient = actionClient.use(async ({ next }) => {
|
||||
export const authActionClient = actionClient
|
||||
.use(async ({ next }) => {
|
||||
const { user, ability } = await requireUserAbility();
|
||||
|
||||
return next({
|
||||
ctx: { user, ability },
|
||||
});
|
||||
});
|
||||
})
|
||||
.use(posthogMiddleware);
|
||||
|
|
|
@ -3,11 +3,11 @@ import { ActionError, authActionClient } from "@/features/safe-action/server";
|
|||
import { getEmailClient } from "@/utils/emails";
|
||||
import { subject } from "@casl/ability";
|
||||
import { prisma } from "@rallly/database";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import { formatEventDateTime } from "./utils";
|
||||
|
||||
export const cancelEventAction = authActionClient
|
||||
.metadata({ actionName: "cancel_event" })
|
||||
.inputSchema(
|
||||
z.object({
|
||||
eventId: z.string(),
|
||||
|
@ -43,14 +43,8 @@ export const cancelEventAction = authActionClient
|
|||
},
|
||||
});
|
||||
|
||||
revalidatePath("/", "layout");
|
||||
|
||||
ctx.posthog?.capture({
|
||||
event: "cancel_event",
|
||||
distinctId: ctx.user.id,
|
||||
properties: {
|
||||
event_id: parsedInput.eventId,
|
||||
},
|
||||
ctx.captureProperties({
|
||||
eventId: parsedInput.eventId,
|
||||
});
|
||||
|
||||
// notify attendees
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
"use server";
|
||||
import { prisma } from "@rallly/database";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { authActionClient } from "@/features/safe-action/server";
|
||||
import { setupSchema } from "./schema";
|
||||
|
||||
export const updateUserAction = authActionClient
|
||||
export const completeSetupAction = authActionClient
|
||||
.metadata({ actionName: "complete_setup" })
|
||||
.inputSchema(setupSchema)
|
||||
.action(async ({ parsedInput, ctx }) => {
|
||||
const { name, timeZone, locale } = parsedInput;
|
||||
|
@ -20,19 +20,13 @@ export const updateUserAction = authActionClient
|
|||
},
|
||||
});
|
||||
|
||||
ctx.posthog?.capture({
|
||||
event: "user_setup_completed",
|
||||
distinctId: ctx.user.id,
|
||||
properties: {
|
||||
ctx.captureProperties({
|
||||
$set: {
|
||||
name,
|
||||
timeZone,
|
||||
locale,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
revalidatePath("/", "layout");
|
||||
|
||||
redirect("/");
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ import { useSafeAction } from "@/features/safe-action/client";
|
|||
import { useTimezone } from "@/features/timezone";
|
||||
import { useTranslation } from "@/i18n/client";
|
||||
|
||||
import { updateUserAction } from "../actions";
|
||||
import { completeSetupAction } from "../actions";
|
||||
import { type SetupFormValues, setupSchema } from "../schema";
|
||||
|
||||
interface SetupFormProps {
|
||||
|
@ -30,7 +30,7 @@ interface SetupFormProps {
|
|||
export function SetupForm({ defaultValues }: SetupFormProps) {
|
||||
const { timezone } = useTimezone();
|
||||
const { i18n } = useTranslation();
|
||||
const userSetupAction = useSafeAction(updateUserAction);
|
||||
const completeSetup = useSafeAction(completeSetupAction);
|
||||
|
||||
const form = useForm<SetupFormValues>({
|
||||
resolver: zodResolver(setupSchema),
|
||||
|
@ -45,7 +45,7 @@ export function SetupForm({ defaultValues }: SetupFormProps) {
|
|||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(async (data) => {
|
||||
await userSetupAction.executeAsync(data);
|
||||
await completeSetup.executeAsync(data);
|
||||
})}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
|
@ -108,8 +108,8 @@ export function SetupForm({ defaultValues }: SetupFormProps) {
|
|||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{userSetupAction.result.serverError && (
|
||||
<FormMessage>{userSetupAction.result.serverError}</FormMessage>
|
||||
{completeSetup.result.serverError && (
|
||||
<FormMessage>{completeSetup.result.serverError}</FormMessage>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
import { ActionError, authActionClient } from "@/features/safe-action/server";
|
||||
import { subject } from "@casl/ability";
|
||||
import { prisma } from "@rallly/database";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import { getUser } from "./queries";
|
||||
|
||||
export const changeRoleAction = authActionClient
|
||||
.metadata({ actionName: "change_role" })
|
||||
.inputSchema(
|
||||
z.object({
|
||||
userId: z.string(),
|
||||
|
@ -47,11 +47,10 @@ export const changeRoleAction = authActionClient
|
|||
role,
|
||||
},
|
||||
});
|
||||
|
||||
revalidatePath("/control-panel");
|
||||
});
|
||||
|
||||
export const deleteUserAction = authActionClient
|
||||
.metadata({ actionName: "delete_user" })
|
||||
.inputSchema(
|
||||
z.object({
|
||||
userId: z.string(),
|
||||
|
@ -86,8 +85,6 @@ export const deleteUserAction = authActionClient
|
|||
},
|
||||
});
|
||||
|
||||
revalidatePath("/control-panel");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue