diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..ca71811 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +.* +!*.gitignore +*~ + +*.blend +*dump.sql +*.log +*.mp4 +*.nohup +*.tar.gz +*.txt +*.webm +*.zip + +avatar/ +blender/ +log/ +node_modules/ +public/users/ +sinais/ +uploads/ diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..910bacf --- /dev/null +++ b/Makefile @@ -0,0 +1,71 @@ +SCRIPT_NAME := wikilibras-db-api +SCRIPT_PATH := $(CURDIR)/${SCRIPT_NAME} +SCRIPT_PATH_TMP := /tmp/${SCRIPT_NAME} +SCRIPT_PATH_ETC := /etc/init.d/${SCRIPT_NAME} +NPM := $(shell which npm) + +install: + @ npm install + @ sudo npm install -g supervisor + +autostart-enable: + @ install -m 777 -p ${SCRIPT_PATH} ${SCRIPT_PATH_TMP} + @ sed -i "s##$(CURDIR)#" ${SCRIPT_PATH_TMP} + @ sed -i "s##${SCRIPT_NAME}#" ${SCRIPT_PATH_TMP} + @ sed -i "s##${NPM}#" ${SCRIPT_PATH_TMP} + @ sudo install -m 755 -p ${SCRIPT_PATH_TMP} ${SCRIPT_PATH_ETC} + @ sudo update-rc.d -f ${SCRIPT_NAME} remove + @ sudo update-rc.d -f ${SCRIPT_NAME} defaults + +autostart-disable: + @ sudo update-rc.d -f ${SCRIPT_NAME} remove + @ sudo rm -f ${SCRIPT_PATH_ETC} + +clean: + @ sudo chmod -R 777 ./public/users/ ./uploads/ + @ find ./public/users/ -type d -empty -delete + +distclean: + @ sudo rm -rf ./public/users/ ./uploads/ + +run: + @ sudo ${NPM} start + +unistall: distclean + @ rm -rf ./node_modules/ + +ROLENAME := wikilibras +DATABASE := wikilibras +PASSWORD := wikilibras123 +DBFILEIN := wikilibras-db-api.sql +DBOUTBAK := wikilibras-db-api-dump.sql +VERBOSE := #-v + +create-db: .create-role .create-db .restore-db +drop-db: .restart-db .drop-db .drop-role + +dump-db: + @ sudo su postgres -c "PGPASSWORD='${PASSWORD}' pg_dump ${VERBOSE} -b --inserts ${ROLENAME};" > ${DBOUTBAK} + +.create-db: + @ sudo su postgres -c "psql -c \"CREATE DATABASE ${DATABASE} OWNER ${ROLENAME};\"" + +.create-role: + @ sudo su postgres -c "psql -c \"CREATE USER ${ROLENAME} WITH PASSWORD '${PASSWORD}';\"" + +.drop-db: + @ sudo su postgres -c "psql -c \"DROP DATABASE ${DATABASE};\"" + +.drop-role: + @ sudo su postgres -c "psql -c \"DROP USER ${ROLENAME};\"" + +populate-db: + @ sudo su postgres -c "psql --set ON_ERROR_STOP=off -f ${DBOUTBAK} ${ROLENAME}" + +.restart-db: + @ sudo service postgresql restart + +.restore-db: + @ sudo su postgres -c "psql --set ON_ERROR_STOP=off -f ${DBFILEIN} ${ROLENAME}" + +.PHONY: autostart-enable autostart-disable clean distclean install run uninstall \ No newline at end of file diff --git a/app.js b/app.js new file mode 100755 index 0000000..bd64f1e --- /dev/null +++ b/app.js @@ -0,0 +1,94 @@ +var _ = require("lodash"); +var async = require("async"); +var bodyParser = require("body-parser"); +var cookieParser = require("cookie-parser"); +var express = require("express"); +var favicon = require("serve-favicon"); +var http = require("http"); +var logger = require("morgan"); +var multer = require("multer"); +var path = require("path"); +var util = require("util"); +var files = require(path.join(__dirname, "helpers/files")); +var routes = require(path.join(__dirname, "routes/index")); +var users = require(path.join(__dirname, "routes/users")); +var app = express(); +var server = http.createServer(app); + +var upload = multer( +{ + dest: "uploads/" +}); + +server.maxConnections = 5000; + +// static path +app.use("/sinais", express.static(path.join(__dirname, "sinais"))); +app.use("/avatar", express.static(path.join(__dirname, "avatar"))); +app.use("/blender", express.static(path.join(__dirname, "blender"))); +app.use("/public", express.static(path.join(__dirname, "public"))); + +// view engine setup +app.set("views", path.join(__dirname, "views")); +app.set("view engine", "pug"); + +// uncomment after placing your favicon in /public +app.use(favicon(path.join(__dirname, "public", "img", "favicon.ico"))); +app.use(logger("dev")); +app.use(bodyParser.json({limit: "50mb"})); +app.use(bodyParser.urlencoded( +{ + extended: true +})); + +app.use(cookieParser()); +app.use(function(req, res, next) +{ + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE"); + res.header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type"); + next(); +}); + +app.use("/", routes); +app.use("/users", users); + +// catch 404 and forward to error handler +app.use(function(req, res, next) +{ + var err = new Error("Not Found"); + err.status = 404; + next(err); +}); + +// error handlers + +// development error handler +// will print stacktrace +if (app.get("env") === "development") +{ + app.use(function(err, req, res, next) + { + res.status(err.status || 500); + res.render("error" + , { + message: err.message + , error: err + }); + }); +} + +// production error handler +// no stacktraces leaked to user +app.use(function(err, req, res, next) +{ + res.status(err.status || 500); + res.render("error" + , { + message: err.message + , error: + {} + }); +}); + +module.exports = app; diff --git a/bin/www b/bin/www new file mode 100644 index 0000000..42ff926 --- /dev/null +++ b/bin/www @@ -0,0 +1,90 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../app'); +var debug = require('debug')('wikilibrasdbapi:server'); +var http = require('http'); + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '200'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/helpers/files.js b/helpers/files.js new file mode 100755 index 0000000..a5079bb --- /dev/null +++ b/helpers/files.js @@ -0,0 +1,104 @@ +var parameters = require('../helpers/parameters'); +var http = require('http'); +var fs = require('fs'); +var _ = require('lodash'); + +/* +* Função que processa o vídeo (seja baixando, seja pegando o vídeo enviado) +* Deve retornar um objeto contendo o nome e o caminho +*/ +function downloadAndMoveVideo(req, folder, callback) +{ + console.log(req.files[0].path + " " + req.files[0].originalname); + // Se enviou o arquivo na requisição + if (req.files[0].fieldname !== undefined) + { + // Se a validação falhar + if (parameters.checkVideo(req.files[0].originalname) === false) + { + var error = 'Vídeo enviado com extensão inválida'; + return callback(error); + } + /* Move o vídeo submetido para a pasta com o seu ID correspondente */ + try + { + if (!_.isEmpty(req.body.wikilibras)) + { + fs.renameSync(req.files[0].path, 'avatar/' + req.files[0].originalname); + fs.renameSync(req.files[1].path, 'blender/' + req.files[1].originalname); + } + else + { + fs.renameSync(req.files[0].path, 'uploads/' + req.files[0].originalname); + } + } + catch (err) + { + console.log("Erro ao mover o vídeo submetido: " + err); + callback("Erro ao mover o vídeo submetido: " + err); + } + return callback(); + } // Se o arquivo não foi enviado, mas um video_url foi + else if (req.body.video_url !== undefined) + { + // Requisição para baixar o vídeo + http.get(req.body.video_url, function(response) + { + + // Se o vídeo não foi baixado com sucesso + if (response.statusCode !== 200) + { + var error = 'Problema ao carregar video_url: status ' + response.statusCode; + return callback(error); + } + + // Nome do arquivo + var filename = req.body.video_url.substring(req.body.video_url.lastIndexOf('/') + 1); + + // Tira os parâmetros HTTP + if (filename.lastIndexOf("?") !== -1) + { + filename = filename.substring(0, filename.lastIndexOf("?")); + } + + var path = folder + '/' + filename; + + // Cria o stream para escrita + var file = fs.createWriteStream(path); + + // Salva o arquivo em disco + response.pipe(file); + + // Quando a escrita acabar + file.on('finish', function() + { + // Fecha o arquivo + file.close(function() + { + + // Retorna o vídeo baixado + locals.video = { + 'path': path + }; + + // Chama o callback para prosseguir execução + callback(); + }); + }); + + // Se deu erro na requisição de baixar o vídeo + }).on('error', function(e) { + var error = 'Problema ao carregar video_url: ' + e.message; + return callback(error); + }); + + // Se nem o vídeo foi enviado e nem o video_url foi preenchido + } + else + { + var error = "Video deve ser enviado como parâmetro 'video' ou como 'video_url'"; + return callback(error); + } +} + +module.exports.downloadAndMoveVideo = downloadAndMoveVideo; diff --git a/helpers/parameters.js b/helpers/parameters.js new file mode 100755 index 0000000..3d145c5 --- /dev/null +++ b/helpers/parameters.js @@ -0,0 +1,182 @@ +function getServiceType(service_type) +{ + switch(service_type) + { + case 'video-legenda': return 2; break; + case 'video': return 3; break; + case 'texto': return 4; break; + case 'ios': return 4; break; + case 'legenda': return 5; break; + case 'audio': return 6; break; + } +}; + +function getLanguage(language) +{ + switch(language) + { + case 'portugues': return 'portugues'; break; + case 'glosa': return 'glosa'; break; + } +}; + +function getPosition(position) +{ + switch(position) + { + case 'superior-esquerdo': return 'top_left'; break; + case 'superior-direito': return 'top_right'; break; + case 'inferior-direito': return 'bottom_right'; break; + case 'inferior-esquerdo': return 'bottom_left'; break; + } +}; + +function getSize(size) +{ + switch(size) + { + case 'pequeno': return 1; break; + case 'medio': return 2; break; + case 'grande': return 3; break; + } +}; + +function getTransparency(transparency) +{ + switch(transparency) + { + case 'opaco': return 'opaque'; break; + case 'transparente': return 'transp'; break; + } +}; + +function getSize(size) +{ + switch(size) + { + case 'pequeno': return 'small'; break; + case 'medio': return 'medium'; break; + case 'grande': return 'large'; break; + } +}; + +function checkServiceType(service_type) +{ + var t_types = ['video', 'texto']; + for (var i = 0; i < t_types.length; i++) + { + if (service_type === t_types[i]) + { + return true; + } + } + return false; +}; + +function checkLanguage(language) +{ + var t_types = ['portugues', 'glosa']; + for (var i = 0; i < t_types.length; i++) + { + if (language === t_types[i]) + { + return true; + } + } + return false; +}; + +function checkPosition(position) +{ + var t_types = ['superior-esquerdo', 'superior-direito', 'inferior-esquerdo', 'inferior-direito']; + for (var i = 0; i < t_types.length; i++) + { + if (position === t_types[i]) + { + return true; + } + } + return false; +}; + +function checkSize(size) +{ + var t_types = ['pequeno', 'medio', 'grande']; + for (var i = 0; i < t_types.length; i++) + { + if (size === t_types[i]) + { + return true; + } + } + return false; +}; + +function checkTransparency(transparency) +{ + var t_types = ['opaco', 'transparente']; + for (var i = 0; i < t_types.length; i++) + { + if (transparency === t_types[i]) + { + return true; + } + } + return false; +}; + +function checkVideo(file) { + var accepted_file_types = ['flv', 'ts', 'avi', 'mp4', 'mov', 'webm', 'wmv', 'mkv', 'srt']; + return check_type(file, accepted_file_types); +}; + +function checkSubtitle(file) +{ + var accepted_file_types = ['srt']; + return check_type(file, accepted_file_types); +}; + +function checkAudio(file) +{ + var accepted_file_types = ['mp3', 'wav', 'aac', 'flac', 'ogg', 'wma']; + return check_type(file, accepted_file_types); +}; + +function check_type(file, accepted_file_types) +{ + var ext = file.substring(file.lastIndexOf('.') + 1).toLowerCase(); + var isValidFile = false; + for (var i = 0; i < accepted_file_types.length; i++) + { + if (ext == accepted_file_types[i]) + { + isValidFile = true; + break; + } + } + if (!isValidFile) + { + file.value = null; + } + return isValidFile; +}; + +function errorMessage(message) +{ + return JSON.stringify({ 'error': message }) +}; + +module.exports.getServiceType = getServiceType; +module.exports.getLanguage = getLanguage; +module.exports.getPosition = getPosition; +module.exports.getSize = getSize; +module.exports.getTransparency = getTransparency; +module.exports.checkServiceType = checkServiceType; +module.exports.checkLanguage = checkLanguage; +module.exports.checkPosition = checkPosition; +module.exports.checkSize = checkSize; +module.exports.checkTransparency = checkTransparency; +module.exports.checkVideo = checkVideo; +module.exports.checkSubtitle = checkSubtitle; +module.exports.checkAudio = checkAudio; +module.exports.errorMessage = errorMessage; diff --git a/inicial.txt b/inicial.txt deleted file mode 100644 index 7162174..0000000 --- a/inicial.txt +++ /dev/null @@ -1 +0,0 @@ -Repositorio wikilibrasdbapi Criado diff --git a/package.json b/package.json new file mode 100755 index 0000000..e8e3c8f --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "Wikilibras-DBAPI", + "description": "", + "version": "1.0.0", + "private": true, + "scripts": + { + "start": "supervisor ./bin/www" + }, + "dependencies": + { + "bluebird": "~3.4.6", + "body-parser": "~1.13.2", + "cookie-parser": "~1.3.5", + "debug": "~2.2.0", + "express": "~4.13.1", + "lodash": "~4.15.0", + "mkdirp": "~0.5.1", + "morgan": "~1.6.1", + "multer": "~1.2.0", + "pg": "^4.4.6", + "pg-promise": "~5.3.3", + "pug": "~2.0.0-beta6", + "serve-favicon": "~2.3.0" + } +} diff --git a/public/img/favicon.ico b/public/img/favicon.ico new file mode 100644 index 0000000..40e3b98 Binary files /dev/null and b/public/img/favicon.ico differ diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css new file mode 100755 index 0000000..9453385 --- /dev/null +++ b/public/stylesheets/style.css @@ -0,0 +1,8 @@ +body { + padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +a { + color: #00B7FF; +} diff --git a/routes/index.js b/routes/index.js new file mode 100755 index 0000000..33a929f --- /dev/null +++ b/routes/index.js @@ -0,0 +1,645 @@ +var express = require("express"); +var promise = require("bluebird"); // or any other Promise/A+ compatible library; +var _ = require("lodash"); +var pgp = require("pg-promise")({promiseLib: promise}); // overriding the default (ES6 Promise); +var db = pgp("postgres://wikilibras:wikilibras123@localhost:5432/wikilibras"); +var parameters = require("../helpers/parameters"); +var async = require("async"); +var files = require("../helpers/files.js"); +var fs = require('fs'); +var path = require("path"); +var util = require("util"); +var mkdirp = require("mkdirp"); +var multer = require("multer"); +var upload = multer({dest: "uploads/"}); +var users = path.join(__dirname, "../public/users"); +var sys = require("util"); +var exec = require("child_process").exec; +var child = null; +var router = express.Router(); + +// GET home page +router.get("/", function(req, res, next) +{ + res.render("index" + , { + title: "Wikilibras-DB API", + version: "1.0.0" + }); +}); + +router.get("/countvideo", function(req, res, next) +{ + var response = {}; + db.query('SELECT COUNT(*) FROM sinal;') + .then(function(result) + { + // response.status = true; + // response.message = ""; + // response.data = result; + // TODO uncomment lines above, remove the line below and + response = [parseInt(result[0].count)]; + res.status(203); + }) + .catch(function(error) + { + response.status = false; + response.message = ""; + response.data = error; + res.status(500); + }) + .finally(function() + { + res.send(response); + pgp.end(); + }); +}); + +router.get("/listselos", function(req, res, next) +{ + var response = {}; + db.query('SELECT "nomeSelo", "idSelo" FROM "tipoSelo";') + .then(function(result) + { + // response.status = true; + // response.data = result; + response = result; + res.status(203); + }) + .catch(function(error) + { + response.status = false; + response.error = error; + res.status(500); + }) + .finally(function() + { + res.send(response); + pgp.end(); + }); +}); + +router.get("/countuservideos", function(req, res, next) +{ + var response = {}; + var limit = 10; + if (!_.isEmpty(req.query.limit)) + { + var _limit = parseInt(req.query.limit); + if ((0 <= _limit) && (_limit <= 1000)) + { + limit = _limit; + } + } + var query = 'SELECT DISTINCT' + + ' usuario as username,' + + ' "idUsuario" as email,' + + ' CAST(COUNT(*) as INTEGER) as videos' + + ' FROM sinal' + + ' GROUP BY usuario, "idUsuario"' + + ' ORDER BY videos DESC, usuario' + + ' LIMIT ' + limit + ';'; + db.query(query) + .then(function(result) + { + // response.status = true; + // response.data = result; + response = result; + res.status(203); + }) + .catch(function(error) + { + response.status = false; + response.error = error; + console.log(error); + res.status(500); + }) + .finally(function() + { + res.send(response); + pgp.end(); + }); +}); + +router.get("/listall", function(req, res, next) +{ + var response = {}; + // db.query('SELECT s."nome", s."data", s.cidade, s.estado, s.file, s.avatar, s.blender, ts."nomeSelo", s."version" FROM "sinal" s, "tipoSelo" ts WHERE s."idSelo" = ts."idSelo"') + db.query('SELECT s."data", s."usuario", s."idSinal", s."idSelo", s."nome", s."classe", s."frase", s."estado", s."cidade", s."file" FROM "sinal" s, "tipoSelo" ts WHERE s."idSelo" = ts."idSelo"') + .then(function(result) + { + response = result; + res.status(203); + }) + .catch(function(error) + { + response = error; + res.status(500); + }) + .finally(function() + { + res.send(response); + pgp.end(); + }); +}); + +/* +// only for test +router.get("/populate", function(req, res, next) +{ + var response = {}; + exec("sudo su postgres -c 'psql --set ON_ERROR_STOP=off -f ../wikilibras-db-api-dump.sql wikilibras'", + function(error, stdout, stderr) + { + if (error) + { + response.status = false; + response.message = "Error while populate"; + console.log(stdout); + res.status = 500; + } + else if (stderr.length > 0) + { + response.status = false; + response.message = "The Database already been populated"; + console.log(stderr); + res.status = 304; + } + else + { + response.status = true; + response.message = "The Database has been populated"; + res.status = 201; + } + res.send(response); + }); +}); +*/ + +router.get("/reset", function(req, res, next) +{ + var response = {}; + db.query('TRUNCATE TABLE sinal, selo; ALTER SEQUENCE sequence RESTART WITH 1;') + .then(function(result) + { + response.status = true; + response.message = "The database has been truncated"; + res.status(203); + }) + .catch(function(error) + { + response.status = false; + response.message = "The database cannot be truncated"; + response.error = error; + res.status(500); + }) + .finally(function() + { + res.send(response); + pgp.end(); + // console.log(JSON.stringify(response, null, 4)); + }); +}); + +router.get("/version", function(req, res, next) +{ + var response = []; + db.query('SELECT MAX(version) FROM sinal;') + .then(function(result) + { + response = { "version": result[0].max }; + }) + .catch(function(error) + { + response = error; + }) + .finally(function() + { + pgp.end(); + res.status(203).send(response); + }); +}); + +router.get("/sinais", function(req, res, next) +{ + var results = []; + if ((!_.isEmpty(req.param("selo"))) && (!_.isEmpty(req.param("version")))) + { + db.query('SELECT s."nome", s."data", s.cidade, s."version", s.estado, s.file, s.avatar, s.blender, ts."nomeSelo" FROM "sinal" s, "tipoSelo" ts WHERE s."idSelo" = ts."idSelo" AND ts."nomeSelo" = $1 AND s."version" = $2', [req.param("selo"), req.param("version")]) + .then(function(result) + { + res.status(203).send(result); + }) + .catch(function(error) + { + // error; + }) + .finally(function() + { + pgp.end(); + }); + } + else + { + if (!_.isEmpty(req.param("version"))) + { + db.query("select nome, data, file, avatar, blender version FROM sinal WHERE version = $1", req.param("version")) + .then(function(result) + { + res.status(203).send(result); + }) + .catch(function(error) + { + // error; + }) + .finally(function() + { + pgp.end(); + }); + } + if (!_.isEmpty(req.param("selo"))) + { + db.query('SELECT s."nome", s."data", s.cidade, s.estado, s.file, s.avatar, s.blender, ts."nomeSelo", s.version FROM "sinal" s, "tipoSelo" ts WHERE s."idSelo" = ts."idSelo" AND ts."nomeSelo" = $1', req.param("selo")) + + .then(function(result) + { + res.status(203).send(result); + }) + .catch(function(error) + { + // error; + }) + .finally(function() + { + pgp.end(); + }); + } + } +}); + +router.post('/gsinal', function(req, res, next) +{ + console.log("\n\n\n============================================="); + console.log("[" + new Date().toISOString() + "] Requisição do IP: " + req.ip); + console.log("== Parametros: " + util.inspect(req.body)); + console.log("== Body: " + JSON.stringify(req.headers)); + db.query('UPDATE sinal SET "idSelo" = ($1), version = version + 1 WHERE nome = ($2)', [req.body.selo, req.body.nome]) + .then(function(data) + { + res.status(203).send('Sinal ' + req.body.nome + ' atualizado com sucesso [without archive]'); + }) + .catch(function(error) + { + console.log("Erro " + error); + }); +}); + +/* +router.post('/addsinal', upload.array('video', 2), function(req, res, next) +{ + console.log("\n\n\n============================================="); + console.log("[" + new Date().toISOString() + "]"); + console.log("From: " + req.ip); + // console.log("Files: " + req.files[0]); + // console.log("headers: " + JSON.stringify(req.headers)); + console.log("body: " + JSON.stringify(req.body)); + + if (req.method === "OPTIONS") + { + res.header('Access-Control-Allow-Origin', req.headers.origin); + } + else + { + res.header('Access-Control-Allow-Origin', '*'); + } + + if (_.isEmpty(req.body.nome)) + { + res.send(500, 'O valor do parâmetro nome está vazio'); + return; + } + async.series([ + function(callback) + { + console.log("\t >> ORDEM 1"); + if (_.isEmpty(req.body.wikilibras)) + { + console.log("Movendo arquivo para conversão..."); + files.downloadAndMoveVideo(req, 'sinais', callback); + } + else + { + files.downloadAndMoveVideo(req, 'wikilibras', callback); + } + }, + function(callback) + { + console.log("\t >> ORDEM 2"); + if (_.isEmpty(req.body.wikilibras)) + { + console.log("Convertendo para .webm..."); + child = exec('avconv -v error -i uploads/' + req.files[0].originalname + ' -acodec libvorbis -vcodec libvpx -an sinais/' + req.files[0].originalname.slice(0, -4) + '.webm -y', function(error, stdout, stderr) + { + console.log('stdout: ' + stdout); + console.log('stderr: ' + stderr); + if (error !== null) + { + console.log('exec error: ' + error); + } + }); + + child.on('exit', function() + { + setTimeout(function() + { + console.log("Conversão para webm concluída."); + callback(null, 1); + }, 500); + }); + } + else + { + callback(); + } + }, + function(callback) + { + console.log("== Alterando o db"); + db.query('SELECT "idSelo" FROM "sinal" WHERE nome = $1', req.body.nome) + .then(function(result) + { + var d = new Date(); + if (Object.keys(result).length > 0) + { + if (!_.isEmpty(req.body.overwrite)) + { + if (!_.isEmpty(req.body.wikilibras)) + { + db.query('UPDATE sinal SET avatar = ($1), blender = ($2) WHERE nome = ($3)', [req.files[0].originalname, req.files[1].originalname, req.body.nome]) + .then(function(data) + { + res.send(200, 'Sinal ' + req.body.nome + ' atualizado com sucesso [with avatar/blender].'); + }) + .catch(function(error) + { + console.log("Erro " + error); + }); + } + if (!_.isEmpty(req.files)) + { + db.query('UPDATE sinal SET "idSelo" = ($1), version = version + 1, file = ($2) WHERE nome = ($3)', [req.body.selo, req.files[0].originalname, req.body.nome]) + .then(function(data) + { + res.status(200).send('Sinal ' + req.body.nome + ' atualizado com sucesso [with archive].'); + }) + .catch(function(error) + { + console.log("Erro " + error); + }); + } + else + { + db.query('UPDATE sinal SET "idSelo" = ($1) WHERE nome = ($2)', [req.body.selo, req.body.nome]) + .then(function(data) + { + res.status(200).send('Sinal ' + req.body.nome + ' atualizado com sucesso [without archive].'); + }) + .catch(function(error) + { + console.log("Erro " + error); + }); + } + } + else + { + res.status(500).send('Sinal já cadastrado no sistema'); + } + } + else + { + if (_.isEmpty(req.body.selo)) + { + req.body.selo = 7; + } + db.one('insert into sinal (usuario, nome, "idSelo", data, version, estado, cidade, file) values ($1, $2, $3, $4, $5, $6, $7, $8) returning "idSinal"', [req.body.login, req.body.nome, req.body.selo, d.getFullYear() + "/" + d.getMonth() + "/" + d.getDate(), 1, req.body.estado, req.body.cidade, req.files[0].originalname.slice(0, -4) + '.webm']) + .then(function(data) + { + res.status(200).send('Sinal ' + req.body.nome + ' adicionado com sucesso.'); + }) + .catch(function(error) + { + console.log("Erro " + error); + }); + } + }) + .catch(function(error) + { + console.log(error); + }) + .finally(function() + { + pgp.end(); + }); + } + , ], function(err) + { + if (err) + { + res.send(500, "Error"); + return; + } + }); +}); +*/ + +router.post('/addsinal', upload.array('video', 2), function(req, res, next) +{ + // console.log("[" + new Date().toISOString() + "]"); + // console.log("From: " + req.ip); + // console.log("Files: " + JSON.stringify(req.files[0], null, 4)); + // console.log("headers: " + JSON.stringify(req.headers, null, 4)); + // console.log("body: " + JSON.stringify(req.body, null, 4)); + if (req.method === "OPTIONS") + { + res.header('Access-Control-Allow-Origin', req.headers.origin); + } + else + { + res.header('Access-Control-Allow-Origin', '*'); + } + if (req.files.length < 1) + { + res.status(500).send("ERROR: O campo 'video' não contém nenhum arquivo do sinal"); + return; + } + var directory = users; + var login = req.body["login"]; + var estado = req.body["estado"]; + var classe = req.body["classe-gramatical"]; + var nome = req.body["nome"]; + var cidade = req.body["cidade"]; + var frase = req.body["frases"]; + var selo = req.body["selo"]; + var filesize = req.files[0].size; + var input = req.files[0].path; + var videoref = ""; + var output = ""; + var maxfilesize = 25; // MB + if (filesize > (maxfilesize * 1048576)) + { + res.status(500).send("O tamanho do arquivo deve ter no máximo " + maxfilesize + " MB"); + return; + } + if (_.isEmpty(nome)) + { + res.status(500).send("ERROR: O campo 'nome' não foi encontrado"); + return; + } + else + { + nome = nome.trim().toUpperCase(); + videoref = nome + "_REF.webm"; + } + if (_.isEmpty(selo)) + { + selo = 7; + } + if (_.isEmpty(login)) + { + console.log("WARNING: O campo 'login' não foi encontrado"); + } + else + { + directory = path.join(directory, login); + } + if (_.isEmpty(estado)) + { + console.log("WARNING: O campo 'estado' não foi encontrado"); + } + else + { + directory = path.join(directory, estado); + } + if (_.isEmpty(classe)) + { + console.log("WARNING: O campo 'classe' não foi encontrado"); + } + else + { + directory = path.join(directory, classe); + } + if (!fs.existsSync(directory)) + { + console.log("Criando diretorio: " + directory); + mkdirp(directory, function (err) + { + if (err) + { + console.error(err); + res.status(500).send(err); + return; + } + else + { + console.log("Diretório criado com sucesso!"); + } + }); + } + output = path.join(directory, videoref); + async.series([ + // Converter para formato webm + function(callback) + { + console.log("Convertendo para webm..."); + child = exec("avconv -y -v error -i \"" + input + "\" -acodec libvorbis -vcodec libvpx -an \"" + output + "\"", + function(error, stdout, stderr) + { + if (error || (stderr.length > 0)) + { + var log = "O arquivo de vídeo é inválido, ou ocorreu um erro durante a conversão\nDetalhes: " + stdout + stderr; + log = log.trim(); + res.status(500).send(log.trim()); + callback(true, log); + } + else + { + res.status(200); + return 0; + } + }); + child.on("exit", function(code) + { + setTimeout(function() + { + if (code === 0) + { + console.log("Conversão concluída"); + callback(null, 1); + } + else + { + callback(true, new Error("Erro durante conversão")); + } + }, 500); + }); + }, + function(callback) + { + console.log("Verificando se este sinal já existe"); + db.query('SELECT "idSinal" FROM "sinal" WHERE usuario = ($1) AND estado = ($2) AND classe = ($3) AND nome = ($4)', [login, estado, classe, nome]) + .then(function(result) + { + var d = new Date(); + var date = d.getFullYear() + "/" + d.getMonth() + "/" + d.getDate(); + if (Object.keys(result).length > 0) + { + var idSinal = result[0].idSinal; + console.log("Existe, atualizando o sinal[" + idSinal + "]: '" + nome + "'"); + db.query('UPDATE sinal SET "idSelo" = ($1), version = ($2), data = ($3), cidade = ($4), frase = ($5), file = ($6) WHERE "idSinal" = ($7)', [7, 0, date, cidade, frase, videoref, idSinal]) + .then(function(data) + { + res.status(200).send("Sinal atualizado"); + }) + .catch(function(error) + { + var message = "Erro ao atualizar sinal '" + nome + "'\n" + error; + console.log(message); + res.status(500).send(message); + }); + } + else + { + console.log("Não existe, inserindo o sinal: '" + nome + "'"); + db.one('INSERT INTO sinal (usuario, nome, classe, "idSelo", data, version, estado, cidade, frase, file) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING "idSinal"', [login, nome, classe, selo, date, 0, estado, cidade, frase, videoref]) + .then(function(data) + { + var message = "Sinal[" + data.idSinal + "]: '" + nome + "', inserido com sucesso"; + console.log(message); + res.status(200).send(message); + }) + .catch(function(error) + { + res.status(500).send(error); + }); + } + }) + .catch(function(error) + { + console.log(error); + }) + .finally(function() + { + pgp.end(); + }); + } + ], + function(err, results) + { + if (err) + { + console.log(results[0]); + } + }); +}); + +module.exports = router; diff --git a/routes/users.js b/routes/users.js new file mode 100644 index 0000000..623e430 --- /dev/null +++ b/routes/users.js @@ -0,0 +1,9 @@ +var express = require('express'); +var router = express.Router(); + +/* GET users listing. */ +router.get('/', function(req, res, next) { + res.send('respond with a resource'); +}); + +module.exports = router; diff --git a/views/error.pug b/views/error.pug new file mode 100755 index 0000000..51ec12c --- /dev/null +++ b/views/error.pug @@ -0,0 +1,6 @@ +extends layout + +block content + h1= message + h2= error.status + pre #{error.stack} diff --git a/views/index.pug b/views/index.pug new file mode 100755 index 0000000..f1bc8da --- /dev/null +++ b/views/index.pug @@ -0,0 +1,5 @@ +extends layout + +block content + h1= title + p Welcome to #{title} #{version} diff --git a/views/layout.pug b/views/layout.pug new file mode 100755 index 0000000..15af079 --- /dev/null +++ b/views/layout.pug @@ -0,0 +1,7 @@ +doctype html +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body + block content diff --git a/wikilibras-db-api b/wikilibras-db-api new file mode 100644 index 0000000..a21102b --- /dev/null +++ b/wikilibras-db-api @@ -0,0 +1,30 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Service provided +# Description: Enable service provided API at boot +### END INIT INFO + +set -e + +export PATH="$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +CWD="" + +case "$1" in + start) + cd "$CWD" + "" start& + ;; + *) + echo "Usage: /etc/init.d/ {start}" + exit 1 + ;; +esac + +exit 0 diff --git a/wikilibras-db-api.sql b/wikilibras-db-api.sql new file mode 100644 index 0000000..ec458a7 --- /dev/null +++ b/wikilibras-db-api.sql @@ -0,0 +1,89 @@ +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SET check_function_bodies = false; +SET client_min_messages = warning; + +CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; + +COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; + +SET search_path = public, pg_catalog; + +SET default_tablespace = ''; + +SET default_with_oids = false; + +CREATE TABLE selo ( + "idSelo" integer NOT NULL, + "idSinal" integer, + data character varying(30), + estado character varying(30), + cidade character varying(30) +); + +ALTER TABLE public.selo OWNER TO wikilibras; + +CREATE SEQUENCE sequence + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER TABLE public.sequence OWNER TO wikilibras; + +CREATE TABLE sinal ( + data character varying(30), + -- TODO create table users + "idUsuario" integer, + usuario character varying(30), + version integer DEFAULT 0, + "idSinal" integer DEFAULT nextval('sequence'::regclass) NOT NULL, + "idSelo" integer, + nome character varying(30), + classe character varying(30), + frase character varying(100), + estado character varying(30), + cidade character varying(30), + file character varying(100), + blender character varying(100), + avatar character varying(100) +); + +ALTER TABLE public.sinal OWNER TO wikilibras; + +CREATE TABLE "tipoSelo" ( + "nomeSelo" character varying(40), + "idSelo" integer DEFAULT nextval('sequence'::regclass) NOT NULL +); + +ALTER TABLE public."tipoSelo" OWNER TO wikilibras; + +SELECT pg_catalog.setval('sequence', 1, false); + +INSERT INTO "tipoSelo" VALUES ('wikilibras', 1); +INSERT INTO "tipoSelo" VALUES ('especialista', 2); +INSERT INTO "tipoSelo" VALUES ('invalido_wikilibras', 3); +INSERT INTO "tipoSelo" VALUES ('invalido_especialista', 4); +INSERT INTO "tipoSelo" VALUES ('animadores', 5); +INSERT INTO "tipoSelo" VALUES ('invalido_animadores', 6); +INSERT INTO "tipoSelo" VALUES ('null', 7); + +ALTER TABLE ONLY sinal + ADD CONSTRAINT "idSinal" PRIMARY KEY ("idSinal"); + +ALTER TABLE ONLY "tipoSelo" + ADD CONSTRAINT "tipoSelo_pkey" PRIMARY KEY ("idSelo"); + +ALTER TABLE ONLY selo + ADD CONSTRAINT "selo_idSelo_fkey" FOREIGN KEY ("idSelo") REFERENCES "tipoSelo"("idSelo"); + +ALTER TABLE ONLY selo + ADD CONSTRAINT "selo_idSinal_fkey" FOREIGN KEY ("idSinal") REFERENCES sinal("idSinal"); + +REVOKE ALL ON SCHEMA public FROM PUBLIC; +REVOKE ALL ON SCHEMA public FROM postgres; +GRANT ALL ON SCHEMA public TO postgres; +GRANT ALL ON SCHEMA public TO PUBLIC; -- libgit2 0.21.2