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",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@prisma/client": "^6.5.0",
"@quixo3/prisma-session-store": "^3.1.13",
"bcrypt": "^5.1.1",
"ejs": "^3.1.10",
"express": "^5.1.0",
"express-session": "^1.18.1",
"express-validator": "^7.2.1",
"multer": "^1.4.5-lts.2",
"passport": "^0.7.0",
"passport-local": "^1.0.0"
},
"devDependencies": {
"css-loader": "^7.1.2",
"html-webpack-plugin": "^5.6.3",
"prisma": "^6.5.0",
"style-loader": "^4.0.0",
"template-ejs-loader": "^0.9.4",
"webpack": "^5.98.0",
"webpack-cli": "^6.0.1"
}
"name": "file-uploader",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@prisma/client": "^6.5.0",
"@quixo3/prisma-session-store": "^3.1.13",
"@supabase/supabase-js": "^2.49.8",
"bcrypt": "^5.1.1",
"ejs": "^3.1.10",
"express": "^5.1.0",
"express-session": "^1.18.1",
"express-validator": "^7.2.1",
"multer": "^1.4.5-lts.2",
"passport": "^0.7.0",
"passport-local": "^1.0.0"
},
"devDependencies": {
"@eslint/js": "^9.27.0",
"css-loader": "^7.1.2",
"eslint": "^9.27.0",
"eslint-config-prettier": "^10.1.5",
"html-webpack-plugin": "^5.6.3",
"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
generator client {
provider = "prisma-client-js"
previewFeatures = ["relationJoins"]
output = "../node_modules/.prisma/client"
provider = "prisma-client-js"
previewFeatures = ["relationJoins"]
output = "../node_modules/.prisma/client"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
username String @unique @db.VarChar(50)
email String @unique
password String
files File[]
Folder Folder[]
id String @id @default(uuid())
username String @unique @db.VarChar(50)
email String @unique
password String
files File[]
Folder Folder[]
}
model File {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
url String
size Int
mimetype String
createdAt DateTime @default(now())
owner User? @relation(fields: [ownerId], references: [id])
ownerId Int?
folder Folder? @relation(fields: [folderId], references: [id])
folderId Int?
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?
}
model Folder {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
creation_date DateTime @default(now())
modification_date DateTime @default(now())
File File[]
owner User? @relation(fields: [owner_user_id], references: [id])
owner_user_id Int?
parentId Int?
parent Folder? @relation("ParentDirectory", fields: [parentId], references: [id])
Directories Folder[] @relation("ParentDirectory")
id String @id @default(uuid())
name String @db.VarChar(255)
creation_date DateTime @default(now())
modification_date DateTime @default(now())
File File[]
owner User? @relation(fields: [owner_user_id], references: [id])
owner_user_id String?
parentId String?
parent Folder? @relation("ParentDirectory", fields: [parentId], references: [id])
Directories Folder[] @relation("ParentDirectory")
}
model Session {
id String @id
sid String @unique
data String
expiresAt DateTime
id String @id
sid String @unique
data String
expiresAt DateTime
}

View file

@ -1,17 +1,21 @@
const multer = require('multer');
const Db = require('../models/db');
const {
uploadFile,
mkDirectory,
getDirectoryContents,
getProperPath,
} = 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 createDirectory = async (req, res) => {
const { parentId, 'directory-name': directoryName } = req.body;
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) {
res.redirect(referer);
@ -21,34 +25,44 @@ const createDirectory = 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 = {
name: req.file.originalname,
path: req.file.path,
filename: req.file.filename,
folderId: Number(folderId),
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 result = await db.file.createFile(req.user.id, file);
console.log(result);
const supabaseResult = await uploadFile(data);
if (supabaseResult.error === null) {
const result = await db.file.createFile(req.user.id, file);
console.log(result);
}
} catch (e) {
console.log(e);
console.error(e);
}
res.redirect('/');
res.redirect(referer);
};
const directoryContents = async (req, res) => {
if (!req.user) return res.redirect('/');
const { directoryId } = req.query;
const { id } = req.user;
const directoryListing = await getDirectoryContents(
Number(directoryId),
id,
);
const directoryListing = await getDirectoryContents(directoryId, id);
if (directoryListing) {
res.render('partials/directoryListing', {
@ -60,10 +74,6 @@ const directoryContents = async (req, res) => {
}
};
const getFiles = (req, res) => {
if (req.user) {
// logic to grab the files
}
};
const getFiles = (req, res) => {};
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) => {
if (req.user) {
// res.render('main', {
// pageTitle: 'FileUpload',
// folder: { id: req.user.rootDirectoryId },
// });
res.redirect(`/${req.user.username}/${req.user.rootDirectoryId}`);
// if unauthenticated
res.redirect(`/fs/${req.user.username}/${req.user.rootDirectoryId}`);
// if unauthenticated redirect
} else {
res.render('main', { pageTitle: 'FileUpload', folder: {} });
}
@ -16,16 +14,19 @@ const indexGet = async (req, res) => {
const userDirectoryNavigation = async (req, res) => {
const { username, directoryId } = req.params;
if (username !== req.user.username) res.redirect('/');
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', {
pageTitle: 'FileUpload - Dashboard',
folder: { id: directoryId },
folder: { id: directoryId, name: dirContents.name },
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) {
try {
await this.prisma.folder.findUnique({
@ -78,7 +91,7 @@ class File {
async createFile(userId, file) {
try {
await this.prisma.file.create({
const result = await this.prisma.file.create({
data: {
name: file.name,
size: file.size,
@ -88,10 +101,33 @@ class File {
ownerId: userId,
},
});
return result;
} catch (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 };

View file

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

View file

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

View file

@ -1,17 +1,62 @@
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();
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) {
return await db.file.createDirectory(
user,
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>
<a href="/auth/logout">sign out</a>
<%- include('partials/fileUpload') %>
<%- include('partials/parentDirectories') %>
<%- include('partials/directoryListing') %>
<% } else { %>
<form action="/auth/login" method="post">
<label for="username">Username</label>
@ -16,5 +18,4 @@
<% } %>
<%- include('partials/directoryListing') %>
<%- include('partials/footer') %>

View file

@ -1,13 +1,12 @@
<div class="container">
<h1> <%= directoryListing.name %> </h1>
<h2> Folders</h2>
<ul>
<% 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>
<h2> Files: </h2>
<% directoryListing.File.forEach(f => { %>
<p> <%= f.name %> </p>
<p> <%= f.name %> <%= f.id %></p>
<% }); %>
</div>

View file

@ -1,7 +1,8 @@
<form action="/file" method="post" enctype="multipart/form-data">
<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="folderName" value=<%= folder.name %>>
<button type="submit">Upload</button>
</div>
</form>
@ -10,7 +11,7 @@
<form action="/file/directory" method="post">
<div class="container">
<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 %>>
<button>Create Directory</button>
</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>