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])
uploaderId Int
fileName String @db.LongText
public Boolean @default(true)
uploaded DateTime @default(now())
fileName String @db.LongText
internalName String @db.LongText
public Boolean @default(true)
uploaded DateTime @default(now())
}
enum Role {

View file

@ -27,16 +27,15 @@
<div class="rounded-md transition-all bg-mantle">
<div
in:fade|global={{ delay: 100 * i }}
class="flex place-content-between px-1.5 w-full h-14 rounded-md transition-all {error
? 'bg-red-300 dark:bg-red-700/60'
: ''}"
style={error
class="flex place-content-between px-1.5 w-full h-14 rounded-md transition-all
{url ? 'bg-blue/30' : ''} {error ? 'bg-red/30' : ''}"
style={error || url
? ''
: `background: linear-gradient(90deg, rgb(var(--ctp-surface0)) ${percent}%, transparent ${percent}%);`}
>
<div class="flex overflow-x-scroll flex-col my-auto overflow-y-clip">
{#if url}
<a href={url} class="text-blue">{file.name}</a>
<a href={url} class="font-bold text-blue">{file.name}</a>
{:else}
<p>{file.name}</p>
{/if}

View file

@ -13,14 +13,17 @@
<div class="flex my-auto md:space-x-6">
<a
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>
</a>
<div class="flex my-auto space-x-3">
<HeaderLink href="/dashboard">Dashboard</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 class="flex my-auto space-x-2">

View file

@ -7,12 +7,10 @@ export function goBack() {
}
export function bytesToHumanReadable(bytes: number) {
if (bytes === 0) {
return '0 B';
}
let e = Math.floor(Math.log(bytes) / Math.log(1024));
return (bytes / Math.pow(1024, e)).toFixed(2) + ' ' + ' KMGTP'.charAt(e) + 'B';
let i = bytes == 0 ? 0 : Math.floor(Math.log(bytes) / Math.log(1024));
return (
+(bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB'][i]
);
}
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({
where: { id: uploaderId }
});
@ -98,6 +103,7 @@ export async function createUpload(id: string, uploaderId: number, fileName: str
id,
uploaderId,
fileName,
internalName,
public: settings?.newPostsPublic
}
});
@ -113,6 +119,7 @@ export async function getUpload(id: string) {
select: {
id: true,
fileName: true,
internalName: true,
public: true,
uploaded: true,
uploader: true

View file

@ -25,5 +25,5 @@ function du() {
if (!building) {
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 { get } from 'svelte/store';
import { request } from '$lib';
import { toast } from 'svelte-sonner';
import { onMount } from 'svelte';
export let data;
console.log(data);
user.set(data?.user);
let input: HTMLInputElement,
@ -16,6 +19,10 @@
let running = false;
onMount(() => {
fileProgress.set({});
});
// lazy again
function progress(name: string, data: any) {
let _ = get(fileProgress);
@ -37,11 +44,22 @@
const response = await request(body, (percent: number) => {
progress(k, { percent });
}).catch(() => {
toast.error(k + ' failed to upload.');
progress(k, { error: true });
});
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>
<h1 class="text-2xl font-bold">Welcome, {data.user.username}.</h1>
<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>
</div>
<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 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' });
let id = generateId(undefined, 10);
let internalName = `${Date.now()}-${file.name}`;
const object = await minio
.putObject(
BUCKET,
`${user.id}/${file.name}`,
`${user.id}/${internalName}`,
Buffer.from(await file.arrayBuffer()),
file.size,
{
@ -34,7 +35,7 @@ export const POST = async ({ request, cookies }) => {
if (!object)
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)
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 { error } from '@sveltejs/kit';
export const GET = async ({ params }) => {
export const GET = async ({ params, locals }) => {
const id = params.id;
const file = await getUpload(id);
if (!file) throw error(404, { status: 404, message: 'File Not Found' });
const object = await minio.getObject(BUCKET, `${file.uploader.id}/${file.fileName}`);
const metadata = await minio.statObject(BUCKET, `${file.uploader.id}/${file.fileName}`);
if (!file.public && locals?.user?.id !== file.uploader.id)
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();

View file

@ -2,14 +2,17 @@ import { getSettings, getUpload } from '$lib/server/database';
import minio, { BUCKET } from '$lib/server/minio';
import { error } from '@sveltejs/kit';
export async function load({ params }) {
export async function load({ params, locals }) {
const file = await getUpload(params.id);
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);
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) {
if (file && metadata)