From e1b8f9c1c392eb3c5416c3821c8ea90f02978a76 Mon Sep 17 00:00:00 2001 From: Dustin Davis Date: Wed, 24 Nov 2021 14:04:26 -0700 Subject: [PATCH] Export all notes --- .gitignore | 3 +++ app/routes.py | 23 ++++++++++++++++++++++- client/src/components/Header.vue | 6 ++++++ client/src/services/notes.ts | 7 +++++++ client/src/services/requests.ts | 16 ++++++++++++++++ 5 files changed, 54 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3494350..4f6a58b 100644 --- a/.gitignore +++ b/.gitignore @@ -244,3 +244,6 @@ test.py # Docker volume /dailynotes-volume + +# Generated exports +export.zip diff --git a/app/routes.py b/app/routes.py index 04c8d28..5fbf186 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,6 +1,7 @@ +import zipfile from app import app, db, argon2 from app.models import User, Note, Meta, aes_encrypt, aes_encrypt_old -from flask import render_template, request, jsonify, abort +from flask import render_template, request, jsonify, abort, send_file from flask_jwt_extended import jwt_required, create_access_token, get_jwt_identity @@ -396,6 +397,26 @@ def search(): return jsonify(notes=sorted_nodes), 200 +@app.route('/api/export') +@jwt_required() +def export(): + username = get_jwt_identity() + user = User.query.filter_by(username=username.lower()).first() + + if not user: + abort(400) + + zf = zipfile.ZipFile('export.zip', mode='w') + notes = user.notes + for note in notes: + ret_note = note.serialize + zf.writestr(ret_note['title'] + '.md', ret_note['data'], zipfile.ZIP_DEFLATED) + print(ret_note) + zf.close() + + return send_file('../export.zip', as_attachment=True) + + @app.route('/', defaults={'path': ''}) @app.route('/') def catch_all(path): diff --git a/client/src/components/Header.vue b/client/src/components/Header.vue index e0d9bb1..f4a4de0 100644 --- a/client/src/components/Header.vue +++ b/client/src/components/Header.vue @@ -86,6 +86,7 @@ {{ sidebar.autoSave ? 'Disable Auto-Save' : 'Enable Auto-Save' }} + Export Notes Logout @@ -104,6 +105,7 @@ import format from 'date-fns/format'; import SidebarInst from '../services/sidebar'; import {clearToken} from '../services/user'; +import {NoteService} from '../services/notes'; import {IHeaderOptions} from '../interfaces'; @@ -189,6 +191,10 @@ export default class Header extends Vue { this.isSaving = false; } + public async exportNotes() { + NoteService.exportNotes(); + } + public logout() { clearToken(); this.$router.push({name: 'Login'}); diff --git a/client/src/services/notes.ts b/client/src/services/notes.ts index 06e8ea3..4a41cb7 100644 --- a/client/src/services/notes.ts +++ b/client/src/services/notes.ts @@ -92,5 +92,12 @@ export const NoteService = { */ deleteNote: async (uuid: string): Promise => { await Requests.delete(`/delete_note/${uuid}`); + }, + + /** + * Exports all notes to a zip file and downloads + */ + exportNotes: async (): Promise => { + Requests.download("/export", "export.zip"); } }; diff --git a/client/src/services/requests.ts b/client/src/services/requests.ts index 54091b2..20a1d5f 100644 --- a/client/src/services/requests.ts +++ b/client/src/services/requests.ts @@ -70,5 +70,21 @@ export const Requests = { delete: (url: string): AxiosPromise => { return axios.delete(url); + }, + + download: (url: string, filename: string): void => { + axios({ + url: url, // File URL Goes Here + method: "GET", + responseType: "blob" + }).then(res => { + var FILE = window.URL.createObjectURL(new Blob([res.data])); + var docUrl = document.createElement("a"); + docUrl.href = FILE; + docUrl.setAttribute("download", filename); + document.body.appendChild(docUrl); + docUrl.click(); + document.body.removeChild(docUrl); + }); } };