SpreadSheets600 commited on
Commit
24c4edb
·
1 Parent(s): bf5c835

Add admin SQLite DB export download

Browse files
app/blueprints/admin.py CHANGED
@@ -1,14 +1,22 @@
1
  from flask import (
2
  Blueprint,
 
 
3
  flash,
4
  redirect,
5
  render_template,
6
  request,
 
7
  url_for,
8
  current_app,
9
  )
10
  from flask_login import current_user, login_required
11
  from functools import wraps
 
 
 
 
 
12
 
13
  from app.extensions import db
14
  from app.models import Note, NoteType, Subject, User
@@ -157,3 +165,48 @@ def toggle_admin(id):
157
  user.is_admin = not user.is_admin
158
  db.session.commit()
159
  return redirect(url_for("admin.users"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from flask import (
2
  Blueprint,
3
+ abort,
4
+ after_this_request,
5
  flash,
6
  redirect,
7
  render_template,
8
  request,
9
+ send_file,
10
  url_for,
11
  current_app,
12
  )
13
  from flask_login import current_user, login_required
14
  from functools import wraps
15
+ import os
16
+ import sqlite3
17
+ import tempfile
18
+ from datetime import datetime
19
+ from sqlalchemy.engine import make_url
20
 
21
  from app.extensions import db
22
  from app.models import Note, NoteType, Subject, User
 
165
  user.is_admin = not user.is_admin
166
  db.session.commit()
167
  return redirect(url_for("admin.users"))
168
+
169
+
170
+ @admin_bp.route("/export-db")
171
+ @admin_required
172
+ def export_db():
173
+ uri = current_app.config.get("SQLALCHEMY_DATABASE_URI", "")
174
+ try:
175
+ parsed = make_url(uri)
176
+ except Exception:
177
+ abort(400, "Invalid database URL.")
178
+
179
+ if parsed.drivername != "sqlite":
180
+ abort(400, "Export endpoint only supports SQLite source databases.")
181
+
182
+ if not parsed.database or parsed.database == ":memory:":
183
+ abort(400, "In-memory SQLite database cannot be exported.")
184
+
185
+ db_path = parsed.database
186
+ if not os.path.isabs(db_path):
187
+ db_path = os.path.abspath(db_path)
188
+
189
+ if not os.path.exists(db_path):
190
+ abort(404, "Database file not found.")
191
+
192
+ fd, backup_path = tempfile.mkstemp(prefix="porahobe_backup_", suffix=".db")
193
+ os.close(fd)
194
+
195
+ src = sqlite3.connect(db_path)
196
+ dst = sqlite3.connect(backup_path)
197
+ try:
198
+ src.backup(dst)
199
+ finally:
200
+ src.close()
201
+ dst.close()
202
+
203
+ @after_this_request
204
+ def _cleanup(response):
205
+ try:
206
+ os.remove(backup_path)
207
+ except OSError:
208
+ pass
209
+ return response
210
+
211
+ filename = f"porahobe_backup_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.db"
212
+ return send_file(backup_path, as_attachment=True, download_name=filename)
app/templates/admin/dashboard.html CHANGED
@@ -33,7 +33,13 @@
33
 
34
  <!-- Management Links -->
35
  <div class="glass p-8 rounded-2xl">
36
- <h2 class="font-display text-2xl font-bold mb-6">Management</h2>
 
 
 
 
 
 
37
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
38
  <a href="{{ url_for('admin.subjects') }}" class="glass p-6 rounded-xl hover:bg-white/5 transition group">
39
  <h3 class="font-display text-xl font-bold mb-2 group-hover:text-blue-400 transition">Subjects</h3>
 
33
 
34
  <!-- Management Links -->
35
  <div class="glass p-8 rounded-2xl">
36
+ <div class="flex items-center justify-between gap-4 mb-6">
37
+ <h2 class="font-display text-2xl font-bold">Management</h2>
38
+ <a href="{{ url_for('admin.export_db') }}"
39
+ class="bg-blue-500/20 border border-blue-500/50 rounded-lg px-4 py-2 font-mono text-xs hover:bg-blue-500/30 transition">
40
+ Download DB Backup
41
+ </a>
42
+ </div>
43
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
44
  <a href="{{ url_for('admin.subjects') }}" class="glass p-6 rounded-xl hover:bg-white/5 transition group">
45
  <h3 class="font-display text-xl font-bold mb-2 group-hover:text-blue-400 transition">Subjects</h3>