Refactor init and readme

This commit is contained in:
Minecon724 2025-04-07 09:14:05 +02:00
commit ab7757cf46
Signed by: Minecon724
GPG key ID: A02E6E67AB961189
6 changed files with 226 additions and 301 deletions

11
.env.dev Normal file
View file

@ -0,0 +1,11 @@
# .env for development
SECRET_KEY=123456
FLASK_ENV=development
DATABASE_URL=sqlite:///anonchat.db
SESSION_TYPE=filesystem
RATELIMIT_STORAGE_URL=memory://
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin

295
README.md
View file

@ -1,229 +1,122 @@
# AnonChat
# anonchat
An anonymous chat application built with Flask.
A simple Flask-based web application for anonymous inquiries or messages, designed for easy deployment and management.
## Features
- Anonymous inquiries and messaging
- Admin dashboard to manage inquiries
- Customizable site title
- Redis-based session storage for improved scalability
- Integrated error tracking with Sentry
* **Anonymous Inquiries:** Allows users to submit inquiries without revealing their identity.
* **Admin Interface:** Secure area for administrators to view and manage inquiries.
* **Webhook Notifications:** Optionally sends notifications to a specified URL upon new inquiry submissions.
* **Automatic Data Deletion:** Periodically deletes inquiries older than a configurable duration (default: 48 hours) to maintain privacy.
* **Rate Limiting:** Protects against abuse by limiting the number of requests from a single IP address.
* **CSRF Protection:** Implemented using Flask-WTF to prevent cross-site request forgery attacks.
* **Configurable:** Easily configured using environment variables.
* **Containerized:** Ready for deployment using Docker and Docker Compose.
* **Database Migrations:** Simple migration system managed via Flask CLI commands.
* **Optional Sentry Integration:** Supports error tracking with Sentry.
## Development Approach
## Technology Stack
AnonChat was created using "vibe coding" - a programming approach where developers leverage AI tools to generate code through natural language prompts rather than writing code manually. This modern development method allows focusing on high-level problem-solving and design while letting AI handle implementation details.
Rest assured though, I know what I'm (or the AI is) doing. Here's what would happen if I didn't:
1. [my saas was built with Cursor, zero hand written code \
AI is no longer just an assistant, it's also the builder \
Now, you can continue to whine about it or start building.](https://xcancel.com/leojr94_/status/1900767509621674109)
2. [random thing are happening, maxed out usage on api keys, people bypassing the subscription, creating random shit on db \
there are just some weird ppl out there](https://xcancel.com/leojr94_/status/1901560276488511759)
* **Backend:** Python 3.11+, Flask
* **Database:** PostgreSQL (recommended) or SQLite (default) via Flask-SQLAlchemy
* **Session Management:** Redis (recommended) or Filesystem via Flask-Session
* **Rate Limiting:** Redis via Flask-Limiter
* **Task Scheduling:** Flask-APScheduler
* **Password Hashing:** Argon2 via argon2-cffi
* **WSGI Server:** Gunicorn
* **Dependency Management:** Poetry
* **Containerization:** Docker, Docker Compose
* **Deployment:** Configured for Fly.io (optional)
## Configuration
AnonChat can be configured using environment variables:
The application is configured via environment variables. You can set these in a `.env` file in the project root or directly in your deployment environment.
- `SECRET_KEY`: Secret key for session management
- `DATABASE_URL`: Database connection string (defaults to SQLite)
- `ADMIN_USERNAME`: Admin username for admin dashboard
- `ADMIN_PASSWORD`: Admin password for admin dashboard
- `ADMIN_FORCE_RESET`: When set to "true", forces a reset of the admin password to the value in ADMIN_PASSWORD (defaults to "false")
- `SITE_TITLE`: Customizable site title (defaults to "AnonChat")
- `BEHIND_PROXY`: Set to "true" when running behind a reverse proxy to properly handle client IP addresses (defaults to "false")
- `RATELIMIT_STORAGE_URL`: Storage backend for rate limiting (defaults to memory storage)
- `REDIS_URL`: Redis connection URL for session storage (defaults to "redis://localhost:6379/0")
- `AUTO_DELETE_HOURS`: Number of hours after which closed inquiries are automatically deleted (defaults to 48)
- `SENTRY_DSN`: Sentry Data Source Name for error tracking and monitoring (optional)
**Required:**
You can set these variables in a `.env` file:
* `SECRET_KEY`: A long, random string used for session signing and security. Generate one using `python -c 'import secrets; print(secrets.token_hex(24))'`.
* `DATABASE_URL`: The connection string for your database (e.g., `postgresql://user:password@host:port/dbname` or `sqlite:///instance/anonchat.db`).
* `REDIS_URL`: The connection string for your Redis instance (e.g., `redis://localhost:6379/0`). Used for sessions (if `SESSION_TYPE=redis`) and rate limiting.
* `ADMIN_PASSWORD`: The password for the admin user. Set this on first run or when needing to reset.
```
SECRET_KEY=your_secret_key_here
FLASK_APP=src/anonchat
FLASK_ENV=development
SITE_TITLE=My Custom Chat
BEHIND_PROXY=true
REDIS_URL=redis://redis:6379/0
AUTO_DELETE_HOURS=72
SENTRY_DSN=https://your-sentry-dsn
**Optional:**
* `SITE_TITLE`: The title displayed on the website (default: `AnonChat`).
* `WEBHOOK_ENABLED`: Set to `true` to enable webhook notifications (default: `false`).
* `WEBHOOK_URL`: The URL to send webhook notifications to.
* `WEBHOOK_SECRET`: A secret key to sign webhook payloads (recommended if webhooks are enabled).
* `ADMIN_USERNAME`: The username for the admin user (default: `admin`).
* `ADMIN_FORCE_RESET`: Set to `true` to force reset the admin password on startup using `ADMIN_PASSWORD`.
* `AUTO_DELETE_HOURS`: The number of hours after which inquiries are automatically deleted (default: `48`).
* `RATELIMIT_STORAGE_URI`: Overrides `REDIS_URL` specifically for rate limiting.
* `BEHIND_PROXY`: Set to `true` if the application is running behind a reverse proxy (e.g., Nginx, Traefik) to correctly identify client IPs (default: `false`).
* `SESSION_TYPE`: `redis` (default) or `filesystem`.
* `SESSION_FILE_DIR`: Directory for filesystem sessions (if `SESSION_TYPE=filesystem`, default: `/dev/shm/flask_session`).
* `SENTRY_DSN`: Your Sentry DSN to enable error tracking.
## Setup and Installation
**Prerequisites:**
* Python 3.11+
* Poetry
* Docker & Docker Compose (Recommended for ease of setup)
* Redis Server (if using Redis for sessions/rate limiting)
* PostgreSQL Server (if using PostgreSQL)
**1. Clone the Repository:**
```bash
git clone <repository-url>
cd anonchat
```
## Reverse Proxy Configuration
**2. Using Docker (Recommended):**
When running AnonChat behind a reverse proxy (like Nginx or Apache), set the `BEHIND_PROXY` environment variable to "true" to ensure rate limiting works correctly. This enables the application to use the X-Forwarded-For header to determine the client's real IP address.
This is the easiest way to get started.
Your reverse proxy should be configured to pass the client IP address in the X-Forwarded-For header:
* **Create a `.env` file:** Copy `.env.example` (if it exists) or create a new `.env` file and fill in the required environment variables (see Configuration section). Pay special attention to `SECRET_KEY`, `DATABASE_URL`, `REDIS_URL`, and `ADMIN_PASSWORD`.
* **Build and Run:**
```bash
docker-compose up --build -d
```
### Nginx Example
The application should now be accessible at `http://localhost:5000` (or the port mapped in `docker-compose.yml`).
```nginx
server {
listen 80;
server_name your-domain.com;
**3. Local Development (Without Docker):**
location / {
proxy_pass http://localhost:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
* **Install Dependencies:**
```bash
poetry install
```
* **Set Environment Variables:** Create a `.env` file in the project root and define the necessary variables (see Configuration).
* **Ensure Services are Running:** Make sure your PostgreSQL (if used) and Redis servers are running and accessible based on your `.env` configuration.
* **Run the Development Server:**
```bash
poetry run start
```
## Error Tracking with Sentry
The application should be running at `http://localhost:5000`.
AnonChat includes integration with Sentry for error tracking and performance monitoring. This helps identify and diagnose issues in production environments.
## Running the Application
### Features
* **Docker:** Use `docker-compose up -d` to start and `docker-compose down` to stop.
* **Local:** Use `poetry run start` for the development server. For production-like environments without Docker, use `gunicorn`:
```bash
# Example gunicorn command (adjust workers as needed)
poetry run gunicorn --workers 4 --bind 0.0.0.0:5000 "anonchat:app"
```
- Automatic error capturing and reporting
- Performance monitoring
- Contextual information for better debugging
- Real-time alerts for critical issues
## Deployment
### Configuration
This application includes configuration files for deployment:
To enable Sentry integration:
* `Dockerfile`: Defines the container image.
* `docker-compose.yml`: For local multi-container setups (app, db, redis).
* `fly.toml`: Configuration for deploying to Fly.io.
1. Sign up for a free Sentry account at [sentry.io](https://sentry.io)
2. Create a new project and get your DSN (Data Source Name)
3. Set the `SENTRY_DSN` environment variable in your `.env` file or deployment environment:
Refer to the specific platform's documentation for deployment steps using these files. Ensure all necessary environment variables are set in your deployment environment.
```
SENTRY_DSN=https://your-sentry-project-key@sentry.io/your-project-id
```
## License
When the `SENTRY_DSN` variable is set, error tracking will be automatically enabled when the application starts.
## Installation
1. Clone the repository
2. Install dependencies with Poetry: `poetry install`
3. Create `.env` file with your configuration
4. Run the application: `poetry run start`
## Development
This project uses Poetry for dependency management.
- Install dependencies: `poetry install`
- Run tests: `poetry run pytest`
- Run the application: `poetry run start`
### Database Migrations
AnonChat includes a custom database migration system to handle schema changes. When you make changes to the database models, you should create a migration script to apply these changes to existing databases.
#### Running Migrations
- Run all pending migrations: `poetry run flask --app src/anonchat run-migrations`
- The migrations are also automatically run when using the `init-db` command or when starting the application with the entrypoint script.
#### Creating New Migrations
To create a new migration:
1. Create a new Python file in the `src/anonchat/migrations` directory with a descriptive name (e.g., `add_new_column.py`)
2. Implement a `run_migration(db)` function that performs the necessary schema changes
3. The migration script should be idempotent (safe to run multiple times)
Example migration script:
```python
from sqlalchemy import inspect
from sqlalchemy.sql import text
from flask import current_app
def run_migration(db):
"""Add a new column to a table."""
# Check if the column already exists
inspector = inspect(db.engine)
columns = [col['name'] for col in inspector.get_columns('your_table')]
# Only apply changes if needed
if 'your_new_column' not in columns:
current_app.logger.info("Adding new column to table")
with db.engine.connect() as conn:
conn.execute(text("ALTER TABLE your_table ADD COLUMN your_new_column TEXT"))
conn.commit()
return True
return False # Return True if changes were made, False otherwise
```
Migrations are run in alphabetical order, so you may want to prefix migration filenames with a timestamp or sequence number for more complex projects.
## Admin Authentication
AnonChat includes a secure admin authentication system that protects administrative routes and functions. This ensures that only authorized users can access the admin dashboard, manage inquiries, and configure system settings.
### Security Features
- **Secure Password Storage**: Admin passwords are securely hashed using SHA-256 with the application's secret key as salt
- **Session-Based Authentication**: Uses Flask sessions to maintain admin login state
- **Protected Routes**: All admin routes are protected by middleware that verifies authentication
- **Password Management**: Admins can change their password through the Admin Settings page
- **Logout Functionality**: Secure logout to clear session data
### Setting Admin Credentials
Admin credentials are set using environment variables:
```
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your-secure-password
ADMIN_FORCE_RESET=false
```
These values should be set in your `.env` file or server environment. The default admin user is created automatically when the application first runs.
#### Password Reset
You can force a reset of the admin password by setting `ADMIN_FORCE_RESET=true` in your environment variables. This is useful when:
- You need to recover from a forgotten admin password
- You're deploying to a new environment and want to ensure the admin credentials are set correctly
- You want to update the admin password during deployment without accessing the admin interface
When enabled, the application will update the admin user's password to match the value in `ADMIN_PASSWORD` during initialization or when running the `init-db` command.
### Admin Functions
- View and respond to user inquiries
- Delete inquiries
- Configure webhook settings
- Change admin password
### Security Best Practices
- Always use a strong, unique password for the admin account
- Keep your SECRET_KEY secure and unique for each deployment
- In production, ensure you're using HTTPS to protect admin credentials during transmission
- Change the default admin password immediately after deployment
## TODO: Security Improvements
The following security enhancements are planned for future releases:
- [ ] Implement CAPTCHA protection for admin login
- Add CAPTCHA verification to prevent brute force attacks
- Support multiple CAPTCHA providers (reCAPTCHA, hCaptcha)
- Implement rate limiting for failed login attempts
- Add IP-based blocking after multiple failed attempts
### Authentication Methods
- [ ] Add OAuth 2.0 support for admin authentication
- Integrate with common providers (Google, GitHub, Microsoft)
- Implement proper PKCE flow for added security
- Support for custom OAuth providers for enterprise deployments
- Add multi-factor authentication options
### Read-Only Links
- [ ] Implement read-only sharing links for inquiries
- Generate unique, cryptographically secure sharing links
- Allow users to create links that provide view-only access
- Set optional expiration times for sharing links
- Allow users to revoke sharing links at any time
This project is licensed under the 0BSD license - see the [LICENSE.txt](LICENSE.txt) file for details.

View file

@ -1,3 +1,4 @@
import logging
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os
@ -15,43 +16,65 @@ from flask_apscheduler import APScheduler
# Load environment variables from .env file
load_dotenv()
DEFAULTS = {
'SECRET_KEY': '',
'SITE_TITLE': 'AnonChat',
'BEHIND_PROXY': False,
'RATELIMIT_STORAGE_URI': os.environ.get('REDIS_URL'),
'RATELIMIT_HEADERS_ENABLED': True,
'RATELIMIT_KEY_PREFIX': 'anonchat_rate_limit',
'DATABASE_URL': 'sqlite:///anonchat.db', # Actual key: SQLALCHEMY_DATABASE_URI
'SESSION_TYPE': 'redis',
'SESSION_PERMANENT': False,
'SESSION_USE_SIGNER': True,
# If SESSION_TYPE is redis
'SESSION_REDIS': redis.from_url(os.environ.get('REDIS_URL', 'redis://localhost:6379/0')),
# If SESSION_TYPE is filesystem
'SESSION_FILE_DIR': '/dev/shm/flask_session',
'SESSION_FILE_THRESHOLD': 100,
'SESSION_FILE_MODE': 384,
'SESSION_KEY_PREFIX': 'anonchat_session:'
}
def get(key, type: type = str):
env_value = os.environ.get(key)
if env_value is not None:
if type == bool:
env_value = env_value.lower() == 'true'
return env_value or DEFAULTS[key]
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'sqlite:///anonchat.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', os.urandom(24).hex())
app.config['SITE_TITLE'] = os.environ.get('SITE_TITLE', 'AnonChat')
# Webhook configurations
app.config['WEBHOOK_ENABLED'] = os.environ.get('WEBHOOK_ENABLED', 'false').lower() == 'true'
app.config['WEBHOOK_URL'] = os.environ.get('WEBHOOK_URL', '')
app.config['WEBHOOK_SECRET'] = os.environ.get('WEBHOOK_SECRET', '')
# Admin configurations
app.config['ADMIN_USERNAME'] = os.environ.get('ADMIN_USERNAME', 'admin')
app.config['ADMIN_PASSWORD'] = os.environ.get('ADMIN_PASSWORD', None)
app.config['ADMIN_FORCE_RESET'] = os.environ.get('ADMIN_FORCE_RESET', 'false').lower() == 'true'
# Auto-deletion configuration
app.config['AUTO_DELETE_HOURS'] = int(os.environ.get('AUTO_DELETE_HOURS', 48))
app.config['SECRET_KEY'] = get('SECRET_KEY')
app.config['SITE_TITLE'] = get('SITE_TITLE')
app.config['BEHIND_PROXY'] = get('BEHIND_PROXY', bool)
# Rate limit configurations
app.config['RATELIMIT_STORAGE_URI'] = os.environ.get('RATELIMIT_STORAGE_URI', os.environ.get('REDIS_URL'))
app.config['RATELIMIT_HEADERS_ENABLED'] = True
app.config['RATELIMIT_KEY_PREFIX'] = 'anonchat_rate_limit'
# Whether app is behind a proxy (get from env, default to False)
app.config['BEHIND_PROXY'] = os.environ.get('BEHIND_PROXY', 'false').lower() == 'true'
app.config['RATELIMIT_STORAGE_URI'] = get('RATELIMIT_STORAGE_URI')
app.config['RATELIMIT_HEADERS_ENABLED'] = get('RATELIMIT_HEADERS_ENABLED')
app.config['RATELIMIT_KEY_PREFIX'] = get('RATELIMIT_KEY_PREFIX')
# Database configuration
app.config['SQLALCHEMY_DATABASE_URI'] = get('DATABASE_URL')
# Redis session configuration
app.config['SESSION_TYPE'] = os.environ.get('SESSION_TYPE', 'redis')
app.config['SESSION_PERMANENT'] = False
app.config['SESSION_USE_SIGNER'] = True
app.config['SESSION_TYPE'] = get('SESSION_TYPE')
app.config['SESSION_PERMANENT'] = get('SESSION_PERMANENT')
app.config['SESSION_USE_SIGNER'] = get('SESSION_USE_SIGNER')
if app.config['SESSION_TYPE'] == 'redis':
app.config['SESSION_REDIS'] = redis.from_url(os.environ.get('REDIS_URL', 'redis://localhost:6379/0'))
app.config['SESSION_REDIS'] = get('SESSION_REDIS')
elif app.config['SESSION_TYPE'] == 'filesystem':
app.config['SESSION_FILE_DIR'] = os.environ.get('SESSION_FILE_DIR', '/dev/shm/flask_session')
app.config['SESSION_FILE_THRESHOLD'] = os.environ.get('SESSION_FILE_THRESHOLD', 100)
app.config['SESSION_FILE_MODE'] = os.environ.get('SESSION_FILE_MODE', 384)
app.config['SESSION_KEY_PREFIX'] = 'anonchat_session:'
# Scheduler configuration
app.config['SCHEDULER_API_ENABLED'] = False
app.config['SCHEDULER_TIMEZONE'] = 'UTC'
app.config['SESSION_FILE_DIR'] = get('SESSION_FILE_DIR', '/dev/shm/flask_session')
app.config['SESSION_FILE_THRESHOLD'] = get('SESSION_FILE_THRESHOLD', 100)
app.config['SESSION_FILE_MODE'] = get('SESSION_FILE_MODE', 384)
app.config['SESSION_KEY_PREFIX'] = get('SESSION_KEY_PREFIX')
app.url_map.strict_slashes = False
@ -139,27 +162,25 @@ def run_migrations_command():
# Import migrations module here to avoid circular imports
from .migrations import run_migrations
print("Running database migrations...")
app.logger.info("Running database migrations...")
migrations_run = run_migrations(db)
print(f"Database migrations completed: {migrations_run} migrations applied.")
app.logger.info(f"Database migrations completed: {migrations_run} migrations applied.")
# Flask CLI command to initialize the database
@app.cli.command("init-db")
def init_db_command():
def _init_db():
"""Create database tables and initialize default data."""
# Explicitly import models here to ensure they are registered before create_all
from .models import Inquiry, Message, Settings, Admin
with app.app_context():
print("Creating database tables...")
app.logger.info("Creating database tables...")
db.create_all() # Should now create all tables including Inquiry
# Run migrations after creating tables
# This ensures any new columns added to existing tables are properly added
print("Running database migrations...")
app.logger.info("Running database migrations...")
from .migrations import run_migrations
run_migrations(db)
print("Initializing default settings...")
app.logger.info("Initializing default settings...")
# Initialize settings if they don't exist
# Note: Models already imported above
if not Settings.query.first():
@ -170,60 +191,46 @@ def init_db_command():
)
db.session.add(default_settings)
db.session.commit()
print("Default settings initialized.")
app.logger.info("Default settings initialized.")
else:
print("Settings already exist.")
app.logger.info("Settings already exist.")
print("Initializing admin user...")
app.logger.info("Initializing admin user...")
# Initialize admin user if it doesn't exist
# Note: Models already imported above
admin_user = Admin.query.filter_by(username=app.config['ADMIN_USERNAME']).first()
if not admin_user and app.config['ADMIN_PASSWORD'] == None:
print("Admin user not found and no password provided. Skipping admin user initialization.")
elif app.config['ADMIN_FORCE_RESET'] or not admin_user:
if admin_user:
admin_user.password_hash = Admin.hash_password(app.config['ADMIN_PASSWORD'])
print("Admin user password reset.")
admin_user = Admin.query.first()
if not admin_user or os.environ.get('ADMIN_FORCE_RESET'):
admin_password = os.environ.get('ADMIN_PASSWORD')
if admin_password is not None:
if admin_user:
admin_user.password_hash = Admin.hash_password(admin_password)
app.logger.info("Admin user password reset.")
else:
admin_user = Admin(
username=os.environ.get('ADMIN_USERNAME'),
password_hash=Admin.hash_password(admin_password)
)
db.session.add(admin_user)
db.session.commit()
app.logger.info("Admin user initialized.")
else:
admin_user = Admin(
username=app.config['ADMIN_USERNAME'],
password_hash=Admin.hash_password(app.config['ADMIN_PASSWORD'])
)
db.session.add(admin_user)
db.session.commit()
print("Admin user initialized.")
if os.environ.get('ADMIN_FORCE_RESET'):
app.logger.warning("Admin force reset is enabled, but no password was provided. Skipping admin user initialization.")
else:
app.logger.warning("Admin user not found and no password provided. Skipping admin user initialization.")
else:
print("Admin user already exists.")
print("Database initialization complete.")
app.logger.info("No need to initialize admin user.")
app.logger.info("Database initialization complete.")
# Flask CLI command to initialize the database
@app.cli.command("init-db")
def init_db_command():
_init_db()
def run() -> None:
# Note: The database initialization is now handled by the 'init-db' command
# and should be run separately, e.g., via the entrypoint script.
# Keeping the original db.create_all() here might be redundant or could
# be useful for local development outside Docker.
# Consider if you still need the initialization logic within run().
with app.app_context():
# Explicitly import all models to ensure they're registered before db.create_all()
from .models import Inquiry, Message, Settings, Admin
db.create_all()
# Initialize settings if they don't exist
if not Settings.query.first():
default_settings = Settings(
webhook_enabled=app.config['WEBHOOK_ENABLED'],
webhook_url=app.config['WEBHOOK_URL'],
webhook_secret=app.config['WEBHOOK_SECRET']
)
db.session.add(default_settings)
db.session.commit()
# Initialize admin user if it doesn't exist
if not Admin.query.filter_by(username=app.config['ADMIN_USERNAME']).first():
admin_user = Admin(
username=app.config['ADMIN_USERNAME'],
password_hash=Admin.hash_password(app.config['ADMIN_PASSWORD'])
)
db.session.add(admin_user)
db.session.commit()
app.logger.setLevel(logging.DEBUG)
_init_db()
app.run(debug=True)

View file

@ -26,7 +26,8 @@ def run_migration(db):
'smtp_username': 'VARCHAR(255)',
'smtp_password': 'VARCHAR(255)',
'recipient_email': 'VARCHAR(255)',
'notification_show_message': 'BOOLEAN DEFAULT FALSE'
'notification_show_message': 'BOOLEAN DEFAULT FALSE',
'auto_delete_hours': 'INTEGER DEFAULT 48'
}
# Add each column if it doesn't exist

