diff --git a/AMW_angular/io/src/app/resource/resource-template.ts b/AMW_angular/io/src/app/resource/resource-template.ts new file mode 100644 index 000000000..9c8da0b71 --- /dev/null +++ b/AMW_angular/io/src/app/resource/resource-template.ts @@ -0,0 +1,9 @@ +export interface ResourceTemplate { + id: number; + relatedResourceIdentifier: string; + name: string; + targetPath: string; + targetPlatforms: string[]; + fileContent: string; + sourceType?: 'RESOURCE' | 'RESOURCE_TYPE'; +} diff --git a/AMW_angular/io/src/app/resource/resource-templates.service.ts b/AMW_angular/io/src/app/resource/resource-templates.service.ts new file mode 100644 index 000000000..b5f94a570 --- /dev/null +++ b/AMW_angular/io/src/app/resource/resource-templates.service.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { ResourceTemplate } from './resource-template'; +import { catchError, shareReplay, switchMap } from 'rxjs/operators'; +import { Observable, Subject } from 'rxjs'; +import { BaseService } from '../base/base.service'; +import { toSignal } from '@angular/core/rxjs-interop'; + +@Injectable({ providedIn: 'root' }) +export class ResourceTemplatesService extends BaseService { + private templates$: Subject = new Subject(); + private templatesForType$: Subject = new Subject(); + + private templateById$: Observable = this.templates$.pipe( + switchMap((id: number) => this.getResourceTemplates(id)), + shareReplay(1), + ); + + private templateByTypeId$: Observable = this.templatesForType$.pipe( + switchMap((id: number) => this.getResourceTypeTemplates(id)), + shareReplay(1), + ); + + resourceTemplates = toSignal(this.templateById$, { initialValue: [] }); + + resourceTypeTemplates = toSignal(this.templateByTypeId$, { initialValue: [] }); + + constructor(private http: HttpClient) { + super(); + } + + setIdForResourceTemplateList(id: number) { + this.templates$.next(id); + } + + setIdForResourceTypeTemplateList(id: number) { + this.templatesForType$.next(id); + } + + getResourceTemplates(id: number): Observable { + return this.http + .get(`${this.getBaseUrl()}/resources/templates/${id}`, { + headers: this.getHeaders(), + }) + .pipe(catchError(this.handleError)); + } + + getResourceTypeTemplates(id: number): Observable { + return this.http + .get(`${this.getBaseUrl()}/resources/templates/resourceType/${id}`, { + headers: this.getHeaders(), + }) + .pipe(catchError(this.handleError)); + } +} diff --git a/AMW_angular/io/src/app/resource/resource-types.service.ts b/AMW_angular/io/src/app/resource/resource-types.service.ts index d6669b459..d9d392e12 100644 --- a/AMW_angular/io/src/app/resource/resource-types.service.ts +++ b/AMW_angular/io/src/app/resource/resource-types.service.ts @@ -10,7 +10,7 @@ import { ResourceTypeRequest } from './resource-type-request'; @Injectable({ providedIn: 'root' }) export class ResourceTypesService extends BaseService { private reload$ = new Subject(); - private resourceTypeId$: Subject = new Subject(); + private resourceTypeId$: Subject = new Subject(); private predefinedResourceTypes$ = this.getPredefinedResourceTypes(); private rootResourceTypes$ = this.reload$.pipe( diff --git a/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.html b/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.html index 48bbb36a5..88d9df63a 100644 --- a/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.html +++ b/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.html @@ -5,22 +5,14 @@
@if ((this.resource()?.name && !permissions().canEditResource)) {
- Not Authorized! You are not allowed to Edit-Resources. + Not Authorized! You are not allowed to edit resources.
} @else if (id() === 0) {
- Please provide a resource-id to edit a resource. + Please provide the resource id to edit a resource.
} @else { - + }
diff --git a/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.ts b/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.ts index b01a97165..f3558f428 100644 --- a/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.ts +++ b/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, inject, Signal, signal } from '@angular/core'; +import { Component, computed, inject, Signal } from '@angular/core'; import { LoadingIndicatorComponent } from '../../shared/elements/loading-indicator.component'; import { PageComponent } from '../../layout/page/page.component'; import { ActivatedRoute } from '@angular/router'; @@ -6,21 +6,25 @@ import { map } from 'rxjs/operators'; import { toSignal } from '@angular/core/rxjs-interop'; import { ResourceService } from '../../resource/resource.service'; import { Resource } from '../../resource/resource'; -import { EntryAction, TileListEntry, TileListEntryOutput } from '../../shared/tile/tile-list/tile-list.component'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TileComponent } from '../../shared/tile/tile.component'; import { AuthService } from '../../auth/auth.service'; import { ResourceFunctionsListComponent } from './resource-functions/resource-functions-list.component'; +import { ResourceTemplatesListComponent } from './resource-templates/resource-templates-list.component'; @Component({ selector: 'app-resource-edit', standalone: true, - imports: [LoadingIndicatorComponent, PageComponent, TileComponent, ResourceFunctionsListComponent], + imports: [ + LoadingIndicatorComponent, + PageComponent, + TileComponent, + ResourceFunctionsListComponent, + ResourceTemplatesListComponent, + ], templateUrl: './resource-edit.component.html', }) export class ResourceEditComponent { private authService = inject(AuthService); - private modalService = inject(NgbModal); private resourceService = inject(ResourceService); private route = inject(ActivatedRoute); @@ -44,51 +48,4 @@ export class ResourceEditComponent { return { canEditResource: false }; } }); - - templatesData = signal([ - { - title: 'Instance Templates', - entries: [ - { name: 'startJob_0.sh', description: 'startJob_0.sh', id: 0 }, - { name: 'startJob_1.sh', description: 'job 2 again', id: 1 }, - ] as TileListEntry[], - canEdit: true, - canDelete: true, - }, - { - title: 'Resource Type Templates', - entries: [{ name: 'seg', description: 'segmentation', id: 666 }] as TileListEntry[], - canOverwrite: false, - }, - ]); - - add() { - this.modalService.open('This would open a modal to add something'); - } - - doListAction($event: TileListEntryOutput) { - switch ($event.action) { - case EntryAction.edit: - this.edit($event.id); - return; - case EntryAction.delete: - this.delete($event.id); - return; - case EntryAction.overwrite: - this.overwrite($event.id); - return; - } - } - - private edit(id: number) { - this.modalService.open('This would open a modal to edit with id:' + id); - } - - private delete(id: number) { - this.modalService.open('This would open a modal to delete with id:' + id); - } - - private overwrite(id: number) { - this.modalService.open('This would open a modal to overwrite with id:' + id); - } } diff --git a/AMW_angular/io/src/app/resources/resource-edit/resource-functions/resource-functions-list.component.html b/AMW_angular/io/src/app/resources/resource-edit/resource-functions/resource-functions-list.component.html index d440c8f9c..430eb4074 100644 --- a/AMW_angular/io/src/app/resources/resource-edit/resource-functions/resource-functions-list.component.html +++ b/AMW_angular/io/src/app/resources/resource-edit/resource-functions/resource-functions-list.component.html @@ -1,7 +1,7 @@ + diff --git a/AMW_angular/io/src/app/resources/resource-edit/resource-templates/resource-templates-list.component.spec.ts b/AMW_angular/io/src/app/resources/resource-edit/resource-templates/resource-templates-list.component.spec.ts new file mode 100644 index 000000000..a0b92d7a0 --- /dev/null +++ b/AMW_angular/io/src/app/resources/resource-edit/resource-templates/resource-templates-list.component.spec.ts @@ -0,0 +1,27 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { InputSignal, signal } from '@angular/core'; +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { ResourceTemplatesListComponent } from './resource-templates-list.component'; +import { Resource } from '../../../resource/resource'; + +describe('ResourceTemplatesComponent', () => { + let component: ResourceTemplatesListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ResourceTemplatesListComponent], + providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()], + }).compileComponents(); + + fixture = TestBed.createComponent(ResourceTemplatesListComponent); + component = fixture.componentInstance; + component.resource = signal(null) as unknown as InputSignal; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AMW_angular/io/src/app/resources/resource-edit/resource-templates/resource-templates-list.component.ts b/AMW_angular/io/src/app/resources/resource-edit/resource-templates/resource-templates-list.component.ts new file mode 100644 index 000000000..e72e21065 --- /dev/null +++ b/AMW_angular/io/src/app/resources/resource-edit/resource-templates/resource-templates-list.component.ts @@ -0,0 +1,128 @@ +import { Component, computed, inject, input, OnDestroy } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { LoadingIndicatorComponent } from '../../../shared/elements/loading-indicator.component'; +import { TileComponent } from '../../../shared/tile/tile.component'; +import { EntryAction, TileListEntryOutput } from '../../../shared/tile/tile-list/tile-list.component'; +import { Action, AuthService } from '../../../auth/auth.service'; +import { Resource } from '../../../resource/resource'; +import { Subject } from 'rxjs'; +import { ResourceTemplatesService } from '../../../resource/resource-templates.service'; +import { ResourceTemplate } from '../../../resource/resource-template'; + +const RESOURCE_PERM = 'RESOURCE_TEMPLATE'; +const RESOURCETYPE_PERM = 'RESOURCETYPE_TEMPLATE'; + +@Component({ + selector: 'app-resource-templates-list', + standalone: true, + imports: [LoadingIndicatorComponent, TileComponent], + templateUrl: './resource-templates-list.component.html', +}) +export class ResourceTemplatesListComponent implements OnDestroy { + private authService = inject(AuthService); + private modalService = inject(NgbModal); + private templatesService = inject(ResourceTemplatesService); + private destroy$ = new Subject(); + + resource = input.required(); + contextId = input.required(); + templates = this.templatesService.resourceTemplates; + + isLoading = computed(() => { + if (this.resource() != null) { + this.templatesService.setIdForResourceTemplateList(this.resource().id); + return false; + } + }); + + permissions = computed(() => { + if (this.authService.restrictions().length > 0 && this.resource()) { + return { + canShowInstanceTemplates: this.authService.hasPermission(RESOURCE_PERM, Action.READ), + canShowTypeTemplates: this.authService.hasPermission(RESOURCETYPE_PERM, Action.READ), + canAdd: + (this.contextId() === 1 || this.contextId === null) && + this.authService.hasResourceGroupPermission(RESOURCE_PERM, Action.CREATE, this.resource().resourceGroupId), + canEdit: + (this.contextId() === 1 || this.contextId === null) && + this.authService.hasResourceGroupPermission(RESOURCE_PERM, Action.UPDATE, this.resource().resourceGroupId), + canDelete: + (this.contextId() === 1 || this.contextId === null) && + this.authService.hasResourceGroupPermission(RESOURCE_PERM, Action.DELETE, this.resource().resourceGroupId), + }; + } else { + return { + canShowInstanceTemplates: false, + canShowTypeTemplates: false, + canAdd: false, + canEdit: false, + canDelete: false, + }; + } + }); + + templatesData = computed(() => { + if (this.templates()?.length > 0) { + const instanceTemplates = this.mapListEntries( + this.templates().filter((template) => template.sourceType === 'RESOURCE'), + ); + const typeTemplates = this.mapListEntries( + this.templates().filter((template) => template.sourceType === 'RESOURCE_TYPE'), + ); + + const result = []; + if (this.permissions().canShowInstanceTemplates) { + result.push({ + title: 'Resource Instance Templates', + entries: instanceTemplates, + canEdit: this.permissions().canEdit, + canDelete: this.permissions().canDelete, + }); + } + if (this.permissions().canShowTypeTemplates) { + result.push({ + title: 'Resource Type Templates', + entries: typeTemplates, + canEdit: false, + canDelete: false, + }); + } + return result; + } else return null; + }); + + ngOnDestroy(): void { + this.destroy$.next(undefined); + } + + doListAction($event: TileListEntryOutput) { + switch ($event.action) { + case EntryAction.edit: + this.editTemplate($event.id); + return; + case EntryAction.delete: + this.deleteTemplate($event.id); + return; + } + } + + mapListEntries(templates: ResourceTemplate[]) { + return templates.map((template) => ({ + name: template.name, + description: template.targetPath, + id: template.id, + })); + } + + addTemplate() { + this.modalService.open('This would open a modal to add a new instance template'); + } + + private editTemplate(id: number) { + this.modalService.open('This would open a modal to edit template with id: ' + id); + } + + private deleteTemplate(id: number) { + this.modalService.open('This would open a modal to delete template with id: ' + id); + } +} diff --git a/AMW_angular/io/src/app/resources/resource-functions.service.ts b/AMW_angular/io/src/app/resources/resource-functions.service.ts index 326b06677..71662ca49 100644 --- a/AMW_angular/io/src/app/resources/resource-functions.service.ts +++ b/AMW_angular/io/src/app/resources/resource-functions.service.ts @@ -11,8 +11,8 @@ import { RevisionInformation } from '../shared/model/revisionInformation'; @Injectable({ providedIn: 'root' }) export class ResourceFunctionsService extends BaseService { private path = `${this.getBaseUrl()}/resources`; - private functions$: Subject = new Subject(); - private functionsForType$: Subject = new Subject(); + private functions$: Subject = new Subject(); + private functionsForType$: Subject = new Subject(); private functionById$: Observable = this.functions$.pipe( switchMap((id: number) => this.getResourceFunctions(id)), diff --git a/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-edit.component.html b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-edit.component.html index 2ed86e3fa..bfbb2dc7d 100644 --- a/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-edit.component.html +++ b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-edit.component.html @@ -12,15 +12,10 @@ Please provide a resourceType-id to edit a resource. } @else { - + + diff --git a/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-templates/resource-type-templates-list.component.spec.ts b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-templates/resource-type-templates-list.component.spec.ts new file mode 100644 index 000000000..41d1c968f --- /dev/null +++ b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-templates/resource-type-templates-list.component.spec.ts @@ -0,0 +1,27 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { InputSignal, signal } from '@angular/core'; +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { ResourceTypeTemplatesListComponent } from './resource-type-templates-list.component'; +import { ResourceType } from '../../../resource/resource-type'; + +describe('ResourceTypeTemplatesListComponent', () => { + let component: ResourceTypeTemplatesListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ResourceTypeTemplatesListComponent], + providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()], + }).compileComponents(); + + fixture = TestBed.createComponent(ResourceTypeTemplatesListComponent); + component = fixture.componentInstance; + component.resourceType = signal(null) as unknown as InputSignal; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-templates/resource-type-templates-list.component.ts b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-templates/resource-type-templates-list.component.ts new file mode 100644 index 000000000..dff35b1c4 --- /dev/null +++ b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-templates/resource-type-templates-list.component.ts @@ -0,0 +1,111 @@ +import { Component, computed, inject, input, OnDestroy } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { LoadingIndicatorComponent } from '../../../shared/elements/loading-indicator.component'; +import { TileComponent } from '../../../shared/tile/tile.component'; +import { EntryAction, TileListEntryOutput } from '../../../shared/tile/tile-list/tile-list.component'; +import { Action, AuthService } from '../../../auth/auth.service'; +import { Subject } from 'rxjs'; +import { ResourceTemplatesService } from '../../../resource/resource-templates.service'; +import { ResourceTemplate } from '../../../resource/resource-template'; +import { ResourceType } from '../../../resource/resource-type'; + +const RESOURCETYPE_PERM = 'RESOURCETYPE_TEMPLATE'; + +@Component({ + selector: 'app-resource-type-templates-list', + standalone: true, + imports: [LoadingIndicatorComponent, TileComponent], + templateUrl: './resource-type-templates-list.component.html', +}) +export class ResourceTypeTemplatesListComponent implements OnDestroy { + private authService = inject(AuthService); + private modalService = inject(NgbModal); + private templatesService = inject(ResourceTemplatesService); + private destroy$ = new Subject(); + + resourceType = input.required(); + contextId = input.required(); + templates = this.templatesService.resourceTypeTemplates; + + isLoading = computed(() => { + if (this.resourceType() != null) { + this.templatesService.setIdForResourceTypeTemplateList(this.resourceType().id); + return false; + } + }); + + permissions = computed(() => { + if (this.authService.restrictions().length > 0 && this.resourceType()) { + return { + canShowTypeTemplates: this.authService.hasPermission(RESOURCETYPE_PERM, Action.READ), + canAdd: + (this.contextId() === 1 || this.contextId === null) && + this.authService.hasResourceTypePermission(RESOURCETYPE_PERM, Action.CREATE, this.resourceType().name), + canEdit: + (this.contextId() === 1 || this.contextId === null) && + this.authService.hasResourceTypePermission(RESOURCETYPE_PERM, Action.UPDATE, this.resourceType().name), + canDelete: + (this.contextId() === 1 || this.contextId === null) && + this.authService.hasResourceTypePermission(RESOURCETYPE_PERM, Action.DELETE, this.resourceType().name), + }; + } else { + return { + canShowTypeTemplates: false, + canAdd: false, + canEdit: false, + canDelete: false, + }; + } + }); + + templatesData = computed(() => { + if (this.templates()?.length > 0) { + const typeTemplates = this.mapListEntries(this.templates()); + return [ + { + title: 'Resource Type Templates', + entries: typeTemplates, + canEdit: this.permissions().canEdit, + canDelete: this.permissions().canDelete, + }, + ]; + } else { + return null; + } + }); + + ngOnDestroy(): void { + this.destroy$.next(undefined); + } + + doListAction($event: TileListEntryOutput) { + switch ($event.action) { + case EntryAction.edit: + this.editTemplate($event.id); + return; + case EntryAction.delete: + this.deleteTemplate($event.id); + return; + } + } + + mapListEntries(templates: ResourceTemplate[]) { + return templates.map((template) => ({ + name: template.name, + description: template.targetPath, + id: template.id, + })); + } + + addTemplate() { + this.modalService.open('This would open a modal to add a new resource type template'); + } + + private editTemplate(id: number) { + this.modalService.open('This would open a modal to edit template with id: ' + id); + } + + private deleteTemplate(id: number) { + this.modalService.open('This would open a modal to delete template with id: ' + id); + } +} diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/template/entity/TemplateDescriptorEntity.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/template/entity/TemplateDescriptorEntity.java index 59b94a390..6d13b4cff 100644 --- a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/template/entity/TemplateDescriptorEntity.java +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/template/entity/TemplateDescriptorEntity.java @@ -79,6 +79,16 @@ public class TemplateDescriptorEntity implements Identifiable, Serializable, Cop @Transient private boolean relationTemplate; + public enum TemplateSourceType { + RESOURCE, + RESOURCE_TYPE + } + + @Transient + @Getter + @Setter + private TemplateSourceType sourceType; + @Version private long v; diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/TemplateDTO.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/TemplateDTO.java index a826ebda5..75601644b 100644 --- a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/TemplateDTO.java +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/TemplateDTO.java @@ -44,6 +44,7 @@ public class TemplateDTO { private String targetPath; private Set targetPlatforms; private String fileContent; + private String sourceType; public TemplateDTO(TemplateDescriptorEntity template){ this.id = template.getId(); @@ -57,5 +58,6 @@ public TemplateDTO(TemplateDescriptorEntity template){ } } this.fileContent = template.getFileContent(); + this.sourceType = template.getSourceType() != null ? template.getSourceType().name() : null; } } diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/resources/ResourceTemplatesRest.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/resources/ResourceTemplatesRest.java index ae53bc1d9..7eb526cd9 100644 --- a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/resources/ResourceTemplatesRest.java +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/resources/ResourceTemplatesRest.java @@ -23,20 +23,19 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; +import javax.ws.rs.*; import javax.ws.rs.core.Response; import ch.mobi.itc.mobiliar.rest.dtos.TemplateDTO; import ch.puzzle.itc.mobiliar.business.resourcegroup.boundary.ResourceGroupLocator; +import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceEntity; import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceGroupEntity; +import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceTypeEntity; import ch.puzzle.itc.mobiliar.business.template.boundary.TemplateEditor; import ch.puzzle.itc.mobiliar.business.template.control.TemplatesScreenDomainService; import ch.puzzle.itc.mobiliar.business.template.entity.TemplateDescriptorEntity; @@ -46,14 +45,19 @@ import ch.puzzle.itc.mobiliar.common.exception.ValidationException; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import javax.persistence.EntityManager; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; /** - * Rest boundary for Resource-Templates + * Rest boundary for Resource and Resource Type Templates */ @RequestScoped -@Path("/resources/{resourceGroupName}/{releaseName}/templates") -@Api(value = "/resources/{resourceGroupName}/{releaseName}/templates", description = "Resource templates") -public class ResourceTemplatesRest {; +@Path("/resources/templates") +@Api(value = "/resources/templates") +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +public class ResourceTemplatesRest { @Inject ResourceGroupLocator resourceGroupLocator; @@ -64,7 +68,11 @@ public class ResourceTemplatesRest {; @Inject TemplateEditor templateEditor; + @Inject + private EntityManager entityManager; + @GET + @Path("/{resourceGroupName}/{releaseName}") @ApiOperation(value = "Get all templates for a resource in a specific release") public List getResourceTemplates(@PathParam("resourceGroupName") String resourceGroupName, @PathParam("releaseName") String releaseName) throws ValidationException { @@ -79,7 +87,43 @@ public List getResourceTemplates(@PathParam("resourceGroupName") St } @GET - @Path("/{templateName}") + @Path("/{resourceId: \\d+}") + @ApiOperation(value = "Get all templates for a resource, including inherited resource type templates") + public List getResourceTemplatesById(@PathParam("resourceId") Integer resourceId) throws NotFoundException { + ResourceEntity resource = entityManager.find(ResourceEntity.class, resourceId); + if (resource == null) { + throw new NotFoundException("Resource not found"); + } + List resourceTemplates = templateService.getGlobalTemplateDescriptorsForResource(resource); + resourceTemplates.forEach(template -> template.setSourceType(TemplateDescriptorEntity.TemplateSourceType.RESOURCE)); + List resourceTypeTemplates = templateService.getGlobalTemplateDescriptorsForResourceType(resource.getResourceType()); + resourceTypeTemplates.forEach(template -> template.setSourceType(TemplateDescriptorEntity.TemplateSourceType.RESOURCE_TYPE)); + List combinedTemplates = Stream.concat(resourceTemplates.stream(), resourceTypeTemplates.stream()) + .collect(Collectors.toList()); + return combinedTemplates.stream() + .map(TemplateDTO::new) + .collect(Collectors.toList()); + } + + @GET + @Path("/resourceType/{resourceTypeId: \\d+}") + @ApiOperation(value = "Get all templates for a resource type") + public Response getResourceTypeTemplatesById(@PathParam("resourceTypeId") Integer resourceTypeId) throws NotFoundException { + + ResourceTypeEntity resourceType = entityManager.find(ResourceTypeEntity.class, resourceTypeId); + if (resourceType == null) { + throw new NotFoundException("Resource type not found"); + } + List templates = templateService.getGlobalTemplateDescriptorsForResourceType(resourceType); + List templateDTOs = new ArrayList<>(); + for (TemplateDescriptorEntity template : templates) { + templateDTOs.add(new TemplateDTO(template)); + } + return Response.status(Response.Status.OK).entity(templateDTOs).build(); + } + + @GET + @Path("/{resourceGroupName}/{releaseName}/{templateName}") @ApiOperation(value = "Get a template for a resource in a specific release") public TemplateDTO getResourceTemplate(@PathParam("resourceGroupName") String resourceGroupName, @PathParam("releaseName") String releaseName, @@ -92,7 +136,7 @@ public TemplateDTO getResourceTemplate(@PathParam("resourceGroupName") String re } @DELETE - @Path("/{templateName}") + @Path("/{resourceGroupName}/{releaseName}/{templateName}") @ApiOperation(value = "Delete a template for a resource in a specific release") public Response deleteResourceTemplate(@PathParam("resourceGroupName") String resourceGroupName, @PathParam("releaseName") String releaseName, @@ -106,6 +150,7 @@ public Response deleteResourceTemplate(@PathParam("resourceGroupName") String re } @POST + @Path("/{resourceGroupName}/{releaseName}") @ApiOperation(value = "Create a template for a resource in a specific release") public Response createResourceTemplates(@PathParam("resourceGroupName") String resourceGroupName, @PathParam("releaseName") String releaseName, @@ -119,7 +164,7 @@ public Response createResourceTemplates(@PathParam("resourceGroupName") String r } @PUT - @Path("/{templateName}") + @Path("/{resourceGroupName}/{releaseName}/{templateName}") @ApiOperation(value = "Update a template for a resource in a specific release") public Response updateResourceTemplates(@PathParam("resourceGroupName") String resourceGroupName, @PathParam("releaseName") String releaseName,