Commit bf34bf1e43a19651d5d75d9177066a161fa73338

Authored by André Araújo
1 parent feb33337
Exists in master

First code

.gitignore 0 → 100755
... ... @@ -0,0 +1,21 @@
  1 +.*
  2 +!*.gitignore
  3 +*~
  4 +
  5 +*.blend
  6 +*dump.sql
  7 +*.log
  8 +*.mp4
  9 +*.nohup
  10 +*.tar.gz
  11 +*.txt
  12 +*.webm
  13 +*.zip
  14 +
  15 +avatar/
  16 +blender/
  17 +log/
  18 +node_modules/
  19 +public/users/
  20 +sinais/
  21 +uploads/
... ...
Makefile 0 → 100755
... ... @@ -0,0 +1,71 @@
  1 +SCRIPT_NAME := wikilibras-db-api
  2 +SCRIPT_PATH := $(CURDIR)/${SCRIPT_NAME}
  3 +SCRIPT_PATH_TMP := /tmp/${SCRIPT_NAME}
  4 +SCRIPT_PATH_ETC := /etc/init.d/${SCRIPT_NAME}
  5 +NPM := $(shell which npm)
  6 +
  7 +install:
  8 + @ npm install
  9 + @ sudo npm install -g supervisor
  10 +
  11 +autostart-enable:
  12 + @ install -m 777 -p ${SCRIPT_PATH} ${SCRIPT_PATH_TMP}
  13 + @ sed -i "s#<CURDIR>#$(CURDIR)#" ${SCRIPT_PATH_TMP}
  14 + @ sed -i "s#<SCRIPT_NAME>#${SCRIPT_NAME}#" ${SCRIPT_PATH_TMP}
  15 + @ sed -i "s#<NPM>#${NPM}#" ${SCRIPT_PATH_TMP}
  16 + @ sudo install -m 755 -p ${SCRIPT_PATH_TMP} ${SCRIPT_PATH_ETC}
  17 + @ sudo update-rc.d -f ${SCRIPT_NAME} remove
  18 + @ sudo update-rc.d -f ${SCRIPT_NAME} defaults
  19 +
  20 +autostart-disable:
  21 + @ sudo update-rc.d -f ${SCRIPT_NAME} remove
  22 + @ sudo rm -f ${SCRIPT_PATH_ETC}
  23 +
  24 +clean:
  25 + @ sudo chmod -R 777 ./public/users/ ./uploads/
  26 + @ find ./public/users/ -type d -empty -delete
  27 +
  28 +distclean:
  29 + @ sudo rm -rf ./public/users/ ./uploads/
  30 +
  31 +run:
  32 + @ sudo ${NPM} start
  33 +
  34 +unistall: distclean
  35 + @ rm -rf ./node_modules/
  36 +
  37 +ROLENAME := wikilibras
  38 +DATABASE := wikilibras
  39 +PASSWORD := wikilibras123
  40 +DBFILEIN := wikilibras-db-api.sql
  41 +DBOUTBAK := wikilibras-db-api-dump.sql
  42 +VERBOSE := #-v
  43 +
  44 +create-db: .create-role .create-db .restore-db
  45 +drop-db: .restart-db .drop-db .drop-role
  46 +
  47 +dump-db:
  48 + @ sudo su postgres -c "PGPASSWORD='${PASSWORD}' pg_dump ${VERBOSE} -b --inserts ${ROLENAME};" > ${DBOUTBAK}
  49 +
  50 +.create-db:
  51 + @ sudo su postgres -c "psql -c \"CREATE DATABASE ${DATABASE} OWNER ${ROLENAME};\""
  52 +
  53 +.create-role:
  54 + @ sudo su postgres -c "psql -c \"CREATE USER ${ROLENAME} WITH PASSWORD '${PASSWORD}';\""
  55 +
  56 +.drop-db:
  57 + @ sudo su postgres -c "psql -c \"DROP DATABASE ${DATABASE};\""
  58 +
  59 +.drop-role:
  60 + @ sudo su postgres -c "psql -c \"DROP USER ${ROLENAME};\""
  61 +
  62 +populate-db:
  63 + @ sudo su postgres -c "psql --set ON_ERROR_STOP=off -f ${DBOUTBAK} ${ROLENAME}"
  64 +
  65 +.restart-db:
  66 + @ sudo service postgresql restart
  67 +
  68 +.restore-db:
  69 + @ sudo su postgres -c "psql --set ON_ERROR_STOP=off -f ${DBFILEIN} ${ROLENAME}"
  70 +
  71 +.PHONY: autostart-enable autostart-disable clean distclean install run uninstall
0 72 \ No newline at end of file
... ...
app.js 0 → 100755
... ... @@ -0,0 +1,94 @@
  1 +var _ = require("lodash");
  2 +var async = require("async");
  3 +var bodyParser = require("body-parser");
  4 +var cookieParser = require("cookie-parser");
  5 +var express = require("express");
  6 +var favicon = require("serve-favicon");
  7 +var http = require("http");
  8 +var logger = require("morgan");
  9 +var multer = require("multer");
  10 +var path = require("path");
  11 +var util = require("util");
  12 +var files = require(path.join(__dirname, "helpers/files"));
  13 +var routes = require(path.join(__dirname, "routes/index"));
  14 +var users = require(path.join(__dirname, "routes/users"));
  15 +var app = express();
  16 +var server = http.createServer(app);
  17 +
  18 +var upload = multer(
  19 +{
  20 + dest: "uploads/"
  21 +});
  22 +
  23 +server.maxConnections = 5000;
  24 +
  25 +// static path
  26 +app.use("/sinais", express.static(path.join(__dirname, "sinais")));
  27 +app.use("/avatar", express.static(path.join(__dirname, "avatar")));
  28 +app.use("/blender", express.static(path.join(__dirname, "blender")));
  29 +app.use("/public", express.static(path.join(__dirname, "public")));
  30 +
  31 +// view engine setup
  32 +app.set("views", path.join(__dirname, "views"));
  33 +app.set("view engine", "pug");
  34 +
  35 +// uncomment after placing your favicon in /public
  36 +app.use(favicon(path.join(__dirname, "public", "img", "favicon.ico")));
  37 +app.use(logger("dev"));
  38 +app.use(bodyParser.json({limit: "50mb"}));
  39 +app.use(bodyParser.urlencoded(
  40 +{
  41 + extended: true
  42 +}));
  43 +
  44 +app.use(cookieParser());
  45 +app.use(function(req, res, next)
  46 +{
  47 + res.header("Access-Control-Allow-Origin", "*");
  48 + res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE");
  49 + res.header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type");
  50 + next();
  51 +});
  52 +
  53 +app.use("/", routes);
  54 +app.use("/users", users);
  55 +
  56 +// catch 404 and forward to error handler
  57 +app.use(function(req, res, next)
  58 +{
  59 + var err = new Error("Not Found");
  60 + err.status = 404;
  61 + next(err);
  62 +});
  63 +
  64 +// error handlers
  65 +
  66 +// development error handler
  67 +// will print stacktrace
  68 +if (app.get("env") === "development")
  69 +{
  70 + app.use(function(err, req, res, next)
  71 + {
  72 + res.status(err.status || 500);
  73 + res.render("error"
  74 + , {
  75 + message: err.message
  76 + , error: err
  77 + });
  78 + });
  79 +}
  80 +
  81 +// production error handler
  82 +// no stacktraces leaked to user
  83 +app.use(function(err, req, res, next)
  84 +{
  85 + res.status(err.status || 500);
  86 + res.render("error"
  87 + , {
  88 + message: err.message
  89 + , error:
  90 + {}
  91 + });
  92 +});
  93 +
  94 +module.exports = app;
... ...
bin/www 0 → 100644
... ... @@ -0,0 +1,90 @@
  1 +#!/usr/bin/env node
  2 +
  3 +/**
  4 + * Module dependencies.
  5 + */
  6 +
  7 +var app = require('../app');
  8 +var debug = require('debug')('wikilibrasdbapi:server');
  9 +var http = require('http');
  10 +
  11 +/**
  12 + * Get port from environment and store in Express.
  13 + */
  14 +
  15 +var port = normalizePort(process.env.PORT || '200');
  16 +app.set('port', port);
  17 +
  18 +/**
  19 + * Create HTTP server.
  20 + */
  21 +
  22 +var server = http.createServer(app);
  23 +
  24 +/**
  25 + * Listen on provided port, on all network interfaces.
  26 + */
  27 +
  28 +server.listen(port);
  29 +server.on('error', onError);
  30 +server.on('listening', onListening);
  31 +
  32 +/**
  33 + * Normalize a port into a number, string, or false.
  34 + */
  35 +
  36 +function normalizePort(val) {
  37 + var port = parseInt(val, 10);
  38 +
  39 + if (isNaN(port)) {
  40 + // named pipe
  41 + return val;
  42 + }
  43 +
  44 + if (port >= 0) {
  45 + // port number
  46 + return port;
  47 + }
  48 +
  49 + return false;
  50 +}
  51 +
  52 +/**
  53 + * Event listener for HTTP server "error" event.
  54 + */
  55 +
  56 +function onError(error) {
  57 + if (error.syscall !== 'listen') {
  58 + throw error;
  59 + }
  60 +
  61 + var bind = typeof port === 'string'
  62 + ? 'Pipe ' + port
  63 + : 'Port ' + port;
  64 +
  65 + // handle specific listen errors with friendly messages
  66 + switch (error.code) {
  67 + case 'EACCES':
  68 + console.error(bind + ' requires elevated privileges');
  69 + process.exit(1);
  70 + break;
  71 + case 'EADDRINUSE':
  72 + console.error(bind + ' is already in use');
  73 + process.exit(1);
  74 + break;
  75 + default:
  76 + throw error;
  77 + }
  78 +}
  79 +
  80 +/**
  81 + * Event listener for HTTP server "listening" event.
  82 + */
  83 +
  84 +function onListening() {
  85 + var addr = server.address();
  86 + var bind = typeof addr === 'string'
  87 + ? 'pipe ' + addr
  88 + : 'port ' + addr.port;
  89 + debug('Listening on ' + bind);
  90 +}
