...
 
Commits (15)
......@@ -15,3 +15,6 @@ WebContent
# Testing
/servers
C:\\nppdf32Log\\debuglog.txt
# Angular
src/main/angular
......@@ -5,11 +5,21 @@ Aplicación y arquitectura de ejemplo para la asignatura Desarrollo Ágil de
Aplicaciones del Grado en Ingeniería Informática de la Escuela Superior de
Ingeniería Informática de la Universidad de Vigo.
## Dependencias
Este proyecto está diseñado para ser desarrollado en un entorno con:
* Maven 3
* Java 8
* MySQL 5.7.6+ o 8+
Además, se recomienda emplear la última versión de Eclipse IDE for Enterprise
Java Developers.
## Ejecución con Maven
La configuración de Maven ha sido preparada para permitir varios tipos de
ejecución.
### Ejecución de la aplicación con Tomcat y MySQL
### Ejecución
El proyecto está configurado para poder ejecutar la aplicación sin tener que
realizar ninguna configuración adicional salvo tener disponible un servidor
......@@ -26,24 +36,43 @@ comandos (desde la raíz el proyecto):
Una vez configurada la base de datos podemos lanzar la ejecución con el comando:
`mvn -Prun-tomcat-mysql -DskipTests=true package cargo:run`
`mvn -Prun clean package cargo:run`
La aplicación se servirá en la URL local: http://localhost:9080/DAAExample
Para detener la ejecución podemos utilizar `Ctrl+C`.
### Ejecución de la aplicación con Tomcat y MySQL con redespliegue automático
### Ejecución con redespliegue automático
Durante el desarrollo es interesante que la apliación se redespliegue de forma
automática cada vez que se hace un cambio. Para ello podemos utilizar el
siguiente comand:
`mvn -Prun-tomcat-mysql -DskipTests=true package cargo:start fizzed-watcher:run`
`mvn -Prun clean package cargo:start fizzed-watcher:run`
La aplicación se servirá en la URL local: http://localhost:9080/DAAExample
Para detener la ejecución podemos utilizar `Ctrl+C`.
### Ejecución con redespliegue automático independiente para backend y frontend (recomendado)
A diferencia del modo de redespliegue automático anterior, en este caso
*backend* y *frontend* se ejecutarán de forma independiente. Esto hace que los
cambios en las clases Java harán que se redespliegue el *backend*, mientras que
los cambios en Angular harán que se redespliegue el *frontend*.
`mvn -Prun-independent-autoredeploy clean exec:exec@npm-build exec:exec@npm-start package cargo:start fizzed-watcher:run`
La aplicación se servirá en local:
* **Backend**: http://localhost:9080/DAAExample
* **Frontend**: http://localhost:4200
Para detener la ejecución podemos utilizar `Ctrl+C`.
*Nota*: si accedes a la URL del backend también verás la aplicación de frontend,
pero no se redesplegará automáticamente con los cambios.
### Construcción con tests de unidad e integración
En esta construcción se ejecutarán todos los tests relacionados con el backend:
......@@ -57,16 +86,3 @@ memoria.
El comando para lanzar esta construcción es:
`mvn install`
### Construcción con tests de unidad, integración y aceptación
Esta construcción es similar a la previa, añadiendo las **pruebas de
aceptación**, que comprueban que las fucionalidades de la aplicación están
correctamente implementadas.
En estas pruebas se descarga y arranca el un servidor Tomcat 8 en el que se
despliega la aplicación configurada para utilizar una base de datos HSQL. Las
pruebas se hacen sobre la interfaz web con Selenium, que iniciará un Firefox
local de forma automática.
`mvn -Pacceptance-tests-cargo install`
DROP DATABASE IF EXISTS `daaexample`;
CREATE DATABASE `daaexample`;
CREATE TABLE `daaexample`.`people` (
......@@ -5,16 +6,17 @@ CREATE TABLE `daaexample`.`people` (
`name` varchar(50) NOT NULL,
`surname` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `daaexample`.`users` (
`login` varchar(100) NOT NULL,
`password` varchar(64) NOT NULL,
`role` varchar(10) NOT NULL,
PRIMARY KEY (`login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
GRANT ALL ON `daaexample`.* TO 'daa'@'localhost' IDENTIFIED BY 'daa';
CREATE USER IF NOT EXISTS 'daa'@'localhost' IDENTIFIED WITH mysql_native_password BY 'daa';
GRANT ALL ON `daaexample`.* TO 'daa'@'localhost';
INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Antón','Pérez');
INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Manuel','Martínez');
......
DROP DATABASE IF EXISTS `daaexample`;
CREATE DATABASE `daaexample`;
CREATE TABLE `daaexample`.`people` (
......@@ -5,13 +6,14 @@ CREATE TABLE `daaexample`.`people` (
`name` varchar(50) NOT NULL,
`surname` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `daaexample`.`users` (
`login` varchar(100) NOT NULL,
`password` varchar(64) NOT NULL,
`role` varchar(10) NOT NULL,
PRIMARY KEY (`login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
GRANT ALL ON `daaexample`.* TO 'daa'@'localhost' IDENTIFIED BY 'daa';
CREATE USER IF NOT EXISTS 'daa'@'localhost' IDENTIFIED WITH mysql_native_password BY 'daa';
GRANT ALL ON `daaexample`.* TO 'daa'@'localhost';
This diff is collapsed.
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# dependencies
/node_modules
# profiling files
chrome-profiler-events.json
speed-measure-plugin.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
# Angular
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.1.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angular": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"aot": true,
"outputPath": "dist/angular",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss",
"node_modules/bootstrap/dist/css/bootstrap.css"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "angular:build"
},
"configurations": {
"production": {
"browserTarget": "angular:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "angular:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"src/styles.scss",
"node_modules/bootstrap/dist/css/bootstrap.css"
],
"scripts": [],
"assets": [
"src/favicon.ico",
"src/assets"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"angular-e2e": {
"root": "e2e/",
"projectType": "application",
"prefix": "",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "angular:serve"
},
"configurations": {
"production": {
"devServerTarget": "angular:serve:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "angular"
}
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.e2e.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};
\ No newline at end of file
import { AppPage } from './app.po';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('Welcome to angular!');
});
});
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getTitleText() {
return element(by.css('app-root h1')).getText();
}
}
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}
\ No newline at end of file
This diff is collapsed.
{
"name": "daa-example",
"version": "0.2.0-alpha.14",
"scripts": {
"ng": "./node_modules/.bin/ng",
"start": "./node_modules/.bin/ng serve",
"build": "./node_modules/.bin/ng build --base-href .",
"test": "./node_modules/.bin/ng test",
"lint": "./node_modules/.bin/ng lint",
"e2e": "./node_modules/.bin/ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~11.2.1",
"@angular/cdk": "^11.2.1",
"@angular/common": "~11.2.1",
"@angular/compiler": "~11.2.1",
"@angular/core": "~11.2.1",
"@angular/forms": "~11.2.1",
"@angular/platform-browser": "~11.2.1",
"@angular/platform-browser-dynamic": "~11.2.1",
"@angular/router": "~11.2.1",
"angular2-notifications": "^9.0.0",
"bootstrap": "^4.4.1",
"core-js": "^2.5.4",
"jquery": "^3.5.1",
"popper.js": "^1.16.1",
"rxjs": "~6.6.3",
"tslib": "^2.0.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1102.1",
"@angular/cli": "~11.2.1",
"@angular/compiler-cli": "~11.2.1",
"@angular/language-service": "~11.2.1",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.1.1",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",
"ts-node": "~7.0.0",
"tslint": "~6.1.0",
"typescript": "~4.0.7"
}
}
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {UnauthenticatedGuard} from './guards/unauthenticated.guard';
import {AuthenticatedGuard} from './guards/authenticated.guard';
import {LoginPanelComponent} from './components/login-panel/login-panel.component';
import {MainPanelComponent} from './components/main-panel/main-panel.component';
const routes: Routes = [
{
path: 'welcome',
pathMatch: 'full',
component: LoginPanelComponent,
canActivate: [UnauthenticatedGuard]
},
{
path: '',
component: MainPanelComponent,
canActivate: [AuthenticatedGuard],
children: [
{
path: '',
redirectTo: 'people',
pathMatch: 'full'
},
{
path: 'people',
loadChildren: () => import('./modules/people/people.module').then(m => m.PeopleModule)
}
]
}
];
@NgModule({
imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })],
exports: [RouterModule]
})
export class AppRoutingModule { }
<!--
~ DAA Example
~
~ Copyright (C) 2019 - Miguel Reboiro-Jato.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<router-outlet></router-outlet>
/*!
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'angular'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('angular');
});
it('should render title in a h1 tag', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to angular!');
});
});
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'angular';
}
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {LoginPanelComponent} from './components/login-panel/login-panel.component';
import {MainPanelComponent} from './components/main-panel/main-panel.component';
import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import {FormsModule} from '@angular/forms';
import {CommonModule, HashLocationStrategy, LocationStrategy} from '@angular/common';
import {AuthenticationInterceptor} from './interceptors/authentication.interceptor';
@NgModule({
declarations: [
AppComponent,
LoginPanelComponent,
MainPanelComponent
],
imports: [
AppRoutingModule,
BrowserModule,
CommonModule,
FormsModule,
HttpClientModule
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthenticationInterceptor,
multi: true
},
{
provide: LocationStrategy,
useClass: HashLocationStrategy
}
],
bootstrap: [AppComponent]
})
export class AppModule {
}
<!--
~ DAA Example
~
~ Copyright (C) 2019 - Miguel Reboiro-Jato.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<div id="container">
<div class="form-singin">
<h1 class="h1 mb-3 font-weight-normal">DAA Example - Angular</h1>
<label for="login" class="sr-only">Usuario</label>
<input id="login" name="login" type="text" class="form-control" placeholder="Usuario" required autofocus
[(ngModel)]="login"/>
<label for="password" class="sr-only">Contraseña</label>
<input id="password" name="password" type="password" class="form-control" placeholder="Contraseña" required
[(ngModel)]="password"/>
<button class="btn btn-lg btn-primary btn-block mt-3" (click)="onLogin()">Entrar</button>
</div>
</div>
/*!
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#container {
height: 100%;
width: 100%;
display: -ms-flexbox;
display: -webkit-box;
display: flex;
-ms-flex-align: center;
-ms-flex-pack: center;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
padding-top: 40px;
padding-bottom: 40px;
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
#login {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
#password {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { LoginPanelComponent } from './login-panel.component';
describe('LoginPanelComponent', () => {
let component: LoginPanelComponent;
let fixture: ComponentFixture<LoginPanelComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ LoginPanelComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginPanelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {Component} from '@angular/core';
import {Router} from '@angular/router';
import {AuthenticationService} from '../../services/authentication.service';
import {UserModel} from '../../models';
@Component({
selector: 'app-login-panel',
templateUrl: './login-panel.component.html',
styleUrls: ['./login-panel.component.scss']
})
export class LoginPanelComponent {
public login: string;
public password: string;
public constructor(
private readonly authenticationService: AuthenticationService,
private readonly router: Router
) {
this.login = '';
this.password = '';
}
public onLogin(): void {
const credentials: UserModel = {
login: this.login,
password: this.password
};
this.authenticationService.tryLogin(credentials)
.subscribe(() => this.router.navigate(['/']));
}
}
<!--
~ DAA Example
~
~ Copyright (C) 2019 - Miguel Reboiro-Jato.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<header>
<div class="navbar navbar-dark bg-dark box-shadow">
<div class="container d-flex justify-content-between">
<a href="#" class="navbar-brand d-flex align-items-center">
<strong>DAA Example</strong>
</a>
<button class="btn btn-dark" role="button" (click)="onLogout()">Cerrar sesión</button>
</div>
</div>
</header>
<div class="container">
<router-outlet></router-outlet>
</div>
/*!
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MainPanelComponent } from './main-panel.component';
describe('MainPanelComponent', () => {
let component: MainPanelComponent;
let fixture: ComponentFixture<MainPanelComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ MainPanelComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MainPanelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {Component} from '@angular/core';
import {Observable} from 'rxjs';
import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout';
import {map} from 'rxjs/operators';
import {Router} from '@angular/router';
import {AuthenticationService} from '../../services/authentication.service';
@Component({
selector: 'app-main-panel',
templateUrl: './main-panel.component.html',
styleUrls: ['./main-panel.component.scss']
})
export class MainPanelComponent {
public readonly isHandset$: Observable<boolean>;
public constructor(
private readonly authenticationService: AuthenticationService,
private readonly breakpointObserver: BreakpointObserver,
private readonly router: Router
) {
this.isHandset$ = this.breakpointObserver.observe(Breakpoints.Handset)
.pipe(
map(result => result.matches)
);
}
public onLogout(): void {
this.authenticationService.logout();
this.router.navigate(['welcome']);
}
}
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {inject, TestBed} from '@angular/core/testing';
import {AuthenticatedGuard} from './authenticated.guard';
describe('AuthenticatedGuard', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AuthenticatedGuard]
});
});
it('should ...', inject([AuthenticatedGuard], (guard: AuthenticatedGuard) => {
expect(guard).toBeTruthy();
}));
});
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
import {Observable} from 'rxjs';
import {map, tap} from 'rxjs/operators';
import {AuthenticationService} from '../services/authentication.service';
@Injectable({
providedIn: 'root'
})
export class AuthenticatedGuard implements CanActivate {
public constructor(
private readonly authenticationService: AuthenticationService,
private readonly router: Router
) {}
public canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean | Observable<boolean> {
if (this.authenticationService.isLogged()) {
return true;
} else {
return this.authenticationService.tryLoginWithStoredCredentials()
.pipe(
map(user => user !== null),
tap(isLogged => {
if (!isLogged) {
this.router.navigate(['welcome']);
}
})
);
}
}
}
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {inject, TestBed} from '@angular/core/testing';
import {UnauthenticatedGuard} from './unauthenticated.guard';
describe('UnauthenticatedGuard', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [UnauthenticatedGuard]
});
});
it('should ...', inject([UnauthenticatedGuard], (guard: UnauthenticatedGuard) => {
expect(guard).toBeTruthy();
}));
});
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
import {map, tap} from 'rxjs/operators';
import {Observable} from 'rxjs';
import {AuthenticationService} from '../services/authentication.service';
@Injectable({
providedIn: 'root'
})
export class UnauthenticatedGuard implements CanActivate {
public constructor(
private readonly authenticationService: AuthenticationService,
private readonly router: Router
) {}
public canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean | Observable<boolean> {
if (this.authenticationService.isLogged()) {
this.router.navigate([]);
return false;
} else {
return this.authenticationService.tryLoginWithStoredCredentials()
.pipe(
map(user => user === null),
tap(isNotLogged => {
if (!isNotLogged) {
this.router.navigate([]);
}
})
);
}
}
}
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {Injectable} from '@angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Observable} from 'rxjs';
import {AuthenticationService} from '../services/authentication.service';
@Injectable({
providedIn: 'root'
})
export class AuthenticationInterceptor implements HttpInterceptor {
public constructor(public authenticationService: AuthenticationService) {
}
public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const user = this.authenticationService.loggedUser;
if (user !== null) {
request = request.clone({
setHeaders: {
Authorization: 'Basic ' + btoa(user.login + ':' + user.password)
}
});
}
return next.handle(request);
}
}
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export * from './user.model';
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export interface UserModel {
readonly login: string;
readonly password: string;
}
<!--
~ DAA Example
~
~ Copyright (C) 2019 - Miguel Reboiro-Jato.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<form id="people-form" class="mb-5 mb-10">
<input name="id" type="hidden" value=""/>
<div class="row">
<div class="col-sm-4">
<input name="name" type="text" value="" placeholder="Nombre" class="form-control" required [(ngModel)]="name"/>
</div>
<div class="col-sm-5">
<input name="surname" type="text" value="" placeholder="Apellido" class="form-control" required
[(ngModel)]="surname"/>
</div>
<div class="col-sm-3">
<button id="btnSubmit" class="btn btn-primary"
(click)="onModify()">{{person.id === undefined ? 'Crear' : 'Editar'}}</button>
<button id="btnClear" class="btn" (click)="onClean()">Limpiar</button>
</div>
</div>
</form>
/*!
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { PeopleFormComponent } from './people-form.component';
describe('PeopleFormComponent', () => {
let component: PeopleFormComponent;
let fixture: ComponentFixture<PeopleFormComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ PeopleFormComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PeopleFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {PersonModel} from '../../models/person.model';
@Component({
selector: 'app-people-form',
templateUrl: './people-form.component.html',
styleUrls: ['./people-form.component.scss']
})
export class PeopleFormComponent {
public activePerson: PersonModel;
@Output()
public readonly modify: EventEmitter<PersonModel>;
@Output()
public readonly clean: EventEmitter<never>;
public name: string;
public surname: string;
public constructor() {
this.modify = new EventEmitter<PersonModel>();
this.clean = new EventEmitter<never>();
}
@Input()
public set person(person: PersonModel) {
this.activePerson = person;
this.name = person.name;
this.surname = person.surname;
}
public get person(): PersonModel {
return this.activePerson;
}
public onModify() {
this.modify.emit({
id: this.person.id,
name: this.name,
surname: this.surname
});
}
public onClean() {
this.clean.emit();
}
}
<!--
~ DAA Example
~
~ Copyright (C) 2019 - Miguel Reboiro-Jato.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<table id="people-list" class="table">
<thead>
<tr class="row">
<th class="col-sm-4">Nombre</th>
<th class="col-sm-5">Apellido</th>
<th class="col-sm-3">&nbsp;</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let person of people" class="row" id="person-{{person.id}}">
<td class="col-sm-4 name">{{person.name}}</td>
<td class="col-sm-5 surname">{{person.surname}}</td>
<td class="col-sm-3">
<button class="btn btn-primary edit" (click)="onEdit(person)">Edit</button>
<button class="btn btn-warning delete" (click)="onDelete(person)">Delete</button>
</td>
</tr>
</tbody>
</table>
/*!
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { PeopleListComponent } from './people-list.component';
describe('PeopleListComponent', () => {
let component: PeopleListComponent;
let fixture: ComponentFixture<PeopleListComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ PeopleListComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PeopleListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {PersonModel} from '../../models/person.model';
import {PeopleService} from '../../services/people.service';
@Component({
selector: 'app-people-list',
templateUrl: './people-list.component.html',
styleUrls: ['./people-list.component.scss']
})
export class PeopleListComponent {
@Input()
public people: PersonModel[] = [];
@Output()
public readonly edit: EventEmitter<PersonModel>;
@Output()
public readonly delete: EventEmitter<PersonModel>;
public constructor() {
this.edit = new EventEmitter<PersonModel>();
this.delete = new EventEmitter<PersonModel>();
}
public onEdit(person: PersonModel) {
this.edit.emit(person);
}
public onDelete(person: PersonModel) {
this.delete.emit(person);
}
}
<!--
~ DAA Example
~
~ Copyright (C) 2019 - Miguel Reboiro-Jato.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<h1 class="display-5 mt-3 mb-3">Personas</h1>
<app-people-form [person]="activePerson" (clean)="onCleanForm()" (modify)="onModifyForm($event)"></app-people-form>
<app-people-list [people]="people" (edit)="onEdit($event)" (delete)="onDelete($event)"></app-people-list>
/*!
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { PeopleMainComponent } from './people-main.component';
describe('PeopleMainComponent', () => {
let component: PeopleMainComponent;
let fixture: ComponentFixture<PeopleMainComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ PeopleMainComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PeopleMainComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, OnInit } from '@angular/core';
import {PersonModel} from '../../models/person.model';
import {PeopleService} from '../../services/people.service';
import {map, mergeMap} from 'rxjs/operators';
@Component({
selector: 'app-people-main',
templateUrl: './people-main.component.html',
styleUrls: ['./people-main.component.scss']
})
export class PeopleMainComponent implements OnInit {
public activePerson: PersonModel;
public people: PersonModel[];
public constructor(
private readonly peopleService: PeopleService
) {
this.people = [];
this.clearActivePerson();
}
public ngOnInit(): void {
this.peopleService.list()
.subscribe(people => this.people = people);
}
public onEdit(person: PersonModel): void {
this.activePerson = person;
}
public onDelete(person: PersonModel): void {
if (confirm(`¿Estás seguro de que deseas eliminar a ${person.name} ${person.surname}?`)) {
this.peopleService.delete(person)
.pipe(
mergeMap(() => this.peopleService.list())
)
.subscribe(people => this.people = people);
}
}
public onCleanForm(): void {
this.clearActivePerson();
}
public onModifyForm(person: PersonModel): void {
if (person.id === undefined) {
this.peopleService.create(person)
.pipe(
mergeMap(() => this.peopleService.list())
)
.subscribe(people => {
this.people = people;
this.clearActivePerson();
});
} else {
this.peopleService.modify(person)
.pipe(
mergeMap(() => this.peopleService.list())
)
.subscribe(people => {
this.people = people;
this.clearActivePerson();
});
}
}
private clearActivePerson(): void {
this.activePerson = { id: undefined, name: '', surname: '' };
}
}
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export class PersonModel {
id?: number;
name: string;
surname: string;
}
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {PeopleMainComponent} from './components/people-main/people-main.component';
const routes: Routes = [
{
path: '',
component: PeopleMainComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PeopleRoutingModule { }
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {PeopleRoutingModule} from './people-routing.module';
import {PeopleListComponent} from './components/people-list/people-list.component';
import {PeopleFormComponent} from './components/people-form/people-form.component';
import {PeopleMainComponent} from './components/people-main/people-main.component';
import {FormsModule} from '@angular/forms';
@NgModule({
declarations: [
PeopleFormComponent,
PeopleListComponent,
PeopleMainComponent
],
imports: [
CommonModule,
FormsModule,
PeopleRoutingModule
]
})
export class PeopleModule {
}
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { TestBed } from '@angular/core/testing';
import { PeopleService } from './people.service';
describe('PeopleService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: PeopleService = TestBed.get(PeopleService);
expect(service).toBeTruthy();
});
});
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
import {environment} from '../../../../environments/environment';
import {Observable} from 'rxjs';
import {PersonModel} from '../models/person.model';
@Injectable({
providedIn: 'root'
})
export class PeopleService {
public constructor(private readonly http: HttpClient) { }
public list(): Observable<PersonModel[]> {
return this.http.get<PersonModel[]>(`${environment.restApi}/people`);
}
public create(person: PersonModel): Observable<PersonModel> {
const data = new HttpParams()
.set('name', person.name)
.set('surname', person.surname);
return this.http.post<PersonModel>(`${environment.restApi}/people`, data);
}
public modify(person: PersonModel): Observable<PersonModel> {
const data = new HttpParams()
.set('name', person.name)
.set('surname', person.surname);
return this.http.put<PersonModel>(`${environment.restApi}/people/${person.id}`, data);
}
public delete(person: PersonModel): Observable<number> {
return this.http.delete<number>(`${environment.restApi}/people/${person.id}`);
}
}
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { TestBed } from '@angular/core/testing';
import { AuthenticationService } from './authentication.service';
describe('AuthenticationService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: AuthenticationService = TestBed.get(AuthenticationService);
expect(service).toBeTruthy();
});
});
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {Injectable} from '@angular/core';
import {BehaviorSubject, Observable, of} from 'rxjs';
import {UserModel} from '../models';
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
import {environment} from '../../environments/environment';
import {catchError, tap} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class AuthenticationService {
private static readonly USER_STORAGE_KEY = 'user';
private readonly _loggedUser$: BehaviorSubject<UserModel | null>;
private static clearCredentials(): void {
localStorage.removeItem(AuthenticationService.USER_STORAGE_KEY);
}
private static loadCredentials(): UserModel | null {
const serializedCredentials = localStorage.getItem(AuthenticationService.USER_STORAGE_KEY);
if (serializedCredentials !== null) {
return JSON.parse(serializedCredentials);
} else {
return null;
}
}
private static storeCredentials(login: string, password: string): void {
const credentials: UserModel = {login, password};
localStorage.setItem(AuthenticationService.USER_STORAGE_KEY, JSON.stringify(credentials));
}
public constructor(
private readonly http: HttpClient
) {
this._loggedUser$ = new BehaviorSubject<UserModel | null>(null);
}
public get loggedUser$(): Observable<UserModel | null> {
return this._loggedUser$.asObservable();
}
public get loggedUser(): UserModel | null {
return this._loggedUser$.value;
}
public isLogged(): boolean {
return this.loggedUser !== null;
}
public logout(): void {
AuthenticationService.clearCredentials();
this._loggedUser$.next(null);
}
public tryLoginWithStoredCredentials(): Observable<UserModel | null> {
const credentials = AuthenticationService.loadCredentials();
if (credentials !== null) {
return this.tryLogin(credentials);
} else {
return of<UserModel | null>(null);
}
}
public tryLogin(login: UserModel): Observable<UserModel | null>;
public tryLogin(login: string, password: string): Observable<UserModel | null>;
public tryLogin(login: string | UserModel, password?: string): Observable<UserModel> {
let userLogin: string;
let userPassword: string;
if (typeof login === 'string') {
if (password === undefined) {
throw new TypeError('password can\'t be null when login is string');
}
userLogin = login;
userPassword = password;
} else {
userLogin = login.login;
userPassword = login.password;
}
AuthenticationService.storeCredentials(userLogin, userPassword);
const headers = new HttpHeaders()
.set('Authorization', 'Basic ' + btoa(userLogin + ':' + userPassword));
return this.http.get<never>(`${environment.restApi}/users/${userLogin}`, {
headers: headers
})
.pipe(
tap(user => this._loggedUser$.next({
login: userLogin,
password: userPassword
})),
catchError(error => {
this._loggedUser$.next(null);
throw error;
})
);
}
}
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export const environment = {
production: true,
restApi: 'http://daaexample.com:9080/DAAExample/rest'
};
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false,
restApi: 'http://localhost:9080/DAAExample/rest'
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
<!--
~ DAA Example
~
~ Copyright (C) 2019 - Miguel Reboiro-Jato.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DAA Example</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../coverage'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';
/**
* If the application will be indexed by Google Search, the following is required.
* Googlebot uses a renderer based on Chrome 41.
* https://developers.google.com/search/docs/guides/rendering
**/
// import 'core-js/es6/array';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
**/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
*/
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
/*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*/
// (window as any).__Zone_enable_cross_context_check = true;
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
/*!
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* You can add global styles to this file, and also import other style files */
html, body {
height: 100%;
}
/*
* DAA Example
*
* Copyright (C) 2019 - Miguel Reboiro-Jato.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"types": []
},
"files": [
"main.ts",
"polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts",
"polyfills.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}
{
"extends": "../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
]
}
}
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"downlevelIteration": true,
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"module": "es2020",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2018",
"dom"
]
}
}
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warn"
},
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"no-output-on-prefix": true,
"no-inputs-metadata-property": true,
"no-outputs-metadata-property": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-output-rename": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true
}
}
......@@ -10,6 +10,27 @@
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.headers</param-name>
<param-value>Authorization</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.methods</param-name>
<param-value>GET, POST, DELETE, PUT</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/rest/*</url-pattern>
</filter-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Protected Area</web-resource-name>
......@@ -18,7 +39,6 @@
<http-method>DELETE</http-method>
<http-method>GET</http-method>
<http-method>POST</http-method>
<http-method>OPTIONS</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
......@@ -30,11 +50,10 @@
<web-resource-collection>
<web-resource-name>Admin Area</web-resource-name>
<url-pattern>/rest/people/*</url-pattern>
<http-method>GET</http-method>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>GET</http-method>
<http-method>POST</http-method>
<http-method>OPTIONS</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
......
html, body {
height: 100%;
}
body {
display: -ms-flexbox;
display: -webkit-box;
display: flex;
-ms-flex-align: center;
-ms-flex-pack: center;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
padding-top: 40px;
padding-bottom: 40px;
}
#form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
#form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
#form-signin .form-control:focus {
z-index: 2;
}
#login {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
#password {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>DAA Example - Login</title>
<link rel="stylesheet" href="css/login.css">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
</head>
<body class="text-center">
<form id="form-singin">
<h1 class="h1 mb-3 font-weight-normal">DAA Example</h1>
<label for="login" class="sr-only">Usuario</label> <input id="login"
name="login" type="text" class="form-control" placeholder="Usuario"
required autofocus /> <label for="password" class="sr-only">Contraseña</label>
<input id="password" name="password" type="password"
class="form-control" placeholder="Contraseña" required />
<button type="submit" class="btn btn-lg btn-primary btn-block mt-3">Entrar</button>
</form>
<script type="text/javascript"
src="http://code.jquery.com/jquery-2.2.4.min.js"></script>
<script
src="https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"></script>
<script type="text/javascript" src="js/login.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#form-singin').submit(function(event) {
event.preventDefault();
var login = $('#login').val();
var password = $('#password').val();
doLogin(login, password);
});
});
</script>
</body>
</html>
\ No newline at end of file
var PeopleDAO = (function() {
var resourcePath = "rest/people/";
var requestByAjax = function(data, done, fail, always) {
done = typeof done !== 'undefined' ? done : function() {};
fail = typeof fail !== 'undefined' ? fail : function() {};
always = typeof always !== 'undefined' ? always : function() {};
let authToken = localStorage.getItem('authorization-token');
if (authToken !== null) {
data.beforeSend = function(xhr) {
xhr.setRequestHeader('Authorization', 'Basic ' + authToken);
};
}
$.ajax(data).done(done).fail(fail).always(always);
};
function PeopleDAO() {
this.listPeople = function(done, fail, always) {
requestByAjax({
url : resourcePath,
type : 'GET'
}, done, fail, always);
};
this.addPerson = function(person, done, fail, always) {
requestByAjax({
url : resourcePath,
type : 'POST',
data : person
}, done, fail, always);
};
this.modifyPerson = function(person, done, fail, always) {
requestByAjax({
url : resourcePath + person.id,
type : 'PUT',
data : person
}, done, fail, always);
};
this.deletePerson = function(id, done, fail, always) {
requestByAjax({
url : resourcePath + id,
type : 'DELETE',
}, done, fail, always);
};
}
return PeopleDAO;
})();
\ No newline at end of file
function doLogin(login, password) {
$.ajax({
url: 'rest/users/' + login,
type: 'GET',
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Basic ' + btoa(login + ":" + password));
}
})
.done(function() {
localStorage.setItem('authorization-token', btoa(login + ":" + password));
window.location = 'main.html';
})
.fail(function() {
alert('Invalid login and/or password.');
});
}
function doLogout() {
localStorage.removeItem('authorization-token');
window.location = 'index.html';
}
\ No newline at end of file
var PeopleView = (function() {
var dao;
// Referencia a this que permite acceder a las funciones públicas desde las funciones de jQuery.
var self;
var formId = 'people-form';
var listId = 'people-list';
var formQuery = '#' + formId;
var listQuery = '#' + listId;
function PeopleView(peopleDao, formContainerId, listContainerId) {
dao = peopleDao;
self = this;
insertPeopleForm($('#' + formContainerId));
insertPeopleList($('#' + listContainerId));
this.init = function() {
dao.listPeople(function(people) {
$.each(people, function(key, person) {
appendToTable(person);
});
});
// La acción por defecto de enviar formulario (submit) se sobreescribe
// para que el envío sea a través de AJAX
$(formQuery).submit(function(event) {
var person = self.getPersonInForm();
if (self.isEditing()) {
dao.modifyPerson(person,
function(person) {
$('#person-' + person.id + ' td.name').text(person.name);
$('#person-' + person.id + ' td.surname').text(person.surname);
self.resetForm();
},
showErrorMessage,
self.enableForm
);
} else {
dao.addPerson(person,
function(person) {
appendToTable(person);
self.resetForm();
},
showErrorMessage,
self.enableForm
);
}
return false;
});
$('#btnClear').click(this.resetForm);
};
this.getPersonInForm = function() {
var form = $(formQuery);
return {
'id': form.find('input[name="id"]').val(),
'name': form.find('input[name="name"]').val(),
'surname': form.find('input[name="surname"]').val()
};
};
this.getPersonInRow = function(id) {
var row = $('#person-' + id);
if (row !== undefined) {
return {
'id': id,
'name': row.find('td.name').text(),
'surname': row.find('td.surname').text()
};
} else {
return undefined;
}
};
this.editPerson = function(id) {
var row = $('#person-' + id);
if (row !== undefined) {
var form = $(formQuery);
form.find('input[name="id"]').val(id);
form.find('input[name="name"]').val(row.find('td.name').text());
form.find('input[name="surname"]').val(row.find('td.surname').text());
$('input#btnSubmit').val('Modificar');
}
};
this.deletePerson = function(id) {
if (confirm('Está a punto de eliminar a una persona. ¿Está seguro de que desea continuar?')) {
dao.deletePerson(id,
function() {
$('tr#person-' + id).remove();
},
showErrorMessage
);
}
};
this.isEditing = function() {
return $(formQuery + ' input[name="id"]').val() != "";
};
this.disableForm = function() {
$(formQuery + ' input').prop('disabled', true);
};
this.enableForm = function() {
$(formQuery + ' input').prop('disabled', false);
};
this.resetForm = function() {
$(formQuery)[0].reset();
$(formQuery + ' input[name="id"]').val('');
$('#btnSubmit').val('Crear');
};
};
var insertPeopleList = function(parent) {
parent.append(
'<table id="' + listId + '" class="table">\
<thead>\
<tr class="row">\
<th class="col-sm-4">Nombre</th>\
<th class="col-sm-5">Apellido</th>\
<th class="col-sm-3">&nbsp;</th>\
</tr>\
</thead>\
<tbody>\
</tbody>\
</table>'
);
};
var insertPeopleForm = function(parent) {
parent.append(
'<form id="' + formId + '" class="mb-5 mb-10">\
<input name="id" type="hidden" value=""/>\
<div class="row">\
<div class="col-sm-4">\
<input name="name" type="text" value="" placeholder="Nombre" class="form-control" required/>\
</div>\
<div class="col-sm-5">\
<input name="surname" type="text" value="" placeholder="Apellido" class="form-control" required/>\
</div>\
<div class="col-sm-3">\
<input id="btnSubmit" type="submit" value="Crear" class="btn btn-primary" />\
<input id="btnClear" type="reset" value="Limpiar" class="btn" />\
</div>\
</div>\
</form>'
);
};
var createPersonRow = function(person) {
return '<tr id="person-'+ person.id +'" class="row">\
<td class="name col-sm-4">' + person.name + '</td>\
<td class="surname col-sm-5">' + person.surname + '</td>\
<td class="col-sm-3">\
<a class="edit btn btn-primary" href="#">Editar</a>\
<a class="delete btn btn-warning" href="#">Eliminar</a>\
</td>\
</tr>';
};
var showErrorMessage = function(jqxhr, textStatus, error) {
alert(textStatus + ": " + error);
};
var addRowListeners = function(person) {
$('#person-' + person.id + ' a.edit').click(function() {
self.editPerson(person.id);
});
$('#person-' + person.id + ' a.delete').click(function() {
self.deletePerson(person.id);
});
};
var appendToTable = function(person) {
$(listQuery + ' > tbody:last')
.append(createPersonRow(person));
addRowListeners(person);
};
return PeopleView;
})();
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DAA Example</title>
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
</head>
<body>
<header>
<div class="navbar navbar-dark bg-dark box-shadow">
<div class="container d-flex justify-content-between">
<a href="#" class="navbar-brand d-flex align-items-center">
<strong>DAA Example</strong>
</a>
<button id="logout" class="btn btn-dark">Cerrar sesión</button>
</div>
</div>
</header>
<div class="container">
<div id="people-container">
<h1 class="display-5 mt-3 mb-3">Personas</h1>
</div>
</div>
<script type="text/javascript"
src="http://code.jquery.com/jquery-2.2.4.min.js"></script>
<script type="text/javascript" src="js/dao/people.js"></script>
<script type="text/javascript" src="js/view/people.js"></script>
<script type="text/javascript" src="js/login.js"></script>
<script type="text/javascript">
$(document).ready(
function() {
$('#logout').click(function(event) {
event.preventDefault();
doLogout();
});
var view = new PeopleView(new PeopleDAO(),
'people-container', 'people-container'
);
view.init();
});
</script>
</body>
</html>
\ No newline at end of file
package es.uvigo.esei.daa.dao;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentId;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newName;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newSurname;
import static es.uvigo.esei.daa.dataset.PeopleDataset.nonExistentId;
import static es.uvigo.esei.daa.dataset.PeopleDataset.nonExistentPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.people;
import static es.uvigo.esei.daa.dataset.PeopleDataset.peopleWithout;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.containsPeopleInAnyOrder;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.equalsToPerson;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import es.uvigo.esei.daa.entities.Person;
import es.uvigo.esei.daa.listeners.ApplicationContextBinding;
import es.uvigo.esei.daa.listeners.ApplicationContextJndiBindingTestExecutionListener;
import es.uvigo.esei.daa.listeners.DbManagement;
import es.uvigo.esei.daa.listeners.DbManagementTestExecutionListener;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:contexts/mem-context.xml")
@TestExecutionListeners({
DbUnitTestExecutionListener.class,
DbManagementTestExecutionListener.class,
ApplicationContextJndiBindingTestExecutionListener.class
})
@ApplicationContextBinding(
jndiUrl = "java:/comp/env/jdbc/daaexample",
type = DataSource.class
)
@DbManagement(
create = "classpath:db/hsqldb.sql",
drop = "classpath:db/hsqldb-drop.sql"
)
@DatabaseSetup("/datasets/dataset.xml")
@ExpectedDatabase("/datasets/dataset.xml")
public class PeopleDAOTest {
private PeopleDAO dao;
@Before
public void setUp() throws Exception {
this.dao = new PeopleDAO();
}
@Test
public void testList() throws DAOException {
assertThat(this.dao.list(), containsPeopleInAnyOrder(people()));
}
@Test
public void testGet() throws DAOException {
final Person person = this.dao.get(existentId());
assertThat(person, is(equalsToPerson(existentPerson())));
}
@Test(expected = IllegalArgumentException.class)
public void testGetNonExistentId() throws DAOException {
this.dao.get(nonExistentId());
}
@Test
@ExpectedDatabase("/datasets/dataset-delete.xml")
public void testDelete() throws DAOException {
this.dao.delete(existentId());
assertThat(this.dao.list(), containsPeopleInAnyOrder(peopleWithout(existentId())));
}
@Test(expected = IllegalArgumentException.class)
public void testDeleteNonExistentId() throws DAOException {
this.dao.delete(nonExistentId());
}
@Test
@ExpectedDatabase("/datasets/dataset-modify.xml")
public void testModify() throws DAOException {
final Person person = existentPerson();
person.setName(newName());
person.setSurname(newSurname());
this.dao.modify(person);
final Person persistentPerson = this.dao.get(person.getId());
assertThat(persistentPerson, is(equalsToPerson(person)));
}
@Test(expected = IllegalArgumentException.class)
public void testModifyNonExistentId() throws DAOException {
this.dao.modify(nonExistentPerson());
}
@Test(expected = IllegalArgumentException.class)
public void testModifyNullPerson() throws DAOException {
this.dao.modify(null);
}
@Test
@ExpectedDatabase("/datasets/dataset-add.xml")
public void testAdd() throws DAOException {
final Person person = this.dao.add(newName(), newSurname());
assertThat(person, is(equalsToPerson(newPerson())));
final Person persistentPerson = this.dao.get(person.getId());
assertThat(persistentPerson, is(equalsToPerson(newPerson())));
}
@Test(expected = IllegalArgumentException.class)
public void testAddNullName() throws DAOException {
this.dao.add(null, newSurname());
}
@Test(expected = IllegalArgumentException.class)
public void testAddNullSurname() throws DAOException {
this.dao.add(newName(), null);
}
}
package es.uvigo.esei.daa.dao;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentId;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newName;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newSurname;
import static es.uvigo.esei.daa.dataset.PeopleDataset.people;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.containsPeopleInAnyOrder;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.equalsToPerson;
import static org.easymock.EasyMock.anyString;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.reset;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.sql.SQLException;
import org.junit.Test;
import com.mysql.jdbc.Statement;
import es.uvigo.esei.daa.entities.Person;
import es.uvigo.esei.daa.util.DatabaseQueryUnitTest;
public class PeopleDAOUnitTest extends DatabaseQueryUnitTest {
@Test
public void testList() throws Exception {
final Person[] people = people();
for (Person person : people) {
expectPersonRow(person);
}
expect(result.next()).andReturn(false);
result.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
assertThat(peopleDAO.list(), containsPeopleInAnyOrder(people));
}
@Test(expected = DAOException.class)
public void testListUnexpectedException() throws Exception {
expect(result.next()).andThrow(new SQLException());
result.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.list();
}
@Test
public void testGet() throws Exception {
final Person existentPerson = existentPerson();
expectPersonRow(existentPerson);
result.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
assertThat(peopleDAO.get(existentId()), is(equalTo(existentPerson)));
}
@Test(expected = IllegalArgumentException.class)
public void testGetMissing() throws Exception {
expect(result.next()).andReturn(false);
result.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.get(existentId());
}
@Test(expected = DAOException.class)
public void testGetUnexpectedException() throws Exception {
expect(result.next()).andThrow(new SQLException());
result.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.get(existentId());
}
@Test
public void testAdd() throws Exception {
final Person person = newPerson();
reset(connection);
expect(connection.prepareStatement(anyString(), eq(Statement.RETURN_GENERATED_KEYS)))
.andReturn(statement);
expect(statement.executeUpdate()).andReturn(1);
expect(statement.getGeneratedKeys()).andReturn(result);
// Key retrieval
expect(result.next()).andReturn(true);
expect(result.getInt(1)).andReturn(person.getId());
connection.close();
result.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
final Person newPerson = peopleDAO.add(person.getName(), person.getSurname());
assertThat(newPerson, is(equalsToPerson(person)));
}
@Test(expected = IllegalArgumentException.class)
public void testAddNullName() throws Exception {
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
resetAll(); // No expectations
peopleDAO.add(null, newSurname());
}
@Test(expected = IllegalArgumentException.class)
public void testAddNullSurname() throws Exception {
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
resetAll(); // No expectations
peopleDAO.add(newName(), null);
}
@Test(expected = DAOException.class)
public void testAddZeroUpdatedRows() throws Exception {
reset(connection);
expect(connection.prepareStatement(anyString(), eq(1)))
.andReturn(statement);
expect(statement.executeUpdate()).andReturn(0);
connection.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.add(newName(), newSurname());
}
@Test(expected = DAOException.class)
public void testAddNoGeneratedKey() throws Exception {
reset(connection);
expect(connection.prepareStatement(anyString(), eq(1)))
.andReturn(statement);
expect(statement.executeUpdate()).andReturn(1);
expect(statement.getGeneratedKeys()).andReturn(result);
expect(result.next()).andReturn(false);
result.close();
connection.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.add(newName(), newSurname());
}
@Test(expected = DAOException.class)
public void testAddUnexpectedException() throws Exception {
reset(connection);
expect(connection.prepareStatement(anyString(), eq(1)))
.andReturn(statement);
expect(statement.executeUpdate()).andThrow(new SQLException());
connection.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.add(newName(), newSurname());
}
@Test
public void testDelete() throws Exception {
expect(statement.executeUpdate()).andReturn(1);
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.delete(existentId());
}
@Test(expected = IllegalArgumentException.class)
public void testDeleteInvalidId() throws Exception {
expect(statement.executeUpdate()).andReturn(0);
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.delete(existentId());
}
@Test(expected = DAOException.class)
public void testDeleteUnexpectedException() throws Exception {
expect(statement.executeUpdate()).andThrow(new SQLException());
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.delete(existentId());
}
@Test
public void testModify() throws Exception {
expect(statement.executeUpdate()).andReturn(1);
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.modify(existentPerson());
}
@Test(expected = IllegalArgumentException.class)
public void testModifyNullPerson() throws Exception {
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
resetAll(); // No expectations
peopleDAO.modify(null);
}
@Test(expected = IllegalArgumentException.class)
public void testModifyZeroUpdatedRows() throws Exception {
expect(statement.executeUpdate()).andReturn(0);
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.modify(existentPerson());
}
@Test(expected = DAOException.class)
public void testModifyUnexpectedException() throws Exception {
expect(statement.executeUpdate()).andThrow(new SQLException());
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.modify(existentPerson());
}
private void expectPersonRow(Person person) throws SQLException {
expect(result.next()).andReturn(true);
expect(result.getInt("id")).andReturn(person.getId());
expect(result.getString("name")).andReturn(person.getName());
expect(result.getString("surname")).andReturn(person.getSurname());
}
}
......@@ -2,7 +2,7 @@ package es.uvigo.esei.daa.entities;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
......
......@@ -2,13 +2,16 @@ package es.uvigo.esei.daa.filters;
import java.io.IOException;
import java.security.Principal;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.SecurityContext;
......@@ -18,9 +21,18 @@ import es.uvigo.esei.daa.dao.DAOException;
import es.uvigo.esei.daa.dao.UsersDAO;
import es.uvigo.esei.daa.entities.User;
/**
* This performs the Basic HTTP authentication following (almost) the same
* rules as the defined in the web.xml file.
*
* @author Miguel Reboiro Jato
*/
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthorizationFilter implements ContainerRequestFilter {
// Add here the list of REST paths that an administrator can access.
private final static List<String> ADMIN_PATHS = Arrays.asList("people");
private final UsersDAO dao;
public AuthorizationFilter() {
......@@ -46,7 +58,11 @@ public class AuthorizationFilter implements ContainerRequestFilter {
if (this.dao.checkLogin(userPass[0], userPass[1])) {
final User user = this.dao.get(userPass[0]);
requestContext.setSecurityContext(new UserSecurityContext(user));
if (isAdminPath(requestContext) && !user.getRole().equals("ADMIN")) {
requestContext.abortWith(createResponse());
} else {
requestContext.setSecurityContext(new UserSecurityContext(user));
}
} else {
requestContext.abortWith(createResponse());
}
......@@ -59,6 +75,17 @@ public class AuthorizationFilter implements ContainerRequestFilter {
}
}
private static boolean isAdminPath(ContainerRequestContext context) {
final List<PathSegment> pathSegments = context.getUriInfo().getPathSegments();
if (pathSegments.isEmpty()) {
return false;
} else {
final String path = pathSegments.get(0).getPath();
return ADMIN_PATHS.contains(path);
}
}
private static Response createResponse() {
return Response.status(Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"DAAExample\"")
......
package es.uvigo.esei.daa.rest;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentId;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newName;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newSurname;
import static es.uvigo.esei.daa.dataset.PeopleDataset.people;
import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasBadRequestStatus;
import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasInternalServerErrorStatus;
import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasOkStatus;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.containsPeopleInAnyOrder;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.equalsToPerson;
import static java.util.Arrays.asList;
import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.anyString;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.List;
import javax.ws.rs.core.Response;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import es.uvigo.esei.daa.dao.DAOException;
import es.uvigo.esei.daa.dao.PeopleDAO;
import es.uvigo.esei.daa.entities.Person;
public class PeopleResourceUnitTest {
private PeopleDAO daoMock;
private PeopleResource resource;
@Before
public void setUp() throws Exception {
daoMock = createMock(PeopleDAO.class);
resource = new PeopleResource(daoMock);
}
@After
public void tearDown() throws Exception {
try {
verify(daoMock);
} finally {
daoMock = null;
resource = null;
}
}
@Test
@SuppressWarnings("unchecked")
public void testList() throws Exception {
final List<Person> people = asList(people());
expect(daoMock.list()).andReturn(people);
replay(daoMock);
final Response response = resource.list();
assertThat(response, hasOkStatus());
assertThat((List<Person>) response.getEntity(), containsPeopleInAnyOrder(people()));
}
@Test
public void testListDAOException() throws Exception {
expect(daoMock.list()).andThrow(new DAOException());
replay(daoMock);
final Response response = resource.list();
assertThat(response, hasInternalServerErrorStatus());
}
@Test
public void testGet() throws Exception {
final Person person = existentPerson();
expect(daoMock.get(person.getId())).andReturn(person);
replay(daoMock);
final Response response = resource.get(person.getId());
assertThat(response, hasOkStatus());
assertThat((Person) response.getEntity(), is(equalsToPerson(person)));
}
@Test
public void testGetDAOException() throws Exception {
expect(daoMock.get(anyInt())).andThrow(new DAOException());
replay(daoMock);
final Response response = resource.get(existentId());
assertThat(response, hasInternalServerErrorStatus());
}
@Test
public void testGetIllegalArgumentException() throws Exception {
expect(daoMock.get(anyInt())).andThrow(new IllegalArgumentException());
replay(daoMock);
final Response response = resource.get(existentId());
assertThat(response, hasBadRequestStatus());
}
@Test
public void testDelete() throws Exception {
daoMock.delete(anyInt());
replay(daoMock);
final Response response = resource.delete(1);
assertThat(response, hasOkStatus());
}
@Test
public void testDeleteDAOException() throws Exception {
daoMock.delete(anyInt());
expectLastCall().andThrow(new DAOException());
replay(daoMock);
final Response response = resource.delete(1);
assertThat(response, hasInternalServerErrorStatus());
}
@Test
public void testDeleteIllegalArgumentException() throws Exception {
daoMock.delete(anyInt());
expectLastCall().andThrow(new IllegalArgumentException());
replay(daoMock);
final Response response = resource.delete(1);
assertThat(response, hasBadRequestStatus());
}
@Test
public void testModify() throws Exception {
final Person person = existentPerson();
person.setName(newName());
person.setSurname(newSurname());
daoMock.modify(person);
replay(daoMock);
final Response response = resource.modify(
person.getId(), person.getName(), person.getSurname());
assertThat(response, hasOkStatus());
assertEquals(person, response.getEntity());
}
@Test
public void testModifyDAOException() throws Exception {
daoMock.modify(anyObject());
expectLastCall().andThrow(new DAOException());
replay(daoMock);
final Response response = resource.modify(existentId(), newName(), newSurname());
assertThat(response, hasInternalServerErrorStatus());
}
@Test
public void testModifyIllegalArgumentException() throws Exception {
daoMock.modify(anyObject());
expectLastCall().andThrow(new IllegalArgumentException());
replay(daoMock);
final Response response = resource.modify(existentId(), newName(), newSurname());
assertThat(response, hasBadRequestStatus());
}
@Test
public void testModifyNullPointerException() throws Exception {
daoMock.modify(anyObject());
expectLastCall().andThrow(new NullPointerException());
replay(daoMock);
final Response response = resource.modify(existentId(), newName(), newSurname());
assertThat(response, hasBadRequestStatus());
}
@Test
public void testAdd() throws Exception {
expect(daoMock.add(newName(), newSurname()))
.andReturn(newPerson());
replay(daoMock);
final Response response = resource.add(newName(), newSurname());
assertThat(response, hasOkStatus());
assertThat((Person) response.getEntity(), is(equalsToPerson(newPerson())));
}
@Test
public void testAddDAOException() throws Exception {
expect(daoMock.add(anyString(), anyString()))
.andThrow(new DAOException());
replay(daoMock);
final Response response = resource.add(newName(), newSurname());
assertThat(response, hasInternalServerErrorStatus());
}
@Test
public void testAddIllegalArgumentException() throws Exception {
expect(daoMock.add(anyString(), anyString()))
.andThrow(new IllegalArgumentException());
replay(daoMock);
final Response response = resource.add(newName(), newSurname());
assertThat(response, hasBadRequestStatus());
}
}
......@@ -8,7 +8,7 @@ import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasOkStatus;
import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasUnauthorized;
import static es.uvigo.esei.daa.matchers.IsEqualToUser.equalsToUser;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import java.io.IOException;
......@@ -17,13 +17,7 @@ import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
......@@ -74,20 +68,6 @@ public class UsersResourceTest extends JerseyTest {
config.property("com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE);
}
@Override
protected TestContainerFactory getTestContainerFactory() {
return new GrizzlyWebTestContainerFactory();
}
@Override
protected DeploymentContext configureDeployment() {
return ServletDeploymentContext.forServlet(
new ServletContainer(ResourceConfig.forApplication(configure()))
)
.servletPath("/rest")
.build();
}
@Test
public void testGetAdminOwnUser() throws IOException {
final String admin = adminLogin();
......
package es.uvigo.esei.daa.suites;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import es.uvigo.esei.daa.web.PeopleWebTest;
@SuiteClasses({
PeopleWebTest.class
})
@RunWith(Suite.class)
public class AcceptanceTestSuite {
}
......@@ -4,12 +4,10 @@ import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import es.uvigo.esei.daa.dao.PeopleDAOTest;
import es.uvigo.esei.daa.rest.PeopleResourceTest;
import es.uvigo.esei.daa.rest.UsersResourceTest;
@SuiteClasses({
PeopleDAOTest.class,
PeopleResourceTest.class,
UsersResourceTest.class
})
......
......@@ -4,14 +4,10 @@ import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import es.uvigo.esei.daa.dao.PeopleDAOUnitTest;
import es.uvigo.esei.daa.entities.PersonUnitTest;
import es.uvigo.esei.daa.rest.PeopleResourceUnitTest;
@SuiteClasses({
PersonUnitTest.class,
PeopleDAOUnitTest.class,
PeopleResourceUnitTest.class
PersonUnitTest.class
})
@RunWith(Suite.class)
public class UnitTestSuite {
......
package es.uvigo.esei.daa.util;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.support.ui.ExpectedCondition;
/*
* Implementation based on https://stackoverflow.com/questions/33348600/selenium-wait-for-ajax-content-to-load-universal-approach
*/
public class AdditionalConditions {
public static ExpectedCondition<Boolean> jQueryAjaxCallsHaveCompleted() {
return driver ->
(Boolean) ((JavascriptExecutor) driver).executeScript("return (window.jQuery !== null) && (jQuery.active === 0)");
}
}
package es.uvigo.esei.daa.util;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
/**
* Utility class to wait for several JavaScript events to complete.
*
* Code adapted from
* https://www.swtestacademy.com/selenium-wait-javascript-angular-ajax/
*/
public class JSWaiter {
private final WebDriverWait jsWait;
private final JavascriptExecutor jsExec;
private JSWaiter(WebDriver driver) {
this.jsWait = new WebDriverWait(driver, 10);
this.jsExec = (JavascriptExecutor) driver;
}
public static JSWaiter wait(WebDriver driver) {
return new JSWaiter(driver);
}
public void untilAngular5Ready() {
try {
final Object angular5Check = jsExec.executeScript("return getAllAngularRootElements()[0].attributes['ng-version']");
if (angular5Check != null) {
final Boolean angularPageLoaded = (Boolean) jsExec
.executeScript("return window.getAllAngularTestabilities().findIndex(x=>!x.isStable()) === -1");
if (!angularPageLoaded) {
poll(20);
waitForAngular5Load();
poll(20);
}
}
} catch (WebDriverException ignored) {}
}
private void waitForAngular5Load() {
String angularReadyScript = "return window.getAllAngularTestabilities().findIndex(x=>!x.isStable()) === -1";
try {
final ExpectedCondition<Boolean> angularLoad = driver -> Boolean
.valueOf(((JavascriptExecutor) driver).executeScript(angularReadyScript).toString());
final boolean angularReady = Boolean.valueOf(jsExec.executeScript(angularReadyScript).toString());
if (!angularReady) {
jsWait.until(angularLoad);
}
} catch (WebDriverException ignored) {}
}
private void poll(long milis) {
try {
Thread.sleep(milis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
This diff is collapsed.
......@@ -139,9 +139,17 @@
<Realm className="org.apache.catalina.realm.JDBCRealm"
driverName="org.hsqldb.jdbc.JDBCDriver"
connectionURL="jdbc:hsqldb:hsql://localhost/daatestdb?user=sa"
userTable="users" userNameCol="login" userCredCol="password"
userRoleTable="users" roleNameCol="role" />
userTable="users"
userNameCol="login"
userCredCol="password"
userRoleTable="users"
roleNameCol="role"
digest="SHA-256"
>
<CredentialHandler className="org.apache.catalina.realm.MessageDigestCredentialHandler"
algorithm="SHA-256"/>
</Realm>
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
<!-- SingleSignOn valve, share authentication between web applications
......
......@@ -138,7 +138,7 @@
<Realm className="org.apache.catalina.realm.JDBCRealm"
driverName="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost/daaexample"
connectionURL="jdbc:mysql://localhost/daaexample?useSSL=false"
connectionName="daa"
connectionPassword="daa"
userTable="users" userNameCol="login" userCredCol="password"
......