private uploads

This commit is contained in:
cirroskais 2024-07-06 01:45:06 -04:00
parent 6ea5c20cf3
commit 5468158b51
No known key found for this signature in database
GPG key ID: 5FC73EBF2678E33D
12 changed files with 69 additions and 28 deletions

View file

@ -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;

View file

@ -51,9 +51,10 @@ model Upload {
uploader User @relation(fields: [uploaderId], references: [id]) uploader User @relation(fields: [uploaderId], references: [id])
uploaderId Int uploaderId Int
fileName String @db.LongText fileName String @db.LongText
public Boolean @default(true) internalName String @db.LongText
uploaded DateTime @default(now()) public Boolean @default(true)
uploaded DateTime @default(now())
} }
enum Role { enum Role {

View file

@ -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}

View file

@ -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="/admin">Admin</HeaderLink> <HeaderLink href="/links">Links</HeaderLink>
{#if $user?.role === 'ADMINISTRATOR'}
<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">

View file

@ -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(

View file

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

View file

@ -25,5 +25,5 @@ function du() {
if (!building) { if (!building) {
du(); du();
setTimeout(du, 1000 * 60 * 10); setTimeout(du, 1000 * 60 * 5);
} }

View file

@ -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">

View file

View 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' });

View file

@ -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();

View file

@ -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)