Harsh Upadhyay commited on
Commit
89a2809
Β·
1 Parent(s): 7ce8c97

added postgres and updated routes.

Browse files
backend/app/__init__.py CHANGED
@@ -3,7 +3,6 @@ from flask import Flask
3
  from flask_jwt_extended import JWTManager
4
  from flask_cors import CORS
5
  from app.routes.routes import main # βœ… Make sure this works
6
- from app.database import init_db
7
  import logging
8
 
9
  jwt = JWTManager()
@@ -19,9 +18,6 @@ def create_app(config_object):
19
  handler.setFormatter(formatter)
20
  app.logger.addHandler(handler)
21
 
22
- # 🧱 Initialize DB
23
- init_db()
24
-
25
  # πŸ” Initialize JWT
26
  jwt.init_app(app)
27
 
 
3
  from flask_jwt_extended import JWTManager
4
  from flask_cors import CORS
5
  from app.routes.routes import main # βœ… Make sure this works
 
6
  import logging
7
 
8
  jwt = JWTManager()
 
18
  handler.setFormatter(formatter)
19
  app.logger.addHandler(handler)
20
 
 
 
 
21
  # πŸ” Initialize JWT
22
  jwt.init_app(app)
23
 
backend/app/database.py CHANGED
@@ -1,314 +1,233 @@
1
- import sqlite3
2
- import os
3
- import logging
4
- from datetime import datetime
5
- import json
6
-
7
- BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
8
- DB_PATH = os.path.join(BASE_DIR, 'legal_docs.db')
9
-
10
- def init_db():
11
- """Initialize the database with required tables"""
12
- try:
13
- conn = sqlite3.connect(DB_PATH)
14
- cursor = conn.cursor()
15
-
16
- # Create users table
17
- cursor.execute('''
18
- CREATE TABLE IF NOT EXISTS users (
19
- id INTEGER PRIMARY KEY AUTOINCREMENT,
20
- username TEXT UNIQUE NOT NULL,
21
- email TEXT UNIQUE NOT NULL,
22
- password_hash TEXT NOT NULL,
23
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
24
- )
25
- ''')
26
-
27
- # Create documents table
28
- cursor.execute('''
29
- CREATE TABLE IF NOT EXISTS documents (
30
- id INTEGER PRIMARY KEY AUTOINCREMENT,
31
- title TEXT NOT NULL,
32
- full_text TEXT,
33
- summary TEXT,
34
- clauses TEXT,
35
- features TEXT,
36
- context_analysis TEXT,
37
- file_path TEXT,
38
- upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
39
- )
40
- ''')
41
-
42
- # Create question_answers table for persisting Q&A
43
- cursor.execute('''
44
- CREATE TABLE IF NOT EXISTS question_answers (
45
- id INTEGER PRIMARY KEY AUTOINCREMENT,
46
- document_id INTEGER NOT NULL,
47
- user_id INTEGER NOT NULL,
48
- question TEXT NOT NULL,
49
- answer TEXT NOT NULL,
50
- score REAL DEFAULT 0.0,
51
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
52
- FOREIGN KEY (document_id) REFERENCES documents (id) ON DELETE CASCADE,
53
- FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
54
- )
55
- ''')
56
-
57
- conn.commit()
58
- logging.info("Database initialized successfully")
59
- except Exception as e:
60
- logging.error(f"Error initializing database: {str(e)}")
61
- raise
62
- finally:
63
- conn.close()
64
-
65
- def get_db_connection():
66
- """Get a database connection"""
67
- conn = sqlite3.connect(DB_PATH)
68
- conn.row_factory = sqlite3.Row
69
- return conn
70
-
71
- # Initialize database when module is imported
72
- init_db()
73
-
74
- def search_documents(query, search_type='all'):
75
- conn = sqlite3.connect(DB_PATH)
76
- c = conn.cursor()
77
-
78
- try:
79
- # Check if query is a number (potential ID)
80
- is_id_search = query.isdigit()
81
-
82
- if is_id_search:
83
- # Search by ID
84
- c.execute('''
85
- SELECT id, title, summary, upload_time, 1.0 as match_score
86
- FROM documents
87
- WHERE id = ?
88
- ''', (int(query),))
89
- else:
90
- # Search by title
91
- c.execute('''
92
- SELECT id, title, summary, upload_time, 1.0 as match_score
93
- FROM documents
94
- WHERE title LIKE ?
95
- ORDER BY id DESC
96
- ''', (f'%{query}%',))
97
-
98
- results = []
99
- for row in c.fetchall():
100
- results.append({
101
- "id": row[0],
102
- "title": row[1],
103
- "summary": row[2] or "",
104
- "upload_time": row[3],
105
- "match_score": row[4]
106
- })
107
-
108
- return results
109
- except sqlite3.Error as e:
110
- logging.error(f"Search error: {str(e)}")
111
- raise
112
- finally:
113
- conn.close()
114
-
115
- def migrate_add_user_id_to_documents():
116
- """Add user_id column to documents table if it doesn't exist."""
117
- try:
118
- conn = sqlite3.connect(DB_PATH)
119
- cursor = conn.cursor()
120
- # Check if user_id column exists
121
- cursor.execute("PRAGMA table_info(documents)")
122
- columns = [row[1] for row in cursor.fetchall()]
123
- if 'user_id' not in columns:
124
- cursor.execute('ALTER TABLE documents ADD COLUMN user_id INTEGER')
125
- conn.commit()
126
- logging.info("Added user_id column to documents table.")
127
- except Exception as e:
128
- logging.error(f"Migration error: {str(e)}")
129
- raise
130
- finally:
131
- conn.close()
132
-
133
- # Call migration on import
134
- migrate_add_user_id_to_documents()
135
-
136
- def migrate_add_phone_company_to_users():
137
- """Add phone and company columns to users table if they don't exist."""
138
- try:
139
- conn = sqlite3.connect(DB_PATH)
140
- cursor = conn.cursor()
141
- cursor.execute("PRAGMA table_info(users)")
142
- columns = [row[1] for row in cursor.fetchall()]
143
- if 'phone' not in columns:
144
- cursor.execute('ALTER TABLE users ADD COLUMN phone TEXT')
145
- if 'company' not in columns:
146
- cursor.execute('ALTER TABLE users ADD COLUMN company TEXT')
147
- conn.commit()
148
- except Exception as e:
149
- logging.error(f"Migration error: {str(e)}")
150
- raise
151
- finally:
152
- conn.close()
153
-
154
- # Call migration on import
155
- migrate_add_phone_company_to_users()
156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  def save_document(title, full_text, summary, clauses, features, context_analysis, file_path, user_id):
158
- """Save a document to the database, associated with a user_id"""
159
  try:
160
- conn = get_db_connection()
161
- cursor = conn.cursor()
162
- cursor.execute('''
163
- INSERT INTO documents (title, full_text, summary, clauses, features, context_analysis, file_path, user_id)
164
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
165
- ''', (title, full_text, summary, str(clauses), str(features), str(context_analysis), file_path, user_id))
166
- conn.commit()
167
- return cursor.lastrowid
 
 
 
 
 
168
  except Exception as e:
169
- logging.error(f"Error saving document: {str(e)}")
170
  raise
171
  finally:
172
- conn.close()
173
 
174
  def get_all_documents(user_id=None):
175
- """Get all documents for a user from the database, including file size if available"""
176
  try:
177
- conn = get_db_connection()
178
- cursor = conn.cursor()
179
  if user_id is not None:
