Skip to content

Commit 4152a29

Browse files
authored
Merge pull request #212 from clowder-framework/release/1.17.0
1.17.0 release
2 parents 1e41093 + 8e521e6 commit 4152a29

File tree

17 files changed

+603
-175
lines changed

17 files changed

+603
-175
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

7+
## 1.17.0 - 2021-04-29
8+
9+
### Fixed
10+
- Close channel after submitting events to RabbitMQMessageService.
11+
12+
### Added
13+
- Endpoint `/api/datasets/createfrombag` to ingest datasets in BagIt format. Includes basic dataset metadata, files,
14+
folders and technical metadata. Downloading datasets now includes extra Datacite and Clowder metadata.
15+
- Endpoint `/api/files/bulkRemove` to delete multiple files in one call. [#12](https://github.com/clowder-framework/clowder/issues/12)
16+
- Log an event each time that a user archives or unarchives a file.
17+
- Persist name of message bus response queue, preventing status messages from getting lost after a reboot.
18+
19+
### Changed
20+
- Updated Sphinx dependencies due to security and changes in required packages.
21+
722
## 1.16.0 - 2021-03-31
823

924
### Fixed

app/Global.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ object Global extends WithFilters(new GzipFilter(), new Jsonp(), CORSFilter()) w
4545

4646
val users: UserService = DI.injector.getInstance(classOf[UserService])
4747

48+
// get clowder unique ID
49+
Logger.info(s"Starting clowder with id = ${AppConfiguration.getInstance}")
50+
4851
// set the default ToS version
4952
AppConfiguration.setDefaultTermsOfServicesVersion()
5053

app/api/Datasets.scala

Lines changed: 309 additions & 11 deletions
Large diffs are not rendered by default.

app/api/Files.scala

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
package api
22

3-
import scala.annotation.tailrec
4-
import java.io.FileInputStream
5-
import java.net.{URL, URLEncoder}
6-
7-
import javax.inject.Inject
8-
import javax.mail.internet.MimeUtility
9-
import _root_.util.{FileUtils, JSONLD, Parsers, RequestUtils, SearchUtils}
3+
import _root_.util._
104
import com.mongodb.casbah.Imports._
11-
import controllers.Previewers
5+
import controllers.{Previewers, Utils}
126
import jsonutils.JsonUtil
137
import models._
148
import play.api.Logger
@@ -18,16 +12,16 @@ import play.api.libs.concurrent.Execution.Implicits._
1812
import play.api.libs.iteratee.Enumerator
1913
import play.api.libs.json.Json._
2014
import play.api.libs.json._
21-
import play.api.mvc.{Action, ResponseHeader, Result, SimpleResult}
15+
import play.api.mvc.{ResponseHeader, SimpleResult}
2216
import services._
17+
import services.s3.S3ByteStorageService
2318

24-
import scala.collection.mutable.ListBuffer
25-
import scala.util.parsing.json.JSONArray
26-
import java.text.SimpleDateFormat
19+
import java.io.FileInputStream
20+
import java.net.URL
2721
import java.util.Date
28-
29-
import controllers.Utils
30-
import services.s3.S3ByteStorageService
22+
import javax.inject.Inject
23+
import scala.annotation.tailrec
24+
import scala.collection.mutable.ListBuffer
3125

3226
/**
3327
* Json API for files.
@@ -1616,6 +1610,32 @@ class Files @Inject()(
16161610
}
16171611
}
16181612

1613+
def bulkDeleteFiles() = PrivateServerAction (parse.json) {implicit request=>
1614+
request.user match {
1615+
case Some(user) => {
1616+
val fileIds = request.body.\("fileIds").asOpt[List[String]].getOrElse(List.empty[String])
1617+
if (fileIds.isEmpty){
1618+
BadRequest("No file ids supplied")
1619+
} else {
1620+
var resourceRefList: ListBuffer[ResourceRef] = ListBuffer.empty[ResourceRef]
1621+
for (fileId <- fileIds) {
1622+
if (UUID.isValid(fileId)) {
1623+
val current_resource_ref = ResourceRef(ResourceRef.file, UUID(fileId))
1624+
resourceRefList += current_resource_ref
1625+
}
1626+
}
1627+
val filesIdsCanDelete = Permission.checkPermissions(request.user, Permission.DeleteFile, resourceRefList.toList).approved.map(_.id)
1628+
for (id <- filesIdsCanDelete) {
1629+
files.removeFile(id,Utils.baseUrl(request), request.apiKey, request.user)
1630+
}
1631+
Ok(toJson(Map("status" -> "success")))
1632+
}
1633+
}
1634+
case None => {
1635+
BadRequest("No user supplied")
1636+
}
1637+
}
1638+
}
16191639

16201640
def removeFile(id: UUID) = PermissionAction(Permission.DeleteFile, Some(ResourceRef(ResourceRef.file, id))) { implicit request =>
16211641
files.get(id) match {
@@ -1856,9 +1876,11 @@ class Files @Inject()(
18561876
}
18571877

18581878
def archive(id: UUID) = PermissionAction(Permission.ArchiveFile, Some(ResourceRef(ResourceRef.file, id))) { implicit request =>
1879+
implicit val user = request.user
18591880
files.get(id) match {
18601881
case Some(file) => {
18611882
files.setStatus(id, FileStatus.ARCHIVED)
1883+
sinkService.logFileArchiveEvent(file, user)
18621884
Ok(toJson(Map("status" -> "success")))
18631885
}
18641886
case None => {
@@ -1869,9 +1891,11 @@ class Files @Inject()(
18691891
}
18701892

18711893
def unarchive(id: UUID) = PermissionAction(Permission.ArchiveFile, Some(ResourceRef(ResourceRef.file, id))) { implicit request =>
1894+
implicit val user = request.user
18721895
files.get(id) match {
18731896
case Some(file) => {
18741897
files.setStatus(id, FileStatus.PROCESSED)
1898+
sinkService.logFileUnarchiveEvent(file, user)
18751899
Ok(toJson(Map("status" -> "success")))
18761900
}
18771901
case None => {

app/api/Status.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ class Status @Inject()(spaces: SpaceService,
3131

3232
def status = UserAction(needActive=false) { implicit request =>
3333

34-
Ok(Json.obj("version" -> getVersionInfo,
34+
Ok(Json.obj("instance" -> AppConfiguration.getInstance,
35+
"version" -> getVersionInfo,
3536
"counts" -> getCounts(request.user),
3637
"plugins" -> getPlugins(request.user),
3738
"extractors" -> Json.toJson(extractors.getExtractorNames(List.empty))))

app/controllers/Application.scala

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
package controllers
22

3-
import java.net.URL
4-
5-
import javax.inject.{Inject, Singleton}
6-
import api.Permission
7-
import api.Permission._
8-
import play.api.{Logger, Play, Routes}
3+
import models.{Event, UUID, UserStatus}
4+
import play.api.Play.current
95
import play.api.mvc.Action
6+
import play.api.{Logger, Play, Routes}
107
import services._
11-
import models.{Event, UUID, User, UserStatus}
12-
import org.owasp.html.Sanitizers
13-
import play.api.Logger
14-
import play.api.libs.concurrent.Execution.Implicits._
15-
import play.api.Play.current
168
import util.Formatters.sanitizeHTML
179

10+
import java.net.URL
11+
import javax.inject.{Inject, Singleton}
1812
import scala.collection.immutable.List
1913
import scala.collection.mutable.ListBuffer
2014

@@ -368,6 +362,7 @@ class Application @Inject() (files: FileService, collections: CollectionService,
368362
api.routes.javascript.Files.updateFileName,
369363
api.routes.javascript.Files.updateDescription,
370364
api.routes.javascript.Files.extract,
365+
api.routes.javascript.Files.bulkDeleteFiles,
371366
api.routes.javascript.Files.removeFile,
372367
api.routes.javascript.Files.follow,
373368
api.routes.javascript.Files.unfollow,

app/services/AppConfigurationService.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ trait AppConfigurationService {
7070
object AppConfiguration {
7171
val appConfig: AppConfigurationService = DI.injector.getInstance(classOf[AppConfigurationService])
7272

73+
// ----------------------------------------------------------------------
74+
def getInstance: String = {
75+
appConfig.getProperty[String]("instance") match {
76+
case Some(id) => id
77+
case None => {
78+
val id = scala.util.Random.alphanumeric.take(10).mkString
79+
appConfig.setProperty("instance", id)
80+
id
81+
}
82+
}
83+
}
84+
7385
// ----------------------------------------------------------------------
7486

7587
/** Set the default theme */

app/services/EventSinkService.scala

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class EventSinkService {
3232
def logEvent(message: JsValue) = {
3333
// Inject timestamp before logging the event
3434
val event = message.as[JsObject] + ("created" -> Json.toJson(java.util.Date.from(Instant.now())))
35-
Logger.info("Submitting message to event sink exchange: " + Json.stringify(event))
35+
Logger.debug("Submitting message to event sink exchange: " + Json.stringify(event))
3636
try {
3737
messageService.submit(exchangeName, queueName, event, "fanout")
3838
} catch {
@@ -261,6 +261,34 @@ class EventSinkService {
261261
"size" -> (dataset.files.length + dataset.folders.length)
262262
))
263263
}
264+
265+
def logFileArchiveEvent(file: File, archiver: Option[User]) = {
266+
logEvent(Json.obj(
267+
"category" -> "archive",
268+
"type" -> "file",
269+
"resource_id" -> file.id,
270+
"resource_name" -> file.filename,
271+
"author_id" -> file.author.id,
272+
"author_name" -> file.author.fullName,
273+
"user_id" -> archiver.get.id,
274+
"user_name" -> archiver.get.getMiniUser.fullName,
275+
"size" -> file.length
276+
))
277+
}
278+
279+
def logFileUnarchiveEvent(file: File, unarchiver: Option[User]) = {
280+
logEvent(Json.obj(
281+
"category" -> "unarchive",
282+
"type" -> "file",
283+
"resource_id" -> file.id,
284+
"resource_name" -> file.filename,
285+
"author_id" -> file.author.id,
286+
"author_name" -> file.author.fullName,
287+
"user_id" -> unarchiver.get.id,
288+
"user_name" -> unarchiver.get.getMiniUser.fullName,
289+
"size" -> file.length
290+
))
291+
}
264292
}
265293

266294
//case class EventSinkMessage(created: Long, category: String, metadata: JsValue)

app/services/mongodb/MongoDBFileService.scala

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -806,67 +806,67 @@ class MongoDBFileService @Inject() (
806806
def removeFile(id: UUID, host: String, apiKey: Option[String], user: Option[User]){
807807
get(id) match{
808808
case Some(file) => {
809-
if(!file.isIntermediate){
810-
val fileDatasets = datasets.findByFileIdDirectlyContain(file.id)
811-
for(fileDataset <- fileDatasets){
812-
datasets.removeFile(fileDataset.id, id)
813-
if(!file.xmlMetadata.isEmpty){
814-
datasets.index(fileDataset.id)
815-
}
809+
if(!file.isIntermediate){
810+
val fileDatasets = datasets.findByFileIdDirectlyContain(file.id)
811+
for(fileDataset <- fileDatasets){
812+
datasets.removeFile(fileDataset.id, id)
813+
if(!file.xmlMetadata.isEmpty){
814+
datasets.index(fileDataset.id)
815+
}
816816

817-
if(!file.thumbnail_id.isEmpty && !fileDataset.thumbnail_id.isEmpty){
818-
if(file.thumbnail_id.get.equals(fileDataset.thumbnail_id.get)){
819-
datasets.newThumbnail(fileDataset.id)
820-
collections.get(fileDataset.collections).found.foreach(collection => {
821-
if(!collection.thumbnail_id.isEmpty){
822-
if(collection.thumbnail_id.get.equals(fileDataset.thumbnail_id.get)){
823-
collections.createThumbnail(collection.id)
817+
if(!file.thumbnail_id.isEmpty && !fileDataset.thumbnail_id.isEmpty){
818+
if(file.thumbnail_id.get.equals(fileDataset.thumbnail_id.get)){
819+
datasets.newThumbnail(fileDataset.id)
820+
collections.get(fileDataset.collections).found.foreach(collection => {
821+
if(!collection.thumbnail_id.isEmpty){
822+
if(collection.thumbnail_id.get.equals(fileDataset.thumbnail_id.get)){
823+
collections.createThumbnail(collection.id)
824+
}
824825
}
825-
}
826-
})
827-
}
826+
})
827+
}
828+
}
829+
828830
}
829-
830-
}
831831

832-
val fileFolders = folders.findByFileId(file.id)
833-
for(fileFolder <- fileFolders) {
834-
folders.removeFile(fileFolder.id, file.id)
835-
}
836-
for(section <- sections.findByFileId(file.id)){
837-
sections.removeSection(section)
838-
}
839-
for(comment <- comments.findCommentsByFileId(id)){
840-
comments.removeComment(comment)
841-
}
842-
for(texture <- threeD.findTexturesByFileId(file.id)){
843-
ThreeDTextureDAO.removeById(new ObjectId(texture.id.stringify))
844-
}
845-
for (follower <- file.followers) {
846-
userService.unfollowFile(follower, id)
832+
val fileFolders = folders.findByFileId(file.id)
833+
for(fileFolder <- fileFolders) {
834+
folders.removeFile(fileFolder.id, file.id)
835+
}
836+
for(section <- sections.findByFileId(file.id)){
837+
sections.removeSection(section)
838+
}
839+
for(comment <- comments.findCommentsByFileId(id)){
840+
comments.removeComment(comment)
841+
}
842+
for(texture <- threeD.findTexturesByFileId(file.id)){
843+
ThreeDTextureDAO.removeById(new ObjectId(texture.id.stringify))
844+
}
845+
for (follower <- file.followers) {
846+
userService.unfollowFile(follower, id)
847+
}
847848
}
848-
}
849849

850-
// delete the actual file
851-
if(isLastPointingToLoader(file.loader, file.loader_id)) {
852-
for(preview <- previews.findByFileId(file.id)){
853-
previews.removePreview(preview)
850+
// delete the actual file
851+
if(isLastPointingToLoader(file.loader, file.loader_id)) {
852+
for(preview <- previews.findByFileId(file.id)){
853+
previews.removePreview(preview)
854+
}
855+
if(!file.thumbnail_id.isEmpty)
856+
thumbnails.remove(UUID(file.thumbnail_id.get))
857+
ByteStorageService.delete(file.loader, file.loader_id, FileDAO.COLLECTION)
854858
}
855-
if(!file.thumbnail_id.isEmpty)
856-
thumbnails.remove(UUID(file.thumbnail_id.get))
857-
ByteStorageService.delete(file.loader, file.loader_id, FileDAO.COLLECTION)
858-
}
859859

860-
import UUIDConversions._
861-
FileDAO.removeById(file.id)
862-
appConfig.incrementCount('files, -1)
863-
appConfig.incrementCount('bytes, -file.length)
864-
current.plugin[ElasticsearchPlugin].foreach {
865-
_.delete(id.stringify)
866-
}
860+
import UUIDConversions._
861+
FileDAO.removeById(file.id)
862+
appConfig.incrementCount('files, -1)
863+
appConfig.incrementCount('bytes, -file.length)
864+
current.plugin[ElasticsearchPlugin].foreach {
865+
_.delete(id.stringify)
866+
}
867867

868-
// finally remove metadata - if done before file is deleted, document metadataCounts won't match
869-
metadatas.removeMetadataByAttachTo(ResourceRef(ResourceRef.file, id), host, apiKey, user)
868+
// finally remove metadata - if done before file is deleted, document metadataCounts won't match
869+
metadatas.removeMetadataByAttachTo(ResourceRef(ResourceRef.file, id), host, apiKey, user)
870870
}
871871
case None => Logger.debug("File not found")
872872
}

0 commit comments

Comments
 (0)