summaryrefslogtreecommitdiffstats
path: root/flaskr
diff options
context:
space:
mode:
Diffstat (limited to 'flaskr')
-rw-r--r--flaskr/__init__.py44
-rw-r--r--flaskr/db.py49
-rw-r--r--flaskr/schema.sql17
-rw-r--r--flaskr/static/favicon.icobin1001 -> 0 bytes
-rw-r--r--flaskr/static/img/flask-powered.pngbin4890 -> 0 bytes
-rw-r--r--flaskr/static/style.css142
-rw-r--r--flaskr/templates/auth/login.html15
-rw-r--r--flaskr/templates/auth/register.html15
-rw-r--r--flaskr/templates/base.html25
-rw-r--r--flaskr/templates/blog/create.html15
-rw-r--r--flaskr/templates/blog/index.html34
-rw-r--r--flaskr/templates/blog/update.html20
-rw-r--r--flaskr/views/auth.py102
-rw-r--r--flaskr/views/blog.py104
14 files changed, 0 insertions, 582 deletions
diff --git a/flaskr/__init__.py b/flaskr/__init__.py
deleted file mode 100644
index 25c2682..0000000
--- a/flaskr/__init__.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import os
-
-from flask import Flask
-
-
-def create_app(test_config=None):
- # Create app object. Configuration files are relative to instance folder.
- app = Flask(__name__, instance_relative_config=True)
-
- # Config
- app.config.from_mapping(
- SECRET_KEY='dev',
- DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'),
- )
-
- if test_config is None:
- app.config.from_pyfile('config.py', silent=True)
- else:
- app.config.from_mapping(test_config)
-
- try:
- os.makedirs(app.instance_path)
- except OSError:
- pass
-
- # Routes
- @app.route('/hello')
- def hello():
- return 'Hello, World!'
-
- # Register functions and blueprints
- from . import db
- db.init_app(app)
-
- from .views import auth
- app.register_blueprint(auth.bp)
-
- from .views import blog
- app.register_blueprint(blog.bp)
-
- # Extra
- app.add_url_rule('/', endpoint='index')
-
- return app \ No newline at end of file
diff --git a/flaskr/db.py b/flaskr/db.py
deleted file mode 100644
index eb4e8b4..0000000
--- a/flaskr/db.py
+++ /dev/null
@@ -1,49 +0,0 @@
-import sqlite3
-
-import click
-from flask import current_app, g
-
-
-def get_db():
- """Returns database connection
-
- sqlite3: https://docs.python.org/3/library/sqlite3.html
- """
- if 'db' not in g:
- g.db = sqlite3.connect(
- current_app.config['DATABASE'],
- detect_types=sqlite3.PARSE_DECLTYPES
- )
- # Return rows that behave like dicts
- g.db.row_factory = sqlite3.Row
-
- return g.db
-
-
-def close_db(e=None):
- db = g.pop('db', None)
-
- if db:
- db.close()
-
-# CLI:
-# https://flask.palletsprojects.com/en/3.0.x/cli/
-
-def init_db():
- db = get_db()
-
- with current_app.open_resource('schema.sql') as f:
- db.executescript(f.read().decode('utf8'))
-
-
-def init_db_command():
- """Clear the existing data and create new tables."""
- init_db()
- click.echo('Initialized the database.')
-
-# Register function with application
-
-def init_app(app):
- app.teardown_appcontext(close_db) # callback after returning response
- app.cli.add_command(init_db_command) \ No newline at end of file
diff --git a/flaskr/schema.sql b/flaskr/schema.sql
deleted file mode 100644
index be76d7e..0000000
--- a/flaskr/schema.sql
+++ /dev/null
@@ -1,17 +0,0 @@
-DROP TABLE IF EXISTS user;
-DROP TABLE IF EXISTS post;
-
-CREATE TABLE user (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- username TEXT UNIQUE NOT NULL,
- password TEXT NOT NULL
-);
-
-CREATE TABLE post (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- author_id INTEGER NOT NULL,
- created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- title TEXT NOT NULL,
- body TEXT NOT NULL,
- FOREIGN KEY (author_id) REFERENCES user (id)
-);
diff --git a/flaskr/static/favicon.ico b/flaskr/static/favicon.ico
deleted file mode 100644
index aecf115..0000000
--- a/flaskr/static/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/flaskr/static/img/flask-powered.png b/flaskr/static/img/flask-powered.png
deleted file mode 100644
index 9d20f17..0000000
--- a/flaskr/static/img/flask-powered.png
+++ /dev/null
Binary files differ
diff --git a/flaskr/static/style.css b/flaskr/static/style.css
deleted file mode 100644
index 1a73cc5..0000000
--- a/flaskr/static/style.css
+++ /dev/null
@@ -1,142 +0,0 @@
-html {
- font-family: sans-serif;
- background: #eee;
- padding: 1rem;
-}
-
-body {
- max-width: 960px;
- margin: 0 auto;
- background: white;
-}
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
- font-family: serif;
- 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;
-}
-
-nav h1 {
- flex: auto;
- margin: 0;
-}
-
-nav h1 a {
- text-decoration: none;
- padding: 0.25rem 0.5rem;
-}
-
-nav ul {
- display: flex;
- list-style: none;
- margin: 0;
- padding: 0;
-}
-
-nav ul li a,
-nav ul li span,
-header .action {
- display: block;
- padding: 0.5rem;
-}
-
-.content {
- padding: 0 1rem 1rem;
-}
-
-.content > header {
- border-bottom: 1px solid lightgray;
- display: flex;
- align-items: flex-end;
-}
-
-.content > header h1 {
- flex: auto;
- margin: 1rem 0 0.25rem 0;
-}
-
-.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 {
- 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;
-}
-
-.content form {
- margin: 1em 0;
- display: flex;
- flex-direction: column;
-}
-
-.content label {
- font-weight: bold;
- margin-bottom: 0.5em;
-}
-
-.content input,
-.content textarea {
- margin-bottom: 1em;
-}
-
-.content textarea {
- min-height: 12em;
- resize: vertical;
-}
-
-input.danger {
- color: #cc2f2e;
-}
-
-input[type="submit"] {
- align-self: start;
- min-width: 10em;
-}
diff --git a/flaskr/templates/auth/login.html b/flaskr/templates/auth/login.html
deleted file mode 100644
index b7dd5dc..0000000
--- a/flaskr/templates/auth/login.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{% extends 'base.html' %}
-
-{% block header %}
- <h1>{% block title %}Log In{% endblock %}</h1>
-{% endblock %}
-
-{% block content %}
- <form method="post">
- <label for="username">Username</label>
- <input name="username" id="username" required>
- <label for="password">Password</label>
- <input type="password" name="password" id="password" required>
- <input type="submit" value="Log In">
- </form>
-{% endblock %} \ No newline at end of file
diff --git a/flaskr/templates/auth/register.html b/flaskr/templates/auth/register.html
deleted file mode 100644
index a3c73cc..0000000
--- a/flaskr/templates/auth/register.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{% extends 'base.html' %}
-
-{% block header %}
- <h1>{% block title %}Register{% endblock %}</h1>
-{% endblock %}
-
-{% block content %}
- <form method="post">
- <label for="username">Username</label>
- <input name="username" id="username" required>
- <label for="password">Password</label>
- <input type="password" name="password" id="password" required>
- <input type="submit" value="Register">
- </form>
-{% endblock %} \ No newline at end of file
diff --git a/flaskr/templates/base.html b/flaskr/templates/base.html
deleted file mode 100644
index 6ea4864..0000000
--- a/flaskr/templates/base.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!doctype html>
-<title>{% block title %}{% endblock %} - Flaskr</title>
-<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
-<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
-<nav>
- <h1><a href="{{ url_for('index') }}">Flaskr</a></h1>
- <ul>
- {% if g.user %}
- <li><span>{{ g.user['username'] }}</span>
- <li><a href="{{ url_for('auth.logout') }}">Log Out</a>
- {% else %}
- <li><a href="{{ url_for('auth.register') }}">Register</a>
- <li><a href="{{ url_for('auth.login') }}">Log In</a>
- {% endif %}
- </ul>
-</nav>
-<section class="content">
- <header>
- {% block header %}{% endblock %}
- </header>
- {% for message in get_flashed_messages() %}
- <div class="flash">{{ message }}</div>
- {% endfor %}
- {% block content %}{% endblock %}
-</section> \ No newline at end of file
diff --git a/flaskr/templates/blog/create.html b/flaskr/templates/blog/create.html
deleted file mode 100644
index 88e31e4..0000000
--- a/flaskr/templates/blog/create.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{% extends 'base.html' %}
-
-{% block header %}
- <h1>{% block title %}New Post{% endblock %}</h1>
-{% endblock %}
-
-{% block content %}
- <form method="post">
- <label for="title">Title</label>
- <input name="title" id="title" value="{{ request.form['title'] }}" required>
- <label for="body">Body</label>
- <textarea name="body" id="body">{{ request.form['body'] }}</textarea>
- <input type="submit" value="Save">
- </form>
-{% endblock %}
diff --git a/flaskr/templates/blog/index.html b/flaskr/templates/blog/index.html
deleted file mode 100644
index 0fb2e80..0000000
--- a/flaskr/templates/blog/index.html
+++ /dev/null
@@ -1,34 +0,0 @@
-{% extends 'base.html' %}
-
-
-{% block header %}
- <h1>{% block title %}Posts{% endblock %}</h1>
- {% if g.user %}
- <a class="action" href="{{ url_for('blog.create') }}">New</a>
- {% endif %}
-{% endblock %}
-
-{% block content %}
- {% for post in posts %}
- <article class="post">
- <header>
- <div>
- {% if post['title'].__len__() > 80 %}
- <h1><abbr title="{{ post['title'] }}">{{ post['title'][:80] }}</abbr></h1>
- {% else %}
- <h1>{{ post['title'] }}</h1>
- {% endif %}
- <div class="about">by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}</div>
- </div>
- {% if g.user['id'] == post['author_id'] %}
- <a class="action" href="{{ url_for('blog.update', id=post['id']) }}">Edit</a>
- {% endif %}
- </header>
- <p class="body">{{ post['body'] }}</p>
- </article>
- {% if not loop.last %}
- {# Separate posts with a line #}
- <hr>
- {% endif %}
- {% endfor %}
-{% endblock %} \ No newline at end of file
diff --git a/flaskr/templates/blog/update.html b/flaskr/templates/blog/update.html
deleted file mode 100644
index 7420f57..0000000
--- a/flaskr/templates/blog/update.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends 'base.html' %}
-
-{% block header %}
- <h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1>
-{% endblock %}
-
-{% block content %}
- <form method="post">
- <label for="title">Title</label>
- <input name="title" id="title"
- value="{{ request.form['title'] or post['title'] }}" required>
- <label for="body">Body</label>
- <textarea name="body" id="body">{{ request.form['body'] or post['body'] }}</textarea>
- <input type="submit" value="Save">
- </form>
- <hr>
- <form action="{{ url_for('blog.delete', id=post['id']) }}" method="post">
- <input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');">
- </form>
-{% endblock %} \ No newline at end of file
diff --git a/flaskr/views/auth.py b/flaskr/views/auth.py
deleted file mode 100644
index 2dc32af..0000000
--- a/flaskr/views/auth.py
+++ /dev/null
@@ -1,102 +0,0 @@
-"""Authentication blueprint"""
-
-import functools
-
-from flask import (
- Blueprint, flash, g, redirect, render_template, request, session, url_for
-)
-from werkzeug.security import check_password_hash, generate_password_hash
-from flaskr.db import get_db
-
-bp = Blueprint('auth', __name__, url_prefix='/auth')
-
[email protected]('/register', methods=('GET', 'POST'))
-def register():
- if request.method == 'POST':
- # Form validation
- username = request.form['username']
- password = request.form['password']
- db = get_db()
- error = None
-
- if not username:
- error = 'Username is required.'
- elif not password:
- error = 'Password is required.'
-
- if error is None:
- try:
- # NOTE: don't use f-string here. Use `?` placeholders so that
- # database library can escape the fields
- # (otherwise SQL injection vulnerability)
- db.execute(
- "INSERT INTO user (username, password) VALUES (?, ?)",
- (username, generate_password_hash(password))
- )
- db.commit()
- except db.IntegrityError:
- error = f"User {username} is already registered."
- else:
- return redirect(url_for("auth.login"))
-
- flash(error)
-
- return render_template('auth/register.html')
-
-
[email protected]('/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 user WHERE username = ?', (username,)
- ).fetchone()
-
- if user is None:
- error = 'Incorrect username.'
- elif not check_password_hash(user['password'], password):
- error = 'Incorrect password.'
-
- if error is None:
- session.clear()
- session['user_id'] = user['id']
- return redirect(url_for('index'))
-
- flash(error)
-
- return render_template('auth/login.html')
-
-
-# runs before the view function, no matter what URL is requested
[email protected]_app_request
-def load_logged_in_user():
- user_id = session.get('user_id')
-
- if user_id is None:
- g.user = None
- else:
- g.user = get_db().execute(
- 'SELECT * FROM user WHERE id = ?', (user_id,)
- ).fetchone()
-
-
-def logout():
- session.clear()
- return redirect(url_for('index'))
-
-
-# Define decorator to require authentication in other views
-def login_required(view):
- """view is a function that returns HTML (and is part of a blueprint)"""
- @functools.wraps(view)
- def wrapped_view(**kwargs):
- if g.user is None:
- return redirect(url_for('auth.login'))
-
- return view(**kwargs)
-
- return wrapped_view \ No newline at end of file
diff --git a/flaskr/views/blog.py b/flaskr/views/blog.py
deleted file mode 100644
index 6f728ea..0000000
--- a/flaskr/views/blog.py
+++ /dev/null
@@ -1,104 +0,0 @@
-from flask import (
- Blueprint, flash, g, redirect, render_template, request, url_for
-)
-from werkzeug.exceptions import abort
-
-from flaskr.views.auth import login_required
-from flaskr.db import get_db
-
-# NOTE: no URL prefix
-bp = Blueprint('blog', __name__)
-
-
-def index():
- db = get_db()
- posts = db.execute(
- 'SELECT p.id, title, body, created, author_id, username'
- ' FROM post p JOIN user u ON p.author_id = u.id'
- ' ORDER BY created DESC'
- ).fetchall()
- return render_template('blog/index.html', posts=posts)
-
-
[email protected]('/create', methods=('GET', 'POST'))
-@login_required
-def create():
- if request.method == 'POST':
- title = request.form['title']
- body = request.form['body']
- error = None
-
- if not title:
- error = 'Title is required.'
-
- if error is not None:
- flash(error)
- else:
- db = get_db()
- db.execute(
- 'INSERT INTO post (title, body, author_id)'
- ' VALUES (?, ?, ?)',
- (title, body, g.user['id'])
- )
- db.commit()
- return redirect(url_for('blog.index'))
-
- return render_template('blog/create.html')
-
-
[email protected]('/<int:id>/update', methods=('GET', 'POST'))
-@login_required
-def update(id):
- post = get_post(id)
-
- if request.method == 'POST':
- title = request.form['title']
- body = request.form['body']
- error = None
-
- if not title:
- error = 'Title is required.'
-
- if error is not None:
- flash(error)
- else:
- db = get_db()
- db.execute(
- 'UPDATE post SET title = ?, body = ?'
- ' WHERE id = ?',
- (title, body, id)
- )
- db.commit()
- return redirect(url_for('blog.index'))
-
- return render_template('blog/update.html', post=post)
-
-
[email protected]('/<int:id>/delete', methods=('POST',))
-@login_required
-def delete(id):
- get_post(id)
- db = get_db()
- db.execute('DELETE FROM post WHERE id = ?', (id,))
- db.commit()
- return redirect(url_for('blog.index'))
-
-
-# Helper functions:
-
-def get_post(id, check_author=True):
- post = get_db().execute(
- 'SELECT p.id, title, body, created, author_id, username'
- ' FROM post p JOIN user u ON p.author_id = u.id'
- ' WHERE p.id = ?',
- (id,)
- ).fetchone()
-
- if post is None:
- abort(404, f"Post id {id} doesn't exist.")
-
- if check_author and post['author_id'] != g.user['id']:
- abort(403)
-
- return post