first commit
This commit is contained in:
commit
2440d4ef22
BIN
__pycache__/config.cpython-312.pyc
Normal file
BIN
__pycache__/config.cpython-312.pyc
Normal file
Binary file not shown.
BIN
__pycache__/database.cpython-312.pyc
Normal file
BIN
__pycache__/database.cpython-312.pyc
Normal file
Binary file not shown.
21
config.py
Normal file
21
config.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
from configparser import ConfigParser
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def config(filename='database.ini', section='postgresql'):
|
||||||
|
# create a parser
|
||||||
|
parser = ConfigParser()
|
||||||
|
# read config file
|
||||||
|
parser.read(filename)
|
||||||
|
|
||||||
|
# get section, default to postgresql
|
||||||
|
db = {}
|
||||||
|
if parser.has_section(section):
|
||||||
|
params = parser.items(section)
|
||||||
|
for param in params:
|
||||||
|
db[param[0]] = param[1]
|
||||||
|
else:
|
||||||
|
raise Exception('Section {0} not found in the {1} file'.format(section, filename))
|
||||||
|
|
||||||
|
return db
|
||||||
6
database.ini
Normal file
6
database.ini
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[postgresql]
|
||||||
|
host = 192.168.1.67
|
||||||
|
database = postgres
|
||||||
|
user = test
|
||||||
|
password = test
|
||||||
|
port = 5432
|
||||||
106
database.py
Normal file
106
database.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import config, psycopg2
|
||||||
|
|
||||||
|
def tupleDictionaryFactory(columns, row):
|
||||||
|
columns = [desc[0] for desc in columns]
|
||||||
|
return dict(zip(columns, row))
|
||||||
|
|
||||||
|
def create_messages():
|
||||||
|
database_config = config.config()
|
||||||
|
with psycopg2.connect(**database_config) as conn:
|
||||||
|
try:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
with open('sql/CREATE/messages.sql') as file:
|
||||||
|
sql = file.read()
|
||||||
|
|
||||||
|
cur.execute(sql)
|
||||||
|
except (Exception, psycopg2.DatabaseError) as error:
|
||||||
|
print(error)
|
||||||
|
conn.rollback()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create_channels():
|
||||||
|
database_config = config.config()
|
||||||
|
with psycopg2.connect(**database_config) as conn:
|
||||||
|
try:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
with open('sql/CREATE/channels.sql') as file:
|
||||||
|
sql = file.read()
|
||||||
|
|
||||||
|
cur.execute(sql)
|
||||||
|
except (Exception, psycopg2.DatabaseError) as error:
|
||||||
|
print(error)
|
||||||
|
conn.rollback()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_channel(id):
|
||||||
|
database_config = config.config()
|
||||||
|
with psycopg2.connect(**database_config) as conn:
|
||||||
|
try:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
|
||||||
|
sql = f"SELECT * FROM channels WHERE id=%s;"
|
||||||
|
cur.execute(sql, (id,))
|
||||||
|
rows = cur.fetchone()
|
||||||
|
if rows:
|
||||||
|
rows = tupleDictionaryFactory(cur.description, rows)
|
||||||
|
return rows
|
||||||
|
return {}
|
||||||
|
except (Exception, psycopg2.DatabaseError) as error:
|
||||||
|
print(error)
|
||||||
|
conn.rollback()
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def insert_message(payload):
|
||||||
|
database_config = config.config()
|
||||||
|
with psycopg2.connect(**database_config) as conn:
|
||||||
|
try:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
with open('sql/INSERT/messages.sql') as file:
|
||||||
|
sql = file.read()
|
||||||
|
cur.execute(sql, payload)
|
||||||
|
rows = cur.fetchone()
|
||||||
|
if rows:
|
||||||
|
rows = tupleDictionaryFactory(cur.description, rows)
|
||||||
|
return rows
|
||||||
|
return {}
|
||||||
|
except (Exception, psycopg2.DatabaseError) as error:
|
||||||
|
print(error)
|
||||||
|
conn.rollback()
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def select_message(id):
|
||||||
|
database_config = config.config()
|
||||||
|
with psycopg2.connect(**database_config) as conn:
|
||||||
|
try:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
with open('sql/SELECT/messages.sql') as file:
|
||||||
|
sql = file.read()
|
||||||
|
cur.execute(sql, (id, ))
|
||||||
|
rows = cur.fetchone()
|
||||||
|
if rows:
|
||||||
|
rows = tupleDictionaryFactory(cur.description, rows)
|
||||||
|
return rows
|
||||||
|
return {}
|
||||||
|
except (Exception, psycopg2.DatabaseError) as error:
|
||||||
|
print(error)
|
||||||
|
conn.rollback()
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def select_messages(payload):
|
||||||
|
database_config = config.config()
|
||||||
|
with psycopg2.connect(**database_config) as conn:
|
||||||
|
try:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
with open('sql/SELECT/messagesByChannel.sql') as file:
|
||||||
|
sql = file.read()
|
||||||
|
cur.execute(sql, payload)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
if rows:
|
||||||
|
rows = [tupleDictionaryFactory(cur.description, row) for row in rows]
|
||||||
|
return rows
|
||||||
|
return []
|
||||||
|
except (Exception, psycopg2.DatabaseError) as error:
|
||||||
|
print(error)
|
||||||
|
conn.rollback()
|
||||||
|
return []
|
||||||
5
sql/CREATE/channels.sql
Normal file
5
sql/CREATE/channels.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS channels (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
channel_name char(64) NOT NULL,
|
||||||
|
channel_description TEXT
|
||||||
|
);
|
||||||
11
sql/CREATE/messages.sql
Normal file
11
sql/CREATE/messages.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS messages (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
timestamp TIMESTAMP,
|
||||||
|
channel_id INTEGER NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
message_content TEXT NOT NULL,
|
||||||
|
CONSTRAINT fk_channel_id
|
||||||
|
FOREIGN KEY(channel_id)
|
||||||
|
REFERENCES channels(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
4
sql/INSERT/messages.sql
Normal file
4
sql/INSERT/messages.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
INSERT INTO messages
|
||||||
|
(timestamp, channel_id, user_id, message_content)
|
||||||
|
VALUES (%s, %s, %s, %s)
|
||||||
|
RETURNING *;
|
||||||
7
sql/SELECT/messages.sql
Normal file
7
sql/SELECT/messages.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
SELECT messages.*,
|
||||||
|
row_to_json(users.*) as user,
|
||||||
|
row_to_json(channels.*) As channel
|
||||||
|
FROM messages
|
||||||
|
LEFT JOIN users ON messages.user_id = users.id
|
||||||
|
LEFT JOIN channels ON messages.channel_id = channels.id
|
||||||
|
WHERE messages.id=%s;
|
||||||
14
sql/SELECT/messagesByChannel.sql
Normal file
14
sql/SELECT/messagesByChannel.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
|
||||||
|
SELECT * FROM (
|
||||||
|
SELECT messages.*,
|
||||||
|
row_to_json(users.*) as user,
|
||||||
|
row_to_json(channels.*) As channel
|
||||||
|
FROM messages
|
||||||
|
LEFT JOIN users ON messages.user_id = users.id
|
||||||
|
LEFT JOIN channels ON messages.channel_id = channels.id
|
||||||
|
WHERE messages.channel_id = %s
|
||||||
|
ORDER BY messages.timestamp DESC
|
||||||
|
LIMIT %s
|
||||||
|
) AS subquery
|
||||||
|
ORDER BY subquery.timestamp ASC;
|
||||||
1
static/css/uikit.min.css
vendored
Normal file
1
static/css/uikit.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
static/images/placeholder.webp
Normal file
BIN
static/images/placeholder.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
1
static/js/uikit-icons.min.js
vendored
Normal file
1
static/js/uikit-icons.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/js/uikit.min.js
vendored
Normal file
1
static/js/uikit.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
365
templates/index.html
Normal file
365
templates/index.html
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
|
||||||
|
<title id="title"></title>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Material Icons -->
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||||
|
<!-- Material Symbols - Outlined Set -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
|
||||||
|
<!-- Material Symbols - Rounded Set -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded" rel="stylesheet" />
|
||||||
|
<!-- Material Symbols - Sharp Set -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp" rel="stylesheet" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/uikit.min.css') }}"/>
|
||||||
|
|
||||||
|
<script src="{{ url_for('static', filename='js/uikit.min.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/uikit-icons.min.js') }}"></script>
|
||||||
|
</head>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--body-background: #121212;
|
||||||
|
--background: #1c1c1c;
|
||||||
|
--primary-color: #f7f7f7;
|
||||||
|
--accent-color: #ffb3b3;
|
||||||
|
--secondary-text: #666666;
|
||||||
|
--highlight: #ffd700;
|
||||||
|
--font: 'Arial', sans-serif;;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--body-background);
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-family: var(--font);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle{
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-nav-bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: var(--body-background);
|
||||||
|
box-shadow: 0 2px 5px var(--background);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-nav-bar inline {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button{
|
||||||
|
height: 40px;
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--primary-color);
|
||||||
|
border: none;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.3s, box-shadow 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button span {
|
||||||
|
font-size: 18px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-title{
|
||||||
|
margin: 0px;
|
||||||
|
font-size: x-large;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-container {
|
||||||
|
background-color: var(--body-background);
|
||||||
|
min-height: calc(100vh - 120px);
|
||||||
|
margin-bottom: 60px;
|
||||||
|
margin-top: 60px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-container > * {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.floating-square {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: var(--body-background);
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flexbox;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width-input {
|
||||||
|
padding: 10px;
|
||||||
|
width: calc(100% - 70px);
|
||||||
|
height: auto; /* Allow dynamic height */
|
||||||
|
min-height: 48px; /* Starting height */
|
||||||
|
resize: vertical; /* Allow vertical resizing */
|
||||||
|
overflow: hidden;
|
||||||
|
vertical-align: bottom;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid var(--secondary-text);
|
||||||
|
border-radius: 0px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
outline: none;
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width-input:focus {
|
||||||
|
border-bottom-color: var(--primary-color); /* Highlight upon focus */
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width-button {
|
||||||
|
width: 50px;
|
||||||
|
margin: 0;
|
||||||
|
vertical-align: bottom;
|
||||||
|
padding: 10px;
|
||||||
|
min-width: 50px;
|
||||||
|
border-radius: 4px; /* Match textarea */
|
||||||
|
border: none; /* Match textarea */
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--primary-color);
|
||||||
|
transition: background-color 0.3s, box-shadow 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width-button:hover {
|
||||||
|
background-color: var(--background); /* Darker tone on hover */
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); /* Enhanced hover elevation */
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width-button:active {
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-card {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: var(--body-background);
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 40px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.username {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 5px;
|
||||||
|
font-family: var(--font);
|
||||||
|
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #b0b0b0;
|
||||||
|
font-family: var(--font);
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-left:50px;
|
||||||
|
margin-right: 5px;
|
||||||
|
|
||||||
|
font-family: var(--font);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<div class="my-nav-bar">
|
||||||
|
<inline>
|
||||||
|
<a href="/logout" class="nav-button"><span class="material-icons">menu</span>Menu</a>
|
||||||
|
<p id="room-title" class="nav-title">Room Title</p>
|
||||||
|
</inline>
|
||||||
|
</div>
|
||||||
|
<div class="chat-container" >
|
||||||
|
<div id="chat" style="height: auto;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="test" class="floating-square">
|
||||||
|
<textarea id="messageInput" class="full-width-input" placeholder="Type your thoughts..."></textarea>
|
||||||
|
<button id="sendButton" onclick="sendMessage()" class="full-width-button"><span class="material-symbols-outlined">
|
||||||
|
send
|
||||||
|
</span></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA==" crossorigin="anonymous"></script>
|
||||||
|
<script type="text/javascript" charset="utf-8">
|
||||||
|
var socket = io.connect('http://192.168.1.45:5812/');
|
||||||
|
var current_room = 1;
|
||||||
|
var current_username = 'unknown';
|
||||||
|
var current_channel;
|
||||||
|
var user;
|
||||||
|
|
||||||
|
fetch('/get_session')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
user = data['user'] // Revel in the data!
|
||||||
|
current_username = user[1]
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error fetching session:', error));
|
||||||
|
|
||||||
|
socket.on('messageReceive', function(data) {
|
||||||
|
console.log('Received:', data);
|
||||||
|
let message = data.message;
|
||||||
|
// instead of adding the message do I just reload the messages? what kind of infranstructure would that require.
|
||||||
|
addMessage([data])
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('joined', function(data) {
|
||||||
|
current_channel = data
|
||||||
|
document.getElementById('room-title').innerHTML = data['channel_name']
|
||||||
|
});
|
||||||
|
|
||||||
|
const textarea = document.getElementById('messageInput');
|
||||||
|
textarea.addEventListener('input', function() {
|
||||||
|
this.style.height = 'auto';
|
||||||
|
this.style.height = this.scrollHeight + 'px';
|
||||||
|
document.getElementById('test').style.height = this.scrollHeight + 'px'
|
||||||
|
});
|
||||||
|
textarea.addEventListener('blur', function() {
|
||||||
|
this.style.height = 'auto';
|
||||||
|
this.style.height = '40px';
|
||||||
|
document.getElementById('test').style.height = '50px'
|
||||||
|
this.blur()
|
||||||
|
});
|
||||||
|
|
||||||
|
textarea.addEventListener('keydown', function(event) {
|
||||||
|
if (event.key === 'Enter' && !event.shiftKey) { // Checks if Enter is pressed without shift
|
||||||
|
event.preventDefault();
|
||||||
|
sendMessage(); // Replace with your function
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
joinRoom(current_room)
|
||||||
|
|
||||||
|
function addMessage(data){
|
||||||
|
|
||||||
|
let message_card = document.createElement('div')
|
||||||
|
message_card.setAttribute('class', 'message-card')
|
||||||
|
|
||||||
|
let user_info = document.createElement('div')
|
||||||
|
user_info.setAttribute('class', 'user-info')
|
||||||
|
|
||||||
|
let image = document.createElement('img')
|
||||||
|
image.setAttribute('class', 'avatar')
|
||||||
|
image.setAttribute('src', "static/images/placeholder.webp")
|
||||||
|
|
||||||
|
let username = document.createElement('span')
|
||||||
|
username.setAttribute('class', 'username')
|
||||||
|
username.innerHTML = data[0]['user']['username']
|
||||||
|
|
||||||
|
let timestamp = document.createElement('span')
|
||||||
|
timestamp.setAttribute('class', 'timestamp')
|
||||||
|
timestamp.innerHTML = data[0]['timestamp']
|
||||||
|
|
||||||
|
user_info.append(image)
|
||||||
|
user_info.append(username)
|
||||||
|
user_info.append(timestamp)
|
||||||
|
|
||||||
|
message_card.append(user_info)
|
||||||
|
|
||||||
|
let message = document.createElement('div')
|
||||||
|
message.setAttribute('class', 'message-content')
|
||||||
|
|
||||||
|
test = `${data[0]['message_content']}`
|
||||||
|
for(let i = 1; i < data.length; i++){
|
||||||
|
test = test + `<br>${data[i]['message_content']}`
|
||||||
|
}
|
||||||
|
|
||||||
|
message.innerHTML = `${test}`
|
||||||
|
|
||||||
|
message_card.append(message)
|
||||||
|
|
||||||
|
document.getElementById('chat').append(message_card)
|
||||||
|
|
||||||
|
window.scrollTo(0, document.body.scrollHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMessage(){
|
||||||
|
let message = document.getElementById('messageInput')
|
||||||
|
|
||||||
|
let currentTime = new Date();
|
||||||
|
let timeString = currentTime.toLocaleTimeString();
|
||||||
|
let dateString = currentTime.toLocaleDateString();
|
||||||
|
let dateTimeString = `${dateString} ${timeString}`;
|
||||||
|
|
||||||
|
socket.emit('messageSend', {'message_content': message.value, 'channel_id': current_room, 'user_id': user[0], 'timestamp': currentTime})
|
||||||
|
message.value = '';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function joinRoom(channel_id) {
|
||||||
|
socket.emit('join', {'channel_id': channel_id});
|
||||||
|
current_room = channel_id
|
||||||
|
|
||||||
|
const url = new URL('/get_channel_messsages', window.location.origin);
|
||||||
|
url.searchParams.append('channel_id', channel_id);
|
||||||
|
const response = await fetch(url);
|
||||||
|
data = await response.json()
|
||||||
|
messages = data.messages;
|
||||||
|
console.log(messages.length)
|
||||||
|
console.log(messages)
|
||||||
|
let combinedMessages = []
|
||||||
|
let previous_message;
|
||||||
|
let num_messages = 0
|
||||||
|
|
||||||
|
let grouped = [], tempGroup = [messages[0]];
|
||||||
|
let startTime = new Date(messages[0].timestamp);
|
||||||
|
for (let i = 1; i < messages.length; i++) {
|
||||||
|
if ((new Date(messages[i].timestamp) - startTime <= 300000) &&
|
||||||
|
(messages[i].user.id === messages[i - 1].user.id)) {
|
||||||
|
tempGroup.push(messages[i]);
|
||||||
|
} else {
|
||||||
|
grouped.push(tempGroup);
|
||||||
|
tempGroup = [messages[i]];
|
||||||
|
startTime = new Date(messages[i].timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grouped.push(tempGroup);
|
||||||
|
console.log(grouped)
|
||||||
|
|
||||||
|
for (let i = 0; i< grouped.length; i++){
|
||||||
|
addMessage(grouped[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// To leave a room
|
||||||
|
function leaveRoom(username, room) {
|
||||||
|
socket.emit('leave', { username, room });
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
64
templates/login.html
Normal file
64
templates/login.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
|
||||||
|
<title id="title"></title>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Material Icons -->
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||||
|
<!-- Material Symbols - Outlined Set -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
|
||||||
|
<!-- Material Symbols - Rounded Set -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded" rel="stylesheet" />
|
||||||
|
<!-- Material Symbols - Sharp Set -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp" rel="stylesheet" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/uikit.min.css') }}"/>
|
||||||
|
|
||||||
|
<script src="{{ url_for('static', filename='js/uikit.min.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/uikit-icons.min.js') }}"></script>
|
||||||
|
</head>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--body-background: #121212;
|
||||||
|
--background: #1c1c1c;
|
||||||
|
--primary-color: #f7f7f7;
|
||||||
|
--accent-color: #ffb3b3;
|
||||||
|
--secondary-text: #666666;
|
||||||
|
--highlight: #ffd700;
|
||||||
|
--font: 'Arial', sans-serif;;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--body-background);
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-family: var(--font);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<div class="login-container">
|
||||||
|
<form action="/login" method="post">
|
||||||
|
<div>
|
||||||
|
<label for="username">Username:</label>
|
||||||
|
<input type="text" id="username" name="username">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
<input type="password" id="password" name="password">
|
||||||
|
</div>
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script></script>
|
||||||
|
</html>
|
||||||
136
webserver.py
Normal file
136
webserver.py
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
from flask import Flask, render_template, session, request, redirect, url_for, jsonify
|
||||||
|
from flask_socketio import SocketIO, join_room, leave_room
|
||||||
|
import psycopg2
|
||||||
|
import config, database
|
||||||
|
from functools import wraps
|
||||||
|
from dateutil import parser
|
||||||
|
import datetime
|
||||||
|
import pytz, json
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.secret_key = '11gs22h2h1a4h6ah8e413a45'
|
||||||
|
|
||||||
|
socketio = SocketIO(app)
|
||||||
|
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def inject_user():
|
||||||
|
if 'user_id' in session.keys() and session['user_id'] is not None:
|
||||||
|
database_config = config.config()
|
||||||
|
with psycopg2.connect(**database_config) as conn:
|
||||||
|
try:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
sql = f"SELECT * FROM users WHERE id=%s;"
|
||||||
|
cur.execute(sql, (session['user_id'],))
|
||||||
|
user = cur.fetchone()
|
||||||
|
session['user'] = user
|
||||||
|
except (Exception, psycopg2.DatabaseError) as error:
|
||||||
|
print(error)
|
||||||
|
conn.rollback()
|
||||||
|
return dict(username="")
|
||||||
|
|
||||||
|
return dict(
|
||||||
|
user = user
|
||||||
|
)
|
||||||
|
|
||||||
|
return dict(user=[])
|
||||||
|
|
||||||
|
def login_required(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
if 'user' not in session or session['user'] == None:
|
||||||
|
return redirect('/login')
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
|
@app.route('/get_session')
|
||||||
|
def get_session():
|
||||||
|
return {'user': session.get('user')}
|
||||||
|
|
||||||
|
@app.route('/get_channel_messsages', methods=["GET"])
|
||||||
|
def get_channel_messages():
|
||||||
|
|
||||||
|
channel_id = request.args['channel_id']
|
||||||
|
limit = 100
|
||||||
|
page = 1
|
||||||
|
|
||||||
|
offset = (page - 1) * limit
|
||||||
|
|
||||||
|
messages = database.select_messages((channel_id, limit))
|
||||||
|
return jsonify(messages=messages)
|
||||||
|
|
||||||
|
@app.route('/logout', methods=['GET'])
|
||||||
|
def logout():
|
||||||
|
if 'user' in session.keys():
|
||||||
|
session['user'] = None
|
||||||
|
return redirect('/login')
|
||||||
|
|
||||||
|
@app.route('/login', methods=["POST", "GET"])
|
||||||
|
def login():
|
||||||
|
session.clear()
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
username = request.form.get('username')
|
||||||
|
password = request.form.get('password')
|
||||||
|
|
||||||
|
database_config = config.config()
|
||||||
|
with psycopg2.connect(**database_config) as conn:
|
||||||
|
try:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
sql = f"SELECT * FROM users WHERE username=%s;"
|
||||||
|
cur.execute(sql, (username,))
|
||||||
|
user = cur.fetchone()
|
||||||
|
print(user)
|
||||||
|
except (Exception, psycopg2.DatabaseError) as error:
|
||||||
|
print(error)
|
||||||
|
conn.rollback()
|
||||||
|
print(password, user[2])
|
||||||
|
if user and user[2] == password:
|
||||||
|
print(password, user[2])
|
||||||
|
session['user'] = user
|
||||||
|
return redirect('/')
|
||||||
|
|
||||||
|
if 'user' not in session.keys():
|
||||||
|
session['user'] = None
|
||||||
|
|
||||||
|
return render_template('login.html')
|
||||||
|
|
||||||
|
@socketio.on('join')
|
||||||
|
def on_join(data):
|
||||||
|
print(data)
|
||||||
|
channel = database.get_channel(data['channel_id'])
|
||||||
|
room = channel['channel_name']
|
||||||
|
join_room(room)
|
||||||
|
socketio.emit('joined', channel, to=request.sid)
|
||||||
|
|
||||||
|
@socketio.on('leave')
|
||||||
|
def on_leave(data):
|
||||||
|
username = data['username']
|
||||||
|
room = data['room']
|
||||||
|
print(f"{username} joined the {room}!")
|
||||||
|
leave_room(room)
|
||||||
|
|
||||||
|
@socketio.on('messageSend')
|
||||||
|
def handle_event(data):
|
||||||
|
print('Received:', data)
|
||||||
|
channel = database.get_channel(data['channel_id'])
|
||||||
|
|
||||||
|
payload = (
|
||||||
|
datetime.datetime.now(),
|
||||||
|
data['channel_id'],
|
||||||
|
data['user_id'],
|
||||||
|
data['message_content']
|
||||||
|
)
|
||||||
|
row = database.insert_message(payload)
|
||||||
|
message = database.select_message(row['id'])
|
||||||
|
print(message)
|
||||||
|
message['timestamp'] = str(message['timestamp'])
|
||||||
|
socketio.emit('messageReceive', message, to=channel['channel_name'])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
socketio.run(app, host="0.0.0.0", port=5812, debug=True)
|
||||||
Loading…
x
Reference in New Issue
Block a user