File size: 12,175 Bytes
54862ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
from flask import Flask, render_template, request, redirect, url_for, flash
from flask_login import (
    LoginManager,
    login_user,
    login_required,
    logout_user,
    current_user,
    UserMixin
)
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
from pymongo import MongoClient
from recommend import find_top_common_books
from urllib.parse import quote_plus, unquote_plus
from bson.objectid import ObjectId
import requests
import logging
import os

app = Flask(__name__, template_folder='../templates', static_folder='../static')
app.secret_key = os.environ.get('SECRET_KEY', 'your_default_secret_key')  # Replace with a secure key

# Initialize Flask-Login
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'  # Redirect to 'login' view if unauthorized

# Google Books API Key from environment variable
GOOGLE_BOOKS_API_KEY = 'AIzaSyBqTms_1DSR3xcxCapdmizZiZUMaswaI9M'  # Set this in your environment

# Initialize MongoDB client
client = MongoClient("mongodb+srv://Atharva:whatismongodb@book.xx2jw.mongodb.net/book_dataset?retryWrites=true&w=majority")
db = client['book_dataset']
books_collection = db['BOOK']
reviews_collection = db['reviews']  # New Collection for Reviews
users_collection = db['users']      # New Collection for Users

# Simple in-memory cache for book details
book_details_cache = {}

def fetch_book_details(title, api_key=None):
    """Fetch book details from Google Books API based on the book title."""
    if title in book_details_cache:
        return book_details_cache[title]
    
    base_url = "https://www.googleapis.com/books/v1/volumes/"
    params = {
        'q': title,
        'maxResults': 1
    }
    if api_key:
        params['key'] = api_key
    
    try:
        response = requests.get(base_url, params=params)
        response.raise_for_status()
        data = response.json()
        
        if 'items' in data and len(data['items']) > 0:
            volume_info = data['items'][0].get('volumeInfo', {})
            book_details = {
                'authors': volume_info.get('authors', ["Unknown Author"]),
                'description': volume_info.get('description', "No description available."),
                'averageRating': volume_info.get('averageRating', "N/A"),
                'ratingsCount': volume_info.get('ratingsCount', "N/A"),
                'publishedDate': volume_info.get('publishedDate', "N/A"),
                'pageCount': volume_info.get('pageCount', "N/A"),
                'language': volume_info.get('language', "N/A"),
                'publisher': volume_info.get('publisher', "N/A")
            }
            book_details_cache[title] = book_details
            return book_details
        else:
            book_details = {
                'authors': ["Unknown Author"],
                'description': "No description available.",
                'averageRating': "N/A",
                'ratingsCount': "N/A",
                'publishedDate': "N/A",
                'pageCount': "N/A",
                'language': "N/A",
                'publisher': "N/A"
            }
            book_details_cache[title] = book_details
            return book_details
    except requests.exceptions.RequestException as e:
        print(f"Error fetching details for '{title}': {e}")
        book_details = {
            'authors': ["Unknown Author"],
            'description': "No description available.",
            'averageRating': "N/A",
            'ratingsCount': "N/A",
            'publishedDate': "N/A",
            'pageCount': "N/A",
            'language': "N/A",
            'publisher': "N/A"
        }
        book_details_cache[title] = book_details
        return book_details

# User Model
class User(UserMixin):
    def __init__(self, user_data):
        self.id = str(user_data['_id'])
        self.username = user_data['username']
        self.email = user_data['email']

    @staticmethod
    def get(user_id):
        user_data = users_collection.find_one({'_id': ObjectId(user_id)})
        if user_data:
            return User(user_data)
        return None

@login_manager.user_loader
def load_user(user_id):
    return User.get(user_id)

# Logging Configuration
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# Create handlers
file_handler = logging.FileHandler('app.log')
stream_handler = logging.StreamHandler()

# Create formatters and add to handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
stream_handler.setFormatter(formatter)

# Add handlers to the logger
logger.addHandler(file_handler)
logger.addHandler(stream_handler)

# Registration Route
@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        # Get form data
        username = request.form.get('username')
        email = request.form.get('email')
        password = request.form.get('password')
        confirm_password = request.form.get('confirm_password')

        # Validate form inputs
        if not all([username, email, password, confirm_password]):
            logger.warning('Please fill out all fields.')
            flash('Please fill out all fields.', 'danger')
            return redirect(url_for('register'))

        if password != confirm_password:
            logger.warning('Passwords do not match.')
            flash('Passwords do not match.', 'danger')
            return redirect(url_for('register'))

        # Check if user already exists
        existing_user = users_collection.find_one({'email': email})
        if existing_user:
            logger.warning('Email already registered.')
            flash('Email already registered.', 'danger')
            return redirect(url_for('register'))

        # Hash the password
        password_hash = generate_password_hash(password)

        # Create new user document
        new_user = {
            'username': username,
            'email': email,
            'password': password_hash,
            'created_at': datetime.utcnow()
        }

        # Insert the new user into the database
        users_collection.insert_one(new_user)

        logger.info('Registration successful!')
        flash('Registration successful! Please log in.', 'success')
        return redirect(url_for('login'))

    return render_template('register.html')

