From c012f545f406010995860f8712daf277d62c8f09 Mon Sep 17 00:00:00 2001 From: gspikehalo <2318002579@qq.com> Date: Thu, 16 Jan 2025 22:53:21 -0800 Subject: [PATCH] finish merging --- .../user/dataset/DatasetAccessResource.scala | 10 +- .../user/dataset/DatasetResource.scala | 123 +++++++++++++++++- .../service/user/dataset/dataset.service.ts | 41 +++--- 3 files changed, 152 insertions(+), 22 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/dataset/DatasetAccessResource.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/dataset/DatasetAccessResource.scala index 1dd0cd334e..e208e31d65 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/dataset/DatasetAccessResource.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/dataset/DatasetAccessResource.scala @@ -26,13 +26,15 @@ object DatasetAccessResource { .getInstance(StorageConfig.jdbcUrl, StorageConfig.jdbcUsername, StorageConfig.jdbcPassword) .createDSLContext() - def userHasReadAccess(ctx: DSLContext, did: UInteger, uid: UInteger): Boolean = { + def isDatasetPublic(ctx: DSLContext, did: UInteger): Boolean = { val datasetDao = new DatasetDao(ctx.configuration()) - val isDatasetPublic = Option(datasetDao.fetchOneByDid(did)) + Option(datasetDao.fetchOneByDid(did)) .flatMap(dataset => Option(dataset.getIsPublic)) .contains(1.toByte) + } - isDatasetPublic || + def userHasReadAccess(ctx: DSLContext, did: UInteger, uid: UInteger): Boolean = { + isDatasetPublic(ctx, did) || userHasWriteAccess(ctx, did, uid) || getDatasetUserAccessPrivilege(ctx, did, uid) == DatasetUserAccessPrivilege.READ } @@ -92,7 +94,7 @@ class DatasetAccessResource { @GET @Path("/owner/{did}") def getOwnerEmailOfDataset(@PathParam("did") did: UInteger): String = { - var email = ""; + var email = "" withTransaction(context) { ctx => val owner = getOwner(ctx, did) if (owner != null) { diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/dataset/DatasetResource.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/dataset/DatasetResource.scala index fd3bfaf796..b27ceed37e 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/dataset/DatasetResource.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/dataset/DatasetResource.scala @@ -345,7 +345,6 @@ object DatasetResource { } @Produces(Array(MediaType.APPLICATION_JSON, "image/jpeg", "application/pdf")) -@RolesAllowed(Array("REGULAR", "ADMIN")) @Path("/dataset") class DatasetResource { private val ERR_USER_HAS_NO_ACCESS_TO_DATASET_MESSAGE = "User has no read access to this dataset" @@ -378,6 +377,22 @@ class DatasetResource { ) } + def getPublicDashboardDataset(ctx: DSLContext, did: UInteger): DashboardDataset = { + if (!isDatasetPublic(ctx, did)) { + throw new ForbiddenException(ERR_USER_HAS_NO_ACCESS_TO_DATASET_MESSAGE) + } + + val targetDataset = getDatasetByID(ctx, did) + DashboardDataset( + targetDataset, + getOwner(ctx, did).getEmail, + DatasetUserAccessPrivilege.NONE, + false, + List(), + calculateDatasetVersionSize(did) + ) + } + /** * Helper function to create a new dataset version using the given multi-part form. */ @@ -401,6 +416,7 @@ class DatasetResource { } @POST + @RolesAllowed(Array("REGULAR", "ADMIN")) @Path("/create") @Consumes(Array(MediaType.MULTIPART_FORM_DATA)) def createDataset( @@ -477,6 +493,7 @@ class DatasetResource { } @POST + @RolesAllowed(Array("REGULAR", "ADMIN")) @Path("/delete") def deleteDataset(datasetIDs: DatasetIDs, @Auth user: SessionUser): Response = { val uid = user.getUid @@ -501,6 +518,7 @@ class DatasetResource { @POST @Consumes(Array(MediaType.APPLICATION_JSON)) @Produces(Array(MediaType.APPLICATION_JSON)) + @RolesAllowed(Array("REGULAR", "ADMIN")) @Path("/update/name") def updateDatasetName( modificator: DatasetNameModification, @@ -525,6 +543,7 @@ class DatasetResource { @POST @Consumes(Array(MediaType.APPLICATION_JSON)) @Produces(Array(MediaType.APPLICATION_JSON)) + @RolesAllowed(Array("REGULAR", "ADMIN")) @Path("/update/description") def updateDatasetDescription( modificator: DatasetDescriptionModification, @@ -548,6 +567,7 @@ class DatasetResource { } @POST + @RolesAllowed(Array("REGULAR", "ADMIN")) @Path("/{did}/update/publicity") def toggleDatasetPublicity( @PathParam("did") did: UInteger, @@ -574,6 +594,7 @@ class DatasetResource { } @POST + @RolesAllowed(Array("REGULAR", "ADMIN")) @Path("/{did}/version/create") @Consumes(Array(MediaType.MULTIPART_FORM_DATA)) def createDatasetVersion( @@ -607,6 +628,7 @@ class DatasetResource { * @return list of user accessible DashboardDataset objects */ @GET + @RolesAllowed(Array("REGULAR", "ADMIN")) @Path("") def listDatasets( @Auth user: SessionUser @@ -684,6 +706,7 @@ class DatasetResource { } @GET + @RolesAllowed(Array("REGULAR", "ADMIN")) @Path("/{did}/version/list") def getDatasetVersionList( @PathParam("did") did: UInteger, @@ -706,6 +729,34 @@ class DatasetResource { } @GET + @Path("/{did}/publicVersion/list") + def getPublicDatasetVersionList( + @PathParam("did") did: UInteger + ): List[DatasetVersion] = { + withTransaction(context)(ctx => { + + // + if (!isDatasetPublic(ctx, did)) { + throw new ForbiddenException(ERR_USER_HAS_NO_ACCESS_TO_DATASET_MESSAGE) + } + + val result: java.util.List[DatasetVersion] = ctx + .selectFrom(DATASET_VERSION) + .where(DATASET_VERSION.DID.eq(did)) + .orderBy(DATASET_VERSION.CREATION_TIME.desc()) // or .asc() for ascending + .fetchInto(classOf[DatasetVersion]) + + val datasetVersions = result.asScala.toList + + // 打印返回结果 + println(s"Returning DatasetVersions: $datasetVersions") + + datasetVersions + }) + } + + @GET + @RolesAllowed(Array("REGULAR", "ADMIN")) @Path("/{did}/version/latest") def retrieveLatestDatasetVersion( @PathParam("did") did: UInteger, @@ -753,6 +804,7 @@ class DatasetResource { } @GET + @RolesAllowed(Array("REGULAR", "ADMIN")) @Path("/{did}/version/{dvid}/rootFileNodes") def retrieveDatasetVersionRootFileNodes( @PathParam("did") did: UInteger, @@ -794,6 +846,45 @@ class DatasetResource { } @GET + @Path("/{did}/publicVersion/{dvid}/rootFileNodes") + def retrievePublicDatasetVersionRootFileNodes( + @PathParam("did") did: UInteger, + @PathParam("dvid") dvid: UInteger + ): DatasetVersionRootFileNodesResponse = { + withTransaction(context)(ctx => { + val dataset = getPublicDashboardDataset(ctx, did) + val targetDatasetPath = PathUtils.getDatasetPath(did) + val datasetVersion = getDatasetVersionByID(ctx, dvid) + val datasetName = dataset.dataset.getName + val fileNodes = GitVersionControlLocalFileStorage.retrieveRootFileNodesOfVersion( + targetDatasetPath, + datasetVersion.getVersionHash + ) + val versionHash = getDatasetVersionByID(ctx, dvid).getVersionHash + val size = calculateDatasetVersionSize(did, Some(versionHash)) + val ownerFileNode = DatasetFileNode + .fromPhysicalFileNodes( + Map((dataset.ownerEmail, datasetName, datasetVersion.getName) -> fileNodes.asScala.toList) + ) + .head + + DatasetVersionRootFileNodesResponse( + ownerFileNode.children.get + .find(_.getName == datasetName) + .head + .children + .get + .find(_.getName == datasetVersion.getName) + .head + .children + .get, + size + ) + }) + } + + @GET + @RolesAllowed(Array("REGULAR", "ADMIN")) @Path("/{did}") def getDataset( @PathParam("did") did: UInteger, @@ -807,11 +898,22 @@ class DatasetResource { }) } + @GET + @Path("/public/{did}") + def getPublicDataset( + @PathParam("did") did: UInteger + ): DashboardDataset = { + withTransaction(context)(ctx => { + val dashboardDataset = getPublicDashboardDataset(ctx, did) + val size = calculateDatasetVersionSize(did) + dashboardDataset.copy(size = size) + }) + } + @GET @Path("/file") def retrieveDatasetSingleFile( - @QueryParam("path") pathStr: String, - @Auth user: SessionUser + @QueryParam("path") pathStr: String ): Response = { val decodedPathStr = URLDecoder.decode(pathStr, StandardCharsets.UTF_8.name()) @@ -863,6 +965,7 @@ class DatasetResource { * @return A Response containing the dataset version as a ZIP file. */ @GET + @RolesAllowed(Array("REGULAR", "ADMIN")) @Path("/version-zip") def retrieveDatasetVersionZip( @QueryParam("did") did: UInteger, @@ -935,4 +1038,18 @@ class DatasetResource { .`type`("application/zip") .build() } + + @GET + @Path("/datasetUserAccess") + def workflowUserAccess( + @QueryParam("did") did: UInteger + ): java.util.List[UInteger] = { + val records = context + .select(DATASET_USER_ACCESS.UID) + .from(DATASET_USER_ACCESS) + .where(DATASET_USER_ACCESS.DID.eq(did)) + .fetch() + + records.getValues(DATASET_USER_ACCESS.UID) + } } diff --git a/core/gui/src/app/dashboard/service/user/dataset/dataset.service.ts b/core/gui/src/app/dashboard/service/user/dataset/dataset.service.ts index eecd65260a..6581623313 100644 --- a/core/gui/src/app/dashboard/service/user/dataset/dataset.service.ts +++ b/core/gui/src/app/dashboard/service/user/dataset/dataset.service.ts @@ -1,7 +1,6 @@ import { Injectable } from "@angular/core"; import { HttpClient, HttpParams } from "@angular/common/http"; import { map } from "rxjs/operators"; -import { NotificationService } from "../../../../common/service/notification/notification.service"; import { Dataset, DatasetVersion } from "../../../../common/type/dataset"; import { AppSettings } from "../../../../common/app-setting"; import { Observable } from "rxjs"; @@ -23,15 +22,15 @@ export const DATASET_VERSION_BASE_URL = "version"; export const DATASET_VERSION_RETRIEVE_LIST_URL = DATASET_VERSION_BASE_URL + "/list"; export const DATASET_VERSION_LATEST_URL = DATASET_VERSION_BASE_URL + "/latest"; export const DEFAULT_DATASET_NAME = "Untitled dataset"; +export const DATASET_PUBLIC_VERSION_BASE_URL = "publicVersion"; +export const DATASET_PUBLIC_VERSION_RETRIEVE_LIST_URL = DATASET_PUBLIC_VERSION_BASE_URL + "/list"; +export const DATASET_GET_OWNERS_URL = DATASET_BASE_URL + "/datasetUserAccess"; @Injectable({ providedIn: "root", }) export class DatasetService { - constructor( - private http: HttpClient, - private notificationService: NotificationService - ) {} + constructor(private http: HttpClient) {} public createDataset( dataset: Dataset, @@ -51,8 +50,11 @@ export class DatasetService { return this.http.post(`${AppSettings.getApiEndpoint()}/${DATASET_CREATE_URL}`, formData); } - public getDataset(did: number): Observable { - return this.http.get(`${AppSettings.getApiEndpoint()}/${DATASET_BASE_URL}/${did}`); + public getDataset(did: number, isLogin: boolean = true): Observable { + const apiUrl = isLogin + ? `${AppSettings.getApiEndpoint()}/${DATASET_BASE_URL}/${did}` + : `${AppSettings.getApiEndpoint()}/dataset/public/${did}`; + return this.http.get(apiUrl); } public retrieveDatasetVersionSingleFile(path: string): Observable { @@ -119,11 +121,13 @@ export class DatasetService { /** * retrieve a list of versions of a dataset. The list is sorted so that the latest versions are at front. * @param did + * @param isLogin */ - public retrieveDatasetVersionList(did: number): Observable { - return this.http.get( - `${AppSettings.getApiEndpoint()}/${DATASET_BASE_URL}/${did}/${DATASET_VERSION_RETRIEVE_LIST_URL}` - ); + public retrieveDatasetVersionList(did: number, isLogin: boolean = true): Observable { + const apiEndPont = isLogin + ? `${AppSettings.getApiEndpoint()}/${DATASET_BASE_URL}/${did}/${DATASET_VERSION_RETRIEVE_LIST_URL}` + : `${AppSettings.getApiEndpoint()}/${DATASET_BASE_URL}/${did}/${DATASET_PUBLIC_VERSION_RETRIEVE_LIST_URL}`; + return this.http.get(apiEndPont); } /** @@ -148,14 +152,17 @@ export class DatasetService { * retrieve a list of nodes that represent the files in the version * @param did * @param dvid + * @param isLogin */ public retrieveDatasetVersionFileTree( did: number, - dvid: number + dvid: number, + isLogin: boolean = true ): Observable<{ fileNodes: DatasetFileNode[]; size: number }> { - return this.http.get<{ fileNodes: DatasetFileNode[]; size: number }>( - `${AppSettings.getApiEndpoint()}/${DATASET_BASE_URL}/${did}/${DATASET_VERSION_BASE_URL}/${dvid}/rootFileNodes` - ); + const apiUrl = isLogin + ? `${AppSettings.getApiEndpoint()}/${DATASET_BASE_URL}/${did}/${DATASET_VERSION_BASE_URL}/${dvid}/rootFileNodes` + : `${AppSettings.getApiEndpoint()}/${DATASET_BASE_URL}/${did}/${DATASET_PUBLIC_VERSION_BASE_URL}/${dvid}/rootFileNodes`; + return this.http.get<{ fileNodes: DatasetFileNode[]; size: number }>(apiUrl); } public deleteDatasets(dids: number[]): Observable { @@ -184,4 +191,8 @@ export class DatasetService { {} ); } + + public getDatasetOwners(did: number): Observable { + return this.http.get(`${AppSettings.getApiEndpoint()}/${DATASET_GET_OWNERS_URL}?did=${did}`); + } }