Skip to content
Closed
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
32 changes: 32 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,35 @@
2. **Access the application:**
Open your web browser and navigate to `http://localhost:3000` to access the to-do list application.

Code Review Comments
1. Move db into a separate package
2. Make the database initialization thread safe
3. Consider moving database out of flask. Instead use a db service class. The class will encapsulate all the database interactions, including inserts, gets, deletes, updates, etc.
4. DB migrations should not be tied to code. Maybe move it to github actions to continuosly update the db schema. Consdier using atlas.
5. Also consider adding linters to avoid conflicting versions to counter conflicting commits.
6. Break down HTTP verbs into different functions (Separation of concern)
7. Provide UTC timezone in timestamps.
8. Add service request response objects.
9. Request data validation can be attached to request objects. Ex: field is required, field has the right type. Use a library to do this. For example, see Marshmallow. (spend more time finding the right library)
10. Linters to make sure sql strings are not inline, making them sql injection safe.
11. Paginate get tasks
12. Feature addition requests: history trail, multi user support - see family todo lists, notifications, notifications for family members, admin control, invite users
13. Handling feature concurrency
- within a feature, engineers work together using some ticketing system for tasks
- before launching a feature, we feature flag it and rollout slowly
14. Write unit tests
15. Write integration / smoke tests
16. Add observability metrics
17. When handling task, handle concurrent writes to it. Right now, one request will overwrite another. Easiest to use OCC because we don't expect it to happen often.
18. Set debug false for prod
19. Create a config for database connection details and other things like setting log levels and debug for run.
20. Raw html is hard to scale. Move to a more robust framework, like react.
21. API documentation.
- use protos to communicate service contract
- openapi framework for service contract
- read swagger documentation
- generate client objects using contract yaml file
22. Move frontend out of flask so it is not tied to one backend server. This way, cluster of frontend can individually connect to different backend server cluster (using gateway/load balancer in between, helps with rate limit, auth, logging, metrics, etc) and we can scale things independently.
23. Add authorization support
24. Feature request - customer signup
25.
87 changes: 48 additions & 39 deletions python/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,91 @@
import datetime
from flask import Flask, request, jsonify, g

DATABASE = 'todos.db'
app = Flask(__name__, static_folder='public', static_url_path='')
DATABASE = "todos.db"
app = Flask(__name__, static_folder="public", static_url_path="")

@app.route('/')

@app.route("/")
def index():
return app.send_static_file('index.html')
return app.send_static_file("index.html")


def get_db():
db = getattr(g, '_database', None)
db = getattr(g, "_database", None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
db.row_factory = sqlite3.Row

db.execute("""
db.execute(
"""
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
completed INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL
)
""")
"""
)
db.commit()
return db

@app.route('/api/tasks', methods=['GET', 'POST'])

@app.route("/api/tasks", methods=["GET", "POST"])
def handle_tasks():
db = get_db()
if request.method == 'POST':
if request.method == "POST":
data = request.get_json()
if not data or 'title' not in data:
return jsonify({'error': 'Title is required'}), 400
if not data or "title" not in data:
return jsonify({"error": "Title is required"}), 400

title = data['title']
title = data["title"]
created_at = datetime.datetime.now().isoformat()

cursor = db.execute(
'INSERT INTO todos (title, created_at) VALUES (?, ?)',
(title, created_at)
"INSERT INTO todos (title, created_at) VALUES (?, ?)", (title, created_at)
)
db.commit()

return jsonify({
'id': cursor.lastrowid,
'title': title,
'completed': 0,
'created_at': created_at
}), 201

return (
jsonify(
{
"id": cursor.lastrowid,
"title": title,
"completed": 0,
"created_at": created_at,
}
),
201,
)

else:
cursor = db.execute('SELECT id, title, completed, created_at FROM todos ORDER BY created_at DESC')
cursor = db.execute(
"SELECT id, title, completed, created_at FROM todos ORDER BY created_at DESC"
)
tasks = [dict(row) for row in cursor.fetchall()]
return jsonify(tasks)

@app.route('/api/tasks/<int:task_id>', methods=['PATCH', 'DELETE'])

@app.route("/api/tasks/<int:task_id>", methods=["PATCH", "DELETE"])
def handle_task(task_id):
db = get_db()
if request.method == 'PATCH':
if request.method == "PATCH":
data = request.get_json()
if 'completed' not in data:
return jsonify({'error': 'Missing "completed" field'}), 400
if "completed" not in data:
return jsonify({"error": 'Missing "completed" field'}), 400

# Convert boolean to integer (1 for true, 0 for false)
completed = 1 if data['completed'] else 0

db.execute(
'UPDATE todos SET completed = ? WHERE id = ?',
(completed, task_id)
)
completed = 1 if data["completed"] else 0

db.execute("UPDATE todos SET completed = ? WHERE id = ?", (completed, task_id))
db.commit()
return jsonify({'message': 'Task updated successfully'})
return jsonify({"message": "Task updated successfully"})

elif request.method == 'DELETE':
db.execute('DELETE FROM todos WHERE id = ?', (task_id,))
elif request.method == "DELETE":
db.execute("DELETE FROM todos WHERE id = ?", (task_id,))
db.commit()
return jsonify({'message': 'Task deleted successfully'})
return jsonify({"message": "Task deleted successfully"})


if __name__ == '__main__':
app.run(debug=True, port=3000)
if __name__ == "__main__":
app.run(debug=True, port=3000)
2 changes: 1 addition & 1 deletion python/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
blinker==1.9.0
click==8.2.1
click==8.1.8
Flask==3.1.1
itsdangerous==2.2.0
Jinja2==3.1.6
Expand Down