180
- cursor.execute('SELECT * FROM documents WHERE user_id = ? ORDER BY upload_time DESC', (user_id,))
181
- else:
182
- cursor.execute('SELECT * FROM documents ORDER BY upload_time DESC')
183
- documents = [dict(row) for row in cursor.fetchall()]
184
  for doc in documents:
185
- file_path = doc.get('file_path')
186
- if file_path and os.path.exists(file_path):
187
- doc['size'] = os.path.getsize(file_path)
188
- else:
189
- doc['size'] = None
190
- return documents
191
- except Exception as e:
192
- logging.error(f"Error fetching documents: {str(e)}")
193
- raise
194
  finally:
195
- conn.close()
196
 
197
  def get_document_by_id(doc_id, user_id=None):
198
- """Get a specific document by ID, optionally filtered by user_id"""
199
  try:
200
- conn = get_db_connection()
201
- cursor = conn.cursor()
202
  if user_id is not None:
203
- cursor.execute('SELECT * FROM documents WHERE id = ? AND user_id = ?', (doc_id, user_id))
204
- else:
205
- cursor.execute('SELECT * FROM documents WHERE id = ?', (doc_id,))
206
- document = cursor.fetchone()
207
- return dict(document) if document else None
208
- except Exception as e:
209
- logging.error(f"Error fetching document {doc_id}: {str(e)}")
210
- raise
211
  finally:
212
- conn.close()
213
 
214
  def delete_document(doc_id):
215
- """Delete a document from the database and return its file_path"""
216
  try:
217
- conn = get_db_connection()
218
- cursor = conn.cursor()
219
- # Fetch the file_path before deleting
220
- cursor.execute('SELECT file_path FROM documents WHERE id = ?', (doc_id,))
221
- row = cursor.fetchone()
222
- file_path = row[0] if row and row[0] else None
223
- # Now delete the document
224
- cursor.execute('DELETE FROM documents WHERE id = ?', (doc_id,))
225
- conn.commit()
226
  return file_path
227
- except Exception as e:
228
- logging.error(f"Error deleting document {doc_id}: {str(e)}")
229
- raise
230
  finally:
231
- conn.close()
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  def search_questions_answers(query, user_id=None):
234
- conn = get_db_connection()
235
- c = conn.cursor()
236
  try:
237
- sql = '''
238
- SELECT id, document_id, question, answer, created_at
239
- FROM question_answers
240
- WHERE (question LIKE ? OR answer LIKE ?)
241
- '''
242
- params = [f'%{query}%', f'%{query}%']
243
  if user_id is not None:
244
- sql += ' AND user_id = ?'
245
- params.append(user_id)
246
- sql += ' ORDER BY created_at DESC'
247
- c.execute(sql, params)
248
  results = []
249
- for row in c.fetchall():
250
  results.append({
251
- 'id': row[0],
252
- 'document_id': row[1],
253
- 'question': row[2],
254
- 'answer': row[3],
255
- 'created_at': row[4]
256
  })
257
  return results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  except Exception as e:
259
- logging.error(f"Error searching questions/answers: {str(e)}")
260
  raise
261
  finally:
262
- conn.close()
263
 
 
264
  def get_user_profile(username):
265
- """Fetch user profile details by username."""
266
  try:
267
- conn = get_db_connection()
268
- cursor = conn.cursor()
269
- cursor.execute('SELECT username, email, phone, company FROM users WHERE username = ?', (username,))
270
- row = cursor.fetchone()
271
- return dict(row) if row else None
272
- except Exception as e:
273
- logging.error(f"Error fetching user profile: {str(e)}")
274
- raise
 
275
  finally:
276
- conn.close()
277
 
278
  def update_user_profile(username, email, phone, company):
279
- """Update user profile details."""
280
  try:
281
- conn = get_db_connection()
282
- cursor = conn.cursor()
283
- cursor.execute('''
284
- UPDATE users SET email = ?, phone = ?, company = ? WHERE username = ?
285
- ''', (email, phone, company, username))
286
- conn.commit()
287
- return cursor.rowcount > 0
288
- except Exception as e:
289
- logging.error(f"Error updating user profile: {str(e)}")
290
- raise
291
  finally:
292
- conn.close()
293
 
294
  def change_user_password(username, current_password, new_password):
295
- """Change user password if current password matches."""
296
  try:
297
- conn = get_db_connection()
298
- cursor = conn.cursor()
299
- cursor.execute('SELECT password_hash FROM users WHERE username = ?', (username,))
300
- row = cursor.fetchone()
301
- if not row:
302
  return False, 'User not found'
303
- from werkzeug.security import check_password_hash, generate_password_hash
304
- if not check_password_hash(row[0], current_password):
305
  return False, 'Current password is incorrect'
306
- new_hash = generate_password_hash(new_password)
307
- cursor.execute('UPDATE users SET password_hash = ? WHERE username = ?', (new_hash, username))
308
- conn.commit()
309
  return True, 'Password updated successfully'
310
- except Exception as e:
311
- logging.error(f"Error changing password: {str(e)}")
312
- raise
313
  finally:
314
- conn.close()
 
1
+ # All sqlite3 and local DB logic will be removed and replaced with SQLAlchemy/Postgres in the next step.
2
+ # This file will be refactored to use SQLAlchemy models and sessions.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ from sqlalchemy import create_engine, Column, Integer, String, Text, Float, ForeignKey, DateTime
5
+ from sqlalchemy.orm import declarative_base, sessionmaker, relationship
6
+ from sqlalchemy.sql import func
7
+ import os
8
+ from sqlalchemy.exc import IntegrityError
9
+ from werkzeug.security import check_password_hash, generate_password_hash
10
+
11
+ # SQLAlchemy setup
12
+ DATABASE_URL = os.environ.get('DATABASE_URL')
13
+ engine = create_engine(DATABASE_URL)
14
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
15
+ Base = declarative_base()
16
+
17
+ # User model
18
+ class User(Base):
19
+ __tablename__ = 'users'
20
+ id = Column(Integer, primary_key=True, index=True)
21
+ username = Column(String, unique=True, nullable=False, index=True)
22
+ email = Column(String, unique=True, nullable=False, index=True)
23
+ password_hash = Column(String, nullable=False)
24
+ phone = Column(String)
25
+ company = Column(String)
26
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
27
+ documents = relationship('Document', back_populates='user')
28
+ question_answers = relationship('QuestionAnswer', back_populates='user')
29
+
30
+ # Document model
31
+ class Document(Base):
32
+ __tablename__ = 'documents'
33
+ id = Column(Integer, primary_key=True, index=True)
34
+ title = Column(String, nullable=False)
35
+ full_text = Column(Text)
36
+ summary = Column(Text)
37
+ clauses = Column(Text)
38
+ features = Column(Text)
39
+ context_analysis = Column(Text)
40
+ file_path = Column(String)
41
+ upload_time = Column(DateTime(timezone=True), server_default=func.now())
42
+ user_id = Column(Integer, ForeignKey('users.id'))
43
+ user = relationship('User', back_populates='documents')
44
+ question_answers = relationship('QuestionAnswer', back_populates='document')
45
+
46
+ # QuestionAnswer model
47
+ class QuestionAnswer(Base):
48
+ __tablename__ = 'question_answers'
49
+ id = Column(Integer, primary_key=True, index=True)
50
+ document_id = Column(Integer, ForeignKey('documents.id'), nullable=False)
51
+ user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
52
+ question = Column(Text, nullable=False)
53
+ answer = Column(Text, nullable=False)
54
+ score = Column(Float, default=0.0)
55
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
56
+ document = relationship('Document', back_populates='question_answers')
57
+ user = relationship('User', back_populates='question_answers')
58
+
59
+ # Create tables if they don't exist
60
+ Base.metadata.create_all(bind=engine)
61
+
62
+ def get_db_session():
63
+ return SessionLocal()
64
+
65
+ # --- Document CRUD ---
66
  def save_document(title, full_text, summary, clauses, features, context_analysis, file_path, user_id):
