add gitignore

This commit is contained in:
Jonas Heinrich 2025-08-19 09:46:19 +02:00
parent 9d23264fb2
commit bd71307c1d
5 changed files with 217 additions and 156 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules/

View file

@ -36,6 +36,10 @@
cp -r ${./templates} $out/share/fragify/
# Provide a WSGI entry file for uWSGI to load
install -Dm644 ${./fragify.py} $out/share/fragify/fragify_wsgi.py
# Install built assets if present
if [ -d ./assets ]; then
cp -r ./assets $out/share/fragify/
fi
'';
meta.mainProgram = "fragify";

View file

@ -12,162 +12,184 @@ import sys
from jinja2 import Environment, FileSystemLoader
class BaseTemplateResource:
"""Base class for resources that need template rendering"""
def _get_template_dir(self):
"""Get the template directory path, handling both development and installed environments"""
# Allow overriding via environment variable (for packaged deployments)
env_dir = os.environ.get('FRAGIFY_TEMPLATES_DIR')
if env_dir and os.path.exists(env_dir):
return env_dir
"""Base class for resources that need template rendering"""
def _get_template_dir(self):
"""Get the template directory path, handling both development and installed environments"""
# Allow overriding via environment variable (for packaged deployments)
env_dir = os.environ.get('FRAGIFY_TEMPLATES_DIR')
if env_dir and os.path.exists(env_dir):
return env_dir
# Get the directory where this script is located
script_dir = os.path.dirname(os.path.abspath(__file__))
# Try development templates first
dev_template_dir = os.path.join(script_dir, 'templates')
if os.path.exists(dev_template_dir):
return dev_template_dir
# Try to find templates relative to the executable
try:
# If we're running from a Nix store, look for templates in share/fragify
if '/nix/store/' in script_dir:
# Go up from bin to share/fragify/templates
share_dir = os.path.join(script_dir, '..', 'share', 'fragify', 'templates')
if os.path.exists(share_dir):
return share_dir
# Alternative: look for templates in the same store path
store_root = script_dir.split('/nix/store/')[1].split('/')[0]
store_path = f"/nix/store/{store_root}"
alt_share_dir = os.path.join(store_path, 'share', 'fragify', 'templates')
if os.path.exists(alt_share_dir):
return alt_share_dir
except Exception:
pass
# Last resort: try to find any templates directory
for root, dirs, files in os.walk('/nix/store'):
if 'templates' in dirs and 'index.html' in os.listdir(os.path.join(root, 'templates')):
return os.path.join(root, 'templates')
# Fallback to current directory
return dev_template_dir
# Get the directory where this script is located
script_dir = os.path.dirname(os.path.abspath(__file__))
# Try development templates first
dev_template_dir = os.path.join(script_dir, 'templates')
if os.path.exists(dev_template_dir):
return dev_template_dir
# Try to find templates relative to the executable
try:
# If we're running from a Nix store, look for templates in share/fragify
if '/nix/store/' in script_dir:
# Go up from bin to share/fragify/templates
share_dir = os.path.join(script_dir, '..', 'share', 'fragify', 'templates')
if os.path.exists(share_dir):
return share_dir
# Alternative: look for templates in the same store path
store_root = script_dir.split('/nix/store/')[1].split('/')[0]
store_path = f"/nix/store/{store_root}"
alt_share_dir = os.path.join(store_path, 'share', 'fragify', 'templates')
if os.path.exists(alt_share_dir):
return alt_share_dir
except Exception:
pass
# Last resort: try to find any templates directory
for root, dirs, files in os.walk('/nix/store'):
if 'templates' in dirs and 'index.html' in os.listdir(os.path.join(root, 'templates')):
return os.path.join(root, 'templates')
# Fallback to current directory
return dev_template_dir
class StaticResource:
def __init__(self, directory: str):
self._directory = directory
self._static = falcon.asgi.StaticRoute(directory)
def on_get(self, req, resp, path):
# Serve static files via falcon's static route helper
static_app = falcon.asgi.StaticRoute(self._directory)
return static_app(req, resp)
class FragifyApp(BaseTemplateResource):
def __init__(self):
self.fragdenstaat_api = "https://fragdenstaat.de/api/v1"
# Setup Jinja2 template environment
template_dir = self._get_template_dir()
print(f"Using template directory: {template_dir}")
self.jinja_env = Environment(loader=FileSystemLoader(template_dir))
def on_get(self, req, resp):
"""Serve the main page"""
template = self.jinja_env.get_template('index.html')
resp.content_type = 'text/html; charset=utf-8'
resp.text = template.render()
def on_post(self, req, resp):
"""Handle form submission and generate link"""
try:
# Parse form data - use get_param for form fields
publicbody_id = req.get_param('publicbody_id', default='')
subject = req.get_param('subject', default='')
body = req.get_param('body', default='')
# Generate FragDenStaat.de link
base_url = "https://fragdenstaat.de/anfrage-stellen/"
if publicbody_id:
base_url += f"an/{publicbody_id}/"
params = {}
if subject:
params['subject'] = subject
if body:
params['body'] = body
if params:
base_url += "?" + urlencode(params)
resp.content_type = 'application/json'
resp.text = json.dumps({
'success': True,
'link': base_url
})
except Exception as e:
resp.status = falcon.HTTP_500
resp.content_type = 'application/json'
resp.text = json.dumps({
'success': False,
'error': str(e)
})
def __init__(self):
self.fragdenstaat_api = "https://fragdenstaat.de/api/v1"
# Setup Jinja2 template environment
template_dir = self._get_template_dir()
print(f"Using template directory: {template_dir}")
self.jinja_env = Environment(loader=FileSystemLoader(template_dir))
def on_get(self, req, resp):
"""Serve the main page"""
template = self.jinja_env.get_template('index.html')
resp.content_type = 'text/html; charset=utf-8'
resp.text = template.render()
def on_post(self, req, resp):
"""Handle form submission and generate link"""
try:
# Parse form data - use get_param for form fields
publicbody_id = req.get_param('publicbody_id', default='')
subject = req.get_param('subject', default='')
body = req.get_param('body', default='')
# Generate FragDenStaat.de link
base_url = "https://fragdenstaat.de/anfrage-stellen/"
if publicbody_id:
base_url += f"an/{publicbody_id}/"
params = {}
if subject:
params['subject'] = subject
if body:
params['body'] = body
if params:
base_url += "?" + urlencode(params)
resp.content_type = 'application/json'
resp.text = json.dumps({
'success': True,
'link': base_url
})
except Exception as e:
resp.status = falcon.HTTP_500
resp.content_type = 'application/json'
resp.text = json.dumps({
'success': False,
'error': str(e)
})
class ImpressumResource(BaseTemplateResource):
def __init__(self):
template_dir = self._get_template_dir()
self.jinja_env = Environment(loader=FileSystemLoader(template_dir))
def on_get(self, req, resp):
"""Serve the Impressum page"""
template = self.jinja_env.get_template('impressum.html')
resp.content_type = 'text/html; charset=utf-8'
resp.text = template.render()
def __init__(self):
template_dir = self._get_template_dir()
self.jinja_env = Environment(loader=FileSystemLoader(template_dir))
def on_get(self, req, resp):
"""Serve the Impressum page"""
template = self.jinja_env.get_template('impressum.html')
resp.content_type = 'text/html; charset=utf-8'
resp.text = template.render()
class DatenschutzResource(BaseTemplateResource):
def __init__(self):
template_dir = self._get_template_dir()
self.jinja_env = Environment(loader=FileSystemLoader(template_dir))
def on_get(self, req, resp):
"""Serve the Datenschutz page"""
template = self.jinja_env.get_template('datenschutz.html')
resp.content_type = 'text/html; charset=utf-8'
resp.text = template.render()
def __init__(self):
template_dir = self._get_template_dir()
self.jinja_env = Environment(loader=FileSystemLoader(template_dir))
def on_get(self, req, resp):
"""Serve the Datenschutz page"""
template = self.jinja_env.get_template('datenschutz.html')
resp.content_type = 'text/html; charset=utf-8'
resp.text = template.render()
class PublicBodiesResource:
def __init__(self):
self.fragdenstaat_api = "https://fragdenstaat.de/api/v1"
def on_get(self, req, resp):
"""API endpoint to search public bodies"""
try:
search = req.get_param('search', default='')
page = req.get_param('page', default=1)
# Build API URL
url = f"{self.fragdenstaat_api}/publicbody/"
params = {
'limit': 20,
'offset': (int(page) - 1) * 20
}
if search:
params['q'] = search
# Make request to FragDenStaat API
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
resp.content_type = 'application/json'
resp.text = json.dumps(data)
except Exception as e:
resp.status = falcon.HTTP_500
resp.content_type = 'application/json'
resp.text = json.dumps({
'error': str(e),
'results': [],
'next': None
})
def __init__(self):
self.fragdenstaat_api = "https://fragdenstaat.de/api/v1"
def on_get(self, req, resp):
"""API endpoint to search public bodies"""
try:
search = req.get_param('search', default='')
page = req.get_param('page', default=1)
# Build API URL
url = f"{self.fragdenstaat_api}/publicbody/"
params = {
'limit': 20,
'offset': (int(page) - 1) * 20
}
if search:
params['q'] = search
# Make request to FragDenStaat API
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
resp.content_type = 'application/json'
resp.text = json.dumps(data)
except Exception as e:
resp.status = falcon.HTTP_500
resp.content_type = 'application/json'
resp.text = json.dumps({
'error': str(e),
'results': [],
'next': None
})
# Create Falcon application
app = falcon.App()
# Discover static assets directory
STATIC_DIR = os.environ.get('FRAGIFY_STATIC_DIR')
if not STATIC_DIR:
# Prefer local assets folder in development
script_dir = os.path.dirname(os.path.abspath(__file__))
candidate = os.path.join(script_dir, 'assets')
if os.path.isdir(candidate):
STATIC_DIR = candidate
else:
# Fallback to packaged location under share
STATIC_DIR = os.path.join(script_dir, '..', 'share', 'fragify', 'assets')
# Add routes
fragify = FragifyApp()
impressum = ImpressumResource()
@ -179,11 +201,16 @@ app.add_route('/impressum', impressum)
app.add_route('/datenschutz', datenschutz)
app.add_route('/api/publicbodies', publicbodies)
# Static file route
if STATIC_DIR and os.path.isdir(STATIC_DIR):
app.add_static_route('/static', STATIC_DIR)
if __name__ == '__main__':
import wsgiref.simple_server
print("Starting Fragify web application...")
print("Open your browser and navigate to: http://localhost:8000")
httpd = wsgiref.simple_server.make_server('localhost', 8000, app)
httpd.serve_forever()
import wsgiref.simple_server
print("Starting Fragify web application...")
print("Open your browser and navigate to: http://localhost:8000")
print(f"Serving static assets from: {STATIC_DIR}")
httpd = wsgiref.simple_server.make_server('localhost', 8000, app)
httpd.serve_forever()

