basic functionality done

This commit is contained in:
Smigz 2025-06-08 11:12:26 -04:00
parent 1c5e2d1055
commit 269d7784ab
19 changed files with 5917 additions and 4677 deletions

View file

@ -1,13 +0,0 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: ["eslint:recommended", "prettier"],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
};

View file

@ -0,0 +1,21 @@
const { defineConfig } = require('eslint/config');
const js = require('@eslint/js');
const globals = require('globals');
module.exports = defineConfig([
{
files: ['**/*.js'],
extends: ['js/recommended'],
plugins: { js },
rules: {
'no-unused-vars': 'warn',
'no-undef': 'warn',
},
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
},
]);

File diff suppressed because it is too large Load diff

View file

@ -1,33 +1,37 @@
{ {
"name": "file-uploader", "name": "file-uploader",
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"description": "", "description": "",
"dependencies": { "dependencies": {
"@prisma/client": "^6.5.0", "@prisma/client": "^6.5.0",
"@quixo3/prisma-session-store": "^3.1.13", "@quixo3/prisma-session-store": "^3.1.13",
"bcrypt": "^5.1.1", "@supabase/supabase-js": "^2.49.8",
"ejs": "^3.1.10", "bcrypt": "^5.1.1",
"express": "^5.1.0", "ejs": "^3.1.10",
"express-session": "^1.18.1", "express": "^5.1.0",
"express-validator": "^7.2.1", "express-session": "^1.18.1",
"multer": "^1.4.5-lts.2", "express-validator": "^7.2.1",
"passport": "^0.7.0", "multer": "^1.4.5-lts.2",
"passport-local": "^1.0.0" "passport": "^0.7.0",
}, "passport-local": "^1.0.0"
"devDependencies": { },
"css-loader": "^7.1.2", "devDependencies": {
"html-webpack-plugin": "^5.6.3", "@eslint/js": "^9.27.0",
"prisma": "^6.5.0", "css-loader": "^7.1.2",
"style-loader": "^4.0.0", "eslint": "^9.27.0",
"template-ejs-loader": "^0.9.4", "eslint-config-prettier": "^10.1.5",
"webpack": "^5.98.0", "html-webpack-plugin": "^5.6.3",
"webpack-cli": "^6.0.1" "prisma": "^6.5.0",
} "style-loader": "^4.0.0",
"template-ejs-loader": "^0.9.4",
"webpack": "^5.98.0",
"webpack-cli": "^6.0.1"
}
} }

View file

@ -0,0 +1,8 @@
-- AlterTable
ALTER TABLE "File" ADD COLUMN "uuid" UUID NOT NULL DEFAULT gen_random_uuid();
-- AlterTable
ALTER TABLE "Folder" ADD COLUMN "uuid" UUID NOT NULL DEFAULT gen_random_uuid();
-- AlterTable
ALTER TABLE "User" ADD COLUMN "uuid" UUID NOT NULL DEFAULT gen_random_uuid();

View file

@ -0,0 +1,58 @@
/*
Warnings:
- The primary key for the `File` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `Folder` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `User` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `uuid` on the `User` table. All the data in the column will be lost.
*/
-- DropForeignKey
ALTER TABLE "File" DROP CONSTRAINT "File_folderId_fkey";
-- DropForeignKey
ALTER TABLE "File" DROP CONSTRAINT "File_ownerId_fkey";
-- DropForeignKey
ALTER TABLE "Folder" DROP CONSTRAINT "Folder_owner_user_id_fkey";
-- DropForeignKey
ALTER TABLE "Folder" DROP CONSTRAINT "Folder_parentId_fkey";
-- AlterTable
ALTER TABLE "File" DROP CONSTRAINT "File_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ALTER COLUMN "ownerId" SET DATA TYPE TEXT,
ALTER COLUMN "folderId" SET DATA TYPE TEXT,
ADD CONSTRAINT "File_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "File_id_seq";
-- AlterTable
ALTER TABLE "Folder" DROP CONSTRAINT "Folder_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ALTER COLUMN "owner_user_id" SET DATA TYPE TEXT,
ALTER COLUMN "parentId" SET DATA TYPE TEXT,
ADD CONSTRAINT "Folder_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "Folder_id_seq";
-- AlterTable
ALTER TABLE "User" DROP CONSTRAINT "User_pkey",
DROP COLUMN "uuid",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "User_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "User_id_seq";
-- AddForeignKey
ALTER TABLE "File" ADD CONSTRAINT "File_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "File" ADD CONSTRAINT "File_folderId_fkey" FOREIGN KEY ("folderId") REFERENCES "Folder"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Folder" ADD CONSTRAINT "Folder_owner_user_id_fkey" FOREIGN KEY ("owner_user_id") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Folder" ADD CONSTRAINT "Folder_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Folder"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View file

