unified upload button + download backups

This commit is contained in:
Hayden 2021-01-20 18:24:17 -09:00
commit eff9d6f559
8 changed files with 118 additions and 30 deletions

View file

@ -4,8 +4,7 @@ import mealplan from "./api/mealplan";
import settings from "./api/settings"; import settings from "./api/settings";
import themes from "./api/themes"; import themes from "./api/themes";
import migration from "./api/migration"; import migration from "./api/migration";
import myUtils from "./api/upload";
// import api from "../api";
export default { export default {
recipes: recipe, recipes: recipe,
@ -14,4 +13,5 @@ export default {
settings: settings, settings: settings,
themes: themes, themes: themes,
migrations: migration, migrations: migration,
utils: myUtils,
}; };

View file

@ -10,6 +10,7 @@ const backupURLs = {
createBackup: `${backupBase}export/database/`, createBackup: `${backupBase}export/database/`,
importBackup: (fileName) => `${backupBase}${fileName}/import/`, importBackup: (fileName) => `${backupBase}${fileName}/import/`,
deleteBackup: (fileName) => `${backupBase}${fileName}/delete/`, deleteBackup: (fileName) => `${backupBase}${fileName}/delete/`,
downloadBackup: (fileName) => `${backupBase}${fileName}/download/`,
}; };
export default { export default {
@ -32,4 +33,8 @@ export default {
let response = apiReq.post(backupURLs.createBackup, data); let response = apiReq.post(backupURLs.createBackup, data);
return response; return response;
}, },
async download(fileName) {
let response = await apiReq.get(backupURLs.downloadBackup(fileName));
return response.data;
},
}; };

View file

@ -0,0 +1,13 @@
import { apiReq } from "./api-utils";
export default {
// import api from "../api";
async uploadFile(url, fileObject) {
let response = await apiReq.post(url, fileObject, {
headers: {
"Content-Type": "multipart/form-data",
},
});
return response.data;
},
};

View file

