more updates and changes

This commit is contained in:
Smigz 2025-06-14 15:22:03 -04:00
parent 269d7784ab
commit 8be819bf91
15 changed files with 426 additions and 98 deletions

133
.dockerignore Normal file
View file

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

View file

@ -31,6 +31,7 @@ model File {
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])

View file

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

View file

@ -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,12 +27,15 @@ 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}`;
try {
const path = await getFullUploadPath(
folderId,
folderName,
req.user.username,
req.file.originalname,
);
const path = `${req.user.username}/${fullPath}/${req.file.originalname}`;
const file = {
const fileMetaData = {
name: req.file.originalname,
path: path,
folderId: folderId,
@ -46,17 +48,24 @@ const fileUpload = async (req, res) => {
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 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,
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,25 @@
<%- include('partials/header') %>
<body>
<% if(currentUser) { %>
<div class="container flex">
<p> Hello <%= currentUser.username %> </p>
<a href="/auth/logout">sign out</a>
</div>
<main>
<div class="container">
<div class="container flex">
<%- include('partials/newDirectory') %>
<%- include('partials/fileUpload') %>
</div>
<%- include('partials/parentDirectories') %>
<%- include('partials/directoryListing') %>
</div>
</main>
<% } else { %>
<main>
<form action="/auth/login" method="post">
<label for="username">Username</label>
<input type="text" name="username">
@ -15,6 +28,7 @@
<button type="submit">Submit</button>
<a href="/auth/register">Click here to register</a>
</form>
</main>
<% } %>

View file

@ -1,12 +1,41 @@
<div class="container">
<h2> Folders</h2>
<ul>
<% directoryListing.Directories.forEach(d => { %>
<li><a href="/fs/<%= currentUser.username %>/<%= d.id %>"> <%= d.name %> </a></li>
<% }); %>
</ul>
<h2> Files: </h2>
<% directoryListing.File.forEach(f => { %>
<p> <%= f.name %> <%= f.id %></p>
<% }); %>
</div>
<section class="container directory-listing">
<div class="directory-grid">
<div class="col file-name heading directory__grid_heading">
<p>Name</p>
</div>
<div class="col file-size directory__grid__heading">
<p>Size</p>
</div>
<div class="col file-modified directory__grid__heading">
<p>Modified Last</p>
</div>
<!-- Combined loop for both directories and files -->
<%
const renderItem = (item, isDirectory) => {
%>
<div class="col file-name">
<% if (isDirectory) { %>
d <a href="/fs/<%= currentUser.username %>/<%= item.id %>"><%= item.name %></a> /
<% } else { %>
- <%= item.name %> <a href="/file/file?fileId=<%= item.id %>">Details</a>
<% } %>
</div>
<div class="col file-size">
<p><%= item?.size %></p>
</div>
<div class="col file-modified">
<p><%= item?.modifiedAt %></p>
</div>
<% }; %>
<!-- Render directories -->
<% directoryListing.Directories.forEach(d => renderItem(d, true)); %>
<!-- Render files -->
<% directoryListing.File.forEach(f => renderItem(f, false)); %>
</div>
</div>
</section>

View file

@ -0,0 +1,10 @@
<div class="container">
<div class="file-metadata">
<h2>File: <%= file.name %> </h2>
<p>Size: <%= file.size %> </p>
<p>Created At: <%= file.createdAt %></p>
<p>Modified At: <%= file?.modifiedAt %></p>
</div>
<a href="/fs/<%= currentUser.username %>/<%= file.folderId %>">Back</a>
</div>

View file

@ -1,18 +1,12 @@
<form action="/file" method="post" enctype="multipart/form-data">
<div class="container file-upload">
<section class="file-upload">
<h2 class="h2-heading">New upload</h2>
<form action="/file" method="post" enctype="multipart/form-data">
<div class="container file-upload">
<input type="file" name="fileUpload" id="fileUpload" class="file" required>
<input type="hidden" name="folderId" value=<%= folder.id %>>
<input type="hidden" name="folderName" value=<%= folder.name %>>
<button type="submit">Upload</button>
</div>
</form>
<form action="/file/directory" method="post">
<div class="container">
<label for="directory-name">Directory Name:</label>
<input type="text" name="directory-name" placeholder="" required>
<input type="hidden" name="parentId" value=<%= folder.id %>>
<button>Create Directory</button>
<button type="submit" class="btn">Upload</button>
</div>
</form>
</form>
</section>

View file

@ -0,0 +1,10 @@
<form action="/file/directory" method="post">
<div class="container">
<div class="form-item">
<label for="directory-name">Directory Name:</label>
<input type="text" name="directory-name" placeholder="" required>
</div>
<input type="hidden" name="parentId" value=<%= folder.id %>>
<button class="btn">Create Directory</button>
</div>
</form>

View file

@ -1,9 +1,8 @@
<div class="container">
<p>
<p class="directory-path">
<% parentDirectories.forEach(d => { %>
<a href="/fs/<%= currentUser.username %>/<%= d.id %>"><%= d.name %></a><span> /</span>
<% }); %>
<span> <%= directoryListing.name %></span>
<span class="current-directory"> <%= directoryListing.name %></span>
</p>
</div>