geonetwork-ui 2.10.0-dev.89f0dfe6f → 2.10.0-dev.8f8114b9e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,7 @@ import {
6
6
  import { Injectable, InjectionToken, inject } from '@angular/core'
7
7
  import {
8
8
  assertValidXml,
9
+ BaseConverter,
9
10
  findConverterForDocument,
10
11
  Gn4Converter,
11
12
  Gn4SearchResults,
@@ -61,6 +62,12 @@ export const DISABLE_DRAFT = new InjectionToken<boolean>('gnDisableDraft', {
61
62
  factory: () => false,
62
63
  })
63
64
 
65
+ export const DEFAULT_RECORD_CONVERTER = new InjectionToken<
66
+ BaseConverter<string>
67
+ >('defaultRecordConverter', {
68
+ factory: () => new Iso19139Converter(),
69
+ })
70
+
64
71
  @Injectable()
65
72
  export class Gn4Repository implements RecordsRepositoryInterface {
66
73
  private httpClient = inject(HttpClient)
@@ -72,6 +79,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
72
79
  private gn4LanguagesApi = inject(LanguagesApiService)
73
80
  private settingsService = inject(Gn4SettingsService)
74
81
  private disableDraft = inject(DISABLE_DRAFT, { optional: true }) ?? false
82
+ private defaultConverter = inject(DEFAULT_RECORD_CONVERTER)
75
83
 
76
84
  _draftsChanged = new Subject<void>()
77
85
  draftsChanged$ = this._draftsChanged.asObservable()
@@ -377,38 +385,52 @@ export class Gn4Repository implements RecordsRepositoryInterface {
377
385
  openRecordForDuplication(
378
386
  uniqueIdentifier: string
379
387
  ): Observable<[CatalogRecord, string, true] | null> {
380
- return this.gn4RecordsApi
381
- .create(
382
- uniqueIdentifier,
383
- '2',
384
- 'METADATA',
385
- '',
386
- false,
387
- undefined,
388
- true,
389
- false,
390
- undefined,
391
- 'body',
392
- false,
393
- {
394
- httpHeaderAccept: 'application/json',
395
- httpContentTypeSelected: 'application/json;charset=UTF-8',
396
- }
397
- )
398
- .pipe(
399
- switchMap((uniqueIdentifier) => {
400
- return this.getRecordAsXml(uniqueIdentifier)
401
- }),
402
- switchMap((xml) => {
403
- return from(
404
- findConverterForDocument(xml)
405
- .readRecord(xml)
406
- .then((record) => {
407
- return [record, xml, true] as [CatalogRecord, string, true]
408
- })
388
+ return this.platformService.getUserPermissionsByGroup().pipe(
389
+ map((permissions) => {
390
+ const groupId =
391
+ permissions.find((p) => p.canApprove)?.groupId?.toString() ??
392
+ permissions.find((p) => p.canEdit)?.groupId?.toString()
393
+ if (!groupId)
394
+ throw new Error(
395
+ 'Current user has no writable group to duplicate into'
396
+ )
397
+ return groupId
398
+ }),
399
+ switchMap((groupId) =>
400
+ this.gn4RecordsApi
401
+ .create(
402
+ uniqueIdentifier,
403
+ groupId,
404
+ 'METADATA',
405
+ '',
406
+ false,
407
+ undefined,
408
+ true,
409
+ false,
410
+ undefined,
411
+ 'body',
412
+ false,
413
+ {
414
+ httpHeaderAccept: 'application/json',
415
+ httpContentTypeSelected: 'application/json;charset=UTF-8',
416
+ }
417
+ )
418
+ .pipe(
419
+ switchMap((uniqueIdentifier) => {
420
+ return this.getRecordAsXml(uniqueIdentifier)
421
+ }),
422
+ switchMap((xml) => {
423
+ return from(
424
+ findConverterForDocument(xml)
425
+ .readRecord(xml)
426
+ .then((record) => {
427
+ return [record, xml, true] as [CatalogRecord, string, true]
428
+ })
429
+ )
430
+ })
409
431
  )
410
- })
411
432
  )
433
+ )
412
434
  }
413
435
 
414
436
  saveRecord(
@@ -605,10 +627,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
605
627
  record: CatalogRecord,
606
628
  referenceRecordSource?: string
607
629
  ): Observable<string> {
608
- // if there's a reference record, use that standard; otherwise, use iso19139
630
+ // if there's a reference record, use that standard; otherwise, use standard based on configuration or default
609
631
  const converter = referenceRecordSource
610
632
  ? findConverterForDocument(referenceRecordSource)
611
- : new Iso19139Converter()
633
+ : this.defaultConverter
612
634
  return from(converter.writeRecord(record, referenceRecordSource))
613
635
  }
614
636
 
@@ -12,11 +12,13 @@ import {
12
12
  } from '../../../../../../../libs/common/domain/src/lib/model/record'
13
13
  import { KeywordType } from '../../../../../../../libs/common/domain/src/lib/model/thesaurus'
14
14
  import { UserModel } from '../../../../../../../libs/common/domain/src/lib/model/user/user.model'
15
+ import { GroupModel } from '../../../../../../../libs/common/domain/src/lib/model/user/group.model'
15
16
  import {
16
17
  PlatformServiceInterface,
17
18
  UploadEvent,
18
19
  } from '../../../../../../../libs/common/domain/src/lib/platform.service.interface'
19
20
  import {
21
+ GroupsApiService,
20
22
  MeApiService,
21
23
  RecordsApiService,
22
24
  RegistriesApiService,
@@ -57,6 +59,7 @@ export const DISABLE_AUTH = new InjectionToken<boolean>('gnDisableAuth', {
57
59
  export class Gn4PlatformService implements PlatformServiceInterface {
58
60
  private meApi = inject(MeApiService)
59
61
  private usersApi = inject(UsersApiService)
62
+ private groupsApi = inject(GroupsApiService)
60
63
  private mapper = inject(Gn4PlatformMapper)
61
64
  private toolsApiService = inject(ToolsApiService)
62
65
  private registriesApiService = inject(RegistriesApiService)
@@ -155,6 +158,49 @@ export class Gn4PlatformService implements PlatformServiceInterface {
155
158
  return this.users$
156
159
  }
157
160
 
161
+ getUserPermissionsByGroup(): Observable<GroupModel[]> {
162
+ if (this.disableAuth) return of([])
163
+ return combineLatest([this.meApi.getMe(), this.groupsApi.getGroups()]).pipe(
164
+ map(([meResponse, groups]) => {
165
+ if (!meResponse) return []
166
+ if (meResponse.admin) {
167
+ return groups.map((group) => ({
168
+ groupId: group.id,
169
+ groupName: group.name,
170
+ isMember: true,
171
+ canEdit: true,
172
+ canApprove: true,
173
+ canAdministrate: true,
174
+ }))
175
+ }
176
+ const reviewerIds = meResponse.groupsWithReviewer ?? []
177
+ const editorIds = meResponse.groupsWithEditor ?? []
178
+ const memberIds = meResponse.groupsWithRegisteredUser ?? []
179
+ const adminIds = meResponse.groupsWithUserAdmin ?? []
180
+ const groupsById = new Map(groups.map((g) => [g.id, g]))
181
+ return [
182
+ ...new Set([
183
+ ...reviewerIds,
184
+ ...editorIds,
185
+ ...groups.map((g) => g.id),
186
+ ]),
187
+ ]
188
+ .filter((id) => groupsById.has(id))
189
+ .map((id) => {
190
+ const group = groupsById.get(id)
191
+ return {
192
+ groupId: group.id,
193
+ groupName: group.name,
194
+ isMember: memberIds.includes(id),
195
+ canEdit: editorIds.includes(id),
196
+ canApprove: reviewerIds.includes(id),
197
+ canAdministrate: adminIds.includes(id),
198
+ }
199
+ })
200
+ })
201
+ )
202
+ }
203
+
158
204
  translateKey(key: string): Observable<string> {
159
205
  // if the key is a URI, use the registries API to look for the translation
160
206
  if (key.match(/^https?:\/\//)) {
@@ -0,0 +1,8 @@
1
+ export interface GroupModel {
2
+ groupId: number
3
+ groupName: string
4
+ isMember: boolean
5
+ canEdit: boolean
6
+ canApprove: boolean
7
+ canAdministrate: boolean
8
+ }
@@ -1 +1,2 @@
1
1
  export * from './user.model'
2
+ export * from './group.model'
@@ -1,5 +1,6 @@
1
1
  import type { Observable } from 'rxjs'
2
2
  import type { UserModel } from './model/user/user.model'
3
+ import type { GroupModel } from './model/user/group.model'
3
4
  import type { Organization } from './model/record/organization.model'
4
5
  import { CatalogRecord, Keyword, UserFeedback } from './model/record'
5
6
  import { KeywordType } from './model/thesaurus'
@@ -28,6 +29,7 @@ export abstract class PlatformServiceInterface {
28
29
  abstract getMe(): Observable<UserModel>
29
30
  abstract isAnonymous(): Observable<boolean>
30
31
  abstract getUsers(): Observable<UserModel[]>
32
+ abstract getUserPermissionsByGroup(): Observable<GroupModel[]>
31
33
  abstract getUsersByOrganization(
32
34
  organisation: Organization
33
35
  ): Observable<UserModel[]>
@@ -359,7 +359,6 @@ export const simpleDatasetRecordFixture = (): DatasetRecord => ({
359
359
  securityConstraints: [],
360
360
  otherConstraints: [],
361
361
  lineage: 'This record was edited manually to test the conversion processes',
362
- sourceRecords: [],
363
362
  spatialRepresentation: 'grid',
364
363
  overviews: [],
365
364
  spatialExtents: [],
@@ -1000,7 +999,6 @@ export const multilingualDatasetFixture: () => DatasetRecord = () => ({
1000
999
  },
1001
1000
  },
1002
1001
  ],
1003
- sourceRecords: [],
1004
1002
  onlineResources: [],
1005
1003
  licenses: [],
1006
1004
  legalConstraints: [],
@@ -1,5 +1,5 @@
1
1
  import { Injectable, inject } from '@angular/core'
2
- import { forkJoin, Observable, of, switchMap } from 'rxjs'
2
+ import { Observable, switchMap } from 'rxjs'
3
3
  import { map, tap } from 'rxjs/operators'
4
4
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
5
5
  import { EditorConfig } from '../models/'
@@ -2,6 +2,7 @@ import * as TOML from '@ltd/j-toml'
2
2
  import {
3
3
  checkMetadataLanguage,
4
4
  checkNewRecordDefaultLanguage,
5
+ checkNewRecordStandard,
5
6
  parseConfigSection,
6
7
  parseMultiConfigSection,
7
8
  parseTranslationsConfigSection,
@@ -297,7 +298,7 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
297
298
  parsed,
298
299
  'editing',
299
300
  [],
300
- ['new_record_default_language'],
301
+ ['new_record_default_language', 'new_record_standard'],
301
302
  warnings,
302
303
  errors
303
304
  )
@@ -310,6 +311,15 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
310
311
  warnings
311
312
  )
312
313
  }
314
+ if (
315
+ parsedEditingSection !== null &&
316
+ parsedEditingSection.new_record_standard !== undefined
317
+ ) {
318
+ parsedEditingSection = checkNewRecordStandard(
319
+ parsedEditingSection,
320
+ warnings
321
+ )
322
+ }
313
323
  editorConfig =
314
324
  parsedEditingSection === null
315
325
  ? null
@@ -318,6 +328,9 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
318
328
  parsedEditingSection.new_record_default_language as
319
329
  | string
320
330
  | undefined,
331
+ NEW_RECORD_STANDARD: parsedEditingSection.new_record_standard as
332
+ | EditorConfig['NEW_RECORD_STANDARD']
333
+ | undefined,
321
334
  } as EditorConfig)
322
335
 
323
336
  customTranslations = parseTranslationsConfigSection(
@@ -69,8 +69,11 @@ export interface MetadataQualityConfig {
69
69
  ENABLED: boolean
70
70
  }
71
71
 
72
+ export type NewRecordStandard = 'iso19139' | 'iso19115-3'
73
+
72
74
  export interface EditorConfig {
73
75
  NEW_RECORD_DEFAULT_LANGUAGE?: string
76
+ NEW_RECORD_STANDARD?: NewRecordStandard
74
77
  }
75
78
 
76
79
  export type CustomTranslations = { [translationKey: string]: string }
@@ -159,3 +159,30 @@ export function checkNewRecordDefaultLanguage(
159
159
  new_record_default_language: lang2,
160
160
  }
161
161
  }
162
+
163
+ export function checkNewRecordStandard(
164
+ parsedConfigSection: any,
165
+ outWarnings: string[]
166
+ ) {
167
+ const standard = parsedConfigSection.new_record_standard
168
+ const normalizedStandard =
169
+ typeof standard === 'string' ? standard.trim().toLowerCase() : null
170
+
171
+ if (
172
+ normalizedStandard === 'iso19139' ||
173
+ normalizedStandard === 'iso19115-3'
174
+ ) {
175
+ return {
176
+ ...parsedConfigSection,
177
+ new_record_standard: normalizedStandard,
178
+ }
179
+ }
180
+
181
+ outWarnings.push(
182
+ `In the [editing] section: new_record_standard = "${standard}" is not a supported metadata standard`
183
+ )
184
+ return {
185
+ ...parsedConfigSection,
186
+ new_record_standard: undefined,
187
+ }
188
+ }
@@ -266,7 +266,7 @@ export function checkFileFormat(
266
266
  new RegExp(`[./]${format}`, 'i').test(link.name.toLowerCase())) ||
267
267
  ('url' in link &&
268
268
  new RegExp(`[./]${format}`, 'i').test(link.url.toString())) ||
269
- ('name' in link && link.name.toLowerCase().includes(format))
269
+ ('name' in link && new RegExp(`\\b${format}\\b`, 'i').test(link.name))
270
270
  )
271
271
  }
272
272