Add data import and export
This commit is contained in:
parent
7a7b40e2ba
commit
222c718509
|
@ -0,0 +1,45 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { importDb } from '$lib/db/import-db';
|
||||||
|
import Button from '../button.svelte';
|
||||||
|
import Modal from '../modal.svelte';
|
||||||
|
|
||||||
|
export let open: boolean;
|
||||||
|
|
||||||
|
async function submit(event: SubmitEvent) {
|
||||||
|
const form = event.target as HTMLFormElement;
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const [dbFile] = formData.getAll('dbFile') as File[];
|
||||||
|
|
||||||
|
if (!dbFile || dbFile.name === '') {
|
||||||
|
// TODO: message that no file was selected
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const importSucceeded = await importDb(dbFile);
|
||||||
|
|
||||||
|
if (importSucceeded === true) {
|
||||||
|
form.reset();
|
||||||
|
open = false;
|
||||||
|
} else {
|
||||||
|
// TODO: message that import failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal title="Import Notes" size="sm" bind:open>
|
||||||
|
<form on:submit|preventDefault={submit}>
|
||||||
|
<p>Select a backup file that you exported previously to restore your notes.</p>
|
||||||
|
<p>
|
||||||
|
<strong>Warning:</strong>
|
||||||
|
This will overwrite all of your current notes.
|
||||||
|
</p>
|
||||||
|
<input type="file" name="dbFile" accept=".db" />
|
||||||
|
<Button type="submit">Import</Button>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
input {
|
||||||
|
margin: 1rem 0 1.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,8 +2,17 @@
|
||||||
import Modal from '../modal.svelte';
|
import Modal from '../modal.svelte';
|
||||||
import { settings } from '$lib/stores';
|
import { settings } from '$lib/stores';
|
||||||
import SettingsColorPicker from './settings-color-picker.svelte';
|
import SettingsColorPicker from './settings-color-picker.svelte';
|
||||||
|
import Button from '../button.svelte';
|
||||||
|
import { exportDb } from '$lib/db/export-db';
|
||||||
|
import SettingsImportModal from './settings-import-modal.svelte';
|
||||||
|
|
||||||
const { open, noteScale, defaultNoteColor } = settings;
|
const { open, noteScale, defaultNoteColor } = settings;
|
||||||
|
let importModalOpen = false;
|
||||||
|
|
||||||
|
function goToImport() {
|
||||||
|
open.set(false);
|
||||||
|
importModalOpen = true;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal title="Settings" size="sm" bind:open={$open}>
|
<Modal title="Settings" size="sm" bind:open={$open}>
|
||||||
|
@ -22,4 +31,23 @@
|
||||||
<legend>Default Note Color</legend>
|
<legend>Default Note Color</legend>
|
||||||
<SettingsColorPicker bind:value={$defaultNoteColor} />
|
<SettingsColorPicker bind:value={$defaultNoteColor} />
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<hr />
|
||||||
|
<div class="settings-buttons">
|
||||||
|
<Button on:click={exportDb}>Export Notes</Button>
|
||||||
|
<Button secondary on:click={goToImport}>Import Notes</Button>
|
||||||
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
<SettingsImportModal bind:open={importModalOpen} />
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.settings-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,30 +1,16 @@
|
||||||
import { Kysely, Migrator } from 'kysely';
|
import { Kysely } from 'kysely';
|
||||||
import { SQLocalKysely } from 'sqlocal/kysely';
|
import { SQLocalKysely } from 'sqlocal/kysely';
|
||||||
import type { Schema } from './schema';
|
import type { Schema } from './schema';
|
||||||
import { migrations } from './migrations/';
|
import { migrate } from './migrator';
|
||||||
|
|
||||||
const sqlocal = new SQLocalKysely('sticky-notes.db');
|
const sqlocal = new SQLocalKysely('sticky-notes.db');
|
||||||
const kysely = new Kysely<Schema>({ dialect: sqlocal.dialect });
|
const kysely = new Kysely<Schema>({ dialect: sqlocal.dialect });
|
||||||
|
|
||||||
await sqlocal.sql`PRAGMA foreign_keys = ON`;
|
try {
|
||||||
|
await migrate(kysely);
|
||||||
const migrator = new Migrator({
|
} catch (err) {
|
||||||
db: kysely,
|
console.error(err);
|
||||||
provider: {
|
|
||||||
async getMigrations() {
|
|
||||||
// TODO: Dynamic import does not work in production build
|
|
||||||
// because the imported chunk imports `sql` from the same
|
|
||||||
// chunk that imports it, so circular dependency. Vite bug.
|
|
||||||
// const { migrations } = await import('./migrations/');
|
|
||||||
return migrations;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const migration = await migrator.migrateToLatest();
|
|
||||||
|
|
||||||
if (migration.error) {
|
|
||||||
console.error(migration.error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const db = kysely;
|
export const db = kysely;
|
||||||
|
export const { getDatabaseFile, overwriteDatabaseFile } = sqlocal;
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { getDatabaseFile } from './client';
|
||||||
|
|
||||||
|
export async function exportDb() {
|
||||||
|
const databaseFile = await getDatabaseFile();
|
||||||
|
const fileUrl = URL.createObjectURL(databaseFile);
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const timestamp = now.toISOString().split('.')[0].replace(/\:/g, '-');
|
||||||
|
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = fileUrl;
|
||||||
|
a.download = `notes-${timestamp}.db`;
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
|
||||||
|
URL.revokeObjectURL(fileUrl);
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { notes, tags } from '$lib/stores';
|
||||||
|
import { db, overwriteDatabaseFile } from './client';
|
||||||
|
import { migrate } from './migrator';
|
||||||
|
|
||||||
|
export async function importDb(dbFile: File): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await overwriteDatabaseFile(dbFile);
|
||||||
|
await migrate(db);
|
||||||
|
await tags.refreshTags();
|
||||||
|
await notes.refreshNotes();
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Kysely, Migrator, sql } from 'kysely';
|
||||||
|
import { migrations } from './migrations/';
|
||||||
|
import type { Schema } from './schema';
|
||||||
|
|
||||||
|
export async function migrate(db: Kysely<Schema>) {
|
||||||
|
const migrator = new Migrator({
|
||||||
|
db,
|
||||||
|
provider: {
|
||||||
|
async getMigrations() {
|
||||||
|
return migrations;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await sql`PRAGMA foreign_keys = ON`.execute(db);
|
||||||
|
const migration = await migrator.migrateToLatest();
|
||||||
|
|
||||||
|
if (migration.error) {
|
||||||
|
console.error(migration.error);
|
||||||
|
throw new Error('Migration failed');
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue