Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ get_docker_container_port_mapping() {
call_web_app() {
# Get the web app name
echo "Getting web app name..."
web_app_name=$(azlocal webapp list --query '[0].name' --output tsv)
web_app_name=$(az webapp list --query '[0].name' --output tsv)

if [ -n "$web_app_name" ]; then
echo "Web app [$web_app_name] successfully retrieved."
Expand All @@ -79,7 +79,7 @@ call_web_app() {

# Get the resource group name
echo "Getting resource group name for web app [$web_app_name]..."
resource_group_name=$(azlocal webapp list --query '[0].resourceGroup' --output tsv)
resource_group_name=$(az webapp list --query '[0].resourceGroup' --output tsv)

if [ -n "$resource_group_name" ]; then
echo "Resource group [$resource_group_name] successfully retrieved."
Expand All @@ -90,7 +90,7 @@ call_web_app() {

# Get the the default host name of the web app
echo "Getting the default host name of the web app [$web_app_name]..."
app_host_name=$(azlocal webapp show \
app_host_name=$(az webapp show \
--name "$web_app_name" \
--resource-group "$resource_group_name" \
--query 'defaultHostName' \
Expand Down
46 changes: 27 additions & 19 deletions samples/web-app-cosmosdb-nosql-api/python/scripts/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,18 @@ RANDOM_SUFFIX=$(echo $RANDOM)
NEW_DB_NAME="vacationplanner_${RANDOM_SUFFIX}"
AZURECOSMOSDB_DATABASENAME=$NEW_DB_NAME
AZURECOSMOSDB_CONTAINERNAME="activities_${RANDOM_SUFFIX}"

# Start azure CLI local mode session
azlocal login

# Change the current directory to the script's directory
#cd "$CURRENT_DIR" || exit
AURECOSMOSDB_PARTITION_KEY="/username"

# Validates if the resource group exists in the subscription, if not creates it
echo "Checking if resource group [$RESOURCE_GROUP_NAME] exists..."
azlocal group show --name $RESOURCE_GROUP_NAME &>/dev/null
az group show --name $RESOURCE_GROUP_NAME &>/dev/null

if [[ $? != 0 ]]; then
echo "No resource group [$RESOURCE_GROUP_NAME] exists"
echo "Creating resource group [$RESOURCE_GROUP_NAME]..."

# Create the resource group
azlocal group create \
az group create \
--name $RESOURCE_GROUP_NAME \
--location $LOCATION \
--only-show-errors 1> /dev/null \
Expand All @@ -47,7 +42,7 @@ else
fi

echo "Create CosmosDB NoSQL Account"
export AZURECOSMOSDB_ENDPOINT=$(azlocal cosmosdb create \
export AZURECOSMOSDB_ENDPOINT=$(az cosmosdb create \
--resource-group $RESOURCE_GROUP_NAME \
--name $WEB_APP_NAME \
--locations regionName=$LOCATION \
Expand All @@ -57,24 +52,39 @@ echo "Create CosmosDB NoSQL Account"
echo "Account created"
echo "AZURECOSMOSDB_ENDPOINT set to $AZURECOSMOSDB_ENDPOINT"

echo "Create CosmosDB NoSQL Database"
az cosmosdb sql database create \
--resource-group $RESOURCE_GROUP_NAME \
--name $AZURECOSMOSDB_DATABASENAME \
--account-name $WEB_APP_NAME

echo "Create CosmosDB NoSQL Container"
az cosmosdb sql container create \
--resource-group $RESOURCE_GROUP_NAME \
--account-name $WEB_APP_NAME \
--database-name $AZURECOSMOSDB_DATABASENAME \
--name $AZURECOSMOSDB_CONTAINERNAME \
--partition-key-path $AURECOSMOSDB_PARTITION_KEY \
--throughput 400

echo "Fetching DB Account primary master key"
export AZURECOSMOSDB_PRIMARY_KEY=$(azlocal cosmosdb keys list \
export AZURECOSMOSDB_PRIMARY_KEY=$(az cosmosdb keys list \
--resource-group $RESOURCE_GROUP_NAME \
--name $WEB_APP_NAME \
--query "primaryMasterKey" \
--output tsv)
echo "Primary master key is $AZURECOSMOSDB_PRIMARY_KEY"

echo "Creating App service"
azlocal appservice plan create --name $WEB_APP_NAME --resource-group $RESOURCE_GROUP_NAME --sku B1 --is-linux
az appservice plan create --name $WEB_APP_NAME --resource-group $RESOURCE_GROUP_NAME --sku B1 --is-linux
echo "App service created"

echo "Creating Web App"
azlocal webapp create --name $WEB_APP_NAME --resource-group $RESOURCE_GROUP_NAME --plan $WEB_APP_NAME --runtime PYTHON:3.13
az webapp create --name $WEB_APP_NAME --resource-group $RESOURCE_GROUP_NAME --plan $WEB_APP_NAME --runtime PYTHON:3.13
echo "Web App created"

echo "Configure appsettings environment variables"
azlocal webapp config appsettings set \
az webapp config appsettings set \
--resource-group $RESOURCE_GROUP_NAME \
--name $WEB_APP_NAME \
--settings AZURECOSMOSDB_ENDPOINT=$AZURECOSMOSDB_ENDPOINT \
Expand All @@ -84,7 +94,7 @@ azlocal webapp config appsettings set \

# Print the application settings of the web app
echo "Retrieving application settings for web app [$WEB_APP_NAME]..."
azlocal webapp config appsettings list \
az webapp config appsettings list \
--resource-group $RESOURCE_GROUP_NAME \
--name $WEB_APP_NAME

Expand All @@ -102,15 +112,13 @@ zip -r "$ZIPFILE" app.py cosmosdb_client.py static templates requirements.txt

# Deploy the web app
echo "Deploying web app [$WEB_APP_NAME] with zip file [$ZIPFILE]..."
echo "Using azlocal webapp deploy command for LocalStack emulator environment."
azlocal webapp deploy \
echo "Using az webapp deploy command for LocalStack emulator environment."
az webapp deploy \
--resource-group $RESOURCE_GROUP_NAME \
--name $WEB_APP_NAME \
--src-path ${ZIPFILE} \
--type zip \
--async true \
--debug \
--verbose 1>/dev/null
--async true

# Remove the zip package of the web app
if [ -f "$ZIPFILE" ]; then
Expand Down
24 changes: 6 additions & 18 deletions samples/web-app-cosmosdb-nosql-api/python/scripts/validate.sh
Original file line number Diff line number Diff line change
@@ -1,48 +1,36 @@
#!/bin/bash

# Variables
ENVIRONMENT=$(az account show --query environmentName --output tsv)

# Choose the appropriate CLI based on the environment
if [[ $ENVIRONMENT == "LocalStack" ]]; then
echo "Using azlocal for LocalStack emulator environment."
AZ="azlocal"
else
echo "Using standard az for AzureCloud environment."
AZ="az"
fi

# Check resource group
$AZ group show \
az group show \
--name local-rg \
--output table

# List resources
$AZ resource list \
az resource list \
--resource-group local-rg \
--output table

# Check Azure Web App
$AZ webapp show \
az webapp show \
--name local-webapp-nosql-test \
--resource-group local-rg \
--output table

# Check Azure CosmosDB account
$AZ cosmosdb show \
az cosmosdb show \
--name local-webapp-nosql-test \
--resource-group local-rg \
--output table

# Check database (not implemented yet)
# $AZ database show \
# az database show \
# --name sampledb \
# --account-name local-webapp-nosqltest \
# --resource-group local-rg \
# --output table

# Check collection (not impleented yet)
# $AZ cosmosdb collection show \
# az cosmosdb collection show \
# --name activities \
# --database-name sampledb \
# --account-name local-webapp-nosql-test \
Expand Down
10 changes: 0 additions & 10 deletions samples/web-app-cosmosdb-nosql-api/python/src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,5 @@ def delete(activity_id: str):

return redirect(url_for('index'))

@app.route('/edit/<string:activity_id>', methods=['POST'])
def edit(activity_id: str):
new_text = request.form.get('new_text')

if new_text:
logger.info(f"Updating ID {activity_id} with activity: {new_text}")
get_cosmos().update_document_activity(activity_id, username, new_text)

return redirect(url_for('index'))

if __name__ == '__main__':
app.run(debug=True)
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,16 @@ def update_document_activity(self, activity_id: str, username: str, new_text: st
logger.warning(f"Update failed: {e}")

def delete_document_by_id(self, doc_id: str, username: str):
self.ensure_initialized()

try:
self.container.delete_item(item=doc_id, partition_key=username)
except exceptions.CosmosResourceNotFoundError:
pass
doc_to_delete = self.container.read_item(item=doc_id, partition_key=[username])
self.container.delete_item(item=doc_to_delete, partition_key=[username])
except exceptions.CosmosResourceNotFoundError as e:
logger.warning(f"Cosmos resource with doc_id {doc_id} and username {username} was not found")
raise e
except exceptions.CosmosHttpResponseError as e:
raise e
except Exception as e:
logger.info(f"DELETE METHOD CRASHED: Error Type: {type(e).__name__}, Message: {e}")
raise e
118 changes: 25 additions & 93 deletions samples/web-app-cosmosdb-nosql-api/python/src/templates/index.html
Original file line number Diff line number Diff line change
@@ -1,54 +1,3 @@
<!--- <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vacation Planner</title>
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
</head>
<body style="background: linear-gradient(135deg, #fff 0%, #a2d4f7 100%); min-height: 100vh;">
<div class="container py-4 my-5 bg-white bg-opacity-75 rounded-4 shadow-lg" style="max-width:480px;">
<img src="{{ url_for('static', filename='summer_banner.jpg') }}" alt="Summer Banner" class="img-fluid rounded mb-3 w-100" style="max-height:180px; object-fit:cover;">
<h1 class="display-9 fw-bold sea-title mb-4">Vacation Planner</h1>
<form method="post" class="sea-form mb-4" style="max-width: 520px; margin: 0 auto;">
<div style="display: flex; width: 100%;">
<input type="text" name="activity" class="form-control sea-input" placeholder="Enter a vacation activity..." required>
<button type="submit" class="sea-btn">Add</button>
</div>
</form>
<div class="table-responsive" style="width: 100%;">
<table class="table sea-table align-middle" style="width: 100%; margin: 0;">
<thead>
<tr>
<th class="text-start">Activity</th>
<th class="text-start align-middle action-col" style="width: 90px;">Action</th>
</tr>
</thead>
<tbody>
{% for activity in activities %}
<tr>
<td class="text-start">{{ activity[1] }}</td>
<td class="text-start align-middle action-col" style="width: 90px;">
<form method="post" action="{{ url_for('delete', activity_id=activity[0]) }}" class="d-inline">
<input type="hidden" name="row_id" value="{{ activity[0] }}">
<button type="submit" class="sea-btn" style="width: 90px;">Delete</button>
</form>
</td>
</tr>
{% else %}
<tr>
<td colspan="2" class="text-center text-muted fst-italic">No vacation plans yet!</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</body>
</html> --->


<!DOCTYPE html>
<html lang="en">
<head>
Expand All @@ -59,15 +8,17 @@ <h1 class="display-9 fw-bold sea-title mb-4">Vacation Planner</h1>
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
</head>
<body style="background: linear-gradient(135deg, #fff 0%, #a2d4f7 100%); min-height: 100vh;">
<div class="container py-4 my-5 bg-white bg-opacity-75 rounded-4 shadow-lg" style="max-width:520px;">
<div class="container py-4 my-5 bg-white bg-opacity-75 rounded-4 shadow-lg" style="max-width:600px;">
<img src="{{ url_for('static', filename='summer_banner.jpg') }}" alt="Summer Banner" class="img-fluid rounded mb-3 w-100" style="max-height:180px; object-fit:cover;">

<h1 class="display-9 fw-bold sea-title mb-4 text-center">Vacation Planner</h1>

<form method="post" class="sea-form mb-4">
<div class="d-flex w-100">
<input type="text" name="activity" class="form-control sea-input" placeholder="Enter a vacation activity..." required>
<button type="submit" class="sea-btn ms-2">Add</button>
<form method="post" action="{{ url_for('index') }}" class="sea-form mb-4" style="max-width: 600px; margin: 0 auto;">
<div style="display: flex; width: 100%;">
<input type="hidden" name="row_id" value="{{ edit_id or '' }}">
<input id="activityInput" type="text" name="activity" class="form-control sea-input"
placeholder="Enter a vacation activity..." value="{{ edit_activity or '' }}" required autofocus>
<button type="submit" class="sea-btn ms-2">{{ 'Update' if edit_id else 'Add' }}</button>
</div>
</form>

Expand All @@ -76,36 +27,23 @@ <h1 class="display-9 fw-bold sea-title mb-4 text-center">Vacation Planner</h1>
<thead>
<tr>
<th class="text-start">Activity</th>
<th class="text-end" style="width: 160px;">Actions</th>
<th class="text-center align-middle" style="width: 220px;" colspan="2">Action</th>
</tr>
</thead>
<tbody>
{% for activity in activities %}
<tr>
<td class="text-start">
<span id="display-{{ activity[0] }}">{{ activity[1] }}</span>

<form id="edit-form-{{ activity[0] }}" method="post" action="{{ url_for('edit', activity_id=activity[0]) }}" class="d-none">
<div class="input-group input-group-sm">
<input type="text" name="new_text" class="form-control" value="{{ activity[1] }}" required>
<button type="submit" class="btn btn-success btn-sm">Save</button>
<button type="button" class="btn btn-secondary btn-sm" onclick="toggleEdit('{{ activity[0] }}')">✕</button>
</div>
<td class="text-start">{{ activity[1] }}</td>

<td class="text-start align-middle p-1" style="width: 110px;">
<form method="post" action="{{ url_for('delete', activity_id=activity[0]) }}" class="d-inline">
<button type="submit" class="sea-btn" style="width: 90px; font-size: 0.9rem;">Delete</button>
</form>
</td>
<td class="text-end">
<div id="actions-{{ activity[0] }}">
<button type="button" class="btn btn-sm btn-outline-primary" onclick="toggleEdit('{{ activity[0] }}')">Edit</button>

<form method="post" action="{{ url_for('delete', activity_id=activity[0]) }}" class="d-inline">
<button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
</form>
</div>
</td>
</tr>
{% else %}
<tr>
<td colspan="2" class="text-center text-muted fst-italic py-4">No vacation plans yet!</td>
<td colspan="3" class="text-center text-muted fst-italic py-4">No vacation plans yet!</td>
</tr>
{% endfor %}
</tbody>
Expand All @@ -114,24 +52,18 @@ <h1 class="display-9 fw-bold sea-title mb-4 text-center">Vacation Planner</h1>
</div>

<script>
function toggleEdit(id) {
const displaySpan = document.getElementById(`display-${id}`);
const editForm = document.getElementById(`edit-form-${id}`);
const actionButtons = document.getElementById(`actions-${id}`);

if (editForm.classList.contains('d-none')) {
// Switch to Edit Mode
editForm.classList.remove('d-none');
displaySpan.classList.add('d-none');
actionButtons.classList.add('d-none');
// Auto-focus logic for the input field
(function () {
const input = document.getElementById('activityInput');
if (!input) return;
if (input.value && input.value.length > 0) {
input.focus();
const len = input.value.length;
input.setSelectionRange(len, len);
} else {
// Switch back to Display Mode
editForm.class
editForm.classList.add('d-none');
displaySpan.classList.remove('d-none');
actionButtons.classList.remove('d-none');
input.focus();
}
}
})();
</script>
</body>
</html>
</html>