anwesh2410 commited on
Commit
54862ee
1 Parent(s): f982485

Upload 39 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ data/Books.csv filter=lfs diff=lfs merge=lfs -text
37
+ data/Ratings.csv filter=lfs diff=lfs merge=lfs -text
38
+ data/Users.csv filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.8-slim
3
+
4
+ # Set the working directory to /app
5
+ WORKDIR /app
6
+
7
+ # Copy only the requirements file
8
+ COPY requirements.txt .
9
+
10
+ # Install dependencies
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Copy the rest of the code into /app
14
+ COPY . .
15
+
16
+ # Change working directory to /app/src where app.py is located
17
+ WORKDIR /app/src
18
+
19
+ # Set environment variables
20
+ ENV FLASK_APP=app.py
21
+ ENV FLASK_RUN_HOST=0.0.0.0
22
+
23
+ # Expose port 5000
24
+ EXPOSE 5000
25
+
26
+ # Run the Flask app
27
+ CMD ["flask", "run"]
README.md ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Book-Recommendation-Site
2
+
app.log ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 2024-11-17 03:19:42,479 - __main__ - INFO - Recommendations generated successfully!
2
+ 2024-11-17 03:19:45,800 - __main__ - INFO - Book details fetched successfully!
3
+ 2024-11-17 03:34:04,612 - __main__ - INFO - Registration successful!
4
+ 2024-11-17 03:34:44,357 - __main__ - INFO - Logged in successfully!
5
+ 2024-11-17 03:35:02,638 - __main__ - INFO - Recommendations generated successfully!
6
+ 2024-11-17 03:39:15,842 - __main__ - INFO - You have been logged out.
7
+ 2024-11-17 12:23:11,937 - __main__ - INFO - Logged in successfully!
8
+ 2024-11-17 12:23:50,376 - __main__ - INFO - Recommendations generated successfully!
9
+ 2024-11-17 12:24:08,042 - __main__ - INFO - Book details fetched successfully!
10
+ 2024-11-17 12:24:13,222 - __main__ - INFO - Book details fetched successfully!
11
+ 2024-11-17 12:24:16,881 - __main__ - INFO - Book details fetched successfully!
12
+ 2024-11-17 12:27:18,715 - __main__ - INFO - Recommendations generated successfully!
13
+ 2024-11-17 12:27:22,185 - __main__ - INFO - Book details fetched successfully!
14
+ 2024-11-17 12:27:25,480 - __main__ - INFO - Book details fetched successfully!
15
+ 2024-11-17 12:33:43,895 - __main__ - INFO - Book details fetched successfully!
16
+ 2024-11-17 12:33:45,377 - __main__ - INFO - Book details fetched successfully!
17
+ 2024-11-17 12:33:49,073 - __main__ - INFO - Recommendations generated successfully!
18
+ 2024-11-17 12:34:20,212 - __main__ - INFO - Recommendations generated successfully!
19
+ 2024-11-17 12:34:22,778 - __main__ - INFO - Book details fetched successfully!
20
+ 2024-11-17 12:36:10,477 - __main__ - INFO - Recommendations generated successfully!
21
+ 2024-11-17 12:36:13,752 - __main__ - INFO - Book details fetched successfully!
22
+ 2024-11-17 16:05:40,689 - __main__ - INFO - Logged in successfully!
23
+ 2024-11-17 16:06:15,027 - __main__ - INFO - Recommendations generated successfully!
24
+ 2024-11-17 16:06:21,275 - __main__ - INFO - Book details fetched successfully!
25
+ 2024-11-17 16:10:38,198 - __main__ - INFO - Book details fetched successfully!
26
+ 2024-11-18 23:04:01,575 - __main__ - INFO - Recommendations generated successfully!
27
+ 2024-11-18 23:04:04,536 - __main__ - INFO - Book details fetched successfully!
28
+ 2024-11-18 23:06:56,319 - __main__ - INFO - Book details fetched successfully!
29
+ 2024-11-18 23:07:29,479 - __main__ - INFO - Book details fetched successfully!
30
+ 2024-11-18 23:18:23,235 - __main__ - INFO - Logged in successfully!
31
+ 2024-11-18 23:23:18,931 - __main__ - INFO - You have been logged out.
32
+ 2024-11-18 23:23:25,279 - __main__ - INFO - Logged in successfully!
33
+ 2024-11-18 23:36:04,092 - __main__ - INFO - You have been logged out.
34
+ 2024-11-19 00:01:32,741 - __main__ - INFO - Logged in successfully!
35
+ 2024-11-19 00:02:07,922 - __main__ - INFO - You have been logged out.
36
+ 2024-11-19 00:02:17,037 - __main__ - INFO - Logged in successfully!
37
+ 2024-11-19 00:20:44,330 - __main__ - INFO - Recommendations generated successfully!
38
+ 2024-11-19 00:21:45,957 - __main__ - INFO - Book details fetched successfully!
39
+ 2024-11-19 00:34:43,886 - __main__ - INFO - Logged in successfully!
40
+ 2024-11-19 00:34:58,523 - __main__ - INFO - Recommendations generated successfully!
41
+ 2024-11-19 00:35:05,188 - __main__ - INFO - Book details fetched successfully!
config/heroku.yml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ build:
2
+ docker:
3
+ web: Dockerfile
4
+ run:
5
+ web: python src/app.py
data/.DS_Store ADDED
Binary file (6.15 kB). View file
 
data/Books.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8a406befc83452ab2dded8050dec47bccb6aa8110edb2512b62d8402f1a3d8dd
3
+ size 73293635
data/Ratings.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fbb819408aa8d42e7675200b26d48d66660b6e712bbd82585e5b564053d29de9
3
+ size 22633892
data/Users.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d82fe997ee9bfc455d0090b67d235694b82a9479d03e036665945ac9d30981bc
3
+ size 11017438
data/vectors1.csv ADDED
The diff for this file is too large to render. See raw diff
 
docker-compose.yml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ web:
5
+ build: .
6
+ ports:
7
+ - "5000:5000"
8
+ volumes:
9
+ - ./src:/src/app
10
+ # env_file:
11
+ # - .env
12
+ depends_on:
13
+ - mongo
14
+ mongo:
15
+ image: mongo:latest
16
+ restart: always
17
+ ports:
18
+ - "27017:27017" # Correctly formatted as a list
19
+ volumes:
20
+ - mongo_data:/data/db
21
+
22
+ volumes:
23
+ mongo_data:
model/book_model.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1a9e68520b930c37dac7b81c596df7278721069a63ee4679615db960a4872615
3
+ size 5193604
model/book_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1a9e68520b930c37dac7b81c596df7278721069a63ee4679615db960a4872615
3
+ size 5193604
model/train_model.py ADDED
File without changes
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Flask==3.0.3
2
+ elasticsearch==7.10.0
3
+ numpy==1.23.5
4
+ pandas==1.3.3
5
+ flask-login
6
+ pymongo
7
+ dnspython
8
+ werkzeug
9
+ joblib
10
+ requests
11
+ python-dotenv
src/__pycache__/app.cpython-38.pyc ADDED
Binary file (8.55 kB). View file
 
src/__pycache__/recommend.cpython-311.pyc ADDED
Binary file (3.32 kB). View file
 
src/__pycache__/recommend.cpython-38.pyc ADDED
Binary file (1.98 kB). View file
 
