Skip to content

Commit c8df8b2

Browse files
Merge branch 'master' into webauthn2
2 parents 9f8cc00 + 96d6b45 commit c8df8b2

File tree

76 files changed

+1935
-1397
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1935
-1397
lines changed

app/controllers/AnnotationIOController.scala

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ class AnnotationIOController @Inject()(
137137
volumeLayersGrouped,
138138
tracingStoreClient,
139139
parsedFiles.otherFiles,
140-
usableDataSource)
140+
usableDataSource,
141+
dataset._id)
141142
mergedSkeletonLayers <- mergeAndSaveSkeletonLayers(skeletonTracings, tracingStoreClient)
142143
annotation <- annotationService.createFrom(request.identity,
143144
dataset,
@@ -167,7 +168,8 @@ class AnnotationIOController @Inject()(
167168
volumeLayersGrouped: Seq[List[UploadedVolumeLayer]],
168169
client: WKRemoteTracingStoreClient,
169170
otherFiles: Map[String, File],
170-
dataSource: DataSourceLike): Fox[List[AnnotationLayer]] =
171+
dataSource: DataSourceLike,
172+
datasetId: ObjectId): Fox[List[AnnotationLayer]] =
171173
if (volumeLayersGrouped.isEmpty)
172174
Fox.successful(List())
173175
else if (volumeLayersGrouped.length > 1 && volumeLayersGrouped.exists(_.length > 1))
@@ -182,7 +184,8 @@ class AnnotationIOController @Inject()(
182184
newTracingId,
183185
uploadedVolumeLayer.tracing,
184186
uploadedVolumeLayer.getDataZipFrom(otherFiles),
185-
dataSource = dataSource)
187+
dataSource = dataSource,
188+
datasetId = datasetId)
186189
} yield
187190
AnnotationLayer(
188191
newTracingId,
@@ -200,6 +203,7 @@ class AnnotationIOController @Inject()(
200203
newTracingId,
201204
VolumeTracings(uploadedVolumeLayersFlat.map(v => VolumeTracingOpt(Some(v.tracing)))),
202205
dataSource,
206+
datasetId,
203207
uploadedVolumeLayersFlat.map(v => v.getDataZipFrom(otherFiles))
204208
)
205209
} yield
@@ -337,8 +341,7 @@ class AnnotationIOController @Inject()(
337341
else volumeTracing.boundingBox
338342

339343
for {
340-
tracingCanHaveSegmentIndex <- canHaveSegmentIndex(organizationId,
341-
dataset.name,
344+
tracingCanHaveSegmentIndex <- canHaveSegmentIndex(dataset._id,
342345
fallbackLayerOpt.map(_.name),
343346
remoteDataStoreClient)
344347
elementClassProto <- fallbackLayerOpt
@@ -358,13 +361,12 @@ class AnnotationIOController @Inject()(
358361
}
359362

360363
private def canHaveSegmentIndex(
361-
organizationId: String,
362-
datasetName: String,
364+
datasetId: ObjectId,
363365
fallbackLayerName: Option[String],
364366
remoteDataStoreClient: WKRemoteDataStoreClient)(implicit ec: ExecutionContext): Fox[Boolean] =
365367
fallbackLayerName match {
366368
case Some(layerName) =>
367-
remoteDataStoreClient.hasSegmentIndexFile(organizationId, datasetName, layerName)
369+
remoteDataStoreClient.hasSegmentIndexFile(datasetId, layerName)
368370
case None =>
369371
Fox.successful(true)
370372
}

app/controllers/DatasetController.scala

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ class DatasetController @Inject()(userService: UserService,
8989
analyticsService: AnalyticsService,
9090
mailchimpClient: MailchimpClient,
9191
wkExploreRemoteLayerService: WKExploreRemoteLayerService,
92+
composeService: ComposeService,
9293
sil: Silhouette[WkEnv])(implicit ec: ExecutionContext, bodyParsers: PlayBodyParsers)
9394
extends Controller
9495
with MetadataAssertions {
@@ -145,10 +146,10 @@ class DatasetController @Inject()(userService: UserService,
145146
_ <- Fox.fromBool(dataSource.dataLayers.nonEmpty) ?~> "dataset.explore.zeroLayers"
146147
folderIdOpt <- Fox.runOptional(request.body.folderPath)(folderPath =>
147148
folderService.getOrCreateFromPathLiteral(folderPath, request.identity._organization)) ?~> "dataset.explore.autoAdd.getFolder.failed"
148-
_ <- wkExploreRemoteLayerService.addRemoteDatasource(dataSource,
149-
request.body.datasetName,
150-
request.identity,
151-
folderIdOpt) ?~> "dataset.explore.autoAdd.failed"
149+
_ <- wkExploreRemoteLayerService.addRemoteDatasourceToDatabase(dataSource,
150+
request.body.datasetName,
151+
request.identity,
152+
folderIdOpt) ?~> "dataset.explore.autoAdd.failed"
152153
} yield Ok
153154
}
154155

@@ -490,4 +491,11 @@ class DatasetController @Inject()(userService: UserService,
490491
}
491492
}
492493

494+
def compose(): Action[ComposeRequest] =
495+
sil.SecuredAction.async(validateJson[ComposeRequest]) { implicit request =>
496+
for {
497+
(dataSource, newDatasetId) <- composeService.composeDataset(request.body, request.identity) ?~> "dataset.compose.failed"
498+
} yield Ok(Json.obj("newDatasetId" -> newDatasetId))
499+
}
500+
493501
}

app/controllers/UserTokenController.scala

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,19 @@ class UserTokenController @Inject()(datasetDAO: DatasetDAO,
182182
isAllowed <- datasetService.isEditableBy(dataset, Some(user))
183183
} yield UserAccessAnswer(isAllowed)
184184

185+
def tryDelete: Fox[UserAccessAnswer] =
186+
for {
187+
_ <- Fox.fromBool(conf.Features.allowDeleteDatasets) ?~> "dataset.delete.disabled"
188+
datasetId <- ObjectId.fromString(id)
189+
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext) ?~> "dataset.notFound"
190+
user <- userBox.toFox ?~> "auth.token.noUser"
191+
} yield UserAccessAnswer(user._organization == dataset._organization && user.isAdmin)
192+
185193
mode match {
186-
case AccessMode.read => tryRead
187-
case AccessMode.write => tryWrite
188-
case _ => Fox.successful(UserAccessAnswer(granted = false, Some("invalid access token")))
194+
case AccessMode.read => tryRead
195+
case AccessMode.write => tryWrite
196+
case AccessMode.delete => tryDelete
197+
case _ => Fox.successful(UserAccessAnswer(granted = false, Some("invalid access token")))
189198
}
190199
}
191200