67
+ session = get_db_session()
68
  try:
69
+ doc = Document(
70
+ title=title,
71
+ full_text=full_text,
72
+ summary=summary,
73
+ clauses=str(clauses),
74
+ features=str(features),
75
+ context_analysis=str(context_analysis),
76
+ file_path=file_path,
77
+ user_id=user_id
78
+ )
79
+ session.add(doc)
80
+ session.commit()
81
+ return doc.id
82
  except Exception as e:
83
+ session.rollback()
84
  raise
85
  finally:
86
+ session.close()
87
 
88
  def get_all_documents(user_id=None):
89
+ session = get_db_session()
90
  try:
91
+ query = session.query(Document)
 
92
  if user_id is not None:
93
+ query = query.filter(Document.user_id == user_id)
94
+ documents = query.order_by(Document.upload_time.desc()).all()
95
+ result = []
 
96
  for doc in documents:
97
+ d = doc.__dict__.copy()
98
+ d.pop('_sa_instance_state', None)
99
+ result.append(d)
100
+ return result
 
 
 
 
 
101
  finally:
102
+ session.close()
103
 
104
  def get_document_by_id(doc_id, user_id=None):
105
+ session = get_db_session()
106
  try:
107
+ query = session.query(Document).filter(Document.id == doc_id)
 
108
  if user_id is not None:
109
+ query = query.filter(Document.user_id == user_id)
110
+ doc = query.first()
111
+ if doc:
112
+ d = doc.__dict__.copy()
113
+ d.pop('_sa_instance_state', None)
114
+ return d
115
+ return None
 
116
  finally:
117
+ session.close()
118
 
119
  def delete_document(doc_id):
120
+ session = get_db_session()
121
  try:
122
+ doc = session.query(Document).filter(Document.id == doc_id).first()
123
+ file_path = doc.file_path if doc else None
124
+ if doc:
125
+ session.delete(doc)
126
+ session.commit()
 
 
 
 
127
  return file_path
 
 
 
128
  finally:
129
+ session.close()
130
 
131
+ def search_documents(query, search_type='all'):
132
+ session = get_db_session()
133
+ try:
134
+ results = []
135
+ if query.isdigit():
136
+ docs = session.query(Document).filter(Document.id == int(query)).all()
137
+ else:
138
+ docs = session.query(Document).filter(Document.title.ilike(f'%{query}%')).order_by(Document.id.desc()).all()
139
+ for doc in docs:
140
+ results.append({
141
+ "id": doc.id,
142
+ "title": doc.title,
143
+ "summary": doc.summary or "",
144
+ "upload_time": doc.upload_time,
145
+ "match_score": 1.0
146
+ })
147
+ return results
148
+ finally:
149
+ session.close()
150
+
151
+ # --- Q&A ---
152
  def search_questions_answers(query, user_id=None):
153
+ session = get_db_session()
 
154
  try:
155
+ q = session.query(QuestionAnswer)
 
 
 
 
 
156
  if user_id is not None:
157
+ q = q.filter(QuestionAnswer.user_id == user_id)
158
+ q = q.filter((QuestionAnswer.question.ilike(f'%{query}%')) | (QuestionAnswer.answer.ilike(f'%{query}%')))
159
+ q = q.order_by(QuestionAnswer.created_at.desc())
 
160
  results = []
161
+ for row in q.all():
162
  results.append({
163
+ 'id': row.id,
164
+ 'document_id': row.document_id,
165
+ 'question': row.question,
166
+ 'answer': row.answer,
167
+ 'created_at': row.created_at
168
  })
169
  return results
170
+ finally:
171
+ session.close()
172
+
173
+ def save_question_answer(document_id, user_id, question, answer, score):
174
+ session = get_db_session()
175
+ try:
176
+ qa = QuestionAnswer(
177
+ document_id=document_id,
178
+ user_id=user_id,
179
+ question=question,
180
+ answer=answer,
181
+ score=score
182
+ )
183
+ session.add(qa)
184
+ session.commit()
185
  except Exception as e:
186
+ session.rollback()
187
  raise
188
  finally:
189
+ session.close()
190
 
191
+ # --- User Profile ---
192
  def get_user_profile(username):
193
+ session = get_db_session()
194
  try:
195
+ user = session.query(User).filter(User.username == username).first()
196
+ if user:
197
+ return {
198
+ 'username': user.username,
199
+ 'email': user.email,
200
+ 'phone': user.phone,
201
+ 'company': user.company
202
+ }
203
+ return None
204
  finally:
205
+ session.close()
206
 
207
  def update_user_profile(username, email, phone, company):
208
+ session = get_db_session()
209
  try:
210
+ user = session.query(User).filter(User.username == username).first()
211
+ if user:
212
+ user.email = email
213
+ user.phone = phone
214
+ user.company = company
215
+ session.commit()
216
+ return True
217
+ return False
 
 
218
  finally:
219
+ session.close()
220
 
221
  def change_user_password(username, current_password, new_password):
222
+ session = get_db_session()
223
  try:
224
+ user = session.query(User).filter(User.username == username).first()
225
+ if not user:
 
 
 
226
  return False, 'User not found'
227
+ if not check_password_hash(user.password_hash, current_password):
 
228
  return False, 'Current password is incorrect'
229
+ user.password_hash = generate_password_hash(new_password)
230
+ session.commit()
 
231
  return True, 'Password updated successfully'
 
 
 
232
  finally:
233
+ session.close()
backend/app/routes/routes.py CHANGED
@@ -1,5 +1,4 @@
1
  import os
2
- import sqlite3
3
  from flask import Blueprint, request, jsonify, send_from_directory, current_app
4
  from werkzeug.utils import secure_filename
5
  from app.utils.extract_text import extract_text_from_pdf
@@ -7,7 +6,7 @@ from app.utils.summarizer import generate_summary
7
  from app.utils.clause_detector import detect_clauses
8
  from app.database import save_document, delete_document
9
  from app.database import get_all_documents, get_document_by_id
10
- from app.database import search_documents
11
  from app.nlp.qa import answer_question
12
  from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity, exceptions as jwt_exceptions
13
  from flask_jwt_extended.exceptions import JWTDecodeError as JWTError
@@ -19,6 +18,9 @@ from app.utils.context_understanding import ContextUnderstanding
19
  import logging
20
  import textract
21
  from app.database import get_user_profile, update_user_profile, change_user_password
 
 
 
22
 
23
  main = Blueprint("main", __name__)
24
 
@@ -28,7 +30,6 @@ legal_domain_processor = LegalDomainFeatures()
28
  context_processor = ContextUnderstanding()
29
 
30
  BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
31
- DB_PATH = os.path.join(BASE_DIR, 'legal_docs.db')
32
  UPLOAD_FOLDER = os.path.join(BASE_DIR, 'uploads')
33
 
34
  # Ensure the upload folder exists
