From d860c1f1e0e91d595b976239494e4e09409cbb91 Mon Sep 17 00:00:00 2001 From: Keiichiro Amemiya Date: Sun, 11 Feb 2024 06:47:59 +0900 Subject: [PATCH] split admin --- local-proxy/h2o.yaml | 8 +- package-lock.json | 91 +++++++++++++++++++ package.json | 5 +- prisma/schema.prisma | 1 + prisma/seed.ts | 17 +++- .../admin.$/route.tsx => admin/app.tsx} | 37 ++------ src/admin/auth-provider.ts | 35 +++++++ .../routes/admin.$ => admin}/data-provider.ts | 25 +++-- src/admin/entry.tsx | 8 ++ .../routes/admin.$ => admin}/event-group.tsx | 0 src/{app/routes/admin.$ => admin}/event.tsx | 2 +- src/{app/routes/admin.$ => admin}/index.css | 0 src/admin/index.html | 12 +++ src/{app/routes/admin.$ => admin}/run.tsx | 2 +- src/admin/trpc.ts | 11 +++ src/{app/routes/admin.$ => admin}/user.tsx | 0 src/admin/vite.config.ts | 17 ++++ src/app/components/header.tsx | 2 +- src/app/root.tsx | 47 +++++++++- src/app/routes/_base.tsx | 53 ----------- .../routes/{_base._index.tsx => _index.tsx} | 0 ...tName.tsx => archives.$eventShortName.tsx} | 0 ...rchives._index.tsx => archives._index.tsx} | 0 .../{_base.register.tsx => register.tsx} | 0 ...._index.tsx => sign-in-options._index.tsx} | 0 ...l.tsx => sign-in-options.change-email.tsx} | 0 ....sign-in._index.tsx => sign-in._index.tsx} | 0 ...se.sign-in.email.tsx => sign-in.email.tsx} | 0 ...ail-change.tsx => verify-email-change.tsx} | 0 src/app/trpc.tsx | 14 +-- src/server/routes/authentication.ts | 23 ++++- 31 files changed, 281 insertions(+), 129 deletions(-) rename src/{app/routes/admin.$/route.tsx => admin/app.tsx} (59%) create mode 100644 src/admin/auth-provider.ts rename src/{app/routes/admin.$ => admin}/data-provider.ts (63%) create mode 100644 src/admin/entry.tsx rename src/{app/routes/admin.$ => admin}/event-group.tsx (100%) rename src/{app/routes/admin.$ => admin}/event.tsx (96%) rename src/{app/routes/admin.$ => admin}/index.css (100%) create mode 100644 src/admin/index.html rename src/{app/routes/admin.$ => admin}/run.tsx (95%) create mode 100644 src/admin/trpc.ts rename src/{app/routes/admin.$ => admin}/user.tsx (100%) create mode 100644 src/admin/vite.config.ts delete mode 100644 src/app/routes/_base.tsx rename src/app/routes/{_base._index.tsx => _index.tsx} (100%) rename src/app/routes/{_base.archives.$eventShortName.tsx => archives.$eventShortName.tsx} (100%) rename src/app/routes/{_base.archives._index.tsx => archives._index.tsx} (100%) rename src/app/routes/{_base.register.tsx => register.tsx} (100%) rename src/app/routes/{_base.sign-in-options._index.tsx => sign-in-options._index.tsx} (100%) rename src/app/routes/{_base.sign-in-options.change-email.tsx => sign-in-options.change-email.tsx} (100%) rename src/app/routes/{_base.sign-in._index.tsx => sign-in._index.tsx} (100%) rename src/app/routes/{_base.sign-in.email.tsx => sign-in.email.tsx} (100%) rename src/app/routes/{_base.verify-email-change.tsx => verify-email-change.tsx} (100%) diff --git a/local-proxy/h2o.yaml b/local-proxy/h2o.yaml index 4d39499..d27158c 100644 --- a/local-proxy/h2o.yaml +++ b/local-proxy/h2o.yaml @@ -8,8 +8,6 @@ hosts: paths: /: proxy.reverse.url: http://host.docker.internal:3000 - /assets: - file.dir: /www/assets - expires: 1 year - header.set: - - "X-Content-Type-Options: nosniff" + /admin: + proxy.reverse.url: http://host.docker.internal:4000/admin + proxy.tunnel: ON diff --git a/package-lock.json b/package-lock.json index cc26397..b215879 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", "@typescript-eslint/eslint-plugin": "^6.21.0", + "@vitejs/plugin-react": "^4.2.1", "cross-env": "^7.0.3", "esbuild": "0.20.0", "eslint": "^8.56.0", @@ -1259,6 +1260,36 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", + "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", + "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-typescript": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz", @@ -5924,6 +5955,47 @@ "@types/estree": "*" } }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -7023,6 +7095,25 @@ "integrity": "sha512-17kVyLq3ePTKOkveHxXuIJZtGYs+cSoev7BlP+Lf4916qfDhk/HBjvlYDe8egrea7LNPHKwSZJK/bzZC+Q6AwQ==", "dev": true }, + "node_modules/@vitejs/plugin-react": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", + "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.5", + "@babel/plugin-transform-react-jsx-self": "^7.23.3", + "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, "node_modules/@vladfrangu/async_event_emitter": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.4.tgz", diff --git a/package.json b/package.json index 06ec893..4f91562 100644 --- a/package.json +++ b/package.json @@ -2,15 +2,17 @@ "private": true, "type": "module", "scripts": { - "build": "panda codegen && cross-env NODE_ENV=production run-p build:*", + "build": "panda codegen && cross-env NODE_ENV=production run-s build:*", "build:types": "tsc --noEmit", "build:remix": "remix vite:build", "build:server": "esbuild src/server/main.ts --bundle --format=esm --sourcemap --platform=node --target=node20 --outdir=out --packages=external --external:./build/server/index.js", + "build:admin": "cd src/admin && vite build", "dev": "panda codegen && run-p dev:*", "dev:remix": "onchange src/server src/shared -ik -- tsx --env-file=.env --env-file=.env.local src/server/main.ts", "dev:db": "docker compose up", "dev:types": "tsc --noEmit --watch --preserveWatchOutput", "dev:prisma": "prisma generate --watch", + "dev:admin": "cd src/admin && npx vite", "lint": "eslint --ext .ts,.tsx src" }, "dependencies": { @@ -54,6 +56,7 @@ "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", "@typescript-eslint/eslint-plugin": "^6.21.0", + "@vitejs/plugin-react": "^4.2.1", "cross-env": "^7.0.3", "esbuild": "0.20.0", "eslint": "^8.56.0", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e19fef8..ad258f7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -120,6 +120,7 @@ model UserEmailChange { } enum Role { + SuperAdmin Admin } diff --git a/prisma/seed.ts b/prisma/seed.ts index 4ce3347..d3bb1dd 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -3,6 +3,15 @@ import { PrismaClient, Role } from "@prisma/client"; const prisma = new PrismaClient(); const devSeed = async () => { + await prisma.user.create({ + data: { + email: "superadmin@example.com", + UserRole: { + create: [{ role: Role.SuperAdmin }, { role: Role.Admin }], + }, + }, + }); + await prisma.user.create({ data: { email: "admin@example.com", @@ -223,16 +232,14 @@ const prodSeed = async () => { data: { email: "hoishinxii@gmail.com", UserRole: { - create: { - role: Role.Admin, - }, + create: [{ role: Role.SuperAdmin }, { role: Role.Admin }], }, }, }); }; if (process.env.NODE_ENV === "production") { - prodSeed(); + await prodSeed(); } else { - devSeed(); + await devSeed(); } diff --git a/src/app/routes/admin.$/route.tsx b/src/admin/app.tsx similarity index 59% rename from src/app/routes/admin.$/route.tsx rename to src/admin/app.tsx index 37101b8..e10d0d1 100644 --- a/src/app/routes/admin.$/route.tsx +++ b/src/admin/app.tsx @@ -1,12 +1,8 @@ import "./index.css"; -import { Role } from "@prisma/client"; -import type { LoaderFunctionArgs } from "@remix-run/node"; import { Admin, Resource } from "react-admin"; -import { prisma } from "../../../shared/prisma.server"; -import { assertSession } from "../../session.server"; - +import { authProvider } from "./auth-provider"; import { dataProvider } from "./data-provider"; import { EventCreate, EventEdit, EventList } from "./event"; import { @@ -17,30 +13,13 @@ import { import { RunEdit, RunList } from "./run"; import { UserList } from "./user"; - -export const loader = async ({ request }: LoaderFunctionArgs) => { - const session = await assertSession(request); - - const adminRole = await prisma.userRole.findFirst({ - where: { - userId: session.user.id, - role: Role.Admin, - }, - select: { - role: true, - }, - }); - - if (!adminRole) { - throw new Response(null, { status: 404 }); - } - - return null; -}; - -export default function AdminPage() { +export const App = () => { return ( - + ); -} +}; diff --git a/src/admin/auth-provider.ts b/src/admin/auth-provider.ts new file mode 100644 index 0000000..6779152 --- /dev/null +++ b/src/admin/auth-provider.ts @@ -0,0 +1,35 @@ +import { serialize } from "cookie"; + +import { SIGN_IN_REDIRECT_COOKIE_NAME } from "../shared/constants"; + +import { trpc } from "./trpc"; + +const noop = async () => { + // do nothing +}; + +export const authProvider = { + checkAuth: async () => { + const isSignedIn = await trpc.authentication.isSignedIn.query(); + if (!isSignedIn) { + document.cookie = serialize(SIGN_IN_REDIRECT_COOKIE_NAME, "/admin"); + location.href = "/sign-in"; + return; + } + + const roles = await trpc.authentication.roles.query(); + if (roles.includes("SuperAdmin") || roles.includes("Admin")) { + return; + } + location.href = "/"; + }, + + logout: async () => { + await trpc.authentication.signOut.mutate(); + location.href = "/"; + }, + + login: noop, + checkError: noop, + getPermissions: noop, +}; diff --git a/src/app/routes/admin.$/data-provider.ts b/src/admin/data-provider.ts similarity index 63% rename from src/app/routes/admin.$/data-provider.ts rename to src/admin/data-provider.ts index 432e782..61d44b4 100644 --- a/src/app/routes/admin.$/data-provider.ts +++ b/src/admin/data-provider.ts @@ -2,7 +2,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { CreateResult, - DataProvider, DeleteManyParams, DeleteManyResult, DeleteParams, @@ -18,7 +17,7 @@ import type { UpdateResult, } from "react-admin"; -import { trpcClient } from "../../trpc"; +import { trpc } from "./trpc"; const filterResource = (resource: string) => { switch (resource) { @@ -32,51 +31,49 @@ const filterResource = (resource: string) => { } }; -export const dataProvider: DataProvider = { +export const dataProvider = { getList: ( resource: string, params: GetListParams, ): Promise => { - return trpcClient.admin[filterResource(resource)].getList.query(params); + return trpc.admin[filterResource(resource)].getList.query(params); }, getOne: (resource: string, params: GetOneParams): Promise => { - return trpcClient.admin[filterResource(resource)].getOne.query(params); + return trpc.admin[filterResource(resource)].getOne.query(params); }, getMany: (resource: string, params: any): Promise => { - return trpcClient.admin[filterResource(resource)].getMany.query(params); + return trpc.admin[filterResource(resource)].getMany.query(params); }, getManyReference: ( resource: string, params: any, ): Promise => { - return trpcClient.admin[filterResource(resource)].getManyReference.query( - params, - ); + return trpc.admin[filterResource(resource)].getManyReference.query(params); }, create: (resource: string, params: any): Promise => { - return trpcClient.admin[filterResource(resource)].create.mutate(params); + return trpc.admin[filterResource(resource)].create.mutate(params); }, update: (resource: string, params: UpdateParams): Promise => { - return trpcClient.admin[filterResource(resource)].update.mutate(params); + return trpc.admin[filterResource(resource)].update.mutate(params); }, updateMany: (resource: string, params: any): Promise => { - return trpcClient.admin[filterResource(resource)].updateMany.mutate(params); + return trpc.admin[filterResource(resource)].updateMany.mutate(params); }, delete: (resource: string, params: DeleteParams): Promise => { - return trpcClient.admin[filterResource(resource)].delete.mutate(params); + return trpc.admin[filterResource(resource)].delete.mutate(params); }, deleteMany: ( resource: string, params: DeleteManyParams, ): Promise => { - return trpcClient.admin[filterResource(resource)].deleteMany.mutate(params); + return trpc.admin[filterResource(resource)].deleteMany.mutate(params); }, }; diff --git a/src/admin/entry.tsx b/src/admin/entry.tsx new file mode 100644 index 0000000..bed9239 --- /dev/null +++ b/src/admin/entry.tsx @@ -0,0 +1,8 @@ +import { createRoot } from "react-dom/client"; + +import { App } from "./app"; + +const rootElement = document.getElementById("root"); +if (rootElement) { + createRoot(rootElement).render(); +} diff --git a/src/app/routes/admin.$/event-group.tsx b/src/admin/event-group.tsx similarity index 100% rename from src/app/routes/admin.$/event-group.tsx rename to src/admin/event-group.tsx diff --git a/src/app/routes/admin.$/event.tsx b/src/admin/event.tsx similarity index 96% rename from src/app/routes/admin.$/event.tsx rename to src/admin/event.tsx index 985feb4..2b30c65 100644 --- a/src/app/routes/admin.$/event.tsx +++ b/src/admin/event.tsx @@ -17,7 +17,7 @@ import { RadioButtonGroupInput, } from "react-admin"; -import { dataSourceType } from "../../../shared/constants"; +import { dataSourceType } from "../shared/constants"; export const EventList = () => ( diff --git a/src/app/routes/admin.$/index.css b/src/admin/index.css similarity index 100% rename from src/app/routes/admin.$/index.css rename to src/admin/index.css diff --git a/src/admin/index.html b/src/admin/index.html new file mode 100644 index 0000000..dcfd889 --- /dev/null +++ b/src/admin/index.html @@ -0,0 +1,12 @@ + + + + + + + + +
+ + + diff --git a/src/app/routes/admin.$/run.tsx b/src/admin/run.tsx similarity index 95% rename from src/app/routes/admin.$/run.tsx rename to src/admin/run.tsx index 90e4674..3a5610e 100644 --- a/src/app/routes/admin.$/run.tsx +++ b/src/admin/run.tsx @@ -13,7 +13,7 @@ import { UrlField, } from "react-admin"; -import { secondsToDurationStr } from "../../../shared/duration"; +import { secondsToDurationStr } from "../shared/duration"; export const RunList = () => ( ({ + links: [ + httpBatchLink({ + url: "/api/trpc", + }), + ], +}); diff --git a/src/app/routes/admin.$/user.tsx b/src/admin/user.tsx similarity index 100% rename from src/app/routes/admin.$/user.tsx rename to src/admin/user.tsx diff --git a/src/admin/vite.config.ts b/src/admin/vite.config.ts new file mode 100644 index 0000000..a2ee0c2 --- /dev/null +++ b/src/admin/vite.config.ts @@ -0,0 +1,17 @@ +import react from "@vitejs/plugin-react"; +import { defineConfig, splitVendorChunkPlugin } from "vite"; + +export default defineConfig({ + plugins: [react(), splitVendorChunkPlugin()], + base: "/admin", + server: { + host: "0.0.0.0", + port: 4000, + origin: "https://www.japanese-restream.org.localhost", + }, + build: { + outDir: "../../dist/admin", + emptyOutDir: true, + }, + clearScreen: false, +}); diff --git a/src/app/components/header.tsx b/src/app/components/header.tsx index 5b236ba..e4aa273 100644 --- a/src/app/components/header.tsx +++ b/src/app/components/header.tsx @@ -2,8 +2,8 @@ import { Button, Text } from "@radix-ui/themes"; import { Link, useRevalidator } from "@remix-run/react"; import { css } from "../../../styled-system/css/css.js"; -import icon from "../images/icon.png"; import { trpc } from "../trpc.js"; +import icon from "../images/icon.png"; export const AppHeader = ({ user }: { user?: { id: string } }) => { const revalidator = useRevalidator(); diff --git a/src/app/root.tsx b/src/app/root.tsx index d1acba6..43ecd4b 100644 --- a/src/app/root.tsx +++ b/src/app/root.tsx @@ -1,21 +1,26 @@ +import "./index.css"; + import { Theme } from "@radix-ui/themes"; import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node"; import { Links, - LiveReload, Meta, Outlet, Scripts, ScrollRestoration, type MetaFunction, json, + useLoaderData, } from "@remix-run/react"; +import { css } from "../../styled-system/css/css.js"; import { ACTIVITY_COOKIE_NAME } from "../shared/constants.js"; import { renewSession } from "../shared/session.server.js"; +import { AppHeader } from "./components/header.js"; import { activityCookieSetCookie, + getSession, parseCookie, parseSessionToken, serializeSessionToken, @@ -64,11 +69,19 @@ const calcHeaders = async (request: Request) => { }; export const loader = async ({ request }: LoaderFunctionArgs) => { - const headers = await calcHeaders(request); - return json(null, { headers }); + const [headers, session] = await Promise.all([ + calcHeaders(request), + getSession(request), + ]); + const user = session + ? { id: session.user.id, email: session.user.email } + : undefined; + return json({ user }, { headers }); }; export default function App() { + const data = useLoaderData(); + return ( @@ -78,12 +91,36 @@ export default function App() { - +
+ +
+ +
+
- ); diff --git a/src/app/routes/_base.tsx b/src/app/routes/_base.tsx deleted file mode 100644 index 9fc6370..0000000 --- a/src/app/routes/_base.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import "../index.css"; - -import type { LoaderFunctionArgs } from "@remix-run/node"; -import { Outlet, json, useLoaderData } from "@remix-run/react"; - -import { css } from "../../../styled-system/css"; -import { AppHeader } from "../components/header"; -import { getSession } from "../cookie.server"; - -export const loader = async ({ request }: LoaderFunctionArgs) => { - const session = await getSession(request); - return json({ - user: session - ? { - id: session.user.id, - email: session.user.email, - } - : undefined, - }); -}; - -export default function BaseLayout() { - const data = useLoaderData(); - - return ( -
- -
- -
-
- ); -} diff --git a/src/app/routes/_base._index.tsx b/src/app/routes/_index.tsx similarity index 100% rename from src/app/routes/_base._index.tsx rename to src/app/routes/_index.tsx diff --git a/src/app/routes/_base.archives.$eventShortName.tsx b/src/app/routes/archives.$eventShortName.tsx similarity index 100% rename from src/app/routes/_base.archives.$eventShortName.tsx rename to src/app/routes/archives.$eventShortName.tsx diff --git a/src/app/routes/_base.archives._index.tsx b/src/app/routes/archives._index.tsx similarity index 100% rename from src/app/routes/_base.archives._index.tsx rename to src/app/routes/archives._index.tsx diff --git a/src/app/routes/_base.register.tsx b/src/app/routes/register.tsx similarity index 100% rename from src/app/routes/_base.register.tsx rename to src/app/routes/register.tsx diff --git a/src/app/routes/_base.sign-in-options._index.tsx b/src/app/routes/sign-in-options._index.tsx similarity index 100% rename from src/app/routes/_base.sign-in-options._index.tsx rename to src/app/routes/sign-in-options._index.tsx diff --git a/src/app/routes/_base.sign-in-options.change-email.tsx b/src/app/routes/sign-in-options.change-email.tsx similarity index 100% rename from src/app/routes/_base.sign-in-options.change-email.tsx rename to src/app/routes/sign-in-options.change-email.tsx diff --git a/src/app/routes/_base.sign-in._index.tsx b/src/app/routes/sign-in._index.tsx similarity index 100% rename from src/app/routes/_base.sign-in._index.tsx rename to src/app/routes/sign-in._index.tsx diff --git a/src/app/routes/_base.sign-in.email.tsx b/src/app/routes/sign-in.email.tsx similarity index 100% rename from src/app/routes/_base.sign-in.email.tsx rename to src/app/routes/sign-in.email.tsx diff --git a/src/app/routes/_base.verify-email-change.tsx b/src/app/routes/verify-email-change.tsx similarity index 100% rename from src/app/routes/_base.verify-email-change.tsx rename to src/app/routes/verify-email-change.tsx diff --git a/src/app/trpc.tsx b/src/app/trpc.tsx index 6a2f584..bcc0b7f 100644 --- a/src/app/trpc.tsx +++ b/src/app/trpc.tsx @@ -1,9 +1,5 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { - createTRPCProxyClient, - createTRPCReact, - httpBatchLink, -} from "@trpc/react-query"; +import { createTRPCReact, httpBatchLink } from "@trpc/react-query"; import { useState, type PropsWithChildren } from "react"; import type { AppRouter } from "../server/router.js"; @@ -28,11 +24,3 @@ export const TrpcProvider = ({ children }: PropsWithChildren) => { ); }; - -export const trpcClient = createTRPCProxyClient({ - links: [ - httpBatchLink({ - url: "/api/trpc", - }), - ], -}); diff --git a/src/server/routes/authentication.ts b/src/server/routes/authentication.ts index d743e13..bbfbef0 100644 --- a/src/server/routes/authentication.ts +++ b/src/server/routes/authentication.ts @@ -1,5 +1,5 @@ import { prisma } from "../../shared/prisma.server"; -import { authenticatedProcedure, router } from "../trpc"; +import { authenticatedProcedure, publicProcedure, router } from "../trpc"; import { discordAuthenticationRouter } from "./authentication/discord"; import { passkeyRouter } from "./authentication/passkey"; @@ -8,6 +8,27 @@ export const authenticationRouter = router({ discord: discordAuthenticationRouter, passkey: passkeyRouter, + roles: publicProcedure.query(async ({ ctx }) => { + if (typeof ctx.user === "undefined") { + return []; + } + + const roles = await prisma.userRole.findMany({ + where: { + userId: ctx.user.id, + }, + select: { + role: true, + }, + }); + + return roles.map((role) => role.role); + }), + + isSignedIn: publicProcedure.query(({ ctx }) => { + return typeof ctx.user !== "undefined"; + }), + signOut: authenticatedProcedure.mutation(async ({ ctx }) => { await prisma.userSession.delete({ where: {