From 15a3563a3ff00a876ef8c6a1de8a245773416e2e Mon Sep 17 00:00:00 2001 From: argirok Date: Tue, 10 Sep 2024 09:36:07 +0300 Subject: [PATCH] [develop-16-deposit | WIP | ADDED ] initial implementation for deposit functionality --- .../deposit-file/deposit-file.component.ts | 462 ++++++++++++++++++ deposit/deposit-file/deposit-file.module.ts | 20 + .../deposit-routing.module.ts | 17 + .../deposit.component.ts | 101 ++++ .../deposit-grant-redirect/deposit.module.ts | 16 + error-interceptor.service.ts | 2 +- .../result/resultLanding.component.html | 2 +- landingPages/result/resultLanding.module.ts | 1 + .../entity-actions.component.ts | 4 + utils/entity-actions/entity-actions.module.ts | 3 +- 10 files changed, 625 insertions(+), 3 deletions(-) create mode 100644 deposit/deposit-file/deposit-file.component.ts create mode 100644 deposit/deposit-file/deposit-file.module.ts create mode 100644 deposit/deposit-grant-redirect/deposit-routing.module.ts create mode 100644 deposit/deposit-grant-redirect/deposit.component.ts create mode 100644 deposit/deposit-grant-redirect/deposit.module.ts diff --git a/deposit/deposit-file/deposit-file.component.ts b/deposit/deposit-file/deposit-file.component.ts new file mode 100644 index 00000000..98295c64 --- /dev/null +++ b/deposit/deposit-file/deposit-file.component.ts @@ -0,0 +1,462 @@ +import {Component, Input, ViewChild} from '@angular/core'; +import {ActivatedRoute, Router} from "@angular/router"; +import {Subscriber, Subscription} from "rxjs"; +import {Meta, Title} from "@angular/platform-browser"; +import {UserManagementService} from "../../services/user-management.service"; +import {properties} from "../../../../environments/environment"; +import {HttpClient, HttpHeaders, HttpParams} from "@angular/common/http"; +import {ResultLandingInfo} from "../../utils/entities/resultLandingInfo"; +import {MatDialog} from "@angular/material/dialog"; +import {LoginErrorCodes} from "../../login/utils/guardHelper.class"; +import {UserInfo} from "os"; +import {User} from "../../login/utils/helper.class"; +import {error} from "protractor"; + +declare var UIkit: any; + +@Component({ + selector: 'deposition', + template: ` +
+ Authorize + +
+ + +
+ + + Upload a file to deposit + +
+
+ + + + + + +
{{orcidMessage}}
+
+ + +
+ +
+
+ + + +
Your file has been deposited in Zenodo with the main metadata of this record. You can view the new record + here, edit and publish it. +
+
The preserved DOI is {{space.metadata.prereserve_doi.doi}}.
+
+
+
+ ` +}) + +export class DepositionComponent { + authorized = false; + @Input() result: ResultLandingInfo; + //JK9mqlayFu793ylUmCtiU8yZRbl1pBYD3uWrZ3Dg + // Client Secret + // Mr1L2SK1La4vpGXSyqMFqo551YVM4koNKPFIM1vdRw8sTb5AlD89KZwGLaqN + // zenodo + //sandbox + // api = "https://zenodo.org/"; + // client_id= "xxXNLIu088eSAHAbERWEK8CCeIICELUpIDba9Gfh"; + // client_secret =""; + //sandbox + api = "https://sandbox.zenodo.org/"; + client_id = "JK9mqlayFu793ylUmCtiU8yZRbl1pBYD3uWrZ3Dg"; + client_secret = "Mr1L2SK1La4vpGXSyqMFqo551YVM4koNKPFIM1vdRw8sTb5AlD89KZwGLaqN"; + redirect_uri = "http://localhost:4300/deposit"//"https://scoobydoo.di.uoa.gr:4300"; + authorizeUrl = this.api + "oauth/authorize?response_type=code&client_id=" + + this.client_id + "&scope=deposit%3Awrite+deposit%3Aactions&state=step1&redirect_uri=" + + encodeURIComponent(this.redirect_uri); + /*authorizeByRefreshUrl = this.api + "oauth/authorize?response_type=token&client_id=" + + this.client_id + "&scope=deposit%3Awrite+deposit%3Aactions&state=step1&redirect_uri=" + + encodeURIComponent(this.redirect_uri)*/ + filesToUpload: Array; +depositService = "http://localhost:8182/"; + + public subscriptions: Subscription[] = []; + + public showLoading: boolean = false; + public message: string = ""; + public orcidMessage: string = ""; + + public source: string = ""; + public code: string = ""; + public gotToken: boolean = false; + public space = null; + public window: any; + user//: User; + userTokens; + + @ViewChild('grantModal') grantModal; + @ViewChild('depositInfoModal') depositInfoModal; + + constructor(private route: ActivatedRoute, + private _router: Router, + private userManagementService: UserManagementService, + private _meta: Meta, private _title: Title, private _http: HttpClient/*, private dialog: MatDialog*/) { + } + + ngOnInit() { + + // this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { + // this.user = user; + //TODO remove later + this.user = {}; + this.user.id = "034130792470362"; + this.user.firstname = "Argiro"; + this.user.lastname = "Kokogiannaki"; + this.user.email = "argirok@di.uoa.gr" + if (this.user) { + console.log(this.user) + this.isAuthorizedBefore(); + } else { + this.authorized = false; + } + // })); + this.subscriptions.push(this.route.queryParams.subscribe(params => { + if (params['code']) { + this.code = params['code']; + localStorage.setItem('deposit_code', this.code); + } else if (localStorage.getItem('deposit_code')) { + this.code = localStorage.getItem('deposit_code'); + } + // if(this.code){ + // this.authorized = true; + // } + this.gotToken = false; + + })); + } + + ngOnDestroy() { + this.subscriptions.forEach(subscription => { + if (subscription instanceof Subscriber) { + subscription.unsubscribe(); + } + }); + } + + isAuthorizedBefore() { + this._http.get(this.depositService + "deposit/user/token?aaiId=" + this.user.id).subscribe(res => { + console.log(res) + if (res) { + this.authorized = true; + this.userTokens = res; + } + }) + } + + authorize() { + this.window = window.open(/*this.userTokens.zenodoRefresh?this.authorizeByRefreshUrl:*/this.authorizeUrl, '_blank', + 'location=yes,height=700,width=540,left=500,top=100,scrollbars=yes,status=yes'); + // this.requestGrant = false; + this.closeGrantModal(); + + let self = this; + window.onmessage = function (ev) { + if (ev.isTrusted && ev.origin == location.origin) { + let user = { + id: self.user.id, + firstName: self.user.firstname, + lastName: self.user.lastname, + email: self.user.email, + zenodoToken: ev.data['access_token'], + zenodoDuration: ev.data['expires_in'], + zenodoRefresh: ev.data['refresh_token'], + zenodoUserId: ev.data['user']['id'], + code: ev.data['code'] + + }; + + self._http.post(self.depositService + "deposit/user/save", user).subscribe(res => { + if (res) { + self.authorized = true; + self.userTokens = res; + } + }) + /* if (ev.isTrusted && ev.origin == location.origin && ev.data == 'success') { + self.authorized = true; + UIkit.notification({ + message: 'Thank you for authorizing OpenAIRE to deposit to your Zenodo account!', + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + localStorage.setItem('deposit_token', ''); + localStorage.setItem('deposit_refresh', ''); + localStorage.setItem('deposit_code', ''); + /!*if (self.currentAction == "add") { + self.saveWorkPreparation(); + } else if (self.currentAction == "delete") { + self.deleteWorks(); + } else if (self.currentAction == "update") { + self.updateWorkPreparation(); + }*!/ + }*/ + } + } + } + + openGrantModal(title: string) { + // if(this.isMobile) { + // this.grantFsModal.okButton = false; + // this.grantFsModal.title = title; + // this.grantFsModal.open(); + // } else { + this.grantModal.cancelButton = true; + this.grantModal.okButton = true; + this.grantModal.okButtonText = "Grant OpenAIRE"; + this.grantModal.okButtonLeft = false; + this.grantModal.alertTitle = title; + this.grantModal.open(); + // } + } + + closeGrantModal() { + this.grantModal.cancel(); + } + + // the following method uses client ID and client Secret, which are sessitive data. + // Our API should return the response, without revealing the call to ORCID. + /*public getToken() { + this.showLoading = true; + console.log(this.code) + + this._http.get(properties.utilsService + "/deposit/getToken?code=" + this.code).subscribe(res => { + console.log(res) + localStorage.setItem('deposit_token', res['access_token']); + localStorage.setItem('deposit_refresh', res['refresh_token']); + }) + + } + + public getTokenByRefresh() { + this.showLoading = true; + console.log(this.code, localStorage.getItem('deposit_refresh')) + + this._http.get(properties.utilsService + "/deposit/getToken?refresh_token=" + localStorage.getItem('deposit_refresh')).subscribe(res => { + console.log(res) + localStorage.setItem('deposit_token', res['access_token']); + localStorage.setItem('deposit_refresh', res['refresh_token']); + }) + + }*/ + + public getInfo(func) { + if (this.userTokens.zenodoToken) { + + /* this._http.get("http://localhost:8000/deposit/info?token=" + localStorage.getItem('deposit_token')).subscribe(res => { + console.log(res) + + })*/ + this._http.get(this.api + "api/deposit/depositions?access_token=" + this.userTokens.zenodoToken).subscribe(res => { + console.log(res) + alert(res) + if(func) { + func(); + } + + }, error => { + console.log(error) + console.log(this.userTokens) + // if(this.userTokens.zenodoRefresh){ + this.getAccessByRefresh(func); + // }else{ + // this.authorize(); + // } + }) + } else { + console.log("no token") + this.authorize(); + } + } + + public start() { + this.space = null; + console.log(this.userTokens.zenodoToken) + if (this.userTokens.zenodoToken) { + let record = this.resultInfoToZenodoRecord(); + console.log(record) + this._http.post(this.api + "api/deposit/depositions?access_token=" + this.userTokens.zenodoToken, { + + "metadata": record + /*, + "submitted": true*/ + }).subscribe(res => { + console.log(res) + this.space = res; + // this.meta(); + console.log(res) + this.depositFile(); + }, error => { + console.error(error.message) + }) + } + } + + private resultInfoToZenodoRecord() { + let record = { + "upload_type": this.result.resultType, + title: this.result.title, + publication_date: this.result.date, + creators: [], + description: this.result.description + } + if (this.result.resultType == 'publication' && this.result.types[0]) { + record['publication_type'] = this.result.types[0]; + } + for (let author of this.result.authors) { + record.creators.push({name: author.fullName, orcid: author.orcid}) + } + return record; + } + + public deposit() { + console.log(this.userTokens.zenodoToken) + if (this.userTokens.zenodoToken) { + this._http.get(this.api + "api/deposit/depositions/73262?access_token=" +this.userTokens.zenodoToken, {}).subscribe(res => { + //http://localhost:8000 + this._http.post(properties.utilsService/* "http://localhost:8000"*/ + "/deposit/upload?token=" + this.userTokens.zenodoToken, res).subscribe(res => { + console.log(res) + + }) +// this.depositFile(res['links']['bucket'],localStorage.getItem('deposit_token')) +// console.log(res) + }) + + } + } + + + private publish() { + let record = this.resultInfoToZenodoRecord(); + console.log(record) + //uncomment to get and save DOI - published can't be deleted + this._http.post(this.api + "api/deposit/depositions/" + this.space.id + "/actions/publish?access_token=" + this.userTokens.zenodoToken, {}).subscribe(res => { + console.log(res) + }, error => { + console.error(error.message) + }) + + } + + + + + + fileChangeEvent(fileInput: any) { + this.filesToUpload = >fileInput.target.files; + if (this.filesToUpload.length == 0) { + // this.errorMessage = "There is no selected file to upload."; + return; + } + this.start(); + } + + + depositFile() { + + const formData: FormData = new FormData(); + formData.append('file', this.filesToUpload[0]); + const fileName = this.filesToUpload[0].name; // Replace with file name + + let url = `${this.space.links.bucket}/${fileName}`; + + + const data = { + name: fileName, + ...formData + }; + + const headers = new HttpHeaders({ + 'Content-type': 'application/octet-stream' + }); + let params = new HttpParams(); + params = params.set('access_token', this.userTokens.zenodoToken); + this._http.put(url, { + name: fileName, + ...formData + }, {headers, params}).subscribe((res) => { + console.log(res); + this.depositInfoModal.cancelButton = false; + this.depositInfoModal.okButton = true; + this.depositInfoModal.okButtonLeft = false; + this.depositInfoModal.alertTitle = "Deposit info"; + this.depositInfoModal.open(); + console.log(this.space) + // this.publish(); + }, error => { + console.log(error.headers) + console.error(error.data) + this.deleteSpace(); + }); + /* }); + }*/ + } + + deleteSpace() { + this._http.delete(this.api + "api/deposit/depositions/" + this.space.id + "?access_token=" + this.userTokens.zenodoToken, + ).subscribe(res => { + console.log(res) + + + }, error => { + console.error(error.message) + UIkit.notification({ + message: 'An error occured while uploading your file. The deposition may not be completed.', + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + }) + } + + checkAccess(func){ + if(!this.user){ + //TODO redirect + + }else{ + if(this.userTokens){ + this.getInfo(func); + }else{ + this.authorize(); + } + } + } + + getAccessByRefresh(func){ +/* + this._http.get(this.depositService + "deposit/zenodo/getTokenByRefresh?token=" + this.userTokens.zenodoRefresh).subscribe(res => { + console.log(res) + /!*localStorage.setItem('deposit_token', res['access_token']); + localStorage.setItem('deposit_refresh', res['refresh_token']); + this.message = "
Thank you for authorizing OpenAIRE to use your Zenodo account for deposit!
" + + "
This window will automatically close and you will be ready to deposit.
"; + if(window && window.opener) { + window.opener.postMessage(res,"*"); + window.close(); + } + setTimeout(() => { + this.message += "
If this window does not close automatically, please close it and continue!
"; + }, 3000);*!/ + }, error =>{ + console.log(error); + })*/ + this.authorize(); + } +} diff --git a/deposit/deposit-file/deposit-file.module.ts b/deposit/deposit-file/deposit-file.module.ts new file mode 100644 index 00000000..70474521 --- /dev/null +++ b/deposit/deposit-file/deposit-file.module.ts @@ -0,0 +1,20 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {DepositionComponent} from "./deposit-file.component"; +import {AlertModalModule} from "../../utils/modal/alertModal.module"; + +@NgModule({ + imports: [ + CommonModule, + AlertModalModule, + ], + declarations: [ + DepositionComponent + ], + providers: [], + exports: [ + DepositionComponent + ] +}) +export class DepositFileModule { +} diff --git a/deposit/deposit-grant-redirect/deposit-routing.module.ts b/deposit/deposit-grant-redirect/deposit-routing.module.ts new file mode 100644 index 00000000..e9ce0f81 --- /dev/null +++ b/deposit/deposit-grant-redirect/deposit-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import {DepositComponent} from "./deposit.component"; +import {LoginGuard} from "../../login/loginGuard.guard"; +import {PreviousRouteRecorder} from "../../utils/piwik/previousRouteRecorder.guard"; + + +@NgModule({ + imports: [ + RouterModule.forChild([ + { path: '', component: DepositComponent, + // canActivate: [LoginGuard], + canDeactivate: [PreviousRouteRecorder] } + ]) + ] +}) +export class DepositRoutingModule { } diff --git a/deposit/deposit-grant-redirect/deposit.component.ts b/deposit/deposit-grant-redirect/deposit.component.ts new file mode 100644 index 00000000..d05c61d2 --- /dev/null +++ b/deposit/deposit-grant-redirect/deposit.component.ts @@ -0,0 +1,101 @@ +import { Component } from '@angular/core'; +import {ActivatedRoute, Router} from "@angular/router"; +import {Subscriber, Subscription} from "rxjs"; +import {properties} from "../../../../environments/environment"; +import {Meta, Title} from "@angular/platform-browser"; +import {RouterHelper} from "../../utils/routerHelper.class"; +import {UserManagementService} from "../../services/user-management.service"; +import {HttpClient} from "@angular/common/http"; + +@Component({ + selector: 'deposit', + template: ` +
+
{{depositMessage}}
+
+ +
+ ` +}) + +export class DepositComponent { + public subscriptions: Subscription[] = []; + + public showLoading: boolean = false; + public message: string = ""; + public depositMessage: string = ""; + + public source: string = ""; + public code: string = ""; + + public gotToken: boolean = false; + + public routerHelper:RouterHelper = new RouterHelper(); + depositService = "http://localhost:8182/"; + constructor(private route: ActivatedRoute, + private _router: Router, + private userManagementService: UserManagementService, + private _meta: Meta, private _title: Title, private _http: HttpClient) {} + + ngOnInit() { + var description = "Openaire, Deposit, Zenodo"; + this.updateTitle("Connect with Zenodo"); + this.updateDescription(description); + this.updateUrl( properties.domain + properties.baseLink + this.route.url); + + this.subscriptions.push(this.route.queryParams.subscribe(params => { + this.gotToken = false; + this.code = params['code']; + if (this.code) { + this.getToken(); + } else { + this.message = "No code provided to authorize OpenAIRE to deposit in your Zenodo account. Please try again!" + } + })); + } + + ngOnDestroy() { + this.subscriptions.forEach(subscription => { + if (subscription instanceof Subscriber) { + subscription.unsubscribe(); + } + }); + } + public getToken() { + this.showLoading = true; + console.log(this.code) + + // this._http.get(properties.utilsService + "/deposit/getToken?code=" + this.code).subscribe(res => { + this._http.get(this.depositService + "deposit/zenodo/getTokenByCode?code=" + this.code).subscribe(res => { + console.log(res) + res["code"] = this.code + this.message = "
Thank you for authorizing OpenAIRE to use your Zenodo account for deposit!
" + + "
This window will automatically close and you will be ready to deposit.
"; + if(window && window.opener) { + window.opener.postMessage(res,"*"); + window.close(); + } + setTimeout(() => { + this.message += "
If this window does not close automatically, please close it and continue!
"; + }, 3000); + }) + + } + + + private updateTitle(title: string) { + this._title.setTitle(title); + this._meta.updateTag({content: title}, "property='og:title'"); + } + + private updateDescription(description: string) { + this._meta.updateTag({content: description}, "name='description'"); + this._meta.updateTag({content: description}, "property='og:description'"); + } + + private updateUrl(url: string) { + this._meta.updateTag({content: url}, "property='og:url'"); + } +} diff --git a/deposit/deposit-grant-redirect/deposit.module.ts b/deposit/deposit-grant-redirect/deposit.module.ts new file mode 100644 index 00000000..588f01b4 --- /dev/null +++ b/deposit/deposit-grant-redirect/deposit.module.ts @@ -0,0 +1,16 @@ +import {NgModule} from '@angular/core'; +import {DepositRoutingModule} from "./deposit-routing.module"; +import {DepositComponent} from "./deposit.component"; +import {LoadingModule} from "../../utils/loading/loading.module"; +import {CommonModule} from "@angular/common"; + + +@NgModule({ + imports: [CommonModule, DepositRoutingModule, LoadingModule], + declarations:[DepositComponent], + exports: [DepositComponent] +}) + + +export class DepositModule { +} diff --git a/error-interceptor.service.ts b/error-interceptor.service.ts index 701c6697..c9569bd5 100644 --- a/error-interceptor.service.ts +++ b/error-interceptor.service.ts @@ -11,7 +11,7 @@ import * as url from "url"; @Injectable() export class ErrorInterceptorService implements HttpInterceptor { - private static UNAUTHORIZED_WHITELIST = [properties.orcidAPIURL, properties.registryUrl? (properties.registryUrl + 'verification/'):null, properties.eoscDataTransferAPI].filter(value => !!value); + private static UNAUTHORIZED_WHITELIST = [properties.orcidAPIURL, properties.registryUrl? (properties.registryUrl + 'verification/'):null, properties.eoscDataTransferAPI, "https://sandbox.zenodo.org/"].filter(value => !!value); private url: string = null; constructor(private router: Router) { diff --git a/landingPages/result/resultLanding.component.html b/landingPages/result/resultLanding.component.html index f3a0de11..3d164096 100644 --- a/landingPages/result/resultLanding.component.html +++ b/landingPages/result/resultLanding.component.html @@ -77,7 +77,7 @@
- diff --git a/landingPages/result/resultLanding.module.ts b/landingPages/result/resultLanding.module.ts index d1792e55..22dcba83 100644 --- a/landingPages/result/resultLanding.module.ts +++ b/landingPages/result/resultLanding.module.ts @@ -39,6 +39,7 @@ import {EntityActionsModule} from "../../utils/entity-actions/entity-actions.mod import {ResultLandingRoutingModule} from "./resultLanding-routing.module"; import {OrcidCoreModule} from "../../orcid/orcid-core.module"; import {SearchTabModule} from "../../utils/tabs/contents/search-tab.module"; +import {DepositionComponent} from "../../deposit/deposit-file/deposit-file.component"; @NgModule({ imports: [ diff --git a/utils/entity-actions/entity-actions.component.ts b/utils/entity-actions/entity-actions.component.ts index 36be6079..82459fcb 100644 --- a/utils/entity-actions/entity-actions.component.ts +++ b/utils/entity-actions/entity-actions.component.ts @@ -51,6 +51,9 @@ import {EnvProperties} from "../properties/env-properties"; Embed
+ + + @@ -92,6 +95,7 @@ export class EntityActionsComponent implements OnInit { @Input() share: boolean = false; @Input() cite: boolean = false; @Input() deposit: boolean = false; + @Input() depositFile: boolean = false; @Input() embed: boolean = false; @Input() url: string; @Input() isMobile: boolean = false; diff --git a/utils/entity-actions/entity-actions.module.ts b/utils/entity-actions/entity-actions.module.ts index b90cd9b3..ebe4a216 100644 --- a/utils/entity-actions/entity-actions.module.ts +++ b/utils/entity-actions/entity-actions.module.ts @@ -7,9 +7,10 @@ import {AlertModalModule} from "../modal/alertModal.module"; import {CiteThisModule} from "../../landingPages/landing-utils/citeThis/citeThis.module"; import {LandingModule} from "../../landingPages/landing-utils/landing.module"; import {InputModule} from "../../sharedComponents/input/input.module"; +import {DepositFileModule} from "../../deposit/deposit-file/deposit-file.module"; @NgModule({ - imports: [CommonModule, IconsModule, AlertModalModule, CiteThisModule, LandingModule, RouterModule, InputModule], + imports: [CommonModule, IconsModule, AlertModalModule, CiteThisModule, LandingModule, RouterModule, InputModule, DepositFileModule], declarations: [EntityActionsComponent], exports: [EntityActionsComponent] })