Omar ID EL MOUMEN
commited on
Commit
·
d2afbf4
1
Parent(s):
255cedd
Remove index display + change index method
Browse files- app.py +46 -20
- static/script.js +1 -47
- templates/index.html +4 -12
app.py
CHANGED
@@ -4,6 +4,8 @@ import json
|
|
4 |
import os
|
5 |
import time
|
6 |
import traceback
|
|
|
|
|
7 |
import warnings
|
8 |
from fastapi import FastAPI, HTTPException
|
9 |
from fastapi.middleware.cors import CORSMiddleware
|
@@ -11,7 +13,8 @@ from fastapi.responses import FileResponse
|
|
11 |
from fastapi.staticfiles import StaticFiles
|
12 |
from pydantic import BaseModel
|
13 |
from typing import Dict, List, Optional
|
14 |
-
|
|
|
15 |
|
16 |
warnings.filterwarnings("ignore")
|
17 |
|
@@ -53,21 +56,47 @@ class BatchDocResponse(BaseModel):
|
|
53 |
class TsgDocFinder:
|
54 |
def __init__(self):
|
55 |
self.main_ftp_url = "https://www.3gpp.org/ftp"
|
56 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
57 |
self.indexer = self.load_indexer()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
|
59 |
def load_indexer(self):
|
60 |
"""Load existing index if available"""
|
61 |
-
if
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
def save_indexer(self):
|
67 |
-
"""Save the updated index"""
|
68 |
-
with open(self.indexer_file, "w", encoding="utf-8") as f:
|
69 |
-
json.dump(self.indexer, f, indent=4, ensure_ascii=False)
|
70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
def get_workgroup(self, doc):
|
72 |
main_tsg = "tsg_ct" if doc[0] == "C" else "tsg_sa" if doc[0] == "S" else None
|
73 |
if main_tsg is None:
|
@@ -97,7 +126,7 @@ class TsgDocFinder:
|
|
97 |
print(f"Error accessing {url}: {e}")
|
98 |
return []
|
99 |
|
100 |
-
def search_document(self, doc_id, release = None):
|
101 |
"""Search for a specific document by its ID"""
|
102 |
original_id = doc_id
|
103 |
|
@@ -105,6 +134,10 @@ class TsgDocFinder:
|
|
105 |
if original_id in self.indexer:
|
106 |
return self.indexer[original_id]
|
107 |
|
|
|
|
|
|
|
|
|
108 |
# Parse the document ID
|
109 |
main_tsg, workgroup, doc = self.get_workgroup(doc_id)
|
110 |
if not main_tsg:
|
@@ -134,7 +167,6 @@ class TsgDocFinder:
|
|
134 |
if doc in file.lower() or original_id in file:
|
135 |
doc_url = f"{docs_url}/{file}"
|
136 |
self.indexer[original_id] = doc_url
|
137 |
-
self.save_indexer()
|
138 |
return doc_url
|
139 |
|
140 |
# Check in ZIP subfolder if it exists
|
@@ -147,7 +179,6 @@ class TsgDocFinder:
|
|
147 |
if doc in file.lower() or original_id in file:
|
148 |
doc_url = f"{zip_url}/{file}"
|
149 |
self.indexer[original_id] = doc_url
|
150 |
-
self.save_indexer()
|
151 |
return doc_url
|
152 |
|
153 |
return f"Document {doc_id} not found"
|
@@ -231,9 +262,4 @@ def find_documents_batch(request: BatchDocRequest):
|
|
231 |
results=results,
|
232 |
missing=missing,
|
233 |
search_time=time.time() - start_time
|
234 |
-
)
|
235 |
-
|
236 |
-
@app.get("/indexed", response_model=List[str])
|
237 |
-
def get_indexed_documents():
|
238 |
-
finder = TsgDocFinder()
|
239 |
-
return list(finder.indexer.keys())
|
|
|
4 |
import os
|
5 |
import time
|
6 |
import traceback
|
7 |
+
import psycopg2
|
8 |
+
from dotenv import load_dotenv
|
9 |
import warnings
|
10 |
from fastapi import FastAPI, HTTPException
|
11 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
13 |
from fastapi.staticfiles import StaticFiles
|
14 |
from pydantic import BaseModel
|
15 |
from typing import Dict, List, Optional
|
16 |
+
|
17 |
+
load_dotenv()
|
18 |
|
19 |
warnings.filterwarnings("ignore")
|
20 |
|
|
|
56 |
class TsgDocFinder:
|
57 |
def __init__(self):
|
58 |
self.main_ftp_url = "https://www.3gpp.org/ftp"
|
59 |
+
self.host = os.environ.get("PGSQL_HOST")
|
60 |
+
self.port = os.environ.get("PGSQL_PORT")
|
61 |
+
self.user = os.environ.get("PGSQL_USER")
|
62 |
+
self.password = os.environ.get("PGSQL_PASSWORD")
|
63 |
+
self.database = os.environ.get("PGSQL_DATABASE")
|
64 |
+
self.conn = self.connect()
|
65 |
self.indexer = self.load_indexer()
|
66 |
+
|
67 |
+
def connect(self):
|
68 |
+
"""Établit une connexion à la base de données PostgreSQL"""
|
69 |
+
try:
|
70 |
+
self.conn = psycopg2.connect(
|
71 |
+
host=self.host,
|
72 |
+
port=self.port,
|
73 |
+
user=self.user,
|
74 |
+
password=self.password,
|
75 |
+
dbname=self.database
|
76 |
+
)
|
77 |
+
return self.conn
|
78 |
+
except Exception as e:
|
79 |
+
print(f"Erreur de connexion à la base de données: {e}")
|
80 |
+
return None
|
81 |
|
82 |
def load_indexer(self):
|
83 |
"""Load existing index if available"""
|
84 |
+
if not self.conn:
|
85 |
+
self.conn = self.connect()
|
86 |
+
if self.conn is None:
|
87 |
+
raise HTTPException(status_code=500, detail="Connexion à la base de donnée impossible")
|
|
|
|
|
|
|
|
|
|
|
88 |
|
89 |
+
cursor = self.conn.cursor()
|
90 |
+
try:
|
91 |
+
cursor.execute("SELECT doc_id, url FROM document_position")
|
92 |
+
rows = cursor.fetchall()
|
93 |
+
doc = {doc_id: url for doc_id, url in rows}
|
94 |
+
except:
|
95 |
+
raise HTTPException(status_code=500, detail="Erreur lors de la récupération")
|
96 |
+
finally:
|
97 |
+
cursor.close()
|
98 |
+
return doc
|
99 |
+
|
100 |
def get_workgroup(self, doc):
|
101 |
main_tsg = "tsg_ct" if doc[0] == "C" else "tsg_sa" if doc[0] == "S" else None
|
102 |
if main_tsg is None:
|
|
|
126 |
print(f"Error accessing {url}: {e}")
|
127 |
return []
|
128 |
|
129 |
+
def search_document(self, doc_id: str, release = None):
|
130 |
"""Search for a specific document by its ID"""
|
131 |
original_id = doc_id
|
132 |
|
|
|
134 |
if original_id in self.indexer:
|
135 |
return self.indexer[original_id]
|
136 |
|
137 |
+
for doc in self.indexer:
|
138 |
+
if original_id.startswith(doc):
|
139 |
+
return self.indexer[doc]
|
140 |
+
|
141 |
# Parse the document ID
|
142 |
main_tsg, workgroup, doc = self.get_workgroup(doc_id)
|
143 |
if not main_tsg:
|
|
|
167 |
if doc in file.lower() or original_id in file:
|
168 |
doc_url = f"{docs_url}/{file}"
|
169 |
self.indexer[original_id] = doc_url
|
|
|
170 |
return doc_url
|
171 |
|
172 |
# Check in ZIP subfolder if it exists
|
|
|
179 |
if doc in file.lower() or original_id in file:
|
180 |
doc_url = f"{zip_url}/{file}"
|
181 |
self.indexer[original_id] = doc_url
|
|
|
182 |
return doc_url
|
183 |
|
184 |
return f"Document {doc_id} not found"
|
|
|
262 |
results=results,
|
263 |
missing=missing,
|
264 |
search_time=time.time() - start_time
|
265 |
+
)
|
|
|
|
|
|
|
|
|
|
static/script.js
CHANGED
@@ -12,9 +12,6 @@ const resultsContainer = document.getElementById('results-container');
|
|
12 |
const resultsList = document.getElementById('results-list');
|
13 |
const resultsStats = document.getElementById('results-stats');
|
14 |
const errorMessage = document.getElementById('error-message');
|
15 |
-
const indexedDocs = document.getElementById('indexed-docs');
|
16 |
-
const indexedCount = document.getElementById('indexed-count');
|
17 |
-
const indexedList = document.getElementById('indexed-list');
|
18 |
|
19 |
// Search mode toggle
|
20 |
singleModeBtn.addEventListener('click', () => {
|
@@ -55,7 +52,6 @@ searchBtn.addEventListener('click', async () => {
|
|
55 |
|
56 |
if (response.ok) {
|
57 |
displaySingleResult(data);
|
58 |
-
loadIndexedDocuments(); // Refresh indexed docs
|
59 |
} else {
|
60 |
displaySingleNotFound(docId, data.detail);
|
61 |
}
|
@@ -100,7 +96,6 @@ batchSearchBtn.addEventListener('click', async () => {
|
|
100 |
|
101 |
if (response.ok) {
|
102 |
displayBatchResults(data);
|
103 |
-
loadIndexedDocuments(); // Refresh indexed docs
|
104 |
} else {
|
105 |
showError('Error processing batch request');
|
106 |
}
|
@@ -192,44 +187,6 @@ function displayBatchResults(data) {
|
|
192 |
resultsContainer.style.display = 'block';
|
193 |
}
|
194 |
|
195 |
-
// Load indexed documents
|
196 |
-
async function loadIndexedDocuments() {
|
197 |
-
try {
|
198 |
-
const response = await fetch(`/indexed`);
|
199 |
-
if (response.ok) {
|
200 |
-
const data = await response.json();
|
201 |
-
displayIndexedDocuments(data);
|
202 |
-
}
|
203 |
-
} catch (error) {
|
204 |
-
console.error('Error loading indexed documents:', error);
|
205 |
-
}
|
206 |
-
}
|
207 |
-
|
208 |
-
// Display indexed documents
|
209 |
-
function displayIndexedDocuments(docIds) {
|
210 |
-
indexedList.innerHTML = '';
|
211 |
-
indexedCount.textContent = `${docIds.length} documents`;
|
212 |
-
|
213 |
-
if (docIds.length === 0) {
|
214 |
-
const emptyMessage = document.createElement('p');
|
215 |
-
emptyMessage.textContent = 'No documents have been indexed yet.';
|
216 |
-
indexedList.appendChild(emptyMessage);
|
217 |
-
return;
|
218 |
-
}
|
219 |
-
|
220 |
-
docIds.forEach(docId => {
|
221 |
-
const docItem = document.createElement('div');
|
222 |
-
docItem.className = 'indexed-item';
|
223 |
-
docItem.textContent = docId;
|
224 |
-
docItem.addEventListener('click', () => {
|
225 |
-
docIdInput.value = docId;
|
226 |
-
singleModeBtn.click();
|
227 |
-
searchBtn.click();
|
228 |
-
});
|
229 |
-
indexedList.appendChild(docItem);
|
230 |
-
});
|
231 |
-
}
|
232 |
-
|
233 |
// Show loader
|
234 |
function showLoader() {
|
235 |
loader.style.display = 'block';
|
@@ -256,7 +213,4 @@ docIdInput.addEventListener('keypress', (e) => {
|
|
256 |
if (e.key === 'Enter') {
|
257 |
searchBtn.click();
|
258 |
}
|
259 |
-
});
|
260 |
-
|
261 |
-
// Load indexed documents on page load
|
262 |
-
document.addEventListener('DOMContentLoaded', loadIndexedDocuments);
|
|
|
12 |
const resultsList = document.getElementById('results-list');
|
13 |
const resultsStats = document.getElementById('results-stats');
|
14 |
const errorMessage = document.getElementById('error-message');
|
|
|
|
|
|
|
15 |
|
16 |
// Search mode toggle
|
17 |
singleModeBtn.addEventListener('click', () => {
|
|
|
52 |
|
53 |
if (response.ok) {
|
54 |
displaySingleResult(data);
|
|
|
55 |
} else {
|
56 |
displaySingleNotFound(docId, data.detail);
|
57 |
}
|
|
|
96 |
|
97 |
if (response.ok) {
|
98 |
displayBatchResults(data);
|
|
|
99 |
} else {
|
100 |
showError('Error processing batch request');
|
101 |
}
|
|
|
187 |
resultsContainer.style.display = 'block';
|
188 |
}
|
189 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
190 |
// Show loader
|
191 |
function showLoader() {
|
192 |
loader.style.display = 'block';
|
|
|
213 |
if (e.key === 'Enter') {
|
214 |
searchBtn.click();
|
215 |
}
|
216 |
+
});
|
|
|
|
|
|
templates/index.html
CHANGED
@@ -21,7 +21,7 @@
|
|
21 |
<div class="search-container">
|
22 |
<div class="search-header">
|
23 |
<h2>Find 3GPP Documents</h2>
|
24 |
-
<p>Enter a TSG document ID (e.g., S1-123456, C2-987654) to locate the document in the 3GPP FTP server.</p>
|
25 |
</div>
|
26 |
|
27 |
<div class="search-mode">
|
@@ -33,14 +33,14 @@
|
|
33 |
<div class="input-group single-input">
|
34 |
<label for="doc-id">Document ID</label>
|
35 |
<div class="input-field">
|
36 |
-
<input type="text" id="doc-id" placeholder="Enter document ID (e.g., S1-123456)">
|
37 |
<button id="search-btn" class="btn">Search</button>
|
38 |
</div>
|
39 |
</div>
|
40 |
|
41 |
<div class="input-group batch-input">
|
42 |
-
<label for="batch-ids">Document IDs (one per line)</label>
|
43 |
-
<textarea id="batch-ids" placeholder="Enter document IDs, one per line (e.g., S1-123456, C2-987654)"></textarea>
|
44 |
<div class="hint">Enter one document ID per line</div>
|
45 |
<button id="batch-search-btn" class="btn" style="margin-top: 10px;">Search All</button>
|
46 |
</div>
|
@@ -61,14 +61,6 @@
|
|
61 |
</div>
|
62 |
<div class="results-list" id="results-list"></div>
|
63 |
</div>
|
64 |
-
|
65 |
-
<div class="indexed-docs" id="indexed-docs">
|
66 |
-
<div class="indexed-header">
|
67 |
-
<h2>Indexed Documents</h2>
|
68 |
-
<div class="indexed-count" id="indexed-count">0 documents</div>
|
69 |
-
</div>
|
70 |
-
<div class="indexed-list" id="indexed-list"></div>
|
71 |
-
</div>
|
72 |
</div>
|
73 |
|
74 |
<footer>
|
|
|
21 |
<div class="search-container">
|
22 |
<div class="search-header">
|
23 |
<h2>Find 3GPP Documents</h2>
|
24 |
+
<p>Enter a TSG document ID / specification ID (e.g., S1-123456, C2-987654 or 31.102) to locate the document in the 3GPP FTP server.</p>
|
25 |
</div>
|
26 |
|
27 |
<div class="search-mode">
|
|
|
33 |
<div class="input-group single-input">
|
34 |
<label for="doc-id">Document ID</label>
|
35 |
<div class="input-field">
|
36 |
+
<input type="text" id="doc-id" placeholder="Enter document ID or specification ID (e.g., S1-123456, 12.345)">
|
37 |
<button id="search-btn" class="btn">Search</button>
|
38 |
</div>
|
39 |
</div>
|
40 |
|
41 |
<div class="input-group batch-input">
|
42 |
+
<label for="batch-ids">Document IDs or Specification IDs (one per line)</label>
|
43 |
+
<textarea id="batch-ids" placeholder="Enter document IDs or specification IDs, one per line (e.g., S1-123456, C2-987654, 31.102)"></textarea>
|
44 |
<div class="hint">Enter one document ID per line</div>
|
45 |
<button id="batch-search-btn" class="btn" style="margin-top: 10px;">Search All</button>
|
46 |
</div>
|
|
|
61 |
</div>
|
62 |
<div class="results-list" id="results-list"></div>
|
63 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
</div>
|
65 |
|
66 |
<footer>
|