diff --git a/.env b/.env new file mode 100644 index 0000000..85236cb --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +# Delphi Database Environment Configuration +SECRET_KEY=your-secret-key-here-change-this-in-production +DATABASE_URL=sqlite:///./delphi.db diff --git a/app/main.py b/app/main.py index 1551825..a92da2f 100644 --- a/app/main.py +++ b/app/main.py @@ -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(): diff --git a/app/templates/base.html b/app/templates/base.html index ebe8c10..a16f733 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -1 +1,87 @@ - + + + + + + {% block title %}Delphi Database{% endblock %} + + + + + + + + + + + {% block extra_head %}{% endblock %} + + + + + + +
+ {% block content %}{% endblock %} +
+ + + + + + + + + + + {% block extra_scripts %}{% endblock %} + + diff --git a/requirements.txt b/requirements.txt index 972ac95..02c7c88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/static/css/custom.css b/static/css/custom.css new file mode 100644 index 0000000..5850552 --- /dev/null +++ b/static/css/custom.css @@ -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; +} diff --git a/static/js/custom.js b/static/js/custom.js new file mode 100644 index 0000000..6dc4d33 --- /dev/null +++ b/static/js/custom.js @@ -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 = 'Loading...'; + } +} + +function hideLoading(button, originalText) { + if (button) { + button.disabled = false; + button.innerHTML = originalText; + } +}