Skip to content

Commit

Permalink
Merge pull request #4 from aaronhmiller/refactor
Browse files Browse the repository at this point in the history
Refactoring to remove DB dependency
  • Loading branch information
aaronhmiller authored Mar 19, 2024
2 parents 6a91285 + 86bfc63 commit 32c62b3
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 117 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
**/charts
**/docker-compose*
**/compose*
**/certs
**/Dockerfile*
**/node_modules
**/npm-debug.log
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
certs/
5 changes: 2 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
ARG PYTHON_VERSION=3.12.2
#FROM python:${PYTHON_VERSION}-slim as base
FROM python:3.12.2-slim as base
FROM python:${PYTHON_VERSION}-slim as base

# Prevents Python from writing pyc files.
ENV PYTHONDONTWRITEBYTECODE=1
Expand Down Expand Up @@ -43,4 +42,4 @@ EXPOSE 8000
env FLASK_APP=app.py

# Run the application.
CMD ["flask","run","--host","0.0.0.0","--port","8000"]
CMD ["flask","run","--host","0.0.0.0","--port","8000","--cert=/etc/certs/cloudflare-cert.pem","--key=/etc/certs/cloudflare-key.pem"]
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ It told me to install Flask (Python framework) and gave me app.py & index.html &
3. in your browser, open the page: [http://localhost:8000](http:localhost:8000)
4. you should see the inital data, and be able to create, update or delete any of the records

## Misconceptions
I thought I had it doing all updates via the REST API. However, I was just making calls to the DB, using the same logic the API used!

Now refactoring to use api.demojoyto.win.

Notes: had to add both AWS IPs to the Cloudflare WAF to prevent 403s.

## To-do

~~Add ability to CREATE new records. This will need POST implemented in the web page (there's a REST call ready to go already)~~
109 changes: 46 additions & 63 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
# Import the necessary modules
from flask import Flask, render_template, jsonify, request
import psycopg2
import requests

app = Flask(__name__)

# Replace the following with your PostgreSQL database details
# NOTE: use localhost if running outside docker compose
db_params = {
'dbname': 'api',
'user': 'ox',
'password': 'ox',
'host': 'postgres',
# 'host': 'localhost',
'port': '5432'
}

# Define a default route
@app.route('/')
def index():
Expand All @@ -35,84 +24,78 @@ def delete():

@app.route('/users', methods=['GET'])
def get_data():
# Connect to PostgreSQL database
connection = psycopg2.connect(**db_params)
cursor = connection.cursor()

cursor.execute('SELECT * FROM users ORDER BY id')
data = cursor.fetchall()
# URL of the RESTful API endpoint
api_url = 'https://api.demojoyto.win/users'

# Close database connection
cursor.close()
connection.close()
# Make a GET request to the RESTful API
response = requests.get(api_url)

# Convert data to JSON and return
return jsonify(data)
# Check if the request was successful
if response.status_code == 200:
return response.json()
else:
# Handle errors or unsuccessful responses
return jsonify({'error': 'Failed to fetch data from API'}), response.status_code

# Define the route for updating data
@app.route('/users/<int:data_id>', methods=['PUT'])
def update_data(data_id):
# Connect to PostgreSQL database
connection = psycopg2.connect(**db_params)
cursor = connection.cursor()
# URL of the RESTful API endpoint
api_url = f'https://api.demojoyto.win/users/{data_id}'

# Get data from the request
data = request.json
new_name = data.get('name')
new_email = data.get('email')

# Update data in the database
cursor.execute('UPDATE users SET name = %s, email = %s WHERE id = %s', (new_name, new_email, data_id))
connection.commit()
# Make a PUT request to the RESTful API
response = requests.put(api_url, json=data)

Check warning on line 50 in app.py

View check run for this annotation

OX Security / ox-security/scan

Server-side Request Forgery (SSRF) vulnerability due to unvalidated URI/URL in 'requests' module

To mitigate SSRF attacks, it is recommended to validate user input before using it to construct URLs or URIs. See https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html for more recommendations.

# Close database connection
cursor.close()
connection.close()
# Check if the request was successful
if response.status_code == 200:
# Return the updated user data
return response.text
else:
# Handle errors or unsuccessful responses
return jsonify({'error': 'Failed to update user data'}), response.status_code

# Return updated data
return jsonify({'message': 'Data updated successfully'})

# Define the route for creating data
@app.route('/users', methods=['POST'])
def create_data():
# Connect to PostgreSQL database
connection = psycopg2.connect(**db_params)
cursor = connection.cursor()

# Get data from the request
data = request.json
new_name = data.get('newName')
new_email = data.get('newEmail')
# URL of the RESTful API endpoint for creating a new user
api_url = 'https://api.demojoyto.win/users'

# Update data in the database
cursor.execute('INSERT INTO users (name, email) VALUES (%s, %s) RETURNING id', (new_name, new_email))
new_data_id = cursor.fetchone()[0]
connection.commit()
# Extract the data to be created from the incoming request
user_data = request.json

# Close database connection
cursor.close()
connection.close()
# Make a POST request to the RESTful API
response = requests.post(api_url, json=user_data)

# Return created data
return jsonify({'id': new_data_id, 'name': new_name, 'email': new_email})
# Check if the request was successful
if response.status_code == 201:
# Return the created user data response
return response.text
else:
# Handle errors or unsuccessful responses
return jsonify({'error': 'Failed to create new user'}), response.status_code

# Define the route for deleting data
@app.route('/users/<int:data_id>', methods=['DELETE'])
def delete_data(data_id):
# Connect to PostgreSQL database
connection = psycopg2.connect(**db_params)
cursor = connection.cursor()

# Delete data from the database
cursor.execute('DELETE FROM users WHERE id = %s', [data_id])
connection.commit()
# URL of the RESTful API endpoint for deleting a user
api_url = f'https://api.demojoyto.win/users/{data_id}'

# Close database connection
cursor.close()
connection.close()
# Make a DELETE request to the RESTful API
response = requests.delete(api_url)

Check warning on line 90 in app.py

View check run for this annotation

OX Security / ox-security/scan

Server-side Request Forgery (SSRF) vulnerability due to unvalidated URI/URL in 'requests' module

To mitigate SSRF attacks, it is recommended to validate user input before using it to construct URLs or URIs. See https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html for more recommendations.

# Return a response
return jsonify({'message': f'Data with ID {data_id} deleted successfully'})
# Check if the request was successful
if response.status_code == 200 or response.status_code == 204:
# Return a success message
return jsonify({'message': f'User with ID {data_id} deleted successfully'}), 200
else:
# Handle errors or unsuccessful responses
return jsonify({'error': 'Failed to delete user'}), response.status_code

if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=8080)
6 changes: 4 additions & 2 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
services:
webapp:
image: aaronhmiller/webapp
image: aaronhmiller/webapp:cloudflare
init: true
container_name: webapp
ports:
- 8000:8000
- 443:8000
volumes:
- ./certs/:/etc/certs/
postgres:
image: postgres
restart: always
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
flask
psycopg2-binary
requests
19 changes: 11 additions & 8 deletions templates/add.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ <h1>Data from API</h1>

<h2>Add Data</h2>
<form id="insert-form">
<label for="newName">Name:</label>
<input type="text" id="newName" name="newName">
<label for="newEmail">Email:</label>
<input type="text" id="newEmail" name="newEmail">
<label for="name">Name:</label>
<input type="text" id="name" name="name">
<label for="email">Email:</label>
<input type="text" id="email" name="email">
<input type="submit" value="Submit">
</form>

Expand Down Expand Up @@ -45,10 +45,11 @@ <h4><a href="delete">Delete Data</a></h4>
document.getElementById('insert-form').addEventListener('submit', function(event) {
event.preventDefault();

const formData = new FormData(event.target);
const form = event.target;
const formData = new FormData(form);
const formDataObject = {};
formData.forEach((value, key) => {
formDataObject[key] = value;
formData.forEach((key, value) => {
formDataObject[value] = key;
});

// Send POST request to insert new data
Expand All @@ -59,10 +60,12 @@ <h4><a href="delete">Delete Data</a></h4>
},
body: JSON.stringify(formDataObject)
})
.then(response => response.json())
.then(response => response.text())
.then(data => {
console.log('Data inserted:', data);
fetchData(); //refresh after adding
form.reset(); //reset form fields to default values
document.getElementById('name').focus(); //move focus to the first input field of the form
})
.catch(error => console.error('Error inserting data:', error));
});
Expand Down
67 changes: 28 additions & 39 deletions templates/delete.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,21 @@
<body>
<h1>Data from API</h1>
<div id="data-container"></div>