... ...
helpers/files.js 0 → 100755
... ... @@ -0,0 +1,104 @@
  1 +var parameters = require('../helpers/parameters');
  2 +var http = require('http');
  3 +var fs = require('fs');
  4 +var _ = require('lodash');
  5 +
  6 +/*
  7 +* Função que processa o vídeo (seja baixando, seja pegando o vídeo enviado)
  8 +* Deve retornar um objeto contendo o nome e o caminho
  9 +*/
  10 +function downloadAndMoveVideo(req, folder, callback)
  11 +{
  12 + console.log(req.files[0].path + " " + req.files[0].originalname);
  13 + // Se enviou o arquivo na requisição
  14 + if (req.files[0].fieldname !== undefined)
  15 + {
  16 + // Se a validação falhar
  17 + if (parameters.checkVideo(req.files[0].originalname) === false)
  18 + {
  19 + var error = 'Vídeo enviado com extensão inválida';
  20 + return callback(error);
  21 + }
  22 + /* Move o vídeo submetido para a pasta com o seu ID correspondente */
  23 + try
  24 + {
  25 + if (!_.isEmpty(req.body.wikilibras))
  26 + {
  27 + fs.renameSync(req.files[0].path, 'avatar/' + req.files[0].originalname);
  28 + fs.renameSync(req.files[1].path, 'blender/' + req.files[1].originalname);
  29 + }
  30 + else
  31 + {
  32 + fs.renameSync(req.files[0].path, 'uploads/' + req.files[0].originalname);
  33 + }
  34 + }
  35 + catch (err)
  36 + {
  37 + console.log("Erro ao mover o vídeo submetido: " + err);
  38 + callback("Erro ao mover o vídeo submetido: " + err);
  39 + }
  40 + return callback();
  41 + } // Se o arquivo não foi enviado, mas um video_url foi
  42 + else if (req.body.video_url !== undefined)
  43 + {
  44 + // Requisição para baixar o vídeo
  45 + http.get(req.body.video_url, function(response)
  46 + {
  47 +
  48 + // Se o vídeo não foi baixado com sucesso
  49 + if (response.statusCode !== 200)
  50 + {
  51 + var error = 'Problema ao carregar video_url: status ' + response.statusCode;
  52 + return callback(error);
  53 + }
  54 +
  55 + // Nome do arquivo
  56 + var filename = req.body.video_url.substring(req.body.video_url.lastIndexOf('/') + 1);
  57 +
  58 + // Tira os parâmetros HTTP
  59 + if (filename.lastIndexOf("?") !== -1)
  60 + {
  61 + filename = filename.substring(0, filename.lastIndexOf("?"));
  62 + }
  63 +
  64 + var path = folder + '/' + filename;
  65 +
  66 + // Cria o stream para escrita
  67 + var file = fs.createWriteStream(path);
  68 +
  69 + // Salva o arquivo em disco
  70 + response.pipe(file);
  71 +
  72 + // Quando a escrita acabar
  73 + file.on('finish', function()
  74 + {
  75 + // Fecha o arquivo
  76 + file.close(function()
  77 + {
  78 +
  79 + // Retorna o vídeo baixado
  80 + locals.video = {
  81 + 'path': path
  82 + };
  83 +
  84 + // Chama o callback para prosseguir execução
  85 + callback();
  86 + });
  87 + });
  88 +
  89 + // Se deu erro na requisição de baixar o vídeo
  90 + }).on('error', function(e) {
  91 + var error = 'Problema ao carregar video_url: ' + e.message;
  92 + return callback(error);
  93 + });
  94 +
  95 + // Se nem o vídeo foi enviado e nem o video_url foi preenchido
  96 + }
  97 + else
  98 + {
  99 + var error = "Video deve ser enviado como parâmetro 'video' ou como 'video_url'";
  100 + return callback(error);
  101 + }
  102 +}
  103 +
  104 +module.exports.downloadAndMoveVideo = downloadAndMoveVideo;
... ...
helpers/parameters.js 0 → 100755
... ... @@ -0,0 +1,182 @@
  1 +function getServiceType(service_type)
  2 +{
  3 + switch(service_type)
  4 + {
  5 + case 'video-legenda': return 2; break;
  6 + case 'video': return 3; break;
  7 + case 'texto': return 4; break;
  8 + case 'ios': return 4; break;
  9 + case 'legenda': return 5; break;
  10 + case 'audio': return 6; break;
  11 + }
  12 +};
  13 +
  14 +function getLanguage(language)
  15 +{
  16 + switch(language)
  17 + {
  18 + case 'portugues': return 'portugues'; break;
  19 + case 'glosa': return 'glosa'; break;
  20 + }
  21 +};
  22 +
  23 +function getPosition(position)
  24 +{
  25 + switch(position)
  26 + {
  27 + case 'superior-esquerdo': return 'top_left'; break;
  28 + case 'superior-direito': return 'top_right'; break;
  29 + case 'inferior-direito': return 'bottom_right'; break;
  30 + case 'inferior-esquerdo': return 'bottom_left'; break;
  31 + }
  32 +};
  33 +
  34 +function getSize(size)
  35 +{
  36 + switch(size)
  37 + {
  38 + case 'pequeno': return 1; break;
  39 + case 'medio': return 2; break;
  40 + case 'grande': return 3; break;
  41 + }
  42 +};
  43 +
  44 +function getTransparency(transparency)
  45 +{
  46 + switch(transparency)
  47 + {
  48 + case 'opaco': return 'opaque'; break;
  49 + case 'transparente': return 'transp'; break;
  50 + }
  51 +};
  52 +
  53 +function getSize(size)
  54 +{
  55 + switch(size)
  56 + {
  57 + case 'pequeno': return 'small'; break;
  58 + case 'medio': return 'medium'; break;
  59 + case 'grande': return 'large'; break;
  60 + }
  61 +};
  62 +
  63 +function checkServiceType(service_type)
  64 +{
  65 + var t_types = ['video', 'texto'];
  66 + for (var i = 0; i < t_types.length; i++)
  67 + {
  68 + if (service_type === t_types[i])
  69 + {
  70 + return true;
  71 + }
  72 + }
  73 + return false;
  74 +};
  75 +
  76 +function checkLanguage(language)
  77 +{
  78 + var t_types = ['portugues', 'glosa'];
  79 + for (var i = 0; i < t_types.length; i++)
  80 + {
  81 + if (language === t_types[i])
  82 + {
  83 + return true;
  84 + }
  85 + }
  86 + return false;
  87 +};
  88 +
  89 +function checkPosition(position)
  90 +{
  91 + var t_types = ['superior-esquerdo', 'superior-direito', 'inferior-esquerdo', 'inferior-direito'];
  92 + for (var i = 0; i < t_types.length; i++)
  93 + {
  94 + if (position === t_types[i])
  95 + {
  96 + return true;
  97 + }
  98 + }
  99 + return false;
  100 +};
  101 +
  102 +function checkSize(size)
  103 +{
  104 + var t_types = ['pequeno', 'medio', 'grande'];
  105 + for (var i = 0; i < t_types.length; i++)
  106 + {
  107 + if (size === t_types[i])
  108 + {
  109 + return true;
  110 + }
  111 + }
  112 + return false;
  113 +};
  114 +
  115 +function checkTransparency(transparency)
  116 +{
  117 + var t_types = ['opaco', 'transparente'];
  118 + for (var i = 0; i < t_types.length; i++)
  119 + {
  120 + if (transparency === t_types[i])
  121 + {
  122 + return true;
  123 + }
  124 + }
  125 + return false;
  126 +};
  127 +
  128 +function checkVideo(file) {
  129 + var accepted_file_types = ['flv', 'ts', 'avi', 'mp4', 'mov', 'webm', 'wmv', 'mkv', 'srt'];
  130 + return check_type(file, accepted_file_types);
  131 +};
  132 +
  133 +function checkSubtitle(file)
  134 +{
  135 + var accepted_file_types = ['srt'];
  136 + return check_type(file, accepted_file_types);
  137 +};
  138 +
  139 +function checkAudio(file)
  140 +{
  141 + var accepted_file_types = ['mp3', 'wav', 'aac', 'flac', 'ogg', 'wma'];
  142 + return check_type(file, accepted_file_types);
  143 +};
  144 +
  145 +function check_type(file, accepted_file_types)
  146 +{
  147 + var ext = file.substring(file.lastIndexOf('.') + 1).toLowerCase();
  148 + var isValidFile = false;
  149 + for (var i = 0; i < accepted_file_types.length; i++)
  150 + {
  151 + if (ext == accepted_file_types[i])
  152 + {
  153 + isValidFile = true;
  154 + break;
  155 + }
  156 + }
  157 + if (!isValidFile)
  158 + {
  159 + file.value = null;
  160 + }
  161 + return isValidFile;
  162 +};
  163 +
  164 +function errorMessage(message)
  165 +{
  166 + return JSON.stringify({ 'error': message })
  167 +};
  168 +
  169 +module.exports.getServiceType = getServiceType;
  170 +module.exports.getLanguage = getLanguage;
  171 +module.exports.getPosition = getPosition;
  172 +module.exports.getSize = getSize;
  173 +module.exports.getTransparency = getTransparency;
  174 +module.exports.checkServiceType = checkServiceType;
  175 +module.exports.checkLanguage = checkLanguage;
  176 +module.exports.checkPosition = checkPosition;
  177 +module.exports.checkSize = checkSize;
  178 +module.exports.checkTransparency = checkTransparency;
  179 +module.exports.checkVideo = checkVideo;
  180 +module.exports.checkSubtitle = checkSubtitle;
  181 +module.exports.checkAudio = checkAudio;
  182 +module.exports.errorMessage = errorMessage;
... ...
inicial.txt
... ... @@ -1 +0,0 @@
1   -Repositorio wikilibrasdbapi Criado
package.json 0 → 100755
... ... @@ -0,0 +1,26 @@
  1 +{
  2 + "name": "Wikilibras-DBAPI",
  3 + "description": "",
  4 + "version": "1.0.0",
  5 + "private": true,
  6 + "scripts":
  7 + {
  8 + "start": "supervisor ./bin/www"
  9 + },
  10 + "dependencies":
  11 + {
  12 + "bluebird": "~3.4.6",
  13 + "body-parser": "~1.13.2",
  14 + "cookie-parser": "~1.3.5",
  15 + "debug": "~2.2.0",
  16 + "express": "~4.13.1",
  17 + "lodash": "~4.15.0",
  18 + "mkdirp": "~0.5.1",
  19 + "morgan": "~1.6.1",
  20 + "multer": "~1.2.0",
  21 + "pg": "^4.4.6",
  22 + "pg-promise": "~5.3.3",
  23 + "pug": "~2.0.0-beta6",
  24 + "serve-favicon": "~2.3.0"
  25 + }
  26 +}
... ...
public/img/favicon.ico 0 → 100644
No preview for this file type
public/stylesheets/style.css 0 → 100755
... ... @@ -0,0 +1,8 @@
  1 +body {
  2 + padding: 50px;
  3 + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
  4 +}
  5 +
  6 +a {
  7 + color: #00B7FF;
  8 +}
... ...
routes/index.js 0 → 100755
... ... @@ -0,0 +1,645 @@
  1 +var express = require("express");
  2 +var promise = require("bluebird"); // or any other Promise/A+ compatible library;
  3 +var _ = require("lodash");
  4 +var pgp = require("pg-promise")({promiseLib: promise}); // overriding the default (ES6 Promise);
  5 +var db = pgp("postgres://wikilibras:wikilibras123@localhost:5432/wikilibras");
  6 +var parameters = require("../helpers/parameters");
  7 +var async = require("async");
  8 +var files = require("../helpers/files.js");
  9 +var fs = require('fs');
  10 +var path = require("path");
  11 +var util = require("util");
  12 +var mkdirp = require("mkdirp");
  13 +var multer = require("multer");
  14 +var upload = multer({dest: "uploads/"});
  15 +var users = path.join(__dirname, "../public/users");
  16 +var sys = require("util");
  17 +var exec = require("child_process").exec;
  18 +var child = null;
  19 +var router = express.Router();
  20 +
  21 +// GET home page
  22 +router.get("/", function(req, res, next)
  23 +{
  24 + res.render("index"
  25 + , {
  26 + title: "Wikilibras-DB API",
  27 + version: "1.0.0"
  28 + });
  29 +});
  30 +
  31 +router.get("/countvideo", function(req, res, next)
  32 +{
  33 + var response = {};
  34 + db.query('SELECT COUNT(*) FROM sinal;')
  35 + .then(function(result)
  36 + {
  37 + // response.status = true;
  38 + // response.message = "";
  39 + // response.data = result;
  40 + // TODO uncomment lines above, remove the line below and
  41 + response = [parseInt(result[0].count)];
  42 + res.status(203);
  43 + })
  44 + .catch(function(error)
  45 + {
  46 + response.status = false;
  47 + response.message = "";
  48 + response.data = error;
  49 + res.status(500);
  50 + })
  51 + .finally(function()
  52 + {
  53 + res.send(response);
  54 + pgp.end();
  55 + });
  56 +});
  57 +
  58 +router.get("/listselos", function(req, res, next)
  59 +{
  60 + var response = {};
  61 + db.query('SELECT "nomeSelo", "idSelo" FROM "tipoSelo";')
  62 + .then(function(result)
  63 + {
  64 + // response.status = true;
  65 + // response.data = result;
  66 + response = result;
  67 + res.status(203);
  68 + })
  69 + .catch(function(error)
  70 + {
  71 + response.status = false;
  72 + response.error = error;
  73 + res.status(500);
  74 + })
  75 + .finally(function()
  76 + {
  77 + res.send(response);
  78 + pgp.end();
  79 + });
  80 +});
  81 +
  82 +router.get("/countuservideos", function(req, res, next)
  83 +{
  84 + var response = {};
  85 + var limit = 10;
  86 + if (!_.isEmpty(req.query.limit))
  87 + {
  88 + var _limit = parseInt(req.query.limit);
  89 + if ((0 <= _limit) && (_limit <= 1000))
  90 + {
  91 + limit = _limit;
  92 + }
  93 + }
  94 + var query = 'SELECT DISTINCT' +
  95 + ' usuario as username,' +
  96 + ' "idUsuario" as email,' +
  97 + ' CAST(COUNT(*) as INTEGER) as videos' +
  98 + ' FROM sinal' +
  99 + ' GROUP BY usuario, "idUsuario"' +
  100 + ' ORDER BY videos DESC, usuario' +
  101 + ' LIMIT ' + limit + ';';
  102 + db.query(query)
  103 + .then(function(result)
  104 + {
  105 + // response.status = true;
  106 + // response.data = result;
  107 + response = result;
  108 + res.status(203);
  109 + })
  110 + .catch(function(error)
  111 + {
  112 + response.status = false;
  113 + response.error = error;
  114 + console.log(error);
  115 + res.status(500);
  116 + })
  117 + .finally(function()
  118 + {
  119 + res.send(response);
  120 + pgp.end();
  121 + });
  122 +});
  123 +
  124 +router.get("/listall", function(req, res, next)
  125 +{
  126 + var response = {};
  127 + // 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"')
  128 + 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"')
  129 + .then(function(result)
  130 + {
  131 + response = result;
  132 + res.status(203);
  133 + })
  134 + .catch(function(error)
  135 + {
  136 + response = error;
  137 + res.status(500);
  138 + })
  139 + .finally(function()
  140 + {
  141 + res.send(response);
  142 + pgp.end();
  143 + });
  144 +});
  145 +
  146 +/*
  147 +// only for test
  148 +router.get("/populate", function(req, res, next)
  149 +{
  150 + var response = {};
  151 + exec("sudo su postgres -c 'psql --set ON_ERROR_STOP=off -f ../wikilibras-db-api-dump.sql wikilibras'",
  152 + function(error, stdout, stderr)
  153 + {
  154 + if (error)
  155 + {
  156 + response.status = false;
  157 + response.message = "Error while populate";
  158 + console.log(stdout);
  159 + res.status = 500;
  160 + }
  161 + else if (stderr.length > 0)
  162 + {
  163 + response.status = false;
  164 + response.message = "The Database already been populated";
  165 + console.log(stderr);
  166 + res.status = 304;
  167 + }
  168 + else
  169 + {
  170 + response.status = true;
  171 + response.message = "The Database has been populated";
  172 + res.status = 201;
  173 + }
  174 + res.send(response);
  175 + });
  176 +});
  177 +*/
  178 +
  179 +router.get("/reset", function(req, res, next)
  180 +{
  181 + var response = {};
  182 + db.query('TRUNCATE TABLE sinal, selo; ALTER SEQUENCE sequence RESTART WITH 1;')
  183 + .then(function(result)
  184 + {
  185 + response.status = true;
  186 + response.message = "The database has been truncated";
  187 + res.status(203);
  188 + })
  189 + .catch(function(error)
  190 + {
  191 + response.status = false;
  192 + response.message = "The database cannot be truncated";
  193 + response.error = error;
  194 + res.status(500);
  195 + })
  196 + .finally(function()
  197 + {
  198 + res.send(response);
  199 + pgp.end();
  200 + // console.log(JSON.stringify(response, null, 4));
  201 + });
  202 +});
  203 +
  204 +router.get("/version", function(req, res, next)
  205 +{
  206 + var response = [];
  207 + db.query('SELECT MAX(version) FROM sinal;')
  208 + .then(function(result)
  209 + {
  210 + response = { "version": result[0].max };
  211 + })
  212 + .catch(function(error)
  213 + {
  214 + response = error;
  215 + })
  216 + .finally(function()
  217 + {
  218 + pgp.end();
  219 + res.status(203).send(response);
  220 + });
  221 +});
  222 +
  223 +router.get("/sinais", function(req, res, next)
  224 +{
  225 + var results = [];
  226 + if ((!_.isEmpty(req.param("selo"))) && (!_.isEmpty(req.param("version"))))
  227 + {
  228 + 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")])
  229 + .then(function(result)
  230 + {
  231 + res.status(203).send(result);
  232 + })
  233 + .catch(function(error)
  234 + {
  235 + // error;
  236 + })
  237 + .finally(function()
  238 + {
  239 + pgp.end();
  240 + });
  241 + }
  242 + else
  243 + {
  244 + if (!_.isEmpty(req.param("version")))
  245 + {
  246 + db.query("select nome, data, file, avatar, blender version FROM sinal WHERE version = $1", req.param("version"))
  247 + .then(function(result)
  248 + {
  249 + res.status(203).send(result);
  250 + })
  251 + .catch(function(error)
  252 + {
  253 + // error;
  254 + })
  255 + .finally(function()
  256 + {
  257 + pgp.end();
  258 + });
  259 + }
  260 + if (!_.isEmpty(req.param("selo")))
  261 + {
  262 + 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"))
  263 +
  264 + .then(function(result)
  265 + {
  266 + res.status(203).send(result);
  267 + })
  268 + .catch(function(error)
  269 + {
  270 + // error;
  271 + })
  272 + .finally(function()
  273 + {
  274 + pgp.end();
  275 + });
  276 + }
  277 + }
  278 +});
  279 +
  280 +router.post('/gsinal', function(req, res, next)
  281 +{
  282 + console.log("\n\n\n=============================================");
  283 + console.log("[" + new Date().toISOString() + "] Requisição do IP: " + req.ip);
  284 + console.log("== Parametros: " + util.inspect(req.body));
  285 + console.log("== Body: " + JSON.stringify(req.headers));
  286 + db.query('UPDATE sinal SET "idSelo" = ($1), version = version + 1 WHERE nome = ($2)', [req.body.selo, req.body.nome])
  287 + .then(function(data)
  288 + {
  289 + res.status(203).send('Sinal ' + req.body.nome + ' atualizado com sucesso [without archive]');
  290 + })
  291 + .catch(function(error)
  292 + {
  293 + console.log("Erro " + error);
  294 + });
  295 +});
  296 +
  297 +/*
  298 +router.post('/addsinal', upload.array('video', 2), function(req, res, next)
  299 +{
  300 + console.log("\n\n\n=============================================");
  301 + console.log("[" + new Date().toISOString() + "]");
  302 + console.log("From: " + req.ip);
  303 + // console.log("Files: " + req.files[0]);
  304 + // console.log("headers: " + JSON.stringify(req.headers));
  305 + console.log("body: " + JSON.stringify(req.body));
  306 +
  307 + if (req.method === "OPTIONS")
  308 + {
  309 + res.header('Access-Control-Allow-Origin', req.headers.origin);
  310 + }
  311 + else
  312 + {
  313 + res.header('Access-Control-Allow-Origin', '*');
  314 + }
  315 +
  316 + if (_.isEmpty(req.body.nome))
  317 + {
  318 + res.send(500, 'O valor do parâmetro nome está vazio');
  319 + return;
  320 + }
  321 + async.series([
  322 + function(callback)
  323 + {
  324 + console.log("\t >> ORDEM 1");
  325 + if (_.isEmpty(req.body.wikilibras))
  326 + {
  327 + console.log("Movendo arquivo para conversão...");
  328 + files.downloadAndMoveVideo(req, 'sinais', callback);
  329 + }
  330 + else
  331 + {
  332 + files.downloadAndMoveVideo(req, 'wikilibras', callback);
  333 + }
  334 + },
  335 + function(callback)
  336 + {
  337 + console.log("\t >> ORDEM 2");
  338 + if (_.isEmpty(req.body.wikilibras))
  339 + {
  340 + console.log("Convertendo para .webm...");
  341 + 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)
  342 + {
  343 + console.log('stdout: ' + stdout);
  344 + console.log('stderr: ' + stderr);
  345 + if (error !== null)
  346 + {
  347 + console.log('exec error: ' + error);
  348 + }
  349 + });
  350 +
  351 + child.on('exit', function()
  352 + {
  353 + setTimeout(function()
  354 + {
  355 + console.log("Conversão para webm concluída.");
  356 + callback(null, 1);
  357 + }, 500);
  358 + });
  359 + }
  360 + else
  361 + {
  362 + callback();
  363 + }
  364 + },
  365 + function(callback)
  366 + {
  367 + console.log("== Alterando o db");
  368 + db.query('SELECT "idSelo" FROM "sinal" WHERE nome = $1', req.body.nome)
  369 + .then(function(result)
  370 + {
  371 + var d = new Date();
  372 + if (Object.keys(result).length > 0)
  373 + {
  374 + if (!_.isEmpty(req.body.overwrite))
  375 + {
  376 + if (!_.isEmpty(req.body.wikilibras))
  377 + {
  378 + db.query('UPDATE sinal SET avatar = ($1), blender = ($2) WHERE nome = ($3)', [req.files[0].originalname, req.files[1].originalname, req.body.nome])
  379 + .then(function(data)
  380 + {
  381 + res.send(200, 'Sinal ' + req.body.nome + ' atualizado com sucesso [with avatar/blender].');
  382 + })
  383 + .catch(function(error)
  384 + {
  385 + console.log("Erro " + error);
  386 + });
  387 + }
  388 + if (!_.isEmpty(req.files))
  389 + {
  390 + 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])
  391 + .then(function(data)
  392 + {
  393 + res.status(200).send('Sinal ' + req.body.nome + ' atualizado com sucesso [with archive].');
  394 + })
  395 + .catch(function(error)
  396 + {
  397 + console.log("Erro " + error);
  398 + });
  399 + }
  400 + else
  401 + {
  402 + db.query('UPDATE sinal SET "idSelo" = ($1) WHERE nome = ($2)', [req.body.selo, req.body.nome])
  403 + .then(function(data)
  404 + {
  405 + res.status(200).send('Sinal ' + req.body.nome + ' atualizado com sucesso [without archive].');
  406 + })
  407 + .catch(function(error)
  408 + {
  409 + console.log("Erro " + error);
  410 + });
  411 + }
  412 + }
  413 + else
  414 + {
  415 + res.status(500).send('Sinal já cadastrado no sistema');
  416 + }
  417 + }
  418 + else
  419 + {
  420 + if (_.isEmpty(req.body.selo))
  421 + {
  422 + req.body.selo = 7;
  423 + }
  424 + 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'])
  425 + .then(function(data)
  426 + {
  427 + res.status(200).send('Sinal ' + req.body.nome + ' adicionado com sucesso.');
  428 + })
  429 + .catch(function(error)
  430 + {
  431 + console.log("Erro " + error);
  432 + });
  433 + }
  434 + })
  435 + .catch(function(error)
  436 + {
  437 + console.log(error);
  438 + })
  439 + .finally(function()
  440 + {
  441 + pgp.end();
  442 + });
  443 + }
  444 + , ], function(err)
  445 + {
  446 + if (err)
  447 + {
  448 + res.send(500, "Error");
  449 + return;
  450 + }
  451 + });
  452 +});
  453 +*/
  454 +
  455 +router.post('/addsinal', upload.array('video', 2), function(req, res, next)
  456 +{
  457 + // console.log("[" + new Date().toISOString() + "]");
  458 + // console.log("From: " + req.ip);
  459 + // console.log("Files: " + JSON.stringify(req.files[0], null, 4));
  460 + // console.log("headers: " + JSON.stringify(req.headers, null, 4));
  461 + // console.log("body: " + JSON.stringify(req.body, null, 4));
  462 + if (req.method === "OPTIONS")
  463 + {
  464 + res.header('Access-Control-Allow-Origin', req.headers.origin);
  465 + }
  466 + else
  467 + {
  468 + res.header('Access-Control-Allow-Origin', '*');
  469 + }
  470 + if (req.files.length < 1)
  471 + {
  472 + res.status(500).send("ERROR: O campo 'video' não contém nenhum arquivo do sinal");
  473 + return;
  474 + }
  475 + var directory = users;
  476 + var login = req.body["login"];
  477 + var estado = req.body["estado"];
  478 + var classe = req.body["classe-gramatical"];
  479 + var nome = req.body["nome"];
  480 + var cidade = req.body["cidade"];
  481 + var frase = req.body["frases"];
  482 + var selo = req.body["selo"];
  483 + var filesize = req.files[0].size;
  484 + var input = req.files[0].path;
  485 + var videoref = "";
  486 + var output = "";
  487 + var maxfilesize = 25; // MB
  488 + if (filesize > (maxfilesize * 1048576))
  489 + {
  490 + res.status(500).send("O tamanho do arquivo deve ter no máximo " + maxfilesize + " MB");
  491 + return;
  492 + }
  493 + if (_.isEmpty(nome))
  494 + {
  495 + res.status(500).send("ERROR: O campo 'nome' não foi encontrado");
  496 + return;
  497 + }
  498 + else
  499 + {
  500 + nome = nome.trim().toUpperCase();
  501 + videoref = nome + "_REF.webm";
  502 + }
  503 + if (_.isEmpty(selo))
  504 + {
  505 + selo = 7;
  506 + }
  507 + if (_.isEmpty(login))
  508 + {
  509 + console.log("WARNING: O campo 'login' não foi encontrado");
  510 + }
  511 + else
  512 + {
  513 + directory = path.join(directory, login);
  514 + }
  515 + if (_.isEmpty(estado))
  516 + {
  517 + console.log("WARNING: O campo 'estado' não foi encontrado");
  518 + }
  519 + else
  520 + {
  521 + directory = path.join(directory, estado);
  522 + }
  523 + if (_.isEmpty(classe))
  524 + {
  525 + console.log("WARNING: O campo 'classe' não foi encontrado");
  526 + }
  527 + else
  528 + {
  529 + directory = path.join(directory, classe);
  530 + }
  531 + if (!fs.existsSync(directory))
  532 + {
  533 + console.log("Criando diretorio: " + directory);
  534 + mkdirp(directory, function (err)
  535 + {
  536 + if (err)
  537 + {
  538 + console.error(err);
  539 + res.status(500).send(err);
  540 + return;
  541 + }
  542 + else
  543 + {
  544 + console.log("Diretório criado com sucesso!");
  545 + }
  546 + });
  547 + }
  548 + output = path.join(directory, videoref);
  549 + async.series([
  550 + // Converter para formato webm
  551 + function(callback)
  552 + {
  553 + console.log("Convertendo para webm...");
  554 + child = exec("avconv -y -v error -i \"" + input + "\" -acodec libvorbis -vcodec libvpx -an \"" + output + "\"",
  555 + function(error, stdout, stderr)
  556 + {
  557 + if (error || (stderr.length > 0))
  558 + {
  559 + var log = "O arquivo de vídeo é inválido, ou ocorreu um erro durante a conversão\nDetalhes: " + stdout + stderr;
  560 + log = log.trim();
  561 + res.status(500).send(log.trim());
  562 + callback(true, log);
  563 + }
  564 + else
  565 + {
  566 + res.status(200);
  567 + return 0;
  568 + }
  569 + });
  570 + child.on("exit", function(code)
  571 + {
  572 + setTimeout(function()
  573 + {
  574 + if (code === 0)
  575 + {
  576 + console.log("Conversão concluída");
  577 + callback(null, 1);
  578 + }
  579 + else
  580 + {
  581 + callback(true, new Error("Erro durante conversão"));
  582 + }
  583 + }, 500);
  584 + });
  585 + },
  586 + function(callback)
  587 + {
  588 + console.log("Verificando se este sinal já existe");
  589 + db.query('SELECT "idSinal" FROM "sinal" WHERE usuario = ($1) AND estado = ($2) AND classe = ($3) AND nome = ($4)', [login, estado, classe, nome])
  590 + .then(function(result)
  591 + {
  592 + var d = new Date();
  593 + var date = d.getFullYear() + "/" + d.getMonth() + "/" + d.getDate();
  594 + if (Object.keys(result).length > 0)
  595 + {
  596 + var idSinal = result[0].idSinal;
  597 + console.log("Existe, atualizando o sinal[" + idSinal + "]: '" + nome + "'");
  598 + 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])
  599 + .then(function(data)
  600 + {
  601 + res.status(200).send("Sinal atualizado");
  602 + })
  603 + .catch(function(error)
  604 + {
  605 + var message = "Erro ao atualizar sinal '" + nome + "'\n" + error;
  606 + console.log(message);
  607 + res.status(500).send(message);
  608 + });
  609 + }
  610 + else
  611 + {
  612 + console.log("Não existe, inserindo o sinal: '" + nome + "'");
  613 + 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])
  614 + .then(function(data)
  615 + {
  616 + var message = "Sinal[" + data.idSinal + "]: '" + nome + "', inserido com sucesso";
  617 + console.log(message);
  618 + res.status(200).send(message);
  619 + })
  620 + .catch(function(error)
  621 + {
  622 + res.status(500).send(error);
  623 + });
  624 + }
  625 + })
  626 + .catch(function(error)
  627 + {
  628 + console.log(error);
  629 + })
  630 + .finally(function()
  631 + {
  632 + pgp.end();
  633 + });
  634 + }
  635 + ],
  636 + function(err, results)
  637 + {
  638 + if (err)
  639 + {
  640 + console.log(results[0]);
  641 + }
  642 + });
  643 +});
  644 +
  645 +module.exports = router;
