diff --git a/memory_bank/schemas/database.md b/memory_bank/schemas/database.md index 5144e48..398b830 100644 --- a/memory_bank/schemas/database.md +++ b/memory_bank/schemas/database.md @@ -10,7 +10,7 @@ Created in: `20250307100515_add_reviews.sql` ### user_books Created in: `20250310143716_add_user_books.sql` -Updated in: `20250310150000_update_user_books_policies.sql`, `20250310160000_add_select_policy.sql`, `20250310160001_add_source_to_user_books.sql` +Updated in: `20250310150000_update_user_books_policies.sql`, `20250310160000_add_select_policy.sql`, `20250310160001_add_source_to_user_books.sql`, `20250403000000_add_book_api_sources.sql` - Table for tracking books in user collections - Has security policies for user access - Includes a source field to track where the book came from @@ -19,6 +19,12 @@ Updated in: `20250310150000_update_user_books_policies.sql`, `20250310160000_add Created in: `20250311143000_add_local_books.sql` - Table for storing locally added books +### book_api_sources +Created in: `20250403000000_add_book_api_sources.sql` +- Table for storing different book API sources (e.g., Google Books API, Open Library API) +- Contains API URLs, keys, and other configuration for each source +- Referenced by user_books.source field + ## Functions - Created timestamp function (`20250310143715_create_timestamp_function.sql`) diff --git a/src/services/bookApiSourceService.ts b/src/services/bookApiSourceService.ts new file mode 100644 index 0000000..dcd7eb8 --- /dev/null +++ b/src/services/bookApiSourceService.ts @@ -0,0 +1,78 @@ +import { supabase } from '@/lib/supabase'; +import { BookApiSource } from '@/types/book'; + +export const getBookApiSources = async (): Promise => { + const { data, error } = await supabase + .from('book_api_sources') + .select('*') + .order('name'); + + if (error) { + console.error('Error fetching book API sources:', error); + throw new Error('Failed to fetch book API sources'); + } + + return data || []; +}; + +export const getBookApiSourceByName = async (name: string): Promise => { + const { data, error } = await supabase + .from('book_api_sources') + .select('*') + .eq('name', name) + .single(); + + if (error) { + if (error.code === 'PGRST116') { + // No rows returned + return null; + } + console.error(`Error fetching book API source with name ${name}:`, error); + throw new Error(`Failed to fetch book API source with name ${name}`); + } + + return data; +}; + +export const createBookApiSource = async (source: Omit): Promise => { + const { data, error } = await supabase + .from('book_api_sources') + .insert(source) + .select() + .single(); + + if (error) { + console.error('Error creating book API source:', error); + throw new Error('Failed to create book API source'); + } + + return data; +}; + +export const updateBookApiSource = async (id: string, updates: Partial>): Promise => { + const { data, error } = await supabase + .from('book_api_sources') + .update(updates) + .eq('id', id) + .select() + .single(); + + if (error) { + console.error(`Error updating book API source with id ${id}:`, error); + throw new Error(`Failed to update book API source with id ${id}`); + } + + return data; +}; + +export const deleteBookApiSource = async (id: string): Promise => { + const { error } = await supabase + .from('book_api_sources') + .delete() + .eq('id', id); + + if (error) { + console.error(`Error deleting book API source with id ${id}:`, error); + throw new Error(`Failed to delete book API source with id ${id}`); + } +}; diff --git a/src/types/book.ts b/src/types/book.ts index 0b6d919..7d4ffa1 100644 --- a/src/types/book.ts +++ b/src/types/book.ts @@ -6,7 +6,7 @@ export interface Author { books?: Book[]; } -export type BookSource = 'google' | 'libro'; +export type BookSource = 'google' | 'libro' | 'openlibrary' | string; export interface Book { source: BookSource; @@ -34,6 +34,17 @@ export interface BookReview { created_at: string; } +export interface BookApiSource { + id: string; + name: string; + api_url: string; + api_key?: string; + description?: string; + is_active: boolean; + created_at: string; + updated_at: string; +} + export interface GoogleBooksResponse { items: { id: string; diff --git a/supabase/migrations/20250403000000_add_book_api_sources.sql b/supabase/migrations/20250403000000_add_book_api_sources.sql new file mode 100644 index 0000000..2e8eb81 --- /dev/null +++ b/supabase/migrations/20250403000000_add_book_api_sources.sql @@ -0,0 +1,40 @@ +-- Create book_api_sources table +CREATE TABLE book_api_sources ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name TEXT NOT NULL UNIQUE, + api_url TEXT NOT NULL, + api_key TEXT, + description TEXT, + is_active BOOLEAN NOT NULL DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- Enable RLS +ALTER TABLE book_api_sources ENABLE ROW LEVEL SECURITY; + +-- Create policies +CREATE POLICY "Book API sources are viewable by everyone" ON book_api_sources FOR +SELECT USING (true); + +CREATE POLICY "Only authenticated users can manage book API sources" ON book_api_sources FOR ALL USING (auth.role() = 'authenticated'); + +-- Create trigger for updated_at +CREATE TRIGGER set_book_api_sources_timestamp BEFORE +UPDATE ON book_api_sources FOR EACH ROW EXECUTE FUNCTION trigger_set_timestamp(); + +-- Insert default sources +INSERT INTO book_api_sources (name, api_url, description) VALUES +('google', 'https://www.googleapis.com/books/v1', 'Google Books API'), +('openlibrary', 'https://openlibrary.org/api', 'Open Library API'); + +-- Drop the check constraint on user_books.source +ALTER TABLE user_books DROP CONSTRAINT valid_source; + +-- Add a new constraint that references the book_api_sources table +ALTER TABLE user_books ADD CONSTRAINT valid_source CHECK ( + source IN (SELECT name FROM book_api_sources) OR source = 'libro' +); + +-- Add comment to explain the constraint +COMMENT ON CONSTRAINT valid_source ON user_books IS 'Source must be "libro" or a name from book_api_sources table';