+ {sessionData && Logged in as {sessionData.user?.name}}
+ {secretMessage && - {secretMessage}}
+
+
+
+ );
+};
diff --git a/src/client/src/server/api/root.ts b/src/client/src/server/api/root.ts
new file mode 100644
index 0000000..93fba92
--- /dev/null
+++ b/src/client/src/server/api/root.ts
@@ -0,0 +1,14 @@
+import { createTRPCRouter } from "~/server/api/trpc";
+import { exampleRouter } from "~/server/api/routers/example";
+
+/**
+ * This is the primary router for your server.
+ *
+ * All routers added in /api/routers should be manually added here.
+ */
+export const appRouter = createTRPCRouter({
+ example: exampleRouter,
+});
+
+// export type definition of API
+export type AppRouter = typeof appRouter;
diff --git a/src/client/src/server/api/routers/example.ts b/src/client/src/server/api/routers/example.ts
new file mode 100644
index 0000000..73de162
--- /dev/null
+++ b/src/client/src/server/api/routers/example.ts
@@ -0,0 +1,25 @@
+import { z } from "zod";
+
+import {
+ createTRPCRouter,
+ publicProcedure,
+ protectedProcedure,
+} from "~/server/api/trpc";
+
+export const exampleRouter = createTRPCRouter({
+ hello: publicProcedure
+ .input(z.object({ text: z.string() }))
+ .query(({ input }) => {
+ return {
+ greeting: `Hello ${input.text}`,
+ };
+ }),
+
+ getAll: publicProcedure.query(({ ctx }) => {
+ return ctx.prisma.example.findMany();
+ }),
+
+ getSecretMessage: protectedProcedure.query(() => {
+ return "you can now see this secret message!";
+ }),
+});
diff --git a/src/client/src/server/api/trpc.ts b/src/client/src/server/api/trpc.ts
new file mode 100644
index 0000000..320ffd6
--- /dev/null
+++ b/src/client/src/server/api/trpc.ts
@@ -0,0 +1,130 @@
+/**
+ * YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS:
+ * 1. You want to modify request context (see Part 1).
+ * 2. You want to create a new middleware or type of procedure (see Part 3).
+ *
+ * TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will
+ * need to use are documented accordingly near the end.
+ */
+
+/**
+ * 1. CONTEXT
+ *
+ * This section defines the "contexts" that are available in the backend API.
+ *
+ * These allow you to access things when processing a request, like the database, the session, etc.
+ */
+import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
+import { type Session } from "next-auth";
+
+import { getServerAuthSession } from "~/server/auth";
+import { prisma } from "~/server/db";
+
+type CreateContextOptions = {
+ session: Session | null;
+};
+
+/**
+ * This helper generates the "internals" for a tRPC context. If you need to use it, you can export
+ * it from here.
+ *
+ * Examples of things you may need it for:
+ * - testing, so we don't have to mock Next.js' req/res
+ * - tRPC's `createSSGHelpers`, where we don't have req/res
+ *
+ * @see https://create.t3.gg/en/usage/trpc#-servertrpccontextts
+ */
+const createInnerTRPCContext = (opts: CreateContextOptions) => {
+ return {
+ session: opts.session,
+ prisma,
+ };
+};
+
+/**
+ * This is the actual context you will use in your router. It will be used to process every request
+ * that goes through your tRPC endpoint.
+ *
+ * @see https://trpc.io/docs/context
+ */
+export const createTRPCContext = async (opts: CreateNextContextOptions) => {
+ const { req, res } = opts;
+
+ // Get the session from the server using the getServerSession wrapper function
+ const session = await getServerAuthSession({ req, res });
+
+ return createInnerTRPCContext({
+ session,
+ });
+};
+
+/**
+ * 2. INITIALIZATION
+ *
+ * This is where the tRPC API is initialized, connecting the context and transformer. We also parse
+ * ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation
+ * errors on the backend.
+ */
+import { initTRPC, TRPCError } from "@trpc/server";
+import superjson from "superjson";
+import { ZodError } from "zod";
+
+const t = initTRPC.context().create({
+ transformer: superjson,
+ errorFormatter({ shape, error }) {
+ return {
+ ...shape,
+ data: {
+ ...shape.data,
+ zodError:
+ error.cause instanceof ZodError ? error.cause.flatten() : null,
+ },
+ };
+ },
+});
+
+/**
+ * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
+ *
+ * These are the pieces you use to build your tRPC API. You should import these a lot in the
+ * "/src/server/api/routers" directory.
+ */
+
+/**
+ * This is how you create new routers and sub-routers in your tRPC API.
+ *
+ * @see https://trpc.io/docs/router
+ */
+export const createTRPCRouter = t.router;
+
+/**
+ * Public (unauthenticated) procedure
+ *
+ * This is the base piece you use to build new queries and mutations on your tRPC API. It does not
+ * guarantee that a user querying is authorized, but you can still access user session data if they
+ * are logged in.
+ */
+export const publicProcedure = t.procedure;
+
+/** Reusable middleware that enforces users are logged in before running the procedure. */
+const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
+ if (!ctx.session || !ctx.session.user) {
+ throw new TRPCError({ code: "UNAUTHORIZED" });
+ }
+ return next({
+ ctx: {
+ // infers the `session` as non-nullable
+ session: { ...ctx.session, user: ctx.session.user },
+ },
+ });
+});
+
+/**
+ * Protected (authenticated) procedure
+ *
+ * If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies
+ * the session is valid and guarantees `ctx.session.user` is not null.
+ *
+ * @see https://trpc.io/docs/procedures
+ */
+export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);
diff --git a/src/client/src/server/auth.ts b/src/client/src/server/auth.ts
new file mode 100644
index 0000000..7e6d826
--- /dev/null
+++ b/src/client/src/server/auth.ts
@@ -0,0 +1,76 @@
+import { type GetServerSidePropsContext } from "next";
+import {
+ getServerSession,
+ type NextAuthOptions,
+ type DefaultSession,
+} from "next-auth";
+import DiscordProvider from "next-auth/providers/discord";
+import { PrismaAdapter } from "@next-auth/prisma-adapter";
+import { env } from "~/env.mjs";
+import { prisma } from "~/server/db";
+
+/**
+ * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
+ * object and keep type safety.
+ *
+ * @see https://next-auth.js.org/getting-started/typescript#module-augmentation
+ */
+declare module "next-auth" {
+ interface Session extends DefaultSession {
+ user: {
+ id: string;
+ // ...other properties
+ // role: UserRole;
+ } & DefaultSession["user"];
+ }
+
+ // interface User {
+ // // ...other properties
+ // // role: UserRole;
+ // }
+}
+
+/**
+ * Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
+ *
+ * @see https://next-auth.js.org/configuration/options
+ */
+export const authOptions: NextAuthOptions = {
+ callbacks: {
+ session({ session, user }) {
+ if (session.user) {
+ session.user.id = user.id;
+ // session.user.role = user.role; <-- put other properties on the session here
+ }
+ return session;
+ },
+ },
+ adapter: PrismaAdapter(prisma),
+ providers: [
+ DiscordProvider({
+ clientId: env.DISCORD_CLIENT_ID,
+ clientSecret: env.DISCORD_CLIENT_SECRET,
+ }),
+ /**
+ * ...add more providers here.
+ *
+ * Most other providers require a bit more work than the Discord provider. For example, the
+ * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account
+ * model. Refer to the NextAuth.js docs for the provider you want to use. Example:
+ *
+ * @see https://next-auth.js.org/providers/github
+ */
+ ],
+};
+
+/**
+ * Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file.
+ *
+ * @see https://next-auth.js.org/configuration/nextjs
+ */
+export const getServerAuthSession = (ctx: {
+ req: GetServerSidePropsContext["req"];
+ res: GetServerSidePropsContext["res"];
+}) => {
+ return getServerSession(ctx.req, ctx.res, authOptions);
+};
diff --git a/src/client/src/server/db.ts b/src/client/src/server/db.ts
new file mode 100644
index 0000000..f3d7be3
--- /dev/null
+++ b/src/client/src/server/db.ts
@@ -0,0 +1,14 @@
+import { PrismaClient } from "@prisma/client";
+
+import { env } from "~/env.mjs";
+
+const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
+
+export const prisma =
+ globalForPrisma.prisma ||
+ new PrismaClient({
+ log:
+ env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
+ });
+
+if (env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
diff --git a/src/client/src/types/index.tsx b/src/client/src/types/index.tsx
deleted file mode 100644
index ab5cce3..0000000
--- a/src/client/src/types/index.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-// Path: types/index.tsx
-
-export interface IBook {
- book_id: string
- title: string
- author: string
- categories: string
- cover: string
- pages: string
- progress: string
- file_name: string
- description: string
- date: string
- rights: string
- tags: string
- identifier: string
- publisher: string
-}
diff --git a/src/client/src/utils/api.ts b/src/client/src/utils/api.ts
new file mode 100644
index 0000000..f4f4ad5
--- /dev/null
+++ b/src/client/src/utils/api.ts
@@ -0,0 +1,68 @@
+/**
+ * This is the client-side entrypoint for your tRPC API. It is used to create the `api` object which
+ * contains the Next.js App-wrapper, as well as your type-safe React Query hooks.
+ *
+ * We also create a few inference helpers for input and output types.
+ */
+import { httpBatchLink, loggerLink } from "@trpc/client";
+import { createTRPCNext } from "@trpc/next";
+import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server";
+import superjson from "superjson";
+
+import { type AppRouter } from "~/server/api/root";
+
+const getBaseUrl = () => {
+ if (typeof window !== "undefined") return ""; // browser should use relative url
+ if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
+ return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
+};
+
+/** A set of type-safe react-query hooks for your tRPC API. */
+export const api = createTRPCNext({
+ config() {
+ return {
+ /**
+ * Transformer used for data de-serialization from the server.
+ *
+ * @see https://trpc.io/docs/data-transformers
+ */
+ transformer: superjson,
+
+ /**
+ * Links used to determine request flow from client to server.
+ *
+ * @see https://trpc.io/docs/links
+ */
+ links: [
+ loggerLink({
+ enabled: (opts) =>
+ process.env.NODE_ENV === "development" ||
+ (opts.direction === "down" && opts.result instanceof Error),
+ }),
+ httpBatchLink({
+ url: `${getBaseUrl()}/api/trpc`,
+ }),
+ ],
+ };
+ },
+ /**
+ * Whether tRPC should await queries when server rendering pages.
+ *
+ * @see https://trpc.io/docs/nextjs#ssr-boolean-default-false
+ */
+ ssr: false,
+});
+
+/**
+ * Inference helper for inputs.
+ *
+ * @example type HelloInput = RouterInputs['example']['hello']
+ */
+export type RouterInputs = inferRouterInputs;
+
+/**
+ * Inference helper for outputs.
+ *
+ * @example type HelloOutput = RouterOutputs['example']['hello']
+ */
+export type RouterOutputs = inferRouterOutputs;
diff --git a/src/client/tailwind.config.cjs b/src/client/tailwind.config.cjs
new file mode 100644
index 0000000..a82e7e9
--- /dev/null
+++ b/src/client/tailwind.config.cjs
@@ -0,0 +1,10 @@
+/** @type {import('tailwindcss').Config} */
+const config = {
+ content: ["./src/**/*.{js,ts,jsx,tsx}"],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+};
+
+module.exports = config;
diff --git a/src/client/theme.tsx b/src/client/theme.tsx
deleted file mode 100644
index 78f5127..0000000
--- a/src/client/theme.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { createTheme } from @material-ui/core/styles;
-
-const theme = createTheme({
- palette: {
- primary: {
- main: '#556cd6',
- },
- secondary: {
- main: '#19857b',
- },
- error: {
- main: '#f44336',
- },
- background: {
- default: '#fff',
- },
- },
-});
-
-export { theme };
diff --git a/src/client/tsconfig.json b/src/client/tsconfig.json
index 0c7555f..03ebb74 100644
--- a/src/client/tsconfig.json
+++ b/src/client/tsconfig.json
@@ -1,8 +1,9 @@
{
"compilerOptions": {
- "target": "es5",
+ "target": "es2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
+ "checkJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
@@ -14,15 +15,19 @@
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
- "plugins": [
- {
- "name": "next"
- }
- ],
+ "noUncheckedIndexedAccess": true,
+ "baseUrl": ".",
"paths": {
- "@/*": ["./src/*"]
+ "~/*": ["./src/*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "include": [
+ ".eslintrc.cjs",
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ "**/*.cjs",
+ "**/*.mjs"
+ ],
"exclude": ["node_modules"]
}