... ...
routes/users.js 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +var express = require('express');
  2 +var router = express.Router();
  3 +
  4 +/* GET users listing. */
  5 +router.get('/', function(req, res, next) {
  6 + res.send('respond with a resource');
  7 +});
  8 +
  9 +module.exports = router;
... ...
views/error.pug 0 → 100755
... ... @@ -0,0 +1,6 @@
  1 +extends layout
  2 +
  3 +block content
  4 + h1= message
  5 + h2= error.status
  6 + pre #{error.stack}
... ...
views/index.pug 0 → 100755
... ... @@ -0,0 +1,5 @@
  1 +extends layout
  2 +
  3 +block content
  4 + h1= title
  5 + p Welcome to #{title} #{version}
... ...
views/layout.pug 0 → 100755
... ... @@ -0,0 +1,7 @@
  1 +doctype html
  2 +html
  3 + head
  4 + title= title
  5 + link(rel='stylesheet', href='/stylesheets/style.css')
  6 + body
  7 + block content
... ...
wikilibras-db-api 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +#!/bin/sh
  2 +
  3 +### BEGIN INIT INFO
  4 +# Provides: <SCRIPT_NAME>
  5 +# Required-Start: $all
  6 +# Required-Stop: $all
  7 +# Default-Start: 2 3 4 5
  8 +# Default-Stop: 0 1 6
  9 +# Short-Description: Service provided <SCRIPT_NAME>
  10 +# Description: Enable service provided <SCRIPT_NAME> API at boot
  11 +### END INIT INFO
  12 +
  13 +set -e
  14 +
  15 +export PATH="$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  16 +
  17 +CWD="<CURDIR>"
  18 +
  19 +case "$1" in
  20 + start)
  21 + cd "$CWD"
  22 + "<NPM>" start&
  23 + ;;
  24 + *)
  25 + echo "Usage: /etc/init.d/<SCRIPT_NAME> {start}"
  26 + exit 1
  27 + ;;
  28 +esac
  29 +
  30 +exit 0
