added support for embedded resources

This commit is contained in:
Art Lowel
2017-06-01 11:41:15 +02:00
parent c21a255a62
commit 739371cb35
25 changed files with 850 additions and 674 deletions

View File

@@ -1,7 +1,7 @@
import "reflect-metadata";
import { GenericConstructor } from "../../shared/generic-constructor";
import { CacheableObject } from "../object-cache.reducer";
import { NormalizedDSOType } from "../models/normalized-dspace-object-type";
import { ResourceType } from "../../shared/resource-type";
const mapsToMetadataKey = Symbol("mapsTo");
const relationshipKey = Symbol("relationship");
@@ -16,7 +16,7 @@ export const getMapsTo = function(target: any) {
return Reflect.getOwnMetadata(mapsToMetadataKey, target);
};
export const relationship = function(value: NormalizedDSOType): any {
export const relationship = function(value: ResourceType): any {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
if (!target || !propertyKey) {
return;

View File

@@ -13,7 +13,8 @@ import { Observable } from "rxjs/Observable";
import { RemoteData } from "../../data/remote-data";
import { GenericConstructor } from "../../shared/generic-constructor";
import { getMapsTo, getResourceType, getRelationships } from "./build-decorators";
import { NormalizedDSOFactory } from "../models/normalized-dspace-object-factory";
import { NormalizedObjectFactory } from "../models/normalized-object-factory";
import { Request } from "../../data/request.models";
@Injectable()
export class RemoteDataBuildService {
@@ -124,13 +125,13 @@ export class RemoteDataBuildService {
relationships.forEach((relationship: string) => {
if (hasValue(normalized[relationship])) {
const resourceType = getResourceType(normalized, relationship);
const resourceConstructor = NormalizedDSOFactory.getConstructor(resourceType);
const resourceConstructor = NormalizedObjectFactory.getConstructor(resourceType);
if (Array.isArray(normalized[relationship])) {
// without the setTimeout, the actions inside requestService.configure
// are dispatched, but sometimes don't arrive. I'm unsure why atm.
setTimeout(() => {
normalized[relationship].forEach((href: string) => {
this.requestService.configure(href, resourceConstructor)
this.requestService.configure(new Request(href))
});
}, 0);
@@ -142,7 +143,7 @@ export class RemoteDataBuildService {
// without the setTimeout, the actions inside requestService.configure
// are dispatched, but sometimes don't arrive. I'm unsure why atm.
setTimeout(() => {
this.requestService.configure(normalized[relationship], resourceConstructor);
this.requestService.configure(new Request(normalized[relationship]));
},0);
links[relationship] = this.buildSingle(normalized[relationship], resourceConstructor);

View File

@@ -2,7 +2,7 @@ import { autoserialize, inheritSerialization } from "cerialize";
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
import { Bundle } from "../../shared/bundle.model";
import { mapsTo, relationship } from "../builders/build-decorators";
import { NormalizedDSOType } from "./normalized-dspace-object-type";
import { ResourceType } from "../../shared/resource-type";
@mapsTo(Bundle)
@inheritSerialization(NormalizedDSpaceObject)
@@ -11,7 +11,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject {
* The primary bitstream of this Bundle
*/
@autoserialize
@relationship(NormalizedDSOType.NormalizedBitstream)
@relationship(ResourceType.Bitstream)
primaryBitstream: string;
/**
@@ -25,6 +25,6 @@ export class NormalizedBundle extends NormalizedDSpaceObject {
owner: string;
@autoserialize
@relationship(NormalizedDSOType.NormalizedBitstream)
@relationship(ResourceType.Bitstream)
bitstreams: Array<string>;
}

View File

@@ -2,7 +2,7 @@ import { autoserialize, inheritSerialization, autoserializeAs } from "cerialize"
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
import { Collection } from "../../shared/collection.model";
import { mapsTo, relationship } from "../builders/build-decorators";
import { NormalizedDSOType } from "./normalized-dspace-object-type";
import { ResourceType } from "../../shared/resource-type";
@mapsTo(Collection)
@inheritSerialization(NormalizedDSpaceObject)
@@ -30,7 +30,7 @@ export class NormalizedCollection extends NormalizedDSpaceObject {
owner: string;
@autoserialize
@relationship(NormalizedDSOType.NormalizedItem)
@relationship(ResourceType.Item)
items: Array<string>;
}

View File

@@ -2,7 +2,7 @@ import { autoserialize, inheritSerialization, autoserializeAs } from "cerialize"
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
import { Community } from "../../shared/community.model";
import { mapsTo, relationship } from "../builders/build-decorators";
import { NormalizedDSOType } from "./normalized-dspace-object-type";
import { ResourceType } from "../../shared/resource-type";
@mapsTo(Community)
@inheritSerialization(NormalizedDSpaceObject)
@@ -30,7 +30,7 @@ export class NormalizedCommunity extends NormalizedDSpaceObject {
owner: string;
@autoserialize
@relationship(NormalizedDSOType.NormalizedCollection)
@relationship(ResourceType.Collection)
collections: Array<string>;
}

View File

@@ -1,7 +0,0 @@
export enum NormalizedDSOType {
NormalizedBitstream,
NormalizedBundle,
NormalizedItem,
NormalizedCollection,
NormalizedCommunity
}

View File

@@ -1,6 +1,7 @@
import { autoserialize, autoserializeAs } from "cerialize";
import { CacheableObject } from "../object-cache.reducer";
import { Metadatum } from "../../shared/metadatum.model";
import { ResourceType } from "../../shared/resource-type";
/**
* An abstract model class for a DSpaceObject.
@@ -26,7 +27,7 @@ export abstract class NormalizedDSpaceObject implements CacheableObject {
* A string representing the kind of DSpaceObject, e.g. community, item, …
*/
@autoserialize
type: string;
type: ResourceType;
/**
* The name for this DSpaceObject
@@ -49,5 +50,6 @@ export abstract class NormalizedDSpaceObject implements CacheableObject {
/**
* The DSpaceObject that owns this DSpaceObject
*/
@autoserialize
owner: string;
}

View File

@@ -2,7 +2,7 @@ import { inheritSerialization, autoserialize } from "cerialize";
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
import { Item } from "../../shared/item.model";
import { mapsTo, relationship } from "../builders/build-decorators";
import { NormalizedDSOType } from "./normalized-dspace-object-type";
import { ResourceType } from "../../shared/resource-type";
@mapsTo(Item)
@inheritSerialization(NormalizedDSpaceObject)
@@ -33,7 +33,7 @@ export class NormalizedItem extends NormalizedDSpaceObject {
* An array of Collections that are direct parents of this Item
*/
@autoserialize
@relationship(NormalizedDSOType.NormalizedCollection)
@relationship(ResourceType.Collection)
parents: Array<string>;
/**
@@ -42,6 +42,6 @@ export class NormalizedItem extends NormalizedDSpaceObject {
owner: string;
@autoserialize
@relationship(NormalizedDSOType.NormalizedBundle)
@relationship(ResourceType.Bundle)
bundles: Array<string>;
}

View File

@@ -4,25 +4,25 @@ import { NormalizedBundle } from "./normalized-bundle.model";
import { NormalizedItem } from "./normalized-item.model";
import { NormalizedCollection } from "./normalized-collection.model";
import { GenericConstructor } from "../../shared/generic-constructor";
import { NormalizedDSOType } from "./normalized-dspace-object-type";
import { NormalizedCommunity } from "./normalized-community.model";
import { ResourceType } from "../../shared/resource-type";
export class NormalizedDSOFactory {
public static getConstructor(type: NormalizedDSOType): GenericConstructor<NormalizedDSpaceObject> {
export class NormalizedObjectFactory {
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedDSpaceObject> {
switch (type) {
case NormalizedDSOType.NormalizedBitstream: {
case ResourceType.Bitstream: {
return NormalizedBitstream
}
case NormalizedDSOType.NormalizedBundle: {
case ResourceType.Bundle: {
return NormalizedBundle
}
case NormalizedDSOType.NormalizedItem: {
case ResourceType.Item: {
return NormalizedItem
}
case NormalizedDSOType.NormalizedCollection: {
case ResourceType.Collection: {
return NormalizedCollection
}
case NormalizedDSOType.NormalizedCommunity: {
case ResourceType.Community: {
return NormalizedCommunity
}
default: {

View File

@@ -9,8 +9,12 @@ export class SuccessResponse extends Response {
}
export class ErrorResponse extends Response {
constructor(public errorMessage: string) {
errorMessage: string;
constructor(error: Error) {
super(false);
console.error(error);
this.errorMessage = error.message;
}
}

View File

@@ -33,11 +33,8 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
findAll(scopeID?: string): RemoteData<Array<TDomain>> {
const href = this.getFindAllHref(scopeID);
if (!this.responseCache.has(href) && !this.requestService.isPending(href)) {
const request = new FindAllRequest(href, this.normalizedResourceType, scopeID);
this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(href));
}
const request = new FindAllRequest(href, scopeID);
this.requestService.configure(request);
return this.rdbService.buildList<TNormalized, TDomain>(href, this.normalizedResourceType);
// return this.rdbService.buildList(href);
}
@@ -48,21 +45,14 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
findById(id: string): RemoteData<TDomain> {
const href = this.getFindByIDHref(id);
if (!this.objectCache.hasBySelfLink(href) && !this.requestService.isPending(href)) {
const request = new FindByIDRequest(href, this.normalizedResourceType, id);
this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(href));
}
const request = new FindByIDRequest(href, id);
this.requestService.configure(request);
return this.rdbService.buildSingle<TNormalized, TDomain>(href, this.normalizedResourceType);
// return this.rdbService.buildSingle(href);
}
findByHref(href: string): RemoteData<TDomain> {
if (!this.objectCache.hasBySelfLink(href) && !this.requestService.isPending(href)) {
const request = new Request(href, this.normalizedResourceType);
this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(href));
}
this.requestService.configure(new Request(href));
return this.rdbService.buildSingle<TNormalized, TDomain>(href, this.normalizedResourceType);
// return this.rdbService.buildSingle(href));
}

View File

@@ -8,7 +8,7 @@ import { DSpaceRESTv2Serializer } from "../dspace-rest-v2/dspace-rest-v2.seriali
import { CacheableObject } from "../cache/object-cache.reducer";
import { Observable } from "rxjs";
import { Response, SuccessResponse, ErrorResponse } from "../cache/response-cache.models";
import { hasNoValue } from "../../shared/empty.util";
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from "../../shared/empty.util";
import { GlobalConfig, GLOBAL_CONFIG } from "../../../config";
import { RequestState, RequestEntry } from "./request.reducer";
import {
@@ -17,6 +17,12 @@ import {
} from "./request.actions";
import { ResponseCacheService } from "../cache/response-cache.service";
import { RequestService } from "./request.service";
import { NormalizedObjectFactory } from "../cache/models/normalized-object-factory";
import { ResourceType } from "../shared/resource-type";
function isObjectLevel(halObj: any) {
return isNotEmpty(halObj._links) && hasValue(halObj._links.self);
}
@Injectable()
export class RequestEffects {
@@ -38,30 +44,87 @@ export class RequestEffects {
.take(1);
})
.flatMap((entry: RequestEntry) => {
const [ifArray, ifNotArray] = this.restApi.get(entry.request.href)
.share() // share ensures restApi.get() doesn't get called twice when the partitions are used below
.partition((data: DSpaceRESTV2Response) => Array.isArray(data._embedded));
return Observable.merge(
ifArray.map((data: DSpaceRESTV2Response) => {
return new DSpaceRESTv2Serializer(entry.request.resourceType).deserializeArray(data);
}).do((cos: CacheableObject[]) => cos.forEach((t) => this.addToObjectCache(t)))
.map((cos: Array<CacheableObject>): Array<string> => cos.map(t => t.uuid)),
ifNotArray.map((data: DSpaceRESTV2Response) => {
return new DSpaceRESTv2Serializer(entry.request.resourceType).deserialize(data);
}).do((co: CacheableObject) => this.addToObjectCache(co))
.map((co: CacheableObject): Array<string> => [co.uuid])
).map((ids: Array<string>) => new SuccessResponse(ids))
return this.restApi.get(entry.request.href)
.map((data: DSpaceRESTV2Response) => this.processEmbedded(data._embedded))
.map((ids: Array<string>) => new SuccessResponse(ids))
.do((response: Response) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive))
.map((response: Response) => new RequestCompleteAction(entry.request.href))
.catch((error: Error) => Observable.of(new ErrorResponse(error.message))
.catch((error: Error) => Observable.of(new ErrorResponse(error))
.do((response: Response) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive))
.map((response: Response) => new RequestCompleteAction(entry.request.href)));
});
protected processEmbedded(_embedded: any): Array<string> {
if (isNotEmpty(_embedded)) {
if (isObjectLevel(_embedded)) {
return this.deserializeAndCache(_embedded);
}
else {
let uuids = [];
Object.keys(_embedded)
.filter(property => _embedded.hasOwnProperty(property))
.forEach(property => {
uuids = [...uuids, ...this.deserializeAndCache(_embedded[property])];
});
return uuids;
}
}
}
protected deserializeAndCache(obj): Array<string> {
let type: ResourceType;
const isArray = Array.isArray(obj);
if (isArray && isEmpty(obj)) {
return [];
}
if (isArray) {
type = obj[0]["type"];
}
else {
type = obj["type"];
}
if (hasValue(type)) {
const normObjConstructor = NormalizedObjectFactory.getConstructor(type);
if (hasValue(normObjConstructor)) {
const serializer = new DSpaceRESTv2Serializer(normObjConstructor);
if (isArray) {
obj.forEach(o => {
if (isNotEmpty(o._embedded)) {
this.processEmbedded(o._embedded);
}
});
const normalizedObjArr = serializer.deserializeArray(obj);
normalizedObjArr.forEach(t => this.addToObjectCache(t));
return normalizedObjArr.map(t => t.uuid);
}
else {
if (isNotEmpty(obj._embedded)) {
this.processEmbedded(obj._embedded);
}
const normalizedObj = serializer.deserialize(obj);
this.addToObjectCache(normalizedObj);
return [normalizedObj.uuid];
}
}
else {
//TODO move check to Validator?
throw new Error(`The server returned an object with an unknown a known type: ${type}`);
}
}
else {
//TODO move check to Validator
throw new Error(`The server returned an object without a type: ${JSON.stringify(obj)}`);
}
}
protected addToObjectCache(co: CacheableObject): void {
if (hasNoValue(co) || hasNoValue(co.uuid)) {
throw new Error('The server returned an invalid object');

View File

@@ -5,28 +5,25 @@ import { GenericConstructor } from "../shared/generic-constructor";
export class Request<T> {
constructor(
public href: string,
public resourceType: GenericConstructor<T>
) {}
}
export class FindByIDRequest<T> extends Request<T> {
constructor(
href: string,
resourceType: GenericConstructor<T>,
public resourceID: string
) {
super(href, resourceType);
super(href);
}
}
export class FindAllRequest<T> extends Request<T> {
constructor(
href: string,
resourceType: GenericConstructor<T>,
public scopeID?: string,
public paginationOptions?: PaginationOptions,
public sortOptions?: SortOptions
) {
super(href, resourceType);
super(href);
}
}

View File

@@ -8,7 +8,6 @@ import { RequestConfigureAction, RequestExecuteAction } from "./request.actions"
import { ResponseCacheService } from "../cache/response-cache.service";
import { ObjectCacheService } from "../cache/object-cache.service";
import { CacheableObject } from "../cache/object-cache.reducer";
import { GenericConstructor } from "../shared/generic-constructor";
@Injectable()
export class RequestService {
@@ -35,14 +34,13 @@ export class RequestService {
return this.store.select<RequestEntry>('core', 'data', 'request', href);
}
configure<T extends CacheableObject>(href: string, normalizedType: GenericConstructor<T>): void {
const isCached = this.objectCache.hasBySelfLink(href);
const isPending = this.isPending(href);
configure<T extends CacheableObject>(request: Request<T>): void {
const isCached = this.objectCache.hasBySelfLink(request.href);
const isPending = this.isPending(request.href);
if (!(isCached || isPending)) {
const request = new Request(href, normalizedType);
this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(href));
this.store.dispatch(new RequestExecuteAction(request.href));
}
}
}

View File

@@ -84,14 +84,10 @@ describe("DSpaceRESTv2Serializer", () => {
it("should turn a valid document describing a single entity in to a valid model", () => {
const serializer = new DSpaceRESTv2Serializer(TestModel);
const doc = {
"_embedded": testResponses[0],
};
const model = serializer.deserialize(testResponses[0]);
const model = serializer.deserialize(doc);
expect(model.id).toBe(doc._embedded.id);
expect(model.name).toBe(doc._embedded.name);
expect(model.id).toBe(testResponses[0].id);
expect(model.name).toBe(testResponses[0].name);
});
//TODO cant implement/test this yet - depends on how relationships
@@ -127,12 +123,8 @@ describe("DSpaceRESTv2Serializer", () => {
it("should throw an error when dealing with a document describing an array", () => {
const serializer = new DSpaceRESTv2Serializer(TestModel);
const doc = {
"_embedded": testResponses
};
expect(() => {
serializer.deserialize(doc);
serializer.deserialize(testResponses);
}).toThrow();
});

View File

@@ -3,6 +3,7 @@ import { Serializer } from "../serializer";
import { DSpaceRESTV2Response } from "./dspace-rest-v2-response.model";
import { DSpaceRESTv2Validator } from "./dspace-rest-v2.validator";
import { GenericConstructor } from "../shared/generic-constructor";
import { hasNoValue, hasValue } from "../../shared/empty.util";
/**
* This Serializer turns responses from v2 of DSpace's REST API
@@ -49,13 +50,13 @@ export class DSpaceRESTv2Serializer<T> implements Serializer<T> {
* @param response An object returned by the backend
* @returns a model of type T
*/
deserialize(response: DSpaceRESTV2Response): T {
deserialize(response: any): T {
// TODO enable validation, once rest data stabilizes
// new DSpaceRESTv2Validator(response).validate();
if (Array.isArray(response._embedded)) {
if (Array.isArray(response)) {
throw new Error('Expected a single model, use deserializeArray() instead');
}
let normalized = Object.assign({}, response._embedded, this.normalizeLinks(response._embedded._links));
let normalized = Object.assign({}, response, this.normalizeLinks(response._links));
return <T> Deserialize(normalized, this.modelType);
}
@@ -65,13 +66,13 @@ export class DSpaceRESTv2Serializer<T> implements Serializer<T> {
* @param response An object returned by the backend
* @returns an array of models of type T
*/
deserializeArray(response: DSpaceRESTV2Response): Array<T> {
deserializeArray(response: any): Array<T> {
//TODO enable validation, once rest data stabilizes
// new DSpaceRESTv2Validator(response).validate();
if (!Array.isArray(response._embedded)) {
if (!Array.isArray(response)) {
throw new Error('Expected an Array, use deserialize() instead');
}
let normalized = response._embedded.map((resource) => {
let normalized = response.map((resource) => {
return Object.assign({}, resource, this.normalizeLinks(resource._links));
});

View File

@@ -2,6 +2,7 @@ import { Metadatum } from "./metadatum.model"
import { isEmpty, isNotEmpty } from "../../shared/empty.util";
import { CacheableObject } from "../cache/object-cache.reducer";
import { RemoteData } from "../data/remote-data";
import { ResourceType } from "./resource-type";
/**
* An abstract model class for a DSpaceObject.
@@ -23,7 +24,7 @@ export abstract class DSpaceObject implements CacheableObject {
/**
* A string representing the kind of DSpaceObject, e.g. community, item, …
*/
type: string;
type: ResourceType;
/**
* The name for this DSpaceObject

View File

@@ -0,0 +1,11 @@
/**
* TODO replace with actual string enum after upgrade to TypeScript 2.4:
* https://github.com/Microsoft/TypeScript/pull/15486
*/
export enum ResourceType {
Bundle = <any> "bundle",
Bitstream = <any> "bitstream",
Item = <any> "item",
Collection = <any> "collection",
Community = <any> "community"
}

View File

@@ -82,7 +82,7 @@ export function createMockApi() {
let id = req.params.community_id;
try {
req.community_id = id;
req.community = COMMUNITIES.find((community) => {
req.community = COMMUNITIES["communities"].find((community) => {
return community.id === id;
});
next();
@@ -143,7 +143,7 @@ export function createMockApi() {
let id = req.params.collection_id;
try {
req.collection_id = id;
req.collection = COLLECTIONS.find((collection) => {
req.collection = COLLECTIONS["collections"].find((collection) => {
return collection.id === id;
});
next();
@@ -205,7 +205,7 @@ export function createMockApi() {
let id = req.params.item_id;
try {
req.item_id = id;
req.item = ITEMS.find((item) => {
req.item = ITEMS["items"].find((item) => {
return item.id === id;
});
next();
@@ -250,7 +250,7 @@ export function createMockApi() {
let id = req.params.bundle_id;
try {
req.bundle_id = id;
req.bundle = BUNDLES.find((bundle) => {
req.bundle = BUNDLES["bundles"].find((bundle) => {
return bundle.id === id;
});
next();
@@ -280,7 +280,7 @@ export function createMockApi() {
let id = req.params.bitstream_id;
try {
req.bitstream_id = id;
req.bitstream = BITSTREAMS.find((bitstream) => {
req.bitstream = BITSTREAMS["bitstreams"].find((bitstream) => {
return bitstream.id === id;
});
next();

View File

@@ -1,4 +1,5 @@
export const BITSTREAMS = [
export const BITSTREAMS = {
"bitstreams": [
{
"_links": {
"self": { "href": "/bitstreams/3678" },
@@ -43,4 +44,5 @@ export const BITSTREAMS = [
"format": "JPEG",
"mimetype": "image/jpeg"
}
];
]
};

View File

@@ -1,4 +1,5 @@
export const BUNDLES = [
export const BUNDLES = {
"bundles": [
{
"_links": {
"self": { "href": "/bundles/2355" },
@@ -37,4 +38,5 @@ export const BUNDLES = [
{ "key": "dc.title", "value": "THUMBNAIL", "language": "en" }
]
}
];
]
};

View File

@@ -1,4 +1,5 @@
export const COLLECTIONS = [
export const COLLECTIONS = {
"collections": [
{
"_links": {
"self": { "href": "/collections/5179" },
@@ -71,4 +72,5 @@ export const COLLECTIONS = [
}
]
}
];
]
};

View File

@@ -1,4 +1,5 @@
export const COMMUNITIES = [
export const COMMUNITIES = {
"communities": [
{
"name": "Community 1",
"handle": "10673/1",
@@ -83,4 +84,5 @@ export const COMMUNITIES = [
]
}
}
];
]
};

View File

@@ -1,4 +1,5 @@
export const ITEMS = [
export const ITEMS = {
"items": [
{
"_links": {
"self": {
@@ -90,8 +91,87 @@ export const ITEMS = [
"value": "(not specified)",
"language": "en"
}
],
"_embedded": {
"parents": [
{
"_links": {
"self": { "href": "/collections/5179" },
"items": [
{ "href": "/items/8871" },
{ "href": "/items/9978" }
]
},
"id": "5179",
"uuid": "9e32a2e2-6b91-4236-a361-995ccdc14c60",
"type": "collection",
"name": "A Test Collection",
"handle": "123456789/5179",
},
{
"_links": {
"self": { "href": "/collections/6547" },
"items": [
{ "href": "/items/8871" },
{ "href": "/items/9978" }
]
},
"id": "6547",
"uuid": "598ce822-c357-46f3-ab70-63724d02d6ad",
"type": "collection",
"name": "Another Test Collection",
"handle": "123456789/6547",
}
],
"bundles": [
{
"_links": {
"self": { "href": "/bundles/2355" },
"items": [
{ "href": "/items/8871" }
],
"bitstreams": [
{ "href": "/bitstreams/3678" },
],
"primaryBitstream": { "href": "/bitstreams/3678" }
},
"id": "2355",
"uuid": "35e0606d-5e18-4f9c-aa61-74fc751cc3f9",
"type": "bundle",
"name": "ORIGINAL",
"metadata": [
{ "key": "dc.title", "value": "ORIGINAL", "language": "en" }
],
"_embedded": {
"bitstreams": [
{
"_links": {
"self": { "href": "/bitstreams/3678" },
"bundle": { "href": "/bundles/35e0606d-5e18-4f9c-aa61-74fc751cc3f9" },
"retrieve": { "href": "/bitstreams/43c57c2b-206f-4645-8c8f-5f10c84b09fa/retrieve" }
},
"id": "3678",
"uuid": "43c57c2b-206f-4645-8c8f-5f10c84b09fa",
"type": "bitstream",
"name": "do_open_access_CRL.pdf",
"size": 636626,
"checksum": {
"value": "063dfbbbac873aa3fca479b878eccff3",
"algorithm": "MD5"
},
"metadata": [
{ "key": "dc.title", "value": "do_open_access_CRL.pdf", "language": null },
{ "key": "dc.description", "value": "Conference Paper", "language": "en" }
],
"format": "Adobe PDF",
"mimetype": "application/pdf"
}
]
}
}
]
}
},
{
"_links": {
"self": {
@@ -163,6 +243,39 @@ export const ITEMS = [
"value": "(not specified)",
"language": "en"
}
],
"_embedded": {
"parents": [
{
"_links": {
"self": { "href": "/collections/5179" },
"items": [
{ "href": "/items/8871" },
{ "href": "/items/9978" }
]
},
"id": "5179",
"uuid": "9e32a2e2-6b91-4236-a361-995ccdc14c60",
"type": "collection",
"name": "A Test Collection",
"handle": "123456789/5179",
},
{
"_links": {
"self": { "href": "/collections/6547" },
"items": [
{ "href": "/items/8871" },
{ "href": "/items/9978" }
]
},
"id": "6547",
"uuid": "598ce822-c357-46f3-ab70-63724d02d6ad",
"type": "collection",
"name": "Another Test Collection",
"handle": "123456789/6547",
}
]
}
];
}
]
};

View File

@@ -1,4 +1,5 @@
export const METADATA = [
export const METADATA = {
"metadata": [
{
"type": "metadata",
"id": "d58a3098-b390-4cd6-8f52-b088b3daa637",
@@ -179,4 +180,5 @@ export const METADATA = [
"language": "en"
}
}
];
]
};