diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..bd1b43678 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:21-jdk-slim +COPY target/*.jar ./app.jar +ENTRYPOINT java -jar ./app.jar + diff --git a/README.md b/README.md index 45df7adf1..3e3727865 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,36 @@ spring.jpa.hibernate.ddl-auto=update # Running the Project in Production Mode -`mvn spring-boot:run -Pproduction` +`mvn -Pproduction` The default mode when the application is built or started is 'development'. The 'production' mode is turned on by enabling the `production` profile when building or starting the app. + +# Deploy the application for Production + +`mvn package -Pproduction` + +Then run it with + +`java -jar target/*.jar` + + +# Deploying the Project in Control Center + +Bakery has a profile that allows the application being deployed with [Control Center](https://vaadin.com/docs/latest/control-center) + +Compile, package, build the image and upload to docker hub. + +``` +mvn package -P production -P control-center +docker build -t your_docker_id/bakery:latest . +docker push your_docker_id/bakery:latest +``` + +Import it in Control Center selecting the `Identity manager` flag. Then create roles `admin`, `baker` and `barista` in Control Center and create the corresponding users to login into bakery application. + + + # Running in Eclipse or IntelliJ As both IDEs support running Spring Boot applications you just have to import the project and select `com.vaadin.starter.bakery.Application` as main class if not done automatically. Using an IDE will also allow you to speed up development even more. Just check https://vaadin.com/blog/developing-without-server-restarts. @@ -92,6 +118,6 @@ For full terms, see LICENSE Pro components used in the starter are : - [Vaadin Crud](https://vaadin.com/components/vaadin-crud) - [Vaadin Charts](https://vaadin.com/components/vaadin-charts) - - [Vaadin Confirm Dialog](https://vaadin.com/components/vaadin-confirm-dialog) + - [Vaadin Confirm Dialog](https://vaadin.com/components/vaadin-confirm-dialog) Also the tests are created using [Testbench](https://vaadin.com/testbench) library. diff --git a/pom.xml b/pom.xml index f40ad5441..a32609a31 100644 --- a/pom.xml +++ b/pom.xml @@ -202,6 +202,20 @@ + + control-center + + control-center + + + + com.vaadin + control-center-starter + + + + + production diff --git a/src/main/frontend/themes/bakery/styles.css b/src/main/frontend/themes/bakery/styles.css index 24754d29a..a61737e9e 100644 --- a/src/main/frontend/themes/bakery/styles.css +++ b/src/main/frontend/themes/bakery/styles.css @@ -8,6 +8,12 @@ } } +.app-name { + font-size:var(--lumo-font-size-xl); + font-weight:bold; + padding-left: 1em +} + .v-loading-indicator, .v-system-error, .v-reconnect-dialog { diff --git a/src/main/java/com/vaadin/starter/bakery/app/security/SecurityConfiguration.java b/src/main/java/com/vaadin/starter/bakery/app/security/SecurityConfiguration.java index 1a4e965a5..be56216ae 100644 --- a/src/main/java/com/vaadin/starter/bakery/app/security/SecurityConfiguration.java +++ b/src/main/java/com/vaadin/starter/bakery/app/security/SecurityConfiguration.java @@ -1,21 +1,20 @@ package com.vaadin.starter.bakery.app.security; -import com.vaadin.flow.spring.security.VaadinWebSecurity; -import com.vaadin.starter.bakery.backend.data.entity.User; -import com.vaadin.starter.bakery.backend.repositories.UserRepository; -import com.vaadin.starter.bakery.ui.views.login.LoginView; - import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Scope; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import com.vaadin.flow.spring.security.VaadinWebSecurity; +import com.vaadin.starter.bakery.backend.data.entity.User; +import com.vaadin.starter.bakery.backend.repositories.UserRepository; +import com.vaadin.starter.bakery.ui.views.login.LoginView; + /** * Configures spring security, doing the following: *
  • Bypass security checks for static resources,
  • @@ -26,16 +25,9 @@ */ @EnableWebSecurity @Configuration +@Profile("!control-center") public class SecurityConfiguration extends VaadinWebSecurity { - /** - * The password encoder to use when encrypting passwords. - */ - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public CurrentUser currentUser(UserRepository userRepository) { diff --git a/src/main/java/com/vaadin/starter/bakery/app/security/SecurityConfigurationCC.java b/src/main/java/com/vaadin/starter/bakery/app/security/SecurityConfigurationCC.java new file mode 100644 index 000000000..56d2c17f6 --- /dev/null +++ b/src/main/java/com/vaadin/starter/bakery/app/security/SecurityConfigurationCC.java @@ -0,0 +1,27 @@ +package com.vaadin.starter.bakery.app.security; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.spring.security.AuthenticationContext; +import com.vaadin.starter.bakery.backend.data.entity.User; + +/** + * Provide Beans for the control center security context. + * These beans might be removed, but needs important changes in code. + */ +@Configuration +@Profile("control-center") +public class SecurityConfigurationCC { + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + CurrentUser currentUser(AuthenticationContext authCtx) { + User user = new User(); + user.setFirstName(authCtx.getPrincipalName().orElse(null)); + return () -> user; + } +} diff --git a/src/main/java/com/vaadin/starter/bakery/app/security/UserDetailsServiceImpl.java b/src/main/java/com/vaadin/starter/bakery/app/security/UserDetailsServiceImpl.java index 7104d86ba..de2451f83 100644 --- a/src/main/java/com/vaadin/starter/bakery/app/security/UserDetailsServiceImpl.java +++ b/src/main/java/com/vaadin/starter/bakery/app/security/UserDetailsServiceImpl.java @@ -3,11 +3,14 @@ import java.util.Collections; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import com.vaadin.starter.bakery.backend.data.entity.User; @@ -29,6 +32,14 @@ public class UserDetailsServiceImpl implements UserDetailsService { public UserDetailsServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; } + + /** + * The password encoder to use when encrypting passwords. + */ + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } /** * diff --git a/src/main/java/com/vaadin/starter/bakery/ui/AppShell.java b/src/main/java/com/vaadin/starter/bakery/ui/AppShell.java index 6e002eb1d..499df93de 100644 --- a/src/main/java/com/vaadin/starter/bakery/ui/AppShell.java +++ b/src/main/java/com/vaadin/starter/bakery/ui/AppShell.java @@ -8,7 +8,7 @@ import static com.vaadin.starter.bakery.ui.utils.BakeryConst.VIEWPORT; @Viewport(VIEWPORT) -@Theme("bakery") +@Theme(value = "bakery", variant = "dark") @PWA(name = "Bakery App Starter", shortName = "###Bakery###", startPath = "login", backgroundColor = "#227aef", themeColor = "#227aef", diff --git a/src/main/java/com/vaadin/starter/bakery/ui/MainView.java b/src/main/java/com/vaadin/starter/bakery/ui/MainView.java index 6464bccf2..f01990135 100644 --- a/src/main/java/com/vaadin/starter/bakery/ui/MainView.java +++ b/src/main/java/com/vaadin/starter/bakery/ui/MainView.java @@ -1,5 +1,7 @@ package com.vaadin.starter.bakery.ui; +import static com.vaadin.flow.i18n.I18NProvider.translate; + import static com.vaadin.starter.bakery.ui.utils.BakeryConst.TITLE_DASHBOARD; import static com.vaadin.starter.bakery.ui.utils.BakeryConst.TITLE_LOGOUT; import static com.vaadin.starter.bakery.ui.utils.BakeryConst.TITLE_PRODUCTS; @@ -10,11 +12,10 @@ import java.util.List; import java.util.Optional; -import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.HasComponents; -import com.vaadin.flow.component.UI; import com.vaadin.flow.component.applayout.AppLayout; import com.vaadin.flow.component.confirmdialog.ConfirmDialog; import com.vaadin.flow.component.html.Anchor; @@ -28,23 +29,23 @@ import com.vaadin.flow.server.VaadinServlet; import com.vaadin.flow.server.VaadinServletRequest; import com.vaadin.flow.server.auth.AccessAnnotationChecker; -import com.vaadin.starter.bakery.ui.utils.BakeryConst; +import com.vaadin.flow.spring.security.AuthenticationContext; import com.vaadin.starter.bakery.ui.views.HasConfirmation; import com.vaadin.starter.bakery.ui.views.admin.products.ProductsView; import com.vaadin.starter.bakery.ui.views.admin.users.UsersView; import com.vaadin.starter.bakery.ui.views.dashboard.DashboardView; import com.vaadin.starter.bakery.ui.views.storefront.StorefrontView; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import jakarta.annotation.PostConstruct; public class MainView extends AppLayout { @Autowired private AccessAnnotationChecker accessChecker; + @Autowired + private AuthenticationContext authenticationContext; private final ConfirmDialog confirmDialog = new ConfirmDialog(); private Tabs menu; - private static final String LOGOUT_SUCCESS_URL = "/" + BakeryConst.PAGE_ROOT; @PostConstruct public void init() { @@ -53,7 +54,8 @@ public void init() { confirmDialog.setCancelButtonTheme("raised tertiary"); this.setDrawerOpened(false); - Span appName = new Span("###Bakery###"); + Span appName = new Span(translate("app.title")); + appName.addClassName("app-name"); appName.addClassName("hide-on-mobile"); menu = createMenuTabs(); @@ -66,11 +68,7 @@ public void init() { e.getSelectedTab().getId().ifPresent(id -> { if ("logout-tab".equals(id)) { - UI.getCurrent().getPage().setLocation(LOGOUT_SUCCESS_URL); - SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler(); - logoutHandler.logout( - VaadinServletRequest.getCurrent().getHttpServletRequest(), null, - null); + authenticationContext.logout(); } }); }); @@ -155,4 +153,4 @@ private static T populateLink(T a, VaadinIcon icon, St a.add(title); return a; } -} \ No newline at end of file +} diff --git a/src/main/java/com/vaadin/starter/bakery/ui/views/admin/products/ProductsView.java b/src/main/java/com/vaadin/starter/bakery/ui/views/admin/products/ProductsView.java index f26cc2343..7a8128f96 100644 --- a/src/main/java/com/vaadin/starter/bakery/ui/views/admin/products/ProductsView.java +++ b/src/main/java/com/vaadin/starter/bakery/ui/views/admin/products/ProductsView.java @@ -1,5 +1,7 @@ package com.vaadin.starter.bakery.ui.views.admin.products; +import static com.vaadin.flow.i18n.I18NProvider.translate; + import com.vaadin.flow.component.crud.BinderCrudEditor; import com.vaadin.flow.component.formlayout.FormLayout; import com.vaadin.flow.component.grid.Grid; @@ -51,9 +53,9 @@ protected String getBasePage() { } private static BinderCrudEditor createForm() { - TextField name = new TextField("Product name"); + TextField name = new TextField(translate("product.name")); name.getElement().setAttribute("colspan", "2"); - TextField price = new TextField("Unit price"); + TextField price = new TextField(translate("unit.price")); price.getElement().setAttribute("colspan", "2"); FormLayout form = new FormLayout(name, price); diff --git a/src/main/java/com/vaadin/starter/bakery/ui/views/admin/users/UsersView.java b/src/main/java/com/vaadin/starter/bakery/ui/views/admin/users/UsersView.java index 109791d50..d8d8a52af 100644 --- a/src/main/java/com/vaadin/starter/bakery/ui/views/admin/users/UsersView.java +++ b/src/main/java/com/vaadin/starter/bakery/ui/views/admin/users/UsersView.java @@ -1,5 +1,7 @@ package com.vaadin.starter.bakery.ui.views.admin.users; +import static com.vaadin.flow.i18n.I18NProvider.translate; + import jakarta.annotation.security.RolesAllowed; import org.springframework.beans.factory.annotation.Autowired; @@ -53,15 +55,15 @@ protected String getBasePage() { } private static BinderCrudEditor createForm(PasswordEncoder passwordEncoder) { - EmailField email = new EmailField("Email (login)"); + EmailField email = new EmailField(translate("email.field")); email.getElement().setAttribute("colspan", "2"); - TextField first = new TextField("First name"); - TextField last = new TextField("Last name"); - PasswordField password = new PasswordField("Password"); + TextField first = new TextField(translate("first.name.field")); + TextField last = new TextField(translate("last.name.field")); + PasswordField password = new PasswordField(translate("password.field")); password.getElement().setAttribute("colspan", "2"); ComboBox role = new ComboBox<>(); role.getElement().setAttribute("colspan", "2"); - role.setLabel("Role"); + role.setLabel(translate("role.field")); FormLayout form = new FormLayout(email, first, last, password, role); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cd18670af..fe08376dd 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,7 +1,11 @@ +spring.profiles.active=@spring.profiles.active@ +spring.main.allow-bean-definition-overriding=true + server.compression.enabled=true server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css security.basic.enabled=false server.tomcat.uri-encoding=UTF-8 + spring.jackson.serialization.write_dates_as_timestamps=false # Comment out if using anything else than H2 (e.g. MySQL or PostgreSQL) spring.jpa.database-platform=org.hibernate.dialect.H2Dialect @@ -17,3 +21,4 @@ logging.level.org.atmosphere = warn # Ensure application is run in Vaadin 14/npm mode vaadin.compatibilityMode = false + diff --git a/src/main/resources/vaadin-i18n/translations.properties b/src/main/resources/vaadin-i18n/translations.properties new file mode 100644 index 000000000..421372775 --- /dev/null +++ b/src/main/resources/vaadin-i18n/translations.properties @@ -0,0 +1,8 @@ +app.title=Bakery +product.name=Product name +unit.price=Unit price +email.field=Email (login) +first.name.field=First name +last.name.field=Last name +password.field=Password +role.field=Role