app/controllers/WKRemoteDataStoreController.scala

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import com.scalableminds.util.tools.Fox
77
import com.scalableminds.webknossos.datastore.controllers.JobExportProperties
88
import com.scalableminds.webknossos.datastore.helpers.{LayerMagLinkInfo, MagLinkInfo}
99
import com.scalableminds.webknossos.datastore.models.UnfinishedUpload
10-
import com.scalableminds.webknossos.datastore.models.datasource.DataSourceId
10+
import com.scalableminds.webknossos.datastore.models.datasource.{AbstractDataLayer, DataSource, DataSourceId}
1111
import com.scalableminds.webknossos.datastore.models.datasource.inbox.{InboxDataSourceLike => InboxDataSource}
12-
import com.scalableminds.webknossos.datastore.services.{DataSourcePathInfo, DataStoreStatus}
12+
import com.scalableminds.webknossos.datastore.services.{DataSourcePathInfo, DataSourceRegistrationInfo, DataStoreStatus}
1313
import com.scalableminds.webknossos.datastore.services.uploading.{
1414
LinkedLayerIdentifier,
1515
ReserveAdditionalInformation,
@@ -242,13 +242,38 @@ class WKRemoteDataStoreController @Inject()(
242242
}
243243
}
244244

245-
def getPaths(name: String, key: String, organizationId: String, directoryName: String): Action[AnyContent] =
245+
def deleteVirtualDataset(name: String, key: String): Action[ObjectId] =
246+
Action.async(validateJson[ObjectId]) { implicit request =>
247+
dataStoreService.validateAccess(name, key) { _ =>
248+
for {
249+
dataset <- datasetDAO.findOne(request.body)(GlobalAccessContext) ~> NOT_FOUND
250+
_ <- Fox.fromBool(dataset.isVirtual) ?~> "dataset.delete.notVirtual" ~> FORBIDDEN
251+
_ <- datasetDAO.deleteDataset(dataset._id, onlyMarkAsDeleted = true)
252+
} yield Ok
253+
}
254+
}
255+
256+
def findDatasetId(name: String,
257+
key: String,
258+
datasetDirectoryName: String,
259+
organizationId: String): Action[AnyContent] =
246260
Action.async { implicit request =>
247261
dataStoreService.validateAccess(name, key) { _ =>
248262
for {
249-
organization <- organizationDAO.findOne(organizationId)(GlobalAccessContext)
250-
dataset <- datasetDAO.findOneByDirectoryNameAndOrganization(directoryName, organization._id)(
263+
organization <- organizationDAO.findOne(organizationId)(GlobalAccessContext) ?~> Messages(
264+
"organization.notFound",
265+
organizationId) ~> NOT_FOUND
266+
dataset <- datasetDAO.findOneByNameAndOrganization(datasetDirectoryName, organization._id)(
251267
GlobalAccessContext)
268+
} yield Ok(Json.toJson(dataset._id))
269+
}
270+
}
271+
272+
def getPaths(name: String, key: String, datasetId: ObjectId): Action[AnyContent] =
273+
Action.async { implicit request =>
274+
dataStoreService.validateAccess(name, key) { _ =>
275+
for {
276+
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext) ~> NOT_FOUND
252277
layers <- datasetLayerDAO.findAllForDataset(dataset._id)
253278
magsAndLinkedMags <- Fox.serialCombined(layers)(l => datasetService.getPathsForDataLayer(dataset._id, l.name))
254279
magLinkInfos = magsAndLinkedMags.map(_.map { case (mag, linkedMags) => MagLinkInfo(mag, linkedMags) })
@@ -270,6 +295,53 @@ class WKRemoteDataStoreController @Inject()(
270295

271296
}
272297

298+
// Register a datasource from the datastore as a dataset in the database.
299+
// This is called when adding remote virtual datasets (that should only exist in the database)
300+
// by the data store after exploration.
301+
def registerDataSource(name: String,
302+
key: String,
303+
organizationId: String,
304+
directoryName: String,
305+
token: String): Action[DataSourceRegistrationInfo] =
306+
Action.async(validateJson[DataSourceRegistrationInfo]) { implicit request =>
307+
dataStoreService.validateAccess(name, key) { dataStore =>
308+
for {
309+
user <- bearerTokenService.userForToken(token)
310+
organization <- organizationDAO.findOne(organizationId)(GlobalAccessContext) ?~> Messages(
311+
"organization.notFound",
312+
organizationId) ~> NOT_FOUND
313+
_ <- Fox.fromBool(organization._id == user._organization) ?~> "notAllowed" ~> FORBIDDEN
314+
dataset <- datasetService.createVirtualDataset(
315+
directoryName,
316+
organizationId,
317+
dataStore,
318+
request.body.dataSource,
319+
request.body.folderId,
320+
user
321+
)
322+
} yield Ok(dataset._id.toString)
323+
}
324+
}
325+
326+
def updateDataSource(name: String, key: String, datasetId: ObjectId, allowNewPaths: Boolean): Action[DataSource] =
327+
Action.async(validateJson[DataSource]) { implicit request =>
328+
dataStoreService.validateAccess(name, key) { _ =>
329+
for {
330+
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext) ~> NOT_FOUND
331+
abstractDataSource = request.body.copy(dataLayers = request.body.dataLayers.map(AbstractDataLayer.from))
332+
oldDataSource <- datasetService.fullDataSourceFor(dataset)
333+
oldPaths = oldDataSource.toUsable.map(_.allExplicitPaths).getOrElse(List.empty)
334+
newPaths = request.body.allExplicitPaths
335+
_ <- Fox.fromBool(allowNewPaths || newPaths.forall(oldPaths.contains)) ?~> "New mag paths must be a subset of old mag paths" ~> BAD_REQUEST
336+
_ <- datasetDAO.updateDataSourceByDatasetId(datasetId,
337+
name,
338+
abstractDataSource.hashCode(),
339+
abstractDataSource,
340+
isUsable = true)(GlobalAccessContext)
341+
} yield Ok
342+
}
343+
}
344+
273345
def jobExportProperties(name: String, key: String, jobId: ObjectId): Action[AnyContent] = Action.async {
274346
implicit request =>
275347
dataStoreService.validateAccess(name, key) { _ =>

app/controllers/WKRemoteTracingStoreController.scala

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,15 @@ import com.scalableminds.webknossos.datastore.Annotation.AnnotationProto
88
import com.scalableminds.webknossos.datastore.SkeletonTracing.SkeletonTracing
99
import com.scalableminds.webknossos.datastore.VolumeTracing.VolumeTracing
1010
import com.scalableminds.webknossos.datastore.models.annotation.AnnotationLayer
11-
import com.scalableminds.webknossos.datastore.models.datasource.DataSourceId
1211
import com.scalableminds.webknossos.tracingstore.AnnotationUpdatesReport
1312
import com.scalableminds.webknossos.tracingstore.annotation.AnnotationLayerParameters
1413
import com.scalableminds.webknossos.tracingstore.tracings.TracingId
1514
import models.analytics.{AnalyticsService, UpdateAnnotationEvent, UpdateAnnotationViewOnlyEvent}
1615
import models.annotation.AnnotationState._
1716
import models.annotation._
1817
import models.dataset.{DatasetDAO, DatasetService}
19-
import models.organization.OrganizationDAO
2018
import models.user.UserDAO
2119
import models.user.time.TimeSpanService
22-
import play.api.i18n.Messages
2320
import play.api.libs.json.Json
2421
import play.api.mvc.{Action, AnyContent, PlayBodyParsers}
2522
import scalapb.GeneratedMessage
@@ -33,7 +30,6 @@ class WKRemoteTracingStoreController @Inject()(tracingStoreService: TracingStore
3330
wkSilhouetteEnvironment: WkSilhouetteEnvironment,
3431
timeSpanService: TimeSpanService,
3532
datasetService: DatasetService,
36-
organizationDAO: OrganizationDAO,
3733
userDAO: UserDAO,
3834
annotationInformationProvider: AnnotationInformationProvider,
3935
analyticsService: AnalyticsService,
@@ -114,7 +110,7 @@ class WKRemoteTracingStoreController @Inject()(tracingStoreService: TracingStore
114110
tracingStoreService.validateAccess(name, key) { _ =>
115111
implicit val ctx: DBAccessContext = GlobalAccessContext
116112
annotationDataSourceTemporaryStore.find(annotationId) match {
117-
case Some(dataSource) => Fox.successful(Ok(Json.toJson(dataSource)))
113+
case Some(dataSourceAndDatasetId) => Fox.successful(Ok(Json.toJson(dataSourceAndDatasetId._1)))
118114
case None =>
119115
for {
120116
annotation <- annotationDAO.findOne(annotationId) ?~> "annotation.notFound"
@@ -125,15 +121,18 @@ class WKRemoteTracingStoreController @Inject()(tracingStoreService: TracingStore
125121
}
126122
}
127123

128-
def dataSourceIdForAnnotation(name: String, key: String, annotationId: ObjectId): Action[AnyContent] =
124+
def datasetIdForAnnotation(name: String, key: String, annotationId: ObjectId): Action[AnyContent] =
129125
Action.async { implicit request =>
130126
tracingStoreService.validateAccess(name, key) { _ =>
131127
implicit val ctx: DBAccessContext = GlobalAccessContext
132-
for {
133-
annotation <- annotationDAO.findOne(annotationId) ?~> "annotation.notFound"
134-
dataset <- datasetDAO.findOne(annotation._dataset)
135-
organization <- organizationDAO.findOne(dataset._organization)
136-
} yield Ok(Json.toJson(DataSourceId(dataset.directoryName, organization._id)))
128+
annotationDataSourceTemporaryStore.find(annotationId) match {
129+
case Some(dataSourceAndDatasetId) => Fox.successful(Ok(Json.toJson(dataSourceAndDatasetId._2)))
130+
case None =>
131+
for {
132+
annotation <- annotationDAO.findOne(annotationId) ?~> "annotation.notFound"
133+
dataset <- datasetDAO.findOne(annotation._dataset) ?~> "dataset.notFound"
134+
} yield Ok(Json.toJson(dataset._id))
135+
}
137136
}
138137
}
139138

@@ -151,20 +150,12 @@ class WKRemoteTracingStoreController @Inject()(tracingStoreService: TracingStore
151150
}
152151
}
153152

154-
def dataStoreUriForDataset(name: String,
155-
key: String,
156-
organizationId: Option[String],
157-
datasetDirectory: String): Action[AnyContent] =
153+
def dataStoreUriForDataset(name: String, key: String, datasetId: ObjectId): Action[AnyContent] =
158154
Action.async { implicit request =>
159155
tracingStoreService.validateAccess(name, key) { _ =>
160156
implicit val ctx: DBAccessContext = GlobalAccessContext
161157
for {
162-
organizationIdWithFallback <- Fox.fillOption(organizationId) {
163-
datasetDAO.getOrganizationIdForDataset(datasetDirectory)(GlobalAccessContext)
164-
} ?~> Messages("dataset.noAccess", datasetDirectory) ~> FORBIDDEN
165-
dataset <- datasetDAO.findOneByDirectoryNameAndOrganization(datasetDirectory, organizationIdWithFallback) ?~> Messages(
166-
"dataset.noAccess",
167-
datasetDirectory) ~> FORBIDDEN
158+
dataset <- datasetDAO.findOne(datasetId) ?~> "dataset.notFound" ~> NOT_FOUND
168159
dataStore <- datasetService.dataStoreFor(dataset)
169160
} yield Ok(Json.toJson(dataStore.url))
170161
}

app/models/annotation/AnnotationDataSourceTemporaryStore.scala

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@ import scala.concurrent.ExecutionContext
99
import scala.concurrent.duration.DurationInt
1010

1111
/**
12-
* Used to store a mapping from annotation id to datasource. This makes it possible for WK to answer a
13-
* /tracingstores/:name/dataSource request before an annotation is created. This happens when uploading an annotation.
14-
* It also provides a mapping from temporary/compound annotation id (e.g. taskTypeId, projectId) to datasource
12+
* Used to store a mapping from annotation id to datasource and datasetId. This makes it possible for WK to answer a
13+
* /tracingstores/:name/dataSource or /tracingstores/:name/datasetId request before an annotation is created.
14+
* This happens when uploading an annotation.
15+
* It also provides a mapping from temporary/compound annotation id (e.g. taskTypeId, projectId) to datasource.
1516
*/
16-
class AnnotationDataSourceTemporaryStore @Inject()(temporaryStore: TemporaryStore[ObjectId, DataSourceLike]) {
17+
class AnnotationDataSourceTemporaryStore @Inject()(
18+
temporaryStore: TemporaryStore[ObjectId, (DataSourceLike, ObjectId)]) {
1719

1820
private val timeOut = 7 * 24 hours
1921

20-
def store(annotationId: ObjectId, dataSource: DataSourceLike)(implicit ec: ExecutionContext): Unit =
21-
temporaryStore.insert(annotationId, dataSource, Some(timeOut))
22+
def store(annotationId: ObjectId, dataSource: DataSourceLike, datasetId: ObjectId)(
23+
implicit ec: ExecutionContext): Unit =
24+
temporaryStore.insert(annotationId, (dataSource, datasetId), Some(timeOut))
2225

23-
def find(annotationId: ObjectId): Option[DataSourceLike] =
26+
def find(annotationId: ObjectId): Option[(DataSourceLike, ObjectId)] =
2427
temporaryStore.get(annotationId)
2528

2629
}

0 commit comments

Comments
 (0)