Commit 593ea1d2 by Tuomas Riihimäki

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

Graphql backend fixes and Apollo graphql -api for frontend

See merge request !416
2 parents 6ee7b5b9 7d108edd
Showing with 388 additions and 239 deletions
package-lock=false
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"version": 1, "version": 1,
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {
"angular6testi": { "moya-angular": {
"root": "", "root": "",
"sourceRoot": "src", "sourceRoot": "src",
"projectType": "application", "projectType": "application",
...@@ -48,18 +48,18 @@ ...@@ -48,18 +48,18 @@
"serve": { "serve": {
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"options": { "options": {
"browserTarget": "angular6testi:build" "browserTarget": "moya-angular:build"
}, },
"configurations": { "configurations": {
"production": { "production": {
"browserTarget": "angular6testi:build:production" "browserTarget": "moya-angular:build:production"
} }
} }
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n", "builder": "@angular-devkit/build-angular:extract-i18n",
"options": { "options": {
"browserTarget": "angular6testi:build" "browserTarget": "moya-angular:build"
} }
}, },
"test": { "test": {
...@@ -93,7 +93,8 @@ ...@@ -93,7 +93,8 @@
} }
} }
}, },
"angular6testi-e2e": {
"moya-angular-e2e": {
"root": "e2e", "root": "e2e",
"sourceRoot": "e2e", "sourceRoot": "e2e",
"projectType": "application", "projectType": "application",
...@@ -102,7 +103,7 @@ ...@@ -102,7 +103,7 @@
"builder": "@angular-devkit/build-angular:protractor", "builder": "@angular-devkit/build-angular:protractor",
"options": { "options": {
"protractorConfig": "./protractor.conf.js", "protractorConfig": "./protractor.conf.js",
"devServerTarget": "angular6testi:serve" "devServerTarget": "moya-angular:serve"
} }
}, },
"lint": { "lint": {
...@@ -119,7 +120,7 @@ ...@@ -119,7 +120,7 @@
} }
} }
}, },
"defaultProject": "angular6testi", "defaultProject": "moya-angular",
"schematics": { "schematics": {
"@schematics/angular:component": { "@schematics/angular:component": {
"prefix": "moya", "prefix": "moya",
...@@ -129,4 +130,4 @@ ...@@ -129,4 +130,4 @@
"prefix": "moya" "prefix": "moya"
} }
} }
} }
\ No newline at end of file
...@@ -29,15 +29,21 @@ ...@@ -29,15 +29,21 @@
"@angular/router": "6.1.1", "@angular/router": "6.1.1",
"@ngx-translate/core": "^9.0.0", "@ngx-translate/core": "^9.0.0",
"@ngx-translate/http-loader": "^2.0.0", "@ngx-translate/http-loader": "^2.0.0",
"apollo-angular": "^1.1.2",
"apollo-angular-link-http": "^1.1.1",
"apollo-cache-inmemory": "^1.2.6",
"apollo-client": "^2.3.7",
"bootstrap": "^4.0.0", "bootstrap": "^4.0.0",
"core-js": "^2.5.1", "core-js": "^2.5.1",
"graphql": "^0.13.2",
"graphql-tag": "^2.9.2",
"ngx-bootstrap": "^2.0.2", "ngx-bootstrap": "^2.0.2",
"rxjs": "^6.2.2", "rxjs": "^6.2.2",
"ts-helpers": "^1.1.1", "ts-helpers": "^1.1.1",
"zone.js": "^0.8.26" "zone.js": "^0.8.26"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~0.7.0", "@angular-devkit/build-angular": "~0.7.2",
"@angular/cdk": "^6.4.2", "@angular/cdk": "^6.4.2",
"@angular/cli": "6.1.2", "@angular/cli": "6.1.2",
"@angular/compiler-cli": "6.1.1", "@angular/compiler-cli": "6.1.1",
......
...@@ -16,6 +16,9 @@ import { NgModule } from '@angular/core'; ...@@ -16,6 +16,9 @@ import { NgModule } from '@angular/core';
import {LoginModule} from './modules/login/login.module'; import {LoginModule} from './modules/login/login.module';
import {LeftMenuModule} from './menu/left-menu/left-menu.module'; import {LeftMenuModule} from './menu/left-menu/left-menu.module';
import { FrontpageComponent } from './components/frontpage/frontpage.component'; 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';
@NgModule({ @NgModule({
declarations: [ declarations: [
...@@ -32,6 +35,9 @@ import { FrontpageComponent } from './components/frontpage/frontpage.component'; ...@@ -32,6 +35,9 @@ import { FrontpageComponent } from './components/frontpage/frontpage.component';
LoginModule, LoginModule,
LeftMenuModule, LeftMenuModule,
ApolloModule,
HttpLinkModule,
AlertModule.forRoot(), AlertModule.forRoot(),
...@@ -50,6 +56,7 @@ import { FrontpageComponent } from './components/frontpage/frontpage.component'; ...@@ -50,6 +56,7 @@ import { FrontpageComponent } from './components/frontpage/frontpage.component';
LocaleService, LocaleService,
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
UserService, UserService,
{ provide: APOLLO_OPTIONS, useFactory: createApollo, deps: [HttpLink]},
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
......
...@@ -3,10 +3,10 @@ import {map, catchError} from 'rxjs/operators'; ...@@ -3,10 +3,10 @@ import {map, catchError} from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import {Observable, of } from 'rxjs'; import {Observable, of } from 'rxjs';
import {MoyaLocale} from './moya-locale.model'; import {MoyaLocale} from './moya-locale.model';
import {MOYA_BASE_URL} from '../../shared/tools/moya-rest.tool';
import {HttpClient} from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import {TranslateService} from '@ngx-translate/core'; import {TranslateService} from '@ngx-translate/core';
import {MOYA_REST_URL} from '../../shared/config/moya.config';
export const ENGLISH = 'en'; export const ENGLISH = 'en';
...@@ -71,7 +71,7 @@ export class LocaleService { ...@@ -71,7 +71,7 @@ export class LocaleService {
return new Observable<string>(x => x.next(this.selectedLocale)); return new Observable<string>(x => x.next(this.selectedLocale));
} }
return this.http.get<MoyaLocale>(MOYA_BASE_URL + '/v3/locale/').pipe( return this.http.get<MoyaLocale>(MOYA_REST_URL + '/v3/locale/').pipe(
catchError(x => catchError(x =>
of({} as MoyaLocale) of({} as MoyaLocale)
), ),
...@@ -109,7 +109,7 @@ export class LocaleService { ...@@ -109,7 +109,7 @@ export class LocaleService {
}; };
// let's save locale to database, if it fails, we save it into localstorage. No errors to show for user. // let's save locale to database, if it fails, we save it into localstorage. No errors to show for user.
this.http.post(MOYA_BASE_URL + '/v3/locale/', newLocale).pipe(catchError(x => { this.http.post(MOYA_REST_URL + '/v3/locale/', newLocale).pipe(catchError(x => {
localStorage.setItem(LOCALSTORAGE_NAME, locale); localStorage.setItem(LOCALSTORAGE_NAME, locale);
return 'ok'; return 'ok';
})).subscribe(); })).subscribe();
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<mat-table #table [dataSource]="vips | async"> <mat-table #table [dataSource]="vips | async">
<ng-container matColumnDef="host"> <ng-container matColumnDef="host">
<mat-header-cell *matHeaderCellDef translate >vip.host </mat-header-cell> <mat-header-cell *matHeaderCellDef translate >vip.host </mat-header-cell>
<mat-cell *matCellDef="let v" > {{v.host.firstname}} {{ v.host.lastname}} </mat-cell> <mat-cell *matCellDef="let v" > {{v.host.user.firstname}} {{ v.host.user.lastname}} </mat-cell>
</ng-container> </ng-container>
<ng-container matColumnDef="shortdescr"> <ng-container matColumnDef="shortdescr">
......
/** /**
* Created by tuukka on 04/02/17. * Created by tuukka on 04/02/17.
*/ */
import gql from 'graphql-tag';
export class VipProductDelivery { export class VipProductDelivery {
/* static fragments = gql`
public Integer id; fragment vipProductDeliveryPrimitives on VipProductDelivery {
public Integer delivererId; deliverytime
public BigDecimal quantity; id
public Date deliveryTime; notes
public String notes; quantity
*/ }
`;
id: number; id: number;
delivererId: number; delivererId: number;
......
import {VipProductDelivery} from './vip-product-delivery.model'; import {VipProductDelivery} from './vip-product-delivery.model';
import gql from 'graphql-tag';
/** /**
* Created by tuukka on 04/02/17. * Created by tuukka on 04/02/17.
*/ */
...@@ -7,17 +8,14 @@ import {VipProductDelivery} from './vip-product-delivery.model'; ...@@ -7,17 +8,14 @@ import {VipProductDelivery} from './vip-product-delivery.model';
export class VipProduct { export class VipProduct {
/* static fragments = gql`
fragment vipProductPrimitives on VipProduct {
public Integer id; id
public String name; name
public Integer productId; notes
public String notes; quantity
public BigDecimal quantity; }
public BigDecimal delivered; `;
public List<VipProductDeliveryPojo> deliveries = new ArrayList<>();
*/
id: number; id: number;
name: string; name: string;
......
import {VipProduct} from './vip-product.model'; import {VipProduct} from './vip-product.model';
import {User} from '../../../shared/models/user.model'; import {User} from '../../../shared/models/user.model';
import gql from 'graphql-tag';
import {VipProductDelivery} from './vip-product-delivery.model';
/** /**
* Created by tuukka on 04/02/17. * Created by tuukka on 04/02/17.
*/ */
...@@ -8,17 +10,14 @@ import {User} from '../../../shared/models/user.model'; ...@@ -8,17 +10,14 @@ import {User} from '../../../shared/models/user.model';
export class Vip { export class Vip {
/* static fragments = gql`
fragment vipPrimitives on Vip {
public Integer id; created
public String description; description
public String shortdescr; id
public Date created; shortdescr
public Integer eventuserId; }
public Integer creatorId; `;
public Integer hostId;
public List<VipProductPojo> products = new ArrayList<>();
*/
id: number; id: number;
description: string; description: string;
...@@ -35,3 +34,9 @@ export class Vip { ...@@ -35,3 +34,9 @@ export class Vip {
} }
} }
export const VipFragmentsCombined = gql`
${Vip.fragments}
${VipProduct.fragments}
${VipProductDelivery.fragments}
`;
import {forkJoin as observableForkJoin, Observable} from 'rxjs'; import {forkJoin as observableForkJoin, Observable} from 'rxjs';
import {first, switchMap, map} from 'rxjs/operators';
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {Vip} from './models/vip.model'; import {Vip, VipFragmentsCombined} from './models/vip.model';
import {User} from '../../shared/models/user.model'; import {User} from '../../shared/models/user.model';
import {UserService} from '../../shared/services/user.service'; import {UserService} from '../../shared/services/user.service';
import {HttpClient} from '@angular/common/http';
import {MOYA_BASE_URL, MOYA_REST_URL} from '../../shared/config/moya.config';
import gql from 'graphql-tag';
import {EventUser} from '../../shared/models/event-user.model';
import {Apollo} from 'apollo-angular';
import {first, map} from 'rxjs/operators';
import {Error} from 'tslint/lib/error';
export const Q_VIPS_N_DATA = gql`
{
vips {
...vipPrimitives
products {
...vipProductPrimitives
deliveries {
...vipProductDeliveryPrimitives
}
}
creator {
...eventUserPrimitives
user {
...userPrimitives
}
}
host {
...eventUserPrimitives
user {
...userPrimitives
}
}
}
}
${VipFragmentsCombined}
${EventUser.fragments}
${User.fragments}
`;
class VipsQueryRoot {
vips: Array<Vip>;
}
import {HttpClient} from '@angular/common/http';
import {MOYA_BASE_URL} from '../../shared/tools/moya-rest.tool';
@Injectable() @Injectable()
export class ViplistService { export class ViplistService {
constructor(private http: HttpClient, private userService: UserService) {
constructor(private apollo: Apollo, private http: HttpClient, private userService: UserService) {
} }
/** /**
* get vips * get vips
* @param searchString: searchString, skip to return all vips
*/ */
public get(searchString?: string): Observable<Array<Vip>> { public get(): Observable<Array<Vip>> {
return this.apollo.watchQuery<VipsQueryRoot>({query: Q_VIPS_N_DATA}).valueChanges.pipe(map(x => x.data.vips));
if (!searchString) {
return this.http.get(MOYA_BASE_URL + 'v3/vip/all').pipe(
switchMap(res => observableForkJoin(...(res as Array<any>), map(apiRow => this.hostPopulator(apiRow)))));
}
return this.http.get(MOYA_BASE_URL + 'v3/vip/search/' + searchString).pipe(
switchMap(v => observableForkJoin(...(v as Array<any>), map(x => this.hostPopulator(x)))));
} }
...@@ -49,9 +80,7 @@ export class ViplistService { ...@@ -49,9 +80,7 @@ export class ViplistService {
throw new Error('TODO: errori, tyhmä vippi'); throw new Error('TODO: errori, tyhmä vippi');
} }
const res: any = await this.http.delete(MOYA_BASE_URL + 'v3/vip/' + vip.id).pipe( const res: any = await this.http.delete(MOYA_BASE_URL + 'v3/vip/' + vip.id).pipe(first()).toPromise();
first())
.toPromise();
return res.ok; return res.ok;
} }
...@@ -59,21 +88,13 @@ export class ViplistService { ...@@ -59,21 +88,13 @@ export class ViplistService {
public getWithId(id: number): Observable<Vip> { public getWithId(id: number): Observable<Vip> {
return this.http.get(MOYA_BASE_URL + 'v3/vip/' + id).pipe(map(x => x as Vip)); return this.http.get(MOYA_BASE_URL + 'v3/vip/' + id).pipe(map(x => x as Vip));
} }
public create(vip: Vip): Observable<Vip> { public create(vip: Vip): Observable<Vip> {
return this.http.post(MOYA_BASE_URL + 'v3/vip/create', vip).pipe(map(x => x as Vip)); return this.http.post(MOYA_BASE_URL + 'v3/vip/create', vip).pipe(map(x => x as Vip));
} }
private hostPopulator(rawVip: any): Observable<Vip> {
return this.userService.get(rawVip.hostId).pipe(
map((u: User) => {rawVip.host = u; return <Vip> rawVip; }), map(x => x as Vip), );
}
} }
......
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 function createApollo(httpLink: HttpLink) {
return {
link: httpLink.create({uri: MOYA_BASE_URL + 'graphql'}),
cache: new InMemoryCache(),
};
}
import {User} from './user.model';
import gql from 'graphql-tag';
export enum UserGender {
MALE,
FEMALE,
UNSPECIFIED
}
export class EventUser {
static fragments = gql`
fragment eventUserPrimitives on EventUser {
id
eventusercreated
}
`;
id: number;
eventusercreated: Date;
user: User;
constructor() { }
}
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
// vim magic: %s/^\s*\w\+\s\+\(\w\+\)\s\+\(\w\+\)\s*.*;$/ \2: \L\1;/ // vim magic: %s/^\s*\w\+\s\+\(\w\+\)\s\+\(\w\+\)\s*.*;$/ \2: \L\1;/
import gql from 'graphql-tag';
export enum UserGender { export enum UserGender {
MALE, MALE,
FEMALE, FEMALE,
...@@ -12,10 +14,33 @@ export enum UserGender { ...@@ -12,10 +14,33 @@ export enum UserGender {
export class User { export class User {
static fragments = gql`
fragment userPrimitives on User {
address
allergiesFreetext
birthday
email
firstnames
gender
id:
lastname
login
nick
phone
town
shirtSize
town
zip
}
`;
nick: string; nick: string;
login: string; login: string;
eventuserId: number; id: number;
userId: number;
firstname: string; firstname: string;
lastname: string; lastname: string;
password: string; password: string;
...@@ -23,12 +48,12 @@ export class User { ...@@ -23,12 +48,12 @@ export class User {
birthday: Date; birthday: Date;
gender: UserGender; gender: UserGender;
phoneNumber: string; phone: string;
email: string; email: string;
streetAddress: string; streetAddress: string;
zipCode: string; zip: string;
postOffice: string; town: string;
constructor() { } constructor() { }
......
...@@ -4,7 +4,7 @@ import {Injectable} from '@angular/core'; ...@@ -4,7 +4,7 @@ import {Injectable} from '@angular/core';
import {User} from '../models/user.model'; import {User} from '../models/user.model';
import {CacheService} from './cache.service'; import {CacheService} from './cache.service';
import {HttpClient} from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import {MOYA_BASE_URL} from '../tools/moya-rest.tool'; import {MOYA_REST_URL} from '../config/moya.config';
@Injectable() @Injectable()
...@@ -18,7 +18,7 @@ export class UserService { ...@@ -18,7 +18,7 @@ export class UserService {
return observableThrowError('There should be userid'); return observableThrowError('There should be userid');
} }
const path = MOYA_BASE_URL + 'v2/user/' + id; const path = MOYA_REST_URL + 'v2/user/' + id;
return this.cacheService.cacheObservable('moya:UserService', path, return this.cacheService.cacheObservable('moya:UserService', path,
this.http.get<User>(path).pipe(tap(v => {console.log('getting user outside of cache', path); }))); this.http.get<User>(path).pipe(tap(v => {console.log('getting user outside of cache', path); })));
......
export const MOYA_BASE_URL = '/MoyaWeb/rest/';
export abstract class MoyaRestTool {
/*
genUrl(subUrl: string, urlParams?: Map<string, string>): string {
let suffix = '';
if (urlParams) {
urlParams.forEach(function(value: string, key: string) {
if (suffix.length <= 0) {
suffix = '?';
} else {
suffix += '&';
}
suffix += key + '=' + value;
});
}
return '/MoyaWeb/rest/' + subUrl + suffix; // <-- TODO: kauneista
} */
}
...@@ -12,10 +12,11 @@ ...@@ -12,10 +12,11 @@
"node_modules/@types" "node_modules/@types"
], ],
"lib": [ "lib": [
"es2017", "es2018",
"dom" "dom",
"esnext.asynciterable"
], ],
"module": "es2015", "module": "es2015",
"baseUrl": "./" "baseUrl": "./"
} }
} }
\ No newline at end of file
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
"label-position": true, "label-position": true,
"max-line-length": [ "max-line-length": [
true, true,
140 200
], ],
"member-access": false, "member-access": false,
"member-ordering": [ "member-ordering": [
......
...@@ -626,6 +626,11 @@ public class BootstrapBean implements BootstrapBeanLocal { ...@@ -626,6 +626,11 @@ public class BootstrapBean implements BootstrapBeanLocal {
"ALTER TABLE compo_voting_roles ADD CONSTRAINT FK_compo_voting_roles_scheme_id FOREIGN KEY (scheme_id) REFERENCES compo_voting_schemes (id)", "ALTER TABLE compo_voting_roles ADD CONSTRAINT FK_compo_voting_roles_scheme_id FOREIGN KEY (scheme_id) REFERENCES compo_voting_schemes (id)",
"ALTER TABLE compo_voting_roles ADD CONSTRAINT FK_compo_voting_roles_role_id FOREIGN KEY (role_id) REFERENCES roles (id)", "ALTER TABLE compo_voting_roles ADD CONSTRAINT FK_compo_voting_roles_role_id FOREIGN KEY (role_id) REFERENCES roles (id)",
}); });
dbUpdates.add(new String[]{
"ALTER TABLE users DROP COLUMN postal_town;"
});
} }
......
...@@ -131,7 +131,6 @@ public class TestDataBean implements TestDataBeanLocal { ...@@ -131,7 +131,6 @@ public class TestDataBean implements TestDataBeanLocal {
u.resetPassword("kavija"); u.resetPassword("kavija");
u.setPhone("123-45679854"); u.setPhone("123-45679854");
u.setTown("Keikyän MLK"); u.setTown("Keikyän MLK");
u.setPostalTown("Keykyä");
u.setZip("393929"); u.setZip("393929");
userFacade.create(u); userFacade.create(u);
return u; return u;
...@@ -157,7 +156,6 @@ public class TestDataBean implements TestDataBeanLocal { ...@@ -157,7 +156,6 @@ public class TestDataBean implements TestDataBeanLocal {
u.resetPassword("admin"); u.resetPassword("admin");
u.setPhone("1337"); u.setPhone("1337");
u.setTown("Adminila"); u.setTown("Adminila");
u.setPostalTown("Adminila ");
u.setZip("6666"); u.setZip("6666");
// u.setSuperadmin(true); // u.setSuperadmin(true);
userFacade.create(u); userFacade.create(u);
......
...@@ -194,9 +194,6 @@ public class UserFacade extends IntegerPkGenericFacade<User> { ...@@ -194,9 +194,6 @@ public class UserFacade extends IntegerPkGenericFacade<User> {
case "zip": case "zip":
sort = User_.zip; sort = User_.zip;
break; break;
case "postalTown":
sort = User_.postalTown;
break;
case "town": case "town":
sort = User_.town; sort = User_.town;
break; break;
......
...@@ -421,14 +421,6 @@ public class EventUser extends GenericEntity { ...@@ -421,14 +421,6 @@ public class EventUser extends GenericEntity {
return user.isSuperadmin(); return user.isSuperadmin();
} }
public void setPostalTown(String postalTown) {
user.setPostalTown(postalTown);
}
public String getPostalTown() {
return user.getPostalTown();
}
public void setGender(Gender gender) { public void setGender(Gender gender) {
user.setGender(gender); user.setGender(gender);
} }
......
...@@ -112,10 +112,6 @@ public interface IUser { ...@@ -112,10 +112,6 @@ public interface IUser {
public abstract boolean isSuperadmin(); public abstract boolean isSuperadmin();
public abstract void setPostalTown(String postalTown);
public abstract String getPostalTown();
public abstract void setGender(Gender gender); public abstract void setGender(Gender gender);
public abstract Gender getGender(); public abstract Gender getGender();
......
...@@ -96,9 +96,6 @@ public class User extends GenericEntity implements IUser { ...@@ -96,9 +96,6 @@ public class User extends GenericEntity implements IUser {
@Column(name = "zip") @Column(name = "zip")
private String zip = ""; private String zip = "";
@Column(name = "postal_town")
private String postalTown = "";
@Column(name = "town") @Column(name = "town")
private String town = ""; private String town = "";
...@@ -349,15 +346,6 @@ public class User extends GenericEntity implements IUser { ...@@ -349,15 +346,6 @@ public class User extends GenericEntity implements IUser {
return superadmin; return superadmin;
} }
@Override
public void setPostalTown(String postalTown) {
this.postalTown = postalTown;
}
@Override
public String getPostalTown() {
return postalTown;
}
@Override @Override
public void setGender(Gender gender) { public void setGender(Gender gender) {
......
...@@ -29,7 +29,7 @@ public class VipProduct extends GenericEntity { ...@@ -29,7 +29,7 @@ public class VipProduct extends GenericEntity {
/** /**
* If product is null this is used as the name of the product * If product is null this is used as the name of the product
*/ */
private String name; private String name = "";
@ManyToOne() @ManyToOne()
@JoinColumn(nullable = true) @JoinColumn(nullable = true)
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
<hr /> <hr />
<h:outputText value="#{user.address}" /> <h:outputText value="#{user.address}" />
<br /> <br />
<h:outputText value="#{user.zip} #{user.postalTown}" /> <h:outputText value="#{user.zip} #{user.town}" />
<br /> <br />
<br /> <br />
<h:outputText value="#{user.phone}" /> <h:outputText value="#{user.phone}" />
......
package fi.codecrew.moya.graphql; package fi.codecrew.moya.graphql;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.reflect.TypeToken;
import fi.codecrew.moya.beans.*; import fi.codecrew.moya.beans.*;
import fi.codecrew.moya.entitysearch.UserSearchQuery; import fi.codecrew.moya.entitysearch.UserSearchQuery;
import fi.codecrew.moya.enums.apps.UserPermission; import fi.codecrew.moya.enums.apps.UserPermission;
...@@ -12,6 +15,7 @@ import fi.codecrew.moya.utilities.SearchResult; ...@@ -12,6 +15,7 @@ import fi.codecrew.moya.utilities.SearchResult;
import graphql.*; import graphql.*;
import graphql.schema.*; import graphql.schema.*;
import graphql.schema.idl.SchemaPrinter; import graphql.schema.idl.SchemaPrinter;
import org.primefaces.json.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -31,6 +35,7 @@ import java.io.IOException; ...@@ -31,6 +35,7 @@ import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import static graphql.Scalars.*; import static graphql.Scalars.*;
...@@ -54,6 +59,9 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -54,6 +59,9 @@ public class MoyaGraphQLServlet extends HttpServlet {
private PermissionBeanLocal permbean; private PermissionBeanLocal permbean;
@EJB @EJB
private VipBeanLocal vipBean;
@EJB
private RoleBeanLocal rolebean; private RoleBeanLocal rolebean;
@EJB @EJB
private AllergyBeanLocal allergybean; private AllergyBeanLocal allergybean;
...@@ -120,25 +128,61 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -120,25 +128,61 @@ public class MoyaGraphQLServlet extends HttpServlet {
} }
} }
graphql.ExecutionInput.Builder queryBuilder = new graphql.ExecutionInput.Builder();
String query = request.getParameter("query"); String query = request.getParameter("query");
executeQuery(query, response);
if(query == null) {
String q = request.getReader().lines().collect(Collectors.joining());
Map<String, Object> queryJson = new Gson()
.fromJson(
q,
new TypeToken<Map<String, Object>>(){}
.getType());
query = queryJson.get("query").toString();
//Map varJson = new Gson().fromJson(queryJson.get("variables"), Map.class);
//if (queryJson.get("variables") instanceof LinkedTreeMap)
queryBuilder.variables((LinkedTreeMap) queryJson.get("variables"));
}
queryBuilder.query(query);
executeQuery(queryBuilder, response);
} }
private void executeQuery(String query, HttpServletResponse response) throws IOException { private void executeQuery(ExecutionInput.Builder query, HttpServletResponse response) throws IOException {
GraphQL graphql = GraphQL.newGraphQL(schema).build(); GraphQL graphql = GraphQL.newGraphQL(schema).build();
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter(); PrintWriter writer = response.getWriter();
try { try {
Gson gson = new Gson(); Gson gson = new GsonBuilder().serializeNulls().create();
ExecutionResult exec = graphql.execute(query); ExecutionResult exec = graphql.execute(query);
logger.warn("Executed stuff errors: {}", exec.getErrors()); logger.warn("Executed stuff errors: {}", exec.getErrors());
if (exec.getErrors() != null && !exec.getErrors().isEmpty()) {
writer.write(gson.toJson(new ErrorContainer(exec.getErrors()))); Map<String, Object> returnMap = new HashMap<>();
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
if (exec.getData() != null) {
returnMap.put("data", exec.getData());
} else { } else {
Map<String, Object> data = exec.getData(); returnMap.put("data", "");
writer.write(gson.toJson(data));
} }
if (exec.getErrors() != null) {
returnMap.put("errors", exec.getErrors());
} else {
returnMap.put("errors", "");
}
writer.write(gson.toJson(returnMap));
} catch (Exception e) { } catch (Exception e) {
logger.warn("Got exception at graphql ", e); logger.warn("Got exception at graphql ", e);
writer.write("Error completing the query because of exception: " + e.getMessage()); writer.write("Error completing the query because of exception: " + e.getMessage());
...@@ -217,19 +261,19 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -217,19 +261,19 @@ public class MoyaGraphQLServlet extends HttpServlet {
return stringWriter.toString(); return stringWriter.toString();
}).description("Returns the object as a string."); }).description("Returns the object as a string.");
b.addField(Map.Entry.class) b.addField(Map.Entry.class)
.argument(newArgument().name("keys").defaultValue(Collections.emptyMap()).type(GraphQLList.list(GraphQLString))) .argument(newArgument().name("keys").defaultValue(Collections.emptyMap()).type(GraphQLList.list(GraphQLString)))
.name("path") .name("path")
.description("Returngs values from given keys as key, value pairs") .description("Returngs values from given keys as key, value pairs")
.dataFetcher(environment -> { .dataFetcher(environment -> {
Map<String, String> ret = new HashMap<>(); Map<String, String> ret = new HashMap<>();
final JsonObject jsonObj = environment.getSource(); final JsonObject jsonObj = environment.getSource();
((List<String>) environment.getArgument("keys")).forEach(key -> { ((List<String>) environment.getArgument("keys")).forEach(key -> {
if (jsonObj.containsKey(key)) ret.put(key, jsonObj.getString(key)); if (jsonObj.containsKey(key)) ret.put(key, jsonObj.getString(key));
});
return ret.entrySet();
}); });
return ret.entrySet();
});
} }
{ {
EntityGQLBuilder<Role> b = builder.createEntity(Role.class); EntityGQLBuilder<Role> b = builder.createEntity(Role.class);
...@@ -282,7 +326,6 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -282,7 +326,6 @@ public class MoyaGraphQLServlet extends HttpServlet {
b.addField(EventUser_.user); b.addField(EventUser_.user);
b.addField(EventUser_.event).type(builder.typeFor(SIMPLE_EVENT_TYPE_NAME)); b.addField(EventUser_.event).type(builder.typeFor(SIMPLE_EVENT_TYPE_NAME));
b.addField(EventUser_.eventuserCreated); b.addField(EventUser_.eventuserCreated);
b.addField(EventUser_.id);
b.addListField(Role.class).dataFetcher(environment -> userbean.findUsersRoles(environment.getSource())); b.addListField(Role.class).dataFetcher(environment -> userbean.findUsersRoles(environment.getSource()));
b.addListField(UsersEventUserproperty.class).dataFetcher(environment -> eventUserPropertyBean.getUserPropertiesForUser(environment.getSource()).stream().sorted(ENTITY_ID_SORTER).collect(toList())); b.addListField(UsersEventUserproperty.class).dataFetcher(environment -> eventUserPropertyBean.getUserPropertiesForUser(environment.getSource()).stream().sorted(ENTITY_ID_SORTER).collect(toList()));
b.addField(EventUser_.currentPlaces); b.addField(EventUser_.currentPlaces);
...@@ -322,7 +365,6 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -322,7 +365,6 @@ public class MoyaGraphQLServlet extends HttpServlet {
b.addField(User_.email); b.addField(User_.email);
b.addField(User_.address); b.addField(User_.address);
b.addField(User_.zip); b.addField(User_.zip);
b.addField(User_.postalTown);
b.addField(User_.town); b.addField(User_.town);
b.addField(User_.phone); b.addField(User_.phone);
b.addField(User_.gender); b.addField(User_.gender);
...@@ -330,25 +372,25 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -330,25 +372,25 @@ public class MoyaGraphQLServlet extends HttpServlet {
b.addField().name("shirtSize").type(GraphQLString).dataFetcher(new JSONPropertyDataFetcher("meta", "shirtSize")); b.addField().name("shirtSize").type(GraphQLString).dataFetcher(new JSONPropertyDataFetcher("meta", "shirtSize"));
b.addField().name("allergiesFreetext").type(GraphQLString).dataFetcher(new JSONPropertyDataFetcher("meta", "allergies")); b.addField().name("allergiesFreetext").type(GraphQLString).dataFetcher(new JSONPropertyDataFetcher("meta", "allergies"));
b.addListField(UsersAllergy.class) b.addListField(UsersAllergy.class)
.name("allergies") .name("allergies")
.argument(newArgument() .argument(newArgument()
.description("By default, show only allergies, user has selected") .description("By default, show only allergies, user has selected")
.type(GraphQLBoolean) .type(GraphQLBoolean)
.defaultValue(true) .defaultValue(true)
.name("onlySelected")) .name("onlySelected"))
.dataFetcher(environment -> { .dataFetcher(environment -> {
List<UsersAllergy> allergies = allergybean.getUserAllergies((User) environment.getSource()); List<UsersAllergy> allergies = allergybean.getUserAllergies((User) environment.getSource());
logger.warn("Got allergies for user {}", allergies); logger.warn("Got allergies for user {}", allergies);
if (environment.getArgument("onlySelected")) { if (environment.getArgument("onlySelected")) {
logger.warn("Filtering allergies"); logger.warn("Filtering allergies");
return allergies.stream().sorted(ENTITY_ID_SORTER).filter(a -> a.isSelected()).collect(toList()); return allergies.stream().sorted(ENTITY_ID_SORTER).filter(a -> a.isSelected()).collect(toList());
} }
return allergies; return allergies;
}); });
b.addListField(EventUser.class) b.addListField(EventUser.class)
.dataFetcher(environment -> userbean.findAllEventusers(environment.getSource()).stream().sorted(ENTITY_ID_SORTER).collect(toList())) .dataFetcher(environment -> userbean.findAllEventusers(environment.getSource()).stream().sorted(ENTITY_ID_SORTER).collect(toList()))
.description("Only users themselves can fetch eventusers for other events. If another user tries to fetch this data, an exception will be thrown"); .description("Only users themselves can fetch eventusers for other events. If another user tries to fetch this data, an exception will be thrown");
} }
...@@ -363,9 +405,9 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -363,9 +405,9 @@ public class MoyaGraphQLServlet extends HttpServlet {
b.addField(UsersAllergy_.selected); b.addField(UsersAllergy_.selected);
b.addField().type(GraphQLString) b.addField().type(GraphQLString)
.name("allergyname") .name("allergyname")
.dataFetcher(environment -> ((UsersAllergy) environment.getSource()).getAllergy().getName().getDefaultValue()) .dataFetcher(environment -> ((UsersAllergy) environment.getSource()).getAllergy().getName().getDefaultValue())
.description("Shorthand for { allergy {name {defaultvalue}}}"); .description("Shorthand for { allergy {name {defaultvalue}}}");
} }
{ {
...@@ -454,6 +496,43 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -454,6 +496,43 @@ public class MoyaGraphQLServlet extends HttpServlet {
b.addField(Discount_.meta); b.addField(Discount_.meta);
} }
{
EntityGQLBuilder<Vip> b = builder.createEntity(Vip.class);
b.addField(Vip_.id);
b.addField(Vip_.created);
b.addField(Vip_.creator);
b.addField(Vip_.description);
b.addField(Vip_.event);
b.addField(Vip_.host);
b.addField(Vip_.products);
b.addField(Vip_.shortdescr);
}
{
EntityGQLBuilder<VipProduct> b = builder.createEntity(VipProduct.class);
b.addField(VipProduct_.id);
b.addField(VipProduct_.deliveries);
b.addField(VipProduct_.name);
b.addField(VipProduct_.notes);
b.addField(VipProduct_.product);
b.addField(VipProduct_.quantity);
b.addField(VipProduct_.vip);
}
{
EntityGQLBuilder<VipProductDelivery> b = builder.createEntity(VipProductDelivery.class);
b.addField(VipProductDelivery_.id);
b.addField(VipProductDelivery_.deliverer);
b.addField(VipProductDelivery_.deliveryTime);
b.addField(VipProductDelivery_.notes);
b.addField(VipProductDelivery_.quantity);
b.addField(VipProductDelivery_.vipProduct);
}
{ {
EntityGQLBuilder<LanEvent> b = builder.createEntity(SIMPLE_EVENT_TYPE_NAME); EntityGQLBuilder<LanEvent> b = builder.createEntity(SIMPLE_EVENT_TYPE_NAME);
...@@ -464,8 +543,6 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -464,8 +543,6 @@ public class MoyaGraphQLServlet extends HttpServlet {
b.addField(LanEvent_.ticketSalesBegin); b.addField(LanEvent_.ticketSalesBegin);
b.addField(LanEvent_.domains).type(GraphQLList.list(GraphQLString)).dataFetcher(environment -> ((LanEvent) environment.getSource()).getDomains().stream().sorted(ENTITY_ID_SORTER).map(d -> d.getDomain()).collect(toList())); b.addField(LanEvent_.domains).type(GraphQLList.list(GraphQLString)).dataFetcher(environment -> ((LanEvent) environment.getSource()).getDomains().stream().sorted(ENTITY_ID_SORTER).map(d -> d.getDomain()).collect(toList()));
b.addField(LanEvent_.meta); b.addField(LanEvent_.meta);
} }
{ {
EntityGQLBuilder<Compo> b = builder.createEntity(Compo.class); EntityGQLBuilder<Compo> b = builder.createEntity(Compo.class);
...@@ -489,6 +566,7 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -489,6 +566,7 @@ public class MoyaGraphQLServlet extends HttpServlet {
{ {
EntityGQLBuilder<CompoEntry> b = builder.createEntity(CompoEntry.class); EntityGQLBuilder<CompoEntry> b = builder.createEntity(CompoEntry.class);
b.addField(CompoEntry_.id); b.addField(CompoEntry_.id);
b.addField(CompoEntry_.created); b.addField(CompoEntry_.created);
b.addField(CompoEntry_.creator); b.addField(CompoEntry_.creator);
...@@ -543,15 +621,6 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -543,15 +621,6 @@ public class MoyaGraphQLServlet extends HttpServlet {
b.addField(LanEvent_.ticketSalesBegin); b.addField(LanEvent_.ticketSalesBegin);
b.addField(LanEvent_.domains); b.addField(LanEvent_.domains);
b.addField(LanEvent_.meta); b.addField(LanEvent_.meta);
b.addListField(EventMap.class).dataFetcher(environment -> placebean.getMaps().stream().sorted(ENTITY_ID_SORTER).collect(toList()));
b.addListField(Role.class).dataFetcher(environment -> rolebean.listRoles().stream().sorted(ENTITY_ID_SORTER).collect(toList()));
b.addListField(Product.class).dataFetcher(environment -> productbean.findProductsForEvent().stream().sorted(ENTITY_ID_SORTER).collect(toList()));
b.addListField(Compo.class).dataFetcher(environment -> votebean.getCompoList(true).stream().sorted(ENTITY_ID_SORTER).collect(toList()));
b.addField(LanEvent_.id);
} }
return builder; return builder;
} }
...@@ -563,10 +632,37 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -563,10 +632,37 @@ public class MoyaGraphQLServlet extends HttpServlet {
schemaBld.additionalTypes(builder.getTypes()); schemaBld.additionalTypes(builder.getTypes());
schemaBld.query(GraphQLObjectType.newObject() schemaBld.query(GraphQLObjectType.newObject()
.name("moyaQuery") .name("moyaQuery")
.field(getUserSearchField(builder)) .field(getUserSearchField(builder))
.field(getEventSearchQuery(builder)) .field(getEventSearchQuery(builder))
.field(getSingleUserQuery(builder))); .field(getSingleUserQuery(builder))
.field(
newFieldDefinition()
.name("eventmaps")
.type(GraphQLList.list(builder.typeFor(EventMap.class)))
.dataFetcher(environment -> placebean.getMaps().stream().sorted(ENTITY_ID_SORTER).collect(toList())))
.field(
newFieldDefinition()
.name("roles")
.type(GraphQLList.list(builder.typeFor(Role.class)))
.dataFetcher(environment -> rolebean.listRoles().stream().sorted(ENTITY_ID_SORTER).collect(toList())))
.field(
newFieldDefinition()
.name("products")
.type(GraphQLList.list(builder.typeFor(Product.class)))
.dataFetcher(environment -> productbean.findProductsForEvent().stream().sorted(ENTITY_ID_SORTER).collect(toList())))
.field(
newFieldDefinition()
.name("compos")
.type(GraphQLList.list(builder.typeFor(Compo.class)))
.dataFetcher(environment -> votebean.getCompoList(true).stream().sorted(ENTITY_ID_SORTER).collect(toList())))
.field(
newFieldDefinition()
.name("vips")
.type(GraphQLList.list(builder.typeFor(Vip.class)))
.dataFetcher(environment -> vipBean.getAvailableVips()))
);
return schemaBld.build(); return schemaBld.build();
...@@ -575,63 +671,63 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -575,63 +671,63 @@ public class MoyaGraphQLServlet extends HttpServlet {
private GraphQLFieldDefinition getSingleUserQuery(GraphQLBuilder builder) { private GraphQLFieldDefinition getSingleUserQuery(GraphQLBuilder builder) {
return newFieldDefinition() return newFieldDefinition()
.name("user") .name("user")
.argument(newArgument().name("id").defaultValue(null).type(GraphQLInt).description("Id of the global user object. If id is 0, currently logged in user is used")) .argument(newArgument().name("id").defaultValue(null).type(GraphQLInt).description("Id of the global user object. If id is 0, currently logged in user is used"))
.type(builder.typeFor(EventUser.class)) .type(builder.typeFor(EventUser.class))
.dataFetcher(environment -> { .dataFetcher(environment -> {
Integer id = environment.getArgument("userId"); Integer id = environment.getArgument("userId");
EventUser user; EventUser user;
if (id == null || id.equals(0)) { if (id == null || id.equals(0)) {
user = permbean.getCurrentUser(); user = permbean.getCurrentUser();
} else { } else {
user = userbean.findByUserId(id, false); user = userbean.findByUserId(id, false);
} }
if (user == null) { if (user == null) {
throw new NullPointerException("User not found with id " + id); throw new NullPointerException("User not found with id " + id);
} }
return user; return user;
}).build(); }).build();
} }
private GraphQLFieldDefinition.Builder getEventSearchQuery(GraphQLBuilder builder) { private GraphQLFieldDefinition.Builder getEventSearchQuery(GraphQLBuilder builder) {
return newFieldDefinition() return newFieldDefinition()
.name("currentevent") .name("currentevent")
.type(builder.typeFor(LanEvent.class)) .type(builder.typeFor(LanEvent.class))
.dataFetcher(environment -> eventbean.getCurrentEvent()); .dataFetcher(environment -> eventbean.getCurrentEvent());
} }
private GraphQLFieldDefinition.Builder getUserSearchField(GraphQLBuilder builder) { private GraphQLFieldDefinition.Builder getUserSearchField(GraphQLBuilder builder) {
return newFieldDefinition() return newFieldDefinition()
.type(new GraphQLList(builder.typeFor(EventUser.class))) .type(new GraphQLList(builder.typeFor(EventUser.class)))
.name("usersearch") .name("usersearch")
.description("Search users. Using this method requires admin acccess to the database") .description("Search users. Using this method requires admin acccess to the database")
//.argument(newArgument().name("reason").type(GraphQLString).description("Reason for the data request. Reason and requested fields are stored to audit log.")) //.argument(newArgument().name("reason").type(GraphQLString).description("Reason for the data request. Reason and requested fields are stored to audit log."))
.argument(newArgument().name("roles").defaultValue(Collections.emptyList()).description("(Optional) Filter users that belong to a role").type(GraphQLList.list(GraphQLInt))) .argument(newArgument().name("roles").defaultValue(Collections.emptyList()).description("(Optional) Filter users that belong to a role").type(GraphQLList.list(GraphQLInt)))
.argument(newArgument().name("search").type(GraphQLString).defaultValue("").description("(Optional) Filter users that contain the search parameter in their firstname, lastname, nick or email").type(GraphQLString)) .argument(newArgument().name("search").type(GraphQLString).defaultValue("").description("(Optional) Filter users that contain the search parameter in their firstname, lastname, nick or email").type(GraphQLString))
.argument(newArgument().name("page").type(GraphQLInt).defaultValue(0).description("Pagination page ( 0 is the first page)")) .argument(newArgument().name("page").type(GraphQLInt).defaultValue(0).description("Pagination page ( 0 is the first page)"))
.argument(newArgument().name("pagesize").type(GraphQLInt).defaultValue(0).description("Pagination pagesize (0 disables pagination)")) .argument(newArgument().name("pagesize").type(GraphQLInt).defaultValue(0).description("Pagination pagesize (0 disables pagination)"))
.dataFetcher((DataFetcher<List<EventUser>>) environment -> { .dataFetcher((DataFetcher<List<EventUser>>) environment -> {
/* /*
String reason = environment.getArgument("reason"); String reason = environment.getArgument("reason");
if (reason == null || reason.isEmpty()) { if (reason == null || reason.isEmpty()) {
throw new GRPDException("Query did not provide a reason for the request"); throw new GRPDException("Query did not provide a reason for the request");
}*/ }*/
UserSearchQuery query = new UserSearchQuery(environment.getArgument("page"), environment.getArgument("pagesize"), null, null, SearchQuery.QuerySortOrder.UNSORTED); UserSearchQuery query = new UserSearchQuery(environment.getArgument("page"), environment.getArgument("pagesize"), null, null, SearchQuery.QuerySortOrder.UNSORTED);
String searchArg = environment.getArgument("search"); String searchArg = environment.getArgument("search");
if (searchArg != null && !searchArg.isEmpty()) { if (searchArg != null && !searchArg.isEmpty()) {
query.setSearch(searchArg); query.setSearch(searchArg);
} }
List<Integer> roleArgument = environment.getArgument("roles"); List<Integer> roleArgument = environment.getArgument("roles");
if (roleArgument != null && !roleArgument.isEmpty()) { if (roleArgument != null && !roleArgument.isEmpty()) {
query.setFilterRoles(roleArgument.stream().map(id -> rolebean.find(id)).filter(x -> x != null).collect(toList())); query.setFilterRoles(roleArgument.stream().map(id -> rolebean.find(id)).filter(x -> x != null).collect(toList()));
} }
SearchResult<EventUser> ret = userbean.getThisEventsUsers(query); SearchResult<EventUser> ret = userbean.getThisEventsUsers(query);
return ret.getResults(); return ret.getResults();
}); });
} }
......
...@@ -48,8 +48,8 @@ ...@@ -48,8 +48,8 @@
<rewriteservlet.version>3.4.2.Final</rewriteservlet.version> <rewriteservlet.version>3.4.2.Final</rewriteservlet.version>
<iudex.standalone>1.0.23</iudex.standalone> <iudex.standalone>1.0.23</iudex.standalone>
<js.node.version>v8.11.3</js.node.version> <js.node.version>v10.8.0</js.node.version>
<js.npm.version>6.3.0</js.npm.version> <js.npm.version>6.2.0</js.npm.version>
<eirslett.frontend.version>1.4</eirslett.frontend.version> <eirslett.frontend.version>1.4</eirslett.frontend.version>
<payara.version>4.1.2.181</payara.version> <payara.version>4.1.2.181</payara.version>
</properties> </properties>
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!