feat: Complete deletion requests management UI implementation

- Backend API:
  - Added DeletionRequestSerializer and DeletionRequestActionSerializer
  - Added DeletionRequestViewSet with approve/reject/pending_count actions
  - Added /api/deletion_requests/ route

- Frontend:
  - Created deletion-request data model
  - Created deletion-request.service.ts with full CRUD operations
  - Created DeletionRequestsComponent with status filtering (pending/approved/rejected/completed)
  - Created DeletionRequestDetailComponent with impact analysis display
  - Added /deletion-requests route with permissions guard
  - Implemented approve/reject modals with confirmation
  - Added status badges and pending request counter

- All code linted and built successfully

Co-authored-by: dawnsystem <42047891+dawnsystem@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-11-15 15:33:45 +00:00
parent 1b4bc75a80
commit 5edfbfc028
15 changed files with 22738 additions and 0 deletions

21751
src-ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@ import { TrashComponent } from './components/admin/trash/trash.component'
import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component'
import { AppFrameComponent } from './components/app-frame/app-frame.component'
import { DashboardComponent } from './components/dashboard/dashboard.component'
import { DeletionRequestsComponent } from './components/deletion-requests/deletion-requests.component'
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
import { DocumentDetailComponent } from './components/document-detail/document-detail.component'
import { DocumentListComponent } from './components/document-list/document-list.component'
@ -174,6 +175,18 @@ export const routes: Routes = [
componentName: 'TrashComponent',
},
},
{
path: 'deletion-requests',
component: DeletionRequestsComponent,
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.Document,
},
componentName: 'DeletionRequestsComponent',
},
},
// redirect old paths
{
path: 'settings/mail',

View file

@ -0,0 +1,244 @@
<div class="modal-header">
<h4 class="modal-title" i18n>Deletion Request Details</h4>
<button
type="button"
class="btn-close"
aria-label="Close"
(click)="activeModal.dismiss()"
></button>
</div>
<div class="modal-body">
<!-- Request Information -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0" i18n>Request Information</h5>
</div>
<div class="card-body">
<div class="row mb-2">
<div class="col-md-3"><strong i18n>ID:</strong></div>
<div class="col-md-9">{{ deletionRequest.id }}</div>
</div>
<div class="row mb-2">
<div class="col-md-3"><strong i18n>Status:</strong></div>
<div class="col-md-9">
<span
class="badge"
[ngClass]="{
'bg-warning text-dark': deletionRequest.status === DeletionRequestStatus.Pending,
'bg-success': deletionRequest.status === DeletionRequestStatus.Approved,
'bg-danger': deletionRequest.status === DeletionRequestStatus.Rejected,
'bg-info': deletionRequest.status === DeletionRequestStatus.Completed,
'bg-secondary': deletionRequest.status === DeletionRequestStatus.Cancelled
}"
>
{{ deletionRequest.status }}
</span>
</div>
</div>
<div class="row mb-2">
<div class="col-md-3"><strong i18n>Created:</strong></div>
<div class="col-md-9">
{{ deletionRequest.created_at | customDate:'medium' }}
</div>
</div>
<div class="row mb-2">
<div class="col-md-3"><strong i18n>User:</strong></div>
<div class="col-md-9">{{ deletionRequest.user_username }}</div>
</div>
@if (deletionRequest.reviewed_by_username) {
<div class="row mb-2">
<div class="col-md-3"><strong i18n>Reviewed By:</strong></div>
<div class="col-md-9">{{ deletionRequest.reviewed_by_username }}</div>
</div>
}
@if (deletionRequest.reviewed_at) {
<div class="row mb-2">
<div class="col-md-3"><strong i18n>Reviewed At:</strong></div>
<div class="col-md-9">
{{ deletionRequest.reviewed_at | customDate:'medium' }}
</div>
</div>
}
</div>
</div>
<!-- AI Reason -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0" i18n>AI Reason</h5>
</div>
<div class="card-body">
<p>{{ deletionRequest.ai_reason }}</p>
</div>
</div>
<!-- Impact Analysis -->
@if (deletionRequest.impact_summary) {
<div class="card mb-3">
<div class="card-header bg-warning text-dark">
<h5 class="mb-0" i18n>
<i-bs name="exclamation-triangle"></i-bs> Impact Analysis
</h5>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-4">
<div class="text-center p-3 bg-light rounded">
<h3 class="mb-0">{{ deletionRequest.impact_summary.document_count }}</h3>
<small class="text-muted" i18n>Documents</small>
</div>
</div>
@if (deletionRequest.impact_summary.affected_tags?.length > 0) {
<div class="col-md-4">
<div class="text-center p-3 bg-light rounded">
<h3 class="mb-0">{{ deletionRequest.impact_summary.affected_tags.length }}</h3>
<small class="text-muted" i18n>Affected Tags</small>
</div>
</div>
}
@if (deletionRequest.impact_summary.affected_correspondents?.length > 0) {
<div class="col-md-4">
<div class="text-center p-3 bg-light rounded">
<h3 class="mb-0">{{ deletionRequest.impact_summary.affected_correspondents.length }}</h3>
<small class="text-muted" i18n>Affected Correspondents</small>
</div>
</div>
}
</div>
@if (deletionRequest.impact_summary.affected_tags?.length > 0) {
<div class="mb-3">
<strong i18n>Affected Tags:</strong>
<div class="mt-1">
@for (tag of deletionRequest.impact_summary.affected_tags; track tag) {
<span class="badge bg-secondary me-1">{{ tag }}</span>
}
</div>
</div>
}
@if (deletionRequest.impact_summary.affected_correspondents?.length > 0) {
<div class="mb-3">
<strong i18n>Affected Correspondents:</strong>
<div class="mt-1">
@for (correspondent of deletionRequest.impact_summary.affected_correspondents; track correspondent) {
<span class="badge bg-info me-1">{{ correspondent }}</span>
}
</div>
</div>
}
@if (deletionRequest.impact_summary.affected_types?.length > 0) {
<div class="mb-3">
<strong i18n>Affected Document Types:</strong>
<div class="mt-1">
@for (type of deletionRequest.impact_summary.affected_types; track type) {
<span class="badge bg-primary me-1">{{ type }}</span>
}
</div>
</div>
}
</div>
</div>
}
<!-- Documents List -->
@if (deletionRequest.documents_detail && deletionRequest.documents_detail.length > 0) {
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0" i18n>Documents to be Deleted</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm table-hover">
<thead>
<tr>
<th scope="col" i18n>ID</th>
<th scope="col" i18n>Title</th>
<th scope="col" i18n>Correspondent</th>
<th scope="col" i18n>Type</th>
<th scope="col" i18n>Tags</th>
</tr>
</thead>
<tbody>
@for (doc of deletionRequest.documents_detail; track doc.id) {
<tr>
<td>{{ doc.id }}</td>
<td>{{ doc.title }}</td>
<td>{{ doc.correspondent || '-' }}</td>
<td>{{ doc.document_type || '-' }}</td>
<td>
@for (tag of doc.tags; track tag) {
<span class="badge bg-secondary me-1">{{ tag }}</span>
}
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
<!-- Review Comment (if exists or editable) -->
@if (deletionRequest.review_comment || canModify()) {
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0" i18n>Review Comment</h5>
</div>
<div class="card-body">
@if (deletionRequest.review_comment && !canModify()) {
<p>{{ deletionRequest.review_comment }}</p>
} @else if (canModify()) {
<textarea
class="form-control"
rows="3"
[(ngModel)]="reviewComment"
placeholder="Add a comment (optional)"
i18n-placeholder
></textarea>
}
</div>
</div>
}
</div>
<div class="modal-footer">
@if (canModify()) {
<button
type="button"
class="btn btn-danger"
(click)="reject()"
[disabled]="isProcessing"
>
@if (isProcessing) {
<span class="spinner-border spinner-border-sm me-1"></span>
}
<i-bs name="x-circle"></i-bs>
<ng-container i18n>Reject</ng-container>
</button>
<button
type="button"
class="btn btn-success"
(click)="approve()"
[disabled]="isProcessing"
>
@if (isProcessing) {
<span class="spinner-border spinner-border-sm me-1"></span>
}
<i-bs name="check-circle"></i-bs>
<ng-container i18n>Approve</ng-container>
</button>
}
<button
type="button"
class="btn btn-secondary"
(click)="activeModal.dismiss()"
[disabled]="isProcessing"
i18n
>
Close
</button>
</div>

View file

@ -0,0 +1,46 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { DeletionRequestDetailComponent } from './deletion-request-detail.component'
import { DeletionRequestService } from 'src/app/services/rest/deletion-request.service'
import { ToastService } from 'src/app/services/toast.service'
describe('DeletionRequestDetailComponent', () => {
let component: DeletionRequestDetailComponent
let fixture: ComponentFixture<DeletionRequestDetailComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DeletionRequestDetailComponent, HttpClientTestingModule],
providers: [NgbActiveModal, DeletionRequestService, ToastService],
}).compileComponents()
fixture = TestBed.createComponent(DeletionRequestDetailComponent)
component = fixture.componentInstance
component.deletionRequest = {
id: 1,
created_at: '2024-01-01',
updated_at: '2024-01-01',
requested_by_ai: true,
ai_reason: 'Test reason',
user: 1,
user_username: 'testuser',
status: 'pending' as any,
documents: [1, 2],
documents_detail: [],
document_count: 2,
impact_summary: {
document_count: 2,
documents: [],
affected_tags: [],
affected_correspondents: [],
affected_types: [],
},
}
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View file

@ -0,0 +1,88 @@
import { CommonModule } from '@angular/common'
import { Component, inject, Input } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import {
DeletionRequest,
DeletionRequestStatus,
} from 'src/app/data/deletion-request'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DeletionRequestService } from 'src/app/services/rest/deletion-request.service'
import { ToastService } from 'src/app/services/toast.service'
@Component({
selector: 'pngx-deletion-request-detail',
standalone: true,
imports: [
CommonModule,
FormsModule,
NgxBootstrapIconsModule,
CustomDatePipe,
],
templateUrl: './deletion-request-detail.component.html',
styleUrls: ['./deletion-request-detail.component.scss'],
})
export class DeletionRequestDetailComponent {
@Input() deletionRequest: DeletionRequest
public DeletionRequestStatus = DeletionRequestStatus
public activeModal = inject(NgbActiveModal)
private deletionRequestService = inject(DeletionRequestService)
private toastService = inject(ToastService)
public reviewComment: string = ''
public isProcessing: boolean = false
approve(): void {
if (this.isProcessing) return
this.isProcessing = true
this.deletionRequestService
.approve(this.deletionRequest.id, this.reviewComment)
.subscribe({
next: (result) => {
this.toastService.showInfo(
$localize`Deletion request approved successfully`
)
this.isProcessing = false
this.activeModal.close('approved')
},
error: (error) => {
this.toastService.showError(
$localize`Error approving deletion request`,
error
)
this.isProcessing = false
},
})
}
reject(): void {
if (this.isProcessing) return
this.isProcessing = true
this.deletionRequestService
.reject(this.deletionRequest.id, this.reviewComment)
.subscribe({
next: (result) => {
this.toastService.showInfo(
$localize`Deletion request rejected successfully`
)
this.isProcessing = false
this.activeModal.close('rejected')
},
error: (error) => {
this.toastService.showError(
$localize`Error rejecting deletion request`,
error
)
this.isProcessing = false
},
})
}
canModify(): boolean {
return this.deletionRequest.status === DeletionRequestStatus.Pending
}
}

