Compare commits

...

2 Commits

Author SHA1 Message Date
Radek Davidek
2207390eb2 some fixes 2026-03-20 13:34:12 +01:00
Radek Davidek
6f8f28fc28 refactor, some fixes 2026-03-20 13:34:08 +01:00
44 changed files with 297 additions and 143 deletions

4
.gitignore vendored
View File

@ -5,3 +5,7 @@ target
.classpath
.project
.claude
.vscode
.DS_Store
*.log
plans

View File

@ -43,7 +43,7 @@
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>cz.kamma.fabka.httpserver.HttpServerApplication</mainClass>
<mainClass>cz.kamma.fabka.app.HttpServerApplication</mainClass>
</transformer>
</transformers>
</configuration>

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver;
package cz.kamma.fabka.app;
import java.io.IOException;
import java.io.InputStream;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver;
package cz.kamma.fabka.app;
import java.io.IOException;
import java.net.InetSocketAddress;
@ -16,41 +16,41 @@ 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;
import cz.kamma.fabka.auth.AuthService;
import cz.kamma.fabka.auth.AuthenticatedUser;
import cz.kamma.fabka.auth.DatabaseAuthService;
import cz.kamma.fabka.http.ClasspathStaticFileHandler;
import cz.kamma.fabka.http.MultipartFormData;
import cz.kamma.fabka.http.RequestContext;
import cz.kamma.fabka.http.Responses;
import cz.kamma.fabka.http.Router;
import cz.kamma.fabka.repository.model.AttachmentData;
import cz.kamma.fabka.repository.model.ChatLine;
import cz.kamma.fabka.repository.ChatRepository;
import cz.kamma.fabka.repository.model.ChatVoteStats;
import cz.kamma.fabka.repository.model.ForumAttachment;
import cz.kamma.fabka.repository.model.ForumDetail;
import cz.kamma.fabka.repository.model.ForumDisplayView;
import cz.kamma.fabka.repository.model.ForumMessage;
import cz.kamma.fabka.repository.ForumRepository;
import cz.kamma.fabka.repository.model.ForumSummary;
import cz.kamma.fabka.repository.model.MemberProfile;
import cz.kamma.fabka.repository.MemberRepository;
import cz.kamma.fabka.repository.model.MessageRenderSettings;
import cz.kamma.fabka.repository.MysqlClientRepository;
import cz.kamma.fabka.repository.model.PrivateMessageItem;
import cz.kamma.fabka.repository.PrivateMessageRepository;
import cz.kamma.fabka.repository.model.PrivateMessageStats;
import cz.kamma.fabka.repository.model.PrivateThreadRoot;
import cz.kamma.fabka.repository.model.PrivateThreadSummary;
import cz.kamma.fabka.repository.model.QuotedTextItem;
import cz.kamma.fabka.repository.SettingsRepository;
import cz.kamma.fabka.repository.model.UserIcon;
import cz.kamma.fabka.repository.UserIconRepository;
import cz.kamma.fabka.repository.model.VoteStats;
import cz.kamma.fabka.session.SessionData;
import cz.kamma.fabka.session.SessionManager;
import cz.kamma.fabka.web.Pages;
public class HttpServerApplication {

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.auth;
package cz.kamma.fabka.auth;
public interface AuthService {
AuthenticatedUser authenticate(String username, String password);

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.auth;
package cz.kamma.fabka.auth;
public class AuthenticatedUser {
private final long userId;

View File

@ -1,6 +1,6 @@
package cz.kamma.fabka.httpserver.auth;
package cz.kamma.fabka.auth;
import cz.kamma.fabka.httpserver.crypto.Md5;
import cz.kamma.fabka.crypto.Md5;
import java.sql.Connection;
import java.sql.DriverManager;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.auth;
package cz.kamma.fabka.auth;
import java.util.Objects;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.crypto;
package cz.kamma.fabka.crypto;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.http;
package cz.kamma.fabka.http;
import java.io.IOException;
import java.io.InputStream;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.http;
package cz.kamma.fabka.http;
import com.sun.net.httpserver.HttpExchange;

View File

@ -1,9 +1,9 @@
package cz.kamma.fabka.httpserver.http;
package cz.kamma.fabka.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 cz.kamma.fabka.session.SessionData;
import cz.kamma.fabka.session.SessionManager;
import java.io.IOException;
import java.net.URLDecoder;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.http;
package cz.kamma.fabka.http;
import com.sun.net.httpserver.HttpExchange;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.http;
package cz.kamma.fabka.http;
@FunctionalInterface
public interface RouteHandler {

View File

@ -1,8 +1,8 @@
package cz.kamma.fabka.httpserver.http;
package cz.kamma.fabka.http;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import cz.kamma.fabka.httpserver.session.SessionManager;
import cz.kamma.fabka.session.SessionManager;
import java.io.IOException;
import java.util.Map;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.http;
package cz.kamma.fabka.http;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository;
import java.sql.Connection;
import java.sql.DriverManager;
@ -9,6 +9,10 @@ import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import cz.kamma.fabka.repository.model.ChatLine;
import cz.kamma.fabka.repository.model.ChatVoteStats;
public class ChatRepository {
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss");
@ -80,23 +84,26 @@ public class ChatRepository {
if (chatId <= 0) {
return new ChatVoteStats("", "");
}
long startTime = System.nanoTime();
ChatVoteStats result = null;
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(
if (rs.next()) {
result = new ChatVoteStats(
valueOrDefault(rs.getString("thumbup"), ""),
valueOrDefault(rs.getString("thumbdown"), "")
);
}
}
} catch (Exception ex) {
ex.printStackTrace();
return new ChatVoteStats("", "");
}
long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
System.out.println("[DEBUG] CHAT_VOTES_USERS_SQL: chatId=" + chatId + ", duration=" + durationMs + "ms");
return result != null ? result : new ChatVoteStats("", "");
}
public void addChatMessage(long userId, String message) {
@ -120,6 +127,7 @@ public class ChatRepository {
return lines;
}
int safeLimit = limit <= 0 ? 40 : limit;
long startTime = System.nanoTime();
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(CHAT_LINES_SQL)) {
ps.setLong(1, currentUserId);
@ -143,6 +151,8 @@ public class ChatRepository {
} catch (Exception ex) {
ex.printStackTrace();
}
long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
System.out.println("[DEBUG] CHAT_LINES_SQL: userId=" + currentUserId + ", limit=" + safeLimit + ", lines=" + lines.size() + ", duration=" + durationMs + "ms");
return lines;
}

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
@ -16,12 +16,31 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import cz.kamma.fabka.repository.model.AttachmentData;
import cz.kamma.fabka.repository.model.ForumAttachment;
import cz.kamma.fabka.repository.model.ForumDetail;
import cz.kamma.fabka.repository.model.ForumMessage;
import cz.kamma.fabka.repository.model.ForumSummary;
import cz.kamma.fabka.repository.model.QuotedTextItem;
import cz.kamma.fabka.repository.model.VoteStats;
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 FORUM_MESSAGES_SQL_DEBUG =
"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 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 =
@ -96,6 +115,7 @@ public class ForumRepository {
public List<ForumSummary> listActiveForums() {
List<ForumSummary> forums = new ArrayList<>();
long startTime = System.nanoTime();
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(FORUM_SQL);
ResultSet rs = ps.executeQuery()) {
@ -114,6 +134,8 @@ public class ForumRepository {
} catch (Exception ex) {
ex.printStackTrace();
}
long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
System.out.println("[DEBUG] FORUM_SQL: forums=" + forums.size() + ", duration=" + durationMs + "ms");
return forums;
}
@ -136,6 +158,7 @@ public class ForumRepository {
if (userId <= 0) {
return result;
}
long startTime = System.nanoTime();
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(NEW_MESSAGES_COUNT_SQL)) {
ps.setLong(1, userId);
@ -151,6 +174,8 @@ public class ForumRepository {
} catch (Exception ex) {
ex.printStackTrace();
}
long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
System.out.println("[DEBUG] NEW_MESSAGES_COUNT_SQL: userId=" + userId + ", forums=" + result.size() + ", duration=" + durationMs + "ms");
return result;
}
@ -158,24 +183,27 @@ public class ForumRepository {
if (forumId <= 0) {
return null;
}
long startTime = System.nanoTime();
ForumDetail result = 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(
if (rs.next()) {
result = new ForumDetail(
rs.getLong("id"),
rs.getString("name"),
rs.getString("description"),
rs.getString("countdown")
);
}
}
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
System.out.println("[DEBUG] FORUM_DETAIL_SQL: forumId=" + forumId + ", duration=" + durationMs + "ms");
return result;
}
public List<ForumMessage> listMessagesByForumId(long forumId) {
@ -183,46 +211,103 @@ public class ForumRepository {
if (forumId <= 0) {
return messages;
}
if (!loadMessages(messages, forumId, FORUM_MESSAGES_SQL)) {
long startTime = System.nanoTime();
if (!loadMessages(messages, forumId, FORUM_MESSAGES_SQL_DEBUG)) {
loadMessages(messages, forumId, FORUM_MESSAGES_SQL_NO_STICKY);
}
long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
System.out.println("[DEBUG] FORUM_MESSAGES_SQL: forumId=" + forumId + ", rows=" + messages.size() + ", duration=" + durationMs + "ms");
return messages;
}
private boolean loadMessages(List<ForumMessage> out, long forumId, String sql) {
out.clear();
long queryStartTime = System.nanoTime();
// First, fetch all messages and collect item IDs for batch attachment loading
List<Long> itemIds = new ArrayList<>();
List<Timestamp> createdTimes = new ArrayList<>();
List<Long> authorIds = new ArrayList<>();
List<String> usernames = new ArrayList<>();
List<String> cities = new ArrayList<>();
List<String> texts = new ArrayList<>();
List<Long> authorPostCounts = new ArrayList<>();
List<Integer> vvalues = new ArrayList<>();
List<Integer> voteYes = new ArrayList<>();
List<Integer> voteNo = new ArrayList<>();
List<String> voteYesUsers = new ArrayList<>();
List<String> voteNoUsers = new ArrayList<>();
List<Long> ids = new ArrayList<>();
List<Long> quoteItemIds = new ArrayList<>();
List<Integer> stickies = new ArrayList<>();
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");
ids.add(rs.getLong("id"));
itemIds.add(rs.getLong("id"));
createdTimes.add(rs.getTimestamp("created"));
authorIds.add(rs.getLong("author_id"));
usernames.add(rs.getString("username"));
cities.add(rs.getString("city"));
authorPostCounts.add(rs.getLong("author_posts"));
texts.add(rs.getString("text"));
vvalues.add(rs.getInt("vvalue"));
voteYes.add(rs.getInt("vote_yes"));
voteNo.add(rs.getInt("vote_no"));
voteYesUsers.add(rs.getString("vote_yes_users"));
voteNoUsers.add(rs.getString("vote_no_users"));
quoteItemIds.add(rs.getLong("quoteitem"));
stickies.add(rs.getInt("sticky"));
}
}
// Batch load attachments for all items
long attachmentsStartTime = System.nanoTime();
Map<Long, List<ForumAttachment>> attachmentsByItemId = new LinkedHashMap<>();
if (!itemIds.isEmpty()) {
attachmentsByItemId = loadAttachmentsBatch(itemIds);
}
long attachmentsDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - attachmentsStartTime);
System.out.println("[DEBUG] Batch attachment load: " + attachmentsDurationMs + "ms, " + itemIds.size() + " items");
// Build message objects
for (int i = 0; i < ids.size(); i++) {
long messageId = ids.get(i);
long quoteItemId = quoteItemIds.get(i);
QuotedTextItem quotedItem = quoteItemId > 0 ? findQuotedItem(quoteItemId) : null;
List<ForumAttachment> attachments = attachmentsByItemId.getOrDefault(messageId, new ArrayList<>());
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"), ""),
messageId,
authorIds.get(i),
valueOrDefault(usernames.get(i), "N/A"),
formatTs(createdTimes.get(i)),
valueOrDefault(cities.get(i), ""),
authorPostCounts.get(i),
createdTimes.get(i) == null ? 0L : createdTimes.get(i).getTime(),
vvalues.get(i),
voteYes.get(i),
voteNo.get(i),
valueOrDefault(voteYesUsers.get(i), ""),
valueOrDefault(voteNoUsers.get(i), ""),
quoteItemId,
quotedItem,
formatTs(createdTs),
valueOrDefault(rs.getString("text"), ""),
listAttachmentsByForumItemId(rs.getLong("id")),
rs.getInt("sticky") == 1
formatTs(createdTimes.get(i)),
valueOrDefault(texts.get(i), ""),
attachments,
stickies.get(i) == 1
));
}
}
long totalDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - queryStartTime);
System.out.println("[DEBUG] loadMessages completed: total=" + totalDurationMs + "ms, rows=" + out.size());
return true;
} catch (Exception ex) {
System.err.println("[DEBUG] loadMessages ERROR: " + ex.getMessage());
ex.printStackTrace();
return false;
}
}
@ -366,22 +451,25 @@ public class ForumRepository {
if (messageId <= 0) {
return null;
}
long startTime = System.nanoTime();
QuotedTextItem result = 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(
if (rs.next()) {
result = new QuotedTextItem(
valueOrDefault(rs.getString("username"), "N/A"),
valueOrDefault(rs.getString("text"), "")
);
}
}
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
System.out.println("[DEBUG] QUOTED_ITEM_SQL: messageId=" + messageId + ", duration=" + durationMs + "ms");
return result;
}
public long addReply(long forumId, long userId, String message, Long quoteItem) {
@ -501,6 +589,7 @@ public class ForumRepository {
if (forumItemId <= 0) {
return out;
}
long startTime = System.nanoTime();
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(ATTACHMENTS_SQL)) {
ps.setLong(1, forumItemId);
@ -517,6 +606,10 @@ public class ForumRepository {
} catch (Exception ex) {
ex.printStackTrace();
}
long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
if (durationMs > 1) {
System.out.println("[DEBUG] ATTACHMENTS_SQL: forumItemId=" + forumItemId + ", count=" + out.size() + ", duration=" + durationMs + "ms");
}
return out;
}
@ -551,6 +644,36 @@ public class ForumRepository {
}
}
private Map<Long, List<ForumAttachment>> loadAttachmentsBatch(List<Long> itemIds) {
Map<Long, List<ForumAttachment>> result = new LinkedHashMap<>();
if (itemIds.isEmpty()) {
return result;
}
String sql = "SELECT forumitemsid, id, name, ispicture, width, size FROM attachments WHERE forumitemsid IN (" +
String.join(",", java.util.Collections.nCopies(itemIds.size(), "?")) + ") ORDER BY forumitemsid, id";
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(sql)) {
for (int i = 0; i < itemIds.size(); i++) {
ps.setLong(i + 1, itemIds.get(i));
}
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
long forumItemId = rs.getLong("forumitemsid");
ForumAttachment attachment = new ForumAttachment(
rs.getLong("id"),
valueOrDefault(rs.getString("name"), "attachment"),
rs.getInt("ispicture") == 1,
rs.getInt("width")
);
result.computeIfAbsent(forumItemId, k -> new ArrayList<>()).add(attachment);
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return result;
}
private static String valueOrDefault(String value, String defaultValue) {
return value == null || value.isBlank() ? defaultValue : value;
}

