/*
* This file is part of anycook. The new internet cookbook
* Copyright (C) 2014 Jan Graßegger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see [http://www.gnu.org/licenses/].
*/
package de.anycook.api;
import de.anycook.api.util.MediaType;
import de.anycook.api.views.PublicView;
import de.anycook.db.mysql.DBRecipe;
import de.anycook.db.mysql.DBSaveRecipe;
import de.anycook.db.mysql.DBTag;
import de.anycook.db.mysql.DBUser;
import de.anycook.newrecipe.NewRecipe;
import de.anycook.news.life.Lifes;
import de.anycook.notifications.Notification;
import de.anycook.recipe.Recipe;
import de.anycook.recipe.Recipes;
import de.anycook.recipe.ingredient.Ingredient;
import de.anycook.recipe.ingredient.Ingredients;
import de.anycook.recipe.step.Step;
import de.anycook.recipe.step.Steps;
import de.anycook.recipe.tag.Tag;
import de.anycook.session.Session;
import de.anycook.sitemap.SiteMapGenerator;
import de.anycook.user.User;
import de.anycook.utils.enumerations.ImageType;
import de.anycook.utils.enumerations.NotificationType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.queryparser.classic.ParseException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
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.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
@Path("/recipe")
@Produces(MediaType.APPLICATION_JSON)
public class RecipeApi {
private final Logger logger = LogManager.getLogger(getClass());
@Context
private Session session;
@GET
public Response getAll(@HeaderParam(HttpHeaders.IF_MODIFIED_SINCE) final Date date,
@QueryParam("userId") final Integer userId,
@QueryParam("startsWith") final String prefix,
@QueryParam("detailed") final boolean detailed) {
try {
final int loginId =
session.checkLoginWithoutException() ? session.getUser().getId() : -1;
final Annotation[] annotations = detailed ? new Annotation[]{PublicView.Factory.get()}
: new Annotation[]{};
List<Recipe> recipes;
if (date != null) {
recipes = Recipes.getAll(loginId, date);
if (recipes.size() == 0) {
throw new WebApplicationException(Response.Status.NOT_MODIFIED);
}
} else {
recipes = userId != null ?
Recipes.getRecipesFromUser(userId, loginId) : Recipes.getAll(loginId);
}
if (prefix != null) {
recipes = recipes.parallelStream()
.filter(r -> r.getName().toLowerCase().startsWith(prefix.toLowerCase()))
.collect(Collectors.toList());
}
return Response.ok().entity(new GenericEntity<List<Recipe>>(recipes) {}, annotations)
.lastModified(Recipes.getLastModified())
.build();
} catch (SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* Number of recipes
*/
@GET
@Path("number")
public Integer getNum() {
try {
return Recipes.getTotal();
} catch (SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* returns the recipe of the day
*/
@GET
@Path("oftheday")
public Recipe getRecipeOfTheDay() {
try {
return Recipes.getRecipeOfTheDay();
} catch (SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} catch (DBRecipe.RecipeNotFoundException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
}
@GET
@Path("tag")
public List<Tag> getRecipeTags(@QueryParam("active") final Boolean active) {
try {
if (active != null) {
return Tag.getRecipeTags(active);
}
return Tag.getRecipeTags();
} catch (SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@GET
@Path("{recipeName}")
public Response getRecipe(@Context final Request request,
@PathParam("recipeName") final String recipeName) {
try {
final int loginId = session.checkLoginWithoutException() ? session.getUser().getId()
: -1;
Recipes.increaseViewCount(recipeName);
final Recipe recipe = Recipe.init(recipeName, loginId);
final Date lastChangeDate = new Date(recipe.getLastChange());
final Response.ResponseBuilder responseBuilder =
request.evaluatePreconditions(lastChangeDate);
if (responseBuilder != null) {
throw new WebApplicationException(responseBuilder.build());
}
return Response.ok()
.entity(recipe, new Annotation[]{PublicView.Factory.get()})
.lastModified(new Date(recipe.getLastChange()))
.build();
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} catch (final DBRecipe.RecipeNotFoundException e) {
logger.warn(e, e);
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
}
@GET
@Path("{recipeName}/authors")
public List<User> getAuthors(@PathParam("recipeName") final String recipeName) {
try {
return Recipes.getAuthors(recipeName);
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@GET
@Path("{recipeName}/ingredients")
public Response getRecipeIngredients(@Context final Request request,
@PathParam("recipeName") final String recipeName) {
try {
final long lastChange = Recipe.init(recipeName).getLastChange();
final Date lastChangeDate = new Date(lastChange);
final Response.ResponseBuilder responseBuilder =
request.evaluatePreconditions(lastChangeDate);
if (responseBuilder != null) {
throw new WebApplicationException(responseBuilder.build());
}
return Response
.ok(new GenericEntity<List<Ingredient>>(
Ingredients.loadByRecipe(recipeName)) {})
.lastModified(lastChangeDate).build();
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} catch (final DBRecipe.RecipeNotFoundException e) {
logger.warn(e, e);
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
}
@GET
@Path("{recipeName}/tags")
public List<Tag> getRecipeTags(@PathParam("recipeName") final String recipeName) {
try {
return Tag.loadTagsFromRecipe(recipeName);
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@POST
@Path("{recipeName}/tags")
@Consumes(MediaType.APPLICATION_JSON)
public void suggestTags(@PathParam("recipeName") final String recipeName,
final List<Tag> tags) {
final int userId = session.getUser().getId();
try (final DBSaveRecipe dbSaveRecipe = new DBSaveRecipe()) {
for (final Tag tag : tags) {
dbSaveRecipe.suggestTag(recipeName, tag.getName(), userId);
}
//send notification to admin
final Map<String, String> data = new HashMap<>(6);
data.put("userName", session.getUser().getName());
data.put("recipeName", recipeName);
data.put("numTags", Integer.toString(tags.size()));
Notification.sendAdminNotification(NotificationType.ADMIN_SUGGESTED_TAGS, data);
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@PUT
@Path("{recipeName}/tags/{tagName}")
@Consumes(MediaType.APPLICATION_JSON)
public void updateRecipeTag(@PathParam("recipeName") final String recipeName,
@PathParam("tagName") final String tagName,
final Tag tag) {
session.checkAdminLogin();
try {
final Tag oldTag = Tag.getRecipeTag(recipeName, tagName);
if (oldTag.getActive() != tag.getActive() && tag.getActive()) {
Tag.activateRecipeTag(recipeName, tagName);
final Map<String, String> data = new HashMap<>(3);
data.put("tagName", tag.getName());
data.put("recipeName", recipeName);
Notification.sendNotification(oldTag.getSuggester().getId(),
NotificationType.TAG_ACCEPTED, data);
SiteMapGenerator.generateTagSitemap();
}
} catch (final DBUser.UserNotFoundException | SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} catch (final DBTag.TagNotFoundException e) {
logger.warn(e);
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
}
@DELETE
@Path("{recipeName}/tags/{tagName}")
@Consumes(MediaType.APPLICATION_JSON)
public void deleteRecipeTag(@PathParam("recipeName") final String recipeName,
@PathParam("tagName") final String tagName) {
session.checkAdminLogin();
try {
final Tag oldTag = Tag.getRecipeTag(recipeName, tagName);
Tag.deleteRecipeTag(recipeName, tagName);
final Map<String, String> data = new HashMap<>(4);
data.put("tagName", tagName);
data.put("recipeName", recipeName);
Notification.sendNotification(oldTag.getSuggester().getId(),
NotificationType.TAG_DENIED,
data);
} catch (final DBUser.UserNotFoundException | SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} catch (final DBTag.TagNotFoundException e) {
logger.warn(e);
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
}
@GET
@Path("{recipeName}/steps")
public List<Step> getRecipeSteps(@PathParam("recipeName") final String recipeName) {
try {
return Steps.loadRecipeSteps(recipeName);
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
//version
@GET
@PublicView
@Path("{recipeName}/version")
public List<Recipe> getAllVersion(@PathParam("recipeName") final String recipeName) {
try {
final int loginId = session.checkLoginWithoutException() ? session.getUser().getId()
: -1;
return Recipes.getAllVersions(recipeName, loginId);
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@GET
@Path("{recipeName}/version/{versionId}")
@PublicView
public Recipe getVersion(@PathParam("recipeName") final String recipeName,
@PathParam("versionId") final int versionId) {
try {
final int loginId = session.checkLoginWithoutException() ? session.getUser().getId()
: -1;
return Recipe.init(recipeName, versionId, loginId);
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} catch (final DBRecipe.RecipeNotFoundException e) {
logger.warn(e, e);
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
}
@PUT
@Path("{recipeName}/version/{versionId}")
@Consumes(MediaType.APPLICATION_JSON)
public void updateVersion(@PathParam("recipeName") final String recipeName,
@PathParam("versionId") final int versionId,
final Recipe newVersion) {
try {
session.checkAdminLogin();
final int userId = session.getUser().getId();
final Recipe oldVersion = Recipe.init(recipeName, versionId, userId);
if (oldVersion.isActive() != newVersion.isActive()) {
Recipes.setActiveId(recipeName, newVersion.isActive() ? newVersion.getId() : -1);
//version was activated
if (newVersion.isActive()) {
final Map<String, String> data = new HashMap<>();
data.put("recipeName", recipeName);
if (oldVersion.getActiveAuthor() >= 0) {
Notification.sendNotification(oldVersion.getActiveAuthor(),
NotificationType.RECIPE_ACTIVATION, data);
Lifes.addLife(Lifes.CaseType.ACTIVATED, newVersion.getActiveAuthor(),
recipeName);
}
logger.debug("activated version #{} of {}", versionId, recipeName);
Recipes.setLastChange(recipeName);
SiteMapGenerator.generateRecipeSiteMap();
}
}
} catch (final SQLException | DBUser.UserNotFoundException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} catch (final DBRecipe.RecipeNotFoundException e) {
logger.warn(e, e);
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
}
@GET
@Path("{recipeName}/version/{versionId}/ingredients")
public List<Ingredient> getVersionIngredients(@PathParam("recipeName") final String recipeName,
@PathParam("versionId") final int versionId) {
try {
return Ingredients.loadByRecipe(recipeName, versionId);
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@GET
@Path("{recipeName}/version/{versionId}/steps")
public List<Step> getVersionSteps(@PathParam("recipeName") final String recipeName,
@PathParam("versionId") final int versionId) {
try {
return Steps.loadRecipeSteps(recipeName, versionId);
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@GET
@Path("{recipeName}/image")
@Produces("image/png")
public Response getImage(@PathParam("recipeName") final String recipeName,
@DefaultValue("small") @QueryParam("type") final String typeString) {
final ImageType type = ImageType.valueOf(typeString.toUpperCase());
try {
return Response.temporaryRedirect(Recipes.getRecipeImage(recipeName, type)).build();
} catch (final URISyntaxException e) {
logger.error(e, e);
throw new WebApplicationException(400);
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@GET
@Path("{recipeName}/schmeckt")
public Boolean checkSchmeckt(@PathParam("recipeName") final String recipeName) {
session.checkLogin();
try {
return session.checkSchmeckt(recipeName);
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@PUT
@Path("{recipeName}/schmeckt")
public void schmeckt(@PathParam("recipeName") final String recipeName) {
try {
session.checkLogin();
final boolean schmeckt = session.checkSchmeckt(recipeName);
if (!schmeckt) {
session.makeSchmeckt(recipeName);
}
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@DELETE
@Path("{recipeName}/schmeckt")
public void schmecktNicht(@PathParam("recipeName") final String recipeName) {
try {
session.checkLogin();
final boolean schmeckt = session.checkSchmeckt(recipeName);
if (schmeckt) {
session.removeSchmeckt(recipeName);
}
} catch (final SQLException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
public void saveRecipe(final NewRecipe newRecipe) {
logger.info("want to save recipe");
if (newRecipe == null) {
throw new WebApplicationException(400);
}
try {
final int newId;
if (session.checkLoginWithoutException()) {
User user = session.getUser();
newId = newRecipe.save(user.getId());
} else {
logger.debug("user is not authentificated");
newId = newRecipe.save();
}
final Map<String, String> data = new HashMap<>();
data.put("recipeName", newRecipe.name);
data.put("versionId", Integer.toString(newId));
Notification.sendAdminNotification(NotificationType.ADMIN_NEW_VERSION, data);
} catch (final SQLException | IOException | ParseException e) {
logger.error(e, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} catch (final NewRecipe.InvalidRecipeException e) {
logger.warn(e, e);
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
}
}