kopia lustrzana https://github.com/piku/piku
rodzic
817f912994
commit
e7654ff930
|
@ -1,135 +0,0 @@
|
|||
# Created by https://www.gitignore.io
|
||||
|
||||
### Windows ###
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
|
||||
### OSX ###
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
|
||||
### Intellij ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
|
||||
|
||||
*.iml
|
||||
|
||||
## Directory-based project format:
|
||||
.idea/
|
||||
# if you remove the above rule, at least ignore the following:
|
||||
|
||||
# User-specific stuff:
|
||||
# .idea/workspace.xml
|
||||
# .idea/tasks.xml
|
||||
# .idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
# .idea/dataSources.ids
|
||||
# .idea/dataSources.xml
|
||||
# .idea/sqlDataSources.xml
|
||||
# .idea/dynamic.xml
|
||||
# .idea/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
# .idea/gradle.xml
|
||||
# .idea/libraries
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
# .idea/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
|
||||
|
||||
### Java ###
|
||||
*.class
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
target
|
||||
|
||||
|
||||
# Created by https://www.gitignore.io/api/gradle
|
||||
|
||||
### Gradle ###
|
||||
.gradle
|
||||
build/
|
||||
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||
!gradle-wrapper.jar
|
||||
|
||||
# Cache of project
|
||||
.gradletasknamecache
|
||||
|
||||
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
|
||||
# gradle/wrapper/gradle-wrapper.properties
|
||||
# Avoid Maven target files
|
||||
target/
|
|
@ -1,5 +0,0 @@
|
|||
AUTORESTART=1
|
||||
RANGE=10
|
||||
PORT=6500
|
||||
BIND_ADDRESS=0.0.0.0
|
||||
INTERVAL=1
|
|
@ -1,202 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2015 David Åse
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -1 +0,0 @@
|
|||
jwsgi: mvn exec:java -Dexec.mainClass=app.Application
|
|
@ -1,30 +0,0 @@
|
|||
# spark-basic-structure
|
||||
|
||||
[![asciicast](https://asciinema.org/a/257670.svg)](https://asciinema.org/a/257670)
|
||||
|
||||
Java on Piku Micro PaaS on Ubuntu 18 and Linux Mint 19
|
||||
This is an example of one possible way of structuring a Spark application.
|
||||
|
||||
This is a simple Java app that demonstrates deploying your apps on Piku.
|
||||
|
||||
To publish this app to `piku`, make a copy of this folder and run the following commands:
|
||||
|
||||
```bash
|
||||
cd sparkAppp_copy
|
||||
git init .
|
||||
git remote add piku piku@your_server:sparkApp
|
||||
git add .
|
||||
git commit -a -m "initial commit"
|
||||
git push piku master
|
||||
```
|
||||
|
||||
The application has filters, controllers, views, authentication, localization, error handling, and more.
|
||||
It contains the source code for the tutorial found at https://sparktutorials.github.io/2016/06/10/spark-basic-structure.html
|
||||
|
||||
## Some Issues
|
||||
Default procfile should look exactly like this one.
|
||||
The live config needs the app directory under virtual environment.... Please do not specify the virtual environment in the ENV file.
|
||||
|
||||
|
||||
## Screenshot
|
||||
![Application Screenshot](https://sparktutorials.github.io/img/posts/sparkBasicStructure/screenshot.png)
|
|
@ -1,81 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>spark-basic-structure</groupId>
|
||||
<artifactId>spark-basic-structure</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>1.2.1</version>
|
||||
<configuration>
|
||||
<mainClass>app.Application</mainClass>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.sparkjava</groupId>
|
||||
<artifactId>spark-core</artifactId>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sparkjava</groupId>
|
||||
<artifactId>spark-debug-tools</artifactId>
|
||||
<version>0.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sparkjava</groupId>
|
||||
<artifactId>spark-template-velocity</artifactId>
|
||||
<version>2.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.7.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.16.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.5.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>19.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mindrot</groupId>
|
||||
<artifactId>jbcrypt</artifactId>
|
||||
<version>0.3m</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -1,47 +0,0 @@
|
|||
package app;
|
||||
|
||||
import app.book.*;
|
||||
import app.index.*;
|
||||
import app.login.*;
|
||||
import app.user.*;
|
||||
import app.util.*;
|
||||
import static spark.Spark.*;
|
||||
import static spark.debug.DebugScreen.*;
|
||||
|
||||
public class Application {
|
||||
|
||||
// Declare dependencies
|
||||
public static BookDao bookDao;
|
||||
public static UserDao userDao;
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
// Instantiate your dependencies
|
||||
bookDao = new BookDao();
|
||||
userDao = new UserDao();
|
||||
|
||||
// Configure Spark
|
||||
port(6500);
|
||||
staticFiles.location("/public");
|
||||
staticFiles.expireTime(600L);
|
||||
enableDebugScreen();
|
||||
|
||||
// Set up before-filters (called before each get/post)
|
||||
before("*", Filters.addTrailingSlashes);
|
||||
before("*", Filters.handleLocaleChange);
|
||||
|
||||
// Set up routes
|
||||
get(Path.Web.INDEX, IndexController.serveIndexPage);
|
||||
get(Path.Web.BOOKS, BookController.fetchAllBooks);
|
||||
get(Path.Web.ONE_BOOK, BookController.fetchOneBook);
|
||||
get(Path.Web.LOGIN, LoginController.serveLoginPage);
|
||||
post(Path.Web.LOGIN, LoginController.handleLoginPost);
|
||||
post(Path.Web.LOGOUT, LoginController.handleLogoutPost);
|
||||
get("*", ViewUtil.notFound);
|
||||
|
||||
//Set up after-filters (called after each get/post)
|
||||
after("*", Filters.addGzipHeader);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package app.book;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
@Value // All fields are private and final. Getters (but not setters) are generated (https://projectlombok.org/features/Value.html)
|
||||
public class Book {
|
||||
String title;
|
||||
String author;
|
||||
String isbn;
|
||||
|
||||
public String getMediumCover() {
|
||||
return "http://covers.openlibrary.org/b/isbn/" + this.isbn + "-M.jpg";
|
||||
}
|
||||
|
||||
public String getLargeCover() {
|
||||
return "http://covers.openlibrary.org/b/isbn/" + this.isbn + "-L.jpg";
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package app.book;
|
||||
|
||||
import app.login.*;
|
||||
import app.util.*;
|
||||
import spark.*;
|
||||
import java.util.*;
|
||||
import static app.Application.bookDao;
|
||||
import static app.util.JsonUtil.*;
|
||||
import static app.util.RequestUtil.*;
|
||||
|
||||
public class BookController {
|
||||
|
||||
public static Route fetchAllBooks = (Request request, Response response) -> {
|
||||
LoginController.ensureUserIsLoggedIn(request, response);
|
||||
if (clientAcceptsHtml(request)) {
|
||||
HashMap<String, Object> model = new HashMap<>();
|
||||
model.put("books", bookDao.getAllBooks());
|
||||
return ViewUtil.render(request, model, Path.Template.BOOKS_ALL);
|
||||
}
|
||||
if (clientAcceptsJson(request)) {
|
||||
return dataToJson(bookDao.getAllBooks());
|
||||
}
|
||||
return ViewUtil.notAcceptable.handle(request, response);
|
||||
};
|
||||
|
||||
public static Route fetchOneBook = (Request request, Response response) -> {
|
||||
LoginController.ensureUserIsLoggedIn(request, response);
|
||||
if (clientAcceptsHtml(request)) {
|
||||
HashMap<String, Object> model = new HashMap<>();
|
||||
Book book = bookDao.getBookByIsbn(getParamIsbn(request));
|
||||
model.put("book", book);
|
||||
return ViewUtil.render(request, model, Path.Template.BOOKS_ONE);
|
||||
}
|
||||
if (clientAcceptsJson(request)) {
|
||||
return dataToJson(bookDao.getBookByIsbn(getParamIsbn(request)));
|
||||
}
|
||||
return ViewUtil.notAcceptable.handle(request, response);
|
||||
};
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package app.book;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import java.util.*;
|
||||
|
||||
public class BookDao {
|
||||
|
||||
private final List<Book> books = ImmutableList.of(
|
||||
new Book("Moby Dick", "Herman Melville", "9789583001215"),
|
||||
new Book("A Christmas Carol", "Charles Dickens", "9780141324524"),
|
||||
new Book("Pride and Prejudice", "Jane Austen", "9781936594290"),
|
||||
new Book("The Fellowship of The Ring", "J. R. R. Tolkien", "0007171978"),
|
||||
new Book("Harry Potter", "J. K. Rowling", "0747532699"),
|
||||
new Book("War and Peace", "Leo Tolstoy", "9780060798871"),
|
||||
new Book("Don Quixote", "Miguel Cervantes", "9789626345221"),
|
||||
new Book("Ulysses", "James Joyce", "9780394703800"),
|
||||
new Book("The Great Gatsby", "F. Scott Fitzgerald", "9780743273565"),
|
||||
new Book("One Hundred Years of Solitude", "Gabriel Garcia Marquez", "9780060531041"),
|
||||
new Book("The adventures of Huckleberry Finn", "Mark Twain", "9781591940296"),
|
||||
new Book("Alice In Wonderland", "Lewis Carrol", "9780439291491")
|
||||
);
|
||||
|
||||
public Iterable<Book> getAllBooks() {
|
||||
return books;
|
||||
}
|
||||
|
||||
public Book getBookByIsbn(String isbn) {
|
||||
return books.stream().filter(b -> b.getIsbn().equals(isbn)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public Book getRandomBook() {
|
||||
return books.get(new Random().nextInt(books.size()));
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package app.index;
|
||||
|
||||
import app.util.*;
|
||||
import spark.*;
|
||||
import java.util.*;
|
||||
import static app.Application.*;
|
||||
|
||||
public class IndexController {
|
||||
public static Route serveIndexPage = (Request request, Response response) -> {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("users", userDao.getAllUserNames());
|
||||
model.put("book", bookDao.getRandomBook());
|
||||
return ViewUtil.render(request, model, Path.Template.INDEX);
|
||||
};
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
package app.login;
|
||||
|
||||
import app.user.*;
|
||||
import app.util.*;
|
||||
import spark.*;
|
||||
import java.util.*;
|
||||
import static app.util.RequestUtil.*;
|
||||
|
||||
public class LoginController {
|
||||
|
||||
public static Route serveLoginPage = (Request request, Response response) -> {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("loggedOut", removeSessionAttrLoggedOut(request));
|
||||
model.put("loginRedirect", removeSessionAttrLoginRedirect(request));
|
||||
return ViewUtil.render(request, model, Path.Template.LOGIN);
|
||||
};
|
||||
|
||||
public static Route handleLoginPost = (Request request, Response response) -> {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
if (!UserController.authenticate(getQueryUsername(request), getQueryPassword(request))) {
|
||||
model.put("authenticationFailed", true);
|
||||
return ViewUtil.render(request, model, Path.Template.LOGIN);
|
||||
}
|
||||
model.put("authenticationSucceeded", true);
|
||||
request.session().attribute("currentUser", getQueryUsername(request));
|
||||
if (getQueryLoginRedirect(request) != null) {
|
||||
response.redirect(getQueryLoginRedirect(request));
|
||||
}
|
||||
return ViewUtil.render(request, model, Path.Template.LOGIN);
|
||||
};
|
||||
|
||||
public static Route handleLogoutPost = (Request request, Response response) -> {
|
||||
request.session().removeAttribute("currentUser");
|
||||
request.session().attribute("loggedOut", true);
|
||||
response.redirect(Path.Web.LOGIN);
|
||||
return null;
|
||||
};
|
||||
|
||||
// The origin of the request (request.pathInfo()) is saved in the session so
|
||||
// the user can be redirected back after login
|
||||
public static void ensureUserIsLoggedIn(Request request, Response response) {
|
||||
if (request.session().attribute("currentUser") == null) {
|
||||
request.session().attribute("loginRedirect", request.pathInfo());
|
||||
response.redirect(Path.Web.LOGIN);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package app.user;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
@Value // All fields are private and final. Getters (but not setters) are generated (https://projectlombok.org/features/Value.html)
|
||||
public class User {
|
||||
String username;
|
||||
String salt;
|
||||
String hashedPassword;
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package app.user;
|
||||
|
||||
import org.mindrot.jbcrypt.*;
|
||||
import static app.Application.userDao;
|
||||
|
||||
public class UserController {
|
||||
|
||||
// Authenticate the user by hashing the inputted password using the stored salt,
|
||||
// then comparing the generated hashed password to the stored hashed password
|
||||
public static boolean authenticate(String username, String password) {
|
||||
if (username.isEmpty() || password.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
User user = userDao.getUserByUsername(username);
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
String hashedPassword = BCrypt.hashpw(password, user.getSalt());
|
||||
return hashedPassword.equals(user.getHashedPassword());
|
||||
}
|
||||
|
||||
// This method doesn't do anything, it's just included as an example
|
||||
public static void setPassword(String username, String oldPassword, String newPassword) {
|
||||
if (authenticate(username, oldPassword)) {
|
||||
String newSalt = BCrypt.gensalt();
|
||||
String newHashedPassword = BCrypt.hashpw(newSalt, newPassword);
|
||||
// Update the user salt and password
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package app.user;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.*;
|
||||
|
||||
public class UserDao {
|
||||
|
||||
private final List<User> users = ImmutableList.of(
|
||||
// Username Salt for hash Hashed password (the password is "password" for all users)
|
||||
new User("perwendel", "$2a$10$h.dl5J86rGH7I8bD9bZeZe", "$2a$10$h.dl5J86rGH7I8bD9bZeZeci0pDt0.VwFTGujlnEaZXPf/q7vM5wO"),
|
||||
new User("davidase", "$2a$10$e0MYzXyjpJS7Pd0RVvHwHe", "$2a$10$e0MYzXyjpJS7Pd0RVvHwHe1HlCS4bZJ18JuywdEMLT83E1KDmUhCy"),
|
||||
new User("federico", "$2a$10$E3DgchtVry3qlYlzJCsyxe", "$2a$10$E3DgchtVry3qlYlzJCsyxeSK0fftK4v0ynetVCuDdxGVl1obL.ln2")
|
||||
);
|
||||
|
||||
public User getUserByUsername(String username) {
|
||||
return users.stream().filter(b -> b.getUsername().equals(username)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public Iterable<String> getAllUserNames() {
|
||||
return users.stream().map(User::getUsername).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package app.util;
|
||||
|
||||
import spark.*;
|
||||
import static app.util.RequestUtil.*;
|
||||
|
||||
public class Filters {
|
||||
|
||||
// If a user manually manipulates paths and forgets to add
|
||||
// a trailing slash, redirect the user to the correct path
|
||||
public static Filter addTrailingSlashes = (Request request, Response response) -> {
|
||||
if (!request.pathInfo().endsWith("/")) {
|
||||
response.redirect(request.pathInfo() + "/");
|
||||
}
|
||||
};
|
||||
|
||||
// Locale change can be initiated from any page
|
||||
// The locale is extracted from the request and saved to the user's session
|
||||
public static Filter handleLocaleChange = (Request request, Response response) -> {
|
||||
if (getQueryLocale(request) != null) {
|
||||
request.session().attribute("locale", getQueryLocale(request));
|
||||
response.redirect(request.pathInfo());
|
||||
}
|
||||
};
|
||||
|
||||
// Enable GZIP for all responses
|
||||
public static Filter addGzipHeader = (Request request, Response response) -> {
|
||||
response.header("Content-Encoding", "gzip");
|
||||
};
|
||||
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package app.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import java.io.*;
|
||||
|
||||
public class JsonUtil {
|
||||
public static String dataToJson(Object data) {
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
StringWriter sw = new StringWriter();
|
||||
mapper.writeValue(sw, data);
|
||||
return sw.toString();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("IOEXception while mapping object (" + data + ") to JSON");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package app.util;
|
||||
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
|
||||
public class MessageBundle {
|
||||
|
||||
private ResourceBundle messages;
|
||||
|
||||
public MessageBundle(String languageTag) {
|
||||
Locale locale = languageTag != null ? new Locale(languageTag) : Locale.ENGLISH;
|
||||
this.messages = ResourceBundle.getBundle("localization/messages", locale);
|
||||
}
|
||||
|
||||
public String get(String message) {
|
||||
return messages.getString(message);
|
||||
}
|
||||
|
||||
public final String get(final String key, final Object... args) {
|
||||
return MessageFormat.format(get(key), args);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package app.util;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
public class Path {
|
||||
|
||||
// The @Getter methods are needed in order to access
|
||||
// the variables from Velocity Templates
|
||||
public static class Web {
|
||||
@Getter public static final String INDEX = "/index/";
|
||||
@Getter public static final String LOGIN = "/login/";
|
||||
@Getter public static final String LOGOUT = "/logout/";
|
||||
@Getter public static final String BOOKS = "/books/";
|
||||
@Getter public static final String ONE_BOOK = "/books/:isbn/";
|
||||
}
|
||||
|
||||
public static class Template {
|
||||
public final static String INDEX = "/velocity/index/index.vm";
|
||||
public final static String LOGIN = "/velocity/login/login.vm";
|
||||
public final static String BOOKS_ALL = "/velocity/book/all.vm";
|
||||
public static final String BOOKS_ONE = "/velocity/book/one.vm";
|
||||
public static final String NOT_FOUND = "/velocity/notFound.vm";
|
||||
}
|
||||
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package app.util;
|
||||
|
||||
import spark.*;
|
||||
|
||||
public class RequestUtil {
|
||||
|
||||
public static String getQueryLocale(Request request) {
|
||||
return request.queryParams("locale");
|
||||
}
|
||||
|
||||
public static String getParamIsbn(Request request) {
|
||||
return request.params("isbn");
|
||||
}
|
||||
|
||||
public static String getQueryUsername(Request request) {
|
||||
return request.queryParams("username");
|
||||
}
|
||||
|
||||
public static String getQueryPassword(Request request) {
|
||||
return request.queryParams("password");
|
||||
}
|
||||
|
||||
public static String getQueryLoginRedirect(Request request) {
|
||||
return request.queryParams("loginRedirect");
|
||||
}
|
||||
|
||||
public static String getSessionLocale(Request request) {
|
||||
return request.session().attribute("locale");
|
||||
}
|
||||
|
||||
public static String getSessionCurrentUser(Request request) {
|
||||
return request.session().attribute("currentUser");
|
||||
}
|
||||
|
||||
public static boolean removeSessionAttrLoggedOut(Request request) {
|
||||
Object loggedOut = request.session().attribute("loggedOut");
|
||||
request.session().removeAttribute("loggedOut");
|
||||
return loggedOut != null;
|
||||
}
|
||||
|
||||
public static String removeSessionAttrLoginRedirect(Request request) {
|
||||
String loginRedirect = request.session().attribute("loginRedirect");
|
||||
request.session().removeAttribute("loginRedirect");
|
||||
return loginRedirect;
|
||||
}
|
||||
|
||||
public static boolean clientAcceptsHtml(Request request) {
|
||||
String accept = request.headers("Accept");
|
||||
return accept != null && accept.contains("text/html");
|
||||
}
|
||||
|
||||
public static boolean clientAcceptsJson(Request request) {
|
||||
String accept = request.headers("Accept");
|
||||
return accept != null && accept.contains("application/json");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package app.util;
|
||||
|
||||
import org.apache.velocity.app.*;
|
||||
import org.eclipse.jetty.http.*;
|
||||
import spark.*;
|
||||
import spark.template.velocity.*;
|
||||
import java.util.*;
|
||||
import static app.util.RequestUtil.*;
|
||||
|
||||
public class ViewUtil {
|
||||
|
||||
// Renders a template given a model and a request
|
||||
// The request is needed to check the user session for language settings
|
||||
// and to see if the user is logged in
|
||||
public static String render(Request request, Map<String, Object> model, String templatePath) {
|
||||
model.put("msg", new MessageBundle(getSessionLocale(request)));
|
||||
model.put("currentUser", getSessionCurrentUser(request));
|
||||
model.put("WebPath", Path.Web.class); // Access application URLs from templates
|
||||
return strictVelocityEngine().render(new ModelAndView(model, templatePath));
|
||||
}
|
||||
|
||||
public static Route notAcceptable = (Request request, Response response) -> {
|
||||
response.status(HttpStatus.NOT_ACCEPTABLE_406);
|
||||
return new MessageBundle(getSessionLocale(request)).get("ERROR_406_NOT_ACCEPTABLE");
|
||||
};
|
||||
|
||||
public static Route notFound = (Request request, Response response) -> {
|
||||
response.status(HttpStatus.NOT_FOUND_404);
|
||||
return render(request, new HashMap<>(), Path.Template.NOT_FOUND);
|
||||
};
|
||||
|
||||
private static VelocityTemplateEngine strictVelocityEngine() {
|
||||
VelocityEngine configuredEngine = new VelocityEngine();
|
||||
configuredEngine.setProperty("runtime.references.strict", true);
|
||||
configuredEngine.setProperty("resource.loader", "class");
|
||||
configuredEngine.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
|
||||
return new VelocityTemplateEngine(configuredEngine);
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
## Common
|
||||
COMMON_TITLE=Ze Spark Library
|
||||
COMMON_FOOTER_TEXT=Ze application uses <a href="https://openlibrary.org/" target="_blank">OpenLibrary</a> vor images.
|
||||
COMMON_NAV_ALLBOOKS=View all ze books
|
||||
COMMON_NAV_LOGIN=Innlogg
|
||||
COMMON_NAV_LOGOUT=Auslogg
|
||||
ERROR_406_NOT_ACCEPTABLE=No zuitable content found. Please zpecify eizer 'html/text' or 'application/json'.
|
||||
ERROR_404_NOT_FOUND=Ve cannot find ze page you are looking for <small>(error 404)</small>
|
||||
|
||||
|
||||
## Index
|
||||
INDEX_HEADING=Velcome to ze Spark Library
|
||||
INDEX_REGISTERED_USERS=Zere are currently {0} users registered:
|
||||
INDEX_PASSWORD_INFO=It seems zey have all chosen ze password "password" for some reason. Hov silly.
|
||||
INDEX_BOOK_OF_THE_DAY_TEXT=Ze book of ze day is:
|
||||
INDEX_BOOK_OF_THE_DAY_LINK=<strong>{0} von {1}</strong>
|
||||
|
||||
|
||||
## Login
|
||||
LOGIN_HEADING=Innlogg
|
||||
LOGIN_INSTRUCTIONS=Please enter dein username und password. <small><br>(Zee ze <a href="{0}">index page</a> if you need a hint)</small>
|
||||
LOGIN_AUTH_SUCCEEDED=You''re logged in as ''{0}''.
|
||||
LOGIN_AUTH_FAILED=Ze login informazion you zuplied vas incorrect.
|
||||
LOGIN_LOGGED_OUT=You have been logged aus.
|
||||
LOGIN_LABEL_USERNAME=Username
|
||||
LOGIN_LABEL_PASSWORD=Password
|
||||
LOGIN_BUTTON_LOGIN=Innlogg
|
||||
|
||||
|
||||
## Books
|
||||
BOOKS_HEADING_ALL=All ze books
|
||||
BOOKS_CAPTION=<strong>{0}</strong> <br>von {1}
|
||||
BOOKS_BOOK_NOT_FOUND=Book nicht found
|
|
@ -1,33 +0,0 @@
|
|||
## Common
|
||||
COMMON_TITLE=Spark Library
|
||||
COMMON_FOOTER_TEXT=This Application uses <a href="https://openlibrary.org/" target="_blank">OpenLibrary</a> for images.
|
||||
COMMON_NAV_ALLBOOKS=View all books
|
||||
COMMON_NAV_LOGIN=Log in
|
||||
COMMON_NAV_LOGOUT=Log out
|
||||
ERROR_406_NOT_ACCEPTABLE=No suitable content found. Please specify either 'html/text' or 'application/json'.
|
||||
ERROR_404_NOT_FOUND=We can't find the page you're looking for <small>(error 404)</small>
|
||||
|
||||
|
||||
## Index
|
||||
INDEX_HEADING=Welcome to the Spark Library
|
||||
INDEX_REGISTERED_USERS=There are currently {0} users registered:
|
||||
INDEX_PASSWORD_INFO=It seems they've all chosen the password "password" for some reason. How silly.
|
||||
INDEX_BOOK_OF_THE_DAY_TEXT=The book of the day is:
|
||||
INDEX_BOOK_OF_THE_DAY_LINK=<strong>{0} by {1}</strong>
|
||||
|
||||
|
||||
## Login
|
||||
LOGIN_HEADING=Login
|
||||
LOGIN_INSTRUCTIONS=Please enter your username and password. <small><br>(See the <a href="{0}">index page</a> if you need a hint)</small>
|
||||
LOGIN_AUTH_SUCCEEDED=You''re logged in as ''{0}''.
|
||||
LOGIN_AUTH_FAILED=The login information you supplied was incorrect.
|
||||
LOGIN_LOGGED_OUT=You have been logged out.
|
||||
LOGIN_LABEL_USERNAME=Username
|
||||
LOGIN_LABEL_PASSWORD=Password
|
||||
LOGIN_BUTTON_LOGIN=Log in
|
||||
|
||||
|
||||
## Books
|
||||
BOOKS_HEADING_ALL=All books
|
||||
BOOKS_CAPTION=<strong>{0}</strong> <br>by {1}
|
||||
BOOKS_BOOK_NOT_FOUND=Book not found
|
Plik binarny nie jest wyświetlany.
Przed Szerokość: | Wysokość: | Rozmiar: 55 KiB |
Plik binarny nie jest wyświetlany.
Przed Szerokość: | Wysokość: | Rozmiar: 2.0 KiB |
Plik binarny nie jest wyświetlany.
Przed Szerokość: | Wysokość: | Rozmiar: 51 KiB |
Plik binarny nie jest wyświetlany.
Przed Szerokość: | Wysokość: | Rozmiar: 3.4 KiB |
|
@ -1,288 +0,0 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Tahoma, Arial, sans-serif;
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0 0 40px;
|
||||
color: #333;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
font-family: monospace;
|
||||
font-weight: 300;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
small {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
header {
|
||||
background: #274555;
|
||||
border-bottom: 5px solid #ff7761;
|
||||
box-shadow: 0 1px 0 0 rgba(0,0,0,.6);
|
||||
}
|
||||
|
||||
nav {
|
||||
padding: 15px;
|
||||
margin: 0 auto;
|
||||
max-width: 800px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
nav #menu {
|
||||
margin-top: 20px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #ff7761;
|
||||
}
|
||||
|
||||
#menu li {
|
||||
float: left;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
#menu li a, #logout {
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#logo {
|
||||
max-height: 50px;
|
||||
}
|
||||
|
||||
#chooseLanguage {
|
||||
top: 15px;
|
||||
right: 35px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#chooseLanguage li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#chooseLanguage button {
|
||||
cursor: pointer;
|
||||
margin-left: 8px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 9px;
|
||||
opacity: 0.6;
|
||||
border: 1px solid #222;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
#chooseLanguage button:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#content {
|
||||
padding: 15px;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
footer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
border-top: 1px solid #f9a11b;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
nav ul, nav li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
/* Needlessly fancy menu hover effect */
|
||||
|
||||
#menu li {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#menu li a::after, #logout:after {
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 5px;
|
||||
content: '';
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.3s, -webkit-transform 0.3s;
|
||||
transition: opacity 0.3s, transform 0.3s;
|
||||
-webkit-transform: translateY(10px);
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
#logout:hover::after,
|
||||
#logout:focus::after,
|
||||
#menu li a:hover::after,
|
||||
#menu li a:focus::after {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0px);
|
||||
transform: translateY(0px);
|
||||
}
|
||||
|
||||
/* Very basic grid */
|
||||
.row {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.row > * {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.row-2 .col {
|
||||
width: 49%
|
||||
}
|
||||
|
||||
.row-2 .col:nth-child(odd) {
|
||||
margin: 0% 1% 0% 0%
|
||||
}
|
||||
|
||||
.row-2 .col:nth-child(even) {
|
||||
margin: 0% 0% 0% 1%
|
||||
}
|
||||
|
||||
.row-3 .col {
|
||||
width: 32%
|
||||
}
|
||||
|
||||
.row-3 .col:nth-child(3n+1) {
|
||||
margin: 0% 1% 0% 0%
|
||||
}
|
||||
|
||||
.row-3 .col:nth-child(3n+2) {
|
||||
margin: 0% 1% 0% 1%
|
||||
}
|
||||
|
||||
.row-3 .col:nth-child(3n+3) {
|
||||
margin: 0% 0% 0% 1%
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.row .col:nth-child(n) {
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.col img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Book related stuff */
|
||||
|
||||
a.book {
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
a.book:hover {
|
||||
background: #e2e9f5;
|
||||
}
|
||||
|
||||
a .bookCover {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
a .bookCover img {
|
||||
border-radius: 5px;
|
||||
min-height: 200px;
|
||||
max-height: 200px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.bookCover img {
|
||||
margin-top: 20px;
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Login Form */
|
||||
|
||||
#loginForm {
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#loginForm label {
|
||||
display: block;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
#loginForm input {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px 12px;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
margin: 2px 0 20px 0;
|
||||
}
|
||||
|
||||
#loginForm input[type="submit"] {
|
||||
color: white;
|
||||
background: #274555;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notification {
|
||||
padding: 10px;
|
||||
background: #333;
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.good.notification {
|
||||
background: #008900;
|
||||
}
|
||||
|
||||
.bad.notification {
|
||||
background: #bb0000;
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
#parse("/velocity/layout.vm")
|
||||
#@mainLayout()
|
||||
<h1>$msg.get("BOOKS_HEADING_ALL")</h1>
|
||||
<div class="row row-3">
|
||||
#foreach($book in $books)
|
||||
<div class="col">
|
||||
<a class="book" href="$WebPath.getBOOKS()$book.getIsbn()">
|
||||
<div class="bookCover">
|
||||
<img src="$book.getMediumCover()" alt="$book.getTitle()">
|
||||
</div>
|
||||
$msg.get("BOOKS_CAPTION", $book.getTitle(), $book.getAuthor())
|
||||
</a>
|
||||
</div>
|
||||
#end
|
||||
</div>
|
||||
#end
|
|
@ -1,14 +0,0 @@
|
|||
#parse("/velocity/layout.vm")
|
||||
#@mainLayout()
|
||||
#if($book)
|
||||
<h1>$book.getTitle()</h1>
|
||||
<h2>$book.getAuthor()</h2>
|
||||
<div class="book">
|
||||
<div class="bookCover">
|
||||
<img src="$book.getLargeCover()" alt="$book.getTitle()">
|
||||
</div>
|
||||
</div>
|
||||
#else
|
||||
<h1>$msg.get("BOOKS_BOOK_NOT_FOUND")</h1>
|
||||
#end
|
||||
#end
|
|
@ -1,22 +0,0 @@
|
|||
#parse("/velocity/layout.vm")
|
||||
#@mainLayout()
|
||||
<h1>$msg.get("INDEX_HEADING")</h1>
|
||||
<h2>$msg.get("INDEX_REGISTERED_USERS", $users.size())</h2>
|
||||
<ul>
|
||||
#foreach($user in $users)
|
||||
<li>$user</li>
|
||||
#end
|
||||
</ul>
|
||||
<p>$msg.get("INDEX_PASSWORD_INFO")</p>
|
||||
#if($book)
|
||||
<h2>$msg.get("INDEX_BOOK_OF_THE_DAY_TEXT")</h2>
|
||||
<div class="row row-2">
|
||||
<div class="col">
|
||||
<a href="$WebPath.getBOOKS()$book.getIsbn()">$msg.get("INDEX_BOOK_OF_THE_DAY_LINK", $book.getTitle(), $book.getAuthor())</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<img src="$book.getLargeCover()" alt="$book.getTitle()">
|
||||
</div>
|
||||
</div>
|
||||
#end
|
||||
#end
|
|
@ -1,47 +0,0 @@
|
|||
#macro(mainLayout)
|
||||
<html>
|
||||
<head>
|
||||
<title>$msg.get("COMMON_TITLE")</title>
|
||||
<link rel="stylesheet" href="/main.css">
|
||||
<link rel="icon" href="/favicon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<a href="$WebPath.getINDEX()"><img id="logo" src="/img/logo.png" alt="Spark Library"></a>
|
||||
<ul id="chooseLanguage">
|
||||
<form>
|
||||
<li>
|
||||
<button name="locale" value="de" style="background-image: url(/img/german.png);"></button>
|
||||
</li>
|
||||
<li>
|
||||
<button name="locale" value="en" style="background-image: url(/img/english.png);"></button>
|
||||
</li>
|
||||
</form>
|
||||
</ul>
|
||||
<ul id="menu">
|
||||
<li><a href="$WebPath.getBOOKS()">$msg.get("COMMON_NAV_ALLBOOKS")</a></li>
|
||||
#if($currentUser)
|
||||
<li>
|
||||
<form method="post" action="$WebPath.getLOGOUT()">
|
||||
<button id="logout">$msg.get("COMMON_NAV_LOGOUT")</button>
|
||||
</form>
|
||||
</li>
|
||||
#else
|
||||
<li><a href="$WebPath.getLOGIN()"">$msg.get("COMMON_NAV_LOGIN")</a></li>
|
||||
#end
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<div id="content">
|
||||
$bodyContent
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
$msg.get("COMMON_FOOTER_TEXT")
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
#end
|
|
@ -1,22 +0,0 @@
|
|||
#parse("/velocity/layout.vm")
|
||||
#@mainLayout()
|
||||
<form id="loginForm" method="post">
|
||||
#if($authenticationFailed)
|
||||
<p class="bad notification">$msg.get("LOGIN_AUTH_FAILED")</p>
|
||||
#elseif($authenticationSucceeded)
|
||||
<p class="good notification">$msg.get("LOGIN_AUTH_SUCCEEDED", $currentUser)</p>
|
||||
#elseif($loggedOut)
|
||||
<p class="notification">$msg.get("LOGIN_LOGGED_OUT")</p>
|
||||
#end
|
||||
<h1>$msg.get("LOGIN_HEADING")</h1>
|
||||
<p>$msg.get("LOGIN_INSTRUCTIONS", $WebPath.getINDEX())</p>
|
||||
<label>$msg.get("LOGIN_LABEL_USERNAME")</label>
|
||||
<input type="text" name="username" placeholder="$msg.get("LOGIN_LABEL_USERNAME")" value="" required>
|
||||
<label>$msg.get("LOGIN_LABEL_PASSWORD")</label>
|
||||
<input type="password" name="password" placeholder="$msg.get("LOGIN_LABEL_PASSWORD")" value="" required>
|
||||
#if($loginRedirect)
|
||||
<input type="hidden" name="loginRedirect" value="$loginRedirect">
|
||||
#end
|
||||
<input type="submit" value="$msg.get("LOGIN_BUTTON_LOGIN")">
|
||||
</form>
|
||||
#end
|
|
@ -1,4 +0,0 @@
|
|||
#parse("/velocity/layout.vm")
|
||||
#@mainLayout()
|
||||
<h1>$msg.get("ERROR_404_NOT_FOUND")</h1>
|
||||
#end
|
|
@ -1,11 +0,0 @@
|
|||
#* @implicitly included *#
|
||||
#* @vtlvariable name="WebPath" type="app.util.Path.Web" *#
|
||||
#* @vtlvariable name="msg" type="app.util.MessageBundle" *#
|
||||
#* @vtlvariable name="books" type="java.lang.Iterable<app.book.Book>" *#
|
||||
#* @vtlvariable name="book" type="app.book.Book" *#
|
||||
#* @vtlvariable name="users" type="java.lang.Iterable<app.user.User>" *#
|
||||
#* @vtlvariable name="currentUser" type="java.lang.String" *#
|
||||
#* @vtlvariable name="loggedOut" type="java.lang.String" *#
|
||||
#* @vtlvariable name="authenticationFailed" type="java.lang.String" *#
|
||||
#* @vtlvariable name="authenticationSucceeded" type="java.lang.String" *#
|
||||
#* @vtlvariable name="loginRedirect" type="java.lang.String" *#
|
Ładowanie…
Reference in New Issue