├── .env ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── pom.xml ├── src └── main │ ├── configuration │ ├── app.properties │ └── simplelogger.properties │ ├── java │ └── com │ │ └── olegv │ │ ├── AppConfig.java │ │ ├── Application.java │ │ ├── customer │ │ ├── Customer.java │ │ ├── CustomerController.java │ │ ├── CustomerDao.java │ │ └── CustomerDaoImpl.java │ │ ├── dashboard │ │ ├── DashboardController.java │ │ └── LoginController.java │ │ ├── order │ │ ├── Order.java │ │ ├── OrderController.java │ │ └── OrderDao.java │ │ ├── user │ │ ├── User.java │ │ ├── UserController.java │ │ ├── UserDao.java │ │ ├── UserDaoImpl.java │ │ ├── UserRole.java │ │ ├── UserService.java │ │ └── UserServiceImpl.java │ │ └── util │ │ ├── AppProperties.java │ │ ├── CmdArgs.java │ │ ├── Filters.java │ │ ├── JsonUtil.java │ │ ├── MessageBundle.java │ │ ├── Path.java │ │ ├── RequestUtil.java │ │ └── ViewUtil.java │ └── resources │ ├── localization │ ├── messages_de.properties │ └── messages_en.properties │ ├── public │ ├── css │ │ ├── footer.css │ │ ├── main.css │ │ ├── navbar.css │ │ ├── normalize.css │ │ └── skeleton.css │ └── img │ │ └── favicon.png │ ├── velocity │ ├── 404.vm │ ├── 500.vm │ ├── controlpanel │ │ ├── account.vm │ │ ├── controlpanel_layout.vm │ │ ├── customer │ │ │ ├── customer_details.vm │ │ │ └── customers.vm │ │ ├── dashboard.vm │ │ └── order │ │ │ └── orders.vm │ ├── login.vm │ └── partials │ │ ├── footer_partial.vm │ │ └── meta_partial.vm │ └── velocityconfig │ └── velocity_implicit.vm └── system.properties /.env: -------------------------------------------------------------------------------- 1 | HEROKU_ENVIRONMENT_VARIABLE=heroku environment variable 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # Build directory # 22 | target/ 23 | 24 | # IDEA Project files # 25 | *.iws 26 | *.iml 27 | *.ipr 28 | .idea/ 29 | 30 | 31 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 32 | hs_err_pid* 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Oleg Vasiliev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: java -jar target/spark-boilerplate-0.1.jar -heroku 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proper boilerplate project for Spark # 2 | This is a boilerplate project for developing lightweight web apps with [Spark](http://sparkjava.com/)- a micro framework for creating web apps in Java 8 with minimal effort. 3 | 4 | This project contains simple implementations of commonly needed features like localization and authorization with simple role based security. The file structure is organized using a ["package by feature"](http://www.javapractices.com/topic/TopicAction.do?Id=205) style, which has advantages over the standard "package by layer" approach, which is used everywhere. 5 | On the client side we have [Skeleton](http://getskeleton.com/)- a dead simple, responsive boilerplate for front-end that can be easily replaced by more complex solutions like [Bootstrap](http://getbootstrap.com/), [Foundation](http://foundation.zurb.com/), [MDL](https://getmdl.io/) or whatever you want. All view templates are managed by the [Apache Velocity](https://velocity.apache.org/) template engine. It's simple, fast, and provides much more flexibility than rusty Java Server Pages. Also, for convenience, testing and simpler object construction used [Google Guice](https://github.com/google/guice), but it can be easily removed if you prefer to do everything yourself. 6 | 7 | More features will be added later, including API examples, database integration, unit and integration tests, etc.. There is a live example available on [Heroku](https://sparkjava-boilerplate.herokuapp.com) 8 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.olegv 8 | sparkjava-boilerplate 9 | 0.1 10 | 11 | sparkjava-boilerplate 12 | 13 | 14 | UTF-8 15 | com.olegv.Application 16 | 17 | 18 | 19 | 20 | 21 | 22 | org.apache.maven.plugins 23 | maven-compiler-plugin 24 | 3.6.1 25 | 26 | 1.8 27 | 1.8 28 | 29 | 30 | 31 | 32 | 33 | org.apache.maven.plugins 34 | maven-dependency-plugin 35 | 2.5.1 36 | 37 | 38 | copy-dependencies 39 | prepare-package 40 | 41 | copy-dependencies 42 | 43 | 44 | runtime 45 | ${project.build.directory}/${project.build.finalName}.lib 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-jar-plugin 55 | 3.0.2 56 | 57 | 58 | 59 | true 60 | ${application.mainClass} 61 | ${project.build.finalName}.lib/ 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | com.sparkjava 75 | spark-core 76 | 2.7.2 77 | 78 | 79 | 80 | com.sparkjava 81 | spark-template-velocity 82 | 2.5.5 83 | 84 | 85 | 86 | com.sparkjava 87 | spark-debug-tools 88 | 0.5 89 | 90 | 91 | 92 | 93 | com.google.inject 94 | guice 95 | 4.1.0 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | org.projectlombok 115 | lombok 116 | 1.16.16 117 | provided 118 | 119 | 120 | 121 | com.google.collections 122 | google-collections 123 | 1.0 124 | 125 | 126 | 127 | com.fasterxml.jackson.core 128 | jackson-databind 129 | 2.9.10.3 130 | 131 | 132 | 133 | org.mindrot 134 | jbcrypt 135 | 0.4 136 | 137 | 138 | 139 | 140 | 141 | org.slf4j 142 | slf4j-api 143 | 1.7.21 144 | 145 | 146 | 147 | org.slf4j 148 | slf4j-simple 149 | 1.7.21 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /src/main/configuration/app.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oleg-vasiliev/sparkjava-boilerplate/036a503a4de0285e17b728fb1a84ff32575fe21e/src/main/configuration/app.properties -------------------------------------------------------------------------------- /src/main/configuration/simplelogger.properties: -------------------------------------------------------------------------------- 1 | org.slf4j.simpleLogger.logFile = System.out 2 | org.slf4j.simpleLogger.defaultLogLevel = info 3 | org.slf4j.simpleLogger.showDateTime = true 4 | org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd'@'HH:mm:ss.SSS 5 | org.slf4j.simpleLogger.showThreadName = true 6 | org.slf4j.simpleLogger.showLogName = true 7 | org.slf4j.simpleLogger.showShortLogName = true 8 | org.slf4j.simpleLogger.levelInBrackets = true 9 | 10 | org.slf4j.simpleLogger.log.spark.http.matching.MatcherFilter = warn 11 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/AppConfig.java: -------------------------------------------------------------------------------- 1 | package com.olegv; 2 | 3 | import com.google.inject.Binder; 4 | import com.google.inject.Module; 5 | import com.google.inject.Provides; 6 | import com.olegv.customer.CustomerDao; 7 | import com.olegv.customer.CustomerDaoImpl; 8 | import com.olegv.user.UserDao; 9 | import com.olegv.user.UserDaoImpl; 10 | import com.olegv.user.UserService; 11 | import com.olegv.user.UserServiceImpl; 12 | import com.olegv.util.CmdArgs; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | public class AppConfig implements Module { 17 | private static final Logger LOG = LoggerFactory.getLogger(AppConfig.class); 18 | 19 | public AppConfig(CmdArgs cmdArgs) { 20 | // Extract some configuration variables (using CmdArgs object or static AppProperties util) 21 | 22 | } 23 | 24 | @Override 25 | public void configure(Binder binder) { 26 | // Bind service implementations that does not require custom configuration 27 | binder.bind(CustomerDao.class).to(CustomerDaoImpl.class); 28 | binder.bind(UserService.class).to(UserServiceImpl.class); 29 | } 30 | 31 | // Through provider methods we can bind service implementations that requires custom configuration 32 | @Provides 33 | public UserDao provideUserDao() { 34 | return new UserDaoImpl(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/Application.java: -------------------------------------------------------------------------------- 1 | package com.olegv; 2 | 3 | import com.google.inject.Guice; 4 | import com.google.inject.Injector; 5 | import com.olegv.customer.CustomerController; 6 | import com.olegv.dashboard.DashboardController; 7 | import com.olegv.dashboard.LoginController; 8 | import com.olegv.order.OrderController; 9 | import com.olegv.user.UserController; 10 | import com.olegv.util.*; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import spark.Spark; 14 | 15 | import static spark.Spark.*; 16 | import static spark.debug.DebugScreen.enableDebugScreen; 17 | 18 | public class Application { 19 | private static final Logger LOG = LoggerFactory.getLogger(Application.class); 20 | 21 | public static void main(String[] args) { 22 | CmdArgs cmdArgs = new CmdArgs(args); 23 | 24 | Integer port = cmdArgs.switchIntValue("-port", 4567); 25 | if (cmdArgs.switchPresent("-heroku")) { 26 | port = Integer.parseInt(AppProperties.getEnvironmentVariable("PORT", port.toString())); 27 | } 28 | 29 | AppConfig appConfig = new AppConfig(cmdArgs); 30 | Injector injector = Guice.createInjector(appConfig); 31 | 32 | // Configure Spark 33 | port(port); 34 | staticFiles.location("/public"); 35 | // Static files caching is disabled by default 36 | // staticFiles.expireTime(600L); 37 | enableDebugScreen(); 38 | 39 | // Set up before-filters (called before each request) 40 | Spark.before("*", Filters.addTrailingSlashes); 41 | before("*", Filters.handleLocaleChange); 42 | 43 | // Set up routes 44 | redirect.any("/", Path.Web.CONTROL_PANEL_DASHBOARD); 45 | 46 | get(Path.Web.CONTROL_PANEL_DASHBOARD, injector.getInstance(DashboardController.DashboardPageHandler.class)); 47 | get(Path.Web.CONTROL_PANEL_CUSTOMERS, injector.getInstance(CustomerController.CustomersPageHandler.class)); 48 | get(Path.Web.CONTROL_PANEL_CUSTOMERS_ONE, injector.getInstance(CustomerController.CustomerDetailsPageHandler.class)); 49 | get(Path.Web.CONTROL_PANEL_ORDERS, injector.getInstance(OrderController.OrdersPageHandler.class)); 50 | 51 | get(Path.Web.CONTROL_PANEL_ACCOUNT, injector.getInstance(UserController.UserProfilePageHandler.class)); 52 | post(Path.Web.CONTROL_PANEL_ACCOUNT, injector.getInstance(UserController.UserProfileEditRequestHandler.class)); 53 | 54 | get(Path.Web.API_CUSTOMERS, injector.getInstance(CustomerController.APIGetAllCustomersHandler.class)); 55 | 56 | get(Path.Web.LOGIN, injector.getInstance(LoginController.LoginPageHandler.class)); 57 | post(Path.Web.LOGIN, injector.getInstance(LoginController.LoginRequestHandler.class)); 58 | get(Path.Web.LOGOUT, injector.getInstance(LoginController.LogoutRequestHandler.class)); 59 | 60 | Spark.notFound(ViewUtil.notFound); 61 | internalServerError(ViewUtil.internalServerError); 62 | 63 | //Set up after-filters (called after each get/post) 64 | after("*", Filters.addGzipHeader); 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/customer/Customer.java: -------------------------------------------------------------------------------- 1 | package com.olegv.customer; 2 | 3 | 4 | import lombok.Value; 5 | 6 | @Value 7 | public class Customer { 8 | private String uid; 9 | private String name; 10 | private String address; 11 | private String photo; 12 | private String email; 13 | private String phone; 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/customer/CustomerController.java: -------------------------------------------------------------------------------- 1 | package com.olegv.customer; 2 | 3 | import com.google.inject.Inject; 4 | import com.olegv.dashboard.LoginController; 5 | import com.olegv.user.UserRole; 6 | import com.olegv.util.JsonUtil; 7 | import com.olegv.util.Path; 8 | import com.olegv.util.RequestUtil; 9 | import com.olegv.util.ViewUtil; 10 | import spark.Request; 11 | import spark.Response; 12 | import spark.Route; 13 | 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Optional; 17 | 18 | public class CustomerController { 19 | 20 | public static class CustomersPageHandler implements Route { 21 | @Inject 22 | private CustomerDao customerDao; 23 | 24 | @Override 25 | public Object handle(Request request, Response response) throws Exception { 26 | LoginController.ensureUserIsLoggedInWithRole(request, response, UserRole.ROOT); 27 | HashMap model = new HashMap<>(); 28 | model.put("customers", customerDao.getAll()); 29 | return ViewUtil.render(request, model, Path.Template.CONTROL_PANEL_CUSTOMERS); 30 | } 31 | } 32 | 33 | public static class CustomerDetailsPageHandler implements Route { 34 | @Inject 35 | private CustomerDao customerDao; 36 | 37 | @Override 38 | public Object handle(Request request, Response response) throws Exception { 39 | LoginController.ensureUserIsLoggedInWithRole(request, response, UserRole.ROOT); 40 | HashMap model = new HashMap<>(); 41 | Optional customer = customerDao.getByUID(RequestUtil.getPathParamUID(request)); 42 | model.put("customer", customer.orElse(null)); 43 | return ViewUtil.render(request, model, Path.Template.CONTROL_PANEL_CUSTOMER_DETAILS); 44 | } 45 | } 46 | 47 | public static class APIGetAllCustomersHandler implements Route { 48 | 49 | @Inject 50 | private CustomerDao customerDao; 51 | 52 | @Override 53 | public Object handle(Request request, Response response) throws Exception { 54 | List all = customerDao.getAll(); 55 | response.type("application/json"); 56 | return JsonUtil.dataToJson(all); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/customer/CustomerDao.java: -------------------------------------------------------------------------------- 1 | package com.olegv.customer; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | public interface CustomerDao { 7 | 8 | Optional getByUID(String uid); 9 | 10 | List getAll(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/customer/CustomerDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.olegv.customer; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.inject.Singleton; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | @Singleton 10 | public class CustomerDaoImpl implements CustomerDao { 11 | 12 | private final List customers = ImmutableList.of( 13 | new Customer("uid1", "Walter Schultz", "1472 Pockrus Page Rd", "https://randomuser.me/api/portraits/men/89.jpg", "wschultz@mail.com", "202-555-0173"), 14 | new Customer("uid2", "Zachary Wade", "5998 White Oak Dr ", "https://randomuser.me/api/portraits/men/86.jpg", "zwade@mail.com", "512-555-0171"), 15 | new Customer("uid3", "Diana Wood", "6306 Brentwood Dr", "https://randomuser.me/api/portraits/women/10.jpg", "dwood@mail.com", "208-555-0120"), 16 | new Customer("uid4", "Jason Snyder", "7567 Oak Ridge Ln", "https://randomuser.me/api/portraits/men/79.jpg", "jsnyder@mail.com", "803-555-0142"), 17 | new Customer("uid5", "Carl Tran", "2152 Abby Park St", "https://randomuser.me/api/portraits/men/42.jpg", "ctran@mail.com", "515-555-0143") 18 | ); 19 | 20 | @Override 21 | public Optional getByUID(String uid) { 22 | return Optional.ofNullable(customers.stream().filter(b -> b.getUid().equalsIgnoreCase(uid)).findFirst().orElse(null)); 23 | } 24 | 25 | @Override 26 | public List getAll() { 27 | return customers; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/dashboard/DashboardController.java: -------------------------------------------------------------------------------- 1 | package com.olegv.dashboard; 2 | 3 | import com.olegv.util.Path; 4 | import com.olegv.util.ViewUtil; 5 | import spark.Request; 6 | import spark.Response; 7 | import spark.Route; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public class DashboardController { 13 | 14 | public static class DashboardPageHandler implements Route { 15 | @Override 16 | public Object handle(Request request, Response response) throws Exception { 17 | LoginController.ensureUserIsLoggedIn(request, response); 18 | Map model = new HashMap<>(); 19 | return ViewUtil.render(request, model, Path.Template.CONTROL_PANEL_DASHBOARD); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/dashboard/LoginController.java: -------------------------------------------------------------------------------- 1 | package com.olegv.dashboard; 2 | 3 | import com.google.inject.Inject; 4 | import com.olegv.user.User; 5 | import com.olegv.user.UserService; 6 | import com.olegv.util.Path; 7 | import com.olegv.util.RequestUtil; 8 | import com.olegv.util.ViewUtil; 9 | import org.eclipse.jetty.http.HttpStatus; 10 | import spark.Request; 11 | import spark.Response; 12 | import spark.Route; 13 | import spark.Spark; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | import java.util.Optional; 18 | 19 | import static com.olegv.user.UserRole.getUserRoleHomepage; 20 | 21 | public class LoginController { 22 | 23 | public static class LoginPageHandler implements Route { 24 | @Override 25 | public Object handle(Request request, Response response) throws Exception { 26 | Map model = new HashMap<>(); 27 | model.put("loggedOut", RequestUtil.removeSessionAttrLoggedOut(request)); 28 | model.put("loginRedirect", RequestUtil.removeSessionAttrLoginRedirect(request)); 29 | return ViewUtil.render(request, model, Path.Template.LOGIN); 30 | } 31 | } 32 | 33 | public static class LoginRequestHandler implements Route { 34 | @Inject 35 | private UserService userService; 36 | 37 | @Override 38 | public Object handle(Request request, Response response) throws Exception { 39 | Map model = new HashMap<>(); 40 | Optional user = userService.authenticateAndGet(RequestUtil.getQueryUsername(request), RequestUtil.getQueryPassword(request)); 41 | if (!user.isPresent()) { 42 | model.put("authenticationFailed", true); 43 | return ViewUtil.render(request, model, Path.Template.LOGIN); 44 | } 45 | model.put("authenticationSucceeded", true); 46 | request.session().attribute("currentUser", user.get().getUsername()); 47 | request.session().attribute("currentUserRole", user.get().getRole()); 48 | if (RequestUtil.getQueryLoginRedirect(request) != null) { 49 | response.redirect(RequestUtil.getQueryLoginRedirect(request)); 50 | } else { 51 | response.redirect(getUserRoleHomepage(user.get().getRole())); 52 | } 53 | return ViewUtil.render(request, model, Path.Template.LOGIN); 54 | } 55 | } 56 | 57 | public static class LogoutRequestHandler implements Route { 58 | @Override 59 | public Object handle(Request request, Response response) throws Exception { 60 | request.session().removeAttribute("currentUser"); 61 | request.session().removeAttribute("currentUserRole"); 62 | request.session().attribute("loggedOut", true); 63 | response.redirect(Path.Web.LOGIN); 64 | return null; 65 | } 66 | } 67 | 68 | // The origin of the request (request.pathInfo()) is saved in the session so the users can be redirected back after login 69 | public static void ensureUserIsLoggedIn(Request request, Response response) { 70 | if (request.session().attribute("currentUser") == null) { 71 | request.session().attribute("loginRedirect", request.pathInfo()); 72 | response.redirect(Path.Web.LOGIN); 73 | } 74 | } 75 | 76 | public static void ensureUserIsLoggedInWithRole(Request request, Response response, String role) { 77 | ensureUserIsLoggedIn(request, response); 78 | if (role == null) return; 79 | String currentUserRole = request.session().attribute("currentUserRole"); 80 | if (currentUserRole == null || !currentUserRole.equalsIgnoreCase(role)) { 81 | Spark.halt(HttpStatus.NOT_FOUND_404, ViewUtil.render(request, new HashMap<>(), Path.Template.NOT_FOUND)); 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/order/Order.java: -------------------------------------------------------------------------------- 1 | package com.olegv.order; 2 | 3 | import lombok.Value; 4 | 5 | import java.util.Date; 6 | import java.util.Map; 7 | 8 | @Value 9 | public class Order { 10 | String uid; 11 | String customerUid; 12 | Map items; 13 | Integer totalValue; 14 | Date created; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/order/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.olegv.order; 2 | 3 | import com.olegv.dashboard.LoginController; 4 | import com.olegv.util.Path; 5 | import com.olegv.util.ViewUtil; 6 | import spark.Request; 7 | import spark.Response; 8 | import spark.Route; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public class OrderController { 14 | 15 | public static class OrdersPageHandler implements Route { 16 | 17 | @Override 18 | public Object handle(Request request, Response response) throws Exception { 19 | LoginController.ensureUserIsLoggedIn(request, response); 20 | Map model = new HashMap<>(); 21 | return ViewUtil.render(request, model, Path.Template.CONTROL_PANEL_ORDERS); 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/order/OrderDao.java: -------------------------------------------------------------------------------- 1 | package com.olegv.order; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | public interface OrderDao { 7 | 8 | Optional getByUID(String uid); 9 | 10 | List getAll(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/user/User.java: -------------------------------------------------------------------------------- 1 | package com.olegv.user; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class User { 9 | String uid; 10 | String username; 11 | String role; 12 | String salt; 13 | String hashedPassword; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/user/UserController.java: -------------------------------------------------------------------------------- 1 | package com.olegv.user; 2 | 3 | import com.google.inject.Inject; 4 | import com.olegv.dashboard.LoginController; 5 | import com.olegv.util.Path; 6 | import com.olegv.util.RequestUtil; 7 | import com.olegv.util.ViewUtil; 8 | import spark.Request; 9 | import spark.Response; 10 | import spark.Route; 11 | 12 | import java.util.HashMap; 13 | 14 | public class UserController { 15 | 16 | public static class UserProfilePageHandler implements Route { 17 | 18 | @Override 19 | public Object handle(Request request, Response response) throws Exception { 20 | LoginController.ensureUserIsLoggedIn(request, response); 21 | HashMap model = new HashMap<>(); 22 | return ViewUtil.render(request, model, Path.Template.CONTROL_PANEL_ACCOUNT); 23 | } 24 | } 25 | 26 | public static class UserProfileEditRequestHandler implements Route { 27 | 28 | @Inject 29 | private UserService userService; 30 | 31 | @Override 32 | public Object handle(Request request, Response response) throws Exception { 33 | HashMap model = new HashMap<>(); 34 | 35 | LoginController.ensureUserIsLoggedIn(request, response); 36 | String username = RequestUtil.getSessionCurrentUser(request); 37 | String queryPasswordOld = RequestUtil.getQueryPasswordOld(request); 38 | String queryPasswordNew = RequestUtil.getQueryPasswordNew(request); 39 | 40 | if (queryPasswordNew == null || queryPasswordNew.length() < 8) { 41 | model.put("formMessage", "PASSWORD_TOO_SHORT"); 42 | } else if (!userService.setPassword(username, queryPasswordOld, queryPasswordNew)) { 43 | model.put("formMessage", "INVALID_PASSWORD"); 44 | } else { 45 | model.put("formMessage", "SAVED_SUCCESSFULLY"); 46 | } 47 | 48 | return ViewUtil.render(request, model, Path.Template.CONTROL_PANEL_ACCOUNT); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/user/UserDao.java: -------------------------------------------------------------------------------- 1 | package com.olegv.user; 2 | 3 | import java.util.Optional; 4 | 5 | public interface UserDao { 6 | 7 | Optional getUserByUsername(String username); 8 | 9 | Optional getUserByUID(String uid); 10 | 11 | User saveUser(User user); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/user/UserDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.olegv.user; 2 | 3 | import com.google.inject.Singleton; 4 | 5 | import java.util.HashMap; 6 | import java.util.Optional; 7 | 8 | @Singleton 9 | public class UserDaoImpl implements UserDao { 10 | 11 | private final HashMap users; 12 | 13 | // The password is "password" for all users 14 | public UserDaoImpl() { 15 | User root = new User("root-uid", "root@example.com", UserRole.ROOT, "$2a$10$h.dl5J86rGH7I8bD9bZeZe", 16 | "$2a$10$h.dl5J86rGH7I8bD9bZeZeci0pDt0.VwFTGujlnEaZXPf/q7vM5wO"); 17 | User user = new User("user-uid", "user@example.com", UserRole.USER, "$2a$10$E3DgchtVry3qlYlzJCsyxe", 18 | "$2a$10$E3DgchtVry3qlYlzJCsyxeSK0fftK4v0ynetVCuDdxGVl1obL.ln2"); 19 | 20 | users = new HashMap<>(); 21 | users.put(root.getUid(), root); 22 | users.put(user.getUid(), user); 23 | } 24 | 25 | @Override 26 | public Optional getUserByUsername(String username) { 27 | return Optional.ofNullable(users.values().stream().filter(b -> b.getUsername().equals(username)).findFirst().orElse(null)); 28 | } 29 | 30 | @Override 31 | public Optional getUserByUID(String uid) { 32 | return Optional.ofNullable(users.values().stream().filter(b -> b.getUid().equals(uid)).findFirst().orElse(null)); 33 | } 34 | 35 | @Override 36 | public User saveUser(User user) { 37 | users.put(user.getUid(), user); 38 | return user; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/user/UserRole.java: -------------------------------------------------------------------------------- 1 | package com.olegv.user; 2 | 3 | import com.olegv.util.Path; 4 | import lombok.Getter; 5 | 6 | public class UserRole { 7 | 8 | @Getter 9 | public static final String ROOT = "ROOT"; 10 | @Getter 11 | public static final String USER = "USER"; 12 | 13 | public static String getUserRoleHomepage(String role) { 14 | if (ROOT.equalsIgnoreCase(role)) 15 | return Path.Web.CONTROL_PANEL_DASHBOARD; 16 | else if (USER.equalsIgnoreCase(role)) 17 | return Path.Web.CONTROL_PANEL_DASHBOARD; 18 | else 19 | return Path.Web.LOGIN; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/user/UserService.java: -------------------------------------------------------------------------------- 1 | package com.olegv.user; 2 | 3 | import java.util.Optional; 4 | 5 | public interface UserService { 6 | 7 | boolean authenticate(String username, String password); 8 | 9 | Optional authenticateAndGet(String username, String password); 10 | 11 | boolean setPassword(String username, String oldPassword, String newPassword); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/user/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.olegv.user; 2 | 3 | import com.google.inject.Inject; 4 | import com.google.inject.Singleton; 5 | import org.mindrot.jbcrypt.BCrypt; 6 | 7 | import java.util.Optional; 8 | 9 | @Singleton 10 | public class UserServiceImpl implements UserService { 11 | 12 | @Inject 13 | private UserDao userDao; 14 | 15 | @Override 16 | public boolean authenticate(String username, String password) { 17 | if (username.isEmpty() || password.isEmpty()) { 18 | return false; 19 | } 20 | Optional user = userDao.getUserByUsername(username); 21 | if (!user.isPresent()) { 22 | return false; 23 | } 24 | String hashedPassword = BCrypt.hashpw(password, user.get().getSalt()); 25 | return hashedPassword.equals(user.get().getHashedPassword()); 26 | } 27 | 28 | @Override 29 | public Optional authenticateAndGet(String username, String password) { 30 | if (username.isEmpty() || password.isEmpty()) { 31 | return Optional.empty(); 32 | } 33 | Optional user = userDao.getUserByUsername(username); 34 | if (user.isPresent()) { 35 | String hashedPassword = BCrypt.hashpw(password, user.get().getSalt()); 36 | return hashedPassword.equals(user.get().getHashedPassword()) ? user : Optional.empty(); 37 | } else 38 | return Optional.empty(); 39 | } 40 | 41 | @Override 42 | public boolean setPassword(String username, String oldPassword, String newPassword) { 43 | Optional userOptional = authenticateAndGet(username, oldPassword); 44 | if (userOptional.isPresent()) { 45 | String newSalt = BCrypt.gensalt(); 46 | String newHashedPassword = BCrypt.hashpw(newPassword, newSalt); 47 | User user = userOptional.get(); 48 | user.setSalt(newSalt); 49 | user.setHashedPassword(newHashedPassword); 50 | userDao.saveUser(user); 51 | return true; 52 | } else { 53 | return false; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/util/AppProperties.java: -------------------------------------------------------------------------------- 1 | package com.olegv.util; 2 | 3 | import spark.resource.ClassPathResource; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.Properties; 8 | 9 | /** 10 | * Class for simple work with app.properties file and system properties or environment variables. 11 | * Notes: 12 | * - Environment variables defined and controlled by the operating system. Usually naming style is UPPERCASE_WITH_UNDERSCORES. 13 | * - System properties are defined by the JVM and can be specified on the command line when Java application starts. Usually naming style is lowercase.with.dots 14 | */ 15 | public final class AppProperties { 16 | 17 | private static Properties properties; 18 | 19 | public AppProperties() { 20 | loadProperties(); 21 | } 22 | 23 | private static void loadProperties() { 24 | ClassPathResource classPathResource = new ClassPathResource("app.properties"); 25 | properties = new Properties(); 26 | InputStream input = null; 27 | try { 28 | input = classPathResource.getInputStream(); 29 | properties.load(input); 30 | } catch (IOException ex) { 31 | ex.printStackTrace(); 32 | } finally { 33 | if (input != null) { 34 | try { 35 | input.close(); 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | } 41 | } 42 | 43 | public static String getAppProperty(String propertyName, String defaultValue) { 44 | if (properties == null) loadProperties(); 45 | String property = properties.getProperty(propertyName); 46 | return (property != null && !property.isEmpty()) ? property.trim() : defaultValue; 47 | } 48 | 49 | public static String getSystemProperty(String propertyName, String defaultValue) { 50 | String property; 51 | try { 52 | property = System.getProperty(propertyName); 53 | } catch (Exception e) { 54 | property = null; 55 | } 56 | return (property != null && !property.isEmpty()) ? property.trim() : defaultValue; 57 | } 58 | 59 | public static String getEnvironmentVariable(String variableName, String defaultValue) { 60 | String variable; 61 | try { 62 | variable = System.getenv(variableName); 63 | } catch (Exception e) { 64 | variable = null; 65 | } 66 | return (variable != null && !variable.isEmpty()) ? variable.trim() : defaultValue; 67 | 68 | } 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/util/CmdArgs.java: -------------------------------------------------------------------------------- 1 | package com.olegv.util; 2 | 3 | import java.util.HashMap; 4 | import java.util.TreeSet; 5 | 6 | /** 7 | * Small util for work with commandline arguments like "switches" (app.jar -port 4567) or "targets" (app.jar 4567). 8 | */ 9 | public class CmdArgs { 10 | 11 | private String[] args = null; 12 | 13 | private HashMap switchIndexes = new HashMap<>(); 14 | private TreeSet takenIndexes = new TreeSet<>(); 15 | 16 | public CmdArgs(String[] args) { 17 | parse(args); 18 | } 19 | 20 | public void parse(String[] arguments) { 21 | this.args = arguments; 22 | //locate switches. 23 | switchIndexes.clear(); 24 | takenIndexes.clear(); 25 | for (int i = 0; i < args.length; i++) { 26 | if (args[i].startsWith("-")) { 27 | switchIndexes.put(args[i], i); 28 | takenIndexes.add(i); 29 | } 30 | } 31 | } 32 | 33 | public String[] args() { 34 | return args; 35 | } 36 | 37 | public String arg(int index) { 38 | return args[index]; 39 | } 40 | 41 | public boolean switchPresent(String switchName) { 42 | return switchIndexes.containsKey(switchName); 43 | } 44 | 45 | public String switchValue(String switchName) { 46 | return switchValue(switchName, null); 47 | } 48 | 49 | public String switchValue(String switchName, String defaultValue) { 50 | if (!switchIndexes.containsKey(switchName)) return defaultValue; 51 | 52 | int switchIndex = switchIndexes.get(switchName); 53 | if (switchIndex + 1 < args.length) { 54 | takenIndexes.add(switchIndex + 1); 55 | return args[switchIndex + 1]; 56 | } 57 | return defaultValue; 58 | } 59 | 60 | public Long switchLongValue(String switchName) { 61 | return switchLongValue(switchName, null); 62 | } 63 | 64 | public Long switchLongValue(String switchName, Long defaultValue) { 65 | String switchValue = switchValue(switchName, null); 66 | 67 | if (switchValue == null) return defaultValue; 68 | return Long.parseLong(switchValue); 69 | } 70 | 71 | public Integer switchIntValue(String switchName) { 72 | return switchIntValue(switchName, null); 73 | } 74 | 75 | public Integer switchIntValue(String switchName, Integer defaultValue) { 76 | String switchValue = switchValue(switchName, null); 77 | 78 | if (switchValue == null) return defaultValue; 79 | return Integer.parseInt(switchValue); 80 | } 81 | 82 | public Double switchDoubleValue(String switchName) { 83 | return switchDoubleValue(switchName, null); 84 | } 85 | 86 | public Double switchDoubleValue(String switchName, Double defaultValue) { 87 | String switchValue = switchValue(switchName, null); 88 | 89 | if (switchValue == null) return defaultValue; 90 | return Double.parseDouble(switchValue); 91 | } 92 | 93 | 94 | public String[] switchValues(String switchName) { 95 | if (!switchIndexes.containsKey(switchName)) return new String[0]; 96 | 97 | int switchIndex = switchIndexes.get(switchName); 98 | 99 | int nextArgIndex = switchIndex + 1; 100 | while (nextArgIndex < args.length && !args[nextArgIndex].startsWith("-")) { 101 | takenIndexes.add(nextArgIndex); 102 | nextArgIndex++; 103 | } 104 | 105 | String[] values = new String[nextArgIndex - switchIndex - 1]; 106 | for (int j = 0; j < values.length; j++) { 107 | values[j] = args[switchIndex + j + 1]; 108 | } 109 | return values; 110 | } 111 | 112 | public String[] targets() { 113 | String[] targetArray = new String[args.length - takenIndexes.size()]; 114 | int targetIndex = 0; 115 | for (int i = 0; i < args.length; i++) { 116 | if (!takenIndexes.contains(i)) { 117 | targetArray[targetIndex++] = args[i]; 118 | } 119 | } 120 | return targetArray; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/util/Filters.java: -------------------------------------------------------------------------------- 1 | package com.olegv.util; 2 | 3 | import spark.Filter; 4 | import spark.Request; 5 | import spark.Response; 6 | 7 | import static com.olegv.util.RequestUtil.getQueryLocale; 8 | 9 | public class Filters { 10 | 11 | // If a users manually manipulates paths and forgets to add 12 | // a trailing slash, redirect the users to the correct path 13 | public static Filter addTrailingSlashes = (Request request, Response response) -> { 14 | if (!request.pathInfo().endsWith("/")) { 15 | response.redirect(request.pathInfo() + "/"); 16 | } 17 | }; 18 | 19 | // Locale change can be initiated from any page 20 | // The locale is extracted from the request and saved to the users's session 21 | public static Filter handleLocaleChange = (Request request, Response response) -> { 22 | if (getQueryLocale(request) != null) { 23 | request.session().attribute("locale", getQueryLocale(request)); 24 | response.redirect(request.pathInfo()); 25 | } 26 | }; 27 | 28 | // Enable GZIP for all responses 29 | public static Filter addGzipHeader = (Request request, Response response) -> { 30 | response.header("Content-Encoding", "gzip"); 31 | }; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/util/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.olegv.util; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | 5 | import java.io.IOException; 6 | import java.io.StringWriter; 7 | 8 | public class JsonUtil { 9 | 10 | public static String dataToJson(Object data) { 11 | try { 12 | ObjectMapper mapper = new ObjectMapper(); 13 | // mapper.enable(SerializationFeature.INDENT_OUTPUT); 14 | StringWriter sw = new StringWriter(); 15 | mapper.writeValue(sw, data); 16 | return sw.toString(); 17 | } catch (IOException e) { 18 | throw new RuntimeException("IOException while mapping object (" + data + ") to JSON"); 19 | } 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/util/MessageBundle.java: -------------------------------------------------------------------------------- 1 | package com.olegv.util; 2 | 3 | import java.text.MessageFormat; 4 | import java.util.Locale; 5 | import java.util.ResourceBundle; 6 | 7 | /** 8 | * Message bundle for localization purposes. 9 | */ 10 | public class MessageBundle { 11 | 12 | private ResourceBundle messages; 13 | 14 | public MessageBundle(String languageTag) { 15 | Locale locale = languageTag != null ? new Locale(languageTag) : Locale.ENGLISH; 16 | this.messages = ResourceBundle.getBundle("localization/messages", locale); 17 | } 18 | 19 | public String get(String message) { 20 | return messages.getString(message); 21 | } 22 | 23 | public final String get(final String key, final Object... args) { 24 | return MessageFormat.format(get(key), args); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/util/Path.java: -------------------------------------------------------------------------------- 1 | package com.olegv.util; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * Holds all urls and template paths in one place. 7 | */ 8 | @SuppressWarnings("WeakerAccess") 9 | public class Path { 10 | 11 | // The @Getter methods are needed in orders to access the variables from Velocity Templates 12 | public static class Web { 13 | @Getter public static final String CONTROL_PANEL_DASHBOARD = "/controlpanel/"; 14 | @Getter public static final String CONTROL_PANEL_ORDERS = "/controlpanel/orders/"; 15 | @Getter public static final String CONTROL_PANEL_CUSTOMERS = "/controlpanel/customers/"; 16 | @Getter public static final String CONTROL_PANEL_CUSTOMERS_ONE = "/controlpanel/customers/:uid/"; 17 | @Getter public static final String CONTROL_PANEL_ACCOUNT = "/controlpanel/account/"; 18 | @Getter public static final String LOGIN = "/login/"; 19 | @Getter public static final String LOGOUT = "/logout/"; 20 | @Getter public static final String API_CUSTOMERS = "/api/customers/"; 21 | } 22 | 23 | public static class Template { 24 | public final static String CONTROL_PANEL_DASHBOARD = "/velocity/controlpanel/dashboard.vm"; 25 | public final static String CONTROL_PANEL_ORDERS = "/velocity/controlpanel/order/orders.vm"; 26 | public final static String CONTROL_PANEL_CUSTOMERS = "/velocity/controlpanel/customer/customers.vm"; 27 | public final static String CONTROL_PANEL_CUSTOMER_DETAILS = "/velocity/controlpanel/customer/customer_details.vm"; 28 | public final static String CONTROL_PANEL_ACCOUNT = "/velocity/controlpanel/account.vm"; 29 | public final static String LOGIN = "/velocity/login.vm"; 30 | public static final String NOT_FOUND = "/velocity/404.vm"; 31 | public static final String INTERNAL_SERVER_ERROR = "/velocity/500.vm"; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/util/RequestUtil.java: -------------------------------------------------------------------------------- 1 | package com.olegv.util; 2 | 3 | import spark.Request; 4 | 5 | @SuppressWarnings("WeakerAccess") 6 | public class RequestUtil { 7 | 8 | public static String getQueryLocale(Request request) { 9 | return request.queryParams("locale"); 10 | } 11 | 12 | public static String getPathParamUID(Request request) { 13 | return request.params("uid"); 14 | } 15 | 16 | public static String getQueryUsername(Request request) { 17 | return request.queryParams("email"); 18 | } 19 | 20 | public static String getQueryPassword(Request request) { 21 | return request.queryParams("password"); 22 | } 23 | 24 | public static String getQueryLoginRedirect(Request request) { 25 | return request.queryParams("loginRedirect"); 26 | } 27 | 28 | public static String getQueryPasswordOld(Request request) { 29 | return request.queryParams("password_old"); 30 | } 31 | 32 | public static String getQueryPasswordNew(Request request) { 33 | return request.queryParams("password_new"); 34 | } 35 | 36 | public static String getSessionLocale(Request request) { 37 | return request.session().attribute("locale"); 38 | } 39 | 40 | public static String getSessionCurrentUser(Request request) { 41 | return request.session().attribute("currentUser"); 42 | } 43 | 44 | public static String getSessionCurrentUserRole(Request request) {return request.session().attribute("currentUserRole");} 45 | 46 | public static boolean removeSessionAttrLoggedOut(Request request) { 47 | Object loggedOut = request.session().attribute("loggedOut"); 48 | request.session().removeAttribute("loggedOut"); 49 | return loggedOut != null; 50 | } 51 | 52 | public static String removeSessionAttrLoginRedirect(Request request) { 53 | String loginRedirect = request.session().attribute("loginRedirect"); 54 | request.session().removeAttribute("loginRedirect"); 55 | return loginRedirect; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/olegv/util/ViewUtil.java: -------------------------------------------------------------------------------- 1 | package com.olegv.util; 2 | 3 | import com.olegv.user.UserRole; 4 | import org.apache.velocity.app.VelocityEngine; 5 | import org.eclipse.jetty.http.HttpStatus; 6 | import spark.ModelAndView; 7 | import spark.Request; 8 | import spark.Response; 9 | import spark.Route; 10 | import spark.template.velocity.VelocityTemplateEngine; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | /** 16 | * Rendering view templates. Also class holds some request handlers without business logic, like 404 and 500 pages. 17 | */ 18 | public class ViewUtil { 19 | 20 | // Renders a template given a model and a request 21 | // The request is needed to check the users session for language settings 22 | // and to see if the users is logged in 23 | public static String render(Request request, Map model, String templatePath) { 24 | model.put("msg", new MessageBundle(RequestUtil.getSessionLocale(request))); 25 | model.put("currentUser", RequestUtil.getSessionCurrentUser(request)); 26 | model.put("currentUserRole", RequestUtil.getSessionCurrentUserRole(request)); 27 | model.put("WebPath", Path.Web.class); 28 | model.put("UserRole", UserRole.class); 29 | return strictVelocityEngine().render(new ModelAndView(model, templatePath)); 30 | } 31 | 32 | public static Route notAcceptable = (Request request, Response response) -> { 33 | response.status(HttpStatus.NOT_ACCEPTABLE_406); 34 | return new MessageBundle(RequestUtil.getSessionLocale(request)).get("ERROR_406_NOT_ACCEPTABLE"); 35 | }; 36 | 37 | public static Route notFound = (Request request, Response response) -> { 38 | response.status(HttpStatus.NOT_FOUND_404); 39 | return render(request, new HashMap<>(), Path.Template.NOT_FOUND); 40 | }; 41 | 42 | public static Route internalServerError = (Request request, Response response) -> { 43 | response.status(HttpStatus.INTERNAL_SERVER_ERROR_500); 44 | return render(request, new HashMap<>(), Path.Template.INTERNAL_SERVER_ERROR); 45 | }; 46 | 47 | private static VelocityTemplateEngine strictVelocityEngine() { 48 | VelocityEngine configuredEngine = new VelocityEngine(); 49 | configuredEngine.setProperty("runtime.references.strict", true); 50 | configuredEngine.setProperty("resource.loader", "class"); 51 | configuredEngine.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); 52 | return new VelocityTemplateEngine(configuredEngine); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/resources/localization/messages_de.properties: -------------------------------------------------------------------------------- 1 | ## Common 2 | COMMON_TITLE=Spark schablone 3 | COMMON_FOOTER_TEXT=Schlicht schablone projekt mit Spark und Skeleton. 4 | ERROR_406_NOT_ACCEPTABLE=No zuitable content found. Please zpecify eizer 'html/text' or 'application/json'. 5 | ERROR_404_NOT_FOUND=Hoppla! Wir können die Seite nicht finden, die Sie suchen. 6 | ERROR_404_NOT_FOUND_TITLE=404 - Seite nicht gefunden! 7 | ERROR_500_INTERNAL_SERVER_ERROR_TITLE=500 - Interner Serverfehler! 8 | ERROR_500_INTERNAL_SERVER_ERROR=Hoppla! Ein interner Serverfehler ist aufgetreten! 9 | COMMON_FOOTER_LANG_SELECT_LABEL=Sprache: 10 | NAV_DASHBOARD=Instrumententafel 11 | NAV_ORDERS=Bestellungen 12 | NAV_CUSTOMERS=Kunden 13 | NAV_ACCOUNT=Konto 14 | NAV_LOGOUT=Ausloggen 15 | ## Login 16 | LOGIN_HEADING=Innlogg 17 | LOGIN_AUTH_SUCCEEDED=You''re logged in as ''{0}''. 18 | LOGIN_AUTH_FAILED=Ze login informazion you zuplied vas incorrect. 19 | LOGIN_LOGGED_OUT=You have been logged aus. 20 | LOGIN_LABEL_USERNAME=Deine email 21 | LOGIN_LABEL_PASSWORD=Deine password 22 | LOGIN_BUTTON_LOGIN=Innlogg 23 | ## Customers 24 | CUSTOMERS_CUSTOMER_NOT_FOUND=Customer nicht found 25 | LOGIN_HEAD=Bitte loggen Sie sich ein 26 | ## Change Profile 27 | ACCOUNT_OLD_PASSWORD=Altes Passwort 28 | ACCOUNT_NEW_PASSWORD=Neues Passwort 29 | ACCOUNT_SAVE_BTN=Sparen 30 | ## Form errors 31 | INVALID_PASSWORD=Falsches aktuelles Passwort 32 | SAVED_SUCCESSFULLY=Erfolgreich gespeichert 33 | PASSWORD_TOO_SHORT=Neues Passwort ist zu kurz (mindestens 8 Zeichen) 34 | ## Page titles 35 | TITLE_ACCOUNT_SETTINGS=Konto Einstellungen 36 | TITLE_ORDERS=Bestellungen Seite 37 | TITLE_DASHBOARD=Instrumententafel Seite 38 | TITLE_CUSTOMERS=Kunden Seite 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/resources/localization/messages_en.properties: -------------------------------------------------------------------------------- 1 | ## Common 2 | COMMON_TITLE=Spark boilerplate 3 | COMMON_FOOTER_TEXT=Simple boilerplate project made with Spark and Skeleton. 4 | ERROR_406_NOT_ACCEPTABLE=No suitable content found. Please specify either 'html/text' or 'application/json'. 5 | ERROR_404_NOT_FOUND=Oops! We can't find the page you're looking for. 6 | ERROR_404_NOT_FOUND_TITLE=404 - Page Not Found! 7 | ERROR_500_INTERNAL_SERVER_ERROR_TITLE=500 - Internal Server Error! 8 | ERROR_500_INTERNAL_SERVER_ERROR=Oops! An internal server error occurred! 9 | COMMON_FOOTER_LANG_SELECT_LABEL=Language: 10 | NAV_DASHBOARD=Dashboard 11 | NAV_ORDERS=Orders 12 | NAV_CUSTOMERS=Customers 13 | NAV_ACCOUNT=Account 14 | NAV_LOGOUT=Logout 15 | ## Login 16 | LOGIN_HEADING=Login 17 | LOGIN_AUTH_SUCCEEDED=You''re logged in as ''{0}''. 18 | LOGIN_AUTH_FAILED=The login information you supplied was incorrect. 19 | LOGIN_LOGGED_OUT=You have been logged out. 20 | LOGIN_LABEL_USERNAME=Your email 21 | LOGIN_LABEL_PASSWORD=Your password 22 | LOGIN_BUTTON_LOGIN=Log in 23 | ## Customers 24 | CUSTOMERS_CUSTOMER_NOT_FOUND=Customer not found 25 | LOGIN_HEAD=Please login 26 | ## Change Profile 27 | ACCOUNT_OLD_PASSWORD=Old password 28 | ACCOUNT_NEW_PASSWORD=New password 29 | ACCOUNT_SAVE_BTN=Save 30 | ## Form errors 31 | INVALID_PASSWORD=Wrong current password 32 | SAVED_SUCCESSFULLY=Saved successfully 33 | PASSWORD_TOO_SHORT=New password is too short (minimum 8 symbols) 34 | ## Page titles 35 | TITLE_ACCOUNT_SETTINGS=Account settings 36 | TITLE_ORDERS=Orders page 37 | TITLE_DASHBOARD=Dashboard page 38 | TITLE_CUSTOMERS=Customers page 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/resources/public/css/footer.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | 5 | #wrapper { 6 | min-height: 100%; 7 | height: auto !important; 8 | height: 100%; 9 | margin: 0 auto -50px; 10 | } 11 | 12 | #bottom, #push { 13 | height: 50px; 14 | } 15 | 16 | footer { 17 | text-align: center; 18 | } 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/public/css/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | overflow-y: scroll; 3 | } 4 | .circular-img { 5 | border-radius: 50%; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/public/css/navbar.css: -------------------------------------------------------------------------------- 1 | .nav { 2 | background-color: #444; 3 | } 4 | 5 | .nav ul { 6 | list-style: none; 7 | padding: 0; 8 | margin: 0; 9 | } 10 | 11 | .nav li { 12 | text-align: center; 13 | font-size: 1.2em; 14 | border-bottom: 1px solid #888; 15 | margin-bottom: 0; 16 | } 17 | 18 | .nav a { 19 | text-decoration: none; 20 | color: #fff; 21 | display: block; 22 | transition: .4s background-color; 23 | padding: 0 15px 0 15px; 24 | cursor: pointer; 25 | } 26 | 27 | .nav a:hover { 28 | background-color: #33c3f0; 29 | } 30 | 31 | .nav a.active { 32 | background-color: #fff; 33 | color: #444; 34 | } 35 | 36 | @media screen and (min-width: 750px) { 37 | .nav li { 38 | border-bottom: none; 39 | font-size: 1.4em; 40 | } 41 | 42 | .nav a { 43 | padding: 0 15px 0 15px; 44 | } 45 | 46 | .nav .nav-dropdown-content a { 47 | text-align: right; 48 | } 49 | 50 | .nav li.nav-right-side { 51 | float: right; 52 | } 53 | 54 | .nav li { 55 | display: inline-block; 56 | margin-right: -4px; 57 | } 58 | 59 | } 60 | 61 | /* The container
- needed to position the dropdown content */ 62 | .nav-dropdown { 63 | position: relative; 64 | /*display: inline-block;*/ 65 | display: list-item; 66 | } 67 | 68 | /* Dropdown Content (Hidden by Default) */ 69 | .nav-dropdown-content { 70 | display: none; 71 | position: absolute; 72 | background-color: #444; 73 | /*box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);*/ 74 | z-index: 1; 75 | right: 0; 76 | width: 100%; 77 | } 78 | 79 | /* Links inside the dropdown */ 80 | .nav-dropdown-content a { 81 | text-decoration: none; 82 | display: block; 83 | text-align: center; 84 | } 85 | 86 | /* Change color of dropdown links on hover */ 87 | .nav-dropdown-content a:hover { 88 | background-color: #33c3f0; 89 | cursor: pointer; 90 | } 91 | 92 | /* Show the dropdown menu on hover */ 93 | .nav-dropdown:hover .nav-dropdown-content { 94 | display: block; 95 | } 96 | 97 | /* Change the background color of the dropdown button when the dropdown content is shown */ 98 | .nav-dropdown:hover .nav-dropbtn { 99 | /*background-color: #3e8e41;*/ 100 | } 101 | -------------------------------------------------------------------------------- /src/main/resources/public/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * users zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } 428 | -------------------------------------------------------------------------------- /src/main/resources/public/css/skeleton.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V2.0.4 3 | * Copyright 2014, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 12/29/2014 8 | */ 9 | 10 | 11 | /* Table of contents 12 | –––––––––––––––––––––––––––––––––––––––––––––––––– 13 | - Grid 14 | - Base Styles 15 | - Typography 16 | - Links 17 | - Buttons 18 | - Forms 19 | - Lists 20 | - Code 21 | - Tables 22 | - Spacing 23 | - Utilities 24 | - Clearing 25 | - Media Queries 26 | */ 27 | 28 | 29 | /* Grid 30 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 31 | .container { 32 | position: relative; 33 | width: 100%; 34 | max-width: 960px; 35 | margin: 0 auto; 36 | padding: 0 20px; 37 | box-sizing: border-box; } 38 | .column, 39 | .columns { 40 | width: 100%; 41 | float: left; 42 | box-sizing: border-box; } 43 | 44 | /* For devices larger than 400px */ 45 | @media (min-width: 400px) { 46 | .container { 47 | width: 85%; 48 | padding: 0; } 49 | } 50 | 51 | /* For devices larger than 550px */ 52 | @media (min-width: 550px) { 53 | .container { 54 | width: 80%; } 55 | .column, 56 | .columns { 57 | margin-left: 4%; } 58 | .column:first-child, 59 | .columns:first-child { 60 | margin-left: 0; } 61 | 62 | .one.column, 63 | .one.columns { width: 4.66666666667%; } 64 | .two.columns { width: 13.3333333333%; } 65 | .three.columns { width: 22%; } 66 | .four.columns { width: 30.6666666667%; } 67 | .five.columns { width: 39.3333333333%; } 68 | .six.columns { width: 48%; } 69 | .seven.columns { width: 56.6666666667%; } 70 | .eight.columns { width: 65.3333333333%; } 71 | .nine.columns { width: 74.0%; } 72 | .ten.columns { width: 82.6666666667%; } 73 | .eleven.columns { width: 91.3333333333%; } 74 | .twelve.columns { width: 100%; margin-left: 0; } 75 | 76 | .one-third.column { width: 30.6666666667%; } 77 | .two-thirds.column { width: 65.3333333333%; } 78 | 79 | .one-half.column { width: 48%; } 80 | 81 | /* Offsets */ 82 | .offset-by-one.column, 83 | .offset-by-one.columns { margin-left: 8.66666666667%; } 84 | .offset-by-two.column, 85 | .offset-by-two.columns { margin-left: 17.3333333333%; } 86 | .offset-by-three.column, 87 | .offset-by-three.columns { margin-left: 26%; } 88 | .offset-by-four.column, 89 | .offset-by-four.columns { margin-left: 34.6666666667%; } 90 | .offset-by-five.column, 91 | .offset-by-five.columns { margin-left: 43.3333333333%; } 92 | .offset-by-six.column, 93 | .offset-by-six.columns { margin-left: 52%; } 94 | .offset-by-seven.column, 95 | .offset-by-seven.columns { margin-left: 60.6666666667%; } 96 | .offset-by-eight.column, 97 | .offset-by-eight.columns { margin-left: 69.3333333333%; } 98 | .offset-by-nine.column, 99 | .offset-by-nine.columns { margin-left: 78.0%; } 100 | .offset-by-ten.column, 101 | .offset-by-ten.columns { margin-left: 86.6666666667%; } 102 | .offset-by-eleven.column, 103 | .offset-by-eleven.columns { margin-left: 95.3333333333%; } 104 | 105 | .offset-by-one-third.column, 106 | .offset-by-one-third.columns { margin-left: 34.6666666667%; } 107 | .offset-by-two-thirds.column, 108 | .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } 109 | 110 | .offset-by-one-half.column, 111 | .offset-by-one-half.columns { margin-left: 52%; } 112 | 113 | } 114 | 115 | 116 | /* Base Styles 117 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 118 | /* NOTE 119 | html is set to 62.5% so that all the REM measurements throughout Skeleton 120 | are based on 10px sizing. So basically 1.5rem = 15px :) */ 121 | html { 122 | font-size: 62.5%; } 123 | body { 124 | font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ 125 | line-height: 1.6; 126 | font-weight: 400; 127 | font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; 128 | color: #222; } 129 | 130 | 131 | /* Typography 132 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 133 | h1, h2, h3, h4, h5, h6 { 134 | margin-top: 0; 135 | margin-bottom: 2rem; 136 | font-weight: 300; } 137 | h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} 138 | h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } 139 | h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } 140 | h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } 141 | h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } 142 | h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } 143 | 144 | /* Larger than phablet */ 145 | @media (min-width: 550px) { 146 | h1 { font-size: 5.0rem; } 147 | h2 { font-size: 4.2rem; } 148 | h3 { font-size: 3.6rem; } 149 | h4 { font-size: 3.0rem; } 150 | h5 { font-size: 2.4rem; } 151 | h6 { font-size: 1.5rem; } 152 | } 153 | 154 | p { 155 | margin-top: 0; } 156 | 157 | 158 | /* Links 159 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 160 | a { 161 | color: #1EAEDB; } 162 | a:hover { 163 | color: #0FA0CE; } 164 | 165 | 166 | /* Buttons 167 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 168 | .button, 169 | button, 170 | input[type="submit"], 171 | input[type="reset"], 172 | input[type="button"] { 173 | display: inline-block; 174 | height: 38px; 175 | padding: 0 30px; 176 | color: #555; 177 | text-align: center; 178 | font-size: 11px; 179 | font-weight: 600; 180 | line-height: 38px; 181 | letter-spacing: .1rem; 182 | text-transform: uppercase; 183 | text-decoration: none; 184 | white-space: nowrap; 185 | background-color: transparent; 186 | border-radius: 4px; 187 | border: 1px solid #bbb; 188 | cursor: pointer; 189 | box-sizing: border-box; } 190 | .button:hover, 191 | button:hover, 192 | input[type="submit"]:hover, 193 | input[type="reset"]:hover, 194 | input[type="button"]:hover, 195 | .button:focus, 196 | button:focus, 197 | input[type="submit"]:focus, 198 | input[type="reset"]:focus, 199 | input[type="button"]:focus { 200 | color: #333; 201 | border-color: #888; 202 | outline: 0; } 203 | .button.button-primary, 204 | button.button-primary, 205 | input[type="submit"].button-primary, 206 | input[type="reset"].button-primary, 207 | input[type="button"].button-primary { 208 | color: #FFF; 209 | background-color: #33C3F0; 210 | border-color: #33C3F0; } 211 | .button.button-primary:hover, 212 | button.button-primary:hover, 213 | input[type="submit"].button-primary:hover, 214 | input[type="reset"].button-primary:hover, 215 | input[type="button"].button-primary:hover, 216 | .button.button-primary:focus, 217 | button.button-primary:focus, 218 | input[type="submit"].button-primary:focus, 219 | input[type="reset"].button-primary:focus, 220 | input[type="button"].button-primary:focus { 221 | color: #FFF; 222 | background-color: #1EAEDB; 223 | border-color: #1EAEDB; } 224 | 225 | 226 | /* Forms 227 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 228 | input[type="email"], 229 | input[type="number"], 230 | input[type="search"], 231 | input[type="text"], 232 | input[type="tel"], 233 | input[type="url"], 234 | input[type="password"], 235 | textarea, 236 | select { 237 | height: 38px; 238 | padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ 239 | background-color: #fff; 240 | border: 1px solid #D1D1D1; 241 | border-radius: 4px; 242 | box-shadow: none; 243 | box-sizing: border-box; } 244 | /* Removes awkward default styles on some inputs for iOS */ 245 | input[type="email"], 246 | input[type="number"], 247 | input[type="search"], 248 | input[type="text"], 249 | input[type="tel"], 250 | input[type="url"], 251 | input[type="password"], 252 | textarea { 253 | -webkit-appearance: none; 254 | -moz-appearance: none; 255 | appearance: none; } 256 | textarea { 257 | min-height: 65px; 258 | padding-top: 6px; 259 | padding-bottom: 6px; } 260 | input[type="email"]:focus, 261 | input[type="number"]:focus, 262 | input[type="search"]:focus, 263 | input[type="text"]:focus, 264 | input[type="tel"]:focus, 265 | input[type="url"]:focus, 266 | input[type="password"]:focus, 267 | textarea:focus, 268 | select:focus { 269 | border: 1px solid #33C3F0; 270 | outline: 0; } 271 | label, 272 | legend { 273 | display: block; 274 | margin-bottom: .5rem; 275 | font-weight: 600; } 276 | fieldset { 277 | padding: 0; 278 | border-width: 0; } 279 | input[type="checkbox"], 280 | input[type="radio"] { 281 | display: inline; } 282 | label > .label-body { 283 | display: inline-block; 284 | margin-left: .5rem; 285 | font-weight: normal; } 286 | 287 | 288 | /* Lists 289 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 290 | ul { 291 | list-style: circle inside; } 292 | ol { 293 | list-style: decimal inside; } 294 | ol, ul { 295 | padding-left: 0; 296 | margin-top: 0; } 297 | ul ul, 298 | ul ol, 299 | ol ol, 300 | ol ul { 301 | margin: 1.5rem 0 1.5rem 3rem; 302 | font-size: 90%; } 303 | li { 304 | margin-bottom: 1rem; } 305 | 306 | 307 | /* Code 308 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 309 | code { 310 | padding: .2rem .5rem; 311 | margin: 0 .2rem; 312 | font-size: 90%; 313 | white-space: nowrap; 314 | background: #F1F1F1; 315 | border: 1px solid #E1E1E1; 316 | border-radius: 4px; } 317 | pre > code { 318 | display: block; 319 | padding: 1rem 1.5rem; 320 | white-space: pre; } 321 | 322 | 323 | /* Tables 324 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 325 | th, 326 | td { 327 | padding: 12px 15px; 328 | text-align: left; 329 | border-bottom: 1px solid #E1E1E1; } 330 | th:first-child, 331 | td:first-child { 332 | padding-left: 0; } 333 | th:last-child, 334 | td:last-child { 335 | padding-right: 0; } 336 | 337 | 338 | /* Spacing 339 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 340 | button, 341 | .button { 342 | margin-bottom: 1rem; } 343 | input, 344 | textarea, 345 | select, 346 | fieldset { 347 | margin-bottom: 1.5rem; } 348 | pre, 349 | blockquote, 350 | dl, 351 | figure, 352 | table, 353 | p, 354 | ul, 355 | ol, 356 | form { 357 | margin-bottom: 2.5rem; } 358 | 359 | 360 | /* Utilities 361 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 362 | .u-full-width { 363 | width: 100%; 364 | box-sizing: border-box; } 365 | .u-max-full-width { 366 | max-width: 100%; 367 | box-sizing: border-box; } 368 | .u-pull-right { 369 | float: right; } 370 | .u-pull-left { 371 | float: left; } 372 | 373 | 374 | /* Misc 375 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 376 | hr { 377 | margin-top: 3rem; 378 | margin-bottom: 3.5rem; 379 | border-width: 0; 380 | border-top: 1px solid #E1E1E1; } 381 | 382 | 383 | /* Clearing 384 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 385 | 386 | /* Self Clearing Goodness */ 387 | .container:after, 388 | .row:after, 389 | .u-cf { 390 | content: ""; 391 | display: table; 392 | clear: both; } 393 | 394 | 395 | /* Media Queries 396 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 397 | /* 398 | Note: The best way to structure the use of media queries is to create the queries 399 | near the relevant code. For example, if you wanted to change the styles for buttons 400 | on small devices, paste the mobile query code up in the buttons section and style it 401 | there. 402 | */ 403 | 404 | 405 | /* Larger than mobile */ 406 | @media (min-width: 400px) {} 407 | 408 | /* Larger than phablet (also point when grid becomes active) */ 409 | @media (min-width: 550px) {} 410 | 411 | /* Larger than tablet */ 412 | @media (min-width: 750px) {} 413 | 414 | /* Larger than desktop */ 415 | @media (min-width: 1000px) {} 416 | 417 | /* Larger than Desktop HD */ 418 | @media (min-width: 1200px) {} 419 | -------------------------------------------------------------------------------- /src/main/resources/public/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oleg-vasiliev/sparkjava-boilerplate/036a503a4de0285e17b728fb1a84ff32575fe21e/src/main/resources/public/img/favicon.png -------------------------------------------------------------------------------- /src/main/resources/velocity/404.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #include("/velocity/partials/meta_partial.vm") 5 | 6 | $msg.get("ERROR_404_NOT_FOUND_TITLE") 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |

$msg.get("ERROR_404_NOT_FOUND_TITLE")

18 |

$msg.get("ERROR_404_NOT_FOUND")

19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/velocity/500.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #include("/velocity/partials/meta_partial.vm") 5 | 6 | $msg.get("ERROR_500_INTERNAL_SERVER_ERROR_TITLE") 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |

$msg.get("ERROR_500_INTERNAL_SERVER_ERROR_TITLE")

18 |

$msg.get("ERROR_500_INTERNAL_SERVER_ERROR")

19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/velocity/controlpanel/account.vm: -------------------------------------------------------------------------------- 1 | #set($activeMenuLink = "") 2 | #parse("/velocity/controlpanel/controlpanel_layout.vm") 3 | #@mainLayout() 4 |
5 |
6 |

$msg.get("TITLE_ACCOUNT_SETTINGS")

7 |
8 |
9 |
10 |
11 |
12 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 24 | 25 | #if($formMessage) 26 |
$msg.get($formMessage) 27 | #end 28 |
29 |
30 |
31 | 32 | #end 33 | -------------------------------------------------------------------------------- /src/main/resources/velocity/controlpanel/controlpanel_layout.vm: -------------------------------------------------------------------------------- 1 | #macro(mainLayout) 2 | 3 | 4 | 5 | #include("/velocity/partials/meta_partial.vm") 6 | 7 | $msg.get("COMMON_TITLE") 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | 61 |
62 |
63 |
64 | $bodyContent 65 |
66 |
67 |
68 |
69 |
70 | #parse("/velocity/partials/footer_partial.vm") 71 |
72 | 73 | 74 | #end 75 | -------------------------------------------------------------------------------- /src/main/resources/velocity/controlpanel/customer/customer_details.vm: -------------------------------------------------------------------------------- 1 | #set($activeMenuLink = $WebPath.getCONTROL_PANEL_CUSTOMERS()) 2 | #parse("/velocity/controlpanel/controlpanel_layout.vm") 3 | #@mainLayout() 4 |
5 |
6 |
7 | #if($customer) 8 | 9 |

$customer.getName()

10 |
$customer.getEmail()
11 |
$customer.getAddress()
12 |
$customer.getPhone()
13 | #else 14 |

$msg.get("CUSTOMERS_CUSTOMER_NOT_FOUND")

15 | #end 16 | Back 17 |
18 |
19 |
20 | 21 | #end 22 | -------------------------------------------------------------------------------- /src/main/resources/velocity/controlpanel/customer/customers.vm: -------------------------------------------------------------------------------- 1 | #set($activeMenuLink = $WebPath.getCONTROL_PANEL_CUSTOMERS()) 2 | #parse("/velocity/controlpanel/controlpanel_layout.vm") 3 | #@mainLayout() 4 |
5 |
6 |

$msg.get("TITLE_CUSTOMERS")

7 |

Pol, a bene devatio, demolitione! Flavum, salvus lumens rare transferre de mirabilis, velox gabalium. 8 | Resistentias mori! Est fatalis ratione, cesaris. Cum fiscina persuadere, omnes brabeutaes magicae audax, bi-color devatioes. 9 | Hercle, lapsus clemens!, plasmator! Cum navis experimentum, omnes galluses prensionem fidelis, emeritis medicinaes. 10 | nutrix. 11 |

12 |
13 |
14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | #foreach($customer in $customers) 27 | 28 | 29 | 30 | 31 | 32 | 33 | #end 34 | 35 |
First NamePhoneEmailAddress
$customer.name$customer.phone$customer.email$customer.address
36 | 37 |
38 |
39 | 40 | #end 41 | -------------------------------------------------------------------------------- /src/main/resources/velocity/controlpanel/dashboard.vm: -------------------------------------------------------------------------------- 1 | #set($activeMenuLink = $WebPath.getCONTROL_PANEL_DASHBOARD()) 2 | #parse("/velocity/controlpanel/controlpanel_layout.vm") 3 | #@mainLayout() 4 |
5 |
6 |

$msg.get("TITLE_DASHBOARD")

7 |

Ubi est bassus lumen? Secundus, domesticus gabaliums semper visum de mirabilis, nobilis solem. 8 | Lunas cadunt in peritus copinga! Sunt devirginatoes visum bi-color, nobilis eraes. Assimilant tandem ducunt ad pius ignigena. 9 | Cum capio tolerare, omnes cannabises imitari nobilis, bi-color vigiles! 10 |

11 |
12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 | 26 | 27 |
28 | #end 29 | -------------------------------------------------------------------------------- /src/main/resources/velocity/controlpanel/order/orders.vm: -------------------------------------------------------------------------------- 1 | #set($activeMenuLink = $WebPath.getCONTROL_PANEL_ORDERS()) 2 | #parse("/velocity/controlpanel/controlpanel_layout.vm") 3 | #@mainLayout() 4 |
5 |
6 |

$msg.get("TITLE_ORDERS")

7 |

Heu, hippotoxota! Urias congregabo! Pes regius verpa est. Velox, pius ventuss foris acquirere de teres, alter victrix. 8 | Cum index studere, omnes equisoes tractare audax, placidus plasmatores. 9 |

10 |
11 |
12 | #end 13 | -------------------------------------------------------------------------------- /src/main/resources/velocity/login.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #include("/velocity/partials/meta_partial.vm") 5 | 6 | $msg.get("LOGIN_HEADING") 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |
18 |
19 |

$msg.get("LOGIN_HEAD")

20 | 21 | ## 22 | 26 | 27 | 29 | 32 | 33 | #if($loginRedirect) 34 | 35 | #end 36 | #if($authenticationFailed) 37 |
$msg.get("LOGIN_AUTH_FAILED") 38 | #elseif($authenticationSucceeded) 39 |
$msg.get("LOGIN_AUTH_SUCCEEDED", $currentUser) 40 | #elseif($loggedOut) 41 |
$msg.get("LOGIN_LOGGED_OUT") 42 | #end 43 |
44 |
45 |
46 |
47 |
48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/main/resources/velocity/partials/footer_partial.vm: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | $msg.get("COMMON_FOOTER_LANG_SELECT_LABEL") English | Deutch 6 |
7 | $msg.get("COMMON_FOOTER_TEXT") 8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /src/main/resources/velocity/partials/meta_partial.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/velocityconfig/velocity_implicit.vm: -------------------------------------------------------------------------------- 1 | #* @implicitly included *# 2 | 3 | ## Controlpanel common things 4 | #* @vtlvariable name="WebPath" type="com.olegv.util.Path.Web" *# 5 | #* @vtlvariable name="UserRole" type="com.olegv.user.UserRole" *# 6 | #* @vtlvariable name="msg" type="com.olegv.util.MessageBundle" *# 7 | #* @vtlvariable name="currentUser" type="java.lang.String" *# 8 | #* @vtlvariable name="currentUserRole" type="java.lang.String" *# 9 | #* @vtlvariable name="activeMenuLink" type="java.lang.String" *# 10 | 11 | ## Authorization things 12 | #* @vtlvariable name="loggedOut" type="java.lang.String" *# 13 | #* @vtlvariable name="authenticationFailed" type="java.lang.String" *# 14 | #* @vtlvariable name="authenticationSucceeded" type="java.lang.String" *# 15 | #* @vtlvariable name="loginRedirect" type="java.lang.String" *# 16 | 17 | ## User profile 18 | #* @vtlvariable name="user" type="com.olegv.user.User" *# 19 | 20 | ## Customers 21 | #* @vtlvariable name="customers" type="java.lang.Iterable" *# 22 | #* @vtlvariable name="customer" type="com.olegv.customer.Customer" *# 23 | 24 | ## Common form errors/messages and validation 25 | #* @vtlvariable name="formMessage" type="java.lang.String" *# 26 | 27 | -------------------------------------------------------------------------------- /system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | --------------------------------------------------------------------------------