View file

@ -16,4 +16,6 @@ class Settings(db.Model):
smtp_password = db.Column(db.String(255), nullable=True) # Consider storing this securely
recipient_email = db.Column(db.String(255), nullable=True)
notification_show_message = db.Column(db.Boolean, default=False)
notification_show_message = db.Column(db.Boolean, default=False)
auto_delete_hours = db.Column(db.Integer, default=48)

View file

@ -30,6 +30,28 @@
<br>
<div class="auto-delete-settings">
<h3>Auto-Delete Settings</h3>
<div style="margin-bottom: 2rem;">
<p>Configure how long inquiries are kept before being automatically deleted.</p>
</div>
<form method="POST" action="{{ url_for('admin_settings_auto_delete') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div style="margin-top: 1rem;">
<label for="auto_delete_hours">Auto-Delete After (hours):</label>
<input type="number" id="auto_delete_hours" name="auto_delete_hours" value="{{ settings.auto_delete_hours }}" min="1" max="8760">
<small style="display: block; margin-top: 0.5rem;">Set to how many hours inquiries should be kept before automatic deletion. (1-8760 hours, 8760 = 1 year)</small>
</div>
<button type="submit" style="margin-top: 1rem;">Save Auto-Delete Settings</button>
</form>
</div>
<br>
<div class="notification-settings">
<h3>Notification Settings</h3>
@ -150,15 +172,4 @@
<button type="submit" style="margin-top: 1rem;">Save Email Settings</button>
</form>
</div>
<br>
<div class="settings-info">
<h3>System Configuration</h3>
<p>The following settings are configured through environment variables:</p>
<ul style="margin-left: 1.5rem;">
<li><strong>Auto Delete Delay:</strong> {{ config.AUTO_DELETE_HOURS }} hours (set with AUTO_DELETE_HOURS)</li>
</ul>
<p><small>To change these values, update your environment variables or .env file and restart the application.</small></p>
</div>
{% endblock %}