diff --git a/task-1/queries.sql b/task-1/queries.sql index fecf7ad..ab7f9f9 100644 --- a/task-1/queries.sql +++ b/task-1/queries.sql @@ -1,27 +1,86 @@ ----- Queries +-- Question 1: +-- List the title and published year of every book +-- in the 'Science Fiction' genre, ordered by published year (oldest first). --- **Question 1** — List the title and published year of every book in the `'Science Fiction'` genre, ordered by published year (oldest first). +SELECT * FROM books WHERE genre = 'Science Fiction' +ORDER BY published_year DESC; --- **Question 2** — Show every book published before 1950. Display the title and year only. --- **Question 3** — Show every book in the database along with its author's full name. Combine `first_name` and `last_name` into a single column called `author`. _(Hint: you will need a JOIN.)_ +-- Question 2: +-- Show every book published before 1950. +-- Display the title and year only. --- **Question 4** — List all books written by Stephen King. Show the title and published year, ordered by year. _(Hint: JOIN the two tables and filter on the author's name.)_ +SELECT title, published_year FROM books WHERE published_year < 1950; --- **Question 5** — Add yourself as a new author. Use your real name, or make one up. Pick any nationality and birth year. +-- Question 3: +-- Show every book in the database along with its author's full name. +-- Combine first_name and last_name into a single column called author. --- **Question 6** — Add one book for the author you just inserted. It can be a real book or a made-up one. +SELECT title, a.first_name || ' ' || a.last_name as author +FROM books b +INNER JOIN authors a ON b.author_id = a.id; --- **Question 7** — The genre for "The Dark Tower: The Gunslinger" was entered incorrectly as `'Fantasy'`. It should be `'Horror'`. Write an UPDATE to fix it, then verify the change with a SELECT. +-- Question 4: +-- List all books written by Stephen King. +-- Show the title and published year, ordered by year. --- **Question 8** — Delete the book you added in Question 6. Make sure your query targets only that specific row. +SELECT title, published_year +FROM books b +INNER JOIN authors a ON b.author_id = a.id +WHERE a.last_name = 'King' +ORDER BY published_year DESC; ---- +-- Question 5: +-- Add yourself as a new author. Use your real name, or make one up. +-- Pick any nationality and birth year. --- ### Bonus questions _(optional)_ +INSERT INTO authors +(id, first_name, last_name, nationality, birth_year) +VALUES (25, 'Jawad', 'Al Bdiwi', 'Syrian', '1994'); --- These cover topics slightly beyond the core material. Have a go if you finish early. +-- ALTERNATIVE SYNTAX +-- INSERT INTO authors +-- SELECT +-- 25 AS id +-- , 'Jawad' AS first_name +-- , 'Al Bdiwi' AS last_name +-- , 'Syrian' AS nationality +-- , 1994 AS birth_year; --- **Bonus A** — How many books are there per genre? Show the genre name and the count, ordered from most to fewest books. +-- Question 6: +-- Add one book for the author you just inserted. +-- It can be a real book or a made-up one. --- **Bonus B** — Find any authors in the database who have no books at all. _(Hint: you will need a LEFT JOIN and check for NULL.) +INSERT INTO books +(id, title, published_year genre, author_id) +VALUES (101, 'SQL for Beginners', 2026, 'Non-fiction', 25); + +-- ALTERNATIVE SYNTAX +-- INSERT INTO books +-- SELECT +-- 101 AS id +-- , 'SQL for Beginners' AS title +-- , 2026 AS published_year +-- 'Non-fiction' AS genre +-- 25 AS author_id + +-- QUESTION 7: +-- The genre for "The Dark Tower: The Gunslinger" was entered incorrectly as 'Fantasy'. +-- It should be 'Horror'. Write an UPDATE to fix it, +-- then verify the change with a SELECT. + +UPDATE books +SET genre = 'Horror' +WHERE title = 'The Dark Tower: The Gunslinger'; + +SELECT title, genre +FROM books +WHERE title = 'The Dark Tower: The Gunslinger'; + + +-- Question 8: +-- Delete the book you added in Question 6. +-- Make sure your query targets only that specific row. + +DELETE * FROM books +WHERE id = 101; diff --git a/task-2/data/flashcards.db b/task-2/data/flashcards.db new file mode 100644 index 0000000..7891ce7 Binary files /dev/null and b/task-2/data/flashcards.db differ diff --git a/task-2/migrate.js b/task-2/migrate.js new file mode 100644 index 0000000..ebb6856 --- /dev/null +++ b/task-2/migrate.js @@ -0,0 +1,34 @@ +import fs from 'fs'; +import Database from 'better-sqlite3'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const db = new Database(join(__dirname, 'flashcards.db')); + +const data = JSON.parse( + fs.readFileSync(join(__dirname, 'data', 'data.json'), 'utf-8') +); + + +for (const deck of data.decks) { + db.prepare( + 'INSERT INTO decks (id, name, description) VALUES (?, ?, ?)' + ).run(deck.id, deck.name, deck.description); +} + + +for (const card of data.cards) { + db.prepare(` + INSERT INTO cards (id, question, answer, learned, deck_id) + VALUES (?, ?, ?, ?, ?) + `).run( + card.id, + card.question, + card.answer, + card.learned ? 1 : 0, + card.deckId + ); +} + +console.log('✅ Migration done!'); \ No newline at end of file diff --git a/task-2/setup.sql b/task-2/setup.sql new file mode 100644 index 0000000..65a6b71 --- /dev/null +++ b/task-2/setup.sql @@ -0,0 +1,20 @@ +-- SQLite + +CREATE TABLE decks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + description TEXT +); + +CREATE TABLE cards ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + question TEXT NOT NULL, + answer TEXT NOT NULL, + learned INTEGER DEFAULT 0, + deck_id INTEGER, + FOREIGN KEY (deck_id) REFERENCES decks(id) +); + +SELECT * FROM decks; + + \ No newline at end of file diff --git a/task-2/src/database.js b/task-2/src/database.js index a737fcb..c9571fe 100644 --- a/task-2/src/database.js +++ b/task-2/src/database.js @@ -1,43 +1,38 @@ -// database.js -// Your task: implement each function below using better-sqlite3. -// The function signatures are identical to storage.js so you can -// compare the two files side by side. -// -// When every function works correctly, `node app.js` should -// print exactly the same output as it did with storage.js. import Database from 'better-sqlite3'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __dirname = dirname(fileURLToPath(import.meta.url)); -const DB_FILE = join(__dirname, '../data/flashcards.db'); - -const db = new Database(DB_FILE); +const db = new Database(join(__dirname, '../flashcards.db')); // ---------------------------------------------------------------- // Decks // ---------------------------------------------------------------- export function getAllDecks() { - // TODO: return all rows from the decks table - throw new Error('Not implemented'); + return db.prepare('SELECT * FROM decks').all(); } export function getDeckById(id) { - // TODO: return the deck row with the given id, or null if not found - throw new Error('Not implemented'); + return db.prepare('SELECT * FROM decks WHERE id = ?').get(id) ?? null; } export function addDeck(name, description) { - // TODO: insert a new deck and return the new row (including its id) - throw new Error('Not implemented'); + const info = db + .prepare('INSERT INTO decks (name, description) VALUES (?, ?)') + .run(name, description); + + return { + id: info.lastInsertRowid, + name, + description, + }; } export function deleteDeck(deckId) { - // TODO: delete the deck with the given id - // return true if a row was deleted, false otherwise - throw new Error('Not implemented'); + const info = db.prepare('DELETE FROM decks WHERE id = ?').run(deckId); + return info.changes > 0; } // ---------------------------------------------------------------- @@ -45,23 +40,39 @@ export function deleteDeck(deckId) { // ---------------------------------------------------------------- export function getAllCardsForDeck(deckId) { - // TODO: return all card rows whose deckId matches - throw new Error('Not implemented'); + return db + .prepare( + 'SELECT id, question, answer, learned, deck_id AS deckId FROM cards WHERE deck_id = ?' + ) + .all(deckId); } export function addCard(question, answer, deckId) { - // TODO: insert a new card and return the new row (including its id) - throw new Error('Not implemented'); + const info = db + .prepare( + 'INSERT INTO cards (question, answer, learned, deck_id) VALUES (?, ?, 0, ?)' + ) + .run(question, answer, deckId); + + return { + id: info.lastInsertRowid, + question, + answer, + learned: 0, + deckId, + }; } export function markCardLearned(cardId) { - // TODO: set learned = 1 for the card with the given id - // return the updated row, or null if not found - throw new Error('Not implemented'); + db.prepare('UPDATE cards SET learned = 1 WHERE id = ?').run(cardId); + return db + .prepare( + 'SELECT id, question, answer, learned, deck_id AS deckId FROM cards WHERE id = ?' + ) + .get(cardId); } export function deleteCard(cardId) { - // TODO: delete the card with the given id - // return true if a row was deleted, false otherwise - throw new Error('Not implemented'); -} + const info = db.prepare('DELETE FROM cards WHERE id = ?').run(cardId); + return info.changes > 0; +} \ No newline at end of file diff --git a/task-2/src/storage.js b/task-2/src/storage.js index 4e59b53..05ab9b3 100644 --- a/task-2/src/storage.js +++ b/task-2/src/storage.js @@ -1,56 +1,40 @@ // storage.js -// This file handles all reading and writing of data. -// Currently it uses a JSON file on disk. -// In the assignment you will replace each function here with a SQLite query. +// This file now uses SQLite instead of JSON -import { readFileSync, writeFileSync } from 'fs'; +import Database from 'better-sqlite3'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __dirname = dirname(fileURLToPath(import.meta.url)); -const DATA_FILE = join(__dirname, '../data/data.json'); - -// --- helpers (you will remove these when you switch to SQLite) --- - -function readData() { - const raw = readFileSync(DATA_FILE, 'utf-8'); - return JSON.parse(raw); -} - -function writeData(data) { - writeFileSync(DATA_FILE, JSON.stringify(data, null, 2), 'utf-8'); -} +const db = new Database(join(__dirname, '../flashcards.db')); // ---------------------------------------------------------------- // Decks // ---------------------------------------------------------------- export function getAllDecks() { - const data = readData(); - return data.decks; + return db.prepare('SELECT * FROM decks').all(); } export function getDeckById(id) { - const data = readData(); - return data.decks.find(d => d.id === id) ?? null; + return db.prepare('SELECT * FROM decks WHERE id = ?').get(id) ?? null; } export function addDeck(name, description) { - const data = readData(); - const newId = Math.max(0, ...data.decks.map(d => d.id)) + 1; - const deck = { id: newId, name, description }; - data.decks.push(deck); - writeData(data); - return deck; + const info = db + .prepare('INSERT INTO decks (name, description) VALUES (?, ?)') + .run(name, description); + + return { + id: info.lastInsertRowid, + name, + description, + }; } export function deleteDeck(deckId) { - const data = readData(); - const index = data.decks.findIndex(d => d.id === deckId); - if (index === -1) return false; - data.decks.splice(index, 1); - writeData(data); - return true; + const info = db.prepare('DELETE FROM decks WHERE id = ?').run(deckId); + return info.changes > 0; } // ---------------------------------------------------------------- @@ -58,33 +42,39 @@ export function deleteDeck(deckId) { // ---------------------------------------------------------------- export function getAllCardsForDeck(deckId) { - const data = readData(); - return data.cards.filter(c => c.deckId === deckId); + return db + .prepare( + 'SELECT id, question, answer, learned, deck_id AS deckId FROM cards WHERE deck_id = ?' + ) + .all(deckId); } export function addCard(question, answer, deckId) { - const data = readData(); - const newId = Math.max(0, ...data.cards.map(c => c.id)) + 1; - const card = { id: newId, question, answer, learned: false, deckId }; - data.cards.push(card); - writeData(data); - return card; + const info = db + .prepare( + 'INSERT INTO cards (question, answer, learned, deck_id) VALUES (?, ?, 0, ?)' + ) + .run(question, answer, deckId); + + return { + id: info.lastInsertRowid, + question, + answer, + learned: 0, + deckId, + }; } export function markCardLearned(cardId) { - const data = readData(); - const card = data.cards.find(c => c.id === cardId); - if (!card) return null; - card.learned = true; - writeData(data); - return card; + db.prepare('UPDATE cards SET learned = 1 WHERE id = ?').run(cardId); + return db + .prepare( + 'SELECT id, question, answer, learned, deck_id AS deckId FROM cards WHERE id = ?' + ) + .get(cardId); } export function deleteCard(cardId) { - const data = readData(); - const index = data.cards.findIndex(c => c.id === cardId); - if (index === -1) return false; - data.cards.splice(index, 1); - writeData(data); - return true; -} + const info = db.prepare('DELETE FROM cards WHERE id = ?').run(cardId); + return info.changes > 0; +} \ No newline at end of file