Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .coverage
Binary file not shown.
Binary file added flaskr.db
Binary file not shown.
27 changes: 27 additions & 0 deletions project/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os
from pathlib import Path

basedir = Path(__file__).resolve().parent.parent

app = Flask(__name__)

# Basic Config
app.config['SECRET_KEY'] = 'change_me'
app.config['USERNAME'] = 'admin'
app.config['PASSWORD'] = 'admin'

# Database Config
DATABASE = "flaskr.db"
url = os.getenv("DATABASE_URL", f"sqlite:///{basedir / DATABASE}")
if url.startswith("postgres://"):
url = url.replace("postgres://", "postgresql://", 1)
app.config['SQLALCHEMY_DATABASE_URI'] = url
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# Initialize DB
db = SQLAlchemy(app)

# Register models
from project import models
131 changes: 79 additions & 52 deletions project/app.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,9 @@
import os
from functools import wraps
from pathlib import Path

from flask import (
Flask,
render_template,
request,
session,
flash,
redirect,
url_for,
abort,
jsonify,
render_template, request, session, flash,
redirect, url_for, abort, jsonify
)
from flask_sqlalchemy import SQLAlchemy


basedir = Path(__file__).resolve().parent

# configuration
DATABASE = "flaskr.db"
USERNAME = "admin"
PASSWORD = "admin"
SECRET_KEY = "change_me"
url = os.getenv("DATABASE_URL", f"sqlite:///{Path(basedir).joinpath(DATABASE)}")

if url.startswith("postgres://"):
url = url.replace("postgres://", "postgresql://", 1)

SQLALCHEMY_DATABASE_URI = url
SQLALCHEMY_TRACK_MODIFICATIONS = False


# create and initialize a new Flask app
app = Flask(__name__)
# load the config
app.config.from_object(__name__)
# init sqlalchemy
db = SQLAlchemy(app)

from functools import wraps
from project import app, db
from project import models


Expand All @@ -49,20 +14,17 @@ def decorated_function(*args, **kwargs):
flash("Please log in.")
return jsonify({"status": 0, "message": "Please log in."}), 401
return f(*args, **kwargs)

return decorated_function


@app.route("/")
def index():
"""Searches the database for entries, then displays them."""
entries = db.session.query(models.Post)
entries = db.session.query(models.Post).all()
return render_template("index.html", entries=entries)


@app.route("/add", methods=["POST"])
def add_entry():
"""Adds new post to the database."""
if not session.get("logged_in"):
abort(401)
new_entry = models.Post(request.form["title"], request.form["text"])
Expand All @@ -74,12 +36,11 @@ def add_entry():

@app.route("/login", methods=["GET", "POST"])
def login():
"""User login/authentication/session management."""
error = None
if request.method == "POST":
if request.form["username"] != app.config["USERNAME"]:
if request.form["username"] != "admin":
error = "Invalid username"
elif request.form["password"] != app.config["PASSWORD"]:
elif request.form["password"] != "admin":
error = "Invalid password"
else:
session["logged_in"] = True
Expand All @@ -90,7 +51,6 @@ def login():

@app.route("/logout")
def logout():
"""User logout/authentication/session management."""
session.pop("logged_in", None)
flash("You were logged out")
return redirect(url_for("index"))
Expand All @@ -99,11 +59,9 @@ def logout():
@app.route("/delete/<int:post_id>", methods=["GET"])
@login_required
def delete_entry(post_id):
"""Deletes post from database."""
result = {"status": 0, "message": "Error"}
try:
new_id = post_id
db.session.query(models.Post).filter_by(id=new_id).delete()
db.session.query(models.Post).filter_by(id=post_id).delete()
db.session.commit()
result = {"status": 1, "message": "Post Deleted"}
flash("The entry was deleted.")
Expand All @@ -117,9 +75,78 @@ def search():
query = request.args.get("query")
entries = db.session.query(models.Post)
if query:
return render_template("search.html", entries=entries, query=query)
entries = entries.filter(models.Post.title.contains(query))
return render_template("search.html", entries=entries.all(), query=query)
return render_template("search.html")


# === REST API for Notes ===

@app.route("/api/notes", methods=["GET"])
def get_notes():
notes = db.session.query(models.Note).all()
return jsonify([note.to_dict() for note in notes]), 200


