summaryrefslogtreecommitdiff
path: root/fileUtils.ts
diff options
context:
space:
mode:
authorgrothedev <grothedev@gmail.com>2025-12-28 15:41:41 -0500
committergrothedev <grothedev@gmail.com>2025-12-28 15:41:41 -0500
commit163be506b1e7102a7e96f79b7d919c3561095a38 (patch)
tree5d689f37a7d129f83378107029c7beb777c01e5b /fileUtils.ts
claude code deno fileupload project yeeee
Diffstat (limited to 'fileUtils.ts')
-rw-r--r--fileUtils.ts161
1 files changed, 161 insertions, 0 deletions
diff --git a/fileUtils.ts b/fileUtils.ts
new file mode 100644
index 0000000..6360a95
--- /dev/null
+++ b/fileUtils.ts
@@ -0,0 +1,161 @@
+import { config } from "./config.ts";
+import { logger } from "./logger.ts";
+
+export async function ensureUploadDir() {
+ try {
+ await Deno.mkdir(config.uploadDir, { recursive: true });
+ } catch (error) {
+ if (!(error instanceof Deno.errors.AlreadyExists)) {
+ throw error;
+ }
+ }
+}
+
+export async function getUniqueFilename(filename: string): Promise<string> {
+ const filepath = `${config.uploadDir}/${filename}`;
+
+ try {
+ await Deno.stat(filepath);
+ // File exists, append epoch seconds
+ const epoch = Math.floor(Date.now() / 1000);
+ const parts = filename.split(".");
+ if (parts.length > 1) {
+ const ext = parts.pop();
+ return `${parts.join(".")}_${epoch}.${ext}`;
+ }
+ return `${filename}_${epoch}`;
+ } catch (error) {
+ if (error instanceof Deno.errors.NotFound) {
+ return filename;
+ }
+ throw error;
+ }
+}
+
+export async function listUploadedFiles() {
+ const files: Array<{ name: string; size: number; uploaded: Date }> = [];
+
+ try {
+ for await (const entry of Deno.readDir(config.uploadDir)) {
+ if (entry.isFile) {
+ const stat = await Deno.stat(`${config.uploadDir}/${entry.name}`);
+ files.push({
+ name: entry.name,
+ size: stat.size,
+ uploaded: stat.mtime || new Date(),
+ });
+ }
+ }
+ } catch (error) {
+ if (!(error instanceof Deno.errors.NotFound)) {
+ logger.error("Error listing files", { error: String(error) });
+ }
+ }
+
+ return files.sort((a, b) => b.uploaded.getTime() - a.uploaded.getTime());
+}
+
+export async function cleanupExpiredFiles() {
+ if (config.fileExpiration === 0) return;
+
+ const now = Date.now();
+ let cleaned = 0;
+
+ try {
+ for await (const entry of Deno.readDir(config.uploadDir)) {
+ if (entry.isFile) {
+ const filepath = `${config.uploadDir}/${entry.name}`;
+ const stat = await Deno.stat(filepath);
+ const age = now - (stat.mtime?.getTime() || 0);
+
+ if (age > config.fileExpiration) {
+ await Deno.remove(filepath);
+ cleaned++;
+ logger.info("Expired file removed", { filename: entry.name, age });
+ }
+ }
+ }
+ } catch (error) {
+ logger.error("Error during file cleanup", { error: String(error) });
+ }
+
+ if (cleaned > 0) {
+ logger.info("File cleanup completed", { filesRemoved: cleaned });
+ }
+}
+
+// Run cleanup every hour
+setInterval(() => cleanupExpiredFiles(), 60 * 60 * 1000);
+
+export function isAllowedFileType(contentType: string): boolean {
+ if (config.allowedFileTypes.includes("*/*")) {
+ return true;
+ }
+ return config.allowedFileTypes.includes(contentType);
+}
+
+export function formatFileSize(bytes: number): string {
+ if (bytes === 0) return "0 Bytes";
+ const k = 1024;
+ const sizes = ["Bytes", "KB", "MB", "GB"];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
+}
+
+export function getMimeType(filename: string): string {
+ const ext = filename.split(".").pop()?.toLowerCase();
+
+ const mimeTypes: Record<string, string> = {
+ // Images
+ "jpg": "image/jpeg",
+ "jpeg": "image/jpeg",
+ "png": "image/png",
+ "gif": "image/gif",
+ "webp": "image/webp",
+ "svg": "image/svg+xml",
+ "bmp": "image/bmp",
+ "ico": "image/x-icon",
+
+ // Videos
+ "mp4": "video/mp4",
+ "webm": "video/webm",
+ "ogg": "video/ogg",
+ "ogv": "video/ogg",
+ "avi": "video/x-msvideo",
+ "mov": "video/quicktime",
+ "mkv": "video/x-matroska",
+ "m4v": "video/x-m4v",
+ "mpeg": "video/mpeg",
+ "mpg": "video/mpeg",
+
+ // Audio
+ "mp3": "audio/mpeg",
+ "wav": "audio/wav",
+ "ogg": "audio/ogg",
+ "m4a": "audio/mp4",
+ "flac": "audio/flac",
+
+ // Documents
+ "pdf": "application/pdf",
+ "txt": "text/plain",
+ "html": "text/html",
+ "css": "text/css",
+ "js": "application/javascript",
+ "json": "application/json",
+
+ // Archives
+ "zip": "application/zip",
+ "tar": "application/x-tar",
+ "gz": "application/gzip",
+ };
+
+ return mimeTypes[ext || ""] || "application/octet-stream";
+}
+
+export function isViewableInBrowser(mimeType: string): boolean {
+ return mimeType.startsWith("image/") ||
+ mimeType.startsWith("video/") ||
+ mimeType.startsWith("audio/") ||
+ mimeType === "application/pdf" ||
+ mimeType === "text/plain";
+}