@ -65,15 +65,15 @@
<v-divider></v-divider> <v-divider></v-divider>
<v-card-actions> <v-card-actions>
<v-btn disabled color="success" text @click="raiseEvent('download')"> <v-btn color="success" text :href="`/api/backups/${name}/download/`">
{{$t('general.download')}} {{ $t("general.download") }}
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="error" text @click="raiseEvent('delete')"> <v-btn color="error" text @click="raiseEvent('delete')">
{{$t('general.delete')}} {{ $t("general.delete") }}
</v-btn> </v-btn>
<v-btn color="success" text @click="raiseEvent('import')"> <v-btn color="success" text @click="raiseEvent('import')">
{{$t('general.import')}} {{ $t("general.import") }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>

View file

@ -21,10 +21,10 @@
Available Backups Available Backups
<v-spacer></v-spacer> <v-spacer></v-spacer>
<span> <span>
<v-btn color="success" text class="ma-2 white--text"> <UploadBtn
Upload url="/api/backups/upload/"
<v-icon right dark> mdi-cloud-upload </v-icon> @uploaded="getAvailableBackups"
</v-btn> />
</span> </span>
</v-card-title> </v-card-title>
<AvailableBackupCard <AvailableBackupCard
@ -45,12 +45,14 @@
<script> <script>
import api from "../../../api"; import api from "../../../api";
import SuccessFailureAlert from "../../UI/SuccessFailureAlert"; import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
import UploadBtn from "../../UI/UploadBtn";
import AvailableBackupCard from "./AvailableBackupCard"; import AvailableBackupCard from "./AvailableBackupCard";
import NewBackupCard from "./NewBackupCard"; import NewBackupCard from "./NewBackupCard";
export default { export default {
components: { components: {
SuccessFailureAlert, SuccessFailureAlert,
UploadBtn,
AvailableBackupCard, AvailableBackupCard,
NewBackupCard, NewBackupCard,
}, },
@ -70,6 +72,7 @@ export default {
let response = await api.backups.requestAvailable(); let response = await api.backups.requestAvailable();
this.availableBackups = response.imports; this.availableBackups = response.imports;
this.availableTemplates = response.templates; this.availableTemplates = response.templates;
console.log(this.availableBackups);
}, },
deleteBackup() { deleteBackup() {
if (this.$refs.form.validate()) { if (this.$refs.form.validate()) {

View file

@ -1,24 +1,62 @@
<template> <template>
<v-form ref="file"> <v-form ref="file">
<v-file-input <input ref="uploader" class="d-none" type="file" @change="onFileChanged" />
:loading="loading" <v-btn
:label="$t('migration.upload-an-archive')" :loading="isSelecting"
v-model="file" @click="onButtonClick"
accept=".zip" color="success"
@change="upload" text
:prepend-icon="icon" class="ma-2 white--text"
class="file-icon"
> >
</v-file-input> <v-icon left dark> mdi-cloud-upload </v-icon>
<v-btn color="success" text class="ma-2 white--text">
Upload Upload
<v-icon right dark> mdi-cloud-upload </v-icon>
</v-btn> </v-btn>
</v-form> </v-form>
</template> </template>
<script> <script>
export default {}; import api from "../../api";
export default {
props: {
url: String,
},
data: () => ({
defaultButtonText: "Upload",
file: null,
isSelecting: false,
}),
methods: {
async upload() {
if (this.file != null) {
this.isSelecting = true;
let formData = new FormData();
formData.append("archive", this.file);
await api.utils.uploadFile(this.url, formData);
this.isSelecting = false;
this.$emit("uploaded");
}
},
onButtonClick() {
this.isSelecting = true;
window.addEventListener(
"focus",
() => {
this.isSelecting = false;
},
{ once: true }
);
this.$refs.uploader.click();
},
onFileChanged(e) {
this.file = e.target.files[0];
this.upload();
},
},
};
</script> </script>
<style> <style>

View file

@ -1,12 +1,14 @@
import operator import operator
import shutil
from app_config import BACKUP_DIR, TEMPLATE_DIR from app_config import BACKUP_DIR, TEMPLATE_DIR
from db.db_setup import generate_session from db.db_setup import generate_session
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
from models.backup_models import BackupJob, ImportJob, Imports, LocalBackup from models.backup_models import BackupJob, ImportJob, Imports, LocalBackup
from services.backups.exports import backup_all from services.backups.exports import backup_all
from services.backups.imports import ImportDatabase from services.backups.imports import ImportDatabase
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from starlette.responses import FileResponse
from utils.snackbar import SnackResponse from utils.snackbar import SnackResponse
router = APIRouter(tags=["Import / Export"]) router = APIRouter(tags=["Import / Export"])
@ -49,6 +51,33 @@ def export_database(data: BackupJob, db: Session = Depends(generate_session)):
) )
@router.post("/api/backups/upload/")
def upload_backup_zipfile(archive: UploadFile = File(...)):
""" Upload a .zip File to later be imported into Mealie """
dest = BACKUP_DIR.joinpath(archive.filename)
with dest.open("wb") as buffer:
shutil.copyfileobj(archive.file, buffer)
if dest.is_file:
return SnackResponse.success("Backup uploaded")
else:
return SnackResponse.error("Failure uploading file")
@router.get("/api/backups/{file_name}/download/")
def upload_nextcloud_zipfile(file_name: str):
""" Upload a .zip File to later be imported into Mealie """
file = BACKUP_DIR.joinpath(file_name)
if file.is_file:
return FileResponse(
file, media_type="application/octet-stream", filename=file_name
)
else:
return SnackResponse.error("No File Found")
@router.post("/api/backups/{file_name}/import/", status_code=200) @router.post("/api/backups/{file_name}/import/", status_code=200)
def import_database( def import_database(
file_name: str, import_data: ImportJob, db: Session = Depends(generate_session) file_name: str, import_data: ImportJob, db: Session = Depends(generate_session)