You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2021/06/22 09:15:59 UTC
[incubator-streampipes] 02/02: [STREAMPIPES-319] Improve update of
service endpoints
This is an automated email from the ASF dual-hosted git repository.
riemer pushed a commit to branch STREAMPIPES-319
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git
commit d6a4b78f321728fb3233cff29c4c293a7ef8a5b1
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Tue Jun 22 11:15:36 2021 +0200
[STREAMPIPES-319] Improve update of service endpoints
---
.../streampipes/container/html/JSONGenerator.java | 1 +
.../endpoint/ExtensionsServiceEndpointItem.java | 9 +++
.../ExtensionsServiceEndpointGenerator.java | 6 ++
.../endpoint/ExtensionsServiceEndpointUtils.java | 11 +++-
.../verification/structure/GeneralVerifier.java | 9 +--
.../impl/ExtensionsServiceEndpointResource.java | 5 ++
.../rest/impl/PipelineElementImport.java | 24 ++++++--
ui/src/app/add/add.component.ts | 5 +-
.../endpoint-item/endpoint-item.component.html | 11 ++--
.../endpoint-item/endpoint-item.component.scss | 10 ++++
.../endpoint-item/endpoint-item.component.ts | 8 +--
ui/src/app/add/services/add.service.ts | 8 +--
.../app/core-model/gen/streampipes-model-client.ts | 70 +++++++++++-----------
13 files changed, 118 insertions(+), 59 deletions(-)
diff --git a/streampipes-container/src/main/java/org/apache/streampipes/container/html/JSONGenerator.java b/streampipes-container/src/main/java/org/apache/streampipes/container/html/JSONGenerator.java
index dcd4928..04dc293 100644
--- a/streampipes-container/src/main/java/org/apache/streampipes/container/html/JSONGenerator.java
+++ b/streampipes-container/src/main/java/org/apache/streampipes/container/html/JSONGenerator.java
@@ -64,6 +64,7 @@ public class JSONGenerator {
obj.add("editable", new JsonPrimitive(d.isEditable()));
obj.add("includesIcon", new JsonPrimitive(d.isIncludesIcon()));
obj.add("includesDocs", new JsonPrimitive(d.isIncludesDocs()));
+ obj.add("available", new JsonPrimitive(true));
return obj;
}
}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/endpoint/ExtensionsServiceEndpointItem.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/endpoint/ExtensionsServiceEndpointItem.java
index 12dc8e9..9854265 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/endpoint/ExtensionsServiceEndpointItem.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/endpoint/ExtensionsServiceEndpointItem.java
@@ -37,6 +37,7 @@ public class ExtensionsServiceEndpointItem {
private boolean installed;
private boolean editable;
+ private boolean available;
private List<ExtensionsServiceEndpointItem> streams;
@@ -131,4 +132,12 @@ public class ExtensionsServiceEndpointItem {
public void setIncludesDocs(boolean includesDocs) {
this.includesDocs = includesDocs;
}
+
+ public boolean isAvailable() {
+ return available;
+ }
+
+ public void setAvailable(boolean available) {
+ this.available = available;
+ }
}
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointGenerator.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointGenerator.java
index f54ac46..507226d 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointGenerator.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointGenerator.java
@@ -18,6 +18,7 @@
package org.apache.streampipes.manager.execution.endpoint;
import org.apache.streampipes.commons.exceptions.NoServiceEndpointsAvailableException;
+import org.apache.streampipes.model.base.NamedStreamPipesEntity;
import org.apache.streampipes.svcdiscovery.SpServiceDiscovery;
import org.apache.streampipes.svcdiscovery.api.model.DefaultSpServiceGroups;
import org.apache.streampipes.svcdiscovery.api.model.SpServiceUrlProvider;
@@ -40,6 +41,11 @@ public class ExtensionsServiceEndpointGenerator {
this.spServiceUrlProvider = spServiceUrlProvider;
}
+ public ExtensionsServiceEndpointGenerator(NamedStreamPipesEntity entity) {
+ this.appId = entity.getAppId();
+ this.spServiceUrlProvider = ExtensionsServiceEndpointUtils.getPipelineElementType(entity);
+ }
+
public String getEndpointResourceUrl() throws NoServiceEndpointsAvailableException {
return spServiceUrlProvider.getInvocationUrl(selectService(), appId);
}
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointUtils.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointUtils.java
index e959d98..158d776 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointUtils.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointUtils.java
@@ -17,13 +17,18 @@
*/
package org.apache.streampipes.manager.execution.endpoint;
-import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
+import org.apache.streampipes.model.base.NamedStreamPipesEntity;
+import org.apache.streampipes.model.graph.DataProcessorDescription;
import org.apache.streampipes.model.graph.DataProcessorInvocation;
import org.apache.streampipes.svcdiscovery.api.model.SpServiceUrlProvider;
public class ExtensionsServiceEndpointUtils {
- public static SpServiceUrlProvider getPipelineElementType(InvocableStreamPipesEntity entity) {
- return entity instanceof DataProcessorInvocation ? SpServiceUrlProvider.DATA_PROCESSOR : SpServiceUrlProvider.DATA_SINK;
+ public static SpServiceUrlProvider getPipelineElementType(NamedStreamPipesEntity entity) {
+ return isDataProcessor(entity) ? SpServiceUrlProvider.DATA_PROCESSOR : SpServiceUrlProvider.DATA_SINK;
+ }
+
+ private static boolean isDataProcessor(NamedStreamPipesEntity entity) {
+ return entity instanceof DataProcessorInvocation || entity instanceof DataProcessorDescription;
}
}
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/verification/structure/GeneralVerifier.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/verification/structure/GeneralVerifier.java
index e2a6472..182b570 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/verification/structure/GeneralVerifier.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/verification/structure/GeneralVerifier.java
@@ -18,11 +18,12 @@
package org.apache.streampipes.manager.verification.structure;
-import java.util.List;
-
import org.apache.streampipes.manager.verification.messages.VerificationResult;
-import org.apache.streampipes.model.message.NotificationType;
import org.apache.streampipes.model.base.NamedStreamPipesEntity;
+import org.apache.streampipes.model.message.NotificationType;
+import org.apache.streampipes.sdk.utils.Assets;
+
+import java.util.List;
public class GeneralVerifier<T extends NamedStreamPipesEntity> extends AbstractVerifier {
@@ -35,7 +36,7 @@ public class GeneralVerifier<T extends NamedStreamPipesEntity> extends AbstractV
@Override
public List<VerificationResult> validate() {
- if (description.getIconUrl() == null) addWarning(NotificationType.WARNING_NO_ICON);
+ if (!description.isIncludesAssets() || !description.getIncludedAssets().contains(Assets.ICON)) addWarning(NotificationType.WARNING_NO_ICON);
if (description.getName() == null) addWarning(NotificationType.WARNING_NO_NAME);
return validationResults;
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/ExtensionsServiceEndpointResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/ExtensionsServiceEndpointResource.java
index c5dc776..f056477 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/ExtensionsServiceEndpointResource.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/ExtensionsServiceEndpointResource.java
@@ -26,6 +26,7 @@ import org.apache.streampipes.model.base.NamedStreamPipesEntity;
import org.apache.streampipes.model.client.endpoint.ExtensionsServiceEndpoint;
import org.apache.streampipes.model.client.endpoint.ExtensionsServiceEndpointItem;
import org.apache.streampipes.rest.shared.annotation.GsonWithIds;
+import org.apache.streampipes.sdk.utils.Assets;
import org.apache.streampipes.storage.api.IExtensionsServiceEndpointStorage;
import javax.ws.rs.*;
@@ -156,8 +157,12 @@ public class ExtensionsServiceEndpointResource extends AbstractRestResource {
endpoint.setName(entity.getName());
endpoint.setAppId(entity.getAppId());
endpoint.setType(type);
+ endpoint.setAvailable(false);
+ endpoint.setElementId(entity.getElementId());
endpoint.setUri(entity.getElementId());
endpoint.setEditable(!(entity.isInternallyManaged()));
+ endpoint.setIncludesIcon(entity.isIncludesAssets() && entity.getIncludedAssets().contains(Assets.ICON));
+ endpoint.setIncludesDocs(entity.isIncludesAssets() && entity.getIncludedAssets().contains(Assets.DOCUMENTATION));
return endpoint;
}
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementImport.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementImport.java
index 7df61bd..9673cf0 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementImport.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementImport.java
@@ -20,11 +20,14 @@ package org.apache.streampipes.rest.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
+import org.apache.streampipes.commons.exceptions.NoServiceEndpointsAvailableException;
import org.apache.streampipes.commons.exceptions.SepaParseException;
import org.apache.streampipes.manager.assets.AssetManager;
import org.apache.streampipes.manager.endpoint.EndpointItemParser;
+import org.apache.streampipes.manager.execution.endpoint.ExtensionsServiceEndpointGenerator;
import org.apache.streampipes.manager.operations.Operations;
import org.apache.streampipes.manager.storage.UserService;
+import org.apache.streampipes.model.base.NamedStreamPipesEntity;
import org.apache.streampipes.model.message.Message;
import org.apache.streampipes.model.message.Notification;
import org.apache.streampipes.model.message.NotificationType;
@@ -87,15 +90,16 @@ public class PipelineElementImport extends AbstractRestResource {
@PUT
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
- public Response updateElement(@PathParam("username") String username, @PathParam("id") String uri) {
+ public Response updateElement(@PathParam("username") String username, @PathParam("id") String elementId) {
if (!authorized(username)) {
return ok(Notifications.error(NotificationType.UNAUTHORIZED));
}
try {
- uri = URLDecoder.decode(uri, "UTF-8");
- String payload = parseURIContent(uri);
+ NamedStreamPipesEntity entity = find(elementId);
+ String url = new ExtensionsServiceEndpointGenerator(entity).getEndpointResourceUrl();
+ String payload = parseURIContent(url);
return ok(Operations.verifyAndUpdateElement(payload, username));
- } catch (URISyntaxException | IOException | SepaParseException e) {
+ } catch (URISyntaxException | IOException | SepaParseException | NoServiceEndpointsAvailableException e) {
e.printStackTrace();
return constructErrorMessage(new Notification(NotificationType.PARSE_ERROR, e.getMessage()));
}
@@ -137,4 +141,16 @@ public class PipelineElementImport extends AbstractRestResource {
return constructSuccessMessage(NotificationType.STORAGE_SUCCESS.uiNotification());
}
+ private NamedStreamPipesEntity find(String elementId) {
+ if (getPipelineElementStorage().existsDataSink(elementId)) {
+ return getPipelineElementStorage().getDataSinkById(elementId);
+ } else if (getPipelineElementStorage().existsDataProcessor(elementId)) {
+ return getPipelineElementStorage().getDataProcessorById(elementId);
+ } else if (getPipelineElementStorage().existsDataStream(elementId)) {
+ return getPipelineElementStorage().getDataStreamById(elementId);
+ } else {
+ throw new IllegalArgumentException("Could not find element for ID " + elementId);
+ }
+ }
+
}
diff --git a/ui/src/app/add/add.component.ts b/ui/src/app/add/add.component.ts
index 14eec0f..cc2da66 100644
--- a/ui/src/app/add/add.component.ts
+++ b/ui/src/app/add/add.component.ts
@@ -24,7 +24,7 @@ import {PanelType} from "../core-ui/dialog/base-dialog/base-dialog.model";
import {DialogService} from "../core-ui/dialog/base-dialog/base-dialog.service";
import {AddEndpointComponent} from "./dialogs/add-endpoint/add-endpoint.component";
import {EndpointInstallationComponent} from "./dialogs/endpoint-installation/endpoint-installation.component";
-import {RdfEndpointItem} from "../core-model/gen/streampipes-model-client";
+import {ExtensionsServiceEndpointItem} from "../core-model/gen/streampipes-model-client";
@Component({
selector: 'add',
@@ -35,7 +35,7 @@ export class AddComponent implements OnInit {
results: any[];
loading: boolean;
- endpointItems: RdfEndpointItem[];
+ endpointItems: ExtensionsServiceEndpointItem[];
endpointItemsLoadingComplete: boolean;
selectedTab: string;
availableTypes: Array<string> = ["all", "set", "stream", "sepa", "action"];
@@ -132,6 +132,7 @@ export class AddComponent implements OnInit {
this.endpointItemsLoadingComplete = false;
this.AddService.getRdfEndpointItems()
.subscribe(endpointItems => {
+ console.log(endpointItems);
this.endpointItems = endpointItems;
this.endpointItemsLoadingComplete = true;
});
diff --git a/ui/src/app/add/components/endpoint-item/endpoint-item.component.html b/ui/src/app/add/components/endpoint-item/endpoint-item.component.html
index 0f649ae..727b58f 100644
--- a/ui/src/app/add/components/endpoint-item/endpoint-item.component.html
+++ b/ui/src/app/add/components/endpoint-item/endpoint-item.component.html
@@ -18,6 +18,11 @@
<div fxFlex="100" fxLayout="column" fxLayoutAlign="start start" class="pipeline-element-box"
style="padding:5px;margin-bottom:10px;background:{{getSelectedBackground()}}" [ngStyle]="item.editable ? {opacity: 1} : {opacity: 0.5}">
+ <div fxLayout="row" style="width: 100%;">
+ <i class="material-icons" [ngClass]="item.available ? 'endpoint-icon-available' : 'endpoint-icon-critical'">lens</i>
+ <span fxFlex></span>
+ <span class="{{itemTypeStyle}}" matTooltip="Endpoint {{item.available ? 'available' : 'not available'}}">{{itemTypeTitle}}</span>
+ </div>
<div fxFlex fxLayout="row" style="width:100%;">
<div style="margin-right:10px;margin-left:10px;margin-top:10px;">
<div *ngIf="!item.includesIcon || iconError" class="draggable-icon {{item.type}}">{{iconText(item.name)}}</div>
@@ -25,9 +30,7 @@
<img class="icon" [src]="image"></div>
</div>
<div fxFlex fxLayout="column">
- <div fxFlex fxLayoutAlign="end start">
- <span class="{{itemTypeStyle}}">{{itemTypeTitle}}</span>
- </div>
+
<div class="ml-5"><small *ngIf="!(item.editable)">Internally managed by StreamPipes</small></div>
<div fxFlex fxLayoutAlign="start end" class="ml-5">
<button class="small-button-add" mat-button mat-raised-button color="primary" [disabled]="!(item.editable)"
@@ -42,7 +45,7 @@
<button class="small-button-add mat-basic no-min-width" [disabled]="!(item.editable)"
mat-raised-button mat-button [matMenuTriggerFor]="menu"><span style="font-size:12px;">...</span></button>
<mat-menu #menu="matMenu">
- <button mat-menu-item (click)="refresh(item.uri)">
+ <button mat-menu-item (click)="refresh(item.elementId)" [disabled]="!item.available">
<mat-icon>refresh</mat-icon>
<span> Update</span>
</button>
diff --git a/ui/src/app/add/components/endpoint-item/endpoint-item.component.scss b/ui/src/app/add/components/endpoint-item/endpoint-item.component.scss
index d462a67..26ab5fd 100644
--- a/ui/src/app/add/components/endpoint-item/endpoint-item.component.scss
+++ b/ui/src/app/add/components/endpoint-item/endpoint-item.component.scss
@@ -41,3 +41,13 @@
margin-top: -5px;
width: 50px;
}
+
+.endpoint-icon-available {
+ cursor: default;
+ color: #4CAF50;
+}
+
+.endpoint-icon-critical {
+ cursor: default;
+ color: #F44336;
+}
diff --git a/ui/src/app/add/components/endpoint-item/endpoint-item.component.ts b/ui/src/app/add/components/endpoint-item/endpoint-item.component.ts
index dc4ac5e..35ffd80 100644
--- a/ui/src/app/add/components/endpoint-item/endpoint-item.component.ts
+++ b/ui/src/app/add/components/endpoint-item/endpoint-item.component.ts
@@ -19,9 +19,9 @@
import {Component, EventEmitter, Input, OnInit, Output, Sanitizer} from "@angular/core";
import {MatSnackBar} from "@angular/material/snack-bar";
import {PipelineElementEndpointService} from "../../../platform-services/apis/pipeline-element-endpoint.service";
-import {RdfEndpointItem} from "../../../core-model/gen/streampipes-model-client";
import {AddService} from "../../services/add.service";
import {DomSanitizer, SafeUrl} from "@angular/platform-browser";
+import {ExtensionsServiceEndpointItem} from "../../../core-model/gen/streampipes-model-client";
@Component({
selector: 'endpoint-item',
@@ -31,7 +31,7 @@ import {DomSanitizer, SafeUrl} from "@angular/platform-browser";
export class EndpointItemComponent implements OnInit {
@Input()
- item: RdfEndpointItem;
+ item: ExtensionsServiceEndpointItem;
itemTypeTitle: string;
itemTypeStyle: string;
@@ -124,8 +124,8 @@ export class EndpointItemComponent implements OnInit {
event.stopPropagation();
}
- refresh(elementUri) {
- this.PipelineElementEndpointService.update(elementUri)
+ refresh(elementId: string) {
+ this.PipelineElementEndpointService.update(elementId)
.subscribe(msg => {
this.snackBar.open(msg.notifications[0].title, "Ok", {
duration: 2000
diff --git a/ui/src/app/add/services/add.service.ts b/ui/src/app/add/services/add.service.ts
index 42ea08d..0747e91 100644
--- a/ui/src/app/add/services/add.service.ts
+++ b/ui/src/app/add/services/add.service.ts
@@ -21,8 +21,8 @@ import {HttpClient} from "@angular/common/http";
import {AuthStatusService} from "../../services/auth-status.service";
import {PlatformServicesCommons} from "../../platform-services/apis/commons.service";
import {Observable} from "rxjs";
-import {RdfEndpointItem} from "../../core-model/gen/streampipes-model-client";
import {map} from "rxjs/operators";
+import {ExtensionsServiceEndpointItem} from "../../core-model/gen/streampipes-model-client";
@Injectable()
export class AddService {
@@ -37,12 +37,12 @@ export class AddService {
return this.http.get(this.platformServicesCommons.authUserBasePath() + "/rdfendpoints");
}
- getRdfEndpointItems(): Observable<Array<RdfEndpointItem>> {
+ getRdfEndpointItems(): Observable<Array<ExtensionsServiceEndpointItem>> {
return this
.http
.get(this.platformServicesCommons.authUserBasePath() + "/rdfendpoints/items")
.pipe(map(response => {
- return (response as any[]).map(item => RdfEndpointItem.fromData(item));
+ return (response as any[]).map(item => ExtensionsServiceEndpointItem.fromData(item));
}));
}
@@ -54,7 +54,7 @@ export class AddService {
return this.http.delete(this.platformServicesCommons.authUserBasePath() + "/rdfendpoints/" + rdfEndpointId);
}
- getRdfEndpointIcon(item: RdfEndpointItem): Observable<any> {
+ getRdfEndpointIcon(item: ExtensionsServiceEndpointItem): Observable<any> {
return this.http.post(this.platformServicesCommons.authUserBasePath()
+ "/rdfendpoints/items/icon", item, { responseType: 'blob' });
}
diff --git a/ui/src/app/core-model/gen/streampipes-model-client.ts b/ui/src/app/core-model/gen/streampipes-model-client.ts
index add7a29..7036175 100644
--- a/ui/src/app/core-model/gen/streampipes-model-client.ts
+++ b/ui/src/app/core-model/gen/streampipes-model-client.ts
@@ -19,7 +19,7 @@
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
-// Generated using typescript-generator version 2.27.744 on 2021-06-17 21:29:53.
+// Generated using typescript-generator version 2.27.744 on 2021-06-22 10:02:27.
export class Element {
elementId: string;
@@ -36,6 +36,41 @@ export class Element {
}
}
+export class ExtensionsServiceEndpointItem {
+ appId: string;
+ available: boolean;
+ description: string;
+ editable: boolean;
+ elementId: string;
+ includesDocs: boolean;
+ includesIcon: boolean;
+ installed: boolean;
+ name: string;
+ streams: ExtensionsServiceEndpointItem[];
+ type: string;
+ uri: string;
+
+ static fromData(data: ExtensionsServiceEndpointItem, target?: ExtensionsServiceEndpointItem): ExtensionsServiceEndpointItem {
+ if (!data) {
+ return data;
+ }
+ const instance = target || new ExtensionsServiceEndpointItem();
+ instance.name = data.name;
+ instance.description = data.description;
+ instance.elementId = data.elementId;
+ instance.uri = data.uri;
+ instance.type = data.type;
+ instance.appId = data.appId;
+ instance.includesIcon = data.includesIcon;
+ instance.includesDocs = data.includesDocs;
+ instance.installed = data.installed;
+ instance.editable = data.editable;
+ instance.available = data.available;
+ instance.streams = __getCopyArrayFn(ExtensionsServiceEndpointItem.fromData)(data.streams);
+ return instance;
+ }
+}
+
export class FileMetadata {
createdAt: number;
createdByUser: string;
@@ -105,39 +140,6 @@ export class RawUserApiToken {
}
}
-export class RdfEndpointItem {
- appId: string;
- description: string;
- editable: boolean;
- elementId: string;
- includesDocs: boolean;
- includesIcon: boolean;
- installed: boolean;
- name: string;
- streams: RdfEndpointItem[];
- type: string;
- uri: string;
-
- static fromData(data: RdfEndpointItem, target?: RdfEndpointItem): RdfEndpointItem {
- if (!data) {
- return data;
- }
- const instance = target || new RdfEndpointItem();
- instance.name = data.name;
- instance.description = data.description;
- instance.elementId = data.elementId;
- instance.uri = data.uri;
- instance.type = data.type;
- instance.appId = data.appId;
- instance.includesIcon = data.includesIcon;
- instance.includesDocs = data.includesDocs;
- instance.installed = data.installed;
- instance.editable = data.editable;
- instance.streams = __getCopyArrayFn(RdfEndpointItem.fromData)(data.streams);
- return instance;
- }
-}
-
export class User {
email: string;
fullName: string;