... ...
wikilibras-db-api.sql 0 → 100644
... ... @@ -0,0 +1,89 @@
  1 +SET statement_timeout = 0;
  2 +SET lock_timeout = 0;
  3 +SET client_encoding = 'UTF8';
  4 +SET standard_conforming_strings = on;
  5 +SET check_function_bodies = false;
  6 +SET client_min_messages = warning;
  7 +
  8 +CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
  9 +
  10 +COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
  11 +
  12 +SET search_path = public, pg_catalog;
  13 +
  14 +SET default_tablespace = '';
  15 +
  16 +SET default_with_oids = false;
  17 +
  18 +CREATE TABLE selo (
  19 + "idSelo" integer NOT NULL,
  20 + "idSinal" integer,
  21 + data character varying(30),
  22 + estado character varying(30),
  23 + cidade character varying(30)
  24 +);
  25 +
  26 +ALTER TABLE public.selo OWNER TO wikilibras;
  27 +
  28 +CREATE SEQUENCE sequence
  29 + START WITH 1
  30 + INCREMENT BY 1
  31 + NO MINVALUE
  32 + NO MAXVALUE
  33 + CACHE 1;
  34 +
  35 +ALTER TABLE public.sequence OWNER TO wikilibras;
  36 +
  37 +CREATE TABLE sinal (
  38 + data character varying(30),
  39 + -- TODO create table users
  40 + "idUsuario" integer,
  41 + usuario character varying(30),
  42 + version integer DEFAULT 0,
  43 + "idSinal" integer DEFAULT nextval('sequence'::regclass) NOT NULL,
  44 + "idSelo" integer,
  45 + nome character varying(30),
  46 + classe character varying(30),
  47 + frase character varying(100),
  48 + estado character varying(30),
  49 + cidade character varying(30),
  50 + file character varying(100),
  51 + blender character varying(100),
  52 + avatar character varying(100)
  53 +);
  54 +
  55 +ALTER TABLE public.sinal OWNER TO wikilibras;
  56 +
  57 +CREATE TABLE "tipoSelo" (
  58 + "nomeSelo" character varying(40),
  59 + "idSelo" integer DEFAULT nextval('sequence'::regclass) NOT NULL
  60 +);
  61 +
  62 +ALTER TABLE public."tipoSelo" OWNER TO wikilibras;
  63 +
  64 +SELECT pg_catalog.setval('sequence', 1, false);
  65 +
  66 +INSERT INTO "tipoSelo" VALUES ('wikilibras', 1);
  67 +INSERT INTO "tipoSelo" VALUES ('especialista', 2);
  68 +INSERT INTO "tipoSelo" VALUES ('invalido_wikilibras', 3);
  69 +INSERT INTO "tipoSelo" VALUES ('invalido_especialista', 4);
  70 +INSERT INTO "tipoSelo" VALUES ('animadores', 5);
  71 +INSERT INTO "tipoSelo" VALUES ('invalido_animadores', 6);
  72 +INSERT INTO "tipoSelo" VALUES ('null', 7);
  73 +
  74 +ALTER TABLE ONLY sinal
  75 + ADD CONSTRAINT "idSinal" PRIMARY KEY ("idSinal");
  76 +
  77 +ALTER TABLE ONLY "tipoSelo"
  78 + ADD CONSTRAINT "tipoSelo_pkey" PRIMARY KEY ("idSelo");
  79 +
  80 +ALTER TABLE ONLY selo
  81 + ADD CONSTRAINT "selo_idSelo_fkey" FOREIGN KEY ("idSelo") REFERENCES "tipoSelo"("idSelo");
  82 +
  83 +ALTER TABLE ONLY selo
  84 + ADD CONSTRAINT "selo_idSinal_fkey" FOREIGN KEY ("idSinal") REFERENCES sinal("idSinal");
  85 +
  86 +REVOKE ALL ON SCHEMA public FROM PUBLIC;
  87 +REVOKE ALL ON SCHEMA public FROM postgres;
  88 +GRANT ALL ON SCHEMA public TO postgres;
  89 +GRANT ALL ON SCHEMA public TO PUBLIC;
... ...