From f3e0c860771a8706d96da61207a24f8d978d718a Mon Sep 17 00:00:00 2001 From: zPlus Date: Mon, 18 Mar 2019 11:44:42 +0100 Subject: [PATCH] Move from mysql to sqlite. Fix #54 Fix #89 --- .gitignore | 1 + README.md | 26 +- database.schema.sql | 103 +++ freepost/__init__.py | 20 +- freepost/database.py | 1183 +++++++++++++++++---------------- freepost/templates/posts.html | 2 +- freepost_freepost.sql | 272 -------- requirements.txt | 3 +- settings.yaml | 10 +- 9 files changed, 741 insertions(+), 879 deletions(-) create mode 100644 database.schema.sql delete mode 100644 freepost_freepost.sql diff --git a/.gitignore b/.gitignore index 4b9d04d4..a3430b34 100755 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ venv/ *.pyc /freepost/static/css/ +/database.sqlite /settings.production.yaml diff --git a/README.md b/README.md index 1db55cf9..3ec4d7f1 100755 --- a/README.md +++ b/README.md @@ -8,13 +8,12 @@ users can read and comment. ## Setup Python3 virtual environment - mkdir venv - virtualenv -p python3 venv - -> alternative: python3 -m venv venv + cd freepost-directory + python3 -m venv venv (if this doesn't work, try `virtualenv -p python3 venv`) source venv/bin/activate pip3 install -r requirements.txt -## Run dev server +## Run test server source venv/bin/activate python3 -m bottle --debug --reload --bind 127.0.0.1:8000 freepost @@ -25,10 +24,21 @@ Build CSS files stylus --watch --compress --disable-cache --out freepost/static/css/ freepost/static/stylus/freepost.styl -## Deploy - -- Rename `.htaccess.wsgi` or `.htaccess.cgi` to `.htaccess` -- Change settings in `settings.yaml` +# Deployment + +- Build CSS stylesheets (see `Development` above) +- Copy all files to your `public_html` folder +- Make sure `settings.yaml` has restricted permissions, for instance `0600` +- If the SQLite database is located in the same HTML folder, make sure this too has + restricted access +- Rename `.htaccess.wsgi` or `.htaccess.cgi` to `.htaccess` (if you use CGI or WSGI) +- Change settings in `settings.yaml` if needed +- Create Python virtual environment + For tuxfamily only: run `newgrp freepost` before creating the virtenv, for quota reasons +- Create a new empty SQLite database: `cat database.schema.sql | sqlite3 database.sqlite` + +Everything should be setup and working. Make sure your CGI or WSGI server is +configured correctly. ## License diff --git a/database.schema.sql b/database.schema.sql new file mode 100644 index 00000000..24dbe9db --- /dev/null +++ b/database.schema.sql @@ -0,0 +1,103 @@ +BEGIN TRANSACTION; +CREATE TABLE `vote_post` ( + `vote` integer NOT NULL +, `datetime` datetime NOT NULL +, `postId` integer NOT NULL +, `userId` integer NOT NULL +, PRIMARY KEY (`postId`,`userId`) +, CONSTRAINT `FK_EDE89DBC64B64DCC` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) +, CONSTRAINT `FK_EDE89DBCE094D20D` FOREIGN KEY (`postId`) REFERENCES `post` (`id`) +); +CREATE TABLE `vote_comment` ( + `vote` integer NOT NULL +, `datetime` datetime NOT NULL +, `commentId` integer NOT NULL +, `userId` integer NOT NULL +, PRIMARY KEY (`commentId`,`userId`) +, CONSTRAINT `FK_1FC60DF464B64DCC` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) +, CONSTRAINT `FK_1FC60DF46690C3F5` FOREIGN KEY (`commentId`) REFERENCES `comment` (`id`) +); +CREATE TABLE `user` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT +, `hashId` varchar(32) NOT NULL +, `email` varchar(255) DEFAULT NULL +, `email_notifications` integer NOT NULL DEFAULT '1' +, `isActive` integer NOT NULL +, `password` varchar(255) NOT NULL +, `passwordResetToken` varchar(255) DEFAULT NULL +, `passwordResetTokenExpire` datetime DEFAULT NULL +, `registered` datetime NOT NULL +, `salt` varchar(255) NOT NULL +, `username` varchar(255) NOT NULL +, `about` varchar(10000) NOT NULL DEFAULT '' +, `session` varchar(255) DEFAULT NULL +, `preferred_feed` varchar(64) NOT NULL DEFAULT 'hot' +, UNIQUE (`hashId`) +, UNIQUE (`username`) +, UNIQUE (`email`) +, UNIQUE (`passwordResetToken`) +, UNIQUE (`session`) +); +CREATE TABLE `topic` ( + `post_id` integer NOT NULL +, `name` varchar(45) NOT NULL +, PRIMARY KEY (`post_id`,`name`) +, CONSTRAINT `fk_topic_1` FOREIGN KEY (`post_id`) REFERENCES `post` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE TABLE `remember_me` ( + `token` char(128) NOT NULL +, `userId` integer NOT NULL +, `expires` datetime NOT NULL +, PRIMARY KEY (`token`) +, CONSTRAINT `FK_userId` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) +); +CREATE TABLE `post` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT +, `hashId` varchar(32) NOT NULL +, `created` datetime NOT NULL +, `dateCreated` date NOT NULL +, `title` varchar(255) NOT NULL +, `link` text COLLATE BINARY +, `text` longtext NOT NULL +, `vote` integer NOT NULL +, `commentsCount` integer NOT NULL +, `userId` integer DEFAULT NULL +, UNIQUE (`hashId`) +, CONSTRAINT `FK_5A8A6C8D64B64DCC` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) +); +CREATE TABLE `comment` ( + `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT +, `hashId` varchar(32) NOT NULL +, `created` datetime NOT NULL +, `dateCreated` date NOT NULL +, `read` integer NOT NULL +, `text` longtext NOT NULL +, `vote` integer NOT NULL +, `parentId` integer DEFAULT NULL +, `parentUserId` integer DEFAULT NULL +, `postId` integer DEFAULT NULL +, `userId` integer DEFAULT NULL +, UNIQUE (`hashId`) +, CONSTRAINT `FK_9474526C10EE4CEE` FOREIGN KEY (`parentId`) REFERENCES `comment` (`id`) +, CONSTRAINT `FK_9474526C251330C5` FOREIGN KEY (`parentUserId`) REFERENCES `user` (`id`) +, CONSTRAINT `FK_9474526C64B64DCC` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) +, CONSTRAINT `FK_9474526CE094D20D` FOREIGN KEY (`postId`) REFERENCES `post` (`id`) +); +CREATE INDEX "idx_vote_post_IDX_EDE89DBCE094D20D" ON "vote_post" (`postId`); +CREATE INDEX "idx_vote_post_IDX_EDE89DBC64B64DCC" ON "vote_post" (`userId`); +CREATE INDEX "idx_vote_comment_IDX_1FC60DF46690C3F5" ON "vote_comment" (`commentId`); +CREATE INDEX "idx_vote_comment_IDX_1FC60DF464B64DCC" ON "vote_comment" (`userId`); +CREATE INDEX "idx_remember_me_userId" ON "remember_me" (`userId`); +CREATE INDEX "idx_post_vote" ON "post" (`vote`); +CREATE INDEX "idx_post_dateCreated" ON "post" (`dateCreated`); +CREATE INDEX "idx_post_created" ON "post" (`created`); +CREATE INDEX "idx_post_IDX_5A8A6C8D64B64DCC" ON "post" (`userId`); +CREATE INDEX "idx_comment_vote" ON "comment" (`vote`); +CREATE INDEX "idx_comment_isRead" ON "comment" (`read`); +CREATE INDEX "idx_comment_dateCreated" ON "comment" (`dateCreated`); +CREATE INDEX "idx_comment_created" ON "comment" (`created`); +CREATE INDEX "idx_comment_IDX_9474526CE094D20D" ON "comment" (`postId`); +CREATE INDEX "idx_comment_IDX_9474526C64B64DCC" ON "comment" (`userId`); +CREATE INDEX "idx_comment_IDX_9474526C251330C5" ON "comment" (`parentUserId`); +CREATE INDEX "idx_comment_IDX_9474526C10EE4CEE" ON "comment" (`parentId`); +COMMIT; diff --git a/freepost/__init__.py b/freepost/__init__.py index c23627d7..81044573 100755 --- a/freepost/__init__.py +++ b/freepost/__init__.py @@ -3,7 +3,9 @@ import bleach import bottle import configparser +import dateutil.parser import functools +import hashlib import importlib import json import markdown @@ -30,10 +32,10 @@ template = functools.partial ( template, template_settings = { 'filters': { - 'ago': lambda date: timeago.format (date), - 'datetime': lambda date: date.strftime ('%b %-d, %Y - %H:%M%p%z%Z'), + 'ago': lambda date: timeago.format(date), + 'datetime': lambda date: date,# date.strftime ('%b %-d, %Y - %H:%M%p%z%Z'), # TODO this should be renamed. It's only a way to pretty print dates - 'title': lambda date: date.strftime ('%b %-d, %Y - %H:%M%z%Z'), + 'title': lambda date: dateutil.parser.parse(date).strftime('%b %-d, %Y - %H:%M%z%Z'), # Convert markdown to plain text 'md2txt': lambda text: bleach.clean (markdown.markdown (text), tags=[], attributes={}, styles=[], strip=True), @@ -179,7 +181,7 @@ def login (): """ The login page. """ - + return template ('login.html') @post ('/login') @@ -212,7 +214,7 @@ def login_check (): # Start new session session.start (user['id'], remember) - + print(user) # Redirect logged in user to preferred feed if user['preferred_feed'] == 'new': redirect (application.get_url ('homepage') + '?sort=new') @@ -241,7 +243,9 @@ def register_new_account (): # Normalize username username = username.strip () - if len (username) == 0 or database.username_exists (username): + # Check if username already exists. + # Use case-insensitive match to prevent two similar usernames. + if len (username) == 0 or database.username_exists (username, case_sensitive=False): return template ( 'register.html', flash='Name taken, please choose another.') @@ -486,12 +490,12 @@ def post_thread (hash_id): if 0 not in comments_tree: comments_tree[0] = [] - comments_tree[0].append (comment) + comments_tree[0].append(dict(comment)) else: if comment['parentId'] not in comments_tree: comments_tree[comment['parentId']] = [] - comments_tree[comment['parentId']].append (comment) + comments_tree[comment['parentId']].append(dict(comment)) # Build ordered list of comments (recourse tree) def children (parent_id = 0, depth = 0): diff --git a/freepost/database.py b/freepost/database.py index b8537258..d1c0ef2c 100644 --- a/freepost/database.py +++ b/freepost/database.py @@ -1,79 +1,101 @@ -import MySQLdb +import hashlib import re +import sqlite3 from freepost import random, settings -db = MySQLdb.connect ( - host = settings['mysql']['host'], - port = settings['mysql']['port'], - db = settings['mysql']['schema'], - charset = settings['mysql']['charset'], - user = settings['mysql']['username'], - passwd = settings['mysql']['password'], - autocommit = True) +db = sqlite3.connect(settings['sqlite']['database']) -# Since the "db" object is reused for the entire life of website, -# this ping should avoid MySQL timing out if no requests are sent in a while. -db.ping (True) +# Returns SQLite rows as dictionaries instead of tuples. +# https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.row_factory +db.row_factory = sqlite3.Row + +# A custom function to compute SHA-512 because it's not built into SQLite +db.create_function('SHA512', 1, lambda text: + None if text is None else hashlib.sha512(text.encode('UTF-8')).hexdigest()) + +# The REGEXP operator is a special syntax for the regexp() user function. No +# regexp() user function is defined by default and so use of the REGEXP operator +# will normally result in an error message. If an application-defined SQL +# function named "regexp" is added at run-time, then the "X REGEXP Y" operator +# will be implemented as a call to "regexp(Y,X)". +db.create_function('REGEXP', 2, lambda pattern, string: + re.match(pattern, string, flags=re.IGNORECASE) is not None) # Store a new session_id for a user that has logged in # The session token is stored in the user cookies during login, here # we store the hash value of that token. -def new_session (user_id, session_token): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - UPDATE user - SET session = SHA2(%(session)s, 512) - WHERE id = %(user)s - """, - { - 'user': user_id, - 'session': session_token - } - ) +def new_session(user_id, session_token): + with db: + db.execute( + """ + UPDATE user + SET session = SHA512(:session) + WHERE id = :user + """, + { + 'user': user_id, + 'session': session_token + } + ) # Delete user session token on logout def delete_session (user_id): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - UPDATE user - SET session = NULL - WHERE id = %(user)s - """, - { - 'user': user_id - } - ) + with db: + db.execute ( + """ + UPDATE user + SET session = NULL + WHERE id = :user + """, + { + 'user': user_id + } + ) # Check user login credentials # # @return None if bad credentials, otherwise return the user def check_user_credentials (username, password): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT * - FROM user - WHERE - username = %(username)s AND - password = SHA2(CONCAT(%(password)s, salt), 512) AND - isActive = 1 - """, - { - 'username': username, - 'password': password - } - ) - - return cursor.fetchone () + with db: + cursor = db.execute ( + """ + SELECT * + FROM user + WHERE username = :username + AND password = SHA512(:password || salt) + AND isActive = 1 + """, + { + 'username': username, + 'password': password + } + ) + + return cursor.fetchone () # Check if username exists -def username_exists (username): - return get_user_by_username (username) is not None +def username_exists (username, case_sensitive = True): + if not username: + return None + + if case_sensitive: + where = 'WHERE username = :username' + else: + where = 'WHERE LOWER(username) = LOWER(:username)' + + with db: + cursor = db.execute( + """ + SELECT * + FROM user + """ + + where, + { + 'username': username + } + ) + + return cursor.fetchone() is not None # Create new user account def new_user (username, password): @@ -84,20 +106,19 @@ def new_user (username, password): salt = random.ascii_string (16) # Add user to database - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - INSERT INTO user (hashId, isActive, password, registered, salt, username) - VALUES (%(hash_id)s, 1, SHA2(CONCAT(%(password)s, %(salt)s), 512), NOW(), %(salt)s, %(username)s) - """, - { - 'hash_id': hash_id, - 'password': password, - 'salt': salt, - 'username': username - } - ) + with db: + db.execute ( + """ + INSERT INTO user (hashId, isActive, password, registered, salt, username) + VALUES (:hash_id, 1, SHA512(:password || :salt), DATE(), :salt, :username) + """, + { + 'hash_id': hash_id, + 'password': password, + 'salt': salt, + 'username': username + } + ) # Check if session token exists def is_valid_session (token): @@ -105,57 +126,54 @@ def is_valid_session (token): # Return the number of unread replies def count_unread_messages (user_id): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT COUNT(1) as new_messages - FROM comment - WHERE parentUserId = %(user)s AND userId != %(user)s AND `read` = 0 - """, - { - 'user': user_id - } - ) + with db: + cursor = db.execute ( + """ + SELECT COUNT(1) AS new_messages + FROM comment + WHERE parentUserId = :user AND userId != :user AND `read` = 0 + """, + { + 'user': user_id + } + ) - return cursor.fetchone ()['new_messages'] + return cursor.fetchone ()['new_messages'] # Retrieve a user def get_user_by_username (username): if not username: return None - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT * - FROM user - WHERE username = %(username)s - """, - { - 'username': username - } - ) - - return cursor.fetchone () + with db: + cursor = db.execute( + """ + SELECT * + FROM user + WHERE username = :username + """, + { + 'username': username + } + ) + + return cursor.fetchone() # Retrieve a user from a session cookie -def get_user_by_session_token (session_token): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT * - FROM user - WHERE session = SHA2(%(session)s, 512) - """, - { - 'session': session_token - } - ) +def get_user_by_session_token(session_token): + with db: + cursor = db.execute( + """ + SELECT * + FROM user + WHERE session = SHA512(:session) + """, + { + 'session': session_token + } + ) - return cursor.fetchone () + return cursor.fetchone() # Get posts by date (for homepage) def get_posts (page = 0, session_user_id = None, sort = 'hot', topic = None): @@ -165,179 +183,170 @@ def get_posts (page = 0, session_user_id = None, sort = 'hot', topic = None): sort = 'ORDER BY P.dateCreated DESC, P.vote DESC, P.commentsCount DESC' if topic: - topic_name = 'WHERE T.name = %(topic)s' + topic_name = 'WHERE T.name = :topic' else: topic_name = '' - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT - P.*, - U.username, - V.vote AS user_vote, - GROUP_CONCAT(DISTINCT T.name ORDER BY T.name SEPARATOR " ") AS topics - FROM post AS P - JOIN user AS U ON P.userId = U.id - LEFT JOIN vote_post as V ON V.postId = P.id AND V.userId = %(user)s - LEFT JOIN topic as T ON T.post_id = P.id - {topic} - GROUP BY P.id - {order} - LIMIT %(limit)s - OFFSET %(offset)s - """.format (topic=topic_name, order=sort), - { - 'user': session_user_id, - 'limit': settings['defaults']['items_per_page'], - 'offset': page * settings['defaults']['items_per_page'], - 'topic': topic - } - ) - - return cursor.fetchall () + with db: + cursor = db.execute ( + """ + SELECT P.*, + U.username, + V.vote AS user_vote, + GROUP_CONCAT(T.name, " ") AS topics + FROM post AS P + JOIN user AS U ON P.userId = U.id + LEFT JOIN vote_post as V ON V.postId = P.id AND V.userId = :user + LEFT JOIN topic as T ON T.post_id = P.id + {topic} + GROUP BY P.id + {order} + LIMIT :limit + OFFSET :offset + """.format (topic=topic_name, order=sort), + { + 'user': session_user_id, + 'limit': settings['defaults']['items_per_page'], + 'offset': page * settings['defaults']['items_per_page'], + 'topic': topic + } + ) + + return cursor.fetchall () # Retrieve user's own posts def get_user_posts (user_id): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT * - FROM post - WHERE userId = %(user)s - ORDER BY created DESC - LIMIT 50 - """, - { - 'user': user_id - } - ) - - return cursor.fetchall () + with db: + cursor = db.execute ( + """ + SELECT * + FROM post + WHERE userId = :user + ORDER BY created DESC + LIMIT 50 + """, + { + 'user': user_id + } + ) + + return cursor.fetchall() # Retrieve user's own comments def get_user_comments (user_id): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT - C.*, - P.title AS postTitle, - P.hashId AS postHashId - FROM comment AS C - JOIN post AS P ON P.id = C.postId - WHERE C.userId = %(user)s - ORDER BY C.created DESC - LIMIT 50 - """, - { - 'user': user_id - } - ) - - return cursor.fetchall () + with db: + cursor = db.execute ( + """ + SELECT C.*, + P.title AS postTitle, + P.hashId AS postHashId + FROM comment AS C + JOIN post AS P ON P.id = C.postId + WHERE C.userId = :user + ORDER BY C.created DESC + LIMIT 50 + """, + { + 'user': user_id + } + ) + + return cursor.fetchall() # Retrieve user's own replies to other people def get_user_replies (user_id): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT - C.*, - P.title AS postTitle, - P.hashId AS postHashId, - U.username AS username - FROM comment AS C - JOIN post AS P ON P.id = C.postId - JOIN user AS U ON U.id = C.userId - WHERE C.parentUserId = %(user)s AND C.userId != %(user)s - ORDER BY C.created DESC - LIMIT 50 - """, - { - 'user': user_id - } - ) - - return cursor.fetchall () + with db: + cursor = db.execute( + """ + SELECT C.*, + P.title AS postTitle, + P.hashId AS postHashId, + U.username AS username + FROM comment AS C + JOIN post AS P ON P.id = C.postId + JOIN user AS U ON U.id = C.userId + WHERE C.parentUserId = :user AND C.userId != :user + ORDER BY C.created DESC + LIMIT 50 + """, + { + 'user': user_id + } + ) + + return cursor.fetchall() # Update user information def update_user (user_id, about, email, email_notifications, preferred_feed): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - # Update user info, but not email address - cursor.execute ( - """ - UPDATE user - SET about = %(about)s, - email_notifications = %(notifications)s, - preferred_feed = %(preferred_feed)s - WHERE id = %(user)s - """, - { - 'about': about, - 'notifications': email_notifications, - 'user': user_id, - 'preferred_feed': preferred_feed - } - ) + with db: + # Update user info, but not email address + db.execute( + """ + UPDATE user + SET about = :about, + email_notifications = :notifications, + preferred_feed = :preferred_feed + WHERE id = :user + """, + { + 'about': about, + 'notifications': email_notifications, + 'user': user_id, + 'preferred_feed': preferred_feed + } + ) - # Update email address - # IGNORE update if the email address is already specified. This is - # necessary to avoid an "duplicate key" exception when updating value. - cursor.execute ( - """ - UPDATE IGNORE user - SET email = %(email)s - WHERE id = %(user)s - """, - { - 'email': email, - 'user': user_id - } - ) + # Update email address. Convert all addresses to LOWER() case. This + # prevents two users from using the same address with different case. + # IGNORE update if the email address is already specified. This is + # necessary to avoid an "duplicate key" exception when updating value. + db.execute ( + """ + UPDATE OR IGNORE user + SET email = LOWER(:email) + WHERE id = :user + """, + { + 'email': email, + 'user': user_id + } + ) # Set user replies as read def set_replies_as_read (user_id): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - UPDATE comment - SET `read` = 1 - WHERE parentUserId = %(user)s AND `read` = 0 - """, - { - 'user': user_id - } - ) + with db: + db.execute( + """ + UPDATE comment + SET `read` = 1 + WHERE parentUserId = :user AND `read` = 0 + """, + { + 'user': user_id + } + ) # Submit a new post/link def new_post (title, link, text, user_id): # Create a hash_id for the new post hash_id = random.alphanumeric_string (10) - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - INSERT INTO post (hashId, created, dateCreated, title, - link, text, vote, commentsCount, userId) - VALUES (%(hash_id)s, NOW(), CURDATE(), %(title)s, %(link)s, - %(text)s, 0, 0, %(user)s) - """, - { - 'hash_id': hash_id, - 'title': title, - 'link': link, - 'text': text, - 'user': user_id - } - ) + with db: + db.execute( + """ + INSERT INTO post (hashId, created, dateCreated, title, + link, text, vote, commentsCount, userId) + VALUES (:hash_id, DATETIME(), DATE(), :title, :link, + :text, 0, 0, :user) + """, + { + 'hash_id': hash_id, + 'title': title, + 'link': link, + 'text': text, + 'user': user_id + } + ) return hash_id @@ -358,106 +367,110 @@ def replace_post_topics (post_id, topics = ''): # Remove extra topics if the list is too long topics = topics[:settings['defaults']['topics_per_post']] - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - # First we delete the existing topics - cursor.execute ( - """ - DELETE FROM topic WHERE post_id = %(post)s - """, - { - 'post': post_id - } - ) - - # Now insert the new topics - cursor.executemany ( - """ - INSERT INTO topic (post_id, name) - VALUES (%s, %s) - """, - [ (post_id, topic) for topic in topics ] - ) + with db: + # First we delete the existing topics + db.execute ( + """ + DELETE + FROM topic + WHERE post_id = :post + """, + { + 'post': post_id + } + ) + + # Now insert the new topics. + # IGNORE duplicates that trigger UNIQUE constraint. + db.executemany ( + """ + INSERT OR IGNORE INTO topic (post_id, name) + VALUES (?, ?) + """, + [ (post_id, topic) for topic in topics ] + ) # Retrieve a post def get_post (hash, session_user_id = None): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT P.*, U.username, V.vote AS user_vote - FROM post AS P - JOIN user AS U ON P.userId = U.id - LEFT JOIN vote_post as V ON V.postId = P.id AND V.userId = %(user)s - WHERE P.hashId = %(post)s - """, - { - 'user': session_user_id, - 'post': hash - } - ) - - return cursor.fetchone () + with db: + cursor = db.execute ( + """ + SELECT P.*, + U.username, + V.vote AS user_vote + FROM post AS P + JOIN user AS U ON P.userId = U.id + LEFT JOIN vote_post as V ON V.postId = P.id AND V.userId = :user + WHERE P.hashId = :post + """, + { + 'user': session_user_id, + 'post': hash + } + ) + + return cursor.fetchone () # Update a post def update_post (title, link, text, post_hash_id, user_id): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - UPDATE post - SET title = %(title)s, - link = %(link)s, - text = %(text)s - WHERE hashId = %(hash_id)s AND userId = %(user)s - """, - { - 'title': title, - 'link': link, - 'text': text, - 'hash_id': post_hash_id, - 'user': user_id - } - ) + with db: + db.execute ( + """ + UPDATE post + SET title = :title, + link = :link, + text = :text + WHERE hashId = :hash_id + AND userId = :user + """, + { + 'title': title, + 'link': link, + 'text': text, + 'hash_id': post_hash_id, + 'user': user_id + } + ) # Retrieve all comments for a specific post def get_post_comments (post_id, session_user_id = None): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT C.*, U.username, V.vote AS user_vote - FROM comment AS C - JOIN user AS U ON C.userId = U.id - LEFT JOIN vote_comment as V ON V.commentId = C.id AND V.userId = %(user)s - WHERE C.postId = %(post)s - ORDER BY C.vote DESC, C.created ASC - """, - { - 'user': session_user_id, - 'post': post_id - } - ) - - return cursor.fetchall () + with db: + cursor = db.execute ( + """ + SELECT C.*, + U.username, + V.vote AS user_vote + FROM comment AS C + JOIN user AS U ON C.userId = U.id + LEFT JOIN vote_comment as V ON V.commentId = C.id AND V.userId = :user + WHERE C.postId = :post + ORDER BY C.vote DESC, + C.created ASC + """, + { + 'user': session_user_id, + 'post': post_id + } + ) + + return cursor.fetchall () # Retrieve all topics for a specific post def get_post_topics (post_id): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT T.name - FROM topic AS T - WHERE T.post_id = %(post)s - ORDER BY T.name ASC - """, - { - 'post': post_id - } - ) - - return cursor.fetchall () + with db: + cursor = db.execute ( + """ + SELECT T.name + FROM topic AS T + WHERE T.post_id = :post + ORDER BY T.name ASC + """, + { + 'post': post_id + } + ) + + return cursor.fetchall () # Submit a new comment to a post def new_comment (comment_text, post_hash_id, user_id, parent_user_id = None, parent_comment_id = None): @@ -467,170 +480,182 @@ def new_comment (comment_text, post_hash_id, user_id, parent_user_id = None, par # Retrieve post post = get_post (post_hash_id) - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - INSERT INTO comment (hashId, created, dateCreated, `read`, text, vote, - parentId, parentUserId, postId, userId) - VALUES (%(hash_id)s, NOW(), CURDATE(), 0, %(text)s, 0, %(parent_id)s, - %(parent_user_id)s, %(post_id)s, %(user)s) - """, - { - 'hash_id': hash_id, - 'text': comment_text, - 'parent_id': parent_comment_id, - 'parent_user_id': parent_user_id, - 'post_id': post['id'], - 'user': user_id - } - ) - - # Increase comments count for post - cursor.execute ( - """ - UPDATE post - SET commentsCount = commentsCount + 1 - WHERE id = %(post)s - """, - { - 'post': post['id'] - } - ) + with db: + db.execute ( + """ + INSERT INTO comment (hashId, created, dateCreated, `read`, text, vote, + parentId, parentUserId, postId, userId) + VALUES (:hash_id, DATETIME(), DATE(), 0, :text, 0, :parent_id, + :parent_user_id, :post_id, :user) + """, + { + 'hash_id': hash_id, + 'text': comment_text, + 'parent_id': parent_comment_id, + 'parent_user_id': parent_user_id, + 'post_id': post['id'], + 'user': user_id + } + ) + + # Increase comments count for post + db.execute ( + """ + UPDATE post + SET commentsCount = commentsCount + 1 + WHERE id = :post + """, + { + 'post': post['id'] + } + ) return hash_id # Retrieve a single comment def get_comment (hash_id, session_user_id = None): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT - C.*, - P.hashId AS postHashId, - P.title AS postTitle, - U.username, - V.vote AS user_vote - FROM comment AS C - JOIN user AS U ON C.userId = U.id - JOIN post AS P ON P.id = C.postId - LEFT JOIN vote_comment as V ON V.commentId = C.id AND V.userId = %(user)s - WHERE C.hashId = %(comment)s - """, - { - 'user': session_user_id, - 'comment': hash_id - } - ) - - return cursor.fetchone () + with db: + cursor = db.execute( + """ + SELECT C.*, + P.hashId AS postHashId, + P.title AS postTitle, + U.username, + V.vote AS user_vote + FROM comment AS C + JOIN user AS U ON C.userId = U.id + JOIN post AS P ON P.id = C.postId + LEFT JOIN vote_comment as V ON V.commentId = C.id AND V.userId = :user + WHERE C.hashId = :comment + """, + { + 'user': session_user_id, + 'comment': hash_id + } + ) + + return cursor.fetchone() # Retrieve last N newest comments def get_latest_comments (): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT - C.*, - P.hashId AS postHashId, - P.title AS postTitle, - U.username - FROM comment AS C - JOIN user AS U ON C.userId = U.id - JOIN post AS P ON P.id = C.postId - ORDER BY C.id DESC - LIMIT 50 - """, - { - } - ) - - return cursor.fetchall () + with db: + cursor = db.execute ( + """ + SELECT C.*, + P.hashId AS postHashId, + P.title AS postTitle, + U.username + FROM comment AS C + JOIN user AS U ON C.userId = U.id + JOIN post AS P ON P.id = C.postId + ORDER BY C.id DESC + LIMIT 50 + """, + { + } + ) + + return cursor.fetchall () # Update a comment def update_comment (text, comment_hash_id, user_id): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - UPDATE comment - SET text = %(text)s - WHERE hashId = %(comment)s AND userId = %(user)s - """, - { - 'text': text, - 'comment': comment_hash_id, - 'user': user_id - } - ) + with db: + db.execute ( + """ + UPDATE comment + SET text = :text + WHERE hashId = :comment AND userId = :user + """, + { + 'text': text, + 'comment': comment_hash_id, + 'user': user_id + } + ) # Add or update vote to a post def vote_post (post_id, user_id, vote): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - # Insert or update the user vote - cursor.execute ( - """ - INSERT INTO vote_post (vote, datetime, postId, userId) - VALUES (%(vote)s, NOW(), %(post)s, %(user)s) - ON DUPLICATE KEY UPDATE - vote = vote + %(vote)s, - datetime = NOW() - """, - { - 'vote': vote, - 'post': post_id, - 'user': user_id - } - ) - - # Update vote counter for post - cursor.execute ( - """ - UPDATE post - SET vote = vote + %(vote)s - WHERE id = %(post)s - """, - { - 'vote': vote, - 'post': post_id - } - ) + with db: + # Create a new vote for this post, if one doesn't already exist + db.execute( + """ + INSERT OR IGNORE INTO vote_post (vote, datetime, postId, userId) + VALUES (0, DATETIME(), :post, :user) + """, + { + 'post': post_id, + 'user': user_id + } + ) + + # Update user vote (+1 or -1) + db.execute( + """ + UPDATE vote_post + SET vote = vote + :vote + WHERE postId = :post AND userId = :user + """, + { + 'vote': vote, + 'post': post_id, + 'user': user_id + } + ) + + # Update post's total + db.execute ( + """ + UPDATE post + SET vote = vote + :vote + WHERE id = :post + """, + { + 'vote': vote, + 'post': post_id + } + ) # Add or update vote to a comment def vote_comment (comment_id, user_id, vote): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - # Insert or update the user vote - cursor.execute ( - """ - INSERT INTO vote_comment (vote, datetime, commentId, userId) - VALUES (%(vote)s, NOW(), %(comment)s, %(user)s) - ON DUPLICATE KEY UPDATE - vote = vote + %(vote)s, - datetime = NOW() - """, - { - 'vote': vote, - 'comment': comment_id, - 'user': user_id - } - ) - - # Update vote counter for comment - cursor.execute ( - """ - UPDATE comment - SET vote = vote + %(vote)s - WHERE id = %(comment)s - """, - { - 'vote': vote, - 'comment': comment_id - } - ) + with db: + # Create a new vote for this post, if one doesn't already exist + db.execute ( + """ + INSERT INTO vote_comment (vote, datetime, commentId, userId) + VALUES (0, DATETIME(), :comment, :user) + """, + { + 'comment': comment_id, + 'user': user_id + } + ) + + # Update user vote (+1 or -1) + db.execute ( + """ + UPDATE vote_comment + SET vote = vote + :vote + WHERE commentId = :comment AND userId = :user + """, + { + 'vote': vote, + 'comment': comment_id, + 'user': user_id + } + ) + + # Update comment's total + db.execute ( + """ + UPDATE comment + SET vote = vote + :vote + WHERE id = :comment + """, + { + 'vote': vote, + 'comment': comment_id + } + ) # Search posts def search (query, sort='newest', page=0): @@ -643,114 +668,110 @@ def search (query, sort='newest', page=0): if len (query) == 0: return [] - cursor = db.cursor (MySQLdb.cursors.DictCursor) - if sort == 'newest': sort = 'P.created DESC' if sort == 'points': sort = 'P.vote DESC' - cursor.execute ( - """ - SELECT P.*, U.username - FROM post AS P - JOIN user AS U ON P.userId = U.id - WHERE P.title REGEXP %(query)s - ORDER BY {sort} - LIMIT %(limit)s - OFFSET %(offset)s - """.format (sort=sort), - { - 'query': query, - 'sort': sort, - 'limit': settings['defaults']['search_results_per_page'], - 'offset': page * settings['defaults']['search_results_per_page'] - } - ) - - return cursor.fetchall () + with db: + cursor = db.execute ( + """ + SELECT P.*, + U.username + FROM post AS P + JOIN user AS U ON P.userId = U.id + WHERE P.title REGEXP :query + ORDER BY {sort} + LIMIT :limit + OFFSET :offset + """.format (sort=sort), + { + 'query': query, + 'sort': sort, + 'limit': settings['defaults']['search_results_per_page'], + 'offset': page * settings['defaults']['search_results_per_page'] + } + ) + + return cursor.fetchall () # Set reset token for user email def set_password_reset_token (user_id = None, token = None): if not user_id or not token: return - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - UPDATE user - SET passwordResetToken = SHA2(%(token)s, 512), - passwordResetTokenExpire = NOW() + INTERVAL 1 HOUR - WHERE id = %(user)s - """, - { - 'user': user_id, - 'token': token - } - ) + with db: + db.execute ( + """ + UPDATE user + SET passwordResetToken = SHA512(:token), + passwordResetTokenExpire = NOW() + INTERVAL 1 HOUR + WHERE id = :user + """, + { + 'user': user_id, + 'token': token + } + ) # Delete the password reset token for a user def delete_password_reset_token (user_id = None): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - UPDATE user - SET passwordResetToken = NULL, - passwordResetTokenExpire = NULL - WHERE id = %(user)s - """, - { - 'user': user_id - } - ) + with db: + db.execute ( + """ + UPDATE user + SET passwordResetToken = NULL, + passwordResetTokenExpire = NULL + WHERE id = :user + """, + { + 'user': user_id + } + ) # Check if a reset token has expired. def is_password_reset_token_valid (user_id = None): - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - SELECT COUNT(1) AS valid - FROM user - WHERE id = %(user)s AND - passwordResetToken IS NOT NULL AND - passwordResetTokenExpire IS NOT NULL AND - passwordResetTokenExpire > NOW() - """, - { - 'user': user_id - } - ) - - return cursor.fetchone ()['valid'] == 1 + with db: + cursor = db.execute( + """ + SELECT COUNT(1) AS valid + FROM user + WHERE id = :user + AND passwordResetToken IS NOT NULL + AND passwordResetTokenExpire IS NOT NULL + AND passwordResetTokenExpire > DATE() + """, + { + 'user': user_id + } + ) + + return cursor.fetchone()['valid'] == 1 # Reset user password def reset_password (username = None, email = None, new_password = None, secret_token = None): if not new_password: return - cursor = db.cursor (MySQLdb.cursors.DictCursor) - - cursor.execute ( - """ - UPDATE user - SET password = SHA2(CONCAT(%(password)s, `salt`), 512), - passwordResetToken = NULL, - passwordResetTokenExpire = NULL - WHERE username = %(user)s AND - email = %(email)s AND - passwordResetToken = SHA2(%(token)s, 512) AND - passwordResetTokenExpire > NOW() - """, - { - 'password': new_password, - 'user': username, - 'email': email, - 'token': secret_token - } - ) + with db: + db.execute ( + """ + UPDATE user + SET password = SHA512(:password || `salt`), + passwordResetToken = NULL, + passwordResetTokenExpire = NULL + WHERE username = :user + AND email = :email + AND passwordResetToken = SHA512(:token) + AND passwordResetTokenExpire > DATE() + """, + { + 'password': new_password, + 'user': username, + 'email': email, + 'token': secret_token + } + ) diff --git a/freepost/templates/posts.html b/freepost/templates/posts.html index 58717453..25a33ab3 100644 --- a/freepost/templates/posts.html +++ b/freepost/templates/posts.html @@ -45,7 +45,7 @@ - diff --git a/freepost_freepost.sql b/freepost_freepost.sql deleted file mode 100644 index 13fcd266..00000000 --- a/freepost_freepost.sql +++ /dev/null @@ -1,272 +0,0 @@ --- phpMyAdmin SQL Dump --- version 4.3.9 --- http://www.phpmyadmin.net --- --- Host: sql --- Generation Time: Mar 02, 2019 at 10:25 AM --- Server version: 5.5.60-0+deb8u1 --- PHP Version: 5.6.38-0+deb8u1 - -SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; -SET AUTOCOMMIT = 0; -START TRANSACTION; -SET time_zone = "+00:00"; - - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; - --- --- Database: `freepost_freepost` --- -CREATE DATABASE IF NOT EXISTS `freepost_freepost` DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci; -USE `freepost_freepost`; - --- -------------------------------------------------------- - --- --- Table structure for table `comment` --- --- Creation: Jul 03, 2017 at 03:39 PM --- - -DROP TABLE IF EXISTS `comment`; -CREATE TABLE IF NOT EXISTS `comment` ( - `id` int(11) NOT NULL, - `hashId` varchar(32) COLLATE utf8_unicode_ci NOT NULL, - `created` datetime NOT NULL, - `dateCreated` date NOT NULL, - `read` tinyint(1) NOT NULL, - `text` longtext COLLATE utf8_unicode_ci NOT NULL, - `vote` int(11) NOT NULL, - `parentId` int(11) DEFAULT NULL, - `parentUserId` int(11) DEFAULT NULL, - `postId` int(11) DEFAULT NULL, - `userId` int(11) DEFAULT NULL -) ENGINE=InnoDB AUTO_INCREMENT=11235 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - --- -------------------------------------------------------- - --- --- Table structure for table `post` --- --- Creation: Nov 25, 2017 at 04:35 AM --- - -DROP TABLE IF EXISTS `post`; -CREATE TABLE IF NOT EXISTS `post` ( - `id` int(11) NOT NULL, - `hashId` varchar(32) COLLATE utf8_unicode_ci NOT NULL, - `created` datetime NOT NULL, - `dateCreated` date NOT NULL, - `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `link` text COLLATE utf8_unicode_ci, - `text` longtext COLLATE utf8_unicode_ci NOT NULL, - `vote` int(11) NOT NULL, - `commentsCount` int(11) NOT NULL, - `userId` int(11) DEFAULT NULL -) ENGINE=InnoDB AUTO_INCREMENT=13821 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - --- -------------------------------------------------------- - --- --- Table structure for table `remember_me` --- --- Creation: Jul 03, 2017 at 03:39 PM --- - -DROP TABLE IF EXISTS `remember_me`; -CREATE TABLE IF NOT EXISTS `remember_me` ( - `token` char(128) COLLATE utf8_unicode_ci NOT NULL, - `userId` int(10) NOT NULL, - `expires` datetime NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Used for user authentication (keep session alive for returning user)'; - --- -------------------------------------------------------- - --- --- Table structure for table `topic` --- --- Creation: Jul 17, 2018 at 05:50 AM --- - -DROP TABLE IF EXISTS `topic`; -CREATE TABLE IF NOT EXISTS `topic` ( - `post_id` int(11) NOT NULL, - `name` varchar(45) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Topic name' -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - --- -------------------------------------------------------- - --- --- Table structure for table `user` --- --- Creation: Jan 11, 2019 at 08:29 PM --- - -DROP TABLE IF EXISTS `user`; -CREATE TABLE IF NOT EXISTS `user` ( - `id` int(11) NOT NULL, - `hashId` varchar(32) COLLATE utf8_unicode_ci NOT NULL, - `email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `email_notifications` tinyint(1) unsigned NOT NULL DEFAULT '1', - `isActive` tinyint(1) NOT NULL, - `password` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `passwordResetToken` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `passwordResetTokenExpire` datetime DEFAULT NULL, - `registered` datetime NOT NULL, - `salt` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `username` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `about` varchar(10000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '', - `session` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Stores the hash value of the session_id random string that was assigned to a user (and saved to a cookie) when logging in.', - `preferred_feed` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'hot' -) ENGINE=InnoDB AUTO_INCREMENT=341 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - --- -------------------------------------------------------- - --- --- Table structure for table `vote_comment` --- --- Creation: Jul 03, 2017 at 03:39 PM --- - -DROP TABLE IF EXISTS `vote_comment`; -CREATE TABLE IF NOT EXISTS `vote_comment` ( - `vote` smallint(6) NOT NULL, - `datetime` datetime NOT NULL, - `commentId` int(11) NOT NULL, - `userId` int(11) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - --- -------------------------------------------------------- - --- --- Table structure for table `vote_post` --- --- Creation: Jul 03, 2017 at 03:39 PM --- - -DROP TABLE IF EXISTS `vote_post`; -CREATE TABLE IF NOT EXISTS `vote_post` ( - `vote` smallint(6) NOT NULL, - `datetime` datetime NOT NULL, - `postId` int(11) NOT NULL, - `userId` int(11) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - --- --- Indexes for dumped tables --- - --- --- Indexes for table `comment` --- -ALTER TABLE `comment` - ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `hashId` (`hashId`), ADD KEY `IDX_9474526C10EE4CEE` (`parentId`), ADD KEY `IDX_9474526C251330C5` (`parentUserId`), ADD KEY `IDX_9474526CE094D20D` (`postId`), ADD KEY `IDX_9474526C64B64DCC` (`userId`), ADD KEY `created` (`created`), ADD KEY `dateCreated` (`dateCreated`), ADD KEY `isRead` (`read`), ADD KEY `vote` (`vote`); - --- --- Indexes for table `post` --- -ALTER TABLE `post` - ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `hashId` (`hashId`), ADD KEY `IDX_5A8A6C8D64B64DCC` (`userId`), ADD KEY `created` (`created`), ADD KEY `dateCreated` (`dateCreated`), ADD KEY `vote` (`vote`); - --- --- Indexes for table `remember_me` --- -ALTER TABLE `remember_me` - ADD PRIMARY KEY (`token`), ADD KEY `userId` (`userId`); - --- --- Indexes for table `topic` --- -ALTER TABLE `topic` - ADD PRIMARY KEY (`post_id`,`name`); - --- --- Indexes for table `user` --- -ALTER TABLE `user` - ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `hashId` (`hashId`), ADD UNIQUE KEY `username` (`username`), ADD UNIQUE KEY `email` (`email`), ADD UNIQUE KEY `passwordResetCode` (`passwordResetToken`), ADD UNIQUE KEY `session` (`session`); - --- --- Indexes for table `vote_comment` --- -ALTER TABLE `vote_comment` - ADD PRIMARY KEY (`commentId`,`userId`), ADD KEY `IDX_1FC60DF46690C3F5` (`commentId`), ADD KEY `IDX_1FC60DF464B64DCC` (`userId`); - --- --- Indexes for table `vote_post` --- -ALTER TABLE `vote_post` - ADD PRIMARY KEY (`postId`,`userId`), ADD KEY `IDX_EDE89DBCE094D20D` (`postId`), ADD KEY `IDX_EDE89DBC64B64DCC` (`userId`); - --- --- AUTO_INCREMENT for dumped tables --- - --- --- AUTO_INCREMENT for table `comment` --- -ALTER TABLE `comment` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=11235; --- --- AUTO_INCREMENT for table `post` --- -ALTER TABLE `post` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=13821; --- --- AUTO_INCREMENT for table `user` --- -ALTER TABLE `user` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=341; --- --- Constraints for dumped tables --- - --- --- Constraints for table `comment` --- -ALTER TABLE `comment` -ADD CONSTRAINT `FK_9474526C10EE4CEE` FOREIGN KEY (`parentId`) REFERENCES `comment` (`id`), -ADD CONSTRAINT `FK_9474526C251330C5` FOREIGN KEY (`parentUserId`) REFERENCES `user` (`id`), -ADD CONSTRAINT `FK_9474526C64B64DCC` FOREIGN KEY (`userId`) REFERENCES `user` (`id`), -ADD CONSTRAINT `FK_9474526CE094D20D` FOREIGN KEY (`postId`) REFERENCES `post` (`id`); - --- --- Constraints for table `post` --- -ALTER TABLE `post` -ADD CONSTRAINT `FK_5A8A6C8D64B64DCC` FOREIGN KEY (`userId`) REFERENCES `user` (`id`); - --- --- Constraints for table `remember_me` --- -ALTER TABLE `remember_me` -ADD CONSTRAINT `FK_userId` FOREIGN KEY (`userId`) REFERENCES `user` (`id`); - --- --- Constraints for table `topic` --- -ALTER TABLE `topic` -ADD CONSTRAINT `fk_topic_1` FOREIGN KEY (`post_id`) REFERENCES `post` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION; - --- --- Constraints for table `vote_comment` --- -ALTER TABLE `vote_comment` -ADD CONSTRAINT `FK_1FC60DF464B64DCC` FOREIGN KEY (`userId`) REFERENCES `user` (`id`), -ADD CONSTRAINT `FK_1FC60DF46690C3F5` FOREIGN KEY (`commentId`) REFERENCES `comment` (`id`); - --- --- Constraints for table `vote_post` --- -ALTER TABLE `vote_post` -ADD CONSTRAINT `FK_EDE89DBC64B64DCC` FOREIGN KEY (`userId`) REFERENCES `user` (`id`), -ADD CONSTRAINT `FK_EDE89DBCE094D20D` FOREIGN KEY (`postId`) REFERENCES `post` (`id`); -COMMIT; - -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/requirements.txt b/requirements.txt index b0bd0ebb..d5656773 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,10 @@ bleach bottle +python-dateutil jinja2 markdown -mysqlclient pyld +pysqlite3 pyyaml requests timeago diff --git a/settings.yaml b/settings.yaml index b46a8ab1..7311c09f 100644 --- a/settings.yaml +++ b/settings.yaml @@ -12,14 +12,8 @@ cookies: # Used to verify that cookies haven't been tampered with. secret: "secret random string" -mysql: - host: localhost - port: 3306 - schema: freepost_freepost - # charset: utf8mb4 - charset: utf8 - username: freepost - password: freepost +sqlite: + database: ./database.sqlite # Emails are sent using the local sendmail MTA. sendmail: