kafkaTemplate() {
+ return new KafkaTemplate<>(producerFactory());
+ }
+}
diff --git a/src/main/java/com/example/chatapplication/utils/Destinations.java b/src/main/java/com/example/chatapplication/utils/Destinations.java
new file mode 100644
index 0000000..64a2f57
--- /dev/null
+++ b/src/main/java/com/example/chatapplication/utils/Destinations.java
@@ -0,0 +1,17 @@
+package com.example.chatapplication.utils;
+
+public class Destinations {
+ public static class Room {
+ public static String publicMessages(String roomId) {
+ return "/topic/" + roomId + ".public.messages";
+ }
+
+ public static String privateMessage(String roomId) {
+ return "/queue/" + roomId + ".private.messages";
+ }
+
+ public static String connectedUsers(String roomId) {
+ return "/topic/" + roomId + ".connected.users";
+ }
+ }
+}
diff --git a/src/main/java/com/example/chatapplication/utils/SystemMessages.java b/src/main/java/com/example/chatapplication/utils/SystemMessages.java
new file mode 100644
index 0000000..d0c79c7
--- /dev/null
+++ b/src/main/java/com/example/chatapplication/utils/SystemMessages.java
@@ -0,0 +1,20 @@
+package com.example.chatapplication.utils;
+
+import com.example.chatapplication.chatroom.model.Message;
+import com.example.chatapplication.chatroom.model.MessageBuilder;
+
+public class SystemMessages {
+ public static final Message welcome(String roomId, String username) {
+ return new MessageBuilder()
+ .newMessage()
+ .toRoomId(roomId)
+ .systemMessage().withContent(username + " joined :D");
+ }
+
+ public static final Message goodbye(String roomId, String username) {
+ return new MessageBuilder()
+ .newMessage()
+ .toRoomId(roomId)
+ .systemMessage().withContent(username + " left :(");
+ }
+}
diff --git a/src/main/java/com/example/chatapplication/utils/SystemUsers.java b/src/main/java/com/example/chatapplication/utils/SystemUsers.java
new file mode 100644
index 0000000..958e86b
--- /dev/null
+++ b/src/main/java/com/example/chatapplication/utils/SystemUsers.java
@@ -0,0 +1,13 @@
+package com.example.chatapplication.utils;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@AllArgsConstructor
+public enum SystemUsers {
+ ADMIN("admin");
+ private String username;
+
+}
diff --git a/src/main/resources/application-docker.properties b/src/main/resources/application-docker.properties
new file mode 100644
index 0000000..79d8a5a
--- /dev/null
+++ b/src/main/resources/application-docker.properties
@@ -0,0 +1,34 @@
+# OracleDB connection settings
+spring.datasource.url=jdbc:oracle:thin:@host.docker.internal:1521:orcl
+spring.datasource.username=dev
+spring.datasource.password=123456
+spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
+
+# HikariCP settings
+spring.datasource.hikari.minimumIdle=5
+spring.datasource.hikari.maximumPoolSize=20
+spring.datasource.hikari.idleTimeout=30000
+spring.datasource.hikari.maxLifetime=2000000
+spring.datasource.hikari.connectionTimeout=30000
+spring.datasource.hikari.poolName=HikariPoolChatApplication
+
+# JPA settings
+spring.jpa.hibernate.ddl-auto=update
+
+# Redis config
+spring.redis.host=host.docker.internal
+spring.redis.port=6381
+spring.data.redis.repositories.enabled=true
+
+# Kafka
+spring.kafka.bootstrap-servers=host.docker.internal:9092
+spring.kafka.consumer.auto-offset-reset=earliest
+
+# Session
+spring.session.store-type=redis
+
+# Relay host and port
+chat.app.relay.host=host.docker.internal
+chat.app.relay.port=61613
+chat.app.relay.username=admin
+chat.app.relay.password=admin
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..c244014
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,34 @@
+# OracleDB connection settings
+spring.datasource.url=jdbc:oracle:thin:@localhost:1521:orcl
+spring.datasource.username=dev
+spring.datasource.password=123456
+spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
+
+# HikariCP settings
+spring.datasource.hikari.minimumIdle=5
+spring.datasource.hikari.maximumPoolSize=20
+spring.datasource.hikari.idleTimeout=30000
+spring.datasource.hikari.maxLifetime=2000000
+spring.datasource.hikari.connectionTimeout=30000
+spring.datasource.hikari.poolName=HikariPoolChatApplication
+
+# JPA settings
+spring.jpa.hibernate.ddl-auto=update
+
+# Redis config
+spring.redis.host=localhost
+spring.redis.port=6381
+spring.data.redis.repositories.enabled=true
+
+# Kafka
+spring.kafka.bootstrap-servers=host.docker.internal:9092
+spring.kafka.consumer.auto-offset-reset=earliest
+
+# Session
+spring.session.store-type=redis
+
+# Relay host and port
+chat.app.relay.host=localhost
+chat.app.relay.port=61613
+chat.app.relay.username=admin
+chat.app.relay.password=admin
\ No newline at end of file
diff --git a/src/main/resources/static/css/main.css b/src/main/resources/static/css/main.css
new file mode 100644
index 0000000..7a1d716
--- /dev/null
+++ b/src/main/resources/static/css/main.css
@@ -0,0 +1,19 @@
+.full-height {
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+
+body {
+ font-family: "Poppins", Arial, Helvetica, sans-serif;
+ font-size: 13px;
+ color: #495057;
+}
+
+.online {
+ color: #2ecc71;
+}
+
+.offline {
+ color: dimgray;
+}
\ No newline at end of file
diff --git a/src/main/resources/static/images/avatar.png b/src/main/resources/static/images/avatar.png
new file mode 100644
index 0000000..e082974
Binary files /dev/null and b/src/main/resources/static/images/avatar.png differ
diff --git a/src/main/resources/static/images/github.png b/src/main/resources/static/images/github.png
new file mode 100644
index 0000000..f7bdf96
Binary files /dev/null and b/src/main/resources/static/images/github.png differ
diff --git a/src/main/resources/static/images/networking.png b/src/main/resources/static/images/networking.png
new file mode 100644
index 0000000..0b6faf8
Binary files /dev/null and b/src/main/resources/static/images/networking.png differ
diff --git a/src/main/resources/static/images/redis_icon.png b/src/main/resources/static/images/redis_icon.png
new file mode 100644
index 0000000..e94fb3b
Binary files /dev/null and b/src/main/resources/static/images/redis_icon.png differ
diff --git a/src/main/resources/static/images/sell.png b/src/main/resources/static/images/sell.png
new file mode 100644
index 0000000..8217d1f
Binary files /dev/null and b/src/main/resources/static/images/sell.png differ
diff --git a/src/main/resources/static/images/welcome-back.png b/src/main/resources/static/images/welcome-back.png
new file mode 100644
index 0000000..32c08db
Binary files /dev/null and b/src/main/resources/static/images/welcome-back.png differ
diff --git a/src/main/resources/static/js/join-room.js b/src/main/resources/static/js/join-room.js
new file mode 100644
index 0000000..e062550
--- /dev/null
+++ b/src/main/resources/static/js/join-room.js
@@ -0,0 +1,149 @@
+let socket;
+let stompClient;
+let roomId;
+let chatBox;
+let messageField;
+let btnSend;
+let messageForm;
+let username;
+
+$(document).ready(function () {
+ roomId = $("#roomId").text();
+ chatBox = $("#chatBox");
+ messageField = $("#messageField");
+ btnSend = $("#btnSend");
+ messageForm = $("#messageForm");
+ username = $("#username");
+
+ console.log("Room ID: " + roomId);
+ connect();
+ messageForm.on("submit", function (e) {
+ e.preventDefault();
+ sendMessage();
+ });
+});
+function connect() {
+ console.log("Connecting to websocket....")
+ socket = new SockJS("/ws")
+ stompClient = Stomp.over(socket);
+ stompClient.connect(
+ {'roomId': roomId},
+ stompSuccess,
+ stompFailure
+ );
+}
+
+function stompSuccess() {
+ console.log("Successfully connect to websocket");
+ stompClient.subscribe("/room/connected.users", updateConnectedUsers());
+ stompClient.subscribe('/room/old.messages', oldMessages);
+ stompClient.subscribe('/topic/' + roomId + '.public.messages', publicMessages);
+}
+
+function stompFailure() {
+ console.log("Failed to connect to websocket");
+}
+
+function updateConnectedUsers(response) {
+ console.log("Getting connected user successfully");
+ // let connectedUsers = JSON.parse(response.body);
+ // console.log(connectedUsers);
+}
+
+function publicMessages(message) {
+ console.log("Handling public message");
+ let publicMessage = JSON.parse(message.body);
+ appendMessage(publicMessage);
+}
+
+function appendMessage(message) {
+ if(message.fromUser == "admin") {
+ appendPublicMessage(message);
+ return;
+ }
+ let usernameValue = username.text();
+ let formattedTime = $.format.date(message.dateTime, 'dd/MM/yyyy HH:mm');
+ if(message.fromUser == usernameValue) {
+ chatBox.append(
+ `
+
+
+
+
+
+
+
+
${message.fromUser}
+
+
${message.content}
+
+
${formattedTime}
+
+
+
+
`
+ );
+ } else {
+ chatBox.append(
+ `
+
+
+
+
+
+
${message.fromUser}
+
+
${message.content}
+
+
${formattedTime}
+
+
+
+
+
+
+
`
+ )
+ }
+ scrollDownMessagesPanel();
+}
+function appendPublicMessage(publicMessage) {
+ chatBox.append(
+ 'System: '+publicMessage.content+'
'
+ );
+ scrollDownMessagesPanel();
+}
+function oldMessages(response) {
+ console.log("Handling old message");
+ let messages = JSON.parse(response.body);
+ $.each(messages, function (index, message) {
+ appendMessage(message);
+ });
+}
+
+function sendMessage() {
+ if(messageField.val().trim().length === 0 ) {
+ messageField.focus();
+ return;
+ }
+
+ let messageObject = {
+ 'username': null,
+ 'roomId': null,
+ 'dateTime': null,
+ 'fromUser': null,
+ 'content': messageField.val(),
+ 'toUser': null
+ };
+ stompClient.send('/room/send.message',{}, JSON.stringify(messageObject));
+ messageField.val('').focus();
+
+}
+
+function emptyInputMessage() {
+ messageField.val("");
+}
+
+function scrollDownMessagesPanel() {
+ chatBox.animate({"scrollTop": chatBox[0].scrollHeight}, "fast");
+}
\ No newline at end of file
diff --git a/src/main/resources/static/js/jquery-dateformat.js b/src/main/resources/static/js/jquery-dateformat.js
new file mode 100644
index 0000000..5f50e97
--- /dev/null
+++ b/src/main/resources/static/js/jquery-dateformat.js
@@ -0,0 +1,495 @@
+var DateFormat = {};
+
+(function($) {
+ var daysInWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+ var shortDaysInWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+ var shortMonthsInYear = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ var longMonthsInYear = ['January', 'February', 'March', 'April', 'May', 'June',
+ 'July', 'August', 'September', 'October', 'November', 'December'];
+ var shortMonthsToNumber = { 'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
+ 'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12' };
+
+ var YYYYMMDD_MATCHER = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.?\d{0,3}[Z\-+]?(\d{2}:?\d{2})?/;
+
+ $.format = (function() {
+ function numberToLongDay(value) {
+ // 0 to Sunday
+ // 1 to Monday
+ return daysInWeek[parseInt(value, 10)] || value;
+ }
+
+ function numberToShortDay(value) {
+ // 0 to Sun
+ // 1 to Mon
+ return shortDaysInWeek[parseInt(value, 10)] || value;
+ }
+
+ function numberToShortMonth(value) {
+ // 1 to Jan
+ // 2 to Feb
+ var monthArrayIndex = parseInt(value, 10) - 1;
+ return shortMonthsInYear[monthArrayIndex] || value;
+ }
+
+ function numberToLongMonth(value) {
+ // 1 to January
+ // 2 to February
+ var monthArrayIndex = parseInt(value, 10) - 1;
+ return longMonthsInYear[monthArrayIndex] || value;
+ }
+
+ function shortMonthToNumber(value) {
+ // Jan to 01
+ // Feb to 02
+ return shortMonthsToNumber[value] || value;
+ }
+
+ function parseTime(value) {
+ // 10:54:50.546
+ // => hour: 10, minute: 54, second: 50, millis: 546
+ // 10:54:50
+ // => hour: 10, minute: 54, second: 50, millis: ''
+ var time = value,
+ hour,
+ minute,
+ second,
+ millis = '',
+ delimited,
+ timeArray;
+
+ if(time.indexOf('.') !== -1) {
+ delimited = time.split('.');
+ // split time and milliseconds
+ time = delimited[0];
+ millis = delimited[delimited.length - 1];
+ }
+
+ timeArray = time.split(':');
+
+ if(timeArray.length >= 3) {
+ hour = timeArray[0];
+ minute = timeArray[1];
+ // '20 GMT-0200 (BRST)'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
+ // => 20
+ // '20Z'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
+ // => 20
+ second = timeArray[2].replace(/\s.+/, '').replace(/[a-z]/gi, '');
+ // '01:10:20 GMT-0200 (BRST)'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
+ // => 01:10:20
+ // '01:10:20Z'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
+ // => 01:10:20
+ time = time.replace(/\s.+/, '').replace(/[a-z]/gi, '');
+ return {
+ time: time,
+ hour: hour,
+ minute: minute,
+ second: second,
+ millis: millis
+ };
+ }
+
+ return { time : '', hour : '', minute : '', second : '', millis : '' };
+ }
+
+
+ function padding(value, length) {
+ var paddingCount = length - String(value).length;
+ for(var i = 0; i < paddingCount; i++) {
+ value = '0' + value;
+ }
+ return value;
+ }
+
+ return {
+
+ parseDate: function(value) {
+ var values,
+ subValues;
+
+ var parsedDate = {
+ date: null,
+ year: null,
+ month: null,
+ dayOfMonth: null,
+ dayOfWeek: null,
+ time: null
+ };
+
+ if(typeof value == 'number') {
+ return this.parseDate(new Date(value));
+ } else if(typeof value.getFullYear == 'function') {
+ parsedDate.year = String(value.getFullYear());
+ // d = new Date(1900, 1, 1) // 1 for Feb instead of Jan.
+ // => Thu Feb 01 1900 00:00:00
+ parsedDate.month = String(value.getMonth() + 1);
+ parsedDate.dayOfMonth = String(value.getDate());
+ parsedDate.time = parseTime(value.toTimeString() + '.' + value.getMilliseconds());
+ } else if(value.search(YYYYMMDD_MATCHER) != -1) {
+ /* 2009-04-19T16:11:05+02:00 || 2009-04-19T16:11:05Z */
+ values = value.split(/[T\+-]/);
+ parsedDate.year = values[0];
+ parsedDate.month = values[1];
+ parsedDate.dayOfMonth = values[2];
+ parsedDate.time = parseTime(values[3].split('.')[0]);
+ } else {
+ values = value.split(' ');
+ if(values.length === 6 && isNaN(values[5])) {
+ // values[5] == year
+ /*
+ * This change is necessary to make `Mon Apr 28 2014 05:30:00 GMT-0300` work
+ * like `case 7`
+ * otherwise it will be considered like `Wed Jan 13 10:43:41 CET 2010
+ * Fixes: https://github.com/phstc/jquery-dateFormat/issues/64
+ */
+ values[values.length] = '()';
+ }
+ switch (values.length) {
+ case 6:
+ /* Wed Jan 13 10:43:41 CET 2010 */
+ parsedDate.year = values[5];
+ parsedDate.month = shortMonthToNumber(values[1]);
+ parsedDate.dayOfMonth = values[2];
+ parsedDate.time = parseTime(values[3]);
+ break;
+ case 2:
+ /* 2009-12-18 10:54:50.546 */
+ subValues = values[0].split('-');
+ parsedDate.year = subValues[0];
+ parsedDate.month = subValues[1];
+ parsedDate.dayOfMonth = subValues[2];
+ parsedDate.time = parseTime(values[1]);
+ break;
+ case 7:
+ /* Tue Mar 01 2011 12:01:42 GMT-0800 (PST) */
+ case 9:
+ /* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0800 (China Standard Time) */
+ case 10:
+ /* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0200 (W. Europe Daylight Time) */
+ parsedDate.year = values[3];
+ /* edited by Andrey, for Mon 18 Apr 2016 -//-: '[Day] [Month]' format (russian) */
+ var parsedVal1 = parseInt(values[1]);
+ var parsedVal2 = parseInt(values[2]);
+ if (parsedVal1 && !parsedVal2) {
+ parsedDate.month = shortMonthToNumber(values[2]);
+ parsedDate.dayOfMonth = values[1];
+ } else {
+ parsedDate.month = shortMonthToNumber(values[1]);
+ parsedDate.dayOfMonth = values[2];
+ }
+ parsedDate.time = parseTime(values[4]);
+ break;
+ case 1:
+ /* added by Jonny, for 2012-02-07CET00:00:00 (Doctrine Entity -> Json Serializer) */
+ subValues = values[0].split('');
+ parsedDate.year = subValues[0] + subValues[1] + subValues[2] + subValues[3];
+ parsedDate.month = subValues[5] + subValues[6];
+ parsedDate.dayOfMonth = subValues[8] + subValues[9];
+ parsedDate.time = parseTime(subValues[13] + subValues[14] + subValues[15] + subValues[16] + subValues[17] + subValues[18] + subValues[19] + subValues[20]);
+ break;
+ default:
+ return null;
+ }
+ }
+
+ if(parsedDate.time) {
+ parsedDate.date = new Date(parsedDate.year, parsedDate.month - 1, parsedDate.dayOfMonth, parsedDate.time.hour, parsedDate.time.minute, parsedDate.time.second, parsedDate.time.millis);
+ } else {
+ parsedDate.date = new Date(parsedDate.year, parsedDate.month - 1, parsedDate.dayOfMonth);
+ }
+
+ parsedDate.dayOfWeek = String(parsedDate.date.getDay());
+
+ return parsedDate;
+ },
+
+ date : function(value, format) {
+ try {
+ var parsedDate = this.parseDate(value);
+
+ if(parsedDate === null) {
+ return value;
+ }
+
+ var year = parsedDate.year,
+ month = parsedDate.month,
+ dayOfMonth = parsedDate.dayOfMonth,
+ dayOfWeek = parsedDate.dayOfWeek,
+ time = parsedDate.time;
+ var hour;
+
+ var pattern = '',
+ retValue = '',
+ unparsedRest = '',
+ inQuote = false;
+
+ /* Issue 1 - variable scope issue in format.date (Thanks jakemonO) */
+ for(var i = 0; i < format.length; i++) {
+ var currentPattern = format.charAt(i);
+ // Look-Ahead Right (LALR)
+ var nextRight = format.charAt(i + 1);
+
+ if (inQuote) {
+ if (currentPattern == "'") {
+ retValue += (pattern === '') ? "'" : pattern;
+ pattern = '';
+ inQuote = false;
+ } else {
+ pattern += currentPattern;
+ }
+ continue;
+ }
+ pattern += currentPattern;
+ unparsedRest = '';
+ switch (pattern) {
+ case 'ddd':
+ retValue += numberToLongDay(dayOfWeek);
+ pattern = '';
+ break;
+ case 'dd':
+ if(nextRight === 'd') {
+ break;
+ }
+ retValue += padding(dayOfMonth, 2);
+ pattern = '';
+ break;
+ case 'd':
+ if(nextRight === 'd') {
+ break;
+ }
+ retValue += parseInt(dayOfMonth, 10);
+ pattern = '';
+ break;
+ case 'D':
+ if(dayOfMonth == 1 || dayOfMonth == 21 || dayOfMonth == 31) {
+ dayOfMonth = parseInt(dayOfMonth, 10) + 'st';
+ } else if(dayOfMonth == 2 || dayOfMonth == 22) {
+ dayOfMonth = parseInt(dayOfMonth, 10) + 'nd';
+ } else if(dayOfMonth == 3 || dayOfMonth == 23) {
+ dayOfMonth = parseInt(dayOfMonth, 10) + 'rd';
+ } else {
+ dayOfMonth = parseInt(dayOfMonth, 10) + 'th';
+ }
+ retValue += dayOfMonth;
+ pattern = '';
+ break;
+ case 'MMMM':
+ retValue += numberToLongMonth(month);
+ pattern = '';
+ break;
+ case 'MMM':
+ if(nextRight === 'M') {
+ break;
+ }
+ retValue += numberToShortMonth(month);
+ pattern = '';
+ break;
+ case 'MM':
+ if(nextRight === 'M') {
+ break;
+ }
+ retValue += padding(month, 2);
+ pattern = '';
+ break;
+ case 'M':
+ if(nextRight === 'M') {
+ break;
+ }
+ retValue += parseInt(month, 10);
+ pattern = '';
+ break;
+ case 'y':
+ case 'yyy':
+ if(nextRight === 'y') {
+ break;
+ }
+ retValue += pattern;
+ pattern = '';
+ break;
+ case 'yy':
+ if(nextRight === 'y') {
+ break;
+ }
+ retValue += String(year).slice(-2);
+ pattern = '';
+ break;
+ case 'yyyy':
+ retValue += year;
+ pattern = '';
+ break;
+ case 'HH':
+ retValue += padding(time.hour, 2);
+ pattern = '';
+ break;
+ case 'H':
+ if(nextRight === 'H') {
+ break;
+ }
+ retValue += parseInt(time.hour, 10);
+ pattern = '';
+ break;
+ case 'hh':
+ /* time.hour is '00' as string == is used instead of === */
+ hour = (parseInt(time.hour, 10) === 0 ? 12 : time.hour < 13 ? time.hour
+ : time.hour - 12);
+ retValue += padding(hour, 2);
+ pattern = '';
+ break;
+ case 'h':
+ if(nextRight === 'h') {
+ break;
+ }
+ hour = (parseInt(time.hour, 10) === 0 ? 12 : time.hour < 13 ? time.hour
+ : time.hour - 12);
+ retValue += parseInt(hour, 10);
+ // Fixing issue https://github.com/phstc/jquery-dateFormat/issues/21
+ // retValue = parseInt(retValue, 10);
+ pattern = '';
+ break;
+ case 'mm':
+ retValue += padding(time.minute, 2);
+ pattern = '';
+ break;
+ case 'm':
+ if(nextRight === 'm') {
+ break;
+ }
+ retValue += parseInt(time.minute,10);
+ pattern = '';
+ break;
+ case 'ss':
+ /* ensure only seconds are added to the return string */
+ retValue += padding(time.second.substring(0, 2), 2);
+ pattern = '';
+ break;
+ case 's':
+ if(nextRight === 's') {
+ break;
+ }
+ retValue += parseInt(time.second,10);
+ pattern = '';
+ break;
+ case 'S':
+ case 'SS':
+ if(nextRight === 'S') {
+ break;
+ }
+ retValue += pattern;
+ pattern = '';
+ break;
+ case 'SSS':
+ retValue += padding(time.millis.substring(0, 3), 3);
+ pattern = '';
+ break;
+ case 'a':
+ retValue += time.hour >= 12 ? 'PM' : 'AM';
+ pattern = '';
+ break;
+ case 'p':
+ retValue += time.hour >= 12 ? 'p.m.' : 'a.m.';
+ pattern = '';
+ break;
+ case 'E':
+ retValue += numberToShortDay(dayOfWeek);
+ pattern = '';
+ break;
+ case "'":
+ pattern = '';
+ inQuote = true;
+ break;
+ default:
+ retValue += currentPattern;
+ pattern = '';
+ break;
+ }
+ }
+ retValue += unparsedRest;
+ return retValue;
+ } catch (e) {
+ if(console && console.log) {
+ console.log(e);
+ }
+ return value;
+ }
+ },
+ /*
+ * JavaScript Pretty Date
+ * Copyright (c) 2011 John Resig (ejohn.org)
+ * Licensed under the MIT and GPL licenses.
+ *
+ * Takes an ISO time and returns a string representing how long ago the date
+ * represents
+ *
+ * ('2008-01-28T20:24:17Z') // => '2 hours ago'
+ * ('2008-01-27T22:24:17Z') // => 'Yesterday'
+ * ('2008-01-26T22:24:17Z') // => '2 days ago'
+ * ('2008-01-14T22:24:17Z') // => '2 weeks ago'
+ * ('2007-12-15T22:24:17Z') // => 'more than 5 weeks ago'
+ *
+ */
+ prettyDate : function(time) {
+ var date;
+ var diff;
+ var abs_diff;
+ var day_diff;
+ var abs_day_diff;
+ var tense;
+
+ if(typeof time === 'string' || typeof time === 'number') {
+ date = new Date(time);
+ }
+
+ if(typeof time === 'object') {
+ date = new Date(time.toString());
+ }
+
+ diff = (((new Date()).getTime() - date.getTime()) / 1000);
+
+ abs_diff = Math.abs(diff);
+ abs_day_diff = Math.floor(abs_diff / 86400);
+
+ if(isNaN(abs_day_diff)) {
+ return;
+ }
+
+ tense = diff < 0 ? 'from now' : 'ago';
+
+ if(abs_diff < 60) {
+ if(diff >= 0)
+ return 'just now';
+ else
+ return 'in a moment';
+ } else if(abs_diff < 120) {
+ return '1 minute ' + tense;
+ } else if(abs_diff < 3600) {
+ return Math.floor(abs_diff / 60) + ' minutes ' + tense;
+ } else if(abs_diff < 7200) {
+ return '1 hour ' + tense;
+ } else if(abs_diff < 86400) {
+ return Math.floor(abs_diff / 3600) + ' hours ' + tense;
+ } else if(abs_day_diff === 1) {
+ if(diff >= 0)
+ return 'Yesterday';
+ else
+ return 'Tomorrow';
+ } else if(abs_day_diff < 7) {
+ return abs_day_diff + ' days ' + tense;
+ } else if(abs_day_diff === 7) {
+ return '1 week ' + tense;
+ } else if(abs_day_diff < 31) {
+ return Math.ceil(abs_day_diff / 7) + ' weeks ' + tense;
+ } else {
+ return 'more than 5 weeks ' + tense;
+ }
+ },
+ toBrowserTimeZone : function(value, format) {
+ return this.date(new Date(value), format || 'MM/dd/yyyy HH:mm:ss');
+ }
+ };
+ }());
+}(DateFormat));
+;// require dateFormat.js
+// please check `dist/jquery.dateFormat.js` for a complete version
+(function($) {
+ $.format = DateFormat.format;
+}(jQuery));
diff --git a/src/main/resources/templates/chat.html b/src/main/resources/templates/chat.html
new file mode 100644
index 0000000..9419b45
--- /dev/null
+++ b/src/main/resources/templates/chat.html
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+ Chat
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html
new file mode 100644
index 0000000..5c28bdd
--- /dev/null
+++ b/src/main/resources/templates/login.html
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+ Login
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/templates/new-account.html b/src/main/resources/templates/new-account.html
new file mode 100644
index 0000000..2b65e7c
--- /dev/null
+++ b/src/main/resources/templates/new-account.html
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+ Create New Account
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/com/example/chatapplication/ChatApplicationTests.java b/src/test/java/com/example/chatapplication/ChatApplicationTests.java
new file mode 100644
index 0000000..922e66e
--- /dev/null
+++ b/src/test/java/com/example/chatapplication/ChatApplicationTests.java
@@ -0,0 +1,13 @@
+package com.example.chatapplication;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class ChatApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/src/test/java/com/example/chatapplication/RoomRepositoryTest.java b/src/test/java/com/example/chatapplication/RoomRepositoryTest.java
new file mode 100644
index 0000000..f02aaff
--- /dev/null
+++ b/src/test/java/com/example/chatapplication/RoomRepositoryTest.java
@@ -0,0 +1,32 @@
+package com.example.chatapplication;
+
+import com.example.chatapplication.chatroom.model.Room;
+import com.example.chatapplication.chatroom.repository.RoomRepository;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.data.redis.AutoConfigureDataRedis;
+import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest;
+
+import java.util.Optional;
+
+@DataRedisTest
+public class RoomRepositoryTest {
+ @Autowired
+ private RoomRepository roomRepository;
+
+ @Test
+ public void testCreateRoom() {
+ String id = "456";
+ String name = "Trò chuyện linh tinh";
+ String description = "Truyện trò tâm sự đủ thứ";
+ Room room = new Room();
+ room.setId(id);
+ room.setName(name);
+ room.setDescription(description);
+
+ Room savedRoom = roomRepository.save(room);
+ Optional optionalRoom = roomRepository.findById(id);
+ Assertions.assertThat(optionalRoom.isPresent()).isTrue();
+ }
+}
diff --git a/src/test/java/com/example/chatapplication/UserRepositoryTest.java b/src/test/java/com/example/chatapplication/UserRepositoryTest.java
new file mode 100644
index 0000000..7bd6b9f
--- /dev/null
+++ b/src/test/java/com/example/chatapplication/UserRepositoryTest.java
@@ -0,0 +1,44 @@
+package com.example.chatapplication;
+
+import com.example.chatapplication.authentication.model.Role;
+import com.example.chatapplication.authentication.repository.RoleRepository;
+import com.example.chatapplication.authentication.repository.UserRepository;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.annotation.Rollback;
+
+import java.util.Optional;
+
+@DataJpaTest
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
+@Rollback(value = false)
+public class UserRepositoryTest {
+ @Autowired
+ private UserRepository userRepository;
+ @Autowired
+ private RoleRepository roleRepository;
+
+ @Test
+ public void getRoleTest() {
+ int id = 3;
+ Optional optionalRole = roleRepository.findById(id);
+
+ Assertions.assertThat(optionalRole.isPresent()).isTrue();
+ }
+
+ @Test
+ public void createRoleTest() {
+ Role role = new Role();
+ role.setName("USER");
+
+ Role savedRole = roleRepository.save(role);
+ Assertions.assertThat(savedRole.getId()).isNotNull();
+
+ Optional optionalRole = roleRepository.findById(savedRole.getId());
+ Assertions.assertThat(optionalRole.isPresent()).isTrue();
+ }
+}