Skip to content

Commit

Permalink
assign user as an admin
Browse files Browse the repository at this point in the history
  • Loading branch information
Hoishin committed Jun 10, 2024
1 parent 4797ed5 commit cfde3cd
Show file tree
Hide file tree
Showing 16 changed files with 228 additions and 91 deletions.
37 changes: 37 additions & 0 deletions app/lib/session.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { type AppLoadContext } from "@remix-run/cloudflare";

export const assertSession = async (
request: Request,
context: AppLoadContext,
) => {
const session = await context.authenticator.isAuthenticated(request);
if (session) {
const user = await context.prisma.users.findUnique({
where: { id: session.userId },
select: {
id: true,
discordId: true,
displayName: true,
isAdmin: true,
},
});
if (user) {
return user;
}
}
throw new Response("not found", { status: 404 });
};

export const assertAdminSession = async (
request: Request,
context: AppLoadContext,
) => {
const user = await assertSession(request, context);
const superAdminDiscordIds =
context.cloudflare.env.SUPER_ADMIN_DISCORD_ID.split(",");
const isSuperAdmin = superAdminDiscordIds.includes(user.discordId);
if (isSuperAdmin || user.isAdmin) {
return { ...user, isSuperAdmin };
}
throw new Response("not found", { status: 404 });
};
14 changes: 0 additions & 14 deletions app/routes/_auth.admin.$/resources/users.tsx

This file was deleted.

17 changes: 0 additions & 17 deletions app/routes/_auth.admin.$/route.tsx

This file was deleted.

30 changes: 0 additions & 30 deletions app/routes/_auth.admin.api.$.tsx

This file was deleted.

28 changes: 0 additions & 28 deletions app/routes/_auth.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { ComponentProps } from "react";
import { BulkDeleteButton, Datagrid as RaDatagrid } from "react-admin";

import {
BulkDeleteButton,
Datagrid as RaDatagrid,
Edit as RaEdit,
} from "react-admin";

export const Datagrid = (props: ComponentProps<typeof RaDatagrid>) => {
return (
Expand All @@ -10,3 +13,7 @@ export const Datagrid = (props: ComponentProps<typeof RaDatagrid>) => {
/>
);
};

export const Edit = (props: ComponentProps<typeof RaEdit>) => {
return <RaEdit mutationMode="pessimistic" {...props} />;
};
42 changes: 42 additions & 0 deletions app/routes/admin.$/resources/users.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
DateField,
List,
Resource,
TextField,
DateInput,
SimpleForm,
TextInput,
BooleanInput,
BooleanField,
} from "react-admin";
import { Datagrid, Edit } from "../components/override";

const UsersList = () => (
<List>
<Datagrid rowClick="edit">
<TextField source="id" />
<TextField source="discordId" />
<TextField source="displayName" />
<BooleanField source="isAdmin" />
<DateField source="createdAt" />
<DateField source="updatedAt" />
</Datagrid>
</List>
);

const UsersEdit = () => (
<Edit>
<SimpleForm>
<TextInput source="id" readOnly />
<TextInput source="discordId" />
<TextInput source="displayName" />
<BooleanInput source="isAdmin" />
<DateInput source="createdAt" readOnly />
<DateInput source="updatedAt" readOnly />
</SimpleForm>
</Edit>
);

export const usersResource = (
<Resource name="users" list={UsersList} edit={UsersEdit} />
);
24 changes: 24 additions & 0 deletions app/routes/admin.$/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import "./styles.css";

import { dataProvider } from "ra-data-simple-prisma";
import { Admin } from "react-admin";
import { assertAdminSession } from "../../lib/session.server";
import { type LoaderFunctionArgs } from "@remix-run/cloudflare";
import { usersResource } from "./resources/users";

export const loader = async ({ request, context }: LoaderFunctionArgs) => {
await assertAdminSession(request, context);
return new Response();
};

export default () => {
return (
<Admin
disableTelemetry
basename="/admin"
dataProvider={dataProvider("/admin/api")}
>
{usersResource}
</Admin>
);
};
File renamed without changes.
68 changes: 68 additions & 0 deletions app/routes/admin.api.$.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
type ActionFunctionArgs,
type LoaderFunctionArgs,
json,
} from "@remix-run/cloudflare";
import { type RaPayload, defaultHandler } from "ra-data-simple-prisma";
import { z } from "zod";
import { assertAdminSession } from "../lib/session.server";
import { P, match } from "ts-pattern";

const getMethods = [
"getList",
"getOne",
"getMany",
"getManyReference",
] as const;
const createMethods = ["create"] as const;
const updateMethods = ["update", "updateMany"] as const;
const deleteMethods = ["delete", "deleteMany"] as const;

const handlerSchema = z.object({
resource: z.string(),
method: z.enum([
...getMethods,
...createMethods,
...updateMethods,
...deleteMethods,
]),
params: z.any(),
model: z.string().optional(),
});

const handler = async ({
request,
context,
}: LoaderFunctionArgs | ActionFunctionArgs) => {
const [user, payload] = await Promise.all([
assertAdminSession(request, context),
request.json(),
]);

const parseResult = handlerSchema.safeParse(payload);
if (!parseResult.success) {
throw new Response(parseResult.error.message, { status: 400 });
}

const data = parseResult.data;

match({ data, user }).with(
{
data: { resource: "users", method: P.not(P.union(...getMethods)) },
user: { isSuperAdmin: P.not(true) },
},
() => {
throw new Response("unauthorized", { status: 401 });
},
);

const result = await defaultHandler(
parseResult.data as RaPayload,
context.prisma,
);
return json(result);
};

export const loader = handler;

export const action = handler;
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default tseslint.config(
{
rules: {
"@typescript-eslint/only-throw-error": "off",
"@typescript-eslint/no-unused-vars": "off",
},
},
);
12 changes: 12 additions & 0 deletions migrations/0002_create-user-roles.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- CreateTable
CREATE TABLE "UserRoles" (
"id" TEXT NOT NULL PRIMARY KEY,
"userId" TEXT NOT NULL,
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "UserRoles_userId_fkey" FOREIGN KEY ("userId") REFERENCES "Users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);

-- CreateIndex
CREATE UNIQUE INDEX "UserRoles_userId_key" ON "UserRoles"("userId");
25 changes: 25 additions & 0 deletions migrations/0003_move-role-columns.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- DropIndex
DROP INDEX "UserRoles_userId_key";

-- DropTable
PRAGMA foreign_keys=off;
DROP TABLE "UserRoles";
PRAGMA foreign_keys=on;

-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Users" (
"id" TEXT NOT NULL PRIMARY KEY,
"discordId" TEXT NOT NULL,
"displayName" TEXT NOT NULL,
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_Users" ("createdAt", "discordId", "displayName", "id", "updatedAt") SELECT "createdAt", "discordId", "displayName", "id", "updatedAt" FROM "Users";
DROP TABLE "Users";
ALTER TABLE "new_Users" RENAME TO "Users";
CREATE UNIQUE INDEX "Users_discordId_key" ON "Users"("discordId");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"react-dom": "^18.3.1",
"remix-auth": "^3.7.0",
"remix-auth-discord": "^1.3.3",
"ts-pattern": "^5.1.2",
"zod": "^3.23.8"
},
"devDependencies": {
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ model Users {
id String @id @default(uuid())
discordId String @unique
displayName String
isAdmin Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

0 comments on commit cfde3cd

Please sign in to comment.