(add): Backup/Restore
This commit is contained in:
@@ -36,9 +36,13 @@
|
||||
type="number"
|
||||
id="id_from"
|
||||
class={iS.normal}
|
||||
title="Alt + Q"
|
||||
onclick={(e) => {
|
||||
e.target.select();
|
||||
}}
|
||||
onchange={() => {
|
||||
if (functions.pagerFromUpdate) functions.pagerFromUpdate();
|
||||
}}
|
||||
bind:value={pager.idFrom}
|
||||
/>
|
||||
<div>-</div>
|
||||
@@ -46,6 +50,7 @@
|
||||
type="number"
|
||||
id="id_to"
|
||||
class={iS.normal}
|
||||
title="Alt + W"
|
||||
onclick={(e) => {
|
||||
e.target.select();
|
||||
}}
|
||||
@@ -62,6 +67,7 @@
|
||||
{#if functions.prevPage}
|
||||
<button
|
||||
class={bS[prefix.color]}
|
||||
title="Alt + B"
|
||||
onclick={() => {
|
||||
functions.prevPage();
|
||||
}}>Prev Page</button
|
||||
@@ -70,6 +76,7 @@
|
||||
{#if functions.nextPage}
|
||||
<button
|
||||
class={bS[prefix.color]}
|
||||
title="Alt + N"
|
||||
onclick={() => {
|
||||
functions.nextPage();
|
||||
}}>Next Page</button
|
||||
|
||||
@@ -43,7 +43,7 @@ export const iS = {
|
||||
};
|
||||
|
||||
export const rBS = {
|
||||
white: 'even:bg-gray-100',
|
||||
white: 'even:bg-gray-200',
|
||||
blue: 'even:bg-blue-100',
|
||||
yellow: 'even:bg-yellow-100',
|
||||
green: 'even:bg-green-100',
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { baskets, prefixes, tickets } from '$lib/server/db/schema';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
const chunk_size = 300;
|
||||
|
||||
export const GET = async () => {
|
||||
const data = {};
|
||||
data.prefixes = await db.select().from(prefixes);
|
||||
data.baskets = await db.select().from(baskets);
|
||||
data.tickets = await db.select().from(tickets);
|
||||
return json(data);
|
||||
};
|
||||
|
||||
export const POST = async ({ request }) => {
|
||||
const reqData = await request.json();
|
||||
for (let i = 0; i < reqData.prefixes.length; i += chunk_size) {
|
||||
const chunk = reqData.prefixes.slice(i, i + chunk_size);
|
||||
await db
|
||||
.insert(prefixes)
|
||||
.values(chunk)
|
||||
.onConflictDoUpdate({
|
||||
target: prefixes.prefix,
|
||||
set: { color: sql`EXCLUDED.color`, weight: sql`EXCLUDED.weight` }
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < reqData.baskets.length; i += chunk_size) {
|
||||
const chunk = reqData.baskets.slice(i, i + chunk_size);
|
||||
await db
|
||||
.insert(baskets)
|
||||
.values(chunk)
|
||||
.onConflictDoUpdate({
|
||||
target: [baskets.prefix, baskets.b_id],
|
||||
set: {
|
||||
description: sql`EXCLUDED.description`,
|
||||
donors: sql`EXCLUDED.donors`,
|
||||
winning_ticket: sql`EXCLUDED.winning_ticket`
|
||||
}
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < reqData.tickets.length; i += chunk_size) {
|
||||
const chunk = reqData.tickets.slice(i, i + chunk_size);
|
||||
await db
|
||||
.insert(tickets)
|
||||
.values(chunk)
|
||||
.onConflictDoNothing({
|
||||
target: [tickets.prefix, tickets.t_id],
|
||||
set: {
|
||||
first_name: sql`EXCLUDED.first_name`,
|
||||
last_name: sql`EXCLUDED.last_name`,
|
||||
phone_number: sql`EXCLUDED.phone_number`,
|
||||
pref: sql`EXCLUDED.pref`
|
||||
}
|
||||
});
|
||||
}
|
||||
return json({ details: 'Data loaded successfully.' });
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
import { baskets, prefixes, tickets } from '$lib/server/db/schema';
|
||||
import { getPath, getSettings } from '$lib/server/settings/index.js';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export const HEAD = async ({ params }) => {
|
||||
const target = params.target;
|
||||
const pushData = {};
|
||||
if (target === 'prefixes') {
|
||||
pushData.prefixes = await db.select().from(prefixes);
|
||||
} else if (target === 'tickets') {
|
||||
pushData.tickets = await db.select().from(tickets);
|
||||
} else if (target === 'baskets') {
|
||||
pushData.baskets = await db.select().from(baskets);
|
||||
} else {
|
||||
throw error(400, 'Can only push prefixes, tickets, or baskets.');
|
||||
}
|
||||
|
||||
const s = getSettings();
|
||||
if (s.remote_server) {
|
||||
const connStr = getPath(s);
|
||||
const res = await fetch(`${connStr}/api/backuprestore`, {
|
||||
method: 'POST',
|
||||
headers: { 'TAM-KEY': s.remote_key, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(pushData)
|
||||
});
|
||||
if (!res.ok) throw error(res.status, res.statusText);
|
||||
return new Response(null, { status: 200, statusText: 'Data pushed successfully.' });
|
||||
} else {
|
||||
throw error(500, 'Server not set.');
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { getPath, getSettings } from '$lib/server/settings';
|
||||
import { error, json } from '@sveltejs/kit';
|
||||
|
||||
export const GET = async () => {
|
||||
const s = getSettings();
|
||||
if (s.remote_server) {
|
||||
const connStr = getPath(s);
|
||||
try {
|
||||
const res = await fetch(`${connStr}/api/backuprestore`, {
|
||||
headers: { TAM_KEY: s.remote_key }
|
||||
});
|
||||
if (!res.ok) throw error(res.status);
|
||||
const data = await res.json();
|
||||
return json(data);
|
||||
} catch {
|
||||
return json({});
|
||||
}
|
||||
} else {
|
||||
return json({});
|
||||
}
|
||||
};
|
||||
|
||||
export const POST = async ({ request }) => {
|
||||
const resData = await request.json();
|
||||
const s = getSettings();
|
||||
if (s.remote_server) {
|
||||
const connStr = getPath(s);
|
||||
const res = await fetch(`${connStr}/api/backuprestore`, {
|
||||
method: 'POST',
|
||||
headers: { 'TAM-KEY': s.remote_key, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(resData)
|
||||
});
|
||||
if (!res.ok) throw error(res.status);
|
||||
return json([]);
|
||||
}
|
||||
};
|
||||
@@ -21,7 +21,7 @@
|
||||
curIdx = idx;
|
||||
const elemIdx = document.getElementById(`${idx}_first`);
|
||||
if (elemIdx) {
|
||||
elemIdx.focus();
|
||||
elemIdx.select();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -44,6 +44,13 @@
|
||||
items = [...resData];
|
||||
setTimeout(() => focusIdx(0));
|
||||
},
|
||||
pagerFromUpdate(){
|
||||
if (itemsLength === 0) {
|
||||
pager.idTo = pager.idFrom;
|
||||
} else {
|
||||
pager.idTo = pager.idFrom + (itemsLength - 1);
|
||||
}
|
||||
},
|
||||
save: async () => {
|
||||
if (itemsBuffer.length > 0) {
|
||||
const res = await fetch('/api/baskets', {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
curIdx = idx;
|
||||
const elemIdx = document.getElementById(`${idx}_first`);
|
||||
if (elemIdx) {
|
||||
elemIdx.focus();
|
||||
elemIdx.select();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -44,6 +44,13 @@
|
||||
items = [...resData];
|
||||
setTimeout(() => focusIdx(0));
|
||||
},
|
||||
pagerFromUpdate(){
|
||||
if (itemsLength === 0) {
|
||||
pager.idTo = pager.idFrom;
|
||||
} else {
|
||||
pager.idTo = pager.idFrom + (itemsLength - 1);
|
||||
}
|
||||
},
|
||||
save: async () => {
|
||||
if (itemsBuffer.length > 0) {
|
||||
const res = await fetch('/api/drawing', {
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<a href={resolve('/settings/auth-keys')} class={bS.gray}>Auth Keys</a>
|
||||
{/if}
|
||||
<a href={resolve('/settings/prefixes')} class={bS.gray}>Prefixes</a>
|
||||
<a href={resolve('/settings/backuprestore')} class={bS.gray}>Backup/Restore</a>
|
||||
</HeaderBar>
|
||||
<h1 class="text-xl font-bold">{pageTitle}</h1>
|
||||
<div class="flex flex-col gap-1 w-full py-1">
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { getSettings } from "$lib/server/settings/index.js"
|
||||
|
||||
export const load = async () => {
|
||||
const s = getSettings();
|
||||
return { remoteServer: s.remote_server };
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<script>
|
||||
import { resolve } from '$app/paths';
|
||||
import HeaderBar from '$lib/client/components/HeaderBar.svelte';
|
||||
import { bS, iS } from '$lib/client/styles';
|
||||
|
||||
const pageTitle = 'Backup and Restore | TAM';
|
||||
|
||||
let { data } = $props();
|
||||
let remoteServer = $derived(data.remoteServer);
|
||||
let uploadFile = $state();
|
||||
let contents = $state('');
|
||||
let results = $state('');
|
||||
|
||||
function setResult(newResult) {
|
||||
results = newResult;
|
||||
setTimeout(() => (results = ''), 10000);
|
||||
}
|
||||
|
||||
async function downloadBackupFile(target) {
|
||||
let fetch_url;
|
||||
if (target === 'local') {
|
||||
fetch_url = '/api/backuprestore/local';
|
||||
} else if (target === 'remote') {
|
||||
fetch_url = '/api/backuprestore/remote';
|
||||
}
|
||||
const now = new Date();
|
||||
const date = {
|
||||
year: now.getFullYear(),
|
||||
month: String(now.getMonth() + 1).padStart(2, '0'),
|
||||
day: String(now.getDate()).padStart(2, '0'),
|
||||
hour: String(now.getHours()).padStart(2, '0'),
|
||||
minutes: String(now.getMinutes()).padStart(2, '0')
|
||||
};
|
||||
const res = await fetch(fetch_url);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
const jsonString = JSON.stringify(data, null, 2);
|
||||
const blob = new Blob([jsonString], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `TAM_${date.year}-${date.month}-${date.day} ${date.hour}:${date.minutes}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
async function fileUpload(target) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(uploadFile[0], 'utf-8');
|
||||
reader.onload = function (e) {
|
||||
contents = String(e.target.result);
|
||||
};
|
||||
console.log(contents);
|
||||
setTimeout(async () => {
|
||||
const res = await fetch(`/api/backuprestore/${target}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: contents
|
||||
});
|
||||
if (res.ok) setResult('File uploaded successfully. Check to see if your data exists.');
|
||||
else setResult('Error uploading file.');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
async function pushData(target) {
|
||||
const res = await fetch(`/api/backuprestore/push/${target}`, { method: 'HEAD' });
|
||||
const targetStr = target.charAt(0).toUpperCase() + target.slice(1);
|
||||
if (res.ok) {
|
||||
setResult(`${targetStr} pushed successfully.`);
|
||||
} else {
|
||||
setResult(`Error pushing ${targetStr}. Error [${res.status}] ${res.statusText}.`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{pageTitle}</title>
|
||||
</svelte:head>
|
||||
|
||||
<div id="app_container" class="p-1">
|
||||
<HeaderBar>
|
||||
<a href={resolve('/settings')} class={bS.gray}>Back to Settings</a>
|
||||
<div>Other Settings</div>
|
||||
<a href={resolve('/settings/prefixes')} class={bS.gray}>Prefixes</a>
|
||||
{#if remoteServer}
|
||||
<a href={resolve('/settings/auth-keys')} class={bS.gray}>Auth Keys</a>
|
||||
{/if}
|
||||
</HeaderBar>
|
||||
<h1 class="text-xl font-bold">{pageTitle}</h1>
|
||||
<div class="my-1 p-1 border border-black rounded">
|
||||
<h2 class="text-lg font-bold">Backup File Downloads</h2>
|
||||
<div class="flex flex-row gap-1">
|
||||
<button class={bS.gray} onclick={downloadBackupFile('local')}>Local Data</button>
|
||||
{#if remoteServer}
|
||||
<button class={bS.gray} onclick={downloadBackupFile('remote')}>Remote Data</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-1 p-1 border border-black rounded">
|
||||
<h2 class="text-lg font-bold">Backup File Upload</h2>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
<input
|
||||
type="file"
|
||||
accept=".json"
|
||||
class="{iS.normal} rounded file:bg-gray-300 file:border file:border-black file:px-2 file:py-1 file:rounded"
|
||||
bind:files={uploadFile}
|
||||
/>
|
||||
<button class={bS.gray} onclick={() => fileUpload('local')}>Upload to Local</button>
|
||||
{#if remoteServer}
|
||||
<button class={bS.gray} onclick={() => fileUpload('remote')}>Upload to Remote</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if remoteServer}
|
||||
<div class="my-1 p-1 border border-black rounded">
|
||||
<h2 class="text-lg font-bold">Push Data to Server</h2>
|
||||
<div class="flex flex-row gap-1">
|
||||
<button class={bS.gray} onclick={() => pushData('prefixes')}>Push Prefixes</button>
|
||||
<button class={bS.gray} onclick={() => pushData('tickets')}>Push Tickets</button>
|
||||
<button class={bS.gray} onclick={() => pushData('baskets')}>Push Baskets</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="my-1 p-1 border border-black rounded">
|
||||
<span class="font-bold">Status: </span><span>{results}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,6 +44,13 @@
|
||||
items = [...resData];
|
||||
setTimeout(() => focusIdx(0));
|
||||
},
|
||||
pagerFromUpdate(){
|
||||
if (itemsLength === 0) {
|
||||
pager.idTo = pager.idFrom;
|
||||
} else {
|
||||
pager.idTo = pager.idFrom + (itemsLength - 1);
|
||||
}
|
||||
},
|
||||
save: async () => {
|
||||
if (itemsBuffer.length > 0) {
|
||||
const res = await fetch('/api/tickets', {
|
||||
|
||||
Reference in New Issue
Block a user