Skip to content

Commit f4e6b6f

Browse files
author
SM_SAYEED
committed
switching to drive backed database for materials
1 parent 308cd45 commit f4e6b6f

File tree

3 files changed

+291
-377
lines changed

3 files changed

+291
-377
lines changed

app.py

Lines changed: 161 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -98,35 +98,79 @@ def file_to_table_name(filename: str) -> str:
9898
#==================================================#
9999

100100
def ensure_uploads_log_schema():
101-
"""Create/upgrade uploads_log to the expected schema; ensure uniqueness."""
101+
"""Public catalog (uploads_log) + audit history (uploads_audit) with triggers."""
102102
with sqlite3.connect(DB_NAME) as conn:
103103
c = conn.cursor()
104-
# Create table if missing (includes UNIQUE on the key)
104+
105+
# Public catalog (what public pages read)
105106
c.execute("""
106107
CREATE TABLE IF NOT EXISTS uploads_log (
107-
id INTEGER PRIMARY KEY AUTOINCREMENT,
108-
property TEXT NOT NULL,
109-
tab TEXT NOT NULL,
110-
filename TEXT NOT NULL,
111-
uploaded_at TEXT,
108+
id INTEGER PRIMARY KEY AUTOINCREMENT,
109+
property TEXT NOT NULL,
110+
tab TEXT NOT NULL, -- 'dataset' | 'results'
111+
filename TEXT NOT NULL, -- human-visible label
112+
uploaded_at TEXT, -- first/last touch, maintained by app
113+
-- Drive-first metadata
114+
storage TEXT, -- 'drive' | 'local' (legacy)
115+
drive_id TEXT,
116+
preview_url TEXT,
117+
download_url TEXT,
118+
source TEXT,
119+
description TEXT,
112120
UNIQUE(property, tab, filename)
113121
)
114122
""")
115-
# Ensure uploaded_at column exists (for older DBs)
116-
cols = {row[1] for row in c.execute("PRAGMA table_info(uploads_log)").fetchall()}
117-
if "uploaded_at" not in cols:
118-
c.execute("ALTER TABLE uploads_log ADD COLUMN uploaded_at TEXT")
119-
# Try to migrate from legacy logged_at if it exists
120-
try:
121-
c.execute("UPDATE uploads_log SET uploaded_at = COALESCE(uploaded_at, logged_at) WHERE uploaded_at IS NULL")
122-
except sqlite3.OperationalError:
123-
pass # logged_at may not exist; ignore
124123

125-
# Ensure a unique index exists even if the table was created long ago
124+
# Ensure index exists even on old DBs
126125
c.execute("""
127126
CREATE UNIQUE INDEX IF NOT EXISTS idx_uploads_unique
128127
ON uploads_log(property, tab, filename)
129128
""")
129+
130+
# ---- Audit table
131+
c.execute("""
132+
CREATE TABLE IF NOT EXISTS uploads_audit (
133+
id INTEGER PRIMARY KEY AUTOINCREMENT,
134+
property TEXT NOT NULL,
135+
tab TEXT NOT NULL,
136+
filename TEXT NOT NULL,
137+
action TEXT NOT NULL, -- add | update | delete
138+
at TEXT NOT NULL
139+
)
140+
""")
141+
142+
# Helper to create triggers idempotently
143+
def ensure_trigger(name, ddl):
144+
c.execute("SELECT 1 FROM sqlite_master WHERE type='trigger' AND name=?", (name,))
145+
if not c.fetchone():
146+
c.execute(ddl)
147+
148+
ensure_trigger("trg_ul_insert_audit", """
149+
CREATE TRIGGER trg_ul_insert_audit
150+
AFTER INSERT ON uploads_log
151+
BEGIN
152+
INSERT INTO uploads_audit(property, tab, filename, action, at)
153+
VALUES (NEW.property, NEW.tab, NEW.filename, 'add',
154+
COALESCE(NEW.uploaded_at, CURRENT_TIMESTAMP));
155+
END;""")
156+
157+
ensure_trigger("trg_ul_update_audit", """
158+
CREATE TRIGGER trg_ul_update_audit
159+
AFTER UPDATE ON uploads_log
160+
BEGIN
161+
INSERT INTO uploads_audit(property, tab, filename, action, at)
162+
VALUES (NEW.property, NEW.tab, NEW.filename, 'update',
163+
COALESCE(NEW.uploaded_at, CURRENT_TIMESTAMP));
164+
END;""")
165+
166+
ensure_trigger("trg_ul_delete_audit", """
167+
CREATE TRIGGER trg_ul_delete_audit
168+
AFTER DELETE ON uploads_log
169+
BEGIN
170+
INSERT INTO uploads_audit(property, tab, filename, action, at)
171+
VALUES (OLD.property, OLD.tab, OLD.filename, 'delete', CURRENT_TIMESTAMP);
172+
END;""")
173+
130174
conn.commit()
131175

132176
#==================================================#
@@ -489,38 +533,38 @@ def logout():
489533

490534
#########################################################
491535

492-
# -- Admin-only home page (upload/import/query) --
493-
@app.route('/admin', methods=['GET', 'POST'])
536+
# --- Admin Dashboard (history-only) ---
537+
@app.route('/admin', methods=['GET'])
494538
def admin_home():
495539
if not session.get('admin'):
496540
return redirect(url_for('login'))
497541

498-
# Get all uploads (materials) from uploads_log
499-
uploads = []
542+
# Make sure catalog + audit schema exist (triggers are created here too)
543+
ensure_uploads_log_schema()
544+
545+
# Build the history table: when first added, and whether still present publicly
500546
with sqlite3.connect(DB_NAME) as conn:
501547
c = conn.cursor()
502548
c.execute("""
503-
SELECT property, tab, filename, uploaded_at
504-
FROM uploads_log
505-
ORDER BY uploaded_at DESC
549+
WITH hist AS (
550+
SELECT property, tab, filename,
551+
MIN(CASE WHEN action='add' THEN at END) AS first_added,
552+
MAX(at) AS last_event
553+
FROM uploads_audit
554+
GROUP BY property, tab, filename
555+
)
556+
SELECT
557+
h.filename AS file_name,
558+
COALESCE(h.first_added, h.last_event) AS uploaded_at,
559+
CASE WHEN u.rowid IS NULL THEN 'Absent' ELSE 'Present' END AS public_view_status
560+
FROM hist h
561+
LEFT JOIN uploads_log u
562+
ON u.property=h.property AND u.tab=h.tab AND u.filename=h.filename
563+
ORDER BY uploaded_at DESC, h.filename;
506564
""")
507-
uploads = c.fetchall()
565+
audit_rows = c.fetchall()
508566

509-
# Get all music clips from the music_clips table
510-
music_clips = []
511-
try:
512-
with sqlite3.connect(DB_NAME) as conn:
513-
c = conn.cursor()
514-
c.execute("SELECT filename, title, description FROM music_clips ORDER BY rowid DESC")
515-
music_clips = c.fetchall()
516-
except Exception:
517-
music_clips = []
518-
519-
return render_template(
520-
'admin_home.html',
521-
uploads=uploads,
522-
music_clips=music_clips
523-
)
567+
return render_template('admin_home.html', audit_rows=audit_rows)
524568

525569
#########################################################
526570

@@ -641,10 +685,10 @@ def diag_routes():
641685

642686
#########################################################
643687

