feat: Framework for profile uploads with Minio

This commit is contained in:
April Hall 2025-02-21 18:35:00 -05:00
parent 6adf4afcd7
commit d0ee1296c4
Signed by: arithefirst
GPG Key ID: 4508A15C4DB91C5B
7 changed files with 92 additions and 3 deletions

3
.gitignore vendored
View File

@ -29,3 +29,6 @@ vite.config.ts.timestamp-*
# Sqlite
src/lib/server/db/users.db*
# Storage
minio-storage

BIN
bun.lockb

Binary file not shown.

View File

@ -49,12 +49,14 @@
"@sveltejs/adapter-node": "^5.2.12",
"@types/better-sqlite3": "^7.6.12",
"@types/express": "^5.0.0",
"@types/minio": "^7.1.1",
"better-auth": "^1.1.16",
"better-sqlite3": "^11.8.1",
"cassandra-driver": "^4.7.2",
"express": "^4.21.2",
"lucide-svelte": "^0.474.0",
"markdown-it-highlightjs": "^4.2.0",
"minio": "^8.0.4",
"mode-watcher": "^0.5.1",
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1",

View File

@ -1,12 +1,26 @@
<script lang="ts">
import { generateStream } from '$lib/functions/generateReadableStream';
import { Button } from '$lib/components/ui/button/index';
import { Input } from '$lib/components/ui/input/index';
let files: FileList;
function submit() {
if (files.length === 0) return;
generateStream(files[0]);
}
</script>
<form class="grid w-full items-start gap-3">
<form class="grid w-full items-start gap-3" onsubmit={submit}>
<fieldset class="grid w-full gap-3 rounded-lg border p-4">
<legend class="-ml-1 px-1 text-sm font-medium"> Upload Profile Image </legend>
<Input type="file" accept="image/jpeg, image/png" />
<input
class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0
file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1
focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
type="file"
accept="image/jpeg, image/png"
bind:files
/>
<Button type="submit">Update Profile Photo</Button>
</fieldset>
</form>

View File

@ -0,0 +1,13 @@
export async function generateStream(file: File): Promise<boolean> {
try {
await fetch(`/api/upload/`, {
method: 'POST',
body: file,
});
return true;
} catch (e) {
console.error(`Error sending stream: ${(e as Error).message}`);
return false;
}
}

View File

@ -0,0 +1,54 @@
import * as Minio from 'minio';
import { Readable } from 'stream';
import { v4 } from 'uuid';
interface ClientParams {
endPoint: string;
port: number;
accessKey: string;
secretKey: string;
useSSL: boolean;
}
class MinioClient {
private client: Minio.Client;
constructor(params: ClientParams) {
this.client = new Minio.Client({
endPoint: params.endPoint,
port: params.port,
useSSL: params.useSSL,
accessKey: params.accessKey,
secretKey: params.secretKey,
});
}
async uploadProfile(stream: Readable) {
try {
const bucket = 'profile-photos';
if (await !this.client.bucketExists(bucket)) {
await this.client.makeBucket(bucket, 'us-east-1');
console.log('Bucket "' + bucket + '" created in "us-east-1".');
}
const upload = await this.client.putObject(bucket, v4(), stream);
console.log(upload);
} catch (e) {
console.error(`Error uploading file: ${(e as Error).message}`);
}
}
}
let fsClient: MinioClient | undefined;
if (process.env.BUILDING !== 'true') {
fsClient = new MinioClient({
endPoint: 'localhost',
port: 9000,
accessKey: 'minioadmin',
secretKey: 'minioadmin',
useSSL: false,
});
}
export { fsClient };

View File

@ -0,0 +1,3 @@
export const POST = async () => {
return new Response(undefined, { status: 204 });
};