View File

@ -1,6 +1,6 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository;
import cz.kamma.fabka.httpserver.crypto.Md5;
import cz.kamma.fabka.crypto.Md5;
import java.sql.Connection;
import java.sql.DriverManager;
@ -10,6 +10,8 @@ import java.sql.Timestamp;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import cz.kamma.fabka.repository.model.MemberProfile;
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");

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository;
import java.sql.Connection;
import java.sql.DriverManager;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository;
import java.sql.Connection;
import java.sql.DriverManager;
@ -10,6 +10,12 @@ import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import cz.kamma.fabka.repository.model.PrivateMessageItem;
import cz.kamma.fabka.repository.model.PrivateMessageStats;
import cz.kamma.fabka.repository.model.PrivateThreadRoot;
import cz.kamma.fabka.repository.model.PrivateThreadSummary;
public class PrivateMessageRepository {
private static final DateTimeFormatter DATE_TIME = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
@ -51,6 +57,7 @@ public class PrivateMessageRepository {
"where (m.to_user=? or m.from_user=?) and m.deleted=0 and m.reply_to is null " +
"order by " + order;
long startTime = System.nanoTime();
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, userId);
@ -72,6 +79,8 @@ public class PrivateMessageRepository {
} catch (Exception ex) {
ex.printStackTrace();
}
long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
System.out.println("[DEBUG] PM listThreads: userId=" + userId + ", threads=" + out.size() + ", duration=" + durationMs + "ms");
return out;
}

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository;
import java.sql.Connection;
import java.sql.DriverManager;
@ -7,6 +7,8 @@ import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;
import cz.kamma.fabka.repository.model.MessageRenderSettings;
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=?";

