feat: Set up SessionMiddleware and Jinja2 Template Configuration

- Add SECRET_KEY environment variable and .env file for session management
- Configure SessionMiddleware with FastAPI for user session handling
- Set up Jinja2 template engine with template directory configuration
- Mount static files directory for CSS, JS, and image assets
- Create comprehensive base.html template with Bootstrap 5 CDN
- Add Bootstrap Icons and custom styling support
- Include responsive navigation with user authentication state
- Create placeholder CSS and JavaScript files for customization
- Add aiofiles dependency for static file serving

This establishes the web framework foundation with session management
and templating system ready for frontend development.
This commit is contained in:
HotSwapp
2025-10-06 18:27:44 -05:00
parent de983a73d2
commit 227c74294f
6 changed files with 256 additions and 2 deletions

3
.env Normal file
View File

@@ -0,0 +1,3 @@
# Delphi Database Environment Configuration
SECRET_KEY=your-secret-key-here-change-this-in-production
DATABASE_URL=sqlite:///./delphi.db

View File

@@ -5,20 +5,36 @@ This module initializes the FastAPI application, sets up database connections,
and provides the main application instance.
"""
import os
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends
from fastapi import FastAPI, Depends, Request
from fastapi.middleware.sessions import SessionMiddleware
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
from dotenv import load_dotenv
from .database import create_tables, get_db, get_database_url
from .models import User
# Load environment variables
load_dotenv()
# Get SECRET_KEY from environment variables
SECRET_KEY = os.getenv("SECRET_KEY")
if not SECRET_KEY:
raise ValueError("SECRET_KEY environment variable must be set")
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Configure Jinja2 templates
templates = Jinja2Templates(directory="app/templates")
@asynccontextmanager
async def lifespan(app: FastAPI):
@@ -54,6 +70,21 @@ app = FastAPI(
lifespan=lifespan
)
# Add CORS middleware for cross-origin requests
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, specify allowed origins
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Add SessionMiddleware for session management
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)
# Mount static files directory
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/")
async def root():

View File

@@ -1 +1,87 @@
<!-- Base template with Bootstrap 5 and navigation -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Delphi Database{% endblock %}</title>
<!-- Bootstrap 5 CSS CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="{{ url_for('static', path='/css/custom.css') }}" rel="stylesheet">
{% block extra_head %}{% endblock %}
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<img src="{{ url_for('static', path='/logo/delphi-logo.webp') }}" alt="Delphi Logo" width="30" height="30" class="d-inline-block align-text-top me-2">
Delphi Database
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link {% if request.url.path == '/' %}active{% endif %}" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link {% if 'dashboard' in request.url.path %}active{% endif %}" href="/dashboard">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link {% if 'admin' in request.url.path %}active{% endif %}" href="/admin">Admin</a>
</li>
</ul>
<ul class="navbar-nav">
{% if 'user' in session %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle me-1"></i>{{ session.user.username }}
</a>
<ul class="dropdown-menu" aria-labelledby="userDropdown">
<li><a class="dropdown-item" href="/profile">Profile</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="/logout">Logout</a></li>
</ul>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link {% if 'login' in request.url.path %}active{% endif %}" href="/login">Login</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="container-fluid mt-4">
{% block content %}{% endblock %}
</main>
<!-- Footer -->
<footer class="bg-light text-center text-muted mt-5 py-3">
<div class="container">
<small>&copy; 2025 Delphi Database. All rights reserved.</small>
</div>
</footer>
<!-- Bootstrap 5 JS Bundle CDN -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom JS -->
<script src="{{ url_for('static', path='/js/custom.js') }}"></script>
{% block extra_scripts %}{% endblock %}
</body>
</html>

View File

@@ -7,3 +7,4 @@ passlib[bcrypt]==1.7.4
python-dotenv==1.0.0
uvicorn[standard]==0.24.0
jinja2==3.1.2
aiofiles==23.2.1

51
static/css/custom.css Normal file
View File

@@ -0,0 +1,51 @@
/* Custom CSS for Delphi Database */
/* Additional styles can be added here */
body {
background-color: #f8f9fa;
}
/* Custom navbar styles */
.navbar-brand {
font-weight: bold;
}
/* Custom card styles */
.card {
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
border: 1px solid rgba(0, 0, 0, 0.125);
}
/* Custom button styles */
.btn-primary {
background-color: #0d6efd;
border-color: #0d6efd;
}
.btn-primary:hover {
background-color: #0b5ed7;
border-color: #0a58ca;
}
/* Custom form styles */
.form-control:focus {
border-color: #86b7fe;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
/* Custom table styles */
.table th {
background-color: #f8f9fa;
border-bottom: 2px solid #dee2e6;
}
/* Custom alert styles */
.alert {
border: none;
border-radius: 0.375rem;
}
/* Custom footer styles */
footer {
margin-top: auto;
}

82
static/js/custom.js Normal file
View File

@@ -0,0 +1,82 @@
// Custom JavaScript for Delphi Database
document.addEventListener('DOMContentLoaded', function() {
// Initialize tooltips if any
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// Auto-hide alerts after 5 seconds
var alerts = document.querySelectorAll('.alert:not(.alert-permanent)');
alerts.forEach(function(alert) {
setTimeout(function() {
var bsAlert = new bootstrap.Alert(alert);
bsAlert.close();
}, 5000);
});
// Confirm delete actions
var deleteButtons = document.querySelectorAll('[data-confirm-delete]');
deleteButtons.forEach(function(button) {
button.addEventListener('click', function(e) {
var message = this.getAttribute('data-confirm-delete') || 'Are you sure you want to delete this item?';
if (!confirm(message)) {
e.preventDefault();
}
});
});
// Form validation enhancement
var forms = document.querySelectorAll('.needs-validation');
Array.prototype.slice.call(forms).forEach(function(form) {
form.addEventListener('submit', function(event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add('was-validated');
}, false);
});
// Dynamic form field enabling/disabling
var toggleFields = document.querySelectorAll('[data-toggle-field]');
toggleFields.forEach(function(element) {
element.addEventListener('change', function() {
var targetSelector = this.getAttribute('data-toggle-field');
var targetField = document.querySelector(targetSelector);
if (targetField) {
targetField.disabled = !this.checked;
}
});
});
});
// Utility functions
function formatDate(dateString) {
if (!dateString) return '';
var date = new Date(dateString);
return date.toLocaleDateString();
}
function formatCurrency(amount) {
if (!amount) return '$0.00';
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
}
function showLoading(button) {
if (button) {
button.disabled = true;
button.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Loading...';
}
}
function hideLoading(button, originalText) {
if (button) {
button.disabled = false;
button.innerHTML = originalText;
}
}