Merge pull request 'v2-overhaul' (#1) from v2-overhaul into main
Reviewed-on: #1
This commit was merged in pull request #1.
	This commit is contained in:
		
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,6 @@
 | 
				
			|||||||
.env
 | 
					 | 
				
			||||||
.venv
 | 
					.venv
 | 
				
			||||||
app.db
 | 
					app.log
 | 
				
			||||||
__pycache__
 | 
					__pycache__/
 | 
				
			||||||
 | 
					database.db
 | 
				
			||||||
 | 
					.env
 | 
				
			||||||
flask_session
 | 
					flask_session
 | 
				
			||||||
@@ -10,7 +10,7 @@ COPY requirements.txt .
 | 
				
			|||||||
RUN pip install --no-cache-dir -r requirements.txt
 | 
					RUN pip install --no-cache-dir -r requirements.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Copy the rest of the application code into the container
 | 
					# Copy the rest of the application code into the container
 | 
				
			||||||
COPY src/ ./src
 | 
					COPY src src
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Expose the port the app runs on
 | 
					# Expose the port the app runs on
 | 
				
			||||||
EXPOSE 5000
 | 
					EXPOSE 5000
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,6 @@
 | 
				
			|||||||
prismic is a simple messageboard made in python
 | 
					prismic is a simple messageboard made in python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Planned features
 | 
					## Planned features
 | 
				
			||||||
- [ ] user board creation
 | 
					- [x] user board creation
 | 
				
			||||||
- [ ] custom profiles
 | 
					- [x] custom profiles
 | 
				
			||||||
- [x] moderation tools
 | 
					- [x] moderation tools
 | 
				
			||||||
@@ -1,2 +1,5 @@
 | 
				
			|||||||
flask
 | 
					mysql-connector-python # only required for external mysql connection
 | 
				
			||||||
 | 
					python-dotenv # only required if using .env file
 | 
				
			||||||
 | 
					python-dateutil 
 | 
				
			||||||
flask-session
 | 
					flask-session
 | 
				
			||||||
 | 
					flask
 | 
				
			||||||
							
								
								
									
										716
									
								
								src/database.py
									
									
									
									
									
								
							
							
						
						
									
										716
									
								
								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
 | 
					log = logging.getLogger(__name__)
 | 
				
			||||||
logger = logging.getLogger(__name__)
 | 
					FETCHALL = 0
 | 
				
			||||||
 | 
					FETCHONE = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Database:
 | 
					class Database:
 | 
				
			||||||
    def __init__(self, db_name):
 | 
					    def __init__(self):
 | 
				
			||||||
        # Initialize the database connection
 | 
					        self.connection = None
 | 
				
			||||||
        logger.info("Initializing database connection...")
 | 
					        self.cursor = None
 | 
				
			||||||
        self.connection = sqlite3.connect(db_name, check_same_thread=False)
 | 
					
 | 
				
			||||||
 | 
					    def connect_sqlite(self, db_path):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.connection = sqlite3.connect(db_path, check_same_thread=False)
 | 
				
			||||||
            self.cursor = self.connection.cursor()
 | 
					            self.cursor = self.connection.cursor()
 | 
				
			||||||
        logger.info("Database connection established.")
 | 
					            log.info("Connected to SQLite database")
 | 
				
			||||||
 | 
					        except sqlite3.Error as e:
 | 
				
			||||||
 | 
					            log.error(f"SQLite connection error: {e}")
 | 
				
			||||||
 | 
					            raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Create users table if it doesn't exist
 | 
					    def connect_mysql(self):
 | 
				
			||||||
        logger.info("Creating users table if it doesn't exist...")
 | 
					        try:
 | 
				
			||||||
        self.cursor.execute('''
 | 
					            self.connection = mysql.connector.connect(
 | 
				
			||||||
            CREATE TABLE IF NOT EXISTS users (
 | 
					                host=env('MYSQL_HOST'),
 | 
				
			||||||
 | 
					                user=env('MYSQL_USER'),
 | 
				
			||||||
 | 
					                password=env('MYSQL_PASSWORD'),
 | 
				
			||||||
 | 
					                database=env('MYSQL_DATABASE')
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            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):
 | 
				
			||||||
 | 
					        if self.cursor:
 | 
				
			||||||
 | 
					            self.cursor.close()
 | 
				
			||||||
 | 
					        if self.connection:
 | 
				
			||||||
 | 
					            self.connection.close()
 | 
				
			||||||
 | 
					        log.warning("Database connection closed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def execute_query(self, query, params=None, fetch_type=FETCHALL):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if params:
 | 
				
			||||||
 | 
					                self.cursor.execute(query, params)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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,
 | 
					            id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
				
			||||||
                username TEXT NOT NULL UNIQUE,
 | 
					            username TEXT NOT NULL,
 | 
				
			||||||
            password TEXT NOT NULL,
 | 
					            password TEXT NOT NULL,
 | 
				
			||||||
                session TEXT,
 | 
					            about TEXT DEFAULT 'No description',
 | 
				
			||||||
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 | 
					            permissions TEXT DEFAULT 'user',
 | 
				
			||||||
            )
 | 
					            avatar MEDIUMBLOB,
 | 
				
			||||||
        ''')
 | 
					 | 
				
			||||||
        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,
 | 
					            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 | 
				
			||||||
                FOREIGN KEY (owner_id) REFERENCES users (id) ON DELETE SET NULL
 | 
					            token TEXT,
 | 
				
			||||||
 | 
					            avatar_type TEXT
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        ''')
 | 
					    """)
 | 
				
			||||||
        logger.info("Boards table created.")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Create posts table if it doesn't exist
 | 
					    # Create posts table
 | 
				
			||||||
        logger.info("Creating posts table if it doesn't exist...")
 | 
					    log.info("Creating posts table")
 | 
				
			||||||
        self.cursor.execute('''
 | 
					    db.execute_query("""
 | 
				
			||||||
            CREATE TABLE IF NOT EXISTS posts (
 | 
					        CREATE TABLE posts (
 | 
				
			||||||
            id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
					            id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
				
			||||||
            user_id INTEGER NOT NULL,
 | 
					            user_id INTEGER NOT NULL,
 | 
				
			||||||
            board_id INTEGER NOT NULL,
 | 
					            board_id INTEGER NOT NULL,
 | 
				
			||||||
            content TEXT NOT NULL,
 | 
					            content TEXT NOT NULL,
 | 
				
			||||||
 | 
					            reference INTREGER,
 | 
				
			||||||
 | 
					            type TEXT DEFAULT 'post',
 | 
				
			||||||
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 | 
					            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 | 
				
			||||||
                refence INTEGER,
 | 
					            FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
 | 
				
			||||||
                FOREIGN KEY (refence) REFERENCES posts (id) ON DELETE CASCADE,
 | 
					            FOREIGN KEY (board_id) REFERENCES boards (id) ON DELETE CASCADE,
 | 
				
			||||||
                FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL,
 | 
					            FOREIGN KEY (reference) REFERENCES posts (id) ON DELETE SET NULL
 | 
				
			||||||
                FOREIGN KEY (board_id) REFERENCES boards (id) ON DELETE CASCADE
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        ''')
 | 
					    """)
 | 
				
			||||||
        logger.info("Posts table created.")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # 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)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    """)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def close(self):
 | 
					    # Create attachments table
 | 
				
			||||||
        # Close the database connection
 | 
					    db.execute_query("""
 | 
				
			||||||
        logger.info("Closing database connection...")
 | 
					        CREATE TABLE attachments (
 | 
				
			||||||
        self.connection.commit()
 | 
					            id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
				
			||||||
        self.connection.close()
 | 
					            post_id INTEGER NOT NULL,
 | 
				
			||||||
        logger.info("Database connection closed.")
 | 
					            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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_user(self, username, password):
 | 
					    if not env('SYSTEM_PASSWORD'):
 | 
				
			||||||
        logger.info(f"Creating user: {username}")
 | 
					        log.warning("SYSTEM_PASSWORD is empty, generating random password")
 | 
				
			||||||
 | 
					        password = os.urandom(16).hex()
 | 
				
			||||||
        # Check if the user already exists
 | 
					        log.info(f"Generated system user password: {password}")
 | 
				
			||||||
        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:
 | 
					    else:
 | 
				
			||||||
            logger.info("No reference post provided.")
 | 
					        password = env('SYSTEM_PASSWORD')
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
        # Create a new post
 | 
					    log.info("Creating system user")
 | 
				
			||||||
        self.cursor.execute('''
 | 
					    db.execute_query("""
 | 
				
			||||||
            INSERT INTO posts (user_id, board_id, content, refence)
 | 
					        INSERT INTO users (username, password, about, permissions)
 | 
				
			||||||
        VALUES (?, ?, ?, ?)
 | 
					        VALUES (?, ?, ?, ?)
 | 
				
			||||||
        ''', (user_id, board_id, content, ref))
 | 
					    """, (
 | 
				
			||||||
        self.connection.commit()
 | 
					        env('SYSTEM_USER', default='system'),
 | 
				
			||||||
        logger.info(f"Post created for user {user_id} in board {board_id}.")
 | 
					        env('SYSTEM_PASSWORD', default=hashlib.sha256(password.encode()).hexdigest()),
 | 
				
			||||||
        post_id = self.cursor.lastrowid
 | 
					        env('SYSTEM_ABOUT', default='System User'),
 | 
				
			||||||
        logger.info(f"Post ID: {post_id}")
 | 
					        env('SYSTEM_PERMISSIONS', default='admin')
 | 
				
			||||||
        return post_id
 | 
					    ))
 | 
				
			||||||
    
 | 
					
 | 
				
			||||||
 | 
					    # Create system boards
 | 
				
			||||||
    def get_post(self, post_id):
 | 
					    boards_names = env('SYSTEM_BOARDS', default='General,Random,System').split(',')
 | 
				
			||||||
        # Get post by ID
 | 
					    if "System" not in boards_names:
 | 
				
			||||||
        logger.info(f"Getting post {post_id}...")
 | 
					        boards_names.append("System")
 | 
				
			||||||
        self.cursor.execute('''
 | 
					
 | 
				
			||||||
            SELECT * FROM posts WHERE id = ?
 | 
					    log.info(f"Creating system boards: {', '.join(boards_names)}")
 | 
				
			||||||
        ''', (post_id,))
 | 
					    for board_name in boards_names:
 | 
				
			||||||
        post = self.cursor.fetchone()
 | 
					        db.execute_query("""
 | 
				
			||||||
 | 
					            INSERT INTO boards (name, description, owner_id)
 | 
				
			||||||
        if post:
 | 
					            VALUES (?, ?, ?)
 | 
				
			||||||
            logger.info(f"Post {post_id} found.")
 | 
					        """, (
 | 
				
			||||||
            return post
 | 
					            board_name,
 | 
				
			||||||
        logger.warning(f"Post {post_id} not found.")
 | 
					            f"This is a automatically created board for {board_name}",
 | 
				
			||||||
        return None
 | 
					            1
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_post_references(self, post_id, limit=10, offset=0):
 | 
					    log.info("First time run completed")
 | 
				
			||||||
        # 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]}.")
 | 
					 | 
				
			||||||
            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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # 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()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        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'] = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        logger.info(f"Post converted to dictionary.")
 | 
					 | 
				
			||||||
        return data
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def create_session(self, username, password):
 | 
					 | 
				
			||||||
        logger.info(f"Creating session for user {username}...")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # 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()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        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
 | 
					 | 
				
			||||||
@@ -1,44 +1,42 @@
 | 
				
			|||||||
{% extends "base.html" %}
 | 
					{% extends "templates/base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block content %}
 | 
					{% block head %}
 | 
				
			||||||
    <h1>{{ board.name }}</h1>
 | 
					    <link rel="stylesheet" href="../static/css/form.css">
 | 
				
			||||||
    <h6>Created at: {{ board.created_at }}</h6>
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block main_content %}
 | 
				
			||||||
 | 
					    <h1 class="board-name">/{{ board.name }}/</h1>
 | 
				
			||||||
    <p>{{ board.description }}</p>
 | 
					    <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>
 | 
					        <br>
 | 
				
			||||||
    <h1>Posts:</h1>
 | 
					        <h3>Post to this board</h3>
 | 
				
			||||||
    <ul class="posts">
 | 
					        <form action="/post" method="POST" enctype="multipart/form-data">
 | 
				
			||||||
        {% for post in board.posts %}
 | 
					            <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>
 | 
				
			||||||
 | 
					    <ul class="post-list">
 | 
				
			||||||
 | 
					        {% for post in posts %}
 | 
				
			||||||
            <li>
 | 
					            <li>
 | 
				
			||||||
                <h3>From {{ post.user.name }}</h3>
 | 
					                {% include "templates/short_post.html" %}
 | 
				
			||||||
                <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>
 | 
					 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
        {% endfor %}
 | 
					        {% endfor %}
 | 
				
			||||||
        {% if board.posts|length == 0 %}
 | 
					 | 
				
			||||||
            <li>No posts found.</li>
 | 
					 | 
				
			||||||
        {% endif %}
 | 
					 | 
				
			||||||
    </ul>
 | 
					    </ul>
 | 
				
			||||||
 | 
					    {% if total_pages > 0 %}
 | 
				
			||||||
        <div id="nav">
 | 
					        <div id="nav">
 | 
				
			||||||
        <h5>Page {{ page }}</h5>
 | 
					            <h5>Page {{ page }} of {{ total_pages }}</h5>
 | 
				
			||||||
            {% if page > 1 %}
 | 
					            {% if page > 1 %}
 | 
				
			||||||
                <h5><a href="/boards/{{ board.name }}?page={{ page - 1 }}">Previous Page</a></h5>
 | 
					                <h5><a href="/boards/{{ board.name }}?page={{ page - 1 }}">Previous Page</a></h5>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
        
 | 
					            {% if posts|length == 10 %}
 | 
				
			||||||
        {% if board.posts|length == 10 %}
 | 
					 | 
				
			||||||
                <h5><a href="/boards/{{ board.name }}?page={{ page + 1 }}">Next Page</a></h5>
 | 
					                <h5><a href="/boards/{{ board.name }}?page={{ page + 1 }}">Next Page</a></h5>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
{% endblock %}
 | 
					{% 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>
 | 
					    <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>
 | 
					    <br>
 | 
				
			||||||
    <ul class="posts">
 | 
					    <ul class="board-list">
 | 
				
			||||||
        {% for board in boards %}
 | 
					        {% for board in boards %}
 | 
				
			||||||
            <li>
 | 
					            <li>
 | 
				
			||||||
                <h3>/{{ board.name }}/</h3>
 | 
					                <h3><a class="board-name" href="/boards/{{ board.name }}">/{{ board.name }}/</a></h3>
 | 
				
			||||||
                <h6>created at {{ board.created_at }}</h6>
 | 
					 | 
				
			||||||
                <p>{{ board.description }}</p>
 | 
					                <p>{{ board.description }}</p>
 | 
				
			||||||
                <h6><a href="/boards/{{ board.name }}">View Board</a></h6>
 | 
					 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
        {% endfor %}
 | 
					        {% endfor %}
 | 
				
			||||||
        {% if boards|length == 0 %}
 | 
					 | 
				
			||||||
        <li>No boards found.</li>
 | 
					 | 
				
			||||||
        {% endif %}
 | 
					 | 
				
			||||||
    </ul>
 | 
					    </ul>
 | 
				
			||||||
 | 
					    {% if total_pages > 0 %}
 | 
				
			||||||
        <div id="nav">
 | 
					        <div id="nav">
 | 
				
			||||||
        <h5>Page {{ page }}</h5>
 | 
					            <h5>Page {{ page }} of {{ total_pages }}</h5>
 | 
				
			||||||
            {% if page > 1 %}
 | 
					            {% if page > 1 %}
 | 
				
			||||||
                <h5><a href="/boards?page={{ page - 1 }}">Previous Page</a></h5>
 | 
					                <h5><a href="/boards?page={{ page - 1 }}">Previous Page</a></h5>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
            {% if boards|length == 10 %}
 | 
					            {% if boards|length == 10 %}
 | 
				
			||||||
                <h5><a href="/boards?page={{ page + 1 }}">Next Page</a></h5>
 | 
					                <h5><a href="/boards?page={{ page + 1 }}">Next Page</a></h5>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
{% endblock %}
 | 
					{% 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>
 | 
					    <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>
 | 
					    <br>
 | 
				
			||||||
    <h1>Latest Posts:</h1>
 | 
					    {% if session.name %}
 | 
				
			||||||
    <ul class="posts">
 | 
					        <p>Hello <a class="user-{{session.perms}}" href="/users/{{session.name}}">{{session.name}}</a>! You are logged in.</p>
 | 
				
			||||||
        {% for post in posts %}
 | 
					    {% else %}
 | 
				
			||||||
            <li>
 | 
					        <p><a href="/login">Login</a> or <a href="/register">Register</a></p>
 | 
				
			||||||
                <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 %}
 | 
					    {% 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>
 | 
					    </ul>
 | 
				
			||||||
{% endblock %}
 | 
					{% 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>
 | 
					    <h1>Login</h1>
 | 
				
			||||||
    <p><a href="/register">Register</a> if you don't have an account.</p>
 | 
					    <p><a href="/register">Register</a> if you don't have an account.</p>
 | 
				
			||||||
    <br>
 | 
					    <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 %}
 | 
					{% block head %}
 | 
				
			||||||
    <h2>From {{ post.user.name }} in /{{ post.board.name }}/</h2>
 | 
					    <link rel="stylesheet" href="../static/css/form.css">
 | 
				
			||||||
    <h6>posted at {{ post.created_at }}</h6>
 | 
					{% endblock %}
 | 
				
			||||||
    {% if post.reference %}
 | 
					
 | 
				
			||||||
    <h6><b>ref post:</b> <a href="/posts/{{ post.reference.id }}">{{ post.reference.content }}</a></h6>
 | 
					{% 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 %}
 | 
				
			||||||
 | 
					    {% 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 %}
 | 
					    {% endif %}
 | 
				
			||||||
    <p>{{post.content}}</p>
 | 
					    <p>{{post.content}}</p>
 | 
				
			||||||
 | 
					    <h6>
 | 
				
			||||||
        {% if session.name == "SYSTEM" %}
 | 
					        {% if session.name == "SYSTEM" %}
 | 
				
			||||||
    <h6><a href="/delete/post/{{ post.id }}">Delete</a></h6>
 | 
					            <a href="/delete/post/{{ post.id }}">Delete</a>
 | 
				
			||||||
        {% elif session.name == post.user.name %}
 | 
					        {% elif session.name == post.user.name %}
 | 
				
			||||||
    <h6><a href="/delete/post/{{ post.id }}">Delete</a></h6>
 | 
					            <a href="/delete/post/{{ post.id }}">Delete</a>
 | 
				
			||||||
    {% endif %}
 | 
					 | 
				
			||||||
    <br>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <h3>Replies:</h3>
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    <ul class="posts">
 | 
					 | 
				
			||||||
        {% for reply in post.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 %}
 | 
					        {% endif %}
 | 
				
			||||||
    </h6>
 | 
					    </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>
 | 
				
			||||||
 | 
					    <h1>{{ post.replies }} Replies</h1>
 | 
				
			||||||
 | 
					    <ul class="post-list">
 | 
				
			||||||
 | 
					        {% for post in replies %}
 | 
				
			||||||
 | 
					            <li>
 | 
				
			||||||
 | 
					                {% include "templates/post.html" %}
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
        {% endfor %}
 | 
					        {% endfor %}
 | 
				
			||||||
        {% if post.replies|length == 0 %}
 | 
					 | 
				
			||||||
        <li>No replies found.</li>
 | 
					 | 
				
			||||||
        {% endif %}
 | 
					 | 
				
			||||||
    </ul>
 | 
					    </ul>
 | 
				
			||||||
 | 
					    {% if total_pages > 0 %}
 | 
				
			||||||
        <div id="nav">
 | 
					        <div id="nav">
 | 
				
			||||||
        <h5>Page {{ page }}</h5>
 | 
					            <h5>Page {{ page }} of {{ total_pages }}</h5>
 | 
				
			||||||
            {% if page > 1 %}
 | 
					            {% if page > 1 %}
 | 
				
			||||||
                <h5><a href="/posts/{{ post.id }}?page={{ page - 1 }}">Previous Page</a></h5>
 | 
					                <h5><a href="/posts/{{ post.id }}?page={{ page - 1 }}">Previous Page</a></h5>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
        
 | 
					            {% if replies|length == 10 %}
 | 
				
			||||||
        {% if post.replies|length == 10 %}
 | 
					 | 
				
			||||||
                <h5><a href="/posts/{{ post.id }}?page={{ page + 1 }}">Next Page</a></h5>
 | 
					                <h5><a href="/posts/{{ post.id }}?page={{ page + 1 }}">Next Page</a></h5>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
        </div>
 | 
					        </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>
 | 
					 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
@@ -1,42 +1,25 @@
 | 
				
			|||||||
{% extends "base.html" %}
 | 
					{% extends "templates/base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block content %}
 | 
					{% block main_content %}
 | 
				
			||||||
    <h1>Posts</h1>
 | 
					    <h1>Posts</h1>
 | 
				
			||||||
    <h5>Page {{ page }}</h5>
 | 
					    <p>Here are the latest posts from all boards.</p>
 | 
				
			||||||
    <br>
 | 
					    <br>
 | 
				
			||||||
    <ul class="posts">
 | 
					    <ul class="post-list">
 | 
				
			||||||
        {% for post in posts %}
 | 
					        {% for post in posts %}
 | 
				
			||||||
            <li>
 | 
					            <li>
 | 
				
			||||||
                <h3>From {{ post.user.name }} in /{{ post.board.name }}/</h3>
 | 
					                {% include "templates/short_post.html" %}   
 | 
				
			||||||
                <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>
 | 
					 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
        {% endfor %}
 | 
					        {% endfor %}
 | 
				
			||||||
        {% if posts|length == 0 %}
 | 
					 | 
				
			||||||
        <li>No replies found.</li>
 | 
					 | 
				
			||||||
        {% endif %}
 | 
					 | 
				
			||||||
    </ul>
 | 
					    </ul>
 | 
				
			||||||
 | 
					    {% if total_pages > 0 %}
 | 
				
			||||||
        <div id="nav">
 | 
					        <div id="nav">
 | 
				
			||||||
        <h5>Page {{ page }}</h5>
 | 
					            <h5>Page {{ page }} of {{ total_pages }}</h5>
 | 
				
			||||||
            {% if page > 1 %}
 | 
					            {% if page > 1 %}
 | 
				
			||||||
                <h5><a href="/posts?page={{ page - 1 }}">Previous Page</a></h5>
 | 
					                <h5><a href="/posts?page={{ page - 1 }}">Previous Page</a></h5>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
            {% if posts|length == 10 %}
 | 
					            {% if posts|length == 10 %}
 | 
				
			||||||
                <h5><a href="/posts?page={{ page + 1 }}">Next Page</a></h5>
 | 
					                <h5><a href="/posts?page={{ page + 1 }}">Next Page</a></h5>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
{% endblock %}
 | 
					{% 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>
 | 
					    <h1>Register</h1>
 | 
				
			||||||
    <br>
 | 
					    <br>
 | 
				
			||||||
    <form method="POST" action="/register">
 | 
					    <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 charset="UTF-8">
 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
    <title>Prismic</title>
 | 
					    <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>
 | 
					</head>
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
    <header>
 | 
					    <header>
 | 
				
			||||||
        <div id="title">
 | 
					        <div id="title">
 | 
				
			||||||
            <h1>Prismic</h1>
 | 
					            <h1>Prismic</h1>
 | 
				
			||||||
            <h2>ver: 1.0</h2>
 | 
					            <h2>ver: 2.0</h2>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <nav>
 | 
					        <nav>
 | 
				
			||||||
            <ul>
 | 
					            <ul>
 | 
				
			||||||
                <li><a href="/">Home</a></li>
 | 
					                <li><a href="/">Home</a></li>
 | 
				
			||||||
                <li><a href="/boards">Boards</a></li>
 | 
					                <li><a href="/boards">Boards</a></li>
 | 
				
			||||||
                <li><a href="/posts">Posts</a></li>
 | 
					                <li><a href="/posts">Posts</a></li>
 | 
				
			||||||
                {% if session.name %}
 | 
					                <li><a href="/users">Users</a></li>
 | 
				
			||||||
                <li><a href="/post">Post</a></li>
 | 
					 | 
				
			||||||
                {% endif %}
 | 
					 | 
				
			||||||
                <li>{% if session.name %}
 | 
					                <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 %}
 | 
					                {% else %}
 | 
				
			||||||
                    <a href="/login">Login</a>
 | 
					                    <a href="/login">Login</a>
 | 
				
			||||||
                {% endif %}</li>
 | 
					                {% endif %}</li>
 | 
				
			||||||
@@ -30,10 +30,10 @@
 | 
				
			|||||||
        </nav>
 | 
					        </nav>
 | 
				
			||||||
    </header>
 | 
					    </header>
 | 
				
			||||||
    <main>
 | 
					    <main>
 | 
				
			||||||
        {% block content %}{% endblock %}
 | 
					        {% block main_content %}{% endblock %}
 | 
				
			||||||
    </main>
 | 
					    </main>
 | 
				
			||||||
    <footer>
 | 
					    <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>
 | 
					    </footer>
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
</html>
 | 
					</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 %}
 | 
					{% block head %}
 | 
				
			||||||
    <h1>{{ user.name }}</h1>
 | 
					    <link rel="stylesheet" href="../static/css/user.css">
 | 
				
			||||||
    <h6>Joined at: {{ user.created_at }}</h6>
 | 
					{% 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>
 | 
					    <br>
 | 
				
			||||||
    <h1>Posts:</h1>
 | 
					    <h2><span class="user-{{user.perms}}">{{ user.name }}'s</span> Posts</h2>
 | 
				
			||||||
    <ul class="posts">
 | 
					    <ul class="post-list">
 | 
				
			||||||
        {% for post in user.posts %}
 | 
					        {% for post in posts %}
 | 
				
			||||||
            <li>
 | 
					            <li>
 | 
				
			||||||
                <h3>From {{ post.user.name }} in /{{ post.board.name }}/</h3>
 | 
					                {% include "templates/short_post.html" %}   
 | 
				
			||||||
                <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>
 | 
					 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
        {% endfor %}
 | 
					        {% endfor %}
 | 
				
			||||||
        {% if user.posts|length == 0 %}
 | 
					 | 
				
			||||||
            <li>No posts found.</li>
 | 
					 | 
				
			||||||
        {% endif %}
 | 
					 | 
				
			||||||
    </ul>
 | 
					    </ul>
 | 
				
			||||||
 | 
					    {% if total_pages > 0 %}
 | 
				
			||||||
        <div id="nav">
 | 
					        <div id="nav">
 | 
				
			||||||
        <h5>Page {{ page }}</h5>
 | 
					            <h5>Page {{ page }} of {{ total_pages }}</h5>
 | 
				
			||||||
            {% if page > 1 %}
 | 
					            {% if page > 1 %}
 | 
				
			||||||
        <h5><a href="/user/{{ user.name }}?page={{ page - 1 }}">Previous Page</a></h5>
 | 
					                <h5><a href="/users/{{user.d}}?page={{ page - 1 }}">Previous Page</a></h5>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
        
 | 
					            {% if posts|length == 10 %}
 | 
				
			||||||
        {% if user.posts|length == 10 %}
 | 
					                <h5><a href="/users/{{user.id}}?page={{ page + 1 }}">Next Page</a></h5>
 | 
				
			||||||
        <h5><a href="/user/{{ user.name }}?page={{ page + 1 }}">Next Page</a></h5>
 | 
					 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
{% endblock %}
 | 
					{% 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 %}
 | 
				
			||||||
							
								
								
									
										958
									
								
								src/main.py
									
									
									
									
									
								
							
							
						
						
									
										958
									
								
								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;
 | 
					    --bg: #000;
 | 
				
			||||||
    --fg: #fff;
 | 
					    --fg: #fff;
 | 
				
			||||||
    --accent: #7139f3;
 | 
					    --accent: #7139f3;
 | 
				
			||||||
 | 
					    --admin: #39f3da;
 | 
				
			||||||
 | 
					    --user: #5b6dd4;
 | 
				
			||||||
 | 
					    --board: #515699;
 | 
				
			||||||
 | 
					    --time: #30597a;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
body {
 | 
					body {
 | 
				
			||||||
@@ -83,11 +87,18 @@ a {
 | 
				
			|||||||
    text-decoration: none;
 | 
					    text-decoration: none;
 | 
				
			||||||
    transition: all 0.3s ease;
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
a:hover {
 | 
					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;
 | 
					    list-style: none;
 | 
				
			||||||
    padding: 0;
 | 
					    padding: 0;
 | 
				
			||||||
    margin: 0;
 | 
					    margin: 0;
 | 
				
			||||||
@@ -97,90 +108,41 @@ ul.posts {
 | 
				
			|||||||
    gap: 10px;
 | 
					    gap: 10px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
form {
 | 
					ul.post-list {
 | 
				
			||||||
 | 
					    list-style: none;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    margin-left: 10px;
 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
    flex-direction: column;
 | 
					    flex-direction: column;
 | 
				
			||||||
    gap: 10px;
 | 
					    gap: 10px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
input[type="text"],
 | 
					.user-admin {
 | 
				
			||||||
input[type="password"] {
 | 
					    color: var(--admin);
 | 
				
			||||||
    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,
 | 
					.user-user {
 | 
				
			||||||
input[type="password"]:focus {
 | 
					    color: var(--user);
 | 
				
			||||||
    outline: none;
 | 
					 | 
				
			||||||
    border-color: var(--accent);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
button[type="submit"] {
 | 
					.board-name {
 | 
				
			||||||
    padding: 10px 20px;
 | 
					    color: var(--board);
 | 
				
			||||||
    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;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
button[type="submit"]:hover {
 | 
					.time {
 | 
				
			||||||
    background-color: var(--fg);
 | 
					    color: var(--time);
 | 
				
			||||||
    color: var(--bg);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
button[type="submit"]:disabled {
 | 
					 | 
				
			||||||
    background-color: var(--bg);
 | 
					 | 
				
			||||||
    color: var(--fg);
 | 
					 | 
				
			||||||
    cursor: not-allowed;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
select {
 | 
					#title {
 | 
				
			||||||
    padding: 10px;
 | 
					    background: linear-gradient(90deg, var(--accent), var(--admin));
 | 
				
			||||||
    border: 1px solid var(--fg);
 | 
					    background-clip: text;
 | 
				
			||||||
    border-radius: 5px;
 | 
					    -webkit-background-clip: text;
 | 
				
			||||||
    background-color: var(--bg);
 | 
					    -webkit-text-fill-color: transparent;
 | 
				
			||||||
    color: var(--fg);
 | 
					 | 
				
			||||||
    font-size: 1rem;
 | 
					 | 
				
			||||||
    width: 300px;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
select:focus {
 | 
					.attachments {
 | 
				
			||||||
    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 {
 | 
					 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
    gap: 10px;
 | 
					    gap: 10px;
 | 
				
			||||||
    margin-top: 20px;
 | 
					    align-items: center;
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
p {
 | 
					 | 
				
			||||||
    white-space:pre;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										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],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
		Reference in New Issue
	
	Block a user