From 8be819bf912001800861ca760f5584c54334e416 Mon Sep 17 00:00:00 2001 From: Mike Smith <89040888+smiggiddy@users.noreply.github.com> Date: Sat, 14 Jun 2025 15:22:03 -0400 Subject: [PATCH] more updates and changes --- .dockerignore | 133 ++++++++++++++++++ file-uploader/prisma/schema.prisma | 21 +-- file-uploader/public/css/style.css | 27 +++- .../src/controllers/fileController.js | 100 ++++++++----- file-uploader/src/middlewares/auth.js | 1 + file-uploader/src/models/file.js | 46 +++++- file-uploader/src/routes/fileRouter.js | 1 + file-uploader/src/services/fileService.js | 21 ++- file-uploader/src/utils/formatHelpers.js | 32 +++++ file-uploader/src/views/main.ejs | 36 +++-- .../src/views/partials/directoryListing.ejs | 53 +++++-- file-uploader/src/views/partials/fileInfo.ejs | 10 ++ .../src/views/partials/fileUpload.ejs | 28 ++-- .../src/views/partials/newDirectory.ejs | 10 ++ .../src/views/partials/parentDirectories.ejs | 5 +- 15 files changed, 426 insertions(+), 98 deletions(-) create mode 100644 .dockerignore create mode 100644 file-uploader/src/utils/formatHelpers.js create mode 100644 file-uploader/src/views/partials/fileInfo.ejs create mode 100644 file-uploader/src/views/partials/newDirectory.ejs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a3bd578 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,133 @@ +*.db +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +.DS_STORE diff --git a/file-uploader/prisma/schema.prisma b/file-uploader/prisma/schema.prisma index 9b55480..20808ad 100644 --- a/file-uploader/prisma/schema.prisma +++ b/file-uploader/prisma/schema.prisma @@ -25,16 +25,17 @@ model User { } model File { - id String @id @default(uuid()) - name String @db.VarChar(255) - url String - size Int - mimetype String - createdAt DateTime @default(now()) - owner User? @relation(fields: [ownerId], references: [id]) - ownerId String? - folder Folder? @relation(fields: [folderId], references: [id]) - folderId String? + id String @id @default(uuid()) + name String @db.VarChar(255) + url String + size Int + mimetype String + createdAt DateTime @default(now()) + modifiedAt DateTime @default("2020-03-19T14:21:00+02:00") + owner User? @relation(fields: [ownerId], references: [id]) + ownerId String? + folder Folder? @relation(fields: [folderId], references: [id]) + folderId String? } model Folder { diff --git a/file-uploader/public/css/style.css b/file-uploader/public/css/style.css index ab92e28..cefe11d 100644 --- a/file-uploader/public/css/style.css +++ b/file-uploader/public/css/style.css @@ -24,6 +24,11 @@ body { -webkit-font-smoothing: antialiased; } +html, +body { + height: 100%; +} + /* 6. Improve media defaults */ img, picture, @@ -134,9 +139,19 @@ input[type='file']:focus::file-selector-button { } } -/* .file { */ -/* opacity: 0; */ -/* width: 0.1px; */ -/* height: 0.1px; */ -/* position: absolute; */ -/* } */ +main { + display: flex; + align-items: center; + justify-content: center; +} +.flex { + display: flex; +} + +.directory-grid { + display: grid; + grid-template-columns: 1fr 1fr 1fr; +} + +@media screen and (max-width: 600px) { +} diff --git a/file-uploader/src/controllers/fileController.js b/file-uploader/src/controllers/fileController.js index f6d1f58..a95d5ba 100644 --- a/file-uploader/src/controllers/fileController.js +++ b/file-uploader/src/controllers/fileController.js @@ -1,17 +1,16 @@ const multer = require('multer'); -const Db = require('../models/db'); const { - uploadFile, + uploadToStorage, mkDirectory, getDirectoryContents, - getProperPath, + createFileRecord, + getFileMetaData, + editFile, } = require('../services/fileService'); -// const storage = multer.memoryStorage(); -// const upload = multer({ storage: storage }); +const { getFullUploadPath, formatBytes } = require('../utils/formatHelpers'); +const { Prisma } = require('@prisma/client'); const upload = multer({ dest: '/tmp/odin/' }); -const db = new Db(); - const createDirectory = async (req, res) => { const { parentId, 'directory-name': directoryName } = req.body; const referer = req.get('referer'); @@ -28,35 +27,45 @@ const fileUpload = async (req, res) => { const { folderId, folderName } = req.body; const referer = req.get('referer'); - let fullPath = await getProperPath(folderId); - fullPath = - fullPath === '/' ? `/${folderName}` : fullPath + `/${folderName}`; - - const path = `${req.user.username}/${fullPath}/${req.file.originalname}`; - const file = { - name: req.file.originalname, - path: path, - folderId: folderId, - mimetype: req.file.mimetype, - size: req.file.size, - }; - const data = { - name: req.file.originalname, - path: path, - data: req.file.filename, - }; - try { - const supabaseResult = await uploadFile(data); - if (supabaseResult.error === null) { - const result = await db.file.createFile(req.user.id, file); - console.log(result); + const path = await getFullUploadPath( + folderId, + folderName, + req.user.username, + req.file.originalname, + ); + + const fileMetaData = { + name: req.file.originalname, + path: path, + folderId: folderId, + mimetype: req.file.mimetype, + size: req.file.size, + }; + const data = { + name: req.file.originalname, + path: path, + data: req.file.filename, + }; + + const supabaseResult = await uploadToStorage(data); + if (!supabaseResult.error) { + // make sure the uploaded file and the db have the same ID + fileMetaData.id = supabaseResult.data.id; + const result = await createFileRecord(req.user.id, fileMetaData); + if (result instanceof Prisma.PrismaClientKnownRequestError) { + // if user uploads the samefile name, just overwrite it + await editFile(fileMetaData); + } + res.redirect(referer); + } else { + console.error(supabaseResult.error); + res.status(500).redirect(referer); } } catch (e) { console.error(e); + res.status(500).redirect(referer); } - - res.redirect(referer); }; const directoryContents = async (req, res) => { @@ -74,6 +83,31 @@ const directoryContents = async (req, res) => { } }; -const getFiles = (req, res) => {}; +const getFileData = async (req, res) => { + const { fileId } = req.query; -module.exports = { createDirectory, upload, fileUpload, directoryContents }; + try { + const file = await getFileMetaData(fileId); + file.size = formatBytes(file.size); + + res.render('partials/fileInfo', { + file: file, + pageTitle: 'FileUpload - File Details', + }); + } catch (e) { + console.error(e); + res.redirect('/'); + } +}; + +const editFilePost = async (req, res) => { + const { id, name, url, modifiedAt, mimetype } = req.body; +}; + +module.exports = { + createDirectory, + upload, + fileUpload, + directoryContents, + getFileData, +}; diff --git a/file-uploader/src/middlewares/auth.js b/file-uploader/src/middlewares/auth.js index 21a4cf4..afef4fb 100644 --- a/file-uploader/src/middlewares/auth.js +++ b/file-uploader/src/middlewares/auth.js @@ -51,6 +51,7 @@ passport.deserializeUser(async (id, done) => { }); const loggedIn = function (req, res, next) { + if (req.isAuthenticated()) console.log('logged in'); if (!req.user) res.redirect('/'); next(); }; diff --git a/file-uploader/src/models/file.js b/file-uploader/src/models/file.js index 574a538..2b62aac 100644 --- a/file-uploader/src/models/file.js +++ b/file-uploader/src/models/file.js @@ -1,4 +1,4 @@ -const { PrismaClient } = require('@prisma/client'); +const { Prisma, PrismaClient } = require('@prisma/client'); class File { constructor() { @@ -87,8 +87,6 @@ class File { } } - async getDirectoriesByUser() {} - async createFile(userId, file) { try { const result = await this.prisma.file.create({ @@ -99,6 +97,48 @@ class File { url: file.path, folderId: file.folderId, ownerId: userId, + id: file.id, + }, + }); + return result; + } catch (e) { + return e; + } + } + + async editFile(file) { + const data = await this.prisma.file.update({ + where: { id: file.id }, + data: { + name: file.name, + size: file.size, + mimetype: file.mimetype, + url: file.path, + modifiedAt: new Date(), + }, + }); + return data; + } + + async deleteFile(userId, fileId) { + try { + const result = await this.prisma.file.delete({ + data: { + id: fileId, + ownerId: userId, + }, + }); + return result; + } catch (e) { + console.error(e); + return e; + } + } + async getFileMetaData(fileId) { + try { + const result = await this.prisma.file.findUnique({ + where: { + id: fileId, }, }); return result; diff --git a/file-uploader/src/routes/fileRouter.js b/file-uploader/src/routes/fileRouter.js index f095dcf..8382642 100644 --- a/file-uploader/src/routes/fileRouter.js +++ b/file-uploader/src/routes/fileRouter.js @@ -11,5 +11,6 @@ fileRouter.post( ); fileRouter.post('/directory', loggedIn, fileController.createDirectory); fileRouter.get('/directory', loggedIn, fileController.directoryContents); +fileRouter.get('/file', loggedIn, fileController.getFileData); module.exports = fileRouter; diff --git a/file-uploader/src/services/fileService.js b/file-uploader/src/services/fileService.js index 2d74ba2..73ce4aa 100644 --- a/file-uploader/src/services/fileService.js +++ b/file-uploader/src/services/fileService.js @@ -8,6 +8,14 @@ const supabase = createClient(supabaseUrl, supabaseKey); const db = new Db(); +async function createFileRecord(userId, fileData) { + return await db.file.createFile(userId, fileData); +} + +async function editFile(file) { + return await db.file.editFile(file); +} + async function getDirectoryContents(directoryId, userId) { return await db.file.getDirectoryContents(directoryId, userId); } @@ -16,10 +24,10 @@ async function getParentDirectories(directoryId) { return await db.file.getParentFolders(directoryId); } -async function uploadFile(file) { +async function uploadToStorage(file) { const { data, error } = await supabase.storage .from('odin') - .upload(file.path, file.data); + .upload(file.path, file.data, { upsert: true }); return { data: data, error: error }; } @@ -52,11 +60,18 @@ async function getProperPath(directoryId) { return directories.join('/'); } +async function getFileMetaData(fileId) { + return await db.file.getFileMetaData(fileId); +} + module.exports = { - uploadFile, + createFileRecord, + editFile, + uploadToStorage, mkDirectory, getParentDirectories, getDirectoryContents, getProperPath, rmDirectory, + getFileMetaData, }; diff --git a/file-uploader/src/utils/formatHelpers.js b/file-uploader/src/utils/formatHelpers.js new file mode 100644 index 0000000..ecb001e --- /dev/null +++ b/file-uploader/src/utils/formatHelpers.js @@ -0,0 +1,32 @@ +const { getProperPath } = require('../services/fileService'); + +const getFullUploadPath = async (folderId, folderName, username, filename) => { + let fullPath = await getProperPath(folderId); + fullPath = fullPath === '/' ? `${folderName}` : fullPath + `/${folderName}`; + + return `${username}/${fullPath}/${filename}`; +}; + +const formatBytes = (bytes, decimals = 2) => { + if (!+bytes) return '0 Bytes'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = [ + 'Bytes', + 'KiB', + 'MiB', + 'GiB', + 'TiB', + 'PiB', + 'EiB', + 'ZiB', + 'YiB', + ]; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; +}; + +module.exports = { formatBytes, getFullUploadPath }; diff --git a/file-uploader/src/views/main.ejs b/file-uploader/src/views/main.ejs index f068957..fe50d4e 100644 --- a/file-uploader/src/views/main.ejs +++ b/file-uploader/src/views/main.ejs @@ -1,20 +1,34 @@ <%- include('partials/header') %> <% if(currentUser) { %> +

