Skip to content

Commit

Permalink
migrate getBookFromISBN to class service
Browse files Browse the repository at this point in the history
  • Loading branch information
shawnmclean committed Dec 18, 2024
1 parent b015410 commit 83e3480
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 47 deletions.
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
# The database URL is used to connect to your Supabase database.
POSTGRES_URL="postgresql://postgres:postgres@localhost:5432/postgres"

GOOGLE_BOOKS_API_KEY="123"
ISBN_DB_API_KEY="123"

TRIGGER_SECRET_KEY="123"
Expand Down
2 changes: 1 addition & 1 deletion apps/sovoli.com/src/services/baseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export abstract class BaseService<TOptions, TResult = void> {
protected readonly name: string = new.target.name,
) {}

abstract execute(options: TOptions): Promise<TResult>;
protected abstract execute(options: TOptions): Promise<TResult>;

public async call(options: TOptions): Promise<TResult> {
return tracer.startActiveSpan(this.name + ".call", async (span) => {
Expand Down
9 changes: 5 additions & 4 deletions apps/sovoli.com/src/services/books/findBookByISBN.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { SelectBook } from "@sovoli/db/schema";
import { db, eq, or, schema, sql } from "@sovoli/db";

import { getBookFromISBNdb } from "../isbndb/getBookFromISBNdb";
import { GetBookFromISBNdb } from "../isbndb/getBookFromISBNdb";

export interface FindBookByISBNOptions {
isbn: string;
Expand Down Expand Up @@ -29,10 +29,11 @@ export const findBookByISBN = async ({
}

console.log("no internal results, searching externally");
const getBookFromISBNdb = new GetBookFromISBNdb();

const externalBook = await getBookFromISBNdb({ isbn });
const { book } = await getBookFromISBNdb.call({ isbn });

if (!externalBook) {
if (!book) {
console.log("no external book found");
return {};
}
Expand All @@ -41,7 +42,7 @@ export const findBookByISBN = async ({

const [insertedBooks] = await db
.insert(schema.Book)
.values(externalBook)
.values(book)
.onConflictDoUpdate({
target: [schema.Book.isbn10, schema.Book.isbn13],
set: {
Expand Down
79 changes: 45 additions & 34 deletions apps/sovoli.com/src/services/isbndb/getBookFromISBNdb.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,62 @@
import type { InsertBook } from "@sovoli/db/schema";

import type { Book } from "./models";
import type { ISBNdbBook } from "./models";
import { env } from "~/env";
import { AsyncResilience } from "~/utils/retry/AsyncResilience";
import { retryAsync } from "~/utils/retry/retry-async";
import { BaseService } from "../baseService";
import { transformISBNdbToInsertBook } from "./transformISBNdbToBook";

// Response object structure for getBookByISBN
export interface GetBookFromISBNdbResponse {
book: Book; // Book object for the given ISBN
export interface GetBookFromISBNdbResult {
book: InsertBook | null;
}

export interface GetBookFromISBNdbOptions {
isbn: string;
}

export const getBookFromISBNdb = async ({
isbn,
}: GetBookFromISBNdbOptions): Promise<InsertBook | null> => {
const apiKey = env.ISBN_DB_API_KEY;
const url = `https://api2.isbndb.com/book/${isbn}`;

const fetchBookFunc = async () => {
const response = await fetch(url, {
headers: {
Authorization: apiKey,
"Content-Type": "application/json",
},
});

if (response.status === 429) {
throw new Error("Rate limit hit");
export class GetBookFromISBNdb extends BaseService<
GetBookFromISBNdbOptions,
GetBookFromISBNdbResult
> {
protected async execute({ isbn }: GetBookFromISBNdbOptions) {
const apiKey = env.ISBN_DB_API_KEY;
const url = `https://api2.isbndb.com/book/${isbn}`;

const fetchBookFunc = async () => {
const response = await fetch(url, {
headers: {
Authorization: apiKey,
"Content-Type": "application/json",
},
});

if (response.status === 429) {
throw new Error("Rate limit hit");
}

return response;
};

const response = await retryAsync(
fetchBookFunc,
AsyncResilience.exponentialBackoffWithJitter(),
);

if (!response.ok) {
this.logger.error("Failed to fetch book data");
return {
book: null,
};
}

return response;
};
const data = (await response.json()) as {
book: ISBNdbBook;
};
const book = transformISBNdbToInsertBook(data.book);

const response = await retryAsync(
fetchBookFunc,
AsyncResilience.exponentialBackoffWithJitter(),
);

if (!response.ok) {
console.error(response);
throw new Error("Failed to fetch book data");
return {
book,
};
}

const data = (await response.json()) as GetBookFromISBNdbResponse;
return transformISBNdbToInsertBook(data.book);
};
}
2 changes: 1 addition & 1 deletion apps/sovoli.com/src/services/isbndb/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface OtherISBN {
}

// Represents the full structure of a book in the response
export interface Book {
export interface ISBNdbBook {
title: string; // Title of the book
title_long?: string; // Long version of the title (if any)
isbn: string; // ISBN-10
Expand Down
4 changes: 2 additions & 2 deletions apps/sovoli.com/src/services/isbndb/searchBooksFromISBNdb.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { InsertBook } from "@sovoli/db/schema";

import type { Book } from "./models";
import type { ISBNdbBook } from "./models";
import { env } from "~/env";
import { AsyncResilience } from "~/utils/retry/AsyncResilience";
import { retryAsync } from "~/utils/retry/retry-async";
import { transformISBNdbToInsertBook } from "./transformISBNdbToBook";

// Response object structure for getBooksByQuery or getBooksByISBN
export interface SearchBooksFromISBNdbResponse {
books: Book[]; // Array of Book objects from the given query or ISBN search
books: ISBNdbBook[]; // Array of Book objects from the given query or ISBN search
}

export interface SearchtBooksFromISBNdbOptions {
Expand Down
4 changes: 2 additions & 2 deletions apps/sovoli.com/src/services/isbndb/transformISBNdbToBook.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { InsertBook } from "@sovoli/db/schema";

import type { Book } from "./models";
import type { ISBNdbBook } from "./models";

/**
* Transforms the ISBNdb API response into the InsertBookSchema for the database.
* @param book - The book data from ISBNdb API.
* @returns The transformed InsertBookSchema object.
*/
export const transformISBNdbToInsertBook = (book: Book): InsertBook => {
export const transformISBNdbToInsertBook = (book: ISBNdbBook): InsertBook => {
return {
isbn13: book.isbn13, // Ensure fallback to null if no isbn13
isbn10: book.isbn, // ISBN10 field
Expand Down
2 changes: 1 addition & 1 deletion apps/sovoli.com/src/services/knowledge/getKnowledges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class GetKnowledges extends BaseService<
GetKnowledgesOptions,
GetKnowledgesResult
> {
async execute({
protected async execute({
authUserId,
username,
page = 1,
Expand Down
1 change: 0 additions & 1 deletion turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
"VERCEL_URL",
"VERCEL_PROJECT_PRODUCTION_URL",
"npm_lifecycle_event",
"GOOGLE_BOOKS_API_KEY",
"TRIGGER_SECRET_KEY",
"AUTH_SECRET",
"OPENAI_API_KEY",
Expand Down

0 comments on commit 83e3480

Please sign in to comment.