diff --git a/LICENSE b/LICENSE index e270514..50712b5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,28 +1,21 @@ -Copyright 2010 Pallets +MIT License -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: +Copyright (c) 2023 PV Tejas -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 7ea53b3..21131b6 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,20 @@ Use `gunicorn -w 2 'flaskr:create_app()'` to run app. Increase the number of wor ## Initializing database The first time you install the app in each environment, you need to initialize database using `flask --app flaskr init-db`. This only needs to be run once per environment, and **will delete existing database if run again**. -## Secret key +## Config file + +The config file is located at `/var/flaskr-instance/config.py` in production, and in `instance/` in development. The instance folder is created when the database is initialized. + +### Secret Key Every website with login needs a secret key to hash passwords with. -`/var/flaskr-instance/config.py` must contain a line `SECRET_KEY = '`, which must be randomly generated. +The config file must contain a line `SECRET_KEY = '`, which must be randomly generated. Suggested way of generating the key is `python -c 'import secrets; print(secrets.token_hex())'`, which returns a hexadecimal string with length 64. You may choose to randomly generate a key using a different method, but ensure that it is resistant to brute-force attacks. + +### Registration +Since this blog is meant to be updated by a limited number of people, registration is forbidden (403) by default. In addition, registration (/auth/register) and login (/auth/login) URLs are not hyperlinked anywhere. Registration can be opened by including `REGISTER = True`, and is closed by default. + +### Name +The default app name is "Flaskr", and it is visible on the header bar as well as the page title. Including a line `NAME = ''` in the config file replaces "Flaskr" with your chosen name. + +### Static folder +The default static folder is the one included in the repository. You can use a separate static folder to use your own assets by including a line `STATIC_FOLDER = ''` in the config file. diff --git a/flaskr/__init__.py b/flaskr/__init__.py index ed7d989..efa74c8 100644 --- a/flaskr/__init__.py +++ b/flaskr/__init__.py @@ -8,6 +8,8 @@ def create_app(test_config=None): app.config.from_mapping( SECRET_KEY='dev', DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'), + REGISTER=False, + NAME='Flaskr' ) app.wsgi_app = ProxyFix( @@ -21,6 +23,9 @@ def create_app(test_config=None): # load the test config if passed in app.config.from_mapping(test_config) + if app.config.get('STATIC_FOLDER') is not None: + app.static_folder = app.config.get('STATIC_FOLDER') + # ensure the instance folder exists try: os.makedirs(app.instance_path) diff --git a/flaskr/auth.py b/flaskr/auth.py index 5874e97..f76418e 100644 --- a/flaskr/auth.py +++ b/flaskr/auth.py @@ -1,7 +1,7 @@ import functools from flask import ( - Blueprint, flash, g, redirect, render_template, request, session, url_for + Blueprint, flash, g, redirect, render_template, request, session, url_for, current_app, abort ) from werkzeug.security import check_password_hash, generate_password_hash @@ -11,7 +11,8 @@ bp = Blueprint('auth', __name__, url_prefix='/auth') @bp.route('/register', methods=('GET', 'POST')) def register(): - return "Admin only", 403 + if not current_app.config['REGISTER']: + abort(403) if request.method == 'POST': username = request.form['username'] password = request.form['password'] diff --git a/flaskr/blog.py b/flaskr/blog.py index 523cfaf..ec37df5 100644 --- a/flaskr/blog.py +++ b/flaskr/blog.py @@ -7,7 +7,6 @@ from flaskr.auth import login_required from flaskr.db import get_db import markdown -import datetime bp = Blueprint('blog', __name__) @@ -21,8 +20,6 @@ def index(): ).fetchall() posts = [] for post in db_posts: - if post['created'] > datetime.datetime.utcnow(): - continue post = dict(post) post['body'] = markdown.markdown(post['body']) posts.append(post) @@ -69,6 +66,13 @@ def get_post(id, check_author=True): return post +@bp.route('/') +def post(id): + post = get_post(id, check_author=False) + post = dict(post) + post['body'] = markdown.markdown(post['body']) + return render_template('blog/post.html', post=post) + @bp.route('//update', methods=('GET', 'POST')) @login_required def update(id): @@ -77,23 +81,19 @@ def update(id): if request.method == 'POST': title = request.form['title'] body = request.form['body'] - created = request.form['created'] error = None if not title: error = 'Title is required.' - if not created: - error = "Created is required." - if error is not None: flash(error) else: db = get_db() db.execute( - 'UPDATE post SET title = ?, body = ?, created = ?' + 'UPDATE post SET title = ?, body = ?' ' WHERE id = ?', - (title, body, created, id) + (title, body, id) ) db.commit() return redirect(url_for('blog.index')) @@ -108,3 +108,7 @@ def delete(id): db.execute('DELETE FROM post WHERE id = ?',(id,)) db.commit() return redirect(url_for('blog.index')) + +@bp.route('/temp') +def temp(): + return render_template('temp.html') diff --git a/flaskr/static/favicon.png b/flaskr/static/favicon.png index b8cb77d..910818c 100644 Binary files a/flaskr/static/favicon.png and b/flaskr/static/favicon.png differ diff --git a/flaskr/static/style.css b/flaskr/static/style.css index 63f3f5b..a45a73b 100644 --- a/flaskr/static/style.css +++ b/flaskr/static/style.css @@ -1,11 +1,6 @@ html { font-family: sans-serif; background: #eee; padding: 1rem; } body { max-width: 960px; margin: 0 auto; background: white; } h1 { font-family: serif; color: #377ba8; margin: 1rem 0; } -h2 { color: #377ba8; margin: 1rem 0; } -h3 { color: #377ba8; margin: 1rem 0; } -h4 { color: #377ba8; margin: 1rem 0; } -h5 { color: #377ba8; margin: 1rem 0; } -h6 { color: #377ba8; margin: 1rem 0; } a { color: #377ba8; } hr { border: none; border-top: 1px solid lightgray; } nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; } @@ -19,12 +14,7 @@ nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; } .flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; } .post > header { display: flex; align-items: flex-end; font-size: 0.85em; } .post > header > div:first-of-type { flex: auto; } -.post > header h1 { margin-bottom: 0; } -.post > h2 { margin-bottom: 0; } -.post > h3 { margin-bottom: 0; } -.post > h4 { margin-bottom: 0; } -.post > h5 { margin-bottom: 0; } -.post > h6 { margin-bottom: 0; } +.post > header h1 { font-size: 1.5em; margin-bottom: 0; } .post .about { color: slategray; font-style: italic; } .post .body { white-space: pre-line; } .content:last-child { margin-bottom: 0; } diff --git a/flaskr/templates/base.html b/flaskr/templates/base.html index 6c1d788..b06559b 100644 --- a/flaskr/templates/base.html +++ b/flaskr/templates/base.html @@ -1,19 +1,19 @@ -{% block title %}{% endblock %} - blogsparkinfinite +{% block title %}{% endblock %} - {{ config['NAME'] }} +
+
+
{% block header %}{% endblock %} diff --git a/flaskr/templates/blog/index.html b/flaskr/templates/blog/index.html index ffd9fbc..d5a9167 100644 --- a/flaskr/templates/blog/index.html +++ b/flaskr/templates/blog/index.html @@ -12,14 +12,13 @@
-

