v2-overhaul #1
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
.env
|
||||
.venv
|
||||
app.db
|
||||
__pycache__
|
||||
app.log
|
||||
__pycache__/
|
||||
database.db
|
||||
.env
|
||||
flask_session
|
@ -10,7 +10,7 @@ COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy the rest of the application code into the container
|
||||
COPY src/ ./src
|
||||
COPY src src
|
||||
|
||||
# Expose the port the app runs on
|
||||
EXPOSE 5000
|
||||
|
@ -2,6 +2,6 @@
|
||||
prismic is a simple messageboard made in python
|
||||
|
||||
## Planned features
|
||||
- [ ] user board creation
|
||||
- [ ] custom profiles
|
||||
- [x] user board creation
|
||||
- [x] custom profiles
|
||||
- [x] moderation tools
|
@ -1,2 +1,5 @@
|
||||
flask
|
||||
flask-session
|
||||
mysql-connector-python # only required for external mysql connection
|
||||
python-dotenv # only required if using .env file
|
||||
python-dateutil
|
||||
flask-session
|
||||
flask
|
730
src/database.py
730
src/database.py
@ -1,581 +1,185 @@
|
||||
import sqlite3, logging, os
|
||||
import os, hashlib, sqlite3, logging
|
||||
from os import getenv as env
|
||||
import mysql.connector
|
||||
|
||||
|
||||
# Configure logging
|
||||
logger = logging.getLogger(__name__)
|
||||
log = logging.getLogger(__name__)
|
||||
FETCHALL = 0
|
||||
FETCHONE = 1
|
||||
|
||||
|
||||
class Database:
|
||||
def __init__(self, db_name):
|
||||
# Initialize the database connection
|
||||
logger.info("Initializing database connection...")
|
||||
self.connection = sqlite3.connect(db_name, check_same_thread=False)
|
||||
self.cursor = self.connection.cursor()
|
||||
logger.info("Database connection established.")
|
||||
def __init__(self):
|
||||
self.connection = None
|
||||
self.cursor = None
|
||||
|
||||
# Create users table if it doesn't exist
|
||||
logger.info("Creating users table if it doesn't exist...")
|
||||
self.cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL,
|
||||
session TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
def connect_sqlite(self, db_path):
|
||||
try:
|
||||
self.connection = sqlite3.connect(db_path, check_same_thread=False)
|
||||
self.cursor = self.connection.cursor()
|
||||
log.info("Connected to SQLite database")
|
||||
except sqlite3.Error as e:
|
||||
log.error(f"SQLite connection error: {e}")
|
||||
raise
|
||||
|
||||
def connect_mysql(self):
|
||||
try:
|
||||
self.connection = mysql.connector.connect(
|
||||
host=env('MYSQL_HOST'),
|
||||
user=env('MYSQL_USER'),
|
||||
password=env('MYSQL_PASSWORD'),
|
||||
database=env('MYSQL_DATABASE')
|
||||
)
|
||||
''')
|
||||
logger.info("Users table created.")
|
||||
|
||||
# Create boards table if it doesn't exist
|
||||
logger.info("Creating boards table if it doesn't exist...")
|
||||
self.cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS boards (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
owner_id INTEGER NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (owner_id) REFERENCES users (id) ON DELETE SET NULL
|
||||
)
|
||||
''')
|
||||
logger.info("Boards table created.")
|
||||
|
||||
# Create posts table if it doesn't exist
|
||||
logger.info("Creating posts table if it doesn't exist...")
|
||||
self.cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
board_id INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
refence INTEGER,
|
||||
FOREIGN KEY (refence) REFERENCES posts (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (board_id) REFERENCES boards (id) ON DELETE CASCADE
|
||||
)
|
||||
''')
|
||||
logger.info("Posts table created.")
|
||||
|
||||
self.cursor = self.connection.cursor()
|
||||
log.info("Connected to MySQL database")
|
||||
except mysql.connector.Error as e:
|
||||
log.error(f"MySQL connection error: {e}")
|
||||
raise
|
||||
|
||||
def close(self):
|
||||
# Close the database connection
|
||||
logger.info("Closing database connection...")
|
||||
self.connection.commit()
|
||||
self.connection.close()
|
||||
logger.info("Database connection closed.")
|
||||
if self.cursor:
|
||||
self.cursor.close()
|
||||
if self.connection:
|
||||
self.connection.close()
|
||||
log.warning("Database connection closed")
|
||||
|
||||
|
||||
def create_user(self, username, password):
|
||||
logger.info(f"Creating user: {username}")
|
||||
|
||||
# Check if the user already exists
|
||||
logger.info(f"Checking if user {username} already exists...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM users WHERE username = ?
|
||||
''', (username,))
|
||||
user = self.cursor.fetchone()
|
||||
|
||||
if user:
|
||||
logger.warning(f"User {username} already exists.")
|
||||
return False
|
||||
logger.info(f"User {username} does not exist.")
|
||||
|
||||
# Create a new user
|
||||
self.cursor.execute('''
|
||||
INSERT INTO users (username, password)
|
||||
VALUES (?, ?)
|
||||
''', (username, password))
|
||||
|
||||
self.connection.commit()
|
||||
logger.info(f"User {username} created.")
|
||||
return True
|
||||
|
||||
|
||||
def get_user(self, username):
|
||||
# Get user by username
|
||||
logger.info(f"Getting user {username}...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM users WHERE username = ?
|
||||
''', (username,))
|
||||
user = self.cursor.fetchone()
|
||||
|
||||
if user:
|
||||
logger.info(f"User {username} found.")
|
||||
return user
|
||||
logger.warning(f"User {username} not found.")
|
||||
return None
|
||||
|
||||
|
||||
def create_board(self, name, description, owner_id):
|
||||
logger.info(f"Creating board: {name}")
|
||||
|
||||
# Check if the board already exists
|
||||
logger.info(f"Checking if board {name} already exists...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM boards WHERE name = ?
|
||||
''', (name,))
|
||||
board = self.cursor.fetchone()
|
||||
|
||||
if board:
|
||||
logger.warning(f"Board {name} already exists.")
|
||||
return False
|
||||
logger.info(f"Board {name} does not exist.")
|
||||
|
||||
# Check if the owner exists
|
||||
logger.info(f"Checking if owner {owner_id} exists...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM users WHERE id = ?
|
||||
''', (owner_id,))
|
||||
owner = self.cursor.fetchone()
|
||||
|
||||
if not owner:
|
||||
logger.warning(f"Owner {owner_id} does not exist.")
|
||||
return False
|
||||
logger.info(f"Owner {owner_id} exists.")
|
||||
|
||||
# Create a new board
|
||||
self.cursor.execute('''
|
||||
INSERT INTO boards (name, owner_id, description)
|
||||
VALUES (?, ?, ?)
|
||||
''', (name, owner_id, description))
|
||||
|
||||
self.connection.commit()
|
||||
logger.info(f"Board {name} created.")
|
||||
return True
|
||||
|
||||
|
||||
def get_board(self, name):
|
||||
# Get board by name
|
||||
logger.info(f"Getting board {name}...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM boards WHERE name = ?
|
||||
''', (name,))
|
||||
board = self.cursor.fetchone()
|
||||
|
||||
if board:
|
||||
logger.info(f"Board {name} found.")
|
||||
return board
|
||||
logger.warning(f"Board {name} not found.")
|
||||
return None
|
||||
|
||||
|
||||
def create_post(self, user_id, board_id, content, ref=None):
|
||||
logger.info(f"Creating post for user {user_id} in board {board_id}...")
|
||||
|
||||
# Check if the user exists
|
||||
logger.info(f"Checking if user {user_id} exists...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM users WHERE id = ?
|
||||
''', (user_id,))
|
||||
user = self.cursor.fetchone()
|
||||
|
||||
if not user:
|
||||
logger.warning(f"User {user_id} does not exist.")
|
||||
return False
|
||||
logger.info(f"User {user_id} exists.")
|
||||
|
||||
# Check if the board exists
|
||||
logger.info(f"Checking if board {board_id} exists...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM boards WHERE id = ?
|
||||
''', (board_id,))
|
||||
board = self.cursor.fetchone()
|
||||
|
||||
if not board:
|
||||
logger.warning(f"Board {board_id} does not exist.")
|
||||
return False
|
||||
logger.info(f"Board {board_id} exists.")
|
||||
|
||||
# Check if the reference post exists
|
||||
if ref is not None:
|
||||
logger.info(f"Checking if reference post {ref} exists...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM posts WHERE id = ?
|
||||
''', (ref,))
|
||||
reference_post = self.cursor.fetchone()
|
||||
|
||||
if not reference_post:
|
||||
logger.warning(f"Reference post {ref} does not exist.")
|
||||
return False
|
||||
logger.info(f"Reference post {ref} exists.")
|
||||
else:
|
||||
logger.info("No reference post provided.")
|
||||
|
||||
# Create a new post
|
||||
self.cursor.execute('''
|
||||
INSERT INTO posts (user_id, board_id, content, refence)
|
||||
VALUES (?, ?, ?, ?)
|
||||
''', (user_id, board_id, content, ref))
|
||||
self.connection.commit()
|
||||
logger.info(f"Post created for user {user_id} in board {board_id}.")
|
||||
post_id = self.cursor.lastrowid
|
||||
logger.info(f"Post ID: {post_id}")
|
||||
return post_id
|
||||
|
||||
|
||||
def get_post(self, post_id):
|
||||
# Get post by ID
|
||||
logger.info(f"Getting post {post_id}...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM posts WHERE id = ?
|
||||
''', (post_id,))
|
||||
post = self.cursor.fetchone()
|
||||
|
||||
if post:
|
||||
logger.info(f"Post {post_id} found.")
|
||||
return post
|
||||
logger.warning(f"Post {post_id} not found.")
|
||||
return None
|
||||
|
||||
|
||||
def get_post_references(self, post_id, limit=10, offset=0):
|
||||
# Get references for a post
|
||||
logger.info(f"Getting references for post {post_id}...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM posts WHERE refence = ? LIMIT ? OFFSET ?
|
||||
''', (post_id, limit, offset))
|
||||
references = self.cursor.fetchall()
|
||||
|
||||
if references:
|
||||
logger.info(f"References found for post {post_id}.")
|
||||
return references
|
||||
logger.warning(f"No references found for post {post_id}.")
|
||||
return None
|
||||
|
||||
|
||||
def get_posts(self, board_id, limit, offset):
|
||||
if board_id:
|
||||
logger.info(f"Getting posts for board {board_id}...")
|
||||
|
||||
# Check if the board exists
|
||||
logger.info(f"Checking if board {board_id} exists...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM boards WHERE id = ?
|
||||
''', (board_id,))
|
||||
board = self.cursor.fetchone()
|
||||
|
||||
if not board:
|
||||
logger.warning(f"Board {board_id} does not exist.")
|
||||
return None
|
||||
logger.info(f"Board {board_id} exists.")
|
||||
|
||||
# Get posts for the board
|
||||
logger.info(f"Getting posts for board {board_id}...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM posts WHERE board_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?
|
||||
''', (board_id, limit, offset))
|
||||
posts = self.cursor.fetchall()
|
||||
|
||||
if posts:
|
||||
logger.info(f"Posts found for board {board_id}.")
|
||||
return posts
|
||||
logger.warning(f"No posts found for board {board_id}.")
|
||||
return None
|
||||
|
||||
else:
|
||||
# Get all posts
|
||||
logger.info(f"Getting all posts...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM posts ORDER BY created_at DESC LIMIT ? OFFSET ?
|
||||
''', (limit, offset))
|
||||
posts = self.cursor.fetchall()
|
||||
|
||||
if posts:
|
||||
logger.info(f"Posts found.")
|
||||
return posts
|
||||
logger.warning(f"No posts found.")
|
||||
return None
|
||||
|
||||
|
||||
def get_latest_posts(self, limit):
|
||||
logger.info(f"Getting latest posts...")
|
||||
|
||||
# Get latest posts
|
||||
logger.info(f"Getting latest posts...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM posts ORDER BY created_at DESC LIMIT ?
|
||||
''', (limit,))
|
||||
posts = self.cursor.fetchall()
|
||||
|
||||
if posts:
|
||||
logger.info(f"Latest posts found.")
|
||||
return posts
|
||||
logger.warning(f"No latest posts found.")
|
||||
return None
|
||||
|
||||
|
||||
def get_boards(self, limit, offset):
|
||||
logger.info(f"Listing boards with limit {limit} and offset {offset}...")
|
||||
|
||||
# Get boards with pagination
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM boards ORDER BY created_at DESC LIMIT ? OFFSET ?
|
||||
''', (limit, offset))
|
||||
boards = self.cursor.fetchall()
|
||||
|
||||
if boards:
|
||||
logger.info(f"Boards found.")
|
||||
return boards
|
||||
logger.warning(f"No boards found.")
|
||||
return None
|
||||
|
||||
|
||||
def post_to_dict(self, post):
|
||||
# Convert post to dictionary
|
||||
logger.info(f"Converting post to dictionary...")
|
||||
data = {
|
||||
'id': post[0],
|
||||
'content': post[3],
|
||||
'short_content': post[3][:40] + '...' if len(post[3]) > 40 else post[3],
|
||||
'created_at': post[4],
|
||||
'url': f"/post/{post[0]}",
|
||||
}
|
||||
|
||||
# Get user information
|
||||
logger.info(f"Getting user information for post {post[0]}...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM users WHERE id = ?
|
||||
''', (post[1],))
|
||||
user = self.cursor.fetchone()
|
||||
|
||||
if user:
|
||||
data['user'] = {
|
||||
'id': user[0],
|
||||
'name': user[1],
|
||||
'created_at': user[3]
|
||||
}
|
||||
logger.info(f"User information found for post {post[0]}.")
|
||||
else:
|
||||
logger.warning(f"User information not found for post {post[0]}.")
|
||||
data['user'] = {
|
||||
'id': None,
|
||||
'name': None,
|
||||
'created_at': None
|
||||
}
|
||||
|
||||
# Get board information
|
||||
logger.info(f"Getting board information for post {post[0]}...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM boards WHERE id = ?
|
||||
''', (post[2],))
|
||||
board = self.cursor.fetchone()
|
||||
|
||||
if board:
|
||||
data['board'] = {
|
||||
'id': board[0],
|
||||
'name': board[1],
|
||||
'description': board[3],
|
||||
'created_at': board[4]
|
||||
}
|
||||
logger.info(f"Board information found for post {post[0]}.")
|
||||
else:
|
||||
logger.warning(f"Board information not found for post {post[0]}.")
|
||||
data['board'] = {
|
||||
'id': None,
|
||||
'name': None,
|
||||
'description': None,
|
||||
'created_at': None
|
||||
}
|
||||
|
||||
# Get reference post information
|
||||
if post[5] is not None:
|
||||
logger.info(f"Getting reference post information for post {post[0]}...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM posts WHERE id = ?
|
||||
''', (post[5],))
|
||||
reference_post = self.cursor.fetchone()
|
||||
|
||||
if reference_post:
|
||||
data['reference'] = {
|
||||
'id': reference_post[0],
|
||||
'content': reference_post[3],
|
||||
'short_content': reference_post[3][:20] + '...' if len(reference_post[3]) > 20 else reference_post[3],
|
||||
'created_at': reference_post[4]
|
||||
}
|
||||
logger.info(f"Reference post information found for post {post[0]}.")
|
||||
def execute_query(self, query, params=None, fetch_type=FETCHALL):
|
||||
try:
|
||||
if params:
|
||||
self.cursor.execute(query, params)
|
||||
else:
|
||||
logger.warning(f"Reference post information not found for post {post[0]}.")
|
||||
data['reference'] = {
|
||||
'id': None,
|
||||
'content': None,
|
||||
'created_at': None
|
||||
}
|
||||
else:
|
||||
logger.info("No reference post.")
|
||||
data['reference'] = None
|
||||
self.cursor.execute(query)
|
||||
self.connection.commit()
|
||||
log.debug(f"Executed query: {query}")
|
||||
if fetch_type == FETCHALL:
|
||||
return self.fetchall()
|
||||
elif fetch_type == FETCHONE:
|
||||
return self.fetchone()
|
||||
elif fetch_type is None:
|
||||
log.debug("No fetch type specified, returning None")
|
||||
return None
|
||||
else:
|
||||
log.warning("Invalid fetch type, returning None")
|
||||
return None
|
||||
except (sqlite3.Error, mysql.connector.Error) as e:
|
||||
log.critical(f"Query execution error: {e}")
|
||||
raise
|
||||
|
||||
# get post references
|
||||
logger.info(f"Getting post references for post {post[0]}...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM posts WHERE refence = ?
|
||||
''', (post[0],))
|
||||
references = self.cursor.fetchall()
|
||||
def fetchall(self):
|
||||
try:
|
||||
result = self.cursor.fetchall()
|
||||
log.debug(f"Fetched all results")
|
||||
return result
|
||||
except (sqlite3.Error, mysql.connector.Error) as e:
|
||||
log.error(f"Fetchall error: {e}")
|
||||
raise
|
||||
|
||||
if references:
|
||||
data['replies'] = []
|
||||
for reference in references:
|
||||
reference_data = {
|
||||
'id': reference[0],
|
||||
'content': reference[3],
|
||||
'short_content': reference[3][:20] + '...' if len(reference[3]) > 20 else reference[3],
|
||||
'created_at': reference[4]
|
||||
}
|
||||
data['replies'].append(reference_data)
|
||||
logger.info(f"Post references found for post {post[0]}.")
|
||||
else:
|
||||
logger.warning(f"No post references found for post {post[0]}.")
|
||||
data['replies'] = []
|
||||
def fetchone(self):
|
||||
try:
|
||||
result = self.cursor.fetchone()
|
||||
log.debug(f"Fetched one result")
|
||||
return result
|
||||
except (sqlite3.Error, mysql.connector.Error) as e:
|
||||
log.critical(f"Fetchone error: {e}")
|
||||
raise
|
||||
|
||||
logger.info(f"Post converted to dictionary.")
|
||||
return data
|
||||
|
||||
def first_time_run(db:Database):
|
||||
log.info("First time run detected, initializing database")
|
||||
|
||||
# Create users table
|
||||
log.info("Creating users table")
|
||||
db.execute_query("""
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
about TEXT DEFAULT 'No description',
|
||||
permissions TEXT DEFAULT 'user',
|
||||
avatar MEDIUMBLOB,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
token TEXT,
|
||||
avatar_type TEXT
|
||||
)
|
||||
""")
|
||||
|
||||
# Create posts table
|
||||
log.info("Creating posts table")
|
||||
db.execute_query("""
|
||||
CREATE TABLE posts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
board_id INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
reference INTREGER,
|
||||
type TEXT DEFAULT 'post',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (board_id) REFERENCES boards (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (reference) REFERENCES posts (id) ON DELETE SET NULL
|
||||
)
|
||||
""")
|
||||
|
||||
# Create boards table
|
||||
log.info("Creating boards table")
|
||||
db.execute_query("""
|
||||
CREATE TABLE boards (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
owner_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (owner_id) REFERENCES users (id)
|
||||
)
|
||||
""")
|
||||
|
||||
# Create attachments table
|
||||
db.execute_query("""
|
||||
CREATE TABLE attachments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
post_id INTEGER NOT NULL,
|
||||
file_data MEDIUMBLOB NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
file_type TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (post_id) REFERENCES posts (id) ON DELETE CASCADE
|
||||
)
|
||||
""")
|
||||
|
||||
# Create system user
|
||||
|
||||
if not env('SYSTEM_PASSWORD'):
|
||||
log.warning("SYSTEM_PASSWORD is empty, generating random password")
|
||||
password = os.urandom(16).hex()
|
||||
log.info(f"Generated system user password: {password}")
|
||||
else:
|
||||
password = env('SYSTEM_PASSWORD')
|
||||
|
||||
log.info("Creating system user")
|
||||
db.execute_query("""
|
||||
INSERT INTO users (username, password, about, permissions)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""", (
|
||||
env('SYSTEM_USER', default='system'),
|
||||
env('SYSTEM_PASSWORD', default=hashlib.sha256(password.encode()).hexdigest()),
|
||||
env('SYSTEM_ABOUT', default='System User'),
|
||||
env('SYSTEM_PERMISSIONS', default='admin')
|
||||
))
|
||||
|
||||
def create_session(self, username, password):
|
||||
logger.info(f"Creating session for user {username}...")
|
||||
# Create system boards
|
||||
boards_names = env('SYSTEM_BOARDS', default='General,Random,System').split(',')
|
||||
if "System" not in boards_names:
|
||||
boards_names.append("System")
|
||||
|
||||
# Check if the user exists
|
||||
logger.info(f"Checking if user {username} exists...")
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM users WHERE username = ?
|
||||
''', (username,))
|
||||
user = self.cursor.fetchone()
|
||||
log.info(f"Creating system boards: {', '.join(boards_names)}")
|
||||
for board_name in boards_names:
|
||||
db.execute_query("""
|
||||
INSERT INTO boards (name, description, owner_id)
|
||||
VALUES (?, ?, ?)
|
||||
""", (
|
||||
board_name,
|
||||
f"This is a automatically created board for {board_name}",
|
||||
1
|
||||
))
|
||||
|
||||
if not user:
|
||||
logger.warning(f"User {username} does not exist.")
|
||||
return None
|
||||
logger.info(f"User {username} exists.")
|
||||
|
||||
# Check if the password is correct
|
||||
logger.info(f"Checking password for user {username}...")
|
||||
if user[2] != password:
|
||||
logger.warning(f"Incorrect password for user {username}.")
|
||||
return None
|
||||
logger.info(f"Password for user {username} is correct.")
|
||||
|
||||
# Create a new session and overwrite the old one if it exists
|
||||
session = os.urandom(16).hex()
|
||||
self.cursor.execute('''
|
||||
UPDATE users SET session = ? WHERE username = ?
|
||||
''', (session, username))
|
||||
|
||||
self.connection.commit()
|
||||
logger.info(f"Session created for user {username}.")
|
||||
return session
|
||||
|
||||
|
||||
def get_session(self, session):
|
||||
logger.info(f"Getting session {session}...")
|
||||
|
||||
# Get session information
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM users WHERE session = ?
|
||||
''', (session,))
|
||||
user = self.cursor.fetchone()
|
||||
|
||||
if user:
|
||||
logger.info(f"Session {session} found.")
|
||||
return user
|
||||
logger.warning(f"Session {session} not found.")
|
||||
return None
|
||||
|
||||
|
||||
def delete_session(self, session):
|
||||
logger.info(f"Deleting session {session}...")
|
||||
|
||||
# Delete session
|
||||
self.cursor.execute('''
|
||||
UPDATE users SET session = NULL WHERE session = ?
|
||||
''', (session,))
|
||||
|
||||
self.connection.commit()
|
||||
logger.info(f"Session {session} deleted.")
|
||||
return True
|
||||
|
||||
|
||||
def user_to_dict(self, user):
|
||||
# Convert user to dictionary
|
||||
logger.info(f"Converting user to dictionary...")
|
||||
data = {
|
||||
'id': user[0],
|
||||
'name': user[1],
|
||||
'created_at': user[4]
|
||||
}
|
||||
logger.info(f"User converted to dictionary.")
|
||||
return data
|
||||
|
||||
|
||||
def get_user_posts(self, user_id, limit=10, offset=0):
|
||||
logger.info(f"Getting posts for user {user_id}...")
|
||||
|
||||
# Get posts for the user
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM posts WHERE user_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?
|
||||
''', (user_id, limit, offset))
|
||||
posts = self.cursor.fetchall()
|
||||
|
||||
if posts:
|
||||
logger.info(f"Posts found for user {user_id}.")
|
||||
return posts
|
||||
logger.warning(f"No posts found for user {user_id}.")
|
||||
return None
|
||||
|
||||
|
||||
def board_to_dict(self, board):
|
||||
# Convert board to dictionary
|
||||
logger.info(f"Converting board to dictionary...")
|
||||
data = {
|
||||
'id': board[0],
|
||||
'name': board[1],
|
||||
'description': board[3],
|
||||
'created_at': board[4]
|
||||
}
|
||||
logger.info(f"Board converted to dictionary.")
|
||||
return data
|
||||
|
||||
|
||||
def get_board_posts(self, board_id, limit=10, offset=0):
|
||||
logger.info(f"Getting posts for board {board_id}...")
|
||||
|
||||
# Get posts for the board
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM posts WHERE board_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?
|
||||
''', (board_id, limit, offset))
|
||||
posts = self.cursor.fetchall()
|
||||
|
||||
if posts:
|
||||
logger.info(f"Posts found for board {board_id}.")
|
||||
return posts
|
||||
logger.warning(f"No posts found for board {board_id}.")
|
||||
return None
|
||||
|
||||
|
||||
def get_all_boards_for_post(self):
|
||||
logger.info(f"Getting all boards...")
|
||||
|
||||
# Get all boards
|
||||
self.cursor.execute('''
|
||||
SELECT * FROM boards
|
||||
''')
|
||||
boards = self.cursor.fetchall()
|
||||
|
||||
if boards:
|
||||
logger.info(f"Boards found.")
|
||||
# Convert boards to dictionary
|
||||
boards = [{"id": board[0], "name": board[1]} for board in boards]
|
||||
boards.sort(key=lambda x: x['name'])
|
||||
return boards
|
||||
logger.warning(f"No boards found.")
|
||||
return None
|
||||
|
||||
def delete_post(self, post_id):
|
||||
logger.info(f"Deleting post {post_id}...")
|
||||
|
||||
# Delete post
|
||||
self.cursor.execute('''
|
||||
DELETE FROM posts WHERE id = ?
|
||||
''', (post_id,))
|
||||
|
||||
self.connection.commit()
|
||||
logger.info(f"Post {post_id} deleted.")
|
||||
return True
|
||||
log.info("First time run completed")
|
@ -1,44 +1,42 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "templates/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ board.name }}</h1>
|
||||
<h6>Created at: {{ board.created_at }}</h6>
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="../static/css/form.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block main_content %}
|
||||
<h1 class="board-name">/{{ board.name }}/</h1>
|
||||
<p>{{ board.description }}</p>
|
||||
{% if board.owner_id == session.user_id %}
|
||||
<h6><a href="/boards/delete/{{ board.id }}">Delete Board</a></h6>
|
||||
{% endif %}
|
||||
{% if session.user_id %}
|
||||
<br>
|
||||
<h3>Post to this board</h3>
|
||||
<form action="/post" method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="board_id" value="{{ board.id }}">
|
||||
<textarea name="content" placeholder="Content" required></textarea>
|
||||
<input type="file" name="attachments" multiple>
|
||||
<button type="submit">Post</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<br>
|
||||
<h1>Posts:</h1>
|
||||
<ul class="posts">
|
||||
{% for post in board.posts %}
|
||||
<ul class="post-list">
|
||||
{% for post in posts %}
|
||||
<li>
|
||||
<h3>From {{ post.user.name }}</h3>
|
||||
<h6>posted at {{ post.created_at }}</h6>
|
||||
{% if post.reference %}
|
||||
<h6><b>ref post:</b> <a href="/posts/{{ post.reference.id }}">{{ post.reference.short_content }}</a></h6>
|
||||
{% endif %}
|
||||
<p>{{ post.short_content }}</p>
|
||||
<h6><a href="/posts/{{ post.id }}">View Post</a>
|
||||
{% if post.replies|length > 0 %}
|
||||
({{ post.replies|length }} replies)
|
||||
{% endif %}
|
||||
{% if session.name == "SYSTEM" %}
|
||||
| <a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% elif session.name == post.user.name %}
|
||||
| <a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% endif %}
|
||||
</h6>
|
||||
{% include "templates/short_post.html" %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if board.posts|length == 0 %}
|
||||
<li>No posts found.</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div id="nav">
|
||||
<h5>Page {{ page }}</h5>
|
||||
{% if page > 1 %}
|
||||
<h5><a href="/boards/{{ board.name }}?page={{ page - 1 }}">Previous Page</a></h5>
|
||||
{% endif %}
|
||||
|
||||
{% if board.posts|length == 10 %}
|
||||
<h5><a href="/boards/{{ board.name }}?page={{ page + 1 }}">Next Page</a></h5>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if total_pages > 0 %}
|
||||
<div id="nav">
|
||||
<h5>Page {{ page }} of {{ total_pages }}</h5>
|
||||
{% if page > 1 %}
|
||||
<h5><a href="/boards/{{ board.name }}?page={{ page - 1 }}">Previous Page</a></h5>
|
||||
{% endif %}
|
||||
{% if posts|length == 10 %}
|
||||
<h5><a href="/boards/{{ board.name }}?page={{ page + 1 }}">Next Page</a></h5>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -1,30 +1,30 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "templates/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="../static/css/boards.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block main_content %}
|
||||
<h1>Boards</h1>
|
||||
<h5>Page {{ page }}</h5>
|
||||
<p>Here are the boards available on this site. To create a new board, click <a href="/boards/new">here</a>.</p>
|
||||
<br>
|
||||
<ul class="posts">
|
||||
<ul class="board-list">
|
||||
{% for board in boards %}
|
||||
<li>
|
||||
<h3>/{{ board.name }}/</h3>
|
||||
<h6>created at {{ board.created_at }}</h6>
|
||||
<h3><a class="board-name" href="/boards/{{ board.name }}">/{{ board.name }}/</a></h3>
|
||||
<p>{{ board.description }}</p>
|
||||
<h6><a href="/boards/{{ board.name }}">View Board</a></h6>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if boards|length == 0 %}
|
||||
<li>No boards found.</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div id="nav">
|
||||
<h5>Page {{ page }}</h5>
|
||||
{% if page > 1 %}
|
||||
<h5><a href="/boards?page={{ page - 1 }}">Previous Page</a></h5>
|
||||
{% endif %}
|
||||
|
||||
{% if boards|length == 10 %}
|
||||
<h5><a href="/boards?page={{ page + 1 }}">Next Page</a></h5>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if total_pages > 0 %}
|
||||
<div id="nav">
|
||||
<h5>Page {{ page }} of {{ total_pages }}</h5>
|
||||
{% if page > 1 %}
|
||||
<h5><a href="/boards?page={{ page - 1 }}">Previous Page</a></h5>
|
||||
{% endif %}
|
||||
{% if boards|length == 10 %}
|
||||
<h5><a href="/boards?page={{ page + 1 }}">Next Page</a></h5>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
9
src/html/error.html
Normal file
9
src/html/error.html
Normal file
@ -0,0 +1,9 @@
|
||||
{% extends "templates/base.html" %}
|
||||
|
||||
{% block main_content %}
|
||||
<h1>OOPSIE WOOPSIE!!</h1>
|
||||
<h3>UwU you did a slight fucky wucky >w<</h3>
|
||||
<br>
|
||||
<h3>Thwe bwunder in qwestion :3</h3>
|
||||
<p>{{error}}</p>
|
||||
{% endblock %}
|
@ -1,33 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "templates/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="../static/css/index.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block main_content %}
|
||||
<h1>Welcome to Prismic</h1>
|
||||
<p>Prismic is a simple textboard</p>
|
||||
<p>Prismic is a simple, fast, and secure way to share your thoughts with the world. Join us today and start sharing your ideas!</p>
|
||||
<br>
|
||||
<h1>Latest Posts:</h1>
|
||||
<ul class="posts">
|
||||
{% for post in posts %}
|
||||
<li>
|
||||
<h3>From {{ post.user.name }} in /{{ post.board.name }}/</h3>
|
||||
<h6>posted at {{ post.created_at }}</h6>
|
||||
{% if post.reference %}
|
||||
<h6><b>ref post:</b> <a href="/posts/{{ post.reference.id }}">{{ post.reference.short_content if post.reference.id is not none else '[NOT FOUND]' }}</a></h6>
|
||||
{% endif %}
|
||||
<p>{{ post.short_content }}</p>
|
||||
<h6><a href="/posts/{{ post.id }}">View Post</a>
|
||||
{% if post.replies|length > 0 %}
|
||||
({{ post.replies|length }} replies)
|
||||
{% endif %}
|
||||
{% if session.name == "SYSTEM" %}
|
||||
| <a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% elif session.name == post.user.name %}
|
||||
| <a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% endif %}
|
||||
</h6>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if posts|length == 0 %}
|
||||
<li>No posts found.</li>
|
||||
{% endif %}
|
||||
{% if session.name %}
|
||||
<p>Hello <a class="user-{{session.perms}}" href="/users/{{session.name}}">{{session.name}}</a>! You are logged in.</p>
|
||||
{% else %}
|
||||
<p><a href="/login">Login</a> or <a href="/register">Register</a></p>
|
||||
{% endif %}
|
||||
<br>
|
||||
<h3>Stats:</h3>
|
||||
<ul class="stats">
|
||||
<li>Total Posts: {{ total_posts }}</li>
|
||||
<li>Total Boards: {{ total_boards }}</li>
|
||||
<li>Total Users: {{ total_users }}</li>
|
||||
<li>Total Attachments: {{ total_attachments }}</li>
|
||||
</ul>
|
||||
{% endblock %}
|
@ -1,6 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "templates/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="../static/css/form.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block main_content %}
|
||||
<h1>Login</h1>
|
||||
<p><a href="/register">Register</a> if you don't have an account.</p>
|
||||
<br>
|
||||
|
22
src/html/new_board.html
Normal file
22
src/html/new_board.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% extends "templates/base.html" %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="../static/css/form.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block main_content %}
|
||||
<h1>New Board</h1>
|
||||
<br>
|
||||
<form method="POST" action="/boards/new">
|
||||
<label for="board_name">Board Name:</label>
|
||||
<input type="text" id="name" name="name" required>
|
||||
<br>
|
||||
<label for="board_description">Board Description:</label>
|
||||
<input type="text" id="description" name="description" required>
|
||||
<br>
|
||||
<button type="submit">Create Board</button>
|
||||
</form>
|
||||
{% if error %}
|
||||
<p style="color: red;">{{ error }}</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -1,20 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<form method="POST" action="/post">
|
||||
<label for="board">Board:</label>
|
||||
<select id="board" name="board">
|
||||
{% for board in boards %}
|
||||
<option value="{{ board.id }}">{{ board.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<br>
|
||||
<label for="content">Content:</label>
|
||||
<textarea id="content" name="content" rows="4" cols="50" required></textarea>
|
||||
<br>
|
||||
<button type="submit">Post</button>
|
||||
</form>
|
||||
{% if error %}
|
||||
<p style="color: red;">{{ error }}</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -1,61 +1,75 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "templates/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>From {{ post.user.name }} in /{{ post.board.name }}/</h2>
|
||||
<h6>posted at {{ post.created_at }}</h6>
|
||||
{% if post.reference %}
|
||||
<h6><b>ref post:</b> <a href="/posts/{{ post.reference.id }}">{{ post.reference.content }}</a></h6>
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="../static/css/form.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block main_content %}
|
||||
<h1><a href="/users/{{post.user.name}}" class="user-{{post.user.perms}}">{{post.user.name}}</a> posted in <a href="/boards/{{post.board.name}}" class="board-name">/{{post.board.name}}/</a></h1>
|
||||
<h4>posted at <span class="time">{{post.created_at}}</span></h4>
|
||||
{% if post.reference and post.reference.show %}
|
||||
<h4>ref post: <a href="/posts/{{post.reference.id}}">{{post.reference.short_content}}</a></h4>
|
||||
{% endif %}
|
||||
<p>{{ post.content }}</p>
|
||||
{% if session.name == "SYSTEM" %}
|
||||
<h6><a href="/delete/post/{{ post.id }}">Delete</a></h6>
|
||||
{% elif session.name == post.user.name %}
|
||||
<h6><a href="/delete/post/{{ post.id }}">Delete</a></h6>
|
||||
{% if post.attachments %}
|
||||
<h4>{{post.attachments|length}} attachments</h4>
|
||||
<div class="attachments">
|
||||
{% for attachment in post.attachments %}
|
||||
{% if attachment.type == "image" %}
|
||||
<a href="{{attachment.url}}"><img src="{{attachment.url}}" alt="{{attachment.file_name}}" width="350px"></a>
|
||||
{% elif attachment.type == "video" %}
|
||||
<video width="350px" controls>
|
||||
<source src="{{attachment.url}}" type="video/mp4">
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
{% elif attachment.type == "audio" %}
|
||||
<audio controls>
|
||||
<source src="{{attachment.url}}" type="audio/mpeg">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
{% else %}
|
||||
<a href="{{attachment.url}}">{{attachment.file_name}}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p>{{post.content}}</p>
|
||||
<h6>
|
||||
{% if session.name == "SYSTEM" %}
|
||||
<a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% elif session.name == post.user.name %}
|
||||
<a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% endif %}
|
||||
</h6>
|
||||
{% if session.user_id %}
|
||||
<br>
|
||||
<h3>Reply to this post</h3>
|
||||
<form action="/post" method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="board_id" value="{{ post.board.id }}">
|
||||
<input type="hidden" name="reference" value="{{ post.id }}">
|
||||
<input type="hidden" name="type" value="comment">
|
||||
<textarea name="content" placeholder="Content" required></textarea>
|
||||
<input type="file" name="attachments" multiple>
|
||||
<button type="submit">Post</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<br>
|
||||
|
||||
<h3>Replies:</h3>
|
||||
|
||||
<ul class="posts">
|
||||
{% for reply in post.replies %}
|
||||
<h1>{{ post.replies }} Replies</h1>
|
||||
<ul class="post-list">
|
||||
{% for post in replies %}
|
||||
<li>
|
||||
<h3>From {{ reply.user.name }} in /{{ reply.board.name }}/</h3>
|
||||
<h6>posted at {{ reply.created_at }}</h6>
|
||||
<p>{{ reply.short_content }}</p>
|
||||
<h6><a href="/posts/{{ reply.id }}">View Reply</a>
|
||||
{% if session.name == "SYSTEM" %}
|
||||
| <a href="/delete/post/{{ reply.id }}">Delete</a>
|
||||
{% elif session.name == post.user.name %}
|
||||
| <a href="/delete/post/{{ reply.id }}">Delete</a>
|
||||
{% endif %}
|
||||
</h6>
|
||||
{% include "templates/post.html" %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if post.replies|length == 0 %}
|
||||
<li>No replies found.</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div id="nav">
|
||||
<h5>Page {{ page }}</h5>
|
||||
{% if page > 1 %}
|
||||
<h5><a href="/posts/{{ post.id }}?page={{ page - 1 }}">Previous Page</a></h5>
|
||||
{% endif %}
|
||||
|
||||
{% if post.replies|length == 10 %}
|
||||
<h5><a href="/posts/{{ post.id }}?page={{ page + 1 }}">Next Page</a></h5>
|
||||
{% endif %}
|
||||
</div>
|
||||
<br>
|
||||
<form method="POST" action="/post">
|
||||
<input type="hidden" name="ref" value="{{ post.id }}">
|
||||
<input type="hidden" name="board" value="{{ post.board.id }}">
|
||||
<input type="hidden" name="redirect" value="/posts/{{ post.id }}">
|
||||
<label for="content">Reply:</label>
|
||||
<textarea id="content" name="content" rows="4" cols="50" required></textarea>
|
||||
<br>
|
||||
<button type="submit">Post</button>
|
||||
</form>
|
||||
{% if error %}
|
||||
<p style="color: red;">{{ error }}</p>
|
||||
{% if total_pages > 0 %}
|
||||
<div id="nav">
|
||||
<h5>Page {{ page }} of {{ total_pages }}</h5>
|
||||
{% if page > 1 %}
|
||||
<h5><a href="/posts/{{ post.id }}?page={{ page - 1 }}">Previous Page</a></h5>
|
||||
{% endif %}
|
||||
{% if replies|length == 10 %}
|
||||
<h5><a href="/posts/{{ post.id }}?page={{ page + 1 }}">Next Page</a></h5>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -1,42 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "templates/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% block main_content %}
|
||||
<h1>Posts</h1>
|
||||
<h5>Page {{ page }}</h5>
|
||||
<p>Here are the latest posts from all boards.</p>
|
||||
<br>
|
||||
<ul class="posts">
|
||||
<ul class="post-list">
|
||||
{% for post in posts %}
|
||||
<li>
|
||||
<h3>From {{ post.user.name }} in /{{ post.board.name }}/</h3>
|
||||
<h6>posted at {{ post.created_at }}</h6>
|
||||
{% if post.reference %}
|
||||
<h6><b>ref post:</b> <a href="/posts/{{ post.reference.id }}">{{ post.reference.short_content }}</a></h6>
|
||||
{% endif %}
|
||||
<p>{{ post.short_content }}</p>
|
||||
<h6><a href="/posts/{{ post.id }}">View post</a>
|
||||
{% if post.replies|length > 0 %}
|
||||
({{ post.replies|length }} replies)
|
||||
{% endif %}
|
||||
{% if session.name == "SYSTEM" %}
|
||||
| <a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% elif session.name == post.user.name %}
|
||||
| <a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% endif %}
|
||||
</h6>
|
||||
{% include "templates/short_post.html" %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if posts|length == 0 %}
|
||||
<li>No replies found.</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div id="nav">
|
||||
<h5>Page {{ page }}</h5>
|
||||
{% if page > 1 %}
|
||||
<h5><a href="/posts?page={{ page - 1 }}">Previous Page</a></h5>
|
||||
{% endif %}
|
||||
|
||||
{% if posts|length == 10 %}
|
||||
<h5><a href="/posts?page={{ page + 1 }}">Next Page</a></h5>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if total_pages > 0 %}
|
||||
<div id="nav">
|
||||
<h5>Page {{ page }} of {{ total_pages }}</h5>
|
||||
{% if page > 1 %}
|
||||
<h5><a href="/posts?page={{ page - 1 }}">Previous Page</a></h5>
|
||||
{% endif %}
|
||||
{% if posts|length == 10 %}
|
||||
<h5><a href="/posts?page={{ page + 1 }}">Next Page</a></h5>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -1,6 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "templates/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="../static/css/form.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block main_content %}
|
||||
<h1>Register</h1>
|
||||
<br>
|
||||
<form method="POST" action="/register">
|
||||
|
52
src/html/settings.html
Normal file
52
src/html/settings.html
Normal file
@ -0,0 +1,52 @@
|
||||
{% extends "templates/base.html" %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="../static/css/form.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block main_content %}
|
||||
<h1>Settings</h1>
|
||||
<br>
|
||||
<form method="POST" action="/settings">
|
||||
<input type="hidden" name="type" value="username">
|
||||
<label for="username">Change username:</label>
|
||||
<input type="text" id="username" name="username" value="{{ session.name }}" required>
|
||||
<button type="submit">Change username</button>
|
||||
</form>
|
||||
<br>
|
||||
<form method="POST" action="/settings">
|
||||
<input type="hidden" name="type" value="password">
|
||||
<label for="old_password">Old password:</label>
|
||||
<input type="password" id="old_password" name="old_password" required>
|
||||
<label for="new_password">New password:</label>
|
||||
<input type="password" id="new_password" name="new_password" required>
|
||||
<button type="submit">Change password</button>
|
||||
</form>
|
||||
<br>
|
||||
<form method="POST" action="/settings">
|
||||
<input type="hidden" name="type" value="about">
|
||||
<label for="about">About me:</label>
|
||||
<textarea id="about" name="about" required>{{ session.about }}</textarea>
|
||||
<button type="submit">Change about me</button>
|
||||
</form>
|
||||
<br>
|
||||
<form method="POST" action="/settings" enctype="multipart/form-data">
|
||||
<input type="hidden" name="type" value="avatar">
|
||||
<label for="avatar">Change avatar:</label>
|
||||
<input type="file" id="avatar" name="avatar" accept="image/png, image/jpeg, image/jpg, image/gif" required>
|
||||
<button type="submit">Change avatar</button>
|
||||
</form>
|
||||
<br>
|
||||
<form method="POST" action="/settings">
|
||||
<input type="hidden" name="type" value="delete">
|
||||
<label for="delete_account">Delete account:</label>
|
||||
<input type="password" id="delete_account" name="delete_account" placeholder="Enter your password to confirm" required>
|
||||
<button type="submit">Delete account</button>
|
||||
</form>
|
||||
{% if error %}
|
||||
<p style="color: red;">{{ error }}</p>
|
||||
{% endif %}
|
||||
{% if success %}
|
||||
<p style="color: green;">{{ success }}</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -4,24 +4,24 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Prismic</title>
|
||||
<link rel="stylesheet" href="../static/base.css">
|
||||
<meta name="author" content="Alfie King">
|
||||
<link rel="stylesheet" href="../../static/css/base.css">
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div id="title">
|
||||
<h1>Prismic</h1>
|
||||
<h2>ver: 1.0</h2>
|
||||
<h2>ver: 2.0</h2>
|
||||
</div>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/boards">Boards</a></li>
|
||||
<li><a href="/posts">Posts</a></li>
|
||||
{% if session.name %}
|
||||
<li><a href="/post">Post</a></li>
|
||||
{% endif %}
|
||||
<li><a href="/users">Users</a></li>
|
||||
<li>{% if session.name %}
|
||||
<a href="/user/{{ session.name }}">{{ session.name }}</a>
|
||||
<a class="user-{{session.perms}}" href="/users/{{ session.name }}">{{ session.name }}</a>
|
||||
{% else %}
|
||||
<a href="/login">Login</a>
|
||||
{% endif %}</li>
|
||||
@ -30,10 +30,10 @@
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
{% block main_content %}{% endblock %}
|
||||
</main>
|
||||
<footer>
|
||||
<p>Created by <a href="https://alfieking.dev">Alfie King</a>{% if session.name %} | <a href="/logout">Logout</a>{% endif %}</p>
|
||||
<p>Created by <a href="https://alfieking.dev">Alfie King</a>{% if session.name %} | <a href="/settings">Settings</a> | <a href="/logout">Logout</a>{% endif %}</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
29
src/html/templates/post.html
Normal file
29
src/html/templates/post.html
Normal file
@ -0,0 +1,29 @@
|
||||
<div class="post">
|
||||
<h3><a href="/users/{{post.user.name}}" class="user-{{post.user.perms}}">{{post.user.name}}</a> posted in <a href="/boards/{{post.board.name}}" class="board-name">/{{post.board.name}}/</a></h3>
|
||||
<h6>posted at <span class="time">{{post.created_at}}</span></h6>
|
||||
{% if post.reference and post.reference.show %}
|
||||
<h6>ref post: <a href="/posts/{{post.reference.id}}">{{post.reference.short_content}}</a></h6>
|
||||
{% endif %}
|
||||
{% if post.attachments %}
|
||||
<h6>{{post.attachments|length}} attachments</h6>
|
||||
<div class="attachments">
|
||||
{% for attachment in post.attachments %}
|
||||
{% if attachment.type == "image" %}
|
||||
<a href="{{attachment.url}}"><img src="{{attachment.url}}" alt="{{attachment.file_name}}" width="100px"></a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p>{{post.short_content}}</p>
|
||||
<h6>
|
||||
<a href="/posts/{{ post.id }}">View Post</a>
|
||||
{% if post.replies > 0 %}
|
||||
({{ post.replies }} replies)
|
||||
{% endif %}
|
||||
{% if session.name == "SYSTEM" %}
|
||||
| <a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% elif session.name == post.user.name %}
|
||||
| <a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% endif %}
|
||||
</h6>
|
||||
</div>
|
29
src/html/templates/short_post.html
Normal file
29
src/html/templates/short_post.html
Normal file
@ -0,0 +1,29 @@
|
||||
<div class="post">
|
||||
<h3><a href="/users/{{post.user.name}}" class="user-{{post.user.perms}}">{{post.user.name}}</a> posted in <a href="/boards/{{post.board.name}}" class="board-name">/{{post.board.name}}/</a></h3>
|
||||
<h6>posted at <span class="time">{{post.created_at}}</span></h6>
|
||||
{% if post.reference and post.reference.show %}
|
||||
<h6>ref post: <a href="/post/{{post.reference.url}}">{{post.reference.short_content}}</a></h6>
|
||||
{% endif %}
|
||||
{% if post.attachments %}
|
||||
<h6>{{post.attachments|length}} attachments</h6>
|
||||
<div class="attachments">
|
||||
{% for attachment in post.attachments %}
|
||||
{% if attachment.type == "image" %}
|
||||
<a href="{{attachment.url}}"><img src="{{attachment.url}}" alt="{{attachment.file_name}}" width="100px"></a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p>{{post.short_content}}</p>
|
||||
<h6>
|
||||
<a href="/posts/{{ post.id }}">View Post</a>
|
||||
{% if post.replies > 0 %}
|
||||
({{ post.replies }} replies)
|
||||
{% endif %}
|
||||
{% if session.name == "SYSTEM" %}
|
||||
| <a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% elif session.name == post.user.name %}
|
||||
| <a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% endif %}
|
||||
</h6>
|
||||
</div>
|
@ -1,43 +1,40 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "templates/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ user.name }}</h1>
|
||||
<h6>Joined at: {{ user.created_at }}</h6>
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="../static/css/user.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block main_content %}
|
||||
<div class="user">
|
||||
{% if user.avatar_url %}
|
||||
<img src="{{user.avatar_url}}" alt="Avatar">
|
||||
{% else %}
|
||||
<img src="/static/content/default_avatar.png" alt="Default Avatar">
|
||||
{% endif %}
|
||||
<div class="info">
|
||||
<h1 class="user-{{user.perms}}">{{user.name}}</h1>
|
||||
<h4>Joined at <span class="time">{{user.created_at}}</span></h4>
|
||||
</div>
|
||||
</div>
|
||||
<p>{{user.about}}</p>
|
||||
<br>
|
||||
<h1>Posts:</h1>
|
||||
<ul class="posts">
|
||||
{% for post in user.posts %}
|
||||
<h2><span class="user-{{user.perms}}">{{ user.name }}'s</span> Posts</h2>
|
||||
<ul class="post-list">
|
||||
{% for post in posts %}
|
||||
<li>
|
||||
<h3>From {{ post.user.name }} in /{{ post.board.name }}/</h3>
|
||||
<h6>posted at {{ post.created_at }}</h6>
|
||||
{% if post.reference %}
|
||||
<h6><b>ref post:</b> <a href="/posts/{{ post.reference.id }}">{{ post.reference.content }}</a></h6>
|
||||
{% endif %}
|
||||
<p>{{ post.content }}</p>
|
||||
<h6><a href="/posts/{{ post.id }}">View Post</a>
|
||||
{% if post.replies|length > 0 %}
|
||||
({{ post.replies|length }} replies)
|
||||
{% endif %}
|
||||
{% if session.name == "SYSTEM" %}
|
||||
| <a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% elif session.name == post.user.name %}
|
||||
| <a href="/delete/post/{{ post.id }}">Delete</a>
|
||||
{% endif %}
|
||||
</h6>
|
||||
{% include "templates/short_post.html" %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if user.posts|length == 0 %}
|
||||
<li>No posts found.</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div id="nav">
|
||||
<h5>Page {{ page }}</h5>
|
||||
{% if page > 1 %}
|
||||
<h5><a href="/user/{{ user.name }}?page={{ page - 1 }}">Previous Page</a></h5>
|
||||
{% endif %}
|
||||
|
||||
{% if user.posts|length == 10 %}
|
||||
<h5><a href="/user/{{ user.name }}?page={{ page + 1 }}">Next Page</a></h5>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if total_pages > 0 %}
|
||||
<div id="nav">
|
||||
<h5>Page {{ page }} of {{ total_pages }}</h5>
|
||||
{% if page > 1 %}
|
||||
<h5><a href="/users/{{user.d}}?page={{ page - 1 }}">Previous Page</a></h5>
|
||||
{% endif %}
|
||||
{% if posts|length == 10 %}
|
||||
<h5><a href="/users/{{user.id}}?page={{ page + 1 }}">Next Page</a></h5>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
39
src/html/users.html
Normal file
39
src/html/users.html
Normal file
@ -0,0 +1,39 @@
|
||||
{% extends "templates/base.html" %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="../static/css/user.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block main_content %}
|
||||
<h1>Users</h1>
|
||||
<p>Here are the boards users on this site.</p>
|
||||
<br>
|
||||
<ul class="board-list">
|
||||
{% for user in users %}
|
||||
<li>
|
||||
<a href="/users/{{user.name}}" class="user">
|
||||
{% if user.avatar_url %}
|
||||
<img src="{{user.avatar_url}}" alt="Avatar">
|
||||
{% else %}
|
||||
<img src="/static/content/default_avatar.png" alt="Default Avatar">
|
||||
{% endif %}
|
||||
<div class="info">
|
||||
<h1 class="user-{{user.perms}}">{{user.name}}</h1>
|
||||
<h4>Joined at <span class="time">{{user.created_at}}</span></h4>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if total_pages > 0 %}
|
||||
<div id="nav">
|
||||
<h5>Page {{ page }} of {{ total_pages }}</h5>
|
||||
{% if page > 1 %}
|
||||
<h5><a href="/users?page={{ page - 1 }}">Previous Page</a></h5>
|
||||
{% endif %}
|
||||
{% if boards|length == 10 %}
|
||||
<h5><a href="/users?page={{ page + 1 }}">Next Page</a></h5>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
974
src/main.py
974
src/main.py
File diff suppressed because it is too large
Load Diff
BIN
src/static/content/default_avatar.png
Normal file
BIN
src/static/content/default_avatar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 387 KiB |
@ -2,6 +2,10 @@
|
||||
--bg: #000;
|
||||
--fg: #fff;
|
||||
--accent: #7139f3;
|
||||
--admin: #39f3da;
|
||||
--user: #5b6dd4;
|
||||
--board: #515699;
|
||||
--time: #30597a;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -83,11 +87,18 @@ a {
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--fg);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
ul.posts {
|
||||
div#nav {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
ul.board-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
@ -97,90 +108,41 @@ ul.posts {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
form {
|
||||
ul.post-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"] {
|
||||
padding: 10px;
|
||||
border: 1px solid var(--fg);
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
font-size: 1rem;
|
||||
width: 300px;
|
||||
.user-admin {
|
||||
color: var(--admin);
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
input[type="password"]:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
.user-user {
|
||||
color: var(--user);
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background-color: var(--accent);
|
||||
color: var(--bg);
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
width: 300px;
|
||||
font-weight: 600;
|
||||
.board-name {
|
||||
color: var(--board);
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
background-color: var(--fg);
|
||||
color: var(--bg);
|
||||
}
|
||||
button[type="submit"]:disabled {
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
cursor: not-allowed;
|
||||
.time {
|
||||
color: var(--time);
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 10px;
|
||||
border: 1px solid var(--fg);
|
||||
border-radius: 5px;
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
font-size: 1rem;
|
||||
width: 300px;
|
||||
#title {
|
||||
background: linear-gradient(90deg, var(--accent), var(--admin));
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
textarea {
|
||||
padding: 10px;
|
||||
border: 1px solid var(--fg);
|
||||
border-radius: 5px;
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
font-size: 1rem;
|
||||
width: 300px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
div#nav {
|
||||
.attachments {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
white-space:pre;
|
||||
align-items: center;
|
||||
}
|
89
src/static/css/form.css
Normal file
89
src/static/css/form.css
Normal file
@ -0,0 +1,89 @@
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"] {
|
||||
padding: 10px;
|
||||
border: 1px solid var(--fg);
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
font-size: 1rem;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
input[type="password"]:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: linear-gradient(90deg, var(--accent), var(--admin));
|
||||
color: var(--bg);
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
width: 300px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
button[type="submit"]:disabled {
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 10px;
|
||||
border: 1px solid var(--fg);
|
||||
border-radius: 5px;
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
font-size: 1rem;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
textarea {
|
||||
padding: 10px;
|
||||
border: 1px solid var(--fg);
|
||||
border-radius: 5px;
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
font-size: 1rem;
|
||||
width: 300px;
|
||||
height: 150px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
padding: 10px;
|
||||
border: 1px solid var(--fg);
|
||||
border-radius: 5px;
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
font-size: 1rem;
|
||||
width: 300px;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
7
src/static/css/index.css
Normal file
7
src/static/css/index.css
Normal file
@ -0,0 +1,7 @@
|
||||
ul.stats {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
27
src/static/css/user.css
Normal file
27
src/static/css/user.css
Normal file
@ -0,0 +1,27 @@
|
||||
.user {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
border: 1px solid var(--fg);
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.user img {
|
||||
height: 80px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
a.user {
|
||||
text-decoration: none;
|
||||
color: var(--fg);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
a.user:hover {
|
||||
opacity: 1;
|
||||
transform: scale(1.05);
|
||||
border-color: var(--accent);
|
||||
}
|
145
src/utils.py
Normal file
145
src/utils.py
Normal file
@ -0,0 +1,145 @@
|
||||
import logging, re, database, datetime, os
|
||||
from dateutil import tz
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
STRING = re.compile(r'^[\w ]+$')
|
||||
STRING_NO_SPACE = re.compile(r'^[\w]+$')
|
||||
TZ = os.getenv('TZ', 'UTC')
|
||||
|
||||
|
||||
def convert_utc_to_local(utc_dt:str, local_tz:str) -> str:
|
||||
utc_dt = datetime.datetime.strptime(utc_dt, '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
log.debug(f"Converting UTC datetime {utc_dt} to local timezone {local_tz}")
|
||||
if not isinstance(utc_dt, datetime.datetime):
|
||||
raise ValueError("utc_dt must be a datetime object")
|
||||
|
||||
if not isinstance(local_tz, str):
|
||||
raise ValueError("local_tz must be a string")
|
||||
|
||||
local_tz = tz.gettz(local_tz)
|
||||
if local_tz is None:
|
||||
raise ValueError(f"Invalid timezone: {local_tz}")
|
||||
|
||||
local_dt = utc_dt.replace(tzinfo=tz.tzutc()).astimezone(local_tz)
|
||||
log.debug(f"Converted datetime: {local_dt}")
|
||||
return local_dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
|
||||
def validate_string(input_string:str, allowed_regex:re.Pattern=STRING, max_length:int=None, min_length:int=None) -> bool:
|
||||
if max_length and len(input_string) > max_length:
|
||||
return False
|
||||
if min_length and len(input_string) < min_length:
|
||||
return False
|
||||
if not re.match(allowed_regex, input_string):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def validate_board_name(board_name:str) -> bool:
|
||||
log.debug(f"Validating board name: {board_name}")
|
||||
return validate_string(board_name, allowed_regex=STRING_NO_SPACE, max_length=20, min_length=3)
|
||||
|
||||
|
||||
def validate_username(username:str) -> bool:
|
||||
log.debug(f"Validating username: {username}")
|
||||
return validate_string(username, allowed_regex=STRING_NO_SPACE, max_length=20, min_length=3)
|
||||
|
||||
|
||||
class data_converter:
|
||||
def __init__(self, db:database.Database):
|
||||
self.db = db
|
||||
|
||||
def board_to_dict(self, board):
|
||||
return {
|
||||
'id': board[0],
|
||||
'name': board[1],
|
||||
'description': board[2],
|
||||
'created_at': convert_utc_to_local(board[3], 'Europe/London'),
|
||||
'owner_id': board[4]
|
||||
}
|
||||
|
||||
def post_to_dict(self, post, show_reference:bool=True):
|
||||
log.debug(f"Converting post with id ({post[0]}) to dict")
|
||||
data = {
|
||||
'id': post[0],
|
||||
'content': post[3],
|
||||
'short_content': post[3][:50] + '...' if len(post[3]) > 50 else post[3],
|
||||
'created_at': convert_utc_to_local(post[6], 'Europe/London'),
|
||||
'type': post[5],
|
||||
}
|
||||
|
||||
# Get the user who created the post
|
||||
user = self.db.execute_query("SELECT * FROM users WHERE id = ?", (post[1],), fetch_type=database.FETCHONE)
|
||||
if user:
|
||||
data['user'] = {
|
||||
'id': user[0],
|
||||
'name': user[1],
|
||||
'about': user[3],
|
||||
'avatar_url': '/users/' + str(user[0]) + '/avatar' if user[5] else None,
|
||||
'perms': user[4],
|
||||
}
|
||||
else:
|
||||
data['user'] = None
|
||||
|
||||
# Get the board where the post was created
|
||||
board = self.db.execute_query("SELECT * FROM boards WHERE id = ?", (post[2],), fetch_type=database.FETCHONE)
|
||||
if board:
|
||||
data['board'] = {
|
||||
'id': board[0],
|
||||
'name': board[1],
|
||||
'description': board[2],
|
||||
'created_at': convert_utc_to_local(board[3], 'Europe/London'),
|
||||
'owner_id': board[4]
|
||||
}
|
||||
else:
|
||||
data['board'] = None
|
||||
|
||||
# Get the reference post if it exists
|
||||
if post[4]:
|
||||
reference_post = self.db.execute_query("SELECT * FROM posts WHERE id = ?", (post[4],), fetch_type=database.FETCHONE)
|
||||
if reference_post:
|
||||
data['reference'] = {
|
||||
'id': reference_post[0],
|
||||
'content': reference_post[3],
|
||||
'short_content': reference_post[3][:25] + '...' if len(reference_post[3]) > 25 else reference_post[3],
|
||||
'created_at': convert_utc_to_local(reference_post[6], 'Europe/London'),
|
||||
'type': reference_post[5],
|
||||
'show': show_reference
|
||||
}
|
||||
else:
|
||||
data['reference'] = None
|
||||
else:
|
||||
data['reference'] = None
|
||||
|
||||
# Get the attachments for the post
|
||||
attachments = self.db.execute_query("SELECT * FROM attachments WHERE post_id = ?", (post[0],), fetch_type=database.FETCHALL)
|
||||
data['attachments'] = []
|
||||
for attachment in attachments:
|
||||
data['attachments'].append({
|
||||
'id': attachment[0],
|
||||
'file_name': attachment[3],
|
||||
'created_at': convert_utc_to_local(attachment[5], 'Europe/London'),
|
||||
'url': '/attachment/' + str(attachment[0]),
|
||||
'type': attachment[4].split('/')[0],
|
||||
'mime_type': attachment[4]
|
||||
})
|
||||
|
||||
# Get the number of replies to the post
|
||||
replies = self.db.execute_query("SELECT COUNT(*) FROM posts WHERE reference = ?", (post[0],), fetch_type=database.FETCHONE)
|
||||
data['replies'] = replies[0] if replies else 0
|
||||
log.debug(f"Post converted to dict")
|
||||
return data
|
||||
|
||||
|
||||
def user_to_dict(self, user):
|
||||
return {
|
||||
'id': user[0],
|
||||
'name': user[1],
|
||||
'about': user[3],
|
||||
'avatar_url': '/users/' + str(user[0]) + '/avatar' if user[5] else None,
|
||||
'perms': user[4],
|
||||
'created_at': convert_utc_to_local(user[6], 'Europe/London'),
|
||||
'avatar_type': user[8],
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user