gemini (kostenintensives) app-aufbauen

This commit is contained in:
br committed 2026-08-01 16:42:30 +02:00
1 parent c74bca1e13
commit 8232f4d8f7
11 files changed
+659 -519

No files matched your search

+150
View File
@@ -0,0 +1,150 @@
# ==============================================================================
# Formeleon - Multi-tenant Form Generator
# Author: Björn Runden <br@runden.net>
# Module: utils/cli.py
# Description: CLI Commands for database seeding and administration.
# ==============================================================================
import uuid
import click
from flask.cli import with_appcontext
from models import db, Tenant, User, UserRole, FormConfig, TenantStatus
def register_cli_commands(app):
"""
Register custom CLI commands for the application.
:param app: Flask application instance
"""
@app.cli.command('seed-db')
@with_appcontext
def seed_db():
"""Seed the database with test data for development."""
# Check if data already exists
if Tenant.query.first():
print('Database already contains data. Use --force to overwrite.')
return
# Create dev tenant
tenant = Tenant(
id='tenant_dev_123',
name='Development Tenant',
public_key='dev-public-key-12345',
allowed_origins='http://localhost:5000,http://localhost:8080',
status=TenantStatus.ACTIVE
)
db.session.add(tenant)
# Create sample form
sample_form = FormConfig(
id='form_sample_001',
tenant_id='tenant_dev_123',
name='Contact Form',
description='Sample contact form for testing',
form_structure={
'version': '1.0',
'fields': [
{
'name': 'name',
'type': 'text',
'label': 'Your Name',
'required': True,
'validation': {'min_length': 2, 'max_length': 100}
},
{
'name': 'email',
'type': 'email',
'label': 'Email Address',
'required': True,
'validation': {'pattern': r'^[^@]+@[^@]+\.[^@]+$'}
},
{
'name': 'subject',
'type': 'text',
'label': 'Subject',
'required': True
},
{
'name': 'message',
'type': 'textarea',
'label': 'Message',
'required': True,
'validation': {'min_length': 10}
}
],
'submit_button': {
'text': 'Send Message',
'style': 'primary'
}
},
settings={
'email_notification': {
'enabled': False,
'recipients': ['admin@formeleon.local']
},
'success_message': 'Thank you for your message!',
'recaptcha_enabled': True
}
)
db.session.add(sample_form)
# Create dev admin user
admin_user = User(
id='user_dev_admin_001',
email=app.config.get('DEV_USER_EMAIL', 'dev@formeleon.local'),
name=app.config.get('DEV_USER_NAME', 'Dev Admin'),
role=UserRole.ADMIN,
sub='dev-admin-sub-123'
)
db.session.add(admin_user)
# Associate user with tenant
from models import user_tenant_association
db.session.execute(
user_tenant_association.insert().values(
user_id=admin_user.id,
tenant_id=tenant.id
)
)
db.session.commit()
print(f'Seeded database with tenant {tenant.id}, form {sample_form.id}, and user {admin_user.id}')
@app.cli.command('create-tenant')
@click.argument('name')
@click.argument('public_key')
@click.option('--allowed-origins', default='*', help='Comma-separated allowed origins')
@with_appcontext
def create_tenant(name, public_key, allowed_origins):
"""Create a new tenant."""
tenant_id = f'tenant_{name.lower().replace(" ", "_")}_{str(uuid.uuid4())[:8]}'
tenant = Tenant(
id=tenant_id,
name=name,
public_key=public_key,
allowed_origins=allowed_origins
)
db.session.add(tenant)
db.session.commit()
print(f'Created tenant {tenant_id} with public key {public_key}')
@app.cli.command('create-user')
@click.argument('email')
@click.argument('name')
@click.option('--role', default='user', help='User role: admin, tenant_admin, user, viewer')
@with_appcontext
def create_user(email, name, role):
"""Create a new user."""
from models import UserRole as UR
role_enum = getattr(UR, role.upper(), UR.USER)
user_id = f'user_{email.split("@")[0]}_{str(uuid.uuid4())[:8]}'
user = User(
id=user_id,
email=email,
name=name,
role=role_enum
)
db.session.add(user)
db.session.commit()
print(f'Created user {user_id} with email {email} and role {role}')
+34
View File
@@ -0,0 +1,34 @@
# ==============================================================================
# Formeleon - Multi-tenant Form Generator
# Author: Björn Runden <br@runden.net>
# Module: utils/helpers.py
# Description: Helper and utility functions for the application.
# ==============================================================================
import hashlib
from typing import Optional
from flask import request
def hash_ip(ip: str) -> str:
"""
Create a hash of an IP address for privacy-preserving storage.
:param ip: IP address string
:return: SHA256 hash of the IP
"""
if not ip:
return ''
return hashlib.sha256(ip.encode()).hexdigest()
def get_request_context() -> dict:
"""
Get standardized request context for audit logging.
:return: Dictionary with request context information
"""
return {
'ip_address': request.remote_addr,
'user_agent': request.user_agent.string if request.user_agent else None,
'path': request.path,
'method': request.method
}
+97
View File
@@ -0,0 +1,97 @@
# ==============================================================================
# Formeleon - Multi-tenant Form Generator
# Author: Björn Runden <br@runden.net>
# Module: utils/middleware.py
# Description: Custom middlewares and context processors for the Flask app.
# ==============================================================================
from flask import session, request
from typing import Optional
from models import db, User, UserRole, Tenant
def setup_dev_mode_middleware(app):
"""
Set up development mode middleware for fake authentication.
This middleware injects a fake Zitadel user session when DEV_MODE is enabled,
bypassing the need for actual OIDC authentication during development.
:param app: Flask application instance
"""
@app.before_request
def inject_dev_user():
"""Inject dev user into session if DEV_MODE is enabled."""
# Skip for static files, auth routes, and API routes
if (request.path.startswith('/static/') or
request.path.startswith('/auth/') or
request.path.startswith('/api/')):
return
# Skip if user is already authenticated
if 'user_id' in session:
return
# Inject dev user
dev_user = {
'id': 'user_dev_admin_001',
'email': app.config.get('DEV_USER_EMAIL', 'dev@formeleon.local'),
'name': app.config.get('DEV_USER_NAME', 'Dev Admin'),
'role': app.config.get('DEV_USER_ROLE', 'admin'),
'sub': 'dev-admin-sub-123',
'tenant_id': app.config.get('DEV_TENANT_ID', 'tenant_dev_123'),
'dev_mode': True
}
session['user_id'] = dev_user['id']
session['user'] = dev_user
session.permanent = True
# Load or create dev user in database
user = User.query.get(dev_user['id'])
if not user:
role = UserRole.ADMIN if dev_user['role'] == 'admin' else UserRole.USER
user = User(
id=dev_user['id'],
email=dev_user['email'],
name=dev_user['name'],
role=role,
sub=dev_user['sub']
)
db.session.add(user)
db.session.commit()
# Ensure dev tenant exists
tenant = Tenant.query.get(dev_user['tenant_id'])
if not tenant:
tenant = Tenant(
id=dev_user['tenant_id'],
name='Development Tenant',
public_key='dev-public-key-12345',
allowed_origins='*'
)
db.session.add(tenant)
db.session.commit()
def get_current_user() -> Optional[User]:
"""
Get the current authenticated user from the request context.
:return: User object if authenticated, None otherwise
"""
if 'user_id' not in session:
return None
return User.query.get(session['user_id'])
def get_current_tenant() -> Optional[Tenant]:
"""
Get the current tenant from the request context.
:return: Tenant object if available, None otherwise
"""
if 'tenant_id' in session:
return Tenant.query.get(session['tenant_id'])
user = get_current_user()
if user and user.tenants:
return user.tenants[0]
return None