@@ -53,41 +54,35 @@ def extract_text_from_file(file_path):
53
  else:
54
  raise Exception("Unsupported file type for text extraction.")
55
 
 
 
 
 
 
 
 
 
56
  @main.route('/upload', methods=['POST'])
57
  @jwt_required()
58
  def upload_file():
59
  try:
60
  if 'file' not in request.files:
61
  return jsonify({'error': 'No file part'}), 400
62
-
63
  file = request.files['file']
64
- if file.filename == '':
65
  return jsonify({'error': 'No selected file'}), 400
66
-
67
- # Only allow PDF files
68
  if not (file.filename.lower().endswith('.pdf')):
69
  return jsonify({'error': 'File type not allowed. Only PDF files are supported.'}), 400
70
-
71
- # Save file first
72
  filename = secure_filename(file.filename)
73
  file_path = os.path.join(UPLOAD_FOLDER, filename)
74
  file.save(file_path)
75
-
76
- # Get user_id from JWT identity
77
  identity = get_jwt_identity()
78
- conn = sqlite3.connect(DB_PATH)
79
- cursor = conn.cursor()
80
- cursor.execute('SELECT id FROM users WHERE username = ?', (identity,))
81
- user_row = cursor.fetchone()
82
- conn.close()
83
- if not user_row:
84
  return jsonify({"success": False, "error": "User not found"}), 401
85
- user_id = user_row[0]
86
-
87
- # Create initial document entry
88
  doc_id = save_document(
89
  title=filename,
90
- full_text="", # Will be updated later
91
  summary="Processing...",
92
  clauses="[]",
93
  features="{}",
@@ -95,89 +90,44 @@ def upload_file():
95
  file_path=file_path,
96
  user_id=user_id
97
  )
98
-
99
- # Return immediate response with document ID
100
  return jsonify({
101
  'message': 'File uploaded successfully',
102
  'document_id': doc_id,
103
  'title': filename,
104
  'status': 'processing'
105
  }), 200
106
-
107
  except Exception as e:
108
  logging.error(f"Error during file upload: {str(e)}")
109
  return jsonify({'error': str(e)}), 500
110
 
111
-
112
  @main.route('/documents', methods=['GET'])
113
  @jwt_required()
114
  def list_documents():
115
- logging.debug("Attempting to list documents...")
116
  try:
117
- identity = get_jwt_identity()
118
- logging.debug(f"JWT identity for listing documents: {identity}")
119
  docs = get_all_documents()
120
- logging.info(f"Successfully fetched {len(docs)} documents.")
121
  return jsonify(docs), 200
122
- except jwt_exceptions.NoAuthorizationError as e:
123
- logging.error(f"No authorization token provided for list documents: {str(e)}")
124
- return jsonify({"success": False, "error": "Authorization token missing"}), 401
125
- except jwt_exceptions.InvalidHeaderError as e:
126
- logging.error(f"Invalid authorization header for list documents: {str(e)}")
127
- return jsonify({"success": False, "error": "Invalid authorization header"}), 422
128
- except JWTError as e: # Catch general JWT errors
129
- logging.error(f"JWT error for list documents: {str(e)}")
130
- return jsonify({"success": False, "error": f"JWT error: {str(e)}"}), 422
131
  except Exception as e:
132
  logging.error(f"Error listing documents: {str(e)}", exc_info=True)
133
  return jsonify({"error": str(e)}), 500
134
 
135
-
136
  @main.route('/get_document/<int:doc_id>', methods=['GET'])
137
  @jwt_required()
138
  def get_document(doc_id):
139
- logging.debug(f"Attempting to get document with ID: {doc_id}")
140
  try:
141
- identity = get_jwt_identity()
142
- logging.debug(f"JWT identity for getting document: {identity}")
143
  doc = get_document_by_id(doc_id)
144
  if doc:
145
- logging.info(f"Successfully fetched document {doc_id}")
146
  return jsonify(doc), 200
147
  else:
148
- logging.warning(f"Document with ID {doc_id} not found.")
149
  return jsonify({"error": "Document not found"}), 404
150
- except jwt_exceptions.NoAuthorizationError as e:
151
- logging.error(f"No authorization token provided for get document: {str(e)}")
152
- return jsonify({"success": False, "error": "Authorization token missing"}), 401
153
- except jwt_exceptions.InvalidHeaderError as e:
154
- logging.error(f"Invalid authorization header for get document: {str(e)}")
155
- return jsonify({"success": False, "error": "Invalid authorization header"}), 422
156
- except JWTError as e: # Catch general JWT errors
157
- logging.error(f"JWT error for get document: {str(e)}")
158
- return jsonify({"success": False, "error": f"JWT error: {str(e)}"}), 422
159
  except Exception as e:
160
  logging.error(f"Error getting document {doc_id}: {str(e)}", exc_info=True)
161
  return jsonify({"error": str(e)}), 500
162
 
163
-
164
  @main.route('/documents/download/<filename>', methods=['GET'])
165
  @jwt_required()
166
  def download_document(filename):
167
- logging.debug(f"Attempting to download file: {filename}")
168
  try:
169
- identity = get_jwt_identity()
170
- logging.debug(f"JWT identity for downloading document: {identity}")
171
  return send_from_directory(UPLOAD_FOLDER, filename, as_attachment=True)
172
- except jwt_exceptions.NoAuthorizationError as e:
173
- logging.error(f"No authorization token provided for download document: {str(e)}")
174
- return jsonify({"success": False, "error": "Authorization token missing"}), 401
175
- except jwt_exceptions.InvalidHeaderError as e:
176
- logging.error(f"Invalid authorization header for download document: {str(e)}")
177
- return jsonify({"success": False, "error": "Invalid authorization header"}), 422
178
- except JWTError as e: # Catch general JWT errors
179
- logging.error(f"JWT error for download document: {str(e)}")
180
- return jsonify({"success": False, "error": f"JWT error: {str(e)}"}), 422
181
  except Exception as e:
182
  logging.error(f"Error downloading file {filename}: {str(e)}", exc_info=True)
183
  return jsonify({"error": f"Error downloading file: {str(e)}"}), 500
@@ -185,20 +135,8 @@ def download_document(filename):
185
  @main.route('/documents/view/<filename>', methods=['GET'])
186
  @jwt_required()
187
  def view_document(filename):
188
- logging.debug(f"Attempting to view file: {filename}")
189
  try:
190
- identity = get_jwt_identity()
191
- logging.debug(f"JWT identity for viewing document: {identity}")
192
  return send_from_directory(UPLOAD_FOLDER, filename)
193
- except jwt_exceptions.NoAuthorizationError as e:
194
- logging.error(f"No authorization token provided for view document: {str(e)}")
195
- return jsonify({"success": False, "error": "Authorization token missing"}), 401
196
- except jwt_exceptions.InvalidHeaderError as e:
197
- logging.error(f"Invalid authorization header for view document: {str(e)}")
198
- return jsonify({"success": False, "error": "Invalid authorization header"}), 422
199
- except JWTError as e: # Catch general JWT errors
200
- logging.error(f"JWT error for view document: {str(e)}")
201
- return jsonify({"success": False, "error": f"JWT error: {str(e)}"}), 422
202
  except Exception as e:
203
  logging.error(f"Error viewing file {filename}: {str(e)}", exc_info=True)
204
  return jsonify({"error": f"Error viewing file: {str(e)}"}), 500
