Compare commits

...

13 Commits

  1. 11
      README.md
  2. 32
      pom.xml
  3. 27
      src/main/java/com/mousetech/gourmetj/AdminMainBean.java
  4. 30
      src/main/java/com/mousetech/gourmetj/CookieBean.java
  5. 65
      src/main/java/com/mousetech/gourmetj/JSFUtils.java
  6. 30
      src/main/java/com/mousetech/gourmetj/RecipeDetailBean.java
  7. 12
      src/main/java/com/mousetech/gourmetj/SpringPrimeFacesApplication.java
  8. 43
      src/main/java/com/mousetech/gourmetj/SpringSecurityConfig.java
  9. 2
      src/main/java/com/mousetech/gourmetj/UserSession.java
  10. 2
      src/main/java/com/mousetech/gourmetj/WelcomePageRedirect.java
  11. 9
      src/main/java/com/mousetech/gourmetj/persistence/dao/RecipeRepository.java
  12. 13
      src/main/java/com/mousetech/gourmetj/persistence/service/RecipeService.java
  13. 6
      src/main/java/com/mousetech/gourmetj/springweb/PictureController.java
  14. 2
      src/main/resources/META-INF/resources/WEB-INF/faces-config.xml
  15. 5
      src/main/resources/META-INF/resources/WEB-INF/layout/layout.xhtml
  16. 22
      src/main/resources/META-INF/resources/detailEdit.xhtml
  17. 12
      src/main/resources/META-INF/resources/error/error400.jsp
  18. 12
      src/main/resources/META-INF/resources/error/error404.html
  19. 12
      src/main/resources/META-INF/resources/error/error404.jsp
  20. 14
      src/main/resources/META-INF/resources/error/viewExpired.html
  21. 0
      src/main/resources/META-INF/resources/images/favicon.ico
  22. 19
      src/main/resources/META-INF/resources/index.html
  23. 26
      src/main/resources/META-INF/resources/index.xhtml
  24. 36
      src/main/resources/META-INF/resources/login.xhtml
  25. 21
      src/main/resources/META-INF/resources/main.xhtml
  26. 2
      src/main/resources/META-INF/resources/recipeDetails.xhtml
  27. 166
      src/main/resources/META-INF/resources/recipePrint.xhtml
  28. 4
      src/main/resources/META-INF/resources/shoppingList.xhtml
  29. 18
      src/main/resources/application.yml
  30. 281
      src/main/resources/schema.sql

@ -107,4 +107,13 @@ Note that by default, JSF caches ViewState in a session so every
JSF View can cause a session to be created, not just Views that
reference View- or SessionScoped backing beans. This is alterable
by setting an option in the faces-config.
force
force
### Developer/deployer note. Because of caching, updated installations
may not render properly. Manually request the "/main.xtml" resource
and that should flush out stale info being used by "/main.jsf".
Followup: Excess payloads were being added to the welcome page
that caused HTTP "400" errors fetching resources. A fix has
been made, although the ultimate solution will probably be more
JSF-friendly.

@ -7,7 +7,7 @@
<groupId>com.mousetech.gourmet</groupId>
<artifactId>gourmetj</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
<packaging>jar</packaging>
<name>GourmetJ</name>
@ -84,10 +84,6 @@
<artifactId>gson</artifactId>
<scope>runtime</scope>
</dependency>
<!-- <dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
@ -96,14 +92,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--
https://mvnrepository.com/artifact/jakarta.persistence/jakarta.persistence-api -->
<!-- <dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
</dependency>-->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
@ -129,21 +117,11 @@
<scope>provided</scope>
</dependency>
<!-- <dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- <dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
</dependency>
-->
<!--
https://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-core -->
@ -158,7 +136,6 @@
</dependency>
<!--
https://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-webp -->
<!-- In Core??? -->
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-webp</artifactId>
@ -166,7 +143,6 @@
<scope>compile</scope>
</dependency>
<!-- Needed only if you deploy ImageIO plugins as part of a web app.
Make sure you add the IIOProviderContextListener to your web.xml.
-->
<dependency>
<groupId>com.twelvemonkeys.servlet</groupId>
@ -184,12 +160,6 @@
<version>8.0.30</version>
</dependency>
<!-- <dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.faces</artifactId>
<version>${com.sun.faces.version}</version>
</dependency>-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>