{{ post['title'] }}

+

{{ post['title'] }}

by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}
{% if g.user['id'] == post['author_id'] %} - Edit - {% endif %} + Edit + {% endif %}
-

{{ post['body']|safe }}

{% if not loop.last %}
diff --git a/flaskr/templates/blog/post.html b/flaskr/templates/blog/post.html new file mode 100644 index 0000000..701a37f --- /dev/null +++ b/flaskr/templates/blog/post.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}{{ post['title']}}{% endblock %}

+{% endblock %} + +{% block content %} +
+
+
+

{{ post['title'] }}

+
by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}
+
+ {% if g.user['id'] == post['author_id'] %} + Edit + {% endif %} +
+

{{ post['body']|safe }}

+
+{% endblock %} diff --git a/flaskr/templates/blog/update.html b/flaskr/templates/blog/update.html index 6ba221f..af66322 100644 --- a/flaskr/templates/blog/update.html +++ b/flaskr/templates/blog/update.html @@ -11,9 +11,6 @@ value="{{ request.form['title'] or post['title'] }}" required> - -
diff --git a/requirements.txt b/requirements.txt index 055a19a..0e3f2aa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,11 +8,11 @@ dnspython==2.3.0 email-validator==2.0.0.post2 exceptiongroup==1.1.1 Flask==2.3.2 -# -e git+https://gitlab.com/pvtejas/based4tech.git@4be89bd767a7c5a84ab62fbf4ad924ae1af077f1#egg=flaskr +-e git+https://gitlab.com/pvtejas/based4tech.git@bda624e4dc1cf97ba2b5b3fcb66a5b28398307bc#egg=flaskr gunicorn==20.1.0 h11==0.14.0 httpcore==0.17.0 -httptools +httptools==0.5.0 httpx==0.24.0 idna==3.4 iniconfig==2.0.0 @@ -20,20 +20,20 @@ itsdangerous==2.1.2 Jinja2==3.1.2 Markdown==3.4.3 MarkupSafe==2.1.2 -orjson +orjson==3.8.11 packaging==23.1 pluggy==1.0.0 pyproject_hooks==1.0.0 pytest==7.3.2 python-dotenv==1.0.0 python-multipart==0.0.6 -# PyYAML==6.0 +PyYAML==6.0 sniffio==1.3.0 tomli==2.0.1 typing_extensions==4.5.0 -ujson +ujson==5.7.0 uvicorn==0.22.0 -uvloop +uvloop==0.17.0 watchfiles==0.19.0 websockets==11.0.3 Werkzeug==2.3.3