add gitignore
This commit is contained in:
parent
9d23264fb2
commit
bd71307c1d
5 changed files with 217 additions and 156 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
node_modules/
|
||||||
|
|
@ -36,6 +36,10 @@
|
||||||
cp -r ${./templates} $out/share/fragify/
|
cp -r ${./templates} $out/share/fragify/
|
||||||
# Provide a WSGI entry file for uWSGI to load
|
# Provide a WSGI entry file for uWSGI to load
|
||||||
install -Dm644 ${./fragify.py} $out/share/fragify/fragify_wsgi.py
|
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";
|
meta.mainProgram = "fragify";
|
||||||
|
|
|
||||||
279
fragify.py
279
fragify.py
|
|
@ -12,162 +12,184 @@ import sys
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
class BaseTemplateResource:
|
class BaseTemplateResource:
|
||||||
"""Base class for resources that need template rendering"""
|
"""Base class for resources that need template rendering"""
|
||||||
|
|
||||||
def _get_template_dir(self):
|
def _get_template_dir(self):
|
||||||
"""Get the template directory path, handling both development and installed environments"""
|
"""Get the template directory path, handling both development and installed environments"""
|
||||||
# Allow overriding via environment variable (for packaged deployments)
|
# Allow overriding via environment variable (for packaged deployments)
|
||||||
env_dir = os.environ.get('FRAGIFY_TEMPLATES_DIR')
|
env_dir = os.environ.get('FRAGIFY_TEMPLATES_DIR')
|
||||||
if env_dir and os.path.exists(env_dir):
|
if env_dir and os.path.exists(env_dir):
|
||||||
return env_dir
|
return env_dir
|
||||||
|
|
||||||
# Get the directory where this script is located
|
# Get the directory where this script is located
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
# Try development templates first
|
# Try development templates first
|
||||||
dev_template_dir = os.path.join(script_dir, 'templates')
|
dev_template_dir = os.path.join(script_dir, 'templates')
|
||||||
if os.path.exists(dev_template_dir):
|
if os.path.exists(dev_template_dir):
|
||||||
return dev_template_dir
|
return dev_template_dir
|
||||||
|
|
||||||
# Try to find templates relative to the executable
|
# Try to find templates relative to the executable
|
||||||
try:
|
try:
|
||||||
# If we're running from a Nix store, look for templates in share/fragify
|
# If we're running from a Nix store, look for templates in share/fragify
|
||||||
if '/nix/store/' in script_dir:
|
if '/nix/store/' in script_dir:
|
||||||
# Go up from bin to share/fragify/templates
|
# Go up from bin to share/fragify/templates
|
||||||
share_dir = os.path.join(script_dir, '..', 'share', 'fragify', 'templates')
|
share_dir = os.path.join(script_dir, '..', 'share', 'fragify', 'templates')
|
||||||
if os.path.exists(share_dir):
|
if os.path.exists(share_dir):
|
||||||
return share_dir
|
return share_dir
|
||||||
|
|
||||||
# Alternative: look for templates in the same store path
|
# Alternative: look for templates in the same store path
|
||||||
store_root = script_dir.split('/nix/store/')[1].split('/')[0]
|
store_root = script_dir.split('/nix/store/')[1].split('/')[0]
|
||||||
store_path = f"/nix/store/{store_root}"
|
store_path = f"/nix/store/{store_root}"
|
||||||
alt_share_dir = os.path.join(store_path, 'share', 'fragify', 'templates')
|
alt_share_dir = os.path.join(store_path, 'share', 'fragify', 'templates')
|
||||||
if os.path.exists(alt_share_dir):
|
if os.path.exists(alt_share_dir):
|
||||||
return alt_share_dir
|
return alt_share_dir
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Last resort: try to find any templates directory
|
# Last resort: try to find any templates directory
|
||||||
for root, dirs, files in os.walk('/nix/store'):
|
for root, dirs, files in os.walk('/nix/store'):
|
||||||
if 'templates' in dirs and 'index.html' in os.listdir(os.path.join(root, 'templates')):
|
if 'templates' in dirs and 'index.html' in os.listdir(os.path.join(root, 'templates')):
|
||||||
return os.path.join(root, 'templates')
|
return os.path.join(root, 'templates')
|
||||||
|
|
||||||
# Fallback to current directory
|
# Fallback to current directory
|
||||||
return dev_template_dir
|
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):
|
class FragifyApp(BaseTemplateResource):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.fragdenstaat_api = "https://fragdenstaat.de/api/v1"
|
self.fragdenstaat_api = "https://fragdenstaat.de/api/v1"
|
||||||
# Setup Jinja2 template environment
|
# Setup Jinja2 template environment
|
||||||
template_dir = self._get_template_dir()
|
template_dir = self._get_template_dir()
|
||||||
print(f"Using template directory: {template_dir}")
|
print(f"Using template directory: {template_dir}")
|
||||||
self.jinja_env = Environment(loader=FileSystemLoader(template_dir))
|
self.jinja_env = Environment(loader=FileSystemLoader(template_dir))
|
||||||
|
|
||||||
def on_get(self, req, resp):
|
def on_get(self, req, resp):
|
||||||
"""Serve the main page"""
|
"""Serve the main page"""
|
||||||
template = self.jinja_env.get_template('index.html')
|
template = self.jinja_env.get_template('index.html')
|
||||||
resp.content_type = 'text/html; charset=utf-8'
|
resp.content_type = 'text/html; charset=utf-8'
|
||||||
resp.text = template.render()
|
resp.text = template.render()
|
||||||
|
|
||||||
def on_post(self, req, resp):
|
def on_post(self, req, resp):
|
||||||
"""Handle form submission and generate link"""
|
"""Handle form submission and generate link"""
|
||||||
try:
|
try:
|
||||||
# Parse form data - use get_param for form fields
|
# Parse form data - use get_param for form fields
|
||||||
publicbody_id = req.get_param('publicbody_id', default='')
|
publicbody_id = req.get_param('publicbody_id', default='')
|
||||||
subject = req.get_param('subject', default='')
|
subject = req.get_param('subject', default='')
|
||||||
body = req.get_param('body', default='')
|
body = req.get_param('body', default='')
|
||||||
|
|
||||||
# Generate FragDenStaat.de link
|
# Generate FragDenStaat.de link
|
||||||
base_url = "https://fragdenstaat.de/anfrage-stellen/"
|
base_url = "https://fragdenstaat.de/anfrage-stellen/"
|
||||||
if publicbody_id:
|
if publicbody_id:
|
||||||
base_url += f"an/{publicbody_id}/"
|
base_url += f"an/{publicbody_id}/"
|
||||||
|
|
||||||
params = {}
|
params = {}
|
||||||
if subject:
|
if subject:
|
||||||
params['subject'] = subject
|
params['subject'] = subject
|
||||||
if body:
|
if body:
|
||||||
params['body'] = body
|
params['body'] = body
|
||||||
|
|
||||||
if params:
|
if params:
|
||||||
base_url += "?" + urlencode(params)
|
base_url += "?" + urlencode(params)
|
||||||
|
|
||||||
resp.content_type = 'application/json'
|
resp.content_type = 'application/json'
|
||||||
resp.text = json.dumps({
|
resp.text = json.dumps({
|
||||||
'success': True,
|
'success': True,
|
||||||
'link': base_url
|
'link': base_url
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
resp.status = falcon.HTTP_500
|
resp.status = falcon.HTTP_500
|
||||||
resp.content_type = 'application/json'
|
resp.content_type = 'application/json'
|
||||||
resp.text = json.dumps({
|
resp.text = json.dumps({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
})
|
})
|
||||||
|
|
||||||
class ImpressumResource(BaseTemplateResource):
|
class ImpressumResource(BaseTemplateResource):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
template_dir = self._get_template_dir()
|
template_dir = self._get_template_dir()
|
||||||
self.jinja_env = Environment(loader=FileSystemLoader(template_dir))
|
self.jinja_env = Environment(loader=FileSystemLoader(template_dir))
|
||||||
|
|
||||||
def on_get(self, req, resp):
|
def on_get(self, req, resp):
|
||||||
"""Serve the Impressum page"""
|
"""Serve the Impressum page"""
|
||||||
template = self.jinja_env.get_template('impressum.html')
|
template = self.jinja_env.get_template('impressum.html')
|
||||||
resp.content_type = 'text/html; charset=utf-8'
|
resp.content_type = 'text/html; charset=utf-8'
|
||||||
resp.text = template.render()
|
resp.text = template.render()
|
||||||
|
|
||||||
class DatenschutzResource(BaseTemplateResource):
|
class DatenschutzResource(BaseTemplateResource):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
template_dir = self._get_template_dir()
|
template_dir = self._get_template_dir()
|
||||||
self.jinja_env = Environment(loader=FileSystemLoader(template_dir))
|
self.jinja_env = Environment(loader=FileSystemLoader(template_dir))
|
||||||
|
|
||||||
def on_get(self, req, resp):
|
def on_get(self, req, resp):
|
||||||
"""Serve the Datenschutz page"""
|
"""Serve the Datenschutz page"""
|
||||||
template = self.jinja_env.get_template('datenschutz.html')
|
template = self.jinja_env.get_template('datenschutz.html')
|
||||||
resp.content_type = 'text/html; charset=utf-8'
|
resp.content_type = 'text/html; charset=utf-8'
|
||||||
resp.text = template.render()
|
resp.text = template.render()
|
||||||
|
|
||||||
class PublicBodiesResource:
|
class PublicBodiesResource:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.fragdenstaat_api = "https://fragdenstaat.de/api/v1"
|
self.fragdenstaat_api = "https://fragdenstaat.de/api/v1"
|
||||||
|
|
||||||
def on_get(self, req, resp):
|
def on_get(self, req, resp):
|
||||||
"""API endpoint to search public bodies"""
|
"""API endpoint to search public bodies"""
|
||||||
try:
|
try:
|
||||||
search = req.get_param('search', default='')
|
search = req.get_param('search', default='')
|
||||||
page = req.get_param('page', default=1)
|
page = req.get_param('page', default=1)
|
||||||
|
|
||||||
# Build API URL
|
# Build API URL
|
||||||
url = f"{self.fragdenstaat_api}/publicbody/"
|
url = f"{self.fragdenstaat_api}/publicbody/"
|
||||||
params = {
|
params = {
|
||||||
'limit': 20,
|
'limit': 20,
|
||||||
'offset': (int(page) - 1) * 20
|
'offset': (int(page) - 1) * 20
|
||||||
}
|
}
|
||||||
|
|
||||||
if search:
|
if search:
|
||||||
params['q'] = search
|
params['q'] = search
|
||||||
|
|
||||||
# Make request to FragDenStaat API
|
# Make request to FragDenStaat API
|
||||||
response = requests.get(url, params=params, timeout=10)
|
response = requests.get(url, params=params, timeout=10)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
resp.content_type = 'application/json'
|
resp.content_type = 'application/json'
|
||||||
resp.text = json.dumps(data)
|
resp.text = json.dumps(data)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
resp.status = falcon.HTTP_500
|
resp.status = falcon.HTTP_500
|
||||||
resp.content_type = 'application/json'
|
resp.content_type = 'application/json'
|
||||||
resp.text = json.dumps({
|
resp.text = json.dumps({
|
||||||
'error': str(e),
|
'error': str(e),
|
||||||
'results': [],
|
'results': [],
|
||||||
'next': None
|
'next': None
|
||||||
})
|
})
|
||||||
|
|
||||||
# Create Falcon application
|
# Create Falcon application
|
||||||
app = falcon.App()
|
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
|
# Add routes
|
||||||
fragify = FragifyApp()
|
fragify = FragifyApp()
|
||||||
impressum = ImpressumResource()
|
impressum = ImpressumResource()
|
||||||
|
|
@ -179,11 +201,16 @@ app.add_route('/impressum', impressum)
|
||||||
app.add_route('/datenschutz', datenschutz)
|
app.add_route('/datenschutz', datenschutz)
|
||||||
app.add_route('/api/publicbodies', publicbodies)
|
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__':
|
if __name__ == '__main__':
|
||||||
import wsgiref.simple_server
|
import wsgiref.simple_server
|
||||||
|
|
||||||
print("Starting Fragify web application...")
|
print("Starting Fragify web application...")
|
||||||
print("Open your browser and navigate to: http://localhost:8000")
|
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 = wsgiref.simple_server.make_server('localhost', 8000, app)
|
||||||
httpd.serve_forever()
|
httpd.serve_forever()
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}Fragify{% endblock %}</title>
|
<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="/static/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="/static/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/select2-bootstrap-5-theme.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
|
@ -63,6 +63,34 @@
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
word-break: break-all;
|
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 {
|
.loading {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
@ -139,9 +167,9 @@
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="/static/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="/static/js/jquery.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/select2.min.js"></script>
|
||||||
|
|
||||||
{% block extra_js %}{% endblock %}
|
{% block extra_js %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@
|
||||||
theme: 'bootstrap-5',
|
theme: 'bootstrap-5',
|
||||||
placeholder: 'Behörde auswählen...',
|
placeholder: 'Behörde auswählen...',
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
|
width: '100%',
|
||||||
ajax: {
|
ajax: {
|
||||||
url: '/api/publicbodies',
|
url: '/api/publicbodies',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue