diff --git a/.gitignore b/.gitignore index d2ca5e9..7032593 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -.env .venv -app.db -__pycache__ +app.log +__pycache__/ +database.db +.env flask_session \ No newline at end of file diff --git a/dockerfile b/dockerfile index 88e9628..22d952d 100644 --- a/dockerfile +++ b/dockerfile @@ -10,7 +10,7 @@ COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy the rest of the application code into the container -COPY src/ ./src +COPY src src # Expose the port the app runs on EXPOSE 5000 diff --git a/readme.md b/readme.md index 6ce3e28..67fb375 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,6 @@ prismic is a simple messageboard made in python ## Planned features -- [ ] user board creation -- [ ] custom profiles +- [x] user board creation +- [x] custom profiles - [x] moderation tools \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2114d63..aade3db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ -flask -flask-session \ No newline at end of file +mysql-connector-python # only required for external mysql connection +python-dotenv # only required if using .env file +python-dateutil +flask-session +flask \ No newline at end of file diff --git a/src/database.py b/src/database.py index a203755..378be11 100644 --- a/src/database.py +++ b/src/database.py @@ -1,581 +1,185 @@ -import sqlite3, logging, os +import os, hashlib, sqlite3, logging +from os import getenv as env +import mysql.connector -# Configure logging -logger = logging.getLogger(__name__) +log = logging.getLogger(__name__) +FETCHALL = 0 +FETCHONE = 1 class Database: - def __init__(self, db_name): - # Initialize the database connection - logger.info("Initializing database connection...") - self.connection = sqlite3.connect(db_name, check_same_thread=False) - self.cursor = self.connection.cursor() - logger.info("Database connection established.") + def __init__(self): + self.connection = None + self.cursor = None - # Create users table if it doesn't exist - logger.info("Creating users table if it doesn't exist...") - self.cursor.execute(''' - CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - username TEXT NOT NULL UNIQUE, - password TEXT NOT NULL, - session TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + def connect_sqlite(self, db_path): + try: + self.connection = sqlite3.connect(db_path, check_same_thread=False) + self.cursor = self.connection.cursor() + log.info("Connected to SQLite database") + except sqlite3.Error as e: + log.error(f"SQLite connection error: {e}") + raise + + def connect_mysql(self): + try: + self.connection = mysql.connector.connect( + host=env('MYSQL_HOST'), + user=env('MYSQL_USER'), + password=env('MYSQL_PASSWORD'), + database=env('MYSQL_DATABASE') ) - ''') - logger.info("Users table created.") - - # Create boards table if it doesn't exist - logger.info("Creating boards table if it doesn't exist...") - self.cursor.execute(''' - CREATE TABLE IF NOT EXISTS boards ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL UNIQUE, - owner_id INTEGER NOT NULL, - description TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (owner_id) REFERENCES users (id) ON DELETE SET NULL - ) - ''') - logger.info("Boards table created.") - - # Create posts table if it doesn't exist - logger.info("Creating posts table if it doesn't exist...") - self.cursor.execute(''' - CREATE TABLE IF NOT EXISTS posts ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - board_id INTEGER NOT NULL, - content TEXT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - refence INTEGER, - FOREIGN KEY (refence) REFERENCES posts (id) ON DELETE CASCADE, - FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL, - FOREIGN KEY (board_id) REFERENCES boards (id) ON DELETE CASCADE - ) - ''') - logger.info("Posts table created.") - + self.cursor = self.connection.cursor() + log.info("Connected to MySQL database") + except mysql.connector.Error as e: + log.error(f"MySQL connection error: {e}") + raise def close(self): - # Close the database connection - logger.info("Closing database connection...") - self.connection.commit() - self.connection.close() - logger.info("Database connection closed.") + if self.cursor: + self.cursor.close() + if self.connection: + self.connection.close() + log.warning("Database connection closed") - - def create_user(self, username, password): - logger.info(f"Creating user: {username}") - - # Check if the user already exists - logger.info(f"Checking if user {username} already exists...") - self.cursor.execute(''' - SELECT * FROM users WHERE username = ? - ''', (username,)) - user = self.cursor.fetchone() - - if user: - logger.warning(f"User {username} already exists.") - return False - logger.info(f"User {username} does not exist.") - - # Create a new user - self.cursor.execute(''' - INSERT INTO users (username, password) - VALUES (?, ?) - ''', (username, password)) - - self.connection.commit() - logger.info(f"User {username} created.") - return True - - - def get_user(self, username): - # Get user by username - logger.info(f"Getting user {username}...") - self.cursor.execute(''' - SELECT * FROM users WHERE username = ? - ''', (username,)) - user = self.cursor.fetchone() - - if user: - logger.info(f"User {username} found.") - return user - logger.warning(f"User {username} not found.") - return None - - - def create_board(self, name, description, owner_id): - logger.info(f"Creating board: {name}") - - # Check if the board already exists - logger.info(f"Checking if board {name} already exists...") - self.cursor.execute(''' - SELECT * FROM boards WHERE name = ? - ''', (name,)) - board = self.cursor.fetchone() - - if board: - logger.warning(f"Board {name} already exists.") - return False - logger.info(f"Board {name} does not exist.") - - # Check if the owner exists - logger.info(f"Checking if owner {owner_id} exists...") - self.cursor.execute(''' - SELECT * FROM users WHERE id = ? - ''', (owner_id,)) - owner = self.cursor.fetchone() - - if not owner: - logger.warning(f"Owner {owner_id} does not exist.") - return False - logger.info(f"Owner {owner_id} exists.") - - # Create a new board - self.cursor.execute(''' - INSERT INTO boards (name, owner_id, description) - VALUES (?, ?, ?) - ''', (name, owner_id, description)) - - self.connection.commit() - logger.info(f"Board {name} created.") - return True - - - def get_board(self, name): - # Get board by name - logger.info(f"Getting board {name}...") - self.cursor.execute(''' - SELECT * FROM boards WHERE name = ? - ''', (name,)) - board = self.cursor.fetchone() - - if board: - logger.info(f"Board {name} found.") - return board - logger.warning(f"Board {name} not found.") - return None - - - def create_post(self, user_id, board_id, content, ref=None): - logger.info(f"Creating post for user {user_id} in board {board_id}...") - - # Check if the user exists - logger.info(f"Checking if user {user_id} exists...") - self.cursor.execute(''' - SELECT * FROM users WHERE id = ? - ''', (user_id,)) - user = self.cursor.fetchone() - - if not user: - logger.warning(f"User {user_id} does not exist.") - return False - logger.info(f"User {user_id} exists.") - - # Check if the board exists - logger.info(f"Checking if board {board_id} exists...") - self.cursor.execute(''' - SELECT * FROM boards WHERE id = ? - ''', (board_id,)) - board = self.cursor.fetchone() - - if not board: - logger.warning(f"Board {board_id} does not exist.") - return False - logger.info(f"Board {board_id} exists.") - - # Check if the reference post exists - if ref is not None: - logger.info(f"Checking if reference post {ref} exists...") - self.cursor.execute(''' - SELECT * FROM posts WHERE id = ? - ''', (ref,)) - reference_post = self.cursor.fetchone() - - if not reference_post: - logger.warning(f"Reference post {ref} does not exist.") - return False - logger.info(f"Reference post {ref} exists.") - else: - logger.info("No reference post provided.") - - # Create a new post - self.cursor.execute(''' - INSERT INTO posts (user_id, board_id, content, refence) - VALUES (?, ?, ?, ?) - ''', (user_id, board_id, content, ref)) - self.connection.commit() - logger.info(f"Post created for user {user_id} in board {board_id}.") - post_id = self.cursor.lastrowid - logger.info(f"Post ID: {post_id}") - return post_id - - - def get_post(self, post_id): - # Get post by ID - logger.info(f"Getting post {post_id}...") - self.cursor.execute(''' - SELECT * FROM posts WHERE id = ? - ''', (post_id,)) - post = self.cursor.fetchone() - - if post: - logger.info(f"Post {post_id} found.") - return post - logger.warning(f"Post {post_id} not found.") - return None - - - def get_post_references(self, post_id, limit=10, offset=0): - # Get references for a post - logger.info(f"Getting references for post {post_id}...") - self.cursor.execute(''' - SELECT * FROM posts WHERE refence = ? LIMIT ? OFFSET ? - ''', (post_id, limit, offset)) - references = self.cursor.fetchall() - - if references: - logger.info(f"References found for post {post_id}.") - return references - logger.warning(f"No references found for post {post_id}.") - return None - - - def get_posts(self, board_id, limit, offset): - if board_id: - logger.info(f"Getting posts for board {board_id}...") - - # Check if the board exists - logger.info(f"Checking if board {board_id} exists...") - self.cursor.execute(''' - SELECT * FROM boards WHERE id = ? - ''', (board_id,)) - board = self.cursor.fetchone() - - if not board: - logger.warning(f"Board {board_id} does not exist.") - return None - logger.info(f"Board {board_id} exists.") - - # Get posts for the board - logger.info(f"Getting posts for board {board_id}...") - self.cursor.execute(''' - SELECT * FROM posts WHERE board_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ? - ''', (board_id, limit, offset)) - posts = self.cursor.fetchall() - - if posts: - logger.info(f"Posts found for board {board_id}.") - return posts - logger.warning(f"No posts found for board {board_id}.") - return None - - else: - # Get all posts - logger.info(f"Getting all posts...") - self.cursor.execute(''' - SELECT * FROM posts ORDER BY created_at DESC LIMIT ? OFFSET ? - ''', (limit, offset)) - posts = self.cursor.fetchall() - - if posts: - logger.info(f"Posts found.") - return posts - logger.warning(f"No posts found.") - return None - - - def get_latest_posts(self, limit): - logger.info(f"Getting latest posts...") - - # Get latest posts - logger.info(f"Getting latest posts...") - self.cursor.execute(''' - SELECT * FROM posts ORDER BY created_at DESC LIMIT ? - ''', (limit,)) - posts = self.cursor.fetchall() - - if posts: - logger.info(f"Latest posts found.") - return posts - logger.warning(f"No latest posts found.") - return None - - - def get_boards(self, limit, offset): - logger.info(f"Listing boards with limit {limit} and offset {offset}...") - - # Get boards with pagination - self.cursor.execute(''' - SELECT * FROM boards ORDER BY created_at DESC LIMIT ? OFFSET ? - ''', (limit, offset)) - boards = self.cursor.fetchall() - - if boards: - logger.info(f"Boards found.") - return boards - logger.warning(f"No boards found.") - return None - - - def post_to_dict(self, post): - # Convert post to dictionary - logger.info(f"Converting post to dictionary...") - data = { - 'id': post[0], - 'content': post[3], - 'short_content': post[3][:40] + '...' if len(post[3]) > 40 else post[3], - 'created_at': post[4], - 'url': f"/post/{post[0]}", - } - - # Get user information - logger.info(f"Getting user information for post {post[0]}...") - self.cursor.execute(''' - SELECT * FROM users WHERE id = ? - ''', (post[1],)) - user = self.cursor.fetchone() - - if user: - data['user'] = { - 'id': user[0], - 'name': user[1], - 'created_at': user[3] - } - logger.info(f"User information found for post {post[0]}.") - else: - logger.warning(f"User information not found for post {post[0]}.") - data['user'] = { - 'id': None, - 'name': None, - 'created_at': None - } - - # Get board information - logger.info(f"Getting board information for post {post[0]}...") - self.cursor.execute(''' - SELECT * FROM boards WHERE id = ? - ''', (post[2],)) - board = self.cursor.fetchone() - - if board: - data['board'] = { - 'id': board[0], - 'name': board[1], - 'description': board[3], - 'created_at': board[4] - } - logger.info(f"Board information found for post {post[0]}.") - else: - logger.warning(f"Board information not found for post {post[0]}.") - data['board'] = { - 'id': None, - 'name': None, - 'description': None, - 'created_at': None - } - - # Get reference post information - if post[5] is not None: - logger.info(f"Getting reference post information for post {post[0]}...") - self.cursor.execute(''' - SELECT * FROM posts WHERE id = ? - ''', (post[5],)) - reference_post = self.cursor.fetchone() - - if reference_post: - data['reference'] = { - 'id': reference_post[0], - 'content': reference_post[3], - 'short_content': reference_post[3][:20] + '...' if len(reference_post[3]) > 20 else reference_post[3], - 'created_at': reference_post[4] - } - logger.info(f"Reference post information found for post {post[0]}.") + def execute_query(self, query, params=None, fetch_type=FETCHALL): + try: + if params: + self.cursor.execute(query, params) else: - logger.warning(f"Reference post information not found for post {post[0]}.") - data['reference'] = { - 'id': None, - 'content': None, - 'created_at': None - } - else: - logger.info("No reference post.") - data['reference'] = None + self.cursor.execute(query) + self.connection.commit() + log.debug(f"Executed query: {query}") + if fetch_type == FETCHALL: + return self.fetchall() + elif fetch_type == FETCHONE: + return self.fetchone() + elif fetch_type is None: + log.debug("No fetch type specified, returning None") + return None + else: + log.warning("Invalid fetch type, returning None") + return None + except (sqlite3.Error, mysql.connector.Error) as e: + log.critical(f"Query execution error: {e}") + raise - # get post references - logger.info(f"Getting post references for post {post[0]}...") - self.cursor.execute(''' - SELECT * FROM posts WHERE refence = ? - ''', (post[0],)) - references = self.cursor.fetchall() + def fetchall(self): + try: + result = self.cursor.fetchall() + log.debug(f"Fetched all results") + return result + except (sqlite3.Error, mysql.connector.Error) as e: + log.error(f"Fetchall error: {e}") + raise - if references: - data['replies'] = [] - for reference in references: - reference_data = { - 'id': reference[0], - 'content': reference[3], - 'short_content': reference[3][:20] + '...' if len(reference[3]) > 20 else reference[3], - 'created_at': reference[4] - } - data['replies'].append(reference_data) - logger.info(f"Post references found for post {post[0]}.") - else: - logger.warning(f"No post references found for post {post[0]}.") - data['replies'] = [] + def fetchone(self): + try: + result = self.cursor.fetchone() + log.debug(f"Fetched one result") + return result + except (sqlite3.Error, mysql.connector.Error) as e: + log.critical(f"Fetchone error: {e}") + raise - logger.info(f"Post converted to dictionary.") - return data + +def first_time_run(db:Database): + log.info("First time run detected, initializing database") + + # Create users table + log.info("Creating users table") + db.execute_query(""" + CREATE TABLE users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL, + password TEXT NOT NULL, + about TEXT DEFAULT 'No description', + permissions TEXT DEFAULT 'user', + avatar MEDIUMBLOB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + token TEXT, + avatar_type TEXT + ) + """) + + # Create posts table + log.info("Creating posts table") + db.execute_query(""" + CREATE TABLE posts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + board_id INTEGER NOT NULL, + content TEXT NOT NULL, + reference INTREGER, + type TEXT DEFAULT 'post', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, + FOREIGN KEY (board_id) REFERENCES boards (id) ON DELETE CASCADE, + FOREIGN KEY (reference) REFERENCES posts (id) ON DELETE SET NULL + ) + """) + + # Create boards table + log.info("Creating boards table") + db.execute_query(""" + CREATE TABLE boards ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + owner_id INTEGER NOT NULL, + FOREIGN KEY (owner_id) REFERENCES users (id) + ) + """) + + # Create attachments table + db.execute_query(""" + CREATE TABLE attachments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + post_id INTEGER NOT NULL, + file_data MEDIUMBLOB NOT NULL, + file_name TEXT NOT NULL, + file_type TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (post_id) REFERENCES posts (id) ON DELETE CASCADE + ) + """) + + # Create system user + + if not env('SYSTEM_PASSWORD'): + log.warning("SYSTEM_PASSWORD is empty, generating random password") + password = os.urandom(16).hex() + log.info(f"Generated system user password: {password}") + else: + password = env('SYSTEM_PASSWORD') + log.info("Creating system user") + db.execute_query(""" + INSERT INTO users (username, password, about, permissions) + VALUES (?, ?, ?, ?) + """, ( + env('SYSTEM_USER', default='system'), + env('SYSTEM_PASSWORD', default=hashlib.sha256(password.encode()).hexdigest()), + env('SYSTEM_ABOUT', default='System User'), + env('SYSTEM_PERMISSIONS', default='admin') + )) - def create_session(self, username, password): - logger.info(f"Creating session for user {username}...") + # Create system boards + boards_names = env('SYSTEM_BOARDS', default='General,Random,System').split(',') + if "System" not in boards_names: + boards_names.append("System") - # Check if the user exists - logger.info(f"Checking if user {username} exists...") - self.cursor.execute(''' - SELECT * FROM users WHERE username = ? - ''', (username,)) - user = self.cursor.fetchone() + log.info(f"Creating system boards: {', '.join(boards_names)}") + for board_name in boards_names: + db.execute_query(""" + INSERT INTO boards (name, description, owner_id) + VALUES (?, ?, ?) + """, ( + board_name, + f"This is a automatically created board for {board_name}", + 1 + )) - if not user: - logger.warning(f"User {username} does not exist.") - return None - logger.info(f"User {username} exists.") - - # Check if the password is correct - logger.info(f"Checking password for user {username}...") - if user[2] != password: - logger.warning(f"Incorrect password for user {username}.") - return None - logger.info(f"Password for user {username} is correct.") - - # Create a new session and overwrite the old one if it exists - session = os.urandom(16).hex() - self.cursor.execute(''' - UPDATE users SET session = ? WHERE username = ? - ''', (session, username)) - - self.connection.commit() - logger.info(f"Session created for user {username}.") - return session - - - def get_session(self, session): - logger.info(f"Getting session {session}...") - - # Get session information - self.cursor.execute(''' - SELECT * FROM users WHERE session = ? - ''', (session,)) - user = self.cursor.fetchone() - - if user: - logger.info(f"Session {session} found.") - return user - logger.warning(f"Session {session} not found.") - return None - - - def delete_session(self, session): - logger.info(f"Deleting session {session}...") - - # Delete session - self.cursor.execute(''' - UPDATE users SET session = NULL WHERE session = ? - ''', (session,)) - - self.connection.commit() - logger.info(f"Session {session} deleted.") - return True - - - def user_to_dict(self, user): - # Convert user to dictionary - logger.info(f"Converting user to dictionary...") - data = { - 'id': user[0], - 'name': user[1], - 'created_at': user[4] - } - logger.info(f"User converted to dictionary.") - return data - - - def get_user_posts(self, user_id, limit=10, offset=0): - logger.info(f"Getting posts for user {user_id}...") - - # Get posts for the user - self.cursor.execute(''' - SELECT * FROM posts WHERE user_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ? - ''', (user_id, limit, offset)) - posts = self.cursor.fetchall() - - if posts: - logger.info(f"Posts found for user {user_id}.") - return posts - logger.warning(f"No posts found for user {user_id}.") - return None - - - def board_to_dict(self, board): - # Convert board to dictionary - logger.info(f"Converting board to dictionary...") - data = { - 'id': board[0], - 'name': board[1], - 'description': board[3], - 'created_at': board[4] - } - logger.info(f"Board converted to dictionary.") - return data - - - def get_board_posts(self, board_id, limit=10, offset=0): - logger.info(f"Getting posts for board {board_id}...") - - # Get posts for the board - self.cursor.execute(''' - SELECT * FROM posts WHERE board_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ? - ''', (board_id, limit, offset)) - posts = self.cursor.fetchall() - - if posts: - logger.info(f"Posts found for board {board_id}.") - return posts - logger.warning(f"No posts found for board {board_id}.") - return None - - - def get_all_boards_for_post(self): - logger.info(f"Getting all boards...") - - # Get all boards - self.cursor.execute(''' - SELECT * FROM boards - ''') - boards = self.cursor.fetchall() - - if boards: - logger.info(f"Boards found.") - # Convert boards to dictionary - boards = [{"id": board[0], "name": board[1]} for board in boards] - boards.sort(key=lambda x: x['name']) - return boards - logger.warning(f"No boards found.") - return None - - def delete_post(self, post_id): - logger.info(f"Deleting post {post_id}...") - - # Delete post - self.cursor.execute(''' - DELETE FROM posts WHERE id = ? - ''', (post_id,)) - - self.connection.commit() - logger.info(f"Post {post_id} deleted.") - return True \ No newline at end of file + log.info("First time run completed") \ No newline at end of file diff --git a/src/html/board.html b/src/html/board.html index 10d04a3..3d796bb 100644 --- a/src/html/board.html +++ b/src/html/board.html @@ -1,44 +1,42 @@ -{% extends "base.html" %} +{% extends "templates/base.html" %} -{% block content %} -
{{ board.description }}
+ {% if board.owner_id == session.user_id %} +{{ post.short_content }}
-Here are the boards available on this site. To create a new board, click here.
{{ board.description }}
-{{error}}
+{% endblock %} \ No newline at end of file diff --git a/src/html/index.html b/src/html/index.html index 087d3e7..6cab558 100644 --- a/src/html/index.html +++ b/src/html/index.html @@ -1,33 +1,24 @@ -{% extends "base.html" %} +{% extends "templates/base.html" %} -{% block content %} +{% block head %} + +{% endblock %} + +{% block main_content %}Prismic is a simple textboard
+Prismic is a simple, fast, and secure way to share your thoughts with the world. Join us today and start sharing your ideas!
{{ post.short_content }}
-Hello {{session.name}}! You are logged in.
+ {% else %} + + {% endif %} +Register if you don't have an account.
{{ error }}
+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/src/html/newpost.html b/src/html/newpost.html deleted file mode 100644 index 1237dc3..0000000 --- a/src/html/newpost.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base.html" %} - -{% block content %} - - {% if error %} -{{ error }}
- {% endif %} -{% endblock %} \ No newline at end of file diff --git a/src/html/post.html b/src/html/post.html index 3ff5ef6..fda3e64 100644 --- a/src/html/post.html +++ b/src/html/post.html @@ -1,61 +1,75 @@ -{% extends "base.html" %} +{% extends "templates/base.html" %} -{% block content %} -{{ post.content }}
- {% if session.name == "SYSTEM" %} -{{post.content}}
+{{ reply.short_content }}
-{{ error }}
+ {% if total_pages > 0 %} + {% endif %} {% endblock %} \ No newline at end of file diff --git a/src/html/posts.html b/src/html/posts.html index 2cb362f..badfcec 100644 --- a/src/html/posts.html +++ b/src/html/posts.html @@ -1,42 +1,25 @@ -{% extends "base.html" %} +{% extends "templates/base.html" %} -{% block content %} +{% block main_content %}Here are the latest posts from all boards.
{{ post.short_content }}
-{{ error }}
+ {% endif %} + {% if success %} +{{ success }}
+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/src/html/base.html b/src/html/templates/base.html similarity index 61% rename from src/html/base.html rename to src/html/templates/base.html index 98bf18c..eb7c2db 100644 --- a/src/html/base.html +++ b/src/html/templates/base.html @@ -4,24 +4,24 @@