# Login Route
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        # Get form data
        email = request.form.get('email')
        password = request.form.get('password')

        # Find user by email
        user_data = users_collection.find_one({'email': email})
        if user_data and check_password_hash(user_data['password'], password):
            user = User(user_data)
            login_user(user)
            logger.info('Logged in successfully!')
            flash('Logged in successfully!', 'success')
            next_page = request.args.get('next')
            return redirect(next_page or url_for('homepage'))
        else:
            logger.warning('Invalid email or password.')
            flash('Invalid email or password.', 'danger')
            return redirect(url_for('login'))

    return render_template('login.html')

# Logout Route
@app.route('/logout')
@login_required
def logout():
    logout_user()
    logger.info('You have been logged out.')
    flash('You have been logged out.', 'success')
    return redirect(url_for('homepage'))

# Contact Route
@app.route('/contact', methods=['GET', 'POST'])
def contact():
    if request.method == 'POST':
        # Retrieve form data
        name = request.form.get('name')
        email = request.form.get('email')
        review = request.form.get('review')
        rating = request.form.get('rating')
        
        # Input Validation (Basic Example)
        if not all([name, email, review, rating]):
            logger.warning("All fields are required!")
            flash("All fields are required!", "danger")
            return redirect(url_for('contact'))
        
        try:
            # Convert rating to integer
            rating = int(rating)
            if rating < 1 or rating > 5:
                raise ValueError("Rating must be between 1 and 5.")
        except ValueError as ve:
            logger.warning(str(ve))
            flash(str(ve), "danger")
            return redirect(url_for('contact'))
        
        # Create a review document
        review_document = {
            "name": name,
            "email": email,
            "review": review,
            "rating": rating,
            "timestamp": datetime.utcnow()  # Optional: Add timestamp
        }
        
        # Insert the review into the 'reviews' collection
        reviews_collection.insert_one(review_document)
        
        logger.info("Thank you for your feedback!")
        flash("Thank you for your feedback!", "success")
        return redirect(url_for('homepage'))
    
    return render_template('contact.html')

# Homepage Route
@app.route('/')
def homepage():
    return render_template('homepage.html')

# Recommendations Route
@app.route('/recommendations', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        book1 = request.form.get('book1')
        book2 = request.form.get('book2')
        book3 = request.form.get('book3')

        input_books = []
        for book in [book1, book2, book3]:
            if book:
                matched_books = search_books(book)
                if matched_books:
                    input_books.append(matched_books[0]['title'])

        if not input_books:
            flash("Please enter at least one valid book.", "danger")
            return redirect(url_for('index'))

        recommendations = find_top_common_books(input_books)
        logger.info('Recommendations generated successfully!')

        # Flatten the recommendations list
        flattened_recommendations = [
            book for sublist in recommendations for book in sublist
        ]

        # Pass the flattened list to the template with minimal information
        return render_template(
            'index.html',
            recommendations=flattened_recommendations
        )

    return render_template('index.html')

# Book Detail Route
@app.route('/book/<title>')
def book_detail(title):
    # Decode the title from URL
    decoded_title = unquote_plus(title)

    # Fetch book details (from cache or API)
    details = fetch_book_details(decoded_title, GOOGLE_BOOKS_API_KEY)

    # Fetch image_url from MongoDB if needed
    book_in_db = books_collection.find_one({"Book-Title": decoded_title}, {"Image-URL-M": 1})
    image_url = book_in_db["Image-URL-M"] if book_in_db else "/static/default.jpg"

    enriched_book = {
        'title': decoded_title,
        'image_url': image_url,
        'authors': details['authors'],
        'description': details['description'],
        'averageRating': details['averageRating'],
        'ratingsCount': details['ratingsCount'],
        'publishedDate': details['publishedDate'],
        'pageCount': details['pageCount'],
        'language': details['language'],
        'publisher': details['publisher']
    }

    logger.info('Book details fetched successfully!')
    return render_template('book_detail.html', book=enriched_book)

# Profile Route
@app.route('/profile')
@login_required
def profile():
    return render_template('profile.html', user=current_user)

# Search Books Function
def search_books(title):
    """Search for book titles in MongoDB similar to the given title."""
    query = {"Book-Title": {"$regex": str(title), "$options": "i"}}
    matched_books = books_collection.find(query, {"Book-Title": 1, "Image-URL-M": 1}).limit(2)
    return [{"title": book["Book-Title"], "image_url": book["Image-URL-M"]} for book in matched_books]

# Template Filters
@app.template_filter('url_encode')
def url_encode_filter(s):
    return quote_plus(s)

@app.template_filter('url_decode')
def url_decode_filter(s):
    return unquote_plus(s)

if __name__ == '__main__':
    app.run(debug=True)