@@ -206,30 +144,15 @@ def view_document(filename):
206
  @main.route('/documents/<int:doc_id>', methods=['DELETE'])
207
  @jwt_required()
208
  def delete_document_route(doc_id):
209
- logging.debug(f"Attempting to delete document with ID: {doc_id}")
210
  try:
211
- identity = get_jwt_identity()
212
- logging.debug(f"JWT identity for deleting document: {identity}")
213
- file_path_to_delete = delete_document(doc_id) # This returns the file path
214
  if file_path_to_delete and os.path.exists(file_path_to_delete):
215
  os.remove(file_path_to_delete)
216
- logging.info(f"Successfully deleted file {file_path_to_delete} from file system.")
217
- logging.info(f"Document {doc_id} deleted from database.")
218
  return jsonify({"success": True, "message": "Document deleted successfully"}), 200
219
- except jwt_exceptions.NoAuthorizationError as e:
220
- logging.error(f"No authorization token provided for delete document: {str(e)}")
221
- return jsonify({"success": False, "error": "Authorization token missing"}), 401
222
- except jwt_exceptions.InvalidHeaderError as e:
223
- logging.error(f"Invalid authorization header for delete document: {str(e)}")
224
- return jsonify({"success": False, "error": "Invalid authorization header"}), 422
225
- except JWTError as e: # Catch general JWT errors
226
- logging.error(f"JWT error for delete document: {str(e)}")
227
- return jsonify({"success": False, "error": f"JWT error: {str(e)}"}), 422
228
  except Exception as e:
229
  logging.error(f"Error deleting document {doc_id}: {str(e)}", exc_info=True)
230
  return jsonify({"success": False, "error": f"Error deleting document: {str(e)}"}), 500
231
 
232
-
233
  @main.route('/register', methods=['POST'])
234
  @handle_errors
235
  def register():
@@ -237,32 +160,25 @@ def register():
237
  username = data.get("username")
238
  password = data.get("password")
239
  email = data.get("email")
240
-
241
  if not username or not password:
242
  logging.warning("Registration attempt with missing username or password.")
243
  return jsonify({"error": "Username and password are required"}), 400
244
-
245
  hashed_pw = generate_password_hash(password)
246
- conn = None
247
-
248
  try:
249
- conn = sqlite3.connect(DB_PATH)
250
- cursor = conn.cursor()
251
- cursor.execute("INSERT INTO users (username, password_hash, email) VALUES (?, ?, ?)", (username, hashed_pw, email))
252
- conn.commit()
253
- logging.info(f"User {username} registered successfully.")
254
  return jsonify({"message": "User registered successfully", "username": username, "email": email}), 201
255
- except sqlite3.IntegrityError:
256
- logging.warning(f"Registration attempt for existing username: {username}")
257
  return jsonify({"error": "Username already exists"}), 409
258
  except Exception as e:
 
259
  logging.error(f"Database error during registration: {str(e)}", exc_info=True)
260
  return jsonify({"error": f"Database error: {str(e)}"}), 500
261
  finally:
262
- if conn:
263
- conn.close()
264
-
265
-
266
 
267
  @main.route('/login', methods=['POST'])
268
  @handle_errors
@@ -270,91 +186,60 @@ def login():
270
  data = request.get_json()
271
  username = data.get("username")
272
  password = data.get("password")
273
-
274
  if not username or not password:
275
  logging.warning("Login attempt with missing username or password.")
276
  return jsonify({"error": "Username and password are required"}), 400
277
-
278
- conn = None
279
  try:
280
- conn = sqlite3.connect(DB_PATH)
281
- cursor = conn.cursor()
282
- # Allow login with either username or email
283
- cursor.execute(
284
- "SELECT password_hash, email, username FROM users WHERE username = ? OR email = ?",
285
- (username, username)
286
- )
287
- user = cursor.fetchone()
288
- conn.close()
289
-
290
- logging.debug(f"Login attempt for user: {username}")
291
- if user:
292
- stored_password_hash = user[0]
293
- user_email = user[1]
294
- user_username = user[2]
295
- password_match = check_password_hash(stored_password_hash, password)
296
- if password_match:
297
- access_token = create_access_token(identity=user_username)
298
- logging.info(f"User {user_username} logged in successfully.")
299
- return jsonify(access_token=access_token, username=user_username, email=user_email), 200
300
- else:
301
- logging.warning(f"Failed login attempt for username/email: {username} - Incorrect password.")
302
- return jsonify({"error": "Bad username or password"}), 401
303
  else:
304
- logging.warning(f"Failed login attempt: Username or email {username} not found.")
305
  return jsonify({"error": "Bad username or password"}), 401
306
  except Exception as e:
307
  logging.error(f"Database error during login: {str(e)}", exc_info=True)
308
  return jsonify({"error": f"Database error: {str(e)}"}), 500
309
  finally:
310
- if conn:
311
- conn.close()
312
-
313
 
314
  @main.route('/process-document/<int:doc_id>', methods=['POST'])
315
  @jwt_required()
316
  def process_document(doc_id):
317
  try:
318
- # Get the document
319
  document = get_document_by_id(doc_id)
320
  if not document:
321
  return jsonify({'error': 'Document not found'}), 404
322
-
323
  file_path = document['file_path']
324
-
325
- # Extract text
326
  text = extract_text_from_file(file_path)
327
  if not text:
328
  return jsonify({'error': 'Could not extract text from file'}), 400
329
-
330
- # Process the document
331
  summary = generate_summary(text)
332
  clauses = detect_clauses(text)
333
  features = legal_domain_processor.process_legal_document(text)
334
  context_analysis = context_processor.analyze_context(text)
335
-
336
  # Update the document with processed content
337
- conn = sqlite3.connect(DB_PATH)
338
- cursor = conn.cursor()
339
- cursor.execute('''
340
- UPDATE documents
341
- SET full_text = ?, summary = ?, clauses = ?, features = ?, context_analysis = ?
342
- WHERE id = ?
343
- ''', (text, summary, str(clauses), str(features), str(context_analysis), doc_id))
344
- conn.commit()
345
- conn.close()
346
-
 
 
347
  return jsonify({
348
  'message': 'Document processed successfully',
349
  'document_id': doc_id,
350
  'status': 'completed'
351
  }), 200
352
-
353
  except Exception as e:
354
  logging.error(f"Error processing document: {str(e)}")
355
  return jsonify({'error': str(e)}), 500
356
 
357
-
358
  @main.route('/documents/summary/<int:doc_id>', methods=['POST'])
359
  @jwt_required()
360
  def generate_document_summary(doc_id):
@@ -362,24 +247,25 @@ def generate_document_summary(doc_id):
362
  doc = get_document_by_id(doc_id)
363
  if not doc:
364
  return jsonify({"error": "Document not found"}), 404
365
- # If summary exists and is not empty, return it
366
  summary = doc.get('summary', '')
367
  if summary and summary.strip() and summary != 'Processing...':
368
  return jsonify({"summary": summary}), 200
369
  file_path = doc.get('file_path', '')
370
  if not file_path or not os.path.exists(file_path):
371
  return jsonify({"error": "File not found for this document"}), 404
372
- # Extract text from file (PDF, DOC, DOCX)
373
  text = extract_text_from_file(file_path)
374
  if not text.strip():
375
  return jsonify({"error": "No text available for summarization"}), 400
376
  summary = generate_summary(text)
377
  # Save the summary to the database
