refs #0 The root commit.

This commit is contained in:
sbps-test user
2023-09-13 20:37:14 +03:00
commit 10eb607154
66 changed files with 1643 additions and 0 deletions

1
front/.env Normal file
View File

@@ -0,0 +1 @@
BACKEND_URL="http://app:8000"

17
front/package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "library",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.5.0",
"svelte": "^3.54.0",
"vite": "^4.0.0"
},
"type": "module"
}

12
front/src/app.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,26 @@
<script>
export let item, project, title;
import { enhance } from '$app/forms';
import Field from './Field.svelte';
import { getKeys, handleCreateItem } from './util.js';
$: keys = getKeys(item);
</script>
<form method="POST" enctype="multipart/form-data" use:enhance>
<table>
<tbody>
{#each keys as key}
<tr><td>
<Field project={project} item={item} key={key}/>
</td></tr>
{/each}
<tr><td>
<button on:click|preventDefault={handleCreateItem(title)}>Save</button>
</td></tr>
</tbody>
</table>
</form>

View File

@@ -0,0 +1,35 @@
<script>
export let item, project, title;
import { enhance } from '$app/forms';
import Field from '$lib/components/Field.svelte';
import FileField from '$lib/components/FileField.svelte';
import { getKeys, getType, handleUpdateItem, handleDeleteItem } from './util.js';
$: keys = getKeys(item);
</script>
<form method="POST" enctype="multipart/form-data" use:enhance>
<table>
<tbody>
{#each keys as key}
<tr><td>
{#if getType(project, key) == 'file'}
<FileField title={title} item={item} key={key}/>
{:else}
<Field project={project} item={item} key={key}/>
{/if}
</td></tr>
{/each}
<tr><td>
<button on:click|preventDefault={handleUpdateItem(title, item.id)}>Save</button>
</td></tr>
<tr><td>
<button on:click|preventDefault={handleDeleteItem(title, item.id)}>Delete</button>
</td></tr>
</tbody>
</table>
</form>

View File

@@ -0,0 +1,17 @@
<script>
export let item, key, project;
import { capitalize, getType, getValue } from './util.js';
$: type = getType(project, key);
$: value = getValue(item, key);
$: label = capitalize(key);
</script>
<label for={key}>{label}:</label>
<input
name={key}
value={value}
autocomplete="off"
type={type}
/>

View File

@@ -0,0 +1,21 @@
<script>
export let item, key, title;
import { capitalize, getFileName, getValue, handleDeleteFile } from './util.js';
$: label = capitalize(key);
$: value = getValue(item, key);
$: fileName = getFileName(value);
</script>
<label for={key}>{label}:</label>
<input
name={key}
autocomplete="off"
type="file"
id={key}
/>
{#if value}
<a href="/{title}/{item.id}/{key}" target="_blank">{fileName}</a>
<button on:click|preventDefault={handleDeleteFile(title, item.id, key)}>Delete File</button>
{/if}

View File

@@ -0,0 +1,12 @@
<script>
export let keys;
import { capitalize } from './util.js';
</script>
<tr>
<td></td>
{#each keys as key}
<td>{capitalize(key)}</td>
{/each}
</tr>

View File

@@ -0,0 +1,21 @@
<script>
export let title, item, project;
import { getKeys, getValue, getType, getFileName } from './util.js';
$: keys = getKeys(item);
</script>
<tr>
<td><a href="/{title}/{item.id}">{item.id}</a></td>
{#each keys as key}
<td>
{#if getType(project, key) == 'file'}
{getFileName(getValue(item, key) || "")}
{:else}
{getValue(item, key) || ""}
{/if}
</td>
{/each}
</tr>

View File

@@ -0,0 +1,20 @@
<script>
export let items, title, project;
import HeadRow from './HeadRow.svelte';
import Row from './Row.svelte';
import { getKeys } from './util.js';
</script>
{#if items.length !== 0}
<table>
<thead>
<HeadRow keys={getKeys(items[0])}/>
</thead>
<tbody>
{#each items as item (item.id)}
<Row project={project} item={item} title={title}/>
{/each}
</tbody>
</table>
{/if}

View File

@@ -0,0 +1,85 @@
import { goto, invalidate } from '$app/navigation';
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
};
export function getFileName(str) {
if (str !== null) {
return str.replace(/^.*[\\\/]/, '');
}
};
export function getKeys(item) {
return Object.keys(item).filter(key => key !== 'id');
};
export function getValue(item, key) {
return item[key];
};
export function getTableField(project, item, key) {
const value = getValue(item, key) || '';
const type = getType(project, key);
if (type == 'file') {
return getFileName(value);
} else {
return value;
}
};
export function getType(project, key) {
const type = project.fields[key].type;
const type_map = {
str: 'text',
int: 'number',
file: 'file'
}
return type_map[type]
};
export async function handleCreateItem(project) {
const form = document.querySelector('form');
const data = new FormData(form);
const response = await fetch(
`/${project}/add`,
{
method: 'POST',
body: data,
}
)
const item = await response.json();
goto(`/${project}/${item.id}`, {invalidateAll: true});
};
export async function handleUpdateItem(project, item_id) {
const form = document.querySelector('form');
const data = new FormData(form);
const response = await fetch(
`/${project}/${item_id}`,
{
method: 'PUT',
body: data,
}
);
invalidate('data:item');
invalidate('data:items');
};
export async function handleDeleteItem(project, item_id) {
const response = await fetch(
`/${project}/${item_id}`,
{
method: 'DELETE',
}
);
goto(`/${project}`, {invalidateAll: true});
};
export async function handleDeleteFile(project, item_id, field) {
const response = await fetch(`/${project}/${item_id}/${field}`, {method: 'DELETE'})
document.querySelector(`input[name=${field}]`).value = "";
invalidate('data:item');
invalidate('data:items');
};

View File

@@ -0,0 +1,147 @@
import { error, fail } from '@sveltejs/kit';
import { BACKEND_URL } from '$env/static/private';
const module = "book"
export async function getItems(project) {
const response = await fetch(`${BACKEND_URL}/${project}/`);
return response.json();
}
export async function getItem(project, id) {
const response = await fetch(`${BACKEND_URL}/${project}/${id}`);
if (response.status !== 200) throw error(response.status);
const item = await response.json();
return item;
}
export async function getEmptyItem(project) {
const response = await fetch(`${BACKEND_URL}/_module/${project}`);
if (response.status !== 200) throw error(response.status);
const result = await response.json();
const fields = Object.keys(result.fields);
const entries = new Map(fields.map(field => [field, null]))
return Object.fromEntries(entries);
}
export async function getProjects() {
const response = await fetch(`${BACKEND_URL}/_module/`);
if (response.status !== 200) throw error(response.status);
return response.json();
}
export async function getProject(project) {
const response = await fetch(`${BACKEND_URL}/_module/${project}`);
if (response.status !== 200) throw error(response.status);
return response.json();
}
export async function createItem(project, data) {
const fields = Object.fromEntries(
Array.from(data.entries()).filter(
([key, value]) => !(value instanceof File)
)
);
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(fields)
}
const response = await fetch(`${BACKEND_URL}/${project}/`, options);
if (response.status !== 200) throw error(response.status);
const item = await response.json();
const files = Object.fromEntries(
Array.from(data.entries()).filter(
([key, value]) => value instanceof File && value.size !== 0
)
);
for (let key in files)
{
const data = new FormData()
data.append('file', files[key]);
await uploadFile(project, item["id"], key, data);
}
return item;
}
export async function updateItem(project, id, data) {
const files = Object.fromEntries(
Array.from(data.entries()).filter(
([key, value]) => value instanceof File && value.size !== 0
)
);
for (let key in files)
{
const data = new FormData()
data.append('file', files[key]);
await deleteFile(project, id, key);
await uploadFile(project, id, key, data);
}
const fields = Object.fromEntries(
Array.from(data.entries()).filter(
([key, value]) => !(value instanceof File)
)
);
const options = {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(fields)
}
const response = await fetch(`${BACKEND_URL}/${project}/${id}`, options);
if (response.status !== 200) throw error(response.status);
const item = response.json();
return item;
}
export async function deleteItem(project, id) {
const options = {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
}
const response = await fetch(`${BACKEND_URL}/${project}/${id}`, options);
if (response.status !== 200) throw error(response.status);
return response.json();
}
export async function readFile(project, id, field) {
const response = await fetch(`${BACKEND_URL}/${project}/${id}/${field}`);
return response;
}
export async function uploadFile(project, id, field, data) {
const options = {
method: 'POST',
body: data,
}
const response = await fetch(`${BACKEND_URL}/${project}/${id}/${field}`, options);
if (response.status !== 200) throw error(response.status);
return response.json();
}
export async function deleteFile(project, id, field) {
const options = {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
}
const response = await fetch(`${BACKEND_URL}/${project}/${id}/${field}`, options);
if (response.status !== 200) throw error(response.status);
return response.json();
}

View File

@@ -0,0 +1,8 @@
import { getProjects } from '$lib/server/database.js';
export async function load() {
const projects = await getProjects();
return {
projects: projects
};
}

View File

@@ -0,0 +1,12 @@
<script>
export let data;
</script>
<nav>
<a href="/">home</a> |
{#each data.projects as project}
<a href="/{project}">{project}</a>&nbsp;
{/each}
</nav>
<slot/>

View File

@@ -0,0 +1 @@
<h1>home</h1>

View File

@@ -0,0 +1,16 @@
import { error } from '@sveltejs/kit';
import { getItems, getProject, getProjects } from '$lib/server/database.js';
export async function load({ params, depends }) {
depends('data:items');
const projects = await getProjects();
if (!projects.includes(params.project)) throw error(404);
const items = await getItems(params.project);
const project = await getProject(params.project);
return {
title: params.project,
project: project,
items: items
};
}

View File

@@ -0,0 +1,7 @@
<script>
export let data;
</script>
<h1>{data.title}</h1>
<slot/>

View File

@@ -0,0 +1,12 @@
<script>
import { enhance } from '$app/forms';
import Table from '$lib/components/Table.svelte';
export let data;
</script>
<Table project={data.project} items={data.items} title={data.title}/>
<form action="/{data.title}/add" method="GET">
<input type="submit" value="Add"/>
</form>

View File

View File

@@ -0,0 +1,8 @@
import { getItem } from '$lib/server/database.js';
export async function load({ params, depends }) {
depends('data:item');
const item = await getItem(params.project, params.id);
return { item };
}

View File

@@ -0,0 +1,7 @@
<script>
import EditForm from '$lib/components/EditForm.svelte';
export let data;
</script>
<EditForm project={data.project} title={data.title} item={data.item}/>

View File

@@ -0,0 +1,14 @@
import { deleteItem, updateItem } from '$lib/server/database.js';
import { redirect } from '@sveltejs/kit';
import { json } from '@sveltejs/kit';
export async function PUT({ params, request }) {
const updateData = await request.formData();
const item = await updateItem(params.project, params.id, updateData);
return json(item);
}
export async function DELETE({ params }) {
const item = await deleteItem(params.project, params.id);
return json(item);
}

View File

@@ -0,0 +1,12 @@
import { readFile, deleteFile } from '$lib/server/database.js';
import { json } from '@sveltejs/kit';
export async function DELETE({params}) {
const response = await deleteFile(params.project, params.id, params.field);
return json(response);
}
export async function GET({params}) {
const response = await readFile(params.project, params.id, params.field);
return new Response(response.body);
}

View File

@@ -0,0 +1,8 @@
import { fail, redirect } from '@sveltejs/kit';
import { getEmptyItem } from '$lib/server/database.js';
export async function load({ params }) {
const item = await getEmptyItem(params.project);
return { item };
}

View File

@@ -0,0 +1,8 @@
<script>
import { enhance } from '$app/forms';
import AddForm from '$lib/components/AddForm.svelte';
export let data;
</script>
<AddForm project={data.project} title={data.title} item={data.item}/>

View File

@@ -0,0 +1,8 @@
import { createItem } from '$lib/server/database.js';
import { json } from '@sveltejs/kit';
export async function POST({ params, request }) {
const data = await request.formData();
const item = await createItem(params.project, data);
return json(item);
}

BIN
front/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

10
front/svelte.config.js Normal file
View File

@@ -0,0 +1,10 @@
import adapter from '@sveltejs/adapter-auto';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter()
}
};
export default config;

6
front/vite.config.js Normal file
View File

@@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});