Skip to content

Commit 9d378ce

Browse files
committed
feat: enhance User and Favorite Resolvers and Update Fixtures
- Update Favorite and User entities to improve data modeling - Refactor Favorite and User resolvers to enhance query capabilities - Add new resolvers.ts file to consolidate all resolvers - Update GraphQL type definitions to reflect changes in resolvers - Refactor server.ts to improve server setup and initialization - Update book fixtures loading script to align with entity changes This update enhances the functionality of the GraphQL API, improves the server setup process, and ensures that the fixtures align with the updated entities.
1 parent 159237b commit 9d378ce

File tree

8 files changed

+142
-53
lines changed

8 files changed

+142
-53
lines changed

src/entity/Favorite.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1-
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm";
1+
import {
2+
Entity,
3+
PrimaryGeneratedColumn,
4+
Column,
5+
ManyToOne,
6+
Unique,
7+
} from "typeorm";
28
import { User } from "./User";
39
import { Book } from "./Book";
410

511
@Entity()
12+
@Unique(["userId", "bookId"])
613
export class Favorite {
714
@PrimaryGeneratedColumn()
815
id: number;
916

17+
@Column()
18+
userId: number;
19+
20+
@Column()
21+
bookId: number;
22+
1023
@ManyToOne(() => User, (user) => user.favorites)
1124
user: User;
1225

src/graphql/resolvers/Favorite.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,32 @@ import { Book } from "../../entity/Book";
55

66
export const FavoriteResolvers = {
77
Mutation: {
8-
favoriteBook: async (_, { userId, bookId }, context) => {
8+
favoriteBook: async (_, { bookId }, context) => {
99
try {
10-
console.log("User ID:", userId);
11-
console.log("Book ID:", bookId);
12-
if (!userId || !bookId) {
13-
throw new Error("User ID or Book ID not provided");
10+
const userId = context.userId;
11+
12+
if (!userId) {
13+
throw new Error("User ID not provided");
14+
}
15+
16+
if (!bookId) {
17+
throw new Error("Book ID not provided");
1418
}
19+
1520
const dataSourceInstance = await dataSource;
1621
const userRepository = dataSourceInstance.getRepository(User);
1722
const bookRepository = dataSourceInstance.getRepository(Book);
1823
const favoriteRepository = dataSourceInstance.getRepository(Favorite);
1924

20-
const user = await userRepository.findOne({ where: { id: userId } });
25+
let user = await userRepository.findOne({ where: { id: userId } });
2126
const book = await bookRepository.findOne({ where: { id: bookId } });
22-
if (!user || !book) {
23-
throw new Error("User or Book not found");
27+
if (!book) {
28+
throw new Error("Book not found");
29+
}
30+
31+
if (!user) {
32+
user = userRepository.create({ auth0Id: userId });
33+
await userRepository.save(user);
2434
}
2535

2636
const favorite = new Favorite();

src/graphql/resolvers/User.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,30 @@ export const UserResolvers = {
1414
throw error;
1515
}
1616
},
17+
user: async (_, __, context) => {
18+
try {
19+
const userId = context.userId;
20+
21+
if (!userId) {
22+
throw new Error("User ID not provided");
23+
}
24+
25+
const dataSourceInstance = await dataSource;
26+
const userRepository = dataSourceInstance.getRepository(User);
27+
const user = await userRepository.findOne({
28+
where: { auth0Id: userId },
29+
relations: ["favorites", "favorites.book"],
30+
});
31+
32+
if (!user) {
33+
throw new Error("User not found");
34+
}
35+
36+
return user;
37+
} catch (error) {
38+
console.error("Error fetching user:", error);
39+
throw error;
40+
}
41+
},
1742
},
1843
};

src/graphql/resolvers/resolvers.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { UserResolvers } from "./User";
2+
import { BookResolvers } from "./Book";
3+
import { FavoriteResolvers } from "./Favorite";
4+
5+
export const resolvers = {
6+
Query: {
7+
...UserResolvers.Query,
8+
...BookResolvers.Query,
9+
...FavoriteResolvers.Query,
10+
},
11+
Mutation: {
12+
...FavoriteResolvers.Mutation,
13+
},
14+
};

src/graphql/types/index.ts

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1 @@
1-
import { UserType } from "./User";
2-
import { BookType } from "./Book";
3-
import { BooksPageType } from "./BooksPage";
4-
import { FavoriteType } from "./Favorite";
5-
6-
export const typeDefs = `#graphql
7-
${UserType}
8-
${BookType}
9-
${BooksPageType}
10-
${FavoriteType}
11-
type Query {
12-
hello: String!
13-
dbInfo: String!
14-
users: [User!]!
15-
books(author: String, title: String, cursor: String, limit: Int): BooksPage!
16-
favoriteBooks(userId: ID!): [Favorite!]!
17-
}
18-
type Mutation {
19-
favoriteBook(userId: ID!, bookId: ID!): Favorite
20-
}`;
1+
export * from "./typeDefs";

src/graphql/types/typeDefs.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { UserType } from "./User";
2+
import { BookType } from "./Book";
3+
import { BooksPageType } from "./BooksPage";
4+
import { FavoriteType } from "./Favorite";
5+
6+
export const typeDefs = `#graphql
7+
${UserType}
8+
${BookType}
9+
${BooksPageType}
10+
${FavoriteType}
11+
type Query {
12+
hello: String!
13+
dbInfo: String!
14+
user: User
15+
users: [User!]!
16+
books(author: String, title: String, cursor: String, limit: Int): BooksPage!
17+
favoriteBooks(userId: ID!): [Favorite!]!
18+
}
19+
type Mutation {
20+
favoriteBook(bookId: ID!): Favorite
21+
}`;

src/scripts/loadBookFixtures.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,55 @@ import { faker } from "@faker-js/faker";
22
import { AppDataSource } from "../data-source";
33
import { User } from "../entity/User";
44
import { Book } from "../entity/Book";
5+
import { Favorite } from "../entity/Favorite";
56
import booksData from "../fixtures/raw/books.json";
67

78
const initializeBooks = async (bookData) => {
9+
if (isNaN(Date.parse(bookData.publicationDate))) {
10+
console.log(
11+
`Skipping book with invalid publication date: ${bookData.title}`
12+
);
13+
return;
14+
}
815
const book = new Book();
916
book.title = bookData.title;
1017
book.author = bookData.author;
1118
book.publicationDate = new Date(bookData.publicationDate);
1219
book.image = bookData.image || faker.image.url({ width: 150, height: 150 });
1320
book.rating = bookData.rating;
1421
book.ratingsCount = bookData.ratingsCount;
15-
await AppDataSource.manager.save(book);
16-
console.log("Saved a new book with id: " + book.id);
22+
return AppDataSource.manager.save(book);
1723
};
1824

19-
const loadBooks = async () => {
20-
const books = await AppDataSource.manager.find(Book);
25+
const initializeUsers = async () => {
26+
const user = new User();
27+
user.auth0Id = "1234567890";
28+
return AppDataSource.manager.save(user);
29+
};
30+
31+
const initializeFavorites = async (user, book) => {
32+
const favorite = new Favorite();
33+
favorite.userId = user.id;
34+
favorite.bookId = book.id;
35+
return AppDataSource.manager.save(favorite);
2136
};
2237

2338
AppDataSource.initialize()
2439
.then(async () => {
25-
console.log("Dropping and recreating the books table...");
40+
console.log("Dropping and recreating the tables...");
41+
await AppDataSource.manager.query('TRUNCATE "favorite" CASCADE');
42+
await AppDataSource.manager.query('TRUNCATE "user", "book" CASCADE');
2643
await AppDataSource.synchronize(true);
2744

2845
console.log("Inserting new books into the database...");
29-
await Promise.all(booksData.map(initializeBooks));
46+
const books = await Promise.all(booksData.map(initializeBooks));
47+
48+
console.log("Inserting new users into the database...");
49+
const user = await initializeUsers();
3050

31-
console.log("Loading books from the database...");
32-
await loadBooks();
51+
console.log("Marking some books as favorites for the user...");
52+
await Promise.all(
53+
books.slice(0, 5).map((book) => initializeFavorites(user, book))
54+
);
3355
})
3456
.catch((error) => console.log(error));

src/server.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import { typeDefs } from "./graphql/types";
88
import winston from "winston";
99
import jwt from "jsonwebtoken";
1010

11+
interface GraphQLContext {
12+
userId?: string;
13+
lambdaEvent?: any;
14+
lambdaContext?: any;
15+
}
16+
1117
const logger = winston.createLogger({
1218
level: "info",
1319
format: winston.format.json(),
@@ -25,15 +31,21 @@ if (process.env.NODE_ENV !== "production") {
2531
);
2632
}
2733

28-
interface MyContext {
29-
userId?: string;
30-
lambdaEvent?: any;
31-
lambdaContext?: any;
32-
}
34+
const decodeToken = (token: string): string | undefined => {
35+
if (token?.startsWith("Bearer ")) {
36+
const jwtToken = token.slice(7, token.length).trimStart();
37+
try {
38+
const decoded = jwt.verify(jwtToken, process.env.JWT_SECRET);
39+
return decoded.sub;
40+
} catch (err) {
41+
logger.error("Invalid token");
42+
}
43+
}
44+
};
3345

3446
let server: ApolloServer | undefined;
3547
try {
36-
server = new ApolloServer<MyContext>({
48+
server = new ApolloServer<GraphQLContext>({
3749
typeDefs,
3850
resolvers,
3951
introspection: true,
@@ -49,16 +61,7 @@ const graphqlHandler = startServerAndCreateLambdaHandler(
4961
{
5062
context: async ({ event, context }) => {
5163
const token = event.headers.authorization || "";
52-
let userId;
53-
if (token && token.startsWith("Bearer ")) {
54-
const jwtToken = token.slice(7, token.length).trimLeft();
55-
try {
56-
const decoded = jwt.verify(jwtToken, process.env.JWT_SECRET);
57-
userId = decoded.sub;
58-
} catch (err) {
59-
logger.error("Invalid token");
60-
}
61-
}
64+
const userId = decodeToken(token);
6265

6366
return {
6467
userId,

0 commit comments

Comments
 (0)