644-
# -- View and import (admin only) --
688+
# -- View and import (admin + Public) --
645689
@app.route('/materials/<property_name>/<tab>', methods=['GET', 'POST'])
646690
def property_detail(property_name, tab):
647-
# ---- titles / guards ----
691+
# Titles / guards
648692
pretty_titles = {
649693
'bandgap': 'Band Gap',
650694
'formation_energy': 'Formation Energy',
@@ -654,99 +698,104 @@ def property_detail(property_name, tab):
654698
if property_name not in pretty_titles or tab not in ('dataset', 'results'):
655699
return "Not found.", 404
656700

701+
ensure_uploads_log_schema()
702+
657703
upload_message = ""
658704
edit_message = ""
659705
is_admin = bool(session.get('admin'))
660706

661-
# ---- admin POST handlers ----
707+
# -------- Admin-only: Drive-based upload -----------
662708
if is_admin and request.method == 'POST':
663-
# Inline row edit
664-
if 'edit_row' in request.form:
709+
if 'add_drive' in request.form:
710+
# Form fields (Drive)
711+
drive_link = (request.form.get('drive_link') or '').strip()
712+
label = (request.form.get('label') or '').strip() # human filename label (required)
713+
source = (request.form.get('row_source') or '').strip() if tab == 'dataset' else None
714+
desc = (request.form.get('row_description') or '').strip()
715+
716+
# Parse Drive ID from link or raw id
717+
def _extract_drive_id(link: str):
718+
m = re.search(r'/d/([a-zA-Z0-9_-]+)', link) or re.search(r'[?&]id=([a-zA-Z0-9_-]+)', link)
719+
if m: return m.group(1)
720+
if re.match(r'^[a-zA-Z0-9_-]{10,}$', link): return link
721+
return None
722+
723+
drive_id = _extract_drive_id(drive_link)
724+
if not label or not drive_id:
725+
upload_message = "❌ Provide a valid Drive link/ID and a label."
726+
else:
727+
preview_url = f"https://drive.google.com/file/d/{drive_id}/preview"
728+
download_url = f"https://drive.google.com/uc?export=download&id={drive_id}"
729+
730+
# Upsert into public catalog (dedup by key)
731+
with sqlite3.connect(DB_NAME) as conn:
732+
c = conn.cursor()
733+
c.execute("""
734+
INSERT INTO uploads_log
735+
(property, tab, filename, uploaded_at, storage, drive_id, preview_url, download_url, source, description)
736+
VALUES (?, ?, ?, CURRENT_TIMESTAMP, 'drive', ?, ?, ?, ?, ?)
737+
ON CONFLICT(property, tab, filename)
738+
DO UPDATE SET
739+
uploaded_at = CURRENT_TIMESTAMP,
740+
storage='drive', drive_id=excluded.drive_id,
741+
preview_url=excluded.preview_url, download_url=excluded.download_url,
742+
source=excluded.source, description=excluded.description
743+
""", (property_name, tab, label, drive_id, preview_url, download_url, source, desc))
744+
conn.commit()
745+
upload_message = f"✅ Added '{label}' from Drive."
746+
747+
elif 'edit_row' in request.form:
748+
# Inline update (source/description)
665749
row_filename = request.form.get('row_filename') or ''
666750
safe_row_filename = secure_filename(os.path.basename(row_filename))
667751
new_desc = (request.form.get('row_description') or '').strip()
668-
669752
with sqlite3.connect(DB_NAME) as conn:
670753
c = conn.cursor()
671754
if tab == 'dataset':
672755
new_source = (request.form.get('row_source') or '').strip()
673-
c.execute(
674-
"""
756+
c.execute("""
675757
UPDATE uploads_log
676-
SET source = ?, description = ?
677-
WHERE property = ? AND tab = ? AND filename = ?
678-
""",
679-
(new_source, new_desc, property_name, tab, safe_row_filename),
680-
)
758+
SET source=?, description=?, uploaded_at=CURRENT_TIMESTAMP
759+
WHERE property=? AND tab=? AND filename=?""",
760+
(new_source, new_desc, property_name, tab, safe_row_filename))
681761
else:
682-
c.execute(
683-
"""
762+
c.execute("""
684763
UPDATE uploads_log
685-
SET description = ?
686-
WHERE property = ? AND tab = ? AND filename = ?
687-
""",
688-
(new_desc, property_name, tab, safe_row_filename),
689-
)
764+
SET description=?, uploaded_at=CURRENT_TIMESTAMP
765+
WHERE property=? AND tab=? AND filename=?""",
766+
(new_desc, property_name, tab, safe_row_filename))
690767
conn.commit()
691-
692768
edit_message = f"Updated info for {safe_row_filename}."
693769

694-
# New file upload
695-
elif 'file' in request.files:
696-
f = request.files['file']
697-
if not f or f.filename == '':
698-
upload_message = "No file selected."
699-
else:
700-
# Validate extension by tab
701-
if tab == 'dataset':
702-
is_allowed = allowed_dataset_file(f.filename)
703-
allowed_types = "CSV or NPY"
704-
else: # results
705-
is_allowed = allowed_results_file(f.filename)
706-
allowed_types = "JPG, PNG, GIF, PDF, or DOCX"
707-
708-
if not is_allowed:
709-
upload_message = f"File type not allowed. Only {allowed_types} supported."
710-
else:
711-
# Save to disk (under /uploads/<property>/<tab>/)
712-
property_folder = os.path.join(app.config['UPLOAD_FOLDER'], property_name, tab)
713-
os.makedirs(property_folder, exist_ok=True)
714-
safe_filename = secure_filename(os.path.basename(f.filename))
715-
filepath = os.path.join(property_folder, safe_filename)
716-
f.save(filepath)
717-
718-
# Log to DB (idempotent; no Python datetime)
719-
with sqlite3.connect(DB_NAME) as conn:
720-
c = conn.cursor()
721-
c.execute(
722-
"""
723-
INSERT INTO uploads_log (property, tab, filename, uploaded_at)
724-
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
725-
ON CONFLICT(property, tab, filename)
726-
DO UPDATE SET uploaded_at = CURRENT_TIMESTAMP
727-
""",
728-
(property_name, tab, safe_filename),
729-
)
730-
conn.commit()
731-
732-
upload_message = f"File {safe_filename} uploaded for {pretty_titles[property_name]} {tab.title()}!"
733-
734-
# ---- fetch current uploads (unique per key thanks to UNIQUE index) ----
770+
# -------- Fetch current public rows -----------
735771
with sqlite3.connect(DB_NAME) as conn:
736772
c = conn.cursor()
737-
c.execute(
738-
"""
773+
c.execute("""
739774
SELECT filename,
740-
COALESCE(source, '') AS source,
741-
COALESCE(description, '') AS description,
742-
uploaded_at
775+
COALESCE(source,'') AS source,
776+
COALESCE(description,'') AS description,
777+
uploaded_at,
778+
COALESCE(storage,'local') AS storage,
779+
COALESCE(preview_url,'') AS preview_url,
780+
COALESCE(download_url,'') AS download_url
743781
FROM uploads_log
744-
WHERE property = ? AND tab = ?
782+
WHERE property=? AND tab=?
745783
ORDER BY uploaded_at DESC, filename
746-
""",
747-
(property_name, tab),
748-
)
749-
uploads = c.fetchall()
784+
""", (property_name, tab))
785+
rows = c.fetchall()
786+
787+
# Normalize rows for the template
788+
uploads = []
789+
for fname, source, description, uploaded_at, storage, purl, durl in rows:
790+
uploads.append({
791+
"filename": fname,
792+
"source": source,
793+
"description": description,
794+
"uploaded_at": uploaded_at,
795+
"storage": storage,
796+
"preview_url": purl,
797+
"download_url": durl,
798+
})
750799

751800
return render_template(
752801
'property_detail.html',

0 commit comments

Comments
 (0)