View file

@ -0,0 +1,114 @@
<pngx-page-header
title="Deletion Requests"
i18n-title
info="Manage AI-initiated deletion requests. Review and approve or reject document deletions recommended by the AI system."
i18n-info
>
@if (getPendingCount() > 0) {
<div class="badge bg-warning text-dark ms-2">
{{ getPendingCount() }} <ng-container i18n>pending</ng-container>
</div>
}
</pngx-page-header>
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTab" class="nav-tabs mb-3">
<li [ngbNavItem]="DeletionRequestStatus.Pending">
<button ngbNavLink (click)="onTabChange(DeletionRequestStatus.Pending)">
<ng-container i18n>Pending</ng-container>
@if (getStatusCount(DeletionRequestStatus.Pending) > 0) {
<span class="badge bg-warning text-dark ms-1">
{{ getStatusCount(DeletionRequestStatus.Pending) }}
</span>
}
</button>
</li>
<li [ngbNavItem]="DeletionRequestStatus.Approved">
<button ngbNavLink (click)="onTabChange(DeletionRequestStatus.Approved)">
<ng-container i18n>Approved</ng-container>
</button>
</li>
<li [ngbNavItem]="DeletionRequestStatus.Rejected">
<button ngbNavLink (click)="onTabChange(DeletionRequestStatus.Rejected)">
<ng-container i18n>Rejected</ng-container>
</button>
</li>
<li [ngbNavItem]="DeletionRequestStatus.Completed">
<button ngbNavLink (click)="onTabChange(DeletionRequestStatus.Completed)">
<ng-container i18n>Completed</ng-container>
</button>
</li>
</ul>
<div [ngbNavOutlet]="nav"></div>
@if (deletionRequestService.loading) {
<div class="text-center my-5">
<div class="spinner-border" role="status">
<span class="visually-hidden" i18n>Loading...</span>
</div>
</div>
} @else if (filteredRequests.length === 0) {
<div class="alert alert-info" i18n>
No deletion requests found with status: {{ activeTab }}
</div>
} @else {
<div class="table-responsive">
<table class="table table-hover table-striped align-middle">
<thead>
<tr>
<th scope="col" i18n>ID</th>
<th scope="col" i18n>Created</th>
<th scope="col" i18n>Documents</th>
<th scope="col" i18n>AI Reason</th>
<th scope="col" i18n>Status</th>
<th scope="col" i18n>Actions</th>
</tr>
</thead>
<tbody>
@for (request of filteredRequests | slice: (page-1) * pageSize : page * pageSize; track request.id) {
<tr (click)="viewDetails(request)" style="cursor: pointer;">
<td>{{ request.id }}</td>
<td>{{ request.created_at | customDate:'short' }}</td>
<td>
<span class="badge bg-primary">
{{ request.document_count }} <ng-container i18n>docs</ng-container>
</span>
</td>
<td>
<div class="text-truncate" style="max-width: 300px;" [ngbTooltip]="request.ai_reason">
{{ request.ai_reason }}
</div>
</td>
<td>
<span class="badge" [ngClass]="getStatusBadgeClass(request.status)">
{{ request.status }}
</span>
</td>
<td>
<button
class="btn btn-sm btn-outline-primary"
(click)="viewDetails(request); $event.stopPropagation()"
i18n
>
<i-bs name="eye"></i-bs> View Details
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
@if (collectionSize > pageSize) {
<div class="d-flex justify-content-center mt-3">
<ngb-pagination
[(page)]="page"
[pageSize]="pageSize"
[collectionSize]="collectionSize"
[maxSize]="5"
[rotate]="true"
[boundaryLinks]="true"
></ngb-pagination>
</div>
}
}

View file

@ -0,0 +1,6 @@
// Component-specific styles for deletion requests
.text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View file

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { DeletionRequestsComponent } from './deletion-requests.component'
import { DeletionRequestService } from 'src/app/services/rest/deletion-request.service'
import { ToastService } from 'src/app/services/toast.service'
describe('DeletionRequestsComponent', () => {
let component: DeletionRequestsComponent
let fixture: ComponentFixture<DeletionRequestsComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DeletionRequestsComponent, HttpClientTestingModule],
providers: [DeletionRequestService, ToastService],
}).compileComponents()
fixture = TestBed.createComponent(DeletionRequestsComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View file

@ -0,0 +1,141 @@
import { CommonModule } from '@angular/common'
import { Component, OnInit, OnDestroy, inject } from '@angular/core'
import { FormsModule } from '@angular/forms'
import {
NgbModal,
NgbNavModule,
NgbPaginationModule,
NgbTooltipModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Subject, takeUntil } from 'rxjs'
import {
DeletionRequest,
DeletionRequestStatus,
} from 'src/app/data/deletion-request'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DeletionRequestService } from 'src/app/services/rest/deletion-request.service'
import { ToastService } from 'src/app/services/toast.service'
import { PageHeaderComponent } from '../common/page-header/page-header.component'
import { LoadingComponentWithPermissions } from '../loading-component/loading.component'
import { DeletionRequestDetailComponent } from './deletion-request-detail/deletion-request-detail.component'
@Component({
selector: 'pngx-deletion-requests',
standalone: true,
imports: [
CommonModule,
FormsModule,
NgbNavModule,
NgbPaginationModule,
NgbTooltipModule,
NgxBootstrapIconsModule,
PageHeaderComponent,
CustomDatePipe,
],
templateUrl: './deletion-requests.component.html',
styleUrls: ['./deletion-requests.component.scss'],
})
export class DeletionRequestsComponent
extends LoadingComponentWithPermissions
implements OnInit, OnDestroy
{
public DeletionRequestStatus = DeletionRequestStatus
public deletionRequestService = inject(DeletionRequestService)
private modalService = inject(NgbModal)
private toastService = inject(ToastService)
protected unsubscribeNotifier: Subject<void> = new Subject()
public deletionRequests: DeletionRequest[] = []
public filteredRequests: DeletionRequest[] = []
public activeTab: DeletionRequestStatus = DeletionRequestStatus.Pending
public page: number = 1
public pageSize: number = 25
public collectionSize: number = 0
ngOnInit(): void {
this.loadDeletionRequests()
}
ngOnDestroy(): void {
this.unsubscribeNotifier.next()
this.unsubscribeNotifier.complete()
}
loadDeletionRequests(): void {
this.deletionRequestService
.listAll()
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe({
next: (result) => {
this.deletionRequests = result.results
this.filterByStatus()
},
error: (error) => {
this.toastService.showError(
$localize`Error loading deletion requests`,
error
)
},
})
}
filterByStatus(): void {
this.filteredRequests = this.deletionRequests.filter(
(req) => req.status === this.activeTab
)
this.collectionSize = this.filteredRequests.length
this.page = 1
}
onTabChange(status: DeletionRequestStatus): void {
this.activeTab = status
this.filterByStatus()
}
viewDetails(request: DeletionRequest): void {
const modalRef = this.modalService.open(DeletionRequestDetailComponent, {
size: 'xl',
backdrop: 'static',
})
modalRef.componentInstance.deletionRequest = request
modalRef.result.then(
(result) => {
if (result === 'approved' || result === 'rejected') {
this.loadDeletionRequests()
}
},
() => {
// Modal dismissed
}
)
}
getStatusBadgeClass(status: DeletionRequestStatus): string {
switch (status) {
case DeletionRequestStatus.Pending:
return 'bg-warning text-dark'
case DeletionRequestStatus.Approved:
return 'bg-success'
case DeletionRequestStatus.Rejected:
return 'bg-danger'
case DeletionRequestStatus.Completed:
return 'bg-info'
case DeletionRequestStatus.Cancelled:
return 'bg-secondary'
default:
return 'bg-secondary'
}
}
getPendingCount(): number {
return this.deletionRequests.filter(
(req) => req.status === DeletionRequestStatus.Pending
).length
}
getStatusCount(status: DeletionRequestStatus): number {
return this.deletionRequests.filter((req) => req.status === status).length
}
}

