mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 19:14:16 +00:00
Merge pull request #62 from ragestudio/files-controller
Files controller
This commit is contained in:
commit
666b6b2817
1
.gitignore
vendored
1
.gitignore
vendored
@ -19,6 +19,7 @@
|
|||||||
/**/**/package-lock.json
|
/**/**/package-lock.json
|
||||||
/**/**/yarn.lock
|
/**/**/yarn.lock
|
||||||
/**/**/.evite
|
/**/**/.evite
|
||||||
|
/**/**/uploads
|
||||||
/**/**/d_data
|
/**/**/d_data
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
|
@ -14,16 +14,18 @@
|
|||||||
"connect-mongo": "4.6.0",
|
"connect-mongo": "4.6.0",
|
||||||
"corenode": "0.28.26",
|
"corenode": "0.28.26",
|
||||||
"dicebar_lib": "1.0.1",
|
"dicebar_lib": "1.0.1",
|
||||||
|
"dotenv": "16.0.1",
|
||||||
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
|
"formidable": "^2.0.1",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "8.5.1",
|
||||||
"linebridge": "0.11.13",
|
"linebridge": "0.11.14",
|
||||||
"moment": "2.29.1",
|
"moment": "2.29.1",
|
||||||
"mongoose": "6.1.9",
|
"mongoose": "6.1.9",
|
||||||
"nanoid": "3.2.0",
|
"nanoid": "3.2.0",
|
||||||
"passport": "0.5.2",
|
"passport": "0.5.2",
|
||||||
"passport-jwt": "4.0.0",
|
"passport-jwt": "4.0.0",
|
||||||
"passport-local": "1.0.0",
|
"passport-local": "1.0.0",
|
||||||
"path-to-regexp": "6.2.0",
|
"path-to-regexp": "6.2.0"
|
||||||
"dotenv": "16.0.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
@ -2,17 +2,68 @@ import { Controller } from "linebridge/dist/server"
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import stream from "stream"
|
import stream from "stream"
|
||||||
|
const formidable = require("formidable")
|
||||||
|
|
||||||
function resolveToUrl(filepath) {
|
function resolveToUrl(filepath, req) {
|
||||||
return `${global.globalPublicURI}/uploads/${filepath}`
|
const host = req ? (req.protocol + "://" + req.get("host")) : global.globalPublicURI
|
||||||
|
|
||||||
|
return `${host}/upload/${filepath}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Get maximunFileSize by type of user subscription (free, premium, etc) when `PermissionsAPI` is ready
|
||||||
|
const maximumFileSize = 80 * 1024 * 1024 // max file size in bytes (80MB) By default, the maximum file size is 80MB.
|
||||||
|
const maximunFilesPerRequest = 10
|
||||||
|
const acceptedMimeTypes = [
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/gif",
|
||||||
|
"video/mp4",
|
||||||
|
"video/webm",
|
||||||
|
"video/quicktime",
|
||||||
|
"video/x-msvideo",
|
||||||
|
"video/x-ms-wmv",
|
||||||
|
]
|
||||||
|
|
||||||
|
function videoTranscode(originalFilePath, outputPath, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const ffmpeg = require("fluent-ffmpeg")
|
||||||
|
|
||||||
|
const filename = path.basename(originalFilePath)
|
||||||
|
const outputFilepath = `${outputPath}/${filename.split(".")[0]}.${options.format ?? "webm"}`
|
||||||
|
|
||||||
|
console.debug(`[TRANSCODING] Transcoding ${originalFilePath} to ${outputFilepath}`)
|
||||||
|
|
||||||
|
const onEnd = async () => {
|
||||||
|
// remove
|
||||||
|
await fs.promises.unlink(originalFilePath)
|
||||||
|
|
||||||
|
console.debug(`[TRANSCODING] Transcoding ${originalFilePath} to ${outputFilepath} finished`)
|
||||||
|
|
||||||
|
return resolve(outputFilepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onError = (err) => {
|
||||||
|
console.error(`[TRANSCODING] Transcoding ${originalFilePath} to ${outputFilepath} failed`, err)
|
||||||
|
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ffmpeg(originalFilePath)
|
||||||
|
.audioBitrate(options.audioBitrate ?? 128)
|
||||||
|
.videoBitrate(options.videoBitrate ?? 1024)
|
||||||
|
.videoCodec(options.videoCodec ?? "libvpx")
|
||||||
|
.audioCodec(options.audioCodec ?? "libvorbis")
|
||||||
|
.format(options.format ?? "webm")
|
||||||
|
.output(outputFilepath)
|
||||||
|
.on("error", onError)
|
||||||
|
.on("end", onEnd)
|
||||||
|
.run()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class FilesController extends Controller {
|
export default class FilesController extends Controller {
|
||||||
static disabled = true
|
|
||||||
|
|
||||||
get = {
|
get = {
|
||||||
"/uploads/:id": {
|
"/upload/:id": {
|
||||||
enabled: false,
|
|
||||||
fn: (req, res) => {
|
fn: (req, res) => {
|
||||||
const filePath = path.join(global.uploadPath, req.params?.id)
|
const filePath = path.join(global.uploadPath, req.params?.id)
|
||||||
|
|
||||||
@ -32,37 +83,86 @@ export default class FilesController extends Controller {
|
|||||||
|
|
||||||
post = {
|
post = {
|
||||||
"/upload": {
|
"/upload": {
|
||||||
enabled: false,
|
middlewares: ["withAuthentication"],
|
||||||
middlewares: ["withAuthentication", "fileUpload"],
|
|
||||||
fn: async (req, res) => {
|
fn: async (req, res) => {
|
||||||
const urls = []
|
// check directories exist
|
||||||
const failed = []
|
if (!fs.existsSync(global.uploadCachePath)) {
|
||||||
|
await fs.promises.mkdir(global.uploadCachePath, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(global.uploadPath)) {
|
if (!fs.existsSync(global.uploadPath)) {
|
||||||
await fs.promises.mkdir(global.uploadPath, { recursive: true })
|
await fs.promises.mkdir(global.uploadPath, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.files) {
|
// decode body form-data
|
||||||
for await (let file of req.files) {
|
const form = formidable({
|
||||||
try {
|
multiples: true,
|
||||||
const filename = `${req.decodedToken.user_id}-${new Date().getTime()}-${file.filename}`
|
keepExtensions: true,
|
||||||
|
uploadDir: global.uploadCachePath,
|
||||||
|
maxFileSize: maximumFileSize,
|
||||||
|
maxFields: maximunFilesPerRequest,
|
||||||
|
filter: (stream) => {
|
||||||
|
// check if is allowed mime type
|
||||||
|
if (!acceptedMimeTypes.includes(stream.mimetype)) {
|
||||||
|
failed.push({
|
||||||
|
fileName: file.originalFilename,
|
||||||
|
mimetype: file.mimetype,
|
||||||
|
error: "mimetype not allowed",
|
||||||
|
})
|
||||||
|
|
||||||
const diskPath = path.join(global.uploadPath, filename)
|
return false
|
||||||
|
|
||||||
await fs.promises.writeFile(diskPath, file.data)
|
|
||||||
|
|
||||||
urls.push(resolveToUrl(filename))
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
failed.push(file.filename)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.json({
|
return true
|
||||||
urls: urls,
|
}
|
||||||
failed: failed,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const results = await new Promise((resolve, reject) => {
|
||||||
|
const processedFiles = []
|
||||||
|
|
||||||
|
form.parse(req, async (err, fields, data) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (let file of data.files) {
|
||||||
|
// check if is video need to transcode
|
||||||
|
switch (file.mimetype) {
|
||||||
|
case "video/quicktime": {
|
||||||
|
file.filepath = await videoTranscode(file.filepath, global.uploadCachePath)
|
||||||
|
file.newFilename = path.basename(file.filepath)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move file to upload path
|
||||||
|
await fs.promises.rename(file.filepath, path.join(global.uploadPath, file.newFilename))
|
||||||
|
|
||||||
|
// push final filepath to urls
|
||||||
|
processedFiles.push({
|
||||||
|
name: file.originalFilename,
|
||||||
|
id: file.newFilename,
|
||||||
|
url: resolveToUrl(file.newFilename, req),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(processedFiles)
|
||||||
|
})
|
||||||
|
}).catch((err) => {
|
||||||
|
res.status(400).json({
|
||||||
|
error: err.message,
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (results) {
|
||||||
|
return res.json(results)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ Array.prototype.updateFromObjectKeys = function (obj) {
|
|||||||
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Server as LinebridgeServer } from "linebridge/dist/server"
|
import { Server as LinebridgeServer } from "linebridge/dist/server"
|
||||||
|
import express from "express"
|
||||||
import bcrypt from "bcrypt"
|
import bcrypt from "bcrypt"
|
||||||
import passport from "passport"
|
import passport from "passport"
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ class Server {
|
|||||||
middlewares = require("./middlewares")
|
middlewares = require("./middlewares")
|
||||||
|
|
||||||
httpInstance = new LinebridgeServer({
|
httpInstance = new LinebridgeServer({
|
||||||
|
httpEngine: "express",
|
||||||
port: this.httpListenPort,
|
port: this.httpListenPort,
|
||||||
wsPort: this.wsListenPort,
|
wsPort: this.wsListenPort,
|
||||||
headers: {
|
headers: {
|
||||||
@ -65,6 +67,9 @@ class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.httpInstance.httpInterface.use(express.json())
|
||||||
|
this.httpInstance.httpInterface.use(express.urlencoded({ extended: true }))
|
||||||
|
|
||||||
this.httpInstance.wsInterface["clients"] = []
|
this.httpInstance.wsInterface["clients"] = []
|
||||||
this.httpInstance.wsInterface["findUserIdFromClientID"] = (searchClientId) => {
|
this.httpInstance.wsInterface["findUserIdFromClientID"] = (searchClientId) => {
|
||||||
return this.httpInstance.wsInterface.clients.find(client => client.id === searchClientId)?.userId ?? false
|
return this.httpInstance.wsInterface.clients.find(client => client.id === searchClientId)?.userId ?? false
|
||||||
@ -83,7 +88,10 @@ class Server {
|
|||||||
global.wsInterface = this.httpInstance.wsInterface
|
global.wsInterface = this.httpInstance.wsInterface
|
||||||
global.httpListenPort = this.listenPort
|
global.httpListenPort = this.listenPort
|
||||||
global.globalPublicURI = this.env.globalPublicURI
|
global.globalPublicURI = this.env.globalPublicURI
|
||||||
|
|
||||||
global.uploadPath = this.env.uploadPath ?? path.resolve(process.cwd(), "uploads")
|
global.uploadPath = this.env.uploadPath ?? path.resolve(process.cwd(), "uploads")
|
||||||
|
global.uploadCachePath = this.env.uploadCachePath ?? path.resolve(process.cwd(), "cache")
|
||||||
|
|
||||||
global.jwtStrategy = this.options.jwtStrategy
|
global.jwtStrategy = this.options.jwtStrategy
|
||||||
global.signLocation = this.env.signLocation
|
global.signLocation = this.env.signLocation
|
||||||
|
|
||||||
@ -155,10 +163,6 @@ class Server {
|
|||||||
req.jwtStrategy = this.options.jwtStrategy
|
req.jwtStrategy = this.options.jwtStrategy
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
this.httpInstance.middlewares["useWS"] = (req, res, next) => {
|
|
||||||
req.ws = global.wsInterface
|
|
||||||
next()
|
|
||||||
}
|
|
||||||
|
|
||||||
passport.use(new LocalStrategy({
|
passport.use(new LocalStrategy({
|
||||||
usernameField: "username",
|
usernameField: "username",
|
||||||
@ -183,6 +187,11 @@ class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initWebsockets() {
|
initWebsockets() {
|
||||||
|
this.httpInstance.middlewares["useWS"] = (req, res, next) => {
|
||||||
|
req.ws = global.wsInterface
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
const onAuthenticated = (socket, user_id) => {
|
const onAuthenticated = (socket, user_id) => {
|
||||||
this.attachClientSocket(socket, user_id)
|
this.attachClientSocket(socket, user_id)
|
||||||
socket.emit("authenticated")
|
socket.emit("authenticated")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user