@app.route("/api/notes/<int:note_id>", methods=["GET"])
def get_note(note_id):
note = db.session.get(models.Note, note_id)
if note:
return jsonify(note.to_dict()), 200
return jsonify({"error": "Note not found"}), 404


@app.route("/api/notes", methods=["POST"])
def create_note():
try:
data = request.get_json(force=True)
except Exception:
return jsonify({"error": "Invalid JSON"}), 400

if not data or "content" not in data:
return jsonify({"error": "Content is required"}), 400

note = models.Note(content=data["content"])
db.session.add(note)
db.session.commit()
return jsonify(note.to_dict()), 201


@app.route("/api/notes/<int:note_id>", methods=["PUT"])
def update_note(note_id):
note = db.session.get(models.Note, note_id)
if not note:
return jsonify({"error": "Note not found"}), 404

try:
data = request.get_json(force=True)
except Exception:
return jsonify({"error": "Invalid JSON"}), 400

if not data or "content" not in data:
return jsonify({"error": "Content is required"}), 400

note.content = data["content"]
db.session.commit()
return jsonify(note.to_dict()), 200


@app.route("/api/notes/<int:note_id>", methods=["DELETE"])
def delete_note(note_id):
note = db.session.get(models.Note, note_id)
if not note:
return jsonify({"error": "Note not found"}), 404
db.session.delete(note)
db.session.commit()
return jsonify({"message": "Note deleted"}), 200


# === Swagger UI Route ===

@app.route("/docs")
def swagger_ui():
return app.send_static_file("swagger-ui/index.html")


if __name__ == "__main__":
app.run()
app.run(debug=True)
5 changes: 5 additions & 0 deletions project/create_tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from app import app, db

with app.app_context():
db.create_all()
print("Tables created successfully.")
Binary file modified project/flaskr.db
Binary file not shown.
6 changes: 6 additions & 0 deletions project/init_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from project import create_app, db

app = create_app()

with app.app_context():
db.create_all()
15 changes: 12 additions & 3 deletions project/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from project.app import db

from project import db

class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
Expand All @@ -11,4 +10,14 @@ def __init__(self, title, text):
self.text = text

def __repr__(self):
return f"<title {self.title}>"
return f"<Post {self.title}>"

class Note(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String, nullable=False)

def __init__(self, content):
self.content = content

def to_dict(self):
return {"id": self.id, "content": self.content}
Binary file added project/static/swagger-uI/favicon-16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added project/static/swagger-uI/favicon-32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions project/static/swagger-uI/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}

*,
*:before,
*:after {
box-sizing: inherit;
}

body {
margin: 0;
background: #fafafa;
}
19 changes: 19 additions & 0 deletions project/static/swagger-uI/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="stylesheet" type="text/css" href="index.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
</head>

<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
</body>
</html>
79 changes: 79 additions & 0 deletions project/static/swagger-uI/oauth2-redirect.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;

if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1).replace('?', '&');
} else {
qp = location.search.substring(1);
}

arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};

isValid = qp.state === sentState;

if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
});
}

if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}

oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}

if (document.readyState !== 'loading') {
run();
} else {
document.addEventListener('DOMContentLoaded', function () {
run();
});
}
</script>
</body>
</html>
20 changes: 20 additions & 0 deletions project/static/swagger-uI/swagger-initializer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">

// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "/static/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});

//</editor-fold>
};
2 changes: 2 additions & 0 deletions project/static/swagger-uI/swagger-ui-bundle.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions project/static/swagger-uI/swagger-ui-bundle.js.map

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions project/static/swagger-uI/swagger-ui-es-bundle-core.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions project/static/swagger-uI/swagger-ui-es-bundle-core.js.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions project/static/swagger-uI/swagger-ui-es-bundle.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions project/static/swagger-uI/swagger-ui-es-bundle.js.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions project/static/swagger-uI/swagger-ui-standalone-preset.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions project/static/swagger-uI/swagger-ui.css

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions project/static/swagger-uI/swagger-ui.css.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions project/static/swagger-uI/swagger-ui.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions project/static/swagger-uI/swagger-ui.js.map

Large diffs are not rendered by default.

Loading