Skip to content
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
21 changes: 17 additions & 4 deletions common-lib/src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,23 @@ usageRights.applicable = [
#-----------------------------------------------------------------------------------------
usageRights.stdUserExcluded = []

#--------------------------------------------------------------------------------------------
# List of leases that should be associated with an image when a rights category is selected
# (on upload or image edit)
# Format should be:
# usageRights.leases = [ (array)
# {
# category: "<<category-id>>",
# type: "allow-use | deny-use | allow-syndication | deny-syndication",
# startDate: "TODAY | UPLOAD | TAKEN | TXDATE", <- other than today all entries map to image metadata field
# duration: <<int nos years>>, <- optional and will be indefinite if excluded
# notes: "<<text string>>" <- optional
# },
# ...
# ]
#--------------------------------------------------------------------------------------------
# usageRights.leases = []

usageRightsConfigProvider = {
className: "com.gu.mediaservice.lib.config.RuntimeUsageRightsConfig"
config {
Expand Down Expand Up @@ -123,10 +140,6 @@ usageRightsConfigProvider = {
# }
# can be left blank or excluded if not required
# -------------------------------------------------------
usageInstructions {
}
usageRestrictions {
}

# -------------------------------------------------------------
# Announcements - notifications to be seen by users
Expand Down
71 changes: 71 additions & 0 deletions kahuna/public/js/common/usageRightsUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// -using config lease definitions to create leases for image based on chosen rights category-
export function createCategoryLeases(leaseDefs, image) {
const leaseTypes = ["allow-use", "deny-use", "allow-syndication", "deny-syndication"];
const leases = [];
leaseDefs.forEach((leaseDef) => {
//-establish start date: TODAY | UPLOAD | TAKEN | TXDATE-
const startDteType = leaseDef.startDate ?? "NONE";
let startDate = undefined;
switch (startDteType) {
case ("TODAY"):
startDate = new Date();
break;
case ("UPLOAD"):
startDate = new Date(image.data.uploadTime);
break;
case ("TAKEN"):
if (image.data.metadata.dateTaken) {
startDate = new Date(image.data.metadata.dateTaken);
}
break;
case ("TXDATE"):
if (image.data.metadata.domainMetadata &&
image.data.metadata.domainMetadata.programmes &&
image.data.metadata.domainMetadata.programmes.originalTxDate) {
startDate = new Date(image.data.metadata.domainMetadata.programmes.originalTxDate);
}
break;
}
// -check we have acceptable type and startDate-
if (leaseTypes.includes(leaseDef.type ?? "") && startDate) {
const lease = {};
lease["access"] = leaseDef.type;
lease["createdAt"] = (new Date()).toISOString();
lease["leasedBy"] = "Usage_Rights_Category";
lease["startDate"] = startDate.toISOString();
lease["notes"] = leaseDef.notes ?? "";

if (leaseDef.duration) {
let endDate = startDate;
endDate.setFullYear(endDate.getFullYear() + leaseDef.duration);
lease["endDate"] = endDate.toISOString();
}
lease["mediaId"] = image.data.id;
leases.push(lease);
}
});
return leases;
}

/* ******************************************************************************
Remove any leases from image that have same type as any rights-cat applied leases
********************************************************************************* */
export function removeCategoryLeases(categories, image, removeRights) {
const mtchCats = categories.filter(cat => cat.value === removeRights);
if (mtchCats.length === 0) {
return [];
}
const removeCat = mtchCats[0];
if (removeCat.leases.length === 0) {
return [];
}
const removeLeases = [];
image.data.leases.data.leases.forEach(lease => {
const mtches = removeCat.leases.filter(catLease => catLease.type === lease.access);
if (mtches.length > 0) {
removeLeases.push(lease);
}
});

return removeLeases;
}
63 changes: 60 additions & 3 deletions kahuna/public/js/edits/image-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {imageService} from '../image/service';
import '../services/label';
import {imageAccessor} from '../services/image-accessor';
import {usageRightsEditor} from '../usage-rights/usage-rights-editor';
import { createCategoryLeases, removeCategoryLeases } from '../common/usageRightsUtils.js';
import {metadataTemplates} from "../metadata-templates/metadata-templates";
import {leases} from '../leases/leases';
import {archiver} from '../components/gr-archiver-status/gr-archiver-status';
Expand Down Expand Up @@ -273,6 +274,7 @@ imageEditor.controller('ImageEditorCtrl', [
const image = ctrl.image;
const resource = image.data.userMetadata.data.usageRights;
editsService.update(resource, data, image);
batchSetLeasesFromUsageRights(image, data.category);
});
}

Expand Down Expand Up @@ -316,10 +318,65 @@ imageEditor.controller('ImageEditorCtrl', [
ctrl.showUsageRights = ctrl.usageRightsCategory === undefined;
}

function batchSetLeasesFromUsageRights(image, rightsCat) {
const category = ctrl.categories.find(cat => cat.value === rightsCat);
if (!category || image.data.usageRights.category === rightsCat) {
return;
}
if (category.leases.length === 0) {
// possibility of removal only
if (!image.data.usageRights.category) {
return;
}
const removeLeases = removeCategoryLeases(ctrl.categories, image, image.data.usageRights.category);
if (removeLeases.length > 0) {
$rootScope.$broadcast('events:rights-category:delete-leases', {
catLeases: removeLeases,
batch: false
});
}
return;
}
const catLeases = createCategoryLeases(category.leases, image);
if (catLeases.length === 0) {
// possibility of remove only of leases due to missing date info on image
if (!image.data.usageRights.category) {
return;
}
const removeLeases = removeCategoryLeases(ctrl.categories, image, image.data.usageRights.category);
if (removeLeases.length > 0) {
$rootScope.$broadcast('events:rights-category:delete-leases', {
catLeases: removeLeases,
batch: false
});
}
return;
}
$rootScope.$broadcast('events:rights-category:add-leases', {
catLeases: catLeases,
batch: false
});
}

function batchApplyUsageRights() {
$rootScope.$broadcast(batchApplyUsageRightsEvent, {
data: ctrl.usageRights.data
});
$rootScope.$broadcast(batchApplyUsageRightsEvent, {
data: ctrl.usageRights.data
});

//-rights category derived leases-
const mtchingRightsCats = ctrl.categories.filter(c => c.value == ctrl.usageRights.data.category);
if (mtchingRightsCats.length > 0) {
const rightsCat = mtchingRightsCats[0];
if (rightsCat.leases.length > 0) {
const catLeases = createCategoryLeases(rightsCat.leases, ctrl.image);
if (catLeases.length > 0) {
$rootScope.$broadcast('events:rights-category:add-leases', {
catLeases: catLeases,
batch: true
});
}
}
}
}

function openCollectionTree() {
Expand Down
26 changes: 26 additions & 0 deletions kahuna/public/js/leases/leases.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,32 @@ leases.controller('LeasesCtrl', [
// which also isn't ideal, but isn't quadratic either.
const batchAddLeasesEvent = 'events:batch-apply:add-leases';
const batchRemoveLeasesEvent = 'events:batch-apply:remove-leases';
const rightsCatAddLeasesEvent = 'events:rights-category:add-leases';
const rightsCatDeleteLeasesEvent = 'events:rights-category:delete-leases';

//-handle rights cat assigned lease-
$scope.$on(rightsCatAddLeasesEvent,
(e, payload) => {
let matchImages = ctrl.images.filter(img => img.data.id === payload.catLeases[0].mediaId);
if (angular.isDefined(matchImages.toArray)) {
matchImages = matchImages.toArray();
};
if (matchImages.length || payload.batch) {
leaseService.replace(matchImages[0], payload.catLeases);
}
}
);

//-handle deletion of leases from previous rights category-
$scope.$on(rightsCatDeleteLeasesEvent,
(e, payload) => {
if (payload.catLeases && 0 < payload.catLeases.length) {
payload.catLeases.forEach(lease => {
leaseService.deleteLease(lease, ctrl.images);
});
}
}
);

if (Boolean(ctrl.withBatch)) {
$scope.$on(batchAddLeasesEvent,
Expand Down
36 changes: 36 additions & 0 deletions kahuna/public/js/usage-rights/usage-rights-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {List} from 'immutable';

import '../services/image-list';

import { createCategoryLeases, removeCategoryLeases } from '../common/usageRightsUtils.js';

import template from './usage-rights-editor.html';
import './usage-rights-editor.css';

Expand Down Expand Up @@ -202,6 +204,10 @@ usageRightsEditor.controller(
const resource = image.data.userMetadata.data.usageRights;
return editsService.update(resource, data, image, true);
},
({ image }) => {
const prevRights = (0 < ctrl.usageRights.size) ? ctrl.usageRights.first().data.category : "";
return setLeasesFromUsageRights(image, prevRights);
},
({ image }) => setMetadataFromUsageRights(image, true),
({ image }) => image.get()
],'images-updated');
Expand All @@ -227,6 +233,36 @@ usageRightsEditor.controller(
'Unexpected error';
}

function setLeasesFromUsageRights(image, prevRights) {
if (ctrl.category.leases.length === 0) {
// possibility of removal only
const removeLeases = removeCategoryLeases(ctrl.categories, image, prevRights);
if (removeLeases && removeLeases.length > 0) {
$rootScope.$broadcast('events:rights-category:delete-leases', {
catLeases: removeLeases,
batch: false
});
}
return;
}
const catLeases = createCategoryLeases(ctrl.category.leases, image);
if (catLeases.length === 0) {
// possibility of removal only - missing tx date etc.
const removeLeases = removeCategoryLeases(ctrl.categories, image, prevRights);
if (removeLeases && removeLeases.length > 0) {
$rootScope.$broadcast('events:rights-category:delete-leases', {
catLeases: removeLeases,
batch: false
});
}
return;
}
$rootScope.$broadcast('events:rights-category:add-leases', {
catLeases: catLeases,
batch: false
});
}

// HACK: This should probably live somewhere else, but it's the least intrusive
// here. This updates the metadata based on the usage rights to stop users having
// to enter content twice.
Expand Down
3 changes: 3 additions & 0 deletions metadata-editor/app/controllers/EditsApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.gu.mediaservice.lib.config.{RuntimeUsageRightsConfig, UsageRightsConf
import com.gu.mediaservice.model._
import lib.EditsConfig
import model.UsageRightsProperty
import model.UsageRightsLease
import play.api.libs.json._
import play.api.mvc.Security.AuthenticatedRequest
import play.api.mvc.{AnyContent, BaseController, ControllerComponents}
Expand Down Expand Up @@ -75,6 +76,7 @@ case class CategoryResponse(
defaultRestrictions: Option[String],
caution: Option[String],
properties: List[UsageRightsProperty] = List(),
leases: Seq[UsageRightsLease] = Seq(),
usageRestrictions: Option[String],
usageSpecialInstructions: Option[String]
)
Expand All @@ -90,6 +92,7 @@ object CategoryResponse {
defaultRestrictions = u.defaultRestrictions,
caution = u.caution,
properties = UsageRightsProperty.getPropertiesForSpec(u, config.usageRightsConfig),
leases = UsageRightsLease.getLeasesForSpec(u, config.usageRightsLeases),
usageRestrictions = config.customUsageRestrictions.get(u.category),
usageSpecialInstructions = config.customSpecialInstructions.get(u.category)
)
Expand Down
4 changes: 3 additions & 1 deletion metadata-editor/app/lib/EditsConfig.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package lib

import com.gu.mediaservice.lib.config.{CommonConfig, GridConfigResources}

import model.UsageRightsLease

class EditsConfig(resources: GridConfigResources) extends CommonConfig(resources) {
val editsTable = string("dynamo.table.edits")
Expand All @@ -14,6 +14,8 @@ class EditsConfig(resources: GridConfigResources) extends CommonConfig(resources
val kahunaUri: String = services.kahunaBaseUri
val loginUriTemplate: String = services.loginUriTemplate

val usageRightsLeases: Seq[UsageRightsLease] = configuration.getOptional[Seq[UsageRightsLease]]("usageRights.leases").getOrElse(Seq.empty)

val customSpecialInstructions: Map[String, String] =
configuration.getOptional[Map[String, String]]("usageInstructions").getOrElse(Map.empty)

Expand Down
57 changes: 57 additions & 0 deletions metadata-editor/app/model/UsageRightsLease.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package model

import com.gu.mediaservice.model._
import play.api.ConfigLoader
import play.api.libs.json._
import scala.jdk.CollectionConverters._

case class UsageRightsLease(
category: String,
`type`: String,
startDate: String,
duration: Option[Int],
notes: Option[String]
)

object UsageRightsLease {

def getLeasesForSpec(u: UsageRightsSpec, leases: Seq[UsageRightsLease]): Seq[UsageRightsLease] = leases.filter(_.category == u.category)

implicit val writes: Writes[UsageRightsLease] = Json.writes[UsageRightsLease]

implicit val configLoader: ConfigLoader[Seq[UsageRightsLease]] = {
ConfigLoader(_.getConfigList).map(
_.asScala.toSeq.map(config => {

val categoryId = if (config.hasPath("category")) {
config.getString("category")
} else ""

val leaseType = if (config.hasPath("type")) {
config.getString("type")
} else ""

val startDate = if (config.hasPath("startDate")) {
config.getString("startDate")
} else ""

val duration = if (config.hasPath("duration")) {
Some(config.getInt("duration"))
} else None

val notes = if (config.hasPath("notes")) {
Some(config.getString("notes"))
} else None

UsageRightsLease (
category = categoryId,
`type` = leaseType,
startDate = startDate,
duration = duration,
notes = notes
)

}))
}

}
Loading