Rubén Cid Lara
"Es una aplicación que muestra una previsión diaria de la presencia de medusas en las playas de Ibiza"
Que nos muestre la información en tiempo real del estado de las playas de Ibiza.
Las banderas que existen en ese mismo instante, mediante una colaboración ciudadana, que se podría extender a la colaboración con la Cruz Roja de Ibiza.
La temperatura que hay en la isla.
La afluencia de personas.
Además de una pequeña red social donde se pueden subir fotos y compartirlas en las distintas redes sociales como Instagram, Twitter, Facebook…
Fijos: 50€
Anuales: 97.87€
52.41.44.230
Es una aplicación que está disponible para IOS, que nos proporciona:
¿De qué se compone?
Text
¿Posibles soluciones?
¿Con qué tecnologías se ha creado?
El V8 compila el JavaScript a código máquina nativo.
[
{
"_id": ObjectId("4efa8d2b7d284dad101e4bc7"),
"Last Name": "PELLERIN",
"First Name": "Franck",
"Age": 29,
"Address": {
"Street": "1 chemin des Loges",
"City": "VERSAILLES"
}
},
{
"_id": ObjectId("4efa8d2b7d284dad101e4sd3"),
"Last Name": "CID",
"First Name": "RUBEN",
"Age": 19,
"Address": {
"Street": "1 chemin des Loges",
"City": "IBIZA"
}
}
]Github
Youtube
map.component.html
Estructura de los componentes
Text
main.ts
import {bootstrap} from '@angular/platform-browser-dynamic';
import {AppComponent} from './app/app.component';
import {HTTP_PROVIDERS} from "@angular/http";
import {FORM_PROVIDERS} from '@angular/common';
import {WebSocket} from './app/services/websocket.service';
import {TRANSLATE_PROVIDERS} from 'ng2-translate/ng2-translate';
import { ROUTER_PROVIDERS } from '@angular/router-deprecated';
import {RestApi} from "./app/services/rest.service";
// Modo produccion
import {enableProdMode} from "@angular/core";
enableProdMode();
//Lanzamiento
bootstrap(
AppComponent, [
HTTP_PROVIDERS,
FORM_PROVIDERS,
ROUTER_PROVIDERS,
WebSocket,
RestApi,
TRANSLATE_PROVIDERS
]
).catch(err => console.error(err));app.component.ts
import {Component, OnInit} from '@angular/core';
import {RouteConfig, ROUTER_DIRECTIVES} from '@angular/router-deprecated';
import {TranslateService, TranslatePipe} from 'ng2-translate/ng2-translate';
import {MapBoxComponent} from './map';
import {HomeComponent} from './home';
@Component({
selector: 'ibiza-jelly',
template: require('./app.component.html'),
directives: [ROUTER_DIRECTIVES],
pipes: [TranslatePipe]
})
// Sistema de enrutamiento
@RouteConfig([
{
path: '/',
name: 'Home',
component: HomeComponent,
useAsDefault: true
},
{
path: '/map',
name: 'Map',
component: MapBoxComponent
}
])
export class AppComponent {
constructor(translate:TranslateService) {
var userLang = navigator.language.split('-')[0]; // Idioma del navegador
userLang = /(es|en)/gi.test(userLang) ? userLang : 'es';
// Idioma por defecto
translate.setDefaultLang('es');
// Usamos el idioma del navegaor
translate.use(userLang);
}
}
app.component.html
renderiza Home
home.component.ts
import {Component, ElementRef, OnInit} from '@angular/core';
import {HeaderMainComponent} from './header';
import {ContentComponent} from './content';
import {FooterComponent} from './footer';
import {Router} from '@angular/router-deprecated';
import {tokenNotExpired} from "../shared/token.service";
@Component({
selector: 'ibiza-jelly',
template: require('./home.component.html'),
directives: [HeaderMainComponent, ContentComponent, FooterComponent],
styles: [require('./home.component.scss')]
})
export class HomeComponent implements OnInit {
public token: string;
notification: boolean = false;
msgNoti: any;
typeForm: string = 'signup';
constructor(private router: Router, private ele: ElementRef) {}
notify(e) {
this.notification = true;
this.msgNoti = e;
let pro = new Promise((resolve) => {
setTimeout(()=> {
let ele = this.ele.nativeElement.querySelector('.notify');
if (!!ele) {
ele.classList.remove('bounceInLeft');
}
resolve(null);
}, 5000);
});
pro.then(()=> {
this.notification = false;
});
}
ngOnInit() {
if (tokenNotExpired('token')) {
this.router.navigate(['Map']);
}
}
changeFormType(e) {
console.log(e + ' evento');
this.typeForm = e;
}
}
home.component.html
<ijelly-header (typeFormChanged)="changeFormType($event)" [typeForm]="typeForm"></ijelly-header>
<ijelly-content></ijelly-content>
<div class="notify animated bounceInLeft" [ngClass]="{error: msgNoti.error}" *ngIf="notification">
<span class="notify-text">{{msgNoti.msg}}</span>
</div>
<ijelly-footer (typeFormChanged)="changeType($event)"
[typeForm]="typeForm"
(notification)="notify($event)"></ijelly-footer>
header.component.ts
import {Component, Input, Output, EventEmitter} from '@angular/core';
import {ROUTER_DIRECTIVES} from '@angular/router-deprecated';
import {TranslateService, TranslatePipe} from 'ng2-translate/ng2-translate';
@Component({
selector: 'ijelly-header',
template: require('./header.component.html'),
styles: [require('./header.component.scss')],
directives: [ROUTER_DIRECTIVES],
pipes: [TranslatePipe]
})
export class HeaderMainComponent {
@Output() typeFormChanged = new EventEmitter();
@Input() typeForm:string;
constructor(private translate: TranslateService) {
}
setTypeForm(type) {
console.log(type);
this.typeForm = type;
this.typeFormChanged.emit(this.typeForm);
}
}
header.component.html
<header>
<nav class="align-justify align-middle row">
<div class="align-justify align-middle row">
<ul>
<li class="show-for-large"><a [routerLink]="['Home']">Home</a></li>
<li class="show-for-large"><a data-scroll data-options='{ "easing": "easeInQuad" }' href="#form-login-register">{{'MAP' | translate}}</a></li>
</ul>
<ul>
<li class="show-for-large"><a (click)="setTypeForm('signin')">{{'LOGIN' | translate}}</a></li>
<li class="show-for-large"><a (click)="setTypeForm('signup')">{{'SIGNUP' | translate}}</a></li>
<li class="hide-for-large"><a href="javascript:void(0)">
<i class="fa fa-bars fa-2x" aria-hidden="true"></i>
</a></li>
</ul>
</div>
</nav>
</header>
footer.component.ts
import {Component, OnInit, Output, Input, EventEmitter} from '@angular/core';
import {ControlGroup, FormBuilder, Validators, Control} from '@angular/common';
import {CustomValidators} from '../../shared/email-validator';
import {RestApi} from '../../services/rest.service';
import {SignUpUser} from '../../models/rest.models';
import {TranslateService, TranslatePipe} from 'ng2-translate/ng2-translate';
import {ROUTER_DIRECTIVES, Router} from '@angular/router-deprecated';
import {LocalStorage} from "../../services/localstorage.service";
@Component({
selector: 'ijelly-footer',
template: require('./footer.component.html'),
styles: [require('./footer.component.scss')],
providers: [RestApi],
directives: [ROUTER_DIRECTIVES],
pipes: [TranslatePipe]
})
export class FooterComponent extends LocalStorage implements OnInit {
public registerForm:ControlGroup;
public loginForm:ControlGroup;
public username:Control;
public email:Control;
public password:Control;
public emailLogin:Control;
public notification;
@Output() notification = new EventEmitter();
@Output() typeFormChanged = new EventEmitter();
@Input() typeForm:string;
constructor(private _formBuilder:FormBuilder,
private _api:RestApi,
private translate:TranslateService,
private _router:Router) {
super();
}
createUser(user:SignUpUser) {
this._api.createUser(user).subscribe(() => {
this.notification.emit({msg: 'El usuario se ha creado correctamente', error: false});
// this.changeType('signin');
}, () => {
this.notification.emit({msg: 'No se ha podido crear el usuario', error: true});
});
}
loginUser(user) {
this._api.loginUser(user).subscribe(
(infoUser) => {
if (infoUser && infoUser.json()) {
this.setId(infoUser.json().id);
this._api.getJWToken().subscribe(
(token) => {
token = token.json().token;
this._api.setToken(token);
this._router.navigate(['Map']);
}
);
return;
}
this.notification.emit({msg: "Email o contraseña inválida.", error: true});
},
(error) => {
this.notification.emit({msg: error.json().error, error: true});
}
);
}
ngOnInit():any {
this.username = new Control('', Validators.compose([Validators.required]),
CustomValidators.checkUsername
);
this.email = new Control('', Validators.compose([Validators.required,
CustomValidators.emailValidator]),
CustomValidators.checkEmail
);
this.password = new Control('', Validators.compose([Validators.required,
Validators.minLength(8)]));
this.emailLogin = new Control('', Validators.compose([Validators.required,
CustomValidators.emailValidator]),
CustomValidators.checkEmailLogin);
this.registerForm = this._formBuilder.group({
'username': this.username,
'email': this.email,
'password': this.password
});
this.loginForm = this._formBuilder.group({
'email': this.emailLogin,
'password': this.password
});
}
changeType(type) {
this.typeForm = type;
this.typeFormChanged.emit(type);
}
}
footer.component.html
<footer>
<div class="main" [ngSwitch]='typeForm' id="form-login-register">
<!--swicth-->
<div class="center" *ngSwitchWhen="'signup'">
<h2 >{{'BEGINJOY' | translate}}</h2>
<h3 class="text-center">{{'BEGINJOYTEXT' | translate}}</h3>
<form action="" novalidate [ngFormModel]="registerForm" (ngSubmit)="createUser(registerForm.value)">
<label>{{'USERNAME' | translate}}:
<input
[class.error]="username.errors && username.dirty && !username.valid && !username.pending"
[class.correct]="!username.errors && username.dirty && username.valid && !username.pending"
type="text" name="username" ngControl="username">
<div *ngIf="username.dirty && !username.valid && !username.pending">
<small class="error" *ngIf="username.errors && username.errors.usernameTaken">*{{ "EXISTS" | translate}}</small>
</div>
<div *ngIf="username.dirty && username.valid && !username.pending">
<small class="correct" *ngIf="!username.errors">Ok</small>
</div>
</label>
<label>Email:
<input
[class.error]="email.errors && email.dirty && !email.valid && !email.pending"
[class.correct]="!email.errors && email.dirty && email.valid && !email.pending"
type="email" name="email" ngControl="email">
<div *ngIf="email.dirty && !email.valid && !email.pending">
<small class="error" *ngIf="email.errors && email.errors.emailTaken">*{{ "EXISTS" | translate}}</small>
</div>
<div *ngIf="email.dirty && email.valid && !email.pending">
<small class="correct" *ngIf="!email.errors">Ok</small>
</div>
</label>
<label>{{'PASSWORD' | translate}}:
<input type="password"
[class.error]="password.errors && password.dirty && !password.valid && !password.pending"
[class.correct]="!password.errors && password.dirty && password.valid && !password.pending"
name="password" ngControl="password" >
<div *ngIf="password.dirty && !password.valid && !password.pending">
<small class="error" *ngIf="password.errors">*{{'MINIMUM8' | translate}}</small>
</div>
<div *ngIf="password.dirty && password.valid && !password.pending">
<small class="correct" *ngIf="!password.errors">Ok</small>
</div>
</label>
<button class="button primary" type="submit" [disabled]="!registerForm.valid">{{'CREATEACCOUNT' | translate}}</button>
</form>
<a (click)="typeForm = 'signin'">{{'LOGIN' | translate}}</a>
</div>
<div class="center" *ngSwitchWhen="'signin'">
<h2 >{{'BEGINJOY' | translate}}</h2>
<h3 class="text-center">{{'BEGINJOYTEXT' | translate}}</h3>
<form action="" novalidate [ngFormModel]="loginForm" (ngSubmit)="loginUser(loginForm.value)">
<label>Email:
<input
[class.error]="emailLogin.errors && emailLogin.dirty && !emailLogin.valid && !emailLogin.pending"
[class.correct]="!emailLogin.errors && emailLogin.dirty && emailLogin.valid && !emailLogin.pending"
type="email" name="email" ngControl="email">
<div *ngIf="emailLogin.dirty && !emailLogin.valid && !emailLogin.pending">
<small class="error" *ngIf="emailLogin.errors">*{{ "NOEXISTS" | translate}}</small>
</div>
<div *ngIf="emailLogin.dirty && emailLogin.valid && !emailLogin.pending">
<small class="correct" *ngIf="!emailLogin.errors">Ok</small>
</div>
</label>
<label>{{'PASSWORD' | translate}}:
<input type="password"
[class.error]="password.errors && password.dirty && !password.valid && !password.pending"
[class.correct]="!password.errors && password.dirty && password.valid && !password.pending"
name="password" ngControl="password" >
<div *ngIf="password.dirty && !password.valid && !password.pending">
<small class="error" *ngIf="password.errors">*{{'MINIMUM8' | translate}}</small>
</div>
<div *ngIf="password.dirty && password.valid && !password.pending">
<small class="correct" *ngIf="!password.errors">Ok</small>
</div>
</label>
<button class="button primary" type="submit" [disabled]="!loginForm.valid">{{'LOGIN' | translate}}</button>
</form>
<a (click)="typeForm = 'signup'">{{'SIGNUP' | translate}}</a>
</div>
</div>
</footer>
Validadores
import {Http, HTTP_PROVIDERS} from '@angular/http';
import {ReflectiveInjector} from '@angular/core';
import {Control} from '@angular/common';
function checkUser(control: Control, source: string, register: boolean = true) {
// Inyeccion manual Http
let injector = ReflectiveInjector.resolveAndCreate([HTTP_PROVIDERS]);
let http = injector.get(Http);
let body = JSON.stringify({[source]: control.value});
return new Promise(resolve => {
http.post('/user/check', body)
.subscribe(value => {
let msg = value.json().message;
let rt = null;
if (!register) {
rt = {[msg]: true};
}
resolve(rt);
}, error => {
let msg = error.json().message;
let rt = null;
if (register) {
rt = {[msg]: true};
}
resolve(rt);
});
});
}
export class CustomValidators {
static emailValidator(control: Control) {
let email = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
if (!control.value.match(email)) {
return {'invalidEmailAddress': true};
}
}
static checkUsername(control: Control) {
return checkUser(control, 'username');
}
static checkEmail(control: Control) {
return checkUser(control, 'email');
}
static checkEmailLogin(control: Control) {
return checkUser(control, 'email', false);
}
}
Check bakend
check: function (req, res) {
var find;
var email = false;
if(!!req.param('username')){
find = {username: req.param('username')};
}else if(!!req.param('email')){
find = {email: req.param('email')};
email = true;
}
if(!!find){
User.findOne(find).exec((err, user) => {
if(err) return res.negotiate(err);
if(!!user && !email){
// nombre de usuario ya esta en uso
res.status(400).json({'message': 'usernameTaken'});
return;
}else if(!!user && !!email){
// mail ya esta en uso
res.status(400).json({'message': 'emailTaken'});
return;
}
// no esta cogido
res.json({'message': null});
return;
});
return;
}
res.json({'message': null});
return;
}<!DOCTYPE html>
<html>
<head>
<base href="/">
<title><%=typeof title == 'undefined' ? 'IbizaJelly' : title%></title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<!--STYLES-->
<link rel="stylesheet" href="/styles/font-awesome.min.css">
<link rel="stylesheet" href="/styles/importer.css">
<link rel="stylesheet" href="/styles/normalize.css">
<!--STYLES END-->
</head>
<body>
<ibiza-jelly></ibiza-jelly>
<!--SCRIPTS-->
<script src="/js/dependencies/sails.io.js"></script>
<script src="/js/dependencies/modernizr.custom.js"></script>
<script src="/js/dependencies/pathLoader.js"></script>
<script src="/js/dependencies/smooth-scroll.min.js"></script>
<script src="/js/dependencies/upup.min.js"></script>
<script src="/js/polyfills.js"></script>
<script src="/js/vendor.js"></script>
<script src="/js/app.js"></script>
<script src="/js/classie.js"></script>
<script src="/js/main.js"></script>
<!--SCRIPTS END-->
<script>
smoothScroll.init({
speed: 500, // Integer. How fast to complete the scroll in milliseconds
easing: 'easeInOutCubic', // Easing pattern to use
updateURL: false // Boolean. If true, update the URL hash on scroll
});
</script>
</body>
</html>
EC2
PaaS
Las características que nos ofrece el servicio gratuito de Amazon son:
750 horas de uso de EC2 con una instancia t2.micro de Linux, RHEL, o SLES
Se añaden 15 GB de ancho de banda saliente en todos los servicios de AWS
1 GB de transferencia de datos regionales.
Docker.
Deploy