Skip to content

Commit 8a5abe6

Browse files
matt-aitkenD-K-P
authored andcommitted
Added @ splat route
1 parent 164b36e commit 8a5abe6

File tree

6 files changed

+110
-16
lines changed

6 files changed

+110
-16
lines changed

apps/webapp/app/models/admin.server.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
import { redirect } from "@remix-run/server-runtime";
12
import { prisma } from "~/db.server";
23
import { SearchParams } from "~/routes/admin._index";
4+
import {
5+
clearImpersonationId,
6+
commitImpersonationSession,
7+
setImpersonationId,
8+
} from "~/services/impersonation.server";
9+
import { requireUser } from "~/services/session.server";
310

411
const pageSize = 20;
512

@@ -219,3 +226,26 @@ export async function setV3Enabled(userId: string, id: string, v3Enabled: boolea
219226
},
220227
});
221228
}
229+
230+
export async function redirectWithImpersonation(request: Request, userId: string, path: string) {
231+
const user = await requireUser(request);
232+
if (!user.admin) {
233+
throw new Error("Unauthorized");
234+
}
235+
236+
const session = await setImpersonationId(userId, request);
237+
238+
return redirect(path, {
239+
headers: { "Set-Cookie": await commitImpersonationSession(session) },
240+
});
241+
}
242+
243+
export async function clearImpersonation(request: Request, path: string) {
244+
const session = await clearImpersonationId(request);
245+
246+
return redirect(path, {
247+
headers: {
248+
"Set-Cookie": await commitImpersonationSession(session),
249+
},
250+
});
251+
}

apps/webapp/app/routes/@.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
2+
import { clearImpersonation } from "~/models/admin.server";
3+
4+
export async function loader({ request, params }: LoaderFunctionArgs) {
5+
return clearImpersonation(request, "/admin");
6+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
2+
import { redirect } from "remix-typedjson";
3+
import { $replica } from "~/db.server";
4+
import { clearImpersonation, redirectWithImpersonation } from "~/models/admin.server";
5+
import { logger } from "~/services/logger.server";
6+
import { requireUser } from "~/services/session.server";
7+
8+
export async function loader({ request, params }: LoaderFunctionArgs) {
9+
const user = await requireUser(request);
10+
if (!user.admin) {
11+
return redirect("/");
12+
}
13+
14+
const path = params["*"];
15+
const organizationSlug = params.organizationSlug;
16+
17+
logger.debug("Impersonating user", { path, organizationSlug });
18+
19+
if (!organizationSlug) {
20+
logger.debug("Exiting impersonation mode");
21+
return clearImpersonation(request, "/admin");
22+
}
23+
24+
const org = await $replica.organization.findFirst({
25+
where: {
26+
slug: organizationSlug,
27+
deletedAt: null,
28+
},
29+
select: {
30+
members: {
31+
select: {
32+
user: {
33+
select: {
34+
id: true,
35+
confirmedBasicDetails: true,
36+
},
37+
},
38+
},
39+
},
40+
},
41+
});
42+
43+
if (!org) {
44+
logger.debug("Organization not found", { organizationSlug });
45+
return clearImpersonation(request, "/admin");
46+
}
47+
48+
const firstValidMember = org.members.find((m) => m.user.confirmedBasicDetails);
49+
50+
if (!firstValidMember) {
51+
logger.debug("No valid members found", { organizationSlug });
52+
return clearImpersonation(request, "/admin");
53+
}
54+
55+
return redirectWithImpersonation(
56+
request,
57+
firstValidMember.user.id,
58+
`/orgs/${organizationSlug}/${path}`
59+
);
60+
}

apps/webapp/app/routes/admin._index.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
TableRow,
2020
} from "~/components/primitives/Table";
2121
import { useUser } from "~/hooks/useUser";
22-
import { adminGetUsers } from "~/models/admin.server";
22+
import { adminGetUsers, redirectWithImpersonation } from "~/models/admin.server";
2323
import { commitImpersonationSession, setImpersonationId } from "~/services/impersonation.server";
2424
import { requireUserId } from "~/services/session.server";
2525
import { createSearchParams } from "~/utils/searchParams";
@@ -53,12 +53,9 @@ export async function action({ request }: ActionFunctionArgs) {
5353
const payload = Object.fromEntries(await request.formData());
5454
const { id } = FormSchema.parse(payload);
5555

56-
const session = await setImpersonationId(id, request);
57-
58-
return redirect("/", {
59-
headers: { "Set-Cookie": await commitImpersonationSession(session) },
60-
});
56+
return redirectWithImpersonation(request, id, "/");
6157
}
58+
6259
export default function AdminDashboardRoute() {
6360
const user = useUser();
6461
const { users, filters, page, pageCount } = useTypedLoaderData<typeof loader>();

apps/webapp/app/routes/admin.orgs.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,15 @@ export default function AdminDashboardRoute() {
104104
<TableCell>{org.v2Enabled ? "✅" : ""}</TableCell>
105105
<TableCell>{org.v3Enabled ? "✅" : ""}</TableCell>
106106
<TableCell>{org.deletedAt ? "☠️" : ""}</TableCell>
107-
<TableCell isSticky={true}> </TableCell>
107+
<TableCell isSticky={true}>
108+
<LinkButton
109+
to={`/@/orgs/${org.slug}`}
110+
className="mr-2"
111+
variant="tertiary/small"
112+
>
113+
Impersonate
114+
</LinkButton>
115+
</TableCell>
108116
</TableRow>
109117
);
110118
})
Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
2-
import { redirect } from "remix-typedjson";
3-
import { clearImpersonationId, commitImpersonationSession } from "~/services/impersonation.server";
2+
import { clearImpersonation } from "~/models/admin.server";
43

54
export async function action({ request }: ActionFunctionArgs) {
6-
const session = await clearImpersonationId(request);
7-
8-
return redirect("/admin", {
9-
headers: {
10-
"Set-Cookie": await commitImpersonationSession(session),
11-
},
12-
});
5+
return clearImpersonation(request, "/admin");
136
}

0 commit comments

Comments
 (0)