src/app.py ADDED
@@ -0,0 +1,353 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, redirect, url_for, flash
2
+ from flask_login import (
3
+ LoginManager,
4
+ login_user,
5
+ login_required,
6
+ logout_user,
7
+ current_user,
8
+ UserMixin
9
+ )
10
+ from werkzeug.security import generate_password_hash, check_password_hash
11
+ from datetime import datetime
12
+ from pymongo import MongoClient
13
+ from recommend import find_top_common_books
14
+ from urllib.parse import quote_plus, unquote_plus
15
+ from bson.objectid import ObjectId
16
+ import requests
17
+ import logging
18
+ import os
19
+
20
+ app = Flask(__name__, template_folder='../templates', static_folder='../static')
21
+ app.secret_key = os.environ.get('SECRET_KEY', 'your_default_secret_key') # Replace with a secure key
22
+
23
+ # Initialize Flask-Login
24
+ login_manager = LoginManager()
25
+ login_manager.init_app(app)
26
+ login_manager.login_view = 'login' # Redirect to 'login' view if unauthorized
27
+
28
+ # Google Books API Key from environment variable
29
+ GOOGLE_BOOKS_API_KEY = 'AIzaSyBqTms_1DSR3xcxCapdmizZiZUMaswaI9M' # Set this in your environment
30
+
31
+ # Initialize MongoDB client
32
+ client = MongoClient("mongodb+srv://Atharva:whatismongodb@book.xx2jw.mongodb.net/book_dataset?retryWrites=true&w=majority")
33
+ db = client['book_dataset']
34
+ books_collection = db['BOOK']
35
+ reviews_collection = db['reviews'] # New Collection for Reviews
36
+ users_collection = db['users'] # New Collection for Users
37
+
38
+ # Simple in-memory cache for book details
39
+ book_details_cache = {}
40
+
41
+ def fetch_book_details(title, api_key=None):
42
+ """Fetch book details from Google Books API based on the book title."""
43
+ if title in book_details_cache:
44
+ return book_details_cache[title]
45
+
46
+ base_url = "https://www.googleapis.com/books/v1/volumes/"
47
+ params = {
48
+ 'q': title,
49
+ 'maxResults': 1
50
+ }
51
+ if api_key:
52
+ params['key'] = api_key
53
+
54
+ try:
55
+ response = requests.get(base_url, params=params)
56
+ response.raise_for_status()
57
+ data = response.json()
58
+
59
+ if 'items' in data and len(data['items']) > 0:
60
+ volume_info = data['items'][0].get('volumeInfo', {})
61
+ book_details = {
62
+ 'authors': volume_info.get('authors', ["Unknown Author"]),
63
+ 'description': volume_info.get('description', "No description available."),
64
+ 'averageRating': volume_info.get('averageRating', "N/A"),
65
+ 'ratingsCount': volume_info.get('ratingsCount', "N/A"),
66
+ 'publishedDate': volume_info.get('publishedDate', "N/A"),
67
+ 'pageCount': volume_info.get('pageCount', "N/A"),
68
+ 'language': volume_info.get('language', "N/A"),
69
+ 'publisher': volume_info.get('publisher', "N/A")
70
+ }
71
+ book_details_cache[title] = book_details
72
+ return book_details
73
+ else:
74
+ book_details = {
75
+ 'authors': ["Unknown Author"],
76
+ 'description': "No description available.",
77
+ 'averageRating': "N/A",
78
+ 'ratingsCount': "N/A",
79
+ 'publishedDate': "N/A",
80
+ 'pageCount': "N/A",
81
+ 'language': "N/A",
82
+ 'publisher': "N/A"
83
+ }
84
+ book_details_cache[title] = book_details
85
+ return book_details
86
+ except requests.exceptions.RequestException as e:
87
+ print(f"Error fetching details for '{title}': {e}")
88
+ book_details = {
89
+ 'authors': ["Unknown Author"],
90
+ 'description': "No description available.",
91
+ 'averageRating': "N/A",
92
+ 'ratingsCount': "N/A",
93
+ 'publishedDate': "N/A",
94
+ 'pageCount': "N/A",
95
+ 'language': "N/A",
96
+ 'publisher': "N/A"
97
+ }
98
+ book_details_cache[title] = book_details
99
+ return book_details
100
+
101
+ # User Model
102
+ class User(UserMixin):
103
+ def __init__(self, user_data):
104
+ self.id = str(user_data['_id'])
105
+ self.username = user_data['username']
106
+ self.email = user_data['email']
107
+
108
+ @staticmethod
109
+ def get(user_id):
110
+ user_data = users_collection.find_one({'_id': ObjectId(user_id)})
111
+ if user_data:
112
+ return User(user_data)
113
+ return None
114
+
115
+ @login_manager.user_loader
116
+ def load_user(user_id):
117
+ return User.get(user_id)
118
+
119
+ # Logging Configuration
120
+ logger = logging.getLogger(__name__)
121
+ logger.setLevel(logging.DEBUG)
122
+
123
+ # Create handlers
124
+ file_handler = logging.FileHandler('app.log')
125
+ stream_handler = logging.StreamHandler()
126
+
127
+ # Create formatters and add to handlers
128
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
129
+ file_handler.setFormatter(formatter)
130
+ stream_handler.setFormatter(formatter)
131
+
132
+ # Add handlers to the logger
133
+ logger.addHandler(file_handler)
134
+ logger.addHandler(stream_handler)
135
+
136
+ # Registration Route
137
+ @app.route('/register', methods=['GET', 'POST'])
138
+ def register():
139
+ if request.method == 'POST':
140
+ # Get form data
141
+ username = request.form.get('username')
142
+ email = request.form.get('email')
143
+ password = request.form.get('password')
144
+ confirm_password = request.form.get('confirm_password')
145
+
146
+ # Validate form inputs
147
+ if not all([username, email, password, confirm_password]):
148
+ logger.warning('Please fill out all fields.')
149
+ flash('Please fill out all fields.', 'danger')
150
+ return redirect(url_for('register'))
151
+
152
+ if password != confirm_password:
153
+ logger.warning('Passwords do not match.')
154
+ flash('Passwords do not match.', 'danger')
155
+ return redirect(url_for('register'))
156
+
157
+ # Check if user already exists
158
+ existing_user = users_collection.find_one({'email': email})
159
+ if existing_user:
160
+ logger.warning('Email already registered.')
161
+ flash('Email already registered.', 'danger')
162
+ return redirect(url_for('register'))
163
+
164
+ # Hash the password
165
+ password_hash = generate_password_hash(password)
166
+
167
+ # Create new user document
168
+ new_user = {
169
+ 'username': username,
170
+ 'email': email,
171
+ 'password': password_hash,
172
+ 'created_at': datetime.utcnow()
173
+ }
174
+
175
+ # Insert the new user into the database
176
+ users_collection.insert_one(new_user)
177
+
178
+ logger.info('Registration successful!')
179
+ flash('Registration successful! Please log in.', 'success')
180
+ return redirect(url_for('login'))
181
+
182
+ return render_template('register.html')
183
+
184
+ # Login Route
185
+ @app.route('/login', methods=['GET', 'POST'])
186
+ def login():
187
+ if request.method == 'POST':
188
+ # Get form data
189
+ email = request.form.get('email')
190
+ password = request.form.get('password')
191
+
192
+ # Find user by email
193
+ user_data = users_collection.find_one({'email': email})
194
+ if user_data and check_password_hash(user_data['password'], password):
195
+ user = User(user_data)
196
+ login_user(user)
197
+ logger.info('Logged in successfully!')
198
+ flash('Logged in successfully!', 'success')
199
+ next_page = request.args.get('next')
200
+ return redirect(next_page or url_for('homepage'))
201
+ else:
202
+ logger.warning('Invalid email or password.')
203
+ flash('Invalid email or password.', 'danger')
204
+ return redirect(url_for('login'))
205
+
206
+ return render_template('login.html')
207
+
208
+ # Logout Route
209
+ @app.route('/logout')
210
+ @login_required
211
+ def logout():
212
+ logout_user()
213
+ logger.info('You have been logged out.')
214
+ flash('You have been logged out.', 'success')
215
+ return redirect(url_for('homepage'))
216
+
217
+ # Contact Route
218
+ @app.route('/contact', methods=['GET', 'POST'])
219
+ def contact():
220
+ if request.method == 'POST':
221
+ # Retrieve form data
222
+ name = request.form.get('name')
223
+ email = request.form.get('email')
224
+ review = request.form.get('review')
225
+ rating = request.form.get('rating')
226
+
227
+ # Input Validation (Basic Example)
228
+ if not all([name, email, review, rating]):
229
+ logger.warning("All fields are required!")
230
+ flash("All fields are required!", "danger")
231
+ return redirect(url_for('contact'))
232
+
233
+ try:
234
+ # Convert rating to integer
235
+ rating = int(rating)
236
+ if rating < 1 or rating > 5:
237
+ raise ValueError("Rating must be between 1 and 5.")
238
+ except ValueError as ve:
239
+ logger.warning(str(ve))
240
+ flash(str(ve), "danger")
241
+ return redirect(url_for('contact'))
242
+
243
+ # Create a review document
244
+ review_document = {
245
+ "name": name,
246
+ "email": email,
247
+ "review": review,
248
+ "rating": rating,
249
+ "timestamp": datetime.utcnow() # Optional: Add timestamp
250
+ }
251
+
252
+ # Insert the review into the 'reviews' collection
253
+ reviews_collection.insert_one(review_document)
254
+
255
+ logger.info("Thank you for your feedback!")
256
+ flash("Thank you for your feedback!", "success")
257
+ return redirect(url_for('homepage'))
258
+
259
+ return render_template('contact.html')
260
+
261
+ # Homepage Route
262
+ @app.route('/')
263
+ def homepage():
264
+ return render_template('homepage.html')
265
+
266
+ # Recommendations Route
267
+ @app.route('/recommendations', methods=['GET', 'POST'])
268
+ def index():
269
+ if request.method == 'POST':
270
+ book1 = request.form.get('book1')
271
+ book2 = request.form.get('book2')
272
+ book3 = request.form.get('book3')
273
+
274
+ input_books = []
275
+ for book in [book1, book2, book3]:
276
+ if book:
277
+ matched_books = search_books(book)
278
+ if matched_books:
279
+ input_books.append(matched_books[0]['title'])
280
+
281
+ if not input_books:
282
+ flash("Please enter at least one valid book.", "danger")
283
+ return redirect(url_for('index'))
284
+
285
+ recommendations = find_top_common_books(input_books)
286
+ logger.info('Recommendations generated successfully!')
287
+
288
+ # Flatten the recommendations list
289
+ flattened_recommendations = [
290
+ book for sublist in recommendations for book in sublist
291
+ ]
292
+
293
+ # Pass the flattened list to the template with minimal information
294
+ return render_template(
295
+ 'index.html',
296
+ recommendations=flattened_recommendations
297
+ )
298
+
299
+ return render_template('index.html')
300
+
301
+ # Book Detail Route
302
+ @app.route('/book/<title>')
303
+ def book_detail(title):
304
+ # Decode the title from URL
305
+ decoded_title = unquote_plus(title)
306
+
307
+ # Fetch book details (from cache or API)
308
+ details = fetch_book_details(decoded_title, GOOGLE_BOOKS_API_KEY)
309
+
310
+ # Fetch image_url from MongoDB if needed
311
+ book_in_db = books_collection.find_one({"Book-Title": decoded_title}, {"Image-URL-M": 1})
312
+ image_url = book_in_db["Image-URL-M"] if book_in_db else "/static/default.jpg"
313
+
314
+ enriched_book = {
315
+ 'title': decoded_title,
316
+ 'image_url': image_url,
317
+ 'authors': details['authors'],
318
+ 'description': details['description'],
319
+ 'averageRating': details['averageRating'],
320
+ 'ratingsCount': details['ratingsCount'],
321
+ 'publishedDate': details['publishedDate'],
322
+ 'pageCount': details['pageCount'],
323
+ 'language': details['language'],
324
+ 'publisher': details['publisher']
325
+ }
326
+
327
+ logger.info('Book details fetched successfully!')
328
+ return render_template('book_detail.html', book=enriched_book)
329
+
330
+ # Profile Route
331
+ @app.route('/profile')
332
+ @login_required
333
+ def profile():
334
+ return render_template('profile.html', user=current_user)
335
+
336
+ # Search Books Function
337
+ def search_books(title):
338
+ """Search for book titles in MongoDB similar to the given title."""
339
+ query = {"Book-Title": {"$regex": str(title), "$options": "i"}}
340
+ matched_books = books_collection.find(query, {"Book-Title": 1, "Image-URL-M": 1}).limit(2)
341
+ return [{"title": book["Book-Title"], "image_url": book["Image-URL-M"]} for book in matched_books]
342
+
343
+ # Template Filters
344
+ @app.template_filter('url_encode')
345
+ def url_encode_filter(s):
346
+ return quote_plus(s)
347
+
348
+ @app.template_filter('url_decode')
349
+ def url_decode_filter(s):
350
+ return unquote_plus(s)
351
+
352
+ if __name__ == '__main__':
353
+ app.run(debug=True)
src/index_books.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from elasticsearch import Elasticsearch, helpers
2
+ import pandas as pd
3
+
4
+ # Load books data
5
+ books_df = pd.read_csv('data/Books.csv', dtype={'Year-Of-Publication': 'str'}, low_memory=False)
6
+
7
+ # Elasticsearch setup
8
+ es = Elasticsearch("http://localhost:9202")
9
+
10
+ # Create an index for books (skip if already exists)
11
+ index_name = 'books'
12
+ if not es.indices.exists(index=index_name):
13
+ es.indices.create(index=index_name)
14
+
15
+ # Bulk indexing books data
16
+ actions = [
17
+ {
18
+ "_index": index_name,
19
+ "_id": row['ISBN'],
20
+ "_source": {
21
+ "Book-Title": row['Book-Title'],
22
+ "Book-Author": row['Book-Author'],
23
+ "Year-Of-Publication": row['Year-Of-Publication'],
24
+ "Publisher": row['Publisher'],
25
+ "Image-URL-S": row['Image-URL-S'],
26
+ "Image-URL-M": row['Image-URL-M'],
27
+ "Image-URL-L": row['Image-URL-L']
28
+ }
29
+ }
30
+ for _, row in books_df.iterrows()
31
+ ]
32
+
33
+ # Indexing
34
+ helpers.bulk(es, actions)
src/recommend.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import joblib
2
+ from pymongo import MongoClient
3
+ from collections import Counter
4
+ import pandas as pd
5
+ from urllib.parse import quote_plus
6
+ # Load the pre-trained KNN model
7
+ model_path = 'model/book_model.joblib'
8
+ model = joblib.load(model_path)
9
+
10
+ # # # MongoDB client setup
11
+ client = MongoClient("mongodb+srv://Atharva:whatismongodb@book.xx2jw.mongodb.net/")
12
+ db = client['book_dataset']
13
+ # books_collection = db['BOOK']
14
+ # Updated MongoClient initialization
15
+ # Encode credentials
16
+ # username = 'debook'
17
+ # password = 'debook?' # Replace with your actual password
18
+
19
+ # encoded_username = quote_plus(username)
20
+ # encoded_password = quote_plus(password)
21
+
22
+ # # Create the connection string
23
+ # connection_string = f"mongodb+srv://{encoded_username}:{encoded_password}@cluster0.chkfn.mongodb.net/book_dataset?retryWrites=true&w=majority"
24
+
25
+ # # Initialize MongoDB client
26
+ # client = MongoClient(connection_string)
27
+ # db = client['book_dataset']
28
+
29
+ books_collection = db['BOOK']
30
+ df = pd.read_csv('data/vectors1.csv', index_col='Book-Title')
31
+
32
+ def search_books(title):
33
+ """Search for book titles in MongoDB similar to the given title."""
34
+ query = {"Book-Title": {"$regex": str(title), "$options": "i"}}
35
+ matched_books = books_collection.find(query, {"Book-Title": 1, "Image-URL-M": 1}).limit(2)
36
+ return [{"title": book["Book-Title"], "image_url": book["Image-URL-M"]} for book in matched_books]
37
+
38
+ def find_top_common_books(titles):
39
+ book_counter = Counter()
40
+ recommended_books = []
41
+
42
+ for title in titles:
43
+ try:
44
+ book = df.loc[title]
45
+ except KeyError as e:
46
+ print('The given book', e, 'does not exist')
47
+ continue # Skip to the next title if the book is not found
48
+
49
+ # Find nearest neighbors
50
+ distance, indice = model.kneighbors([book.values], n_neighbors=30)
51
+
52
+ # Get top 5 recommended books for this title
53
+ recommended_books = pd.DataFrame({
54
+ 'title': df.iloc[indice[0]].index.values,
55
+ 'distance': distance[0]
56
+ }).sort_values(by='distance', ascending=True).head(5)['title'].values
57
+
58
+ # Update the counter with the recommended books
59
+ book_counter.update(recommended_books)
60
+
61
+ # Get the top common books
62
+ top_common_books = [book for book, _ in book_counter.most_common(7)]
63
+ final_recommendations=[]
64
+ for b in top_common_books:
65
+ final_recommendations.append(search_books(b))
66
+
67
+
68
+ return final_recommendations
src/utils.py ADDED
File without changes
static/images/community.png ADDED
static/images/github.jpeg ADDED
static/images/hihi.jpg ADDED
static/images/library.jpg ADDED
static/images/personalized.png ADDED
static/images/track.png ADDED
static/library.jpg ADDED
static/styles.css ADDED
@@ -0,0 +1,1027 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Reset default browser styles */
2
+ * {
3
+ margin: 0;
4
+ padding: 0;
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ /* Reset some basic elements */
9
+ body, h1, h2, h3, p, ul, li, form, input, textarea, button {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ /* General Styles */
16
+ body {
17
+ font-family: 'Lato', sans-serif;
18
+ margin: 0;
19
+ padding: 0;
20
+ background-color: #f9f9f9;
21
+ color: #333;
22
+ }
23
+
24
+ /* Navigation Bar */
25
+ nav {
26
+ background-color: #4CAF50;
27
+ padding: 15px 20px;
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ }
32
+
33
+ nav .logo {
34
+ float: left;
35
+ padding: 15px 20px;
36
+ font-size: 1.5em;
37
+ font-weight: 700;
38
+ }
39
+
40
+ nav .logo a {
41
+ color: #fff;
42
+ text-decoration: none;
43
+ font-size: 1.5em;
44
+ margin-left: 20px;
45
+ font-weight: bold;
46
+ }
47
+
48
+ .nav-links {
49
+ list-style: none;
50
+ display: flex;
51
+ gap: 15px;
52
+ }
53
+
54
+ nav .nav-links {
55
+ float: right;
56
+ margin-right: 20px;
57
+ }
58
+
59
+ nav .nav-links li {
60
+ display: inline-block;
61
+ line-height: 60px;
62
+ margin-left: 20px;
63
+ }
64
+
65
+ nav .nav-links li a {
66
+ display: block;
67
+ padding: 0 20px;
68
+ text-decoration: none;
69
+ color: #fff;
70
+ font-size: 1.1em;
71
+ font-weight: 500;
72
+ }
73
+
74
+ .nav-links li a {
75
+ color: #fff;
76
+ text-decoration: none;
77
+ font-weight: 500;
78
+ padding: 8px 12px;
79
+ border-radius: 4px;
80
+ transition: background-color 0.3s ease;
81
+ }
82
+
83
+ nav .nav-links li a:hover {
84
+ text-decoration: underline;
85
+ }
86
+
87
+ .nav-links li a:hover {
88
+ background-color: #45a049;
89
+ }
90
+
91
+ /* Hero Section (homepage.html) */
92
+ .hero {
93
+ position: relative;
94
+ background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('/static/library.jpg') no-repeat center center/cover;
95
+ height: 80vh;
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ color: #ffffff;
100
+ text-align: center;
101
+ overflow: hidden;
102
+ background-image: url('{{ url_for('static', filename='images/hero-background.jpg') }}');
103
+ background-size: cover;
104
+ background-position: center;
105
+ }
106
+
107
+ .hero::after {
108
+ content: '';
109
+ position: absolute;
110
+ top: 0;
111
+ left: 0;
112
+ right: 0;
113
+ bottom: 0;
114
+ background: rgba(0, 0, 0, 0.3);
115
+ content: "";
116
+ position: absolute;
117
+ top: 0;
118
+ left: 0;
119
+ width: 100%;
120
+ height: 100%;
121
+ background-color: rgba(0, 0, 0, 0.5);
122
+ }
123
+
124
+ .hero-content {
125
+ position: relative;
126
+ z-index: 1;
127
+ animation: fadeInUp 1s ease-out forwards;
128
+ text-align: center;
129
+ max-width: 600px;
130
+ padding: 20px;
131
+ }
132
+
133
+ .hero-content h1 {
134
+ font-size: 4em;
135
+ margin-bottom: 20px;
136
+ text-shadow: 2px 2px 8px rgba(0,0,0,0.7);
137
+ font-size: 3em;
138
+ }
139
+
140
+
141
+ .hero-content p {
142
+ font-size: 1.5em;
143
+ margin-bottom: 30px;
144
+ text-shadow: 1px 1px 5px rgba(0,0,0,0.6);
145
+ font-size: 1.2em;
146
+ }
147
+
148
+ .cta-button {
149
+ background: linear-gradient(45deg, #ff6347, #ff4c29);
150
+ color: #fff;
151
+ padding: 15px 40px;
152
+ border: none;
153
+ border-radius: 50px;
154
+ font-size: 1.3em;
155
+ cursor: pointer;
156
+ text-decoration: none;
157
+ transition: transform 0.3s, box-shadow 0.3s;
158
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
159
+ background-color: #4CAF50;
160
+ padding: 15px 30px;
161
+ border-radius: 5px;
162
+ font-size: 1em;
163
+ transition: background-color 0.3s ease;
164
+ }
165
+
166
+ .cta-button:hover {
167
+ transform: translateY(-5px);
168
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
169
+ background-color: #45a049;
170
+ }
171
+
172
+ /* About Section (homepage.html) */
173
+ .about {
174
+ padding: 80px 20px;
175
+ background-color: #fff;
176
+ text-align: center;
177
+ animation: fadeIn 1s ease-out forwards;
178
+ }
179
+
180
+ .about h2 {
181
+ font-size: 2.5em;
182
+ margin-bottom: 20px;
183
+ color: #333;
184
+ }
185
+
186
+ .about p {
187
+ font-size: 1.2em;
188
+ color: #555;
189
+ margin-bottom: 20px;
190
+ max-width: 800px;
191
+ margin-left: auto;
192
+ margin-right: auto;
193
+ line-height: 1.6;
194
+ transition: color 0.3s;
195
+ }
196
+
197
+ .about p:hover {
198
+ color: #ff6347;
199
+ }
200
+
201
+ /* Features Section (homepage.html) */
202
+ .features {
203
+ background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('/static/images/hihi.jpg') no-repeat center center/cover;
204
+
205
+ padding: 80px 20px;
206
+ background-color: #f0f8ff;
207
+ text-align: center;
208
+ padding: 60px 0;
209
+ background-color: #f1f1f1;
210
+ }
211
+
212
+ .features h2 {
213
+ font-size: 2.5em;
214
+ margin-bottom: 40px;
215
+ color: #333;
216
+ position: relative;
217
+ text-align: center;
218
+ margin-bottom: 40px;
219
+ color: #222;
220
+ }
221
+
222
+ .features h2::after {
223
+ content: '';
224
+ width: 60px;
225
+ height: 4px;
226
+ background: #ff6347;
227
+ display: block;
228
+ margin: 10px auto 0;
229
+ border-radius: 2px;
230
+ }
231
+
232
+ .feature-list {
233
+ display: flex;
234
+ flex-wrap: wrap;
235
+ justify-content: center;
236
+ gap: 20px;
237
+ }
238
+
239
+ .features-grid {
240
+ display: flex;
241
+ flex-wrap: wrap;
242
+ gap: 20px;
243
+ justify-content: center;
244
+ }
245
+
246
+ .feature-item {
247
+ width: 280px;
248
+ margin: 15px;
249
+ padding: 30px 20px;
250
+ background-color: #fff;
251
+ border-radius: 15px;
252
+ box-shadow: 0 6px 20px rgba(0,0,0,0.1);
253
+ transition: transform 0.3s, box-shadow 0.3s;
254
+ background-color: #fff;
255
+ padding: 20px;
256
+ border-radius: 8px;
257
+ flex: 1 1 250px;
258
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
259
+ text-align: center;
260
+ }
261
+
262
+ .feature-item:hover {
263
+ transform: translateY(-10px);
264
+ box-shadow: 0 12px 30px rgba(0,0,0,0.2);
265
+ }
266
+
267
+ .feature-item h3 {
268
+ font-size: 1.8em;
269
+ margin-bottom: 15px;
270
+ color: #333;
271
+ margin-bottom: 15px;
272
+ color: #4CAF50;
273
+ }
274
+
275
+ .feature-item p {
276
+ font-size: 1em;
277
+ color: #666;
278
+ line-height: 1.5;
279
+ color: #555;
280
+ line-height: 1.6;
281
+ }
282
+
283
+ /* Call to Action Section (homepage.html) */
284
+ .cta {
285
+ padding: 80px 20px;
286
+ text-align: center;
287
+ background: linear-gradient(135deg, #ff7e5f, #feb47b);
288
+ color: #fff;
289
+ animation: slideIn 1s ease-out forwards;
290
+ }
291
+
292
+ .cta h2 {
293
+ font-size: 2.5em;
294
+ margin-bottom: 20px;
295
+ }
296
+
297
+ .cta .cta-button {
298
+ background: #fff;
299
+ color: #ff6347;
300
+ padding: 15px 40px;
301
+ border: none;
302
+ border-radius: 50px;
303
+ font-size: 1.3em;
304
+ cursor: pointer;
305
+ text-decoration: none;
306
+ transition: background-color 0.3s, color 0.3s;
307
+ }
308
+
309
+ .cta .cta-button:hover {
310
+ background-color: #ff4c29;
311
+ color: #fff;
312
+ }
313
+
314
+ /* Main Container */
315
+ .container {
316
+ width: 90%;
317
+ max-width: 1200px;
318
+ margin: 40px auto;
319
+ padding: 20px;
320
+ background-color: #ffffff;
321
+ border-radius: 8px;
322
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
323
+ animation: fadeIn 1s ease-out forwards;
324
+ }
325
+
326
+ /* Homepage Headings and Text */
327
+ .container h1 {
328
+ font-size: 3.5em;
329
+ margin-bottom: 15px;
330
+ color: #333;
331
+ text-align: center;
332
+ text-shadow: 1px 1px 5px rgba(0,0,0,0.1);
333
+ }
334
+
335
+ .container p {
336
+ font-size: 1.3em;
337
+ color: #555;
338
+ margin-bottom: 50px;
339
+ text-align: center;
340
+ max-width: 800px;
341
+ margin-left: auto;
342
+ margin-right: auto;
343
+ line-height: 1.6;
344
+ }
345
+
346
+ /* Animations */
347
+ @keyframes fadeInUp {
348
+ from {
349
+ opacity: 0;
350
+ transform: translateY(40px);
351
+ }
352
+ to {
353
+ opacity: 1;
354
+ transform: translateY(0);
355
+ }
356
+ }
357
+
358
+ @keyframes fadeIn {
359
+ from {
360
+ opacity: 0;
361
+ }
362
+ to {
363
+ opacity: 1;
364
+ }
365
+ }
366
+
367
+ @keyframes slideIn {
368
+ from {
369
+ opacity: 0;
370
+ transform: translateX(-100%);
371
+ }
372
+ to {
373
+ opacity: 1;
374
+ transform: translateX(0);
375
+ }
376
+ }
377
+
378
+ /* styles.css */
379
+
380
+ /* ... Existing Styles ... */
381
+
382
+ /* Contact Section */
383
+ .contact {
384
+ padding: 80px 20px;
385
+ background-color: #fff;
386
+ text-align: center;
387
+ animation: fadeIn 1s ease-out forwards;
388
+ }
389
+
390
+ .contact h1 {
391
+ font-size: 2.5em;
392
+ margin-bottom: 20px;
393
+ color: #333;
394
+ }
395
+
396
+ .contact p {
397
+ font-size: 1.2em;
398
+ color: #555;
399
+ margin-bottom: 40px;
400
+ max-width: 800px;
401
+ margin-left: auto;
402
+ margin-right: auto;
403
+ line-height: 1.6;
404
+ }
405
+
406
+ .contact-form {
407
+ max-width: 600px;
408
+ margin: auto;
409
+ text-align: left;
410
+ background-color: #f9f9f9;
411
+ padding: 30px;
412
+ border-radius: 10px;
413
+ box-shadow: 0 6px 20px rgba(0,0,0,0.1);
414
+ transition: box-shadow 0.3s;
415
+ }
416
+
417
+ .contact-form:hover {
418
+ box-shadow: 0 12px 30px rgba(0,0,0,0.2);
419
+ }
420
+
421
+ .contact-form .form-group {
422
+ margin-bottom: 25px;
423
+ }
424
+
425
+ .contact-form label {
426
+ display: block;
427
+ font-size: 1.1em;
428
+ color: #333;
429
+ margin-bottom: 8px;
430
+ font-weight: 600;
431
+ }
432
+
433
+ .contact-form input[type="text"],
434
+ .contact-form input[type="email"],
435
+ .contact-form textarea,
436
+ .contact-form select {
437
+ width: 100%;
438
+ padding: 12px 15px;
439
+ border: 2px solid #ccc;
440
+ border-radius: 5px;
441
+ font-size: 1em;
442
+ transition: border-color 0.3s, box-shadow 0.3s;
443
+ }
444
+
445
+ .contact-form input[type="text"]:focus,
446
+ .contact-form input[type="email"]:focus,
447
+ .contact-form textarea:focus,
448
+ .contact-form select:focus {
449
+ border-color: #ff6347;
450
+ box-shadow: 0 0 5px rgba(255, 99, 71, 0.5);
451
+ outline: none;
452
+ }
453
+
454
+ .contact-form button {
455
+ background-color: #ff6347;
456
+ color: #fff;
457
+ padding: 15px 30px;
458
+ border: none;
459
+ border-radius: 50px;
460
+ font-size: 1.2em;
461
+ cursor: pointer;
462
+ transition: background-color 0.3s, transform 0.3s, box-shadow 0.3s;
463
+ display: block;
464
+ margin: auto;
465
+ }
466
+
467
+ .contact-form button:hover {
468
+ background-color: #ff4c29;
469
+ transform: translateY(-3px);
470
+ box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2);
471
+ }
472
+
473
+ /* Responsive Adjustments */
474
+ @media (max-width: 768px) {
475
+ .contact-form {
476
+ padding: 20px;
477
+ }
478
+
479
+ .contact-form button {
480
+ width: 100%;
481
+ }
482
+ }
483
+
484
+ /* Form Styles (recommendations) */
485
+ form {
486
+
487
+ display: flex;
488
+ flex-direction: column;
489
+ align-items: center;
490
+ margin-bottom: 50px;
491
+ }
492
+
493
+ .form-group {
494
+ margin-bottom: 20px;
495
+ width: 100%;
496
+ max-width: 500px;
497
+ }
498
+
499
+ label {
500
+ display: block;
501
+ font-size: 1em;
502
+ color: #333;
503
+ margin-bottom: 8px;
504
+ }
505
+
506
+ input[type="text"],
507
+ input[type="email"],
508
+ textarea {
509
+ width: 100%;
510
+ padding: 12px;
511
+ border: 2px solid #ccc;
512
+ border-radius: 5px;
513
+ font-size: 1em;
514
+ }
515
+
516
+ button {
517
+ background-color: #ff6347;
518
+ color: #fff;
519
+ padding: 15px 30px;
520
+ border: none;
521
+ border-radius: 5px;
522
+ font-size: 1.2em;
523
+ cursor: pointer;
524
+ transition: background-color 0.3s;
525
+ margin-top: 20px;
526
+ }
527
+
528
+ button:hover {
529
+ background-color: #ff4c29;
530
+ }
531
+
532
+ /* Recommendations/Book Cards */
533
+ .recommendations {
534
+ display: flex;
535
+ flex-wrap: wrap;
536
+ justify-content: center;
537
+ margin-top: 40px;
538
+ gap: 20px;
539
+ }
540
+
541
+ .book-card {
542
+ background-color: #fafafa;
543
+ border: 1px solid #ddd;
544
+ border-radius: 3px;
545
+ margin: 15px;
546
+ padding: 20px;
547
+ width: 220px;
548
+ text-align: center;
549
+ transition: box-shadow 0.3s;
550
+ width: 200px;
551
+ padding: 10px;
552
+ }
553
+
554
+ .book-card:hover {
555
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
556
+ }
557
+
558
+ .book-card img {
559
+ max-width: 100%;
560
+ height: auto;
561
+ margin-bottom: 15px;
562
+ border-radius: 5px;
563
+ }
564
+
565
+ .book-card img.book-image {
566
+ width: 100%;
567
+ height: 300px;
568
+ object-fit: cover;
569
+ }
570
+
571
+ .book-card h3 {
572
+ font-size: 1.1em;
573
+ color: #333;
574
+ margin-bottom: 10px;
575
+ margin: 10px 0;
576
+ }
577
+
578
+ .book-card p {
579
+ font-size: 1em;
580
+ color: #666;
581
+ line-height: 1.5;
582
+ font-size: 0.9em;
583
+ color: #555;
584
+ }
585
+
586
+ .book-card .btn {
587
+ display: inline-block;
588
+ margin-top: 10px;
589
+ padding: 8px 12px;
590
+ background-color: #333;
591
+ color: #fff;
592
+ text-decoration: none;
593
+ border-radius: 3px;
594
+ }
595
+
596
+ .book-card .btn:hover {
597
+ background-color: #555;
598
+ }
599
+
600
+ /* Book Detail Page */
601
+ .book-detail {
602
+ display: flex;
603
+ flex-wrap: wrap;
604
+ margin-top: 40px;
605
+ background-color: #fff;
606
+ padding: 30px;
607
+ border-radius: 10px;
608
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
609
+ padding: 20px;
610
+ }
611
+
612
+ .book-detail img.book-image-detail {
613
+ width: 300px;
614
+ height: 450px;
615
+ object-fit: cover;
616
+ float: left;
617
+ margin-right: 20px;
618
+ }
619
+
620
+ .book-detail-image {
621
+ max-width: 300px;
622
+ width: 100%;
623
+ height: auto;
624
+ border-radius: 10px;
625
+ margin-right: 30px;
626
+ }
627
+
628
+ .book-detail-content {
629
+ display: grid;
630
+ grid-template-columns: 1fr;
631
+ gap: 20px;
632
+ }
633
+
634
+ @media (min-width: 768px) {
635
+ .book-detail-content {
636
+ grid-template-columns: 1fr 2fr;
637
+ }
638
+ }
639
+
640
+ .book-image-detail {
641
+ width: 100%;
642
+ height: auto;
643
+ border-radius: 8px;
644
+ object-fit: cover;
645
+ }
646
+
647
+ .book-info {
648
+ display: flex;
649
+ flex-direction: column;
650
+ justify-content: flex-start;
651
+ flex: 1;
652
+ min-width: 250px;
653
+ }
654
+
655
+ .book-info h1 {
656
+ font-size: 2em;
657
+ margin-bottom: 10px;
658
+ color: #222;
659
+ margin-top: 0;
660
+ }
661
+
662
+ .book-info p {
663
+ margin: 8px 0;
664
+ line-height: 1.6;
665
+ }
666
+
667
+ .book-info strong {
668
+ color: #555;
669
+ }
670
+
671
+ .book-detail-info h1 {
672
+ font-size: 2.5em;
673
+ margin-bottom: 10px;
674
+ color: #333;
675
+ margin-bottom: 20px;
676
+ }
677
+
678
+ .book-detail-info p {
679
+ font-size: 1em;
680
+ color: #555;
681
+ margin-bottom: 10px;
682
+ }
683
+
684
+ .book-detail-info h2 {
685
+ font-size: 1.8em;
686
+ margin-top: 20px;
687
+ margin-bottom: 10px;
688
+ color: #333;
689
+ }
690
+
691
+ .back-button {
692
+ display: inline-block;
693
+ margin-top: 20px;
694
+ padding: 10px 20px;
695
+ background-color: #ff6347;
696
+ color: #fff;
697
+ text-decoration: none;
698
+ border-radius: 5px;
699
+ transition: background-color 0.3s;
700
+ }
701
+
702
+ .back-button:hover {
703
+ background-color: #ff4c29;
704
+ }
705
+
706
+ /* Contact Section */
707
+ .contact {
708
+ padding: 60px 20px;
709
+ background-color: #fff;
710
+ text-align: center;
711
+ }
712
+
713
+ .contact h1 {
714
+ font-size: 2.5em;
715
+ margin-bottom: 20px;
716
+ color: #333;
717
+ }
718
+
719
+ .contact p {
720
+ font-size: 1.1em;
721
+ color: #666;
722
+ margin-bottom: 40px;
723
+ }
724
+
725
+ .contact-form {
726
+ max-width: 600px;
727
+ margin: auto;
728
+ text-align: left;
729
+ }
730
+
731
+ .contact-form .form-group {
732
+ margin-bottom: 20px;
733
+ }
734
+
735
+ .contact-form label {
736
+ display: block;
737
+ font-size: 1em;
738
+ color: #333;
739
+ margin-bottom: 8px;
740
+ }
741
+
742
+ .contact-form input[type="text"],
743
+ .contact-form input[type="email"],
744
+ .contact-form textarea {
745
+ width: 100%;
746
+ padding: 12px;
747
+ border: 2px solid #ccc;
748
+ border-radius: 5px;
749
+ font-size: 1em;
750
+ }
751
+
752
+ /* Flash Messages */
753
+ .flash-messages {
754
+ width: 90%;
755
+ max-width: 1200px;
756
+ margin: 20px auto;
757
+ padding: 10px;
758
+ width: 80%;
759
+ margin: 20px auto;
760
+ }
761
+
762
+ .alert {
763
+ padding: 15px;
764
+ border-radius: 5px;
765
+ margin-bottom: 10px;
766
+ font-size: 1em;
767
+ text-align: center;
768
+ margin-bottom: 10px;
769
+ border-radius: 4px;
770
+ }
771
+
772
+ .alert-success {
773
+ background-color: #d4edda;
774
+ color: #155724;
775
+ background-color: #4CAF50;
776
+ }
777
+
778
+ .alert-danger {
779
+ background-color: #f8d7da;
780
+ color: #721c24;
781
+ background-color: #f44336;
782
+ }
783
+
784
+ .alert-error {
785
+ background-color: #f8d7da;
786
+ color: #721c24;
787
+ }
788
+
789
+ /* Footer */
790
+ footer {
791
+ text-align: center;
792
+ padding: 20px;
793
+ background-color: #f1f1f1;
794
+ margin-top: 40px;
795
+ font-size: 0.9em;
796
+ color: #555;
797
+ background-color: #333;
798
+ color: #fff;
799
+ padding: 20px 0;
800
+ text-align: center;
801
+ margin-top: 60px;
802
+ padding: 15px 0;
803
+ position: fixed;
804
+ width: 100%;
805
+ bottom: 0;
806
+ }
807
+
808
+ /* Responsive Design */
809
+ @media (max-width: 768px) {
810
+ nav .nav-links {
811
+ float: none;
812
+ text-align: center;
813
+ }
814
+ nav .nav-links li {
815
+ display: block;
816
+ line-height: 30px;
817
+ margin: 10px 0;
818
+ }
819
+ .container h1,
820
+ .contact h1,
821
+ .about h2,
822
+ .features h2 {
823
+ font-size: 2em;
824
+ }
825
+ input[type="text"],
826
+ input[type="email"],
827
+ textarea {
828
+ max-width: 100%;
829
+ }
830
+ .book-card {
831
+ width: 45%;
832
+ }
833
+ .book-detail {
834
+ flex-direction: column;
835
+ align-items: center;
836
+ }
837
+ .book-detail-image {
838
+ margin-right: 0;
839
+ margin-bottom: 20px;
840
+ }
841
+ .feature-list {
842
+ flex-direction: column;
843
+ align-items: center;
844
+ }
845
+ .feature-item {
846
+ width: 80%;
847
+ }
848
+ footer {
849
+ position: relative;
850
+ }
851
+ .recommendations {
852
+ flex-direction: column;
853
+ align-items: center;
854
+ }
855
+ .book-detail img.book-image-detail {
856
+ float: none;
857
+ display: block;
858
+ margin: 0 auto 20px auto;
859
+ }
860
+ .hero-content h1 {
861
+ font-size: 2.5em;
862
+ }
863
+
864
+ .hero-content p {
865
+ font-size: 1em;
866
+ }
867
+
868
+ .cta-button {
869
+ padding: 10px 20px;
870
+ font-size: 0.9em;
871
+ }
872
+
873
+ .nav-links {
874
+ flex-direction: column;
875
+ align-items: center;
876
+ }
877
+
878
+ .features-grid {
879
+ flex-direction: column;
880
+ align-items: center;
881
+ }
882
+
883
+ .links-grid {
884
+ flex-direction: column;
885
+ align-items: center;
886
+ }
887
+ }
888
+
889
+ @media (max-width: 480px) {
890
+ .book-card {
891
+ width: 100%;
892
+ }
893
+ .hero-content h1 {
894
+ font-size: 2em;
895
+ }
896
+ .hero-content p {
897
+ font-size: 1em;
898
+ }
899
+ .cta h2 {
900
+ font-size: 1.5em;
901
+ }
902
+ }
903
+
904
+ /* Authentication Forms */
905
+ .auth-form {
906
+ max-width: 400px;
907
+ margin: 40px auto;
908
+ padding: 30px;
909
+ background: #fff;
910
+ border-radius: 10px;
911
+ box-shadow: 0 6px 20px rgba(0,0,0,0.1);
912
+ display: flex;
913
+ flex-direction: column;
914
+ }
915
+
916
+ .auth-form label {
917
+ display: block;
918
+ margin-bottom: 5px;
919
+ font-weight: bold;
920
+ }
921
+
922
+ .auth-form input[type="text"],
923
+ .auth-form input[type="email"],
924
+ .auth-form input[type="password"] {
925
+ width: 100%;
926
+ padding: 10px;
927
+ margin-bottom: 15px;
928
+ border: 2px solid #ccc;
929
+ border-radius: 5px;
930
+ border: 1px solid #ccc;
931
+ border-radius: 3px;
932
+ }
933
+
934
+ .auth-form button {
935
+ width: 100%;
936
+ padding: 15px;
937
+ background-color: #ff6347;
938
+ color: #fff;
939
+ border: none;
940
+ border-radius: 5px;
941
+ font-size: 1em;
942
+ cursor: pointer;
943
+ padding: 10px;
944
+ background-color: #333;
945
+ border-radius: 3px;
946
+ }
947
+
948
+ .auth-form button:hover {
949
+ background-color: #ff4c29;
950
+ background-color: #555;
951
+ }
952
+
953
+ /* Book Detail Styling */
954
+ .book-detail {
955
+ padding: 20px;
956
+ }
957
+
958
+ .book-detail-content {
959
+ display: flex;
960
+ flex-wrap: wrap;
961
+ gap: 20px;
962
+ }
963
+
964
+ .book-image-detail {
965
+ max-width: 300px;
966
+ width: 100%;
967
+ height: auto;
968
+ border: 1px solid #ccc;
969
+ border-radius: 5px;
970
+ }
971
+
972
+ .book-info {
973
+ flex: 1;
974
+ min-width: 250px;
975
+ }
976
+
977
+ .book-info h1 {
978
+ margin-top: 0;
979
+ }
980
+
981
+ .book-info p {
982
+ line-height: 1.6;
983
+ }
984
+
985
+ /* Links Section */
986
+ .links {
987
+ padding: 60px 0;
988
+ }
989
+
990
+ .links h2 {
991
+ text-align: center;
992
+ margin-bottom: 40px;
993
+ color: #222;
994
+ }
995
+
996
+ .links-grid {
997
+ display: flex;
998
+ justify-content: center;
999
+ gap: 20px;
1000
+ flex-wrap: wrap;
1001
+ }
1002
+
1003
+ .link-item {
1004
+ background-color: #4CAF50;
1005
+ color: #fff;
1006
+ padding: 15px 25px;
1007
+ text-decoration: none;
1008
+ border-radius: 5px;
1009
+ transition: background-color 0.3s ease;
1010
+ font-weight: 500;
1011
+ }
1012
+
1013
+ .link-item:hover {
1014
+ background-color: #45a049;
1015
+ }
1016
+
1017
+ /* Background Image for Index Page */
1018
+ .index-page {
1019
+ /* background-image: url('../images/background.jpg'); */
1020
+ background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('/static/library.jpg') no-repeat center center/cover;
1021
+
1022
+ background-size: cover;
1023
+ background-repeat: no-repeat;
1024
+ background-position: center;
1025
+ min-height: 100vh;
1026
+ }
1027
+
templates/base.html ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- templates/base.html -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>{% block title %}BookNest{% endblock %}</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
8
+ <!-- Include any other head elements or meta tags here -->
9
+ </head>
10
+ <body>
11
+ <!-- Include Navigation Bar -->
12
+ {% include 'navbar.html' %}
13
+
14
+ <!-- Flash Messages -->
15
+ {% with messages = get_flashed_messages(with_categories=true) %}
16
+ {% if messages %}
17
+ <div class="flash-messages">
18
+ {% for category, message in messages %}
19
+ <div class="alert alert-{{ category }}">{{ message }}</div>
20
+ {% endfor %}
21
+ </div>
22
+ {% endif %}
23
+ {% endwith %}
24
+
25
+ <!-- Main Content -->
26
+ {% block content %}
27
+ <!-- Default content can go here -->
28
+ {% endblock %}
29
+
30
+ <!-- Include Footer -->
31
+ {% include 'footer.html' %}
32
+ </body>
33
+ </html>
templates/book_detail.html ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ book.title }} - BookNest</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
8
+ <!-- Google Fonts -->
9
+ <link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap" rel="stylesheet">
10
+ </head>
11
+ <body>
12
+ <!-- Include Navigation Bar -->
13
+ {% include 'navbar.html' %}
14
+
15
+ <!-- Flash Messages -->
16
+ {% with messages = get_flashed_messages(with_categories=true) %}
17
+ {% if messages %}
18
+ <div class="flash-messages">
19
+ {% for category, message in messages %}
20
+ <div class="alert alert-{{ category }}">{{ message }}</div>
21
+ {% endfor %}
22
+ </div>
23
+ {% endif %}
24
+ {% endwith %}
25
+
26
+ <!-- Book Details -->
27
+ <div class="container book-detail">
28
+ <div class="book-detail-content">
29
+ <img src="{{ book.image_url }}" alt="{{ book.title }}" class="book-image-detail">
30
+ <div class="book-info">
31
+ <h1>{{ book.title }}</h1>
32
+ <p><strong>Authors:</strong> {{ book.authors | join(', ') }}</p>
33
+ <p><strong>Average Rating:</strong> {{ book.averageRating }}</p>
34
+ <p><strong>Ratings Count:</strong> {{ book.ratingsCount }}</p>
35
+ <p><strong>Published Date:</strong> {{ book.publishedDate }}</p>
36
+ <p><strong>Page Count:</strong> {{ book.pageCount }}</p>
37
+ <p><strong>Language:</strong> {{ book.language }}</p>
38
+
39
+ <p><strong>Publisher:</strong> {{ book.publisher }}</p>
40
+ <p><strong>Description:</strong> {{ book.description }}</p>
41
+
42
+ </div>
43
+ </div>
44
+ </div>
45
+
46
+ <!-- Include Footer -->
47
+ {% include 'footer.html' %}
48
+ </body>
49
+ </html>
templates/contact.html ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- templates/contact.html -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Contact - BookNest</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
8
+ </head>
9
+ <body class="index-page">
10
+ <!-- Include Navigation Bar -->
11
+ {% include 'navbar.html' %}
12
+
13
+ <!-- Flash Messages -->
14
+ {% with messages = get_flashed_messages(with_categories=true) %}
15
+ {% if messages %}
16
+ <div class="flash-messages">
17
+ {% for category, message in messages %}
18
+ <div class="alert alert-{{ category }}">{{ message }}</div>
19
+ {% endfor %}
20
+ </div>
21
+ {% endif %}
22
+ {% endwith %}
23
+
24
+ <!-- Contact Form -->
25
+ <div class="container">
26
+ <h1>Contact Us</h1>
27
+ <form action="{{ url_for('contact') }}" method="POST" class="auth-form">
28
+ <label for="name">Name:</label>
29
+ <input type="text" name="name" id="name" required>
30
+
31
+ <label for="email">Email:</label>
32
+ <input type="email" name="email" id="email" required>
33
+
34
+ <label for="review">Review:</label>
35
+ <textarea name="review" id="review" rows="5" required></textarea>
36
+
37
+ <label for="rating">Rating (1-5):</label>
38
+ <input type="number" name="rating" id="rating" min="1" max="5" required>
39
+
40
+ <button type="submit">Submit</button>
41
+ </form>
42
+ </div>
43
+
44
+ <!-- Include Footer -->
45
+ {% include 'footer.html' %}
46
+ </body>
47
+ </html>
templates/footer.html ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ <!-- templates/footer.html -->
2
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
3
+ <footer>
4
+ <p>&copy; Done by students of B-Tech AI IIT J</p>
5
+ </footer>
templates/homepage.html ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Home - BookNest</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
8
+ <!-- Google Fonts -->
9
+ <link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap" rel="stylesheet">
10
+ </head>
11
+ <body>
12
+ <!-- Include Navigation Bar -->
13
+ {% include 'navbar.html' %}
14
+
15
+ <!-- Flash Messages -->
16
+ {% with messages = get_flashed_messages(with_categories=true) %}
17
+ {% if messages %}
18
+ <div class="flash-messages">
19
+ {% for category, message in messages %}
20
+ <div class="alert alert-{{ category }}">{{ message }}</div>
21
+ {% endfor %}
22
+ </div>
23
+ {% endif %}
24
+ {% endwith %}
25
+
26
+ <!-- Hero Section -->
27
+ <section class="hero">
28
+ <div class="hero-content">
29
+ <h1>Welcome to BookNest</h1>
30
+ <p>Your ultimate destination for book recommendations and reviews.</p>
31
+ <a href="{{ url_for('index') }}" class="cta-button">Get Started</a>
32
+
33
+ <a href="{{ url_for('login') }}" class="cta-button">Login</a>
34
+ </div>
35
+ </section>
36
+
37
+ <!-- Features Section -->
38
+ <section class="features">
39
+ <div class="container">
40
+ <h2>Why Choose BookNest?</h2>
41
+ <div class="features-grid">
42
+ <div class="feature-item">
43
+ <img src="{{ url_for('static', filename='images/personalized.png') }}" alt="Personalized Recommendations" class="feature-image">
44
+ <h3>Personalized Recommendations</h3>
45
+ <p>Get book suggestions tailored to your reading preferences.</p>
46
+ </div>
47
+ <div class="feature-item">
48
+ <img src="{{ url_for('static', filename='images/community.png') }}" alt="Community Reviews" class="feature-image">
49
+ <h3>Community Reviews</h3>
50
+ <p>Read and write reviews from a community of book enthusiasts.</p>
51
+ </div>
52
+ <div class="feature-item">
53
+ <img src="{{ url_for('static', filename='images/track.png') }}" alt="Track Your Reading" class="feature-image">
54
+ <h3>Track Your Reading</h3>
55
+ <p>Keep track of the books you've read and want to read.</p>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </section>
60
+
61
+ <!-- Links Section -->
62
+ <section class="links">
63
+ <div class="container">
64
+ <h2>Connect with Us</h2>
65
+ <div class="links-grid">
66
+ <a href="https://github.com/anwesh2410/Book-Recommendation-Site" target="_blank" class="link-item">
67
+ <img src="{{ url_for('static', filename='images/github.jpeg') }}" alt="GitHub" class="link-icon"> GitHub
68
+ </a>
69
+
70
+ </div>
71
+ </div>
72
+ </section>
73
+
74
+ <!-- Include Footer -->
75
+ {% include 'footer.html' %}
76
+ </body>
77
+ </html>
templates/index.html ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Recommendations - BookNest</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
8
+ <!-- Link to Google Fonts for better typography -->
9
+ <link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap" rel="stylesheet">
10
+ </head>
11
+ <body class="index-page">
12
+ <!-- Include Navigation Bar -->
13
+ {% include 'navbar.html' %}
14
+
15
+ <!-- Flash Messages -->
16
+ {% with messages = get_flashed_messages(with_categories=true) %}
17
+ {% if messages %}
18
+ <div class="flash-messages">
19
+ {% for category, message in messages %}
20
+ <div class="alert alert-{{ category }}">{{ message }}</div>
21
+ {% endfor %}
22
+ </div>
23
+ {% endif %}
24
+ {% endwith %}
25
+
26
+ <!-- Recommendations Form -->
27
+ <div class="container">
28
+ <h1>Get Book Recommendations</h1>
29
+ <form action="{{ url_for('index') }}" method="POST" class="auth-form">
30
+ <label for="book1">Book 1:</label>
31
+ <input type="text" name="book1" id="book1" placeholder="Enter book title" required>
32
+
33
+ <label for="book2">Book 2:</label>
34
+ <input type="text" name="book2" id="book2" placeholder="Enter book title">
35
+
36
+ <label for="book3">Book 3:</label>
37
+ <input type="text" name="book3" id="book3" placeholder="Enter book title">
38
+
39
+ <button type="submit">Get Recommendations</button>
40
+ </form>
41
+ </div>
42
+
43
+ <!-- Recommendations Results -->
44
+ {% if recommendations %}
45
+ <div class="container">
46
+ <h2>Recommended Books:</h2>
47
+ <div class="recommendations">
48
+ {% for book in recommendations %}
49
+ <div class="book-card">
50
+ <img src="{{ book.image_url }}" alt="{{ book.title }}" class="book-image">
51
+ <h3>{{ book.title }}</h3>
52
+ <p><strong>Authors:</strong> {{ book.authors | join(', ') }}</p>
53
+ <a href="{{ url_for('book_detail', title=book.title|url_encode) }}" class="btn">View Details</a>
54
+ </div>
55
+ {% endfor %}
56
+ </div>
57
+ </div>
58
+ {% endif %}
59
+
60
+ <!-- Include Footer -->
61
+ {% include 'footer.html' %}
62
+ </body>
63
+ </html>
templates/login.html ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- templates/login.html -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Login - BookNest</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
8
+ </head>
9
+ <body class="index-page">
10
+ <!-- Include Navigation Bar -->
11
+ {% include 'navbar.html' %}
12
+
13
+ <!-- Flash Messages -->
14
+ {% with messages = get_flashed_messages(with_categories=true) %}
15
+ {% if messages %}
16
+ <div class="flash-messages">
17
+ {% for category, message in messages %}
18
+ <div class="alert alert-{{ category }}">{{ message }}</div>
19
+ {% endfor %}
20
+ </div>
21
+ {% endif %}
22
+ {% endwith %}
23
+
24
+ <!-- Login Form -->
25
+ <div class="container">
26
+ <h1>Login</h1>
27
+ <form action="{{ url_for('login') }}" method="POST" class="auth-form">
28
+ <label for="email">Email:</label>
29
+ <input type="email" name="email" required>
30
+
31
+ <label for="password">Password:</label>
32
+ <input type="password" name="password" required>
33
+
34
+ <button type="submit">Login</button>
35
+ </form>
36
+ <p>Don't have an account? <a href="{{ url_for('register') }}">Register here</a>.</p>
37
+ </div>
38
+
39
+ <!-- Include Footer -->
40
+ {% include 'footer.html' %}
41
+ </body>
42
+ </html>
templates/navbar.html ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- templates/navbar.html -->
2
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
3
+ <nav>
4
+ <div class="logo">
5
+ <a href="{{ url_for('homepage') }}">BookNest</a>
6
+ </div>
7
+ <ul class="nav-links">
8
+ <li><a href="{{ url_for('homepage') }}">Home</a></li>
9
+ <li><a href="{{ url_for('index') }}">Recommendations</a></li>
10
+ <li><a href="{{ url_for('contact') }}">Contact</a></li>
11
+ {% if current_user.is_authenticated %}
12
+ <li><a href="{{ url_for('profile') }}">Profile</a></li>
13
+ <li><a href="{{ url_for('logout') }}">Logout</a></li>
14
+ {% else %}
15
+ <li><a href="{{ url_for('login') }}">Login</a></li>
16
+ <li><a href="{{ url_for('register') }}">Register</a></li>
17
+ {% endif %}
18
+ </ul>
19
+ </nav>
templates/profile.html ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- templates/profile.html -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>{{ current_user.username }}'s Profile - BookNest</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
8
+ </head>
9
+ <body class="index-page">
10
+ <!-- Include Navigation Bar -->
11
+ {% include 'navbar.html' %}
12
+
13
+ <!-- Flash Messages -->
14
+ {% with messages = get_flashed_messages(with_categories=true) %}
15
+ {% if messages %}
16
+ <div class="flash-messages">
17
+ {% for category, message in messages %}
18
+ <div class="alert alert-{{ category }}">{{ message }}</div>
19
+ {% endfor %}
20
+ </div>
21
+ {% endif %}
22
+ {% endwith %}
23
+
24
+ <!-- Profile Content -->
25
+ <div class="container">
26
+ <h1>Welcome, {{ current_user.username }}!</h1>
27
+ <p><strong>Email:</strong> {{ current_user.email }}</p>
28
+ <!-- Add more user-specific information here if needed -->
29
+ </div>
30
+
31
+ <!-- Include Footer -->
32
+ {% include 'footer.html' %}
33
+ </body>
34
+ </html>
templates/register.html ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- templates/register.html -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Register - BookNest</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
8
+ </head>
9
+ <body class="index-page">
10
+ <!-- Include Navigation Bar -->
11
+ {% include 'navbar.html' %}
12
+
13
+ <!-- Flash Messages -->
14
+ {% with messages = get_flashed_messages(with_categories=true) %}
15
+ {% if messages %}
16
+ <div class="flash-messages">
17
+ {% for category, message in messages %}
18
+ <div class="alert alert-{{ category }}">{{ message }}</div>
19
+ {% endfor %}
20
+ </div>
21
+ {% endif %}
22
+ {% endwith %}
23
+
24
+ <!-- Registration Form -->
25
+ <div class="container">
26
+ <h1>Register</h1>
27
+ <form action="{{ url_for('register') }}" method="POST" class="auth-form">
28
+ <label for="username">Username:</label>
29
+ <input type="text" name="username" required>
30
+
31
+ <label for="email">Email:</label>
32
+ <input type="email" name="email" required>
33
+
34
+ <label for="password">Password:</label>
35
+ <input type="password" name="password" required>
36
+
37
+ <label for="confirm_password">Confirm Password:</label>
38
+ <input type="password" name="confirm_password" required>
39
+
40
+ <button type="submit">Register</button>
41
+ </form>
42
+ <p>Already have an account? <a href="{{ url_for('login') }}">Log in here</a>.</p>
43
+ </div>
44
+
45
+ <!-- Include Footer -->
46
+ {% include 'footer.html' %}
47
+ </body>
48
+ </html>