378
- conn = sqlite3.connect(DB_PATH)
379
- cursor = conn.cursor()
380
- cursor.execute('UPDATE documents SET summary = ? WHERE id = ?', (summary, doc_id))
381
- conn.commit()
382
- conn.close()
 
 
 
383
  return jsonify({"summary": summary}), 200
384
  except Exception as e:
385
  return jsonify({"error": f"Error generating summary: {str(e)}"}), 500
@@ -387,50 +273,29 @@ def generate_document_summary(doc_id):
387
  @main.route('/ask-question', methods=['POST', 'OPTIONS'])
388
  def ask_question():
389
  if request.method == 'OPTIONS':
390
- # Allow CORS preflight without authentication
391
  return '', 204
392
  return _ask_question_impl()
393
 
394
  @jwt_required()
395
  def _ask_question_impl():
396
- logging.debug('ask_question route called. Method: %s', request.method)
397
  data = request.get_json()
398
  document_id = data.get('document_id')
399
  question = data.get('question', '').strip()
400
  if not document_id or not question:
401
- logging.debug('Missing document_id or question in /ask-question')
402
  return jsonify({"success": False, "error": "document_id and question are required"}), 400
403
  if not question:
404
- logging.debug('Empty question in /ask-question')
405
  return jsonify({"success": False, "error": "Question cannot be empty"}), 400
406
  identity = get_jwt_identity()
407
- conn = sqlite3.connect(DB_PATH)
408
- cursor = conn.cursor()
409
- cursor.execute('SELECT id FROM users WHERE username = ?', (identity,))
410
- user_row = cursor.fetchone()
411
- if not user_row:
412
- conn.close()
413
- logging.debug('User not found in /ask-question')
414
- return jsonify({"success": False, "error": "User not found"}), 401
415
- user_id = user_row[0]
416
- # Fetch document and check ownership
417
- cursor.execute('SELECT summary FROM documents WHERE id = ? AND user_id = ?', (document_id, user_id))
418
- row = cursor.fetchone()
419
- conn.close()
420
- if not row:
421
- logging.debug('Document not found or not owned by user in /ask-question')
422
  return jsonify({"success": False, "error": "Document not found or not owned by user"}), 404
423
- summary = row[0]
424
  if not summary or not summary.strip():
425
- logging.debug('Summary not available for this document in /ask-question')
426
  return jsonify({"success": False, "error": "Summary not available for this document"}), 400
427
  try:
428
  result = answer_question(question, summary)
429
- logging.debug('Answer generated successfully in /ask-question')
430
-
431
- # Save the question and answer to database
432
  save_question_answer(document_id, user_id, question, result.get('answer', ''), result.get('score', 0.0))
433
-
434
  return jsonify({"success": True, "answer": result.get('answer', ''), "score": result.get('score', 0.0)}), 200
435
  except Exception as e:
436
  logging.error(f"Error answering question: {str(e)}")
@@ -441,62 +306,17 @@ def _ask_question_impl():
441
  def get_previous_questions(doc_id):
442
  try:
443
  identity = get_jwt_identity()
444
- conn = sqlite3.connect(DB_PATH)
445
- cursor = conn.cursor()
446
- cursor.execute('SELECT id FROM users WHERE username = ?', (identity,))
447
- user_row = cursor.fetchone()
448
- if not user_row:
449
- conn.close()
450
- return jsonify({"success": False, "error": "User not found"}), 401
451
- user_id = user_row[0]
452
-
453
- # Check if document belongs to user
454
- cursor.execute('SELECT id FROM documents WHERE id = ? AND user_id = ?', (doc_id, user_id))
455
- if not cursor.fetchone():
456
- conn.close()
457
  return jsonify({"success": False, "error": "Document not found or not owned by user"}), 404
458
-
459
- # Fetch previous questions for this document
460
- cursor.execute('''
461
- SELECT id, question, answer, score, created_at
462
- FROM question_answers
463
- WHERE document_id = ? AND user_id = ?
464
- ORDER BY created_at DESC
465
- ''', (doc_id, user_id))
466
-
467
- questions = []
468
- for row in cursor.fetchall():
469
- questions.append({
470
- 'id': row[0],
471
- 'question': row[1],
472
- 'answer': row[2],
473
- 'score': row[3],
474
- 'timestamp': row[4]
475
- })
476
-
477
- conn.close()
478
  return jsonify({"success": True, "questions": questions}), 200
479
-
480
  except Exception as e:
481
  logging.error(f"Error fetching previous questions: {str(e)}")
482
  return jsonify({"success": False, "error": f"Error fetching previous questions: {str(e)}"}), 500
483
 
484
- def save_question_answer(document_id, user_id, question, answer, score):
485
- """Save question and answer to database"""
486
- try:
487
- conn = sqlite3.connect(DB_PATH)
488
- cursor = conn.cursor()
489
- cursor.execute('''
490
- INSERT INTO question_answers (document_id, user_id, question, answer, score, created_at)
491
- VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
492
- ''', (document_id, user_id, question, answer, score))
493
- conn.commit()
494
- conn.close()
495
- logging.info(f"Question and answer saved for document {document_id}")
496
- except Exception as e:
497
- logging.error(f"Error saving question and answer: {str(e)}")
498
- raise
499
-
500
  @main.route('/search', methods=['GET'])
501
  @jwt_required()
502
  def search_all():
@@ -505,19 +325,8 @@ def search_all():
505
  if not query:
506
  return jsonify({'error': 'Query parameter "q" is required.'}), 400
507
  identity = get_jwt_identity()
508
- # Get user_id
509
- conn = sqlite3.connect(DB_PATH)
510
- cursor = conn.cursor()
511
- cursor.execute('SELECT id FROM users WHERE username = ?', (identity,))
512
- user_row = cursor.fetchone()
513
- conn.close()
514
- if not user_row:
515
- return jsonify({'error': 'User not found'}), 401
516
- user_id = user_row[0]
517
- # Search documents (title, summary)
518
- from app.database import search_documents, search_questions_answers
519
  doc_results = search_documents(query)
520
- # Search Q&A
521
  qa_results = search_questions_answers(query, user_id=user_id)