@ -0,0 +1,12 @@
/*
Warnings:
- You are about to drop the column `uuid` on the `File` table. All the data in the column will be lost.
- You are about to drop the column `uuid` on the `Folder` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "File" DROP COLUMN "uuid";
-- AlterTable
ALTER TABLE "Folder" DROP COLUMN "uuid";

View file

@ -5,54 +5,54 @@
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
previewFeatures = ["relationJoins"] previewFeatures = ["relationJoins"]
output = "../node_modules/.prisma/client" output = "../node_modules/.prisma/client"
} }
datasource db { datasource db {
provider = "postgresql" provider = "postgresql"
url = env("DATABASE_URL") url = env("DATABASE_URL")
} }
model User { model User {
id Int @id @default(autoincrement()) id String @id @default(uuid())
username String @unique @db.VarChar(50) username String @unique @db.VarChar(50)
email String @unique email String @unique
password String password String
files File[] files File[]
Folder Folder[] Folder Folder[]
} }
model File { model File {
id Int @id @default(autoincrement()) id String @id @default(uuid())
name String @db.VarChar(255) name String @db.VarChar(255)
url String url String
size Int size Int
mimetype String mimetype String
createdAt DateTime @default(now()) createdAt DateTime @default(now())
owner User? @relation(fields: [ownerId], references: [id]) owner User? @relation(fields: [ownerId], references: [id])
ownerId Int? ownerId String?
folder Folder? @relation(fields: [folderId], references: [id]) folder Folder? @relation(fields: [folderId], references: [id])
folderId Int? folderId String?
} }
model Folder { model Folder {
id Int @id @default(autoincrement()) id String @id @default(uuid())
name String @db.VarChar(255) name String @db.VarChar(255)
creation_date DateTime @default(now()) creation_date DateTime @default(now())
modification_date DateTime @default(now()) modification_date DateTime @default(now())
File File[] File File[]
owner User? @relation(fields: [owner_user_id], references: [id]) owner User? @relation(fields: [owner_user_id], references: [id])
owner_user_id Int? owner_user_id String?
parentId Int? parentId String?
parent Folder? @relation("ParentDirectory", fields: [parentId], references: [id]) parent Folder? @relation("ParentDirectory", fields: [parentId], references: [id])
Directories Folder[] @relation("ParentDirectory") Directories Folder[] @relation("ParentDirectory")
} }
model Session { model Session {
id String @id id String @id
sid String @unique sid String @unique
data String data String
expiresAt DateTime expiresAt DateTime
} }

View file

@ -1,17 +1,21 @@
const multer = require('multer'); const multer = require('multer');
const Db = require('../models/db'); const Db = require('../models/db');
const { const {
uploadFile,
mkDirectory, mkDirectory,
getDirectoryContents, getDirectoryContents,
getProperPath,
} = require('../services/fileService'); } = require('../services/fileService');
const upload = multer({ dest: '/tmp/testing/' }); // const storage = multer.memoryStorage();
// const upload = multer({ storage: storage });
const upload = multer({ dest: '/tmp/odin/' });
const db = new Db(); const db = new Db();
const createDirectory = async (req, res) => { const createDirectory = async (req, res) => {
const { parentId, 'directory-name': directoryName } = req.body; const { parentId, 'directory-name': directoryName } = req.body;
const referer = req.get('referer'); const referer = req.get('referer');
const result = await mkDirectory(directoryName, req.user, Number(parentId)); const result = await mkDirectory(directoryName, req.user, parentId);
if (referer && result) { if (referer && result) {
res.redirect(referer); res.redirect(referer);
@ -21,34 +25,44 @@ const createDirectory = async (req, res) => {
}; };
const fileUpload = async (req, res) => { const fileUpload = async (req, res) => {
const { folderId } = req.body; 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 = { const file = {
name: req.file.originalname, name: req.file.originalname,
path: req.file.path, path: path,
filename: req.file.filename, folderId: folderId,
folderId: Number(folderId),
mimetype: req.file.mimetype, mimetype: req.file.mimetype,
size: req.file.size, size: req.file.size,
}; };
const data = {
name: req.file.originalname,
path: path,
data: req.file.filename,
};
try { try {
const result = await db.file.createFile(req.user.id, file); const supabaseResult = await uploadFile(data);
console.log(result); if (supabaseResult.error === null) {
const result = await db.file.createFile(req.user.id, file);
console.log(result);
}
} catch (e) { } catch (e) {
console.log(e); console.error(e);
} }
res.redirect('/'); res.redirect(referer);
}; };
const directoryContents = async (req, res) => { const directoryContents = async (req, res) => {
if (!req.user) return res.redirect('/');
const { directoryId } = req.query; const { directoryId } = req.query;
const { id } = req.user; const { id } = req.user;
const directoryListing = await getDirectoryContents( const directoryListing = await getDirectoryContents(directoryId, id);
Number(directoryId),
id,
);
if (directoryListing) { if (directoryListing) {
res.render('partials/directoryListing', { res.render('partials/directoryListing', {
@ -60,10 +74,6 @@ const directoryContents = async (req, res) => {
} }
}; };
const getFiles = (req, res) => { const getFiles = (req, res) => {};
if (req.user) {
// logic to grab the files
}
};
module.exports = { createDirectory, upload, fileUpload, directoryContents }; module.exports = { createDirectory, upload, fileUpload, directoryContents };

View file

@ -1,14 +1,12 @@
const { getDirectoryContents } = require('../services/fileService'); const {
getDirectoryContents,
getParentDirectories,
} = require('../services/fileService');
const indexGet = async (req, res) => { const indexGet = async (req, res) => {
if (req.user) { if (req.user) {
// res.render('main', { res.redirect(`/fs/${req.user.username}/${req.user.rootDirectoryId}`);
// pageTitle: 'FileUpload', // if unauthenticated redirect
// folder: { id: req.user.rootDirectoryId },
// });
res.redirect(`/${req.user.username}/${req.user.rootDirectoryId}`);
// if unauthenticated
} else { } else {
res.render('main', { pageTitle: 'FileUpload', folder: {} }); res.render('main', { pageTitle: 'FileUpload', folder: {} });
} }
@ -16,16 +14,19 @@ const indexGet = async (req, res) => {
const userDirectoryNavigation = async (req, res) => { const userDirectoryNavigation = async (req, res) => {
const { username, directoryId } = req.params; const { username, directoryId } = req.params;
if (username !== req.user.username) res.redirect('/'); if (username !== req.user.username) res.redirect('/');
const dirContents = await getDirectoryContents(directoryId, req.user.id); const dirContents = await getDirectoryContents(directoryId, req.user.id);
const parentDirectories = await getParentDirectories(directoryId);
if (!dirContents) res.redirect('/'); if (!dirContents || !parentDirectories) res.redirect('/');
res.render('main', { res.render('main', {
pageTitle: 'FileUpload - Dashboard', pageTitle: 'FileUpload - Dashboard',
folder: { id: directoryId }, folder: { id: directoryId, name: dirContents.name },
directoryListing: dirContents, directoryListing: dirContents,
parentDirectories: parentDirectories,
}); });
}; };

View file

@ -50,4 +50,9 @@ passport.deserializeUser(async (id, done) => {
} }
}); });
module.exports = { passport }; const loggedIn = function (req, res, next) {
if (!req.user) res.redirect('/');
next();
};
module.exports = { passport, loggedIn };

View file

@ -21,6 +21,19 @@ class File {
} }
} }
async deleteDirectory(user, dirName) {
try {
await this.prisma.folder.delete({
data: {
name: dirName,
},
});
} catch (e) {
console.error(e);
return { error: 'issue deleting directory' };
}
}
async getDirectoryIdByName(dirId) { async getDirectoryIdByName(dirId) {
try { try {
await this.prisma.folder.findUnique({ await this.prisma.folder.findUnique({
@ -78,7 +91,7 @@ class File {
async createFile(userId, file) { async createFile(userId, file) {
try { try {
await this.prisma.file.create({ const result = await this.prisma.file.create({
data: { data: {
name: file.name, name: file.name,
size: file.size, size: file.size,
@ -88,10 +101,33 @@ class File {
ownerId: userId, ownerId: userId,
}, },
}); });
return result;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return e;
} }
} }
async getParentFolders(folderId) {
const result = await this.prisma.$queryRaw`
WITH RECURSIVE folder_hierarchy AS (
SELECT id, name, "parentId", 1 AS level
FROM "Folder"
WHERE id = ${folderId}
UNION ALL
SELECT f.id, f.name, f."parentId", h.level + 1
FROM "Folder" f
JOIN folder_hierarchy h ON f.id = h."parentId"
)
SELECT id, name
FROM folder_hierarchy
WHERE id != ${folderId}
ORDER BY level DESC;
`;
return result;
}
} }
module.exports = { File }; module.exports = { File };

View file

@ -1,5 +1,6 @@
const { Router } = require('express'); const { Router } = require('express');
const fileController = require('../controllers/fileController'); const fileController = require('../controllers/fileController');
const { loggedIn } = require('../middlewares/auth');
const fileRouter = Router(); const fileRouter = Router();
@ -8,7 +9,7 @@ fileRouter.post(
fileController.upload.single('fileUpload'), fileController.upload.single('fileUpload'),
fileController.fileUpload, fileController.fileUpload,
); );
fileRouter.post('/directory', fileController.createDirectory); fileRouter.post('/directory', loggedIn, fileController.createDirectory);
fileRouter.get('/directory', fileController.directoryContents); fileRouter.get('/directory', loggedIn, fileController.directoryContents);
module.exports = fileRouter; module.exports = fileRouter;

View file

@ -1,12 +1,13 @@
const { Router } = require('express'); const { Router } = require('express');
const indexController = require('../controllers/indexController'); const indexController = require('../controllers/indexController');
const { loggedIn } = require('../middlewares/auth');
indexRouter = Router(); const indexRouter = Router();
indexRouter.get('/', indexController.indexGet); indexRouter.get('/', indexController.indexGet);
indexRouter.get( indexRouter.get(
'/:username/:directoryId', '/fs/:username/:directoryId',
loggedIn,
indexController.userDirectoryNavigation, indexController.userDirectoryNavigation,
); );
module.exports = indexRouter; module.exports = indexRouter;

View file

@ -1,17 +1,62 @@
const Db = require('../models/db'); const Db = require('../models/db');
const { createClient } = require('@supabase/supabase-js');
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_KEY;
const supabase = createClient(supabaseUrl, supabaseKey);
const db = new Db(); const db = new Db();
async function getDirectoryContents(directoryId, userId) { async function getDirectoryContents(directoryId, userId) {
return await db.file.getDirectoryContents(Number(directoryId), userId); return await db.file.getDirectoryContents(directoryId, userId);
}
async function getParentDirectories(directoryId) {
return await db.file.getParentFolders(directoryId);
}
async function uploadFile(file) {
const { data, error } = await supabase.storage
.from('odin')
.upload(file.path, file.data);
return { data: data, error: error };
} }
async function mkDirectory(directoryName, user, parentDirectoryId) { async function mkDirectory(directoryName, user, parentDirectoryId) {
return await db.file.createDirectory( return await db.file.createDirectory(
user, user,
directoryName, directoryName,
Number(parentDirectoryId), parentDirectoryId,
); );
} }
module.exports = { mkDirectory, getDirectoryContents }; async function rmDirectory(directoryName, user, parentDirectoryId) {
return await db.file.deleteDirectory(
user,
directoryName,
parentDirectoryId,
);
}
async function getProperPath(directoryId) {
const fullPath = await db.file.getParentFolders(directoryId);
let directories;
if (fullPath && fullPath.length > 0) {
directories = fullPath.map((d) => d.name);
} else {
return '/';
}
return directories.join('/');
}
module.exports = {
uploadFile,
mkDirectory,
getParentDirectories,
getDirectoryContents,
getProperPath,
rmDirectory,
};

View file

@ -4,6 +4,8 @@
<p> Hello <%= currentUser.username %> </p> <p> Hello <%= currentUser.username %> </p>
<a href="/auth/logout">sign out</a> <a href="/auth/logout">sign out</a>
<%- include('partials/fileUpload') %> <%- include('partials/fileUpload') %>
<%- include('partials/parentDirectories') %>
<%- include('partials/directoryListing') %>
<% } else { %> <% } else { %>
<form action="/auth/login" method="post"> <form action="/auth/login" method="post">
<label for="username">Username</label> <label for="username">Username</label>
@ -16,5 +18,4 @@
<% } %> <% } %>
<%- include('partials/directoryListing') %>
<%- include('partials/footer') %> <%- include('partials/footer') %>

View file

@ -1,13 +1,12 @@
<div class="container"> <div class="container">
<h1> <%= directoryListing.name %> </h1>
<h2> Folders</h2> <h2> Folders</h2>
<ul> <ul>
<% directoryListing.Directories.forEach(d => { %> <% directoryListing.Directories.forEach(d => { %>
<li><a href="/<%= currentUser.username %>/<%= d.id %>"> <%= d.name %> </a></li> <li><a href="/fs/<%= currentUser.username %>/<%= d.id %>"> <%= d.name %> </a></li>
<% }); %> <% }); %>
</ul> </ul>
<h2> Files: </h2> <h2> Files: </h2>
<% directoryListing.File.forEach(f => { %> <% directoryListing.File.forEach(f => { %>
<p> <%= f.name %> </p> <p> <%= f.name %> <%= f.id %></p>
<% }); %> <% }); %>
</div> </div>

View file

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

View file

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