From 911853a183d7c67304ad7c6bbc57995651aed6e5 Mon Sep 17 00:00:00 2001 From: Radek Davidek Date: Fri, 20 Mar 2026 10:48:09 +0100 Subject: [PATCH] first commit --- .gitignore | 8 + pom.xml | 55 + .../cz/kamma/fabka/httpserver/AppConfig.java | 54 + .../httpserver/HttpServerApplication.java | 1448 +++++++++++++++++ .../fabka/httpserver/auth/AuthService.java | 5 + .../httpserver/auth/AuthenticatedUser.java | 19 + .../httpserver/auth/DatabaseAuthService.java | 52 + .../fabka/httpserver/auth/EnvAuthService.java | 21 + .../cz/kamma/fabka/httpserver/crypto/Md5.java | 31 + .../http/ClasspathStaticFileHandler.java | 77 + .../httpserver/http/MultipartFormData.java | 184 +++ .../fabka/httpserver/http/RequestContext.java | 157 ++ .../fabka/httpserver/http/Responses.java | 32 + .../fabka/httpserver/http/RouteHandler.java | 6 + .../kamma/fabka/httpserver/http/Router.java | 49 + .../http/StaticFileHttpHandler.java | 82 + .../httpserver/repository/AttachmentData.java | 25 + .../fabka/httpserver/repository/ChatLine.java | 64 + .../httpserver/repository/ChatRepository.java | 241 +++ .../httpserver/repository/ChatVoteStats.java | 19 + .../repository/ForumAttachment.java | 31 + .../httpserver/repository/ForumDetail.java | 31 + .../repository/ForumDisplayView.java | 94 ++ .../httpserver/repository/ForumMessage.java | 136 ++ .../repository/ForumRepository.java | 564 +++++++ .../httpserver/repository/ForumSummary.java | 55 + .../httpserver/repository/MemberProfile.java | 49 + .../repository/MemberRepository.java | 140 ++ .../repository/MessageRenderSettings.java | 25 + .../repository/MysqlClientRepository.java | 166 ++ .../repository/PrivateMessageItem.java | 57 + .../repository/PrivateMessageRepository.java | 273 ++++ .../repository/PrivateMessageStats.java | 19 + .../repository/PrivateThreadRoot.java | 37 + .../repository/PrivateThreadSummary.java | 57 + .../httpserver/repository/QuotedTextItem.java | 19 + .../repository/SettingsRepository.java | 96 ++ .../fabka/httpserver/repository/UserIcon.java | 19 + .../repository/UserIconRepository.java | 66 + .../httpserver/repository/VoteStats.java | 31 + .../fabka/httpserver/session/SessionData.java | 43 + .../httpserver/session/SessionManager.java | 94 ++ .../web/LegacyMessageFormatter.java | 123 ++ .../cz/kamma/fabka/httpserver/web/Pages.java | 919 +++++++++++ src/main/resources/app.properties | 16 + src/main/resources/webapp/chat.html | 103 ++ src/main/resources/webapp/client.js | 310 ++++ src/main/resources/webapp/css/all.css | 453 ++++++ src/main/resources/webapp/favicon.ico | Bin 0 -> 630 bytes src/main/resources/webapp/forum.html | 43 + src/main/resources/webapp/forumdisplay.html | 124 ++ src/main/resources/webapp/images/busy.gif | Bin 0 -> 722 bytes src/main/resources/webapp/images/cat-line.gif | Bin 0 -> 284 bytes .../webapp/images/cellpic-fp-big.gif | Bin 0 -> 315 bytes .../resources/webapp/images/cellpic-fp.gif | Bin 0 -> 293 bytes .../resources/webapp/images/cernypetr.jpg | Bin 0 -> 9934 bytes src/main/resources/webapp/images/delete.gif | Bin 0 -> 951 bytes src/main/resources/webapp/images/edit.gif | Bin 0 -> 798 bytes src/main/resources/webapp/images/gif.gif | Bin 0 -> 183 bytes src/main/resources/webapp/images/icon1.gif | Bin 0 -> 1336 bytes src/main/resources/webapp/images/locked.gif | Bin 0 -> 202 bytes .../resources/webapp/images/newthread.gif | Bin 0 -> 2356 bytes src/main/resources/webapp/images/pm.gif | Bin 0 -> 1026 bytes src/main/resources/webapp/images/pm_read.gif | Bin 0 -> 1078 bytes src/main/resources/webapp/images/post_old.gif | Bin 0 -> 522 bytes src/main/resources/webapp/images/quote.gif | Bin 0 -> 794 bytes src/main/resources/webapp/images/reply.gif | Bin 0 -> 2194 bytes src/main/resources/webapp/images/sortasc.gif | Bin 0 -> 951 bytes src/main/resources/webapp/images/sortdesc.gif | Bin 0 -> 943 bytes .../resources/webapp/images/voting_no.png | Bin 0 -> 490 bytes .../resources/webapp/images/voting_yes.png | Bin 0 -> 505 bytes .../resources/webapp/images/whos_online.gif | Bin 0 -> 1440 bytes src/main/resources/webapp/login.html | 44 + src/main/resources/webapp/member.html | 214 +++ src/main/resources/webapp/message.html | 81 + src/main/resources/webapp/newpm.html | 64 + src/main/resources/webapp/newthread.html | 62 + src/main/resources/webapp/notif.wav | Bin 0 -> 37804 bytes src/main/resources/webapp/private.html | 109 ++ target/classes/app.properties | 16 + .../cz/kamma/fabka/httpserver/AppConfig.class | Bin 0 -> 2654 bytes .../httpserver/HttpServerApplication.class | Bin 0 -> 62435 bytes .../fabka/httpserver/auth/AuthService.class | Bin 0 -> 248 bytes .../httpserver/auth/AuthenticatedUser.class | Bin 0 -> 663 bytes .../httpserver/auth/DatabaseAuthService.class | Bin 0 -> 2967 bytes .../httpserver/auth/EnvAuthService.class | Bin 0 -> 998 bytes .../kamma/fabka/httpserver/crypto/Md5.class | Bin 0 -> 1684 bytes .../http/ClasspathStaticFileHandler.class | Bin 0 -> 3541 bytes .../http/MultipartFormData$FileItem.class | Bin 0 -> 1086 bytes .../httpserver/http/MultipartFormData.class | Bin 0 -> 7529 bytes .../httpserver/http/RequestContext.class | Bin 0 -> 6715 bytes .../fabka/httpserver/http/Responses.class | Bin 0 -> 1805 bytes .../fabka/httpserver/http/RouteHandler.class | Bin 0 -> 328 bytes .../kamma/fabka/httpserver/http/Router.class | Bin 0 -> 3134 bytes .../http/StaticFileHttpHandler.class | Bin 0 -> 3028 bytes .../repository/AttachmentData.class | Bin 0 -> 804 bytes .../httpserver/repository/ChatLine.class | Bin 0 -> 1478 bytes .../repository/ChatRepository.class | Bin 0 -> 11783 bytes .../httpserver/repository/ChatVoteStats.class | Bin 0 -> 694 bytes .../repository/ForumAttachment.class | Bin 0 -> 914 bytes .../httpserver/repository/ForumDetail.class | Bin 0 -> 933 bytes .../repository/ForumDisplayView.class | Bin 0 -> 2482 bytes .../httpserver/repository/ForumMessage.class | Bin 0 -> 3545 bytes .../repository/ForumRepository.class | Bin 0 -> 27142 bytes .../httpserver/repository/ForumSummary.class | Bin 0 -> 1491 bytes .../httpserver/repository/MemberProfile.class | Bin 0 -> 1355 bytes .../repository/MemberRepository.class | Bin 0 -> 7246 bytes .../repository/MessageRenderSettings.class | Bin 0 -> 877 bytes .../MysqlClientRepository$SqlExecution.class | Bin 0 -> 2246 bytes .../repository/MysqlClientRepository.class | Bin 0 -> 6746 bytes .../repository/PrivateMessageItem.class | Bin 0 -> 1368 bytes .../repository/PrivateMessageRepository.class | Bin 0 -> 14136 bytes .../repository/PrivateMessageStats.class | Bin 0 -> 614 bytes .../repository/PrivateThreadRoot.class | Bin 0 -> 1098 bytes .../repository/PrivateThreadSummary.class | Bin 0 -> 1367 bytes .../repository/QuotedTextItem.class | Bin 0 -> 665 bytes .../repository/SettingsRepository.class | Bin 0 -> 4969 bytes .../httpserver/repository/UserIcon.class | Bin 0 -> 653 bytes .../repository/UserIconRepository.class | Bin 0 -> 3660 bytes .../httpserver/repository/VoteStats.class | Bin 0 -> 897 bytes .../httpserver/session/SessionData.class | Bin 0 -> 1650 bytes .../httpserver/session/SessionManager.class | Bin 0 -> 4201 bytes .../web/LegacyMessageFormatter.class | Bin 0 -> 4286 bytes .../cz/kamma/fabka/httpserver/web/Pages.class | Bin 0 -> 46019 bytes target/classes/webapp/chat.html | 103 ++ target/classes/webapp/client.js | 310 ++++ target/classes/webapp/css/all.css | 453 ++++++ target/classes/webapp/favicon.ico | Bin 0 -> 630 bytes target/classes/webapp/forum.html | 43 + target/classes/webapp/forumdisplay.html | 124 ++ target/classes/webapp/images/busy.gif | Bin 0 -> 722 bytes target/classes/webapp/images/cat-line.gif | Bin 0 -> 284 bytes .../classes/webapp/images/cellpic-fp-big.gif | Bin 0 -> 315 bytes target/classes/webapp/images/cellpic-fp.gif | Bin 0 -> 293 bytes target/classes/webapp/images/cernypetr.jpg | Bin 0 -> 9934 bytes target/classes/webapp/images/delete.gif | Bin 0 -> 951 bytes target/classes/webapp/images/edit.gif | Bin 0 -> 798 bytes target/classes/webapp/images/gif.gif | Bin 0 -> 183 bytes target/classes/webapp/images/icon1.gif | Bin 0 -> 1336 bytes target/classes/webapp/images/locked.gif | Bin 0 -> 202 bytes target/classes/webapp/images/newthread.gif | Bin 0 -> 2356 bytes target/classes/webapp/images/pm.gif | Bin 0 -> 1026 bytes target/classes/webapp/images/pm_read.gif | Bin 0 -> 1078 bytes target/classes/webapp/images/post_old.gif | Bin 0 -> 522 bytes target/classes/webapp/images/quote.gif | Bin 0 -> 794 bytes target/classes/webapp/images/reply.gif | Bin 0 -> 2194 bytes target/classes/webapp/images/sortasc.gif | Bin 0 -> 951 bytes target/classes/webapp/images/sortdesc.gif | Bin 0 -> 943 bytes target/classes/webapp/images/voting_no.png | Bin 0 -> 490 bytes target/classes/webapp/images/voting_yes.png | Bin 0 -> 505 bytes target/classes/webapp/images/whos_online.gif | Bin 0 -> 1440 bytes target/classes/webapp/login.html | 44 + target/classes/webapp/member.html | 214 +++ target/classes/webapp/message.html | 81 + target/classes/webapp/newpm.html | 64 + target/classes/webapp/newthread.html | 62 + target/classes/webapp/notif.wav | Bin 0 -> 37804 bytes target/classes/webapp/private.html | 109 ++ .../compile/default-compile/createdFiles.lst | 44 + .../compile/default-compile/inputFiles.lst | 42 + 160 files changed, 9105 insertions(+) create mode 100755 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/cz/kamma/fabka/httpserver/AppConfig.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/HttpServerApplication.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/auth/AuthService.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/auth/AuthenticatedUser.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/auth/DatabaseAuthService.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/auth/EnvAuthService.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/crypto/Md5.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/http/ClasspathStaticFileHandler.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/http/MultipartFormData.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/http/RequestContext.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/http/Responses.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/http/RouteHandler.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/http/Router.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/http/StaticFileHttpHandler.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/AttachmentData.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/ChatLine.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/ChatRepository.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/ChatVoteStats.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/ForumAttachment.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/ForumDetail.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/ForumDisplayView.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/ForumMessage.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/ForumRepository.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/ForumSummary.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/MemberProfile.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/MemberRepository.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/MessageRenderSettings.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/MysqlClientRepository.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageItem.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageRepository.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageStats.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/PrivateThreadRoot.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/PrivateThreadSummary.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/QuotedTextItem.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/SettingsRepository.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/UserIcon.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/UserIconRepository.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/repository/VoteStats.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/session/SessionData.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/session/SessionManager.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/web/LegacyMessageFormatter.java create mode 100644 src/main/java/cz/kamma/fabka/httpserver/web/Pages.java create mode 100644 src/main/resources/app.properties create mode 100644 src/main/resources/webapp/chat.html create mode 100644 src/main/resources/webapp/client.js create mode 100644 src/main/resources/webapp/css/all.css create mode 100644 src/main/resources/webapp/favicon.ico create mode 100644 src/main/resources/webapp/forum.html create mode 100644 src/main/resources/webapp/forumdisplay.html create mode 100644 src/main/resources/webapp/images/busy.gif create mode 100644 src/main/resources/webapp/images/cat-line.gif create mode 100644 src/main/resources/webapp/images/cellpic-fp-big.gif create mode 100644 src/main/resources/webapp/images/cellpic-fp.gif create mode 100644 src/main/resources/webapp/images/cernypetr.jpg create mode 100644 src/main/resources/webapp/images/delete.gif create mode 100644 src/main/resources/webapp/images/edit.gif create mode 100644 src/main/resources/webapp/images/gif.gif create mode 100644 src/main/resources/webapp/images/icon1.gif create mode 100644 src/main/resources/webapp/images/locked.gif create mode 100644 src/main/resources/webapp/images/newthread.gif create mode 100644 src/main/resources/webapp/images/pm.gif create mode 100644 src/main/resources/webapp/images/pm_read.gif create mode 100644 src/main/resources/webapp/images/post_old.gif create mode 100644 src/main/resources/webapp/images/quote.gif create mode 100644 src/main/resources/webapp/images/reply.gif create mode 100644 src/main/resources/webapp/images/sortasc.gif create mode 100644 src/main/resources/webapp/images/sortdesc.gif create mode 100644 src/main/resources/webapp/images/voting_no.png create mode 100644 src/main/resources/webapp/images/voting_yes.png create mode 100644 src/main/resources/webapp/images/whos_online.gif create mode 100644 src/main/resources/webapp/login.html create mode 100644 src/main/resources/webapp/member.html create mode 100644 src/main/resources/webapp/message.html create mode 100644 src/main/resources/webapp/newpm.html create mode 100644 src/main/resources/webapp/newthread.html create mode 100644 src/main/resources/webapp/notif.wav create mode 100644 src/main/resources/webapp/private.html create mode 100644 target/classes/app.properties create mode 100644 target/classes/cz/kamma/fabka/httpserver/AppConfig.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/HttpServerApplication.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/auth/AuthService.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/auth/AuthenticatedUser.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/auth/DatabaseAuthService.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/auth/EnvAuthService.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/crypto/Md5.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/http/ClasspathStaticFileHandler.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/http/MultipartFormData$FileItem.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/http/MultipartFormData.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/http/RequestContext.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/http/Responses.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/http/RouteHandler.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/http/Router.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/http/StaticFileHttpHandler.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/AttachmentData.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/ChatLine.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/ChatRepository.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/ChatVoteStats.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/ForumAttachment.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/ForumDetail.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/ForumDisplayView.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/ForumMessage.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/ForumRepository.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/ForumSummary.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/MemberProfile.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/MemberRepository.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/MessageRenderSettings.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/MysqlClientRepository$SqlExecution.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/MysqlClientRepository.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/PrivateMessageItem.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/PrivateMessageRepository.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/PrivateMessageStats.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/PrivateThreadRoot.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/PrivateThreadSummary.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/QuotedTextItem.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/SettingsRepository.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/UserIcon.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/UserIconRepository.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/repository/VoteStats.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/session/SessionData.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/session/SessionManager.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/web/LegacyMessageFormatter.class create mode 100644 target/classes/cz/kamma/fabka/httpserver/web/Pages.class create mode 100644 target/classes/webapp/chat.html create mode 100644 target/classes/webapp/client.js create mode 100644 target/classes/webapp/css/all.css create mode 100644 target/classes/webapp/favicon.ico create mode 100644 target/classes/webapp/forum.html create mode 100644 target/classes/webapp/forumdisplay.html create mode 100644 target/classes/webapp/images/busy.gif create mode 100644 target/classes/webapp/images/cat-line.gif create mode 100644 target/classes/webapp/images/cellpic-fp-big.gif create mode 100644 target/classes/webapp/images/cellpic-fp.gif create mode 100644 target/classes/webapp/images/cernypetr.jpg create mode 100644 target/classes/webapp/images/delete.gif create mode 100644 target/classes/webapp/images/edit.gif create mode 100644 target/classes/webapp/images/gif.gif create mode 100644 target/classes/webapp/images/icon1.gif create mode 100644 target/classes/webapp/images/locked.gif create mode 100644 target/classes/webapp/images/newthread.gif create mode 100644 target/classes/webapp/images/pm.gif create mode 100644 target/classes/webapp/images/pm_read.gif create mode 100644 target/classes/webapp/images/post_old.gif create mode 100644 target/classes/webapp/images/quote.gif create mode 100644 target/classes/webapp/images/reply.gif create mode 100644 target/classes/webapp/images/sortasc.gif create mode 100644 target/classes/webapp/images/sortdesc.gif create mode 100644 target/classes/webapp/images/voting_no.png create mode 100644 target/classes/webapp/images/voting_yes.png create mode 100644 target/classes/webapp/images/whos_online.gif create mode 100644 target/classes/webapp/login.html create mode 100644 target/classes/webapp/member.html create mode 100644 target/classes/webapp/message.html create mode 100644 target/classes/webapp/newpm.html create mode 100644 target/classes/webapp/newthread.html create mode 100644 target/classes/webapp/notif.wav create mode 100644 target/classes/webapp/private.html create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..ebd831b --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +servers +target +bin +.settings +.metadata +.classpath +.project + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..addbfe1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,55 @@ + + 4.0.0 + + cz.kamma.fabka + app-httpserver + 1.0-SNAPSHOT + FabkovaChata HttpServer + + + 11 + 11 + UTF-8 + + + + + org.mariadb.jdbc + mariadb-java-client + 3.5.3 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + false + + + cz.kamma.fabka.httpserver.HttpServerApplication + + + + + + + + + diff --git a/src/main/java/cz/kamma/fabka/httpserver/AppConfig.java b/src/main/java/cz/kamma/fabka/httpserver/AppConfig.java new file mode 100644 index 0000000..d51d30f --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/AppConfig.java @@ -0,0 +1,54 @@ +package cz.kamma.fabka.httpserver; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public class AppConfig { + private static final Properties properties = new Properties(); + + static { + try (InputStream in = AppConfig.class.getClassLoader().getResourceAsStream("app.properties")) { + if (in != null) { + properties.load(in); + } + } catch (IOException e) { + // Properties file not found, will use defaults + } + } + + public static String get(String key, String defaultValue) { + String value = properties.getProperty(key); + if (value == null || value.isBlank()) { + value = System.getenv(toEnvFormat(key)); + } + return (value == null || value.isBlank()) ? defaultValue : value; + } + + public static int getInt(String key, int defaultValue) { + String value = get(key, String.valueOf(defaultValue)); + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public static long getLong(String key, long defaultValue) { + String value = get(key, String.valueOf(defaultValue)); + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public static boolean getBoolean(String key, boolean defaultValue) { + String value = get(key, String.valueOf(defaultValue)); + return Boolean.parseBoolean(value); + } + + private static String toEnvFormat(String key) { + return key.toUpperCase().replace(".", "_").replace("-", "_"); + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/HttpServerApplication.java b/src/main/java/cz/kamma/fabka/httpserver/HttpServerApplication.java new file mode 100644 index 0000000..d391f1d --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/HttpServerApplication.java @@ -0,0 +1,1448 @@ +package cz.kamma.fabka.httpserver; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +import com.sun.net.httpserver.HttpServer; + +import cz.kamma.fabka.httpserver.auth.AuthService; +import cz.kamma.fabka.httpserver.auth.AuthenticatedUser; +import cz.kamma.fabka.httpserver.auth.DatabaseAuthService; +import cz.kamma.fabka.httpserver.http.ClasspathStaticFileHandler; +import cz.kamma.fabka.httpserver.http.MultipartFormData; +import cz.kamma.fabka.httpserver.http.RequestContext; +import cz.kamma.fabka.httpserver.http.Responses; +import cz.kamma.fabka.httpserver.http.Router; +import cz.kamma.fabka.httpserver.repository.AttachmentData; +import cz.kamma.fabka.httpserver.repository.ChatLine; +import cz.kamma.fabka.httpserver.repository.ChatRepository; +import cz.kamma.fabka.httpserver.repository.ChatVoteStats; +import cz.kamma.fabka.httpserver.repository.ForumAttachment; +import cz.kamma.fabka.httpserver.repository.ForumDetail; +import cz.kamma.fabka.httpserver.repository.ForumDisplayView; +import cz.kamma.fabka.httpserver.repository.ForumMessage; +import cz.kamma.fabka.httpserver.repository.ForumRepository; +import cz.kamma.fabka.httpserver.repository.ForumSummary; +import cz.kamma.fabka.httpserver.repository.MemberProfile; +import cz.kamma.fabka.httpserver.repository.MemberRepository; +import cz.kamma.fabka.httpserver.repository.MessageRenderSettings; +import cz.kamma.fabka.httpserver.repository.MysqlClientRepository; +import cz.kamma.fabka.httpserver.repository.PrivateMessageItem; +import cz.kamma.fabka.httpserver.repository.PrivateMessageRepository; +import cz.kamma.fabka.httpserver.repository.PrivateMessageStats; +import cz.kamma.fabka.httpserver.repository.PrivateThreadRoot; +import cz.kamma.fabka.httpserver.repository.PrivateThreadSummary; +import cz.kamma.fabka.httpserver.repository.QuotedTextItem; +import cz.kamma.fabka.httpserver.repository.SettingsRepository; +import cz.kamma.fabka.httpserver.repository.UserIcon; +import cz.kamma.fabka.httpserver.repository.UserIconRepository; +import cz.kamma.fabka.httpserver.repository.VoteStats; +import cz.kamma.fabka.httpserver.session.SessionData; +import cz.kamma.fabka.httpserver.session.SessionManager; +import cz.kamma.fabka.httpserver.web.Pages; + + +public class HttpServerApplication { + private static final String AUTH_USER_KEY = "auth.username"; + private static final String AUTH_USER_ID_KEY = "auth.userId"; + private static final String CURRENT_PAGE_KEY = "app.currentPage"; + private static final String SETTINGS_SORT_TYPE_PREFIX = "SETTINGS_SORTING_TYPE_FORUMDISPLAY"; + private static final String SETTINGS_SORT_BY_PREFIX = "SETTINGS_SORTING_BY_FORUMDISPLAY"; + private static final String SETTINGS_PERPAGE_PREFIX = "SETTINGS_PERPAGE_RECORDS_FORUMDISPLAY"; + private static final String SETTINGS_SHOWIMG_PREFIX = "SETTINGS_SHOW_IMAGES_FORUMDISPLAY"; + private static final String SETTINGS_SHOWTYPE_PREFIX = "SETTINGS_SHOW_TYPE_FORUMDISPLAY"; + private static final String SETTINGS_PM_PRIVATE_ORDER = "SETTINGS_PM_PRIVATE_ORDER"; + private static final String SETTINGS_PM_PRIVATE_PERPAGE = "SETTINGS_PM_PRIVATE_PERPAGE"; + private static final String SETTINGS_PM_THREAD_PERPAGE = SETTINGS_PERPAGE_PREFIX + "_PM"; + private static final String SETTINGS_SOUND_ONOFF = "SETTINGS_SOUND_ONOFF"; + private static final String PM_ERROR_KEY = "pm.error"; + private static final String NEW_THREAD_ERROR_KEY = "newthread.error"; + private static final String MEMBER_ERROR_KEY = "member.error"; + + public static void main(String[] args) throws IOException { + int port = AppConfig.getInt("app.server.port", 8080); + int threads = AppConfig.getInt("app.server.threads", 24); + + SessionManager sessionManager = new SessionManager(Duration.ofMinutes(AppConfig.getInt("app.session.timeout.minutes", 30))); + String jdbcUrl = AppConfig.get("app.db.url", ""); + String dbUser = AppConfig.get("app.db.user", "fabkovachata"); + String dbPassword = AppConfig.get("app.db.password", ""); + if (dbPassword.isBlank()) { + throw new IllegalStateException("app.db.password must be set in app.properties or as environment variable APP_DB_PASSWORD"); + } + String mysqlAdminJdbcUrl = AppConfig.get("app.db.mysql_admin_url", ""); + + AuthService authService = new DatabaseAuthService(jdbcUrl, dbUser, dbPassword); + ForumRepository forumRepository = new ForumRepository(jdbcUrl, dbUser, dbPassword); + ChatRepository chatRepository = new ChatRepository(jdbcUrl, dbUser, dbPassword); + PrivateMessageRepository privateMessageRepository = new PrivateMessageRepository(jdbcUrl, dbUser, dbPassword); + UserIconRepository userIconRepository = new UserIconRepository(jdbcUrl, dbUser, dbPassword); + MemberRepository memberRepository = new MemberRepository(jdbcUrl, dbUser, dbPassword); + SettingsRepository settingsRepository = new SettingsRepository(jdbcUrl, dbUser, dbPassword); + MysqlClientRepository mysqlClientRepository = new MysqlClientRepository(mysqlAdminJdbcUrl, dbUser, dbPassword); + + Router router = new Router(sessionManager); + wireRoutes( + router, + sessionManager, + authService, + forumRepository, + chatRepository, + privateMessageRepository, + userIconRepository, + memberRepository, + settingsRepository, + mysqlClientRepository + ); + + HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); + ExecutorService executor = Executors.newFixedThreadPool(threads); + server.setExecutor(executor); + + server.createContext("/", router); + server.createContext("/css", new ClasspathStaticFileHandler("/css", "webapp/css")); + server.createContext("/images", new ClasspathStaticFileHandler("/images", "webapp/images")); + server.createContext("/client.js", new ClasspathStaticFileHandler("/client.js", "webapp/client.js")); + server.createContext("/notif.wav", new ClasspathStaticFileHandler("/notif.wav", "webapp/notif.wav")); + server.createContext("/favicon.ico", new ClasspathStaticFileHandler("/favicon.ico", "webapp/favicon.ico")); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + server.stop(0); + executor.shutdown(); + })); + + server.start(); + System.out.println("FabkovaChata HttpServer skeleton started on http://localhost:" + port); + } + + private static void wireRoutes( + Router router, + SessionManager sessionManager, + AuthService authService, + ForumRepository forumRepository, + ChatRepository chatRepository, + PrivateMessageRepository privateMessageRepository, + UserIconRepository userIconRepository, + MemberRepository memberRepository, + SettingsRepository settingsRepository, + MysqlClientRepository mysqlClientRepository + ) { + router.get("/", ctx -> { + if (isAuthenticated(ctx)) { + Responses.redirect(ctx.exchange(), "/forum"); + } else { + Responses.redirect(ctx.exchange(), "/login"); + } + }); + + router.get("/login", ctx -> { + boolean showError = "1".equals(ctx.queryParam("error")); + Responses.html(ctx.exchange(), 200, Pages.loginPage(showError)); + }); + router.get("/chat.jsp", ctx -> Responses.redirect(ctx.exchange(), "/chat")); + router.get("/private.jsp", ctx -> { + String oBy = ctx.queryParam("oBy"); + String perpage = ctx.queryParam("perpage"); + String iPageNo = ctx.queryParam("iPageNo"); + if (oBy == null || oBy.isBlank()) { + String extra = ""; + if (perpage != null && !perpage.isBlank()) { + extra += (extra.isEmpty() ? "?" : "&") + "perpage=" + URLEncoder.encode(perpage, StandardCharsets.UTF_8); + } + if (iPageNo != null && !iPageNo.isBlank()) { + extra += (extra.isEmpty() ? "?" : "&") + "iPageNo=" + URLEncoder.encode(iPageNo, StandardCharsets.UTF_8); + } + Responses.redirect(ctx.exchange(), "/private" + extra); + } else { + String location = "/private?oBy=" + URLEncoder.encode(oBy, StandardCharsets.UTF_8); + if (perpage != null && !perpage.isBlank()) { + location += "&perpage=" + URLEncoder.encode(perpage, StandardCharsets.UTF_8); + } + if (iPageNo != null && !iPageNo.isBlank()) { + location += "&iPageNo=" + URLEncoder.encode(iPageNo, StandardCharsets.UTF_8); + } + Responses.redirect(ctx.exchange(), location); + } + }); + router.get("/newpm.jsp", ctx -> { + String uid = ctx.queryParam("uid"); + if (uid == null || uid.isBlank()) { + Responses.redirect(ctx.exchange(), "/newpm"); + } else { + Responses.redirect(ctx.exchange(), "/newpm?uid=" + URLEncoder.encode(uid, StandardCharsets.UTF_8)); + } + }); + router.get("/newthread.jsp", ctx -> Responses.redirect(ctx.exchange(), "/newthread")); + router.get("/member.jsp", ctx -> Responses.redirect(ctx.exchange(), "/member")); + router.get("/message.jsp", ctx -> { + String pmid = ctx.queryParam("pmid"); + String perpage = ctx.queryParam("perpage"); + String iPageNo = ctx.queryParam("iPageNo"); + if (pmid == null || pmid.isBlank()) { + Responses.redirect(ctx.exchange(), "/private"); + } else { + String location = "/message?pmid=" + URLEncoder.encode(pmid, StandardCharsets.UTF_8); + if (perpage != null && !perpage.isBlank()) { + location += "&perpage=" + URLEncoder.encode(perpage, StandardCharsets.UTF_8); + } + if (iPageNo != null && !iPageNo.isBlank()) { + location += "&iPageNo=" + URLEncoder.encode(iPageNo, StandardCharsets.UTF_8); + } + Responses.redirect(ctx.exchange(), location); + } + }); + router.get("/mysqlc.jsp", ctx -> Responses.redirect(ctx.exchange(), "/mysqlc")); + router.get("/mysqlc", ctx -> handleMysqlClient(ctx, sessionManager, privateMessageRepository, mysqlClientRepository)); + router.post("/mysqlc", ctx -> handleMysqlClient(ctx, sessionManager, privateMessageRepository, mysqlClientRepository)); + router.post("/mysqlc.jsp", ctx -> handleMysqlClient(ctx, sessionManager, privateMessageRepository, mysqlClientRepository)); + + router.post("/process/login", ctx -> { + String username = ctx.formParam("uname"); + String password = ctx.formParam("passwd"); + if (username == null || password == null) { + Responses.redirect(ctx.exchange(), "/login?error=1"); + return; + } + + AuthenticatedUser user = authService.authenticate(username, password); + if (user == null) { + Responses.redirect(ctx.exchange(), "/login?error=1"); + return; + } + + SessionData session = ctx.getOrCreateSession(); + session.setAttribute(AUTH_USER_KEY, user.getUsername()); + session.setAttribute(AUTH_USER_ID_KEY, user.getUserId()); + Map userSettings = settingsRepository.loadUserSettings(user.getUserId()); + for (Map.Entry entry : userSettings.entrySet()) { + session.setAttribute(entry.getKey(), entry.getValue()); + } + Responses.redirect(ctx.exchange(), "/forum"); + }); + + router.post("/process/logout", ctx -> { + ctx.invalidateSession(); + Responses.redirect(ctx.exchange(), "/login"); + }); + + router.post("/process/replythread", ctx -> handleReplyThread(ctx, forumRepository)); + router.post("/process/replythread.jsp", ctx -> handleReplyThread(ctx, forumRepository)); + + router.post("/process/savemember", ctx -> handleSaveMember(ctx, memberRepository, settingsRepository)); + router.post("/process/savemember.jsp", ctx -> handleSaveMember(ctx, memberRepository, settingsRepository)); + router.post("/process/saveicon", ctx -> handleSaveIcon(ctx, userIconRepository)); + router.post("/process/saveicon.jsp", ctx -> handleSaveIcon(ctx, userIconRepository)); + + router.get("/process/setsticky.jsp", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + long forumId = parseLong(ctx.queryParam("forumid"), -1L); + long postId = parseLong(ctx.queryParam("postid"), -1L); + if (postId > 0) { + forumRepository.toggleSticky(postId); + } + if (forumId > 0) { + Responses.redirect(ctx.exchange(), "/forumdisplay?f=" + forumId); + } else { + Responses.redirect(ctx.exchange(), "/forum"); + } + }); + + router.get("/process/deletepost.jsp", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + long forumId = parseLong(ctx.queryParam("forumid"), -1L); + long postId = parseLong(ctx.queryParam("postid"), -1L); + if (postId > 0 && userId > 0) { + forumRepository.deletePost(postId, userId); + } + if (forumId > 0) { + Responses.redirect(ctx.exchange(), "/forumdisplay?f=" + forumId); + } else { + Responses.redirect(ctx.exchange(), "/forum"); + } + }); + + router.get("/forum", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + session.setAttribute(CURRENT_PAGE_KEY, "forum"); + String username = String.valueOf(session.getAttribute(AUTH_USER_KEY)); + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + forumRepository.putHistoryRecord(userId, 0L); + List forums = forumRepository.listActiveForums(); + Map newCounts = forumRepository.getNewMessagesCountByUserId(userId); + int loggedUsersCount = sessionManager.countSessionsWithAttribute(AUTH_USER_KEY); + List loggedUsers = sessionManager.sessionAttributeValues(AUTH_USER_KEY); + PrivateMessageStats pmStats = privateMessageRepository.stats(userId); + Responses.html(ctx.exchange(), 200, Pages.forumPage(username, forums, newCounts, loggedUsersCount, loggedUsers, pmStats)); + }); + + router.get("/member", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + session.setAttribute(CURRENT_PAGE_KEY, "member"); + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + String username = String.valueOf(session.getAttribute(AUTH_USER_KEY)); + MemberProfile profile = memberRepository.findByUserId(userId); + Object soundRaw = session.getAttribute(SETTINGS_SOUND_ONOFF); + String soundSetting = (soundRaw == null) ? "soundon" : String.valueOf(soundRaw); + String error = (String) session.getAttribute(MEMBER_ERROR_KEY); + session.setAttribute(MEMBER_ERROR_KEY, null); + int loggedUsersCount = sessionManager.countSessionsWithAttribute(AUTH_USER_KEY); + List loggedUsers = sessionManager.sessionAttributeValues(AUTH_USER_KEY); + PrivateMessageStats pmStats = privateMessageRepository.stats(userId); + Responses.html( + ctx.exchange(), + 200, + Pages.memberPage( + username, + profile, + !"soundoff".equalsIgnoreCase(soundSetting), + error, + loggedUsersCount, + loggedUsers, + pmStats + ) + ); + }); + + router.get("/chat", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + session.setAttribute(CURRENT_PAGE_KEY, "chat"); + String username = String.valueOf(session.getAttribute(AUTH_USER_KEY)); + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + int loggedUsersCount = sessionManager.countSessionsWithAttribute(AUTH_USER_KEY); + List loggedUsers = sessionManager.sessionAttributeValues(AUTH_USER_KEY); + PrivateMessageStats pmStats = privateMessageRepository.stats(userId); + Responses.html(ctx.exchange(), 200, Pages.chatPage(username, loggedUsersCount, loggedUsers, pmStats)); + }); + + router.get("/private", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + session.setAttribute(CURRENT_PAGE_KEY, "private"); + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + String username = String.valueOf(session.getAttribute(AUTH_USER_KEY)); + String orderBy = readDisplaySetting( + ctx, session, settingsRepository, userId, + SETTINGS_PM_PRIVATE_ORDER, "oBy", "readed desc, lastitem desc" + ); + String perPage = readDisplaySetting( + ctx, session, settingsRepository, userId, + SETTINGS_PM_PRIVATE_PERPAGE, "perpage", "25" + ); + int pageNo = Math.max(1, parseInt(ctx.queryParam("iPageNo"), 1)); + List allThreads = privateMessageRepository.listThreads(userId, orderBy); + int totalRows = allThreads.size(); + int pageSize; + if ("all".equalsIgnoreCase(perPage)) { + pageSize = Math.max(totalRows, 1); + } else { + int parsed = parseInt(perPage, 25); + pageSize = parsed <= 0 ? 25 : parsed; + } + int totalPages = Math.max(1, (int) Math.ceil(totalRows / (double) pageSize)); + int currentPage = Math.min(Math.max(1, pageNo), totalPages); + int fromIdx = totalRows == 0 ? 0 : (currentPage - 1) * pageSize; + int toIdx = totalRows == 0 ? 0 : Math.min(fromIdx + pageSize, totalRows); + List threads = totalRows == 0 ? List.of() : allThreads.subList(fromIdx, toIdx); + int rowsFrom = totalRows == 0 ? 0 : fromIdx + 1; + int rowsTo = toIdx; + int loggedUsersCount = sessionManager.countSessionsWithAttribute(AUTH_USER_KEY); + List loggedUsers = sessionManager.sessionAttributeValues(AUTH_USER_KEY); + PrivateMessageStats pmStats = privateMessageRepository.stats(userId); + Responses.html( + ctx.exchange(), + 200, + Pages.privatePage( + username, + threads, + orderBy, + perPage, + currentPage, + totalPages, + rowsFrom, + rowsTo, + totalRows, + loggedUsersCount, + loggedUsers, + pmStats + ) + ); + }); + + router.post("/private", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + String orderBy = valueOrDefault(ctx.formParam("oBy"), "readed desc, lastitem desc"); + String perPage = valueOrDefault(ctx.formParam("perpage"), "25"); + String iPageNo = valueOrDefault(ctx.formParam("iPageNo"), "1"); + session.setAttribute(SETTINGS_PM_PRIVATE_ORDER, orderBy); + session.setAttribute(SETTINGS_PM_PRIVATE_PERPAGE, perPage); + settingsRepository.upsertUserSetting(userId, SETTINGS_PM_PRIVATE_ORDER, orderBy); + settingsRepository.upsertUserSetting(userId, SETTINGS_PM_PRIVATE_PERPAGE, perPage); + String action = valueOrDefault(ctx.formParam("dowhat"), ""); + String idsCsv = valueOrDefault(ctx.formParam("pmidsCsv"), ""); + List ids = Arrays.stream(idsCsv.split(",")) + .map(String::trim) + .filter(s -> !s.isBlank()) + .map(s -> parseLong(s, -1L)) + .filter(id -> id > 0) + .collect(Collectors.toList()); + privateMessageRepository.bulkAction(userId, ids, action); + Responses.redirect( + ctx.exchange(), + "/private?oBy=" + URLEncoder.encode(orderBy, StandardCharsets.UTF_8) + + "&perpage=" + URLEncoder.encode(perPage, StandardCharsets.UTF_8) + + "&iPageNo=" + URLEncoder.encode(iPageNo, StandardCharsets.UTF_8) + ); + }); + + router.get("/newpm", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + session.setAttribute(CURRENT_PAGE_KEY, "newpm"); + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + String username = String.valueOf(session.getAttribute(AUTH_USER_KEY)); + long toUser = parseLong(ctx.queryParam("uid"), 0L); + if (toUser <= 0) { + Responses.redirect(ctx.exchange(), "/private"); + return; + } + String toUsername = privateMessageRepository.usernameById(toUser); + String error = valueOrDefault((String) session.getAttribute(PM_ERROR_KEY), ""); + session.setAttribute(PM_ERROR_KEY, null); + int loggedUsersCount = sessionManager.countSessionsWithAttribute(AUTH_USER_KEY); + List loggedUsers = sessionManager.sessionAttributeValues(AUTH_USER_KEY); + PrivateMessageStats pmStats = privateMessageRepository.stats(userId); + Responses.html(ctx.exchange(), 200, Pages.newPmPage(username, toUser, toUsername, error, loggedUsersCount, loggedUsers, pmStats)); + }); + + router.get("/newthread", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + session.setAttribute(CURRENT_PAGE_KEY, "newthread"); + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + String username = String.valueOf(session.getAttribute(AUTH_USER_KEY)); + String error = valueOrDefault((String) session.getAttribute(NEW_THREAD_ERROR_KEY), ""); + session.setAttribute(NEW_THREAD_ERROR_KEY, null); + int loggedUsersCount = sessionManager.countSessionsWithAttribute(AUTH_USER_KEY); + List loggedUsers = sessionManager.sessionAttributeValues(AUTH_USER_KEY); + PrivateMessageStats pmStats = privateMessageRepository.stats(userId); + Responses.html(ctx.exchange(), 200, Pages.newThreadPage(username, error, loggedUsersCount, loggedUsers, pmStats)); + }); + + router.post("/process/newthread", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + String forumname = ctx.formParam("forumname"); + String description = ctx.formParam("description"); + String password = ctx.formParam("password"); + StringBuilder err = new StringBuilder(); + if (forumname == null || forumname.isBlank()) { + err.append("Thread name must be specified.
"); + } + if (description == null || description.isBlank()) { + err.append("Description must be specified.
"); + } + if (err.length() > 0) { + session.setAttribute(NEW_THREAD_ERROR_KEY, err.toString()); + Responses.redirect(ctx.exchange(), "/newthread"); + return; + } + forumRepository.createThread(userId, forumname, description, password); + Responses.redirect(ctx.exchange(), "/forum"); + }); + router.post("/process/newthread.jsp", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + String forumname = ctx.formParam("forumname"); + String description = ctx.formParam("description"); + String password = ctx.formParam("password"); + StringBuilder err = new StringBuilder(); + if (forumname == null || forumname.isBlank()) { + err.append("Thread name must be specified.
"); + } + if (description == null || description.isBlank()) { + err.append("Description must be specified.
"); + } + if (err.length() > 0) { + session.setAttribute(NEW_THREAD_ERROR_KEY, err.toString()); + Responses.redirect(ctx.exchange(), "/newthread"); + return; + } + forumRepository.createThread(userId, forumname, description, password); + Responses.redirect(ctx.exchange(), "/forum"); + }); + + router.get("/message", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + session.setAttribute(CURRENT_PAGE_KEY, "message"); + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + String username = String.valueOf(session.getAttribute(AUTH_USER_KEY)); + long pmid = parseLong(ctx.queryParam("pmid"), -1L); + String perPage = readDisplaySetting( + ctx, session, settingsRepository, userId, + SETTINGS_PM_THREAD_PERPAGE, "perpage", "10" + ); + int pageNo = Math.max(1, parseInt(ctx.queryParam("iPageNo"), 1)); + PrivateThreadRoot root = privateMessageRepository.threadRoot(userId, pmid); + if (root == null) { + Responses.redirect(ctx.exchange(), "/private"); + return; + } + privateMessageRepository.markThreadRead(userId, pmid); + List allMessages = privateMessageRepository.threadMessages(userId, pmid); + int totalRows = allMessages.size(); + int pageSize; + if ("all".equalsIgnoreCase(perPage)) { + pageSize = Math.max(totalRows, 1); + } else { + int parsed = parseInt(perPage, 10); + pageSize = parsed <= 0 ? 10 : parsed; + } + int totalPages = Math.max(1, (int) Math.ceil(totalRows / (double) pageSize)); + int currentPage = Math.min(Math.max(1, pageNo), totalPages); + int fromIdx = totalRows == 0 ? 0 : (currentPage - 1) * pageSize; + int toIdx = totalRows == 0 ? 0 : Math.min(fromIdx + pageSize, totalRows); + List messages = totalRows == 0 ? List.of() : allMessages.subList(fromIdx, toIdx); + int rowsFrom = totalRows == 0 ? 0 : fromIdx + 1; + int rowsTo = toIdx; + String error = valueOrDefault((String) session.getAttribute(PM_ERROR_KEY), ""); + session.setAttribute(PM_ERROR_KEY, null); + int loggedUsersCount = sessionManager.countSessionsWithAttribute(AUTH_USER_KEY); + List loggedUsers = sessionManager.sessionAttributeValues(AUTH_USER_KEY); + PrivateMessageStats pmStats = privateMessageRepository.stats(userId); + Responses.html( + ctx.exchange(), + 200, + Pages.messagePage( + username, + root, + messages, + perPage, + currentPage, + totalPages, + rowsFrom, + rowsTo, + totalRows, + error, + loggedUsersCount, + loggedUsers, + pmStats + ) + ); + }); + + router.post("/process/replymessage", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + long fromUser = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + long toUser = parseLong(ctx.formParam("to_user"), -1L); + long pmid = parseLong(ctx.formParam("pmid"), -1L); + String perPage = valueOrDefault(ctx.formParam("perpage"), "10"); + String iPageNo = valueOrDefault(ctx.formParam("iPageNo"), "1"); + session.setAttribute(SETTINGS_PM_THREAD_PERPAGE, perPage); + settingsRepository.upsertUserSetting(fromUser, SETTINGS_PM_THREAD_PERPAGE, perPage); + String title = ctx.formParam("title"); + String message = ctx.formParam("message"); + StringBuilder err = new StringBuilder(); + if (message == null || message.isBlank()) { + err.append("Message text must be specified.
"); + } + if (title == null || title.isBlank()) { + err.append("Title must be specified.
"); + } + if (toUser <= 0) { + err.append("to_user must be specified.
"); + } + if (err.length() > 0) { + session.setAttribute(PM_ERROR_KEY, err.toString()); + if (pmid > 0) { + Responses.redirect( + ctx.exchange(), + "/message?pmid=" + pmid + + "&perpage=" + URLEncoder.encode(perPage, StandardCharsets.UTF_8) + + "&iPageNo=" + URLEncoder.encode(iPageNo, StandardCharsets.UTF_8) + ); + } else { + Responses.redirect(ctx.exchange(), "/newpm?uid=" + toUser); + } + return; + } + privateMessageRepository.send(fromUser, toUser, title, message, pmid > 0 ? pmid : null); + if (pmid > 0) { + Responses.redirect( + ctx.exchange(), + "/message?pmid=" + pmid + + "&perpage=" + URLEncoder.encode(perPage, StandardCharsets.UTF_8) + + "&iPageNo=" + URLEncoder.encode(iPageNo, StandardCharsets.UTF_8) + ); + } else { + Responses.redirect(ctx.exchange(), "/private"); + } + }); + router.post("/process/replymessage.jsp", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + long fromUser = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + long toUser = parseLong(ctx.formParam("to_user"), -1L); + long pmid = parseLong(ctx.formParam("pmid"), -1L); + String perPage = valueOrDefault(ctx.formParam("perpage"), "10"); + String iPageNo = valueOrDefault(ctx.formParam("iPageNo"), "1"); + session.setAttribute(SETTINGS_PM_THREAD_PERPAGE, perPage); + settingsRepository.upsertUserSetting(fromUser, SETTINGS_PM_THREAD_PERPAGE, perPage); + String title = ctx.formParam("title"); + String message = ctx.formParam("message"); + StringBuilder err = new StringBuilder(); + if (message == null || message.isBlank()) { + err.append("Message text must be specified.
"); + } + if (title == null || title.isBlank()) { + err.append("Title must be specified.
"); + } + if (toUser <= 0) { + err.append("to_user must be specified.
"); + } + if (err.length() > 0) { + session.setAttribute(PM_ERROR_KEY, err.toString()); + if (pmid > 0) { + Responses.redirect( + ctx.exchange(), + "/message?pmid=" + pmid + + "&perpage=" + URLEncoder.encode(perPage, StandardCharsets.UTF_8) + + "&iPageNo=" + URLEncoder.encode(iPageNo, StandardCharsets.UTF_8) + ); + } else { + Responses.redirect(ctx.exchange(), "/newpm?uid=" + toUser); + } + return; + } + privateMessageRepository.send(fromUser, toUser, title, message, pmid > 0 ? pmid : null); + if (pmid > 0) { + Responses.redirect( + ctx.exchange(), + "/message?pmid=" + pmid + + "&perpage=" + URLEncoder.encode(perPage, StandardCharsets.UTF_8) + + "&iPageNo=" + URLEncoder.encode(iPageNo, StandardCharsets.UTF_8) + ); + } else { + Responses.redirect(ctx.exchange(), "/private"); + } + }); + + router.get("/forumdisplay", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + session.setAttribute(CURRENT_PAGE_KEY, "forumdisplay"); + + long forumId = parseLong(ctx.queryParam("f"), -1L); + if (forumId <= 0) { + Responses.redirect(ctx.exchange(), "/forum"); + return; + } + + ForumDetail forum = forumRepository.findForumById(forumId); + if (forum == null) { + Responses.redirect(ctx.exchange(), "/forum"); + return; + } + + String username = String.valueOf(session.getAttribute(AUTH_USER_KEY)); + long quoteItemId = parseLong(ctx.queryParam("q"), 0L); + QuotedTextItem quotedTextItem = quoteItemId > 0 ? forumRepository.findQuotedItem(quoteItemId) : null; + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + forumRepository.putHistoryRecord(userId, forumId); + String searchText = valueOrDefault(ctx.queryParam("stext"), ""); + String showType = readDisplaySetting( + ctx, session, settingsRepository, userId, + SETTINGS_SHOWTYPE_PREFIX + forumId, "showType", "full" + ); + String showImg = readDisplaySetting( + ctx, session, settingsRepository, userId, + SETTINGS_SHOWIMG_PREFIX + forumId, "showImg", "true" + ); + String sortBy = readDisplaySetting( + ctx, session, settingsRepository, userId, + SETTINGS_SORT_BY_PREFIX + forumId, "sortBy", "fi.id" + ); + String sortType = readDisplaySetting( + ctx, session, settingsRepository, userId, + SETTINGS_SORT_TYPE_PREFIX + forumId, "sortType", "desc" + ); + String perPage = readDisplaySetting( + ctx, session, settingsRepository, userId, + SETTINGS_PERPAGE_PREFIX + forumId, "perpage", "50" + ); + int pageNo = Math.max(1, parseInt(ctx.queryParam("iPageNo"), 1)); + + List messagesAll = forumRepository.listMessagesByForumId(forumId); + List filtered = filterAndSortMessages(messagesAll, searchText, sortBy, sortType); + if ("only".equalsIgnoreCase(showImg)) { + filtered = filtered.stream() + .filter(m -> m.getAttachments() != null && m.getAttachments().stream().anyMatch(ForumAttachment::isPicture)) + .collect(Collectors.toList()); + } + ForumDisplayView view = paginate(filtered, pageNo, perPage, searchText, showType, showImg, sortBy, sortType); + + int loggedUsersCount = sessionManager.countSessionsWithAttribute(AUTH_USER_KEY); + List loggedUsers = sessionManager.sessionAttributeValues(AUTH_USER_KEY); + MessageRenderSettings renderSettings = settingsRepository.loadMessageRenderSettings(); + PrivateMessageStats pmStats = privateMessageRepository.stats(userId); + Responses.html( + ctx.exchange(), + 200, + Pages.forumDisplayPage( + username, + userId, + forum, + view, + quoteItemId > 0 ? quoteItemId : null, + quotedTextItem, + loggedUsersCount, + loggedUsers, + renderSettings, + pmStats + ) + ); + }); + + router.get("/process/showimage", ctx -> { + if (!isAuthenticated(ctx)) { + Responses.text(ctx.exchange(), 401, "Unauthorized"); + return; + } + long attachmentId = parseLong(ctx.queryParam("attachmentid"), -1L); + if (attachmentId > 0) { + AttachmentData attachment = forumRepository.findAttachmentData(attachmentId); + if (attachment == null) { + Responses.text(ctx.exchange(), 404, "Attachment not found"); + return; + } + Responses.send(ctx.exchange(), 200, attachment.getContentType(), attachment.getData()); + return; + } + if (!"yes".equals(ctx.queryParam("userIcon"))) { + Responses.text(ctx.exchange(), 400, "Unsupported image request"); + return; + } + + SessionData session = ctx.getSession(); + long requestedUserId = parseLong(ctx.queryParam("uid"), -1L); + Object userIdRaw = session == null ? null : session.getAttribute(AUTH_USER_ID_KEY); + long sessionUserId = parseLong(userIdRaw, -1L); + long userId = requestedUserId > 0 ? requestedUserId : sessionUserId; + if (userId <= 0) { + Responses.text(ctx.exchange(), 404, "User icon not found"); + return; + } + + UserIcon icon = userIconRepository.findLatestByUserId(userId); + if (icon == null) { + Responses.text(ctx.exchange(), 404, "User icon not found"); + return; + } + + Responses.send(ctx.exchange(), 200, icon.getContentType(), icon.getData()); + }); + + router.get("/process/download", ctx -> { + if (!isAuthenticated(ctx)) { + Responses.text(ctx.exchange(), 401, "Unauthorized"); + return; + } + long attachmentId = parseLong(ctx.queryParam("attachmentid"), -1L); + AttachmentData attachment = forumRepository.findAttachmentData(attachmentId); + if (attachment == null) { + Responses.text(ctx.exchange(), 404, "Attachment not found"); + return; + } + String safeName = attachment.getName().replace("\"", ""); + ctx.exchange().getResponseHeaders().set( + "Content-Disposition", + "inline; filename=\"" + safeName + "\"; filename*=UTF-8''" + + URLEncoder.encode(safeName, StandardCharsets.UTF_8) + ); + Responses.send(ctx.exchange(), 200, attachment.getContentType(), attachment.getData()); + }); + + router.post("/process/ajaxreq.jsp", ctx -> { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.text(ctx.exchange(), 401, "invalid"); + return; + } + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + String ajaxMethod = ctx.formParam("ajaxMethod"); + long universalId = parseLong(ctx.formParam("universalId"), -1L); + if (userId <= 0 || ajaxMethod == null || ajaxMethod.isBlank()) { + Responses.text(ctx.exchange(), 400, "invalid"); + return; + } + if (ajaxMethod.startsWith("ajaxVoting")) { + if (universalId <= 0) { + Responses.text(ctx.exchange(), 400, "invalid"); + return; + } + int requestedVote = ajaxMethod.endsWith("_yes") ? 1 : -1; + Integer existingVote = forumRepository.getUserVote(userId, universalId); + if (existingVote == null) { + forumRepository.addVote(userId, universalId, requestedVote); + } else if (existingVote == requestedVote) { + forumRepository.deleteVote(userId, universalId); + } else { + forumRepository.updateVote(userId, universalId, requestedVote); + } + VoteStats stats = forumRepository.getVoteStats(universalId); + String xml = "\n" + + "\n" + + "" + universalId + "\n" + + "" + stats.getYes() + "\n" + + "" + stats.getNo() + "\n" + + ""; + Responses.send(ctx.exchange(), 200, "text/xml; charset=UTF-8", xml.getBytes(StandardCharsets.UTF_8)); + return; + } + + if (ajaxMethod.startsWith("ajaxChatVoting")) { + if (universalId <= 0) { + Responses.text(ctx.exchange(), 400, "invalid"); + return; + } + if (!chatRepository.alreadyChatVoted(userId, universalId)) { + int vote = ajaxMethod.endsWith("_yes") ? 1 : -1; + chatRepository.addChatVote(userId, universalId, vote); + } + ChatVoteStats stats = chatRepository.getChatVoteStats(universalId); + String xml = "\n" + + "\n" + + "" + universalId + "\n" + + "" + xmlEscape(stats.getThumbUpUsers()) + "\n" + + "" + xmlEscape(stats.getThumbDownUsers()) + "\n" + + ""; + Responses.send(ctx.exchange(), 200, "text/xml; charset=UTF-8", xml.getBytes(StandardCharsets.UTF_8)); + return; + } + + if (ajaxMethod.startsWith("asynchUpdate") || ajaxMethod.startsWith("chat_text_") || "firstUpdateChat".equals(ajaxMethod)) { + if (universalId > 0) { + chatRepository.confirmChatRead(userId, universalId); + } + if (ajaxMethod.startsWith("chat_text_")) { + String message = ajaxMethod.substring("chat_text_".length()).trim(); + chatRepository.addChatMessage(userId, message); + } + + StringBuilder resp = new StringBuilder("\n\n"); + resp.append(" \n"); + for (SessionData activeSession : sessionManager.activeSessions()) { + Object activeUsername = activeSession.getAttribute(AUTH_USER_KEY); + Object activeUserId = activeSession.getAttribute(AUTH_USER_ID_KEY); + if (activeUsername == null || activeUserId == null) { + continue; + } + long activeUid = parseLong(activeUserId, -1L); + if (activeUid <= 0) { + continue; + } + long inactiveMinutes = Math.max(0, (System.currentTimeMillis() - activeSession.lastAccessMillis()) / 60000L); + boolean inChat = "chat".equals(String.valueOf(activeSession.getAttribute(CURRENT_PAGE_KEY))); + resp.append(" ").append(activeUid).append("\n") + .append(" ").append(xmlEscape(String.valueOf(activeUsername))).append("\n") + .append(" ").append(inChat).append("\n") + .append(" ").append(inactiveMinutes).append("\n"); + } + resp.append(" \n"); + + Map newCounts = forumRepository.getNewMessagesCountByUserId(userId); + if (!newCounts.isEmpty()) { + resp.append(" \n"); + for (Map.Entry entry : newCounts.entrySet()) { + resp.append(" ").append(entry.getKey()).append("\n") + .append(" ").append(entry.getValue()).append("\n"); + } + resp.append(" \n"); + } + + long maxChatId = -1L; + boolean includeLines = "firstUpdateChat".equals(ajaxMethod) + || (("asynchUpdateChat".equals(ajaxMethod) || ajaxMethod.startsWith("chat_text_")) + && chatRepository.hasUserNewChatMessage(userId)); + if (includeLines) { + resp.append(" \n"); + List lines = chatRepository.listRecentChatLines(userId, 40); + for (ChatLine line : lines) { + resp.append(" \n") + .append(" ").append(xmlEscape(line.getFromName())).append("\n") + .append(" ").append(line.getId()).append("\n") + .append(" ").append(line.getNewMessage()).append("\n") + .append(" \n") + .append(" \n") + .append(" ").append(xmlEscape(line.getThumbUpUsers())).append("\n") + .append(" ").append(xmlEscape(line.getThumbDownUsers())).append("\n") + .append(" \n"); + if (line.getId() > maxChatId) { + maxChatId = line.getId(); + } + } + resp.append(" \n"); + } + + int unreadPm = chatRepository.getUnreadMessagesByUser(userId); + if (unreadPm > 0) { + resp.append(" ").append(unreadPm).append("\n"); + } + if (chatRepository.hasUserNewChatMessageOther(userId)) { + resp.append(" 1\n"); + } + resp.append(""); + Responses.send(ctx.exchange(), 200, "text/xml; charset=UTF-8", resp.toString().getBytes(StandardCharsets.UTF_8)); + if (maxChatId > -1L) { + chatRepository.confirmChatDownloaded(userId, maxChatId); + } + return; + } + + Responses.text(ctx.exchange(), 400, "invalid"); + }); + } + + private static boolean isAuthenticated(RequestContext ctx) { + SessionData session = ctx.getSession(); + return session != null && session.getAttribute(AUTH_USER_KEY) != null; + } + + private static void handleReplyThread( + RequestContext ctx, + ForumRepository forumRepository + ) throws IOException { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + String contentType = ctx.exchange().getRequestHeaders().getFirst("Content-Type"); + boolean multipart = contentType != null && contentType.toLowerCase().startsWith("multipart/form-data"); + + long forumId; + long quoteItem; + String message; + List attachments; + + if (multipart) { + MultipartFormData data = MultipartFormData.parse(ctx.exchange(), AppConfig.getInt("app.multipart.max_bytes", 1024 * 1024 * 50), StandardCharsets.UTF_8); + forumId = parseLong(data.field("forumid"), -1L); + quoteItem = parseLong(data.field("quoteItem"), 0L); + message = valueOrDefault(data.field("message"), ""); + attachments = new ArrayList<>(); + attachments.addAll(data.files("attachment[]")); + attachments.addAll(data.files("attachment")); + } else { + forumId = parseLong(ctx.formParam("forumid"), -1L); + quoteItem = parseLong(ctx.formParam("quoteItem"), 0L); + message = valueOrDefault(ctx.formParam("message"), ""); + attachments = List.of(); + } + + boolean hasMessage = message != null && !message.isBlank(); + boolean hasAttachments = attachments != null && !attachments.isEmpty(); + if (forumId > 0 && userId > 0 && (hasMessage || hasAttachments)) { + long forumItemId = forumRepository.addReply(forumId, userId, message, quoteItem > 0 ? quoteItem : null); + if (forumItemId > 0 && hasAttachments) { + for (MultipartFormData.FileItem fileItem : attachments) { + forumRepository.addAttachment( + forumItemId, + fileItem.getFileName(), + fileItem.getData(), + fileItem.getContentType() + ); + } + } + } + + if (forumId > 0) { + Responses.redirect(ctx.exchange(), "/forumdisplay?f=" + forumId); + } else { + Responses.redirect(ctx.exchange(), "/forum"); + } + } + + private static void handleSaveIcon( + RequestContext ctx, + UserIconRepository userIconRepository + ) throws IOException { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + if (userId <= 0) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + + String contentType = ctx.exchange().getRequestHeaders().getFirst("Content-Type"); + boolean multipart = contentType != null && contentType.toLowerCase().startsWith("multipart/form-data"); + if (!multipart) { + session.setAttribute(MEMBER_ERROR_KEY, "Icon upload requires multipart/form-data."); + Responses.redirect(ctx.exchange(), "/member"); + return; + } + + try { + MultipartFormData data = MultipartFormData.parse(ctx.exchange(), AppConfig.getInt("app.multipart.icon_max_bytes", 1024 * 1024), StandardCharsets.UTF_8); + List icons = new ArrayList<>(); + icons.addAll(data.files("iconfile")); + icons.addAll(data.files("iconFile")); + icons.addAll(data.files("icon")); + if (!icons.isEmpty()) { + MultipartFormData.FileItem icon = icons.get(0); + boolean ok = userIconRepository.saveUserIcon(userId, icon.getData(), icon.getContentType()); + if (!ok) { + session.setAttribute(MEMBER_ERROR_KEY, "Error while saving icon."); + } else { + session.setAttribute(MEMBER_ERROR_KEY, null); + } + } + } catch (IOException ex) { + session.setAttribute(MEMBER_ERROR_KEY, "Icon upload is too large. Maximum is 1MB."); + } + Responses.redirect(ctx.exchange(), "/member"); + } + + private static void handleSaveMember( + RequestContext ctx, + MemberRepository memberRepository, + SettingsRepository settingsRepository + ) throws IOException { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + if (userId <= 0) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + + String changePwd = ctx.formParam("changePwd"); + String changeInfo = ctx.formParam("changeInfo"); + if (changePwd != null && !changePwd.isBlank()) { + String oldPassword = valueOrDefault(ctx.formParam("oldpassword"), ""); + String password = valueOrDefault(ctx.formParam("password"), ""); + String passwordConfirm = valueOrDefault(ctx.formParam("passwordconfirm"), ""); + if (oldPassword.isBlank() || password.isBlank() || passwordConfirm.isBlank()) { + session.setAttribute(MEMBER_ERROR_KEY, "If you want to change password, please fill out all password fields."); + Responses.redirect(ctx.exchange(), "/member"); + return; + } + if (!password.equals(passwordConfirm)) { + session.setAttribute(MEMBER_ERROR_KEY, "The entered passwords do not match."); + Responses.redirect(ctx.exchange(), "/member"); + return; + } + if (!memberRepository.changePassword(userId, oldPassword, password)) { + session.setAttribute(MEMBER_ERROR_KEY, "Old password does not match your current password."); + Responses.redirect(ctx.exchange(), "/member"); + return; + } + session.setAttribute(MEMBER_ERROR_KEY, null); + Responses.redirect(ctx.exchange(), "/forum"); + return; + } + + if (changeInfo != null && !changeInfo.isBlank()) { + String email = valueOrDefault(ctx.formParam("email"), ""); + String emailConfirm = valueOrDefault(ctx.formParam("emailconfirm"), ""); + String firstName = valueOrDefault(ctx.formParam("firstname"), ""); + String lastName = valueOrDefault(ctx.formParam("lastname"), ""); + String city = valueOrDefault(ctx.formParam("city"), ""); + if (email.isBlank() || firstName.isBlank() || lastName.isBlank() || city.isBlank()) { + session.setAttribute(MEMBER_ERROR_KEY, "Please fill all registration parameters."); + Responses.redirect(ctx.exchange(), "/member"); + return; + } + if (!email.equals(emailConfirm)) { + session.setAttribute(MEMBER_ERROR_KEY, "The entered emails do not match."); + Responses.redirect(ctx.exchange(), "/member"); + return; + } + if (!memberRepository.updatePersonalInfo(userId, email, firstName, lastName, city)) { + session.setAttribute(MEMBER_ERROR_KEY, "Error while saving personal info."); + Responses.redirect(ctx.exchange(), "/member"); + return; + } + String soundValue = "soundon".equals(ctx.formParam("soundonoff")) ? "soundon" : "soundoff"; + session.setAttribute(SETTINGS_SOUND_ONOFF, soundValue); + settingsRepository.upsertUserSetting(userId, SETTINGS_SOUND_ONOFF, soundValue); + session.setAttribute(MEMBER_ERROR_KEY, null); + Responses.redirect(ctx.exchange(), "/forum"); + return; + } + + Responses.redirect(ctx.exchange(), "/member"); + } + + private static void handleMysqlClient( + RequestContext ctx, + SessionManager sessionManager, + PrivateMessageRepository privateMessageRepository, + MysqlClientRepository mysqlClientRepository + ) throws IOException { + SessionData session = ctx.getSession(); + if (session == null || session.getAttribute(AUTH_USER_KEY) == null) { + Responses.redirect(ctx.exchange(), "/login"); + return; + } + + String username = String.valueOf(session.getAttribute(AUTH_USER_KEY)); + if (!isMysqlAdmin(username)) { + Responses.text(ctx.exchange(), 403, "MySQL Client is available only for admin user."); + return; + } + + long userId = parseLong(session.getAttribute(AUTH_USER_ID_KEY), -1L); + String database = "mysql"; + String tableName = ""; + String rowsToShow = "50"; + String myQuery = ""; + String tableSql = ""; + String dbNameForAction = ""; + String command = ""; + String info = ""; + String error = ""; + MysqlClientRepository.SqlExecution result = null; + + Map params = "POST".equalsIgnoreCase(ctx.method()) ? ctx.formParams() : ctx.queryParams(); + if (!params.isEmpty()) { + database = valueOrDefault(params.get("database"), database); + tableName = valueOrDefault(params.get("tablename"), tableName); + rowsToShow = valueOrDefault(params.get("rowsToShow"), rowsToShow); + myQuery = valueOrDefault(params.get("myQuery"), myQuery); + tableSql = valueOrDefault(params.get("tableSql"), tableSql); + dbNameForAction = valueOrDefault(params.get("databasename"), dbNameForAction); + command = valueOrDefault(params.get("command"), command); + } + + if (!command.isBlank()) { + try { + switch (command.toLowerCase()) { + case "changedatabase": + tableName = ""; + break; + case "changetable": + if (!database.isBlank() && !tableName.isBlank()) { + int limit = parseInt(rowsToShow, 50); + if (limit <= 0) { + limit = 50; + } + myQuery = "SELECT * FROM " + mysqlIdentifier(database) + "." + mysqlIdentifier(tableName) + + " LIMIT " + limit; + result = mysqlClientRepository.executeSql(database, myQuery); + info = "Query OK. Returned " + result.getRows().size() + " row(s)."; + } + break; + case "showcreatetable": + if (!database.isBlank() && !tableName.isBlank()) { + myQuery = mysqlClientRepository.showCreateTable(database, tableName); + info = "SHOW CREATE TABLE loaded into query textarea."; + } + break; + case "executemyquery": + if (!myQuery.isBlank()) { + result = mysqlClientRepository.executeSql(database, myQuery); + info = result.isResultSet() + ? "Query OK. Returned " + result.getRows().size() + " row(s)." + : "Statement OK. Affected rows: " + result.getUpdateCount() + "."; + } + break; + case "createdatabase": + if (!dbNameForAction.isBlank()) { + mysqlClientRepository.executeSql("", "CREATE DATABASE " + mysqlIdentifier(dbNameForAction)); + database = dbNameForAction; + info = "Database created: " + dbNameForAction; + } + break; + case "deletedatabase": + String targetDbToDrop = valueOrDefault(dbNameForAction, database); + if (!targetDbToDrop.isBlank()) { + mysqlClientRepository.executeSql("", "DROP DATABASE " + mysqlIdentifier(targetDbToDrop)); + if (targetDbToDrop.equalsIgnoreCase(database)) { + database = "mysql"; + tableName = ""; + } + info = "Database deleted: " + targetDbToDrop; + } + break; + case "deletetable": + if (!database.isBlank() && !tableName.isBlank()) { + mysqlClientRepository.executeSql(database, "DROP TABLE " + mysqlIdentifier(tableName)); + tableName = ""; + info = "Table deleted."; + } + break; + case "createtable": + String createSql = valueOrDefault(tableSql, myQuery); + if (!createSql.isBlank()) { + mysqlClientRepository.executeSql(database, createSql); + info = "CREATE TABLE statement executed."; + } + break; + default: + break; + } + } catch (Exception ex) { + error = ex.getMessage() == null ? "Unknown SQL error." : ex.getMessage(); + } + } + + List databases = mysqlClientRepository.listDatabases(); + if ((database == null || database.isBlank()) && !databases.isEmpty()) { + database = databases.get(0); + } + List tables = mysqlClientRepository.listTables(database); + if (!tableName.isBlank() && !tables.contains(tableName)) { + tableName = ""; + } + + int loggedUsersCount = sessionManager.countSessionsWithAttribute(AUTH_USER_KEY); + List loggedUsers = sessionManager.sessionAttributeValues(AUTH_USER_KEY); + PrivateMessageStats pmStats = privateMessageRepository.stats(userId); + Responses.html( + ctx.exchange(), + 200, + Pages.mysqlClientPage( + username, + databases, + tables, + database, + tableName, + rowsToShow, + myQuery, + tableSql, + command, + info, + error, + result, + loggedUsersCount, + loggedUsers, + pmStats + ) + ); + } + + private static long parseLong(Object raw, long defaultValue) { + if (raw == null) { + return defaultValue; + } + if (raw instanceof Number) { + return ((Number) raw).longValue(); + } + try { + return Long.parseLong(String.valueOf(raw)); + } catch (NumberFormatException ex) { + return defaultValue; + } + } + + private static int parseInt(String raw, int defaultValue) { + if (raw == null || raw.isBlank()) { + return defaultValue; + } + try { + return Integer.parseInt(raw); + } catch (NumberFormatException ex) { + return defaultValue; + } + } + + private static String readDisplaySetting( + RequestContext ctx, + SessionData session, + SettingsRepository settingsRepository, + long userId, + String key, + String paramName, + String defaultValue + ) { + String fromParam = ctx.queryParam(paramName); + if (fromParam != null && !fromParam.isBlank()) { + session.setAttribute(key, fromParam); + settingsRepository.upsertUserSetting(userId, key, fromParam); + return fromParam; + } + Object fromSession = session.getAttribute(key); + if (fromSession == null || String.valueOf(fromSession).isBlank()) { + session.setAttribute(key, defaultValue); + return defaultValue; + } + return String.valueOf(fromSession); + } + + private static List filterAndSortMessages( + List source, + String searchText, + String sortBy, + String sortType + ) { + List filtered = source == null ? new ArrayList<>() : source.stream() + .filter(m -> searchText == null || searchText.isBlank() + || m.getText().toLowerCase().contains(searchText.toLowerCase())) + .collect(Collectors.toCollection(ArrayList::new)); + + Comparator comparator; + if ("ua.username".equals(sortBy)) { + comparator = Comparator.comparing(m -> valueOrDefault(m.getAuthor(), "").toLowerCase()); + } else if ("vvalue".equals(sortBy)) { + comparator = Comparator.comparingInt(ForumMessage::getVoteValue); + } else { + comparator = Comparator.comparingLong(ForumMessage::getCreatedEpochMillis); + } + if (!"asc".equalsIgnoreCase(sortType)) { + comparator = comparator.reversed(); + } + comparator = Comparator.comparing(ForumMessage::isSticky).reversed().thenComparing(comparator); + filtered.sort(comparator); + return filtered; + } + + private static ForumDisplayView paginate( + List filtered, + int pageNo, + String perPage, + String searchText, + String showType, + String showImg, + String sortBy, + String sortType + ) { + int totalRows = filtered == null ? 0 : filtered.size(); + int pageSize; + if ("all".equalsIgnoreCase(perPage)) { + pageSize = Math.max(totalRows, 1); + } else { + int parsed = parseInt(perPage, 50); + pageSize = parsed <= 0 ? 50 : parsed; + } + int totalPages = Math.max(1, (int) Math.ceil(totalRows / (double) pageSize)); + int currentPage = Math.min(Math.max(1, pageNo), totalPages); + int fromIdx = totalRows == 0 ? 0 : (currentPage - 1) * pageSize; + int toIdx = totalRows == 0 ? 0 : Math.min(fromIdx + pageSize, totalRows); + List page = totalRows == 0 ? List.of() : filtered.subList(fromIdx, toIdx); + + int startRow = totalRows == 0 ? 0 : fromIdx + 1; + int endRow = toIdx; + + return new ForumDisplayView( + page, + totalRows, + startRow, + endRow, + totalPages, + currentPage, + valueOrDefault(searchText, ""), + valueOrDefault(showType, "full"), + valueOrDefault(showImg, "true"), + valueOrDefault(sortBy, "fi.id"), + valueOrDefault(sortType, "desc"), + valueOrDefault(perPage, "50") + ); + } + + private static String valueOrDefault(String value, String defaultValue) { + return value == null || value.isBlank() ? defaultValue : value; + } + + private static boolean isMysqlAdmin(String username) { + return username != null && "kamma".equalsIgnoreCase(username); + } + + private static String mysqlIdentifier(String value) { + return "`" + value.replace("`", "``") + "`"; + } + + private static String xmlEscape(String input) { + if (input == null) { + return ""; + } + return input.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); + } + + private static String toCdata(String input) { + if (input == null) { + return ""; + } + return input.replace("]]>", "]]]]>"); + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/auth/AuthService.java b/src/main/java/cz/kamma/fabka/httpserver/auth/AuthService.java new file mode 100644 index 0000000..e537e31 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/auth/AuthService.java @@ -0,0 +1,5 @@ +package cz.kamma.fabka.httpserver.auth; + +public interface AuthService { + AuthenticatedUser authenticate(String username, String password); +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/auth/AuthenticatedUser.java b/src/main/java/cz/kamma/fabka/httpserver/auth/AuthenticatedUser.java new file mode 100644 index 0000000..e176f64 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/auth/AuthenticatedUser.java @@ -0,0 +1,19 @@ +package cz.kamma.fabka.httpserver.auth; + +public class AuthenticatedUser { + private final long userId; + private final String username; + + public AuthenticatedUser(long userId, String username) { + this.userId = userId; + this.username = username; + } + + public long getUserId() { + return userId; + } + + public String getUsername() { + return username; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/auth/DatabaseAuthService.java b/src/main/java/cz/kamma/fabka/httpserver/auth/DatabaseAuthService.java new file mode 100644 index 0000000..206e1cc --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/auth/DatabaseAuthService.java @@ -0,0 +1,52 @@ +package cz.kamma.fabka.httpserver.auth; + +import cz.kamma.fabka.httpserver.crypto.Md5; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class DatabaseAuthService implements AuthService { + private static final String AUTH_SQL = "SELECT id, username FROM user_accounts WHERE userhash=? AND passwd=? LIMIT 1"; + + private final String jdbcUrl; + private final String jdbcUser; + private final String jdbcPassword; + + public DatabaseAuthService(String jdbcUrl, String jdbcUser, String jdbcPassword) { + this.jdbcUrl = jdbcUrl; + this.jdbcUser = jdbcUser; + this.jdbcPassword = jdbcPassword; + } + + @Override + public AuthenticatedUser authenticate(String username, String password) { + if (isBlank(username) || isBlank(password)) { + return null; + } + + String userHash = Md5.hash(username); + String passHash = Md5.hash(password); + + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(AUTH_SQL)) { + ps.setString(1, userHash); + ps.setString(2, passHash); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + return null; + } + return new AuthenticatedUser(rs.getLong("id"), rs.getString("username")); + } + } catch (SQLException ex) { + ex.printStackTrace(); + return null; + } + } + + private static boolean isBlank(String value) { + return value == null || value.isBlank(); + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/auth/EnvAuthService.java b/src/main/java/cz/kamma/fabka/httpserver/auth/EnvAuthService.java new file mode 100644 index 0000000..8c117c6 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/auth/EnvAuthService.java @@ -0,0 +1,21 @@ +package cz.kamma.fabka.httpserver.auth; + +import java.util.Objects; + +public class EnvAuthService implements AuthService { + private final String expectedUser; + private final String expectedPassword; + + public EnvAuthService(String expectedUser, String expectedPassword) { + this.expectedUser = expectedUser; + this.expectedPassword = expectedPassword; + } + + @Override + public AuthenticatedUser authenticate(String username, String password) { + if (Objects.equals(expectedUser, username) && Objects.equals(expectedPassword, password)) { + return new AuthenticatedUser(0L, username); + } + return null; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/crypto/Md5.java b/src/main/java/cz/kamma/fabka/httpserver/crypto/Md5.java new file mode 100644 index 0000000..ec98c95 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/crypto/Md5.java @@ -0,0 +1,31 @@ +package cz.kamma.fabka.httpserver.crypto; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public final class Md5 { + private Md5() { + } + + public static String hash(String value) { + if (value == null) { + return ""; + } + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest(value.getBytes(StandardCharsets.UTF_8)); + StringBuilder hex = new StringBuilder(digest.length * 2); + for (byte b : digest) { + String part = Integer.toHexString(b & 0xff); + if (part.length() == 1) { + hex.append('0'); + } + hex.append(part); + } + return hex.toString(); + } catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException("MD5 algorithm is unavailable", ex); + } + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/http/ClasspathStaticFileHandler.java b/src/main/java/cz/kamma/fabka/httpserver/http/ClasspathStaticFileHandler.java new file mode 100644 index 0000000..c9e8af2 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/http/ClasspathStaticFileHandler.java @@ -0,0 +1,77 @@ +package cz.kamma.fabka.httpserver.http; + +import java.io.IOException; +import java.io.InputStream; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +public class ClasspathStaticFileHandler implements HttpHandler { + private final String basePath; + private final String prefix; + + public ClasspathStaticFileHandler(String prefix, String basePath) { + this.prefix = prefix; + this.basePath = basePath; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + if (!"GET".equals(exchange.getRequestMethod())) { + Responses.text(exchange, 405, "Method not allowed"); + return; + } + + String path = exchange.getRequestURI().getPath(); + if (path.startsWith(prefix)) { + String rel = path.substring(prefix.length()); + if (rel.startsWith("/")) { + rel = rel.substring(1); + } + String resourcePath = basePath + "/" + rel; + serveResource(exchange, resourcePath); + } else { + Responses.text(exchange, 404, "Not found"); + } + } + + private void serveResource(HttpExchange exchange, String resourcePath) throws IOException { + try (InputStream in = ClasspathStaticFileHandler.class.getClassLoader().getResourceAsStream(resourcePath)) { + if (in == null) { + Responses.text(exchange, 404, "Not found"); + return; + } + + byte[] data = in.readAllBytes(); + String contentType = guessContentType(resourcePath); + + Responses.send(exchange, 200, contentType, data); + } + } + + private static String guessContentType(String resourcePath) { + String file = resourcePath.toLowerCase(); + if (file.endsWith(".css")) { + return "text/css; charset=UTF-8"; + } + if (file.endsWith(".js")) { + return "application/javascript; charset=UTF-8"; + } + if (file.endsWith(".png")) { + return "image/png"; + } + if (file.endsWith(".jpg") || file.endsWith(".jpeg")) { + return "image/jpeg"; + } + if (file.endsWith(".gif")) { + return "image/gif"; + } + if (file.endsWith(".wav")) { + return "audio/wav"; + } + if (file.endsWith(".ico")) { + return "image/x-icon"; + } + return "application/octet-stream"; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/http/MultipartFormData.java b/src/main/java/cz/kamma/fabka/httpserver/http/MultipartFormData.java new file mode 100644 index 0000000..f69b662 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/http/MultipartFormData.java @@ -0,0 +1,184 @@ +package cz.kamma.fabka.httpserver.http; + +import com.sun.net.httpserver.HttpExchange; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class MultipartFormData { + private static final Pattern NAME_PATTERN = Pattern.compile("name=\"([^\"]*)\""); + private static final Pattern FILENAME_PATTERN = Pattern.compile("filename=\"([^\"]*)\""); + + private final Map fields; + private final Map> files; + + private MultipartFormData(Map fields, Map> files) { + this.fields = fields; + this.files = files; + } + + public static MultipartFormData parse(HttpExchange exchange, int maxBytes, Charset textCharset) throws IOException { + String contentType = exchange.getRequestHeaders().getFirst("Content-Type"); + if (contentType == null || !contentType.toLowerCase(Locale.ROOT).startsWith("multipart/form-data")) { + return new MultipartFormData(Map.of(), Map.of()); + } + String boundary = extractBoundary(contentType); + if (boundary == null || boundary.isBlank()) { + return new MultipartFormData(Map.of(), Map.of()); + } + + byte[] body = exchange.getRequestBody().readNBytes(maxBytes + 1); + if (body.length > maxBytes) { + throw new IOException("Multipart payload too large"); + } + String raw = new String(body, StandardCharsets.ISO_8859_1); + String delimiter = "--" + boundary; + String[] parts = raw.split(Pattern.quote(delimiter)); + + Map fields = new HashMap<>(); + Map> files = new HashMap<>(); + + for (String part : parts) { + if (part == null || part.isBlank() || "--".equals(part.trim())) { + continue; + } + String normalized = part; + if (normalized.startsWith("\r\n")) { + normalized = normalized.substring(2); + } + if (normalized.endsWith("\r\n")) { + normalized = normalized.substring(0, normalized.length() - 2); + } + int headerEnd = normalized.indexOf("\r\n\r\n"); + if (headerEnd < 0) { + continue; + } + + String headerBlock = normalized.substring(0, headerEnd); + String payload = normalized.substring(headerEnd + 4); + String disposition = headerValue(headerBlock, "content-disposition"); + if (disposition == null || disposition.isBlank()) { + continue; + } + String fieldName = dispositionValue(disposition, NAME_PATTERN); + if (fieldName == null || fieldName.isBlank()) { + continue; + } + String fileNameRaw = dispositionValue(disposition, FILENAME_PATTERN); + byte[] payloadBytes = payload.getBytes(StandardCharsets.ISO_8859_1); + + if (fileNameRaw == null) { + String value = new String(payloadBytes, textCharset); + fields.put(fieldName, value); + continue; + } + + String fileName = sanitizeFileName(new String(fileNameRaw.getBytes(StandardCharsets.ISO_8859_1), textCharset)); + if (fileName.isBlank() || payloadBytes.length == 0) { + continue; + } + String fileContentType = headerValue(headerBlock, "content-type"); + if (fileContentType == null || fileContentType.isBlank()) { + fileContentType = "application/octet-stream"; + } + files.computeIfAbsent(fieldName, k -> new ArrayList<>()) + .add(new FileItem(fieldName, fileName, fileContentType, payloadBytes)); + } + + return new MultipartFormData(fields, files); + } + + public String field(String name) { + return fields.get(name); + } + + public List files(String fieldName) { + return files.getOrDefault(fieldName, List.of()); + } + + private static String extractBoundary(String contentType) { + String[] items = contentType.split(";"); + for (String item : items) { + String trimmed = item.trim(); + if (!trimmed.toLowerCase(Locale.ROOT).startsWith("boundary=")) { + continue; + } + String boundary = trimmed.substring("boundary=".length()); + if (boundary.startsWith("\"") && boundary.endsWith("\"") && boundary.length() >= 2) { + boundary = boundary.substring(1, boundary.length() - 1); + } + return boundary; + } + return null; + } + + private static String headerValue(String headers, String headerName) { + for (String line : headers.split("\r\n")) { + int idx = line.indexOf(':'); + if (idx <= 0) { + continue; + } + String key = line.substring(0, idx).trim().toLowerCase(Locale.ROOT); + if (!headerName.equalsIgnoreCase(key)) { + continue; + } + return line.substring(idx + 1).trim(); + } + return null; + } + + private static String dispositionValue(String disposition, Pattern pattern) { + Matcher matcher = pattern.matcher(disposition); + if (!matcher.find()) { + return null; + } + return matcher.group(1); + } + + private static String sanitizeFileName(String fileName) { + String name = fileName == null ? "" : fileName.trim(); + int slash = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\')); + if (slash >= 0 && slash + 1 < name.length()) { + name = name.substring(slash + 1); + } + return name; + } + + public static final class FileItem { + private final String fieldName; + private final String fileName; + private final String contentType; + private final byte[] data; + + public FileItem(String fieldName, String fileName, String contentType, byte[] data) { + this.fieldName = fieldName; + this.fileName = fileName; + this.contentType = contentType; + this.data = data; + } + + public String getFieldName() { + return fieldName; + } + + public String getFileName() { + return fileName; + } + + public String getContentType() { + return contentType; + } + + public byte[] getData() { + return data; + } + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/http/RequestContext.java b/src/main/java/cz/kamma/fabka/httpserver/http/RequestContext.java new file mode 100644 index 0000000..9c7c149 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/http/RequestContext.java @@ -0,0 +1,157 @@ +package cz.kamma.fabka.httpserver.http; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import cz.kamma.fabka.httpserver.session.SessionData; +import cz.kamma.fabka.httpserver.session.SessionManager; + +import java.io.IOException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +public class RequestContext { + private static final int MAX_FORM_BYTES = 1024 * 1024; + + private final HttpExchange exchange; + private final SessionManager sessionManager; + + private Map queryParams; + private Map formParams; + private Map cookies; + private SessionData session; + + public RequestContext(HttpExchange exchange, SessionManager sessionManager) { + this.exchange = exchange; + this.sessionManager = sessionManager; + } + + public HttpExchange exchange() { + return exchange; + } + + public String method() { + return exchange.getRequestMethod(); + } + + public String path() { + return exchange.getRequestURI().getPath(); + } + + public String queryParam(String key) { + return queryParams().get(key); + } + + public String formParam(String key) throws IOException { + return formParams().get(key); + } + + public Map queryParams() { + if (queryParams == null) { + queryParams = parseEncodedParams(exchange.getRequestURI().getRawQuery()); + } + return queryParams; + } + + public Map formParams() throws IOException { + if (formParams != null) { + return formParams; + } + + String contentType = exchange.getRequestHeaders().getFirst("Content-Type"); + if (contentType == null || !contentType.toLowerCase().startsWith("application/x-www-form-urlencoded")) { + formParams = Collections.emptyMap(); + return formParams; + } + + byte[] body = exchange.getRequestBody().readNBytes(MAX_FORM_BYTES + 1); + if (body.length > MAX_FORM_BYTES) { + throw new IOException("Form payload too large"); + } + formParams = parseEncodedParams(new String(body, StandardCharsets.UTF_8)); + return formParams; + } + + public Map cookies() { + if (cookies == null) { + cookies = parseCookies(exchange.getRequestHeaders()); + } + return cookies; + } + + public SessionData getSession() { + if (session != null) { + return session; + } + String sessionId = cookies().get(SessionManager.COOKIE_NAME); + session = sessionManager.get(sessionId); + return session; + } + + public SessionData getOrCreateSession() { + SessionData existing = getSession(); + if (existing != null) { + return existing; + } + session = sessionManager.create(); + addCookie(SessionManager.COOKIE_NAME, session.id(), true); + return session; + } + + public void invalidateSession() { + String sessionId = cookies().get(SessionManager.COOKIE_NAME); + sessionManager.invalidate(sessionId); + expireCookie(SessionManager.COOKIE_NAME); + session = null; + } + + public void addCookie(String name, String value, boolean httpOnly) { + String cookie = name + "=" + value + "; Path=/; SameSite=Lax" + (httpOnly ? "; HttpOnly" : ""); + exchange.getResponseHeaders().add("Set-Cookie", cookie); + } + + public void expireCookie(String name) { + exchange.getResponseHeaders().add("Set-Cookie", name + "=; Max-Age=0; Path=/; SameSite=Lax; HttpOnly"); + } + + private static Map parseCookies(Headers headers) { + String rawCookie = headers.getFirst("Cookie"); + if (rawCookie == null || rawCookie.isBlank()) { + return Collections.emptyMap(); + } + Map parsed = new HashMap<>(); + String[] chunks = rawCookie.split(";"); + for (String chunk : chunks) { + int idx = chunk.indexOf('='); + if (idx <= 0) { + continue; + } + String key = chunk.substring(0, idx).trim(); + String value = chunk.substring(idx + 1).trim(); + parsed.put(key, value); + } + return parsed; + } + + private static Map parseEncodedParams(String encoded) { + if (encoded == null || encoded.isBlank()) { + return Collections.emptyMap(); + } + Map parsed = new LinkedHashMap<>(); + for (String pair : encoded.split("&")) { + if (pair.isBlank()) { + continue; + } + int idx = pair.indexOf('='); + String rawKey = idx >= 0 ? pair.substring(0, idx) : pair; + String rawValue = idx >= 0 ? pair.substring(idx + 1) : ""; + String key = URLDecoder.decode(rawKey, StandardCharsets.UTF_8); + String value = URLDecoder.decode(rawValue, StandardCharsets.UTF_8); + parsed.put(key, value); + } + return parsed; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/http/Responses.java b/src/main/java/cz/kamma/fabka/httpserver/http/Responses.java new file mode 100644 index 0000000..2e5788c --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/http/Responses.java @@ -0,0 +1,32 @@ +package cz.kamma.fabka.httpserver.http; + +import com.sun.net.httpserver.HttpExchange; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public final class Responses { + private Responses() { + } + + public static void html(HttpExchange exchange, int status, String body) throws IOException { + send(exchange, status, "text/html; charset=UTF-8", body.getBytes(StandardCharsets.UTF_8)); + } + + public static void text(HttpExchange exchange, int status, String body) throws IOException { + send(exchange, status, "text/plain; charset=UTF-8", body.getBytes(StandardCharsets.UTF_8)); + } + + public static void redirect(HttpExchange exchange, String location) throws IOException { + exchange.getResponseHeaders().set("Location", location); + exchange.sendResponseHeaders(302, -1); + exchange.close(); + } + + public static void send(HttpExchange exchange, int status, String contentType, byte[] body) throws IOException { + exchange.getResponseHeaders().set("Content-Type", contentType); + exchange.sendResponseHeaders(status, body.length); + exchange.getResponseBody().write(body); + exchange.close(); + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/http/RouteHandler.java b/src/main/java/cz/kamma/fabka/httpserver/http/RouteHandler.java new file mode 100644 index 0000000..caceed7 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/http/RouteHandler.java @@ -0,0 +1,6 @@ +package cz.kamma.fabka.httpserver.http; + +@FunctionalInterface +public interface RouteHandler { + void handle(RequestContext ctx) throws Exception; +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/http/Router.java b/src/main/java/cz/kamma/fabka/httpserver/http/Router.java new file mode 100644 index 0000000..69fe03c --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/http/Router.java @@ -0,0 +1,49 @@ +package cz.kamma.fabka.httpserver.http; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import cz.kamma.fabka.httpserver.session.SessionManager; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class Router implements HttpHandler { + private final Map routes = new ConcurrentHashMap<>(); + private final SessionManager sessionManager; + + public Router(SessionManager sessionManager) { + this.sessionManager = sessionManager; + } + + public void get(String path, RouteHandler handler) { + routes.put(key("GET", path), handler); + } + + public void post(String path, RouteHandler handler) { + routes.put(key("POST", path), handler); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + RequestContext ctx = new RequestContext(exchange, sessionManager); + RouteHandler handler = routes.get(key(exchange.getRequestMethod(), exchange.getRequestURI().getPath())); + if (handler == null) { + Responses.text(exchange, 404, "Not found"); + return; + } + + try { + handler.handle(ctx); + } catch (IllegalArgumentException ex) { + Responses.text(exchange, 400, ex.getMessage()); + } catch (Exception ex) { + ex.printStackTrace(); + Responses.text(exchange, 500, "Internal server error"); + } + } + + private static String key(String method, String path) { + return method + " " + path; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/http/StaticFileHttpHandler.java b/src/main/java/cz/kamma/fabka/httpserver/http/StaticFileHttpHandler.java new file mode 100644 index 0000000..42c2cfd --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/http/StaticFileHttpHandler.java @@ -0,0 +1,82 @@ +package cz.kamma.fabka.httpserver.http; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class StaticFileHttpHandler implements HttpHandler { + private final Path basePath; + private final String prefix; + private final boolean directoryMode; + + public StaticFileHttpHandler(Path basePath, String prefix, boolean directoryMode) { + this.basePath = basePath.toAbsolutePath().normalize(); + this.prefix = prefix; + this.directoryMode = directoryMode; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + if (!"GET".equals(exchange.getRequestMethod())) { + Responses.text(exchange, 405, "Method not allowed"); + return; + } + + Path target; + if (directoryMode) { + String path = exchange.getRequestURI().getPath(); + String rel = path.substring(prefix.length()); + if (rel.startsWith("/")) { + rel = rel.substring(1); + } + target = basePath.resolve(rel).normalize(); + if (!target.startsWith(basePath)) { + Responses.text(exchange, 403, "Forbidden"); + return; + } + } else { + target = basePath; + } + + if (!Files.exists(target) || Files.isDirectory(target)) { + Responses.text(exchange, 404, "Not found"); + return; + } + + String contentType = Files.probeContentType(target); + if (contentType == null) { + contentType = guessContentType(target); + } + + Responses.send(exchange, 200, contentType, Files.readAllBytes(target)); + } + + private static String guessContentType(Path path) { + String file = path.getFileName().toString().toLowerCase(); + if (file.endsWith(".css")) { + return "text/css; charset=UTF-8"; + } + if (file.endsWith(".js")) { + return "application/javascript; charset=UTF-8"; + } + if (file.endsWith(".png")) { + return "image/png"; + } + if (file.endsWith(".jpg") || file.endsWith(".jpeg")) { + return "image/jpeg"; + } + if (file.endsWith(".gif")) { + return "image/gif"; + } + if (file.endsWith(".wav")) { + return "audio/wav"; + } + if (file.endsWith(".ico")) { + return "image/x-icon"; + } + return "application/octet-stream"; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/AttachmentData.java b/src/main/java/cz/kamma/fabka/httpserver/repository/AttachmentData.java new file mode 100644 index 0000000..f0b774f --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/AttachmentData.java @@ -0,0 +1,25 @@ +package cz.kamma.fabka.httpserver.repository; + +public class AttachmentData { + private final String name; + private final String contentType; + private final byte[] data; + + public AttachmentData(String name, String contentType, byte[] data) { + this.name = name; + this.contentType = contentType; + this.data = data; + } + + public String getName() { + return name; + } + + public String getContentType() { + return contentType; + } + + public byte[] getData() { + return data; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/ChatLine.java b/src/main/java/cz/kamma/fabka/httpserver/repository/ChatLine.java new file mode 100644 index 0000000..1a659e1 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/ChatLine.java @@ -0,0 +1,64 @@ +package cz.kamma.fabka.httpserver.repository; + +public class ChatLine { + private final long id; + private final long createdBy; + private final String fromName; + private final String time; + private final String text; + private final int newMessage; + private final String thumbUpUsers; + private final String thumbDownUsers; + + public ChatLine( + long id, + long createdBy, + String fromName, + String time, + String text, + int newMessage, + String thumbUpUsers, + String thumbDownUsers + ) { + this.id = id; + this.createdBy = createdBy; + this.fromName = fromName; + this.time = time; + this.text = text; + this.newMessage = newMessage; + this.thumbUpUsers = thumbUpUsers; + this.thumbDownUsers = thumbDownUsers; + } + + public long getId() { + return id; + } + + public long getCreatedBy() { + return createdBy; + } + + public String getFromName() { + return fromName; + } + + public String getTime() { + return time; + } + + public String getText() { + return text; + } + + public int getNewMessage() { + return newMessage; + } + + public String getThumbUpUsers() { + return thumbUpUsers; + } + + public String getThumbDownUsers() { + return thumbDownUsers; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/ChatRepository.java b/src/main/java/cz/kamma/fabka/httpserver/repository/ChatRepository.java new file mode 100644 index 0000000..bffc663 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/ChatRepository.java @@ -0,0 +1,241 @@ +package cz.kamma.fabka.httpserver.repository; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Timestamp; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public class ChatRepository { + private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss"); + private static final ZoneId APP_ZONE = ZoneId.of("Europe/Prague"); + + private static final String CHAT_VOTES_USERS_SQL = + "SELECT " + + "COALESCE((SELECT GROUP_CONCAT(ua.username SEPARATOR ',') " + + " FROM chat_voting cv JOIN user_accounts ua ON ua.id=cv.voteby " + + " WHERE cv.chatid=? AND cv.votevalue=1), '') AS thumbup, " + + "COALESCE((SELECT GROUP_CONCAT(ua.username SEPARATOR ',') " + + " FROM chat_voting cv JOIN user_accounts ua ON ua.id=cv.voteby " + + " WHERE cv.chatid=? AND cv.votevalue=-1), '') AS thumbdown"; + + private static final String CHAT_LINES_SQL = + "SELECT ch.id, ch.text, ch.created, ch.createdby, ua.username, " + + "CASE WHEN ch.id > COALESCE((SELECT confirmedid FROM chat_history WHERE userid=?), 0) THEN 1 ELSE 0 END AS newmess, " + + "COALESCE((SELECT GROUP_CONCAT(ua2.username SEPARATOR ',') " + + " FROM chat_voting cv JOIN user_accounts ua2 ON ua2.id=cv.voteby " + + " WHERE cv.chatid=ch.id AND cv.votevalue=1), '') AS thumbup, " + + "COALESCE((SELECT GROUP_CONCAT(ua2.username SEPARATOR ',') " + + " FROM chat_voting cv JOIN user_accounts ua2 ON ua2.id=cv.voteby " + + " WHERE cv.chatid=ch.id AND cv.votevalue=-1), '') AS thumbdown " + + "FROM chat ch JOIN user_accounts ua ON ua.id=ch.createdby " + + "ORDER BY ch.id DESC LIMIT ?"; + + private final String jdbcUrl; + private final String jdbcUser; + private final String jdbcPassword; + + public ChatRepository(String jdbcUrl, String jdbcUser, String jdbcPassword) { + this.jdbcUrl = jdbcUrl; + this.jdbcUser = jdbcUser; + this.jdbcPassword = jdbcPassword; + } + + public boolean alreadyChatVoted(long userId, long chatId) { + if (userId <= 0 || chatId <= 0) { + return false; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement("select 1 from chat_voting where voteby=? and chatid=? limit 1")) { + ps.setLong(1, userId); + ps.setLong(2, chatId); + try (ResultSet rs = ps.executeQuery()) { + return rs.next(); + } + } catch (Exception ex) { + return false; + } + } + + public void addChatVote(long userId, long chatId, int voteValue) { + if (userId <= 0 || chatId <= 0) { + return; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement("insert into chat_voting (voteby, chatid, votevalue) values (?,?,?)")) { + ps.setLong(1, userId); + ps.setLong(2, chatId); + ps.setInt(3, voteValue); + ps.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public ChatVoteStats getChatVoteStats(long chatId) { + if (chatId <= 0) { + return new ChatVoteStats("", ""); + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(CHAT_VOTES_USERS_SQL)) { + ps.setLong(1, chatId); + ps.setLong(2, chatId); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + return new ChatVoteStats("", ""); + } + return new ChatVoteStats( + valueOrDefault(rs.getString("thumbup"), ""), + valueOrDefault(rs.getString("thumbdown"), "") + ); + } + } catch (Exception ex) { + ex.printStackTrace(); + return new ChatVoteStats("", ""); + } + } + + public void addChatMessage(long userId, String message) { + if (userId <= 0 || message == null || message.isBlank()) { + return; + } + String sanitized = message.replace("<", "<"); + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement("insert into chat (createdby, text) values (?,?)")) { + ps.setLong(1, userId); + ps.setString(2, sanitized); + ps.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public List listRecentChatLines(long currentUserId, int limit) { + List lines = new ArrayList<>(); + if (currentUserId <= 0) { + return lines; + } + int safeLimit = limit <= 0 ? 40 : limit; + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(CHAT_LINES_SQL)) { + ps.setLong(1, currentUserId); + ps.setInt(2, safeLimit); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + long createdBy = rs.getLong("createdby"); + int newMsg = createdBy == currentUserId ? 2 : rs.getInt("newmess"); + lines.add(new ChatLine( + rs.getLong("id"), + createdBy, + valueOrDefault(rs.getString("username"), ""), + formatTime(rs.getTimestamp("created")), + valueOrDefault(rs.getString("text"), ""), + newMsg, + valueOrDefault(rs.getString("thumbup"), ""), + valueOrDefault(rs.getString("thumbdown"), "") + )); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return lines; + } + + public void confirmChatRead(long userId, long chatId) { + if (userId <= 0 || chatId <= 0) { + return; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement( + "insert into chat_history (userid, confirmedid) values (?,?) on duplicate key update confirmedid=?" + )) { + ps.setLong(1, userId); + ps.setLong(2, chatId); + ps.setLong(3, chatId); + ps.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public void confirmChatDownloaded(long userId, long chatId) { + if (userId <= 0 || chatId <= 0) { + return; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement( + "insert into chat_history (userid, downloadedid) values (?,?) on duplicate key update downloadedid=?" + )) { + ps.setLong(1, userId); + ps.setLong(2, chatId); + ps.setLong(3, chatId); + ps.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public boolean hasUserNewChatMessage(long userId) { + return hasUserNewChatMessageInternal(userId, false); + } + + public boolean hasUserNewChatMessageOther(long userId) { + return hasUserNewChatMessageInternal(userId, true); + } + + private boolean hasUserNewChatMessageInternal(long userId, boolean onlyOtherUsers) { + if (userId <= 0) { + return false; + } + String sql = onlyOtherUsers + ? "SELECT COALESCE((SELECT downloadedid FROM chat_history WHERE userid=?), 0) " + + " < COALESCE((SELECT MAX(id) FROM chat WHERE createdby<>?), 0) AS hasNew" + : "SELECT COALESCE((SELECT downloadedid FROM chat_history WHERE userid=?), 0) " + + " < COALESCE((SELECT MAX(id) FROM chat), 0) AS hasNew"; + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setLong(1, userId); + if (onlyOtherUsers) { + ps.setLong(2, userId); + } + try (ResultSet rs = ps.executeQuery()) { + return rs.next() && rs.getInt("hasNew") == 1; + } + } catch (Exception ex) { + return false; + } + } + + public int getUnreadMessagesByUser(long userId) { + if (userId <= 0) { + return 0; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement( + "select count(id) as cnt from messages where deleted=0 and readed=0 and to_user=?" + )) { + ps.setLong(1, userId); + try (ResultSet rs = ps.executeQuery()) { + return rs.next() ? rs.getInt("cnt") : 0; + } + } catch (Exception ex) { + return 0; + } + } + + private static String formatTime(Timestamp ts) { + if (ts == null) { + return ""; + } + return ts.toInstant().atZone(APP_ZONE).format(TIME_FORMAT); + } + + private static String valueOrDefault(String value, String defaultValue) { + return value == null ? defaultValue : value; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/ChatVoteStats.java b/src/main/java/cz/kamma/fabka/httpserver/repository/ChatVoteStats.java new file mode 100644 index 0000000..0b43a3c --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/ChatVoteStats.java @@ -0,0 +1,19 @@ +package cz.kamma.fabka.httpserver.repository; + +public class ChatVoteStats { + private final String thumbUpUsers; + private final String thumbDownUsers; + + public ChatVoteStats(String thumbUpUsers, String thumbDownUsers) { + this.thumbUpUsers = thumbUpUsers; + this.thumbDownUsers = thumbDownUsers; + } + + public String getThumbUpUsers() { + return thumbUpUsers; + } + + public String getThumbDownUsers() { + return thumbDownUsers; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/ForumAttachment.java b/src/main/java/cz/kamma/fabka/httpserver/repository/ForumAttachment.java new file mode 100644 index 0000000..695b48c --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/ForumAttachment.java @@ -0,0 +1,31 @@ +package cz.kamma.fabka.httpserver.repository; + +public class ForumAttachment { + private final long id; + private final String name; + private final boolean picture; + private final int width; + + public ForumAttachment(long id, String name, boolean picture, int width) { + this.id = id; + this.name = name; + this.picture = picture; + this.width = width; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public boolean isPicture() { + return picture; + } + + public int getWidth() { + return width; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/ForumDetail.java b/src/main/java/cz/kamma/fabka/httpserver/repository/ForumDetail.java new file mode 100644 index 0000000..3c38d2d --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/ForumDetail.java @@ -0,0 +1,31 @@ +package cz.kamma.fabka.httpserver.repository; + +public class ForumDetail { + private final long id; + private final String name; + private final String description; + private final String countdown; + + public ForumDetail(long id, String name, String description, String countdown) { + this.id = id; + this.name = name; + this.description = description; + this.countdown = countdown; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getCountdown() { + return countdown; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/ForumDisplayView.java b/src/main/java/cz/kamma/fabka/httpserver/repository/ForumDisplayView.java new file mode 100644 index 0000000..9a8e544 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/ForumDisplayView.java @@ -0,0 +1,94 @@ +package cz.kamma.fabka.httpserver.repository; + +import java.util.List; + +public class ForumDisplayView { + private final List messages; + private final int totalRows; + private final int startRow; + private final int endRow; + private final int totalPages; + private final int currentPage; + private final String searchText; + private final String showType; + private final String showImg; + private final String sortBy; + private final String sortType; + private final String perPage; + + public ForumDisplayView( + List messages, + int totalRows, + int startRow, + int endRow, + int totalPages, + int currentPage, + String searchText, + String showType, + String showImg, + String sortBy, + String sortType, + String perPage + ) { + this.messages = messages; + this.totalRows = totalRows; + this.startRow = startRow; + this.endRow = endRow; + this.totalPages = totalPages; + this.currentPage = currentPage; + this.searchText = searchText; + this.showType = showType; + this.showImg = showImg; + this.sortBy = sortBy; + this.sortType = sortType; + this.perPage = perPage; + } + + public List getMessages() { + return messages; + } + + public int getTotalRows() { + return totalRows; + } + + public int getStartRow() { + return startRow; + } + + public int getEndRow() { + return endRow; + } + + public int getTotalPages() { + return totalPages; + } + + public int getCurrentPage() { + return currentPage; + } + + public String getSearchText() { + return searchText; + } + + public String getShowType() { + return showType; + } + + public String getShowImg() { + return showImg; + } + + public String getSortBy() { + return sortBy; + } + + public String getSortType() { + return sortType; + } + + public String getPerPage() { + return perPage; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/ForumMessage.java b/src/main/java/cz/kamma/fabka/httpserver/repository/ForumMessage.java new file mode 100644 index 0000000..62bd4a9 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/ForumMessage.java @@ -0,0 +1,136 @@ +package cz.kamma.fabka.httpserver.repository; + +import java.util.List; + +public class ForumMessage { + private final long id; + private final long authorId; + private final String author; + private final String authorJoinDate; + private final String authorCity; + private final long authorPosts; + private final long createdEpochMillis; + private final int voteValue; + private final int voteYes; + private final int voteNo; + private final String voteYesUsers; + private final String voteNoUsers; + private final long quoteItemId; + private final QuotedTextItem quotedItem; + private final String createdAt; + private final String text; + private final List attachments; + private final boolean sticky; + + public ForumMessage( + long id, + long authorId, + String author, + String authorJoinDate, + String authorCity, + long authorPosts, + long createdEpochMillis, + int voteValue, + int voteYes, + int voteNo, + String voteYesUsers, + String voteNoUsers, + long quoteItemId, + QuotedTextItem quotedItem, + String createdAt, + String text, + List attachments, + boolean sticky + ) { + this.id = id; + this.authorId = authorId; + this.author = author; + this.authorJoinDate = authorJoinDate; + this.authorCity = authorCity; + this.authorPosts = authorPosts; + this.createdEpochMillis = createdEpochMillis; + this.voteValue = voteValue; + this.voteYes = voteYes; + this.voteNo = voteNo; + this.voteYesUsers = voteYesUsers; + this.voteNoUsers = voteNoUsers; + this.quoteItemId = quoteItemId; + this.quotedItem = quotedItem; + this.createdAt = createdAt; + this.text = text; + this.attachments = attachments; + this.sticky = sticky; + } + + public long getId() { + return id; + } + + public String getAuthor() { + return author; + } + + public long getAuthorId() { + return authorId; + } + + public String getAuthorJoinDate() { + return authorJoinDate; + } + + public String getAuthorCity() { + return authorCity; + } + + public long getAuthorPosts() { + return authorPosts; + } + + public long getCreatedEpochMillis() { + return createdEpochMillis; + } + + public int getVoteValue() { + return voteValue; + } + + public int getVoteYes() { + return voteYes; + } + + public int getVoteNo() { + return voteNo; + } + + public String getVoteYesUsers() { + return voteYesUsers; + } + + public String getVoteNoUsers() { + return voteNoUsers; + } + + public long getQuoteItemId() { + return quoteItemId; + } + + public QuotedTextItem getQuotedItem() { + return quotedItem; + } + + public String getCreatedAt() { + return createdAt; + } + + public String getText() { + return text; + } + + public List getAttachments() { + return attachments; + } + + public boolean isSticky() { + return sticky; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/ForumRepository.java b/src/main/java/cz/kamma/fabka/httpserver/repository/ForumRepository.java new file mode 100644 index 0000000..3a15ef6 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/ForumRepository.java @@ -0,0 +1,564 @@ +package cz.kamma.fabka.httpserver.repository; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class ForumRepository { + private static final DateTimeFormatter DATETIME_FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"); + private static final ZoneId APP_ZONE = ZoneId.of("Europe/Prague"); + private static final String FORUM_DETAIL_SQL = + "SELECT id, name, description, countdown FROM forum WHERE id=? AND active=1 LIMIT 1"; + private static final String QUOTED_ITEM_SQL = + "SELECT fi.text, ua.username FROM forum_items fi JOIN user_accounts ua ON ua.id=fi.createdby WHERE fi.id=? LIMIT 1"; + private static final String ATTACHMENTS_SQL = + "SELECT id, name, ispicture, width, size FROM attachments WHERE forumitemsid=? ORDER BY id"; + private static final String ATTACHMENT_DATA_SQL = + "SELECT name, mimetype, data FROM attachments WHERE id=? LIMIT 1"; + private static final String FORUM_MESSAGES_SQL = + "SELECT fi.id, fi.text, fi.created, fi.quoteitem, fi.sticky, ua.id AS author_id, ua.username, ua.created AS author_created, ua.city, " + + " (SELECT COUNT(*) FROM forum_items fi2 WHERE fi2.createdby=ua.id AND fi2.deleted=0) AS author_posts, " + + " COALESCE((SELECT SUM(votevalue) FROM voting v WHERE v.forumitemid=fi.id), 0) AS vvalue, " + + " (SELECT COUNT(*) FROM voting v WHERE v.forumitemid=fi.id AND v.votevalue=1) AS vote_yes, " + + " (SELECT COUNT(*) FROM voting v WHERE v.forumitemid=fi.id AND v.votevalue=-1) AS vote_no, " + + " (SELECT GROUP_CONCAT(ua2.username SEPARATOR ',') FROM voting v JOIN user_accounts ua2 ON ua2.id=v.voteby WHERE v.forumitemid=fi.id AND v.votevalue=1) AS vote_yes_users, " + + " (SELECT GROUP_CONCAT(ua2.username SEPARATOR ',') FROM voting v JOIN user_accounts ua2 ON ua2.id=v.voteby WHERE v.forumitemid=fi.id AND v.votevalue=-1) AS vote_no_users " + + "FROM forum_items fi JOIN user_accounts ua ON ua.id=fi.createdby " + + "WHERE fi.forumid=? AND fi.deleted=0 ORDER BY fi.created DESC"; + private static final String FORUM_MESSAGES_SQL_NO_STICKY = + "SELECT fi.id, fi.text, fi.created, fi.quoteitem, 0 AS sticky, ua.id AS author_id, ua.username, ua.created AS author_created, ua.city, " + + " (SELECT COUNT(*) FROM forum_items fi2 WHERE fi2.createdby=ua.id AND fi2.deleted=0) AS author_posts, " + + " COALESCE((SELECT SUM(votevalue) FROM voting v WHERE v.forumitemid=fi.id), 0) AS vvalue, " + + " (SELECT COUNT(*) FROM voting v WHERE v.forumitemid=fi.id AND v.votevalue=1) AS vote_yes, " + + " (SELECT COUNT(*) FROM voting v WHERE v.forumitemid=fi.id AND v.votevalue=-1) AS vote_no, " + + " (SELECT GROUP_CONCAT(ua2.username SEPARATOR ',') FROM voting v JOIN user_accounts ua2 ON ua2.id=v.voteby WHERE v.forumitemid=fi.id AND v.votevalue=1) AS vote_yes_users, " + + " (SELECT GROUP_CONCAT(ua2.username SEPARATOR ',') FROM voting v JOIN user_accounts ua2 ON ua2.id=v.voteby WHERE v.forumitemid=fi.id AND v.votevalue=-1) AS vote_no_users " + + "FROM forum_items fi JOIN user_accounts ua ON ua.id=fi.createdby " + + "WHERE fi.forumid=? AND fi.deleted=0 ORDER BY fi.created DESC"; + private static final String ALREADY_VOTED_SQL = "SELECT 1 FROM voting WHERE voteby=? AND forumitemid=? LIMIT 1"; + private static final String ADD_VOTE_SQL = "INSERT INTO voting (voteby, forumitemid, votevalue) VALUES (?,?,?)"; + private static final String GET_USER_VOTE_SQL = "SELECT votevalue FROM voting WHERE voteby=? AND forumitemid=? LIMIT 1"; + private static final String UPDATE_VOTE_SQL = "UPDATE voting SET votevalue=? WHERE voteby=? AND forumitemid=?"; + private static final String DELETE_VOTE_SQL = "DELETE FROM voting WHERE voteby=? AND forumitemid=?"; + private static final String TOGGLE_STICKY_SQL = "update forum_items set sticky=IF(sticky=1,0,1) where id=?"; + private static final String DELETE_POST_SQL = "update forum_items set deleted=1 where id=? and createdby=?"; + private static final String VOTE_STATS_SQL = + "SELECT " + + " (SELECT COUNT(*) FROM voting WHERE forumitemid=? AND votevalue=1) AS yes_count, " + + " (SELECT COUNT(*) FROM voting WHERE forumitemid=? AND votevalue=-1) AS no_count, " + + " (SELECT GROUP_CONCAT(ua.username SEPARATOR ',') FROM voting v JOIN user_accounts ua ON ua.id=v.voteby WHERE v.forumitemid=? AND v.votevalue=1) AS yes_users, " + + " (SELECT GROUP_CONCAT(ua.username SEPARATOR ',') FROM voting v JOIN user_accounts ua ON ua.id=v.voteby WHERE v.forumitemid=? AND v.votevalue=-1) AS no_users"; + private static final String INSERT_HISTORY_SQL = + "insert into history (userid, forumid) values (?,?)"; + private static final String NEW_MESSAGES_COUNT_SQL = + "SELECT f.id AS forum_id, COUNT(fi.id) AS message_count " + + "FROM forum f " + + "LEFT JOIN (" + + " SELECT forumid, MAX(created) AS last_seen " + + " FROM history WHERE userid=? GROUP BY forumid" + + ") h ON h.forumid=f.id " + + "LEFT JOIN forum_items fi " + + " ON fi.forumid=f.id AND fi.deleted=0 AND (h.last_seen IS NULL OR fi.created>h.last_seen) " + + "WHERE f.active=1 " + + "GROUP BY f.id " + + "HAVING COUNT(fi.id) > 0"; + + private static final String FORUM_SQL = + "SELECT f.id, f.name, f.description, f.created, f.password, " + + " (SELECT COUNT(*) FROM forum_items fi WHERE fi.forumid=f.id AND fi.deleted=0) AS posts_count, " + + " (SELECT ua.username FROM forum_items fi JOIN user_accounts ua ON ua.id=fi.createdby " + + " WHERE fi.forumid=f.id AND fi.deleted=0 ORDER BY fi.created DESC LIMIT 1) AS last_post_user, " + + " (SELECT fi.created FROM forum_items fi WHERE fi.forumid=f.id AND fi.deleted=0 ORDER BY fi.created DESC LIMIT 1) AS last_post_at " + + "FROM forum f WHERE f.active=1 ORDER BY COALESCE(last_post_at, f.created) DESC"; + + private final String jdbcUrl; + private final String jdbcUser; + private final String jdbcPassword; + + public ForumRepository(String jdbcUrl, String jdbcUser, String jdbcPassword) { + this.jdbcUrl = jdbcUrl; + this.jdbcUser = jdbcUser; + this.jdbcPassword = jdbcPassword; + } + + public List listActiveForums() { + List forums = new ArrayList<>(); + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(FORUM_SQL); + ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + forums.add(new ForumSummary( + rs.getLong("id"), + rs.getString("name"), + rs.getString("description"), + formatTs(rs.getTimestamp("created")), + valueOrDefault(rs.getString("last_post_user"), "N/A"), + formatTs(rs.getTimestamp("last_post_at")), + rs.getLong("posts_count"), + rs.getString("password") != null && !rs.getString("password").isBlank() + )); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return forums; + } + + public void putHistoryRecord(long userId, long forumId) { + if (userId <= 0) { + return; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(INSERT_HISTORY_SQL)) { + ps.setLong(1, userId); + ps.setLong(2, forumId); + ps.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public Map getNewMessagesCountByUserId(long userId) { + Map result = new LinkedHashMap<>(); + if (userId <= 0) { + return result; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(NEW_MESSAGES_COUNT_SQL)) { + ps.setLong(1, userId); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + long forumId = rs.getLong("forum_id"); + int count = rs.getInt("message_count"); + if (count > 0) { + result.put(forumId, count); + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return result; + } + + public ForumDetail findForumById(long forumId) { + if (forumId <= 0) { + return null; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(FORUM_DETAIL_SQL)) { + ps.setLong(1, forumId); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + return null; + } + return new ForumDetail( + rs.getLong("id"), + rs.getString("name"), + rs.getString("description"), + rs.getString("countdown") + ); + } + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + public List listMessagesByForumId(long forumId) { + List messages = new ArrayList<>(); + if (forumId <= 0) { + return messages; + } + if (!loadMessages(messages, forumId, FORUM_MESSAGES_SQL)) { + loadMessages(messages, forumId, FORUM_MESSAGES_SQL_NO_STICKY); + } + return messages; + } + + private boolean loadMessages(List out, long forumId, String sql) { + out.clear(); + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setLong(1, forumId); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + Timestamp createdTs = rs.getTimestamp("created"); + long quoteItemId = rs.getLong("quoteitem"); + QuotedTextItem quotedItem = quoteItemId > 0 ? findQuotedItem(quoteItemId) : null; + out.add(new ForumMessage( + rs.getLong("id"), + rs.getLong("author_id"), + valueOrDefault(rs.getString("username"), "N/A"), + formatTs(rs.getTimestamp("author_created")), + valueOrDefault(rs.getString("city"), ""), + rs.getLong("author_posts"), + createdTs == null ? 0L : createdTs.getTime(), + rs.getInt("vvalue"), + rs.getInt("vote_yes"), + rs.getInt("vote_no"), + valueOrDefault(rs.getString("vote_yes_users"), ""), + valueOrDefault(rs.getString("vote_no_users"), ""), + quoteItemId, + quotedItem, + formatTs(createdTs), + valueOrDefault(rs.getString("text"), ""), + listAttachmentsByForumItemId(rs.getLong("id")), + rs.getInt("sticky") == 1 + )); + } + } + return true; + } catch (Exception ex) { + return false; + } + } + + public boolean alreadyVoted(long userId, long forumItemId) { + if (userId <= 0 || forumItemId <= 0) { + return false; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(ALREADY_VOTED_SQL)) { + ps.setLong(1, userId); + ps.setLong(2, forumItemId); + try (ResultSet rs = ps.executeQuery()) { + return rs.next(); + } + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public Integer getUserVote(long userId, long forumItemId) { + if (userId <= 0 || forumItemId <= 0) { + return null; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(GET_USER_VOTE_SQL)) { + ps.setLong(1, userId); + ps.setLong(2, forumItemId); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + return rs.getInt("votevalue"); + } + return null; + } + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + public void addVote(long userId, long forumItemId, int voteValue) { + if (userId <= 0 || forumItemId <= 0) { + return; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(ADD_VOTE_SQL)) { + ps.setLong(1, userId); + ps.setLong(2, forumItemId); + ps.setInt(3, voteValue); + ps.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public void updateVote(long userId, long forumItemId, int voteValue) { + if (userId <= 0 || forumItemId <= 0) { + return; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(UPDATE_VOTE_SQL)) { + ps.setInt(1, voteValue); + ps.setLong(2, userId); + ps.setLong(3, forumItemId); + ps.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public void deleteVote(long userId, long forumItemId) { + if (userId <= 0 || forumItemId <= 0) { + return; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(DELETE_VOTE_SQL)) { + ps.setLong(1, userId); + ps.setLong(2, forumItemId); + ps.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public VoteStats getVoteStats(long forumItemId) { + if (forumItemId <= 0) { + return new VoteStats(0, 0, "", ""); + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(VOTE_STATS_SQL)) { + ps.setLong(1, forumItemId); + ps.setLong(2, forumItemId); + ps.setLong(3, forumItemId); + ps.setLong(4, forumItemId); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + return new VoteStats(0, 0, "", ""); + } + return new VoteStats( + rs.getInt("yes_count"), + rs.getInt("no_count"), + valueOrDefault(rs.getString("yes_users"), ""), + valueOrDefault(rs.getString("no_users"), "") + ); + } + } catch (Exception ex) { + ex.printStackTrace(); + return new VoteStats(0, 0, "", ""); + } + } + + public void toggleSticky(long forumItemId) { + if (forumItemId <= 0) { + return; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(TOGGLE_STICKY_SQL)) { + ps.setLong(1, forumItemId); + ps.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public void deletePost(long forumItemId, long userId) { + if (forumItemId <= 0 || userId <= 0) { + return; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(DELETE_POST_SQL)) { + ps.setLong(1, forumItemId); + ps.setLong(2, userId); + ps.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public QuotedTextItem findQuotedItem(long messageId) { + if (messageId <= 0) { + return null; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(QUOTED_ITEM_SQL)) { + ps.setLong(1, messageId); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + return null; + } + return new QuotedTextItem( + valueOrDefault(rs.getString("username"), "N/A"), + valueOrDefault(rs.getString("text"), "") + ); + } + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + public long addReply(long forumId, long userId, String message, Long quoteItem) { + if (forumId <= 0 || userId <= 0) { + return -1L; + } + String safeMessage = message == null ? "" : message; + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword)) { + conn.setAutoCommit(false); + try (PreparedStatement insert = conn.prepareStatement( + "insert into forum_items (forumid, createdby, text, quoteitem) VALUES (?,?,?,?)", + Statement.RETURN_GENERATED_KEYS + )) { + insert.setLong(1, forumId); + insert.setLong(2, userId); + insert.setString(3, safeMessage); + if (quoteItem != null && quoteItem > 0) { + insert.setLong(4, quoteItem); + } else { + insert.setNull(4, Types.BIGINT); + } + insert.executeUpdate(); + long forumItemId = -1L; + try (ResultSet keys = insert.getGeneratedKeys()) { + if (keys.next()) { + forumItemId = keys.getLong(1); + } + } + if (forumItemId <= 0) { + conn.rollback(); + return -1L; + } + try (PreparedStatement upd = conn.prepareStatement("update forum set last_post=? where id=?")) { + upd.setTimestamp(1, new Timestamp(System.currentTimeMillis())); + upd.setLong(2, forumId); + upd.executeUpdate(); + } + conn.commit(); + return forumItemId; + } + } catch (Exception ex) { + ex.printStackTrace(); + return -1L; + } + } + + public boolean addAttachment(long forumItemId, String fileName, byte[] data, String contentType) { + if (forumItemId <= 0 || fileName == null || fileName.isBlank() || data == null || data.length == 0) { + return false; + } + String safeContentType = (contentType == null || contentType.isBlank()) ? "application/octet-stream" : contentType; + int width = 0; + int height = 0; + boolean isPicture = false; + try { + BufferedImage image = ImageIO.read(new ByteArrayInputStream(data)); + if (image != null) { + isPicture = true; + width = image.getWidth(); + height = image.getHeight(); + } + } catch (Exception ignored) { + // If image decode fails, persist as generic attachment. + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement( + "insert into attachments (forumitemsid, name, data, ispicture, width, height, size, mimetype) values (?,?,?,?,?,?,?,?)" + )) { + ps.setLong(1, forumItemId); + ps.setString(2, fileName); + ps.setBytes(3, data); + ps.setInt(4, isPicture ? 1 : 0); + ps.setInt(5, width); + ps.setInt(6, height); + ps.setInt(7, data.length); + ps.setString(8, safeContentType); + ps.executeUpdate(); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public boolean createThread(long userId, String forumName, String description, String password) { + if (userId <= 0) { + return false; + } + String safeName = forumName == null ? "" : forumName.trim(); + String safeDesc = description == null ? "" : description.trim(); + if (safeName.isBlank() || safeDesc.isBlank()) { + return false; + } + String safePassword = (password == null || password.isBlank()) ? null : password.trim(); + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement( + "insert into forum (name, description, password, createdby, active) values (?,?,?,?,1)" + )) { + ps.setString(1, safeName); + ps.setString(2, safeDesc); + if (safePassword == null) { + ps.setNull(3, Types.VARCHAR); + } else { + ps.setString(3, safePassword); + } + ps.setLong(4, userId); + ps.executeUpdate(); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public List listAttachmentsByForumItemId(long forumItemId) { + List out = new ArrayList<>(); + if (forumItemId <= 0) { + return out; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(ATTACHMENTS_SQL)) { + ps.setLong(1, forumItemId); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + out.add(new ForumAttachment( + rs.getLong("id"), + valueOrDefault(rs.getString("name"), "attachment"), + rs.getInt("ispicture") == 1, + rs.getInt("width") + )); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return out; + } + + public AttachmentData findAttachmentData(long attachmentId) { + if (attachmentId <= 0) { + return null; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(ATTACHMENT_DATA_SQL)) { + ps.setLong(1, attachmentId); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + return null; + } + byte[] data = rs.getBytes("data"); + if (data == null || data.length == 0) { + return null; + } + String contentType = rs.getString("mimetype"); + if (contentType == null || contentType.isBlank()) { + contentType = "application/octet-stream"; + } + return new AttachmentData( + valueOrDefault(rs.getString("name"), "attachment.bin"), + contentType, + data + ); + } + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + private static String valueOrDefault(String value, String defaultValue) { + return value == null || value.isBlank() ? defaultValue : value; + } + + private static String formatTs(Timestamp ts) { + if (ts == null) { + return "N/A"; + } + return DATETIME_FORMAT.format(ts.toInstant().atZone(APP_ZONE).toLocalDateTime()); + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/ForumSummary.java b/src/main/java/cz/kamma/fabka/httpserver/repository/ForumSummary.java new file mode 100644 index 0000000..0f0a292 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/ForumSummary.java @@ -0,0 +1,55 @@ +package cz.kamma.fabka.httpserver.repository; + +public class ForumSummary { + private final long id; + private final String name; + private final String description; + private final String createdAt; + private final String lastPostUser; + private final String lastPostAt; + private final long postsCount; + private final boolean locked; + + public ForumSummary(long id, String name, String description, String createdAt, String lastPostUser, String lastPostAt, long postsCount, boolean locked) { + this.id = id; + this.name = name; + this.description = description; + this.createdAt = createdAt; + this.lastPostUser = lastPostUser; + this.lastPostAt = lastPostAt; + this.postsCount = postsCount; + this.locked = locked; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getCreatedAt() { + return createdAt; + } + + public String getLastPostUser() { + return lastPostUser; + } + + public String getLastPostAt() { + return lastPostAt; + } + + public long getPostsCount() { + return postsCount; + } + + public boolean isLocked() { + return locked; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/MemberProfile.java b/src/main/java/cz/kamma/fabka/httpserver/repository/MemberProfile.java new file mode 100644 index 0000000..cccbc51 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/MemberProfile.java @@ -0,0 +1,49 @@ +package cz.kamma.fabka.httpserver.repository; + +public class MemberProfile { + private final long id; + private final String username; + private final String firstName; + private final String lastName; + private final String city; + private final String email; + private final String createdAt; + + public MemberProfile(long id, String username, String firstName, String lastName, String city, String email, String createdAt) { + this.id = id; + this.username = username; + this.firstName = firstName; + this.lastName = lastName; + this.city = city; + this.email = email; + this.createdAt = createdAt; + } + + public long getId() { + return id; + } + + public String getUsername() { + return username; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getCity() { + return city; + } + + public String getEmail() { + return email; + } + + public String getCreatedAt() { + return createdAt; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/MemberRepository.java b/src/main/java/cz/kamma/fabka/httpserver/repository/MemberRepository.java new file mode 100644 index 0000000..54a3662 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/MemberRepository.java @@ -0,0 +1,140 @@ +package cz.kamma.fabka.httpserver.repository; + +import cz.kamma.fabka.httpserver.crypto.Md5; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Timestamp; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +public class MemberRepository { + private static final DateTimeFormatter DATETIME_FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"); + private static final ZoneId APP_ZONE = ZoneId.of("Europe/Prague"); + private static final String SELECT_PROFILE_SQL = + "select id, username, firstname, surename, city, email, created from user_accounts where id=? limit 1"; + private static final String UPDATE_INFO_SQL = + "update user_accounts set email=?, firstname=?, surename=?, city=? where id=?"; + private static final String SELECT_PASSWORD_SQL = + "select passwd from user_accounts where id=? limit 1"; + private static final String UPDATE_PASSWORD_SQL = + "update user_accounts set passwd=? where id=?"; + + private final String jdbcUrl; + private final String jdbcUser; + private final String jdbcPassword; + + public MemberRepository(String jdbcUrl, String jdbcUser, String jdbcPassword) { + this.jdbcUrl = jdbcUrl; + this.jdbcUser = jdbcUser; + this.jdbcPassword = jdbcPassword; + } + + public MemberProfile findByUserId(long userId) { + if (userId <= 0) { + return null; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(SELECT_PROFILE_SQL)) { + ps.setLong(1, userId); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + return null; + } + return new MemberProfile( + rs.getLong("id"), + valueOrDefault(rs.getString("username")), + valueOrDefault(rs.getString("firstname")), + valueOrDefault(rs.getString("surename")), + valueOrDefault(rs.getString("city")), + valueOrDefault(rs.getString("email")), + formatTs(rs.getTimestamp("created")) + ); + } + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + public boolean updatePersonalInfo(long userId, String email, String firstName, String lastName, String city) { + if (userId <= 0) { + return false; + } + String safeEmail = safe(email); + String safeFirstName = safe(firstName); + String safeLastName = safe(lastName); + String safeCity = safe(city); + if (safeEmail.isBlank() || safeFirstName.isBlank() || safeLastName.isBlank() || safeCity.isBlank()) { + return false; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(UPDATE_INFO_SQL)) { + ps.setString(1, safeEmail); + ps.setString(2, safeFirstName); + ps.setString(3, safeLastName); + ps.setString(4, safeCity); + ps.setLong(5, userId); + return ps.executeUpdate() > 0; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public boolean changePassword(long userId, String oldPassword, String newPassword) { + if (userId <= 0) { + return false; + } + String safeOld = safe(oldPassword); + String safeNew = safe(newPassword); + if (safeOld.isBlank() || safeNew.isBlank()) { + return false; + } + String currentHash; + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(SELECT_PASSWORD_SQL)) { + ps.setLong(1, userId); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + return false; + } + currentHash = valueOrDefault(rs.getString("passwd")); + } + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + + if (!Md5.hash(safeOld).equalsIgnoreCase(currentHash)) { + return false; + } + + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(UPDATE_PASSWORD_SQL)) { + ps.setString(1, Md5.hash(safeNew)); + ps.setLong(2, userId); + return ps.executeUpdate() > 0; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + private static String safe(String value) { + return value == null ? "" : value.trim(); + } + + private static String valueOrDefault(String value) { + return value == null ? "" : value; + } + + private static String formatTs(Timestamp ts) { + if (ts == null) { + return "N/A"; + } + return DATETIME_FORMAT.format(ts.toInstant().atZone(APP_ZONE).toLocalDateTime()); + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/MessageRenderSettings.java b/src/main/java/cz/kamma/fabka/httpserver/repository/MessageRenderSettings.java new file mode 100644 index 0000000..be4b3b6 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/MessageRenderSettings.java @@ -0,0 +1,25 @@ +package cz.kamma.fabka.httpserver.repository; + +public class MessageRenderSettings { + private final String youtubeSnippet; + private final String webmSnippet; + private final String youtubeReplaceUrls; + + public MessageRenderSettings(String youtubeSnippet, String webmSnippet, String youtubeReplaceUrls) { + this.youtubeSnippet = youtubeSnippet; + this.webmSnippet = webmSnippet; + this.youtubeReplaceUrls = youtubeReplaceUrls; + } + + public String getYoutubeSnippet() { + return youtubeSnippet; + } + + public String getWebmSnippet() { + return webmSnippet; + } + + public String getYoutubeReplaceUrls() { + return youtubeReplaceUrls; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/MysqlClientRepository.java b/src/main/java/cz/kamma/fabka/httpserver/repository/MysqlClientRepository.java new file mode 100644 index 0000000..0a403f5 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/MysqlClientRepository.java @@ -0,0 +1,166 @@ +package cz.kamma.fabka.httpserver.repository; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; + +public class MysqlClientRepository { + private final String jdbcUrl; + private final String jdbcUser; + private final String jdbcPassword; + + public MysqlClientRepository(String jdbcUrl, String jdbcUser, String jdbcPassword) { + this.jdbcUrl = jdbcUrl; + this.jdbcUser = jdbcUser; + this.jdbcPassword = jdbcPassword; + } + + public List listDatabases() { + List result = new ArrayList<>(); + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SHOW DATABASES")) { + while (rs.next()) { + result.add(rs.getString(1)); + } + } catch (SQLException ignored) { + // Keep UI usable even if metadata query fails. + } + return result; + } + + public List listTables(String database) { + List result = new ArrayList<>(); + if (database == null || database.isBlank()) { + return result; + } + String sql = "SHOW TABLES FROM " + qid(database); + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql)) { + while (rs.next()) { + result.add(rs.getString(1)); + } + } catch (SQLException ignored) { + // Keep UI usable even if metadata query fails. + } + return result; + } + + public String showCreateTable(String database, String tableName) throws SQLException { + if (database == null || database.isBlank() || tableName == null || tableName.isBlank()) { + return ""; + } + String useSql = "USE " + qid(database); + String createSql = "SHOW CREATE TABLE " + qid(tableName); + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + Statement stmt = conn.createStatement()) { + stmt.execute(useSql); + try (ResultSet rs = stmt.executeQuery(createSql)) { + if (rs.next()) { + return rs.getString(2); + } + return ""; + } + } + } + + public SqlExecution executeSql(String database, String sql) throws SQLException { + SqlExecution result = new SqlExecution(); + result.setExecutedSql(sql == null ? "" : sql); + if (sql == null || sql.isBlank()) { + return result; + } + String useSql = (database == null || database.isBlank()) ? null : "USE " + qid(database); + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + Statement stmt = conn.createStatement()) { + if (useSql != null) { + stmt.execute(useSql); + } + boolean hasResultSet = stmt.execute(sql); + if (hasResultSet) { + result.setResultSet(true); + try (ResultSet rs = stmt.getResultSet()) { + ResultSetMetaData rsmd = rs.getMetaData(); + int count = rsmd.getColumnCount(); + List columns = new ArrayList<>(); + for (int i = 1; i <= count; i++) { + columns.add(rsmd.getColumnLabel(i)); + } + result.setColumns(columns); + + List> rows = new ArrayList<>(); + while (rs.next()) { + List row = new ArrayList<>(); + for (int i = 1; i <= count; i++) { + row.add(rs.getString(i)); + } + rows.add(row); + } + result.setRows(rows); + } + } else { + result.setResultSet(false); + result.setUpdateCount(stmt.getUpdateCount()); + } + } + return result; + } + + private static String qid(String identifier) { + return "`" + identifier.replace("`", "``") + "`"; + } + + public static final class SqlExecution { + private String executedSql = ""; + private boolean resultSet; + private int updateCount; + private List columns = List.of(); + private List> rows = List.of(); + + public String getExecutedSql() { + return executedSql; + } + + public void setExecutedSql(String executedSql) { + this.executedSql = executedSql; + } + + public boolean isResultSet() { + return resultSet; + } + + public void setResultSet(boolean resultSet) { + this.resultSet = resultSet; + } + + public int getUpdateCount() { + return updateCount; + } + + public void setUpdateCount(int updateCount) { + this.updateCount = updateCount; + } + + public List getColumns() { + return columns; + } + + public void setColumns(List columns) { + this.columns = columns == null ? List.of() : columns; + } + + public List> getRows() { + return rows; + } + + public void setRows(List> rows) { + this.rows = rows == null ? List.of() : rows; + } + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageItem.java b/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageItem.java new file mode 100644 index 0000000..6a3a870 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageItem.java @@ -0,0 +1,57 @@ +package cz.kamma.fabka.httpserver.repository; + +public class PrivateMessageItem { + private final long id; + private final long fromUserId; + private final String fromUsername; + private final String fromJoinDate; + private final long fromPosts; + private final String createdAt; + private final String message; + + public PrivateMessageItem( + long id, + long fromUserId, + String fromUsername, + String fromJoinDate, + long fromPosts, + String createdAt, + String message + ) { + this.id = id; + this.fromUserId = fromUserId; + this.fromUsername = fromUsername; + this.fromJoinDate = fromJoinDate; + this.fromPosts = fromPosts; + this.createdAt = createdAt; + this.message = message; + } + + public long getId() { + return id; + } + + public long getFromUserId() { + return fromUserId; + } + + public String getFromUsername() { + return fromUsername; + } + + public String getFromJoinDate() { + return fromJoinDate; + } + + public long getFromPosts() { + return fromPosts; + } + + public String getCreatedAt() { + return createdAt; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageRepository.java b/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageRepository.java new file mode 100644 index 0000000..7305c44 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageRepository.java @@ -0,0 +1,273 @@ +package cz.kamma.fabka.httpserver.repository; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public class PrivateMessageRepository { + private static final DateTimeFormatter DATE_TIME = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"); + private static final ZoneId APP_ZONE = ZoneId.of("Europe/Prague"); + + private final String jdbcUrl; + private final String jdbcUser; + private final String jdbcPassword; + + public PrivateMessageRepository(String jdbcUrl, String jdbcUser, String jdbcPassword) { + this.jdbcUrl = jdbcUrl; + this.jdbcUser = jdbcUser; + this.jdbcPassword = jdbcPassword; + } + + public PrivateMessageStats stats(long userId) { + if (userId <= 0) { + return new PrivateMessageStats(0, 0); + } + int unread = scalarCount("select count(id) as cnt from messages where deleted=0 and readed=0 and to_user=?", userId); + int total = scalarCount("select count(id) as cnt from messages where deleted=0 and (to_user=? or from_user=?)", userId, userId); + return new PrivateMessageStats(unread, total); + } + + public List listThreads(long userId, String orderBy) { + List out = new ArrayList<>(); + if (userId <= 0) { + return out; + } + String order = sanitizeOrder(orderBy); + String sql = "select m.id, m.title, m.created, m.readed, " + + "coalesce((select max(created) from messages where reply_to=m.id), m.created) as lastitem, " + + "ua.username as sender, ua2.username as recipient, " + + "coalesce((select count(id) from messages where reply_to=m.id),0) as replies, " + + "coalesce((select count(id) from messages where deleted=0 and readed=0 and to_user=? and (id=m.id or reply_to=m.id)),0) as unread_thread " + + "from messages m " + + "join user_accounts ua on ua.id=m.from_user " + + "join user_accounts ua2 on ua2.id=m.to_user " + + "where (m.to_user=? or m.from_user=?) and m.deleted=0 and m.reply_to is null " + + "order by " + order; + + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setLong(1, userId); + ps.setLong(2, userId); + ps.setLong(3, userId); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + out.add(new PrivateThreadSummary( + rs.getLong("id"), + valueOrDefault(rs.getString("title"), ""), + formatTs(rs.getTimestamp("created")), + valueOrDefault(rs.getString("sender"), ""), + valueOrDefault(rs.getString("recipient"), ""), + rs.getLong("replies"), + rs.getInt("unread_thread") == 0 + )); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return out; + } + + public void bulkAction(long userId, List ids, String action) { + if (userId <= 0 || ids == null || ids.isEmpty() || action == null || action.isBlank()) { + return; + } + StringBuilder inPart = new StringBuilder(); + for (int i = 0; i < ids.size(); i++) { + if (i > 0) { + inPart.append(","); + } + inPart.append("?"); + } + String sql; + if ("delete".equalsIgnoreCase(action)) { + sql = "update messages set deleted=1 where id in (" + inPart + ") and (to_user=? or from_user=?)"; + } else if ("read".equalsIgnoreCase(action)) { + sql = "update messages set readed=1 where id in (" + inPart + ") and (to_user=? or from_user=?)"; + } else if ("unread".equalsIgnoreCase(action)) { + sql = "update messages set readed=0 where id in (" + inPart + ") and (to_user=? or from_user=?)"; + } else { + return; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(sql)) { + int idx = 1; + for (Long id : ids) { + ps.setLong(idx++, id == null ? -1L : id); + } + ps.setLong(idx++, userId); + ps.setLong(idx, userId); + ps.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public String usernameById(long userId) { + if (userId <= 0) { + return ""; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement("select username from user_accounts where id=? limit 1")) { + ps.setLong(1, userId); + try (ResultSet rs = ps.executeQuery()) { + return rs.next() ? valueOrDefault(rs.getString("username"), "") : ""; + } + } catch (Exception ex) { + return ""; + } + } + + public PrivateThreadRoot threadRoot(long userId, long pmid) { + if (userId <= 0 || pmid <= 0) { + return null; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement( + "select id, from_user, to_user, title from messages where id=? and deleted=0 and (to_user=? or from_user=?) limit 1" + )) { + ps.setLong(1, pmid); + ps.setLong(2, userId); + ps.setLong(3, userId); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + return null; + } + long fromUser = rs.getLong("from_user"); + long toUser = rs.getLong("to_user"); + long other = fromUser == userId ? toUser : fromUser; + String title = valueOrDefault(rs.getString("title"), ""); + String replyTitle = title.startsWith("Re:") ? title : "Re: " + title; + return new PrivateThreadRoot( + rs.getLong("id"), + other, + title, + replyTitle, + usernameById(other) + ); + } + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + public void markThreadRead(long userId, long pmid) { + if (userId <= 0 || pmid <= 0) { + return; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement( + "update messages set readed=1 where to_user=? and (id=? or reply_to=?)" + )) { + ps.setLong(1, userId); + ps.setLong(2, pmid); + ps.setLong(3, pmid); + ps.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public List threadMessages(long userId, long pmid) { + List out = new ArrayList<>(); + if (userId <= 0 || pmid <= 0) { + return out; + } + String sql = "select m.id, m.from_user, m.message, m.created, ua.username, ua.created as user_created, " + + "(select count(*) from forum_items fi where fi.createdby=ua.id and fi.deleted=0) as posts " + + "from messages m join user_accounts ua on ua.id=m.from_user " + + "where (m.to_user=? or m.from_user=?) and m.deleted=0 and (m.id=? or m.reply_to=?) " + + "order by m.created desc"; + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setLong(1, userId); + ps.setLong(2, userId); + ps.setLong(3, pmid); + ps.setLong(4, pmid); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + out.add(new PrivateMessageItem( + rs.getLong("id"), + rs.getLong("from_user"), + valueOrDefault(rs.getString("username"), ""), + formatTs(rs.getTimestamp("user_created")), + rs.getLong("posts"), + formatTs(rs.getTimestamp("created")), + valueOrDefault(rs.getString("message"), "") + )); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return out; + } + + public boolean send(long fromUser, long toUser, String title, String message, Long replyTo) { + if (fromUser <= 0 || toUser <= 0) { + return false; + } + if (title == null || title.isBlank() || message == null || message.isBlank()) { + return false; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement( + "insert into messages (from_user, to_user, title, message, deleted, readed, reply_to) values (?,?,?,?,0,0,?)" + )) { + ps.setLong(1, fromUser); + ps.setLong(2, toUser); + ps.setString(3, title); + ps.setString(4, message); + if (replyTo != null && replyTo > 0) { + ps.setLong(5, replyTo); + } else { + ps.setNull(5, Types.BIGINT); + } + ps.executeUpdate(); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + private int scalarCount(String sql, long... params) { + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 0; i < params.length; i++) { + ps.setLong(i + 1, params[i]); + } + try (ResultSet rs = ps.executeQuery()) { + return rs.next() ? rs.getInt("cnt") : 0; + } + } catch (Exception ex) { + return 0; + } + } + + private static String sanitizeOrder(String orderBy) { + if ("readed asc, lastitem asc".equalsIgnoreCase(orderBy)) { + return "readed asc, lastitem asc"; + } + return "readed desc, lastitem desc"; + } + + private static String formatTs(Timestamp ts) { + if (ts == null) { + return ""; + } + return ts.toInstant().atZone(APP_ZONE).format(DATE_TIME); + } + + private static String valueOrDefault(String value, String dflt) { + return value == null ? dflt : value; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageStats.java b/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageStats.java new file mode 100644 index 0000000..46a09b9 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageStats.java @@ -0,0 +1,19 @@ +package cz.kamma.fabka.httpserver.repository; + +public class PrivateMessageStats { + private final int unread; + private final int total; + + public PrivateMessageStats(int unread, int total) { + this.unread = unread; + this.total = total; + } + + public int getUnread() { + return unread; + } + + public int getTotal() { + return total; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateThreadRoot.java b/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateThreadRoot.java new file mode 100644 index 0000000..61123e9 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateThreadRoot.java @@ -0,0 +1,37 @@ +package cz.kamma.fabka.httpserver.repository; + +public class PrivateThreadRoot { + private final long rootId; + private final long otherUserId; + private final String title; + private final String replyTitle; + private final String otherUsername; + + public PrivateThreadRoot(long rootId, long otherUserId, String title, String replyTitle, String otherUsername) { + this.rootId = rootId; + this.otherUserId = otherUserId; + this.title = title; + this.replyTitle = replyTitle; + this.otherUsername = otherUsername; + } + + public long getRootId() { + return rootId; + } + + public long getOtherUserId() { + return otherUserId; + } + + public String getTitle() { + return title; + } + + public String getReplyTitle() { + return replyTitle; + } + + public String getOtherUsername() { + return otherUsername; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateThreadSummary.java b/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateThreadSummary.java new file mode 100644 index 0000000..075b6b5 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateThreadSummary.java @@ -0,0 +1,57 @@ +package cz.kamma.fabka.httpserver.repository; + +public class PrivateThreadSummary { + private final long id; + private final String title; + private final String createdAt; + private final String senderName; + private final String recipientName; + private final long replies; + private final boolean allRead; + + public PrivateThreadSummary( + long id, + String title, + String createdAt, + String senderName, + String recipientName, + long replies, + boolean allRead + ) { + this.id = id; + this.title = title; + this.createdAt = createdAt; + this.senderName = senderName; + this.recipientName = recipientName; + this.replies = replies; + this.allRead = allRead; + } + + public long getId() { + return id; + } + + public String getTitle() { + return title; + } + + public String getCreatedAt() { + return createdAt; + } + + public String getSenderName() { + return senderName; + } + + public String getRecipientName() { + return recipientName; + } + + public long getReplies() { + return replies; + } + + public boolean isAllRead() { + return allRead; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/QuotedTextItem.java b/src/main/java/cz/kamma/fabka/httpserver/repository/QuotedTextItem.java new file mode 100644 index 0000000..568700a --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/QuotedTextItem.java @@ -0,0 +1,19 @@ +package cz.kamma.fabka.httpserver.repository; + +public class QuotedTextItem { + private final String author; + private final String text; + + public QuotedTextItem(String author, String text) { + this.author = author; + this.text = text; + } + + public String getAuthor() { + return author; + } + + public String getText() { + return text; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/SettingsRepository.java b/src/main/java/cz/kamma/fabka/httpserver/repository/SettingsRepository.java new file mode 100644 index 0000000..aed20ae --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/SettingsRepository.java @@ -0,0 +1,96 @@ +package cz.kamma.fabka.httpserver.repository; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.HashMap; +import java.util.Map; + +public class SettingsRepository { + private static final String SETTING_SQL = "SELECT value FROM settings WHERE name=? LIMIT 1"; + private static final String USER_SETTINGS_SQL = "SELECT name, value FROM settings WHERE userid=?"; + private static final String UPDATE_USER_SETTING_SQL = "UPDATE settings SET value=? WHERE userid=? AND name=?"; + private static final String INSERT_USER_SETTING_SQL = "INSERT INTO settings (userid, name, value) VALUES (?,?,?)"; + + private final String jdbcUrl; + private final String jdbcUser; + private final String jdbcPassword; + + public SettingsRepository(String jdbcUrl, String jdbcUser, String jdbcPassword) { + this.jdbcUrl = jdbcUrl; + this.jdbcUser = jdbcUser; + this.jdbcPassword = jdbcPassword; + } + + public MessageRenderSettings loadMessageRenderSettings() { + String youtubeSnippet = getSetting("YOUTUBE_EMBED_SNIPPET"); + String webmSnippet = getSetting("WEBM_EMBED_SNIPPET"); + String youtubeReplace = getSetting("YOUTUBE_REPLACE_URL"); + return new MessageRenderSettings(youtubeSnippet, webmSnippet, youtubeReplace); + } + + public Map loadUserSettings(long userId) { + Map out = new HashMap<>(); + if (userId <= 0) { + return out; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(USER_SETTINGS_SQL)) { + ps.setLong(1, userId); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + String key = rs.getString("name"); + String value = rs.getString("value"); + if (key != null && !key.isBlank() && value != null) { + out.put(key, value); + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return out; + } + + public void upsertUserSetting(long userId, String name, String value) { + if (userId <= 0 || name == null || name.isBlank()) { + return; + } + String safeValue = value == null ? "" : value; + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword)) { + int updated; + try (PreparedStatement up = conn.prepareStatement(UPDATE_USER_SETTING_SQL)) { + up.setString(1, safeValue); + up.setLong(2, userId); + up.setString(3, name); + updated = up.executeUpdate(); + } + if (updated < 1) { + try (PreparedStatement ins = conn.prepareStatement(INSERT_USER_SETTING_SQL)) { + ins.setLong(1, userId); + ins.setString(2, name); + ins.setString(3, safeValue); + ins.executeUpdate(); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + private String getSetting(String key) { + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(SETTING_SQL)) { + ps.setString(1, key); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + return rs.getString(1); + } + } + } catch (Exception ex) { + return null; + } + return null; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/UserIcon.java b/src/main/java/cz/kamma/fabka/httpserver/repository/UserIcon.java new file mode 100644 index 0000000..a368d58 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/UserIcon.java @@ -0,0 +1,19 @@ +package cz.kamma.fabka.httpserver.repository; + +public class UserIcon { + private final byte[] data; + private final String contentType; + + public UserIcon(byte[] data, String contentType) { + this.data = data; + this.contentType = contentType; + } + + public byte[] getData() { + return data; + } + + public String getContentType() { + return contentType; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/UserIconRepository.java b/src/main/java/cz/kamma/fabka/httpserver/repository/UserIconRepository.java new file mode 100644 index 0000000..fd39ace --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/UserIconRepository.java @@ -0,0 +1,66 @@ +package cz.kamma.fabka.httpserver.repository; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; + +public class UserIconRepository { + private static final String USER_ICON_SQL = "SELECT data, mimetype FROM user_icon WHERE userid=? ORDER BY id DESC LIMIT 1"; + private static final String INSERT_USER_ICON_SQL = "INSERT INTO user_icon (userid, data, mimetype) VALUES (?,?,?)"; + + private final String jdbcUrl; + private final String jdbcUser; + private final String jdbcPassword; + + public UserIconRepository(String jdbcUrl, String jdbcUser, String jdbcPassword) { + this.jdbcUrl = jdbcUrl; + this.jdbcUser = jdbcUser; + this.jdbcPassword = jdbcPassword; + } + + public UserIcon findLatestByUserId(long userId) { + if (userId <= 0) { + return null; + } + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(USER_ICON_SQL)) { + ps.setLong(1, userId); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + return null; + } + byte[] data = rs.getBytes("data"); + if (data == null || data.length == 0) { + return null; + } + String mime = rs.getString("mimetype"); + if (mime == null || mime.isBlank()) { + mime = "image/jpeg"; + } + return new UserIcon(data, mime); + } + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + public boolean saveUserIcon(long userId, byte[] data, String contentType) { + if (userId <= 0 || data == null || data.length == 0) { + return false; + } + String safeMime = (contentType == null || contentType.isBlank()) ? "application/octet-stream" : contentType; + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(INSERT_USER_ICON_SQL)) { + ps.setLong(1, userId); + ps.setBytes(2, data); + ps.setString(3, safeMime); + ps.executeUpdate(); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/repository/VoteStats.java b/src/main/java/cz/kamma/fabka/httpserver/repository/VoteStats.java new file mode 100644 index 0000000..1c2d7ac --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/repository/VoteStats.java @@ -0,0 +1,31 @@ +package cz.kamma.fabka.httpserver.repository; + +public class VoteStats { + private final int yes; + private final int no; + private final String yesUsers; + private final String noUsers; + + public VoteStats(int yes, int no, String yesUsers, String noUsers) { + this.yes = yes; + this.no = no; + this.yesUsers = yesUsers; + this.noUsers = noUsers; + } + + public int getYes() { + return yes; + } + + public int getNo() { + return no; + } + + public String getYesUsers() { + return yesUsers; + } + + public String getNoUsers() { + return noUsers; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/session/SessionData.java b/src/main/java/cz/kamma/fabka/httpserver/session/SessionData.java new file mode 100644 index 0000000..c24ddd7 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/session/SessionData.java @@ -0,0 +1,43 @@ +package cz.kamma.fabka.httpserver.session; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class SessionData { + private final String id; + private volatile long lastAccessMillis; + private final ConcurrentHashMap attributes = new ConcurrentHashMap<>(); + + public SessionData(String id) { + this.id = id; + this.lastAccessMillis = System.currentTimeMillis(); + } + + public String id() { + return id; + } + + public long lastAccessMillis() { + return lastAccessMillis; + } + + public void touch() { + lastAccessMillis = System.currentTimeMillis(); + } + + public Object getAttribute(String key) { + return attributes.get(key); + } + + public void setAttribute(String key, Object value) { + if (value == null) { + attributes.remove(key); + return; + } + attributes.put(key, value); + } + + public Map attributes() { + return attributes; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/session/SessionManager.java b/src/main/java/cz/kamma/fabka/httpserver/session/SessionManager.java new file mode 100644 index 0000000..9eeb370 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/session/SessionManager.java @@ -0,0 +1,94 @@ +package cz.kamma.fabka.httpserver.session; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class SessionManager { + public static final String COOKIE_NAME = "FCHSESSION"; + + private final long timeoutMillis; + private final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); + + public SessionManager(Duration timeout) { + this.timeoutMillis = timeout.toMillis(); + } + + public SessionData get(String sessionId) { + if (sessionId == null || sessionId.isBlank()) { + return null; + } + SessionData data = sessions.get(sessionId); + if (data == null) { + return null; + } + if (isExpired(data)) { + sessions.remove(sessionId); + return null; + } + data.touch(); + return data; + } + + public SessionData create() { + cleanupExpired(); + String id = UUID.randomUUID().toString(); + SessionData data = new SessionData(id); + sessions.put(id, data); + return data; + } + + public void invalidate(String sessionId) { + if (sessionId != null) { + sessions.remove(sessionId); + } + } + + public int countSessionsWithAttribute(String attributeKey) { + cleanupExpired(); + int count = 0; + for (SessionData session : sessions.values()) { + Object value = session.getAttribute(attributeKey); + if (value != null && !String.valueOf(value).isBlank()) { + count++; + } + } + return count; + } + + public List sessionAttributeValues(String attributeKey) { + cleanupExpired(); + List values = new ArrayList<>(); + for (SessionData session : sessions.values()) { + Object value = session.getAttribute(attributeKey); + if (value == null) { + continue; + } + String asString = String.valueOf(value).trim(); + if (!asString.isBlank()) { + values.add(asString); + } + } + return values; + } + + public List activeSessions() { + cleanupExpired(); + return new ArrayList<>(sessions.values()); + } + + private boolean isExpired(SessionData data) { + return System.currentTimeMillis() - data.lastAccessMillis() > timeoutMillis; + } + + private void cleanupExpired() { + for (Map.Entry entry : sessions.entrySet()) { + if (isExpired(entry.getValue())) { + sessions.remove(entry.getKey()); + } + } + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/web/LegacyMessageFormatter.java b/src/main/java/cz/kamma/fabka/httpserver/web/LegacyMessageFormatter.java new file mode 100644 index 0000000..0215e34 --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/web/LegacyMessageFormatter.java @@ -0,0 +1,123 @@ +package cz.kamma.fabka.httpserver.web; + +import java.util.StringTokenizer; + +public final class LegacyMessageFormatter { + private LegacyMessageFormatter() { + } + + public static String convertMessageToHtml( + String message, + String youtubeSnippet, + String webmSnippet, + String youtubeReplaceUrls + ) { + if (message == null || message.isEmpty()) { + return message == null ? "" : message; + } + + String rendered = message; + if (youtubeSnippet != null && youtubeSnippet.length() > 10) { + rendered = processYoutubeLink(rendered, youtubeSnippet, youtubeReplaceUrls); + } + if (webmSnippet != null && webmSnippet.length() > 10) { + rendered = processWebMLink(rendered, webmSnippet); + } + + StringTokenizer strtok = new StringTokenizer(rendered, "\n\t "); + while (strtok.hasMoreTokens()) { + String part = strtok.nextToken(); + String lower = part.toLowerCase(); + if (part.startsWith("http://") || part.startsWith("https://")) { + if (lower.endsWith(".gif") || lower.endsWith(".png") || lower.endsWith(".jpg") + || lower.endsWith(".bmp")) { + rendered = rendered.replace(part, ""); + } else { + rendered = rendered.replace(part, "" + part + ""); + } + } + } + return rendered.replace("\r\n", "
").replace("\n", "
"); + } + + private static String processYoutubeLink(String message, String youtubeSnippet, String replaceUrls) { + String rendered = message; + if (replaceUrls != null && !replaceUrls.isBlank()) { + String[] tmp = replaceUrls.split(","); + for (String rep : tmp) { + rendered = rendered.replace(rep.trim(), "www.youtube.com"); + } + } + + String[] parts = null; + String part = null; + if (rendered.contains("")) { + part = rendered.substring(rendered.indexOf("")); + parts = part.split(" "); + } else if (rendered.contains("")); + parts = part.split(" "); + } else if (rendered.contains("//www.youtube.com")) { + StringTokenizer strtok = new StringTokenizer(rendered, " \n\t"); + while (strtok.hasMoreTokens()) { + part = strtok.nextToken(); + if (part.contains("//www.youtube.com")) { + String videoId = extractYoutubeToken(part); + if (videoId != null) { + return rendered.replace(part, youtubeSnippet.replace("#YT_LINK#", videoId)); + } + } + } + + int from = rendered.indexOf("http://www.youtube.com"); + if (from >= 0) { + int to = rendered.indexOf(" ", from); + if (to > from) { + part = rendered.substring(from, to); + parts = part.split(" "); + } + } + } + if (part == null || parts == null) { + return rendered; + } + + for (String p : parts) { + if (p.startsWith("src=") && p.contains("youtube.com")) { + String[] split = p.split("=", 2); + if (split.length < 2) { + continue; + } + String videoId = extractYoutubeToken(split[1]); + if (videoId != null) { + return rendered.replace(part, youtubeSnippet.replace("#YT_LINK#", videoId)); + } + } + } + return rendered; + } + + private static String processWebMLink(String message, String webmSnippet) { + String rendered = message; + StringTokenizer strtok = new StringTokenizer(rendered, " \n\t"); + while (strtok.hasMoreTokens()) { + String part = strtok.nextToken(); + String lower = part.toLowerCase(); + if (lower.startsWith("http") && lower.endsWith(".webm")) { + rendered = rendered.replace(part, webmSnippet.replace("#WEBM_URL#", part)); + } + } + return rendered; + } + + private static String extractYoutubeToken(String value) { + StringTokenizer strtok = new StringTokenizer(value, "/=&?\""); + while (strtok.hasMoreTokens()) { + String tok = strtok.nextToken().trim(); + if (tok.length() == 11) { + return tok; + } + } + return null; + } +} diff --git a/src/main/java/cz/kamma/fabka/httpserver/web/Pages.java b/src/main/java/cz/kamma/fabka/httpserver/web/Pages.java new file mode 100644 index 0000000..3f9b41a --- /dev/null +++ b/src/main/java/cz/kamma/fabka/httpserver/web/Pages.java @@ -0,0 +1,919 @@ +package cz.kamma.fabka.httpserver.web; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import cz.kamma.fabka.httpserver.repository.ForumAttachment; +import cz.kamma.fabka.httpserver.repository.ForumDetail; +import cz.kamma.fabka.httpserver.repository.ForumDisplayView; +import cz.kamma.fabka.httpserver.repository.ForumMessage; +import cz.kamma.fabka.httpserver.repository.ForumSummary; +import cz.kamma.fabka.httpserver.repository.MemberProfile; +import cz.kamma.fabka.httpserver.repository.MessageRenderSettings; +import cz.kamma.fabka.httpserver.repository.MysqlClientRepository; +import cz.kamma.fabka.httpserver.repository.PrivateMessageItem; +import cz.kamma.fabka.httpserver.repository.PrivateMessageStats; +import cz.kamma.fabka.httpserver.repository.PrivateThreadRoot; +import cz.kamma.fabka.httpserver.repository.PrivateThreadSummary; +import cz.kamma.fabka.httpserver.repository.QuotedTextItem; + +public final class Pages { + private static final String LOGIN_TEMPLATE = readTemplate("webapp/login.html"); + private static final String FORUM_TEMPLATE = readTemplate("webapp/forum.html"); + private static final String FORUM_DISPLAY_TEMPLATE = readTemplate("webapp/forumdisplay.html"); + private static final String CHAT_TEMPLATE = readTemplate("webapp/chat.html"); + private static final String PRIVATE_TEMPLATE = readTemplate("webapp/private.html"); + private static final String NEW_PM_TEMPLATE = readTemplate("webapp/newpm.html"); + private static final String MESSAGE_TEMPLATE = readTemplate("webapp/message.html"); + private static final String NEW_THREAD_TEMPLATE = readTemplate("webapp/newthread.html"); + private static final String MEMBER_TEMPLATE = readTemplate("webapp/member.html"); + + private Pages() { + } + + public static String loginPage(boolean showError) { + String error = showError + ? "\n" + + "
\n" + + "
Invalid user or password.
\n" + + "

\n" + : ""; + return LOGIN_TEMPLATE + .replace("{{COMMON_HEADER}}", "") + .replace("{{COMMON_FOOTER}}", renderLoginFooter()) + .replace("{{ERROR_BLOCK}}", error); + } + + public static String forumPage( + String username, + List forums, + Map newCounts, + int loggedUsersCount, + List loggedUsers, + PrivateMessageStats pmStats + ) { + StringBuilder forumRows = new StringBuilder(); + if (forums == null || forums.isEmpty()) { + forumRows.append("No forums found.\n"); + } else { + for (ForumSummary forum : forums) { + forumRows.append("") + .append("") + .append("
") + .append(escapeHtml(forum.getName())).append("") + .append(forum.isLocked() ? "" : "") + .append("") + .append(renderNewCountText(newCounts == null ? null : newCounts.get(forum.getId()))) + .append("") + .append("
") + .append("
").append(escapeHtml(forum.getDescription())).append("
") + .append("
").append(escapeHtml(forum.getCreatedAt())).append("
") + .append("") + .append("") + .append("
") + .append("
").append(escapeHtml(forum.getLastPostUser())).append("
") + .append("
").append(escapeHtml(forum.getLastPostAt())).append("
") + .append("
") + .append("") + .append("") + .append(forum.getPostsCount()) + .append("") + .append("\n"); + } + } + + String loggedUsersHtml = (loggedUsers == null || loggedUsers.isEmpty()) + ? "-" + : loggedUsers.stream() + .map(user -> "" + escapeHtml(user) + " (0)") + .collect(Collectors.joining(", ")); + + return FORUM_TEMPLATE + .replace("{{COMMON_HEADER}}", renderCommonHeader(username, "New thread", true, pmStats)) + .replace("{{COMMON_FOOTER}}", renderCommonFooter(Math.max(0, loggedUsersCount), loggedUsers)) + .replace("{{FORUM_ROWS}}", forumRows.toString()) + .replace("{{LOGGED_USERS_COUNT}}", String.valueOf(Math.max(0, loggedUsersCount))) + .replace("{{LOGGED_USERS_LIST}}", loggedUsersHtml); + } + + private static String renderNewCountText(Integer count) { + if (count == null || count <= 0) { + return ""; + } + return " " + count + " new message(s)"; + } + + public static String forumDisplayPage( + String username, + long currentUserId, + ForumDetail forum, + ForumDisplayView view, + Long quoteItemId, + QuotedTextItem quotedTextItem, + int loggedUsersCount, + List loggedUsers, + MessageRenderSettings renderSettings, + PrivateMessageStats pmStats + ) { + StringBuilder messageRows = new StringBuilder(); + List messages = view == null ? List.of() : view.getMessages(); + boolean showFull = view == null || !"simple".equalsIgnoreCase(view.getShowType()); + if (messages == null || messages.isEmpty()) { + messageRows.append("") + .append("
No messages.
\n"); + } else { + for (ForumMessage message : messages) { + messageRows.append("") + .append("") + .append("") + .append(showFull + ? "" + : "") + .append("") + .append("
") + .append("
") + .append(escapeHtml(message.getCreatedAt())) + .append(" ") + .append("") + .append(message.getVoteYes()).append("") + .append("") + .append(message.getVoteNo()).append("") + .append("") + .append("") + .append(message.getAuthorId() == currentUserId + ? "" + + "" + : "") + .append(" ") + .append(message.isSticky() ? "[STICKY]" : "SET [STICKY]") + .append("") + .append("
" + + "" + + "" + + "" + + "" + + "" + + "
 
" + + "
Join Date: " + escapeHtml(message.getAuthorJoinDate()) + "
" + + "
Location: " + escapeHtml(message.getAuthorCity()) + "
" + + "
Posts: " + message.getAuthorPosts() + "
" + + "
" + + "
") + .append(renderQuotedBlock(message.getQuotedItem())) + .append(LegacyMessageFormatter.convertMessageToHtml( + message.getText(), + renderSettings == null ? null : renderSettings.getYoutubeSnippet(), + renderSettings == null ? null : renderSettings.getWebmSnippet(), + renderSettings == null ? null : renderSettings.getYoutubeReplaceUrls() + )) + .append(renderAttachments(message.getAttachments(), view == null ? "true" : view.getShowImg())) + .append("
\n"); + } + } + + String loggedUsersHtml = (loggedUsers == null || loggedUsers.isEmpty()) + ? "-" + : loggedUsers.stream() + .map(user -> "" + escapeHtml(user) + " (0)") + .collect(Collectors.joining(", ")); + + String forumName = forum == null ? "" : forum.getName(); + String forumDesc = forum == null ? "" : forum.getDescription(); + String forumCountdown = forum == null ? "" : valueOrDefault(forum.getCountdown(), ""); + long loadPageTime = System.currentTimeMillis(); + long forumId = forum == null ? 0 : forum.getId(); + String searchText = view == null ? "" : valueOrDefault(view.getSearchText(), ""); + String showType = view == null ? "full" : valueOrDefault(view.getShowType(), "full"); + String showImg = view == null ? "true" : valueOrDefault(view.getShowImg(), "true"); + String sortBy = view == null ? "fi.id" : valueOrDefault(view.getSortBy(), "fi.id"); + String sortType = view == null ? "desc" : valueOrDefault(view.getSortType(), "desc"); + String perPage = view == null ? "50" : valueOrDefault(view.getPerPage(), "50"); + String baseLink = "/forumdisplay?f=" + forumId + + "&stext=" + url(searchText) + + "&showType=" + url(showType) + + "&showImg=" + url(showImg) + + "&sortBy=" + url(sortBy) + + "&perpage=" + url(perPage); + + int rowsFrom = view == null ? 0 : view.getStartRow(); + int rowsTo = view == null ? 0 : view.getEndRow(); + int rowsTotal = view == null ? 0 : view.getTotalRows(); + int currentPage = view == null ? 1 : Math.max(1, view.getCurrentPage()); + int totalPages = view == null ? 1 : Math.max(1, view.getTotalPages()); + String quoteBlock = ""; + if (quotedTextItem != null) { + quoteBlock = "
" + + "
Quoting:
" + + "" + + "
" + + "
Originally Posted by " + escapeHtml(quotedTextItem.getAuthor()) + "
" + + "
" + escapeHtml(quotedTextItem.getText()).replace("\n", "
") + "
" + + "
" + + "

"; + } + + return FORUM_DISPLAY_TEMPLATE + .replace("{{COMMON_HEADER}}", renderCommonHeader(username, forumName, true, pmStats)) + .replace("{{COMMON_FOOTER}}", renderCommonFooter(Math.max(0, loggedUsersCount), loggedUsers)) + .replace("{{FORUM_NAME}}", escapeHtml(forumName)) + .replace("{{FORUM_DESCRIPTION}}", escapeHtml(forumDesc)) + .replace("{{COUNTDOWN_BLOCK}}", buildCountdownBlock(forumCountdown, loadPageTime)) + .replace("{{MESSAGE_ROWS}}", messageRows.toString()) + .replace("{{FORUM_ID}}", String.valueOf(forumId)) + .replace("{{SEARCH_TEXT}}", escapeHtml(searchText)) + .replace("{{SHOW_TYPE}}", escapeHtml(showType)) + .replace("{{SHOW_IMG}}", escapeHtml(showImg)) + .replace("{{SORT_BY}}", escapeHtml(sortBy)) + .replace("{{SORT_TYPE}}", escapeHtml(sortType)) + .replace("{{NEXT_SORT_TYPE}}", "desc".equalsIgnoreCase(sortType) ? "asc" : "desc") + .replace("{{PER_PAGE}}", escapeHtml(perPage)) + .replace("{{BASE_LINK}}", baseLink) + .replace("{{QUOTE_ITEM}}", String.valueOf(quoteItemId == null ? 0 : quoteItemId)) + .replace("{{QUOTE_BLOCK}}", quoteBlock) + .replace("{{SHOW_TYPE_OPTIONS}}", options( + new String[]{"full", "simple"}, + new String[]{"Full", "Simple"}, + showType + )) + .replace("{{SHOW_IMG_OPTIONS}}", options( + new String[]{"true", "false", "thumbnail", "only"}, + new String[]{"Yes", "No", "Thumbnail", "Images only"}, + showImg + )) + .replace("{{SORT_BY_OPTIONS}}", options( + new String[]{"fi.id", "ua.username", "vvalue"}, + new String[]{"Time", "Author", "Popularity"}, + sortBy + )) + .replace("{{PER_PAGE_OPTIONS}}", options( + new String[]{"10", "25", "50", "100", "all"}, + new String[]{"10", "25", "50", "100", "All"}, + perPage + )) + .replace("{{ROWS_FROM}}", String.valueOf(rowsFrom)) + .replace("{{ROWS_TO}}", String.valueOf(rowsTo)) + .replace("{{ROWS_TOTAL}}", String.valueOf(rowsTotal)) + .replace("{{PAGE_TDS}}", buildPageTds(baseLink, currentPage, totalPages)) + .replace("{{PAGE_LINKS}}", buildPageLinks(baseLink, currentPage, totalPages)) + .replace("{{LOGGED_USERS_COUNT}}", String.valueOf(Math.max(0, loggedUsersCount))) + .replace("{{LOGGED_USERS_LIST}}", loggedUsersHtml); + } + + private static String buildCountdownBlock(String countdownScript, long loadPageTime) { + if (countdownScript == null || countdownScript.isBlank()) { + return ""; + } + return "" + + "
" + + "
"; + } + + public static String chatPage( + String username, + int loggedUsersCount, + List loggedUsers, + PrivateMessageStats pmStats + ) { + String loggedUsersHtml = (loggedUsers == null || loggedUsers.isEmpty()) + ? "-" + : loggedUsers.stream() + .map(user -> "" + escapeHtml(user) + " (0)") + .collect(Collectors.joining(", ")); + return CHAT_TEMPLATE + .replace("{{COMMON_HEADER}}", renderCommonHeader(username, "Chat", true, pmStats)) + .replace("{{COMMON_FOOTER}}", renderCommonFooter(Math.max(0, loggedUsersCount), loggedUsers)) + .replace("{{LOGGED_USERS_COUNT}}", String.valueOf(Math.max(0, loggedUsersCount))) + .replace("{{LOGGED_USERS_LIST}}", loggedUsersHtml); + } + + public static String privatePage( + String username, + List threads, + String orderBy, + String perPage, + int currentPage, + int totalPages, + int rowsFrom, + int rowsTo, + int totalRows, + int loggedUsersCount, + List loggedUsers, + PrivateMessageStats pmStats + ) { + StringBuilder rows = new StringBuilder(); + if (threads == null || threads.isEmpty()) { + rows.append("No private messages."); + } else { + for (PrivateThreadSummary thread : threads) { + rows.append("") + .append("") + .append(" ") + .append("") + .append("
").append(escapeHtml(thread.getCreatedAt())).append("") + .append(thread.isAllRead() ? "" : "") + .append("").append(escapeHtml(thread.getTitle())).append("") + .append(thread.isAllRead() ? "" : "") + .append("
") + .append("
").append(escapeHtml(thread.getSenderName())).append(" -> ").append(escapeHtml(thread.getRecipientName())).append("
") + .append("
Replies: ").append(thread.getReplies()).append("
") + .append("") + .append("") + .append(""); + } + } + String safeOrder = valueOrDefault(orderBy, "readed desc, lastitem desc"); + String nextOrder = safeOrder.contains("desc") ? "readed asc, lastitem asc" : "readed desc, lastitem desc"; + String safePerPage = valueOrDefault(perPage, "25"); + String baseLink = "/private?oBy=" + url(safeOrder) + "&perpage=" + url(safePerPage); + String dateSortLink = "/private?oBy=" + url(nextOrder) + "&perpage=" + url(safePerPage) + "&iPageNo=1"; + + return PRIVATE_TEMPLATE + .replace("{{COMMON_HEADER}}", renderCommonHeader(username, "Private messages", true, pmStats)) + .replace("{{COMMON_FOOTER}}", renderCommonFooter(Math.max(0, loggedUsersCount), loggedUsers)) + .replace("{{THREAD_ROWS}}", rows.toString()) + .replace("{{THREAD_COUNT}}", String.valueOf(totalRows)) + .replace("{{ORDER_BY}}", escapeHtml(safeOrder)) + .replace("{{PER_PAGE}}", escapeHtml(safePerPage)) + .replace("{{I_PAGE_NO}}", String.valueOf(currentPage)) + .replace("{{PER_PAGE_OPTIONS}}", options( + new String[]{"10", "25", "50", "100", "all"}, + new String[]{"10", "25", "50", "100", "All"}, + safePerPage + )) + .replace("{{ROWS_FROM}}", String.valueOf(rowsFrom)) + .replace("{{ROWS_TO}}", String.valueOf(rowsTo)) + .replace("{{ROWS_TOTAL}}", String.valueOf(totalRows)) + .replace("{{PAGE_LINKS}}", buildPageLinks(baseLink, currentPage, totalPages)) + .replace("{{DATE_SORT_LINK}}", dateSortLink); + } + + public static String newPmPage( + String username, + long toUserId, + String toUsername, + String error, + int loggedUsersCount, + List loggedUsers, + PrivateMessageStats pmStats + ) { + return NEW_PM_TEMPLATE + .replace("{{COMMON_HEADER}}", renderCommonHeader(username, "Private messages", true, pmStats)) + .replace("{{COMMON_FOOTER}}", renderCommonFooter(Math.max(0, loggedUsersCount), loggedUsers)) + .replace("{{TO_USER_ID}}", String.valueOf(toUserId)) + .replace("{{TO_USERNAME}}", escapeHtml(toUsername)) + .replace("{{ERROR_BLOCK}}", renderErrorBlock(error)); + } + + public static String newThreadPage( + String username, + String error, + int loggedUsersCount, + List loggedUsers, + PrivateMessageStats pmStats + ) { + return NEW_THREAD_TEMPLATE + .replace("{{COMMON_HEADER}}", renderCommonHeader(username, "New thread", true, pmStats)) + .replace("{{COMMON_FOOTER}}", renderCommonFooter(Math.max(0, loggedUsersCount), loggedUsers)) + .replace("{{USERNAME}}", escapeHtml(valueOrDefault(username, ""))) + .replace("{{ERROR_BLOCK}}", renderErrorBlock(error)); + } + + public static String memberPage( + String username, + MemberProfile profile, + boolean soundOn, + String error, + int loggedUsersCount, + List loggedUsers, + PrivateMessageStats pmStats + ) { + String empty = ""; + return MEMBER_TEMPLATE + .replace("{{COMMON_HEADER}}", renderCommonHeader(username, "Member details", true, pmStats)) + .replace("{{COMMON_FOOTER}}", renderCommonFooter(Math.max(0, loggedUsersCount), loggedUsers)) + .replace("{{ERROR_BLOCK}}", renderErrorBlock(error)) + .replace("{{USERNAME}}", profile == null ? empty : escapeHtml(valueOrDefault(profile.getUsername(), empty))) + .replace("{{JOIN_DATE}}", profile == null ? "N/A" : escapeHtml(valueOrDefault(profile.getCreatedAt(), "N/A"))) + .replace("{{EMAIL}}", profile == null ? empty : escapeHtml(valueOrDefault(profile.getEmail(), empty))) + .replace("{{FIRST_NAME}}", profile == null ? empty : escapeHtml(valueOrDefault(profile.getFirstName(), empty))) + .replace("{{LAST_NAME}}", profile == null ? empty : escapeHtml(valueOrDefault(profile.getLastName(), empty))) + .replace("{{CITY}}", profile == null ? empty : escapeHtml(valueOrDefault(profile.getCity(), empty))) + .replace("{{SOUND_CHECKED}}", soundOn ? "checked='checked'" : ""); + } + + public static String messagePage( + String username, + PrivateThreadRoot root, + List messages, + String perPage, + int currentPage, + int totalPages, + int rowsFrom, + int rowsTo, + int totalRows, + String error, + int loggedUsersCount, + List loggedUsers, + PrivateMessageStats pmStats + ) { + StringBuilder rows = new StringBuilder(); + if (messages == null || messages.isEmpty()) { + rows.append("
No messages.
"); + } else { + for (PrivateMessageItem item : messages) { + rows.append("") + .append("") + .append("") + .append("
") + .append(escapeHtml(item.getCreatedAt())) + .append("
") + .append("") + .append("
 
") + .append("
Join Date: ").append(escapeHtml(item.getFromJoinDate())).append("
") + .append("
Posts: ").append(item.getFromPosts()).append("
") + .append("
") + .append(LegacyMessageFormatter.convertMessageToHtml(item.getMessage(), null, null, null)) + .append("
"); + } + } + + String empty = ""; + long pmId = root == null ? 0 : root.getRootId(); + long toUserId = root == null ? 0 : root.getOtherUserId(); + String safePerPage = valueOrDefault(perPage, "10"); + String baseLink = "/message?pmid=" + pmId + "&perpage=" + url(safePerPage); + return MESSAGE_TEMPLATE + .replace("{{COMMON_HEADER}}", renderCommonHeader(username, "Private messages", true, pmStats)) + .replace("{{COMMON_FOOTER}}", renderCommonFooter(Math.max(0, loggedUsersCount), loggedUsers)) + .replace("{{ERROR_BLOCK}}", renderErrorBlock(error)) + .replace("{{TO_USERNAME}}", root == null ? empty : escapeHtml(root.getOtherUsername())) + .replace("{{PM_ID}}", String.valueOf(pmId)) + .replace("{{RE_TITLE}}", root == null ? empty : escapeHtml(root.getReplyTitle())) + .replace("{{TO_USER_ID}}", String.valueOf(toUserId)) + .replace("{{PER_PAGE}}", escapeHtml(safePerPage)) + .replace("{{I_PAGE_NO}}", String.valueOf(currentPage)) + .replace("{{THREAD_TITLE}}", root == null ? empty : escapeHtml(root.getTitle())) + .replace("{{MESSAGE_ROWS}}", rows.toString()) + .replace("{{PER_PAGE_OPTIONS}}", options( + new String[]{"10", "25", "50", "100", "all"}, + new String[]{"10", "25", "50", "100", "All"}, + safePerPage + )) + .replace("{{ROWS_FROM}}", String.valueOf(rowsFrom)) + .replace("{{ROWS_TO}}", String.valueOf(rowsTo)) + .replace("{{ROWS_TOTAL}}", String.valueOf(totalRows)) + .replace("{{PAGE_LINKS}}", buildPageLinks(baseLink, currentPage, totalPages)); + } + + public static String mysqlClientPage( + String username, + List databases, + List tables, + String selectedDatabase, + String selectedTable, + String rowsToShow, + String myQuery, + String tableSql, + String command, + String info, + String error, + MysqlClientRepository.SqlExecution result, + int loggedUsersCount, + List loggedUsers, + PrivateMessageStats pmStats + ) { + StringBuilder dbOptions = new StringBuilder(); + if (databases == null || databases.isEmpty()) { + dbOptions.append(""); + } else { + for (String db : databases) { + dbOptions.append(""); + } + } + + StringBuilder tableOptions = new StringBuilder(); + if (tables == null || tables.isEmpty()) { + tableOptions.append(""); + } else { + for (String table : tables) { + tableOptions.append(""); + } + } + + StringBuilder resultHtml = new StringBuilder(); + if (result != null) { + if (result.isResultSet()) { + resultHtml.append(""); + resultHtml.append(""); + resultHtml.append(""); + for (String column : result.getColumns()) { + resultHtml.append(""); + } + resultHtml.append(""); + for (List row : result.getRows()) { + resultHtml.append(""); + for (String value : row) { + resultHtml.append(""); + } + resultHtml.append(""); + } + if (result.getRows().isEmpty()) { + resultHtml.append(""); + } + resultHtml.append("
Result set
").append(escapeHtml(column)).append("
").append(escapeHtml(value == null ? "NULL" : value)).append("
No rows.
"); + } else { + resultHtml.append("") + .append("") + .append("
Affected rows: ").append(result.getUpdateCount()).append("
"); + } + } + + String infoBlock = (info == null || info.isBlank()) + ? "" + : "
" + escapeHtml(info) + "
"; + String errorBlock = (error == null || error.isBlank()) + ? "" + : "
" + escapeHtml(error) + "
"; + + String body = "" + + "" + + "" + + "" + + "kAmMa's Forum MySQL Client" + + "" + + "
" + + "
" + + renderCommonHeader(username, "MySQL Client", true, pmStats) + + "
" + + infoBlock + + errorBlock + + "
" + + "" + + "
" + + " " + + " " + + " " + + " " + + "" + + "
" + + "
" + + " " + + " " + + "Show rows: " + + " " + + "" + + "
" + + "
" + + "
" + + "
" + + "" + + "
" + + "
" + + "
" + + "" + + "
" + + "
" + + resultHtml + + "
" + + renderCommonFooter(Math.max(0, loggedUsersCount), loggedUsers) + + "
"; + return body; + } + + private static String renderCommonHeader(String username, String section, boolean authenticated, PrivateMessageStats pmStats) { + String welcomeName = escapeHtml(valueOrDefault(username, "Guest")); + String sectionHtml = escapeHtml(valueOrDefault(section, "")); + String sectionPart = sectionHtml.isBlank() + ? "" + : ("New thread".equalsIgnoreCase(sectionHtml) && authenticated + ? " - " + sectionHtml + "" + : " - " + sectionHtml); + int unread = pmStats == null ? 0 : Math.max(0, pmStats.getUnread()); + int total = pmStats == null ? 0 : Math.max(0, pmStats.getTotal()); + boolean mysqlAdmin = authenticated && "kamma".equalsIgnoreCase(valueOrDefault(username, "")); + String newPmBanner = unread > 0 + ? " You have unread Private Message" + : " "; + String mysqlLink = mysqlAdmin ? " | MySQL Client" : ""; + String rightBlock = authenticated + ? "" + + "User image" + + "" + + "" + + "

" + + "
Welcome, " + welcomeName + ".
" + + "You last visited: N/A
" + + "
Private Messages: Unread " + unread + ", Total " + total + ".
" + + "
Forum | Chat | " + + "Logout" + + mysqlLink + "
" + + "" + + "" + : "" + + "

" + + "
Welcome, " + welcomeName + ".
" + + "" + + ""; + return "" + + "
" + + "" + + "" + + rightBlock + + "
kAmMa's Forum" + + sectionPart + + "" + newPmBanner + "
" + + "
" + + ""; + } + + private static String renderCommonFooter(int loggedUsersCount, List loggedUsers) { + String loggedUsersHtml = (loggedUsers == null || loggedUsers.isEmpty()) + ? "-" + : loggedUsers.stream() + .map(user -> "" + escapeHtml(user) + " (0)") + .collect(Collectors.joining(", ")); + return "" + + "" + + "" + + "" + + "
Currently Logged Users: " + + Math.max(0, loggedUsersCount) + + "
Who's Online
" + + loggedUsersHtml + + "

" + + "
" + + "kAmMa's Forum System Version 2.43
Copyright ©2006-2021" + + "
" + + ""; + } + + private static String renderErrorBlock(String error) { + if (error == null || error.isBlank()) { + return ""; + } + return "" + + "
" + + "
" + error + "
" + + "

"; + } + + private static String renderLoginFooter() { + return "
" + + "kAmMa's Forum System Version 2.43
Copyright ©2006-2021" + + "
"; + } + + private static String renderQuotedBlock(QuotedTextItem quotedItem) { + if (quotedItem == null) { + return ""; + } + return "
" + + "
Quote:
" + + "" + + "
" + + "
Originally Posted by " + escapeHtml(quotedItem.getAuthor()) + "
" + + "
" + + LegacyMessageFormatter.convertMessageToHtml(quotedItem.getText(), null, null, null) + + "
"; + } + + private static String renderAttachments(List attachments, String showImg) { + if (attachments == null || attachments.isEmpty()) { + return ""; + } + String mode = showImg == null ? "true" : showImg.toLowerCase(); + StringBuilder sb = new StringBuilder(); + sb.append("
Attached Files"); + boolean first = true; + for (ForumAttachment a : attachments) { + if (!first) { + sb.append(""); + } + first = false; + sb.append(""); + } + sb.append("

"); + if (a.isPicture()) { + if ("true".equals(mode) || "yes".equals(mode) || "only".equals(mode)) { + sb.append("") + .append(escapeHtml(a.getName())).append("
") + .append(" 800 ? "width='800'" : "") + .append("/>"); + } else if ("thumbnail".equals(mode)) { + sb.append("") + .append(escapeHtml(a.getName())).append("
") + .append("") + .append(""); + } else { + sb.append("") + .append(escapeHtml(a.getName())).append(""); + } + } else { + sb.append("") + .append(escapeHtml(a.getName())).append(""); + } + sb.append("
"); + return sb.toString(); + } + + private static String options(String[] values, String[] labels, String selected) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < values.length; i++) { + sb.append(""); + } + return sb.toString(); + } + + private static String buildPageLinks(String baseLink, int currentPage, int totalPages) { + if (totalPages <= 1) { + return ""; + } + StringBuilder sb = new StringBuilder(); + int pageWindow = 5; + int currentBlock = (int) Math.ceil(currentPage / (double) pageWindow); + int startPage = ((currentBlock - 1) * pageWindow) + 1; + int endPage = Math.min(startPage + pageWindow - 1, totalPages); + + if (startPage > 1) { + int previousBlockPage = startPage - 1; + sb.append(" « Previous"); + } + + for (int i = startPage; i <= endPage; i++) { + if (i == currentPage) { + sb.append(" ").append(i).append(""); + } else { + sb.append(" ").append(i).append(""); + } + } + + if (endPage < totalPages) { + int nextBlockPage = endPage + 1; + sb.append(" Next »"); + } + return sb.toString(); + } + + private static String buildPageTds(String baseLink, int currentPage, int totalPages) { + if (totalPages <= 1) { + return "1"; + } + StringBuilder sb = new StringBuilder(); + int pageWindow = 5; + int currentBlock = (int) Math.ceil(currentPage / (double) pageWindow); + int startPage = ((currentBlock - 1) * pageWindow) + 1; + int endPage = Math.min(startPage + pageWindow - 1, totalPages); + + if (startPage > 1) { + int previousBlockPage = startPage - 1; + sb.append("« Previous"); + } + + for (int i = startPage; i <= endPage; i++) { + if (i == currentPage) { + sb.append("").append(i).append(""); + } else { + sb.append("") + .append(i).append(""); + } + } + + if (endPage < totalPages) { + int nextBlockPage = endPage + 1; + sb.append("Next »"); + } + return sb.toString(); + } + + private static String url(String value) { + return URLEncoder.encode(value == null ? "" : value, StandardCharsets.UTF_8); + } + + private static String valueOrDefault(String value, String defaultValue) { + return value == null || value.isBlank() ? defaultValue : value; + } + + private static String escapeHtml(String input) { + if (input == null) { + return ""; + } + return input.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); + } + + private static String readTemplate(String path) { + try (InputStream in = Pages.class.getClassLoader().getResourceAsStream(path)) { + if (in == null) { + throw new IllegalStateException("Template not found: " + path); + } + return new String(in.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException ex) { + throw new IllegalStateException("Failed to read template: " + path, ex); + } + } +} diff --git a/src/main/resources/app.properties b/src/main/resources/app.properties new file mode 100644 index 0000000..02d181a --- /dev/null +++ b/src/main/resources/app.properties @@ -0,0 +1,16 @@ +# Database Configuration +app.db.url=jdbc:mariadb://server01:3306/fabkovachata?useUnicode=true&characterEncoding=UTF-8 +app.db.user=fabkovachata +app.db.password=Fch621420+ +app.db.mysql_admin_url=jdbc:mariadb://server01:3306/?useUnicode=true&characterEncoding=UTF-8 + +# Server Configuration +app.server.port=8080 +app.server.threads=10 + +# Session Configuration +app.session.timeout.minutes=30 + +# Multipart Configuration +app.multipart.max_bytes=52428800 +app.multipart.icon_max_bytes=1048576 diff --git a/src/main/resources/webapp/chat.html b/src/main/resources/webapp/chat.html new file mode 100644 index 0000000..b0b4106 --- /dev/null +++ b/src/main/resources/webapp/chat.html @@ -0,0 +1,103 @@ + + + + + + + + + kAmMa's Forum + + +
+
+
+ {{COMMON_HEADER}} +
+ + + + + + + + + + + + +
Chat
+
+
Message:
+
+
+
+ + +
+
+
+
+
+
+

Loading messages...

+
+
+
+ +
+
+ + {{COMMON_FOOTER}} +
+
+ + + + diff --git a/src/main/resources/webapp/client.js b/src/main/resources/webapp/client.js new file mode 100644 index 0000000..a8b0ada --- /dev/null +++ b/src/main/resources/webapp/client.js @@ -0,0 +1,310 @@ +var URLsuffix = '/process/ajaxreq.jsp'; +//var URLsuffix = '/FabkovaChata/process/ajaxreq.jsp'; + +var ajaxHttp = getAjaxLibrary(); + +function getAjaxLibrary() { + var activexmodes = [ "Msxml2.XMLHTTP", "Microsoft.XMLHTTP" ] // activeX + // versions + // to check + // for in IE + if (window.ActiveXObject) { // Test for support for ActiveXObject in IE + // first (as XMLHttpRequest in IE7 is broken) + for ( var i = 0; i < activexmodes.length; i++) { + try { + return new ActiveXObject(activexmodes[i]) + } catch (e) { + // suppress error + } + } + } else if (window.XMLHttpRequest) // if Mozilla, Safari etc + return new XMLHttpRequest() + else + return false +} + +/******************************************************************************* + * F U N C T I O N S + ******************************************************************************/ + +function getHost() { + var url = ""+window.location; + var prot = url.split('//')[0]; + var urlparts = url.split('//')[1].split('/'); + return urlparts[0]; +} + +function getProt() { + var url = ""+window.location; + return url.split('//')[0]; +} + +function ajaxSendRequest(data, onReadyStateChange) +{ + var URL = getProt()+'//'+getHost()+URLsuffix; + //if (ajaxHttp.overrideMimeType) + //ajaxHttp.overrideMimeType('text/xml') + ajaxHttp.open("POST", URL , true); + ajaxHttp.onreadystatechange = onReadyStateChange; + ajaxHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + ajaxHttp.send(data); +} +function ajaxResponseReady() +{ + if (ajaxHttp.readyState == 4) { + /* received */ + if (ajaxHttp.status == 200 || window.location.href.indexOf("http")==-1) { + /* response is ok */ + return true; + } else { + return false; + } + } + return false; +} + +function ajaxGetXmlResponse() { + if (ajaxHttp.responseXML && ajaxHttp.responseXML.parseError && (ajaxHttp.responseXML.parseError.errorCode !=0)) { + ajaxGetXmlError(true); + return null; + } else { + return ajaxHttp.responseXML; + } + } + +function ajaxGetXmlError(withalert) { + if (ajaxHttp.responseXML.parseError.errorCode !=0 ) { + line = ajaxHttp.responseXML.parseError.line; + pos = ajaxHttp.responseXML.parseError.linepos; + error = ajaxHttp.responseXML.parseError.reason; + error = error + "Contact the support ! and send the following informations: error is line " + line + " position " + pos; + error = error + " >>" + ajaxHttp.responseXML.parseError.srcText.substring(pos); + error = error + "GLOBAL:" + ajaxHttp.responseText; + if (withalert) + alert(error); + return error; + } else { + return ""; + } + } + +function convertLinks(text) +{ + var myArray = text.split(" "); + + for (var i = 0 ; i < myArray.length ; i++) { + var part = myArray[i]; + if (part.indexOf("http://")==0) { + text = text.replace(part, '' + part + ''); + } + } + return text; +} + + +/******************************************************************************* + * ajax call functions + */ +function AX_voting(id, yes_no) +{ + var data = 'ajaxMethod=ajaxVoting_'+yes_no +'&universalId='+id; + ajaxSendRequest(data, AX_votingResponse); +} + +function AX_chat_voting(id, yes_no) +{ + var data = 'ajaxMethod=ajaxChatVoting_'+yes_no +'&universalId='+id; + ajaxSendRequest(data, AX_chatVotingResponse); +} + +function AX_sendChatMessage(text, chatid) +{ + var data = 'ajaxMethod=chat_text_' + text +'&universalId='+chatid; + ajaxSendRequest(data, AX_asynchUpdateResponse); +} + +function AX_asynchUpdate() +{ + var data = 'ajaxMethod=asynchUpdate'; + ajaxSendRequest(data, AX_asynchUpdateResponse); +} + +function AX_asynchUpdateChat() +{ + var data = 'ajaxMethod=asynchUpdateChat'; + ajaxSendRequest(data, AX_asynchUpdateResponse); +} + +function AX_firstUpdateChat() +{ + var data = 'ajaxMethod=firstUpdateChat'; + ajaxSendRequest(data, AX_asynchUpdateResponse); +} +/******************************************************************************* + * response functions + */ +function AX_votingResponse() +{ + if (ajaxResponseReady()) { + if (ajaxHttp.responseText.indexOf('invalid') == -1) { + var xmlDocument = ajaxGetXmlResponse(); + var id = xmlDocument.getElementsByTagName("id")[0].firstChild.data; + var yes = xmlDocument.getElementsByTagName("yes")[0].firstChild.data; + var no = xmlDocument.getElementsByTagName("no")[0].firstChild.data; + document.getElementById('yes' + id).innerHTML = yes; + document.getElementById('no' + id).innerHTML = no; + } + } +} + +function AX_chatVotingResponse() +{ + if (ajaxResponseReady()) { + if (ajaxHttp.responseText.indexOf('invalid') == -1) { + var xmlDocument = ajaxGetXmlResponse(); + var id = xmlDocument.getElementsByTagName("id")[0].firstChild.data; + var thumbup = xmlDocument.getElementsByTagName("thumbup")[0].firstChild; + var thumbdown = xmlDocument.getElementsByTagName("thumbdown")[0].firstChild; + + var yesVotes = 0; + var noVotes = 0; + var yesNames = ''; + var noNames = ''; + + if (thumbup!==null && thumbup.data!==null) { + yesNames = thumbup.data; + yesVotes = yesNames.split(',').length; + } + + if (thumbdown!==null && thumbdown.data!==null) { + noNames = thumbdown.data; + noVotes = noNames.split(',').length; + } + + document.getElementById('yes' + id).innerHTML = yesVotes; + document.getElementById('no' + id).innerHTML = noVotes; + document.getElementById('yes' + id).title = yesNames; + document.getElementById('no' + id).title = noNames; + } + } +} + +function AX_asynchUpdateResponse() +{ + if (ajaxResponseReady()) { + if (ajaxHttp.responseText.indexOf('invalid') == -1) { + var xmlDocument = ajaxGetXmlResponse(); + + var userNames = xmlDocument.getElementsByTagName("userName"); + var userIds = xmlDocument.getElementsByTagName("userId"); + var inactives = xmlDocument.getElementsByTagName("inactive"); + var inChat = xmlDocument.getElementsByTagName("inChat"); + + var resStr = ""; + + for (var i=0; i <= (userNames.length-1); i++) + { + if (inChat[i].firstChild.data=='true') + resStr = resStr + "" + userNames[i].firstChild.data + " [Chat] (" + inactives[i].firstChild.data + "), "; + else + resStr = resStr + "" + userNames[i].firstChild.data + " (" + inactives[i].firstChild.data + "), "; + } + + document.getElementById('userCount').innerHTML = ""+userNames.length; + document.getElementById('userList').innerHTML = resStr.substring(0, resStr.length-2); + + var fromNames = xmlDocument.getElementsByTagName("fromName"); + var chatIds = xmlDocument.getElementsByTagName("chatId"); + var newMess = xmlDocument.getElementsByTagName("newMess"); + var times = xmlDocument.getElementsByTagName("time"); + var texts = xmlDocument.getElementsByTagName("text"); + var thumbup = xmlDocument.getElementsByTagName("thumbup"); + var thumbdown = xmlDocument.getElementsByTagName("thumbdown"); + + var resStr = ""; + + var chatNode = document.getElementById('main_chat'); + var newdiv = ''; + + for (var i=0; i <= (chatIds.length-1); i++) + { + var divIdName = 'chatId_'+chatIds[i].firstChild.data; + var color = 'color:#FF0000'; + if (newMess[i].firstChild.data=='1') + color = 'color:#FF0000'; + else if (newMess[i].firstChild.data=='2') + color = 'color:#0000FF'; + else + color = 'color:#000000'; + /* + var yesVotes = 0; + var noVotes = 0; + var yesNames = ' '; + var noNames = ' '; + + if (thumbup[i].firstChild!==null) { + yesNames = thumbup[i].firstChild.data; + yesVotes = yesNames.split(',').length; + } + + if (thumbdown[i].firstChild!==null) { + noNames = thumbdown[i].firstChild.data; + noVotes = noNames.split(',').length; + } + + var chatVotingLine = ' '+yesVotes+''+noVotes+''; + */ + var chatVotingLine = ''; + newdiv += '
['+times[i].firstChild.data+chatVotingLine+'] - '+fromNames[i].firstChild.data+': '+convertLinks(texts[i].firstChild.data)+'
'; + } + + if (chatIds.length>0) + chatNode.innerHTML = newdiv; + + var newTitle = ''; + var fids = xmlDocument.getElementsByTagName("forumId"); + var messCounts = xmlDocument.getElementsByTagName("messageCount"); + if (messCounts.length>0 || document.title.indexOf('(1+)')>-1) { + newTitle += '(1+)'; + } + + var newChatMessages = xmlDocument.getElementsByTagName("newChatMessages"); + if (newChatMessages.length>0 || document.title.indexOf('(CHAT+)')>-1) { + newTitle += '(CHAT+)'; + } + + var newPMs = xmlDocument.getElementsByTagName("newPMCount"); + if (newPMs.length>0) { + document.getElementById('newPM').innerHTML = " You have unread Private Message"; + if (document.title.indexOf('(PM+)')<0) { + newTitle += '(PM+)'; + } + } + + if (newTitle.length!='') { + document.title = newTitle + " kAmMa's Forum"; + } + + var resStr = ''; + for (var i=0; i <= (fids.length-1); i++) + { + resStr = " "+messCounts[i].firstChild.data+" new message(s)"; + if (document.getElementById('newCount'+fids[i].firstChild.data)!=null) { + document.getElementById('newCount'+fids[i].firstChild.data).innerHTML = resStr; + } + } + + var playNotification = xmlDocument.getElementsByTagName("playNotification"); + if (playNotification.length>0 && playNotification[0].firstChild.data=='1' && document.getElementById('soundon')!=null) { + var embed=document.createElement('object'); + embed.setAttribute('type','audio/wav'); + embed.setAttribute('data', 'notif.wav'); + embed.setAttribute('autostart', true); + document.getElementsByTagName('body')[0].appendChild(embed); + } + } + } +} +/* + * END of response functions + */ \ No newline at end of file diff --git a/src/main/resources/webapp/css/all.css b/src/main/resources/webapp/css/all.css new file mode 100644 index 0000000..9d5d622 --- /dev/null +++ b/src/main/resources/webapp/css/all.css @@ -0,0 +1,453 @@ +body +{ + background: #FFFFCC; + color: #000000; + font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; + margin: 5px 10px 10px 10px; + padding: 0px; +} +.voting-buttons +{ + position: relative; + bottom: 1px; +} + +.voting-buttons a +{ + float: none !important; + padding-bottom: 0px !important; + padding-top: 0px !important; +} +a.voting_yes:link, a.voting_yes:visited { + color: #3C922F; + font-weight: bold; + background: url(../images/voting_yes.png) green no-repeat; + border: 1px outset #3C922F; + padding: 2px 4px 2px 20px; + white-space: nowrap; + float: left; + line-height: 10px; + text-decoration: none; +} +a.voting_no:link, a.voting_no:visited { + color: #AE3738; + font-weight: bold; + background: url(../images/voting_no.png) red no-repeat; + border: 1px outset #AE3738; + padding: 2px 4px 2px 20px; + white-space: nowrap; + float: left; + line-height: 10px; + text-decoration: none; +} +a:link, body_alink +{ + color: #0000C0; + text-decoration: none; +} +a:visited, body_avisited +{ + color: #0000C0; + text-decoration: none; +} +a:hover, a:active, body_ahover +{ + color: #663333; + text-decoration: none; +} +.page +{ + background: #FFFFF1; + color: #000000; +} +.page a:link, .page_alink +{ + color: #0000C0; +} +.page a:visited, .page_avisited +{ + color: #0000C0; +} +.page a:hover, .page a:active, .page_ahover +{ + color: #663333; +} +td, th, p, li +{ + font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.tborder +{ + background: #FFFFCC; + color: #000000; + border: 1px solid #0B198C; +} +.tcat +{ + background: #FFFFCC url(../images/cat-line.gif) repeat-x top left; + color: #000000; + font: bold 9pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.tcat a:link, .tcat_alink +{ + color: #0000C0; + text-decoration: none; +} +.tcat a:visited, .tcat_avisited +{ + color: #0000C0; + text-decoration: none; +} +.tcat a:hover, .tcat a:active, .tcat_ahover +{ + color: #663333; + text-decoration: none; +} +.thead +{ + background: #663333 url(../images/cellpic-fp-big.gif) repeat-x top left; + color: #FFFFF1; + font: bold 11px tahoma, verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.thead a:link, .thead_alink +{ + color: #FFFFF1; +} +.thead a:visited, .thead_avisited +{ + color: #FFFFF1; +} +.thead +{ + background: #663333 url(../images/cellpic-fp-big.gif) repeat-x top left; + color: #FFFFF1; + font: bold 11px tahoma, verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.thead a:hover, .thead a:active, .thead_ahover +{ + color: #FFFFCC; +} +.tfoot +{ + background: #663333 url(../images/cellpic-fp.gif) repeat-x top left; + color: #000000; +} +.tfoot a:link, .tfoot_alink +{ + color: #FFFFF1; +} +.tfoot a:visited, .tfoot_avisited +{ + color: #FFFFF1; +} +.tfoot a:hover, .tfoot a:active, .tfoot_ahover +{ + color: #FFFFCC; + text-decoration: underline; +} +.alt1, .alt1Active +{ + background: #FFFFCC; + color: #000000; + font-size: 9pt; +} +.alt2, .alt2Active +{ + background: #FFFF99; + color: #000000; +} +.inlinemod +{ + background: #FFFFCC; + color: #000000; +} +.wysiwyg +{ + background: #FFFFCC; + color: #000000; + font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; + margin: 5px 10px 10px 10px; + padding: 0px; +} +textarea, .bginput +{ + background: #FFFFCC; + font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.bginput option, .bginput optgroup +{ + font-size: 10pt; + font-family: verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.button +{ + background: #FFFFF1; + color: #000000; + font: 11px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +select +{ + background: #FFFFCC; + font: 11px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +option, optgroup +{ + font-size: 11px; + font-family: verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.smallfont +{ + font: 11px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.time +{ + color: #000000; +} +.navbar +{ + color: #0000C0; + font: 11px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.highlight +{ + font-weight: bold; +} +.fjsel +{ + background: #FFFFCC; + color: #000000; +} +.fjdpth0 +{ + background: #FFFFF1; + color: #000000; +} +.panel +{ + background: #FFFFF1; + color: #000000; + padding: 10px; + border: 2px outset; +} +.panelsurround +{ + background: #FFFFCC; + color: #000000; +} +legend +{ + background: transparent; + color: #0000C0; + font: 11px tahoma, verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.vbmenu_control +{ + background: #663333 url(../images/cellpic-fp.gif) repeat-x top left; + color: #FFFFF1; + font: bold 11px tahoma, verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; + padding: 3px 6px 3px 6px; + white-space: nowrap; +} +.vbmenu_control a:link, .vbmenu_control_alink +{ + color: #FFFFFF; + text-decoration: none; +} +.vbmenu_control a:visited, .vbmenu_control_avisited +{ + color: #FFFFFF; + text-decoration: none; +} +.vbmenu_control a:hover, .vbmenu_control a:active, .vbmenu_control_ahover +{ + color: #FFFFFF; + text-decoration: underline; +} +.vbmenu_popup +{ + background: #FFFFFF; + color: #000000; + border: 1px solid #0B198C; +} +.vbmenu_option +{ + background: #FFFFF1; + color: #000000; + font: 11px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; + white-space: nowrap; + cursor: pointer; +} +.vbmenu_option a:link, .vbmenu_option_alink +{ + color: #000000; + text-decoration: none; +} +.vbmenu_option a:visited, .vbmenu_option_avisited +{ + color: #330000; + text-decoration: none; +} +.vbmenu_option a:hover, .vbmenu_option a:active, .vbmenu_option_ahover +{ + color: #330000; + text-decoration: none; +} +.vbmenu_hilite +{ + background: #FFFFCC; + color: #330000; + font: 11px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; + white-space: nowrap; + cursor: pointer; +} +.vbmenu_hilite a:link, .vbmenu_hilite_alink +{ + color: #330000; + text-decoration: none; +} +.vbmenu_hilite a:visited, .vbmenu_hilite_avisited +{ + color: #330000; + text-decoration: none; +} +.vbmenu_hilite a:hover, .vbmenu_hilite a:active, .vbmenu_hilite_ahover +{ + color: #330000; + text-decoration: none; +} +/* ***** styling for 'big' usernames on postbit etc. ***** */ +.bigusername { font-size: 10pt; text-decoration: none;} + +/* ***** small padding on 'thead' elements ***** */ +td.thead, th.thead, div.thead { padding: 4px; } + +/* ***** basic styles for multi-page nav elements */ +.pagenav a { text-decoration: none; } +.pagenav td { padding: 2px 4px 2px 4px; } + +/* ***** de-emphasized text */ +.shade, a.shade:link, a.shade:visited { color: #777777; text-decoration: none; } +a.shade:active, a.shade:hover { color: #FF4400; text-decoration: underline; } +.tcat .shade, .thead .shade, .tfoot .shade { color: #DDDDDD; } + +/* ***** define margin and font-size for elements inside panels ***** */ +.fieldset { margin-bottom: 6px; } +.fieldset, .fieldset td, .fieldset p, .fieldset li { font-size: 11px; } + +/* vbPortal Extras */ +.urlrow, .textrow, .blockform, .boxform, .loginform { + margin: 0px; + font-family: verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.textrow, .blockform, .boxform, .loginform { + font-size: 10px; +} +.urlrow { + font-size: 11px; +} +.textrow, .urlrow { + padding: 2px 2px; +} +.blockform, .loginform { + padding: 0px; +} +.boxform { + padding: 2px; +} +.gogif { + padding: 0px 3px 0px 3px; + margin: 0px; +} + +/* *****thead2 for calendar made by flar ***** */ +.thead2 {url: images/gradients/cellpic-fp-big.gif repeat-x top left; } + +/* *****alt3 alternating color 3 made by flar ***** */ +.alt3 {background-color:#FFFFF1;} + +.nodisplay { + display: none; +} + +#autosearch{ +float:left; +width:205px; +margin:5px 0 0 0; +} +#menucontainer{ +float:left; +position:relative; +width:250px; +height:104px; +} +#results { +} +#results ul { + z-index:10; + position: absolute; + top: 94px; + left: 0px; + border: 1px solid #bfbfbf; + list-style: none; + width: 208px; + display:block; + margin:0; + padding:0; +} +#results ul li { +position:relative; +margin:0; +padding:0; +width:198px; +} +#results ul li a{ + display: block; + color: #444; + background: #fff; + text-decoration: none; + padding: 1px 4px 2px 6px; + width:198px; +} +* html #results ul li a { + margin:0; + padding:0; + display:block; +} +#results ul li a strong { + color: #000; +} +#results ul li a:hover, #results ul li a.hover { + background: #0056f4; + color: #fff; +} +#results ul li a:hover strong, #results ul li a.hover strong { + color: #fff; +} + +input#s{ +margin-top:4px; +width:205px; +font: 12px/12px Verdana, sans-serif; +color:#666666; +padding:3px 5px; +} + +.xdaclear{ +clear:both; +overflow:hidden; + +} + +ul.menu {list-style:none; margin:0; padding:0;} +ul.menu * {margin:0; padding:0} +ul.menu a {display:block; color:#000; text-decoration:none} +ul.menu li {position:relative; float:left; margin-right:2px;font-size:11px;} +ul.menu ul {position:absolute; top:26px; left:0; display:none; opacity:0; list-style:none;width:230px;} +ul.menu ul li {position:relative; border:1px solid #aaa; border-top:none; width:230px; margin:0} +ul.menu ul li a {display:block; padding:3px 5px 3px 12px; background-color:#ffffff} +ul.menu ul li a:hover {background-color:#c5c5c5} +ul.menu ul ul {left:-230px; top:-1px} +ul.menu .menulink {border:1px solid #aaa; padding:5px 5px 5px 5px; font-weight:bold; background:url(/images/header.gif); width:230px} +ul.menu .menulink:hover, ul.menu .menuhover {background:url(/images/header_over.gif)} +ul.menu .sub {background:#fff url(/images/arrow.gif) 4px no-repeat} +ul.menu .topline {border-top:1px solid #aaa} \ No newline at end of file diff --git a/src/main/resources/webapp/favicon.ico b/src/main/resources/webapp/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1442c75e932f089b8cb6c8388c978f2530183442 GIT binary patch literal 630 zcmbVGu@S;B3=|wSGf*-IJ!{a?rS=-BJcx_9V5Z!06yo)Y9Tjjncw35I|B`dL4tpU6 zPL<~5Oli2JPkqTn#j!%xf%NW2+^NFfj)C)?z9`ImeT%| W^i|5_2A?J86U!3w!{BOLywW#=9Or5P literal 0 HcmV?d00001 diff --git a/src/main/resources/webapp/forum.html b/src/main/resources/webapp/forum.html new file mode 100644 index 0000000..d865182 --- /dev/null +++ b/src/main/resources/webapp/forum.html @@ -0,0 +1,43 @@ + + + + + + + + kAmMa's Forum + + +
+
+
+ {{COMMON_HEADER}} +
+
+ + + + +
+
+ Search in threads: + +
+
+
+ + + + + + + + {{FORUM_ROWS}} + +
ThreadLast PostPosts
+
+ {{COMMON_FOOTER}} +
+
+ + diff --git a/src/main/resources/webapp/forumdisplay.html b/src/main/resources/webapp/forumdisplay.html new file mode 100644 index 0000000..6e3ff22 --- /dev/null +++ b/src/main/resources/webapp/forumdisplay.html @@ -0,0 +1,124 @@ + + + + + + + + kAmMa's Forum - {{FORUM_NAME}} + + +
+
+
+ {{COMMON_HEADER}} +
+ + + + + + +
{{FORUM_NAME}}
{{FORUM_DESCRIPTION}}
+
+ + +
+ + + + + + + + + + + +
Reply
+
+ {{QUOTE_BLOCK}} +
+
Message:
+ +
+ +
+
+ + +
+
+
+
+ {{COUNTDOWN_BLOCK}} + +
+ + + + + + + + + {{PAGE_TDS}} + +
+
+ + + + + + + + Search in thread: + +
+
+ Show type: + + + Show images: + + + Sort messages by: + + + + Records per page: + + Rows {{ROWS_FROM}} - {{ROWS_TO}} of {{ROWS_TOTAL}}
+
+
+ + {{MESSAGE_ROWS}} +
+ + {{COMMON_FOOTER}} +
+
+ + diff --git a/src/main/resources/webapp/images/busy.gif b/src/main/resources/webapp/images/busy.gif new file mode 100644 index 0000000000000000000000000000000000000000..80ff48b3e25494afa91354054fa41dac76157b54 GIT binary patch literal 722 zcmZ?wbhEHb6krfwSj5g?mbPCd^-5UfuE~p!l=ki4xbx)R{fGbm|F8I;+s`#5*x50_ z)kx2PnUR5kLGdRGD+2>NgAR}l)GWZj!0cSHi~Ya^OYv}yTsg%Ddv6{qy>`4OMY6Cb zF@)PuW*ztGqQ7B3at^acW*?W}QP{QU;|>oou6EVUKdbB(U+xg)iiuI?#AdF2#V++j zKy$?za^;+m%=HlnIcQtyIsXD^TQxFD(21$ zLNc#MA*4GI**sFRz^9xB8kF6RMGX;NY~W*cE;>z*79YM6O)& zHAHZ7IfnGSKD5^7y16n(pn%S9*~8C+St=P0H2yGFntUpWWz`;4MXccnbYuE4pc}(A zbFUTW--}^4x>vz-Sb{};R@m!T`vT7_(%LQH!onaR*Z=XiVT*0!g8&w}$)}bU3dhdk zJB8{-CZG#J!Dv^JwX9BY&*2;0AFn>!aOKWdO;0$usIhT)29@x7kqji1q0P;u%*&$5$(`5Nw#v$& z000000000000000A^8LV00000EC2ui01W^f000GZU$Kc8?�Uh&<1vZ3I4^>y)QJFG5LBuaz@2cr--&oUk&wlbw0f;}xZm)1fk2 i0RsaRuduGJuoSkpxVI=KE+ZK#FD)l9!8FDjApko`4~u*N literal 0 HcmV?d00001 diff --git a/src/main/resources/webapp/images/cellpic-fp-big.gif b/src/main/resources/webapp/images/cellpic-fp-big.gif new file mode 100644 index 0000000000000000000000000000000000000000..ed29c7a49676ad1a79759a5ba2a8b94edc59889b GIT binary patch literal 315 zcmZ?wbhEHbWMi;oxXQr5&d&b-|NmT1g#sU?iXe@uFrDgfz1k?l+9;#CXw!x`%f$oA ziZ7NcqRelsNk2WeYX6iCJDl{g&t>#gtgVL5MuyVJCY^XXl)j57H3K6Iz8)4ajmVdHc#yyl<0LX$#9Yg>Dm HBZD;nHaASt literal 0 HcmV?d00001 diff --git a/src/main/resources/webapp/images/cernypetr.jpg b/src/main/resources/webapp/images/cernypetr.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c8146864e6fa90d216eb94418d41da6bfd3c478c GIT binary patch literal 9934 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 cjy(40qyhK*GW@k^}G^2KoHL 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/webapp/images/pm.gif b/src/main/resources/webapp/images/pm.gif new file mode 100644 index 0000000000000000000000000000000000000000..444825ce67cad3a04e356b222fd90edac816bcc5 GIT binary patch literal 1026 zcmeH`=}*!D9L9f?GcB90)@8oHY0H{h*XFF=ICE-Cr}I^7bITXjR%^36(Iy#^SBS|_ z4&^654iN<75S1cD#Vb(oNXr{F6i`G_@#vfOcl5k_-hB4#vpsRQVjRbs1bV150IwZv_Dx0;&3(y-sbGn?c?(_t^wPql@KDP$B~oXO^K}rE?mq)J~^!n^^5+Yd|#uGBcQe zSOr}mR`${A-TLpd!@r!U*7HFOy#tO?>0Hyd!E*JESlP3%xKY~!${`^0*D4x!3$US) zFtG)Orvry1p4! zTDmQ7*}r>D^Y1u|JUxt0yZDXseElCLrYWp(Qe|7LrUhx=PPrN~84KcGctab-bU+rY zldv@+cS~-9rR6P}VwYO)sp$f=Wp5;qxeJ138;S2O7CBYg@dDn=>guk<0A&go7+IpL zK-3F@c9*alC^8_hmY-7wR5}|8bM+V}>8c&6*6MZx@<0C!1be)22qHwn_uKv_qI@Qi zB;TO;`YF)~vLuE%ZS_LDp5impLgo#)l&l2R-L`6D$<0TvzGS4dCZ4^DqrJ$@=*T;N zFfiidb+3p0=iWyiT2E8%(Sm$pA~#77%t!r$8;F-nc}T^@Q{mn5$v#Pb-WN_9s-r_c zQ2f{@sWrj32ahKbp4Fy4rp98v3B@#~D=v={mTD|UlSOg4{@#K2&)~z#g09_(ePYds zI)FQZ%T7M56Sfk=)1$8-=@uVcLimBFt$2JA^U^&#{xF8dAYzt~cRz^VX zaizXNt+2s>!ssinWl&1#1+=vEfF88v(f8WYgSND^^cWO=x_`p&ANZ0l`98@#nf+ct zDwGOEFbDwPoUV%M?hyu+-R8#m&3vJBp4CL~Ta4q?7Tg{VM?#@py?SZQwXrh(3$Eq{ zl$3z7o!~d({M!i1hRZ+KAUC#_`oRhsUg!)h+*wp<;0{_xC)MTj2K>R0W{^9|17LUC zz>rTXWHN=z;jXW^Y*Vtf05Gu27=K-j>bgAgyQ|m|%AoHo(O8`&cFgFO|6>uhMKx6S zsx=Y=PMbq6*K*J?@yx7Ls%EjdkF@Q26Ylf+G0fppcAKZFJ)R)8Vh#nvriq#n76+^# zPRIK6K>4ClCzB{9czgyE6>vl<<$_ryFlz*LO|85!A$L;j6#eNGlid=^wsFYeSdRhV zlC=4JQMb#}(blnLK*sO>%wn@`Ru`^U?id*pcg}&85wnO&zEug92Sz!Q{ct&Rgat5$ z4bwbepcaElfi~KfH9D1Hb8~l?$s~9;L+U24H-w<9>ay>;8!lO_RvTur;MRpjQ$#oT zV0a{;Xxdq35&Ai|j5g3-qGeuN_XK_3O}AwDd8#I&>E(^j z1=jr{-t5fOY*5{!(-{3~x=}5*iYRJ!9VWf+lu!`_H4EzPqK3)gwhe!jT2lbb=&H@V z;SYo(VPKsA%M4oW1-pbs{xK&O+xE-rfvNZJs;^`EP7d`7u&{r+avop^cc{VpU*cfQ zWH9-ZJ?s%?JB13cK{0P$t5^^TRnGZ7k7sidr4#rnr|Y^!!Su;ncMSB=hvOE0m1DL; z%O>NzKXiHvgMkFpa6r+uCb{jHsRyfE>9l;f?Xq=QXXMs|LIC<-c=-`&4+LS*;m5Ye zPaJ;|KobcWMaeU7#vLG~c&d|gQ%-+->Hu63cWNMM5B1~UTj=xYsRgCk&zELOYM(lE zn7nq5bmE0~`tl2op?fRI&+e~znjTmD6`EM+_Y(z$&p=RP{LwRo^>taFp2;bc-O%h$ z%(_08*F{`thI6FFMfkgQ#<#zT;}u=(CY^mFTYe}d^M10hyQG1Wpe8Dt`+vx5mHcKU z=J+q()WOYZMd{C-%{U3cHv1P3^Eq@VJMC)4t%LAT7DP^>WImxfIQ0Fs1{g^z<-Fgz z@zUwO4-#lqANADE!ymGcVx;uQ@r-?Y%UW=oWAR9C3+dPH*G?!?j%uA_iGsPj>hpq2 d)9L-8S7lIq^Ov8!75Dn=EBQ(L;z`i4e*rk9eIWn< literal 0 HcmV?d00001 diff --git a/src/main/resources/webapp/images/post_old.gif b/src/main/resources/webapp/images/post_old.gif new file mode 100644 index 0000000000000000000000000000000000000000..b3097c646c07cf39e709d0cfa33a7e83c29aec8c GIT binary patch literal 522 zcmZ?wbhEHb*1cu?=HOjfBwb) zhTba$^%M78dRN*q^W5|QCm#H-?p}ED_5a}5u9m5*rmeVI(mW%-cIt|~A2f}f^Xn%1 zM;329{$kmV*N1Qa-*w@4NJ7iTNnyqeynJ0DD5dcS?@sn)5dcU^qD z`_emSpNd@<-<^K?-_@ts+9}&DAh)1l@~J2PE4${YXgQ~sO^DCwTDtRod~ThiXPLT@ z`;E{4b z*33?geKos+6ATWDN}stE;Qg(b44OoWP^6-I3EV#J1#>T>41ejEx5=2pB&-2!}fX z6cmAge|-f7C61zvC8ee%4XF(sV<-nPmzkO&nSd`i1r4L3ryCurz^M`v9%P6KJPOLR z6d?+k2^zYI1g4Ht5)C&iH^b^2#)lOd%JIsXG#L@o2Lu}c03G5h z0LpdPP!0h<7$+p~1L2@T2YvunT(Dr_0y_+e5@hfpFCLZ(_(aIap#lI16(cZIkl?QZ z0RR(h3}8^90)rj`asW7?0)vbH2o)|=n9=S~i5VF(;3|-yfC4NQ8khorAm|1L3sOe7 z;4cCK7z-Ahz~G_61_uHLiylB#0m7#O8gwWqkRhrn7!xX3kb!jsfCUu> zfHnBGr-uj|1_&_dK*S6VZzDtqP}G!!3lYLD(1>G40(C5@KE(P`#6AoYA^_N6LWT<) z3kF~yp;Ls34JsCB-c06{s$mo>&WI*h-GT`qWL$kK0)zn)E?fvu;Q~Yt61Y8pm>|Fe z1BM=ihvtkS9Wd0$D=ZLzc;W@2^F=VQp;>?p8v+D4-+usx>1p^4@bv4FN#l;)^iG_~HjNI^Y5iH0G$| Yjy&?n!x%%1LC26p7CEFHKLh~)I|>+1H~;_u literal 0 HcmV?d00001 diff --git a/src/main/resources/webapp/images/reply.gif b/src/main/resources/webapp/images/reply.gif new file mode 100644 index 0000000000000000000000000000000000000000..9090957904c850b1138dca21574fd232eb8211d7 GIT binary patch literal 2194 zcmV;D2yORANk%w1VMYKJ0Qdg@b8~fRc8mPbsXbA3zQM|wnVg7>mTiHg%czNTf}nGh z&0B1Wft<;QgodT1s773YbcwBbl*Mj?sB?<5U3jQka-ET#wBEa%sj|XYZIRj9-R0)# zHArfKji=t>@6yuJXMLl3o7BtC-f4cJbd9-#k*kZS+<1P8ld$A#d6Bo6eOz&szoUuI z(A;~8q>h}myPJo7naYr^okUrFV0M|7o~+{J@tvizw6?pdvA&_c z@MwFR$;!}FV|~lb&zYX1*4pC5#@68BD z(A3P$(|M7;p1SPM&)1Ty*?owdcZ;vFv$bu7v59wZi<-2N0gm8V8bd}IwbeMaP zwbs|%^!E10$kXoa?xl%$>BF5$WRixY+GT#IV{nAh)ZWd{(7nCIqN=|A{r!)bt=-_? zikY+e`ujjtd9b#^y1vF*O-jbb&tY&Ncc%0_w_2lOB#>viOcbR8=pLmbBivFO%S~Q<(bM0~ua?!;*7Nf7^Y!+6 zlfS#W!RP7rA^8LW00930EC2ui07d{706+-;0RIVWkz|mDFkyt$!-fq~8Xk@s_3`zO z7l9>IT6o|ku%pM1AVZ22SpiI?47iSnnF9n!DJo@f>02YNjoD+GXK3UlP;)R9Yz=Hz^1&R%E;>L>yw=k{= zhHV}Jtq^t~R_RC^uB-w=r9lG&LiRoX3xGzBc{yl0-0e~2?w4C(aGeOEE-@}63l>1$ z5kE%`T+tY_0WMW=AqNytpdkSWE8Gx-Kq4%_1P71YWyc2^G-6IF9CR4Z05^pAz=$N4 z_&|s#qG-qfhP3FSivtYsVT?4!hy@lL)bi0cjo{KiBMvy=4`O#cL&YzUB!YlDDumO( zD;|s_<&;zsam^_U;F1X>uP6cp0rr%#N-0+W00RpE1W}7HYQ&(2DsY-G!<;h6fI^*k z+Ii=lc+P2pDthRlgeM`ypa3JwB*V!m*Oc(jHWlol#tIce@<%9b)G&tz4!|HlK9i)f zhy=q}VFeWG;gZ7}M%*>R1S44gfB^!F#RImt7Xm13^-DBl)x!qAaNfMinQ^EA2k4hX#sZ#kj3K*RKkc+ z5g3(}CrUUa=L~vkfe9I8_#izQT)`6k^q8Pvf)c1K0}i7U!U^wiKoay}361mlL=P0%(8$1SBwmI3QsFK@h>Tgb@rr z+~*rDXh9kpP=GmD126iJg&y^IfHT}>6teIkE-qoSY0%;gTc|<1Y7l^S;o=PO*?}#t zbr*8? z#WT+^iZ?{zKVpE#85UuSU@QO$wOB#La6t=e$$z#5?wmtf|hcdKItim26g=7xf}u*bCVzJJ0G_JhNv4k|*a$J&7mwgaX)r z1{RhvO~zwSofGHSIWk&Qs!qizJ0(+rrl=Ef!cNFUVV2dgIA+HLjaYT0j>Hi=0xMJl z45FbDvd{xDumKG$ERz|s8c$SNmCPS#QHiLq3YlG~K$At8l?m=43bRV0#7eM-JRlR> zhy(s10uG@XU=R(J7=;cH0~^r5B1_KNsPR~YEJ*kREh=VVLawk26=({v5OPG)2mrb; zj-cTdQ6dk>#5Ur9CK!f8s0J8BV-vEV1H`}vG}(!48g0~ggo`0a_ya8}Ci^%n*WljP z3{Ge<2UoG#AZSZH$j(-SIP3_4LqiHg+f4!;1{^@+bHJ4l9z206@8I^S>9Icxle1q3 z-gLeA*!wWFV{=|8vmhtC{N(;~NB0%yhT_#lP1g$BwCrhPtf%ef;IoE~do|Oax^hEV z@v3O!mB@qI;^xX|Yh77gEb=mb?L$}m%ixo%(IfS-(=``z+w@Yry7W#}No)P(f&RXh z+H!pQ>z5J}V3amN1OA94%35)qM$XR1hR4zi=GHXdtyq01vvld_KypQULq_pZf6@H% zrerEvnC%UB?io+ZADwzXe<1&M`l;_*8yhyZhu@xA9MfMjd)K`x{Bf~nRp;)}l!CwS zTEd+t!X1&{pN~y%SbMxX`YmPX`O3-hZPBUWgvr$W!)YCWe=+^7=|AuZvX13h# VE}D2eH!xT^o|-o?yeyG2{sYe7 zu%H&i0$U(&YA{t(F=bQoSqUa;A|`A?zRJN^jm4Oa2}nl4NR7mZjZgp^(7?hnrpbBy zX$TSmhd@q?N;Rki<)Gv$&=d_KK{yDxD9o}3mcSgCpb@KqG>`=1KwyPxfI&1=LKb=; z1~#C9g=KO>UgL=>tCITzEh-TeRw1_w6=p#n`I7DA3l8Ua8T#t}5!B1+@| znb<}g&;-M92-N_CXlz0jbbuJxfF?hYPos?*k8m*r34fqP#pEA{=NjGHo6!k9=IAOu z8wKsDN7?ym6o(%na_C5rXunCM!+|4cd=9u0!haO)`NmoPc-Q=U0>F0foC69Y`e4sNo zJGG|hY)@0=gSOiG;>&%F6)jaa?>5!9-->nCm37otcQw@BDJyQMDzA%`bT>ES)8Dba zX#dH8fJNE#-Dw)|KTKueLNL#WxczE>-VL>KHAgwa*JV(_bd9{&?D+ zJksBtR`h*tbl=B;cv{83?He<52RkxutS`?__O8TJFMS<8_jBfO>+a&kq0+JD!S=NL z-%}IEU)Ig07H(agSV?#&=>;qAvtrptPL35V{he<;lYioM%i3J!_459K*$bcIrCSmx F{XZLL-_HO5 literal 0 HcmV?d00001 diff --git a/src/main/resources/webapp/images/voting_no.png b/src/main/resources/webapp/images/voting_no.png new file mode 100644 index 0000000000000000000000000000000000000000..47bfe123167abe75eb6771c5849d0cbf90e3bc1b GIT binary patch literal 490 zcmVY5ZxSmce%?)A+d;+h@FKtX(Tp+zd@Tsu<--63-}Xkf|VFQ8W9VFU}vXb zA=n9mn9453M9w64m)qUjz0*lHD2nE>JNstl&1091TxK$;2h)tAfxxgMrfHyXfQTUF z!}GdQ(52@WMpxI6U{0tf6o#-ad1Z5%RbLFJ?MYY9qa%yC`+VN1y-AxydKkD8#o3E< zNUx@6`WFCj&L>K{l0Ou8M70Veq=FFgeOK4D@01d|6{S4B~t_Yc99P?k`Qpa)$yUDtvMuInmkwkw4#+NeucU(;yFk<)@Yq?ToY z1~bYbsg=uP#ceG_Efjk#Y@kM+A~bb&dOJG@U8%v6J(Z3SEO?(!vNzX{E9+6DA?ia5 zDyn9b%ieO<-0$o{{%>} gJi4#`XFzrVpPE+5ecnEI!T;nXocbcu&DxYpO zP5}seX25!8X6B$}ZXi>+nYe!cc=r4E?+(|wARjO>&ATxD+M}~T_Pd`yfaJID-+&50 zN+HzGpFayW$G-aya-0x52g5JscmF}^zJLGD@Z;C#pG@q$d@Mi;WaW=vUG8(){(+cN z&P{&#^})OEU!h(E%m4iR2^s~Te*UOCl>PeiYY11ASM=`}P{95C_?efN7ZF$=KE7FW zb#|rIG#(D7FF*dlLx+>)f7hw%d#`T;6$tQ)Flsf616iyrY`_2ff|GF7KrVy4`8o2ZG|JbAE z1GEGBy8dgH|2I|1#l+SB&Hrdhfi+Q;0#XCY)!E(P?+h9t^tJR%bpBm}{@gnMsdWIm zjLAn;XsDZ+Ol;@0B>#z|{O4%?xre;cnFNVwh|uTdv!bEpK>y$3@d9oG&Xcp)oRl*&^s3{Z09CMiA% z78+%X{%Tlh&eG+^l+G(fsakyH{k9X-I{yFy1ipj-+SmMLJ!`IkqyNMcv@ZVy3>Bw* zu>d&(kR{cnbHwPa=64Igw#CJfeVKkqr^+n<-ZB5(o3_uBzcSHKw6C_Du=ZX+O1ZMP zy}G$2A|^I8E~0g}onNxWOLNACxt)~gnyHeXr3}SJ)+3}LwHF|x=Zh}+{TmJVrOn=h4J&J0J*Ta zqpteZ*5cOK;>Y62#^BZ7=-;iPs@2={vsk_1S^LJ%_NK4d;M?@0hQdSVa8z{aVO3XA zdGB0*>|Q=!%#oaPE#bgiG`)V*!ji@}Q>wei^3Ivf=HBNyBqN`*tvD}2 zd{|&Z=}}i)Qa)a>Z$UknZ3Kf+t*uQElTxVJ;_8WEOudfjope`Tg8509OunesgpsuM zYyYl?uHoeKA^8LW00930EC2ui03HAy06+-;0RIUbNO0gS0)PrCTqw{2!2=|yw0S^6 z-9G~MxYbag3K}#P-ULoj18#_&4T~1dvZByLh9JP)0U(lN1)Mt$vyBjN=E@ZnSfsF# zAq55u9SoB)Y;u9er9OU&JZLabS2#i@3WA8^0^A90F5;+R!ePR$Snd8qApy&mn{#Do z$PwWM+dw7B8hPNN!APbQFw7tw=wm|`J_S~+$P(iRog+#kOJ;~;9t8>skTjW`)B?ON zC{PS(HAdhhTW{W=slmb#K^5mQ%|NL{d@^K#X8{dgLgOBMlr3LO}fE zCqRrCF0bUnh7nvy@r4;=a3)X$0(`LzP*q4#i!hKW$k_`QT!6$27A|DMDhW`)Ndl{Q zF<6HJHIRxa5D3@81Gxxf4Ky{Z5l={y-Es;I + + + + + + + kAmMa's Forum + + +
+
+
+
+ {{COMMON_HEADER}} +
+ + +
+
+ + + + + + + + + + + + + +
 
+
+
+
+ {{ERROR_BLOCK}} + {{COMMON_FOOTER}} +
+
+
+ + diff --git a/src/main/resources/webapp/member.html b/src/main/resources/webapp/member.html new file mode 100644 index 0000000..52543e4 --- /dev/null +++ b/src/main/resources/webapp/member.html @@ -0,0 +1,214 @@ + + + + + + + + + kAmMa's Forum - Member + + +
+
+
+ {{COMMON_HEADER}} +
+ {{ERROR_BLOCK}} + + + + + + + + +
Member details
+
+
+
+ Account + + + + + +
Username: {{USERNAME}}
Join Date: {{JOIN_DATE}}
+
+
+
+
+ +
+ +
+ + + + + + + +
Change user icon
+
+
+
+ User Icon + + + + + + + + + +
+ Current user icon
+ + +
+ Browse your local disk for user icon
+ +
+
+
+ +
+
+
+
+
+ +
+ +
+ + + + + + + +
Change password
+
+
+
+ Old Password + + + + + +
Please enter your current password.
+
+
+ New Password + + + + + + + + +
Please enter a new password for your account.
New Password:
Confirm New Password:
+
+
+ + +
+
+
+
+
+ +
+ +
+ + + + + + + +
Change personal info
+
+
+
+ Email Address + + + + + + + + +
Please enter a valid email address.
Email Address:
Confirm Email Address:
+
+
+ Other informations + + + + + + +
First Name:
 
Last Name:
 
City:
 
+
+
+ Sounds + + + + +
Sound notifications:  
+
+
+ + +
+
+
+
+
+ +
+ {{COMMON_FOOTER}} +
+
+ + + + diff --git a/src/main/resources/webapp/message.html b/src/main/resources/webapp/message.html new file mode 100644 index 0000000..8e92335 --- /dev/null +++ b/src/main/resources/webapp/message.html @@ -0,0 +1,81 @@ + + + + + + + + + kAmMa's Forum + + +
+
+
+ {{COMMON_HEADER}} +
+ {{ERROR_BLOCK}} + + + + + + + + + + +
Reply to {{TO_USERNAME}}
+
+
+
+
+
Message:
+
+
+
+ +
+
+
+
+ + + + + +
+
+
+
+
+
+
+
+ + + +
 
Private Messages: {{THREAD_TITLE}}
+
+ + + + +
+
+ + + Records per page: + +
+ Rows {{ROWS_FROM}} - {{ROWS_TO}} of {{ROWS_TOTAL}} {{PAGE_LINKS}} +
+
+
+ {{MESSAGE_ROWS}} +
+ {{COMMON_FOOTER}} +
+
+ + diff --git a/src/main/resources/webapp/newpm.html b/src/main/resources/webapp/newpm.html new file mode 100644 index 0000000..3a093a2 --- /dev/null +++ b/src/main/resources/webapp/newpm.html @@ -0,0 +1,64 @@ + + + + + + + + + kAmMa's Forum + + +
+
+
+ {{COMMON_HEADER}} +
+ {{ERROR_BLOCK}} + + + + + + + +
+
 
+ Private Message to : {{TO_USERNAME}} +
+ +
+ + + + + + + +
+
+
+ + + + + + + + +
Title:
  
+
Message text:
+
+ +
+
+
+
+
+
+
+ {{COMMON_FOOTER}} +
+
+ + diff --git a/src/main/resources/webapp/newthread.html b/src/main/resources/webapp/newthread.html new file mode 100644 index 0000000..a3d7394 --- /dev/null +++ b/src/main/resources/webapp/newthread.html @@ -0,0 +1,62 @@ + + + + + + + + + kAmMa's Forum - New Thread + + +
+
+
+ {{COMMON_HEADER}} +
+ {{ERROR_BLOCK}} + +
+ + + + + + + + + +
Post New Thread
+
+
+
Logged in as {{USERNAME}}
+ + + + + + + + +
Title:
  
+
Description:
+
+ +
+
+ Thread password +
+
+
+
+
+ +
+
+
+
+ {{COMMON_FOOTER}} +
+
+ + diff --git a/src/main/resources/webapp/notif.wav b/src/main/resources/webapp/notif.wav new file mode 100644 index 0000000000000000000000000000000000000000..3756db587685a995d2bd071925ea90cb53cc1ded GIT binary patch literal 37804 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 MIIYTX|J~;PA3aP-W&i*H literal 0 HcmV?d00001 diff --git a/src/main/resources/webapp/private.html b/src/main/resources/webapp/private.html new file mode 100644 index 0000000..e931bc9 --- /dev/null +++ b/src/main/resources/webapp/private.html @@ -0,0 +1,109 @@ + + + + + + + + + kAmMa's Forum + + +
+
+
+ {{COMMON_HEADER}} +
+
+ + + + +
+
+ + + Records per page: + +
+ Rows {{ROWS_FROM}} - {{ROWS_TO}} of {{ROWS_TOTAL}} {{PAGE_LINKS}} +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + {{THREAD_ROWS}} + + + + + + +
+ + + +
Private Messages
+
+ +
Title / Sender -> Recipient
+
+
+ Selected Messages: + + +
+
+
+
+ {{COMMON_FOOTER}} +
+
+ + + + diff --git a/target/classes/app.properties b/target/classes/app.properties new file mode 100644 index 0000000..02d181a --- /dev/null +++ b/target/classes/app.properties @@ -0,0 +1,16 @@ +# Database Configuration +app.db.url=jdbc:mariadb://server01:3306/fabkovachata?useUnicode=true&characterEncoding=UTF-8 +app.db.user=fabkovachata +app.db.password=Fch621420+ +app.db.mysql_admin_url=jdbc:mariadb://server01:3306/?useUnicode=true&characterEncoding=UTF-8 + +# Server Configuration +app.server.port=8080 +app.server.threads=10 + +# Session Configuration +app.session.timeout.minutes=30 + +# Multipart Configuration +app.multipart.max_bytes=52428800 +app.multipart.icon_max_bytes=1048576 diff --git a/target/classes/cz/kamma/fabka/httpserver/AppConfig.class b/target/classes/cz/kamma/fabka/httpserver/AppConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..3c5853b08fc2fada720823f3cee4f7d9b3cbfa25 GIT binary patch literal 2654 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 + + + + diff --git a/target/classes/webapp/client.js b/target/classes/webapp/client.js new file mode 100644 index 0000000..a8b0ada --- /dev/null +++ b/target/classes/webapp/client.js @@ -0,0 +1,310 @@ +var URLsuffix = '/process/ajaxreq.jsp'; +//var URLsuffix = '/FabkovaChata/process/ajaxreq.jsp'; + +var ajaxHttp = getAjaxLibrary(); + +function getAjaxLibrary() { + var activexmodes = [ "Msxml2.XMLHTTP", "Microsoft.XMLHTTP" ] // activeX + // versions + // to check + // for in IE + if (window.ActiveXObject) { // Test for support for ActiveXObject in IE + // first (as XMLHttpRequest in IE7 is broken) + for ( var i = 0; i < activexmodes.length; i++) { + try { + return new ActiveXObject(activexmodes[i]) + } catch (e) { + // suppress error + } + } + } else if (window.XMLHttpRequest) // if Mozilla, Safari etc + return new XMLHttpRequest() + else + return false +} + +/******************************************************************************* + * F U N C T I O N S + ******************************************************************************/ + +function getHost() { + var url = ""+window.location; + var prot = url.split('//')[0]; + var urlparts = url.split('//')[1].split('/'); + return urlparts[0]; +} + +function getProt() { + var url = ""+window.location; + return url.split('//')[0]; +} + +function ajaxSendRequest(data, onReadyStateChange) +{ + var URL = getProt()+'//'+getHost()+URLsuffix; + //if (ajaxHttp.overrideMimeType) + //ajaxHttp.overrideMimeType('text/xml') + ajaxHttp.open("POST", URL , true); + ajaxHttp.onreadystatechange = onReadyStateChange; + ajaxHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + ajaxHttp.send(data); +} +function ajaxResponseReady() +{ + if (ajaxHttp.readyState == 4) { + /* received */ + if (ajaxHttp.status == 200 || window.location.href.indexOf("http")==-1) { + /* response is ok */ + return true; + } else { + return false; + } + } + return false; +} + +function ajaxGetXmlResponse() { + if (ajaxHttp.responseXML && ajaxHttp.responseXML.parseError && (ajaxHttp.responseXML.parseError.errorCode !=0)) { + ajaxGetXmlError(true); + return null; + } else { + return ajaxHttp.responseXML; + } + } + +function ajaxGetXmlError(withalert) { + if (ajaxHttp.responseXML.parseError.errorCode !=0 ) { + line = ajaxHttp.responseXML.parseError.line; + pos = ajaxHttp.responseXML.parseError.linepos; + error = ajaxHttp.responseXML.parseError.reason; + error = error + "Contact the support ! and send the following informations: error is line " + line + " position " + pos; + error = error + " >>" + ajaxHttp.responseXML.parseError.srcText.substring(pos); + error = error + "GLOBAL:" + ajaxHttp.responseText; + if (withalert) + alert(error); + return error; + } else { + return ""; + } + } + +function convertLinks(text) +{ + var myArray = text.split(" "); + + for (var i = 0 ; i < myArray.length ; i++) { + var part = myArray[i]; + if (part.indexOf("http://")==0) { + text = text.replace(part, '' + part + ''); + } + } + return text; +} + + +/******************************************************************************* + * ajax call functions + */ +function AX_voting(id, yes_no) +{ + var data = 'ajaxMethod=ajaxVoting_'+yes_no +'&universalId='+id; + ajaxSendRequest(data, AX_votingResponse); +} + +function AX_chat_voting(id, yes_no) +{ + var data = 'ajaxMethod=ajaxChatVoting_'+yes_no +'&universalId='+id; + ajaxSendRequest(data, AX_chatVotingResponse); +} + +function AX_sendChatMessage(text, chatid) +{ + var data = 'ajaxMethod=chat_text_' + text +'&universalId='+chatid; + ajaxSendRequest(data, AX_asynchUpdateResponse); +} + +function AX_asynchUpdate() +{ + var data = 'ajaxMethod=asynchUpdate'; + ajaxSendRequest(data, AX_asynchUpdateResponse); +} + +function AX_asynchUpdateChat() +{ + var data = 'ajaxMethod=asynchUpdateChat'; + ajaxSendRequest(data, AX_asynchUpdateResponse); +} + +function AX_firstUpdateChat() +{ + var data = 'ajaxMethod=firstUpdateChat'; + ajaxSendRequest(data, AX_asynchUpdateResponse); +} +/******************************************************************************* + * response functions + */ +function AX_votingResponse() +{ + if (ajaxResponseReady()) { + if (ajaxHttp.responseText.indexOf('invalid') == -1) { + var xmlDocument = ajaxGetXmlResponse(); + var id = xmlDocument.getElementsByTagName("id")[0].firstChild.data; + var yes = xmlDocument.getElementsByTagName("yes")[0].firstChild.data; + var no = xmlDocument.getElementsByTagName("no")[0].firstChild.data; + document.getElementById('yes' + id).innerHTML = yes; + document.getElementById('no' + id).innerHTML = no; + } + } +} + +function AX_chatVotingResponse() +{ + if (ajaxResponseReady()) { + if (ajaxHttp.responseText.indexOf('invalid') == -1) { + var xmlDocument = ajaxGetXmlResponse(); + var id = xmlDocument.getElementsByTagName("id")[0].firstChild.data; + var thumbup = xmlDocument.getElementsByTagName("thumbup")[0].firstChild; + var thumbdown = xmlDocument.getElementsByTagName("thumbdown")[0].firstChild; + + var yesVotes = 0; + var noVotes = 0; + var yesNames = ''; + var noNames = ''; + + if (thumbup!==null && thumbup.data!==null) { + yesNames = thumbup.data; + yesVotes = yesNames.split(',').length; + } + + if (thumbdown!==null && thumbdown.data!==null) { + noNames = thumbdown.data; + noVotes = noNames.split(',').length; + } + + document.getElementById('yes' + id).innerHTML = yesVotes; + document.getElementById('no' + id).innerHTML = noVotes; + document.getElementById('yes' + id).title = yesNames; + document.getElementById('no' + id).title = noNames; + } + } +} + +function AX_asynchUpdateResponse() +{ + if (ajaxResponseReady()) { + if (ajaxHttp.responseText.indexOf('invalid') == -1) { + var xmlDocument = ajaxGetXmlResponse(); + + var userNames = xmlDocument.getElementsByTagName("userName"); + var userIds = xmlDocument.getElementsByTagName("userId"); + var inactives = xmlDocument.getElementsByTagName("inactive"); + var inChat = xmlDocument.getElementsByTagName("inChat"); + + var resStr = ""; + + for (var i=0; i <= (userNames.length-1); i++) + { + if (inChat[i].firstChild.data=='true') + resStr = resStr + "" + userNames[i].firstChild.data + " [Chat] (" + inactives[i].firstChild.data + "), "; + else + resStr = resStr + "" + userNames[i].firstChild.data + " (" + inactives[i].firstChild.data + "), "; + } + + document.getElementById('userCount').innerHTML = ""+userNames.length; + document.getElementById('userList').innerHTML = resStr.substring(0, resStr.length-2); + + var fromNames = xmlDocument.getElementsByTagName("fromName"); + var chatIds = xmlDocument.getElementsByTagName("chatId"); + var newMess = xmlDocument.getElementsByTagName("newMess"); + var times = xmlDocument.getElementsByTagName("time"); + var texts = xmlDocument.getElementsByTagName("text"); + var thumbup = xmlDocument.getElementsByTagName("thumbup"); + var thumbdown = xmlDocument.getElementsByTagName("thumbdown"); + + var resStr = ""; + + var chatNode = document.getElementById('main_chat'); + var newdiv = ''; + + for (var i=0; i <= (chatIds.length-1); i++) + { + var divIdName = 'chatId_'+chatIds[i].firstChild.data; + var color = 'color:#FF0000'; + if (newMess[i].firstChild.data=='1') + color = 'color:#FF0000'; + else if (newMess[i].firstChild.data=='2') + color = 'color:#0000FF'; + else + color = 'color:#000000'; + /* + var yesVotes = 0; + var noVotes = 0; + var yesNames = ' '; + var noNames = ' '; + + if (thumbup[i].firstChild!==null) { + yesNames = thumbup[i].firstChild.data; + yesVotes = yesNames.split(',').length; + } + + if (thumbdown[i].firstChild!==null) { + noNames = thumbdown[i].firstChild.data; + noVotes = noNames.split(',').length; + } + + var chatVotingLine = ' '+yesVotes+''+noVotes+''; + */ + var chatVotingLine = ''; + newdiv += '
['+times[i].firstChild.data+chatVotingLine+'] - '+fromNames[i].firstChild.data+': '+convertLinks(texts[i].firstChild.data)+'
'; + } + + if (chatIds.length>0) + chatNode.innerHTML = newdiv; + + var newTitle = ''; + var fids = xmlDocument.getElementsByTagName("forumId"); + var messCounts = xmlDocument.getElementsByTagName("messageCount"); + if (messCounts.length>0 || document.title.indexOf('(1+)')>-1) { + newTitle += '(1+)'; + } + + var newChatMessages = xmlDocument.getElementsByTagName("newChatMessages"); + if (newChatMessages.length>0 || document.title.indexOf('(CHAT+)')>-1) { + newTitle += '(CHAT+)'; + } + + var newPMs = xmlDocument.getElementsByTagName("newPMCount"); + if (newPMs.length>0) { + document.getElementById('newPM').innerHTML = " You have unread Private Message"; + if (document.title.indexOf('(PM+)')<0) { + newTitle += '(PM+)'; + } + } + + if (newTitle.length!='') { + document.title = newTitle + " kAmMa's Forum"; + } + + var resStr = ''; + for (var i=0; i <= (fids.length-1); i++) + { + resStr = " "+messCounts[i].firstChild.data+" new message(s)"; + if (document.getElementById('newCount'+fids[i].firstChild.data)!=null) { + document.getElementById('newCount'+fids[i].firstChild.data).innerHTML = resStr; + } + } + + var playNotification = xmlDocument.getElementsByTagName("playNotification"); + if (playNotification.length>0 && playNotification[0].firstChild.data=='1' && document.getElementById('soundon')!=null) { + var embed=document.createElement('object'); + embed.setAttribute('type','audio/wav'); + embed.setAttribute('data', 'notif.wav'); + embed.setAttribute('autostart', true); + document.getElementsByTagName('body')[0].appendChild(embed); + } + } + } +} +/* + * END of response functions + */ \ No newline at end of file diff --git a/target/classes/webapp/css/all.css b/target/classes/webapp/css/all.css new file mode 100644 index 0000000..9d5d622 --- /dev/null +++ b/target/classes/webapp/css/all.css @@ -0,0 +1,453 @@ +body +{ + background: #FFFFCC; + color: #000000; + font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; + margin: 5px 10px 10px 10px; + padding: 0px; +} +.voting-buttons +{ + position: relative; + bottom: 1px; +} + +.voting-buttons a +{ + float: none !important; + padding-bottom: 0px !important; + padding-top: 0px !important; +} +a.voting_yes:link, a.voting_yes:visited { + color: #3C922F; + font-weight: bold; + background: url(../images/voting_yes.png) green no-repeat; + border: 1px outset #3C922F; + padding: 2px 4px 2px 20px; + white-space: nowrap; + float: left; + line-height: 10px; + text-decoration: none; +} +a.voting_no:link, a.voting_no:visited { + color: #AE3738; + font-weight: bold; + background: url(../images/voting_no.png) red no-repeat; + border: 1px outset #AE3738; + padding: 2px 4px 2px 20px; + white-space: nowrap; + float: left; + line-height: 10px; + text-decoration: none; +} +a:link, body_alink +{ + color: #0000C0; + text-decoration: none; +} +a:visited, body_avisited +{ + color: #0000C0; + text-decoration: none; +} +a:hover, a:active, body_ahover +{ + color: #663333; + text-decoration: none; +} +.page +{ + background: #FFFFF1; + color: #000000; +} +.page a:link, .page_alink +{ + color: #0000C0; +} +.page a:visited, .page_avisited +{ + color: #0000C0; +} +.page a:hover, .page a:active, .page_ahover +{ + color: #663333; +} +td, th, p, li +{ + font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.tborder +{ + background: #FFFFCC; + color: #000000; + border: 1px solid #0B198C; +} +.tcat +{ + background: #FFFFCC url(../images/cat-line.gif) repeat-x top left; + color: #000000; + font: bold 9pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.tcat a:link, .tcat_alink +{ + color: #0000C0; + text-decoration: none; +} +.tcat a:visited, .tcat_avisited +{ + color: #0000C0; + text-decoration: none; +} +.tcat a:hover, .tcat a:active, .tcat_ahover +{ + color: #663333; + text-decoration: none; +} +.thead +{ + background: #663333 url(../images/cellpic-fp-big.gif) repeat-x top left; + color: #FFFFF1; + font: bold 11px tahoma, verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.thead a:link, .thead_alink +{ + color: #FFFFF1; +} +.thead a:visited, .thead_avisited +{ + color: #FFFFF1; +} +.thead +{ + background: #663333 url(../images/cellpic-fp-big.gif) repeat-x top left; + color: #FFFFF1; + font: bold 11px tahoma, verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.thead a:hover, .thead a:active, .thead_ahover +{ + color: #FFFFCC; +} +.tfoot +{ + background: #663333 url(../images/cellpic-fp.gif) repeat-x top left; + color: #000000; +} +.tfoot a:link, .tfoot_alink +{ + color: #FFFFF1; +} +.tfoot a:visited, .tfoot_avisited +{ + color: #FFFFF1; +} +.tfoot a:hover, .tfoot a:active, .tfoot_ahover +{ + color: #FFFFCC; + text-decoration: underline; +} +.alt1, .alt1Active +{ + background: #FFFFCC; + color: #000000; + font-size: 9pt; +} +.alt2, .alt2Active +{ + background: #FFFF99; + color: #000000; +} +.inlinemod +{ + background: #FFFFCC; + color: #000000; +} +.wysiwyg +{ + background: #FFFFCC; + color: #000000; + font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; + margin: 5px 10px 10px 10px; + padding: 0px; +} +textarea, .bginput +{ + background: #FFFFCC; + font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.bginput option, .bginput optgroup +{ + font-size: 10pt; + font-family: verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.button +{ + background: #FFFFF1; + color: #000000; + font: 11px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +select +{ + background: #FFFFCC; + font: 11px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +option, optgroup +{ + font-size: 11px; + font-family: verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.smallfont +{ + font: 11px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.time +{ + color: #000000; +} +.navbar +{ + color: #0000C0; + font: 11px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.highlight +{ + font-weight: bold; +} +.fjsel +{ + background: #FFFFCC; + color: #000000; +} +.fjdpth0 +{ + background: #FFFFF1; + color: #000000; +} +.panel +{ + background: #FFFFF1; + color: #000000; + padding: 10px; + border: 2px outset; +} +.panelsurround +{ + background: #FFFFCC; + color: #000000; +} +legend +{ + background: transparent; + color: #0000C0; + font: 11px tahoma, verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.vbmenu_control +{ + background: #663333 url(../images/cellpic-fp.gif) repeat-x top left; + color: #FFFFF1; + font: bold 11px tahoma, verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; + padding: 3px 6px 3px 6px; + white-space: nowrap; +} +.vbmenu_control a:link, .vbmenu_control_alink +{ + color: #FFFFFF; + text-decoration: none; +} +.vbmenu_control a:visited, .vbmenu_control_avisited +{ + color: #FFFFFF; + text-decoration: none; +} +.vbmenu_control a:hover, .vbmenu_control a:active, .vbmenu_control_ahover +{ + color: #FFFFFF; + text-decoration: underline; +} +.vbmenu_popup +{ + background: #FFFFFF; + color: #000000; + border: 1px solid #0B198C; +} +.vbmenu_option +{ + background: #FFFFF1; + color: #000000; + font: 11px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; + white-space: nowrap; + cursor: pointer; +} +.vbmenu_option a:link, .vbmenu_option_alink +{ + color: #000000; + text-decoration: none; +} +.vbmenu_option a:visited, .vbmenu_option_avisited +{ + color: #330000; + text-decoration: none; +} +.vbmenu_option a:hover, .vbmenu_option a:active, .vbmenu_option_ahover +{ + color: #330000; + text-decoration: none; +} +.vbmenu_hilite +{ + background: #FFFFCC; + color: #330000; + font: 11px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; + white-space: nowrap; + cursor: pointer; +} +.vbmenu_hilite a:link, .vbmenu_hilite_alink +{ + color: #330000; + text-decoration: none; +} +.vbmenu_hilite a:visited, .vbmenu_hilite_avisited +{ + color: #330000; + text-decoration: none; +} +.vbmenu_hilite a:hover, .vbmenu_hilite a:active, .vbmenu_hilite_ahover +{ + color: #330000; + text-decoration: none; +} +/* ***** styling for 'big' usernames on postbit etc. ***** */ +.bigusername { font-size: 10pt; text-decoration: none;} + +/* ***** small padding on 'thead' elements ***** */ +td.thead, th.thead, div.thead { padding: 4px; } + +/* ***** basic styles for multi-page nav elements */ +.pagenav a { text-decoration: none; } +.pagenav td { padding: 2px 4px 2px 4px; } + +/* ***** de-emphasized text */ +.shade, a.shade:link, a.shade:visited { color: #777777; text-decoration: none; } +a.shade:active, a.shade:hover { color: #FF4400; text-decoration: underline; } +.tcat .shade, .thead .shade, .tfoot .shade { color: #DDDDDD; } + +/* ***** define margin and font-size for elements inside panels ***** */ +.fieldset { margin-bottom: 6px; } +.fieldset, .fieldset td, .fieldset p, .fieldset li { font-size: 11px; } + +/* vbPortal Extras */ +.urlrow, .textrow, .blockform, .boxform, .loginform { + margin: 0px; + font-family: verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif; +} +.textrow, .blockform, .boxform, .loginform { + font-size: 10px; +} +.urlrow { + font-size: 11px; +} +.textrow, .urlrow { + padding: 2px 2px; +} +.blockform, .loginform { + padding: 0px; +} +.boxform { + padding: 2px; +} +.gogif { + padding: 0px 3px 0px 3px; + margin: 0px; +} + +/* *****thead2 for calendar made by flar ***** */ +.thead2 {url: images/gradients/cellpic-fp-big.gif repeat-x top left; } + +/* *****alt3 alternating color 3 made by flar ***** */ +.alt3 {background-color:#FFFFF1;} + +.nodisplay { + display: none; +} + +#autosearch{ +float:left; +width:205px; +margin:5px 0 0 0; +} +#menucontainer{ +float:left; +position:relative; +width:250px; +height:104px; +} +#results { +} +#results ul { + z-index:10; + position: absolute; + top: 94px; + left: 0px; + border: 1px solid #bfbfbf; + list-style: none; + width: 208px; + display:block; + margin:0; + padding:0; +} +#results ul li { +position:relative; +margin:0; +padding:0; +width:198px; +} +#results ul li a{ + display: block; + color: #444; + background: #fff; + text-decoration: none; + padding: 1px 4px 2px 6px; + width:198px; +} +* html #results ul li a { + margin:0; + padding:0; + display:block; +} +#results ul li a strong { + color: #000; +} +#results ul li a:hover, #results ul li a.hover { + background: #0056f4; + color: #fff; +} +#results ul li a:hover strong, #results ul li a.hover strong { + color: #fff; +} + +input#s{ +margin-top:4px; +width:205px; +font: 12px/12px Verdana, sans-serif; +color:#666666; +padding:3px 5px; +} + +.xdaclear{ +clear:both; +overflow:hidden; + +} + +ul.menu {list-style:none; margin:0; padding:0;} +ul.menu * {margin:0; padding:0} +ul.menu a {display:block; color:#000; text-decoration:none} +ul.menu li {position:relative; float:left; margin-right:2px;font-size:11px;} +ul.menu ul {position:absolute; top:26px; left:0; display:none; opacity:0; list-style:none;width:230px;} +ul.menu ul li {position:relative; border:1px solid #aaa; border-top:none; width:230px; margin:0} +ul.menu ul li a {display:block; padding:3px 5px 3px 12px; background-color:#ffffff} +ul.menu ul li a:hover {background-color:#c5c5c5} +ul.menu ul ul {left:-230px; top:-1px} +ul.menu .menulink {border:1px solid #aaa; padding:5px 5px 5px 5px; font-weight:bold; background:url(/images/header.gif); width:230px} +ul.menu .menulink:hover, ul.menu .menuhover {background:url(/images/header_over.gif)} +ul.menu .sub {background:#fff url(/images/arrow.gif) 4px no-repeat} +ul.menu .topline {border-top:1px solid #aaa} \ No newline at end of file diff --git a/target/classes/webapp/favicon.ico b/target/classes/webapp/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1442c75e932f089b8cb6c8388c978f2530183442 GIT binary patch literal 630 zcmbVGu@S;B3=|wSGf*-IJ!{a?rS=-BJcx_9V5Z!06yo)Y9Tjjncw35I|B`dL4tpU6 zPL<~5Oli2JPkqTn#j!%xf%NW2+^NFfj)C)?z9`ImeT%| W^i|5_2A?J86U!3w!{BOLywW#=9Or5P literal 0 HcmV?d00001 diff --git a/target/classes/webapp/forum.html b/target/classes/webapp/forum.html new file mode 100644 index 0000000..d865182 --- /dev/null +++ b/target/classes/webapp/forum.html @@ -0,0 +1,43 @@ + + + + + + + + kAmMa's Forum + + +
+
+
+ {{COMMON_HEADER}} +
+
+ + + + +
+
+ Search in threads: + +
+
+
+ + + + + + + + {{FORUM_ROWS}} + +
ThreadLast PostPosts
+
+ {{COMMON_FOOTER}} +
+
+ + diff --git a/target/classes/webapp/forumdisplay.html b/target/classes/webapp/forumdisplay.html new file mode 100644 index 0000000..6e3ff22 --- /dev/null +++ b/target/classes/webapp/forumdisplay.html @@ -0,0 +1,124 @@ + + + + + + + + kAmMa's Forum - {{FORUM_NAME}} + + +
+
+
+ {{COMMON_HEADER}} +
+ + + + + + +
{{FORUM_NAME}}
{{FORUM_DESCRIPTION}}
+
+ + +
+ + + + + + + + + + + +
Reply
+
+ {{QUOTE_BLOCK}} +
+
Message:
+ +
+ +
+
+ + +
+
+
+
+ {{COUNTDOWN_BLOCK}} + +
+ + + + + + + + + {{PAGE_TDS}} + +
+
+ + + + + + + + Search in thread: + +
+
+ Show type: + + + Show images: + + + Sort messages by: + + + + Records per page: + + Rows {{ROWS_FROM}} - {{ROWS_TO}} of {{ROWS_TOTAL}}
+
+
+ + {{MESSAGE_ROWS}} +
+ + {{COMMON_FOOTER}} +
+
+ + diff --git a/target/classes/webapp/images/busy.gif b/target/classes/webapp/images/busy.gif new file mode 100644 index 0000000000000000000000000000000000000000..80ff48b3e25494afa91354054fa41dac76157b54 GIT binary patch literal 722 zcmZ?wbhEHb6krfwSj5g?mbPCd^-5UfuE~p!l=ki4xbx)R{fGbm|F8I;+s`#5*x50_ z)kx2PnUR5kLGdRGD+2>NgAR}l)GWZj!0cSHi~Ya^OYv}yTsg%Ddv6{qy>`4OMY6Cb zF@)PuW*ztGqQ7B3at^acW*?W}QP{QU;|>oou6EVUKdbB(U+xg)iiuI?#AdF2#V++j zKy$?za^;+m%=HlnIcQtyIsXD^TQxFD(21$ zLNc#MA*4GI**sFRz^9xB8kF6RMGX;NY~W*cE;>z*79YM6O)& zHAHZ7IfnGSKD5^7y16n(pn%S9*~8C+St=P0H2yGFntUpWWz`;4MXccnbYuE4pc}(A zbFUTW--}^4x>vz-Sb{};R@m!T`vT7_(%LQH!onaR*Z=XiVT*0!g8&w}$)}bU3dhdk zJB8{-CZG#J!Dv^JwX9BY&*2;0AFn>!aOKWdO;0$usIhT)29@x7kqji1q0P;u%*&$5$(`5Nw#v$& z000000000000000A^8LV00000EC2ui01W^f000GZU$Kc8?�Uh&<1vZ3I4^>y)QJFG5LBuaz@2cr--&oUk&wlbw0f;}xZm)1fk2 i0RsaRuduGJuoSkpxVI=KE+ZK#FD)l9!8FDjApko`4~u*N literal 0 HcmV?d00001 diff --git a/target/classes/webapp/images/cellpic-fp-big.gif b/target/classes/webapp/images/cellpic-fp-big.gif new file mode 100644 index 0000000000000000000000000000000000000000..ed29c7a49676ad1a79759a5ba2a8b94edc59889b GIT binary patch literal 315 zcmZ?wbhEHbWMi;oxXQr5&d&b-|NmT1g#sU?iXe@uFrDgfz1k?l+9;#CXw!x`%f$oA ziZ7NcqRelsNk2WeYX6iCJDl{g&t>#gtgVL5MuyVJCY^XXl)j57H3K6Iz8)4ajmVdHc#yyl<0LX$#9Yg>Dm HBZD;nHaASt literal 0 HcmV?d00001 diff --git a/target/classes/webapp/images/cernypetr.jpg b/target/classes/webapp/images/cernypetr.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c8146864e6fa90d216eb94418d41da6bfd3c478c GIT binary patch literal 9934 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 cjy(40qyhK*GW@k^}G^2KoHL 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 literal 0 HcmV?d00001 diff --git a/target/classes/webapp/images/pm.gif b/target/classes/webapp/images/pm.gif new file mode 100644 index 0000000000000000000000000000000000000000..444825ce67cad3a04e356b222fd90edac816bcc5 GIT binary patch literal 1026 zcmeH`=}*!D9L9f?GcB90)@8oHY0H{h*XFF=ICE-Cr}I^7bITXjR%^36(Iy#^SBS|_ z4&^654iN<75S1cD#Vb(oNXr{F6i`G_@#vfOcl5k_-hB4#vpsRQVjRbs1bV150IwZv_Dx0;&3(y-sbGn?c?(_t^wPql@KDP$B~oXO^K}rE?mq)J~^!n^^5+Yd|#uGBcQe zSOr}mR`${A-TLpd!@r!U*7HFOy#tO?>0Hyd!E*JESlP3%xKY~!${`^0*D4x!3$US) zFtG)Orvry1p4! zTDmQ7*}r>D^Y1u|JUxt0yZDXseElCLrYWp(Qe|7LrUhx=PPrN~84KcGctab-bU+rY zldv@+cS~-9rR6P}VwYO)sp$f=Wp5;qxeJ138;S2O7CBYg@dDn=>guk<0A&go7+IpL zK-3F@c9*alC^8_hmY-7wR5}|8bM+V}>8c&6*6MZx@<0C!1be)22qHwn_uKv_qI@Qi zB;TO;`YF)~vLuE%ZS_LDp5impLgo#)l&l2R-L`6D$<0TvzGS4dCZ4^DqrJ$@=*T;N zFfiidb+3p0=iWyiT2E8%(Sm$pA~#77%t!r$8;F-nc}T^@Q{mn5$v#Pb-WN_9s-r_c zQ2f{@sWrj32ahKbp4Fy4rp98v3B@#~D=v={mTD|UlSOg4{@#K2&)~z#g09_(ePYds zI)FQZ%T7M56Sfk=)1$8-=@uVcLimBFt$2JA^U^&#{xF8dAYzt~cRz^VX zaizXNt+2s>!ssinWl&1#1+=vEfF88v(f8WYgSND^^cWO=x_`p&ANZ0l`98@#nf+ct zDwGOEFbDwPoUV%M?hyu+-R8#m&3vJBp4CL~Ta4q?7Tg{VM?#@py?SZQwXrh(3$Eq{ zl$3z7o!~d({M!i1hRZ+KAUC#_`oRhsUg!)h+*wp<;0{_xC)MTj2K>R0W{^9|17LUC zz>rTXWHN=z;jXW^Y*Vtf05Gu27=K-j>bgAgyQ|m|%AoHo(O8`&cFgFO|6>uhMKx6S zsx=Y=PMbq6*K*J?@yx7Ls%EjdkF@Q26Ylf+G0fppcAKZFJ)R)8Vh#nvriq#n76+^# zPRIK6K>4ClCzB{9czgyE6>vl<<$_ryFlz*LO|85!A$L;j6#eNGlid=^wsFYeSdRhV zlC=4JQMb#}(blnLK*sO>%wn@`Ru`^U?id*pcg}&85wnO&zEug92Sz!Q{ct&Rgat5$ z4bwbepcaElfi~KfH9D1Hb8~l?$s~9;L+U24H-w<9>ay>;8!lO_RvTur;MRpjQ$#oT zV0a{;Xxdq35&Ai|j5g3-qGeuN_XK_3O}AwDd8#I&>E(^j z1=jr{-t5fOY*5{!(-{3~x=}5*iYRJ!9VWf+lu!`_H4EzPqK3)gwhe!jT2lbb=&H@V z;SYo(VPKsA%M4oW1-pbs{xK&O+xE-rfvNZJs;^`EP7d`7u&{r+avop^cc{VpU*cfQ zWH9-ZJ?s%?JB13cK{0P$t5^^TRnGZ7k7sidr4#rnr|Y^!!Su;ncMSB=hvOE0m1DL; z%O>NzKXiHvgMkFpa6r+uCb{jHsRyfE>9l;f?Xq=QXXMs|LIC<-c=-`&4+LS*;m5Ye zPaJ;|KobcWMaeU7#vLG~c&d|gQ%-+->Hu63cWNMM5B1~UTj=xYsRgCk&zELOYM(lE zn7nq5bmE0~`tl2op?fRI&+e~znjTmD6`EM+_Y(z$&p=RP{LwRo^>taFp2;bc-O%h$ z%(_08*F{`thI6FFMfkgQ#<#zT;}u=(CY^mFTYe}d^M10hyQG1Wpe8Dt`+vx5mHcKU z=J+q()WOYZMd{C-%{U3cHv1P3^Eq@VJMC)4t%LAT7DP^>WImxfIQ0Fs1{g^z<-Fgz z@zUwO4-#lqANADE!ymGcVx;uQ@r-?Y%UW=oWAR9C3+dPH*G?!?j%uA_iGsPj>hpq2 d)9L-8S7lIq^Ov8!75Dn=EBQ(L;z`i4e*rk9eIWn< literal 0 HcmV?d00001 diff --git a/target/classes/webapp/images/post_old.gif b/target/classes/webapp/images/post_old.gif new file mode 100644 index 0000000000000000000000000000000000000000..b3097c646c07cf39e709d0cfa33a7e83c29aec8c GIT binary patch literal 522 zcmZ?wbhEHb*1cu?=HOjfBwb) zhTba$^%M78dRN*q^W5|QCm#H-?p}ED_5a}5u9m5*rmeVI(mW%-cIt|~A2f}f^Xn%1 zM;329{$kmV*N1Qa-*w@4NJ7iTNnyqeynJ0DD5dcS?@sn)5dcU^qD z`_emSpNd@<-<^K?-_@ts+9}&DAh)1l@~J2PE4${YXgQ~sO^DCwTDtRod~ThiXPLT@ z`;E{4b z*33?geKos+6ATWDN}stE;Qg(b44OoWP^6-I3EV#J1#>T>41ejEx5=2pB&-2!}fX z6cmAge|-f7C61zvC8ee%4XF(sV<-nPmzkO&nSd`i1r4L3ryCurz^M`v9%P6KJPOLR z6d?+k2^zYI1g4Ht5)C&iH^b^2#)lOd%JIsXG#L@o2Lu}c03G5h z0LpdPP!0h<7$+p~1L2@T2YvunT(Dr_0y_+e5@hfpFCLZ(_(aIap#lI16(cZIkl?QZ z0RR(h3}8^90)rj`asW7?0)vbH2o)|=n9=S~i5VF(;3|-yfC4NQ8khorAm|1L3sOe7 z;4cCK7z-Ahz~G_61_uHLiylB#0m7#O8gwWqkRhrn7!xX3kb!jsfCUu> zfHnBGr-uj|1_&_dK*S6VZzDtqP}G!!3lYLD(1>G40(C5@KE(P`#6AoYA^_N6LWT<) z3kF~yp;Ls34JsCB-c06{s$mo>&WI*h-GT`qWL$kK0)zn)E?fvu;Q~Yt61Y8pm>|Fe z1BM=ihvtkS9Wd0$D=ZLzc;W@2^F=VQp;>?p8v+D4-+usx>1p^4@bv4FN#l;)^iG_~HjNI^Y5iH0G$| Yjy&?n!x%%1LC26p7CEFHKLh~)I|>+1H~;_u literal 0 HcmV?d00001 diff --git a/target/classes/webapp/images/reply.gif b/target/classes/webapp/images/reply.gif new file mode 100644 index 0000000000000000000000000000000000000000..9090957904c850b1138dca21574fd232eb8211d7 GIT binary patch literal 2194 zcmV;D2yORANk%w1VMYKJ0Qdg@b8~fRc8mPbsXbA3zQM|wnVg7>mTiHg%czNTf}nGh z&0B1Wft<;QgodT1s773YbcwBbl*Mj?sB?<5U3jQka-ET#wBEa%sj|XYZIRj9-R0)# zHArfKji=t>@6yuJXMLl3o7BtC-f4cJbd9-#k*kZS+<1P8ld$A#d6Bo6eOz&szoUuI z(A;~8q>h}myPJo7naYr^okUrFV0M|7o~+{J@tvizw6?pdvA&_c z@MwFR$;!}FV|~lb&zYX1*4pC5#@68BD z(A3P$(|M7;p1SPM&)1Ty*?owdcZ;vFv$bu7v59wZi<-2N0gm8V8bd}IwbeMaP zwbs|%^!E10$kXoa?xl%$>BF5$WRixY+GT#IV{nAh)ZWd{(7nCIqN=|A{r!)bt=-_? zikY+e`ujjtd9b#^y1vF*O-jbb&tY&Ncc%0_w_2lOB#>viOcbR8=pLmbBivFO%S~Q<(bM0~ua?!;*7Nf7^Y!+6 zlfS#W!RP7rA^8LW00930EC2ui07d{706+-;0RIVWkz|mDFkyt$!-fq~8Xk@s_3`zO z7l9>IT6o|ku%pM1AVZ22SpiI?47iSnnF9n!DJo@f>02YNjoD+GXK3UlP;)R9Yz=Hz^1&R%E;>L>yw=k{= zhHV}Jtq^t~R_RC^uB-w=r9lG&LiRoX3xGzBc{yl0-0e~2?w4C(aGeOEE-@}63l>1$ z5kE%`T+tY_0WMW=AqNytpdkSWE8Gx-Kq4%_1P71YWyc2^G-6IF9CR4Z05^pAz=$N4 z_&|s#qG-qfhP3FSivtYsVT?4!hy@lL)bi0cjo{KiBMvy=4`O#cL&YzUB!YlDDumO( zD;|s_<&;zsam^_U;F1X>uP6cp0rr%#N-0+W00RpE1W}7HYQ&(2DsY-G!<;h6fI^*k z+Ii=lc+P2pDthRlgeM`ypa3JwB*V!m*Oc(jHWlol#tIce@<%9b)G&tz4!|HlK9i)f zhy=q}VFeWG;gZ7}M%*>R1S44gfB^!F#RImt7Xm13^-DBl)x!qAaNfMinQ^EA2k4hX#sZ#kj3K*RKkc+ z5g3(}CrUUa=L~vkfe9I8_#izQT)`6k^q8Pvf)c1K0}i7U!U^wiKoay}361mlL=P0%(8$1SBwmI3QsFK@h>Tgb@rr z+~*rDXh9kpP=GmD126iJg&y^IfHT}>6teIkE-qoSY0%;gTc|<1Y7l^S;o=PO*?}#t zbr*8? z#WT+^iZ?{zKVpE#85UuSU@QO$wOB#La6t=e$$z#5?wmtf|hcdKItim26g=7xf}u*bCVzJJ0G_JhNv4k|*a$J&7mwgaX)r z1{RhvO~zwSofGHSIWk&Qs!qizJ0(+rrl=Ef!cNFUVV2dgIA+HLjaYT0j>Hi=0xMJl z45FbDvd{xDumKG$ERz|s8c$SNmCPS#QHiLq3YlG~K$At8l?m=43bRV0#7eM-JRlR> zhy(s10uG@XU=R(J7=;cH0~^r5B1_KNsPR~YEJ*kREh=VVLawk26=({v5OPG)2mrb; zj-cTdQ6dk>#5Ur9CK!f8s0J8BV-vEV1H`}vG}(!48g0~ggo`0a_ya8}Ci^%n*WljP z3{Ge<2UoG#AZSZH$j(-SIP3_4LqiHg+f4!;1{^@+bHJ4l9z206@8I^S>9Icxle1q3 z-gLeA*!wWFV{=|8vmhtC{N(;~NB0%yhT_#lP1g$BwCrhPtf%ef;IoE~do|Oax^hEV z@v3O!mB@qI;^xX|Yh77gEb=mb?L$}m%ixo%(IfS-(=``z+w@Yry7W#}No)P(f&RXh z+H!pQ>z5J}V3amN1OA94%35)qM$XR1hR4zi=GHXdtyq01vvld_KypQULq_pZf6@H% zrerEvnC%UB?io+ZADwzXe<1&M`l;_*8yhyZhu@xA9MfMjd)K`x{Bf~nRp;)}l!CwS zTEd+t!X1&{pN~y%SbMxX`YmPX`O3-hZPBUWgvr$W!)YCWe=+^7=|AuZvX13h# VE}D2eH!xT^o|-o?yeyG2{sYe7 zu%H&i0$U(&YA{t(F=bQoSqUa;A|`A?zRJN^jm4Oa2}nl4NR7mZjZgp^(7?hnrpbBy zX$TSmhd@q?N;Rki<)Gv$&=d_KK{yDxD9o}3mcSgCpb@KqG>`=1KwyPxfI&1=LKb=; z1~#C9g=KO>UgL=>tCITzEh-TeRw1_w6=p#n`I7DA3l8Ua8T#t}5!B1+@| znb<}g&;-M92-N_CXlz0jbbuJxfF?hYPos?*k8m*r34fqP#pEA{=NjGHo6!k9=IAOu z8wKsDN7?ym6o(%na_C5rXunCM!+|4cd=9u0!haO)`NmoPc-Q=U0>F0foC69Y`e4sNo zJGG|hY)@0=gSOiG;>&%F6)jaa?>5!9-->nCm37otcQw@BDJyQMDzA%`bT>ES)8Dba zX#dH8fJNE#-Dw)|KTKueLNL#WxczE>-VL>KHAgwa*JV(_bd9{&?D+ zJksBtR`h*tbl=B;cv{83?He<52RkxutS`?__O8TJFMS<8_jBfO>+a&kq0+JD!S=NL z-%}IEU)Ig07H(agSV?#&=>;qAvtrptPL35V{he<;lYioM%i3J!_459K*$bcIrCSmx F{XZLL-_HO5 literal 0 HcmV?d00001 diff --git a/target/classes/webapp/images/voting_no.png b/target/classes/webapp/images/voting_no.png new file mode 100644 index 0000000000000000000000000000000000000000..47bfe123167abe75eb6771c5849d0cbf90e3bc1b GIT binary patch literal 490 zcmVY5ZxSmce%?)A+d;+h@FKtX(Tp+zd@Tsu<--63-}Xkf|VFQ8W9VFU}vXb zA=n9mn9453M9w64m)qUjz0*lHD2nE>JNstl&1091TxK$;2h)tAfxxgMrfHyXfQTUF z!}GdQ(52@WMpxI6U{0tf6o#-ad1Z5%RbLFJ?MYY9qa%yC`+VN1y-AxydKkD8#o3E< zNUx@6`WFCj&L>K{l0Ou8M70Veq=FFgeOK4D@01d|6{S4B~t_Yc99P?k`Qpa)$yUDtvMuInmkwkw4#+NeucU(;yFk<)@Yq?ToY z1~bYbsg=uP#ceG_Efjk#Y@kM+A~bb&dOJG@U8%v6J(Z3SEO?(!vNzX{E9+6DA?ia5 zDyn9b%ieO<-0$o{{%>} gJi4#`XFzrVpPE+5ecnEI!T;nXocbcu&DxYpO zP5}seX25!8X6B$}ZXi>+nYe!cc=r4E?+(|wARjO>&ATxD+M}~T_Pd`yfaJID-+&50 zN+HzGpFayW$G-aya-0x52g5JscmF}^zJLGD@Z;C#pG@q$d@Mi;WaW=vUG8(){(+cN z&P{&#^})OEU!h(E%m4iR2^s~Te*UOCl>PeiYY11ASM=`}P{95C_?efN7ZF$=KE7FW zb#|rIG#(D7FF*dlLx+>)f7hw%d#`T;6$tQ)Flsf616iyrY`_2ff|GF7KrVy4`8o2ZG|JbAE z1GEGBy8dgH|2I|1#l+SB&Hrdhfi+Q;0#XCY)!E(P?+h9t^tJR%bpBm}{@gnMsdWIm zjLAn;XsDZ+Ol;@0B>#z|{O4%?xre;cnFNVwh|uTdv!bEpK>y$3@d9oG&Xcp)oRl*&^s3{Z09CMiA% z78+%X{%Tlh&eG+^l+G(fsakyH{k9X-I{yFy1ipj-+SmMLJ!`IkqyNMcv@ZVy3>Bw* zu>d&(kR{cnbHwPa=64Igw#CJfeVKkqr^+n<-ZB5(o3_uBzcSHKw6C_Du=ZX+O1ZMP zy}G$2A|^I8E~0g}onNxWOLNACxt)~gnyHeXr3}SJ)+3}LwHF|x=Zh}+{TmJVrOn=h4J&J0J*Ta zqpteZ*5cOK;>Y62#^BZ7=-;iPs@2={vsk_1S^LJ%_NK4d;M?@0hQdSVa8z{aVO3XA zdGB0*>|Q=!%#oaPE#bgiG`)V*!ji@}Q>wei^3Ivf=HBNyBqN`*tvD}2 zd{|&Z=}}i)Qa)a>Z$UknZ3Kf+t*uQElTxVJ;_8WEOudfjope`Tg8509OunesgpsuM zYyYl?uHoeKA^8LW00930EC2ui03HAy06+-;0RIUbNO0gS0)PrCTqw{2!2=|yw0S^6 z-9G~MxYbag3K}#P-ULoj18#_&4T~1dvZByLh9JP)0U(lN1)Mt$vyBjN=E@ZnSfsF# zAq55u9SoB)Y;u9er9OU&JZLabS2#i@3WA8^0^A90F5;+R!ePR$Snd8qApy&mn{#Do z$PwWM+dw7B8hPNN!APbQFw7tw=wm|`J_S~+$P(iRog+#kOJ;~;9t8>skTjW`)B?ON zC{PS(HAdhhTW{W=slmb#K^5mQ%|NL{d@^K#X8{dgLgOBMlr3LO}fE zCqRrCF0bUnh7nvy@r4;=a3)X$0(`LzP*q4#i!hKW$k_`QT!6$27A|DMDhW`)Ndl{Q zF<6HJHIRxa5D3@81Gxxf4Ky{Z5l={y-Es;I + + + + + + + kAmMa's Forum + + +
+
+
+
+ {{COMMON_HEADER}} +
+ + +
+
+ + + + + + + + + + + + + +
 
+
+
+
+ {{ERROR_BLOCK}} + {{COMMON_FOOTER}} +
+
+
+ + diff --git a/target/classes/webapp/member.html b/target/classes/webapp/member.html new file mode 100644 index 0000000..52543e4 --- /dev/null +++ b/target/classes/webapp/member.html @@ -0,0 +1,214 @@ + + + + + + + + + kAmMa's Forum - Member + + +
+
+
+ {{COMMON_HEADER}} +
+ {{ERROR_BLOCK}} + + + + + + + + +
Member details
+
+
+
+ Account + + + + + +
Username: {{USERNAME}}
Join Date: {{JOIN_DATE}}
+
+
+
+
+ +
+ +
+ + + + + + + +
Change user icon
+
+
+
+ User Icon + + + + + + + + + +
+ Current user icon
+ + +
+ Browse your local disk for user icon
+ +
+
+
+ +
+
+
+
+
+ +
+ +
+ + + + + + + +
Change password
+
+
+
+ Old Password + + + + + +
Please enter your current password.
+
+
+ New Password + + + + + + + + +
Please enter a new password for your account.
New Password:
Confirm New Password:
+
+
+ + +
+
+
+
+
+ +
+ +
+ + + + + + + +
Change personal info
+
+
+
+ Email Address + + + + + + + + +
Please enter a valid email address.
Email Address:
Confirm Email Address:
+
+
+ Other informations + + + + + + +
First Name:
 
Last Name:
 
City:
 
+
+
+ Sounds + + + + +
Sound notifications:  
+
+
+ + +
+
+
+
+
+ +
+ {{COMMON_FOOTER}} +
+
+ + + + diff --git a/target/classes/webapp/message.html b/target/classes/webapp/message.html new file mode 100644 index 0000000..8e92335 --- /dev/null +++ b/target/classes/webapp/message.html @@ -0,0 +1,81 @@ + + + + + + + + + kAmMa's Forum + + +
+
+
+ {{COMMON_HEADER}} +
+ {{ERROR_BLOCK}} + + + + + + + + + + +
Reply to {{TO_USERNAME}}
+
+
+
+
+
Message:
+
+
+
+ +
+
+
+
+ + + + + +
+
+
+
+
+
+
+
+ + + +
 
Private Messages: {{THREAD_TITLE}}
+
+ + + + +
+
+ + + Records per page: + +
+ Rows {{ROWS_FROM}} - {{ROWS_TO}} of {{ROWS_TOTAL}} {{PAGE_LINKS}} +
+
+
+ {{MESSAGE_ROWS}} +
+ {{COMMON_FOOTER}} +
+
+ + diff --git a/target/classes/webapp/newpm.html b/target/classes/webapp/newpm.html new file mode 100644 index 0000000..3a093a2 --- /dev/null +++ b/target/classes/webapp/newpm.html @@ -0,0 +1,64 @@ + + + + + + + + + kAmMa's Forum + + +
+
+
+ {{COMMON_HEADER}} +
+ {{ERROR_BLOCK}} + + + + + + + +
+
 
+ Private Message to : {{TO_USERNAME}} +
+ +
+ + + + + + + +
+
+
+ + + + + + + + +
Title:
  
+
Message text:
+
+ +
+
+
+
+
+
+
+ {{COMMON_FOOTER}} +
+
+ + diff --git a/target/classes/webapp/newthread.html b/target/classes/webapp/newthread.html new file mode 100644 index 0000000..a3d7394 --- /dev/null +++ b/target/classes/webapp/newthread.html @@ -0,0 +1,62 @@ + + + + + + + + + kAmMa's Forum - New Thread + + +
+
+
+ {{COMMON_HEADER}} +
+ {{ERROR_BLOCK}} + +
+ + + + + + + + + +
Post New Thread
+
+
+
Logged in as {{USERNAME}}
+ + + + + + + + +
Title:
  
+
Description:
+
+ +
+
+ Thread password +
+
+
+
+
+ +
+
+
+
+ {{COMMON_FOOTER}} +
+
+ + diff --git a/target/classes/webapp/notif.wav b/target/classes/webapp/notif.wav new file mode 100644 index 0000000000000000000000000000000000000000..3756db587685a995d2bd071925ea90cb53cc1ded GIT binary patch literal 37804 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 MIIYTX|J~;PA3aP-W&i*H literal 0 HcmV?d00001 diff --git a/target/classes/webapp/private.html b/target/classes/webapp/private.html new file mode 100644 index 0000000..e931bc9 --- /dev/null +++ b/target/classes/webapp/private.html @@ -0,0 +1,109 @@ + + + + + + + + + kAmMa's Forum + + +
+
+
+ {{COMMON_HEADER}} +
+
+ + + + +
+
+ + + Records per page: + +
+ Rows {{ROWS_FROM}} - {{ROWS_TO}} of {{ROWS_TOTAL}} {{PAGE_LINKS}} +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + {{THREAD_ROWS}} + + + + + + +
+ + + +
Private Messages
+
+ +
Title / Sender -> Recipient
+
+
+ Selected Messages: + + +
+
+
+
+ {{COMMON_FOOTER}} +
+
+ + + + diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..0114e22 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,44 @@ +cz/kamma/fabka/httpserver/session/SessionData.class +cz/kamma/fabka/httpserver/repository/PrivateThreadRoot.class +cz/kamma/fabka/httpserver/repository/MysqlClientRepository.class +cz/kamma/fabka/httpserver/http/Router.class +cz/kamma/fabka/httpserver/repository/PrivateMessageItem.class +cz/kamma/fabka/httpserver/http/Responses.class +cz/kamma/fabka/httpserver/http/ClasspathStaticFileHandler.class +cz/kamma/fabka/httpserver/session/SessionManager.class +cz/kamma/fabka/httpserver/repository/PrivateThreadSummary.class +cz/kamma/fabka/httpserver/repository/QuotedTextItem.class +cz/kamma/fabka/httpserver/web/LegacyMessageFormatter.class +cz/kamma/fabka/httpserver/repository/MemberProfile.class +cz/kamma/fabka/httpserver/repository/ForumSummary.class +cz/kamma/fabka/httpserver/repository/MysqlClientRepository$SqlExecution.class +cz/kamma/fabka/httpserver/repository/ChatVoteStats.class +cz/kamma/fabka/httpserver/http/StaticFileHttpHandler.class +cz/kamma/fabka/httpserver/auth/AuthService.class +cz/kamma/fabka/httpserver/HttpServerApplication.class +cz/kamma/fabka/httpserver/repository/UserIcon.class +cz/kamma/fabka/httpserver/auth/DatabaseAuthService.class +cz/kamma/fabka/httpserver/repository/MessageRenderSettings.class +cz/kamma/fabka/httpserver/repository/PrivateMessageStats.class +cz/kamma/fabka/httpserver/web/Pages.class +cz/kamma/fabka/httpserver/repository/ForumDisplayView.class +cz/kamma/fabka/httpserver/repository/ForumRepository.class +cz/kamma/fabka/httpserver/auth/EnvAuthService.class +cz/kamma/fabka/httpserver/repository/ForumMessage.class +cz/kamma/fabka/httpserver/http/RouteHandler.class +cz/kamma/fabka/httpserver/AppConfig.class +cz/kamma/fabka/httpserver/repository/SettingsRepository.class +cz/kamma/fabka/httpserver/repository/ChatLine.class +cz/kamma/fabka/httpserver/repository/ForumAttachment.class +cz/kamma/fabka/httpserver/repository/AttachmentData.class +cz/kamma/fabka/httpserver/repository/PrivateMessageRepository.class +cz/kamma/fabka/httpserver/repository/MemberRepository.class +cz/kamma/fabka/httpserver/auth/AuthenticatedUser.class +cz/kamma/fabka/httpserver/repository/VoteStats.class +cz/kamma/fabka/httpserver/http/RequestContext.class +cz/kamma/fabka/httpserver/http/MultipartFormData.class +cz/kamma/fabka/httpserver/repository/UserIconRepository.class +cz/kamma/fabka/httpserver/repository/ChatRepository.class +cz/kamma/fabka/httpserver/http/MultipartFormData$FileItem.class +cz/kamma/fabka/httpserver/crypto/Md5.class +cz/kamma/fabka/httpserver/repository/ForumDetail.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..8f9e8d4 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,42 @@ +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/AppConfig.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/HttpServerApplication.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/auth/AuthService.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/auth/AuthenticatedUser.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/auth/DatabaseAuthService.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/auth/EnvAuthService.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/crypto/Md5.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/http/ClasspathStaticFileHandler.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/http/MultipartFormData.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/http/RequestContext.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/http/Responses.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/http/RouteHandler.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/http/Router.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/http/StaticFileHttpHandler.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/AttachmentData.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/ChatLine.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/ChatRepository.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/ChatVoteStats.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/ForumAttachment.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/ForumDetail.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/ForumDisplayView.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/ForumMessage.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/ForumRepository.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/ForumSummary.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/MemberProfile.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/MemberRepository.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/MessageRenderSettings.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/MysqlClientRepository.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageItem.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageRepository.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateMessageStats.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateThreadRoot.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/PrivateThreadSummary.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/QuotedTextItem.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/SettingsRepository.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/UserIcon.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/UserIconRepository.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/repository/VoteStats.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/session/SessionData.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/session/SessionManager.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/web/LegacyMessageFormatter.java +/home/kamma/projects/FabkovaChata/new-version/app-httpserver/src/main/java/cz/kamma/fabka/httpserver/web/Pages.java

CYpk+e}?Y@!B!VFHv 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 literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/auth/AuthService.class b/target/classes/cz/kamma/fabka/httpserver/auth/AuthService.class new file mode 100644 index 0000000000000000000000000000000000000000..20fdf3e386c2e7ab1e5180eb1eb52516c9a2762b GIT binary patch literal 248 zcmX^0Z`VEs1_pBmPId-%b_Nbc2A;&yl8n^6lFa19l2k^92o0aC#Ii*FoW#6z{os_q*Hl9Gbr)S|M~B7Kl~`i@A3ri21ztQi@&g7Zs@ zl2hF>bAa{;fK&tnbz~-|>Va%zWY8kQOsEc^euOLild@8iOV}707#Wy=eq&%@WMO1r PW?%ua7+8TM69XFnsd!41 literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/auth/AuthenticatedUser.class b/target/classes/cz/kamma/fabka/httpserver/auth/AuthenticatedUser.class new file mode 100644 index 0000000000000000000000000000000000000000..81e1866e24c0d88f4aabaebd5d2ae491d7925789 GIT binary patch literal 663 zcmb7CTT22#7(KJzb&wfLXUpJP*!_@i;D~-x z1g_3!#i;7ILxzL8^T2z;^8~*Wc2Dq*Qum}}ECVh^s>4soNME^*P_lJH9nz9&OQl1t zJyNNX`pm2{JwT~aotMavZVX0&BhOtesys)$r%!DnjSUlVBupfcV%Yz$K8F0y^8Ma;6FYSIG5RA0ih2P>MZJT31f&ibvLlt;8tA(lP}@K$%NT>yuW7CmkwpSOTgf&uIDlt*n(2@EuN@ zY5SbZZ)nr=T71^Fb4+p3u;N<5o{bM^wl=NhjXp9-%G0{ROR7?5n08v%Y(_Z0 zp|smS6idcfb()%F7L4Swm}OJ32Kk$r__&cTWbCA2OI?n-trguF%gAY#kxJUkYJr|; zCkT^yCQ_wT?H2{Si$%BulG%c#8zjrc<5K)e=Ncj-MWN zxNDlOXA7pCkDePE93OOKvnBHzSQKci%;ChWmAxf#uHqKh+h#{4k+Zh7DsIdA9mP$7 z`pV(c({n_IKx_kPm4@M+^TkJCH6IzY0ycpyb(Qu43K zi7OmRMs_)rj4dN~ptBg+{S|x&0HVF)1-$nW=;U?}Uh$Z`Z;YI;#}j+Pe4a3!C-A(0 z(&LSILp93?xX|N^_&i<92)YsRc_W?@wG36DM~NuQs1>-6F45zUD6bKak~%?KN&A+u ziL_VJ-euI2_CNj}4HqIxD71`57f3WMquGV$2yfU!(J)*J^$==LO~fCmd4|mq|0^cN z*x^!asbpdizf>x(cpD}YE+r$Cw2mlJ>3bz_{R~^gW0pOM>saA$kGpu_>_6id{7Sx? zjeO(CA36I9SIPf|-|+|cPQG<+NB`26XmV=T15F<<}O}4`-+Gx4pvt^S(yT>|3)`A6hmlU||S z8*Hcd4zUBBVmG=R7+of?lK6()9w_ws6XMuQh;;G>`>>zD*g^ScI3S@Y+E?L5>L9Zr z+(>=yz^$1#>UQAPD(dhBDGv@&A3|fDc#BJ@QQ%jIlFEOtg2M`qDExJYVDL}4{=zOY z!QflMm$uc-Ar!Q{WkvqOVT)trmwuG0e#%bX7iNtI-+B+_<5j({IqUt`S$QzR0pyQ@ z(o=AXyBynw80RAP46XN|cpk#z{+7Zb5PXioey3q-(U{GbuL=vyX1RmzOv{^G+dfE1@p#35fVc1fWk!zM3lFUvovlTCw0~V@hAL6 zk*JD5;sf|7#H{1CiSiI$W@paq%$c2;{qy(dZvYSBn8@L39tN%{aoxlX+%!-zv4SlF zRRgyS+-9)E=aKNF=)N0^h{0|i@CkPV9`3s>8TsM3*B2sTNWS$PYf}zy(L-8sebVT%)cLGA}=FsCon@7H~Q!pcY{+PjSdSBc=9}Kwr zk$3vs?MXSJN+%+6c`SSGvvBf+&K5zwC#YCU!6KBt$0a3y|9|x@qfs2ENt0F)Q!b`L zLOvjt+~}+`#g^n=|0N%#4>4QAIP%1EUp;kwzTEq&*%pepW5Gnhf`x*CyB4-lV|cLS z=?gWfOm&8POXB8hpz)rM{B`GmmVv=pl47Ri40=tCcP+^hOFwX@S!jA$aTxP}l({XL zx_4)`Bt_~U7&dAzW*q4X*4x;kCC-5%4-K|z7Hzq$`bDQkcoC~4OJwblts6t7oiL@)n4LGUHOhx|zhY L3W1kN)o|q>kG}Lo literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/crypto/Md5.class b/target/classes/cz/kamma/fabka/httpserver/crypto/Md5.class new file mode 100644 index 0000000000000000000000000000000000000000..8d6ff3aeb958d58ddc346a97d9415c77a8a56280 GIT binary patch literal 1684 zcma)6-%}e^6#j15WS3&NBT!WCA6Iy!;PHvy;W*6!0pTMOaMa~V(q+F;x9t0F$)_52<-x8cs-fe1gpY1>w@xbc7D8^7^Ss&=NX5r0 zmQ-A4&#BV&E}W4Xoh{Y1R#Vi4BsvdGtRiFkEfd$hxj5ahIrs;)Eo_AhkHekJ%47 zwV}F492{2;gly$2hT;D+2n_x0Vh=aBghlW8RH!u(442OKe&(Gr^pr%H^=cvaERuBM z^&@pc{!9XTY1f+hJ;ygKn+G)!(kybX$b;g1%cs$fK3-Y;_AbN4*07ugAAu;5My+J_ zLP7W|M}52sI3i~+mfFZP7fLECFWf*|imVrkk_d=e=r4)gV;K?z>=;$58gqqL3h6r0 z&CQa_t5xCAT^LNwcjDWloqoMir|x4LuZ8|`C+Ab0iK&s%S7bcZ@QNMyJ90*5M^-U` zH|R4a|048H(@(R1fxJojrHg>*=mFTT&F#0y>I4FPycBdJNh_lT!bMynuN#B7+)BWB z69epwzCbJH(`WvGAi0wnoKZmwHei*e6(7il9qa>BX>1fwk zl88-Cs|j@gjUS#tpV5-qK6;d&pr`w9?Za40%Rukik1_U3Pj-MP#BrSzm?I6c#QqWSUnlf;6njFX%0$a0Qj+Qt zoyw9nS8!E+$MH6XaE)TU_=eW+P`xCbAEtZ}dKPg5?^0zKJxF1sDbo%{=`1itPX>4Kzqpxg|{>W%vm^N8UV|i_PSJ_<| zliu&w^qRCl?^k*+Zi7Pk(0*(C^r!S6v=6F`xw)3KoAs*Xh+*L1w1WJ|mz{nVvjn`ZEHF!mPPq<|?LL&W-!7WtZ~; zZByI{S7pTGzUf=VlU7BZHtkYH zx+>h6Kom%&3Poo==hf_-E&XOEr|Iv+Vv&YrnO9-9xUwQ(xYBcKZm~gzE*mBS`yR;? z;i{knIv7`R?u=OtaB29gK#cpzs&6^Ar{Qxn3Gke6Y$!1FXu-Y>%O%q{1tOEf0`a0_ z`_lF&u2o5M#Igl?!c~@&8?~!7pNW;`d|t!nHGDy!t6YQxtQUIrM0y=B&X<0KST0aUd+TQ^0QO3vgix!e#m)j z9N-7mg;&rriK>3jt{kIg?P^sE?0Lu@%V(a1rqr?TDog*oe3|v{ht(pmDYb5)x6#V1 zFb6%&Mb=2@8 zG=c;)#sV5wgtvUm^G(=&H#c&%- zH0LQ~DbJg8l6MM=Rc%9wd)hwHA8sbFvEMon*Q)r6tX5QT#|Ug7ra&w>W=XmN$z>XX zC#-3TeDu6!`SO~L0hQs+f81k8LHVZFiL-(FEf*<|Rk7lDy!!Znn5E;D%J4NmNOUT# zZse#Z)lGBc9p`&;ILNqJDvj5we2hI$(%;tR09Wpo;5m!?PJ!2^JHm&Z?>n~-lC9zQ z>Kd#I>|Le8vKO2=*?8^4jGZ)#zT;lgaF?%X!ExqlRbm^pZF*EMufle{dH05o@C=0q z+1nc~%&$#kljebi>cv{ee6q=Y`M@=)8ZJioQjA-^fIQ3*9R%)0AgK)_+~64uu~*yNr*$j)QX)D~M{6BzH_K~kmu$TRHJXRP; zY_DU-4YVb8-X2KoYRJ8mA8*LJDes|tleLoA+feSKvaccUr<`iYY0C5)I2^d07`)Am zLc)fbV6Ze=NyP?&>}Hv7W1;ULxIRKnvlQ}#c$8tEV#=Rm!pBL~MUwO)>6s#AMdIc# z_<$17+e=K!0YW>*xlZW!(M=}e@$N`G9_QBQsE&}|JM3dL`vcy@8zgCpW1Zt7|4RP@ DNV|w1 literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/http/MultipartFormData$FileItem.class b/target/classes/cz/kamma/fabka/httpserver/http/MultipartFormData$FileItem.class new file mode 100644 index 0000000000000000000000000000000000000000..388b6ea4cc6c0ef0ccad355602e8a2add95e338b GIT binary patch literal 1086 zcmbVKOK%cU6#nioFa^dwuvV$Bs;$EK7)?x!!MG@f*aUIWhD}!&xX@|mL1r#Zbmd?1 znYb{qiNwT(KfoVlJoiG=23V+@^FH7C&bjy3?;k$_Jc5&hg=<-4a9xQTN~|lfVd18Q zTMXH*C;U!>2ZEtc-{*(i^?BHHw`J^wy$Zuz*Ym|FpKnK@6rtRC)u(E+!zE`hb~hO^ zPrT5RPZ=tuX^g*#>~5Bu1X+zbguGDqLeUrmEfMeVmamybZSFUD>?wa7H07R`5WyM| z+>#<-u(!fc#8sasiJ(lS-hS;K@F3uBm$weMyC-Eo5%HmjwduYX_|oh1Sk|IAct-22 zA7jztc2CIK$#IrT<>^Hj^1AByLi6NRPanymfC8r7N@aDa+;%jG+d|D@SeYVlPrU%` z)DVe$9wpL3$;Ld&Hs+AEky9eCgpD%{kNSBmRqHjJBb_6-4A!uW;ve7z)I$IO literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/http/MultipartFormData.class b/target/classes/cz/kamma/fabka/httpserver/http/MultipartFormData.class new file mode 100644 index 0000000000000000000000000000000000000000..56ae04985d5bb0d8905a5cf0f96f3127f7d577a9 GIT binary patch literal 7529 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 literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/http/RequestContext.class b/target/classes/cz/kamma/fabka/httpserver/http/RequestContext.class new file mode 100644 index 0000000000000000000000000000000000000000..bcf87abed1d0df71ce69263ba2a096bfe21495fc GIT binary patch literal 6715 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 literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/http/RouteHandler.class b/target/classes/cz/kamma/fabka/httpserver/http/RouteHandler.class new file mode 100644 index 0000000000000000000000000000000000000000..921b60059a530f08cc8c09c0b9c38206baff9fa6 GIT binary patch literal 328 zcmah_Jxc>Y5PcKloyHFoD=S+?aKXkRm54zhAe@kXZk?D-?snteZV3J~3x9w=O5D4` z*jdfIH#6@&=Ii_86Tl4)bL{2VCuB3WFO?9k&&T>*Ex4{(O}SjKnuV}z#IMA6tt#X# zN)zs!4YCfyiwR+Hzt*x0#@QyvLBh{2dMzWzq0`>ICK>Xzo}F}e9EYZ($Q|4aDCkZ`n-dn)Im1L0&t{U*-{gkJgz bKz+g%`U!6*)YdFb^3?aRgWXiK#0`KSF{E7c literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/http/Router.class b/target/classes/cz/kamma/fabka/httpserver/http/Router.class new file mode 100644 index 0000000000000000000000000000000000000000..c75079ca7ce27723709ae5f1b10c1776cac5d448 GIT binary patch literal 3134 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 PEwNN8MfO3mV>t9*Y3%-X literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/repository/AttachmentData.class b/target/classes/cz/kamma/fabka/httpserver/repository/AttachmentData.class new file mode 100644 index 0000000000000000000000000000000000000000..7de36841348c80fa961bf02a7e2f80c871966e5a GIT binary patch literal 804 zcmbVJOHaZ;5dOBUAXq^G-|ri(j~oVtS0)aG?AEi@CW## zjI)c04>;Mw%^7{45Ak|BwivqaW{~0 za_=2V$^Ax1W>3f^c~94cJkzU{%Fdu8h8ztX|J+X@6qt;b9kpckBEC(0aT+L(#or2B z>bh*$ShbPHsEv#YS&T7k|HClD#P|K5*P6nY3})G>VvQavMbBuWpoFL@C@q~yj2SbfuIHT`6soG>i1Vuk2A)QzPy(j7KW^QYqccYihec@I}oqH56?A1s8{cr!hlx SYUiKI)?e_Z?d6;QINX2QxnOc(-9HSfddXw4-KSKi`PX!C19J`> zbUWzb(2G6?{Tv23T*IJ)A+D~YPhjf5%n;~1e=%<>yShqGgotlpjNV-rJ=HvIq1{vs z@OqPpfyqR|Wa42mQ8Ag=m`sEy+Ylt-CdDG1ej{z>&dTo)ax_9pjzvhzVm3lX7IP7@ zvS>xf$)S7%OBU@2d08w(u)h*Kg<*=4$bs}`Xs}JAdua3^eHq3r+{JB7U<8x6gBgrc z2Y5%ffLZ!7LYXU;BuvGk(xzfrSyQpFoT*rvWhxezHx5sO(I%WT&2dAsH&c?yhiQxN* u;9ML$lL&r5KhddS=d`U)G1_y9;D?Eu^YP8k6TziKupI|4B!bI^pZp6>)#zjZ literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/repository/ChatRepository.class b/target/classes/cz/kamma/fabka/httpserver/repository/ChatRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..c33ecbdac4ed1369bb12ee35f3a3fe37c7ec10e0 GIT binary patch literal 11783 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 vBgQh_Q8h~bGc!BFu$+n1s$ zxg3z+Y*WbE%&`i^(!4E({5SB|*-}-17&OGC8=0F~eHh-2jwH5pB%tY7g2r(4e~%2g zpKHE$+M*#D)MClTF1<~h9!!A|N>F8lDwI_)Qch7^rtXFiD&?&C3TEz)p_m6_sLwP~ zpi?wx5r`NgdXl~jQPVWpnb4@uQb!9S85UKckU|1EK*dc?P@Ibah{j7@oG`kI^|#5+1$h zopT=bwH}Uyx)3T-VwjDj&eVtKAXDSymDW7&CnDA4)$0k}q)bk|L8*LN9ob;jdgHo5 zhLRk;y$VMZinlX1h^64CYBiX z|0f2+^1okr(>W8dwt?E%p}+I#Yh6@~9HEL)w4Fq4Ctd0uXbM)SR%rDJ4l#kv@CR79 zJwYLSJb@F|+zH&U=1t&zqvHZrsczV-B@!u<#3BU=P^IA;gsODDPs3>xYPdz+AY+AD zWXw1g89S~;#*ndd8*4K=5h>^LS`1QlOe3YJhGDtVu5#&p{3PMAz8`sNQ3 z6byn7{s4cJcxM`HN%ukb;m(~q_uO;N&iwlQ<0pV;*r_7KMg?WuwPDkSdp2x^xF6yH zLrD%8*a3r|^Gq<*yC?jNCnKH@lOv;LK77GY8;D7&<=9A-GgMMF&CNi)r@Z`9=F+@k zc-}s^iu{*Kr$^koYCzn}U73r+Y1S9|E$@$<8kO=i?(B>PHFvpr$B9Fdb?D9mee?6o`OIi%g8v%S{tTouc( zL=AhFy+>5oy|7F zKM|>ME_&Fa>kR3(ebg-#Wp&HuRw8sOanc%q9ATN_3hh3@BP7s@zk|h_GnC?oGk9^s zpTUnC!3@DyIxbY8cw5dgR1sxIVsF#R*dH|E5~=4 z6=dbC8A-GWp_7}m8 SLU6MX94vx23c)R>@6I3VETNhJ literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/repository/ForumDisplayView.class b/target/classes/cz/kamma/fabka/httpserver/repository/ForumDisplayView.class new file mode 100644 index 0000000000000000000000000000000000000000..88459f4b5b62df5e17df9d36e8733e013c0de5a6 GIT binary patch literal 2482 zcmc&#%W@M(6g{oCMkC`0L?Rv`KoYFsF+czlYz)B<5K$6Wq;h4E%~*8g0ZF5pY2i3) zJ|~N$DpRRskq^j6rE+g;WbC0AKox}-=iYvtzNb%LjsN}c?|%R+qHW;_mdlvKU4|8g zdkps(zG8U5u*$H;u+Ff-@Q~qA5v?K~3zUX3ioCvz1m@dMz2}}gQU1Vf`;l4_D0lq+ z$WxP03aqD=m)pHx+-Kf!=(#_6+s{0AN2zfn!{;(|Lpcs2Uj^ZwyAgzw;WyJFODlY) z3Y0haKG>y_qD2ZK<%NpI0tGpGq7zH+*_`UC^d?~_M~a66RwTW!x6_qBD}mZn)4&_` z-HrTFif*>CPLjnenz|GhHR2-;IpCtW7(s%xD`>E{FPoAvZB0+QX0R(K+SQqGhX_UcHiX@Tp z8mrWigz89IbtJhul42c6w2q`(N0P21sn?M%K(R_S&f*-^I1h;H6st7**!dH}xfmnk zoQjckPRGbO^<0d+Q_sgJIQ2q|qSGkGC^?N%jIz@x$FQ7+6{F%bDlu%QVaKTcPN!w@ zDd8Qu8_2dC)hkoQIjVS+RvP$>9Q~Z|0xn_!*U+T@1P<=fXcd>x!ev?mUQCtI&I>dm zjfq2?u{Rk#$L?fx#{T4V#t!9m#vT=P#x50g#y*vF#!i)W#$H)EW49_gW4~4o5J40ZIhQ8iHj=Lz$@w&S*+{--Bp1@;`$qD0Be|F+uNlc-;D*7lQrh)LM)FM~ wxtu1qjpSQKvXv%p8p(@BawSdf8p*efWIIj%!ASnnNUo;IKN`t*bRRDL4?@G$CIA2c literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/repository/ForumMessage.class b/target/classes/cz/kamma/fabka/httpserver/repository/ForumMessage.class new file mode 100644 index 0000000000000000000000000000000000000000..9fd66ed7f5bf452d8f6a259c81efbbb1cebc1008 GIT binary patch literal 3545 zcmds3OLH4V5blvI$&%NSWm_@|k0d;Ty_hH=^QL_hzWs}w3gPEv}Uz( z;T$+{;Lkv+po%T3I8dCq@Q*0Io|PrDo`cFqN>$xGJ@d_c-96K*zyA6C4iy8})#cqu1F=_}3Sv5hT zC}`B^x((YYU>NmHL6fCD% z?zkOnP<2eKsog)YtBrN5*|gw{AKI>2Hkw^vML|%E$4^bnMA_P~1!+CklaA@YKj!<7 zN8fa@tl*k0CYz9>8V?0sFI8XU_l;J|$nP4JeIws+-2?1#XgYbvJg_^KYdc5zN4!C8 z+x*sL7-EX|crLqwMqKzg5Z5)Tjh5NwKqmZ5bX}{NFIgRzFZMa(Ya@wkoExZB&}@OqT4b(JUK|#p9lH#BK1^xi%a}nb&o*(=qBM zQkwnmjzz) zFg5=k`)%VUVmw>pV6MFwxOc=bkJJcBYD6V90+SlCNsaKNMua429(IV{ z2Ok6rdkVfQbQQi2h{TT+MpB7v{WlW&d5=Q+MUTRIF5=OMo{M@E(Q`46qI!1JqnMse zcr>bKG>_tXX56EMo=JK%rsq-~X?kwLqj5c(_9&_6raVgNxoM9k^jyZHw4Td)G^uB1 zJev9iM~3Mdmy+(&C~-rNw25Ns9|JDlINe zLRwrLO{$2r|mlx?FV4L(v T5&KlI-ajZd5$MC*I=21=QF5~b literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/repository/ForumRepository.class b/target/classes/cz/kamma/fabka/httpserver/repository/ForumRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..08beb352c8d1468cf9a1cbf91563c1b7dbfbee7c GIT binary patch literal 27142 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¥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 Pfh0v>T9Z+4@Ra`r92|vl literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/repository/ForumSummary.class b/target/classes/cz/kamma/fabka/httpserver/repository/ForumSummary.class new file mode 100644 index 0000000000000000000000000000000000000000..bfb1507dd7547f7abf94fa1ecf46d1bd967baa4a GIT binary patch literal 1491 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ℜSz^4lni5CMc;oW&&BM~>q&9!j zVYD#+$!M-H45=2=km5+ph|xCRsOWM5(e+l5LgHj8pT%Z;Miy=J7Zu%@Fq$h2LnaGS zEH+~^GL`#u>rODHRiI_Xp}8PL!qHX@hd@!QhZC`gj}vq!h!d`peAI>_e&oD6IIqhU zf7uLeXU%+yiFW`Gx#MSW98dARn>W+9UH+Hc@rU>Ye#Q4rR_z-u|93K~h%#71i?AH` zs>Q^Q18Sj+YMYu;2PuD8J*qy*@mBRdb&TUdb<7=4su}e~zOPjesOPDFP(3fBk3Wyl z9(X@RS))3?cQ^1Oc@v?28Nq%z>%Eg+ceBR(Sl!!M*Q2cHT}<07cH>^s2Qb0?q`9LM zzqah=4lMkR;~TKlLQK-b-Kqt9)G}n$a^%z+emGf+g1Qw&wGAaTioI$A`_vRa(mcTN zAZ7gQp*gnSxs797yUgok6y;vl=|`P6 zm!5d2r*rL5Zw5WN&)0jQER{m6VB(7vOni~(E6t=xGo->Sm9JrvR0K_oah6n+yFBW9 zO4JLPxLki7mw8~P&JfXS@P@1UjR1SsM1pMqI|BEvKac7x2#d`LVPX`#2pUi$_m`|d zTMb%Od;|;H4W6fnzvzQxv zHyWJj2DWCu6_r_Wt=W{PcJ6JJ&Ai#hWCy$!`DxF?e_rv5e?UXqSD`nqJPfVv1Xdj- z$7>U!uJmrV91KqrcQW0;vIRpOigj~sn&gK`isc8^EgZ#UqHHqUcDB8H0`?MT*}K`Z z$FG;ZJ?U%3-a^`oHntyq9INSL$K$yA1g<%OYfs?1s{IBj6Bbq&@27Vk2&}V^?aN-u z#BUnZh&ubQ-(REMT>-eLam`6w{{&Vyu04r$PvC0#MX21?KhBh3oH%VBIa8!Vxafgc5PfUANt1@8b-$o|RNNBt;UfYnE=ZgLNDTyWT)RWMtz%p3O{?}-QK=CU z2YvuQ3Nhw^WK}C*`L3@{{Zj|`%TnvyMYzlv0=}KyLH^Fhn$QXWF!Ef6bk@>tdgspDxX3IP*F8%c9B(@ja zzlPBqWD_0J8)bXnt8{*9$K1ji9<)%$Y6}e;nphQh@&E4;SYNE~eRM)Guhi}KhIq(Z zukn9y;afk>d}~>r?8>JHUNl-9Z}BaG!w9eye1ixcP2mKOr*MNOQ+QX5J7{y4v%}VliCD~gh?^cp`%KmnL14sfjz6g_V;6K5tSnGQ*4pe-$=FqsrwDBq-kcFM=l0i+DDVCPI8!Njo#`x!`E zsSA);P>BVVSWu}8khIMS=zrteGo+Gw`FF_z7h71Ow8 zE-ahIb)`DK^lnMd@MOpJE#-Qv#`#tMrd_u!>8SZQI+a8wwj)bYMV-1Xj8&(5=R$dY zDsQ~no5p&FEq{K)GMWLIrc-eun6F;tOm2NS=_VzUpWNwrAw%}a> z=op5sqk%m--a$pjI4_$pqGJ?gfiwTp38hQD@uP5Y;f8F}tsepz2y70Y$LKpLj^8J!O7LiCourJ5^qr=J$=Y*>S|zuJ zf!Yh?*HDPI!QVret+ZB{q^5C9{98>&Fo2`17{_ot+-LCt?Ii*zVixaX@-;%i BwgCVD literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/repository/MysqlClientRepository.class b/target/classes/cz/kamma/fabka/httpserver/repository/MysqlClientRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..ae141f698de6936dd673ab4e77e3f2a4dd95308e GIT binary patch literal 6746 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 F<}X$-aJ>Kk literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/repository/PrivateThreadRoot.class b/target/classes/cz/kamma/fabka/httpserver/repository/PrivateThreadRoot.class new file mode 100644 index 0000000000000000000000000000000000000000..64583a65400e51a6d5efdaada2479c2b58b29b71 GIT binary patch literal 1098 zcmbVLU2hUm5Iq;zU7$;gh^@3*iuD8b!F^Mcn!cErNQgBSdB4CdYzu62xh1Lp!=GWI ziNwSQe}F&AII~MCfqgK(%*>s=XU;jZyT5+__zB=S8hK=Kkb{F7hX))EIXvX>$iZU= zPXufohUQIQKr{t5LNiqQ!&GVRSSBzdB~WUe%bD~?ax(BwjSeP*BZ0hDm!s=;e4}uK zo5(RC?3ckLFvkK%^=5M&{V$zHMqM0$n zU@GvU)%)mQ$njYEXR>=C{h=|J8#O8Vl3qdSl&)K!JxW?WVXqB3@7aG`!M1^*UfL+iOam^159VQFoC; z-o*w77X=ra9JWvpc=mrT1&V)P=1KQl^-ScogBpDmS^9D;l$e#O5)((6xT8$;Q6>eH zZO}B_qFAESC!`~M&HE1FRp&@~wK>vW#hN4IRqQz|uj0&Mf2GSQ+@`oslpu->5#`7r zk1tc)C5s0%x=c@xi3TL@4z?+C#o|O%vAlFtu|O-Tct(t1kq+J5!QF*KpLiK*dEb!w zOhqh%eSicpof}_R&qz znz%7B(I4QCGTxb1(Xvmx+>hBg_nvb){rT(L4**-(%pilZ3l|j~JRR0_Sl6Md!-fvN zgQpIjF(hQ0f$cHap$r4Tkl#P$7u*kcujAK4C3~H12DhaI4@G+?WXKFeuPs!Kcj@+& z5-oWyMK2unjuPjA6axl_2f?A>dipU#>ZR<-@D)R?ymx)U%}(}?D-8x~x8EirGyAe9 zYQt_*s3YDS3F)_Z(BMkyetc+!Cz8l*@3-FjXT00x{yW}0e7F!DxEZx|>GH{C8`ad4# zs6YJQ#*8@{JL$^7&L1ynCApcY_lK$_UQ4aB7{_u$|A<-4;&~Rgkj-KW(^<^ukkjF| z4tf3I4hjq}Zfplb?&`f9G*3l~o(QpQV2Peth90Fudx!#6dCkIPB4RReF`4L?OpHt> zQk2tRq+pKXUFyCgY35bEuV7v&Lc*&?NP0ypg5?$M2)0*DMM!ybP6WrBbt9xd6EJ~$ z6jz82XskseT^c$~LkslfK8jeRxPp1CV}Uy0kX8d#I-RGiD=mPTN=rzZN{g^erDfQr z(n3@fv~hba98(_d-~PJWsI zFUNuxW5L!Wcr_Nh6brT|!E3SLN3r14B-oDyFUK0@Od7Ts3x13zF@~i-Oc=Hm3tlm| GR(}IwoZt@t literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/repository/QuotedTextItem.class b/target/classes/cz/kamma/fabka/httpserver/repository/QuotedTextItem.class new file mode 100644 index 0000000000000000000000000000000000000000..a9cdd11c2e04a3381727995f67283465a936a228 GIT binary patch literal 665 zcmbVJT}uK%6g{)6ZrYl<+V@L6rCYQQ5j_+^1VJE0C_;~;O}c)qjH6V)svrq^=m+$p zqC4wCls?76oqNyTbIzSRpI`4E0FJPkLLAFU#IT~ns)aQR>kJ7#QmsHTIOR4UbFa($ zO|PP)-)|l>#FThY)O6zaeRayPQ=ILZMM^bdo(Fe=AyfAI;%d~Z3t8p$E+I}iXz*^0 zOJCO!7*j2O$Z%M0Jb4}7>v8X%*E`&6DK!`hITq5BVh{{{705^LW)vuKSEbP|mFUr! z$)-?e(LD;q((FVG7PV*wL#h&tWJ6r|dYb%it$n=%8%b=~NWikO0E^+^A5$@8fA)D@ zZ;J+5o5fNM+qC{Ty@>%wdr;{`H!yd10>j;#zTkr~VgrUtv5`G8IpRU^*1Erwx%H RJ>guk`InrVOU{QDg>T=kgTnv- literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/repository/SettingsRepository.class b/target/classes/cz/kamma/fabka/httpserver/repository/SettingsRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..8f80eda8fa544d1e946073b3b5e24089ca66bc72 GIT binary patch literal 4969 zcmb7HZERE58Gf#F?R)LqKrke5!o#TLSCF7Atu)q7qFOa=>Yr+rHdUR}ACsp3nKZ4_+Uz;^+Sd-w zCI-moocFxv{W{P4oXgvvzV<5s594P(tV7a^c^DHvruaFoV%*2^go>1klL}7x;6+-& zgb$5K`jA0ZK~BXJjTl6}5kn}5pJF58D5*Fi#?vZx`EV3x8Zm&gLUK;QlRmWJDWQ8# z9Hr#*KCHmgD$c8TM#!HPKNnPdLB$uvp)V=;vV_KXD3J)q9vO)bMkM$nCymoaciPB| zcgKtQRAzjegr?ouOrdCGiU*B!$&|2QI35}rDW8km2Nw?y?CDE{MryapJK-4B6Lah+ zC&xyU!}+uX)tVK|yaY{52aH1DOg29z!LvP;NfmcU*cz-4?Or792PMe6vtx{Vek7GK zW2K2vGoLU<)0Frl*`$#^Xyj93T^7p46R83nMv_ldXuc%4b5p2IV%X6Nm@p_Jjg6)pRW z!U{@6NlT3G~JGSfPu+UdE3#`~Y(cY6!k<+6_hGfkLsEJ)u>oYn>&!jp2wJj;T!3XhU81VvcyJWZww{iK3Z+{+2d-BKHseNWqJ(kx4AIixINOv!Y% zK>BDh#>V2M95XHy1Z^#;HR%9-n+Sh?j$a#$g%q5~Tkn>aDOO@@4Tfu6m%mKJv8Hm? z!xl4IX^|RRg5lb@%KBKvFRyj{HdFYi6hu4Qr-LgO)^NSPW<8(l&YJZquDfd1tKk>a z$FqLHe%43= z{1$9wjEk`lOR$;J4*NVF;`gNZv&izx&gV>cc3CswyMaEpU~AcPkv4_rK!@M`Dm;-6 zzrrEf;a54tUWfPa`p&Lf@J&(2j>2#0FJ*0(F@oicWkpQ{fpP>2bo9_0ze1S%HY|uyXBSBSRg}pPs7iLz@vMnu}~sYZq+^hMbl`Zn!;(TJymt(4wmXnZt;s)sH=A{ zuS-{Sbs9?=aA>ac^0@M()-H5Y9Z|! zraAr^Z{cl@tMD#91F3wciPNNU~@X+=(?8Y9VY!yC02zw#Zf*X&}H*qJ;t00!MF!mEKs^r0E5g|^a z^q@JC7$pZNxd8va(X3$LQ*IQ0KGaN4E}?`qpvf>~`N;LU6a@9RX%b{NE58_p1M z7dT$Un|O!gAMrl^!f_)$!Y62xWT}zZXhT41la>?R4HmkOL+2%I#R9U7pSA+%<%yk~ z?IOndh?IVOj>t2Jy&UD2h`krMbCt-OB=)9=w%3TaH;J`(DF29P`vkpG8xGJ*R9cCc zw3`2G7AVj0Vhzlvy~9@AgLsryRoWWBW7g~v_wC({_^X9-4=vaz_XtopC|^@XxsV-U zuQXV@qHAPk=*Humxh-tM?EbUbP<7@VC3cF*V*xeR zC9k(INsJ15Co7ieeSQ(l&1hd$e zCf3;|Cb6PN=CY--)cE4CGeu9yx9MQD?O+m1V_Yq&tQvVr(D%|VQn@E8O-WPm@j`o% zC4f)kX*|nOCj_o=Jcf(b_%eQuUvS+_Dt*I}->}9z_#OVhH3*775%|aOo;BJH5aZJi zo@gc;>ExnSW^`ZqYf!SOxI>SOpbVfa3&TL66r z3A-Ihi@N!=^4Ss8-9R6|wLtBnwu=q4kH5UU3aB{~4}fIB_Br@x=v*+Y4OE+;IS0Jn se^ntX) Y%mf+cQ6NNT{7yE1l5>BO7eb504}W8OuK)l5 literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/repository/UserIconRepository.class b/target/classes/cz/kamma/fabka/httpserver/repository/UserIconRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..ca6e8f1693a84186492cc2c47292f4d671bc6374 GIT binary patch literal 3660 zcmbVPZERat8Gep^?R)LqB(0mTn>DKmusUfnuLZU?U7OX8+d9}u8z(7UfpBBrq&IbJ z*S<-UF>Okj6j01QJL^ktU={3tHO|Knwd*A*B7_=frP*Ps%?~MS0HkN75!p-IV0K z=e%F%ec$uE=brrizkm7wz)SdX5Mhi4;Kx_xkdi|>h%t<-xTN876NWL-gjbM}!(-#B(;ALT&J_(`lk{p3alESGnu@On(T;DZ_+}8Dm{IYqAPkrqW;JA`eNGOR z9OhKa3p7t<5)(7Y`1sgN=5k6Plv*&C&1l{%%ttd$*)GhVrc%6Es5oZ9nKtuFmOxu_ zj8>B~PuQvpx!LSgIWM5Ovx-#~(B<@!S*hGAmU9CBm+gY>oE10~*}K!zaQdbNlz1`6 zFj`Y~!5UjyoVCi6=4_svP^y?U^V4S8mg^c*ajx4HZkWox7QJCEE}GFfbM}TAz3wH}la6tFn}LGL}PiPl>X_JF-iR)GS-MjKg3St%B517;QnI zeMgJORj{&-T`Wi!)=ER@9&2JHYn5t3|B_6X$%s)NGreYSq|K6BI2G4b*eVuOoKSIt zCs{F}uG~g+~&ZcsP|zCnv+lb(C;ZM;R3z4wiH*?GbF0)k1+gVc{dbF5zEN8Idlr3{n;7qlZ!^yG9@m*Dj$b#6pfhUU97oI+s zno4BCk>LUU^yzpLKhW_*tm*g>-V!*l6W{pk0x?*RO9FcbmIB?bqU7#@G~2BqZ1loo zuB#W5fHxvH`FS5%+qad>J3)Tt?%qpeOra+DukQ z0ocJ z_u%OtxCd|l;5{gRAm4*y96PulaDbWz5unEw9O6$K2FUHN+G7yUb45??zQUc!ojvYM z?k#tY;)UwY-k;Mh0En?8fuH>bKC#h%mz&?^-glw=5x%j(kh%%YgCWIGlp~u6C@_>w z1O8#FG2}OV4-k|bT~I^Lsn*P^=m7$Qn`j}|zhU@7t(!PNcYec9f#Lta zwN{_PT9wuZHxZ(>YN!v;WT^LX=vPofZA0px(SFtN8|wS$7}B1?o3>F6xuX{HJJ9Ks z4(_AN@NY3W=Fm~gK?<3m;g>?+){w%a+N()P5p@lp6e(L{gZI%bHh4N;=G$qT@1!~2 zppvfRBbWZ0-$O6gy-15zSN^t3*YUASw?(U@qC@nEFy&piDh6D=U%GT1|8nWJ7?4z) z5GTcH%J~Kp7x*5V!)=$Y;}e%|iwiFGx)@qU50TJG{C3fzo8vQ#ZF`8x6SE;0QVen9_99DhXF$CQ1-`8Il(K{t0jCN5$? zq=-t1(FyK&lOK)aIKlnj<+Jij^sI8~tmEjs3b~(NKTP%Pw7SO}D1lvdegvmPcwy6@!V5AW>~m zkHvBXndaZvqQ8Ys=xKxO8lU%$glLnYxJ{sio_PlZ;*jr&4wju3I$Z5xwoScp_<+$2$??jvi!X+J{()0Mjztif}~bHj8Wzq{;eIIY>$A$O4pd64`c D@}wNW literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/repository/VoteStats.class b/target/classes/cz/kamma/fabka/httpserver/repository/VoteStats.class new file mode 100644 index 0000000000000000000000000000000000000000..9e837b95afe659e9afe49771d6c627f7eb96828a GIT binary patch literal 897 zcmb7BOHUI~6#ni!I?#?hMnKC$0X1nSkzrxN0^^3nq)CH|Y7>{YX88W?)z=|K_ zLK7Pk7XAQ#l;NB^6EW><+?>aE9^ZHF{r%_e7k~r2s=~w8JSup`<~f^fHai}6J-iUG zuhm#Uv;?dq6_}&+(^zGM1?@9=C8MEC22n?6aWXg*@RGDBSKh`+tltSV8?DxC>nzgj z3OGmUi4s_B$B8<=9Q9Q8QTB#}1nsmhhh3S)oEM;@Ph%3Y-|pW;=W;ZX(HGe}m(i)# z7o_n@Wl^Rs(s8WQ>^kbET6MJ4W0F}JDE*lavl~r1&8761uB+v6aluNXIjd5jYMP6l zYA3zS`s#hmA{YKr?D5n1SinmkbC~y0WmALi;}Lv;z5i1zu=McOA9`o1uZ{FBcIdA> z`bq}@6Hyc}m+?erJTa1YK?1Qzc8RiEScCxU;ZKNgV*)GOoWKrWPvC@gX9D*-RV^%Y z4{?BYI%ac?w)l8Vcho5gsPZP?MA!;t+|NpciCBweB35IYi1j#BU&UIXCL+)MZukS% zR|<0CiUF4miJvzVBmz%LhwbU%)zaaoq&e3`IfS1hz4#uYmGm5MzuP_+bR%H}6W_%C>H4Zf%YnpD`yGFHY*b=0- z5@L)~M$zFq1=1eS;YL-}>ZaycmTq!&r+s|G?D8&a5M8ujluX7QOP8>BP1r`Su8zpN z;<2vr^(`U~Zy2V*wPJ*~C~uhho>MF8);=p%DU!_BHC8=fmLdN8 z!5}Y-cGbN0Nv*J2jj115vBK0c=M7u8n!2UhWQ$QZ)q=ObVw{nVk#wd*VB5+-Iz!Hd zc-_&;66hZ(Qc61CZdoZ|zMt?8F27kqSM(D?+gH#FebBvNQ8v_MRmbi7a^!99c!4vm zLi6%{NVM8<2kgypwL5n0A;k)H$I|rIhVXs3!?5)%GJ-#2P{>)i;@JiEr|``URW z+E#}xy;g6M5rKw7xV6p!`bm8T*c-w$z1#|Y0|H12JLvRMI6-?-cmxSI=`3Re5jv+Z zO6x6(d?18O`*`*OQuYVr_ZJA9A(&k}Ln!+V;WI=|UF2Ls;}h+>8;5{=K|zE9O4IRQ~N_X}~KE_!(Co^t1i9SY9_t~s9P)8zRKIXO#Ia}=L< yOHFws?NVcYDbddy<;YlYc_dLkfiwj?Cojhk>~12{;X=q~_U=xGNV3EyWAQJSXIrBH literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/session/SessionManager.class b/target/classes/cz/kamma/fabka/httpserver/session/SessionManager.class new file mode 100644 index 0000000000000000000000000000000000000000..ca35e827de049225a42ebd1660108b04a630db50 GIT binary patch literal 4201 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 literal 0 HcmV?d00001 diff --git a/target/classes/cz/kamma/fabka/httpserver/web/Pages.class b/target/classes/cz/kamma/fabka/httpserver/web/Pages.class new file mode 100644 index 0000000000000000000000000000000000000000..2b3984623e88d27454b8e7304caee2c543bfe264 GIT binary patch literal 46019 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 + + + + + + + + kAmMa's Forum + + +