Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
DATABASE_URL=postgresql://postgres@localhost:5433/readr
BETTER_AUTH_URL=http://localhost:3001
FRONTEND_URL=http://localhost:5173

# Add a random string here
# In order to generate one, you may use this command: openssl rand -base64 32
BETTER_AUTH_SECRET=
2 changes: 1 addition & 1 deletion backend/drizzle.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { defineConfig } from "drizzle-kit";

export default defineConfig({
out: "./drizzle",
schema: "./src/db/schema.ts",
schema: "./src/db/schema/index.ts",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
Expand Down
2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@
"dependencies": {
"@apollo/server": "^5.5.1",
"@as-integrations/express5": "^1.1.2",
"@better-auth/drizzle-adapter": "^1.7.7",
"@graphql-tools/merge": "^9.2.6",
"better-auth": "^1.7.7",
"cors": "^2.8.6",
"csv-parse": "^7.0.3",
"dotenv": "^18.0.5",
Expand Down
309 changes: 307 additions & 2 deletions backend/pnpm-lock.yaml

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions backend/src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "@better-auth/drizzle-adapter/relations-v2";
import { db } from "~/db";
import * as schema from "~/db/schema";

export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
schema,
}),
emailAndPassword: {
enabled: true,
},
trustedOrigins: [process.env.FRONTEND_URL ?? "http://localhost:5173"],
});
7 changes: 5 additions & 2 deletions backend/src/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import "dotenv/config";
import { drizzle } from "drizzle-orm/node-postgres";
import { relations } from "~/db/schema";
import { authRelations } from "~/db/schema/auth";
import { relations } from "~/db/schema/books";

const db = drizzle(process.env.DATABASE_URL!, { relations });
const db = drizzle(process.env.DATABASE_URL!, {
relations: { ...relations, ...authRelations },
});

export { db };
130 changes: 130 additions & 0 deletions backend/src/db/schema/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { defineRelationsPart } from "drizzle-orm";
import { pgTable, text, timestamp, boolean, index } from "drizzle-orm/pg-core";

const user = pgTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").default(false).notNull(),
image: text("image"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});

const session = pgTable(
"session",
{
id: text("id").primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
},
(table) => [index("session_userId_idx").on(table.userId)],
);

const account = pgTable(
"account",
{
id: text("id").primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: timestamp("access_token_expires_at"),
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
scope: text("scope"),
password: text("password"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
},
(table) => [index("account_userId_idx").on(table.userId)],
);

const verification = pgTable(
"verification",
{
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
},
(table) => [index("verification_identifier_idx").on(table.identifier)],
);

const authRelations = defineRelationsPart(
{ user, session, account, verification },
(r) => ({
user: {
sessions: r.many.session({
from: r.user.id,
to: r.session.userId,
}),
accounts: r.many.account({
from: r.user.id,
to: r.account.userId,
}),
},
session: {
user: r.one.user({
from: r.session.userId,
to: r.user.id,
}),
},
account: {
user: r.one.user({
from: r.account.userId,
to: r.user.id,
}),
},
}),
);

type SelectUser = typeof user.$inferSelect;
type InsertUser = typeof user.$inferInsert;

type SelectSession = typeof session.$inferSelect;
type InsertSession = typeof session.$inferInsert;

type SelectAccount = typeof account.$inferSelect;
type InsertAccount = typeof account.$inferInsert;

type SelectVerification = typeof verification.$inferSelect;
type InsertVerification = typeof verification.$inferInsert;

export {
user,
session,
account,
verification,
authRelations,
type SelectUser,
type InsertUser,
type SelectSession,
type InsertSession,
type SelectAccount,
type InsertAccount,
type SelectVerification,
type InsertVerification,
};
File renamed without changes.
2 changes: 2 additions & 0 deletions backend/src/db/schema/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./auth";
export * from "./books";
2 changes: 1 addition & 1 deletion backend/src/db/seed/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { parse } from "csv-parse";
import { db } from "~/db";
import { books, type InsertBook } from "~/db/schema";
import { books, type InsertBook } from "~/db/schema/books";

type BookRow = {
bookId: string;
Expand Down
12 changes: 12 additions & 0 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import cors from "cors";
import { expressMiddleware } from "@as-integrations/express5";
import { typeDefs } from "~/schema/typeDefs.generated";
import { resolvers } from "~/schema/resolvers.generated";
import { toNodeHandler } from "better-auth/node";
import { auth } from "~/auth";

type ApolloContext = {
token?: string;
Expand All @@ -29,6 +31,16 @@ const server = new ApolloServer<ApolloContext>({

await server.start();

app.all(
"/api/auth/{*any}",
cors<cors.CorsRequest>({
origin: process.env.FRONTEND_URL ?? "http://localhost:5173",
methods: ["GET", "POST", "PUT", "DELETE"],
credentials: true,
}),
toNodeHandler(auth),
);

app.use(
"/",
cors<cors.CorsRequest>(),
Expand Down
2 changes: 1 addition & 1 deletion backend/src/schema/books/resolvers/Query/searchBooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { and, asc, ilike, type SQL } from "drizzle-orm";
import { db } from "~/db";
import { books } from "~/db/schema";
import { books } from "~/db/schema/books";
import type { QueryResolvers } from "~/schema/types.generated";

export const searchBooks: NonNullable<QueryResolvers["searchBooks"]> = async (
Expand Down
7 changes: 7 additions & 0 deletions frontend/app/api/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createAuthClient } from "better-auth/react";

const authClient = createAuthClient({
baseURL: "http://localhost:3001",
});

export { authClient };
35 changes: 5 additions & 30 deletions frontend/app/api/types/graphql.generated.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,19 @@
/** Internal type. DO NOT USE DIRECTLY. */
type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
/** Internal type. DO NOT USE DIRECTLY. */
export type Incremental<T> =
| T
| {
[P in keyof T]?: P extends " $fragmentName" | "__typename" ? T[P] : never;
};
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
export type BookQueryVariables = Exact<{
id: number;
}>;

export type BookQuery = {
book: {
__typename: "Book";
id: number;
title: string;
author: string;
publishDate: string | null;
coverImgUrl: string | null;
description: string | null;
} | null;
};

export type BookQuery = { book: { __typename: 'Book', id: number, title: string, author: string, publishDate: string | null, coverImgUrl: string | null, description: string | null } | null };

export type SearchBooksQueryVariables = Exact<{
title?: string | null | undefined;
limit?: number | null | undefined;
offset?: number | null | undefined;
}>;

export type SearchBooksQuery = {
searchBooks: {
__typename: "BookSearchResult";
totalCount: number;
books: Array<{
__typename: "Book";
id: number;
title: string;
author: string;
publishDate: string | null;
coverImgUrl: string | null;
}>;
};
};

export type SearchBooksQuery = { searchBooks: { __typename: 'BookSearchResult', totalCount: number, books: Array<{ __typename: 'Book', id: number, title: string, author: string, publishDate: string | null, coverImgUrl: string | null }> } };
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@react-router/node": "^8.4.0",
"@react-router/serve": "^8.4.0",
"@tailwindcss/vite": "^4.3.3",
"better-auth": "^1.7.7",
"class-variance-authority": "^0.7.1",
"cn": "^0.4.0",
"graphql": "^17.0.2",
Expand Down
Loading