ver 1
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
.env
 | 
			
		||||
.venv
 | 
			
		||||
app.db
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								flask_session/2029240f6d1128be89ddc32729463129
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								flask_session/2029240f6d1128be89ddc32729463129
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								flask_session/95ca1eb92b3063fb8d5ea8eef8e9be3f
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								flask_session/95ca1eb92b3063fb8d5ea8eef8e9be3f
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
flask
 | 
			
		||||
flask-session
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/__pycache__/database.cpython-313.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/__pycache__/database.cpython-313.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										544
									
								
								src/database.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										544
									
								
								src/database.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,544 @@
 | 
			
		||||
import sqlite3, logging, os
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Configure logging
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Database:
 | 
			
		||||
    def __init__(self, db_name):
 | 
			
		||||
        # Initialize the database connection
 | 
			
		||||
        logger.info("Initializing database connection...")
 | 
			
		||||
        self.connection = sqlite3.connect(db_name, check_same_thread=False)
 | 
			
		||||
        self.cursor = self.connection.cursor()
 | 
			
		||||
        logger.info("Database connection established.")
 | 
			
		||||
 | 
			
		||||
        # Create users table if it doesn't exist
 | 
			
		||||
        logger.info("Creating users table if it doesn't exist...")
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            CREATE TABLE IF NOT EXISTS users (
 | 
			
		||||
                id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
			
		||||
                username TEXT NOT NULL UNIQUE,
 | 
			
		||||
                password TEXT NOT NULL,
 | 
			
		||||
                session TEXT,
 | 
			
		||||
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 | 
			
		||||
            )
 | 
			
		||||
        ''')
 | 
			
		||||
        logger.info("Users table created.")
 | 
			
		||||
 | 
			
		||||
        # Create boards table if it doesn't exist
 | 
			
		||||
        logger.info("Creating boards table if it doesn't exist...")
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            CREATE TABLE IF NOT EXISTS boards (
 | 
			
		||||
                id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
			
		||||
                name TEXT NOT NULL UNIQUE,
 | 
			
		||||
                owner_id INTEGER NOT NULL,
 | 
			
		||||
                description TEXT,
 | 
			
		||||
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
                FOREIGN KEY (owner_id) REFERENCES users (id) ON DELETE SET NULL
 | 
			
		||||
            )
 | 
			
		||||
        ''')
 | 
			
		||||
        logger.info("Boards table created.")
 | 
			
		||||
 | 
			
		||||
        # Create posts table if it doesn't exist
 | 
			
		||||
        logger.info("Creating posts table if it doesn't exist...")
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            CREATE TABLE IF NOT EXISTS posts (
 | 
			
		||||
                id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
			
		||||
                user_id INTEGER NOT NULL,
 | 
			
		||||
                board_id INTEGER NOT NULL,
 | 
			
		||||
                content TEXT NOT NULL,
 | 
			
		||||
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
                refence INTEGER,
 | 
			
		||||
                FOREIGN KEY (refence) REFERENCES posts (id) ON DELETE CASCADE,
 | 
			
		||||
                FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL,
 | 
			
		||||
                FOREIGN KEY (board_id) REFERENCES boards (id) ON DELETE CASCADE
 | 
			
		||||
            )
 | 
			
		||||
        ''')
 | 
			
		||||
        logger.info("Posts table created.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def close(self):
 | 
			
		||||
        # Close the database connection
 | 
			
		||||
        logger.info("Closing database connection...")
 | 
			
		||||
        self.connection.commit()
 | 
			
		||||
        self.connection.close()
 | 
			
		||||
        logger.info("Database connection closed.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def create_user(self, username, password):
 | 
			
		||||
        logger.info(f"Creating user: {username}")
 | 
			
		||||
 | 
			
		||||
        # Check if the user already exists
 | 
			
		||||
        logger.info(f"Checking if user {username} already exists...")
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            SELECT * FROM users WHERE username = ?
 | 
			
		||||
        ''', (username,))
 | 
			
		||||
        user = self.cursor.fetchone()
 | 
			
		||||
 | 
			
		||||
        if user:
 | 
			
		||||
            logger.warning(f"User {username} already exists.")
 | 
			
		||||
            return False
 | 
			
		||||
        logger.info(f"User {username} does not exist.")
 | 
			
		||||
 | 
			
		||||
        # Create a new user
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            INSERT INTO users (username, password)
 | 
			
		||||
            VALUES (?, ?)
 | 
			
		||||
        ''', (username, password))
 | 
			
		||||
 | 
			
		||||
        self.connection.commit()
 | 
			
		||||
        logger.info(f"User {username} created.")
 | 
			
		||||
        return True
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def get_user(self, username):
 | 
			
		||||
        # Get user by username
 | 
			
		||||
        logger.info(f"Getting user {username}...")
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            SELECT * FROM users WHERE username = ?
 | 
			
		||||
        ''', (username,))
 | 
			
		||||
        user = self.cursor.fetchone()
 | 
			
		||||
 | 
			
		||||
        if user:
 | 
			
		||||
            logger.info(f"User {username} found.")
 | 
			
		||||
            return user
 | 
			
		||||
        logger.warning(f"User {username} not found.")
 | 
			
		||||
        return None
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def create_board(self, name, description, owner_id):
 | 
			
		||||
        logger.info(f"Creating board: {name}")
 | 
			
		||||
 | 
			
		||||
        # Check if the board already exists
 | 
			
		||||
        logger.info(f"Checking if board {name} already exists...")
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            SELECT * FROM boards WHERE name = ?
 | 
			
		||||
        ''', (name,))
 | 
			
		||||
        board = self.cursor.fetchone()
 | 
			
		||||
 | 
			
		||||
        if board:
 | 
			
		||||
            logger.warning(f"Board {name} already exists.")
 | 
			
		||||
            return False
 | 
			
		||||
        logger.info(f"Board {name} does not exist.")
 | 
			
		||||
 | 
			
		||||
        # Check if the owner exists
 | 
			
		||||
        logger.info(f"Checking if owner {owner_id} exists...")
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            SELECT * FROM users WHERE id = ?
 | 
			
		||||
        ''', (owner_id,))
 | 
			
		||||
        owner = self.cursor.fetchone()
 | 
			
		||||
 | 
			
		||||
        if not owner:
 | 
			
		||||
            logger.warning(f"Owner {owner_id} does not exist.")
 | 
			
		||||
            return False
 | 
			
		||||
        logger.info(f"Owner {owner_id} exists.")
 | 
			
		||||
 | 
			
		||||
        # Create a new board
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            INSERT INTO boards (name, owner_id, description)
 | 
			
		||||
            VALUES (?, ?, ?)
 | 
			
		||||
        ''', (name, owner_id, description))
 | 
			
		||||
 | 
			
		||||
        self.connection.commit()
 | 
			
		||||
        logger.info(f"Board {name} created.")
 | 
			
		||||
        return True
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def get_board(self, name):
 | 
			
		||||
        # Get board by name
 | 
			
		||||
        logger.info(f"Getting board {name}...")
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            SELECT * FROM boards WHERE name = ?
 | 
			
		||||
        ''', (name,))
 | 
			
		||||
        board = self.cursor.fetchone()
 | 
			
		||||
 | 
			
		||||
        if board:
 | 
			
		||||
            logger.info(f"Board {name} found.")
 | 
			
		||||
            return board
 | 
			
		||||
        logger.warning(f"Board {name} not found.")
 | 
			
		||||
        return None
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def create_post(self, user_id, board_id, content, ref=None):
 | 
			
		||||
        logger.info(f"Creating post for user {user_id} in board {board_id}...")
 | 
			
		||||
 | 
			
		||||
        # Check if the user exists
 | 
			
		||||
        logger.info(f"Checking if user {user_id} exists...")
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            SELECT * FROM users WHERE id = ?
 | 
			
		||||
        ''', (user_id,))
 | 
			
		||||
        user = self.cursor.fetchone()
 | 
			
		||||
 | 
			
		||||
        if not user:
 | 
			
		||||
            logger.warning(f"User {user_id} does not exist.")
 | 
			
		||||
            return False
 | 
			
		||||
        logger.info(f"User {user_id} exists.")
 | 
			
		||||
 | 
			
		||||
        # Check if the board exists
 | 
			
		||||
        logger.info(f"Checking if board {board_id} exists...")
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            SELECT * FROM boards WHERE id = ?
 | 
			
		||||
        ''', (board_id,))
 | 
			
		||||
        board = self.cursor.fetchone()
 | 
			
		||||
 | 
			
		||||
        if not board:
 | 
			
		||||
            logger.warning(f"Board {board_id} does not exist.")
 | 
			
		||||
            return False
 | 
			
		||||
        logger.info(f"Board {board_id} exists.")
 | 
			
		||||
 | 
			
		||||
        # Check if the reference post exists
 | 
			
		||||
        if ref is not None:
 | 
			
		||||
            logger.info(f"Checking if reference post {ref} exists...")
 | 
			
		||||
            self.cursor.execute('''
 | 
			
		||||
                SELECT * FROM posts WHERE id = ?
 | 
			
		||||
            ''', (ref,))
 | 
			
		||||
            reference_post = self.cursor.fetchone()
 | 
			
		||||
 | 
			
		||||
            if not reference_post:
 | 
			
		||||
                logger.warning(f"Reference post {ref} does not exist.")
 | 
			
		||||
                return False
 | 
			
		||||
            logger.info(f"Reference post {ref} exists.")
 | 
			
		||||
        else:
 | 
			
		||||
            logger.info("No reference post provided.")
 | 
			
		||||
            
 | 
			
		||||
        # Create a new post
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            INSERT INTO posts (user_id, board_id, content, refence)
 | 
			
		||||
            VALUES (?, ?, ?, ?)
 | 
			
		||||
        ''', (user_id, board_id, content, ref))
 | 
			
		||||
        self.connection.commit()
 | 
			
		||||
        logger.info(f"Post created for user {user_id} in board {board_id}.")
 | 
			
		||||
        post_id = self.cursor.lastrowid
 | 
			
		||||
        logger.info(f"Post ID: {post_id}")
 | 
			
		||||
        return post_id
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def get_post(self, post_id):
 | 
			
		||||
        # Get post by ID
 | 
			
		||||
        logger.info(f"Getting post {post_id}...")
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            SELECT * FROM posts WHERE id = ?
 | 
			
		||||
        ''', (post_id,))
 | 
			
		||||
        post = self.cursor.fetchone()
 | 
			
		||||
 | 
			
		||||
        if post:
 | 
			
		||||
            logger.info(f"Post {post_id} found.")
 | 
			
		||||
            return post
 | 
			
		||||
        logger.warning(f"Post {post_id} not found.")
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def get_post_references(self, post_id, limit=10, offset=0):
 | 
			
		||||
        # Get references for a post
 | 
			
		||||
        logger.info(f"Getting references for post {post_id}...")
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            SELECT * FROM posts WHERE refence = ? LIMIT ? OFFSET ?
 | 
			
		||||
        ''', (post_id, limit, offset))
 | 
			
		||||
        references = self.cursor.fetchall()
 | 
			
		||||
 | 
			
		||||
        if references:
 | 
			
		||||
            logger.info(f"References found for post {post_id}.")
 | 
			
		||||
            return references
 | 
			
		||||
        logger.warning(f"No references found for post {post_id}.")
 | 
			
		||||
        return None
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def get_posts(self, board_id, limit, offset):
 | 
			
		||||
        if board_id:
 | 
			
		||||
            logger.info(f"Getting posts for board {board_id}...")
 | 
			
		||||
 | 
			
		||||
            # Check if the board exists
 | 
			
		||||
            logger.info(f"Checking if board {board_id} exists...")
 | 
			
		||||
            self.cursor.execute('''
 | 
			
		||||
                SELECT * FROM boards WHERE id = ?
 | 
			
		||||
            ''', (board_id,))
 | 
			
		||||
            board = self.cursor.fetchone()
 | 
			
		||||
 | 
			
		||||
            if not board:
 | 
			
		||||
                logger.warning(f"Board {board_id} does not exist.")
 | 
			
		||||
                return None
 | 
			
		||||
            logger.info(f"Board {board_id} exists.")
 | 
			
		||||
 | 
			
		||||
            # Get posts for the board
 | 
			
		||||
            logger.info(f"Getting posts for board {board_id}...")
 | 
			
		||||
            self.cursor.execute('''
 | 
			
		||||
                SELECT * FROM posts WHERE board_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?
 | 
			
		||||
            ''', (board_id, limit, offset))
 | 
			
		||||
            posts = self.cursor.fetchall()
 | 
			
		||||
 | 
			
		||||
            if posts:
 | 
			
		||||
                logger.info(f"Posts found for board {board_id}.")
 | 
			
		||||
                return posts
 | 
			
		||||
            logger.warning(f"No posts found for board {board_id}.")
 | 
			
		||||
            return None
 | 
			
		||||
        
 | 
			
		||||
        else:
 | 
			
		||||
            # Get all posts
 | 
			
		||||
            logger.info(f"Getting all posts...")
 | 
			
		||||
            self.cursor.execute('''
 | 
			
		||||
                SELECT * FROM posts ORDER BY created_at DESC LIMIT ? OFFSET ?
 | 
			
		||||
            ''', (limit, offset))
 | 
			
		||||
            posts = self.cursor.fetchall()
 | 
			
		||||
 | 
			
		||||
            if posts:
 | 
			
		||||
                logger.info(f"Posts found.")
 | 
			
		||||
                return posts
 | 
			
		||||
            logger.warning(f"No posts found.")
 | 
			
		||||
            return None
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def get_latest_posts(self, limit):
 | 
			
		||||
        logger.info(f"Getting latest posts...")
 | 
			
		||||
 | 
			
		||||
        # Get latest posts
 | 
			
		||||
        logger.info(f"Getting latest posts...")
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            SELECT * FROM posts ORDER BY created_at DESC LIMIT ?
 | 
			
		||||
        ''', (limit,))
 | 
			
		||||
        posts = self.cursor.fetchall()
 | 
			
		||||
 | 
			
		||||
        if posts:
 | 
			
		||||
            logger.info(f"Latest posts found.")
 | 
			
		||||
            return posts
 | 
			
		||||
        logger.warning(f"No latest posts found.")
 | 
			
		||||
        return None
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def get_boards(self, limit, offset):
 | 
			
		||||
        logger.info(f"Listing boards with limit {limit} and offset {offset}...")
 | 
			
		||||
 | 
			
		||||
        # Get boards with pagination
 | 
			
		||||
        self.cursor.execute('''
 | 
			
		||||
            SELECT * FROM boards ORDER BY created_at DESC LIMIT ? OFFSET ?
 | 
			
		||||
        ''', (limit, offset))
 | 
			
		||||
        boards = self.cursor.fetchall()
 | 
			
		||||
 | 
			
		||||
        if boards:
 | 
			
		||||
            logger.info(f"Boards found.")
 | 
			
		||||
            return boards
 | 
			
		||||
        logger.warning(f"No boards found.")
 | 
			
		||||
        return None
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def post_to_dict(self, post):
 | 
			
		||||
        # Convert post to dictionary
 | 
			
		||||
        logger.info(f"Converting post to dictionary...")
 | 
			
		||||
        data = {
 | 
			
		||||
            'id': post[0],
 | 
			
		||||
            'content': post[3],
 | 
			
		||||
            '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],
 | 
			
		||||
                    '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
 | 
			
		||||
 | 
			
		||||
        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]
 | 
			
		||||
            return boards
 | 
			
		||||
        logger.warning(f"No boards found.")
 | 
			
		||||
        return None
 | 
			
		||||
							
								
								
									
										43
									
								
								src/html/base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/html/base.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>Prismic</title>
 | 
			
		||||
    <link rel="stylesheet" href="../static/base.css">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <header>
 | 
			
		||||
        <div id="title">
 | 
			
		||||
            <h1>Prismic</h1>
 | 
			
		||||
            <h2>ver: 1.0</h2>
 | 
			
		||||
        </div>
 | 
			
		||||
        <nav>
 | 
			
		||||
            <ul>
 | 
			
		||||
                <li><a href="/">Home</a></li>
 | 
			
		||||
                <li><a href="/boards">Boards</a></li>
 | 
			
		||||
                <li><a href="/posts">Posts</a></li>
 | 
			
		||||
                {% if session.name %}
 | 
			
		||||
                <li><a href="/post">Post</a></li>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
                <li>{% if session.name %}
 | 
			
		||||
                    <a href="/user/{{ session.name }}">{{ session.name }}</a>
 | 
			
		||||
                {% else %}
 | 
			
		||||
                    <a href="/login">Login</a>
 | 
			
		||||
                {% endif %}</li>
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </nav>
 | 
			
		||||
    </header>
 | 
			
		||||
    <main>
 | 
			
		||||
        {% block content %}{% endblock %}
 | 
			
		||||
    </main>
 | 
			
		||||
    <footer>
 | 
			
		||||
        <p>Created by <a href="https://alfieking.dev">Alfie King</a>
 | 
			
		||||
        {% if session.name %}
 | 
			
		||||
        | <a href="/logout">Logout</a>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        </p>
 | 
			
		||||
    </footer>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										35
									
								
								src/html/board.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/html/board.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1>{{ board.name }}</h1>
 | 
			
		||||
    <h6>Created at: {{ board.created_at }}</h6>
 | 
			
		||||
    <p>{{ board.description }}</p>
 | 
			
		||||
    <br>
 | 
			
		||||
    <h1>Posts:</h1>
 | 
			
		||||
    <ul class="posts">
 | 
			
		||||
        {% for post in board.posts %}
 | 
			
		||||
            <li>
 | 
			
		||||
                <h3>From {{ post.user.name }}</h3>
 | 
			
		||||
                <h6>posted at {{ post.created_at }}</h6>
 | 
			
		||||
                {% if post.reference %}
 | 
			
		||||
                <h6><b>ref post:</b> <a href="/posts/{{ post.reference.id }}">{{ post.reference.content }}</a></h6>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
                <p>{{ post.content }}</p>
 | 
			
		||||
                <h6><a href="/posts/{{ post.id }}">View Post</a></h6>
 | 
			
		||||
            </li>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
        {% if board.posts|length == 0 %}
 | 
			
		||||
            <li>No posts found.</li>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </ul>
 | 
			
		||||
    <div id="nav">
 | 
			
		||||
        <h5>Page {{ page }}</h5>
 | 
			
		||||
        {% if page > 1 %}
 | 
			
		||||
        <h5><a href="/boards/{{ board.name }}?page={{ page - 1 }}">Previous Page</a></h5>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        
 | 
			
		||||
        {% if board.posts|length == 10 %}
 | 
			
		||||
        <h5><a href="/boards/{{ board.name }}?page={{ page + 1 }}">Next Page</a></h5>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/html/boards.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/html/boards.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1>Boards</h1>
 | 
			
		||||
    <h5>Page {{ page }}</h5>
 | 
			
		||||
    <br>
 | 
			
		||||
    <ul class="posts">
 | 
			
		||||
        {% for board in boards %}
 | 
			
		||||
            <li>
 | 
			
		||||
                <h3>/{{ board.name }}/</h3>
 | 
			
		||||
                <h6>created at {{ board.created_at }}</h6>
 | 
			
		||||
                <p>{{ board.description }}</p>
 | 
			
		||||
                <h6><a href="/boards/{{ board.name }}">View Board</a></h6>
 | 
			
		||||
            </li>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
        {% if boards|length == 0 %}
 | 
			
		||||
        <li>No boards found.</li>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </ul>
 | 
			
		||||
    <div id="nav">
 | 
			
		||||
        <h5>Page {{ page }}</h5>
 | 
			
		||||
        {% if page > 1 %}
 | 
			
		||||
        <h5><a href="/boards?page={{ page - 1 }}">Previous Page</a></h5>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        
 | 
			
		||||
        {% if boards|length == 10 %}
 | 
			
		||||
        <h5><a href="/boards?page={{ page + 1 }}">Next Page</a></h5>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										24
									
								
								src/html/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/html/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1>Welcome to Prismic</h1>
 | 
			
		||||
    <p>Prismic is a simple textboard</p>
 | 
			
		||||
    <br>
 | 
			
		||||
    <h1>Latest Posts:</h1>
 | 
			
		||||
    <ul class="posts">
 | 
			
		||||
        {% for post in posts %}
 | 
			
		||||
            <li>
 | 
			
		||||
                <h3>From {{ post.user.name }} in /{{ post.board.name }}/</h3>
 | 
			
		||||
                <h6>posted at {{ post.created_at }}</h6>
 | 
			
		||||
                {% if post.reference %}
 | 
			
		||||
                <h6><b>ref post:</b> <a href="/posts/{{ post.reference.id }}">{{ post.reference.content }}</a></h6>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
                <p>{{ post.content }}</p>
 | 
			
		||||
                <h6><a href="/posts/{{ post.id }}">View Post</a></h6>
 | 
			
		||||
            </li>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
        {% if posts|length == 0 %}
 | 
			
		||||
        <li>No posts found.</li>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </ul>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										20
									
								
								src/html/login.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/html/login.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1>Login</h1>
 | 
			
		||||
    <p><a href="/register">Register</a> if you don't have an account.</p>
 | 
			
		||||
    <br>
 | 
			
		||||
    <form method="POST" action="/login">
 | 
			
		||||
        <label for="username">Username:</label>
 | 
			
		||||
        <input type="text" id="username" name="username" required>
 | 
			
		||||
        <br>
 | 
			
		||||
        <label for="password">Password:</label>
 | 
			
		||||
        <input type="password" id="password" name="password" required>
 | 
			
		||||
        <br>
 | 
			
		||||
        <button type="submit">Login</button>
 | 
			
		||||
    </form>
 | 
			
		||||
 | 
			
		||||
    {% if error %}
 | 
			
		||||
        <p style="color: red;">{{ error }}</p>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										20
									
								
								src/html/newpost.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/html/newpost.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
{% 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 %}
 | 
			
		||||
							
								
								
									
										48
									
								
								src/html/post.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/html/post.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h2>From {{ post.user.name }} in /{{ post.board.name }}/</h2>
 | 
			
		||||
    <h6>posted at {{ post.created_at }}</h6>
 | 
			
		||||
    {% if post.reference %}
 | 
			
		||||
    <h6><b>ref post:</b> <a href="/posts/{{ post.reference.id }}">{{ post.reference.content }}</a></h6>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    <p>{{ post.content }}</p>
 | 
			
		||||
    <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.content }}</p>
 | 
			
		||||
                <h6><a href="/posts/{{ reply.id }}">View Reply</a></h6>
 | 
			
		||||
            </li>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
        {% if post.replies|length == 0 %}
 | 
			
		||||
        <li>No replies found.</li>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </ul>
 | 
			
		||||
    <div id="nav">
 | 
			
		||||
        <h5>Page {{ page }}</h5>
 | 
			
		||||
        {% if page > 1 %}
 | 
			
		||||
        <h5><a href="/posts/{{ post.id }}?page={{ page - 1 }}">Previous Page</a></h5>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        
 | 
			
		||||
        {% if post.replies|length == 10 %}
 | 
			
		||||
        <h5><a href="/posts/{{ post.id }}?page={{ page + 1 }}">Next Page</a></h5>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
    <br>
 | 
			
		||||
    <form method="POST" action="/post">
 | 
			
		||||
        <input type="hidden" name="ref" value="{{ post.id }}">
 | 
			
		||||
        <input type="hidden" name="board" value="{{ post.board.id }}">
 | 
			
		||||
        <input type="hidden" name="redirect" value="/posts/{{ post.id }}">
 | 
			
		||||
        <label for="content">Reply:</label>
 | 
			
		||||
        <textarea id="content" name="content" rows="4" cols="50" required></textarea>
 | 
			
		||||
        <br>
 | 
			
		||||
        <button type="submit">Post</button>
 | 
			
		||||
    </form>
 | 
			
		||||
    {% if error %}
 | 
			
		||||
        <p style="color: red;">{{ error }}</p>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										33
									
								
								src/html/posts.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/html/posts.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1>Posts</h1>
 | 
			
		||||
    <h5>Page {{ page }}</h5>
 | 
			
		||||
    <br>
 | 
			
		||||
    <ul class="posts">
 | 
			
		||||
        {% for post in posts %}
 | 
			
		||||
            <li>
 | 
			
		||||
                <h3>From {{ post.user.name }} in /{{ post.board.name }}/</h3>
 | 
			
		||||
                <h6>posted at {{ post.created_at }}</h6>
 | 
			
		||||
                {% if post.reference %}
 | 
			
		||||
                <h6><b>ref post:</b> <a href="/posts/{{ post.reference.id }}">{{ post.reference.content }}</a></h6>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
                <p>{{ post.content }}</p>
 | 
			
		||||
                <h6><a href="/posts/{{ post.id }}">View post</a></h6>
 | 
			
		||||
            </li>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
        {% if posts|length == 0 %}
 | 
			
		||||
        <li>No replies found.</li>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </ul>
 | 
			
		||||
    <div id="nav">
 | 
			
		||||
        <h5>Page {{ page }}</h5>
 | 
			
		||||
        {% if page > 1 %}
 | 
			
		||||
        <h5><a href="/posts?page={{ page - 1 }}">Previous Page</a></h5>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        
 | 
			
		||||
        {% if posts|length == 10 %}
 | 
			
		||||
        <h5><a href="/posts?page={{ page + 1 }}">Next Page</a></h5>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										19
									
								
								src/html/register.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/html/register.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1>Register</h1>
 | 
			
		||||
    <br>
 | 
			
		||||
    <form method="POST" action="/register">
 | 
			
		||||
        <label for="username">Username:</label>
 | 
			
		||||
        <input type="text" id="username" name="username" required>
 | 
			
		||||
        <br>
 | 
			
		||||
        <label for="password">Password:</label>
 | 
			
		||||
        <input type="password" id="password" name="password" required>
 | 
			
		||||
        <br>
 | 
			
		||||
        <button type="submit">Register</button>
 | 
			
		||||
    </form>
 | 
			
		||||
 | 
			
		||||
    {% if error %}
 | 
			
		||||
        <p style="color: red;">{{ error }}</p>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										34
									
								
								src/html/user.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/html/user.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1>{{ user.name }}</h1>
 | 
			
		||||
    <h6>Joined at: {{ user.created_at }}</h6>
 | 
			
		||||
    <br>
 | 
			
		||||
    <h1>Posts:</h1>
 | 
			
		||||
    <ul class="posts">
 | 
			
		||||
        {% for post in user.posts %}
 | 
			
		||||
            <li>
 | 
			
		||||
                <h3>From {{ post.user.name }} in /{{ post.board.name }}/</h3>
 | 
			
		||||
                <h6>posted at {{ post.created_at }}</h6>
 | 
			
		||||
                {% if post.reference %}
 | 
			
		||||
                <h6><b>ref post:</b> <a href="/posts/{{ post.reference.id }}">{{ post.reference.content }}</a></h6>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
                <p>{{ post.content }}</p>
 | 
			
		||||
                <h6><a href="/posts/{{ post.id }}">View Post</a></h6>
 | 
			
		||||
            </li>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
        {% if user.posts|length == 0 %}
 | 
			
		||||
            <li>No posts found.</li>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </ul>
 | 
			
		||||
    <div id="nav">
 | 
			
		||||
        <h5>Page {{ page }}</h5>
 | 
			
		||||
        {% if page > 1 %}
 | 
			
		||||
        <h5><a href="/user/{{ user.name }}?page={{ page - 1 }}">Previous Page</a></h5>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        
 | 
			
		||||
        {% if user.posts|length == 10 %}
 | 
			
		||||
        <h5><a href="/user/{{ user.name }}?page={{ page + 1 }}">Next Page</a></h5>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										345
									
								
								src/main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								src/main.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,345 @@
 | 
			
		||||