522
  return jsonify({
523
  'documents': doc_results,
@@ -575,34 +384,16 @@ def change_password():
575
  def dashboard_stats():
576
  try:
577
  identity = get_jwt_identity()
578
- # Get user_id
579
- conn = sqlite3.connect(DB_PATH)
580
- cursor = conn.cursor()
581
- cursor.execute('SELECT id FROM users WHERE username = ?', (identity,))
582
- user_row = cursor.fetchone()
583
- if not user_row:
584
- conn.close()
585
- return jsonify({'error': 'User not found'}), 401
586
- user_id = user_row[0]
587
- conn.close()
588
-
589
- # Get all documents for this user
590
- from app.database import get_all_documents
591
  documents = get_all_documents(user_id=user_id)
592
  total_documents = len(documents)
593
  processed_documents = sum(1 for doc in documents if doc.get('summary') and doc.get('summary') != 'Processing...')
594
  pending_analysis = total_documents - processed_documents
595
-
596
- # Count recent questions (last 30 days)
597
- conn = sqlite3.connect(DB_PATH)
598
- cursor = conn.cursor()
599
- cursor.execute('''
600
- SELECT COUNT(*) FROM question_answers
601
- WHERE user_id = ? AND created_at >= datetime('now', '-30 days')
602
- ''', (user_id,))
603
- recent_questions = cursor.fetchone()[0]
604
- conn.close()
605
-
606
  return jsonify({
607
  'total_documents': total_documents,
608
  'processed_documents': processed_documents,
 
1
  import os
 
2
  from flask import Blueprint, request, jsonify, send_from_directory, current_app
3
  from werkzeug.utils import secure_filename
4
  from app.utils.extract_text import extract_text_from_pdf
 
6
  from app.utils.clause_detector import detect_clauses
7
  from app.database import save_document, delete_document
8
  from app.database import get_all_documents, get_document_by_id
9
+ from app.database import search_documents, save_question_answer, search_questions_answers
10
  from app.nlp.qa import answer_question
11
  from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity, exceptions as jwt_exceptions
12
  from flask_jwt_extended.exceptions import JWTDecodeError as JWTError
 
18
  import logging
19
  import textract
20
  from app.database import get_user_profile, update_user_profile, change_user_password
21
+ from app.database import SessionLocal, User
22
+ from sqlalchemy.exc import IntegrityError
23
+ from sqlalchemy import or_
24
 
25
  main = Blueprint("main", __name__)
26
 
 
30
  context_processor = ContextUnderstanding()
31
 
32
  BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
 
33
  UPLOAD_FOLDER = os.path.join(BASE_DIR, 'uploads')
34
 
35
  # Ensure the upload folder exists
 
54
  else:
55
  raise Exception("Unsupported file type for text extraction.")
56
 
57
+ def get_user_id_by_username(username):
58
+ session = SessionLocal()
59
+ try:
60
+ user = session.query(User).filter(User.username == username).first()
61
+ return user.id if user else None
62
+ finally:
63
+ session.close()
64
+
65
  @main.route('/upload', methods=['POST'])
66
  @jwt_required()
67
  def upload_file():
68
  try:
69
  if 'file' not in request.files:
70
  return jsonify({'error': 'No file part'}), 400
 
71
  file = request.files['file']
72
+ if not file or file.filename == '':
73
  return jsonify({'error': 'No selected file'}), 400
 
 
74
  if not (file.filename.lower().endswith('.pdf')):
75
  return jsonify({'error': 'File type not allowed. Only PDF files are supported.'}), 400
 
 
76
  filename = secure_filename(file.filename)
77
  file_path = os.path.join(UPLOAD_FOLDER, filename)
78
  file.save(file_path)
 
 
79
  identity = get_jwt_identity()
80
+ user_id = get_user_id_by_username(identity)
81
+ if not user_id:
 
 
 
 
82
  return jsonify({"success": False, "error": "User not found"}), 401
 
 
 
83
  doc_id = save_document(
84
  title=filename,
85
+ full_text="",
86
  summary="Processing...",
87
  clauses="[]",
88
  features="{}",
 
90
  file_path=file_path,
91
  user_id=user_id
92
  )
 
 
93
  return jsonify({
94
  'message': 'File uploaded successfully',
95
  'document_id': doc_id,
96
  'title': filename,
97
  'status': 'processing'
98
  }), 200
 
99
  except Exception as e:
100
  logging.error(f"Error during file upload: {str(e)}")
101
  return jsonify({'error': str(e)}), 500
102
 
 
103
  @main.route('/documents', methods=['GET'])
104
  @jwt_required()
105
  def list_documents():
 
106
  try:
 
 
107
  docs = get_all_documents()
 
108
  return jsonify(docs), 200
 
 
 
 
 
 
 
 
 
109
  except Exception as e:
110
  logging.error(f"Error listing documents: {str(e)}", exc_info=True)
111
  return jsonify({"error": str(e)}), 500
112
 
 
113
  @main.route('/get_document/<int:doc_id>', methods=['GET'])
114
  @jwt_required()
115
  def get_document(doc_id):
 
116
  try:
 
 
117
  doc = get_document_by_id(doc_id)
118
  if doc:
 
119
  return jsonify(doc), 200
120
  else:
 
121
  return jsonify({"error": "Document not found"}), 404
 
 
 
 
 
 
 
 
 
122
  except Exception as e:
123
  logging.error(f"Error getting document {doc_id}: {str(e)}", exc_info=True)
124
  return jsonify({"error": str(e)}), 500
125
 
 
126
  @main.route('/documents/download/<filename>', methods=['GET'])
127
  @jwt_required()
128
  def download_document(filename):
 
129
  try:
 
 
130
  return send_from_directory(UPLOAD_FOLDER, filename, as_attachment=True)
 
 
 
 
 
 
 
 
 
131
  except Exception as e:
132
  logging.error(f"Error downloading file {filename}: {str(e)}", exc_info=True)
133
  return jsonify({"error": f"Error downloading file: {str(e)}"}), 500
 
135
  @main.route('/documents/view/<filename>', methods=['GET'])
136
  @jwt_required()
137
  def view_document(filename):
 
138
  try:
 
 
139
  return send_from_directory(UPLOAD_FOLDER, filename)
 
 
 
 
 
 
 
 
 
140
  except Exception as e:
141
  logging.error(f"Error viewing file {filename}: {str(e)}", exc_info=True)
142
  return jsonify({"error": f"Error viewing file: {str(e)}"}), 500
 
144
  @main.route('/documents/<int:doc_id>', methods=['DELETE'])
145
  @jwt_required()
146
  def delete_document_route(doc_id):
 
147
  try:
148
+ file_path_to_delete = delete_document(doc_id)
 
 
149
  if file_path_to_delete and os.path.exists(file_path_to_delete):
150
  os.remove(file_path_to_delete)
 
 
151
  return jsonify({"success": True, "message": "Document deleted successfully"}), 200
 
 
 
 
 
 
 
 
 
152
  except Exception as e:
153
  logging.error(f"Error deleting document {doc_id}: {str(e)}", exc_info=True)
154
  return jsonify({"success": False, "error": f"Error deleting document: {str(e)}"}), 500
155
 
 
156
  @main.route('/register', methods=['POST'])
157
  @handle_errors
158
  def register():
 
160
  username = data.get("username")
161
  password = data.get("password")
162
  email = data.get("email")
 
163
  if not username or not password:
164
  logging.warning("Registration attempt with missing username or password.")
165
  return jsonify({"error": "Username and password are required"}), 400
 
166
  hashed_pw = generate_password_hash(password)
167
+ session = SessionLocal()
 
168
  try:
169
+ user = User(username=username, password_hash=hashed_pw, email=email)
170
+ session.add(user)
171
+ session.commit()
 
 
172
  return jsonify({"message": "User registered successfully", "username": username, "email": email}), 201
173
+ except IntegrityError:
174
+ session.rollback()
175
  return jsonify({"error": "Username already exists"}), 409
176
  except Exception as e:
177
+ session.rollback()
178
  logging.error(f"Database error during registration: {str(e)}", exc_info=True)
179
  return jsonify({"error": f"Database error: {str(e)}"}), 500
180
  finally:
181
+ session.close()
 
 
 
182
 
183
  @main.route('/login', methods=['POST'])
184
  @handle_errors
 
186
  data = request.get_json()
187
  username = data.get("username")
188
  password = data.get("password")
 
189
  if not username or not password:
190
  logging.warning("Login attempt with missing username or password.")
191
  return jsonify({"error": "Username and password are required"}), 400
192
+ session = SessionLocal()
 
193
  try:
194
+ user = session.query(User).filter(or_(User.username == username, User.email == username)).first()
195
+ if user and check_password_hash(user.password_hash, password):
196
+ access_token = create_access_token(identity=user.username)
197
+ return jsonify(access_token=access_token, username=user.username, email=user.email), 200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  else:
 
199
  return jsonify({"error": "Bad username or password"}), 401
200
  except Exception as e:
201
  logging.error(f"Database error during login: {str(e)}", exc_info=True)
202
  return jsonify({"error": f"Database error: {str(e)}"}), 500
203
  finally:
204
+ session.close()
 
 
205
 
206
  @main.route('/process-document/<int:doc_id>', methods=['POST'])
207
  @jwt_required()
208
  def process_document(doc_id):
209
  try:
 
210
  document = get_document_by_id(doc_id)
211
  if not document:
212
  return jsonify({'error': 'Document not found'}), 404
 
213
  file_path = document['file_path']
 
 
214
  text = extract_text_from_file(file_path)
215
  if not text:
216
  return jsonify({'error': 'Could not extract text from file'}), 400
 
 
217
  summary = generate_summary(text)
218
  clauses = detect_clauses(text)
219
  features = legal_domain_processor.process_legal_document(text)
220
  context_analysis = context_processor.analyze_context(text)
 
221
  # Update the document with processed content
222
+ session = SessionLocal()
223
+ try:
224
+ doc = session.query(User).get(doc_id)
225
+ if doc:
226
+ doc.full_text = text
227
+ doc.summary = summary
228
+ doc.clauses = str(clauses)
229
+ doc.features = str(features)
230
+ doc.context_analysis = str(context_analysis)
231
+ session.commit()
232
+ finally:
233
+ session.close()
234
  return jsonify({
235
  'message': 'Document processed successfully',
236
  'document_id': doc_id,
237
  'status': 'completed'
238
  }), 200
 
239
  except Exception as e:
240
  logging.error(f"Error processing document: {str(e)}")
241
  return jsonify({'error': str(e)}), 500
242
 
 
243
  @main.route('/documents/summary/<int:doc_id>', methods=['POST'])
244
  @jwt_required()
245
  def generate_document_summary(doc_id):
 
247
  doc = get_document_by_id(doc_id)
248
  if not doc:
249
  return jsonify({"error": "Document not found"}), 404
 
250
  summary = doc.get('summary', '')
251
  if summary and summary.strip() and summary != 'Processing...':
252
  return jsonify({"summary": summary}), 200
253
  file_path = doc.get('file_path', '')
254
  if not file_path or not os.path.exists(file_path):
255
  return jsonify({"error": "File not found for this document"}), 404
 
256
  text = extract_text_from_file(file_path)
257
  if not text.strip():
258
  return jsonify({"error": "No text available for summarization"}), 400
259
  summary = generate_summary(text)
260
  # Save the summary to the database
261
+ session = SessionLocal()
262
+ try:
263
+ document = session.query(User).get(doc_id)
264
+ if document:
265
+ document.summary = summary
266
+ session.commit()
267
+ finally:
268
+ session.close()
269
  return jsonify({"summary": summary}), 200
270
  except Exception as e:
271
  return jsonify({"error": f"Error generating summary: {str(e)}"}), 500
 
273
  @main.route('/ask-question', methods=['POST', 'OPTIONS'])
274
  def ask_question():
275
  if request.method == 'OPTIONS':
 
276
  return '', 204
277
  return _ask_question_impl()
278
 
279
  @jwt_required()
280
  def _ask_question_impl():
 
281
  data = request.get_json()
282
  document_id = data.get('document_id')
283
  question = data.get('question', '').strip()
284
  if not document_id or not question:
 
285
  return jsonify({"success": False, "error": "document_id and question are required"}), 400
286
  if not question:
 
287
  return jsonify({"success": False, "error": "Question cannot be empty"}), 400
288
  identity = get_jwt_identity()
289
+ user_id = get_user_id_by_username(identity)
290
+ doc = get_document_by_id(document_id, user_id=user_id)
291
+ if not doc:
 
 
 
 
 
 
 
 
 
 
 
 
292
  return jsonify({"success": False, "error": "Document not found or not owned by user"}), 404
293
+ summary = doc.get('summary', '')
294
  if not summary or not summary.strip():
 
295
  return jsonify({"success": False, "error": "Summary not available for this document"}), 400
296
  try:
297
  result = answer_question(question, summary)
 
 
 
298
  save_question_answer(document_id, user_id, question, result.get('answer', ''), result.get('score', 0.0))
 
299
  return jsonify({"success": True, "answer": result.get('answer', ''), "score": result.get('score', 0.0)}), 200
300
  except Exception as e:
301
  logging.error(f"Error answering question: {str(e)}")
 
306
  def get_previous_questions(doc_id):
307
  try:
308
  identity = get_jwt_identity()
309
+ user_id = get_user_id_by_username(identity)
310
+ doc = get_document_by_id(doc_id, user_id=user_id)
311
+ if not doc:
 
 
 
 
 
 
 
 
 
 
312
  return jsonify({"success": False, "error": "Document not found or not owned by user"}), 404
313
+ qa_results = search_questions_answers('', user_id=user_id)
314
+ questions = [q for q in qa_results if q['document_id'] == doc_id]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  return jsonify({"success": True, "questions": questions}), 200
 
316
  except Exception as e:
317
  logging.error(f"Error fetching previous questions: {str(e)}")
318
  return jsonify({"success": False, "error": f"Error fetching previous questions: {str(e)}"}), 500
319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  @main.route('/search', methods=['GET'])
321
  @jwt_required()
322
  def search_all():
 
325
  if not query:
326
  return jsonify({'error': 'Query parameter "q" is required.'}), 400
327
  identity = get_jwt_identity()
328
+ user_id = get_user_id_by_username(identity)
 
 
 
 
 
 
 
 
 
 
329
  doc_results = search_documents(query)
 
330
  qa_results = search_questions_answers(query, user_id=user_id)
331
  return jsonify({
332
  'documents': doc_results,
 
384
  def dashboard_stats():
385
  try:
386
  identity = get_jwt_identity()
387
+ user_id = get_user_id_by_username(identity)
 
 
 
 
 
 
 
 
 
 
 
 
388
  documents = get_all_documents(user_id=user_id)
389
  total_documents = len(documents)
390
  processed_documents = sum(1 for doc in documents if doc.get('summary') and doc.get('summary') != 'Processing...')
391
  pending_analysis = total_documents - processed_documents
392
+ qa_results = search_questions_answers('', user_id=user_id)
393
+ from datetime import datetime, timedelta
394
+ now = datetime.utcnow()
395
+ last_30_days = now - timedelta(days=30)
396
+ recent_questions = sum(1 for q in qa_results if q['created_at'] and q['created_at'] >= last_30_days)
 
 
 
 
 
 
397
  return jsonify({
398
  'total_documents': total_documents,
399
  'processed_documents': processed_documents,
backend/create_db.py DELETED
@@ -1,17 +0,0 @@
1
- import sqlite3
2
-
3
- conn = sqlite3.connect('./legal_docs.db')
4
- cursor = conn.cursor()
5
-
6
- cursor.execute('''
7
- CREATE TABLE IF NOT EXISTS users (
8
- id INTEGER PRIMARY KEY AUTOINCREMENT,
9
- username TEXT UNIQUE NOT NULL,
10
- password_hash TEXT NOT NULL,
11
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
12
- )
13
- ''')
14
-
15
- conn.commit()
16
- conn.close()
17
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/requirements.txt CHANGED
Binary files a/backend/requirements.txt and b/backend/requirements.txt differ