diff --git a/photoalbum-bootstrap/pom.xml b/photoalbum-bootstrap/pom.xml
index a8a04bb..99668ba 100644
--- a/photoalbum-bootstrap/pom.xml
+++ b/photoalbum-bootstrap/pom.xml
@@ -19,6 +19,57 @@
UTF-8
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.3
+
+ true
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+ com.mgu.photoalbum.bootstrap.Bootstrap
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.4
+
+
+
+ true
+
+
+
+
+
+
+
com.mgu.photoalbum
diff --git a/photoalbum-management/src/main/java/com/mgu/photoalbum/domain/Photo.java b/photoalbum-management/src/main/java/com/mgu/photoalbum/domain/Photo.java
index b7de23d..330b97e 100644
--- a/photoalbum-management/src/main/java/com/mgu/photoalbum/domain/Photo.java
+++ b/photoalbum-management/src/main/java/com/mgu/photoalbum/domain/Photo.java
@@ -176,4 +176,8 @@ public void untag() {
public static PhotoBuilder create() {
return new PhotoBuilder();
}
+
+ public boolean anyTagMatches(final List tags) {
+ return tags.isEmpty() || getTags().stream().anyMatch(tag -> tags.contains(tag));
+ }
}
\ No newline at end of file
diff --git a/photoalbum-management/src/main/java/com/mgu/photoalbum/service/AlbumCommandService.java b/photoalbum-management/src/main/java/com/mgu/photoalbum/service/AlbumCommandService.java
index 14d4e42..28de4a0 100644
--- a/photoalbum-management/src/main/java/com/mgu/photoalbum/service/AlbumCommandService.java
+++ b/photoalbum-management/src/main/java/com/mgu/photoalbum/service/AlbumCommandService.java
@@ -5,4 +5,6 @@ public interface AlbumCommandService {
String createAlbum(String ownerId, String title);
void deleteAlbum(String id);
+
+ void removePhotoFromAlbum(String albumId, String photoId);
}
diff --git a/photoalbum-management/src/main/java/com/mgu/photoalbum/service/AlbumService.java b/photoalbum-management/src/main/java/com/mgu/photoalbum/service/AlbumService.java
index 97ae640..abdc159 100644
--- a/photoalbum-management/src/main/java/com/mgu/photoalbum/service/AlbumService.java
+++ b/photoalbum-management/src/main/java/com/mgu/photoalbum/service/AlbumService.java
@@ -4,7 +4,10 @@
import com.mgu.photoalbum.domain.Album;
import com.mgu.photoalbum.identity.IdGenerator;
import com.mgu.photoalbum.storage.AlbumRepository;
+import org.ektorp.UpdateConflictException;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
@@ -54,6 +57,20 @@ public void deleteAlbum(final String id) {
repository.remove(album);
}
+ @Override
+ public void removePhotoFromAlbum(String albumId, String photoId) {
+ if (!repository.contains(albumId)) {
+ return;
+ }
+ final Album album = repository.get(albumId);
+ album.dissociatePhoto(photoId);
+ try {
+ repository.update(album);
+ } catch (UpdateConflictException e) {
+ throw new UnableToUpdateAlbumException(albumId, e);
+ }
+ }
+
@Override
public Album albumById(final String id) {
if (!repository.contains(id)) {
@@ -64,7 +81,9 @@ public Album albumById(final String id) {
}
@Override
- public List albumsByOwner(String ownerId) {
- return null;
+ public List albumsByOwner(final String ownerId) {
+ final List albumsByOwner = new ArrayList<>();
+ albumsByOwner.addAll(repository.getAllByOwner(ownerId));
+ return Collections.unmodifiableList(albumsByOwner);
}
}
\ No newline at end of file
diff --git a/photoalbum-management/src/main/java/com/mgu/photoalbum/service/PhotoQueryService.java b/photoalbum-management/src/main/java/com/mgu/photoalbum/service/PhotoQueryService.java
index 39fe113..79b674f 100644
--- a/photoalbum-management/src/main/java/com/mgu/photoalbum/service/PhotoQueryService.java
+++ b/photoalbum-management/src/main/java/com/mgu/photoalbum/service/PhotoQueryService.java
@@ -6,13 +6,11 @@
public interface PhotoQueryService {
- byte[] originalById(String id); // byte[] originalById(PhotoId identity)
+ byte[] originalById(String id);
byte[] thumbnailById(String id);
Photo photoById(String id);
- List photosByAlbumAndTags(String albumId, List tags);
-
List search(String albumId, List tags, int offset, int pageSize);
}
\ No newline at end of file
diff --git a/photoalbum-management/src/main/java/com/mgu/photoalbum/service/PhotoService.java b/photoalbum-management/src/main/java/com/mgu/photoalbum/service/PhotoService.java
index 40c74c1..16a71bd 100644
--- a/photoalbum-management/src/main/java/com/mgu/photoalbum/service/PhotoService.java
+++ b/photoalbum-management/src/main/java/com/mgu/photoalbum/service/PhotoService.java
@@ -16,6 +16,7 @@
import java.nio.file.Path;
import java.util.List;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
public class PhotoService implements PhotoCommandService, PhotoQueryService {
@@ -23,6 +24,8 @@ public class PhotoService implements PhotoCommandService, PhotoQueryService {
private final PhotoRepository repository;
+ private final AlbumCommandService albumCommandService;
+
private final PathScheme pathScheme;
private final PathAdapter pathAdapter;
@@ -35,6 +38,7 @@ public class PhotoService implements PhotoCommandService, PhotoQueryService {
@Inject
public PhotoService(
+ final AlbumCommandService albumCommandService,
final PhotoRepository repository,
final PathScheme pathScheme,
final PathAdapter pathAdapter,
@@ -42,11 +46,12 @@ public PhotoService(
final IdGenerator idGenerator,
final ImageScaler scaler) {
this.repository = repository;
+ this.albumCommandService = albumCommandService;
this.pathAdapter = pathAdapter;
this.inputStreamAdapter = inputStreamAdapter;
this.pathScheme = pathScheme;
this.scaler = scaler;
- this.photoIdGenerator = () -> idGenerator.generateId("AL", 14);
+ this.photoIdGenerator = () -> idGenerator.generateId("PH", 14);
}
@Override
@@ -100,6 +105,7 @@ public void deletePhoto(final String photoId) {
final Path photoFolder = pathScheme.constructPathToPhotoFolder(photo.getOwnerId(), photo.getAlbumId(), photoId);
pathAdapter.deleteDirectory(photoFolder);
repository.remove(photo);
+ albumCommandService.removePhotoFromAlbum(photo.getAlbumId(), photoId);
}
@Override
@@ -114,7 +120,7 @@ public void updateMetadata(final String photoId, final String description, final
try {
repository.update(photo);
} catch (UpdateConflictException e) {
- throw new UnableToUpdateMetadata(photoId, description, tags);
+ throw new UnableToUpdateMetadataException(photoId, description, tags);
}
}
@@ -160,13 +166,14 @@ public Photo photoById(final String photoId) {
return repository.get(photoId);
}
- @Override
- public List photosByAlbumAndTags(final String albumId, final List tags) {
- return null;
- }
-
@Override
public List search(final String albumId, final List tags, final int offset, final int pageSize) {
- return null;
+ return repository
+ .getAllByAlbum(albumId)
+ .stream()
+ .filter(photo -> photo.anyTagMatches(tags))
+ .skip(offset)
+ .limit(pageSize)
+ .collect(Collectors.toList());
}
}
\ No newline at end of file
diff --git a/photoalbum-management/src/main/java/com/mgu/photoalbum/service/UnableToUpdateAlbumException.java b/photoalbum-management/src/main/java/com/mgu/photoalbum/service/UnableToUpdateAlbumException.java
new file mode 100644
index 0000000..468e954
--- /dev/null
+++ b/photoalbum-management/src/main/java/com/mgu/photoalbum/service/UnableToUpdateAlbumException.java
@@ -0,0 +1,17 @@
+package com.mgu.photoalbum.service;
+
+import com.mgu.photoalbum.exception.PhotoalbumException;
+
+public class UnableToUpdateAlbumException extends PhotoalbumException {
+
+ private static final String ERROR_MESSAGE = "Unable to update the album with ID %s.";
+
+ public UnableToUpdateAlbumException(final String albumId, final Throwable t) {
+ super(String.format(ERROR_MESSAGE, albumId));
+ }
+
+ @Override
+ public String getErrorCode() {
+ return "P005";
+ }
+}
\ No newline at end of file
diff --git a/photoalbum-management/src/main/java/com/mgu/photoalbum/service/UnableToUpdateMetadata.java b/photoalbum-management/src/main/java/com/mgu/photoalbum/service/UnableToUpdateMetadataException.java
similarity index 71%
rename from photoalbum-management/src/main/java/com/mgu/photoalbum/service/UnableToUpdateMetadata.java
rename to photoalbum-management/src/main/java/com/mgu/photoalbum/service/UnableToUpdateMetadataException.java
index 4f91ba0..360747d 100644
--- a/photoalbum-management/src/main/java/com/mgu/photoalbum/service/UnableToUpdateMetadata.java
+++ b/photoalbum-management/src/main/java/com/mgu/photoalbum/service/UnableToUpdateMetadataException.java
@@ -5,11 +5,11 @@
import java.util.List;
-public class UnableToUpdateMetadata extends PhotoalbumException {
+public class UnableToUpdateMetadataException extends PhotoalbumException {
private static final String ERROR_MESSAGE = "Unable to set description to %s and replace existing tags by %s for photo with ID %s.";
- public UnableToUpdateMetadata(final String photoId, final String description, final List tags) {
+ public UnableToUpdateMetadataException(final String photoId, final String description, final List tags) {
super(String.format(ERROR_MESSAGE, description, StringUtils.join(tags, ","), photoId));
}
diff --git a/photoalbum-management/src/main/java/com/mgu/photoalbum/storage/AlbumRepository.java b/photoalbum-management/src/main/java/com/mgu/photoalbum/storage/AlbumRepository.java
index 567c0ee..91c68ac 100644
--- a/photoalbum-management/src/main/java/com/mgu/photoalbum/storage/AlbumRepository.java
+++ b/photoalbum-management/src/main/java/com/mgu/photoalbum/storage/AlbumRepository.java
@@ -4,7 +4,16 @@
import com.mgu.photoalbum.adapter.couchdb.DocumentRepository;
import com.mgu.photoalbum.domain.Album;
import org.ektorp.CouchDbConnector;
+import org.ektorp.ViewQuery;
+import org.ektorp.support.View;
+import org.ektorp.support.Views;
+import java.util.List;
+
+@Views({
+ @View(name = "all", map = "function(doc) { if (doc.type == '" + Album.DOCUMENT_TYPE + "') emit(null, doc._id)}"),
+ @View(name = "by_owner", map = "function(doc) { if ('type' in doc && doc.type === 'album') { if ('ownerId' in doc) { emit(doc.ownerId, doc); } } }")
+})
public class AlbumRepository extends DocumentRepository {
@Inject
@@ -12,4 +21,13 @@ public AlbumRepository(final CouchDbConnector connector) {
super(Album.class, connector);
initStandardDesignDocument();
}
+
+ public List getAllByOwner(final String ownerId) {
+ final ViewQuery query = new ViewQuery()
+ .designDocId("_design/Album")
+ .viewName("by_owner")
+ .includeDocs(true)
+ .key(ownerId);
+ return connector.queryView(query, Album.class);
+ }
}
\ No newline at end of file
diff --git a/photoalbum-management/src/main/java/com/mgu/photoalbum/storage/PhotoRepository.java b/photoalbum-management/src/main/java/com/mgu/photoalbum/storage/PhotoRepository.java
index 04a6057..83d84a1 100644
--- a/photoalbum-management/src/main/java/com/mgu/photoalbum/storage/PhotoRepository.java
+++ b/photoalbum-management/src/main/java/com/mgu/photoalbum/storage/PhotoRepository.java
@@ -4,7 +4,16 @@
import com.mgu.photoalbum.adapter.couchdb.DocumentRepository;
import com.mgu.photoalbum.domain.Photo;
import org.ektorp.CouchDbConnector;
+import org.ektorp.ViewQuery;
+import org.ektorp.support.View;
+import org.ektorp.support.Views;
+import java.util.List;
+
+@Views({
+ @View(name = "all", map = "function(doc) { if (doc.type == '" + Photo.DOCUMENT_TYPE + "') emit(null, doc._id)}"),
+ @View(name = "by_album", map = "function(doc) { if ('type' in doc && doc.type === 'photo') { if ('albumId' in doc) { emit(doc.albumId, doc); } } }")
+})
public class PhotoRepository extends DocumentRepository {
@Inject
@@ -12,4 +21,13 @@ public PhotoRepository(final CouchDbConnector connector) {
super(Photo.class, connector);
initStandardDesignDocument();
}
+
+ public List getAllByAlbum(final String albumId) {
+ final ViewQuery query = new ViewQuery()
+ .designDocId("_design/Photo")
+ .viewName("by_album")
+ .includeDocs(true)
+ .key(albumId);
+ return connector.queryView(query, Photo.class);
+ }
}
\ No newline at end of file
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/PhotoalbumApplication.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/PhotoalbumApplication.java
index 6aa6ad3..94b668f 100644
--- a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/PhotoalbumApplication.java
+++ b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/PhotoalbumApplication.java
@@ -1,6 +1,7 @@
package com.mgu.photoalbum;
import com.hubspot.dropwizard.guice.GuiceBundle;
+import com.mgu.photoalbum.config.CrossOriginConfig;
import com.mgu.photoalbum.config.ServiceConfig;
import com.mgu.photoalbum.config.ServiceModule;
import com.mgu.photoalbum.security.Principal;
@@ -9,6 +10,11 @@
import io.dropwizard.auth.basic.BasicAuthFactory;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
+import org.eclipse.jetty.servlets.CrossOriginFilter;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterRegistration;
+import java.util.EnumSet;
public class PhotoalbumApplication extends Application {
@@ -35,6 +41,16 @@ public void run(final ServiceConfig serviceConfig, final Environment environment
environment
.jersey()
.register(AuthFactory.binder(new BasicAuthFactory<>(authenticator, "Photoalbum Realm", Principal.class)));
+
+ System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
+
+ FilterRegistration.Dynamic filter = environment.servlets().addFilter("CORSFilter", CrossOriginFilter.class);
+
+ filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, environment.getApplicationContext().getContextPath() + "*");
+ filter.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,PUT,POST,DELETE,OPTIONS");
+ filter.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
+ filter.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "Origin, Content-Type, Accept, Authorization");
+ filter.setInitParameter(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM, "true");
}
@Override
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/config/CrossOriginConfig.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/config/CrossOriginConfig.java
new file mode 100644
index 0000000..5f0939b
--- /dev/null
+++ b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/config/CrossOriginConfig.java
@@ -0,0 +1,112 @@
+package com.mgu.photoalbum.config;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jetty.servlets.CrossOriginFilter;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import java.util.Enumeration;
+import java.util.Vector;
+
+@Deprecated
+public class CrossOriginConfig implements FilterConfig {
+
+ /**
+ * CORS-related parameter that represents all allowed methods.
+ * This is currently hard-wired to GET and POST.
+ * It is not configurable via configuration file.
+ */
+ private static final String ALLOWED_METHODS = "GET,POST,PUT,DELETE,OPTIONS";
+
+ /**
+ * CORS-related parameter that represents allowed origins. Can
+ * contain the special value '*' which is a placeholder for every
+ * origin domain.
+ */
+ private static final String ALLOWED_ORIGINS = "*";
+
+ /**
+ * CORS-related parameter that represents a list of comma-separated
+ * headers. Those headers are allowed.
+ */
+ private static final String ALLOWED_HEADERS = "Origin, Content-Type, Accept, Authorization";
+
+ /**
+ * CORS-related parameter which configures the maximum age of
+ * a pre-flight request. Default value is 1 day
.
+ */
+ private static final int PREFLIGHT_MAX_AGE = 1800;
+
+ /**
+ * CORS-related parameter that determines if credentials are allowed.
+ * Default is false
.
+ */
+ private static final boolean ALLOW_CREDENTIALS = true;
+
+ /**
+ * CORS-related parameter which holds all exposable headers.
+ */
+ private static final String EXPOSED_HEADERS = StringUtils.EMPTY;
+
+ /**
+ * CORS-related parameter which configures chaining of pre-flight
+ * requests.
+ */
+ private static final boolean CHAIN_PREFLIGHT = true;
+
+ @Override
+ public String getFilterName() {
+ return "CORS Filter";
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return null;
+ }
+
+ @Override
+ public String getInitParameter(final String name) {
+ switch (name) {
+ case CrossOriginFilter.ALLOWED_ORIGINS_PARAM:
+ return ALLOWED_ORIGINS;
+ case CrossOriginFilter.ALLOWED_METHODS_PARAM:
+ return ALLOWED_METHODS;
+ case CrossOriginFilter.ALLOWED_HEADERS_PARAM:
+ return ALLOWED_HEADERS;
+ case CrossOriginFilter.PREFLIGHT_MAX_AGE_PARAM:
+ return Integer.toString(PREFLIGHT_MAX_AGE);
+ case CrossOriginFilter.ALLOW_CREDENTIALS_PARAM:
+ return Boolean.toString(ALLOW_CREDENTIALS);
+ case CrossOriginFilter.EXPOSED_HEADERS_PARAM:
+ return EXPOSED_HEADERS;
+ case CrossOriginFilter.CHAIN_PREFLIGHT_PARAM:
+ return Boolean.toString(CHAIN_PREFLIGHT);
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public Enumeration getInitParameterNames() {
+ final Vector parameterNames = new Vector<>();
+ parameterNames.add(CrossOriginFilter.ALLOWED_ORIGINS_PARAM);
+ parameterNames.add(CrossOriginFilter.ALLOWED_METHODS_PARAM);
+ parameterNames.add(CrossOriginFilter.ALLOWED_HEADERS_PARAM);
+ parameterNames.add(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM);
+ parameterNames.add(CrossOriginFilter.CHAIN_PREFLIGHT_PARAM);
+ parameterNames.add(CrossOriginFilter.EXPOSED_HEADERS_PARAM);
+ parameterNames.add(CrossOriginFilter.PREFLIGHT_MAX_AGE_PARAM);
+ return parameterNames.elements();
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("CorsFilterConfig[").append("allowCredentials=").append(ALLOW_CREDENTIALS)
+ .append(", allowedHeaders=[").append(ALLOWED_HEADERS).append("], allowedMethods=[")
+ .append(ALLOWED_METHODS).append("], allowedOrigins=[").append(ALLOWED_ORIGINS)
+ .append("], chainPreflight=").append(CHAIN_PREFLIGHT).append(", exposedHeaders=[")
+ .append(EXPOSED_HEADERS).append("], preflightMaxAge=").append(PREFLIGHT_MAX_AGE);
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/converter/AlbumShortReprConverter.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/converter/AlbumShortReprConverter.java
index f844da1..ad903a1 100644
--- a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/converter/AlbumShortReprConverter.java
+++ b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/converter/AlbumShortReprConverter.java
@@ -22,6 +22,7 @@ public AlbumShortRepr convert(final Album album) {
return AlbumShortRepr
.create()
+ .id(album.getId())
.title(album.getTitle())
.numberOfPhotos(album.getContainingPhotos().size())
.link(
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/converter/PhotoMetadataReprConverter.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/converter/PhotoMetadataReprConverter.java
index 150a1fa..331da9a 100644
--- a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/converter/PhotoMetadataReprConverter.java
+++ b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/converter/PhotoMetadataReprConverter.java
@@ -40,6 +40,7 @@ public PhotoMetadataRepr convert(final Photo photo) {
.href(linkScheme.toPhoto(photo.getAlbumId(), photo.getId()))
.method(HttpMethod.GET)
.relation("viewPhoto")
+ .mediaType("image/jpeg")
.build()
)
.link(
@@ -53,7 +54,7 @@ public PhotoMetadataRepr convert(final Photo photo) {
.link(
LinkRepr
.create()
- .href(linkScheme.toMetadata(photo.getAlbumId(), photo.getId()))
+ .href(linkScheme.toPhoto(photo.getAlbumId(), photo.getId()))
.method(HttpMethod.PUT)
.relation("updateMetadata")
.build()
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/converter/PhotoShortReprConverter.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/converter/PhotoShortReprConverter.java
index 99f6cf2..abbc387 100644
--- a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/converter/PhotoShortReprConverter.java
+++ b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/converter/PhotoShortReprConverter.java
@@ -30,6 +30,7 @@ public PhotoShortRepr convert(final Photo photo) {
.href(linkScheme.toPhoto(photo.getAlbumId(), photo.getId()))
.method(HttpMethod.GET)
.relation("viewPhoto")
+ .mediaType("image/jpeg")
.build()
)
.link(
@@ -38,12 +39,13 @@ public PhotoShortRepr convert(final Photo photo) {
.href(linkScheme.toThumbnail(photo.getAlbumId(), photo.getId()))
.method(HttpMethod.GET)
.relation("viewThumbnail")
+ .mediaType("image/jpeg")
.build()
)
.link(
LinkRepr
.create()
- .href(linkScheme.toMetadata(photo.getAlbumId(), photo.getId()))
+ .href(linkScheme.toPhoto(photo.getAlbumId(), photo.getId()))
.method(HttpMethod.GET)
.relation("viewMetadata")
.build()
@@ -54,6 +56,7 @@ public PhotoShortRepr convert(final Photo photo) {
.href(linkScheme.toDownload(photo.getAlbumId(), photo.getId()))
.method(HttpMethod.GET)
.relation("downloadPhoto")
+ .mediaType("image/jpeg")
.build()
)
.build();
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/representation/AlbumShortRepr.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/representation/AlbumShortRepr.java
index db15420..3f8c113 100644
--- a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/representation/AlbumShortRepr.java
+++ b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/representation/AlbumShortRepr.java
@@ -4,16 +4,23 @@
import org.apache.commons.lang3.StringUtils;
import java.util.Collections;
-import java.util.LinkedList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class AlbumShortRepr {
public static class AlbumShortReprBuilder {
+ private Map namedLinks = new HashMap<>();
+ private String albumId = StringUtils.EMPTY;
private String title = StringUtils.EMPTY;
private int numberOfPhotos = 0;
- private List links = new LinkedList<>();
+
+ public AlbumShortReprBuilder id(final String albumId) {
+ this.albumId = albumId;
+ return this;
+ }
public AlbumShortReprBuilder title(final String title) {
this.title = title;
@@ -26,7 +33,7 @@ public AlbumShortReprBuilder numberOfPhotos(final int numberOfPhotos) {
}
public AlbumShortReprBuilder link(final LinkRepr link) {
- this.links.add(link);
+ this.namedLinks.put(link.getRelation(), link);
return this;
}
@@ -35,6 +42,9 @@ public AlbumShortRepr build() {
}
}
+ @JsonProperty("albumId")
+ private final String albumId;
+
@JsonProperty("title")
private final String title;
@@ -42,12 +52,17 @@ public AlbumShortRepr build() {
private final int numberOfPhotos;
@JsonProperty("links")
- private final List links;
+ private final Map namedLinks;
private AlbumShortRepr(final AlbumShortReprBuilder builder) {
+ this.albumId = builder.albumId;
this.title = builder.title;
this.numberOfPhotos = builder.numberOfPhotos;
- this.links = builder.links;
+ this.namedLinks = builder.namedLinks;
+ }
+
+ public String getAlbumId() {
+ return this.albumId;
}
public String getTitle() {
@@ -58,8 +73,8 @@ public int getNumberOfPhotos() {
return numberOfPhotos;
}
- public List getLinks() {
- return Collections.unmodifiableList(links);
+ public Map getNamedLinks() {
+ return Collections.unmodifiableMap(namedLinks);
}
public static AlbumShortReprBuilder create() {
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/representation/LinkRepr.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/representation/LinkRepr.java
index c16854c..bf52f80 100644
--- a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/representation/LinkRepr.java
+++ b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/representation/LinkRepr.java
@@ -5,6 +5,7 @@
import org.apache.commons.lang3.StringUtils;
import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MediaType;
import java.net.URI;
public class LinkRepr {
@@ -14,6 +15,7 @@ public static class LinkReprBuilder {
private String relation = StringUtils.EMPTY;
private String href = StringUtils.EMPTY;
private String method = HttpMethod.GET;
+ private String mediaType = MediaType.APPLICATION_JSON;
public LinkReprBuilder relation(final String relation) {
this.relation = relation;
@@ -35,6 +37,11 @@ public LinkReprBuilder method(final String method) {
return this;
}
+ public LinkReprBuilder mediaType(final String mediaType) {
+ this.mediaType = mediaType;
+ return this;
+ }
+
public LinkRepr build() {
return new LinkRepr(this);
}
@@ -49,16 +56,21 @@ public LinkRepr build() {
@JsonProperty("method")
private final String method;
- public LinkRepr(final String relation, final String href, final String method) {
+ @JsonProperty("media")
+ private final String mediaType;
+
+ public LinkRepr(final String relation, final String href, final String method, final String mediaType) {
this.relation = relation;
this.href = href;
this.method = method;
+ this.mediaType = mediaType;
}
private LinkRepr(final LinkReprBuilder builder) {
this.relation = builder.relation;
this.href = builder.href;
this.method = builder.method;
+ this.mediaType = builder.mediaType;
}
public String getRelation() {
@@ -73,6 +85,10 @@ public String getMethod() {
return method;
}
+ public String getMediaType() {
+ return this.mediaType;
+ }
+
public static LinkReprBuilder create() {
return new LinkReprBuilder();
}
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/representation/PhotoShortRepr.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/representation/PhotoShortRepr.java
index a02969b..ea22951 100644
--- a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/representation/PhotoShortRepr.java
+++ b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/representation/PhotoShortRepr.java
@@ -2,8 +2,11 @@
import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
public class PhotoShortRepr {
@@ -11,7 +14,7 @@ public static class PhotoShortReprBuilder {
private String description;
private List tags = new LinkedList<>();
- private List links = new LinkedList<>();
+ private Map namedLinks = new HashMap<>();
public PhotoShortReprBuilder description(final String description) {
this.description = description;
@@ -24,7 +27,7 @@ public PhotoShortReprBuilder tags(final List tags) {
}
public PhotoShortReprBuilder link(final LinkRepr link) {
- this.links.add(link);
+ this.namedLinks.put(link.getRelation(), link);
return this;
}
@@ -40,12 +43,12 @@ public PhotoShortRepr build() {
private final List tags;
@JsonProperty("links")
- private final List links;
+ private final Map namedLinks;
private PhotoShortRepr(final PhotoShortReprBuilder builder) {
this.description = builder.description;
this.tags = builder.tags;
- this.links = builder.links;
+ this.namedLinks = builder.namedLinks;
}
public String getDescription() {
@@ -53,11 +56,11 @@ public String getDescription() {
}
public List getTags() {
- return tags;
+ return Collections.unmodifiableList(tags);
}
- public List getLinks() {
- return links;
+ public Map getNamedLinks() {
+ return Collections.unmodifiableMap(namedLinks);
}
public static PhotoShortReprBuilder create() {
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/AlbumResource.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/AlbumResource.java
index 9a1a8fd..6046b27 100644
--- a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/AlbumResource.java
+++ b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/AlbumResource.java
@@ -11,6 +11,7 @@
import com.mgu.photoalbum.security.Authorization;
import com.mgu.photoalbum.security.Principal;
import com.mgu.photoalbum.security.UserIsNotAuthorizedException;
+import com.mgu.photoalbum.service.AlbumCommandService;
import com.mgu.photoalbum.service.AlbumQueryService;
import com.mgu.photoalbum.service.PhotoCommandService;
import com.mgu.photoalbum.service.PhotoQueryService;
@@ -40,6 +41,8 @@ public class AlbumResource {
private final AlbumQueryService albumQueryService;
+ private final AlbumCommandService albumCommandService;
+
private final PhotoCommandService photoCommandService;
private final PhotoQueryService photoQueryService;
@@ -52,11 +55,13 @@ public class AlbumResource {
@Inject
public AlbumResource(
+ final AlbumCommandService albumCommandService,
final AlbumQueryService albumQueryService,
final PhotoQueryService photoQueryService,
final PhotoCommandService photoCommandService,
final Authorization authorization,
final AlbumReprConverter albumConverter) {
+ this.albumCommandService = albumCommandService;
this.albumQueryService = albumQueryService;
this.photoQueryService = photoQueryService;
this.photoCommandService = photoCommandService;
@@ -76,7 +81,7 @@ public Response listAlbum(
@QueryParam("pageSize") Optional optionalPageSize,
@QueryParam("tags") Optional optionalTags) {
- Album album = albumQueryService.albumById(albumId);
+ final Album album = albumQueryService.albumById(albumId);
if (!authorization.isAuthorized(principal, album)) {
throw new UserIsNotAuthorizedException(principal);
@@ -104,11 +109,18 @@ private Optional> parseTags(Optional optionalTags) {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
+ @Timed
public Response uploadPhoto(
@Auth Principal principal,
@PathParam("albumId") String albumId,
UploadPhotoRepr uploadPhotoRepr) {
+ final Album album = albumQueryService.albumById(albumId);
+
+ if (!authorization.isAuthorized(principal, album)) {
+ throw new UserIsNotAuthorizedException(principal);
+ }
+
final String photoId = photoCommandService.uploadPhoto(
principal.getUserId(),
albumId,
@@ -117,4 +129,33 @@ public Response uploadPhoto(
return Response.created(linkScheme.toPhoto(albumId, photoId)).build();
}
+
+ @DELETE
+ @Timed
+ public Response deleteAlbum(
+ @Auth Principal principal,
+ @PathParam("albumId") String albumId) {
+
+ final Album album = albumQueryService.albumById(albumId);
+
+ if (!authorization.isAuthorized(principal, album)) {
+ throw new UserIsNotAuthorizedException(principal);
+ }
+
+ albumCommandService.deleteAlbum(albumId);
+ return Response.noContent().build();
+ }
+
+ @OPTIONS
+ @Timed
+ public Response preflight() {
+ return Response
+ .ok()
+ .header("Access-Control-Allow-Origin", "*")
+ .header("Access-Control-Allow-Methods", "GET,POST,DELETE")
+ .header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization")
+ .encoding("UTF-8")
+ .allow("GET", "POST", "DELETE")
+ .build();
+ }
}
\ No newline at end of file
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/AlbumsResource.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/AlbumsResource.java
index 6e1aaa3..681ffec 100644
--- a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/AlbumsResource.java
+++ b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/AlbumsResource.java
@@ -49,11 +49,6 @@ public AlbumsResource(
public Response createAlbum(
@NotNull CreateAlbumRepr createAlbumRepr,
@Auth Principal principal) {
-
- /*if (createAlbumRepr == null) {
- return Response.status(422).build();
- }*/
-
final String albumId = commandService.createAlbum(principal.getUserId(), createAlbumRepr.getAlbumName());
return Response.created(linkScheme.toAlbum(albumId)).build();
}
@@ -67,4 +62,17 @@ public Response listAlbums(
final GalleryRepr galleryRepr = galleryConverter.convert(queryService.albumsByOwner(principal.getUserId()));
return Response.ok(galleryRepr).build();
}
+
+ @OPTIONS
+ @Timed
+ public Response preflight() {
+ return Response
+ .ok()
+ .header("Access-Control-Allow-Origin", "*")
+ .header("Access-Control-Allow-Methods", "GET,POST")
+ .header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization")
+ .encoding("UTF-8")
+ .allow("GET", "POST")
+ .build();
+ }
}
\ No newline at end of file
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/LinkScheme.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/LinkScheme.java
index 4858efa..ff01277 100644
--- a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/LinkScheme.java
+++ b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/LinkScheme.java
@@ -16,8 +16,6 @@ public class LinkScheme {
private static final String THUMBNAIL_URI_TEMPLATE = "/albums/{albumId}/{photoId}/thumbnail";
- private static final String METADATA_URI_TEMPLATE = "/albums/{albumId}/{photoId}/metadata";
-
public URI toGallery() {
return UriBuilder
.fromUri(GALLERY_URI_TEMPLATE)
@@ -39,10 +37,6 @@ public URI toThumbnail(final String albumId, final String photoId) {
return withAlbumAndPhoto(albumId, photoId, THUMBNAIL_URI_TEMPLATE);
}
- public URI toMetadata(final String albumId, final String photoId) {
- return withAlbumAndPhoto(albumId, photoId, METADATA_URI_TEMPLATE);
- }
-
private URI withAlbumAndPhoto(final String albumId, final String photoId, final String template) {
return UriBuilder
.fromUri(template)
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/MetadataResource.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/MetadataResource.java
deleted file mode 100644
index 26d589f..0000000
--- a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/MetadataResource.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.mgu.photoalbum.resource;
-
-import com.codahale.metrics.annotation.Timed;
-import com.google.inject.Inject;
-import com.mgu.photoalbum.converter.PhotoMetadataReprConverter;
-import com.mgu.photoalbum.domain.Photo;
-import com.mgu.photoalbum.representation.UpdateMetadataRepr;
-import com.mgu.photoalbum.security.Authorization;
-import com.mgu.photoalbum.security.Principal;
-import com.mgu.photoalbum.security.UserIsNotAuthorizedException;
-import com.mgu.photoalbum.service.PhotoCommandService;
-import com.mgu.photoalbum.service.PhotoQueryService;
-import io.dropwizard.auth.Auth;
-
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.PUT;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-public class MetadataResource {
-
- private final PhotoCommandService commandService;
-
- private final PhotoQueryService queryService;
-
- private final Authorization authorization;
-
- private final PhotoMetadataReprConverter converter;
-
- @Inject
- public MetadataResource(
- final PhotoCommandService commandService,
- final PhotoQueryService queryService,
- final Authorization authorization,
- final PhotoMetadataReprConverter converter) {
- this.commandService = commandService;
- this.queryService = queryService;
- this.authorization = authorization;
- this.converter = converter;
- }
-
- @GET
- @Produces(MediaType.APPLICATION_JSON)
- @Timed
- public Response viewMetadata(
- @Auth Principal principal,
- @PathParam("albumId") String albumId,
- @PathParam("photoId") String photoId) {
-
- final Photo photo = queryService.photoById(photoId);
-
- if (!authorization.isAuthorized(principal, photo)) {
- throw new UserIsNotAuthorizedException(principal);
- }
-
- return Response.ok(converter.convert(photo)).build();
- }
-
- @PUT
- @Consumes(MediaType.APPLICATION_JSON)
- @Produces(MediaType.APPLICATION_JSON)
- @Timed
- public Response updateMetadata(
- @Auth Principal principal,
- @PathParam("albumId") String albumId,
- @PathParam("photoId") String photoId,
- UpdateMetadataRepr updateMetadataRepr) {
-
- final Photo photo = queryService.photoById(photoId);
-
- if (!authorization.isAuthorized(principal, photo)) {
- throw new UserIsNotAuthorizedException(principal);
- }
-
- commandService.updateMetadata(photoId, updateMetadataRepr.getDescription(), updateMetadataRepr.getTags());
- return Response.noContent().build();
- }
-}
\ No newline at end of file
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/PhotoResource.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/PhotoResource.java
index 0f8d35d..7953b99 100644
--- a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/PhotoResource.java
+++ b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/PhotoResource.java
@@ -2,7 +2,10 @@
import com.codahale.metrics.annotation.Timed;
import com.google.common.base.Optional;
+import com.google.inject.Inject;
+import com.mgu.photoalbum.converter.PhotoMetadataReprConverter;
import com.mgu.photoalbum.domain.Photo;
+import com.mgu.photoalbum.representation.UpdateMetadataRepr;
import com.mgu.photoalbum.security.Authorization;
import com.mgu.photoalbum.security.Principal;
import com.mgu.photoalbum.security.UserIsNotAuthorizedException;
@@ -10,12 +13,16 @@
import com.mgu.photoalbum.service.PhotoQueryService;
import io.dropwizard.auth.Auth;
+import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path("/albums/{albumId}/{photoId}")
@@ -25,11 +32,19 @@ public class PhotoResource {
private final PhotoQueryService queryService;
+ private final PhotoMetadataReprConverter converter;
+
private final Authorization authorization;
- public PhotoResource(final PhotoCommandService commandService, final PhotoQueryService queryService, final Authorization authorization) {
+ @Inject
+ public PhotoResource(
+ final PhotoCommandService commandService,
+ final PhotoQueryService queryService,
+ final PhotoMetadataReprConverter converter,
+ final Authorization authorization) {
this.commandService = commandService;
this.queryService = queryService;
+ this.converter = converter;
this.authorization = authorization;
}
@@ -65,6 +80,43 @@ public Response viewPhoto(
}
}
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Timed
+ public Response viewMetadata(
+ @Auth Principal principal,
+ @PathParam("albumId") String albumId,
+ @PathParam("photoId") String photoId) {
+
+ final Photo photo = queryService.photoById(photoId);
+
+ if (!authorization.isAuthorized(principal, photo)) {
+ throw new UserIsNotAuthorizedException(principal);
+ }
+
+ return Response.ok(converter.convert(photo)).build();
+ }
+
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Timed
+ public Response updateMetadata(
+ @Auth Principal principal,
+ @PathParam("albumId") String albumId,
+ @PathParam("photoId") String photoId,
+ UpdateMetadataRepr updateMetadataRepr) {
+
+ final Photo photo = queryService.photoById(photoId);
+
+ if (!authorization.isAuthorized(principal, photo)) {
+ throw new UserIsNotAuthorizedException(principal);
+ }
+
+ commandService.updateMetadata(photoId, updateMetadataRepr.getDescription(), updateMetadataRepr.getTags());
+ return Response.noContent().build();
+ }
+
@DELETE
@Timed
public Response deletePhoto(
@@ -81,4 +133,17 @@ public Response deletePhoto(
commandService.deletePhoto(photoId);
return Response.noContent().build();
}
+
+ @OPTIONS
+ @Timed
+ public Response preflight() {
+ return Response
+ .ok()
+ .header("Access-Control-Allow-Origin", "*")
+ .header("Access-Control-Allow-Methods", "GET,PUT,DELETE")
+ .header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization")
+ .encoding("UTF-8")
+ .allow("GET", "PUT", "DELETE")
+ .build();
+ }
}
\ No newline at end of file
diff --git a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/ThumbnailResource.java b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/ThumbnailResource.java
index 76bbb60..a718e6e 100644
--- a/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/ThumbnailResource.java
+++ b/photoalbum-webapp/src/main/java/com/mgu/photoalbum/resource/ThumbnailResource.java
@@ -1,6 +1,7 @@
package com.mgu.photoalbum.resource;
import com.codahale.metrics.annotation.Timed;
+import com.google.inject.Inject;
import com.mgu.photoalbum.domain.Photo;
import com.mgu.photoalbum.security.Authorization;
import com.mgu.photoalbum.security.Principal;
@@ -9,6 +10,7 @@
import io.dropwizard.auth.Auth;
import javax.ws.rs.GET;
+import javax.ws.rs.OPTIONS;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
@@ -21,6 +23,7 @@ public class ThumbnailResource {
private final Authorization authorization;
+ @Inject
public ThumbnailResource(final PhotoQueryService photoQueryService, final Authorization authorization) {
this.queryService = photoQueryService;
this.authorization = authorization;
@@ -43,4 +46,17 @@ public Response viewThumbnail(
final byte[] thumbnailImage = queryService.thumbnailById(photoId);
return Response.ok(thumbnailImage, "image/jpeg").header("Content-Length", String.valueOf(thumbnailImage.length)).build();
}
+
+ @OPTIONS
+ @Timed
+ public Response preflight() {
+ return Response
+ .ok()
+ .header("Access-Control-Allow-Origin", "*")
+ .header("Access-Control-Allow-Methods", "GET")
+ .header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization")
+ .encoding("UTF-8")
+ .allow("GET")
+ .build();
+ }
}
\ No newline at end of file
diff --git a/photoalbum-webapp/src/main/resources/config.yml b/photoalbum-webapp/src/main/resources/config.yml
index e351f19..9f2b9e3 100644
--- a/photoalbum-webapp/src/main/resources/config.yml
+++ b/photoalbum-webapp/src/main/resources/config.yml
@@ -33,6 +33,7 @@ logging:
# sets the log level for our webapp
loggers:
com.cream: ${log.level}
+ org.eclipse.jetty.servlets: DEBUG
# define log appenders for various log channels (console, file)
appenders:
# log to console
diff --git a/photoalbum-webapp/src/test/java/com/mgu/photoalbum/resource/LinkSchemeTest.java b/photoalbum-webapp/src/test/java/com/mgu/photoalbum/resource/LinkSchemeTest.java
index 6d620dd..d08fcfd 100644
--- a/photoalbum-webapp/src/test/java/com/mgu/photoalbum/resource/LinkSchemeTest.java
+++ b/photoalbum-webapp/src/test/java/com/mgu/photoalbum/resource/LinkSchemeTest.java
@@ -29,11 +29,6 @@ public void toThumbnailShouldYieldUrlWithCorrectlySubstitutedPathParameters() {
assertThat(linkScheme.toThumbnail("Aa3b", "Ph0t0").toString(), is("/albums/Aa3b/Ph0t0/thumbnail"));
}
- @Test
- public void toMetadataShouldYieldUrlWithCorrectlySubstitutedPathParameters() {
- assertThat(linkScheme.toMetadata("Aa3b", "Ph0t0").toString(), is("/albums/Aa3b/Ph0t0/metadata"));
- }
-
@Test
public void toDownloadShouldYieldUrlWithCorrectlySubstitutedPathAndQueryParameters() {
assertThat(linkScheme.toDownload("Aa3b", "Ph0t0").toString(), is("/albums/Aa3b/Ph0t0?download=true"));