mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 14:33:33 -07:00
import groups and users
This commit is contained in:
parent
8e22c02630
commit
f7732d4b83
12 changed files with 326 additions and 171 deletions
|
@ -25,24 +25,7 @@
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-checkbox
|
<ImportOptions @update-options="updateOptions" class="mt-5" />
|
||||||
class="mb-n4 mt-1"
|
|
||||||
dense
|
|
||||||
:label="$t('settings.backup.import-recipes')"
|
|
||||||
v-model="importRecipes"
|
|
||||||
></v-checkbox>
|
|
||||||
<v-checkbox
|
|
||||||
class="my-n4"
|
|
||||||
dense
|
|
||||||
:label="$t('settings.backup.import-themes')"
|
|
||||||
v-model="importThemes"
|
|
||||||
></v-checkbox>
|
|
||||||
<v-checkbox
|
|
||||||
class="my-n4"
|
|
||||||
dense
|
|
||||||
:label="$t('settings.backup.import-settings')"
|
|
||||||
v-model="importSettings"
|
|
||||||
></v-checkbox>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
<!-- <v-col>
|
<!-- <v-col>
|
||||||
<v-tooltip top>
|
<v-tooltip top>
|
||||||
|
@ -104,7 +87,9 @@
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ImportOptions from "@/components/Admin/Backup/ImportOptions";
|
||||||
export default {
|
export default {
|
||||||
|
components: { ImportOptions },
|
||||||
props: {
|
props: {
|
||||||
name: {
|
name: {
|
||||||
default: "Backup Name",
|
default: "Backup Name",
|
||||||
|
@ -115,15 +100,22 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
options: {
|
||||||
|
recipes: true,
|
||||||
|
settings: true,
|
||||||
|
themes: true,
|
||||||
|
users: true,
|
||||||
|
groups: true,
|
||||||
|
},
|
||||||
dialog: false,
|
dialog: false,
|
||||||
importRecipes: true,
|
|
||||||
forceImport: false,
|
forceImport: false,
|
||||||
rebaseImport: false,
|
rebaseImport: false,
|
||||||
importThemes: false,
|
|
||||||
importSettings: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
updateOptions(options) {
|
||||||
|
this.options = options;
|
||||||
|
},
|
||||||
open() {
|
open() {
|
||||||
this.dialog = true;
|
this.dialog = true;
|
||||||
},
|
},
|
||||||
|
@ -133,11 +125,13 @@ export default {
|
||||||
raiseEvent(event) {
|
raiseEvent(event) {
|
||||||
let eventData = {
|
let eventData = {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
recipes: this.importRecipes,
|
|
||||||
force: this.forceImport,
|
force: this.forceImport,
|
||||||
rebase: this.rebaseImport,
|
rebase: this.rebaseImport,
|
||||||
themes: this.importThemes,
|
recipes: this.options.recipes,
|
||||||
settings: this.importSettings,
|
settings: this.options.settings,
|
||||||
|
themes: this.options.themes,
|
||||||
|
users: this.options.users,
|
||||||
|
groups: this.options.groups,
|
||||||
};
|
};
|
||||||
this.close();
|
this.close();
|
||||||
this.$emit(event, eventData);
|
this.$emit(event, eventData);
|
||||||
|
|
62
frontend/src/components/Admin/Backup/ImportOptions.vue
Normal file
62
frontend/src/components/Admin/Backup/ImportOptions.vue
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-checkbox
|
||||||
|
v-for="option in options"
|
||||||
|
:key="option.text"
|
||||||
|
class="mb-n4 mt-n3"
|
||||||
|
dense
|
||||||
|
:label="option.text"
|
||||||
|
v-model="option.value"
|
||||||
|
@change="emitValue()"
|
||||||
|
></v-checkbox>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const UPDATE_EVENT = "update-options";
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
options: {
|
||||||
|
recipes: {
|
||||||
|
value: true,
|
||||||
|
text: this.$t("general.recipes"),
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
value: true,
|
||||||
|
text: this.$t("general.settings"),
|
||||||
|
},
|
||||||
|
themes: {
|
||||||
|
value: true,
|
||||||
|
text: this.$t("general.themes"),
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
value: true,
|
||||||
|
text: this.$t("general.users"),
|
||||||
|
},
|
||||||
|
groups: {
|
||||||
|
value: true,
|
||||||
|
text: this.$t("general.groups"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.emitValue();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
emitValue() {
|
||||||
|
this.$emit(UPDATE_EVENT, {
|
||||||
|
recipes: this.options.recipes.value,
|
||||||
|
settings: this.options.settings.value,
|
||||||
|
themes: this.options.themes.value,
|
||||||
|
users: this.options.users.value,
|
||||||
|
groups: this.options.groups.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
|
@ -58,28 +58,40 @@
|
||||||
<v-tab>Recipes</v-tab>
|
<v-tab>Recipes</v-tab>
|
||||||
<v-tab>Themes</v-tab>
|
<v-tab>Themes</v-tab>
|
||||||
<v-tab>Settings</v-tab>
|
<v-tab>Settings</v-tab>
|
||||||
|
<v-tab>Users</v-tab>
|
||||||
|
<v-tab>Groups</v-tab>
|
||||||
</v-tabs>
|
</v-tabs>
|
||||||
<v-tabs-items v-model="tab">
|
<v-tabs-items v-model="tab">
|
||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
<v-card flat>
|
<v-card flat>
|
||||||
<DataTable :data-headers="recipeHeaders" :data-set="recipeData" />
|
<DataTable :data-headers="importHeaders" :data-set="recipeData" />
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
<v-card>
|
<v-card>
|
||||||
<DataTable
|
<DataTable
|
||||||
:data-headers="recipeHeaders"
|
:data-headers="importHeaders"
|
||||||
:data-set="themeData"
|
:data-set="themeData"
|
||||||
/> </v-card
|
/> </v-card
|
||||||
></v-tab-item>
|
></v-tab-item>
|
||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
<v-card
|
<v-card
|
||||||
><DataTable
|
><DataTable
|
||||||
:data-headers="recipeHeaders"
|
:data-headers="importHeaders"
|
||||||
:data-set="settingsData"
|
:data-set="settingsData"
|
||||||
/>
|
/>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
|
<v-tab-item>
|
||||||
|
<v-card
|
||||||
|
><DataTable :data-headers="importHeaders" :data-set="userData" />
|
||||||
|
</v-card>
|
||||||
|
</v-tab-item>
|
||||||
|
<v-tab-item>
|
||||||
|
<v-card
|
||||||
|
><DataTable :data-headers="importHeaders" :data-set="groupData" />
|
||||||
|
</v-card>
|
||||||
|
</v-tab-item>
|
||||||
</v-tabs-items>
|
</v-tabs-items>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
@ -98,7 +110,9 @@ export default {
|
||||||
recipeData: [],
|
recipeData: [],
|
||||||
themeData: [],
|
themeData: [],
|
||||||
settingsData: [],
|
settingsData: [],
|
||||||
recipeHeaders: [
|
userData: [],
|
||||||
|
groupData: [],
|
||||||
|
importHeaders: [
|
||||||
{
|
{
|
||||||
text: "Status",
|
text: "Status",
|
||||||
value: "status",
|
value: "status",
|
||||||
|
@ -117,39 +131,40 @@ export default {
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
recipeNumbers() {
|
recipeNumbers() {
|
||||||
let numbers = { success: 0, failure: 0 };
|
return this.calculateNumbers(this.recipeData);
|
||||||
this.recipeData.forEach(element => {
|
|
||||||
if (element.status) {
|
|
||||||
numbers.success++;
|
|
||||||
} else numbers.failure++;
|
|
||||||
});
|
|
||||||
return numbers;
|
|
||||||
},
|
},
|
||||||
settingsNumbers() {
|
settingsNumbers() {
|
||||||
let numbers = { success: 0, failure: 0 };
|
return this.calculateNumbers(this.settingsData);
|
||||||
this.settingsData.forEach(element => {
|
|
||||||
if (element.status) {
|
|
||||||
numbers.success++;
|
|
||||||
} else numbers.failure++;
|
|
||||||
});
|
|
||||||
return numbers;
|
|
||||||
},
|
},
|
||||||
themeNumbers() {
|
themeNumbers() {
|
||||||
let numbers = { success: 0, failure: 0 };
|
return this.calculateNumbers(this.themeData);
|
||||||
this.themeData.forEach(element => {
|
},
|
||||||
if (element.status) {
|
userNumbers() {
|
||||||
numbers.success++;
|
return this.calculateNumbers(this.userData);
|
||||||
} else numbers.failure++;
|
},
|
||||||
});
|
groupNumbers() {
|
||||||
return numbers;
|
return this.calculateNumbers(this.groupData);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
calculateNumbers(list_array) {
|
||||||
|
if (!list_array) return;
|
||||||
|
let numbers = { success: 0, failure: 0 };
|
||||||
|
list_array.forEach(element => {
|
||||||
|
if (element.status) {
|
||||||
|
numbers.success++;
|
||||||
|
} else numbers.failure++;
|
||||||
|
});
|
||||||
|
return numbers;
|
||||||
|
},
|
||||||
open(importData) {
|
open(importData) {
|
||||||
|
console.log(importData);
|
||||||
this.recipeData = importData.recipeImports;
|
this.recipeData = importData.recipeImports;
|
||||||
this.themeData = importData.themeReport;
|
this.themeData = importData.themeImports;
|
||||||
this.settingsData = importData.settingsReport;
|
this.settingsData = importData.settingsImports;
|
||||||
|
this.userData = importData.userImports;
|
||||||
|
this.groupData = importData.groupImports;
|
||||||
this.dialog = true;
|
this.dialog = true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,57 +15,48 @@
|
||||||
{{ $t("general.create") }}
|
{{ $t("general.create") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
|
<v-expand-transition>
|
||||||
<v-card-text v-if="!fullBackup" class="mt-n6">
|
<div v-if="!fullBackup">
|
||||||
<v-row>
|
<v-card-text class="mt-n4">
|
||||||
<v-col sm="4">
|
<v-row>
|
||||||
<p>{{ $t("general.options") }}:</p>
|
<v-col sm="4">
|
||||||
<v-checkbox
|
<p>{{ $t("general.options") }}:</p>
|
||||||
v-for="option in options"
|
<ImportOptions @update-options="updateOptions" class="mt-5" />
|
||||||
:key="option.text"
|
</v-col>
|
||||||
class="mb-n4 mt-n3"
|
<v-col>
|
||||||
dense
|
<p>{{ $t("general.templates") }}:</p>
|
||||||
:label="option.text"
|
<v-checkbox
|
||||||
v-model="option.value"
|
v-for="template in availableTemplates"
|
||||||
></v-checkbox>
|
:key="template"
|
||||||
</v-col>
|
class="mb-n4 mt-n3"
|
||||||
<v-col>
|
dense
|
||||||
<p>{{ $t("general.templates") }}:</p>
|
:label="template"
|
||||||
<v-checkbox
|
@click="appendTemplate(template)"
|
||||||
v-for="template in availableTemplates"
|
></v-checkbox>
|
||||||
:key="template"
|
</v-col>
|
||||||
class="mb-n4 mt-n3"
|
</v-row>
|
||||||
dense
|
</v-card-text>
|
||||||
:label="template"
|
</div>
|
||||||
@click="appendTemplate(template)"
|
</v-expand-transition>
|
||||||
></v-checkbox>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ImportOptions from "@/components/Admin/Backup/ImportOptions";
|
||||||
import api from "@/api";
|
import api from "@/api";
|
||||||
export default {
|
export default {
|
||||||
|
components: { ImportOptions },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tag: null,
|
tag: null,
|
||||||
fullBackup: true,
|
fullBackup: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
options: {
|
options: {
|
||||||
recipes: {
|
recipes: true,
|
||||||
value: true,
|
settings: true,
|
||||||
text: this.$t("general.recipes"),
|
themes: true,
|
||||||
},
|
users: true,
|
||||||
settings: {
|
groups: true,
|
||||||
value: true,
|
|
||||||
text: this.$t("general.settings"),
|
|
||||||
},
|
|
||||||
themes: {
|
|
||||||
value: true,
|
|
||||||
text: this.$t("general.themes"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
availableTemplates: [],
|
availableTemplates: [],
|
||||||
selectedTemplates: [],
|
selectedTemplates: [],
|
||||||
|
@ -82,6 +73,9 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
updateOptions(options) {
|
||||||
|
this.options = options;
|
||||||
|
},
|
||||||
async getAvailableBackups() {
|
async getAvailableBackups() {
|
||||||
let response = await api.backups.requestAvailable();
|
let response = await api.backups.requestAvailable();
|
||||||
response.templates.forEach(element => {
|
response.templates.forEach(element => {
|
||||||
|
@ -94,9 +88,11 @@ export default {
|
||||||
let data = {
|
let data = {
|
||||||
tag: this.tag,
|
tag: this.tag,
|
||||||
options: {
|
options: {
|
||||||
recipes: this.options.recipes.value,
|
recipes: this.options.recipes,
|
||||||
settings: this.options.settings.value,
|
settings: this.options.settings,
|
||||||
themes: this.options.themes.value,
|
themes: this.options.themes,
|
||||||
|
users: this.options.users,
|
||||||
|
groups: this.options.groups,
|
||||||
},
|
},
|
||||||
templates: this.selectedTemplates,
|
templates: this.selectedTemplates,
|
||||||
};
|
};
|
||||||
|
|
|
@ -46,7 +46,9 @@
|
||||||
"token": "Token",
|
"token": "Token",
|
||||||
"field-required": "Field Required",
|
"field-required": "Field Required",
|
||||||
"apply": "Apply",
|
"apply": "Apply",
|
||||||
"current-parenthesis": "(Current)"
|
"current-parenthesis": "(Current)",
|
||||||
|
"users": "Users",
|
||||||
|
"groups": "Groups"
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
"home-page": "Home Page",
|
"home-page": "Home Page",
|
||||||
|
|
|
@ -17,7 +17,7 @@ function inDarkMode(payload) {
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
activeTheme: {},
|
activeTheme: {},
|
||||||
darkMode: "system",
|
darkMode: "light",
|
||||||
isDark: false,
|
isDark: false,
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
token: "",
|
token: "",
|
||||||
|
|
|
@ -18,7 +18,6 @@ app = FastAPI(
|
||||||
redoc_url=redoc_url,
|
redoc_url=redoc_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def start_scheduler():
|
def start_scheduler():
|
||||||
import mealie.services.scheduler.scheduled_jobs
|
import mealie.services.scheduler.scheduled_jobs
|
||||||
|
|
||||||
|
@ -56,6 +55,7 @@ def main():
|
||||||
host="0.0.0.0",
|
host="0.0.0.0",
|
||||||
port=PORT,
|
port=PORT,
|
||||||
reload=True,
|
reload=True,
|
||||||
|
reload_dirs=["mealie"],
|
||||||
debug=True,
|
debug=True,
|
||||||
log_level="info",
|
log_level="info",
|
||||||
workers=1,
|
workers=1,
|
||||||
|
|
|
@ -28,6 +28,7 @@ class User(SqlAlchemyBase, BaseMixins):
|
||||||
password,
|
password,
|
||||||
group: str = DEFAULT_GROUP,
|
group: str = DEFAULT_GROUP,
|
||||||
admin=False,
|
admin=False,
|
||||||
|
id=None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
group = group if group else DEFAULT_GROUP
|
group = group if group else DEFAULT_GROUP
|
||||||
|
@ -37,11 +38,14 @@ class User(SqlAlchemyBase, BaseMixins):
|
||||||
self.admin = admin
|
self.admin = admin
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
||||||
def update(self, full_name, email, group, admin, session=None):
|
def update(self, full_name, email, group, admin, session=None, id=None, password=None):
|
||||||
self.full_name = full_name
|
self.full_name = full_name
|
||||||
self.email = email
|
self.email = email
|
||||||
self.group = Group.create_if_not_exist(session, group)
|
self.group = Group.create_if_not_exist(session, group)
|
||||||
self.admin = admin
|
self.admin = admin
|
||||||
|
|
||||||
|
if password:
|
||||||
|
self.password = password
|
||||||
|
|
||||||
def update_password(self, password):
|
def update_password(self, password):
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import operator
|
import operator
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||||
from mealie.core.config import BACKUP_DIR, TEMPLATE_DIR
|
from mealie.core.config import BACKUP_DIR, TEMPLATE_DIR
|
||||||
from mealie.db.db_setup import generate_session
|
from mealie.db.db_setup import generate_session
|
||||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
|
||||||
from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup
|
from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup
|
||||||
from mealie.schema.snackbar import SnackResponse
|
from mealie.schema.snackbar import SnackResponse
|
||||||
|
from mealie.services.backups import imports
|
||||||
from mealie.services.backups.exports import backup_all
|
from mealie.services.backups.exports import backup_all
|
||||||
from mealie.services.backups.imports import ImportDatabase
|
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
from starlette.responses import FileResponse
|
from starlette.responses import FileResponse
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@ def export_database(data: BackupJob, session: Session = Depends(generate_session
|
||||||
export_recipes=data.options.recipes,
|
export_recipes=data.options.recipes,
|
||||||
export_settings=data.options.settings,
|
export_settings=data.options.settings,
|
||||||
export_themes=data.options.themes,
|
export_themes=data.options.themes,
|
||||||
|
export_users=data.options.users,
|
||||||
|
export_groups=data.options.groups,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
return SnackResponse.success("Backup Created at " + export_path)
|
return SnackResponse.success("Backup Created at " + export_path)
|
||||||
|
@ -80,17 +82,18 @@ async def upload_nextcloud_zipfile(file_name: str):
|
||||||
def import_database(file_name: str, import_data: ImportJob, session: Session = Depends(generate_session)):
|
def import_database(file_name: str, import_data: ImportJob, session: Session = Depends(generate_session)):
|
||||||
""" Import a database backup file generated from Mealie. """
|
""" Import a database backup file generated from Mealie. """
|
||||||
|
|
||||||
import_db = ImportDatabase(
|
imported = imports.import_database(
|
||||||
session=session,
|
session=session,
|
||||||
zip_archive=import_data.name,
|
archive=import_data.name,
|
||||||
import_recipes=import_data.recipes,
|
import_recipes=import_data.recipes,
|
||||||
force_import=import_data.force,
|
|
||||||
rebase=import_data.rebase,
|
|
||||||
import_settings=import_data.settings,
|
import_settings=import_data.settings,
|
||||||
import_themes=import_data.themes,
|
import_themes=import_data.themes,
|
||||||
|
import_users=import_data.users,
|
||||||
|
import_groups=import_data.groups,
|
||||||
|
force_import=import_data.force,
|
||||||
|
rebase=import_data.rebase,
|
||||||
)
|
)
|
||||||
|
|
||||||
imported = import_db.run()
|
|
||||||
return imported
|
return imported
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,24 @@ class BackupOptions(BaseModel):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ImportJob(BackupOptions):
|
||||||
|
name: str
|
||||||
|
force: bool = False
|
||||||
|
rebase: bool = False
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"name": "my_local_backup.zip",
|
||||||
|
"recipes": True,
|
||||||
|
"force": False,
|
||||||
|
"rebase": False,
|
||||||
|
"themes": False,
|
||||||
|
"settings": False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class BackupJob(BaseModel):
|
class BackupJob(BaseModel):
|
||||||
tag: Optional[str]
|
tag: Optional[str]
|
||||||
options: BackupOptions
|
options: BackupOptions
|
||||||
|
@ -59,24 +77,3 @@ class Imports(BaseModel):
|
||||||
"templates": ["recipes.md", "custom_template.md"],
|
"templates": ["recipes.md", "custom_template.md"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ImportJob(BaseModel):
|
|
||||||
name: str
|
|
||||||
recipes: bool
|
|
||||||
force: bool = False
|
|
||||||
rebase: bool = False
|
|
||||||
themes: bool = False
|
|
||||||
settings: bool = False
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
schema_extra = {
|
|
||||||
"example": {
|
|
||||||
"name": "my_local_backup.zip",
|
|
||||||
"recipes": True,
|
|
||||||
"force": False,
|
|
||||||
"rebase": False,
|
|
||||||
"themes": False,
|
|
||||||
"settings": False,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,20 +3,27 @@ from typing import Optional
|
||||||
from pydantic.main import BaseModel
|
from pydantic.main import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class RecipeImport(BaseModel):
|
class ImportBase(BaseModel):
|
||||||
name: Optional[str]
|
name: str
|
||||||
|
status: bool
|
||||||
|
exception: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
class RecipeImport(ImportBase):
|
||||||
slug: str
|
slug: str
|
||||||
status: bool
|
|
||||||
exception: Optional[str]
|
|
||||||
|
|
||||||
|
|
||||||
class ThemeImport(BaseModel):
|
class ThemeImport(ImportBase):
|
||||||
name: str
|
pass
|
||||||
status: bool
|
|
||||||
exception: Optional[str]
|
|
||||||
|
|
||||||
|
|
||||||
class SettingsImport(BaseModel):
|
class SettingsImport(ImportBase):
|
||||||
name: str
|
pass
|
||||||
status: bool
|
|
||||||
exception: Optional[str]
|
|
||||||
|
class GroupImport(ImportBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UserImport(ImportBase):
|
||||||
|
pass
|
||||||
|
|
|
@ -4,13 +4,14 @@ import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
from fastapi.logger import logger
|
||||||
from mealie.core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR
|
from mealie.core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
from mealie.db.db_setup import create_session
|
from mealie.db.db_setup import create_session
|
||||||
from fastapi.logger import logger
|
|
||||||
from mealie.schema.recipe import Recipe
|
from mealie.schema.recipe import Recipe
|
||||||
from mealie.schema.restore import RecipeImport, SettingsImport, ThemeImport
|
from mealie.schema.restore import GroupImport, RecipeImport, SettingsImport, ThemeImport, UserImport
|
||||||
from mealie.schema.theme import SiteTheme
|
from mealie.schema.theme import SiteTheme
|
||||||
|
from mealie.schema.user import UpdateGroup, UserInDB
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,32 +20,21 @@ class ImportDatabase:
|
||||||
self,
|
self,
|
||||||
session: Session,
|
session: Session,
|
||||||
zip_archive: str,
|
zip_archive: str,
|
||||||
import_recipes: bool = True,
|
|
||||||
import_settings: bool = True,
|
|
||||||
import_themes: bool = True,
|
|
||||||
force_import: bool = False,
|
force_import: bool = False,
|
||||||
rebase: bool = False,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Import a database.zip file exported from mealie.
|
"""Import a database.zip file exported from mealie.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
session (Session): SqlAlchemy Session
|
||||||
zip_archive (str): The filename contained in the backups directory
|
zip_archive (str): The filename contained in the backups directory
|
||||||
import_recipes (bool, optional): Import Recipes?. Defaults to True.
|
|
||||||
import_settings (bool, optional): Determines if settings are imported. Defaults to True.
|
|
||||||
import_themes (bool, optional): Determines if themes are imported. Defaults to True.
|
|
||||||
force_import (bool, optional): Force import will update all existing recipes. If False existing recipes are skipped. Defaults to False.
|
force_import (bool, optional): Force import will update all existing recipes. If False existing recipes are skipped. Defaults to False.
|
||||||
rebase (bool, optional): Rebase will first clear the database and then import Recipes. Defaults to False.
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
Exception: If the zip file does not exists an exception raise.
|
Exception: If the zip file does not exists an exception raise.
|
||||||
"""
|
"""
|
||||||
self.session = session
|
self.session = session
|
||||||
self.archive = BACKUP_DIR.joinpath(zip_archive)
|
self.archive = BACKUP_DIR.joinpath(zip_archive)
|
||||||
self.imp_recipes = import_recipes
|
|
||||||
self.imp_settings = import_settings
|
|
||||||
self.imp_themes = import_themes
|
|
||||||
self.force_imports = force_import
|
self.force_imports = force_import
|
||||||
self.force_rebase = rebase
|
|
||||||
|
|
||||||
if self.archive.is_file():
|
if self.archive.is_file():
|
||||||
self.import_dir = TEMP_DIR.joinpath("active_import")
|
self.import_dir = TEMP_DIR.joinpath("active_import")
|
||||||
|
@ -56,25 +46,6 @@ class ImportDatabase:
|
||||||
else:
|
else:
|
||||||
raise Exception("Import file does not exist")
|
raise Exception("Import file does not exist")
|
||||||
|
|
||||||
def run(self):
|
|
||||||
recipe_report = []
|
|
||||||
settings_report = []
|
|
||||||
theme_report = []
|
|
||||||
if self.imp_recipes:
|
|
||||||
recipe_report = self.import_recipes()
|
|
||||||
if self.imp_settings:
|
|
||||||
settings_report = self.import_settings()
|
|
||||||
if self.imp_themes:
|
|
||||||
theme_report = self.import_themes()
|
|
||||||
|
|
||||||
self.clean_up()
|
|
||||||
|
|
||||||
return {
|
|
||||||
"recipeImports": recipe_report,
|
|
||||||
"settingsReport": settings_report,
|
|
||||||
"themeReport": theme_report,
|
|
||||||
}
|
|
||||||
|
|
||||||
def import_recipes(self):
|
def import_recipes(self):
|
||||||
session = create_session()
|
session = create_session()
|
||||||
recipe_dir: Path = self.import_dir.joinpath("recipes")
|
recipe_dir: Path = self.import_dir.joinpath("recipes")
|
||||||
|
@ -180,8 +151,112 @@ class ImportDatabase:
|
||||||
import_status = SettingsImport(name=name, status=False, exception=str(inst))
|
import_status = SettingsImport(name=name, status=False, exception=str(inst))
|
||||||
|
|
||||||
settings_imports.append(import_status)
|
settings_imports.append(import_status)
|
||||||
|
|
||||||
return settings_imports
|
return settings_imports
|
||||||
|
|
||||||
|
def import_groups(self):
|
||||||
|
groups_file = self.import_dir.joinpath("groups", "groups.json")
|
||||||
|
group_imports = []
|
||||||
|
|
||||||
|
with open(groups_file, "r") as f:
|
||||||
|
groups = [UpdateGroup(**g) for g in json.loads(f.read())]
|
||||||
|
|
||||||
|
for group in groups:
|
||||||
|
item = db.groups.get(self.session, group.name, "name")
|
||||||
|
if item:
|
||||||
|
import_status = GroupImport(name=group.name, status=False, exception="Group Exists")
|
||||||
|
group_imports.append(import_status)
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
db.groups.create(self.session, group.dict())
|
||||||
|
import_status = GroupImport(name=group.name, status=True)
|
||||||
|
|
||||||
|
except Exception as inst:
|
||||||
|
import_status = GroupImport(name=group.name, status=False, exception=str(inst))
|
||||||
|
|
||||||
|
group_imports.append(import_status)
|
||||||
|
|
||||||
|
print(group_imports)
|
||||||
|
return group_imports
|
||||||
|
|
||||||
|
def import_users(self):
|
||||||
|
users_file = self.import_dir.joinpath("users", "users.json")
|
||||||
|
user_imports = []
|
||||||
|
|
||||||
|
with open(users_file, "r") as f:
|
||||||
|
users = [UserInDB(**g) for g in json.loads(f.read())]
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
if user.id == 1:
|
||||||
|
db.users.update(self.session, 1, user.dict())
|
||||||
|
import_status = UserImport(name=user.full_name, status=True)
|
||||||
|
user_imports.append(import_status)
|
||||||
|
continue
|
||||||
|
|
||||||
|
item = db.users.get(self.session, user.email, "email")
|
||||||
|
if item:
|
||||||
|
import_status = UserImport(name=user.full_name, status=False, exception="User Email Exists")
|
||||||
|
user_imports.append(import_status)
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.users.create(self.session, user.dict())
|
||||||
|
import_status = UserImport(name=user.full_name, status=True)
|
||||||
|
|
||||||
|
except Exception as inst:
|
||||||
|
import_status = UserImport(name=user.full_name, status=False, exception=str(inst))
|
||||||
|
|
||||||
|
user_imports.append(import_status)
|
||||||
|
|
||||||
|
return user_imports
|
||||||
|
|
||||||
def clean_up(self):
|
def clean_up(self):
|
||||||
shutil.rmtree(TEMP_DIR)
|
shutil.rmtree(TEMP_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
def import_database(
|
||||||
|
session: Session,
|
||||||
|
archive,
|
||||||
|
import_recipes=True,
|
||||||
|
import_settings=True,
|
||||||
|
import_themes=True,
|
||||||
|
import_users=True,
|
||||||
|
import_groups=True,
|
||||||
|
force_import: bool = False,
|
||||||
|
rebase: bool = False,
|
||||||
|
):
|
||||||
|
import_session = ImportDatabase(session, archive)
|
||||||
|
|
||||||
|
recipe_report = []
|
||||||
|
if import_recipes:
|
||||||
|
recipe_report = import_session.import_recipes()
|
||||||
|
|
||||||
|
settings_report = []
|
||||||
|
if import_settings:
|
||||||
|
settings_report = import_session.import_settings()
|
||||||
|
|
||||||
|
theme_report = []
|
||||||
|
if import_themes:
|
||||||
|
theme_report = import_session.import_themes()
|
||||||
|
|
||||||
|
group_report = []
|
||||||
|
if import_groups:
|
||||||
|
print("Import Groups")
|
||||||
|
group_report = import_session.import_groups()
|
||||||
|
|
||||||
|
user_report = []
|
||||||
|
if import_users:
|
||||||
|
user_report = import_session.import_users()
|
||||||
|
|
||||||
|
import_session.clean_up()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"recipeImports": recipe_report,
|
||||||
|
"settingsImports": settings_report,
|
||||||
|
"themeImports": theme_report,
|
||||||
|
"groupImports": group_report,
|
||||||
|
"userImports": user_report,
|
||||||
|
}
|
||||||
|
|
||||||
|
print(data)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue