model, String templatePath) {
16 | model.put("msg", new MessageBundle(getSessionLocale(request)));
17 | model.put("currentUser", getSessionCurrentUser(request));
18 | model.put("WebPath", Path.Web.class); // Access application URLs from templates
19 | return strictVelocityEngine().render(new ModelAndView(model, templatePath));
20 | }
21 |
22 | public static Route notAcceptable = (Request request, Response response) -> {
23 | response.status(HttpStatus.NOT_ACCEPTABLE_406);
24 | return new MessageBundle(getSessionLocale(request)).get("ERROR_406_NOT_ACCEPTABLE");
25 | };
26 |
27 | public static Route notFound = (Request request, Response response) -> {
28 | response.status(HttpStatus.NOT_FOUND_404);
29 | return render(request, new HashMap<>(), Path.Template.NOT_FOUND);
30 | };
31 |
32 | private static VelocityTemplateEngine strictVelocityEngine() {
33 | VelocityEngine configuredEngine = new VelocityEngine();
34 | configuredEngine.setProperty("runtime.references.strict", true);
35 | configuredEngine.setProperty("resource.loader", "class");
36 | configuredEngine.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
37 | return new VelocityTemplateEngine(configuredEngine);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/resources/localization/messages_de.properties:
--------------------------------------------------------------------------------
1 | ## Common
2 | COMMON_TITLE=Ze Spark Library
3 | COMMON_FOOTER_TEXT=Ze application uses OpenLibrary vor images.
4 | COMMON_NAV_ALLBOOKS=View all ze books
5 | COMMON_NAV_LOGIN=Innlogg
6 | COMMON_NAV_LOGOUT=Auslogg
7 | ERROR_406_NOT_ACCEPTABLE=No zuitable content found. Please zpecify eizer 'html/text' or 'application/json'.
8 | ERROR_404_NOT_FOUND=Ve cannot find ze page you are looking for (error 404)
9 |
10 |
11 | ## Index
12 | INDEX_HEADING=Velcome to ze Spark Library
13 | INDEX_REGISTERED_USERS=Zere are currently {0} users registered:
14 | INDEX_PASSWORD_INFO=It seems zey have all chosen ze password "password" for some reason. Hov silly.
15 | INDEX_BOOK_OF_THE_DAY_TEXT=Ze book of ze day is:
16 | INDEX_BOOK_OF_THE_DAY_LINK={0} von {1}
17 |
18 |
19 | ## Login
20 | LOGIN_HEADING=Innlogg
21 | LOGIN_INSTRUCTIONS=Please enter dein username und password.
(Zee ze index page if you need a hint)
22 | LOGIN_AUTH_SUCCEEDED=You''re logged in as ''{0}''.
23 | LOGIN_AUTH_FAILED=Ze login informazion you zuplied vas incorrect.
24 | LOGIN_LOGGED_OUT=You have been logged aus.
25 | LOGIN_LABEL_USERNAME=Username
26 | LOGIN_LABEL_PASSWORD=Password
27 | LOGIN_BUTTON_LOGIN=Innlogg
28 |
29 |
30 | ## Books
31 | BOOKS_HEADING_ALL=All ze books
32 | BOOKS_CAPTION={0}
von {1}
33 | BOOKS_BOOK_NOT_FOUND=Book nicht found
34 |
--------------------------------------------------------------------------------
/src/main/resources/localization/messages_en.properties:
--------------------------------------------------------------------------------
1 | ## Common
2 | COMMON_TITLE=Spark Library
3 | COMMON_FOOTER_TEXT=This Application uses OpenLibrary for images.
4 | COMMON_NAV_ALLBOOKS=View all books
5 | COMMON_NAV_LOGIN=Log in
6 | COMMON_NAV_LOGOUT=Log out
7 | ERROR_406_NOT_ACCEPTABLE=No suitable content found. Please specify either 'html/text' or 'application/json'.
8 | ERROR_404_NOT_FOUND=We can't find the page you're looking for (error 404)
9 |
10 |
11 | ## Index
12 | INDEX_HEADING=Welcome to the Spark Library
13 | INDEX_REGISTERED_USERS=There are currently {0} users registered:
14 | INDEX_PASSWORD_INFO=It seems they've all chosen the password "password" for some reason. How silly.
15 | INDEX_BOOK_OF_THE_DAY_TEXT=The book of the day is:
16 | INDEX_BOOK_OF_THE_DAY_LINK={0} by {1}
17 |
18 |
19 | ## Login
20 | LOGIN_HEADING=Login
21 | LOGIN_INSTRUCTIONS=Please enter your username and password.
(See the index page if you need a hint)
22 | LOGIN_AUTH_SUCCEEDED=You''re logged in as ''{0}''.
23 | LOGIN_AUTH_FAILED=The login information you supplied was incorrect.
24 | LOGIN_LOGGED_OUT=You have been logged out.
25 | LOGIN_LABEL_USERNAME=Username
26 | LOGIN_LABEL_PASSWORD=Password
27 | LOGIN_BUTTON_LOGIN=Log in
28 |
29 |
30 | ## Books
31 | BOOKS_HEADING_ALL=All books
32 | BOOKS_CAPTION={0}
by {1}
33 | BOOKS_BOOK_NOT_FOUND=Book not found
34 |
--------------------------------------------------------------------------------
/src/main/resources/public/img/english.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tipsy/spark-basic-structure/5af6b78e0869dd2b2f014274ef731a2468ed0b8e/src/main/resources/public/img/english.png
--------------------------------------------------------------------------------
/src/main/resources/public/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tipsy/spark-basic-structure/5af6b78e0869dd2b2f014274ef731a2468ed0b8e/src/main/resources/public/img/favicon.png
--------------------------------------------------------------------------------
/src/main/resources/public/img/german.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tipsy/spark-basic-structure/5af6b78e0869dd2b2f014274ef731a2468ed0b8e/src/main/resources/public/img/german.png
--------------------------------------------------------------------------------
/src/main/resources/public/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tipsy/spark-basic-structure/5af6b78e0869dd2b2f014274ef731a2468ed0b8e/src/main/resources/public/img/logo.png
--------------------------------------------------------------------------------
/src/main/resources/public/main.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | html, body {
6 | margin: 0;
7 | padding: 0;
8 | font-family: Tahoma, Arial, sans-serif;
9 | position: relative;
10 | min-height: 100%;
11 | }
12 |
13 | body {
14 | padding: 0 0 40px;
15 | color: #333;
16 | background: #f9f9f9;
17 | }
18 |
19 | h1, h2, h3, h4 {
20 | font-family: monospace;
21 | font-weight: 300;
22 | color: #444;
23 | }
24 |
25 | small {
26 | color: #555;
27 | }
28 |
29 | header {
30 | background: #274555;
31 | border-bottom: 5px solid #ff7761;
32 | box-shadow: 0 1px 0 0 rgba(0,0,0,.6);
33 | }
34 |
35 | nav {
36 | padding: 15px;
37 | margin: 0 auto;
38 | max-width: 800px;
39 | position: relative;
40 | }
41 |
42 | nav #menu {
43 | margin-top: 20px;
44 | float: right;
45 | }
46 |
47 | a {
48 | text-decoration: none;
49 | color: #ff7761;
50 | }
51 |
52 | #menu li {
53 | float: left;
54 | margin: 0 10px;
55 | }
56 |
57 | #menu li a, #logout {
58 | background: transparent;
59 | cursor: pointer;
60 | border: 0;
61 | font-size: 16px;
62 | display: inline-block;
63 | color: #fff;
64 | text-align: center;
65 | height: 30px;
66 | line-height: 30px;
67 | padding: 0 10px;
68 | text-decoration: none;
69 | }
70 |
71 | #logo {
72 | max-height: 50px;
73 | }
74 |
75 | #chooseLanguage {
76 | top: 15px;
77 | right: 35px;
78 | position: absolute;
79 | }
80 |
81 | #chooseLanguage li {
82 | float: left;
83 | }
84 |
85 | #chooseLanguage button {
86 | cursor: pointer;
87 | margin-left: 8px;
88 | width: 18px;
89 | height: 18px;
90 | border-radius: 9px;
91 | opacity: 0.6;
92 | border: 1px solid #222;
93 | background-size: 100%;
94 | }
95 |
96 | #chooseLanguage button:hover {
97 | opacity: 0.8;
98 | }
99 |
100 | main {
101 | max-width: 800px;
102 | margin: 0 auto;
103 | padding: 15px;
104 | }
105 |
106 | #content {
107 | padding: 15px;
108 | background: #fff;
109 | box-shadow: 0 1px 5px rgba(0, 0, 0, 0.25);
110 | }
111 |
112 | footer {
113 | position: absolute;
114 | left: 0;
115 | bottom: 0;
116 | height: 40px;
117 | line-height: 40px;
118 | width: 100%;
119 | text-align: center;
120 | background: #fff;
121 | border-top: 1px solid #f9a11b;
122 | font-size: 14px;
123 | }
124 |
125 | nav ul, nav li {
126 | margin: 0;
127 | padding: 0;
128 | list-style-type: none;
129 | }
130 |
131 | /* Needlessly fancy menu hover effect */
132 |
133 | #menu li {
134 | position: relative;
135 | }
136 |
137 | #menu li a::after, #logout:after {
138 | position: absolute;
139 | top: 28px;
140 | left: 0;
141 | width: 100%;
142 | height: 4px;
143 | background: rgba(255, 255, 255, 0.5);
144 | border-radius: 5px;
145 | content: '';
146 | opacity: 0;
147 | -webkit-transition: opacity 0.3s, -webkit-transform 0.3s;
148 | transition: opacity 0.3s, transform 0.3s;
149 | -webkit-transform: translateY(10px);
150 | transform: translateY(10px);
151 | }
152 |
153 | #logout:hover::after,
154 | #logout:focus::after,
155 | #menu li a:hover::after,
156 | #menu li a:focus::after {
157 | opacity: 1;
158 | -webkit-transform: translateY(0px);
159 | transform: translateY(0px);
160 | }
161 |
162 | /* Very basic grid */
163 | .row {
164 | width: 100%;
165 | overflow: auto;
166 | }
167 |
168 | .row > * {
169 | float: left;
170 | }
171 |
172 | .row-2 .col {
173 | width: 49%
174 | }
175 |
176 | .row-2 .col:nth-child(odd) {
177 | margin: 0% 1% 0% 0%
178 | }
179 |
180 | .row-2 .col:nth-child(even) {
181 | margin: 0% 0% 0% 1%
182 | }
183 |
184 | .row-3 .col {
185 | width: 32%
186 | }
187 |
188 | .row-3 .col:nth-child(3n+1) {
189 | margin: 0% 1% 0% 0%
190 | }
191 |
192 | .row-3 .col:nth-child(3n+2) {
193 | margin: 0% 1% 0% 1%
194 | }
195 |
196 | .row-3 .col:nth-child(3n+3) {
197 | margin: 0% 0% 0% 1%
198 | }
199 |
200 | @media screen and (max-width: 550px) {
201 | .row .col:nth-child(n) {
202 | width: 100%;
203 | margin-right: 0;
204 | margin-left: 0;
205 | }
206 | }
207 |
208 | .col img {
209 | display: block;
210 | width: 100%;
211 | }
212 |
213 | /* Book related stuff */
214 |
215 | a.book {
216 | display: block;
217 | text-align: center;
218 | text-decoration: none;
219 | color: #333;
220 | padding: 10px;
221 | border-radius: 5px;
222 | }
223 |
224 | a.book:hover {
225 | background: #e2e9f5;
226 | }
227 |
228 | a .bookCover {
229 | padding: 10px;
230 | display: flex;
231 | align-items: center;
232 | justify-content: center;
233 | }
234 |
235 | a .bookCover img {
236 | border-radius: 5px;
237 | min-height: 200px;
238 | max-height: 200px;
239 | width: auto;
240 | }
241 |
242 | .bookCover img {
243 | margin-top: 20px;
244 | border-radius: 10px;
245 | width: 100%;
246 | }
247 |
248 | /* Login Form */
249 |
250 | #loginForm {
251 | max-width: 400px;
252 | margin: 0 auto;
253 | }
254 |
255 | #loginForm label {
256 | display: block;
257 | width: 100%
258 | }
259 |
260 | #loginForm input {
261 | border: 1px solid #ddd;
262 | padding: 8px 12px;
263 | width: 100%;
264 | border-radius: 3px;
265 | margin: 2px 0 20px 0;
266 | }
267 |
268 | #loginForm input[type="submit"] {
269 | color: white;
270 | background: #274555;
271 | border: 0;
272 | cursor: pointer;
273 | }
274 |
275 | .notification {
276 | padding: 10px;
277 | background: #333;
278 | color: white;
279 | border-radius: 3px;
280 | }
281 |
282 | .good.notification {
283 | background: #008900;
284 | }
285 |
286 | .bad.notification {
287 | background: #bb0000;
288 | }
289 |
--------------------------------------------------------------------------------
/src/main/resources/velocity/book/all.vm:
--------------------------------------------------------------------------------
1 | #parse("/velocity/layout.vm")
2 | #@mainLayout()
3 | $msg.get("BOOKS_HEADING_ALL")
4 |
5 | #foreach($book in $books)
6 |
14 | #end
15 |
16 | #end
17 |
--------------------------------------------------------------------------------
/src/main/resources/velocity/book/one.vm:
--------------------------------------------------------------------------------
1 | #parse("/velocity/layout.vm")
2 | #@mainLayout()
3 | #if($book)
4 | $book.getTitle()
5 | $book.getAuthor()
6 |
7 |
8 |
)
9 |
10 |
11 | #else
12 | $msg.get("BOOKS_BOOK_NOT_FOUND")
13 | #end
14 | #end
15 |
--------------------------------------------------------------------------------
/src/main/resources/velocity/index/index.vm:
--------------------------------------------------------------------------------
1 | #parse("/velocity/layout.vm")
2 | #@mainLayout()
3 | $msg.get("INDEX_HEADING")
4 | $msg.get("INDEX_REGISTERED_USERS", $users.size())
5 |
6 | #foreach($user in $users)
7 | - $user
8 | #end
9 |
10 | $msg.get("INDEX_PASSWORD_INFO")
11 | #if($book)
12 | $msg.get("INDEX_BOOK_OF_THE_DAY_TEXT")
13 |
14 |
17 |
18 |
)
19 |
20 |
21 | #end
22 | #end
23 |
--------------------------------------------------------------------------------
/src/main/resources/velocity/layout.vm:
--------------------------------------------------------------------------------
1 | #macro(mainLayout)
2 |
3 |
4 | $msg.get("COMMON_TITLE")
5 |
6 |
7 |
8 |
9 |
10 |
11 |
36 |
37 |
38 |
39 | $bodyContent
40 |
41 |
42 |
45 |
46 |
47 | #end
48 |
--------------------------------------------------------------------------------
/src/main/resources/velocity/login/login.vm:
--------------------------------------------------------------------------------
1 | #parse("/velocity/layout.vm")
2 | #@mainLayout()
3 |
22 | #end
23 |
--------------------------------------------------------------------------------
/src/main/resources/velocity/notFound.vm:
--------------------------------------------------------------------------------
1 | #parse("/velocity/layout.vm")
2 | #@mainLayout()
3 | $msg.get("ERROR_404_NOT_FOUND")
4 | #end
5 |
--------------------------------------------------------------------------------
/src/main/resources/velocityconfig/velocity_implicit.vm:
--------------------------------------------------------------------------------
1 | #* @implicitly included *#
2 | #* @vtlvariable name="WebPath" type="app.util.Path.Web" *#
3 | #* @vtlvariable name="msg" type="app.util.MessageBundle" *#
4 | #* @vtlvariable name="books" type="java.lang.Iterable" *#
5 | #* @vtlvariable name="book" type="app.book.Book" *#
6 | #* @vtlvariable name="users" type="java.lang.Iterable" *#
7 | #* @vtlvariable name="currentUser" type="java.lang.String" *#
8 | #* @vtlvariable name="loggedOut" type="java.lang.String" *#
9 | #* @vtlvariable name="authenticationFailed" type="java.lang.String" *#
10 | #* @vtlvariable name="authenticationSucceeded" type="java.lang.String" *#
11 | #* @vtlvariable name="loginRedirect" type="java.lang.String" *#
12 |
--------------------------------------------------------------------------------