API: Use noOptions for parameters

Move  config.service to _services/
Rationalise  bootstrap and conf (WIP)
Fix auth:
- Add HTTP interceptor
- Bootstrap after login/logout
Some cleanups
This commit is contained in:
phil 2024-03-20 10:48:33 +05:30
parent 8593d0b5cd
commit 4eb4c0d0dd
29 changed files with 223 additions and 296 deletions

View file

@ -17,7 +17,7 @@
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"generate-client": "openapi --input http://127.0.0.1:5000/openapi.json --output ./src/app/openapi --client angular --useOptions --useUnionTypes",
"generate-client": "openapi --input http://127.0.0.1:5000/openapi.json --output ./src/app/openapi --client angular --useUnionTypes",
"source-map-explorer": "source-map-explorer dist/*.js"
},
"licenses": [

View file

@ -0,0 +1,24 @@
import { Injectable } from '@angular/core'
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HTTP_INTERCEPTORS } from '@angular/common/http'
import { Observable } from 'rxjs'
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = localStorage.token
if (!token) {
return next.handle(req)
}
req = req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`),
})
return next.handle(req)
}
}
export const httpInterceptorProviders = [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
]

View file

@ -1,16 +1,18 @@
import { Injectable } from '@angular/core'
import { HttpClient, HttpHeaders } from '@angular/common/http'
import { Observable, BehaviorSubject, from, throwError } from 'rxjs'
import { Observable, BehaviorSubject, from, throwError, of } from 'rxjs'
import { map, catchError } from 'rxjs/operators'
import { User } from '../_models/user'
import { RoleReadNoUsers } from '../openapi'
import { RoleReadNoUsers, ApiService, Token } from '../openapi'
import { BootstrapService } from './bootstrap.service'
import { ConfigService } from './config.service'
interface AuthResponse {
access_token: string,
roles: string[]
}
// interface AuthResponse {
// access_token: string,
// roles: string[]
// }
@Injectable()
export class AuthenticationService {
@ -20,6 +22,9 @@ export class AuthenticationService {
constructor(
private _http: HttpClient,
public api: ApiService,
public bootstrapService: BootstrapService,
public configService: ConfigService,
) {
// set token if saved in local storage
this.user.next(<User>JSON.parse(localStorage.getItem('user')))
@ -54,43 +59,44 @@ export class AuthenticationService {
)
}
login(userName: string, password: string): Observable<AuthResponse> {
login(username: string, password: string): Observable<Token> {
const headers = new HttpHeaders({'Content-Type': 'application/x-www-form-urlencoded'})
var formData: any = new URLSearchParams()
formData.set('username', userName)
formData.set('password', password)
return this._http.post<AuthResponse>(
'/api/token', formData, {headers: headers}
).pipe(map(
(response: AuthResponse) => {
// login successful if there's a jwt token in the response
let token = response.access_token
if (token) {
//const decodedToken = this.helper.decodeToken(token)
// store userName and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('user',
JSON.stringify({
userName: userName,
token: token,
roles: response.roles,
})
)
// var formData: any = new URLSearchParams()
// formData.set('username', userName)
// formData.set('password', password)
return this.api.loginForAccessTokenApiTokenPost({
username: username,
password: password
}).pipe(map(
token => {
localStorage.setItem('token', token.access_token)
// store jwt token in local storage to keep user logged in between page refreshes
// localStorage.setItem('user',
// JSON.stringify({
// userName: username,
// token: token,
// roles: response.roles,
// })
// )
console.log('TODO: AuthenticationService roles to be set by refreshing bootstrap')
this.bootstrapService.get().subscribe(
bsData => this.configService.setConf(bsData)
)
return token
// this.roles = response.roles
// Notify
this.user.next(new User(userName, token))
// this.user.next(new User(userName, token))
// return true to indicate successful login
// return true
} else {
this.user.next(undefined)
this.roles = []
// return false to indicate failed login
// return false
}
return response
// } else {
// this.user.next(undefined)
// this.roles = []
// // return false to indicate failed login
// // return false
// }
// return response
}
))
}
@ -99,24 +105,32 @@ export class AuthenticationService {
// XXX: not completly safe: the server might be down:
// We should actually *check* that the logout response is OK and display message
// clear token remove user from local storage to log user out
let has_token: boolean = this.user.value && !!this.user.value.token
localStorage.removeItem('user')
this.user.next(undefined)
this.roles = []
// let has_token: boolean = this.user.value && !!this.user.value.token
const has_token: boolean = !!localStorage.getItem('token')
// localStorage.removeItem('user')
// this.user.next(undefined)
// this.roles = []
// Tell server that the user has logged out
if (has_token) {
this._http.get('/auth/logout').subscribe(response => {})
this._http.get('/api/logout').subscribe(response => {})
localStorage.removeItem('token')
}
this.bootstrapService.get().subscribe(
bsData => this.configService.setConf(bsData)
)
return has_token
}
logoutAdmin(): void {
}
isAuthorized(roles: string[]) {
isAuthorized(roles: string[]): Observable<boolean> {
// Return true if at least one role in given list matches one role of the authenticated user
if (roles.length == 0) return true
return this.roles.filter(value => -1 !== roles.indexOf(value.name)).length > 0
if (roles.length == 0) return of(true)
// return this.roles.filter(value => -1 !== roles.indexOf(value.name)).length > 0
return this.configService.conf.pipe(map(
conf => conf.bsData?.user.roles.filter(value => -1 !== roles.indexOf(value.name)).length > 0
))
}
}

View file

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { Observable } from 'rxjs'
import { Observable, map } from 'rxjs'
import { ApiService, BootstrapData } from '../openapi'

View file

@ -2,23 +2,25 @@ import { inject, Injectable } from "@angular/core"
import { ActivatedRouteSnapshot, ResolveFn, RouterStateSnapshot } from "@angular/router"
import { Subject, ReplaySubject, BehaviorSubject, Observable, map } from "rxjs"
import { BootstrapData } from "../openapi"
import { BootstrapService } from "./bootstrap.service"
export class Config {
map = {}
proj = {}
measures = {}
geo = {}
constructor(
public bsData?: BootstrapData
) {}
}
@Injectable({
providedIn: 'root'
})
export class ConfigService {
defaultConf: Config = {
'map': {},
'proj': {},
'measures': {},
'geo': {}
}
// defaultConf: Config = {
// 'map': {},
// 'proj': {},
// 'measures': {},
// 'geo': {}
// }
hasConf = new ReplaySubject<undefined>()
public conf: BehaviorSubject<Config> = new BehaviorSubject<Config>(new Config())
@ -36,12 +38,14 @@ export class ConfigService {
}
*/
setConf(c: Object) {
this.conf.value.map = c['map']
this.conf.value.proj = c['proj']
this.conf.value.geo = c['geo']
this.conf.value.measures = c['measures']
this.conf.next(this.conf.value)
setConf(bsData: BootstrapData) {
this.conf.next(new Config(bsData))
// this.conf.value.map = c.
// this.conf.value.proj = c['proj']
// this.conf.value.geo = c['geo']
// this.conf.value.measures = c['measures']
// this.conf.next(this.conf.value)
localStorage.setItem('bsData', JSON.stringify(bsData))
this.hasConf.next(undefined)
}
}

View file

@ -36,7 +36,7 @@ export class DataService {
}
getList(store: string): Observable<MeasuresItem[]> {
return this.api.getModelListApiDataProviderStoreGet({store: store})
return this.api.getModelListApiDataProviderStoreGet(store)
}
getValues(
@ -61,11 +61,8 @@ export class DataService {
// .set('resample', sampling)
// .set('format', format)
// FIXME: add the name of the value to fetch
return this.api.getModelValuesApiStoreNameValuesValueGet({
storeName: store,
value: value,
where: JSON.stringify(p),
resample: sampling
})
return this.api.getModelValuesApiStoreNameValuesValueGet(
store, value, JSON.stringify(p), sampling
)
}
}

View file

@ -34,9 +34,7 @@ export class GeoJsonService {
}
getStyle(store: string): Observable<MaplibreStyle> {
return this.mapService.getLayerStyleApiMapLayerStyleStoreGet(
{store: store}
)
return this.mapService.getLayerStyleApiMapLayerStyleStoreGet(store)
}
getAll(url: string, store: string, params?: object): Observable<MapboxDataAndStyle> {

View file

@ -1,6 +1,6 @@
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { configResolver } from './config.service'
import { configResolver } from './_services/config.service'
import { LoginComponent } from './login/login.component'
import { PageNotFoundComponent } from './pageNotFound.component'

View file

@ -2,7 +2,7 @@
<mat-toolbar fxFlex="2em" id='top-toolbar'>
<span
style='font-family:GisafSymbols'
matTooltip="Gisaf v. {{ version }}"
matTooltip="Gisaf v. {{ (configService.conf | async).bsData?.version }}"
matTooltipPosition="below"
class='gisafIcon'
>
@ -25,12 +25,12 @@
</a>
</nav>
<span class="fill-space"></span>
<span *ngIf="authenticationService.user$ | async; else login" class="auth-indicator">
<span *ngIf="(configService.conf | async).bsData?.user; else login" class="auth-indicator">
<nav>
<a mat-icon-button
(click)='this.authenticationService.logout()'
aria-label="Log out"
matTooltip="User: {{ (authenticationService.user$ | async ).userName }}. Click to log out">
matTooltip="User: {{ (configService.conf | async ).bsData?.user.username }}. Click to log out">
<mat-icon>verified_user</mat-icon>
</a>
</nav>

View file

@ -2,7 +2,7 @@ import { Component, OnInit,
ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'
import { Title } from '@angular/platform-browser'
import { BootstrapService } from './_services/bootstrap.service'
import { ConfigService } from './config.service'
import { ConfigService } from './_services/config.service'
import { MatSnackBar } from '@angular/material/snack-bar'
import { AuthenticationService } from './_services/authentication.service'
@ -52,7 +52,6 @@ export class AppComponent implements OnInit {
this.title = res.title || this.title
this.titleService.setTitle(res.windowTitle || this.title)
this.configService.setConf(res)
this.authenticationService.roles = res.user?.roles || []
if (res.redirect && (window != window.top)) {
// Refusing to be embedded in an iframe
let loc = res.redirect + window.location.pathname
@ -69,10 +68,5 @@ export class AppComponent implements OnInit {
)
}
})
this.authenticationService.isLoggedIn().subscribe({
next: resp => resp,
error: (err: string) => this.snackBar.open(err, 'OK', {duration: 3000})
})
}
}

View file

@ -1,26 +1,17 @@
import { BrowserModule } from '@angular/platform-browser'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { Injector, NgModule, LOCALE_ID } from '@angular/core'
import { NgModule, LOCALE_ID } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { HttpClientModule } from '@angular/common/http'
import { MatButtonModule } from '@angular/material/button'
import { MatIconModule } from '@angular/material/icon'
import { MatSnackBarModule } from '@angular/material/snack-bar'
import { MatSnackBar } from '@angular/material/snack-bar'
import { MatToolbarModule } from '@angular/material/toolbar'
import { MatTooltipModule } from '@angular/material/tooltip'
import { FlexLayoutModule } from 'ngx-flexible-layout'
// import { ApolloModule, APOLLO_OPTIONS } from 'apollo-angular'
// import { HttpLink } from 'apollo-angular/http'
// import { onError } from '@apollo/client/link/error'
// import { split, from, ApolloLink, InMemoryCache, ApolloClientOptions } from '@apollo/client/core'
// import { WebSocketLink } from '@apollo/client/link/ws'
// import { getMainDefinition, getOperationName } from '@apollo/client/utilities'
// import { DefinitionNode } from 'graphql'
import { AppComponent } from './app.component'
import { PageNotFoundComponent } from './pageNotFound.component'
import { LoginModule } from './login/login.module'
@ -28,18 +19,18 @@ import { AuthenticationService } from './_services/authentication.service'
import { BootstrapService } from './_services/bootstrap.service'
import { ModelDataService } from './_services/apollo.service'
import { ActionsService } from './_services/actions.service'
import { ConfigService } from './config.service'
import { ConfigService } from './_services/config.service'
import { ApiService } from './openapi/services/ApiService'
import { AdminService } from './openapi/services/AdminService'
import { DashboardService } from './openapi/services/DashboardService'
import { GeoapiService } from './openapi/services/GeoapiService'
import { MapService } from './openapi/services/MapService'
import { httpInterceptorProviders } from './_helpers/http.interceptor'
import { HtmlSnackbarComponent } from './custom-snackbar/custom-snackbar.component'
import { AppRoutingModule } from './app-routing.module'
import { environment } from '../environments/environment'
@NgModule({
declarations: [
@ -79,95 +70,11 @@ import { environment } from '../environments/environment'
MapService,
ConfigService,
ModelDataService,
httpInterceptorProviders,
{
provide: LOCALE_ID,
useValue: "en-IN"
},
// {
// provide: APOLLO_OPTIONS,
// useFactory(httpLink: HttpLink, snackBar: MatSnackBar) {
// const definitionIsMutation = (d: DefinitionNode) => {
// return d.kind === 'OperationDefinition' && d.operation === 'mutation'
// }
// // See https://github.com/apollographql/apollo-angular/issues/1013
// const linkQueries = httpLink.create({
// uri: '/graphql',
// method: 'GET',
// })
// const linkMutations = httpLink.create({
// uri: '/graphql',
// })
// const splittedLink = split(
// ({ query }) => query.definitions.some(definitionIsMutation),
// linkMutations,
// linkQueries,
// )
// const schedulerQueriesLink = httpLink.create({
// uri: '/graphql_sched',
// method: 'GET',
// })
// const proxyLink = split(
// ({ query }) => {
// let res = query.definitions[0]['name']['value'].startsWith('scheduler_')
// return res
// },
// schedulerQueriesLink,
// splittedLink
// )
// const errorLink = onError(({ graphQLErrors, networkError }) => {
// if (graphQLErrors)
// graphQLErrors.map(({ message, locations, path }) => {
// snackBar.open(`Error: ${message}`, 'close')
// console.error(
// `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
// graphQLErrors
// )
// }
// )
// if (networkError) {
// snackBar.open(
// `Network error: ${networkError['statusText']}`,
// 'close',
// )
// console.error(networkError)
// }
// })
// const httpLinkWithErrorHandling = from([
// errorLink,
// proxyLink,
// ])
// return {
// link: httpLinkWithErrorHandling,
// cache: new InMemoryCache(),
// defaultOptions: {
// watchQuery: {
// fetchPolicy: 'network-only',
// errorPolicy: 'ignore',
// },
// query: {
// fetchPolicy: 'network-only',
// errorPolicy: 'all',
// },
// subscription: {
// fetchPolicy: 'network-only',
// errorPolicy: 'all',
// }
// },
// }
// },
// deps: [
// HttpLink,
// MatSnackBar
// ],
// }
],
bootstrap: [
AppComponent

View file

@ -10,7 +10,7 @@ import { MapControlService } from '../map/map-control.service'
import { LayerNode } from '../map/models'
import { Tag, TagAction } from './info-tags/tags.service'
import { ConfigService } from '../config.service'
import { ConfigService } from '../_services/config.service'
import { DataService } from '../_services/data.service'
import { ApiService, ModelInfo, FeatureInfo, PlotParams } from '../openapi'
@ -466,7 +466,7 @@ export class InfoDataService {
public taggedLayers$ = this.taggedLayers.asObservable()
getModelInfo(store: string): Observable<ModelInfo> {
return this.api.getModelInfoApiModelInfoStoreGet({store: store})
return this.api.getModelInfoApiModelInfoStoreGet(store)
console.warn('Migrate Graphql')
return observableOf()
// return this.apollo.query({
@ -555,11 +555,7 @@ export class InfoDataService {
}
getPlotParams(store: string, id: string, value: string): Observable<PlotParams> {
return this.api.getPlotParamsApiPlotParamsStoreGet({
'store': store,
'id': id,
'value': value
})
return this.api.getPlotParamsApiPlotParamsStoreGet(store, id, value)
console.warn('Migrate Graphql')
return observableOf()
// return this.apollo.query({
@ -592,10 +588,7 @@ export class InfoDataService {
}
getFeatureInfo(store: string, id: string): Observable<FeatureInfo> {
return this.api.getFeatureInfoApiFeatureInfoStoreIdGet({
store: store,
id: id
})
return this.api.getFeatureInfoApiFeatureInfoStoreIdGet(store, id)
// return this.apollo.query({
// query: getFeatureInfoQuery,
// variables: {
@ -710,7 +703,7 @@ export class InfoDataService {
}
public getTagKeys(): Observable<string[]> {
return observableOf(this.configService.conf.value.map['tagKeys'])
return observableOf(this.configService.conf.value.bsData?.map['tagKeys'])
// This could be fetched from the server
/*
return this.apollo.query({

View file

@ -6,7 +6,7 @@ import { Router } from '@angular/router'
import { UntypedFormGroup, UntypedFormControl, Validators } from '@angular/forms'
import { ConfigService } from '../config.service'
import { ConfigService } from '../_services/config.service'
import { AuthenticationService } from '../_services/authentication.service'
@Component({
@ -41,15 +41,8 @@ export class LoginComponent implements OnInit {
this.loading = true
this.authenticationService.login(this.formGroup.value.userName, this.formGroup.value.password).subscribe({
next: result => {
if (result.access_token) {
// login successful
this.router.navigate(['/'])
} else {
// login failed
this.error = 'User name or password is incorrect'
this.loading = false
this.cdr.markForCheck()
}
},
error: error => {
console.error(error)

View file

@ -34,7 +34,7 @@
step=0.01
matTooltip="Opacity of the background layer"
matTooltipPosition="right"
#ngSlider><input matSliderThumb value="{{ (configService.conf | async).map['opacity'] }}" (input)="mapControlService.baseStyleOpacity.next({source: ngSliderThumb, parent: ngSlider, value: ngSliderThumb.value}.value)" #ngSliderThumb="matSliderThumb" />
#ngSlider><input matSliderThumb value="{{ (configService.conf | async).bsData.map.opacity }}" (input)="mapControlService.baseStyleOpacity.next({source: ngSliderThumb, parent: ngSlider, value: ngSliderThumb.value}.value)" #ngSliderThumb="matSliderThumb" />
</mat-slider>
</div>
</mat-expansion-panel>
@ -85,7 +85,7 @@
matTooltip='Filter features from survey categories by status'
matTooltipPosition="right"
>
<mat-option *ngFor="let _status of (configService.conf | async).map['status']" [value]="_status">{{ _status }}</mat-option>
<mat-option *ngFor="let _status of (configService.conf | async).bsData?.map.status" [value]="_status">{{ _status }}</mat-option>
</mat-select>
</mat-form-field>
</div>
@ -147,22 +147,22 @@
<mat-menu #toolsMenu="matMenu" class='extended-width'>
<button mat-menu-item
(click)="downloadSelectedLayers('gpkg')">
Download Geopackage of selected layers (EPSG {{ (configService.conf | async).geo['srid'] }})
Download Geopackage of selected layers (EPSG {{ (configService.conf | async).bsData?.geo.srid }})
</button>
<button mat-menu-item [disabled]=true
(click)="downloadSelectedLayers('dxf')">
Download DXF of selected layers (EPSG {{ (configService.conf | async).geo['srid'] }})
Download DXF of selected layers (EPSG {{ (configService.conf | async).bsData?.geo.srid }})
</button>
<button mat-menu-item [disabled]=true
(click)="downloadSelectedLayers('dxf', true)">
Download DXF of selected layers (reprojected to EPSG {{ (configService.conf | async).geo['srid_for_proj'] }})
Download DXF of selected layers (reprojected to EPSG {{ (configService.conf | async).bsData?.geo.srid_for_proj }})
</button>
<button mat-menu-item
(click)="downloadSelectedLayers('shapefile')">
Download Shapefiles of selected layers (EPSG {{ (configService.conf | async).geo['srid'] }}) [<i><b>Deprecated</b>: use Geopackage</i>]
Download Shapefiles of selected layers (EPSG {{ (configService.conf | async).bsData?.geo.srid }}) [<i><b>Deprecated</b>: use Geopackage</i>]
</button>
</mat-menu>
<button mat-raised-button [disabled]="!canCreateBaseMap"
<button mat-raised-button [disabled]="!(authenticationService.isAuthorized(['base_map_creator']) | async)"
matTooltip="Create a base map with the selected layers"
(click)="createBaseMap()">
Create a base map...

View file

@ -3,7 +3,7 @@ import { Component, OnInit } from '@angular/core'
import { FormControl } from '@angular/forms'
import { MatSnackBar } from '@angular/material/snack-bar'
import { ConfigService } from '../../config.service'
import { ConfigService } from '../../_services/config.service'
import { MapControlService } from '../map-control.service'
import { TreeLayerItemComponent } from '../tree-layer-item/tree-layer-item.component'
import { InfoDataService, TaggedLayer } from '../../info/info-data.service'
@ -30,11 +30,11 @@ export class MapControlsComponent implements OnInit {
baseStyles: BaseStyle[]
baseStyleOpacity: number = this.configService.conf.value.map['opacity']
baseStyleOpacity: number = this.configService.conf.value.bsData?.map.opacity
status = new FormControl([])
canCreateBaseMap: boolean = false
// canCreateBaseMap: boolean = false
constructor(
public snackBar: MatSnackBar,
@ -42,7 +42,7 @@ export class MapControlsComponent implements OnInit {
public mapControlService: MapControlService,
protected infoDataService: InfoDataService,
public mapDataService: MapDataService,
private authenticationService: AuthenticationService,
public authenticationService: AuthenticationService,
public dialog: MatDialog,
) {
}
@ -102,11 +102,11 @@ export class MapControlsComponent implements OnInit {
this.mapControlService.status.next(value)
}
)
this.status.setValue(this.configService.conf.value.map['defaultStatus'])
this.status.setValue(this.configService.conf.value.bsData?.map['defaultStatus'])
this.mapControlService.baseStyleOpacity.next(this.baseStyleOpacity)
this.canCreateBaseMap = this.authenticationService.isAuthorized(['base_map_creator'])
// this.canCreateBaseMap = this.authenticationService.isAuthorized(['base_map_creator'])
}
openSnackBar(msg: string):void {

View file

@ -16,7 +16,7 @@ import Point from '@mapbox/point-geometry'
import * as bbox from '@turf/bbox'
import { GeoJsonService, LiveGeoJsonService, MapboxDataAndStyle } from '../../_services/geojson.service'
import { ConfigService } from '../../config.service'
import { ConfigService } from '../../_services/config.service'
import { MapControlService } from '../map-control.service'
import { LayerNode } from '../models'
import { InfoDataService, Feature, TaggedLayer, FeatureWithField, TaggedFeature } from '../../info/info-data.service'
@ -52,15 +52,15 @@ export class GisafMapboxComponent implements OnInit, OnDestroy {
// XXX: temporary, for keeping map-controls.component.ts happy
map: Map
zoom: number = this.configService.conf.value.map['zoom']
pitch: number = this.configService.conf.value.map['pitch']
lat: number = this.configService.conf.value.map['lat']
lng: number = this.configService.conf.value.map['lng']
bearing: number = this.configService.conf.value.map['bearing']
globalAttribution: string = this.configService.conf.value.map['attribution']
zoom: number = this.configService.conf.value.bsData?.map['zoom']
pitch: number = this.configService.conf.value.bsData?.map['pitch']
lat: number = this.configService.conf.value.bsData?.map['lat']
lng: number = this.configService.conf.value.bsData?.map['lng']
bearing: number = this.configService.conf.value.bsData?.map['bearing']
globalAttribution: string = this.configService.conf.value.bsData?.map['attribution']
baseStyle: BaseStyle = new BaseStyle('None')
protected _baseStyleName: string = this.configService.conf.value.map['style']
protected _baseStyleName: string = this.configService.conf.value.bsData?.map['style']
protected _bottom: number = 0
protected canvas: HTMLElement

View file

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'
import { Subject, BehaviorSubject, forkJoin } from 'rxjs'
import { ConfigService } from '../config.service'
import { ConfigService } from '../_services/config.service'
import { LayerNode } from './models'
import { TaggedLayer, Feature, FeatureWithField } from '../info/info-data.service'
import { BaseMapWithStores } from '../openapi'
@ -35,11 +35,11 @@ export class MapControlService {
public tempSelection = new BehaviorSubject<Object>({})
public search = new Subject<boolean>()
public baseMapLayers: BehaviorSubject<Set<string>> = new BehaviorSubject(new Set())
public status = new BehaviorSubject<string[]>(this.configService.conf.value.map['defaultStatus'])
public status = new BehaviorSubject<string[]>(this.configService.conf.value.bsData?.map['defaultStatus'])
public hasLabels = new BehaviorSubject<boolean>(false)
public hasTags = new BehaviorSubject<boolean>(false)
public layerLoaded = new Subject<LayerNode>()
public baseStyleName = new BehaviorSubject<string>(this.configService.conf.value.map['style'])
public baseStyleName = new BehaviorSubject<string>(this.configService.conf.value.bsData?.map['style'])
public addBaseMap = new Subject<BaseMapWithStores>()
public mapLoaded = new BehaviorSubject<boolean>(false)

View file

@ -89,7 +89,7 @@ export class MapDataService {
// }
getBaseStyle(styleName: string): Observable<BaseStyle> {
return this.mapService.getBaseStyleApiMapBaseStyleNameGet({name: styleName}).pipe(map(
return this.mapService.getBaseStyleApiMapBaseStyleNameGet(styleName).pipe(map(
data => new BaseStyle(data.name, <any>data.style)
))
}

View file

@ -46,7 +46,7 @@
<ng-template matMenuContent let-node="node">
<button mat-menu-item (click)="downloadGpkg(node)">
<mat-icon>file_download</mat-icon>
<span>Download Geopackage (EPSG {{ (configService.conf | async).geo['srid'] }})</span>
<span>Download Geopackage (EPSG {{ (configService.conf | async).bsData?.geo.srid_for_proj }})</span>
</button>
</ng-template>
</mat-menu>
@ -55,19 +55,19 @@
<ng-template matMenuContent let-node="node">
<button mat-menu-item (click)="downloadGpkg(node)">
<mat-icon>file_download</mat-icon>
<span>Download Geopackage (EPSG {{ (configService.conf | async).proj['srid'] }})</span>
<span>Download Geopackage (EPSG {{ (configService.conf | async).bsData?.geo.srid_for_proj }})</span>
</button>
<button mat-menu-item (click)="downloadDXF(node)">
<mat-icon>file_download</mat-icon>
<span>Download DXF (EPSG {{ (configService.conf | async).proj['srid'] }})</span>
<span>Download DXF (EPSG {{ (configService.conf | async).bsData?.geo.proj })</span>
</button>
<button mat-menu-item (click)="downloadDXF(node, true)">
<mat-icon>file_download</mat-icon>
<span>Download DXF (reprojected to EPSG {{ (configService.conf | async).proj['srid_for_proj'] }})</span>
<span>Download DXF (reprojected to EPSG {{ (configService.conf | async).bsData?.geo.srid_for_proj }})</span>
</button>
<button mat-menu-item (click)="downloadLayer(node)">
<mat-icon>file_download</mat-icon>
<span>Download Shapefile (EPSG {{ (configService.conf | async).proj['srid'] }}) [<i><b>Deprecated</b>: use Geopackage</i>]</span>
<span>Download Shapefile (EPSG {{ (configService.conf | async).bsData?.geo.srid }}) [<i><b>Deprecated</b>: use Geopackage</i>]</span>
</button>
<button mat-menu-item (click)="downloadRawData(node)" *ngIf="node.rawSurveyStore">
<mat-icon>file_download</mat-icon>

View file

@ -10,7 +10,7 @@ import { MatSnackBar } from '@angular/material/snack-bar'
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'
import { ConfigService } from '../../config.service'
import { ConfigService } from '../../_services/config.service'
import { Node, PrimaryGroupNode, LayerNode, TreeData } from '../models'
import { MapControlService } from '../map-control.service'
import { MapDataService } from '../map-data.service'

View file

@ -1,6 +1,6 @@
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'
import { ActivatedRoute, Router, ParamMap } from '@angular/router'
import { ConfigService } from '../../config.service'
import { ConfigService } from '../../_services/config.service'
@Component({
selector: 'gisaf-measures-home',
@ -14,8 +14,8 @@ export class MeasuresHomeComponent implements OnInit {
ngOnInit() {
this.configService.conf.subscribe(
conf => {
if (conf.measures) {
this.router.navigate(['/measures', conf.measures['defaultStore']])
if (conf.bsData?.measures) {
this.router.navigate(['/measures', conf.bsData.measures['defaultStore']])
}
}
)

View file

@ -6,7 +6,7 @@ import { Observable, of } from 'rxjs'
import { map, startWith, switchMap } from 'rxjs/operators'
import { DataService } from '../../_services/data.service'
import { ConfigService } from '../../config.service'
import { ConfigService } from '../../_services/config.service'
import { MeasuresItem, DataProvider } from '../../openapi'
@Component({

View file

@ -6,7 +6,7 @@ import { map, startWith, switchMap } from 'rxjs/operators'
import { Observable, of } from 'rxjs'
import { DataService } from '../_services/data.service'
import { ConfigService } from '../config.service'
import { ConfigService } from '../_services/config.service'
import { DataProvider } from '../openapi'
@Component({

View file

@ -21,7 +21,7 @@ export type OpenAPIConfig = {
export const OpenAPI: OpenAPIConfig = {
BASE: '',
VERSION: '2023.4.dev37+gb00bf1f.d20240226',
VERSION: '2023.4.dev51+g15fe7fa.d20240318',
WITH_CREDENTIALS: false,
CREDENTIALS: 'include',
TOKEN: undefined,

View file

@ -29,14 +29,13 @@ export class AdminService {
}
/**
* Get Basket
* @param name
* @returns AdminBasket Successful Response
* @throws ApiError
*/
public getBasketApiAdminBasketNameGet({
name,
}: {
public getBasketApiAdminBasketNameGet(
name: string,
}): Observable<AdminBasket> {
): Observable<AdminBasket> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/api/admin/basket/{name}',

View file

@ -43,14 +43,13 @@ export class ApiService {
}
/**
* Login For Access Token
* @param formData
* @returns Token Successful Response
* @throws ApiError
*/
public loginForAccessTokenApiTokenPost({
formData,
}: {
public loginForAccessTokenApiTokenPost(
formData: Body_login_for_access_token_api_token_post,
}): Observable<Token> {
): Observable<Token> {
return __request(OpenAPI, this.http, {
method: 'POST',
url: '/api/token',
@ -62,6 +61,20 @@ export class ApiService {
},
});
}
/**
* Logout
* @returns any Successful Response
* @throws ApiError
*/
public logoutApiLogoutGet(): Observable<any> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/api/logout',
errors: {
404: `Not found`,
},
});
}
/**
* Get Users
* @returns UserRead Successful Response
@ -154,14 +167,13 @@ export class ApiService {
* Json REST store API compatible with Flask Potion and Angular
* Get the list of items (used for making the list of items in measures)
* Filter only items with at least one measure
* @param store
* @returns MeasuresItem Successful Response
* @throws ApiError
*/
public getModelListApiDataProviderStoreGet({
store,
}: {
public getModelListApiDataProviderStoreGet(
store: string,
}): Observable<Array<MeasuresItem>> {
): Observable<Array<MeasuresItem>> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/api/data-provider/{store}',
@ -177,20 +189,19 @@ export class ApiService {
/**
* Get Model Values
* Get values
* @param storeName
* @param value
* @param where
* @param resample
* @returns any Successful Response
* @throws ApiError
*/
public getModelValuesApiStoreNameValuesValueGet({
storeName,
value,
where,
resample,
}: {
public getModelValuesApiStoreNameValuesValueGet(
storeName: string,
value: string,
where: string,
resample?: (string | null),
}): Observable<any> {
): Observable<any> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/api/{store_name}/values/{value}',
@ -252,16 +263,15 @@ export class ApiService {
}
/**
* Get Feature Info
* @param store
* @param id
* @returns any Successful Response
* @throws ApiError
*/
public getFeatureInfoApiFeatureInfoStoreIdGet({
store,
id,
}: {
public getFeatureInfoApiFeatureInfoStoreIdGet(
store: string,
id: string,
}): Observable<(FeatureInfo | null)> {
): Observable<(FeatureInfo | null)> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/api/feature-info/{store}/{id}',
@ -277,14 +287,13 @@ export class ApiService {
}
/**
* Get Model Info
* @param store
* @returns ModelInfo Successful Response
* @throws ApiError
*/
public getModelInfoApiModelInfoStoreGet({
store,
}: {
public getModelInfoApiModelInfoStoreGet(
store: string,
}): Observable<ModelInfo> {
): Observable<ModelInfo> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/api/model-info/{store}',
@ -299,18 +308,17 @@ export class ApiService {
}
/**
* Get Plot Params
* @param store
* @param id
* @param value
* @returns PlotParams Successful Response
* @throws ApiError
*/
public getPlotParamsApiPlotParamsStoreGet({
store,
id,
value,
}: {
public getPlotParamsApiPlotParamsStoreGet(
store: string,
id: string,
value: string,
}): Observable<PlotParams> {
): Observable<PlotParams> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/api/plot-params/{store}',

View file

@ -45,16 +45,15 @@ export class DashboardService {
}
/**
* Get Dashboard Page
* @param group
* @param name
* @returns DashboardPage_ Successful Response
* @throws ApiError
*/
public getDashboardPageApiDashboardPageGroupNameGet({
group,
name,
}: {
public getDashboardPageApiDashboardPageGroupNameGet(
group: string,
name: string,
}): Observable<DashboardPage_> {
): Observable<DashboardPage_> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/api/dashboard/page/{group}/{name}',

View file

@ -17,20 +17,19 @@ export class GeoapiService {
* Some REST stores coded manually (route prefixed with "gj": geojson).
* :param store_name: name of the model
* :return: json
* @param storeName
* @param ifNoneMatch
* @param simplify
* @param preserveTopology
* @returns any Successful Response
* @throws ApiError
*/
public getGeojsonApiGjStoreNameGet({
storeName,
ifNoneMatch,
simplify,
preserveTopology,
}: {
public getGeojsonApiGjStoreNameGet(
storeName: any,
ifNoneMatch?: (string | null),
simplify?: (number | null),
preserveTopology?: (boolean | null),
}): Observable<any> {
): Observable<any> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/api/gj/{store_name}',

View file

@ -28,14 +28,13 @@ export class MapService {
}
/**
* Get Base Style
* @param name
* @returns BaseStyle Successful Response
* @throws ApiError
*/
public getBaseStyleApiMapBaseStyleNameGet({
name,
}: {
public getBaseStyleApiMapBaseStyleNameGet(
name: string,
}): Observable<BaseStyle> {
): Observable<BaseStyle> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/api/map/base_style/{name}',
@ -49,14 +48,13 @@ export class MapService {
}
/**
* Get Layer Style
* @param store
* @returns any Successful Response
* @throws ApiError
*/
public getLayerStyleApiMapLayerStyleStoreGet({
store,
}: {
public getLayerStyleApiMapLayerStyleStoreGet(
store: string,
}): Observable<(MaplibreStyle | null)> {
): Observable<(MaplibreStyle | null)> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/api/map/layer_style/{store}',