@ -12,27 +12,22 @@ import com.mousetech.gourmetj.persistence.model.Recipe;
import com.mousetech.gourmetj.persistence.service.RecipeService;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.event.AjaxBehaviorEvent;
import jakarta.faces.model.DataModel;
import jakarta.faces.model.ListDataModel;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
/**
* Main control panel backing bean.
*
* The rare and fabled RequestScope, which is otherwise
* useless 90% of the time. Here we maintain no session
* state. so we can better support the session timeout
* for editing functions.
*
* @author timh
* @since Jun 28, 2012
*/
@Named
@RequestScoped
@ViewScoped
public class AdminMainBean implements Serializable {
/**
@ -49,9 +44,6 @@ public class AdminMainBean implements Serializable {
private static final Logger log =
LoggerFactory.getLogger(AdminMainBean.class);
/** Cookie delimiter */
private static final String CKDLM = ",";
/**
* Persistency service for Recipes.
*/
@ -76,7 +68,8 @@ public class AdminMainBean implements Serializable {
}
/**
* @param cookieBean the cookieBean to set
* @param cookieBean the cookieBean to set.
* @deprecated Not invoked by @Inject
*/
public void setCookieBean(CookieBean cookieBean) {
this.cookieBean = cookieBean;
@ -218,13 +211,6 @@ public class AdminMainBean implements Serializable {
}
searchText = searchText.trim();
// Persist current settings
try {
cookieBean.saveCookies();
} catch (UnsupportedEncodingException e) {
// Something is really wrong if we can't create UTF-8!
log.error("Unable to save cookies!", e);
}
RecipeSearchType st = searchtypeEnum();
switch (st) {
case rst_BY_NAME:
@ -289,4 +275,9 @@ public class AdminMainBean implements Serializable {
// items.
return "recipeDetails?faces-redirect=true";
}
public String doLogout() {
JSFUtils.logout();
return null;
}
}

@ -17,7 +17,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
/**
@ -28,7 +28,7 @@ import jakarta.inject.Named;
* @since Jan 31, 2024
*/
@Named
@RequestScoped
@ViewScoped
public class CookieBean {
private static final String KEY_DISPLAY_ROWS = "displayRows";
@ -44,10 +44,15 @@ public class CookieBean {
private Map<String, String> cookieMap;
final Map<String, Object> properties = new HashMap<>();
/**
* Constructor.
*/
public CookieBean() {
properties.put("maxAge", 31536000);
properties.put("path", "/");
properties.put("SameSite", "Strict");
}
@PostConstruct
@ -63,11 +68,6 @@ public class CookieBean {
*/
public void saveCookies()
throws UnsupportedEncodingException {
final Map<String, Object> properties = new HashMap<>();
properties.put("maxAge", 31536000);
properties.put("path", "/");
properties.put("SameSite", "Strict");
for (Entry<String, String> e : cookieMap.entrySet()) {
JSFUtils.outputCookie(e.getKey(), e.getValue(),
properties);
@ -86,6 +86,13 @@ public class CookieBean {
public void setCookieValue(String name, String value) {
cookieMap.put(name, value);
try {
JSFUtils.outputCookie(name, value, properties);
} catch (UnsupportedEncodingException e) {
// Should never happen. But...
log.error("Unable to encode cookie", e);
e.printStackTrace();
}
}
// ************************
@ -97,7 +104,7 @@ public class CookieBean {
}
public void setSearchText(String value) {
cookieMap.put(KEY_SEARCH_FOR, value);
setCookieValue(KEY_SEARCH_FOR, value);
}
// **
@ -110,7 +117,8 @@ public class CookieBean {
}
public void setSearchType(Integer value) {
cookieMap.put(KEY_SEARCH_TYPE, String.valueOf(value));
String st = String.valueOf(value);
setCookieValue(KEY_SEARCH_TYPE, st);
}
// **
@ -123,11 +131,13 @@ public class CookieBean {
}
public void setDisplayListSize(Integer value) {
cookieMap.put(KEY_DISPLAY_ROWS, String.valueOf(value));
setCookieValue(KEY_DISPLAY_ROWS, String.valueOf(value));
}
/**
* IdleMonitor backing methods (session/View timeout)
* Todo: move to a more general location. Currently
* only used by view editor, not Main!
*/
public void sessionIdleListener() {
log.info("Session Idle Listener fired.");

@ -1,8 +1,9 @@
package com.mousetech.gourmetj;
package com.mousetech.gourmetj;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.net.http.HttpResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
@ -12,6 +13,7 @@ import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.Flash;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.slf4j.Logger;
@ -126,29 +128,52 @@ public class JSFUtils {
/**
* Get cookie values.
*/
public static Map<String, String> getCookies(){
Map<String, Object> m0 = getExternalContext().getRequestCookieMap();
Map<String, String>m1 = new HashMap<String, String>();
m1 = m0.entrySet()
.stream()
.collect(Collectors.toMap(
e -> e.getKey(),
e -> ((Cookie)e.getValue()).getValue()));
public static Map<String, String> getCookies() {
Map<String, Object> m0 =
getExternalContext().getRequestCookieMap();
Map<String, String> m1 = new HashMap<String, String>();
m1 = m0.entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey(),
e -> ((Cookie) e.getValue()).getValue()));
return m1;
}
public static String getCookie(String cookieName) {
Map<String, Object> map =
getExternalContext().getRequestCookieMap();
if (map == null) {
return null; // no cookies at all
}
Cookie cookie = (Cookie) map.get(cookieName);
if (cookie == null) {
return null;
}
return cookie.getValue();
}
/**
* Set a cookie value in Response.
* @param name Cookie name
* @param value Cookie value
* @param properties Cookie property Map (timeout, <i>etc.</i>)
* @throws UnsupportedEncodingException
*
* @param name Cookie name
* @param value Cookie value
* @param properties Cookie property Map (timeout,
* <i>etc.</i>)
* @throws UnsupportedEncodingException
*/
public static void outputCookie(String name,
String value, Map<String, Object> properties) throws UnsupportedEncodingException {
getExternalContext().addResponseCookie(name,
URLEncoder.encode(value, "UTF-8"),
properties);
public static void outputCookie(String name, String value,
Map<String, Object> properties)
throws UnsupportedEncodingException {
// getExternalContext().addResponseCookie(name,
// URLEncoder.encode(value, "UTF-8"),
// properties);
Cookie cookie = new Cookie(name, value);
cookie.setMaxAge(31536000);
cookie.setPath("/");
jakarta.servlet.http.HttpServletResponse resp =
(HttpServletResponse) getExternalContext()
.getResponse();
resp.addCookie(cookie);
}
/**
@ -164,5 +189,9 @@ public class JSFUtils {
log.warn("Session did not exist.");
}
}
public static HttpSession getSession(boolean create) {
return (HttpSession) getExternalContext().getSession(create);
}
}

@ -14,15 +14,13 @@ import jakarta.faces.model.ListDataModel;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.servlet.http.Part;
import jakarta.faces.event.AjaxBehaviorEvent;
import org.apache.commons.lang3.StringUtils;
import org.primefaces.event.FileUploadEvent;
import org.primefaces.model.file.UploadedFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.jpa.JpaSystemException;
import com.mousetech.gourmetj.persistence.model.Category;
import com.mousetech.gourmetj.persistence.model.Ingredient;
@ -208,8 +206,8 @@ public class RecipeDetailBean implements Serializable {
/**
* After construction and injection, we obtain the recipe ID
* passed to us, if any and load the recipe. It's also stored
* in @see UserSession for the detail editor and
* passed to us, if any, and load the recipe. It's also stored
* in @see UserSession for the detail editor.
*
* @see PictureController.
*/
@ -651,15 +649,24 @@ public class RecipeDetailBean implements Serializable {
String ingkey = ing.getIngkey();
if (!StringUtils.isEmpty(ingkey)) {
Shopcat scat = this.recipeService
.findShopcatForIngredientKey(ingkey);
ing.setShopCat(scat);
try {
Shopcat scat = this.recipeService
.findShopcatForIngredientKey(ingkey);
ing.setShopCat(scat);
} catch (JpaSystemException ex) {
String msg = String.format(
"Database Error: Unable to fetch info on \"%s\".",
ingkey);
log.error(msg);
JSFUtils.addErrorMessage(msg);
}
}
// get ing list size, set ing position, append
List<IngredientUI> ingredients = getWrappedIngredients();
int lsize = ingredients.size();
ing.setPosition(lsize + 1);
ingredients.add(new IngredientUI(ing));
ingredients.add(new IngredientUI(ing));
}
// ===
@ -713,8 +720,7 @@ public class RecipeDetailBean implements Serializable {
}
if (recipeService.save(this.getRecipe())) {
userSession.setRecipe(null);
return "recipeDetails";
return "recipeDetails?faces-redirect=true";
} else {
JSFUtils.addErrorMessage("Save recipe failed");
return null;
@ -980,7 +986,7 @@ public class RecipeDetailBean implements Serializable {
public String editDescription() {
this.setDetailTab(0);
return "detailEdit?faces-redirect=true";
return "detailEdit.xhtml?faces-redirect=true";
}
public String editIngredients() {

@ -21,9 +21,11 @@ import org.springframework.http.HttpStatus;
"com.mousetech.gourmetj.persistence.model" })
public class SpringPrimeFacesApplication {
final String homePage = "/main.jsf?viewExpired=true";
final String errorPage = "/error/error.html";
final String error404Page = "/error/error404.html";
final String expiredPage = "/main.xhtml";
final String error404Page = "/error/error404.jsp";
final String error400Page = "/error/error400.jsp";
final String expiredPage = "/error/viewExpired.xhtml";
public static void main(String[] args) {
SpringApplication.run(SpringPrimeFacesApplication.class,
@ -62,8 +64,10 @@ public class SpringPrimeFacesApplication {
registry.addErrorPages(new ErrorPage(
HttpStatus.INTERNAL_SERVER_ERROR,
errorPage));
}
registry.addErrorPages(new ErrorPage(
HttpStatus.BAD_REQUEST,
error400Page));
}
};
}
}

@ -21,8 +21,6 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import jakarta.servlet.DispatcherType;
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig {
@ -99,21 +97,24 @@ public class SpringSecurityConfig {
return ocreds;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.formLogin(Customizer.withDefaults())
.authorizeHttpRequests((authorize)-> authorize
.dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
.anyRequest().authenticated()
);
http.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.formLogin(login -> login.loginPage("/login.jsf")
.permitAll()
.failureUrl("/login.jsf?error=true"))
.logout(logout -> logout
.logoutSuccessUrl("/login.jsf"))
.httpBasic(Customizer.withDefaults())
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated());
return http.build();
}
return http.build();
}
/**
* Replaces old antMatchers for determining secured URLs.
* @return customizer
@ -121,13 +122,19 @@ public class SpringSecurityConfig {
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers(
"/javax.faces.resource/**",
"/jakarta.faces.resource/**",
"/",
"/index.jsf",
"/index.xhtml",
"/index.html",
// "/login",
// "/login.jsf", // Leave them for the authenticator!
// "/login.xhtml",
"/main.jsf",
"/main.xhtml",
"/img/**",
"/error/**",
"/RES_NOT_FOUND",
"/recipeDetails.jsf",
"/recipeDetails.xhtml",
"/shoppingList.jsf",
"/recipePrint.jsf");
}

@ -5,10 +5,8 @@ import java.util.ArrayList;
import java.util.List;
import jakarta.enterprise.context.SessionScoped;
import jakarta.faces.model.SelectItem;
import jakarta.inject.Named;
import org.primefaces.PrimeFaces;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ -11,7 +11,7 @@ public class WelcomePageRedirect implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/")
.setViewName("forward:/index.xhtml");
.setViewName("forward:/index.html");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
}

@ -7,7 +7,6 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import com.mousetech.gourmetj.persistence.model.Category;
import com.mousetech.gourmetj.persistence.model.Recipe;
/**
@ -24,7 +23,7 @@ import com.mousetech.gourmetj.persistence.model.Recipe;
public interface RecipeRepository
extends JpaRepository<Recipe, Long> {
List<Recipe> findByTitleContaining(String searchText);
List<Recipe> findByTitleContainingIgnoreCase(String searchText);
@EntityGraph(value="Recipe.findWorkingSet")
public Recipe findDetailsById(Long recipeId);
@ -32,11 +31,11 @@ public interface RecipeRepository
@Query(name = "Recipe.findCusines", nativeQuery = true)
List<String> FindCuisinesNative();
List<Recipe> findByCategories_CategoryContains(String searchText);
List<Recipe> findByCategories_CategoryContainsIgnoreCase(String searchText);
List<Recipe> findByCuisineContains(String searchText);
List<Recipe> findByCuisineContainsIgnoreCase(String searchText);
List<Recipe> findDistinctByIngredientHash_ItemContains(
List<Recipe> findDistinctByIngredientHash_ItemContainsIgnoreCase(
String searchText);
}

@ -50,7 +50,7 @@ public class RecipeService implements Serializable {
public List<Recipe> findByTitle(String searchText) {
return recipeRepository
.findByTitleContaining(searchText);
.findByTitleContainingIgnoreCase(searchText);
}
public Recipe findByPrimaryKey(Long recipeId) {
@ -120,14 +120,19 @@ public class RecipeService implements Serializable {
}
public List<Recipe> findByCategoryLike(String searchText) {
return recipeRepository.findByCategories_CategoryContains(searchText);
return recipeRepository
.findByCategories_CategoryContainsIgnoreCase(
searchText);
}
public List<Recipe> findByCuisineLike(String searchText) {
return recipeRepository.findByCuisineContains(searchText);
return recipeRepository
.findByCuisineContainsIgnoreCase(searchText);
}
public List<Recipe> findByIngredientLike(String searchText) {
return recipeRepository.findDistinctByIngredientHash_ItemContains(searchText);
return recipeRepository
.findDistinctByIngredientHash_ItemContainsIgnoreCase(
searchText);
}
}

@ -17,9 +17,6 @@ import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.Part;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,6 +34,9 @@ import com.mousetech.gourmetj.UserSession;
import com.mousetech.gourmetj.persistence.model.Recipe;
import com.mousetech.gourmetj.persistence.service.RecipeService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
/**
* @author timh
* @since Nov 26, 2021

@ -25,7 +25,7 @@
<navigation-case>
<description>Go Home</description>
<from-outcome>home</from-outcome>
<to-view-id>/main</to-view-id>
<to-view-id>/main.xhtml?faces-redirect=true</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>

@ -28,9 +28,8 @@
</ui:insert>
<!-- -->
<div id="footer">
(C) 2021, 2024 Tim Holloway, Licensed under the <a
href="http://www.apache.org/licenses/LICENSE-2.0"
>Apache License, Version 2.0</a>.
(C) 2021, 2024 Tim Holloway, Licensed under
the Common Development and Distribution License (CDDL).
<p>Based on Gourmet Recipe Manager by T.
Hinkle</p>
</div>

@ -64,10 +64,10 @@
/>
<p:inputText id="rtitle"
size="45" required="true"
focus="true"
placeholder="A recipe title is required."
value="#{recipeDetailBean.recipe.title}"
>
<p:focus />
<f:ajax execute="rtitle"
render="editorPanel"
/>
@ -362,8 +362,8 @@
id="ctlAddIng"
value="+ Add"
onclick="ingButton(); return false;"
>
</p:commandButton>
update=":growl"
/>
</h:panelGroup>
</p:panel>
</p:tab>
@ -377,7 +377,9 @@
rows="30" cols="120"
escape="false"
value="#{recipeDetailBean.recipe.instructions}"
/>
>
<p:focus />
</h:inputTextarea>
</div>
</p:panel>
</p:tab>
@ -387,18 +389,24 @@
rows="30" cols="120"
escape="false"
value="#{recipeDetailBean.recipe.modifications}"
/>
>
<p:focus/>
</h:inputTextarea>
</p:panel>
</p:tab>
</p:tabView>
<p:commandButton id="doSave" value="Save" icon="ui-icon-pencil" ajax="false" disabled="{not recipeDetailBean.dirty}" action="#{recipeDetailBean.doSave}" />
<p:commandButton id="doSave" value="Save"
icon="ui-icon-pencil" ajax="false"
disabled="{not recipeDetailBean.dirty}"
action="#{recipeDetailBean.doSave}"
/>
<p:commandButton id="doCancel" value="Cancel"
ajax="false" immediate="true"
action="recipeDetails.jsf"
/>
<p:commandButton id="doHome" value="Home"
icon="ui-icon-home" ajax="false"
immediate="true" action="main.jsf"
immediate="true" action="home"
/>
</h:form>
</p:panel>

@ -0,0 +1,12 @@
<%@ page language="java" contentType="text/html; charset=US-ASCII"
pageEncoding="US-ASCII" isErrorPage="true"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Error 400 Page - Bad request</title>
</head>
<body>
<font color="red">Error: exception.getMessage() </font><br>
</body>
</html>

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<html>
<head>
<title>ERROR - Page Not Found</title>
</head>
<body>
<h1>Page Not Found</h1>
<p>This URL is invalid.</p>
<p><a href="/main.jsf">Return to Main Page</a></p>
</body>
</html>

@ -0,0 +1,12 @@
<%@ page language="java" contentType="text/html; charset=US-ASCII"
pageEncoding="US-ASCII" isErrorPage="true"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>ERROR - Page Not Found</title>
</head>
<body>
<h1>Page Not Found</h1>
<p><a href="/main.jsf">Return to Main Page</a></p>
</body>
</html>

@ -1,14 +0,0 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>ERROR - Page Expired</title>
</head>
<body>
<h1>Page Expired</h1>
<p>The page state could not be restored because it was
left idle too long.</p>
<p>
<a href="/main.jsf">Return to Main Page</a>
</p>
</body>
</html>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html>
<head><title>Gourmet Recipe Manager</title><head>
<body>
<h1>Gourmet Recipe Manager</h1>
<p>This is an implementation of Thomas Hinkle's
Gourmet Recipe Manager, originally a desktop
application but now available as a Java Web
application.</p>
<p><a href="main.jsf">
Go to Main Page</a></p>
<hr/>
<p>Copyright © 2021, 2024 Tim Holloway. All Rights Reserved.
<p>This is an open-source application under the
Common Development and Distribution License (CDDL).
</p>
<body>
</html>

@ -1,26 +0,0 @@
<?xml version="1.0"?>
<ui:composition template="/WEB-INF/layout/layout.xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jstl"
>
<!-- Print Recipe -->
<ui:define name="title">Gourmet Recipe Manager</ui:define>
<ui:define name="content">
<h:form id="printForm">
<h:messages />
<p>This is an implementation of Thomas Hinkle's
Gourmet Recipe Manager, originally a desktop
application but now available as a Java Web
application.</p>
<h:outputLink
value="/main.jsf"
>Go to Main Page</h:outputLink>
</h:form>
<div style="height: 20px">
<h:outputText value="" />
</div>
</ui:define>
</ui:composition>

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:pe="http://primefaces.org/ui/extensions"
>
<h:head>
<title>Login</title>
</h:head>
<h:body>
<h:form prependId="false" style="width:100%">
<p:panelGrid columns="3" style="width:100%"
styleClass="ui-fluid center ui-noborder"
>
<h:outputText style="width:33%;" value=" " />
<p:panelGrid columns="1" id="grid1">
<h2>Please login</h2>
<p:outputLabel value="Login failed!"
styleClass="red"
rendered="${!empty param['error']}"
/>
<p:outputLabel for="username">User ID</p:outputLabel>
<p:inputText id="username"
placeholder="User name"
/>
<p:outputLabel for="password">Password</p:outputLabel>
<p:password id="password" placeholder="Password" />
<p:commandButton value="Login" ajax="false" />
</p:panelGrid>
<h:outputText style="width:33%;" value=" " />
</p:panelGrid>
</h:form>
</h:body>
</html>

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html >
<ui:composition template="/WEB-INF/layout/layout.xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
@ -10,25 +11,22 @@
<h:messages />
<h:form id="form1">
<div>
<p:focus />
<p:autoComplete id="searchFor" size="45"
placeholder="Recipe title/cuisine/category, etc.)"
value="#{adminMainBean.searchText}"
completeMethod="#{adminMainBean.searchSuggestionList}"
onfocus="jQuery('#form1\\:searchFor_input').select();"
>
<f:ajax event="change" execute="@this"
render="form2:table1"
listener="#{adminMainBean.ajaxUpdateList}"
/>
</p:autoComplete>
<p:defaultCommand target="find" />
<p:commandButton id="find" value="Find"
icon="ui-icon-search"
action="#{adminMainBean.doFind}"
update=":form2:table1"
/>
<p:outputLabel for="@next" value="Search for " />
<p:outputLabel for="@next" value=" In " />
<p:selectOneMenu id="ctlSearchType"
value="#{cookieBean.searchType}"
onchange="jQuery('#form1\\:searchFor_input').trigger('focus');"
>
<f:selectItems
value="#{appBean.searchTypeList}"
@ -37,6 +35,12 @@
listener="#{adminMainBean.resetSuggestions}"
/>
</p:selectOneMenu>
<p:defaultCommand target="find" />
<p:commandButton id="find" value="Find"
icon="ui-icon-search"
action="#{adminMainBean.doFind}"
update=":form2:table1"
/>
<p:commandButton id="ctlClear" value="Clear"
icon="ui-icon-close"
update="@form:searchFor :form2:table1"
@ -55,6 +59,9 @@
<h:outputLabel for="slistSize"
value=" Recipes in Shopping List"
/>
<p:commandButton id="logout" value="Logout"
action="#{adminMainBean.doLogout}"
/>
</div>
</h:form>
<h:form id="form2">

@ -54,7 +54,7 @@
<p:commandButton ajax="false"
value="Print"
icon="ui-icon-print"
action="recipePrint.jsf"
action="/recipePrint.jsf?faces-redirect=true"
styleClass="ui-button-print"
immediate="true"
/>

@ -9,101 +9,106 @@
<!-- Print Recipe -->
<ui:define name="title">Gourmet Recipe Manager</ui:define>
<ui:define name="content">
<h:form id="printForm">
<h:form id="printForm" style="font-size: 12pt;">
<h:messages />
<p:panelGrid
style="margin-bottom: 5px; border-style: none"
<p:commandButton value="Back" immediate="true"
ajax="false" icon="ui-icon-arrowthick-1-w"
style="margin-left: 2em" styleClass="noprint"
action="recipeDetails.jsf?faces-redirect=true"
/>
<p:panelGrid id="pTopGrid"
columns="2" style="margin-bottom: 5px; border-style: none"
>
<p:column>
<p:column width="50%">
<img id="bigpix"
SRC="/img/picture/#{recipeDetailBean.recipe.id}"
/>
<p:panelGrid columns="2" id="pGrid2">
<h:outputLabel for="@next"
value="Category: "
/>
<h:outputText
value="#{userSession.formatCategories(recipeDetailBean.recipe)}"
/>
<h:outputLabel for="@next"
value="Cuisine: "
/>
<h:outputText
value="#{recipeDetailBean.recipe.cuisine}"
/>
<h:outputLabel for="@next"
value="Prep Time: "
/>
<h:outputText
value="#{userSession.formatTime(recipeDetailBean.recipe.preptime)}"
/>
<h:outputLabel for="@next"
value="Cook Time: "
/>
<h:outputText label="Cook Time: "
value="#{userSession.formatTime(recipeDetailBean.recipe.cooktime)}"
/>
</p:panelGrid>
</p:column>
<p:column
style="vertical-align: middle; text-align: left; border: none"
width="50%" style="vertical-align: middle; text-align: left; border: none"
>
<p:commandButton value="Back"
immediate="true" ajax="false"
icon="ui-icon-arrowthick-1-w"
style="margin-left: 2em"
styleClass="noprint"
action="recipeDetails.jsf?faces-redirect=true"
/>
</p:column>
</p:panelGrid>
<p:panelGrid columns="2">
<h:outputLabel for="@next" value="Category: " />
<h:outputText
value="#{userSession.formatCategories(recipeDetailBean.recipe)}"
/>
<h:outputLabel for="@next" value="Cuisine: " />
<h:outputText
value="#{recipeDetailBean.recipe.cuisine}"
/>
<h:outputLabel for="@next" value="Prep Time: " />
<h:outputText
value="#{userSession.formatTime(recipeDetailBean.recipe.preptime)}"
/>
<h:outputLabel for="@next" value="Cook Time: " />
<h:outputText label="Cook Time: "
value="#{userSession.formatTime(recipeDetailBean.recipe.cooktime)}"
/>
</p:panelGrid>
<!-- -->
<p:panelGrid id="ingredientsc">
<f:facet name="header">
<h:outputText styleClass="subtitle"
value="Ingredients"
/>
</f:facet>
<p:column style="padding: 0px 4px">
<p:dataTable id="ingredients"
showDirectLinksArrows="true"
value="#{recipeDetailBean.ingredients}"
var="ingredient"
>
<p:column
style="text-align: right; width: 2em"
>
<f:facet name="header">
<p:panelGrid id="ingredientsc" columns="1">
<f:facet name="header">
<h:outputText styleClass="subtitle"
value="Ingredients"
/>
</f:facet>
<p:column style="padding: 0px 4px">
<p:dataTable id="ingredients"
showDirectLinksArrows="true"
value="#{recipeDetailBean.ingredients}"
var="ingredient"
style="width: 100%; font-size: 12pt;"
>
<p:column
style="text-align: right; width: 2em"
>
<f:facet name="header">
Amt.
</f:facet>
<h:outputText
value="#{ingredient.displayAmount}"
/>
</p:column>
<p:column style="width: 6em">
<f:facet name="header">Units</f:facet>
<h:outputText
value="#{ingredient.unit}"
/>
</p:column>
<p:column>
<f:facet name="header">
<h:outputText
value="#{ingredient.displayAmount}"
/>
</p:column>
<p:column style="width: 6em">
<f:facet name="header">Units</f:facet>
<h:outputText
value="#{ingredient.unit}"
/>
</p:column>
<p:column>
<f:facet name="header">
Item
</f:facet>
<h:outputText
value="#{ingredient.item}"
/>
</p:column>
<p:column align="center"
style="width: 2em"
>
<f:facet name="header">
<h:outputText
value="#{ingredient.item}"
/>
</p:column>
<p:column align="center"
style="width: 2em"
>
<f:facet name="header">
Opt.
</f:facet>
<p:selectBooleanCheckbox
readonly="true"
value="#{ingredient.optionalCB}"
/>
<p:selectBooleanCheckbox
readonly="true"
value="#{ingredient.optionalCB}"
/>
</p:column>
</p:dataTable>
</p:column>
</p:dataTable>
</p:panelGrid>
</p:column>
</p:panelGrid>
<h:outputText
value="Recipe ID: #{recipeDetailBean.recipe.id}"
/>
<p:panelGrid columns="1" style="width: 100%">
<!-- -->
<!-- -->
<p:panelGrid columns="1" style="width: 100%; font-size: 12pt;">
<f:facet name="header">
<h:outputText styleClass="subtitle"
value="Instructions"
@ -114,7 +119,7 @@
value="#{recipeDetailBean.instructions}"
/>
</p:panelGrid>
<p:panelGrid columns="1" style="width: 100%"
<p:panelGrid columns="1" style="width: 100%; font-size: 12pt"
rendered="#{not empty recipeDetailBean.modifications}"
>
<f:facet name="header">
@ -126,6 +131,9 @@
value="#{recipeDetailBean.modifications}"
/>
</p:panelGrid>
<h:outputText
value="Recipe ID: #{recipeDetailBean.recipe.id}"
/>
</h:form>
</ui:define>
</ui:composition>

@ -42,7 +42,7 @@
<p:tab id="overviewTab" title="Shopping List">
<h:form id="form1">
<p:dataTable id="tblRecipes"
style="width: 40em"
style="width: 60em"
value="#{shoppingListBean.recipeList}"
var="item"
>
@ -87,7 +87,7 @@
>
<p:dataTable id="tblShopIngredients"
value="#{shoppingListBean.ingredientList}"
style="width: 40em;"
style="width: 60em;"
sortBy="#{item.shopCat}" var="item"
>
<f:facet name="header">

@ -21,10 +21,13 @@ spring:
ddl-auto: none
database-platform: org.hibernate.dialect.MySQLDialect
# Tracking-modes prevent URL rewrite jsessionid on Primecases
# resources. Which causes "400" errors on initial main.jsf fetch.
server:
servlet:
session:
timeout: '30m'
tracking-modes: 'cookie'
# Theme here overrides joinfaces theme
# context-parameters:
# primefaces:
@ -34,7 +37,18 @@ server:
gourmet:
password:
file: .gourmetpw
joinfaces:
primefaces:
theme: casablanca
theme: bluesky
faces:
project-stage: Production
facelets-libraries: /tags/tags.taglib.xml
#logging:
# level:
# org.springframework.security: TRACE
# org.apache.catalina: TRACE
# jakarta.faces: TRACE
# com.sun.faces: TRACE
# jakarta.servlet: TRACE

@ -0,0 +1,281 @@
-- MariaDB dump 10.19 Distrib 10.5.23-MariaDB, for Linux (x86_64)
--
-- Host: dbase Database: recipes
-- ------------------------------------------------------
-- Server version 10.3.35-MariaDB
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `categories`
--
DROP TABLE IF EXISTS `categories`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`recipe_id` int(11) DEFAULT NULL,
`category` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK_CATEGORY_RECIPE` (`recipe_id`),
CONSTRAINT `FK_CATEGORY_RECIPE` FOREIGN KEY (`recipe_id`) REFERENCES `recipe` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=233 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `convtable`
--
DROP TABLE IF EXISTS `convtable`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `convtable` (
`id` int(11) NOT NULL,
`ckey` varchar(150) COLLATE utf8mb4_bin DEFAULT NULL,
`value` varchar(150) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `crossunitdict`
--
DROP TABLE IF EXISTS `crossunitdict`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `crossunitdict` (
`id` int(11) NOT NULL,
`cukey` varchar(150) COLLATE utf8mb4_bin DEFAULT NULL,
`value` varchar(150) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `density`
--
DROP TABLE IF EXISTS `density`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `density` (
`id` int(11) NOT NULL,
`dkey` varchar(150) COLLATE utf8mb4_bin DEFAULT NULL,
`value` varchar(150) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `info`
--
DROP TABLE IF EXISTS `info`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `info` (
`version_super` int(11) DEFAULT NULL,
`version_major` int(11) DEFAULT NULL,
`version_minor` int(11) DEFAULT NULL,
`last_access` int(11) DEFAULT NULL,
`rowid` int(11) NOT NULL,
PRIMARY KEY (`rowid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `ingredients`
--
DROP TABLE IF EXISTS `ingredients`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `ingredients` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`recipe_id` int(11) DEFAULT NULL,
`refid` int(11) DEFAULT NULL,
`unit` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`amount` float DEFAULT NULL,
`rangeamount` float DEFAULT NULL,
`item` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`ingkey` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`optional` tinyint(1) DEFAULT NULL,
`shopoptional` int(11) DEFAULT NULL,
`inggroup` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`position` int(11) DEFAULT NULL,
`deleted` tinyint(1) DEFAULT NULL,
`shopCat_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `refid` (`refid`),
KEY `FK_INGREDIENT_RECIPE` (`recipe_id`),
CONSTRAINT `FK_INGREDIENT_RECIPE` FOREIGN KEY (`recipe_id`) REFERENCES `recipe` (`id`),
CONSTRAINT `CONSTRAINT_1` CHECK (`deleted` in (0,1)),
CONSTRAINT `CONSTRAINT_2` CHECK (`optional` in (0,1))
) ENGINE=InnoDB AUTO_INCREMENT=3540 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `keylookup`
--
DROP TABLE IF EXISTS `keylookup`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `keylookup` (
`id` int(11) NOT NULL,
`word` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`item` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`ingkey` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`count` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `pantry`
--
DROP TABLE IF EXISTS `pantry`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `pantry` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ingkey` text COLLATE utf8mb4_bin DEFAULT NULL,
`pantry` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `CONSTRAINT_1` CHECK (`pantry` in (0,1))
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `plugin_info`
--
DROP TABLE IF EXISTS `plugin_info`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `plugin_info` (
`plugin` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`id` int(11) NOT NULL,
`version_super` int(11) DEFAULT NULL,
`version_major` int(11) DEFAULT NULL,
`version_minor` int(11) DEFAULT NULL,
`plugin_version` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `recipe`
--
DROP TABLE IF EXISTS `recipe`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `recipe` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`instructions` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`modifications` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`cuisine` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`rating` int(11) DEFAULT NULL,
`description` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`source` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`preptime` int(11) DEFAULT NULL,
`cooktime` int(11) DEFAULT NULL,
`servings` float DEFAULT NULL,
`yields` float DEFAULT NULL,
`yield_unit` varchar(128) COLLATE utf8mb4_bin DEFAULT NULL,
`image` mediumblob DEFAULT NULL,
`thumb` blob DEFAULT NULL,
`deleted` tinyint(1) DEFAULT NULL,
`recipe_hash` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
`ingredient_hash` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
`link` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`last_modified` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `CONSTRAINT_1` CHECK (`deleted` in (0,1))
) ENGINE=InnoDB AUTO_INCREMENT=535 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `recipe_ingredients`
--
DROP TABLE IF EXISTS `recipe_ingredients`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `recipe_ingredients` (
`Recipe_id` int(11) NOT NULL,
`ingredientHash_id` int(11) NOT NULL,
UNIQUE KEY `ingredientHash_id` (`ingredientHash_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `shopcats`
--
DROP TABLE IF EXISTS `shopcats`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `shopcats` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ingkey` text COLLATE utf8mb4_bin DEFAULT NULL,
`shopcategory` mediumtext COLLATE utf8mb4_bin DEFAULT NULL,
`position` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=679 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `shopcatsorder`
--
DROP TABLE IF EXISTS `shopcatsorder`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `shopcatsorder` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`shopcategory` text COLLATE utf8mb4_bin DEFAULT NULL,
`position` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `unitdict`
--
DROP TABLE IF EXISTS `unitdict`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `unitdict` (
`id` int(11) NOT NULL,
`ukey` varchar(150) COLLATE utf8mb4_bin DEFAULT NULL,
`value` varchar(150) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2024-02-18 17:34:22
Loading…
Cancel
Save