diff --git a/backend/.idea/workspace.xml b/backend/.idea/workspace.xml index 5f433da..e461dea 100644 --- a/backend/.idea/workspace.xml +++ b/backend/.idea/workspace.xml @@ -4,9 +4,8 @@ - - - + + @@ -251,7 +250,15 @@ 1698570969924 - + + + 1698573369764 + + + + 1698573369764 + + @@ -277,7 +284,8 @@ - + + diff --git a/backend/src/main/java/core/api/controller/ScheduleController.java b/backend/src/main/java/core/api/controller/ScheduleController.java index 34f0d79..f47b2c4 100644 --- a/backend/src/main/java/core/api/controller/ScheduleController.java +++ b/backend/src/main/java/core/api/controller/ScheduleController.java @@ -231,4 +231,25 @@ public class ScheduleController { List schedules = taskScheduleService.loadMissedSchedules(user.get()); return ResponseEntity.ok(schedules.stream().map(ScheduleInfo::new).toList()); } + + @DeleteMapping("/schedules/{scheduleIDs}/all") + public ResponseEntity> deleteSchedules(@PathVariable long[] scheduleIDs) { + List schedulesToDelete = new LinkedList<>(); + for(long scheduleID: scheduleIDs) { + PermissionResult permissionResult = taskScheduleService.getSchedulePermissions(scheduleID, SecurityContextHolder.getContext().getAuthentication().getName()); + if(!permissionResult.isHasPermissions()) { + return ResponseEntity.status(403).body(new SimpleStatusResponse("failed")); + } + + if(permissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) { + return ResponseEntity.status(404).body(new SimpleStatusResponse("failed")); + } + schedulesToDelete.add(permissionResult.getResult()); + } + + for(BasicTaskSchedule schedule : schedulesToDelete) { + this.taskScheduleService.deleteBasicSchedule(schedule); + } + return ResponseEntity.ok(new SimpleStatusResponse("success")); + } } diff --git a/frontend/src/api/api/schedule.service.ts b/frontend/src/api/api/schedule.service.ts index 688885f..e19c1ef 100644 --- a/frontend/src/api/api/schedule.service.ts +++ b/frontend/src/api/api/schedule.service.ts @@ -512,6 +512,65 @@ export class ScheduleService { ); } + /** + * deletes multiple schedules + * deletes multiple schedules at once + * @param scheduleIDs internal ids of schedules to delete + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public schedulesScheduleIDsAllDelete(scheduleIDs: Array, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public schedulesScheduleIDsAllDelete(scheduleIDs: Array, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public schedulesScheduleIDsAllDelete(scheduleIDs: Array, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public schedulesScheduleIDsAllDelete(scheduleIDs: Array, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + if (scheduleIDs === null || scheduleIDs === undefined) { + throw new Error('Required parameter scheduleIDs was null or undefined when calling schedulesScheduleIDsAllDelete.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (API_TOKEN) required + localVarCredential = this.configuration.lookupCredential('API_TOKEN'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + let responseType_: 'text' | 'json' = 'json'; + if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } + + return this.httpClient.delete>(`${this.configuration.basePath}/schedules/${encodeURIComponent(String(scheduleIDs))}/all`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + /** * get number of active minutes * get number of worked minutes today diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 051dbee..8bf5716 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -70,6 +70,7 @@ import { TaskOverviewComponent } from './dashboard/task-overview/task-overview.c import { ForgottenTaskStartDialogComponent } from './dashboard/forgotten-task-start-dialog/forgotten-task-start-dialog.component'; import {MatAutocompleteModule} from "@angular/material/autocomplete"; import { MissedSchedulesComponent } from './missed-schedules/missed-schedules.component'; +import { MissedScheduleForgetConfirmationDialogComponent } from './missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component'; @NgModule({ declarations: [ AppComponent, @@ -102,7 +103,8 @@ import { MissedSchedulesComponent } from './missed-schedules/missed-schedules.co TaskgroupOverviewComponent, TaskOverviewComponent, ForgottenTaskStartDialogComponent, - MissedSchedulesComponent + MissedSchedulesComponent, + MissedScheduleForgetConfirmationDialogComponent ], imports: [ BrowserModule, diff --git a/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.css b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.html b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.html new file mode 100644 index 0000000..e63b692 --- /dev/null +++ b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.html @@ -0,0 +1,9 @@ +Forget All Missed Schedules + + Are you sure you want to forget all missed schedules? This would delete {{this.missedSchedules.length}} schedules. + This cannot be undone! + + + Cancel + Confirm + diff --git a/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.spec.ts b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.spec.ts new file mode 100644 index 0000000..547e00c --- /dev/null +++ b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MissedScheduleForgetConfirmationDialogComponent } from './missed-schedule-forget-confirmation-dialog.component'; + +describe('MissedScheduleForgetConfirmationDialogComponent', () => { + let component: MissedScheduleForgetConfirmationDialogComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [MissedScheduleForgetConfirmationDialogComponent] + }); + fixture = TestBed.createComponent(MissedScheduleForgetConfirmationDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.ts b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.ts new file mode 100644 index 0000000..afe941c --- /dev/null +++ b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.ts @@ -0,0 +1,40 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {ScheduleInfo, ScheduleService} from "../../../api"; +import {MatSnackBar} from "@angular/material/snack-bar"; + +@Component({ + selector: 'app-missed-schedule-forget-confirmation-dialog', + templateUrl: './missed-schedule-forget-confirmation-dialog.component.html', + styleUrls: ['./missed-schedule-forget-confirmation-dialog.component.css'] +}) +export class MissedScheduleForgetConfirmationDialogComponent { + + constructor(@Inject(MAT_DIALOG_DATA) public missedSchedules: ScheduleInfo[], + private dialogRef: MatDialogRef, + private scheduleService: ScheduleService, + private snackbar: MatSnackBar) { + } + + + cancel() { + this.dialogRef.close(); + } + + confirm() { + this.scheduleService.schedulesScheduleIDsAllDelete(this.missedSchedules.map(schedule => schedule.scheduleID)).subscribe({ + next: resp => { + this.dialogRef.close(resp); + }, + error: err => { + if(err.status == 403) { + this.snackbar.open("No permission", "", {duration: 2000}); + } else if(err.status == 404) { + this.snackbar.open("Not found", "", {duration: 2000}); + } else { + this.snackbar.open("Unexpected error", "", {duration: 2000}); + } + } + }) + } +} diff --git a/frontend/src/app/missed-schedules/missed-schedules.component.html b/frontend/src/app/missed-schedules/missed-schedules.component.html index d4d7741..b903459 100644 --- a/frontend/src/app/missed-schedules/missed-schedules.component.html +++ b/frontend/src/app/missed-schedules/missed-schedules.component.html @@ -15,5 +15,5 @@ - deleteForget All + 0" (click)="forgetAllSchedules()">deleteForget All diff --git a/frontend/src/app/missed-schedules/missed-schedules.component.ts b/frontend/src/app/missed-schedules/missed-schedules.component.ts index 0501267..3cf1baf 100644 --- a/frontend/src/app/missed-schedules/missed-schedules.component.ts +++ b/frontend/src/app/missed-schedules/missed-schedules.component.ts @@ -2,6 +2,10 @@ import {Component, OnInit} from '@angular/core'; import {ScheduleInfo, ScheduleService} from "../../api"; import {NavigationLink} from "../navigation-link-list/navigation-link-list.component"; import {MatSnackBar} from "@angular/material/snack-bar"; +import {MatDialog} from "@angular/material/dialog"; +import { + MissedScheduleForgetConfirmationDialogComponent +} from "./missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component"; @Component({ selector: 'app-missed-schedules', @@ -22,7 +26,8 @@ export class MissedSchedulesComponent implements OnInit{ } ] constructor(private scheduleService: ScheduleService, - private snackbar: MatSnackBar) { + private snackbar: MatSnackBar, + private dialog: MatDialog) { } ngOnInit() { @@ -49,4 +54,13 @@ export class MissedSchedulesComponent implements OnInit{ } }) } + + forgetAllSchedules() { + const dialogRef = this.dialog.open(MissedScheduleForgetConfirmationDialogComponent, {data: this.missedSchedules, width: "400px"}); + dialogRef.afterClosed().subscribe(res => { + if(res != undefined) { + this.missedSchedules = []; + } + }) + } } diff --git a/openapi.yaml b/openapi.yaml index 1ddeaca..0f59d2e 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1717,7 +1717,44 @@ paths: application/json: schema: $ref: '#/components/schemas/SimpleStatusResponse' - + /schedules/{scheduleIDs}/all: + delete: + security: + - API_TOKEN: [] + tags: + - schedule + description: deletes multiple schedules at once + summary: deletes multiple schedules + parameters: + - name: scheduleIDs + in: path + description: internal ids of schedules to delete + required: true + schema: + type: array + items: + type: number + responses: + 200: + description: Operation successfull + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/SimpleStatusResponse' + 403: + description: No permission + content: + application/json: + schema: + $ref: '#/components/schemas/SimpleStatusResponse' + 404: + description: Schedule not found + content: + application/json: + schema: + $ref: '#/components/schemas/SimpleStatusResponse' components: securitySchemes: API_TOKEN:
Are you sure you want to forget all missed schedules? This would delete {{this.missedSchedules.length}} schedules.
This cannot be undone!