from flask import Flask, request, render_template, session, redirect
 | 
			
		||||
import database, logging, os, hashlib, html
 | 
			
		||||
from flask_session import Session
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Global variables
 | 
			
		||||
SYSTEMUID = None
 | 
			
		||||
SYSTEMBID = None
 | 
			
		||||
allowed_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%£^&()-_=+[]{};:'\",.<>?/\\|`~ "
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Configure logging
 | 
			
		||||
console_log = logging.StreamHandler()
 | 
			
		||||
console_log.setFormatter(logging.Formatter("\033[1;32m%(asctime)s\033[0m - \033[1;34m%(levelname)s\033[0m - \033[1;31m%(name)s\033[0m - %(message)s"))
 | 
			
		||||
logger = logging.getLogger()
 | 
			
		||||
logger.addHandler(console_log)
 | 
			
		||||
logger.setLevel(logging.INFO)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Initialize Flask app
 | 
			
		||||
logger.info("Initializing Flask app...")
 | 
			
		||||
app = Flask(__name__, template_folder=os.getenv('TEMPLATE_FOLDER', 'html'), static_folder=os.getenv('STATIC_FOLDER', 'static'))
 | 
			
		||||
app.config["SESSION_PERMANENT"] = True
 | 
			
		||||
app.config["SESSION_TYPE"] = "filesystem"
 | 
			
		||||