View file

@ -4,9 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Fragify{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet">
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/css/select2.min.css" rel="stylesheet">
<link href="/static/css/select2-bootstrap-5-theme.min.css" rel="stylesheet">
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@ -63,6 +63,34 @@
margin-top: 1rem;
word-break: break-all;
}
/* Ensure Select2 scales properly */
.select2-container {
width: 100% !important;
}
.select2-container .select2-selection--single {
height: auto;
min-height: 2.75rem;
}
.select2-container .select2-selection__rendered {
white-space: normal;
line-height: 1.4 !important;
}
.select2-container .select2-selection__arrow {
height: 100% !important;
}
/* Mobile adjustments */
@media (max-width: 576px) {
.main-container {
padding: 1.25rem;
margin: 1rem auto 4rem auto;
}
.title {
font-size: 2rem;
}
.description {
font-size: 1rem;
}
}
.loading {
display: none;
}
@ -139,9 +167,9 @@
</footer>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script src="/static/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/select2.min.js"></script>
{% block extra_js %}{% endblock %}
</body>

View file

@ -57,6 +57,7 @@
theme: 'bootstrap-5',
placeholder: 'Behörde auswählen...',
allowClear: true,
width: '100%',
ajax: {
url: '/api/publicbodies',
dataType: 'json',