diff --git a/18-servicios/client/app.js b/18-servicios/client/app.js new file mode 100644 index 0000000..16cef71 --- /dev/null +++ b/18-servicios/client/app.js @@ -0,0 +1,35 @@ +angular.module('controlCajaApp', ['ui.router','ngCookies', 'ngResource']); + +angular.module('controlCajaApp').config(function ($stateProvider,$locationProvider) { + $stateProvider + .state('total', { + url: '/', + controller: 'CajaCtrl as caja', + templateUrl: 'total.html' + }) + .state('nuevo', { + url: '/nuevo', + controller: 'CajaCtrl as caja', + templateUrl: 'nuevo.html' + }) + .state('lista', { + url: '/lista', + controller: 'CajaCtrl as caja', + templateUrl: 'lista.html' + }) + .state('movimiento', { + url: '/movimiento/:id', // declaracion de parametros en rutas + controller: 'MovimientoCtrl as vm', + templateUrl: 'movimiento.html' + }) + .state('registro', { + url: '/registro', + controller: 'RegistroCtrl as registro', + templateUrl: 'registro.html' + }) + .state('not-found', { + url: '*path', + controller: 'CajaCtrl as caja', + templateUrl: 'total.html' + }); +}); diff --git a/18-servicios/client/appHttpLog.js b/18-servicios/client/appHttpLog.js new file mode 100644 index 0000000..ec065c4 --- /dev/null +++ b/18-servicios/client/appHttpLog.js @@ -0,0 +1,23 @@ +(function () { + + function configuradorInterceptores($httpProvider) { + $httpProvider.interceptors.push(funcionInterceptoraLog); + } + + // Esta función se especializa en escribir en la consola + // información sobre llamadas http + function funcionInterceptoraLog($log) { + + var interceptor = {}; + interceptor.request = function (request) { + $log.info('request:' + request.url); + return request ; + }; + interceptor.responseError = function (response) { + $log.error("excepción: " + response.status + " de :" + response.config.url); + } + return interceptor; + } + + angular.module('controlCajaApp').config(configuradorInterceptores); +}()); diff --git a/18-servicios/client/appSecurity.js b/18-servicios/client/appSecurity.js new file mode 100644 index 0000000..5948427 --- /dev/null +++ b/18-servicios/client/appSecurity.js @@ -0,0 +1,29 @@ +(function () { + function configuradorInterceptores($httpProvider) { + $httpProvider.interceptors.push(funcionInterceptoraSeguridad); + } + + function funcionInterceptoraSeguridad($q, $injector, $cookieStore, $rootScope) { + + var interceptor = {}; + interceptor.request = function (request) { + request.headers["sessionId"] = $cookieStore.get("sessionId"); + return request || $q.when(request); + }; + interceptor.responseError = function (response) { + var state = $injector.get('$state'); + if (response.status === 401) { + $rootScope.mensaje = "No hay derecho!!!"; + state.go('registro'); + } else if (response.status === 419) { + $rootScope.mensaje = "Estoy caduco!!!"; + $cookieStore.remove("sessionId") + state.go('registro'); + }; + return $q.reject(response); + } + return interceptor; + } + + angular.module('controlCajaApp').config(configuradorInterceptores); +}()); diff --git a/18-servicios/client/cajaCtrl.js b/18-servicios/client/cajaCtrl.js new file mode 100644 index 0000000..7093eee --- /dev/null +++ b/18-servicios/client/cajaCtrl.js @@ -0,0 +1,34 @@ +(function () { + var cajaCtrl = function (maestrosFactory, movimientosFactory) { + var vm = this; + + vm.titulo = "Controla tu Cash Flow"; + + vm.maestros = maestrosFactory.get(); + + vm.nuevoMovimiento = new movimientosFactory.movimientos(); + vm.nuevoMovimiento.esIngreso =1; + vm.nuevoMovimiento.fecha = new Date(); + + vm.movimientos = movimientosFactory.movimientos.query(); + + vm.total = movimientosFactory.total.get(); + + vm.guardarMovimiento = function () { + vm.nuevoMovimiento.tipo = vm.tipo(vm.nuevoMovimiento); + vm.nuevoMovimiento.$save() + .then(function(postedData){ + vm.movimientos = movimientosFactory.movimientos.query(); + vm.total = movimientosFactory.total.get(); + vm.nuevoMovimiento.importe = 0; + }); + } + vm.balance = function () { + return vm.total.ingresos - vm.total.gastos + } + vm.tipo = function (movimiento) { + return movimiento.esIngreso && 'Ingreso' || 'Gasto' + } + } + angular.module('controlCajaApp').controller('CajaCtrl', cajaCtrl); +}()); diff --git a/18-servicios/client/index.html b/18-servicios/client/index.html new file mode 100644 index 0000000..aa2a7e5 --- /dev/null +++ b/18-servicios/client/index.html @@ -0,0 +1,53 @@ + + + Control de Caja + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + diff --git a/18-servicios/client/lista.html b/18-servicios/client/lista.html new file mode 100644 index 0000000..e87c459 --- /dev/null +++ b/18-servicios/client/lista.html @@ -0,0 +1,31 @@ +
+

Estos son tus movimientos recientes.

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
IdFecha + TipoCategoríaImporte +
{{movimiento.id }} {{movimiento.fecha | date}}{{movimiento.tipo}}{{movimiento.categoria}}{{movimiento.importe | number:2}} €
+
diff --git a/18-servicios/client/maestrosFactory.js b/18-servicios/client/maestrosFactory.js new file mode 100644 index 0000000..9a9b37d --- /dev/null +++ b/18-servicios/client/maestrosFactory.js @@ -0,0 +1,9 @@ +(function () { + var maestrosFactory =   function ($resource)  { + // Los resursos son totalmente configurables + // Una de sus utilidades de 'fábrica' de una caché simple pero potente + return $resource("/api/pub/maestros/",{},{get: {cache: true}}); + }; + + angular.module('controlCajaApp').factory('maestrosFactory', maestrosFactory); +}()); diff --git a/18-servicios/client/menuCtrl.js b/18-servicios/client/menuCtrl.js new file mode 100644 index 0000000..14ddc17 --- /dev/null +++ b/18-servicios/client/menuCtrl.js @@ -0,0 +1,8 @@ +(function () { + var menuCtrl = function ($state) { + this.isActive = function (estado) { + return $state.is(estado); + } + } + angular.module('controlCajaApp').controller('MenuCtrl',menuCtrl); +}()); diff --git a/18-servicios/client/movimiento.html b/18-servicios/client/movimiento.html new file mode 100644 index 0000000..57fbde5 --- /dev/null +++ b/18-servicios/client/movimiento.html @@ -0,0 +1,41 @@ +
+
+
+
+ Ver el movimiento {{ vm.movimiento.id}} +
+
+
+
+ +
+
+ {{ vm.movimiento.tipo}} +
+
+
+
+ +
+ {{ vm.movimiento.categoria }} +
+
+
+
+
+ +
+ {{ vm.movimiento.fecha || date }} +
+
+
+ +
+ {{ vm.movimiento.importe }} +
+
+
+
+
+
+
diff --git a/18-servicios/client/movimientoCtrl.js b/18-servicios/client/movimientoCtrl.js new file mode 100644 index 0000000..8beb183 --- /dev/null +++ b/18-servicios/client/movimientoCtrl.js @@ -0,0 +1,12 @@ +(function () { + // $stateParams es el servicio de ui-router para acceder a los parámetros de la ruta + var movimientoCtrl = function ($stateParams, movimientosFactory) { + var vm = this; + // El acceso es por nombre de parámetro + var movId = $stateParams.id; + // Se llama al recurso expuesto por la factoría de recursos + // enviándole el objeto que sirve de consulta al método get + vm.movimiento = movimientosFactory.movimientos.get({id:movId}); + } + angular.module('controlCajaApp').controller('MovimientoCtrl', movimientoCtrl); +}()); diff --git a/18-servicios/client/movimientosFactory.js b/18-servicios/client/movimientosFactory.js new file mode 100644 index 0000000..26a5053 --- /dev/null +++ b/18-servicios/client/movimientosFactory.js @@ -0,0 +1,25 @@ +(function () { + + var movimientosFactory =   function ($resource)  { + + var factory  =   {}; + + // $resource("/api/priv/movimientos/"); + // El uso de parametros en los recursos debe especificarse + // Indicando el nombre local y el remoto + // En la url va el nombre remoto + // En el objeto se repute ese nombre como clave y se dice que propiedad será la que lo lleve + // La propiedad empieza por @ + factory.movimientos =  $resource("/api/priv/movimientos/:id", { + id: "@id" + }) + + + + factory.total = $resource("/api/priv/total/"); + + return factory; + }; + + angular.module('controlCajaApp').factory('movimientosFactory', movimientosFactory); +}()); diff --git a/18-servicios/client/not-found.html b/18-servicios/client/not-found.html new file mode 100644 index 0000000..85210c2 --- /dev/null +++ b/18-servicios/client/not-found.html @@ -0,0 +1,3 @@ +
+

No se ha encontrado lo que buscabas

+
diff --git a/18-servicios/client/nuevo.html b/18-servicios/client/nuevo.html new file mode 100644 index 0000000..4dd8ad9 --- /dev/null +++ b/18-servicios/client/nuevo.html @@ -0,0 +1,53 @@ +
+
+
+
+ Introduce tus movimientos +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+ + +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+ {{caja.movimientos}} +
diff --git a/18-servicios/client/registro.html b/18-servicios/client/registro.html new file mode 100644 index 0000000..6d32793 --- /dev/null +++ b/18-servicios/client/registro.html @@ -0,0 +1,37 @@ +
+
+
+
+
+
+ Login +
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+
+
diff --git a/18-servicios/client/registroCtrl.js b/18-servicios/client/registroCtrl.js new file mode 100644 index 0000000..7344f28 --- /dev/null +++ b/18-servicios/client/registroCtrl.js @@ -0,0 +1,26 @@ +(function () { + var registroCtrl = function ($rootScope, $state, $http, $cookieStore) { + var urlBase = "http://localhost:3000/api/"; + var vm = this; + vm.usuario = {}; + vm.entrar = function () { + $http.post(urlBase + 'sesiones/', vm.usuario) + .success(function (data) { + $rootScope.nombre = vm.usuario.email; + $rootScope.mensaje = 'recién entrado'; + $cookieStore.put("sessionId", data); + $state.go("total"); + }); + } + vm.registrar = function () { + $http.post(urlBase + 'usuarios/', vm.usuario) + .success(function (data) { + $rootScope.nombre = vm.usuario.email; + $rootScope.mensaje = 'recién creado'; + $cookieStore.put("sessionId", data); + $state.go("total"); + }); + } + } + angular.module('controlCajaApp').controller('RegistroCtrl', registroCtrl); +}()); diff --git a/18-servicios/client/total.html b/18-servicios/client/total.html new file mode 100644 index 0000000..5fa080d --- /dev/null +++ b/18-servicios/client/total.html @@ -0,0 +1,38 @@ + + +
+ +

{{ caja.titulo }}

+

Comprueba de dónde viene y a dónde va tu dinero.

+
+
+
+

+ + {{ caja.total.ingresos | number:2 }} € + +

+

Total ingresos

+ Acumulado +
+
+

+ + {{ caja.total.gastos | number:2 }} € + +

+

Total gastos

+ Acumulado +
+
+

+ + {{ caja.balance() | number:2 }} € + +

+

Balance

+ Ingresos-Gastos +
+
+
+
diff --git a/18-servicios/package.json b/18-servicios/package.json new file mode 100644 index 0000000..86164f1 --- /dev/null +++ b/18-servicios/package.json @@ -0,0 +1,15 @@ +{ + "name": "ControlCashFlow", + "version": "0.0.0", + "description": "Controla tu flujo de caja", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Alberto Basalo", + "license": "BSD-2-Clause", + "dependencies": { + "express": "~4.12.1", + "body-parser": "~1.0.2" + } +} diff --git a/18-servicios/server.js b/18-servicios/server.js new file mode 100644 index 0000000..1226ead --- /dev/null +++ b/18-servicios/server.js @@ -0,0 +1,196 @@ +var express = require('express'); +var bodyParser = require('body-parser'); + +var app = express(); +app.use(bodyParser()); +app.use(express.static(__dirname + '/client')); + +// Podemos incrustar middelware propio para mostrar por consola todas las llamadas +// Por supuesto hay cientos de paquetes especializados en esta labor +//app.use(function (peticion, respuesta, siguiente) { +// console.log("recibida petición: " + peticion.url); +// if (peticion.body && Object.keys(peticion.body).length>0) { +// console.log("body: " + JSON.stringify(peticion.body)); +// } +// siguiente(); +// }); + +console.log('ready'); + +// Mientras no tengamos base de datos usaremos un contador para ir generando Ids +var maxId = 0; +var movimientos = []; +var totales = []; + +var usuarios = []; +var sesiones = []; + + +app.use('/api/priv/', function (req, res, next) { + var sessionId = req.get('sessionId'); + var sesion = getSesion(sessionId); + if (sesion) { + if (esSesionValida(sesion)) { + sesion.timeStamp = new Date(); + req.usuario = sesion.email; + next(); + } else { + console.log('Sesión caducada:' + JSON.stringify(sesion)); + // Sintaxis mejorada de envío de códigos de estado http + res.status(419).send('Sesión caducada'); + } + } else { + res.status(401).send('Credencial inválida'); + } + +}); + + + + + +// API - REST +// SECURITY +app.route('/api/usuarios') + .get(function (req, res, next) { + // Esto devuelve la lista completa de usuarios y contraseñas + // PELIGRO: Usar sólo a modo de debug mientras desarrollamos + res.json(usuarios); + }) + .post(function (req, res, next) { + var usuario = req.body; + if (existeUsuario(usuario)) { + console.log('email ya registrado:' + usuario.email); + res.status(409).send('email ' + usuario.email + ' ya registrado'); + } else { + console.log('registrado:' + usuario.email); + usuarios.push(usuario); + res.json(newSession(usuario.email)); + } + }); + +// Gestión de sesiones: listado y login +app.route('/api/sesiones') + .get(function (req, res, next) { + // Esto devuelve la lista completa de sesiones + // PELIGRO: Usar sólo a modo de debug mientras desarrollamos + res.json(sesiones); + }) + .post(function (req, res, next) { + var usuario = req.body; + if (esUsuarioValido(usuario)) { + console.log('aceptado:' + usuario.email); + res.json(newSession(usuario.email)); + } else { + console.log('Credencial inválida:' + usuario.email); + res.status(401).send('Credencial inválida'); + } + }); + +function existeUsuario(usuario) { + return usuarios.some(function (u) { + return u.email == usuario.email; + }); +} + +function esUsuarioValido(usuario) { + return usuarios.filter(function (u) { + return u.email == usuario.email && u.password == usuario.password; + })[0]; +} + +function getSesion(sessionId) { + return sesiones.filter(function (s) { + return s.sessionId == sessionId; + })[0] +} + +function esSesionValida(sesion) { + return (new Date() - sesion.timeStamp) < 20 * 60 * 1000; +} + +function newSession(email) { + var sessionId = Math.random() * (88888) + 11111; + var timeStamp = new Date(); + sesiones.push({ + sessionId: sessionId, + email: email, + timeStamp: timeStamp + }); + return sessionId; +} + + +// BUSINESS +app.get('/api/pub/maestros', function (req, res, next) { + var maestros = { + categoriasIngresos: ['Nómina', 'Ventas', 'Intereses Depósitos'], + categoriasGastos: ['Hipotéca', 'Compras', 'Impuestos'] + }; + res.json(maestros); +}); + +app.route('/api/priv/movimientos') + .get(function (req, res, next) { + var movimientosUsuario = movimientos.filter(function (m) { + return m.usuario = req.usuario; + }); + res.json(movimientosUsuario); + }) + .post(function (req, res, next) { + var movimiento = req.body; + movimiento.usuario = req.usuario; + // Avanzamos el contador para asignar un nuevo id al movimiento + maxId++; + movimiento.id = maxId; + movimientos.push(movimiento); + var totalUsuario = getTotalUsuario(req.usuario); + if (movimiento.tipo == 'Ingreso') + totalUsuario.ingresos += movimiento.importe; + else + totalUsuario.gastos += movimiento.importe; + res.status(200).send(); + }); + +// Para las rutas paramétircas creamos otro patrón +// El recurso sigue siendo movimientos, y el parámetro se declara con :id +// Obtención a partir de parámetros +app.get('/api/priv/movimientos/:id', function (req, res, next) { + var movId = req.params.id; + // Buscar en el array el movimiento con este id + movimientoBuscado = getMovimientoById(movId, req.usuario); + res.json(movimientoBuscado); +}); + +function getMovimientoById(id,usuario) { + var movimientoBuscado = movimientos.filter(function (movimiento) { + return movimiento.id == id && movimiento.usuario == usuario; + })[0]; + return movimientoBuscado; +} + +app.get('/api/priv/total', function (req, res, next) { + var totalUsuario = getTotalUsuario(req.usuario); + res.json(totalUsuario); +}); + +function getTotalUsuario(usuario) { + if(usuario===undefined) return {}; + var totalUsuario = totales.filter(function (t) { + return t.usuario == usuario; + })[0]; + if (totalUsuario===undefined) { + totalUsuario = { + usuario : usuario, + ingresos: 0, + gastos: 0 + }; + totales.push(totalUsuario); + } + return totalUsuario; +} + + +console.log('steady'); +app.listen(3000); +console.log('go'); diff --git a/19-angular-con-filtro/client/app.js b/19-angular-con-filtro/client/app.js new file mode 100644 index 0000000..d43daf8 --- /dev/null +++ b/19-angular-con-filtro/client/app.js @@ -0,0 +1,36 @@ +// Incluimos referencia a un módulo propio, con un prefijo propio +angular.module('controlCajaApp', ['ui.router','ngCookies', 'ngResource', 'abFiltros']); + +angular.module('controlCajaApp').config(function ($stateProvider,$locationProvider) { + $stateProvider + .state('total', { + url: '/', + controller: 'CajaCtrl as caja', + templateUrl: 'total.html' + }) + .state('nuevo', { + url: '/nuevo', + controller: 'CajaCtrl as caja', + templateUrl: 'nuevo.html' + }) + .state('lista', { + url: '/lista', + controller: 'CajaCtrl as caja', + templateUrl: 'lista.html' + }) + .state('movimiento', { + url: '/movimiento/:id', // declaracion de parametros en rutas + controller: 'MovimientoCtrl as vm', + templateUrl: 'movimiento.html' + }) + .state('registro', { + url: '/registro', + controller: 'RegistroCtrl as registro', + templateUrl: 'registro.html' + }) + .state('not-found', { + url: '*path', + controller: 'CajaCtrl as caja', + templateUrl: 'total.html' + }); +}); diff --git a/19-angular-con-filtro/client/appHttpLog.js b/19-angular-con-filtro/client/appHttpLog.js new file mode 100644 index 0000000..ec065c4 --- /dev/null +++ b/19-angular-con-filtro/client/appHttpLog.js @@ -0,0 +1,23 @@ +(function () { + + function configuradorInterceptores($httpProvider) { + $httpProvider.interceptors.push(funcionInterceptoraLog); + } + + // Esta función se especializa en escribir en la consola + // información sobre llamadas http + function funcionInterceptoraLog($log) { + + var interceptor = {}; + interceptor.request = function (request) { + $log.info('request:' + request.url); + return request ; + }; + interceptor.responseError = function (response) { + $log.error("excepción: " + response.status + " de :" + response.config.url); + } + return interceptor; + } + + angular.module('controlCajaApp').config(configuradorInterceptores); +}()); diff --git a/19-angular-con-filtro/client/appSecurity.js b/19-angular-con-filtro/client/appSecurity.js new file mode 100644 index 0000000..5948427 --- /dev/null +++ b/19-angular-con-filtro/client/appSecurity.js @@ -0,0 +1,29 @@ +(function () { + function configuradorInterceptores($httpProvider) { + $httpProvider.interceptors.push(funcionInterceptoraSeguridad); + } + + function funcionInterceptoraSeguridad($q, $injector, $cookieStore, $rootScope) { + + var interceptor = {}; + interceptor.request = function (request) { + request.headers["sessionId"] = $cookieStore.get("sessionId"); + return request || $q.when(request); + }; + interceptor.responseError = function (response) { + var state = $injector.get('$state'); + if (response.status === 401) { + $rootScope.mensaje = "No hay derecho!!!"; + state.go('registro'); + } else if (response.status === 419) { + $rootScope.mensaje = "Estoy caduco!!!"; + $cookieStore.remove("sessionId") + state.go('registro'); + }; + return $q.reject(response); + } + return interceptor; + } + + angular.module('controlCajaApp').config(configuradorInterceptores); +}()); diff --git a/19-angular-con-filtro/client/cajaCtrl.js b/19-angular-con-filtro/client/cajaCtrl.js new file mode 100644 index 0000000..7093eee --- /dev/null +++ b/19-angular-con-filtro/client/cajaCtrl.js @@ -0,0 +1,34 @@ +(function () { + var cajaCtrl = function (maestrosFactory, movimientosFactory) { + var vm = this; + + vm.titulo = "Controla tu Cash Flow"; + + vm.maestros = maestrosFactory.get(); + + vm.nuevoMovimiento = new movimientosFactory.movimientos(); + vm.nuevoMovimiento.esIngreso =1; + vm.nuevoMovimiento.fecha = new Date(); + + vm.movimientos = movimientosFactory.movimientos.query(); + + vm.total = movimientosFactory.total.get(); + + vm.guardarMovimiento = function () { + vm.nuevoMovimiento.tipo = vm.tipo(vm.nuevoMovimiento); + vm.nuevoMovimiento.$save() + .then(function(postedData){ + vm.movimientos = movimientosFactory.movimientos.query(); + vm.total = movimientosFactory.total.get(); + vm.nuevoMovimiento.importe = 0; + }); + } + vm.balance = function () { + return vm.total.ingresos - vm.total.gastos + } + vm.tipo = function (movimiento) { + return movimiento.esIngreso && 'Ingreso' || 'Gasto' + } + } + angular.module('controlCajaApp').controller('CajaCtrl', cajaCtrl); +}()); diff --git a/19-angular-con-filtro/client/filtros.js b/19-angular-con-filtro/client/filtros.js new file mode 100644 index 0000000..c96b9d5 --- /dev/null +++ b/19-angular-con-filtro/client/filtros.js @@ -0,0 +1,84 @@ +(function () { + // Los filtros se declaran como funciones que a su vez devuelven... funciones + + // Esas funciones internas se aplican sobre los valores, + // Tienen al menos un parámetro, que sirve de entrada + + + // Esta función le quita acentos, guiones bajos, y caracteres raros + // Los sustituye por guiones bajos + function limpiarCadena() { + var funcionFiltro = function (cadena) { + if (cadena) { + var resultado = cadena.toLowerCase(); + var patron = /[^-A-Za-z0-9]+/g; + return resultado.replace(patron, '_'); + } + }; + return funcionFiltro; + } + + // Devuelve un trozo de texto y tres puntos suspensivos... para indicar que hay más... + // Demuestra que un filtro puede tener parámetros, y cómo tratar valores por defecto. + function recortar() { + var funcionFiltro = function (cadena, largo, quitarInicio) { + if (!cadena) { + return '' + } + if (!largo) { + largo = 10 + } + if (cadena.length <= largo) { + return cadena + } + if (quitarInicio) { + return '...' + cadena.substring(cadena.length - largo) + } else { + return cadena.substring(0, largo) + '...' + } + }; + return funcionFiltro; + } + + // Con control de errores y lógica compleja + function rellenarVacios() { + var funcionFiltro = function (cadena) { + try { + if (!cadena || cadena === undefined || cadena.trim() === "") { + return '---'; + }; + } catch (err) { + return '---'; + } + return cadena; + } + return funcionFiltro; + } + + // Permite tener filtros predeterminadois en un array + function granImporte() { + var funcionFiltro = function (movimientos, valorCorte) { + if (valorCorte) { + var filtrados = []; + for (var i = 0; i < movimientos.length; i++) { + var mov = movimientos[i]; + if (mov.importe >= valorCorte) { + filtrados.push(mov); + } + } + return filtrados; + } else { + return movimientos; + } + }; + return funcionFiltro; + } + + // Podemos usar una sintaxis fluida y declarar todo en una solo línea + // Atención a la declaración de un nuevo módulo genérico + angular.module('abFiltros', []) + .filter('abLimpiarCadena', limpiarCadena) + .filter('abRecortar', recortar) + .filter('abRellenarVacios', rellenarVacios) + .filter('abGranImporte', granImporte); +}()); diff --git a/19-angular-con-filtro/client/index.html b/19-angular-con-filtro/client/index.html new file mode 100644 index 0000000..5aec25c --- /dev/null +++ b/19-angular-con-filtro/client/index.html @@ -0,0 +1,53 @@ + + + Control de Caja + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + diff --git a/19-angular-con-filtro/client/lista.html b/19-angular-con-filtro/client/lista.html new file mode 100644 index 0000000..15bbb3f --- /dev/null +++ b/19-angular-con-filtro/client/lista.html @@ -0,0 +1,33 @@ +
+

Estos son tus movimientos recientes.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
IdFecha + TipoCategoríaImporte +
{{movimiento.id }} {{movimiento.fecha | date}}{{movimiento.tipo}}{{movimiento.categoria}}{{movimiento.importe | number:2}} €
+
diff --git a/19-angular-con-filtro/client/maestrosFactory.js b/19-angular-con-filtro/client/maestrosFactory.js new file mode 100644 index 0000000..9a9b37d --- /dev/null +++ b/19-angular-con-filtro/client/maestrosFactory.js @@ -0,0 +1,9 @@ +(function () { + var maestrosFactory =   function ($resource)  { + // Los resursos son totalmente configurables + // Una de sus utilidades de 'fábrica' de una caché simple pero potente + return $resource("/api/pub/maestros/",{},{get: {cache: true}}); + }; + + angular.module('controlCajaApp').factory('maestrosFactory', maestrosFactory); +}()); diff --git a/19-angular-con-filtro/client/menuCtrl.js b/19-angular-con-filtro/client/menuCtrl.js new file mode 100644 index 0000000..14ddc17 --- /dev/null +++ b/19-angular-con-filtro/client/menuCtrl.js @@ -0,0 +1,8 @@ +(function () { + var menuCtrl = function ($state) { + this.isActive = function (estado) { + return $state.is(estado); + } + } + angular.module('controlCajaApp').controller('MenuCtrl',menuCtrl); +}()); diff --git a/19-angular-con-filtro/client/movimiento.html b/19-angular-con-filtro/client/movimiento.html new file mode 100644 index 0000000..05baa0c --- /dev/null +++ b/19-angular-con-filtro/client/movimiento.html @@ -0,0 +1,52 @@ +
+
+
+
+ Ver el movimiento {{ vm.movimiento.id}} +
+
+
+
+ +
+
+ {{ vm.movimiento.tipo}} +
+
+
+
+ +
+ {{ vm.movimiento.categoria }} +
+
+
+
+
+ +
+ {{ vm.movimiento.fecha || date }} +
+
+
+ +
+ {{ vm.movimiento.importe }} +
+
+
+
+
+ +
+ {{ vm.movimiento.texto | abRellenarVacios | abRecortar | abLimpiarCadena }} +
+
+
+ +
+
+
+
+
+
diff --git a/19-angular-con-filtro/client/movimientoCtrl.js b/19-angular-con-filtro/client/movimientoCtrl.js new file mode 100644 index 0000000..2feb935 --- /dev/null +++ b/19-angular-con-filtro/client/movimientoCtrl.js @@ -0,0 +1,10 @@ +(function () { + // $stateParams es el servicio de ui-router para acceder a los parámetros de la ruta + var movimientoCtrl = function ($stateParams, movimientosFactory) { + var vm = this; + // El acceso es por nombre de parámetro + var movId = $stateParams.id; + vm.movimiento = movimientosFactory.movimientos.get({id:movId}); + } + angular.module('controlCajaApp').controller('MovimientoCtrl', movimientoCtrl); +}()); diff --git a/19-angular-con-filtro/client/movimientosFactory.js b/19-angular-con-filtro/client/movimientosFactory.js new file mode 100644 index 0000000..26a5053 --- /dev/null +++ b/19-angular-con-filtro/client/movimientosFactory.js @@ -0,0 +1,25 @@ +(function () { + + var movimientosFactory =   function ($resource)  { + + var factory  =   {}; + + // $resource("/api/priv/movimientos/"); + // El uso de parametros en los recursos debe especificarse + // Indicando el nombre local y el remoto + // En la url va el nombre remoto + // En el objeto se repute ese nombre como clave y se dice que propiedad será la que lo lleve + // La propiedad empieza por @ + factory.movimientos =  $resource("/api/priv/movimientos/:id", { + id: "@id" + }) + + + + factory.total = $resource("/api/priv/total/"); + + return factory; + }; + + angular.module('controlCajaApp').factory('movimientosFactory', movimientosFactory); +}()); diff --git a/19-angular-con-filtro/client/not-found.html b/19-angular-con-filtro/client/not-found.html new file mode 100644 index 0000000..85210c2 --- /dev/null +++ b/19-angular-con-filtro/client/not-found.html @@ -0,0 +1,3 @@ +
+

No se ha encontrado lo que buscabas

+
diff --git a/19-angular-con-filtro/client/nuevo.html b/19-angular-con-filtro/client/nuevo.html new file mode 100644 index 0000000..b970d92 --- /dev/null +++ b/19-angular-con-filtro/client/nuevo.html @@ -0,0 +1,65 @@ +
+
+
+
+ Introduce tus movimientos +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+ + +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+ +
+
+
diff --git a/19-angular-con-filtro/client/registro.html b/19-angular-con-filtro/client/registro.html new file mode 100644 index 0000000..6d32793 --- /dev/null +++ b/19-angular-con-filtro/client/registro.html @@ -0,0 +1,37 @@ +
+
+
+
+
+
+ Login +
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+
+
diff --git a/19-angular-con-filtro/client/registroCtrl.js b/19-angular-con-filtro/client/registroCtrl.js new file mode 100644 index 0000000..7344f28 --- /dev/null +++ b/19-angular-con-filtro/client/registroCtrl.js @@ -0,0 +1,26 @@ +(function () { + var registroCtrl = function ($rootScope, $state, $http, $cookieStore) { + var urlBase = "http://localhost:3000/api/"; + var vm = this; + vm.usuario = {}; + vm.entrar = function () { + $http.post(urlBase + 'sesiones/', vm.usuario) + .success(function (data) { + $rootScope.nombre = vm.usuario.email; + $rootScope.mensaje = 'recién entrado'; + $cookieStore.put("sessionId", data); + $state.go("total"); + }); + } + vm.registrar = function () { + $http.post(urlBase + 'usuarios/', vm.usuario) + .success(function (data) { + $rootScope.nombre = vm.usuario.email; + $rootScope.mensaje = 'recién creado'; + $cookieStore.put("sessionId", data); + $state.go("total"); + }); + } + } + angular.module('controlCajaApp').controller('RegistroCtrl', registroCtrl); +}()); diff --git a/19-angular-con-filtro/client/total.html b/19-angular-con-filtro/client/total.html new file mode 100644 index 0000000..5fa080d --- /dev/null +++ b/19-angular-con-filtro/client/total.html @@ -0,0 +1,38 @@ + + +
+ +

{{ caja.titulo }}

+

Comprueba de dónde viene y a dónde va tu dinero.

+
+
+
+

+ + {{ caja.total.ingresos | number:2 }} € + +

+

Total ingresos

+ Acumulado +
+
+

+ + {{ caja.total.gastos | number:2 }} € + +

+

Total gastos

+ Acumulado +
+
+

+ + {{ caja.balance() | number:2 }} € + +

+

Balance

+ Ingresos-Gastos +
+
+
+
diff --git a/19-angular-con-filtro/package.json b/19-angular-con-filtro/package.json new file mode 100644 index 0000000..6028a5d --- /dev/null +++ b/19-angular-con-filtro/package.json @@ -0,0 +1,15 @@ +{ + "name": "ControlCashFlow", + "version": "0.0.0", + "description": "Controla tu flujo de caja", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Alberto Basalo", + "license": "BSD-2-Clause", + "dependencies": { + "express": "~4.12.1", + "body-parser": "~1.0.2" + } +} diff --git a/19-angular-con-filtro/server.js b/19-angular-con-filtro/server.js new file mode 100644 index 0000000..8d3fc97 --- /dev/null +++ b/19-angular-con-filtro/server.js @@ -0,0 +1,187 @@ +var express = require('express'); +var bodyParser = require('body-parser'); + +var app = express(); +app.use(bodyParser()); +app.use(express.static(__dirname + '/client')); + +app.use(function (peticion, respuesta, siguiente) { + console.log("recibida petición: " + peticion.url); + if (peticion.body && Object.keys(peticion.body).length>0) { + console.log("body: " + JSON.stringify(peticion.body)); + } + siguiente(); + }); + +console.log('ready'); + +var maxId = 0; +var movimientos = []; +var totales = []; + +var usuarios = []; +var sesiones = []; + + +app.use('/api/priv/', function (req, res, next) { + var sessionId = req.get('sessionId'); + var sesion = getSesion(sessionId); + if (sesion) { + if (esSesionValida(sesion)) { + sesion.timeStamp = new Date(); + req.usuario = sesion.email; + next(); + } else { + console.log('Sesión caducada:' + JSON.stringify(sesion)); + res.status(419).send('Sesión caducada'); + } + } else { + res.status(401).send('Credencial inválida'); + } + +}); + + + + + +// API - REST +// SECURITY +app.route('/api/usuarios') + .get(function (req, res, next) { + // Esto devuelve la lista completa de usuarios y contraseñas + // PELIGRO: Usar sólo a modo de debug mientras desarrollamos + res.json(usuarios); + }) + .post(function (req, res, next) { + var usuario = req.body; + if (existeUsuario(usuario)) { + console.log('email ya registrado:' + usuario.email); + res.status(409).send('email ' + usuario.email + ' ya registrado'); + } else { + console.log('registrado:' + usuario.email); + usuarios.push(usuario); + res.json(newSession(usuario.email)); + } + }); + +// Gestión de sesiones: listado y login +app.route('/api/sesiones') + .get(function (req, res, next) { + // Esto devuelve la lista completa de sesiones + // PELIGRO: Usar sólo a modo de debug mientras desarrollamos + res.json(sesiones); + }) + .post(function (req, res, next) { + var usuario = req.body; + if (esUsuarioValido(usuario)) { + console.log('aceptado:' + usuario.email); + res.json(newSession(usuario.email)); + } else { + console.log('Credencial inválida:' + usuario.email); + res.status(401).send('Credencial inválida'); + } + }); + +function existeUsuario(usuario) { + return usuarios.some(function (u) { + return u.email == usuario.email; + }); +} + +function esUsuarioValido(usuario) { + return usuarios.filter(function (u) { + return u.email == usuario.email && u.password == usuario.password; + })[0]; +} + +function getSesion(sessionId) { + return sesiones.filter(function (s) { + return s.sessionId == sessionId; + })[0] +} + +function esSesionValida(sesion) { + return (new Date() - sesion.timeStamp) < 20 * 60 * 1000; +} + +function newSession(email) { + var sessionId = Math.random() * (88888) + 11111; + var timeStamp = new Date(); + sesiones.push({ + sessionId: sessionId, + email: email, + timeStamp: timeStamp + }); + return sessionId; +} + + +// BUSINESS +app.get('/api/pub/maestros', function (req, res, next) { + var maestros = { + categoriasIngresos: ['Nómina', 'Ventas', 'Intereses Depósitos'], + categoriasGastos: ['Hipotéca', 'Compras', 'Impuestos'] + }; + res.json(maestros); +}); + +app.route('/api/priv/movimientos') + .get(function (req, res, next) { + var movimientosUsuario = movimientos.filter(function (m) { + return m.usuario = req.usuario; + }); + res.json(movimientosUsuario); + }) + .post(function (req, res, next) { + var movimiento = req.body; + movimiento.usuario = req.usuario; + maxId++; + movimiento.id = maxId; + movimientos.push(movimiento); + var totalUsuario = getTotalUsuario(req.usuario); + if (movimiento.tipo == 'Ingreso') + totalUsuario.ingresos += movimiento.importe; + else + totalUsuario.gastos += movimiento.importe; + res.status(200).send(); + }); + +app.get('/api/priv/movimientos/:id', function (req, res, next) { + var movId = req.params.id; + movimientoBuscado = getMovimientoById(movId, req.usuario); + res.json(movimientoBuscado); +}); + +function getMovimientoById(id,usuario) { + var movimientoBuscado = movimientos.filter(function (movimiento) { + return movimiento.id == id && movimiento.usuario == usuario; + })[0]; + return movimientoBuscado; +} + +app.get('/api/priv/total', function (req, res, next) { + var totalUsuario = getTotalUsuario(req.usuario); + res.json(totalUsuario); +}); + +function getTotalUsuario(usuario) { + if(usuario===undefined) return {}; + var totalUsuario = totales.filter(function (t) { + return t.usuario == usuario; + })[0]; + if (totalUsuario===undefined) { + totalUsuario = { + usuario : usuario, + ingresos: 0, + gastos: 0 + }; + totales.push(totalUsuario); + } + return totalUsuario; +} + + +console.log('steady'); +app.listen(3000); +console.log('go'); diff --git a/20-directivas/client/app.js b/20-directivas/client/app.js new file mode 100644 index 0000000..47500cb --- /dev/null +++ b/20-directivas/client/app.js @@ -0,0 +1,36 @@ +// Incluimos referencia al módulo de directivas +angular.module('controlCajaApp', ['ui.router','ngCookies', 'ngResource', 'abFiltros','abDirectivas']); + +angular.module('controlCajaApp').config(function ($stateProvider,$locationProvider) { + $stateProvider + .state('total', { + url: '/', + controller: 'CajaCtrl as caja', + templateUrl: 'total.html' + }) + .state('nuevo', { + url: '/nuevo', + controller: 'CajaCtrl as caja', + templateUrl: 'nuevo.html' + }) + .state('lista', { + url: '/lista', + controller: 'CajaCtrl as caja', + templateUrl: 'lista.html' + }) + .state('movimiento', { + url: '/movimiento/:id', // declaracion de parametros en rutas + controller: 'MovimientoCtrl as vm', + templateUrl: 'movimiento.html' + }) + .state('registro', { + url: '/registro', + controller: 'RegistroCtrl as registro', + templateUrl: 'registro.html' + }) + .state('not-found', { + url: '*path', + controller: 'CajaCtrl as caja', + templateUrl: 'total.html' + }); +}); diff --git a/20-directivas/client/appHttpLog.js b/20-directivas/client/appHttpLog.js new file mode 100644 index 0000000..ec065c4 --- /dev/null +++ b/20-directivas/client/appHttpLog.js @@ -0,0 +1,23 @@ +(function () { + + function configuradorInterceptores($httpProvider) { + $httpProvider.interceptors.push(funcionInterceptoraLog); + } + + // Esta función se especializa en escribir en la consola + // información sobre llamadas http + function funcionInterceptoraLog($log) { + + var interceptor = {}; + interceptor.request = function (request) { + $log.info('request:' + request.url); + return request ; + }; + interceptor.responseError = function (response) { + $log.error("excepción: " + response.status + " de :" + response.config.url); + } + return interceptor; + } + + angular.module('controlCajaApp').config(configuradorInterceptores); +}()); diff --git a/20-directivas/client/appSecurity.js b/20-directivas/client/appSecurity.js new file mode 100644 index 0000000..5948427 --- /dev/null +++ b/20-directivas/client/appSecurity.js @@ -0,0 +1,29 @@ +(function () { + function configuradorInterceptores($httpProvider) { + $httpProvider.interceptors.push(funcionInterceptoraSeguridad); + } + + function funcionInterceptoraSeguridad($q, $injector, $cookieStore, $rootScope) { + + var interceptor = {}; + interceptor.request = function (request) { + request.headers["sessionId"] = $cookieStore.get("sessionId"); + return request || $q.when(request); + }; + interceptor.responseError = function (response) { + var state = $injector.get('$state'); + if (response.status === 401) { + $rootScope.mensaje = "No hay derecho!!!"; + state.go('registro'); + } else if (response.status === 419) { + $rootScope.mensaje = "Estoy caduco!!!"; + $cookieStore.remove("sessionId") + state.go('registro'); + }; + return $q.reject(response); + } + return interceptor; + } + + angular.module('controlCajaApp').config(configuradorInterceptores); +}()); diff --git a/20-directivas/client/cajaCtrl.js b/20-directivas/client/cajaCtrl.js new file mode 100644 index 0000000..7093eee --- /dev/null +++ b/20-directivas/client/cajaCtrl.js @@ -0,0 +1,34 @@ +(function () { + var cajaCtrl = function (maestrosFactory, movimientosFactory) { + var vm = this; + + vm.titulo = "Controla tu Cash Flow"; + + vm.maestros = maestrosFactory.get(); + + vm.nuevoMovimiento = new movimientosFactory.movimientos(); + vm.nuevoMovimiento.esIngreso =1; + vm.nuevoMovimiento.fecha = new Date(); + + vm.movimientos = movimientosFactory.movimientos.query(); + + vm.total = movimientosFactory.total.get(); + + vm.guardarMovimiento = function () { + vm.nuevoMovimiento.tipo = vm.tipo(vm.nuevoMovimiento); + vm.nuevoMovimiento.$save() + .then(function(postedData){ + vm.movimientos = movimientosFactory.movimientos.query(); + vm.total = movimientosFactory.total.get(); + vm.nuevoMovimiento.importe = 0; + }); + } + vm.balance = function () { + return vm.total.ingresos - vm.total.gastos + } + vm.tipo = function (movimiento) { + return movimiento.esIngreso && 'Ingreso' || 'Gasto' + } + } + angular.module('controlCajaApp').controller('CajaCtrl', cajaCtrl); +}()); diff --git a/20-directivas/client/directivas.js b/20-directivas/client/directivas.js new file mode 100644 index 0000000..59cc3e9 --- /dev/null +++ b/20-directivas/client/directivas.js @@ -0,0 +1,89 @@ +(function () { + // El uso más simple es crear directivas para usar como código reutilizable + function piePagina() { + return { + restrict: 'AE', + replace: 'true', + template: '' + }; + }; + + // Dos mejoras, sacar el html a un fichero externo (tpl-directiva) + // Usar Transclude para reutilizar el contenido del usuario y hacer la vista más dinámica + function cabecera() { + return { + restrict: 'AE', + replace: 'true', + transclude: true, + templateUrl: '/tpl-cabecera.html' + }; + }; + + // En este caso el cambio más siginificatico es el scope + // Funciona como un API para la directiva y recibe la info vía atributos + // Por otro lado en este caso hemos tenido que restringir el uso de la directiva + // Los elementos tr dentro de un table requieren definirse explícitamente + function filaMovimiento() { + return { + restrict: 'A', + templateUrl: '/tpl-fila-movimiento.html', + scope: { + movimientoplantilla: "=movimientodirectiva" + } + }; + } + + // Otro uso de las directivas es extender la funionalidad del DOM sin importar los datos + // Este es el lugar donde manipular el DOM y jamás en los controladores + function seleccionado() { + return { + link: function ($scope, element, attrs) { + element.bind('mouseenter', function () { + element.css('background-color', 'lightyellow'); + }); + element.bind('mouseleave', function () { + element.css('background-color', 'white'); + }); + } + }; + } + + // Ejemplo no funcional de cómo extender un plugin hecho a medida + function plugin() { + return { + link: function (scope, element, attrs) { + + // Obtención de parámetros vía atributos + var init = scope.$eval(attrs.ngModel); + var min = scope.$eval(attrs.abSliderMin); + var max = scope.$eval(attrs.abSliderMax); + // Configuración básica + $(element).plugin({ + value: init, + min: min, + max: max, + tooltip: 'hide' + }); + + // Actualizar la vista cuando cambia el modelo + scope.$watch(attrs.ngModel, function (valor) { + $(element).plugin('setValue', valor); + }); + + // Actualizar el modelo cuando cambia la vista + $(element).plugin().on('slide', function (evento) { + scope.$apply(function () { + scope[attrs.ngModel] = evento.value; + }); + }); + } + } + } + + angular.module('abDirectivas', []) + .directive('abPiePagina', piePagina) + .directive('abCabecera', cabecera) + .directive('abFilaMovimiento', filaMovimiento) + .directive('abSeleccionado', seleccionado) + .directive('abPlugin', plugin); +}()); diff --git a/20-directivas/client/filtros.js b/20-directivas/client/filtros.js new file mode 100644 index 0000000..c96b9d5 --- /dev/null +++ b/20-directivas/client/filtros.js @@ -0,0 +1,84 @@ +(function () { + // Los filtros se declaran como funciones que a su vez devuelven... funciones + + // Esas funciones internas se aplican sobre los valores, + // Tienen al menos un parámetro, que sirve de entrada + + + // Esta función le quita acentos, guiones bajos, y caracteres raros + // Los sustituye por guiones bajos + function limpiarCadena() { + var funcionFiltro = function (cadena) { + if (cadena) { + var resultado = cadena.toLowerCase(); + var patron = /[^-A-Za-z0-9]+/g; + return resultado.replace(patron, '_'); + } + }; + return funcionFiltro; + } + + // Devuelve un trozo de texto y tres puntos suspensivos... para indicar que hay más... + // Demuestra que un filtro puede tener parámetros, y cómo tratar valores por defecto. + function recortar() { + var funcionFiltro = function (cadena, largo, quitarInicio) { + if (!cadena) { + return '' + } + if (!largo) { + largo = 10 + } + if (cadena.length <= largo) { + return cadena + } + if (quitarInicio) { + return '...' + cadena.substring(cadena.length - largo) + } else { + return cadena.substring(0, largo) + '...' + } + }; + return funcionFiltro; + } + + // Con control de errores y lógica compleja + function rellenarVacios() { + var funcionFiltro = function (cadena) { + try { + if (!cadena || cadena === undefined || cadena.trim() === "") { + return '---'; + }; + } catch (err) { + return '---'; + } + return cadena; + } + return funcionFiltro; + } + + // Permite tener filtros predeterminadois en un array + function granImporte() { + var funcionFiltro = function (movimientos, valorCorte) { + if (valorCorte) { + var filtrados = []; + for (var i = 0; i < movimientos.length; i++) { + var mov = movimientos[i]; + if (mov.importe >= valorCorte) { + filtrados.push(mov); + } + } + return filtrados; + } else { + return movimientos; + } + }; + return funcionFiltro; + } + + // Podemos usar una sintaxis fluida y declarar todo en una solo línea + // Atención a la declaración de un nuevo módulo genérico + angular.module('abFiltros', []) + .filter('abLimpiarCadena', limpiarCadena) + .filter('abRecortar', recortar) + .filter('abRellenarVacios', rellenarVacios) + .filter('abGranImporte', granImporte); +}()); diff --git a/20-directivas/client/index.html b/20-directivas/client/index.html new file mode 100644 index 0000000..790a357 --- /dev/null +++ b/20-directivas/client/index.html @@ -0,0 +1,59 @@ + + + + Control de Caja + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/20-directivas/client/lista.html b/20-directivas/client/lista.html new file mode 100644 index 0000000..ea9a373 --- /dev/null +++ b/20-directivas/client/lista.html @@ -0,0 +1,39 @@ +
+ + Estos son tus movimientos recientes. +
+ + + + + + + + + + + + + + + + + + + +
IdFecha + TipoCategoríaImporte + Valoración
+
diff --git a/20-directivas/client/maestrosFactory.js b/20-directivas/client/maestrosFactory.js new file mode 100644 index 0000000..9a9b37d --- /dev/null +++ b/20-directivas/client/maestrosFactory.js @@ -0,0 +1,9 @@ +(function () { + var maestrosFactory =   function ($resource)  { + // Los resursos son totalmente configurables + // Una de sus utilidades de 'fábrica' de una caché simple pero potente + return $resource("/api/pub/maestros/",{},{get: {cache: true}}); + }; + + angular.module('controlCajaApp').factory('maestrosFactory', maestrosFactory); +}()); diff --git a/20-directivas/client/menuCtrl.js b/20-directivas/client/menuCtrl.js new file mode 100644 index 0000000..14ddc17 --- /dev/null +++ b/20-directivas/client/menuCtrl.js @@ -0,0 +1,8 @@ +(function () { + var menuCtrl = function ($state) { + this.isActive = function (estado) { + return $state.is(estado); + } + } + angular.module('controlCajaApp').controller('MenuCtrl',menuCtrl); +}()); diff --git a/20-directivas/client/movimiento.html b/20-directivas/client/movimiento.html new file mode 100644 index 0000000..dec2ee1 --- /dev/null +++ b/20-directivas/client/movimiento.html @@ -0,0 +1,55 @@ +
+
+
+
+ Ver el movimiento {{ vm.movimiento.id}} +
+
+
+
+ +
+
+ {{ vm.movimiento.tipo}} +
+
+
+
+ +
+ {{ vm.movimiento.categoria }} +
+
+
+
+
+ +
+ {{ vm.movimiento.fecha || date }} +
+
+
+ +
+ {{ vm.movimiento.importe }} +
+
+
+
+
+ +
+ {{ vm.movimiento.texto | abRellenarVacios | abRecortar | abLimpiarCadena }} +
+
+
+ +
+ +
+
+
+
+
+
+
diff --git a/20-directivas/client/movimientoCtrl.js b/20-directivas/client/movimientoCtrl.js new file mode 100644 index 0000000..2feb935 --- /dev/null +++ b/20-directivas/client/movimientoCtrl.js @@ -0,0 +1,10 @@ +(function () { + // $stateParams es el servicio de ui-router para acceder a los parámetros de la ruta + var movimientoCtrl = function ($stateParams, movimientosFactory) { + var vm = this; + // El acceso es por nombre de parámetro + var movId = $stateParams.id; + vm.movimiento = movimientosFactory.movimientos.get({id:movId}); + } + angular.module('controlCajaApp').controller('MovimientoCtrl', movimientoCtrl); +}()); diff --git a/20-directivas/client/movimientosFactory.js b/20-directivas/client/movimientosFactory.js new file mode 100644 index 0000000..26a5053 --- /dev/null +++ b/20-directivas/client/movimientosFactory.js @@ -0,0 +1,25 @@ +(function () { + + var movimientosFactory =   function ($resource)  { + + var factory  =   {}; + + // $resource("/api/priv/movimientos/"); + // El uso de parametros en los recursos debe especificarse + // Indicando el nombre local y el remoto + // En la url va el nombre remoto + // En el objeto se repute ese nombre como clave y se dice que propiedad será la que lo lleve + // La propiedad empieza por @ + factory.movimientos =  $resource("/api/priv/movimientos/:id", { + id: "@id" + }) + + + + factory.total = $resource("/api/priv/total/"); + + return factory; + }; + + angular.module('controlCajaApp').factory('movimientosFactory', movimientosFactory); +}()); diff --git a/20-directivas/client/not-found.html b/20-directivas/client/not-found.html new file mode 100644 index 0000000..85210c2 --- /dev/null +++ b/20-directivas/client/not-found.html @@ -0,0 +1,3 @@ +
+

No se ha encontrado lo que buscabas

+
diff --git a/20-directivas/client/nuevo.html b/20-directivas/client/nuevo.html new file mode 100644 index 0000000..4421563 --- /dev/null +++ b/20-directivas/client/nuevo.html @@ -0,0 +1,73 @@ +
+ Introduce tus movimientos. +
+
+ +
+
+
+ +
+
+ + +
+
+
+
+ +
+ + +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
diff --git a/20-directivas/client/registro.html b/20-directivas/client/registro.html new file mode 100644 index 0000000..6d32793 --- /dev/null +++ b/20-directivas/client/registro.html @@ -0,0 +1,37 @@ +
+
+
+
+
+
+ Login +
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+
+
diff --git a/20-directivas/client/registroCtrl.js b/20-directivas/client/registroCtrl.js new file mode 100644 index 0000000..7344f28 --- /dev/null +++ b/20-directivas/client/registroCtrl.js @@ -0,0 +1,26 @@ +(function () { + var registroCtrl = function ($rootScope, $state, $http, $cookieStore) { + var urlBase = "http://localhost:3000/api/"; + var vm = this; + vm.usuario = {}; + vm.entrar = function () { + $http.post(urlBase + 'sesiones/', vm.usuario) + .success(function (data) { + $rootScope.nombre = vm.usuario.email; + $rootScope.mensaje = 'recién entrado'; + $cookieStore.put("sessionId", data); + $state.go("total"); + }); + } + vm.registrar = function () { + $http.post(urlBase + 'usuarios/', vm.usuario) + .success(function (data) { + $rootScope.nombre = vm.usuario.email; + $rootScope.mensaje = 'recién creado'; + $cookieStore.put("sessionId", data); + $state.go("total"); + }); + } + } + angular.module('controlCajaApp').controller('RegistroCtrl', registroCtrl); +}()); diff --git a/20-directivas/client/total.html b/20-directivas/client/total.html new file mode 100644 index 0000000..f745c5c --- /dev/null +++ b/20-directivas/client/total.html @@ -0,0 +1,38 @@ +
+ + Comprueba de dónde viene y a dónde va tu dinero. +
+
+
+

+ + {{ caja.total.ingresos | number:2 }} € + +

+

Total ingresos

+ Acumulado +
+
+

+ + {{ caja.total.gastos | number:2 }} € + +

+

Total gastos

+ Acumulado +
+
+

+ + {{ caja.balance() | number:2 }} € + +

+

Balance

+ Ingresos-Gastos +
+
+
+
diff --git a/20-directivas/client/tpl-cabecera.html b/20-directivas/client/tpl-cabecera.html new file mode 100644 index 0000000..5245a7b --- /dev/null +++ b/20-directivas/client/tpl-cabecera.html @@ -0,0 +1,6 @@ +
+
+

Cash Flow

+
+
+
diff --git a/20-directivas/client/tpl-fila-movimiento.html b/20-directivas/client/tpl-fila-movimiento.html new file mode 100644 index 0000000..2c3f4c0 --- /dev/null +++ b/20-directivas/client/tpl-fila-movimiento.html @@ -0,0 +1,10 @@ + + {{movimientoplantilla.id }} + {{movimientoplantilla.fecha | date}} + {{movimientoplantilla.tipo}} + {{movimientoplantilla.categoria}} + + {{movimientoplantilla.importe | number:2}} € + + + diff --git a/20-directivas/client/tpl-valoracion.html b/20-directivas/client/tpl-valoracion.html new file mode 100644 index 0000000..63a8bcd --- /dev/null +++ b/20-directivas/client/tpl-valoracion.html @@ -0,0 +1,12 @@ + + + + * + + + diff --git a/20-directivas/client/valoracion.js b/20-directivas/client/valoracion.js new file mode 100644 index 0000000..d055fd8 --- /dev/null +++ b/20-directivas/client/valoracion.js @@ -0,0 +1,38 @@ +(function () { + var valoracion = function () { + return { + restrict: 'AE', + templateUrl: '/tpl-valoracion.html', + scope: { + valor: '=', + max: '@', + soloLectura: '@' + }, + link: function (scope, elem, attrs) { + function actualizar() { + if(!scope.valor)scope.valor=1; + scope.estrellas = []; + for (var i = 0; i < scope.max; i++) { + var estrella = { + marcada: (i < scope.valor) + }; + scope.estrellas.push(estrella); + } + }; + + scope.marcar = function (indice) { + if (scope.soloLectura && scope.soloLectura === 'true') { + return; + } + scope.valor = indice + 1; + actualizar(); + }; + actualizar(); + } + } + } + + angular.module('abDirectivas') + .directive('abValoracion', valoracion); + +}()); diff --git a/20-directivas/directivas.md b/20-directivas/directivas.md new file mode 100644 index 0000000..1fa3783 --- /dev/null +++ b/20-directivas/directivas.md @@ -0,0 +1,46 @@ +Directivas +========== + +- Se usan de forma **declarativa** en el HTML +- Su función es extender y enriquecer el HTML encapsulando el **acceso al DOM** +- Se definen como **objetos JS** con propiedades específicas + +Propiedades +----------- + +### Restrict +Sirve para determinar dónde se puede aplicar la directiva + +Valores posibles: + + - **A** – Attribute `
` + - **E** – Element ` ` + - **C** – Class `
` + - **M** – Comment ` ` + +### Replace +Indica si el elemento al que se aplica debe ser sustituído por el resultado de la ejecución + +### Transclude +Indica si el elemento al que se aplicaca incluirá el resultado de la ejecución de la directiva en su interior + +### Template y template URL +Cadena de texto o enlace a fichero que contine el HTML que va a devolver la directiva + +### Scope +Intercambio de datos con el elemento que declara la directiva. +Valores posibles: + + - **@** Interpolating, para enlazar un texto dentro de la directiva. ` scope: { soloLectura: '@' } ` + - **=** Data bind, un objeto para doble-enlace que la directiva pueda cambiar ` scope: { valor: '=alias' } ` + - **&** Expression `scope: { onEvaluado: '&' } ` + + +### Link +Una función que enlazará la directiva con el scope + +### Compile +Una función que podrá manipular el DOM creando una función link + +### Controller +Una función que actuará como contrlador para esta directiva diff --git a/20-directivas/package.json b/20-directivas/package.json new file mode 100644 index 0000000..6028a5d --- /dev/null +++ b/20-directivas/package.json @@ -0,0 +1,15 @@ +{ + "name": "ControlCashFlow", + "version": "0.0.0", + "description": "Controla tu flujo de caja", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Alberto Basalo", + "license": "BSD-2-Clause", + "dependencies": { + "express": "~4.12.1", + "body-parser": "~1.0.2" + } +} diff --git a/20-directivas/recursos.md b/20-directivas/recursos.md new file mode 100644 index 0000000..678453c --- /dev/null +++ b/20-directivas/recursos.md @@ -0,0 +1,7 @@ +https://angular-ui.github.io/ +http://ngmodules.org/c +http://angular-js.in/ +http://www.directiv.es/ + +http://www.tamas.io/node-jsexpress-cors-implementation/ +http://stackoverflow.com/questions/23823010/how-to-enable-cors-in-angularjs \ No newline at end of file diff --git a/20-directivas/server.js b/20-directivas/server.js new file mode 100644 index 0000000..8d3fc97 --- /dev/null +++ b/20-directivas/server.js @@ -0,0 +1,187 @@ +var express = require('express'); +var bodyParser = require('body-parser'); + +var app = express(); +app.use(bodyParser()); +app.use(express.static(__dirname + '/client')); + +app.use(function (peticion, respuesta, siguiente) { + console.log("recibida petición: " + peticion.url); + if (peticion.body && Object.keys(peticion.body).length>0) { + console.log("body: " + JSON.stringify(peticion.body)); + } + siguiente(); + }); + +console.log('ready'); + +var maxId = 0; +var movimientos = []; +var totales = []; + +var usuarios = []; +var sesiones = []; + + +app.use('/api/priv/', function (req, res, next) { + var sessionId = req.get('sessionId'); + var sesion = getSesion(sessionId); + if (sesion) { + if (esSesionValida(sesion)) { + sesion.timeStamp = new Date(); + req.usuario = sesion.email; + next(); + } else { + console.log('Sesión caducada:' + JSON.stringify(sesion)); + res.status(419).send('Sesión caducada'); + } + } else { + res.status(401).send('Credencial inválida'); + } + +}); + + + + + +// API - REST +// SECURITY +app.route('/api/usuarios') + .get(function (req, res, next) { + // Esto devuelve la lista completa de usuarios y contraseñas + // PELIGRO: Usar sólo a modo de debug mientras desarrollamos + res.json(usuarios); + }) + .post(function (req, res, next) { + var usuario = req.body; + if (existeUsuario(usuario)) { + console.log('email ya registrado:' + usuario.email); + res.status(409).send('email ' + usuario.email + ' ya registrado'); + } else { + console.log('registrado:' + usuario.email); + usuarios.push(usuario); + res.json(newSession(usuario.email)); + } + }); + +// Gestión de sesiones: listado y login +app.route('/api/sesiones') + .get(function (req, res, next) { + // Esto devuelve la lista completa de sesiones + // PELIGRO: Usar sólo a modo de debug mientras desarrollamos + res.json(sesiones); + }) + .post(function (req, res, next) { + var usuario = req.body; + if (esUsuarioValido(usuario)) { + console.log('aceptado:' + usuario.email); + res.json(newSession(usuario.email)); + } else { + console.log('Credencial inválida:' + usuario.email); + res.status(401).send('Credencial inválida'); + } + }); + +function existeUsuario(usuario) { + return usuarios.some(function (u) { + return u.email == usuario.email; + }); +} + +function esUsuarioValido(usuario) { + return usuarios.filter(function (u) { + return u.email == usuario.email && u.password == usuario.password; + })[0]; +} + +function getSesion(sessionId) { + return sesiones.filter(function (s) { + return s.sessionId == sessionId; + })[0] +} + +function esSesionValida(sesion) { + return (new Date() - sesion.timeStamp) < 20 * 60 * 1000; +} + +function newSession(email) { + var sessionId = Math.random() * (88888) + 11111; + var timeStamp = new Date(); + sesiones.push({ + sessionId: sessionId, + email: email, + timeStamp: timeStamp + }); + return sessionId; +} + + +// BUSINESS +app.get('/api/pub/maestros', function (req, res, next) { + var maestros = { + categoriasIngresos: ['Nómina', 'Ventas', 'Intereses Depósitos'], + categoriasGastos: ['Hipotéca', 'Compras', 'Impuestos'] + }; + res.json(maestros); +}); + +app.route('/api/priv/movimientos') + .get(function (req, res, next) { + var movimientosUsuario = movimientos.filter(function (m) { + return m.usuario = req.usuario; + }); + res.json(movimientosUsuario); + }) + .post(function (req, res, next) { + var movimiento = req.body; + movimiento.usuario = req.usuario; + maxId++; + movimiento.id = maxId; + movimientos.push(movimiento); + var totalUsuario = getTotalUsuario(req.usuario); + if (movimiento.tipo == 'Ingreso') + totalUsuario.ingresos += movimiento.importe; + else + totalUsuario.gastos += movimiento.importe; + res.status(200).send(); + }); + +app.get('/api/priv/movimientos/:id', function (req, res, next) { + var movId = req.params.id; + movimientoBuscado = getMovimientoById(movId, req.usuario); + res.json(movimientoBuscado); +}); + +function getMovimientoById(id,usuario) { + var movimientoBuscado = movimientos.filter(function (movimiento) { + return movimiento.id == id && movimiento.usuario == usuario; + })[0]; + return movimientoBuscado; +} + +app.get('/api/priv/total', function (req, res, next) { + var totalUsuario = getTotalUsuario(req.usuario); + res.json(totalUsuario); +}); + +function getTotalUsuario(usuario) { + if(usuario===undefined) return {}; + var totalUsuario = totales.filter(function (t) { + return t.usuario == usuario; + })[0]; + if (totalUsuario===undefined) { + totalUsuario = { + usuario : usuario, + ingresos: 0, + gastos: 0 + }; + totales.push(totalUsuario); + } + return totalUsuario; +} + + +console.log('steady'); +app.listen(3000); +console.log('go'); diff --git a/21-organizacion/client/app.js b/21-organizacion/client/app.js new file mode 100644 index 0000000..5d0d216 --- /dev/null +++ b/21-organizacion/client/app.js @@ -0,0 +1,35 @@ +angular.module('controlCajaApp', ['ui.router','ngCookies', 'ngResource', 'abFiltros','abDirectivas']); + +angular.module('controlCajaApp').config(function ($stateProvider,$locationProvider) { + $stateProvider + .state('total', { + url: '/', + controller: 'CajaCtrl as caja', + templateUrl: 'total.html' + }) + .state('nuevo', { + url: '/nuevo', + controller: 'CajaCtrl as caja', + templateUrl: 'nuevo.html' + }) + .state('lista', { + url: '/lista', + controller: 'CajaCtrl as caja', + templateUrl: 'lista.html' + }) + .state('movimiento', { + url: '/movimiento/:id', + controller: 'MovimientoCtrl as vm', + templateUrl: 'movimiento.html' + }) + .state('registro', { + url: '/registro', + controller: 'RegistroCtrl as registro', + templateUrl: 'registro.html' + }) + .state('not-found', { + url: '*path', + controller: 'CajaCtrl as caja', + templateUrl: 'total.html' + }); +}); diff --git a/21-organizacion/client/appHttpLog.js b/21-organizacion/client/appHttpLog.js new file mode 100644 index 0000000..0357f95 --- /dev/null +++ b/21-organizacion/client/appHttpLog.js @@ -0,0 +1,21 @@ +(function () { + + function configuradorInterceptores($httpProvider) { + $httpProvider.interceptors.push(funcionInterceptoraLog); + } + + function funcionInterceptoraLog($log) { + + var interceptor = {}; + interceptor.request = function (request) { + $log.info('request:' + request.url); + return request ; + }; + interceptor.responseError = function (response) { + $log.error("excepción: " + response.status + " de :" + response.config.url); + } + return interceptor; + } + + angular.module('controlCajaApp').config(configuradorInterceptores); +}()); diff --git a/21-organizacion/client/appSecurity.js b/21-organizacion/client/appSecurity.js new file mode 100644 index 0000000..5948427 --- /dev/null +++ b/21-organizacion/client/appSecurity.js @@ -0,0 +1,29 @@ +(function () { + function configuradorInterceptores($httpProvider) { + $httpProvider.interceptors.push(funcionInterceptoraSeguridad); + } + + function funcionInterceptoraSeguridad($q, $injector, $cookieStore, $rootScope) { + + var interceptor = {}; + interceptor.request = function (request) { + request.headers["sessionId"] = $cookieStore.get("sessionId"); + return request || $q.when(request); + }; + interceptor.responseError = function (response) { + var state = $injector.get('$state'); + if (response.status === 401) { + $rootScope.mensaje = "No hay derecho!!!"; + state.go('registro'); + } else if (response.status === 419) { + $rootScope.mensaje = "Estoy caduco!!!"; + $cookieStore.remove("sessionId") + state.go('registro'); + }; + return $q.reject(response); + } + return interceptor; + } + + angular.module('controlCajaApp').config(configuradorInterceptores); +}()); diff --git a/21-organizacion/client/cajaCtrl.js b/21-organizacion/client/cajaCtrl.js new file mode 100644 index 0000000..60ab90e --- /dev/null +++ b/21-organizacion/client/cajaCtrl.js @@ -0,0 +1,34 @@ +(function () { + var cajaCtrl = function (maestrosFactory, movimientosFactory) { + var vm = this; + + vm.titulo = "Controla tu Cash Flow"; + + vm.maestros = maestrosFactory.get(); + + vm.nuevoMovimiento = new movimientosFactory.movimientos(); + vm.nuevoMovimiento.esIngreso = 1; + vm.nuevoMovimiento.fecha = new Date(); + + vm.movimientos = movimientosFactory.movimientos.query(); + + vm.total = movimientosFactory.total.get(); + + vm.guardarMovimiento = function () { + vm.nuevoMovimiento.tipo = vm.tipo(vm.nuevoMovimiento); + vm.nuevoMovimiento.$save() + .then(function (postedData) { + vm.movimientos = movimientosFactory.movimientos.query(); + vm.total = movimientosFactory.total.get(); + vm.nuevoMovimiento.importe = 0; + }); + } + vm.balance = function () { + return vm.total.ingresos - vm.total.gastos + } + vm.tipo = function (movimiento) { + return movimiento.esIngreso && 'Ingreso' || 'Gasto' + } + } + angular.module('controlCajaApp').controller('CajaCtrl', cajaCtrl); +}()); diff --git a/21-organizacion/client/directivas.js b/21-organizacion/client/directivas.js new file mode 100644 index 0000000..617471d --- /dev/null +++ b/21-organizacion/client/directivas.js @@ -0,0 +1,74 @@ +(function () { + function piePagina() { + return { + restrict: 'AE', + replace: 'true', + template: '' + }; + }; + + function cabecera() { + return { + restrict: 'AE', + replace: 'true', + transclude: true, + templateUrl: '/tpl-cabecera.html' + }; + }; + + function filaMovimiento() { + return { + restrict: 'A', + templateUrl: '/tpl-fila-movimiento.html', + scope: { + movimientoplantilla: "=movimientodirectiva" + } + }; + } + + function seleccionado() { + return { + link: function ($scope, element, attrs) { + element.bind('mouseenter', function () { + element.css('background-color', 'lightyellow'); + }); + element.bind('mouseleave', function () { + element.css('background-color', 'white'); + }); + } + }; + } + + function plugin() { + return { + link: function (scope, element, attrs) { + var init = scope.$eval(attrs.ngModel); + var min = scope.$eval(attrs.abSliderMin); + var max = scope.$eval(attrs.abSliderMax); + $(element).plugin({ + value: init, + min: min, + max: max, + tooltip: 'hide' + }); + + scope.$watch(attrs.ngModel, function (valor) { + $(element).plugin('setValue', valor); + }); + + $(element).plugin().on('slide', function (evento) { + scope.$apply(function () { + scope[attrs.ngModel] = evento.value; + }); + }); + } + } + } + + angular.module('abDirectivas', []) + .directive('abPiePagina', piePagina) + .directive('abCabecera', cabecera) + .directive('abFilaMovimiento', filaMovimiento) + .directive('abSeleccionado', seleccionado) + .directive('abPlugin', plugin); +}()); diff --git a/21-organizacion/client/filtros.js b/21-organizacion/client/filtros.js new file mode 100644 index 0000000..6398464 --- /dev/null +++ b/21-organizacion/client/filtros.js @@ -0,0 +1,71 @@ +(function () { + + function limpiarCadena() { + var funcionFiltro = function (cadena) { + if (cadena) { + var resultado = cadena.toLowerCase(); + var patron = /[^-A-Za-z0-9]+/g; + return resultado.replace(patron, '_'); + } + }; + return funcionFiltro; + } + + function recortar() { + var funcionFiltro = function (cadena, largo, quitarInicio) { + if (!cadena) { + return '' + } + if (!largo) { + largo = 10 + } + if (cadena.length <= largo) { + return cadena + } + if (quitarInicio) { + return '...' + cadena.substring(cadena.length - largo) + } else { + return cadena.substring(0, largo) + '...' + } + }; + return funcionFiltro; + } + + function rellenarVacios() { + var funcionFiltro = function (cadena) { + try { + if (!cadena || cadena === undefined || cadena.trim() === "") { + return '---'; + }; + } catch (err) { + return '---'; + } + return cadena; + } + return funcionFiltro; + } + + function granImporte() { + var funcionFiltro = function (movimientos, valorCorte) { + if (valorCorte) { + var filtrados = []; + for (var i = 0; i < movimientos.length; i++) { + var mov = movimientos[i]; + if (mov.importe >= valorCorte) { + filtrados.push(mov); + } + } + return filtrados; + } else { + return movimientos; + } + }; + return funcionFiltro; + } + + angular.module('abFiltros', []) + .filter('abLimpiarCadena', limpiarCadena) + .filter('abRecortar', recortar) + .filter('abRellenarVacios', rellenarVacios) + .filter('abGranImporte', granImporte); +}()); diff --git a/21-organizacion/client/index.html b/21-organizacion/client/index.html new file mode 100644 index 0000000..9e98dc5 --- /dev/null +++ b/21-organizacion/client/index.html @@ -0,0 +1,58 @@ + + + + Control de Caja + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/21-organizacion/client/lista.html b/21-organizacion/client/lista.html new file mode 100644 index 0000000..b79a6b7 --- /dev/null +++ b/21-organizacion/client/lista.html @@ -0,0 +1,28 @@ +
+ Estos son tus movimientos recientes. +
+ + + + + + + + + + + + + + + + + + +
IdFecha + TipoCategoríaImporte + Valoración
+
diff --git a/21-organizacion/client/maestrosFactory.js b/21-organizacion/client/maestrosFactory.js new file mode 100644 index 0000000..2ba429d --- /dev/null +++ b/21-organizacion/client/maestrosFactory.js @@ -0,0 +1,7 @@ +(function () { + var maestrosFactory =   function ($resource)  { + return $resource("/api/pub/maestros/",{},{get: {cache: true}}); + }; + + angular.module('controlCajaApp').factory('maestrosFactory', maestrosFactory); +}()); diff --git a/21-organizacion/client/menuCtrl.js b/21-organizacion/client/menuCtrl.js new file mode 100644 index 0000000..14ddc17 --- /dev/null +++ b/21-organizacion/client/menuCtrl.js @@ -0,0 +1,8 @@ +(function () { + var menuCtrl = function ($state) { + this.isActive = function (estado) { + return $state.is(estado); + } + } + angular.module('controlCajaApp').controller('MenuCtrl',menuCtrl); +}()); diff --git a/21-organizacion/client/movimiento.html b/21-organizacion/client/movimiento.html new file mode 100644 index 0000000..dec2ee1 --- /dev/null +++ b/21-organizacion/client/movimiento.html @@ -0,0 +1,55 @@ +
+
+
+
+ Ver el movimiento {{ vm.movimiento.id}} +
+
+
+
+ +
+
+ {{ vm.movimiento.tipo}} +
+
+
+
+ +
+ {{ vm.movimiento.categoria }} +
+
+
+
+
+ +
+ {{ vm.movimiento.fecha || date }} +
+
+
+ +
+ {{ vm.movimiento.importe }} +
+
+
+
+
+ +
+ {{ vm.movimiento.texto | abRellenarVacios | abRecortar | abLimpiarCadena }} +
+
+
+ +
+ +
+
+
+
+
+
+
diff --git a/21-organizacion/client/movimientoCtrl.js b/21-organizacion/client/movimientoCtrl.js new file mode 100644 index 0000000..0d966da --- /dev/null +++ b/21-organizacion/client/movimientoCtrl.js @@ -0,0 +1,8 @@ +(function () { + var movimientoCtrl = function ($stateParams, movimientosFactory) { + var vm = this; + var movId = $stateParams.id; + vm.movimiento = movimientosFactory.movimientos.get({id:movId}); + } + angular.module('controlCajaApp').controller('MovimientoCtrl', movimientoCtrl); +}()); diff --git a/21-organizacion/client/movimientosFactory.js b/21-organizacion/client/movimientosFactory.js new file mode 100644 index 0000000..995a40e --- /dev/null +++ b/21-organizacion/client/movimientosFactory.js @@ -0,0 +1,15 @@ +(function () { + + var movimientosFactory =   function ($resource)  { + + var factory  =   {}; + factory.movimientos =  $resource("/api/priv/movimientos/:id", { + id: "@id" + }) + factory.total = $resource("/api/priv/total/"); + + return factory; + }; + + angular.module('controlCajaApp').factory('movimientosFactory', movimientosFactory); +}()); diff --git a/21-organizacion/client/not-found.html b/21-organizacion/client/not-found.html new file mode 100644 index 0000000..85210c2 --- /dev/null +++ b/21-organizacion/client/not-found.html @@ -0,0 +1,3 @@ +
+

No se ha encontrado lo que buscabas

+
diff --git a/21-organizacion/client/nuevo.html b/21-organizacion/client/nuevo.html new file mode 100644 index 0000000..2b3df9a --- /dev/null +++ b/21-organizacion/client/nuevo.html @@ -0,0 +1,68 @@ +
+ Introduce tus movimientos. +
+
+
+
+
+ +
+
+ + +
+
+
+
+ +
+ + +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
diff --git a/21-organizacion/client/registro.html b/21-organizacion/client/registro.html new file mode 100644 index 0000000..6d32793 --- /dev/null +++ b/21-organizacion/client/registro.html @@ -0,0 +1,37 @@ +
+
+
+
+
+
+ Login +
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+
+
diff --git a/21-organizacion/client/registroCtrl.js b/21-organizacion/client/registroCtrl.js new file mode 100644 index 0000000..7344f28 --- /dev/null +++ b/21-organizacion/client/registroCtrl.js @@ -0,0 +1,26 @@ +(function () { + var registroCtrl = function ($rootScope, $state, $http, $cookieStore) { + var urlBase = "http://localhost:3000/api/"; + var vm = this; + vm.usuario = {}; + vm.entrar = function () { + $http.post(urlBase + 'sesiones/', vm.usuario) + .success(function (data) { + $rootScope.nombre = vm.usuario.email; + $rootScope.mensaje = 'recién entrado'; + $cookieStore.put("sessionId", data); + $state.go("total"); + }); + } + vm.registrar = function () { + $http.post(urlBase + 'usuarios/', vm.usuario) + .success(function (data) { + $rootScope.nombre = vm.usuario.email; + $rootScope.mensaje = 'recién creado'; + $cookieStore.put("sessionId", data); + $state.go("total"); + }); + } + } + angular.module('controlCajaApp').controller('RegistroCtrl', registroCtrl); +}()); diff --git a/21-organizacion/client/total.html b/21-organizacion/client/total.html new file mode 100644 index 0000000..3f8af9e --- /dev/null +++ b/21-organizacion/client/total.html @@ -0,0 +1,34 @@ +
+ Comprueba de dónde viene y a dónde va tu dinero. +
+
+
+

+ + {{ caja.total.ingresos | number:2 }} € + +

+

Total ingresos

+ Acumulado +
+
+

+ + {{ caja.total.gastos | number:2 }} € + +

+

Total gastos

+ Acumulado +
+
+

+ + {{ caja.balance() | number:2 }} € + +

+

Balance

+ Ingresos-Gastos +
+
+
+
diff --git a/21-organizacion/client/tpl-cabecera.html b/21-organizacion/client/tpl-cabecera.html new file mode 100644 index 0000000..5245a7b --- /dev/null +++ b/21-organizacion/client/tpl-cabecera.html @@ -0,0 +1,6 @@ +
+
+

Cash Flow

+
+
+
diff --git a/21-organizacion/client/tpl-fila-movimiento.html b/21-organizacion/client/tpl-fila-movimiento.html new file mode 100644 index 0000000..2c3f4c0 --- /dev/null +++ b/21-organizacion/client/tpl-fila-movimiento.html @@ -0,0 +1,10 @@ + + {{movimientoplantilla.id }} + {{movimientoplantilla.fecha | date}} + {{movimientoplantilla.tipo}} + {{movimientoplantilla.categoria}} + + {{movimientoplantilla.importe | number:2}} € + + + diff --git a/21-organizacion/client/tpl-valoracion.html b/21-organizacion/client/tpl-valoracion.html new file mode 100644 index 0000000..63a8bcd --- /dev/null +++ b/21-organizacion/client/tpl-valoracion.html @@ -0,0 +1,12 @@ + + + + * + + + diff --git a/21-organizacion/client/valoracion.js b/21-organizacion/client/valoracion.js new file mode 100644 index 0000000..d055fd8 --- /dev/null +++ b/21-organizacion/client/valoracion.js @@ -0,0 +1,38 @@ +(function () { + var valoracion = function () { + return { + restrict: 'AE', + templateUrl: '/tpl-valoracion.html', + scope: { + valor: '=', + max: '@', + soloLectura: '@' + }, + link: function (scope, elem, attrs) { + function actualizar() { + if(!scope.valor)scope.valor=1; + scope.estrellas = []; + for (var i = 0; i < scope.max; i++) { + var estrella = { + marcada: (i < scope.valor) + }; + scope.estrellas.push(estrella); + } + }; + + scope.marcar = function (indice) { + if (scope.soloLectura && scope.soloLectura === 'true') { + return; + } + scope.valor = indice + 1; + actualizar(); + }; + actualizar(); + } + } + } + + angular.module('abDirectivas') + .directive('abValoracion', valoracion); + +}()); diff --git a/21-organizacion/package.json b/21-organizacion/package.json new file mode 100644 index 0000000..6028a5d --- /dev/null +++ b/21-organizacion/package.json @@ -0,0 +1,15 @@ +{ + "name": "ControlCashFlow", + "version": "0.0.0", + "description": "Controla tu flujo de caja", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Alberto Basalo", + "license": "BSD-2-Clause", + "dependencies": { + "express": "~4.12.1", + "body-parser": "~1.0.2" + } +} diff --git a/21-organizacion/recursos.md b/21-organizacion/recursos.md new file mode 100644 index 0000000..678453c --- /dev/null +++ b/21-organizacion/recursos.md @@ -0,0 +1,7 @@ +https://angular-ui.github.io/ +http://ngmodules.org/c +http://angular-js.in/ +http://www.directiv.es/ + +http://www.tamas.io/node-jsexpress-cors-implementation/ +http://stackoverflow.com/questions/23823010/how-to-enable-cors-in-angularjs \ No newline at end of file diff --git a/21-organizacion/server.js b/21-organizacion/server.js new file mode 100644 index 0000000..8d3fc97 --- /dev/null +++ b/21-organizacion/server.js @@ -0,0 +1,187 @@ +var express = require('express'); +var bodyParser = require('body-parser'); + +var app = express(); +app.use(bodyParser()); +app.use(express.static(__dirname + '/client')); + +app.use(function (peticion, respuesta, siguiente) { + console.log("recibida petición: " + peticion.url); + if (peticion.body && Object.keys(peticion.body).length>0) { + console.log("body: " + JSON.stringify(peticion.body)); + } + siguiente(); + }); + +console.log('ready'); + +var maxId = 0; +var movimientos = []; +var totales = []; + +var usuarios = []; +var sesiones = []; + + +app.use('/api/priv/', function (req, res, next) { + var sessionId = req.get('sessionId'); + var sesion = getSesion(sessionId); + if (sesion) { + if (esSesionValida(sesion)) { + sesion.timeStamp = new Date(); + req.usuario = sesion.email; + next(); + } else { + console.log('Sesión caducada:' + JSON.stringify(sesion)); + res.status(419).send('Sesión caducada'); + } + } else { + res.status(401).send('Credencial inválida'); + } + +}); + + + + + +// API - REST +// SECURITY +app.route('/api/usuarios') + .get(function (req, res, next) { + // Esto devuelve la lista completa de usuarios y contraseñas + // PELIGRO: Usar sólo a modo de debug mientras desarrollamos + res.json(usuarios); + }) + .post(function (req, res, next) { + var usuario = req.body; + if (existeUsuario(usuario)) { + console.log('email ya registrado:' + usuario.email); + res.status(409).send('email ' + usuario.email + ' ya registrado'); + } else { + console.log('registrado:' + usuario.email); + usuarios.push(usuario); + res.json(newSession(usuario.email)); + } + }); + +// Gestión de sesiones: listado y login +app.route('/api/sesiones') + .get(function (req, res, next) { + // Esto devuelve la lista completa de sesiones + // PELIGRO: Usar sólo a modo de debug mientras desarrollamos + res.json(sesiones); + }) + .post(function (req, res, next) { + var usuario = req.body; + if (esUsuarioValido(usuario)) { + console.log('aceptado:' + usuario.email); + res.json(newSession(usuario.email)); + } else { + console.log('Credencial inválida:' + usuario.email); + res.status(401).send('Credencial inválida'); + } + }); + +function existeUsuario(usuario) { + return usuarios.some(function (u) { + return u.email == usuario.email; + }); +} + +function esUsuarioValido(usuario) { + return usuarios.filter(function (u) { + return u.email == usuario.email && u.password == usuario.password; + })[0]; +} + +function getSesion(sessionId) { + return sesiones.filter(function (s) { + return s.sessionId == sessionId; + })[0] +} + +function esSesionValida(sesion) { + return (new Date() - sesion.timeStamp) < 20 * 60 * 1000; +} + +function newSession(email) { + var sessionId = Math.random() * (88888) + 11111; + var timeStamp = new Date(); + sesiones.push({ + sessionId: sessionId, + email: email, + timeStamp: timeStamp + }); + return sessionId; +} + + +// BUSINESS +app.get('/api/pub/maestros', function (req, res, next) { + var maestros = { + categoriasIngresos: ['Nómina', 'Ventas', 'Intereses Depósitos'], + categoriasGastos: ['Hipotéca', 'Compras', 'Impuestos'] + }; + res.json(maestros); +}); + +app.route('/api/priv/movimientos') + .get(function (req, res, next) { + var movimientosUsuario = movimientos.filter(function (m) { + return m.usuario = req.usuario; + }); + res.json(movimientosUsuario); + }) + .post(function (req, res, next) { + var movimiento = req.body; + movimiento.usuario = req.usuario; + maxId++; + movimiento.id = maxId; + movimientos.push(movimiento); + var totalUsuario = getTotalUsuario(req.usuario); + if (movimiento.tipo == 'Ingreso') + totalUsuario.ingresos += movimiento.importe; + else + totalUsuario.gastos += movimiento.importe; + res.status(200).send(); + }); + +app.get('/api/priv/movimientos/:id', function (req, res, next) { + var movId = req.params.id; + movimientoBuscado = getMovimientoById(movId, req.usuario); + res.json(movimientoBuscado); +}); + +function getMovimientoById(id,usuario) { + var movimientoBuscado = movimientos.filter(function (movimiento) { + return movimiento.id == id && movimiento.usuario == usuario; + })[0]; + return movimientoBuscado; +} + +app.get('/api/priv/total', function (req, res, next) { + var totalUsuario = getTotalUsuario(req.usuario); + res.json(totalUsuario); +}); + +function getTotalUsuario(usuario) { + if(usuario===undefined) return {}; + var totalUsuario = totales.filter(function (t) { + return t.usuario == usuario; + })[0]; + if (totalUsuario===undefined) { + totalUsuario = { + usuario : usuario, + ingresos: 0, + gastos: 0 + }; + totales.push(totalUsuario); + } + return totalUsuario; +} + + +console.log('steady'); +app.listen(3000); +console.log('go'); diff --git a/21b-organizacion/client/_comun/app.js b/21b-organizacion/client/_comun/app.js new file mode 100644 index 0000000..453b61e --- /dev/null +++ b/21b-organizacion/client/_comun/app.js @@ -0,0 +1,34 @@ +angular.module('controlCajaApp', ['ui.router','ngCookies', 'ngResource', 'abFiltros','abDirectivas']); + +angular.module('controlCajaApp').config(function ($stateProvider,$locationProvider) { + $stateProvider + .state('total', { + url: '/', + controller: 'CajaCtrl as caja', + templateUrl: 'controlcaja/total.html' + }) + .state('nuevo', { + url: '/nuevo', + controller: 'CajaCtrl as caja', + templateUrl: 'controlcaja/nuevo.html' + }) + .state('lista', { + url: '/lista', + controller: 'CajaCtrl as caja', + templateUrl: 'controlcaja/lista.html' + }) + .state('movimiento', { + url: '/movimiento/:id', + controller: 'MovimientoCtrl as vm', + templateUrl: 'movimiento/movimiento.html' + }) + .state('registro', { + url: '/registro', + controller: 'RegistroCtrl as registro', + templateUrl: 'registro/registro.html' + }) + .state('not-found', { + url: '*path', + templateUrl: '_comun/not-found.html' + }); +}); diff --git a/21b-organizacion/client/_comun/appHttpLog.js b/21b-organizacion/client/_comun/appHttpLog.js new file mode 100644 index 0000000..0357f95 --- /dev/null +++ b/21b-organizacion/client/_comun/appHttpLog.js @@ -0,0 +1,21 @@ +(function () { + + function configuradorInterceptores($httpProvider) { + $httpProvider.interceptors.push(funcionInterceptoraLog); + } + + function funcionInterceptoraLog($log) { + + var interceptor = {}; + interceptor.request = function (request) { + $log.info('request:' + request.url); + return request ; + }; + interceptor.responseError = function (response) { + $log.error("excepción: " + response.status + " de :" + response.config.url); + } + return interceptor; + } + + angular.module('controlCajaApp').config(configuradorInterceptores); +}()); diff --git a/21b-organizacion/client/_comun/appSecurity.js b/21b-organizacion/client/_comun/appSecurity.js new file mode 100644 index 0000000..5948427 --- /dev/null +++ b/21b-organizacion/client/_comun/appSecurity.js @@ -0,0 +1,29 @@ +(function () { + function configuradorInterceptores($httpProvider) { + $httpProvider.interceptors.push(funcionInterceptoraSeguridad); + } + + function funcionInterceptoraSeguridad($q, $injector, $cookieStore, $rootScope) { + + var interceptor = {}; + interceptor.request = function (request) { + request.headers["sessionId"] = $cookieStore.get("sessionId"); + return request || $q.when(request); + }; + interceptor.responseError = function (response) { + var state = $injector.get('$state'); + if (response.status === 401) { + $rootScope.mensaje = "No hay derecho!!!"; + state.go('registro'); + } else if (response.status === 419) { + $rootScope.mensaje = "Estoy caduco!!!"; + $cookieStore.remove("sessionId") + state.go('registro'); + }; + return $q.reject(response); + } + return interceptor; + } + + angular.module('controlCajaApp').config(configuradorInterceptores); +}()); diff --git a/21b-organizacion/client/_comun/menuCtrl.js b/21b-organizacion/client/_comun/menuCtrl.js new file mode 100644 index 0000000..14ddc17 --- /dev/null +++ b/21b-organizacion/client/_comun/menuCtrl.js @@ -0,0 +1,8 @@ +(function () { + var menuCtrl = function ($state) { + this.isActive = function (estado) { + return $state.is(estado); + } + } + angular.module('controlCajaApp').controller('MenuCtrl',menuCtrl); +}()); diff --git a/21b-organizacion/client/_comun/not-found.html b/21b-organizacion/client/_comun/not-found.html new file mode 100644 index 0000000..85210c2 --- /dev/null +++ b/21b-organizacion/client/_comun/not-found.html @@ -0,0 +1,3 @@ +
+

No se ha encontrado lo que buscabas

+
diff --git a/21b-organizacion/client/controlcaja/cajaCtrl.js b/21b-organizacion/client/controlcaja/cajaCtrl.js new file mode 100644 index 0000000..60ab90e --- /dev/null +++ b/21b-organizacion/client/controlcaja/cajaCtrl.js @@ -0,0 +1,34 @@ +(function () { + var cajaCtrl = function (maestrosFactory, movimientosFactory) { + var vm = this; + + vm.titulo = "Controla tu Cash Flow"; + + vm.maestros = maestrosFactory.get(); + + vm.nuevoMovimiento = new movimientosFactory.movimientos(); + vm.nuevoMovimiento.esIngreso = 1; + vm.nuevoMovimiento.fecha = new Date(); + + vm.movimientos = movimientosFactory.movimientos.query(); + + vm.total = movimientosFactory.total.get(); + + vm.guardarMovimiento = function () { + vm.nuevoMovimiento.tipo = vm.tipo(vm.nuevoMovimiento); + vm.nuevoMovimiento.$save() + .then(function (postedData) { + vm.movimientos = movimientosFactory.movimientos.query(); + vm.total = movimientosFactory.total.get(); + vm.nuevoMovimiento.importe = 0; + }); + } + vm.balance = function () { + return vm.total.ingresos - vm.total.gastos + } + vm.tipo = function (movimiento) { + return movimiento.esIngreso && 'Ingreso' || 'Gasto' + } + } + angular.module('controlCajaApp').controller('CajaCtrl', cajaCtrl); +}()); diff --git a/21b-organizacion/client/controlcaja/lista.html b/21b-organizacion/client/controlcaja/lista.html new file mode 100644 index 0000000..b79a6b7 --- /dev/null +++ b/21b-organizacion/client/controlcaja/lista.html @@ -0,0 +1,28 @@ +
+ Estos son tus movimientos recientes. +
+ + + + + + + + + + + + + + + + + + +
IdFecha + TipoCategoríaImporte + Valoración
+
diff --git a/21b-organizacion/client/controlcaja/nuevo.html b/21b-organizacion/client/controlcaja/nuevo.html new file mode 100644 index 0000000..2b3df9a --- /dev/null +++ b/21b-organizacion/client/controlcaja/nuevo.html @@ -0,0 +1,68 @@ +
+ Introduce tus movimientos. +
+
+
+
+
+ +
+
+ + +
+
+
+
+ +
+ + +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
diff --git a/21b-organizacion/client/controlcaja/total.html b/21b-organizacion/client/controlcaja/total.html new file mode 100644 index 0000000..3f8af9e --- /dev/null +++ b/21b-organizacion/client/controlcaja/total.html @@ -0,0 +1,34 @@ +
+ Comprueba de dónde viene y a dónde va tu dinero. +
+
+
+

+ + {{ caja.total.ingresos | number:2 }} € + +

+

Total ingresos

+ Acumulado +
+
+

+ + {{ caja.total.gastos | number:2 }} € + +

+

Total gastos

+ Acumulado +
+
+

+ + {{ caja.balance() | number:2 }} € + +

+

Balance

+ Ingresos-Gastos +
+
+
+
diff --git a/21b-organizacion/client/data/maestrosFactory.js b/21b-organizacion/client/data/maestrosFactory.js new file mode 100644 index 0000000..2ba429d --- /dev/null +++ b/21b-organizacion/client/data/maestrosFactory.js @@ -0,0 +1,7 @@ +(function () { + var maestrosFactory =   function ($resource)  { + return $resource("/api/pub/maestros/",{},{get: {cache: true}}); + }; + + angular.module('controlCajaApp').factory('maestrosFactory', maestrosFactory); +}()); diff --git a/21b-organizacion/client/data/movimientosFactory.js b/21b-organizacion/client/data/movimientosFactory.js new file mode 100644 index 0000000..995a40e --- /dev/null +++ b/21b-organizacion/client/data/movimientosFactory.js @@ -0,0 +1,15 @@ +(function () { + + var movimientosFactory =   function ($resource)  { + + var factory  =   {}; + factory.movimientos =  $resource("/api/priv/movimientos/:id", { + id: "@id" + }) + factory.total = $resource("/api/priv/total/"); + + return factory; + }; + + angular.module('controlCajaApp').factory('movimientosFactory', movimientosFactory); +}()); diff --git a/21b-organizacion/client/directivas/directivas.js b/21b-organizacion/client/directivas/directivas.js new file mode 100644 index 0000000..42c3c01 --- /dev/null +++ b/21b-organizacion/client/directivas/directivas.js @@ -0,0 +1,74 @@ +(function () { + function piePagina() { + return { + restrict: 'AE', + replace: 'true', + template: '' + }; + }; + + function cabecera() { + return { + restrict: 'AE', + replace: 'true', + transclude: true, + templateUrl: '/directivas/tpl-cabecera.html' + }; + }; + + function filaMovimiento() { + return { + restrict: 'A', + templateUrl: '/directivas/tpl-fila-movimiento.html', + scope: { + movimientoplantilla: "=movimientodirectiva" + } + }; + } + + function seleccionado() { + return { + link: function ($scope, element, attrs) { + element.bind('mouseenter', function () { + element.css('background-color', 'lightyellow'); + }); + element.bind('mouseleave', function () { + element.css('background-color', 'white'); + }); + } + }; + } + + function plugin() { + return { + link: function (scope, element, attrs) { + var init = scope.$eval(attrs.ngModel); + var min = scope.$eval(attrs.abSliderMin); + var max = scope.$eval(attrs.abSliderMax); + $(element).plugin({ + value: init, + min: min, + max: max, + tooltip: 'hide' + }); + + scope.$watch(attrs.ngModel, function (valor) { + $(element).plugin('setValue', valor); + }); + + $(element).plugin().on('slide', function (evento) { + scope.$apply(function () { + scope[attrs.ngModel] = evento.value; + }); + }); + } + } + } + + angular.module('abDirectivas', []) + .directive('abPiePagina', piePagina) + .directive('abCabecera', cabecera) + .directive('abFilaMovimiento', filaMovimiento) + .directive('abSeleccionado', seleccionado) + .directive('abPlugin', plugin); +}()); diff --git a/21b-organizacion/client/directivas/tpl-cabecera.html b/21b-organizacion/client/directivas/tpl-cabecera.html new file mode 100644 index 0000000..5245a7b --- /dev/null +++ b/21b-organizacion/client/directivas/tpl-cabecera.html @@ -0,0 +1,6 @@ +
+
+

Cash Flow

+
+
+
diff --git a/21b-organizacion/client/directivas/tpl-fila-movimiento.html b/21b-organizacion/client/directivas/tpl-fila-movimiento.html new file mode 100644 index 0000000..2c3f4c0 --- /dev/null +++ b/21b-organizacion/client/directivas/tpl-fila-movimiento.html @@ -0,0 +1,10 @@ + + {{movimientoplantilla.id }} + {{movimientoplantilla.fecha | date}} + {{movimientoplantilla.tipo}} + {{movimientoplantilla.categoria}} + + {{movimientoplantilla.importe | number:2}} € + + + diff --git a/21b-organizacion/client/directivas/valoracion/tpl-valoracion.html b/21b-organizacion/client/directivas/valoracion/tpl-valoracion.html new file mode 100644 index 0000000..63a8bcd --- /dev/null +++ b/21b-organizacion/client/directivas/valoracion/tpl-valoracion.html @@ -0,0 +1,12 @@ + + + + * + + + diff --git a/21b-organizacion/client/directivas/valoracion/valoracion.js b/21b-organizacion/client/directivas/valoracion/valoracion.js new file mode 100644 index 0000000..d4ad49d --- /dev/null +++ b/21b-organizacion/client/directivas/valoracion/valoracion.js @@ -0,0 +1,38 @@ +(function () { + var valoracion = function () { + return { + restrict: 'AE', + templateUrl: '/directivas/valoracion/tpl-valoracion.html', + scope: { + valor: '=', + max: '@', + soloLectura: '@' + }, + link: function (scope, elem, attrs) { + function actualizar() { + if(!scope.valor)scope.valor=1; + scope.estrellas = []; + for (var i = 0; i < scope.max; i++) { + var estrella = { + marcada: (i < scope.valor) + }; + scope.estrellas.push(estrella); + } + }; + + scope.marcar = function (indice) { + if (scope.soloLectura && scope.soloLectura === 'true') { + return; + } + scope.valor = indice + 1; + actualizar(); + }; + actualizar(); + } + } + } + + angular.module('abDirectivas') + .directive('abValoracion', valoracion); + +}()); diff --git a/21b-organizacion/client/filtros/filtros.js b/21b-organizacion/client/filtros/filtros.js new file mode 100644 index 0000000..6398464 --- /dev/null +++ b/21b-organizacion/client/filtros/filtros.js @@ -0,0 +1,71 @@ +(function () { + + function limpiarCadena() { + var funcionFiltro = function (cadena) { + if (cadena) { + var resultado = cadena.toLowerCase(); + var patron = /[^-A-Za-z0-9]+/g; + return resultado.replace(patron, '_'); + } + }; + return funcionFiltro; + } + + function recortar() { + var funcionFiltro = function (cadena, largo, quitarInicio) { + if (!cadena) { + return '' + } + if (!largo) { + largo = 10 + } + if (cadena.length <= largo) { + return cadena + } + if (quitarInicio) { + return '...' + cadena.substring(cadena.length - largo) + } else { + return cadena.substring(0, largo) + '...' + } + }; + return funcionFiltro; + } + + function rellenarVacios() { + var funcionFiltro = function (cadena) { + try { + if (!cadena || cadena === undefined || cadena.trim() === "") { + return '---'; + }; + } catch (err) { + return '---'; + } + return cadena; + } + return funcionFiltro; + } + + function granImporte() { + var funcionFiltro = function (movimientos, valorCorte) { + if (valorCorte) { + var filtrados = []; + for (var i = 0; i < movimientos.length; i++) { + var mov = movimientos[i]; + if (mov.importe >= valorCorte) { + filtrados.push(mov); + } + } + return filtrados; + } else { + return movimientos; + } + }; + return funcionFiltro; + } + + angular.module('abFiltros', []) + .filter('abLimpiarCadena', limpiarCadena) + .filter('abRecortar', recortar) + .filter('abRellenarVacios', rellenarVacios) + .filter('abGranImporte', granImporte); +}()); diff --git a/21b-organizacion/client/index.html b/21b-organizacion/client/index.html new file mode 100644 index 0000000..0947174 --- /dev/null +++ b/21b-organizacion/client/index.html @@ -0,0 +1,58 @@ + + + + Control de Caja + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/21b-organizacion/client/movimiento/movimiento.html b/21b-organizacion/client/movimiento/movimiento.html new file mode 100644 index 0000000..dec2ee1 --- /dev/null +++ b/21b-organizacion/client/movimiento/movimiento.html @@ -0,0 +1,55 @@ +
+
+
+
+ Ver el movimiento {{ vm.movimiento.id}} +
+
+
+
+ +
+
+ {{ vm.movimiento.tipo}} +
+
+
+
+ +
+ {{ vm.movimiento.categoria }} +
+
+
+
+
+ +
+ {{ vm.movimiento.fecha || date }} +
+
+
+ +
+ {{ vm.movimiento.importe }} +
+
+
+
+
+ +
+ {{ vm.movimiento.texto | abRellenarVacios | abRecortar | abLimpiarCadena }} +
+
+
+ +
+ +
+
+
+
+
+
+
diff --git a/21b-organizacion/client/movimiento/movimientoCtrl.js b/21b-organizacion/client/movimiento/movimientoCtrl.js new file mode 100644 index 0000000..0d966da --- /dev/null +++ b/21b-organizacion/client/movimiento/movimientoCtrl.js @@ -0,0 +1,8 @@ +(function () { + var movimientoCtrl = function ($stateParams, movimientosFactory) { + var vm = this; + var movId = $stateParams.id; + vm.movimiento = movimientosFactory.movimientos.get({id:movId}); + } + angular.module('controlCajaApp').controller('MovimientoCtrl', movimientoCtrl); +}()); diff --git a/21b-organizacion/client/registro/registro.html b/21b-organizacion/client/registro/registro.html new file mode 100644 index 0000000..a224951 --- /dev/null +++ b/21b-organizacion/client/registro/registro.html @@ -0,0 +1,37 @@ +
+
+
+
+
+
+ Login +
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+
+
diff --git a/21b-organizacion/client/registro/registroCtrl.js b/21b-organizacion/client/registro/registroCtrl.js new file mode 100644 index 0000000..7344f28 --- /dev/null +++ b/21b-organizacion/client/registro/registroCtrl.js @@ -0,0 +1,26 @@ +(function () { + var registroCtrl = function ($rootScope, $state, $http, $cookieStore) { + var urlBase = "http://localhost:3000/api/"; + var vm = this; + vm.usuario = {}; + vm.entrar = function () { + $http.post(urlBase + 'sesiones/', vm.usuario) + .success(function (data) { + $rootScope.nombre = vm.usuario.email; + $rootScope.mensaje = 'recién entrado'; + $cookieStore.put("sessionId", data); + $state.go("total"); + }); + } + vm.registrar = function () { + $http.post(urlBase + 'usuarios/', vm.usuario) + .success(function (data) { + $rootScope.nombre = vm.usuario.email; + $rootScope.mensaje = 'recién creado'; + $cookieStore.put("sessionId", data); + $state.go("total"); + }); + } + } + angular.module('controlCajaApp').controller('RegistroCtrl', registroCtrl); +}()); diff --git a/21b-organizacion/package.json b/21b-organizacion/package.json new file mode 100644 index 0000000..6028a5d --- /dev/null +++ b/21b-organizacion/package.json @@ -0,0 +1,15 @@ +{ + "name": "ControlCashFlow", + "version": "0.0.0", + "description": "Controla tu flujo de caja", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Alberto Basalo", + "license": "BSD-2-Clause", + "dependencies": { + "express": "~4.12.1", + "body-parser": "~1.0.2" + } +} diff --git a/21b-organizacion/server.js b/21b-organizacion/server.js new file mode 100644 index 0000000..73725a9 --- /dev/null +++ b/21b-organizacion/server.js @@ -0,0 +1,10 @@ +var app = require('./server/config.js').configApp(); +require('./server/seguridad.js').seguridad(app); +console.log('ready'); + +require('./server/maestros.js').routeMaestros(app); +require('./server/movimientos.js').routeMovimientos(app); +console.log('steady'); + +app.listen(3000); +console.log('go'); diff --git a/21b-organizacion/server/config.js b/21b-organizacion/server/config.js new file mode 100644 index 0000000..4f70de7 --- /dev/null +++ b/21b-organizacion/server/config.js @@ -0,0 +1,21 @@ +module.exports.configApp = function () { + + var express = require('express'); + var bodyParser = require('body-parser'); + + var app = express(); + + app.use(bodyParser()); + app.use(express.static(__dirname + './../client')); + + app.use(function (peticion, respuesta, siguiente) { + console.log("recibida petición: " + peticion.url); + if (peticion.body && Object.keys(peticion.body).length > 0) { + console.log("body: " + JSON.stringify(peticion.body)); + } + siguiente(); + }); + + return app; + +} diff --git a/21b-organizacion/server/maestros.js b/21b-organizacion/server/maestros.js new file mode 100644 index 0000000..9560cb1 --- /dev/null +++ b/21b-organizacion/server/maestros.js @@ -0,0 +1,9 @@ +module.exports.routeMaestros = function (app) { + app.get('/api/pub/maestros', function (req, res, next) { + var maestros = { + categoriasIngresos: ['Nómina', 'Ventas', 'Intereses Depósitos'], + categoriasGastos: ['Hipotéca', 'Compras', 'Impuestos'] + }; + res.json(maestros); + }); +} diff --git a/21b-organizacion/server/movimientos.js b/21b-organizacion/server/movimientos.js new file mode 100644 index 0000000..c49af25 --- /dev/null +++ b/21b-organizacion/server/movimientos.js @@ -0,0 +1,63 @@ +var maxId = 0; +var movimientos = []; +var totales = []; + +module.exports.routeMovimientos = function (app) { + + + app.route('/api/priv/movimientos') + .get(function (req, res, next) { + var movimientosUsuario = movimientos.filter(function (m) { + return m.usuario = req.usuario; + }); + res.json(movimientosUsuario); + }) + .post(function (req, res, next) { + var movimiento = req.body; + movimiento.usuario = req.usuario; + maxId++; + movimiento.id = maxId; + movimientos.push(movimiento); + var totalUsuario = getTotalUsuario(req.usuario); + if (movimiento.tipo == 'Ingreso') + totalUsuario.ingresos += movimiento.importe; + else + totalUsuario.gastos += movimiento.importe; + res.status(200).send(); + }); + + app.get('/api/priv/movimientos/:id', function (req, res, next) { + var movId = req.params.id; + movimientoBuscado = getMovimientoById(movId, req.usuario); + res.json(movimientoBuscado); + }); + + function getMovimientoById(id, usuario) { + var movimientoBuscado = movimientos.filter(function (movimiento) { + return movimiento.id == id && movimiento.usuario == usuario; + })[0]; + return movimientoBuscado; + } + + app.get('/api/priv/total', function (req, res, next) { + var totalUsuario = getTotalUsuario(req.usuario); + res.json(totalUsuario); + }); + + function getTotalUsuario(usuario) { + if (usuario === undefined) return {}; + var totalUsuario = totales.filter(function (t) { + return t.usuario == usuario; + })[0]; + if (totalUsuario === undefined) { + totalUsuario = { + usuario: usuario, + ingresos: 0, + gastos: 0 + }; + totales.push(totalUsuario); + } + return totalUsuario; + } + +} diff --git a/21b-organizacion/server/seguridad.js b/21b-organizacion/server/seguridad.js new file mode 100644 index 0000000..3c2e4f9 --- /dev/null +++ b/21b-organizacion/server/seguridad.js @@ -0,0 +1,94 @@ +var usuarios = []; +var sesiones = []; + +module.exports.seguridad = function (app) { + + app.use('/api/priv/', function (req, res, next) { + var sessionId = req.get('sessionId'); + var sesion = getSesion(sessionId); + if (sesion) { + if (esSesionValida(sesion)) { + sesion.timeStamp = new Date(); + req.usuario = sesion.email; + next(); + } else { + console.log('Sesión caducada:' + JSON.stringify(sesion)); + res.status(419).send('Sesión caducada'); + } + } else { + res.status(401).send('Credencial inválida'); + } + }); + // API - REST + // SECURITY + app.route('/api/usuarios') + .get(function (req, res, next) { + // Esto devuelve la lista completa de usuarios y contraseñas + // PELIGRO: Usar sólo a modo de debug mientras desarrollamos + res.json(usuarios); + }) + .post(function (req, res, next) { + var usuario = req.body; + if (existeUsuario(usuario)) { + console.log('email ya registrado:' + usuario.email); + res.status(409).send('email ' + usuario.email + ' ya registrado'); + } else { + console.log('registrado:' + usuario.email); + usuarios.push(usuario); + res.json(newSession(usuario.email)); + } + }); + + // Gestión de sesiones: listado y login + app.route('/api/sesiones') + .get(function (req, res, next) { + // Esto devuelve la lista completa de sesiones + // PELIGRO: Usar sólo a modo de debug mientras desarrollamos + res.json(sesiones); + }) + .post(function (req, res, next) { + var usuario = req.body; + if (esUsuarioValido(usuario)) { + console.log('aceptado:' + usuario.email); + res.json(newSession(usuario.email)); + } else { + console.log('Credencial inválida:' + usuario.email); + res.status(401).send('Credencial inválida'); + } + }); + + function existeUsuario(usuario) { + return usuarios.some(function (u) { + return u.email == usuario.email; + }); + } + + function esUsuarioValido(usuario) { + return usuarios.filter(function (u) { + return u.email == usuario.email && u.password == usuario.password; + })[0]; + } + + function getSesion(sessionId) { + return sesiones.filter(function (s) { + return s.sessionId == sessionId; + })[0] + } + + function esSesionValida(sesion) { + return (new Date() - sesion.timeStamp) < 20 * 60 * 1000; + } + + function newSession(email) { + var sessionId = Math.random() * (88888) + 11111; + var timeStamp = new Date(); + sesiones.push({ + sessionId: sessionId, + email: email, + timeStamp: timeStamp + }); + return sessionId; + } + + +}