View file

@ -0,0 +1,50 @@
import { ObjectWithId } from './object-with-id'
export interface DeletionRequestDocument {
id: number
title: string
created: string
correspondent?: string
document_type?: string
tags: string[]
}
export interface DeletionRequestImpactSummary {
document_count: number
documents: DeletionRequestDocument[]
affected_tags: string[]
affected_correspondents: string[]
affected_types: string[]
date_range?: {
earliest: string
latest: string
}
}
export enum DeletionRequestStatus {
Pending = 'pending',
Approved = 'approved',
Rejected = 'rejected',
Cancelled = 'cancelled',
Completed = 'completed',
}
export interface DeletionRequest extends ObjectWithId {
created_at: string
updated_at: string
requested_by_ai: boolean
ai_reason: string
user: number
user_username: string
status: DeletionRequestStatus
documents: number[]
documents_detail: DeletionRequestDocument[]
document_count: number
impact_summary: DeletionRequestImpactSummary
reviewed_at?: string
reviewed_by?: number
reviewed_by_username?: string
review_comment?: string
completed_at?: string
completion_details?: any
}

View file

@ -0,0 +1,79 @@
import { TestBed } from '@angular/core/testing'
import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing'
import { DeletionRequestService } from './deletion-request.service'
import { environment } from 'src/environments/environment'
describe('DeletionRequestService', () => {
let service: DeletionRequestService
let httpMock: HttpTestingController
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [DeletionRequestService],
})
service = TestBed.inject(DeletionRequestService)
httpMock = TestBed.inject(HttpTestingController)
})
afterEach(() => {
httpMock.verify()
})
it('should be created', () => {
expect(service).toBeTruthy()
})
it('should get pending count', () => {
const mockResponse = { count: 5 }
service.getPendingCount().subscribe((response) => {
expect(response.count).toBe(5)
})
const req = httpMock.expectOne(
`${environment.apiBaseUrl}deletion_requests/pending_count/`
)
expect(req.request.method).toBe('GET')
req.flush(mockResponse)
})
it('should approve a deletion request', () => {
const mockRequest = {
id: 1,
status: 'approved',
}
service.approve(1, 'Approved').subscribe((response) => {
expect(response.status).toBe('approved')
})
const req = httpMock.expectOne(
`${environment.apiBaseUrl}deletion_requests/1/approve/`
)
expect(req.request.method).toBe('POST')
expect(req.request.body).toEqual({ review_comment: 'Approved' })
req.flush(mockRequest)
})
it('should reject a deletion request', () => {
const mockRequest = {
id: 1,
status: 'rejected',
}
service.reject(1, 'Rejected').subscribe((response) => {
expect(response.status).toBe('rejected')
})
const req = httpMock.expectOne(
`${environment.apiBaseUrl}deletion_requests/1/reject/`
)
expect(req.request.method).toBe('POST')
expect(req.request.body).toEqual({ review_comment: 'Rejected' })
req.flush(mockRequest)
})
})

