Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Use dsp-ingest for file upload (DEV-2628) #1602

Merged
merged 17 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ import {
UpdateStillImageFileValue,
UpdateTextFileValue,
} from '@dasch-swiss/dsp-js';
import { AppConfigService } from '@dasch-swiss/vre/shared/app-config';
import { NotificationService } from '@dasch-swiss/vre/shared/app-notification';
import { UploadedFileResponse, UploadFileService } from '@dasch-swiss/vre/shared/app-resource-properties';
import { UploadedFile, UploadFileService } from '@dasch-swiss/vre/shared/app-resource-properties';
import { ProjectsSelectors } from '@dasch-swiss/vre/shared/app-state';
import { Store } from '@ngxs/store';
import { filter, map, mergeMap, take } from 'rxjs/operators';

// https://stackoverflow.com/questions/45661010/dynamic-nested-reactive-form-expressionchangedafterithasbeencheckederror
const resolvedPromise = Promise.resolve(null);
Expand Down Expand Up @@ -57,9 +61,11 @@ export class UploadComponent implements OnInit {

constructor(
private _fb: UntypedFormBuilder,
private _acs: AppConfigService,
private _notification: NotificationService,
private _sanitizer: DomSanitizer,
private _upload: UploadFileService
private _upload: UploadFileService,
private _store: Store
) {}

ngOnInit(): void {
Expand All @@ -80,7 +86,6 @@ export class UploadComponent implements OnInit {
this._notification.openSnackBar(error);
this.file = null;
} else {
const formData = new FormData();
this.file = files[0];

// only certain filetypes are supported
Expand All @@ -92,47 +97,52 @@ export class UploadComponent implements OnInit {
// show loading indicator only for files > 1MB
this.isLoading = this.file.size > 1048576;

formData.append(this.file.name, this.file);
this._upload.upload(formData).subscribe(
(res: UploadedFileResponse) => {
// prepare thumbnail url to display something after upload
switch (this.representation) {
case 'stillImage':
const temporaryUrl = res.uploadedFiles[0].temporaryUrl;
const thumbnailUri = '/full/256,/0/default.jpg';
this.thumbnailUrl = this._sanitizer.bypassSecurityTrustUrl(temporaryUrl + thumbnailUri);
break;

case 'document':
this.thumbnailUrl = res.uploadedFiles[0].temporaryUrl;
break;

default:
this.thumbnailUrl = undefined;
break;
this._store
.select(ProjectsSelectors.currentProject)
.pipe(
filter(v => v !== undefined),
take(1),
map(prj => prj.shortcode),
mergeMap(sc => this._upload.upload(this.file, sc))
)
.subscribe(
(res: UploadedFile) => {
// prepare thumbnail url to display something after upload
switch (this.representation) {
case 'stillImage':
this.thumbnailUrl = this._sanitizer.bypassSecurityTrustUrl(res.thumbnailUrl);
break;

case 'document':
this.thumbnailUrl = res.baseUrl;
break;

default:
this.thumbnailUrl = undefined;
break;
}

this.fileControl.setValue(res);
const fileValue = this.getNewValue();

if (fileValue) {
this.fileInfo.emit(fileValue);
}
this.isLoading = false;
},
(e: Error) => {
// as we do not get a proper error message from the server
if (e.message.startsWith('Http failure response for ')) {
e.message =
'ERROR: File upload failed. The iiif server is not reachable at the moment. Please try again later.';
}
this._notification.openSnackBar(e.message);
this.isLoading = false;
this.file = null;
this.thumbnailUrl = null;
this.forceReload.emit();
}

this.fileControl.setValue(res.uploadedFiles[0]);
const fileValue = this.getNewValue();

if (fileValue) {
this.fileInfo.emit(fileValue);
}
this.isLoading = false;
},
(e: Error) => {
// as we do not get a proper error message from the server
if (e.message.startsWith('Http failure response for ')) {
e.message =
'ERROR: File upload failed. The iiif server is not reachable at the moment. Please try again later.';
}
this._notification.openSnackBar(e.message);
this.isLoading = false;
this.file = null;
this.thumbnailUrl = null;
this.forceReload.emit();
}
);
);
}
}
this.fileInput.nativeElement.value = null; // set the html input value to null so in case of an error the user can upload the same file again.
Expand Down
1 change: 1 addition & 0 deletions apps/dsp-app/src/config/config.0845-test-server.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"iiifHost": "iiif.0845-test-server.dasch.swiss",
"iiifPort": 443,
"iiifPath": "",
"ingestUrl": "https://ingest.0845-test-server.dasch.swiss",
"geonameToken": "knora",
"jsonWebToken": "",
"logErrors": true,
Expand Down
1 change: 1 addition & 0 deletions apps/dsp-app/src/config/config.dev-server.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"iiifHost": "iiif.dev.dasch.swiss",
"iiifPort": 443,
"iiifPath": "",
"ingestUrl": "https://ingest.dev.dasch.swiss",
"geonameToken": "knora",
"jsonWebToken": "",
"logErrors": true,
Expand Down
1 change: 1 addition & 0 deletions apps/dsp-app/src/config/config.dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"iiifHost": "0.0.0.0",
"iiifPort": 1024,
"iiifPath": "",
"ingestUrl": "http://0.0.0.0:3340",
"geonameToken": "knora",
"jsonWebToken": "",
"logErrors": true,
Expand Down
1 change: 1 addition & 0 deletions apps/dsp-app/src/config/config.ls-test-server.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"iiifHost": "iiif.ls-test-server.dasch.swiss",
"iiifPort": 443,
"iiifPath": "",
"ingestUrl": "https://ingest.ls-test-server.test.dasch.swiss",
"geonameToken": "knora",
"jsonWebToken": "",
"logErrors": true,
Expand Down
1 change: 1 addition & 0 deletions apps/dsp-app/src/config/config.prod.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"iiifHost": "0.0.0.0",
"iiifPort": 1024,
"iiifPath": "",
"ingestUrl": "http://0.0.0.0:3340",
"geonameToken": "knora",
"jsonWebToken": "",
"logErrors": false,
Expand Down
3 changes: 2 additions & 1 deletion apps/dsp-app/src/config/config.stage-server.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"iiifHost": "iiif.stage.dasch.swiss",
"iiifPort": 443,
"iiifPath": "",
"ingestUrl": "https://ingest.stage.dasch.swiss",
"geonameToken": "knora",
"jsonWebToken": "",
"logErrors": true,
Expand All @@ -19,4 +20,4 @@
"accessToken": ""
}
}
}
}
1 change: 1 addition & 0 deletions apps/dsp-app/src/config/config.test-server.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"iiifHost": "iiif.test.dasch.swiss",
"iiifPort": 443,
"iiifPath": "",
"ingestUrl": "https://ingest.test.dasch.swiss",
"geonameToken": "knora",
"jsonWebToken": "",
"logErrors": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('AppConfigService with dev config', () => {
iiifHost: '0.0.0.0',
iiifPort: 1024,
iiifPath: 'mypath',
ingestUrl: 'http://0.0.0.0:3340',
jsonWebToken: 'mytoken',
logErrors: true,
geonameToken: 'geoname_token',
Expand Down Expand Up @@ -84,6 +85,7 @@ describe('AppConfigService with prod config', () => {
iiifHost: '0.0.0.0',
iiifPort: 1024,
iiifPath: '',
ingestUrl: 'http://0.0.0.0:3340',
jsonWebToken: 'mytoken',
logErrors: true,
geonameToken: 'geoname_token',
Expand Down Expand Up @@ -126,6 +128,7 @@ describe('AppConfigService with prod config', () => {
expect(service.dspIiifConfig.iiifHost).toEqual('0.0.0.0');
expect(service.dspIiifConfig.iiifPort).toEqual(1024);
expect(service.dspIiifConfig.iiifPath).toEqual('');
expect(service.dspIngestConfig.url).toEqual('http://0.0.0.0:3340');
expect(service.dspApiConfig.jsonWebToken).toEqual('mytoken');
expect(service.dspApiConfig.logErrors).toEqual(true);
expect(service.dspAppConfig.geonameToken).toEqual('geoname_token');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { AppConfigToken } from './dsp-api-tokens';
import { DspAppConfig } from './dsp-app-config';
import { DspConfig } from './dsp-config';
import { DspIiifConfig } from './dsp-iiif-config';
import { DspIngestConfig } from './dsp-ingest-config';
import { DspInstrumentationConfig, DspRollbarConfig } from './dsp-instrumentation-config';

@Injectable({
Expand All @@ -19,6 +20,7 @@ export class AppConfigService {
private readonly _dspConfig: DspConfig;
private readonly _dspApiConfig: KnoraApiConfig;
private readonly _dspIiifConfig: DspIiifConfig;
private readonly _dspIngestConfig: DspIngestConfig;
private readonly _dspAppConfig: DspAppConfig;
private readonly _dspInstrumentationConfig: DspInstrumentationConfig;

Expand Down Expand Up @@ -59,6 +61,9 @@ export class AppConfigService {
// init iiif configuration
this._dspIiifConfig = new DspIiifConfig(c.iiifProtocol, c.iiifHost, c.iiifPort, c.iiifPath);

// init ingest configuration
this._dspIngestConfig = new DspIngestConfig(c.ingestUrl);

// init dsp app extended configuration
this._dspAppConfig = new DspAppConfig(c.geonameToken, c.iriBase);

Expand All @@ -81,6 +86,10 @@ export class AppConfigService {
return this._dspIiifConfig;
}

get dspIngestConfig(): DspIngestConfig {
return this._dspIngestConfig;
}

get dspAppConfig(): DspAppConfig {
return this._dspAppConfig;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const AppConfig = z.object({
iiifHost: z.string().nonempty("required 'iiifHost' value missing in config"),
iiifPort: IiifPort,
iiifPath: z.string(),
ingestUrl: z.string(),
geonameToken: z.string().nonempty("required 'geonameToken' value missing in config"),
jsonWebToken: z.string(),
iriBase: z.literal('http://rdfh.ch'),
Expand Down
15 changes: 15 additions & 0 deletions libs/vre/shared/app-config/src/lib/app-config/dsp-ingest-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* configuration to instantiate the ingest url.
*
* @category Config
*/
export class DspIngestConfig {
/**
* @param ingestUrl the url to the ingest service
*/
constructor(public ingestUrl: string) {}

get url(): string {
return this.ingestUrl;
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { HttpClient, HttpParams } from '@angular/common/http';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { AppConfigService } from '@dasch-swiss/vre/shared/app-config';
import { AccessTokenService } from '@dasch-swiss/vre/shared/app-session';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface UploadedFile {
fileType: string;
interface UploadedFileResponse {
internalFilename: string;
originalFilename: string;
temporaryUrl: string;
}

export interface UploadedFileResponse {
uploadedFiles: UploadedFile[];
export interface UploadedFile {
internalFilename: string;
thumbnailUrl: string;
baseUrl: string;
}

@Injectable({
Expand All @@ -26,21 +26,32 @@ export class UploadFileService {
) {}

/**
* uploads files to SIPI
* @param (file)
* uploads files to ingest server
* @param (file) The file to upload
* @param (shortcode) The project shortcode
*/
upload(file: FormData): Observable<UploadedFileResponse> {
const uploadUrl = `${this._acs.dspIiifConfig.iiifUrl}/upload`;

upload(file: File, shortcode: string): Observable<UploadedFile> {
const jwt = this._accessTokenService.getAccessToken()!;
const params = new HttpParams().set('token', jwt);
const headers = new HttpHeaders({
'Content-Type': 'application/octet-stream}',
Authorization: `Bearer ${jwt}`,
});

// --> TODO in order to track the progress change below to true and 'events'
const options = {
params,
reportProgress: false,
observe: 'body' as const,
headers,
};
return this._http.post<UploadedFileResponse>(uploadUrl, file, options);
const url = `${this._acs.dspIngestConfig.url}/projects/${shortcode}/assets/ingest/${file.name}`;
return this._http.post<UploadedFileResponse>(url, file, options).pipe(
map((res: UploadedFileResponse) => {
const baseUrl = `${this._acs.dspIiifConfig.iiifUrl}/${shortcode}/${res.internalFilename}`;
return {
internalFilename: res.internalFilename,
thumbnailUrl: `${baseUrl}/full/^256,/0/default.jpg`,
baseUrl,
};
})
);
}
}
Loading
Loading