Commit 03676fde by Tuomas Riihimäki

Merge branch 'sessions-for-frontend' into 'master'

Login for frontend

See merge request !422
2 parents 72f574ba d2b03bfc
Showing with 746 additions and 198 deletions
# devausnoteja (tmp ohjeet)
# devausnoteja
## Devaus
### devaus workflow
### Testaus
Kehittäminen on nopeampaa jos saa muutokset suoraan käyttöön, tämän takia kannattaa käynnistää lasikala ja webback-palvelin erikseen, ja proxyttää webbakista moya-jutut lasikalaan.
Kehittämisen nopeuttamiseksi kannattaa frontti käynnistää erikseen ja ajaa sitä proxyn läpi halutulle backendille.
ng-palvelimessa on bugi, ja se ei osaa jakaa tavaraa muusta hakemistosta kuin /.
moyaproxy.conf.json sisältää proxyasetukset localhostin porttiin 8080.
Käyttö:
`npm run start`
* `npm run localhost` : Käynnistä frontend paikallista palvelinta vasten (localhost:8080)
* `npm run testserver` : Käynnistä frontend meidän testipalvelinta vasten (https://ng.test.moya.fi)
Tämän jälkeen kirjaa selaimesi sisään moyaan: http://localhost:4200/MoyaWeb/ ja surffata softaan: http://localhost:4200/
Tämän jälkeen surffaa selaimellasi moyaan: `http://localhost:4200/`
### HMR
### Esteettömyys
HMR -asetukset löytyy, niistä ei nykyisellään ole mitään hyötyä. Saa poistaa.
#### Tabindex
Tehtyjä sivuja pitää kokeilla myös surffaamalla niitä läpi tabilla. Jos järjestys on outo, niin tabindex -elementti on avuksi.
Sivun menuvalikot käyttävät indexejä 10:n alta. (8: Login & kieli, 9: ylämenu, 10: vasenmenu).
#### click
Jos pistät linkkiin pelkän (click) -tagin, sitä ei voi painaa näppäimistöllä. (esim: `<a (click)=...><img>...</>`), Tyhjällä `[routerLink]=""` -tagilla saat kerrottua selaimelle "tähän voi mennä ja tätä voi painaa".
## Koodijuttuja
### moya-rest
Oma moduulinsa, joka tullaan jossain vaiheessa eriyttämään ja käyttämään myös mahdollisessa nativescript-kännyappiksessa. Eli sen alle tulee vain servisejä, ei komponentteja.
## Koodijuttuja
### Muuta
Jos tulee muita järkeviä kokonaisuuksia, joita voi käyttää muualla, jaa omaan moduuliinsa.
### Koodaustyyli
## WIP: Hakemistohierarkia
Meillä on käytössä kohtuu tiukka lintteri, ennen committamista kannattaa ajaa `ng lint` ja korjata virheet. `ng lint moya-angular --fix` on ystävämme.
## Hakemistohierarkia
* app
* modules
* viplist
* viplist-manager.module
* viplist-manager
* viplist-info.component
* viplist-admin.component
* components `Componentit joidenka ei ole järkeä olla omia lazyloadattavia moduuleita`
* modules `Kokonaisuuksia jotka on järkeä jakaa omaksi lazyloadattavaksi kokonaisuudekseen.`
* viplist `Esimerkki vain info ja admin -puoella näkyvästä vippilista -moduulista`
* viplist.module
* info
* info-viplist-page.component `Infopuolen landing -sivu. Tämä on routing component info -vippilistasta`
* admin
* admin-viplist-page.component
* models
* viplist.model
* pizza
* pizza-user
* pizza `Esimerkki moduulista joka näkyy kaikilla puolilla`
* pizza-user
* pizza-user.component
* pizza-user.service
* pizza-user.module
......@@ -59,13 +63,14 @@ Jos tulee muita järkeviä kokonaisuuksia, joita voi käyttää muualla, jaa oma
* pizza-list.component (shared for user / admin pizza)
* models
* pizza.model
* shared
* shared `Resurssit jotka ovat käytössä vähän kaikkialla. Esim. käyttäjää on turha jakaa minkään erillisen moduulin alle`
* components
* services
* models
* auth
##Routing
Tämä pitää vielä miettä läpi
* info
* info routing johon tulee pizza-user.module LL
......@@ -76,58 +81,6 @@ Jos tulee muita järkeviä kokonaisuuksia, joita voi käyttää muualla, jaa oma
* pizza-user.component & pizza-user.service
## OLD PLAN
* app
* pages `tämän alla on käytännössä "älykkäät" komponentit (stateful), eli componentit jotka tietää mitä dataa haetaan ja mistä. Käytännössä route -targetit`
* user `Loppukäyttäjälle näkyvät jutut, ohjleman ns. main moduuli`
* pizzatilaus
* listOpen.component.ts `avoimet pizzatilaukset joista voi tilata`
* own_orders.component.ts
* testit yms.
* admin `moya asetukset, esim. tuotteet, korttipohjat, oma moduulinsa joka lazyloadataan`
* admin.module.ts
* pizzatilaus
* pizzaedit.component.ts `hallitse mitä pizzoja voi tilata ylipäätänsä`
* info `tapahtumanaikana "infolaisen" toimesta tehtävät jutut, sisäänotto, käyttäjänhallinta jne. Oma moduulinsa joka lazyloadataan`
* info.module.ts
* viplist
* info-viplist-page.component.ts,html,jne
* viplist.service.ts
* pizzatilaus
* list.component.ts `listaa kaikki, ja lisää uusi pizzailaus`
* old-moya `Monessa paikassa käytettävät target -componentit tulevat omiksi kokonaisuudiksiin`
* old-moya.component.ts
* pizzatilaus `Jos halutaan lazyloadata jokin osa moyasta, tehdään siitä uusi päätason komponentti. Joka sitten laitetetaan user / admin yms. puolille route -targetiksi`
* pizzatilaus-route.module.ts
* pizza-user-order.component.ts
* menu `menu on täällä`
* shared `geneeriset, mihinkään osaan kuulumattomat jaetut modellit ja servicet tulevat tänne`
* models
* user.model.ts
* services
* moya-rest.service.ts
* session.service.ts
* widgets `tyhmät componponentit tulevat tänne`
* button
* html
* ts
* scss
* components `servicet sekä componenttien geneeriset tyhmät osat tulevat tämän hakemiston alle. Tänne voi myös pistää älykkäitä pieniä komponentteja `
* viplist
* list.component.ts
* add.component.ts
* moya-locale
* moya-locale.service.ts
* moya-locale.model.ts `model -tiedosto joka on käytössä vaan moya-locale -servicessä`
* modules `Kokonaiset, monessa paikassa olevat kokonaisuuksien tyhmät osat tulevat tänne. Sen verta isot että ne kannattaa tehdä moduuleiksi`
* pizzatilaus
* anna-pizza.component.ts
* list-pizzat.component.ts,html,jne
* listItem.component.ts
* edit.component.ts
* pizzatilaus.service.ts
* pizza-tilaus.module.ts
......@@ -159,38 +112,10 @@ Integraatio jne. testit, aloitteliat ei riko asioita niin todennäköisesti.
### WebPack ja buildin minimointi
aot-kääntäminen ja siihen comperssointia jne.
### HMR
HMR -asetukset löytyy, niistä ei nykyisellään ole mitään hyötyä. Saa poistaa.
# MoyaAngular, generoidut ohjeet
This project was generated with [angular-cli](https://github.com/angular/angular-cli) version 1.0.0-beta.24.
## 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/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/).
Before running the tests make sure you are serving the app via `ng serve`.
## Deploying to Github Pages
Run `ng github-pages:deploy` to deploy to Github Pages.
## 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).
......@@ -3,6 +3,6 @@
xsi:schemaLocation="http://ocpsoft.org/schema/rewrite-config-prettyfaces
http://ocpsoft.org/xml/ns/prettyfaces/rewrite-config-prettyfaces.xsd">
<rewrite match="^(?!.*\.(js|css|jpg|gif|png|ico|html|svg)$).*$" substitute="/index.html" redirect="chain" />
<rewrite match="^(?!.*\.(js|css|jpg|gif|png|ico|html|svg|json)$).*$" substitute="/index.html" redirect="chain" />
</pretty-config>
......@@ -5,7 +5,8 @@
"angular-cli": {},
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config=moyaproxy.conf.json --base-href=/ && ng lint",
"localhost": "ng serve --proxy-config=localhost-moyaproxy.conf.json --base-href=/",
"testserver": "ng serve --proxy-config=test-moyaproxy.conf.json --base-href=/",
"build": "ng build",
"aot": "ng build --aot",
"test": "ng test",
......@@ -27,8 +28,8 @@
"@angular/platform-browser": "6.1.1",
"@angular/platform-browser-dynamic": "6.1.1",
"@angular/router": "6.1.1",
"@ngx-translate/core": "^9.0.0",
"@ngx-translate/http-loader": "^2.0.0",
"@ngx-translate/core": "^10.0.0",
"@ngx-translate/http-loader": "^3.0.0",
"apollo-angular": "^1.1.2",
"apollo-angular-link-http": "^1.1.1",
"apollo-cache-inmemory": "^1.2.6",
......@@ -37,7 +38,7 @@
"core-js": "^2.5.1",
"graphql": "^0.13.2",
"graphql-tag": "^2.9.2",
"ngx-bootstrap": "^2.0.2",
"ngx-bootstrap": "^3.0.0",
"rxjs": "^6.2.2",
"ts-helpers": "^1.1.1",
"zone.js": "^0.8.26"
......
import { NgModule } from '@angular/core';
import {Routes, RouterModule, PreloadAllModules, NoPreloading} from '@angular/router';
import { LoginComponent } from './modules/login/login.component';
import {FrontpageComponent} from './components/frontpage/frontpage.component';
import {LoginPageComponent} from './modules/login/login-page/login-page.component';
const APP_ROUTES: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'viplist', loadChildren: 'app/modules/viplist/viplist.module#ViplistModule' },
{ path: 'old', loadChildren: 'app/modules/old-moya/old-moya.module#OldMoyaModule' },
{ path: 'index', component: FrontpageComponent },
......
......@@ -6,10 +6,12 @@
<div id="contentarea">
<div id="topbar">
<moya-top-menu></moya-top-menu>
<moya-locale></moya-locale>
<div id="loginlocale">
<moya-login-button></moya-login-button>
<moya-locale></moya-locale>
</div>
</div>
<div id="content">
<router-outlet></router-outlet>
</div>
</div>
......@@ -29,3 +29,8 @@
padding: 20px;
flex-grow: 1;
}
#loginlocale {
display: flex;
}
import {Component, NgZone} from '@angular/core';
import {Router} from '@angular/router';
import {MatDialog, MatDialogConfig} from '@angular/material';
import {LoginComponent} from './modules/login/login-component/login.component';
declare var window: any;
......@@ -10,7 +12,7 @@ declare var window: any;
})
export class AppComponent {
constructor(private router: Router, private zone: NgZone) {
constructor(private router: Router, private zone: NgZone, private dialog: MatDialog) {
window.angularRoute = (url => {
zone.run(() => {
......
......@@ -19,13 +19,15 @@ import { FrontpageComponent } from './components/frontpage/frontpage.component';
import {HttpLink, HttpLinkModule} from 'apollo-angular-link-http';
import {APOLLO_OPTIONS, ApolloModule} from 'apollo-angular';
import {createApollo} from './shared/config/moya.config';
import {MatButtonModule} from '@angular/material';
import {LoginComponent} from './modules/login/login-component/login.component';
@NgModule({
declarations: [
AppComponent,
TopMenuComponent,
MoyaLocaleComponent,
FrontpageComponent
FrontpageComponent,
],
imports: [
BrowserModule,
......@@ -34,6 +36,8 @@ import {createApollo} from './shared/config/moya.config';
HttpClientModule,
LoginModule,
LeftMenuModule,
MatButtonModule,
ApolloModule,
HttpLinkModule,
......
<p>
frontpage works!
</p>
<marquee behavior="scroll" direction="left"> frontpage works! </marquee>
<div id="flagBond">
<img src="assets/Flag_of_Finland.svg" [className]="(fiSelected)? 'disabledFlag':'flag' "height="20px" (click)="selectFinnish()" />
<img src="assets/Flag_of_Britain.svg" [className]="(enSelected)? 'disabledFlag':'flag'" height="20px" (click)="selectEnglish()" />
<img src="assets/Flag_of_Sweden.svg" [className]="(svSelected)? 'disabledFlag':'flag'" height="20px" (click)="selectSwedish()" />
<div class="locales">
<a (click)="selectFinnish()" [routerLink]="" tabindex="8"><img src="assets/Flag_of_Finland.svg" [className]="(fiSelected)? 'disabledFlag':'flag' "height="20px" alt="Suomeksi {{ (fiSelected)? 'valittu':'' }}" /></a>
<a (click)="selectEnglish()" [routerLink]="" tabindex="8"><img src="assets/Flag_of_Britain.svg" [className]="(enSelected)? 'disabledFlag':'flag'" height="20px" alt="In english {{ (enSelected)? 'selected':'' }}" /></a>
<a (click)="selectSwedish()" [routerLink]="" tabindex="8" ><img src="assets/Flag_of_Sweden.svg" [className]="(svSelected)? 'disabledFlag':'flag'" height="20px" alt="Svenska {{ (svSelected)? 'vald':'' }}" /></a>
</div>
......@@ -11,7 +11,11 @@
border: none;
}
/* http://flexboxfroggy.com/ */
#flagBond {
.locales {
display: flex;
height: 100%;
a {
align-self: center;
}
}
import {MenuGroup} from '../models/menu-group.model';
import {OldMoyaComponent} from '../../modules/old-moya/old-moya.component';
import {Permissions} from '../../shared/models/permissions.model';
/**
* Created by tuukka on 13/05/17.
*/
// We use GeneratePermissionFunction to make permission check functions
// This way if we can check permission names on software intialize, and will get exception if there is typo
const _GP = Permissions.GeneratePermissionFunction;
export class MENU {
static ADMIN: MenuGroup[] = [
{
'name': 'shop',
'items': [
{ 'path': '/old/admin', 'name': 'products', 'params': {p: 'product/list.jsf'} }
{ 'path': '/old/admin', 'name': 'products', 'perm': _GP({'VIPLIST': 'ADMIN'}), 'params': {p: 'product/list.jsf'} }
]}
];
......@@ -20,8 +27,8 @@ export class MENU {
{
'name': 'users',
'items': [
{ 'path': '/old/info', 'name': 'listusers', 'params': {p: 'useradmin/list.jsf'} },
{ 'path': '/viplist/info', 'name': 'viplist'}
{ 'path': '/old/info', 'name': 'listusers', 'perm': _GP({'SITE': 'INFO'}), 'params': {p: 'useradmin/list.jsf'} },
{ 'path': '/viplist/info', 'perm': _GP({'VIPLIST': 'INFO'}), 'name': 'viplist'}
]}
];
......@@ -29,54 +36,53 @@ export class MENU {
{
'name': 'frontpage',
'items': [
{ 'path': '/index', 'name': 'frontpage' },
{ 'path': '/index', 'name': 'frontpage', 'perm': _GP({'SITE': 'USER'}) },
]},
{
'name': 'event',
'items': [
{ 'path': '/old/user', 'name': 'poll', 'params': {p: 'poll/index.jsf'} },
{ 'path': '/old/user', 'name': 'invite', 'params': {p: 'user/invite.jsf'} },
{ 'path': '/old/user', 'name': 'poll', 'perm': _GP({'POLL': 'USER'}), 'params': {p: 'poll/index.jsf'} },
{ 'path': '/old/user', 'name': 'invite', 'perm': _GP({'INVITE': 'USER'}), 'params': {p: 'user/invite.jsf'} },
]},
{
'name': 'shop',
'items': [
{ 'path': '/old/user', 'name': 'buytickets', 'params': {p: 'shop/createBill.jsf'} },
{ 'path': '/old/user', 'name': 'reserveplaces', 'params': {p: 'neomap/reserve.jsf'} },
{ 'path': '/old/user', 'name': 'changeplaces', 'params': {p: 'neomap/moveplaces.jsf'} },
{ 'path': '/old/user', 'name': 'foodorders', 'params': {p: 'foodwave/list.jsf'} },
{ 'path': '/old/user', 'name': 'ownorders', 'params': {p: 'bill/list.jsf'} },
{ 'path': '/old/user', 'name': 'accountevents', 'params': {p: 'user/accountEvents.jsf'} },
{ 'path': '/old/user', 'name': 'buytickets', 'perm': _GP({'SHOP': 'USER'}), 'params': {p: 'shop/createBill.jsf'} },
{ 'path': '/old/user', 'name': 'reserveplaces', 'perm': _GP({'MAP': 'USER'}), 'params': {p: 'neomap/reserve.jsf'} },
{ 'path': '/old/user', 'name': 'changeplaces', 'perm': _GP({'MAP': 'USER'}), 'params': {p: 'neomap/moveplaces.jsf'} },
{ 'path': '/old/user', 'name': 'foodorders', 'perm': _GP({'FOOD': 'USER'}), 'params': {p: 'foodwave/list.jsf'} },
{ 'path': '/old/user', 'name': 'ownorders', 'perm': _GP({'SHOP': 'USER', 'CREDIT': 'USER'}), 'params': {p: 'bill/list.jsf'} },
]},
{
'name': 'computerplaces',
'items': [
{ 'path': '/old/user', 'name': 'ownplaces', 'params': {p: 'place/myGroups.jsf'} },
{ 'path': '/old/user', 'name': 'placemap', 'params': {p: 'neomap/view.jsf'} },
{ 'path': '/old/user', 'name': 'ownplaces', 'perm': _GP({'MAP': 'USER'}), 'params': {p: 'place/myGroups.jsf'} },
{ 'path': '/old/user', 'name': 'placemap', 'perm': _GP({'MAP': 'USER'}), 'params': {p: 'neomap/view.jsf'} },
]},
{
'name': 'competitions',
'items': [
{ 'path': '/old/user', 'name': 'compos', 'params': {p: 'voting/compolist.jsf'} },
{ 'path': '/old/user', 'name': 'myentries', 'params': {p: 'voting/myEntries.jsf'} },
{ 'path': '/old/user', 'name': 'compos', 'perm': _GP({'COMPO': 'USER'}), 'params': {p: 'voting/compolist.jsf'} },
{ 'path': '/old/user', 'name': 'myentries', 'perm': _GP({'COMPO': 'USER'}), 'params': {p: 'voting/myEntries.jsf'} },
]},
{
'name': 'profile',
'items': [
{ 'path': '/old/user', 'name': 'myinformation', 'params': {p: 'user/edit.jsf'} },
{ 'path': '/old/user', 'name': 'requestrole', 'params': {p: 'orgrole/requestRole.jsf'} },
{ 'path': '/old/user', 'name': 'changepassword', 'params': {p: 'user/changePassword.jsf'} },
{ 'path': '/old/user', 'name': 'setgameids', 'params': {p: 'user/gameids.jsf'} },
{ 'path': '/old/user', 'name': 'myinformation', 'perm': _GP({'USER': 'USER'}), 'params': {p: 'user/edit.jsf'} },
{ 'path': '/old/user', 'name': 'requestrole', 'perm': _GP({'USER': 'USER'}), 'params': {p: 'orgrole/requestRole.jsf'} },
{ 'path': '/old/user', 'name': 'changepassword', 'perm': _GP({'USER': 'USER'}), 'params': {p: 'user/changePassword.jsf'} },
{ 'path': '/old/user', 'name': 'setgameids', 'perm': _GP({'USER': 'USER'}), 'params': {p: 'user/gameids.jsf'} },
]},
{
'name': 'tournaments',
'items': [
{ 'path': '/old/user', 'name': 'tournamentslist', 'params': {p: 'tournaments/index.jsf'} },
{ 'path': '/old/user', 'name': 'myparticipations', 'params': {p: 'tournaments/myparticipations.jsf'} },
{ 'path': '/old/user', 'name': 'tournamentslist', 'perm': _GP({'TOURNAMENT': 'USER'}), 'params': {p: 'tournaments/index.jsf'} },
{ 'path': '/old/user', 'name': 'myparticipations', 'perm': _GP({'TOURNAMENT': 'USER'}), 'params': {p: 'tournaments/myparticipations.jsf'} },
]},
{
'name': 'lecturesandcourses',
'items': [
{ 'path': '/old/user', 'name': 'participate', 'params': {p: 'lectures/viewLectures.jsf'} },
{ 'path': '/old/user', 'name': 'participate', 'perm': _GP({'LECTURE': 'USER'}), 'params': {p: 'lectures/viewLectures.jsf'} },
]},
];
......
import {Permissions} from '../../shared/models/permissions.model';
export interface MenuItem {
path: string;
name: string;
perm: (permissions: Permissions) => boolean;
params?: any;
}
<nav id="topNav">
<a class="topNavItem" id="userTopNav" routerLink="/index" routerLinkActive="active" translate>menu.top.user</a>
<a class="topNavItem" id="infoTopNav" routerLink="/old/info" routerLinkActive="active" [queryParams]="{p: 'useradmin/list.jsf'}" translate>menu.top.info</a>
<a class="topNavItem" id="adminTopNav" routerLink="/old/admin" routerLinkActive="active" [queryParams]="{p: 'product/list.jsf'}" translate>menu.top.admin</a>
<a tabindex="9" class="topNavItem" id="userTopNav" routerLink="/index" routerLinkActive="active" translate>menu.top.user</a>
<a tabindex="9" class="topNavItem" id="infoTopNav" routerLink="/old/info" routerLinkActive="active" [queryParams]="{p: 'useradmin/list.jsf'}" translate>menu.top.info</a>
<a tabindex="9" class="topNavItem" id="adminTopNav" routerLink="/old/admin" routerLinkActive="active" [queryParams]="{p: 'product/list.jsf'}" translate>menu.top.admin</a>
</nav>
<moya-login *ngIf="!loggedIn"></moya-login>
<span tabindex="8" *ngIf="loggedIn">{{currentUser.firstname}} ({{currentUser.nick}}) {{currentUser.lastname}}</span>
<button tabindex="8" *ngIf="loggedIn" mat-button color="primary" (click)="logout()" >{{ "login.logout" | translate}}</button>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginButtonComponent } from './login-button.component';
describe('LoginButtonComponent', () => {
let component: LoginButtonComponent;
let fixture: ComponentFixture<LoginButtonComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginButtonComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import {MatDialog, MatDialogConfig} from '@angular/material';
import {LoginComponent} from '../login-component/login.component';
import {Router} from '@angular/router';
import {SessionServiceService} from '../../../shared/services/session-service.service';
import {EventUser} from '../../../shared/models/event-user.model';
import {User} from '../../../shared/models/user.model';
@Component({
selector: 'moya-login-button',
templateUrl: './login-button.component.html',
styleUrls: ['./login-button.component.css']
})
export class LoginButtonComponent implements OnInit {
loggedIn = false;
currentUser: User = null;
constructor(private router: Router, private dialog: MatDialog, private sessionService: SessionServiceService) {
this.sessionService.getCurrentUser().subscribe((user: User) => {
this.loggedIn = !user.isAnonymous();
this.currentUser = user;
});
}
logout() {
this.sessionService.logout();
}
ngOnInit() { }
openLoginDialog(): void {
const conf = new MatDialogConfig();
conf.autoFocus = true;
/*conf.height = "800px";
conf.width = "500px";*/
const dialogRef = this.dialog.open(LoginComponent, conf);
dialogRef.componentInstance.loginEvent.subscribe((success) => {
if (success) {
dialogRef.close();
} else {
dialogRef.close();
this.router.navigateByUrl('/login');
}
});
}
}
.loginarea {
display: flex;
}
.loginarea > mat-form-field {
margin-left: 10px;
}
<form class="loginarea" #logindetails="ngForm" (ngSubmit)="login(logindetails)" >
<mat-form-field>
<input tabindex="8" matInput ngModel name="username" placeholder="{{ 'user.username' | translate }}">
</mat-form-field>
<mat-form-field>
<input tabindex="8" matInput type="password" ngModel name="password" placeholder="{{ 'user.password' | translate }}">
</mat-form-field>
<button tabindex="8" type="submit" mat-button color="primary" >{{ 'login.login' | translate }}</button>
</form>
<span *ngIf="loginError" class="loginError">{{ 'login.error'|translate }}</span>
<span *ngIf="loginOk" class="loginOk"> {{ 'login.ok'|translate }}</span>
<span *ngIf="submitting" class="loginInProgress"> {{ 'login.submitting'|translate }}</span>
import {Component, EventEmitter, OnInit, Output} from '@angular/core';
import {TranslatePipe} from '@ngx-translate/core';
import {SessionServiceService} from '../../../shared/services/session-service.service';
import {FormGroup, NgForm} from '@angular/forms';
@Component({
selector: 'moya-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
providers: [TranslatePipe]
})
export class LoginComponent implements OnInit {
@Output()
loginEvent = new EventEmitter<boolean>();
loginError = false;
loginOk = false;
submitting = false;
constructor(private sessionService: SessionServiceService) { }
ngOnInit() {
this.loginError = this.loginOk = this.submitting = false;
}
login(userDetails: NgForm) {
this.loginError = this.loginOk = false;
this.submitting = true;
this.sessionService.doLogin(userDetails.value.username, userDetails.value.password).subscribe((x) => {
this.loginError = this.loginOk = this.submitting = false;
this.loginOk = true;
this.loginEvent.emit(true);
}, (error) => {
this.loginError = this.loginOk = this.submitting = false;
this.loginError = true;
this.loginEvent.emit(false);
});
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginDialogComponent } from './login-dialog.component';
describe('LoginDialogComponent', () => {
let component: LoginDialogComponent;
let fixture: ComponentFixture<LoginDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginDialogComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, NgZone, OnInit} from '@angular/core';
import {MatDialog, MatDialogConfig} from '@angular/material';
import {LoginComponent} from '../login-component/login.component';
import {Router} from '@angular/router';
@Component({
selector: 'moya-login-dialog',
templateUrl: './login-dialog.component.html',
styleUrls: ['./login-dialog.component.css']
})
export class LoginDialogComponent implements OnInit {
constructor(private router: Router, private dialog: MatDialog) { }
ngOnInit() {
}
}
<mat-card>
<mat-card-title>
<mat-toolbar color="primary">
<h1>{{ 'login.title' | translate }}</h1>
</mat-toolbar>
</mat-card-title>
<mat-card-content>
<moya-login (loginEvent)="onLoginEvent($event)" ></moya-login>
</mat-card-content>
</mat-card>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginPageComponent } from './login-page.component';
describe('LoginPageComponent', () => {
let component: LoginPageComponent;
let fixture: ComponentFixture<LoginPageComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginPageComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import {Router} from '@angular/router';
@Component({
selector: 'moya-login-page',
templateUrl: './login-page.component.html',
styleUrls: ['./login-page.component.css']
})
export class LoginPageComponent implements OnInit {
constructor(private router: Router) { }
ngOnInit() {
}
onLoginEvent(success: boolean): void {
if (success) {
this.router.navigateByUrl('/');
}
}
}
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login.component';
const routes: Routes = [
{ path: '', component: LoginComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class LoginRoutingModule { }
<div>Login works!</div>
\ No newline at end of file
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'moya-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
import { LoginRoutingModule } from './login-routing.module';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {LoginComponent} from './login.component';
import {
MatButtonModule,
MatCardModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatMenuModule,
MatToolbarModule
} from '@angular/material';
import {TranslateModule} from '@ngx-translate/core';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {FormsModule} from '@angular/forms';
import { LoginPageComponent } from './login-page/login-page.component';
import {LoginComponent} from './login-component/login.component';
import { LoginDialogComponent } from './login-dialog/login-dialog.component';
import { LoginButtonComponent } from './login-button/login-button.component';
@NgModule({
imports: [
CommonModule,
LoginRoutingModule
MatFormFieldModule,
MatInputModule,
MatButtonModule,
TranslateModule,
BrowserAnimationsModule,
FormsModule,
MatCardModule,
MatDialogModule,
MatToolbarModule,
MatMenuModule
],
declarations: [LoginComponent]
entryComponents: [LoginComponent],
declarations: [LoginComponent, LoginPageComponent, LoginDialogComponent, LoginButtonComponent],
exports: [
LoginComponent,
LoginButtonComponent
]
})
export class LoginModule { }
<iframe id="oldMoyaFrame" [src]="frameUrl | safe" [height]="height" #iframe (load)="changeUrl()" frameborder="0"></iframe>
<iframe id="oldMoyaFrame" [src]="frameUrl | safe" [height]="height" #iframe (load)="changeUrl()" frameborder="0" ></iframe>
import {map} from 'rxjs/operators';
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
import {Component, OnInit, ViewChild, ElementRef, ApplicationRef, ChangeDetectorRef} from '@angular/core';
import {Location, LocationStrategy, HashLocationStrategy, PathLocationStrategy} from '@angular/common';
import {NavigationExtras, ActivatedRoute, Router} from '@angular/router';
import {angularCoreEnv} from '@angular/core/src/render3/jit/environment';
@Component({
selector: 'moya-old',
......@@ -22,10 +23,9 @@ export class OldMoyaComponent implements OnInit {
constructor(private location: Location, private route: ActivatedRoute) {
constructor(private location: Location, private route: ActivatedRoute, private changeDetectionRef: ChangeDetectorRef) {
route.queryParamMap.pipe(map(a => a.get('p'))).subscribe(x => {
console.log(x);
if (x) {
this.frameUrl = '/MoyaWeb/' + x.replace('::', '?');
}
......@@ -42,12 +42,18 @@ export class OldMoyaComponent implements OnInit {
this.location.replaceState(this.location.path(false).split('?', 1)[0] + '?p=' + tmpFrameUrl[1].replace('?', '::') );
}
this.iframe.nativeElement.contentWindow.onscroll = (() => {this.fixHeight(this); });
console.log(this.iframe.nativeElement.contentWindow.document.body.height);
this.height = (this.iframe.nativeElement.contentWindow.document.body.scrollHeight + 50) + 'px';
this.fixHeight(this);
}
fixHeight(self) {
console.log(self.iframe.nativeElement.contentWindow.document.body.scrollHeight);
if (self.height !== (self.iframe.nativeElement.contentWindow.document.body.scrollHeight + 50) + 'px') {
self.height = (self.iframe.nativeElement.contentWindow.document.body.scrollHeight + 50) + 'px';
this.changeDetectionRef.detectChanges();
}
}
......
......@@ -2,7 +2,7 @@ import {InMemoryCache} from 'apollo-cache-inmemory';
import {HttpLink} from 'apollo-angular-link-http';
export const MOYA_BASE_URL = '/MoyaWeb/';
export const MOYA_REST_URL = MOYA_BASE_URL + 'rest/';
export const MOYA_REST_URL = MOYA_BASE_URL + 'rest';
export function createApollo(httpLink: HttpLink) {
......
/**
* Created by tuukka on 22/09/18.
*/
class PermissionFields {
USER = false;
INFO = false;
ADMIN = false;
}
export class Permissions {
static referencePermission = new Permissions();
VIPLIST: PermissionFields = new PermissionFields();
BILLING: PermissionFields = new PermissionFields();
SITE: PermissionFields = new PermissionFields();
SHOP: PermissionFields = new PermissionFields();
POLL: PermissionFields = new PermissionFields();
INVITE: PermissionFields = new PermissionFields();
MAP: PermissionFields = new PermissionFields();
COMPO: PermissionFields = new PermissionFields();
FOOD: PermissionFields = new PermissionFields();
USER: PermissionFields = new PermissionFields();
TOURNAMENT: PermissionFields = new PermissionFields();
CREDIT: PermissionFields = new PermissionFields();
LECTURE: PermissionFields = new PermissionFields();
constructor() {
}
/**
*
* Returns function, which will check user permissions against desired roles.
*
* Returned function can be used as a permission checked.
*
*
* @param requiredPermissions Permissions which are allowed. Example: {VIPLIST: 'ADMIN', BILLING: 'INFO'}
* @constructor
*/
static GeneratePermissionFunction(allowedPermissions: any): ((permissions: Permissions) => boolean) {
// no defines, always true
if (!allowedPermissions) {
return function (permissions: Permissions): boolean {
return true;
};
}
// Check if all permissions exists
// Think this as a runtime typecheck
for (const target in allowedPermissions) {
if (!allowedPermissions.hasOwnProperty(target)) {
continue;
}
if (!(target in Permissions.referencePermission)) {
throw Error('Invalid permission key ' + target);
}
switch (allowedPermissions[target]) {
default:
throw Error('Invalid permission role ' + allowedPermissions[target]);
case 'ADMIN':
case 'INFO':
case 'USER':
}
}
return function (permissions: Permissions): boolean {
// Go trought allowed permissions. If permissions contains allowed permission, return true.
for (const target in allowedPermissions) {
if (permissions[target][allowedPermissions[target]]) {
return true;
}
}
// no permission found, return false
return false;
};
}
}
......@@ -16,6 +16,8 @@ export class User {
static fragments = gql`
fragment userPrimitives on User {
address
......@@ -56,5 +58,18 @@ export class User {
town: string;
constructor() { }
public static NewUser(user) {
if (user === null) {
return new User();
}
return Object.assign(new User(), user);
}
public isAnonymous(): boolean {
return !this.login || this.login === 'anonymous';
}
}
import { TestBed, inject } from '@angular/core/testing';
import { SessionServiceService } from './session-service.service';
describe('SessionServiceService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [SessionServiceService]
});
});
it('should be created', inject([SessionServiceService], (service: SessionServiceService) => {
expect(service).toBeTruthy();
}));
});
import {Injectable} from '@angular/core';
import gql from 'graphql-tag';
import {Permissions} from '../models/permissions.model';
import {Apollo, QueryRef} from 'apollo-angular';
import {BehaviorSubject, Observable, Observer, Subject} from 'rxjs';
import {MOYA_BASE_URL, MOYA_REST_URL} from '../config/moya.config';
import {map, share} from 'rxjs/operators';
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
import {EventUser} from '../models/event-user.model';
import {User} from '../models/user.model';
import {Router} from '@angular/router';
const __REFRESH_TIME_MINUTES = 5;
export const Q_USER_PERMISSIONS = gql`
{
user(id: 0) {
permissions {
feature
admin
info
user
}
}
}
`;
class ApiPermissionType {
feature: string;
admin: boolean;
user: boolean;
info: boolean;
}
class ApiPermissionRoot {
currentuser: { permissions: ApiPermissionType[] };
}
@Injectable({
providedIn: 'root'
})
export class SessionServiceService {
private permissionsObservable: Observable<Permissions>;
private permissionsApollo: QueryRef<ApiPermissionRoot>;
private currentuserSubject: BehaviorSubject<User>;
constructor(private apollo: Apollo, private http: HttpClient, private router: Router) {
// Permissions apollo call.
// rewrite permissions from API -format to moya-angular -format
//
// From:
// [{feature: 'VIPLIST', user:true, admin: false, info:false}, {feature: 'USERLIST', user:true, admin: false, info:false}]
// To:
// {VIPLIST: {USER: true, ADMIN: false, INFO: false}, USERLIST: {USER: true, ADMIN: false, INFO: false}}
this.permissionsApollo = this.apollo.watchQuery<ApiPermissionRoot>({query: Q_USER_PERMISSIONS, pollInterval: __REFRESH_TIME_MINUTES * 60000});
this.permissionsObservable = this.permissionsApollo.valueChanges
.pipe(map((perms) => perms.data.currentuser.permissions
.reduce((permObject, perm) => (permObject[perm.feature] = {'ADMIN': perm.admin, 'USER': perm.user, 'INFO': perm.info}), {}) as Permissions));
// make subject to share the current user.
// This way we can later on update it to everyone who is listening this beautiful observable
this.currentuserSubject = new BehaviorSubject<User>(null);
this.updateCurrentUser();
}
public getPermissions(): Observable<Permissions> {
return this.permissionsObservable;
}
/**
* Returns observable with current user. If we are not logged in, current user is Anonymous.
*
* If there is any change of user status, new user is automatically pushed into this observable.
*
* TODO: Change User to EventUser, and use prober currentUser -api
*/
public getCurrentUser(): Observable<User> {
return this.currentuserSubject.pipe(map(user => User.NewUser(user)));
}
/**
* Request update of current user.
*
* This will push currently logged in user into currentUser -observable.
*/
public updateCurrentUser() {
this.http.get<User>(MOYA_REST_URL + '/v2/user/current').subscribe(
(currentUser) => {
this.currentuserSubject.next(currentUser);
});
}
/**
* Check loginerrors from returned observable
*
* @param username
* @param password
*/
public doLogin(username: string, password: string): Observable<Object> {
let params = new HttpParams();
params = params.set('username', username);
params = params.set('password', password);
const apolloObservable = this.http.post(MOYA_REST_URL + '/user/auth', params, {
headers: new HttpHeaders()
.set('Content-Type', 'application/x-www-form-urlencoded')
})
.pipe(map(x => x as boolean),
share()
);
apolloObservable.subscribe((x) => {
this.permissionsApollo.refetch();
this.updateCurrentUser();
});
return apolloObservable;
}
public logout() {
// Do login with unexiting user and password, this does also logout
let params = new HttpParams();
params = params.set('username', 'anonymous');
params = params.set('password', 'notValidPassword');
const apolloObservable = this.http.post(MOYA_REST_URL + '/user/auth', params, {
headers: new HttpHeaders()
.set('Content-Type', 'application/x-www-form-urlencoded')
}).subscribe(x => x, x => x, () => {
this.permissionsApollo.refetch();
this.updateCurrentUser();
this.router.navigateByUrl('/');
});
}
}
......@@ -23,5 +23,4 @@ export class UserService {
return this.cacheService.cacheObservable('moya:UserService', path,
this.http.get<User>(path).pipe(tap(v => {console.log('getting user outside of cache', path); })));
}
}
......@@ -2,6 +2,22 @@
"test": {
"user": "User"
},
"user": {
"username": "Username",
"password": "Password"
},
"login": {
"login": "Login",
"error": "Error on login",
"ok": "Login success",
"title": "Login",
"submitting": "Loggin in",
"logout": "Logout"
},
"general": {
"yes": "Yes",
"no": "No",
......
......@@ -2,6 +2,21 @@
"test": {
"user": "Käyttäjä"
},
"user": {
"username": "Käyttäjänimi",
"password": "Salasana"
},
"login": {
"login": "Kirjaudu",
"error": "Virhe sisäänkirjautumisessa",
"ok": "Kirjautuminen onnistui",
"title": "Kirjaudu",
"submitting": "Kirjaudutaan",
"logout": "Uloskirjaudu"
},
"general": {
"yes": "Kyllä",
"no": "Ei",
......
......@@ -2,6 +2,24 @@
"test": {
"user": "Användare"
},
"user": {
"username": "Användarnamn",
"password": "Lösenord"
},
"login": {
"login": "Logga in",
"error": "Fel vid inloggning",
"ok": "Logga in framgång",
"title": "Titel",
"submitting": "Skicka in",
"logout": "Logga ut"
},
"general": {
"yes": "Ja",
"no": "Nej",
......
......@@ -2,6 +2,7 @@
@import url('https://fonts.googleapis.com/css?family=Roboto');
@import '~@angular/material/prebuilt-themes/indigo-pink.css';
/*@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';*/
body {
background-color: #f2f3f9;
......
{
"/MoyaWeb": {
"target": "https://localproxy.test.moya.fi",
"secure": true,
"headers": {"host":"localproxy.test.moya.fi" }
}
}
......@@ -13,6 +13,7 @@
],
"lib": [
"es2018",
"es2016",
"dom",
"esnext.asynciterable"
],
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!