private uploads
This commit is contained in:
parent
6ea5c20cf3
commit
5468158b51
12 changed files with 69 additions and 28 deletions
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `internalName` to the `Upload` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `Upload` ADD COLUMN `internalName` LONGTEXT NOT NULL;
|
|
@ -52,6 +52,7 @@ model Upload {
|
||||||
uploaderId Int
|
uploaderId Int
|
||||||
|
|
||||||
fileName String @db.LongText
|
fileName String @db.LongText
|
||||||
|
internalName String @db.LongText
|
||||||
public Boolean @default(true)
|
public Boolean @default(true)
|
||||||
uploaded DateTime @default(now())
|
uploaded DateTime @default(now())
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,16 +27,15 @@
|
||||||
<div class="rounded-md transition-all bg-mantle">
|
<div class="rounded-md transition-all bg-mantle">
|
||||||
<div
|
<div
|
||||||
in:fade|global={{ delay: 100 * i }}
|
in:fade|global={{ delay: 100 * i }}
|
||||||
class="flex place-content-between px-1.5 w-full h-14 rounded-md transition-all {error
|
class="flex place-content-between px-1.5 w-full h-14 rounded-md transition-all
|
||||||
? 'bg-red-300 dark:bg-red-700/60'
|
{url ? 'bg-blue/30' : ''} {error ? 'bg-red/30' : ''}"
|
||||||
: ''}"
|
style={error || url
|
||||||
style={error
|
|
||||||
? ''
|
? ''
|
||||||
: `background: linear-gradient(90deg, rgb(var(--ctp-surface0)) ${percent}%, transparent ${percent}%);`}
|
: `background: linear-gradient(90deg, rgb(var(--ctp-surface0)) ${percent}%, transparent ${percent}%);`}
|
||||||
>
|
>
|
||||||
<div class="flex overflow-x-scroll flex-col my-auto overflow-y-clip">
|
<div class="flex overflow-x-scroll flex-col my-auto overflow-y-clip">
|
||||||
{#if url}
|
{#if url}
|
||||||
<a href={url} class="text-blue">{file.name}</a>
|
<a href={url} class="font-bold text-blue">{file.name}</a>
|
||||||
{:else}
|
{:else}
|
||||||
<p>{file.name}</p>
|
<p>{file.name}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -13,14 +13,17 @@
|
||||||
<div class="flex my-auto md:space-x-6">
|
<div class="flex my-auto md:space-x-6">
|
||||||
<a
|
<a
|
||||||
href="/dashboard"
|
href="/dashboard"
|
||||||
class="hidden md:block flex-none my-auto w-20 fill-text text-xl transition-all hover:scale-105 focus:scale-105 active:scale-95"
|
class="hidden flex-none my-auto w-20 text-xl transition-all md:block fill-text hover:scale-105 focus:scale-105 active:scale-95"
|
||||||
>
|
>
|
||||||
<Logo></Logo>
|
<Logo></Logo>
|
||||||
</a>
|
</a>
|
||||||
<div class="flex my-auto space-x-3">
|
<div class="flex my-auto space-x-3">
|
||||||
<HeaderLink href="/dashboard">Dashboard</HeaderLink>
|
<HeaderLink href="/dashboard">Dashboard</HeaderLink>
|
||||||
<HeaderLink href="/uploads">Uploads</HeaderLink>
|
<HeaderLink href="/uploads">Uploads</HeaderLink>
|
||||||
|
<HeaderLink href="/links">Links</HeaderLink>
|
||||||
|
{#if $user?.role === 'ADMINISTRATOR'}
|
||||||
<HeaderLink href="/admin">Admin</HeaderLink>
|
<HeaderLink href="/admin">Admin</HeaderLink>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex my-auto space-x-2">
|
<div class="flex my-auto space-x-2">
|
||||||
|
|
|
@ -7,12 +7,10 @@ export function goBack() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bytesToHumanReadable(bytes: number) {
|
export function bytesToHumanReadable(bytes: number) {
|
||||||
if (bytes === 0) {
|
let i = bytes == 0 ? 0 : Math.floor(Math.log(bytes) / Math.log(1024));
|
||||||
return '0 B';
|
return (
|
||||||
}
|
+(bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB'][i]
|
||||||
|
);
|
||||||
let e = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
||||||
return (bytes / Math.pow(1024, e)).toFixed(2) + ' ' + ' KMGTP'.charAt(e) + 'B';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function request(
|
export function request(
|
||||||
|
|
|
@ -88,7 +88,12 @@ export async function deleteSession(id: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createUpload(id: string, uploaderId: number, fileName: string) {
|
export async function createUpload(
|
||||||
|
id: string,
|
||||||
|
uploaderId: number,
|
||||||
|
fileName: string,
|
||||||
|
internalName: string
|
||||||
|
) {
|
||||||
const settings = await prisma.userSettings.findFirst({
|
const settings = await prisma.userSettings.findFirst({
|
||||||
where: { id: uploaderId }
|
where: { id: uploaderId }
|
||||||
});
|
});
|
||||||
|
@ -98,6 +103,7 @@ export async function createUpload(id: string, uploaderId: number, fileName: str
|
||||||
id,
|
id,
|
||||||
uploaderId,
|
uploaderId,
|
||||||
fileName,
|
fileName,
|
||||||
|
internalName,
|
||||||
public: settings?.newPostsPublic
|
public: settings?.newPostsPublic
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -113,6 +119,7 @@ export async function getUpload(id: string) {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
fileName: true,
|
fileName: true,
|
||||||
|
internalName: true,
|
||||||
public: true,
|
public: true,
|
||||||
uploaded: true,
|
uploaded: true,
|
||||||
uploader: true
|
uploader: true
|
||||||
|
|
|
@ -25,5 +25,5 @@ function du() {
|
||||||
|
|
||||||
if (!building) {
|
if (!building) {
|
||||||
du();
|
du();
|
||||||
setTimeout(du, 1000 * 60 * 10);
|
setTimeout(du, 1000 * 60 * 5);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,11 @@
|
||||||
import FileComponent from '$lib/components/File.svelte';
|
import FileComponent from '$lib/components/File.svelte';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { request } from '$lib';
|
import { request } from '$lib';
|
||||||
|
import { toast } from 'svelte-sonner';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
console.log(data);
|
||||||
user.set(data?.user);
|
user.set(data?.user);
|
||||||
|
|
||||||
let input: HTMLInputElement,
|
let input: HTMLInputElement,
|
||||||
|
@ -16,6 +19,10 @@
|
||||||
|
|
||||||
let running = false;
|
let running = false;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
fileProgress.set({});
|
||||||
|
});
|
||||||
|
|
||||||
// lazy again
|
// lazy again
|
||||||
function progress(name: string, data: any) {
|
function progress(name: string, data: any) {
|
||||||
let _ = get(fileProgress);
|
let _ = get(fileProgress);
|
||||||
|
@ -37,11 +44,22 @@
|
||||||
const response = await request(body, (percent: number) => {
|
const response = await request(body, (percent: number) => {
|
||||||
progress(k, { percent });
|
progress(k, { percent });
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
|
toast.error(k + ' failed to upload.');
|
||||||
progress(k, { error: true });
|
progress(k, { error: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response && response.success) progress(k, { url: response.body });
|
if (response && response.success) progress(k, { url: response.body });
|
||||||
else progress(k, { error: true });
|
else {
|
||||||
|
if (response && response.body)
|
||||||
|
try {
|
||||||
|
let body = JSON.parse(response.body);
|
||||||
|
toast.error(k + ' failed: ' + body.message);
|
||||||
|
} catch (_) {
|
||||||
|
toast.error(k + ' failed to upload.');
|
||||||
|
}
|
||||||
|
|
||||||
|
progress(k, { error: true });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +87,7 @@
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold">Welcome, {data.user.username}.</h1>
|
<h1 class="text-2xl font-bold">Welcome, {data.user.username}.</h1>
|
||||||
<p class="text-overlay1">
|
<p class="text-overlay1">
|
||||||
Your max upload size is <span class="font-bold">{data.user.maxUploadMB} MB</span>.
|
Your max upload size is <span class="font-bold">{data.user.maxUploadMB} MiB</span>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 p-2 mx-auto w-full rounded-lg shadow-lg bg-crust">
|
<div class="flex flex-col gap-2 p-2 mx-auto w-full rounded-lg shadow-lg bg-crust">
|
||||||
|
|
0
src/routes/(app)/links/+page.svelte
Normal file
0
src/routes/(app)/links/+page.svelte
Normal file
|
@ -15,15 +15,16 @@ export const POST = async ({ request, cookies }) => {
|
||||||
const data = await request.formData();
|
const data = await request.formData();
|
||||||
const file = data.get('file') as File;
|
const file = data.get('file') as File;
|
||||||
|
|
||||||
if (Math.floor(file.size / (1024 * 1024)) > user.maxUploadMB)
|
if (file.size / 1048576 >= user.maxUploadMB)
|
||||||
return error(413, { status: 413, message: 'Content Too Large' });
|
return error(413, { status: 413, message: 'Content Too Large' });
|
||||||
|
|
||||||
let id = generateId(undefined, 10);
|
let id = generateId(undefined, 10);
|
||||||
|
let internalName = `${Date.now()}-${file.name}`;
|
||||||
|
|
||||||
const object = await minio
|
const object = await minio
|
||||||
.putObject(
|
.putObject(
|
||||||
BUCKET,
|
BUCKET,
|
||||||
`${user.id}/${file.name}`,
|
`${user.id}/${internalName}`,
|
||||||
Buffer.from(await file.arrayBuffer()),
|
Buffer.from(await file.arrayBuffer()),
|
||||||
file.size,
|
file.size,
|
||||||
{
|
{
|
||||||
|
@ -34,7 +35,7 @@ export const POST = async ({ request, cookies }) => {
|
||||||
if (!object)
|
if (!object)
|
||||||
return error(500, { status: 500, message: 'Internal Server Error - Contact Administrator' });
|
return error(500, { status: 500, message: 'Internal Server Error - Contact Administrator' });
|
||||||
|
|
||||||
const objectRecord = await createUpload(id, user.id, file.name);
|
const objectRecord = await createUpload(id, user.id, file.name, internalName);
|
||||||
if (!objectRecord)
|
if (!objectRecord)
|
||||||
return error(500, { status: 500, message: 'Internal Server Error - Contact Administrator' });
|
return error(500, { status: 500, message: 'Internal Server Error - Contact Administrator' });
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,17 @@ import { getUpload } from '$lib/server/database.js';
|
||||||
import minio, { BUCKET } from '$lib/server/minio';
|
import minio, { BUCKET } from '$lib/server/minio';
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const GET = async ({ params }) => {
|
export const GET = async ({ params, locals }) => {
|
||||||
const id = params.id;
|
const id = params.id;
|
||||||
|
|
||||||
const file = await getUpload(id);
|
const file = await getUpload(id);
|
||||||
if (!file) throw error(404, { status: 404, message: 'File Not Found' });
|
if (!file) throw error(404, { status: 404, message: 'File Not Found' });
|
||||||
|
|
||||||
const object = await minio.getObject(BUCKET, `${file.uploader.id}/${file.fileName}`);
|
if (!file.public && locals?.user?.id !== file.uploader.id)
|
||||||
const metadata = await minio.statObject(BUCKET, `${file.uploader.id}/${file.fileName}`);
|
throw error(403, { status: 403, message: 'Forbidden' });
|
||||||
|
|
||||||
|
const object = await minio.getObject(BUCKET, `${file.uploader.id}/${file.internalName}`);
|
||||||
|
const metadata = await minio.statObject(BUCKET, `${file.uploader.id}/${file.internalName}`);
|
||||||
|
|
||||||
const ac = new AbortController();
|
const ac = new AbortController();
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,17 @@ import { getSettings, getUpload } from '$lib/server/database';
|
||||||
import minio, { BUCKET } from '$lib/server/minio';
|
import minio, { BUCKET } from '$lib/server/minio';
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
|
|
||||||
export async function load({ params }) {
|
export async function load({ params, locals }) {
|
||||||
const file = await getUpload(params.id);
|
const file = await getUpload(params.id);
|
||||||
if (!file) throw error(404, { status: 404, message: 'File Not Found' });
|
if (!file) throw error(404, { status: 404, message: 'File Not Found' });
|
||||||
|
|
||||||
|
if (!file.public && locals?.user?.id !== file.uploader.id)
|
||||||
|
throw error(403, { status: 403, message: 'Forbidden' });
|
||||||
|
|
||||||
const settings = await getSettings(file.uploader.id);
|
const settings = await getSettings(file.uploader.id);
|
||||||
if (!settings) throw error(500, { status: 500, message: 'Internal Server Error' });
|
if (!settings) throw error(500, { status: 500, message: 'Internal Server Error' });
|
||||||
|
|
||||||
const metadata = await minio.statObject(BUCKET, `${file.uploader.id}/${file.fileName}`);
|
const metadata = await minio.statObject(BUCKET, `${file.uploader.id}/${file.internalName}`);
|
||||||
|
|
||||||
function formatString(input: string) {
|
function formatString(input: string) {
|
||||||
if (file && metadata)
|
if (file && metadata)
|
||||||
|
|
Loading…
Reference in a new issue