initial commit
All checks were successful
Build, / build-image (push) Successful in 11s

This commit is contained in:
2026-02-22 15:01:18 +01:00
commit 23086b838e
8 changed files with 308 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
name: Build,
run-name: ${{ gitea.actor }} is building, testing and deploying the static page
on: [push]
jobs:
build-image:
runs-on: ubuntu-latest
if: gitea.ref == 'refs/heads/main'
steps:
- name: Check out repository code
uses: actions/checkout@v3
with:
github-server-url: 'https://git.secretmine.de/'
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: git.secretmine.de
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Build Image
uses: docker/build-push-action@v5
with:
file: Containerfile
context: .
push: true
tags: git.secretmine.de/secretminede/couplequestions:latest

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
venv/
data/
__pycache__/
questions/

18
Containerfile Normal file
View File

@@ -0,0 +1,18 @@
FROM python:3-slim-trixie
RUN apt-get update && \
apt-get install -y \
locales && \
rm -r /var/lib/apt/lists/*
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
sed -i -e 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/' /etc/locale.gen && \
dpkg-reconfigure --frontend=noninteractive locales
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt
COPY . /app/
CMD ["python3", "-m", "flask", "run", "--host=0.0.0.0"]

57
app.py Normal file
View File

@@ -0,0 +1,57 @@
from flask_bootstrap import Bootstrap5
from flask import Flask, render_template, request
import utils
import random
import config
app = Flask(__name__)
bootstrap = Bootstrap5(app)
utils.check_conditions()
def get_questions(categories, num):
question_pool = []
weights = []
for c in categories:
for q in utils.get_questions_by_category(c):
weight = utils.get_question_weights(c, q)
question_pool.append((c, q))
weights.append(weight)
if num > len(question_pool):
num = len(question_pool)
if num == 0:
return {}
selected_question_tuples = []
while len(selected_question_tuples) < num:
selected = random.choices(question_pool, weights)[0]
if selected not in selected_question_tuples:
selected_question_tuples.append(selected)
questions = {}
for category, question in selected_question_tuples:
utils.set_question_selected(category, question)
if category not in questions:
questions[category] = []
questions[category].append(question)
utils.save_weights()
return questions
@app.route("/", methods=["POST", "GET"])
def index():
categories = utils.get_categories()
selected = categories
n = config.get_num_questions()
questions = {}
if request.method == "POST":
selected = request.form.getlist("categories[]")
n = int(request.form.get("num"))
questions = get_questions(selected, n)
return render_template("index.html", categories=categories, selected=selected, n=n, questions=questions)
if __name__ == '__main__':
app.run()

11
config.py Normal file
View File

@@ -0,0 +1,11 @@
import os
def get_num_questions():
default_num = 5
return int(os.getenv("NUM_QUESTIONS", default_num))
def get_max_last_accessed_days():
default_days = 30
return int(os.getenv("QUESTION_LAST_ACCESSED_DAYS_MAX", default_days))

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
flask
pyyaml
bootstrap-flask

87
templates/index.html Normal file
View File

@@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
{% block head %}
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% block styles %}
<!-- Bootstrap CSS -->
{{ bootstrap.load_css() }}
{% endblock %}
<title>CoupleQuestions</title>
{% endblock %}
</head>
<body>
{% block content %}
<div class="container">
<h1>CoupleQuestions</h1>
<br>
<div class="card">
<div class="card-body">
<h4 class="card-title mb-4">Fragenkategorien auswählen</h4>
<form method="post">
<div class="mb-3">
<label class="form-label">Kategorien</label>
{% for c in categories %}
<div class="form-check">
<input class="form-check-input" type="checkbox"
value="{{ c }}" id="cat_{{ loop.index }}"
name="categories[]" {% if c in selected
%}checked{% endif %}>
<label class="form-check-label"
for="cat_{{ loop.index }}">
{{ c }}
</label>
</div>
{% endfor %}
</div>
<div class="mb-3">
<label class="form-label">Einstellungen</label>
<div class="form-check">
<input class="form-control" type="number"
value="{{ n }}" id="num" name="num">
<label class="form-label" for="num">
Anzahl der Fragen
</label>
</div>
</div>
<button type="submit" class="btn btn-primary">
Fragen auswählen!
</button>
</form>
</div>
</div>
{% if questions | length > 0 %}
<hr>
<h2>Fragen</h2>
<br>
{% for category, c_questions in questions.items() %}
<div class="card">
<div class="card-body">
<h4 class="card-title mb-4">{{ category }}</h4>
<ul>
{% for q in c_questions %}
<li>{{ q }}</li>
{% endfor %}
</ul>
</div>
</div>
<br>
{% endfor %}
{% endif %}
</div>
{% endblock %}
{% block scripts %}
{{ bootstrap.load_js() }}
{% endblock %}
</body>
</html>

102
utils.py Normal file
View File

@@ -0,0 +1,102 @@
import os
import yaml
import json
from datetime import datetime
import config
weights = None
def check_conditions():
folders = ["questions", "questions_custom"]
found_one = False
for folder in folders:
if os.path.exists(folder):
found_one = True
break
if not found_one:
print("No questions folder found.")
exit(1)
def get_questions():
folders = ["questions", "questions_custom"]
questions = {}
for folder in folders:
if not os.path.exists(folder):
continue
for filename in os.listdir(folder):
if filename.endswith(".yml") or filename.endswith(".yaml"):
file_path = os.path.join(folder, filename)
with open(file_path, "r") as f:
file_questions = yaml.safe_load(f)
for category, category_questions in file_questions.items():
if category not in questions:
questions[category] = []
questions[category].extend(category_questions)
questions[category] = list(set(questions[category]))
return questions
def get_categories():
return get_questions().keys()
def get_questions_by_category(category):
questions = get_questions()
if category not in questions:
return []
return questions[category]
def load_weights():
os.makedirs("data/", exist_ok=True)
fname = "data/weights.json"
if os.path.exists(fname):
with open(fname, "r") as fp:
return json.load(fp)
return {}
def get_all_weights():
global weights
if weights is None:
weights = load_weights()
return weights
def save_weights():
weights = get_all_weights()
os.makedirs("data/", exist_ok=True)
fname = "data/weights.json"
with open(fname, "w") as fp:
json.dump(weights, fp, indent=2)
def get_weight_index(c, q):
return f"{c}_{q}"
def get_question_weights(c, q):
max_weight = config.get_max_last_accessed_days()
min_weight = 1
weights = get_all_weights()
c_index = get_weight_index(c, q)
last_selected_ts = 0
if c_index in weights:
last_selected_ts = weights[c_index]
last_selected = datetime.fromtimestamp(last_selected_ts)
now = datetime.now()
days_passed = (now - last_selected).days
days_passed = min(max_weight, days_passed)
days_passed = max(min_weight, days_passed)
return days_passed
def set_question_selected(c, q):
weights = get_all_weights()
c_index = get_weight_index(c, q)
ts = datetime.timestamp(datetime.now())
weights[c_index] = int(ts)