Hello <%= currentUser.username %>

sign out - <%- include('partials/fileUpload') %> - <%- include('partials/parentDirectories') %> - <%- include('partials/directoryListing') %> +
+ +
+
+
+ <%- include('partials/newDirectory') %> + <%- include('partials/fileUpload') %> +
+ + + <%- include('partials/parentDirectories') %> + <%- include('partials/directoryListing') %> +
+
<% } else { %> -
- - - - - - Click here to register -
+
+
+ + + + + + Click here to register +
+
<% } %> diff --git a/file-uploader/src/views/partials/directoryListing.ejs b/file-uploader/src/views/partials/directoryListing.ejs index e886de4..97938c0 100644 --- a/file-uploader/src/views/partials/directoryListing.ejs +++ b/file-uploader/src/views/partials/directoryListing.ejs @@ -1,12 +1,41 @@ -
-

Folders

- -

Files:

- <% directoryListing.File.forEach(f => { %> -

<%= f.name %> <%= f.id %>

- <% }); %> -
+
+
+
+

Name

+
+ +
+

Size

+
+ +
+

Modified Last

+
+ + <% + const renderItem = (item, isDirectory) => { + %> +
+ <% if (isDirectory) { %> + d <%= item.name %> / + <% } else { %> + - <%= item.name %> Details + <% } %> +
+
+

<%= item?.size %>

+
+
+

<%= item?.modifiedAt %>

+
+ <% }; %> + + + <% directoryListing.Directories.forEach(d => renderItem(d, true)); %> + + + <% directoryListing.File.forEach(f => renderItem(f, false)); %> +
+ + +
diff --git a/file-uploader/src/views/partials/fileInfo.ejs b/file-uploader/src/views/partials/fileInfo.ejs new file mode 100644 index 0000000..a7446b7 --- /dev/null +++ b/file-uploader/src/views/partials/fileInfo.ejs @@ -0,0 +1,10 @@ +
+ +
+

