diff --git a/app/app.ts b/app/app.ts index 32d54f7..27cf1b8 100644 --- a/app/app.ts +++ b/app/app.ts @@ -1,5 +1,3 @@ -import type {MediaRow, UserRow} from './types'; - require("dotenv").config(); import express from "express"; @@ -17,7 +15,7 @@ import authRouter from "./routes/auth"; import indexRouter from "./routes/index"; import adduserRouter from "./routes/adduser"; -import {db, createUser} from "./db"; +import {db, createUser, MediaRow} from "./db"; let app = express(); let server = http.createServer(app); @@ -80,8 +78,14 @@ db.serialize(function() { db.run("CREATE TABLE IF NOT EXISTS media ( \ id INTEGER PRIMARY KEY, \ path TEXT NOT NULL, \ - expire INTEGER \ + expire INTEGER \, \ + username TEXT \ )"); + + db.run("ALTER TABLE media ADD COLUMN username TEXT", (err) => { + if(err) + return; + }); //TODO, version new DB, run this command when detecting old DB createUser("admin", process.env.EBPASS || "changeme"); }); diff --git a/app/db.ts b/app/db.ts index 09a348b..81284df 100644 --- a/app/db.ts +++ b/app/db.ts @@ -14,4 +14,18 @@ export function createUser(username: string, password: string) { crypto.pbkdf2Sync(password, salt, 310000, 32, "sha256"), salt ]); +} + +export interface MediaRow { + id? : Number, + path: String, + expire: Number, + username: String +} + +export interface UserRow { + id? : Number, + username: String, + hashed_password: any, + salt: any } \ No newline at end of file diff --git a/app/lib.ts b/app/lib.ts new file mode 100644 index 0000000..c6c7602 --- /dev/null +++ b/app/lib.ts @@ -0,0 +1,4 @@ +export function extension(str: String){ + let file = str.split("/").pop(); + return [file.substr(0,file.lastIndexOf(".")),file.substr(file.lastIndexOf("."),file.length).toLowerCase()]; +} \ No newline at end of file diff --git a/app/multer.ts b/app/multer.ts new file mode 100644 index 0000000..4f2a0e0 --- /dev/null +++ b/app/multer.ts @@ -0,0 +1,72 @@ +import {Request} from 'express'; +import multer, {FileFilterCallback} from 'multer'; + +import {db, MediaRow} from './db' +import {extension} from './lib' + +export type DestinationCallback = (error: Error | null, destination: string) => void +export type FileNameCallback = (error: Error | null, filename: string) => void + +export const fileStorage = multer.diskStorage({ + destination: ( + request: Request, + file: Express.Multer.File, + callback: DestinationCallback + ): void => { + callback(null, __dirname + "/../uploads"); + }, + filename: ( + request: Request, + file: Express.Multer.File, + callback: FileNameCallback + ): void => { + let nameAndExtension = extension(file.originalname); + console.log(`Uploading ${file}`); + db.all("SELECT * FROM media WHERE path = ?", [nameAndExtension[0] + nameAndExtension[1]], (err: Error, exists: []) => { + if (err) { + console.log(err) + callback(err, null) + } + if (exists.length != 0) { + let suffix = new Date().getTime() / 1000; + + if (request.body.title == "" || request.body.title == null || request.body.title == undefined) { + callback(null, nameAndExtension[0] + "-" + suffix + nameAndExtension[1]); + } else { + callback(null, request.body.title + "-" + suffix + nameAndExtension[1]); + } + } else { + if (request.body.title == "" || request.body.title == null || request.body.title == undefined) { + callback(null, nameAndExtension[0] + nameAndExtension[1]); + } else { + callback(null, request.body.title + nameAndExtension[1]); + } + } + }); + } +}); + +export let allowedMimeTypes = [ + "image/png", + "image/jpg", + "image/jpeg", + "image/gif", + "image/webp", + "video/mp4", + "video/mov", + "video/webm", + "audio/mpeg", + "audio/ogg" +]; + +export const fileFilter = ( + request: Request, + file: Express.Multer.File, + callback: FileFilterCallback +): void => { + if (allowedMimeTypes.includes(file.mimetype)) { + callback(null, true) + } else { + callback(null, false) + } +} \ No newline at end of file diff --git a/app/public/css/app.css b/app/public/css/app.css index f7deb68..edbdcf0 100644 --- a/app/public/css/app.css +++ b/app/public/css/app.css @@ -39,7 +39,7 @@ } /* background image by Cole Bemis */ -.nav .user { +.user { padding-left: 20px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-user'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'%3E%3C/path%3E%3Ccircle cx='12' cy='7' r='4'%3E%3C/circle%3E%3C/svg%3E"); background-repeat: no-repeat; @@ -47,7 +47,16 @@ } /* background image by Cole Bemis */ -.nav .logout { +.username { + padding-left: 20px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-user'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'%3E%3C/path%3E%3Ccircle cx='12' cy='7' r='4'%3E%3C/circle%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center left; + color: #73AD21; +} + +/* background image by Cole Bemis */ +.logout { padding-left: 20px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-log-out'%3E%3Cpath d='M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4'%3E%3C/path%3E%3Cpolyline points='16 17 21 12 16 7'%3E%3C/polyline%3E%3Cline x1='21' y1='12' x2='9' y2='12'%3E%3C/line%3E%3C/svg%3E%0A"); background-repeat: no-repeat; @@ -55,7 +64,7 @@ } /* background image by Cole Bemis */ -.nav .adduser { +.adduser { padding-left: 20px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-user-plus'%3E%3Cpath d='M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2'%3E%3C/path%3E%3Ccircle cx='8.5' cy='7' r='4'%3E%3C/circle%3E%3Cline x1='20' y1='8' x2='20' y2='14'%3E%3C/line%3E%3Cline x1='23' y1='11' x2='17' y2='11'%3E%3C/line%3E%3C/svg%3E"); background-repeat: no-repeat; diff --git a/app/routes/auth.ts b/app/routes/auth.ts index a377d36..ca31355 100644 --- a/app/routes/auth.ts +++ b/app/routes/auth.ts @@ -1,11 +1,9 @@ -import type {UserRow} from '../types'; - import crypto from "crypto"; import express from "express"; import passport from "passport"; import {Strategy as LocalStrategy} from "passport-local"; -import {db} from "../db"; +import {db, UserRow} from "../db"; let router = express.Router(); diff --git a/app/routes/index.ts b/app/routes/index.ts index 70968e4..5dc70c6 100644 --- a/app/routes/index.ts +++ b/app/routes/index.ts @@ -1,5 +1,4 @@ -import type {RequestHandler as Middleware, Router, Request, Response, NextFunction} from 'express'; - +import type {RequestHandler as Middleware, Request, Response, NextFunction} from 'express'; import multer from "multer"; import express from "express"; import ffmpeg from "fluent-ffmpeg"; @@ -13,70 +12,27 @@ ffmpeg.setFfprobePath(ffprobepath.path); import fs from "fs"; -import {db, createUser} from "../db"; +import {extension} from "../lib"; +import {db, MediaRow} from "../db"; +import {fileStorage, fileFilter} from "../multer"; import {checkAuth, checkSharexAuth, createEmbedData, handleUpload} from "./middleware"; -import { MediaRow } from '../types'; -function extension(str: String){ - let file = str.split("/").pop(); - return [file.substr(0,file.lastIndexOf(".")),file.substr(file.lastIndexOf("."),file.length).toLowerCase()]; -} - -const storage = multer.diskStorage({ - destination: (req, file, cb) => { - cb(null, "uploads/"); - }, - filename : (req, file, cb) => { - let nameAndExtension = extension(file.originalname); - db.all("SELECT * FROM media WHERE path = ?", [nameAndExtension[0] + nameAndExtension[1]], (err: Error, exists: []) => { - if (exists.length != 0) { - let suffix = new Date().getTime() / 1000; - - if (req.body.title == "" || req.body.title == null || req.body.title == undefined) - cb(null, nameAndExtension[0] + "-" + suffix + nameAndExtension[1]); - else - cb(null, req.body.title + "-" + suffix + nameAndExtension[1]); - } else { - if (req.body.title == "" || req.body.title == null || req.body.title == undefined) - cb(null, nameAndExtension[0] + nameAndExtension[1]); - else - cb(null, req.body.title + nameAndExtension[1]); - } - }); - } -}); - -/**let allowedMimeTypes = [ - "image/png", - "image/jpg", - "image/jpeg", - "image/gif", - "image/webp", - "video/mp4", - "video/mov", - "video/webm", - "audio/mpeg", - "audio/ogg" -]; - -const fileFilter = function(req, file, cb) { - if (allowedMimeTypes.includes(file.mimetype)) { - cb(null, true); - } else { - cb(null, false); - } -};**/ - -let upload = multer({ storage: storage /**, fileFilter: fileFilter**/ }); //maybe make this a env variable? +let upload = multer({ storage: fileStorage /**, fileFilter: fileFilter**/ }); //maybe make this a env variable? const fetchMedia: Middleware = (req, res, next) => { - db.all("SELECT * FROM media", (err: Error, rows: []) => { + //@ts-ignore + let admin: boolean = req.user.username == "admin" ? true : false + //@ts-ignore + let query: string = admin == true ? "SELECT * FROM media" : `SELECT * FROM media WHERE username = '${req.user.username}'`; + + db.all(query, (err:Error, rows: []) => { if (err) return next(err); let files = rows.map((row: MediaRow)=> { return { id: row.id, path: row.path, expire: row.expire, + username: row.username, url: "/" + row.id }; }); @@ -102,26 +58,16 @@ router.get("/gifv/:file", async (req, res, next) => { let width; let height; let nameAndExtension = extension(`uploads/${req.params.file}`); - if (nameAndExtension[1] == ".mp4" || nameAndExtension[1] == ".mov" || nameAndExtension[1] == ".webm") { + if (nameAndExtension[1] == ".mp4" || nameAndExtension[1] == ".mov" || nameAndExtension[1] == ".webm" || nameAndExtension[1] == ".gif") { ffmpeg() - .input(`uploads/${req.params.file}`) - .inputFormat(nameAndExtension[1].substring(1)) - .ffprobe((err: Error, data: ffmpeg.FfprobeData) => { - if (err) return next(err); - width = data.streams[0].width; - height = data.streams[0].height; - return res.render("gifv", { url: url, host: `${req.protocol}://${req.get("host")}`, width: width, height: height }); - }); - } else if (nameAndExtension[1] == ".gif") { - ffmpeg() - .input(`uploads/${req.params.file}`) - .inputFormat("gif") - .ffprobe((err: Error, data: ffmpeg.FfprobeData) => { - if (err) return next(err); - width = data.streams[0].width; - height = data.streams[0].height; - return res.render("gifv", { url: url, host: `${req.protocol}://${req.get("host")}`, width: width, height: height }); - }); + .input(`uploads/${req.params.file}`) + .inputFormat(nameAndExtension[1].substring(1)) + .ffprobe((err: Error, data: ffmpeg.FfprobeData) => { + if (err) return next(err); + width = data.streams[0].width; + height = data.streams[0].height; + return res.render("gifv", { url: url, host: `${req.protocol}://${req.get("host")}`, width: width, height: height }); + }); } else { let imageData = await imageProbe(fs.createReadStream(`uploads/${req.params.file}`)); return res.render("gifv", { url: url, host: `${req.protocol}://${req.get("host")}`, width: imageData.width, height: imageData.height }); diff --git a/app/routes/middleware.ts b/app/routes/middleware.ts index 8dd945b..941e084 100644 --- a/app/routes/middleware.ts +++ b/app/routes/middleware.ts @@ -11,11 +11,7 @@ import fs from "fs"; import process from "process"; import {db} from "../db"; - -function extension(str: String){ - let file = str.split("/").pop(); - return [file.substr(0,file.lastIndexOf(".")),file.substr(file.lastIndexOf("."),file.length).toLowerCase()]; -} +import {extension} from "../lib"; export const checkAuth: Middleware = (req, res, next) => { if (!req.user) { @@ -120,11 +116,10 @@ export const handleUpload: Middleware = (req, res, next) => { let expireDate: Date; if (req.body.expire) { expireDate = new Date(currentdate + (req.body.expire * 24 * 60 * 60 * 1000)); - console.log(req.body.expire); - console.log(expireDate); } else expireDate = null; - db.run("INSERT INTO media (path, expire) VALUES (?, ?)", [files[file].filename, expireDate], function (err) { + //@ts-ignore + db.run("INSERT INTO media (path, expire, username) VALUES (?, ?, ?)", [files[file].filename, expireDate, req.user.username], function (err) { if (err) { console.log(err); return next(err); diff --git a/app/types.ts b/app/types.ts deleted file mode 100644 index f12215c..0000000 --- a/app/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface MediaRow { - id? : Number, - path: String, - expire: Number -} - -export interface UserRow { - id? : Number, - username: String, - hashed_password: any, - salt: any -} \ No newline at end of file diff --git a/app/views/index.ejs b/app/views/index.ejs index bde9c2a..73c2cd2 100644 --- a/app/views/index.ejs +++ b/app/views/index.ejs @@ -1,110 +1,133 @@ - - - - Embedder - - - - - - - -<% -function extension(string) { -return string.slice((string.lastIndexOf(".") - 2 >>> 0) + 2); -} -%> - - -
- -
-

Embedder

-
-
-

Upload a file, copy paste, or drag n' drop into the dashed region

- -

-
-
-

Select file expiration date:

-

Click the file to copy the url

-
-
-
- <% if (Count > 0) { %> -
-
    - <% files.forEach(function(file) { %> -
  • -
    -
    - <% if (extension(file.path) == ".mp4" || extension(file.path) == ".mov" || extension(file.path) == "webp") { %> -
    - - -
    - <% } else if (extension(file.path) == ".gif") { %> -
    - - -
    - <% } else if (extension(file.path) == ".jpg" || extension(file.path) == ".jpeg" || extension(file.path) == ".png" || extension(file.path) == ".gif" || extension(file.path) == ".webp" ) { %> - - <% } else {%> -
    -

    <%=extension(file.path)%> file

    -
    - <% } %> - - - -
    -
    -
    -
    -
  • - <% }); %> -
-
- <% } %> -
- - - + + + + Embedder + + + + + + + + <% + function extension(string) { + return string.slice((string.lastIndexOf(".") - 2 >>> 0) + 2); + } + %> + + +
+ +
+

Embedder

+
+
+

Upload a file, copy paste, or drag n' drop into the dashed region

+ +

+
+
+

+ Select file expiration date: + +

+

Click the file to copy the url

+
+
+
+ <% if (Count > 0) { %> +
+
    + <% files.forEach(function(file) { %> +
  • +
    +
    + <% if (extension(file.path) == ".mp4" || extension(file.path) == ".mov" || extension(file.path) == "webp") { %> +
    + +
    + <% if(user.username == "admin" && file.username != "admin") { %> + <%= file.username %> +
    + <% } %> + Copy as GIFv +
    +
    + <% } else if (extension(file.path) == ".gif") { %> +
    + +
    + <% if(user.username == "admin" && file.username != "admin") { %> + <%= file.username %> +
    + <% } %> + Copy as GIFv +
    +
    + <% } else if (extension(file.path) == ".jpg" || extension(file.path) == ".jpeg" || extension(file.path) == ".png" || extension(file.path) == ".gif" || extension(file.path) == ".webp" ) { %> +
    + +
    + <% if(user.username == "admin" && file.username != "admin") { %> + <%= file.username %> + <% } %> +
    +
    + <% } else {%> +
    +

    <%=extension(file.path)%> file

    +
    + <% if(user.username == "admin" && file.username != "admin") { %> + <%= file.username %> + <% } %> +
    +
    + <% } %> + + + +
    +
    +
    +
    +
  • + <% }); %> +
+
+ <% } %> +
+ + + diff --git a/cypress.config.js b/cypress.config.js index 00a2dac..6f67780 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -2,7 +2,7 @@ const { defineConfig } = require("cypress"); module.exports = defineConfig({ e2e: { - baseUrl: "http://localhost:3000", + baseUrl: "http://localhost:4000", }, chromeWebSecurity: false, "video": false diff --git a/cypress/screenshots/spec.cy.js/The Home Page -- logs out (failed).png b/cypress/screenshots/spec.cy.js/The Home Page -- logs out (failed).png new file mode 100644 index 0000000..a4b62f6 Binary files /dev/null and b/cypress/screenshots/spec.cy.js/The Home Page -- logs out (failed).png differ diff --git a/cypress/screenshots/spec.cy.js/The Upload Page -- ShareX successfully uploads a file (failed).png b/cypress/screenshots/spec.cy.js/The Upload Page -- ShareX successfully uploads a file (failed).png new file mode 100644 index 0000000..dfd60e0 Binary files /dev/null and b/cypress/screenshots/spec.cy.js/The Upload Page -- ShareX successfully uploads a file (failed).png differ