A atch/admin.py => atch/admin.py +40 -0
@@ 0,0 1,40 @@
+import functools
+from flask import Blueprint, flash, g, redirect, render_template, request, session, url_for, abort, jsonify
+from atch.db import get_db
+from werkzeug.security import check_password_hash, generate_password_hash
+
+bp = Blueprint('admin', __name__, url_prefix="/admin")
+
+
+@bp.route('/login', methods=('GET', 'POST'))
+def login():
+ if request.method == 'POST':
+ username = request.form['username']
+ password = request.form['password']
+ db = get_db()
+ error = None
+ user = db.execute(
+ 'SELECT * FROM admins WHERE username = ?', (username,)
+ ).fetchone()
+
+ if (user is None) or (not check_password_hash(user['password'], password)):
+ error = 'Incorrect username or password'
+
+ if error is None:
+ session.clear()
+ session['user_id'] = user['id']
+ return redirect(url_for('dashboard'))
+
+ flash(error)
+
+ return render_template('login.html')
+
+
+def login_required(view):
+ @functools.wraps(view)
+ def wrapped_view(**kwargs):
+ if g.user is None:
+ return redirect(url_for('auth.login'))
+
+ return view(**kwargs)
+ return wrapped_view
M atch/board.py => atch/board.py +11 -10
@@ 12,10 12,10 @@ def browse_board(uri):
if board_name is None:
abort(404)
threads = db.execute(
- 'SELECT threads.thread_id, threads.board, threads.bump_timestamp, threads.title, '
- 'posts.created, posts.name, posts.email, posts.body '
+ 'SELECT threads.thread_id, threads.board, threads.bump_timestamp, threads.title, threads.archived, '
+ 'threads.sticky, posts.created, posts.name, posts.email, posts.body, posts.tripcode '
'FROM threads JOIN posts ON posts.thread=threads.thread_id WHERE threads.board=? and posts.reply_id=0 '
- 'ORDER BY bump_timestamp DESC',
+ 'ORDER BY sticky DESC, bump_timestamp DESC',
(uri,)
).fetchall()
@@ 25,9 25,7 @@ def browse_board(uri):
@bp.route('/<string:uri>/new_thread', methods=['POST'])
def new_thread(uri):
if request.method == 'POST':
- if '◆' in request.form['name']:
- return render_template('post_error.html', why="The diamond symbol (◆) is not allowed in usernames.")
- name = process_name(request.form['name'])
+ name, tripcode = process_name(request.form['name'])
email = request.form['email']
title = request.form['title']
if request.form['body']:
@@ 42,8 40,8 @@ def new_thread(uri):
(thread_number, uri, title)
)
db.execute(
- 'INSERT INTO posts (thread, reply_id, name, email, body, board) VALUES (?, 0, ?, ?, ?, ?)',
- (thread_number, name, email, body, uri)
+ 'INSERT INTO posts (thread, reply_id, name, email, body, board, tripcode) VALUES (?, 0, ?, ?, ?, ?, ?)',
+ (thread_number, name, email, body, uri, tripcode)
)
db.commit()
return redirect(url_for("thread.view_thread", uri=uri, thread=thread_number)) # view specific post
@@ 64,5 62,8 @@ def process_name(name):
go = name.index('#')
trip = sha1(name[go:].encode('utf-8'))
name = name[:go]
- return name + "◆" + trip.hexdigest()[:10]
- return "Anonymous"
+ return name, trip.hexdigest()[:10]
+ else:
+ return name, None
+ else:
+ return "Anonymous", None
M atch/schema.sql => atch/schema.sql +16 -0
@@ 9,6 9,8 @@ CREATE TABLE IF NOT EXISTS threads (
board VARCHAR(7) NOT NULL,
bump_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
title VARCHAR(50),
+ archived INTEGER DEFAULT 0,
+ sticky INTEGER DEFAULT 0,
PRIMARY KEY (thread_id, board),
FOREIGN KEY (board) REFERENCES boards(uri)
@@ 20,9 22,23 @@ CREATE TABLE IF NOT EXISTS posts (
reply_id INTEGER NOT NULL,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
name VARCHAR(50) NOT NULL,
+ tripcode VARCHAR(10),
email VARCHAR(50),
body TEXT NOT NULL,
+ banned INTEGER DEFAULT 0,
+ deleted INTEGER DEFAULT 0,
PRIMARY KEY (thread, board, reply_id),
FOREIGN KEY (thread, board) REFERENCES threads(thread_id, board)
+);
+
+CREATE TABLE IF NOT EXISTS admins (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username VARCHAR(15) NOT NULL,
+ password_hash TEXT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS users (
+ ip_address TEXT PRIMARY KEY NOT NULL,
+ banned INTEGER DEFAULT 0
);=
\ No newline at end of file
M atch/static/css/style.css => atch/static/css/style.css +29 -0
@@ 10,6 10,11 @@ html {
.user {
color: darkgreen;
+ font-weight: bold;
+}
+
+.tripcode {
+ color: darkgreen;
}
.arrows {
@@ 39,4 44,28 @@ html {
padding-left: 15px;
padding-top: 10px;
padding-bottom: 10px;
+}
+
+.tag {
+ font-size: 7pt;
+ padding: 3px;
+ display: inline;
+ font-weight: bolder;
+ vertical-align: middle;
+ color: white;
+ text-transform: uppercase;
+ text-align: center;
+ border: 1px solid black;
+}
+
+.tag.archived {
+ background-color: royalblue;
+}
+
+.tag.banned {
+ background-color: darkred;
+}
+
+.tag.sticky {
+ background-color: rebeccapurple;
}=
\ No newline at end of file
M atch/static/js/threadMain.js => atch/static/js/threadMain.js +3 -4
@@ 8,10 8,9 @@ function parsePostBodies() {
function addReplyTag(tag) {
- let text = $('textarea#body')
- text.append(">>" + tag)
- text.focus()
- console.log("eeee")
+ let text = $('textarea#body');
+ text.append(">>" + tag + "\n");
+ text.focus();
}
M atch/templates/board.html => atch/templates/board.html +9 -0
@@ 33,6 33,12 @@
{% for thread in threads %}
<div class="post_container" id="t{{ thread["threads.thread_id"] }}">
<div class="post header">
+ {% if thread["sticky"] == 1 %}
+ <div class="tag sticky">STICKY</div>
+ {% endif %}
+ {% if thread["archived"] == 1 %}
+ <div class="tag archived">ARCH</div>
+ {% endif %}
<span class="thread_no">Thread No: {{ thread["thread_id"] }} ||</span>
<span class="title">{{ thread["title"] }}</span>
<span class="user">
@@ 42,6 48,9 @@
<a href="mailto:{{ thread["email"] }}">{{ thread["name"] }}</a>
{% endif %}
</span>
+ {% if thread["tripcode"] is not none %}
+ <span class="tripcode"> ◆ {{ thread["tripcode"] }}</span>
+ {% endif %}
<span class="date">{{ thread["created"] }}</span>
<span>
[<a href="{{ url_for("thread.view_thread", uri=uri, thread=thread["thread_id"]) }}">View</a>]
M atch/templates/thread.html => atch/templates/thread.html +16 -0
@@ 15,6 15,7 @@
{% endblock %}
{% block content %}
+ {% if thread_data["archived"] == 0 %}
Reply to this thread:
<form action="{{ url_for('thread.new_reply', uri=uri, thread=thread_data["thread_id"]) }}" name="new_post" method="post" enctype="multipart/form-data">
<table id="new_post">
@@ 35,6 36,9 @@
</tr>
</table>
</form>
+ {% else %}
+ <h1 style="color: navy;">This thread is archived; you may no longer reply.</h1>
+ {% endif %}
<br>
@@ 50,6 54,15 @@
</div>
<div class="post_container">
<div class="post header" id="{{ reply["reply_id"] }}">
+ {% if thread_data["sticky"] == 1 %}
+ <div class="tag sticky">STICKY</div>
+ {% endif %}
+ {% if thread_data["archived"] == 1 %}
+ <div class="tag archived">ARCH</div>
+ {% endif %}
+ {% if reply["banned"] == 1 %}
+ <div class="tag banned">BAN</div>
+ {% endif %}
<span class="user">
{% if reply["email"] == "" %}
{{ reply["name"] }}
@@ 57,6 70,9 @@
<a href="mailto:{{ reply["email"] }}">{{ reply["name"] }}</a>
{% endif %}
</span>
+ {% if reply["tripcode"] is not none %}
+ <span class="tripcode"> ◆ {{ reply["tripcode"] }}</span>
+ {% endif %}
<span class="date">{{ reply["created"] }}</span>
<span class="reply_bt"><a href="#" onclick="return addReplyTag({{ reply["reply_id"] }})">Reply</a></span>
</div>
M atch/thread.py => atch/thread.py +11 -8
@@ 8,7 8,9 @@ bp = Blueprint('thread', __name__)
@bp.route('/<string:uri>/<int:thread>')
def view_thread(uri, thread):
db = get_db()
- thread_data = db.execute('SELECT * FROM threads WHERE board=? and thread_id=?', (uri, thread)).fetchone()
+ thread_data = db.execute('SELECT * FROM threads WHERE board=? AND thread_id=?', (uri, thread)).fetchone()
+ if thread_data is None:
+ abort(404)
replies = db.execute('SELECT * FROM posts WHERE thread=? ORDER BY created', (thread,)).fetchall()
return render_template('thread.html', thread_data=thread_data, replies=replies, uri=uri,
@@ 18,19 20,20 @@ def view_thread(uri, thread):
@bp.route('/<string:uri>/<int:thread>/new_post', methods=['POST'])
def new_reply(uri, thread):
if request.method == 'POST':
- if '◆' in request.form['name']:
- return render_template('post_error.html', why="The diamond symbol (◆) is not allowed in usernames.")
- name = process_name(request.form['name'])
+ name, tripcode = process_name(request.form['name'])
email = request.form['email']
body = request.form['body']
db = get_db()
- reply_no = len(db.execute("SELECT * FROM posts WHERE thread=?", (thread,)).fetchall())
+ reply_no = len(db.execute("SELECT * FROM posts WHERE thread=? AND board=?", (thread, uri)).fetchall())
+ if reply_no == 1000:
+ db.execute('UPDATE threads SET archived = 1 WHERE board=? AND thread_id=?',
+ (uri, thread))
db.execute(
- 'INSERT INTO posts (thread, reply_id, name, email, body, board) VALUES (?, ?, ?, ?, ?, ?)',
- (thread, reply_no, name, email, body, uri)
+ 'INSERT INTO posts (thread, reply_id, name, email, body, board, tripcode) VALUES (?, ?, ?, ?, ?, ?, ?)',
+ (thread, reply_no, name, email, body, uri, tripcode)
)
if request.form["email"] != "sage":
- db.execute('UPDATE threads SET bump_timestamp = CURRENT_TIMESTAMP WHERE board=? and thread_id=?',
+ db.execute('UPDATE threads SET bump_timestamp = CURRENT_TIMESTAMP WHERE board=? AND thread_id=?',
(uri, thread))
db.commit()
return redirect(url_for('thread.view_thread', uri=uri, thread=thread)) # view specific post