View file

@ -0,0 +1,62 @@
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { Observable } from 'rxjs'
import { tap } from 'rxjs/operators'
import { DeletionRequest } from 'src/app/data/deletion-request'
import { AbstractPaperlessService } from './abstract-paperless-service'
@Injectable({
providedIn: 'root',
})
export class DeletionRequestService extends AbstractPaperlessService<DeletionRequest> {
constructor() {
super()
this.resourceName = 'deletion_requests'
}
/**
* Approve a deletion request
* @param id The ID of the deletion request
* @param reviewComment Optional comment for the approval
* @returns Observable of the updated deletion request
*/
approve(id: number, reviewComment?: string): Observable<DeletionRequest> {
this._loading = true
const body = reviewComment ? { review_comment: reviewComment } : {}
return this.http
.post<DeletionRequest>(this.getResourceUrl(id, 'approve'), body)
.pipe(
tap(() => {
this._loading = false
})
)
}
/**
* Reject a deletion request
* @param id The ID of the deletion request
* @param reviewComment Optional comment for the rejection
* @returns Observable of the updated deletion request
*/
reject(id: number, reviewComment?: string): Observable<DeletionRequest> {
this._loading = true
const body = reviewComment ? { review_comment: reviewComment } : {}
return this.http
.post<DeletionRequest>(this.getResourceUrl(id, 'reject'), body)
.pipe(
tap(() => {
this._loading = false
})
)
}
/**
* Get the count of pending deletion requests
* @returns Observable with the count
*/
getPendingCount(): Observable<{ count: number }> {
return this.http.get<{ count: number }>(
this.getResourceUrl(null, 'pending_count')
)
}
}