View File

@ -1,10 +1,12 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import cz.kamma.fabka.repository.model.UserIcon;
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 (?,?,?)";

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class AttachmentData {
private final String name;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class ChatLine {
private final long id;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class ChatVoteStats {
private final String thumbUpUsers;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class ForumAttachment {
private final long id;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class ForumDetail {
private final long id;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
import java.util.List;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
import java.util.List;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class ForumSummary {
private final long id;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class MemberProfile {
private final long id;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class MessageRenderSettings {
private final String youtubeSnippet;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class PrivateMessageItem {
private final long id;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class PrivateMessageStats {
private final int unread;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class PrivateThreadRoot {
private final long rootId;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class PrivateThreadSummary {
private final long id;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class QuotedTextItem {
private final String author;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class UserIcon {
private final byte[] data;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.repository;
package cz.kamma.fabka.repository.model;
public class VoteStats {
private final int yes;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.session;
package cz.kamma.fabka.session;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.session;
package cz.kamma.fabka.session;
import java.time.Duration;
import java.util.ArrayList;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.web;
package cz.kamma.fabka.web;
import java.util.StringTokenizer;

View File

@ -1,4 +1,4 @@
package cz.kamma.fabka.httpserver.web;
package cz.kamma.fabka.web;
import java.io.IOException;
import java.io.InputStream;
@ -8,19 +8,21 @@ 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;
import cz.kamma.fabka.repository.MysqlClientRepository;
import cz.kamma.fabka.repository.model.AttachmentData;
import cz.kamma.fabka.repository.model.ForumAttachment;
import cz.kamma.fabka.repository.model.ForumDetail;
import cz.kamma.fabka.repository.model.ForumDisplayView;
import cz.kamma.fabka.repository.model.ForumMessage;
import cz.kamma.fabka.repository.model.ForumSummary;
import cz.kamma.fabka.repository.model.MemberProfile;
import cz.kamma.fabka.repository.model.MessageRenderSettings;
import cz.kamma.fabka.repository.model.PrivateMessageItem;
import cz.kamma.fabka.repository.model.PrivateMessageStats;
import cz.kamma.fabka.repository.model.PrivateThreadRoot;
import cz.kamma.fabka.repository.model.PrivateThreadSummary;
import cz.kamma.fabka.repository.model.QuotedTextItem;
import cz.kamma.fabka.repository.model.UserIcon;
public final class Pages {
private static final String LOGIN_TEMPLATE = readTemplate("webapp/login.html");