Request-a-copy: Changes to support access expiry as delta/date storage - psql max

This commit is contained in:
Kim Shepherd
2025-03-24 12:56:32 +01:00
parent 57b618ce34
commit 1fff3b5b86
7 changed files with 58 additions and 39 deletions

View File

@@ -125,7 +125,7 @@ export class ItemRequestDataService extends IdentifiableDataService<ItemRequest>
* @param suggestOpenAccess Whether or not to suggest the item to become open access
* @param accessPeriod How long in seconds to grant access, from the decision date (only applies to links, not attachments)
*/
grant(token: string, email: RequestCopyEmail, suggestOpenAccess = false, accessPeriod = 0): Observable<RemoteData<ItemRequest>> {
grant(token: string, email: RequestCopyEmail, suggestOpenAccess = false, accessPeriod: string = null): Observable<RemoteData<ItemRequest>> {
return this.process(token, email, true, suggestOpenAccess, accessPeriod);
}
@@ -137,7 +137,7 @@ export class ItemRequestDataService extends IdentifiableDataService<ItemRequest>
* @param suggestOpenAccess Whether or not to suggest the item to become open access
* @param accessPeriod How long in seconds to grant access, from the decision date (only applies to links, not attachments)
*/
process(token: string, email: RequestCopyEmail, grant: boolean, suggestOpenAccess = false, accessPeriod = 0): Observable<RemoteData<ItemRequest>> {
process(token: string, email: RequestCopyEmail, grant: boolean, suggestOpenAccess = false, accessPeriod: string = null): Observable<RemoteData<ItemRequest>> {
const requestId = this.requestService.generateRequestId();
this.getItemRequestEndpointByToken(token).pipe(
@@ -161,26 +161,6 @@ export class ItemRequestDataService extends IdentifiableDataService<ItemRequest>
return this.rdbService.buildFromRequestUUID(requestId);
}
// TODO: Remove this, after discussion about implications and compare to bitstream data service byItemHandle
// Reviewers may ask that we instead just wrap the REST response in pagination even though we only expect one obj
/**
* Get a sanitized item request using the searchBy method and the access token sent to the original requester.
*
* @param accessToken access token contained in the secure link sent to a requester
*/
getSanitizedRequestByAccessTokenPaged(accessToken: string): Observable<RemoteData<PaginatedList<ItemRequest>>> {
// We only expect / want one result as access tokens are unique
const findListOptions = Object.assign({}, new FindListOptions(), {
elementsPerPage: 1,
currentPage: 1,
searchParams: [
new RequestParam('accessToken', accessToken),
],
});
// Pipe the paginated searchBy results and return a single item request
return this.searchBy('byAccessToken', findListOptions);
}
/**
* Get a sanitized item request using the searchBy method and the access token sent to the original requester.
*

View File

@@ -15,7 +15,7 @@
</div>
<!-- Display access periods if more than one was bound to input. The parent component (grant-request-copy)
sends an empty list if the feature is not enabled or applicable to this request. -->
@if (hasValue(validAccessPeriods) && validAccessPeriods.length > 0) {
@if (hasValue(validAccessPeriods$ | async) && (validAccessPeriods$ | async).length > 0) {
<div class="form-group">
<label for="accessPeriod">{{ 'grant-request-copy.access-period.header' | translate }}</label>
<div ngbDropdown class="d-block">
@@ -28,7 +28,7 @@
</button>
<!-- Access period dropdown -->
<div ngbDropdownMenu aria-labelledby="accessPeriod">
@for (accessPeriod of validAccessPeriods; track accessPeriod) {
@for (accessPeriod of (validAccessPeriods$ | async); track accessPeriod) {
<button
ngbDropdownItem
class="list-element"

View File

@@ -1,6 +1,7 @@
import 'altcha';
import {
AsyncPipe,
Location,
NgClass,
} from '@angular/common';
@@ -8,12 +9,18 @@ import {
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import {
Observable,
Subject,
} from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { BtnDisabledDirective } from '../../shared/btn-disabled.directive';
import { hasValue } from '../../shared/empty.util';
@@ -24,16 +31,20 @@ import { RequestCopyEmail } from './request-copy-email.model';
styleUrls: ['./email-request-copy.component.scss'],
templateUrl: './email-request-copy.component.html',
standalone: true,
imports: [FormsModule, NgClass, TranslateModule, BtnDisabledDirective, NgbDropdownModule],
imports: [FormsModule, NgClass, TranslateModule, BtnDisabledDirective, NgbDropdownModule, AsyncPipe],
})
/**
* A form component for an email to send back to the user requesting an item
*/
export class EmailRequestCopyComponent implements OnInit {
export class EmailRequestCopyComponent implements OnInit, OnDestroy {
/**
* Event emitter for sending the email
*/
@Output() send: EventEmitter<RequestCopyEmail> = new EventEmitter<RequestCopyEmail>();
/**
* Selected access period emmitter, sending the new period up to the parent component
*/
@Output() selectedAccessPeriod: EventEmitter<string> = new EventEmitter();
/**
@@ -49,7 +60,7 @@ export class EmailRequestCopyComponent implements OnInit {
/**
* A list of valid access periods to render in a drop-down menu
*/
@Input() validAccessPeriods: string [] = [];
@Input() validAccessPeriods$: Observable<string[]>;
/**
* The selected access period, e.g. +7DAYS, +12MONTHS, FOREVER. These will be
@@ -57,16 +68,37 @@ export class EmailRequestCopyComponent implements OnInit {
*/
accessPeriod = 'FOREVER';
/**
* Destroy subject for unsubscribing from observables
* @private
*/
private destroy$ = new Subject<void>();
protected readonly hasValue = hasValue;
constructor(protected location: Location) {
}
/**
* Initialise subscription to async valid access periods (from configuration service)
*/
ngOnInit(): void {
// If access periods are present, set the default to the first in the array
if (hasValue(this.validAccessPeriods) && this.validAccessPeriods.length > 0) {
this.selectAccessPeriod(this.validAccessPeriods[0]);
}
this.validAccessPeriods$.pipe(
takeUntil(this.destroy$),
).subscribe((validAccessPeriods) => {
if (hasValue(validAccessPeriods) && validAccessPeriods.length > 0) {
this.selectAccessPeriod(validAccessPeriods[0]);
}
});
}
/**
* Clean up subscriptions and selectors
*/
ngOnDestroy(): void {
this.selectedAccessPeriod.complete();
this.destroy$.next();
this.destroy$.complete();
}
/**

View File

@@ -4,6 +4,7 @@ import {
Input,
Output,
} from '@angular/core';
import { Observable } from 'rxjs';
import { ThemedComponent } from 'src/app/shared/theme-support/themed.component';
import { EmailRequestCopyComponent } from './email-request-copy.component';
@@ -28,7 +29,7 @@ export class ThemedEmailRequestCopyComponent extends ThemedComponent<EmailReques
/**
* Event emitter for a selected / changed access period
*/
@Output() selectedAccessPeriod: EventEmitter<number> = new EventEmitter();
@Output() selectedAccessPeriod: EventEmitter<string> = new EventEmitter();
/**
* The subject of the email
@@ -43,10 +44,10 @@ export class ThemedEmailRequestCopyComponent extends ThemedComponent<EmailReques
/**
* A list of valid access periods, if configured
*/
@Input() validAccessPeriods: number[];
@Input() validAccessPeriods$: Observable<string[]>;
protected inAndOutputNames: (keyof EmailRequestCopyComponent & keyof this)[] = ['send', 'subject', 'message', 'validAccessPeriods', 'selectedAccessPeriod'];
protected inAndOutputNames: (keyof EmailRequestCopyComponent & keyof this)[] = ['send', 'subject', 'message', 'selectedAccessPeriod', 'validAccessPeriods$'];
protected getComponentName(): string {
return 'EmailRequestCopyComponent';

View File

@@ -21,9 +21,9 @@
<!-- Only send access periods for display if an access token was present -->
<ds-email-request-copy [subject]="subject$ | async"
[message]="message$ | async"
[validAccessPeriods]="(hasValue(itemRequestRD.payload.accessToken) ? (validAccessPeriods$ | async) : [])"
(send)="grant($event)"
(selectedAccessPeriod)="selectAccessPeriod($event)"
[validAccessPeriods$]="validAccessPeriods$"
>
<p>{{ 'grant-deny-request-copy.email.permissions.info' | translate }}</p>
<form class="mb-3">

View File

@@ -81,7 +81,7 @@ export class GrantRequestCopyComponent implements OnInit {
/**
* The currently selected access period
*/
accessPeriod: any = 0;
accessPeriod: string = null;
/**
* Will this email attach file(s) directly, or send a secure link with an access token to provide temporary access?
@@ -113,6 +113,9 @@ export class GrantRequestCopyComponent implements OnInit {
}
/**
* Initialize the component - get the item request from route data an duse it to populate the form
*/
ngOnInit(): void {
// Get item request data via the router (async)
this.itemRequestRD$ = this.route.data.pipe(
@@ -157,7 +160,7 @@ export class GrantRequestCopyComponent implements OnInit {
});
}
selectAccessPeriod(accessPeriod: number) {
selectAccessPeriod(accessPeriod: string) {
this.accessPeriod = accessPeriod;
}

View File

@@ -1,4 +1,7 @@
import { NgClass } from '@angular/common';
import {
AsyncPipe,
NgClass,
} from '@angular/common';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
@@ -13,7 +16,7 @@ import { BtnDisabledDirective } from '../../../../../app/shared/btn-disabled.dir
// templateUrl: './email-request-copy.component.html',
templateUrl: './../../../../../app/request-copy/email-request-copy/email-request-copy.component.html',
standalone: true,
imports: [FormsModule, NgClass, TranslateModule, BtnDisabledDirective],
imports: [FormsModule, NgClass, TranslateModule, BtnDisabledDirective, AsyncPipe],
})
export class EmailRequestCopyComponent
extends BaseComponent {