Initial commit
All checks were successful
/ Build App (push) Successful in 49s

This commit is contained in:
2024-07-17 00:26:05 +02:00
commit 9c869d8ee2
28 changed files with 5314 additions and 0 deletions

11
src/app.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}

12
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>

15
src/app.scss Normal file
View File

@ -0,0 +1,15 @@
/* Override global Sass variables from the /utilities folder */
@use 'bulma/sass/utilities' with (
$link: $pink
);
@use 'bulma/sass/base' with (
$body-overflow-y: auto
);
/* Import the components you need */
@use 'bulma/sass/elements';
@use 'bulma/sass/form';
@use 'bulma/sass/components';
@use 'bulma/sass/grid';
@use 'bulma/sass/helpers';
@use 'bulma/sass/layout';
@use 'bulma/sass/themes';

8
src/index.test.ts Normal file
View File

@ -0,0 +1,8 @@
import safePath from '$lib';
import { describe, it, expect } from 'vitest';
describe('safe path', () => {
it('removes non alphanum from string', () => {
expect(safePath('../../!=-.,/abc123')).toBe('abc123');
});
});

5
src/lib/index.ts Normal file
View File

@ -0,0 +1,5 @@
// place files you want to import through the `$lib` alias in this folder.
const safePath = (input: string) => input.replace(/\W/g, '');
export default safePath;

9
src/routes/+error.svelte Normal file
View File

@ -0,0 +1,9 @@
<script>
import { page } from '$app/stores';
</script>
{#if $page.status === 404}
<div class="box">
<h3>Seite nicht gefunden. <a href="/">Zurück zur Startseite.</a></h3>
</div>
{/if}

47
src/routes/+layout.svelte Normal file
View File

@ -0,0 +1,47 @@
<script>
import '../app.scss';
</script>
<header>
<section class="section">
<div class="container is-max-desktop">
<div class="columns">
<div class="column is-two-thirds is-offset-one-fifth">
<h1 class="title">Gabi und Hannes Fotochallenge</h1>
<h2 class="subtitle">Lösungen für die Fotochallenge</h2>
</div>
</div>
</div>
</section>
</header>
<main id="wrapper">
<section class="section">
<div class="container is-max-desktop">
<div id="content">
<div class="columns">
<div class="column is-half is-offset-one-quarter">
<slot />
</div>
</div>
</div>
</div>
</section>
</main>
<style>
:global(body) {
display: flex;
min-height: 100vh;
flex-direction: column;
}
#wrapper {
flex: 1;
}
#content {
text-align: justify;
hyphens: auto;
}
</style>

View File

@ -0,0 +1,60 @@
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { fail } from '@sveltejs/kit';
import type { RequestEvent } from './$types';
import safePath from '$lib';
import { hash } from 'crypto';
import path from 'path';
const storagePath: string = './uploads';
const mkdirIfNotExists = (path: string) => {
if (!existsSync(path)) {
mkdirSync(path);
}
};
export const actions = {
default: async ({ request }: RequestEvent) => {
const data = await request.formData();
const formFiles = data.getAll('files');
if (!formFiles) {
return fail(400, { field: 'files', files: formFiles, missing: true });
} else if (!(formFiles as File[])) {
return fail(400, { field: 'files', files: formFiles, incorrect: true });
}
const files = formFiles as File[];
console.log(files);
if (files.length === 0) {
return fail(400, { field: 'files', files: formFiles, empty: true });
}
const formName = data.get('name');
if (!formName) {
return fail(400, { field: 'name', name: formName, missing: true });
} else if (!(formName as string)) {
return fail(400, { field: 'name', name: formName, incorrect: true });
}
const name = safePath(formName as string);
files.forEach(async (file) => {
const outPath = `${storagePath}/${name}`;
const content = Buffer.from(await file.arrayBuffer());
const ext = path.extname(file.name);
mkdirIfNotExists(outPath);
const filename = hash('sha1', content);
const fullPath = `${outPath}/${filename}${ext}`;
if (existsSync(fullPath)) {
console.warn(`${fullPath} has already been uploaded. Skipping...`);
} else {
writeFileSync(fullPath, Buffer.from(await file.arrayBuffer()), { flag: 'a+' });
}
});
return {
success: true
};
}
};

94
src/routes/+page.svelte Normal file
View File

@ -0,0 +1,94 @@
<script lang="ts">
import { enhance } from '$app/forms';
import type { ActionData } from './$types';
export let form: ActionData;
let files: FileList;
let sending = false;
const siPrefixes = new Map([
[1_000_000, 'M'],
[1_000, 'k']
]);
const fileSize = (files: FileList) => {
const size = Array.from(files)
.map((f) => f.size)
.reduce((a, b) => a + b, 0);
return (
Array.from(siPrefixes)
.filter(([k]) => size >= k)
.map(([k, v]) => `${(size / k).toFixed(1)} ${v}B`)[0] ?? `${size} bytes`
);
};
</script>
<form
enctype="multipart/form-data"
class="box"
method="POST"
use:enhance={() => {
sending = true;
return ({ update }) => {
update({ invalidateAll: true }).finally(async () => {
sending = false;
});
};
}}
>
{#if sending}
<div class="notification is-info">Wird hochgeladen...</div>
{:else if form?.success}
<div class="notification is-success">Erfolgreich hochgeladen</div>
{/if}
<div class="field">
<label for="name" class="label">Name</label>
<div class="control">
<input
id="name"
class="input"
type="text"
name="name"
placeholder="Name"
value={form?.name ?? ''}
required
/>
</div>
{#if form?.field === 'name'}
{#if form?.missing}
<p class="help is-danger">Bitte einen Namen angeben</p>
{:else if form?.incorrect}
<p class="help is-danger">Ungültiger Name</p>
{/if}
{/if}
</div>
<div class="file is-centered has-name is-boxed">
<label class="file-label">
<input class="file-input" type="file" name="files" bind:files multiple required />
<span class="file-cta">
<span class="file-label">Fotos auswählen...</span>
</span>
{#if files}
<span class="file-name"
>{files.length} Bild{#if files.length > 1}er{/if} ausgewählt ({fileSize(files)})</span
>
{:else}
<span class="file-name">Keine Bilder ausgewählt</span>
{/if}
{#if form?.field === 'files'}
{#if form?.missing || form?.empty}
<p class="help is-danger">Bitte mindestens eine Datei auswählen</p>
{:else if form?.incorrect}
<p class="help is-danger">Ungültige Dateien</p>
{/if}
{/if}
</label>
</div>
<div class="field is-grouped is-grouped-centered">
<div class="control">
<button class="button is-link">Hochladen</button>
</div>
</div>
</form>

2
src/variables.scss Normal file
View File

@ -0,0 +1,2 @@
/* Set your brand colors */
$pink: pink;