File: <%= file.name %>

+

Size: <%= file.size %>

+

Created At: <%= file.createdAt %>

+

Modified At: <%= file?.modifiedAt %>

+
+ Back +
diff --git a/file-uploader/src/views/partials/fileUpload.ejs b/file-uploader/src/views/partials/fileUpload.ejs index 35423eb..4f169e5 100644 --- a/file-uploader/src/views/partials/fileUpload.ejs +++ b/file-uploader/src/views/partials/fileUpload.ejs @@ -1,18 +1,12 @@ -
-
- - > - > - -
-
- - -
-
- - - > - +
+

New upload

+ +
+ + > + > +
- + +
+ diff --git a/file-uploader/src/views/partials/newDirectory.ejs b/file-uploader/src/views/partials/newDirectory.ejs new file mode 100644 index 0000000..8b23ee3 --- /dev/null +++ b/file-uploader/src/views/partials/newDirectory.ejs @@ -0,0 +1,10 @@ +
+
+
+ + +
+ > + +
+
diff --git a/file-uploader/src/views/partials/parentDirectories.ejs b/file-uploader/src/views/partials/parentDirectories.ejs index fb20af8..e9524d6 100644 --- a/file-uploader/src/views/partials/parentDirectories.ejs +++ b/file-uploader/src/views/partials/parentDirectories.ejs @@ -1,9 +1,8 @@
-

+

<% parentDirectories.forEach(d => { %> <%= d.name %> / - <% }); %> - <%= directoryListing.name %> + <%= directoryListing.name %>