<h2>Delete Data</h2>
<div id="delete-buttons-container"></div>

<h4><a href="add">Add Data</a></h4>
<h4><a href="update">Update Data</a></h4>

<script>

// Function to populate form fields with data
function populateForm(data) {
document.getElementById('id').value = data.id;
document.getElementById('name').value = data.name;
document.getElementById('email').value = data.email;
}



document.addEventListener('DOMContentLoaded', function () {
// Function to create delete buttons
function createDeleteButton(data) {
const userId = data[0]; // Access the ID from the first element of the inner array
const userId = data.id; // Access the ID from the object
const deleteButton = document.createElement('button');
deleteButton.textContent = `Delete ${userId}`;
deleteButton.addEventListener('click', function () {
Expand All @@ -39,7 +32,7 @@ <h4><a href="update">Update Data</a></h4>
method: 'DELETE',
})
.then(response => {
if (!response.ok) {
if (!response.ok && response.status != 404) { //200 and 404 (no data) are expected
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
Expand All @@ -53,14 +46,12 @@ <h4><a href="update">Update Data</a></h4>
})
.catch(error => console.error('Error deleting data:', error));
});

return deleteButton;
}

// Fetch data from the API and update the UI
fetch('/users')
.then(response => {
if (!response.ok) {
if (!response.ok && response.status != 404) { //200 and 404 (no data) are expected
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
Expand All @@ -69,40 +60,38 @@ <h4><a href="update">Update Data</a></h4>
console.log('Fetched data:', data); // Log the fetched data
const deleteButtonsContainer = document.getElementById('delete-buttons-container');

data.forEach(item => {
const deleteButton = createDeleteButton(item);
deleteButtonsContainer.appendChild(deleteButton);
});
if (data.length) {
data.forEach(item => {
const deleteButton = createDeleteButton(item);
deleteButtonsContainer.appendChild(deleteButton);
});
} else {
console.log("No items left to display.");
}
})
.catch(error => console.error('Error fetching data:', error));
.catch(error => console.error('Error one fetching data:', error));
});
function fetchData() {
fetch('/users')
.then(response => response.json())
.then(data => {
const container = document.getElementById('data-container');
container.innerHTML = ''; // Clear previous data



function fetchData() {
fetch('/users')
.then(response => response.json())
.then(data => {
const container = document.getElementById('data-container');
container.innerHTML = ''; // Clear previous data

if (data.length) {
data.forEach(item => {
const listItem = document.createElement('p');
listItem.textContent = JSON.stringify(item);
container.appendChild(listItem);
});
})
.catch(error => console.error('Error fetching data:', error));
}

// Initial data fetch
fetchData();






} else {
console.log("No items to display.");
}
})
.catch(error => console.error('Error two fetching data:', error));
}
// Initial data fetch
fetchData();
</script>
</body>
</html>
</html>
2 changes: 1 addition & 1 deletion templates/update.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ <h4><a href="delete">Delete Data</a></h4>
email: email,
}),
})
.then(response => response.json())
.then(response => response.text())
.then(data => {
console.log('Data edited successfully:', data);
fetchData(); // Refresh data after editing
Expand Down

0 comments on commit 32c62b3

Please sign in to comment.