Session(app)
 | 
			
		||||
logger.info("Flask app initialized.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Initialize database
 | 
			
		||||
logger.info("Initializing database...")
 | 
			
		||||
db = database.Database('app.db')
 | 
			
		||||
 | 
			
		||||
if db.get_user('SYSTEM') is None:
 | 
			
		||||
    logger.info("Running first time setup...")
 | 
			
		||||
 | 
			
		||||
    logger.info("Creating SYSTEM user...")
 | 
			
		||||
    db.create_user('SYSTEM', 'SYSTEM')
 | 
			
		||||
    SYSTEMUID = db.get_user('SYSTEM')[0]
 | 
			
		||||
    logger.info("SYSTEM user created with UID: %s", SYSTEMUID)
 | 
			
		||||
 | 
			
		||||
    logger.info("Creating SYSTEM board...")
 | 
			
		||||
    db.create_board('System', 'System messages', SYSTEMUID)
 | 
			
		||||
    db.create_board('General', 'General discussion', SYSTEMUID)
 | 
			
		||||
    db.create_board('Linux', 'Linux discussion', SYSTEMUID)
 | 
			
		||||
    db.create_board('Random', 'Random discussion', SYSTEMUID)
 | 
			
		||||
    db.create_board('Tech', 'Tech discussion', SYSTEMUID)
 | 
			
		||||
    db.create_board('Games', 'Games discussion', SYSTEMUID)
 | 
			
		||||
    SYSTEMBID = db.get_board('System')[0]
 | 
			
		||||
    logger.info("SYSTEM board created.")
 | 
			
		||||
 | 
			
		||||
    logger.info("Creating First time setup post...")
 | 
			
		||||
    db.create_post(SYSTEMBID, SYSTEMUID, 'Welcome!')
 | 
			
		||||
    logger.info("First time setup post created.")
 | 
			
		||||
 | 
			
		||||
else:
 | 
			
		||||
    SYSTEMUID = db.get_user('SYSTEM')[0]
 | 
			
		||||
    SYSTEMBID = db.get_board('System')[0]
 | 
			
		||||
    logger.info("SYSTEM user and board already exist.")
 | 
			
		||||
    logger.info("SYSTEM user UID: %s", SYSTEMUID)
 | 
			
		||||
    logger.info("SYSTEM board BID: %s", SYSTEMBID)
 | 
			
		||||
 | 
			
		||||
logger.info("Database initialized.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Helper functions
 | 
			
		||||
 | 
			
		||||
def sanitize_input(input_string):
 | 
			
		||||
    logger.info("Sanitizing input...")
 | 
			
		||||
    # Sanitize input to allow only certain characters
 | 
			
		||||
    if not isinstance(input_string, str):
 | 
			
		||||
        logger.error("Input is not a string.")
 | 
			
		||||
        return None
 | 
			
		||||
    sanitized = ''.join(c for c in input_string if c in allowed_chars)
 | 
			
		||||
    sanitized = html.escape(sanitized)
 | 
			
		||||
    logger.info("Sanitized input")
 | 
			
		||||
    return sanitized
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def hash_password(password):
 | 
			
		||||
    logger.info("Hashing password...")
 | 
			
		||||
    # Hash the password using SHA-256
 | 
			
		||||
    if not isinstance(password, str):
 | 
			
		||||
        logger.error("Password is not a string.")
 | 
			
		||||
        return None
 | 
			
		||||
    hashed = hashlib.sha256(password.encode()).hexdigest()
 | 
			
		||||
    logger.info("Hashed password")
 | 
			
		||||
    return hashed
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Define routes
 | 
			
		||||
@app.route('/')
 | 
			
		||||
def index():
 | 
			
		||||
    logger.info("Rendering index page...")
 | 
			
		||||
    latest_posts = db.get_latest_posts(5)
 | 
			
		||||
    return render_template('index.html', posts=[db.post_to_dict(post) for post in latest_posts])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/posts/<int:post_id>')
 | 
			
		||||
def post(post_id):
 | 
			
		||||
    logger.info("Rendering post page for post ID: %s", post_id)
 | 
			
		||||
    post = db.get_post(post_id)
 | 
			
		||||
    if post is None:
 | 
			
		||||
        logger.error("Post not found: %s", post_id)
 | 
			
		||||
        return "Post not found", 404
 | 
			
		||||
    
 | 
			
		||||
    post = db.post_to_dict(post)
 | 
			
		||||
    page = request.args.get('page', 1, type=int)
 | 
			
		||||
 | 
			
		||||
    logger.info("Post found: %s", post_id)
 | 
			
		||||
    replies = db.get_post_references(post_id, 10, 10 * (page - 1))
 | 
			
		||||
    if replies is None:
 | 
			
		||||
        logger.error("No replies found for post ID: %s", post_id)
 | 
			
		||||
        post['replies'] = []
 | 
			
		||||
    else:
 | 
			
		||||
        logger.info("Found %s replies for post ID: %s", len(replies), post_id)
 | 
			
		||||
        post['replies'] = [db.post_to_dict(reply) for reply in replies]
 | 
			
		||||
        post['replies'].sort(key=lambda x: x['created_at'], reverse=True)
 | 
			
		||||
 | 
			
		||||
    return render_template('post.html', post=post, page=page)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/login', methods=['GET', 'POST'])
 | 
			
		||||
def login():
 | 
			
		||||
    logger.info("Rendering login page...")
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        username = request.form['username']
 | 
			
		||||
        password = request.form['password']
 | 
			
		||||
        username = sanitize_input(username)
 | 
			
		||||
        password = sanitize_input(password)
 | 
			
		||||
        token = db.create_session(username, hash_password(password))
 | 
			
		||||
        if token:
 | 
			
		||||
            logger.info("User %s logged in successfully.", username)
 | 
			
		||||
            session['name'] = username
 | 
			
		||||
            session['id'] = db.get_user(username)[0]
 | 
			
		||||
            session['session'] = token
 | 
			
		||||
            return redirect('/')
 | 
			
		||||
        else:
 | 
			
		||||
            logger.error("Invalid login attempt for user: %s", username)
 | 
			
		||||
            return render_template('login.html', error="Invalid username or password.")
 | 
			
		||||
        
 | 
			
		||||
    return render_template('login.html')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/user/<string:username>')
 | 
			
		||||
def user(username):
 | 
			
		||||
    logger.info("Rendering user page for user: %s", username)
 | 
			
		||||
    username = sanitize_input(username)
 | 
			
		||||
    user = db.get_user(username)
 | 
			
		||||
    if user is None:
 | 
			
		||||
        logger.error("User not found: %s", username)
 | 
			
		||||
        return "User not found", 404
 | 
			
		||||
    
 | 
			
		||||
    user = db.user_to_dict(user)
 | 
			
		||||
    page = request.args.get('page', 1, type=int)
 | 
			
		||||
 | 
			
		||||
    posts = db.get_user_posts(user['id'], 10, 10 * (page - 1))
 | 
			
		||||
    if posts is None:
 | 
			
		||||
        logger.error("No posts found for user: %s", username)
 | 
			
		||||
        user['posts'] = []
 | 
			
		||||
    else:
 | 
			
		||||
        logger.info("Found %s posts for user: %s", len(posts), username)
 | 
			
		||||
        user['posts'] = [db.post_to_dict(post) for post in posts]
 | 
			
		||||
 | 
			
		||||
    return render_template('user.html', user=user, page=page)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/logout')
 | 
			
		||||
def logout():
 | 
			
		||||
    logger.info("Logging out user: %s", session.get('name'))
 | 
			
		||||
    session.clear()
 | 
			
		||||
    return redirect('/')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/posts')
 | 
			
		||||
def posts():
 | 
			
		||||
    logger.info("Rendering posts page...")
 | 
			
		||||
    page = request.args.get('page', 1, type=int)
 | 
			
		||||
    posts = db.get_posts(None, 10, 10 * (page - 1))
 | 
			
		||||
    if posts is None:
 | 
			
		||||
        logger.error("No posts found.")
 | 
			
		||||
        return "No posts found", 404
 | 
			
		||||
    
 | 
			
		||||
    logger.info("Found %s posts.", len(posts))
 | 
			
		||||
    return render_template('posts.html', posts=[db.post_to_dict(post) for post in posts], page=page)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/boards')
 | 
			
		||||
def boards():
 | 
			
		||||
    logger.info("Rendering boards page...")
 | 
			
		||||
 | 
			
		||||
    page = request.args.get('page', 1, type=int)
 | 
			
		||||
    boards = db.get_boards(10, 10 * (page - 1))
 | 
			
		||||
 | 
			
		||||
    if boards is None:
 | 
			
		||||
        logger.error("No boards found.")
 | 
			
		||||
        return "No boards found", 404
 | 
			
		||||
 | 
			
		||||
    logger.info("Found %s boards.", len(boards))
 | 
			
		||||
    return render_template('boards.html', boards=[db.board_to_dict(board) for board in boards], page=page)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/boards/<string:board>')
 | 
			
		||||
def board(board):
 | 
			
		||||
    logger.info("Rendering board page for board: %s", board)
 | 
			
		||||
    board = sanitize_input(board)
 | 
			
		||||
    board = db.get_board(board)
 | 
			
		||||
    if board is None:
 | 
			
		||||
        logger.error("Board not found: %s", board)
 | 
			
		||||
        return "Board not found", 404
 | 
			
		||||
    board_id = board[0]
 | 
			
		||||
    
 | 
			
		||||
    board = db.board_to_dict(board)
 | 
			
		||||
    page = request.args.get('page', 1, type=int)
 | 
			
		||||
 | 
			
		||||
    posts = db.get_board_posts(board_id, 10, 10 * (page - 1))
 | 
			
		||||
    if posts is None:
 | 
			
		||||
        logger.error("No posts found for board ID: %s", board_id)
 | 
			
		||||
        board['posts'] = []
 | 
			
		||||
    else:
 | 
			
		||||
        logger.info("Found %s posts for board ID: %s", len(posts), board_id)
 | 
			
		||||
        board['posts'] = [db.post_to_dict(post) for post in posts]
 | 
			
		||||
 | 
			
		||||
    return render_template('board.html', board=board, page=page)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/post', methods=['POST', 'GET'])
 | 
			
		||||
def post_create():
 | 
			
		||||
    logger.info("Rendering post creation page...")
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        content = request.form['content']
 | 
			
		||||
        board_id = request.form['board']
 | 
			
		||||
        ref = request.form.get('ref', None)
 | 
			
		||||
        token = session.get('session')
 | 
			
		||||
 | 
			
		||||
        # sanitize input
 | 
			
		||||
        content = sanitize_input(content)
 | 
			
		||||
        try:
 | 
			
		||||
            board_id = int(board_id)
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            logger.error("Invalid board ID: %s", board_id)
 | 
			
		||||
            return render_template('newpost.html', error="Invalid board ID.")
 | 
			
		||||
        if not content:
 | 
			
		||||
            logger.error("Post content is empty.")
 | 
			
		||||
            return render_template('newpost.html', error="Post content cannot be empty.")
 | 
			
		||||
        if not board_id:
 | 
			
		||||
            logger.error("Board ID is empty.")
 | 
			
		||||
            return render_template('newpost.html', error="Board ID cannot be empty.")
 | 
			
		||||
        if not token:
 | 
			
		||||
            logger.error("Session token is missing.")
 | 
			
		||||
            return render_template('newpost.html', error="Session expired. Please log in again.")
 | 
			
		||||
        if ref:
 | 
			
		||||
            try:
 | 
			
		||||
                ref = int(ref)
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                logger.error("Invalid reference post ID: %s", ref)
 | 
			
		||||
                return render_template('newpost.html', error="Invalid reference post ID.")
 | 
			
		||||
        if len(content) > 10000:
 | 
			
		||||
            logger.error("Post content exceeds maximum length.")
 | 
			
		||||
            return render_template('newpost.html', error="Post content exceeds maximum length.")
 | 
			
		||||
 | 
			
		||||
        user = db.get_session(token)
 | 
			
		||||
        if user is None:
 | 
			
		||||
            logger.error("Session not found or expired.")
 | 
			
		||||
            return render_template('newpost.html', error="Session expired. Please log in again.")
 | 
			
		||||
        user_id = user[0]
 | 
			
		||||
 | 
			
		||||
        if board_id == SYSTEMBID and user_id != SYSTEMUID:
 | 
			
		||||
            logger.error("User %s is not allowed to post in SYSTEM board.", user_id)
 | 
			
		||||
            return render_template('newpost.html', error="You are not allowed to post in this board.")
 | 
			
		||||
 | 
			
		||||
        if ref:
 | 
			
		||||
            ref = db.get_post(ref)
 | 
			
		||||
            if ref is None:
 | 
			
		||||
                logger.error("Reference post not found: %s", ref)
 | 
			
		||||
                return render_template('newpost.html', error="Reference post not found.")
 | 
			
		||||
            ref = ref[0]
 | 
			
		||||
 | 
			
		||||
        logger.info("Creating post in board ID: %s", board_id)
 | 
			
		||||
        status = db.create_post(board_id, user_id, content, ref)
 | 
			
		||||
 | 
			
		||||
        if type(status) is not int:
 | 
			
		||||
            logger.error("Post creation failed.")
 | 
			
		||||
            return "Post creation failed", 500
 | 
			
		||||
        
 | 
			
		||||
        logger.info("Post created successfully.")
 | 
			
		||||
        if request.form.get('redirect'):
 | 
			
		||||
            return redirect(request.form.get('redirect'))
 | 
			
		||||
        return redirect('/posts/' + str(status))
 | 
			
		||||
    return render_template('newpost.html', boards=db.get_all_boards_for_post())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/register', methods=['GET', 'POST'])
 | 
			
		||||
def register():
 | 
			
		||||
    logger.info("Rendering registration page...")
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        username = request.form['username']
 | 
			
		||||
        password = request.form['password']
 | 
			
		||||
        username = sanitize_input(username)
 | 
			
		||||
        password = sanitize_input(password)
 | 
			
		||||
 | 
			
		||||
        if not username or not password:
 | 
			
		||||
            logger.error("Username or password is empty.")
 | 
			
		||||
            return render_template('register.html', error="Username and password cannot be empty.")
 | 
			
		||||
        
 | 
			
		||||
        if len(username) > 20:
 | 
			
		||||
            logger.error("Username length is invalid.")
 | 
			
		||||
            return render_template('register.html', error="Username must be less than 20 characters.")
 | 
			
		||||
        
 | 
			
		||||
        if len(password) < 6 or len(password) > 20:
 | 
			
		||||
            logger.error("Password length is invalid.")
 | 
			
		||||
            return render_template('register.html', error="Password must be between 6 and 20 characters.")
 | 
			
		||||
 | 
			
		||||
        if db.get_user(username):
 | 
			
		||||
            logger.error("Username already exists: %s", username)
 | 
			
		||||
            return render_template('register.html', error="Username already exists.")
 | 
			
		||||
 | 
			
		||||
        hashed_password = hash_password(password)
 | 
			
		||||
        db.create_user(username, hashed_password)
 | 
			
		||||
        logger.info("User %s registered successfully.", username)
 | 
			
		||||
        db.create_post(SYSTEMUID, SYSTEMUID, f"New user \"{username}\" registered.")
 | 
			
		||||
        # Create a session for the new user
 | 
			
		||||
        token = db.create_session(username, hashed_password)
 | 
			
		||||
        if token:
 | 
			
		||||
            session['name'] = username
 | 
			
		||||
            session['id'] = db.get_user(username)[0]
 | 
			
		||||
            session['session'] = token
 | 
			
		||||
            logger.info("User %s logged in after registration.", username)
 | 
			
		||||
            return redirect('/')
 | 
			
		||||
        else:
 | 
			
		||||
            logger.error("Session creation failed for user: %s", username)
 | 
			
		||||
            return render_template('register.html', error="Session creation failed.")
 | 
			
		||||
        
 | 
			
		||||
    logger.info("GET request for registration page.")
 | 
			
		||||
    if session.get('name'):
 | 
			
		||||
        logger.info("User %s is already logged in.", session['name'])
 | 
			
		||||
        return redirect('/')
 | 
			
		||||
    logger.info("Rendering registration form.")
 | 
			
		||||
 | 
			
		||||
    return render_template('register.html')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Main function
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    logger.info("Starting Flask app...")
 | 
			
		||||
    app.run(host=os.getenv('FLASK_HOST', '0.0.0.0'), port=int(os.getenv('FLASK_PORT', 5000)), debug=os.getenv('FLASK_DEBUG', 'false').lower() == 'true')
 | 
			
		||||
							
								
								
									
										182
									
								
								src/static/base.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								src/static/base.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,182 @@
 | 
			
		||||
:root {
 | 
			
		||||
    --bg: #000;
 | 
			
		||||
    --fg: #fff;
 | 
			
		||||
    --accent: #7139f3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
    background-color: var(--bg);
 | 
			
		||||
    color: var(--fg);
 | 
			
		||||
    font-family: 'code', sans-serif;
 | 
			
		||||
    margin: 20px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1, h2, h3, h4, h5, h6, p {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 30px;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    border-bottom: 1px solid var(--fg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header #title {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: baseline;
 | 
			
		||||
    gap: 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header h1 {
 | 
			
		||||
    font-size: 2rem;
 | 
			
		||||
}
 | 
			
		||||
header h2 {
 | 
			
		||||
    font-size: .8rem;
 | 
			
		||||
    text-wrap: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header nav {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header nav ul {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    gap: 15px;
 | 
			
		||||
    list-style: none;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header nav ul li:last-child {
 | 
			
		||||
    margin-left: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header nav ul li a {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: var(--fg);
 | 
			
		||||
    font-size: 1.2rem;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
    transition: color 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header nav ul li a:hover {
 | 
			
		||||
    color: var(--accent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
main {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
footer {
 | 
			
		||||
    margin-top: 10px;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    border-top: 1px solid var(--fg);
 | 
			
		||||
    font-size: 0.8rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a {
 | 
			
		||||
    color: var(--accent);
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    transition: all 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
a:hover {
 | 
			
		||||
    color: var(--fg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul.posts {
 | 
			
		||||
    list-style: none;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    margin-left: 10px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    gap: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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-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 {
 | 
			
		||||
    background-color: var(--fg);
 | 
			
		||||
    color: var(--bg);
 | 
			
		||||
}
 | 
			
		||||
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: 100px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
textarea:focus {
 | 
			
		||||
    outline: none;
 | 
			
		||||
    border-color: var(--accent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div#nav {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    gap: 10px;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user