19 Commits
Author SHA1 Message Date
traberph 2ac9405f1c fix name of workflow 2025-07-13 16:32:42 +02:00
traberph 835a6d8b48 enforce formatting 2025-07-13 16:27:56 +02:00
traberph cf695f1518 error handling 2025-07-13 16:07:26 +02:00
traberphandGitHub 1597a99360 Merge pull request #7 from FelixDobler/main 2025-07-09 16:06:10 +02:00
28aa4d2962 Update src/api.py
undo escaping of forward slash in regex

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-09 16:01:52 +02:00
Felix DoblerandJakob 11ad5a9320 [fix] make spaetzle search case insensitive
Co-authored-by: Jakob <quantblaxk@gmail.com>
2025-07-09 14:07:13 +02:00
traberphandGitHub 5118db82e2 Merge pull request #4 from FelixDobler/main 2025-05-08 11:38:09 +02:00
Felix Dobler e4c05f310f fix typo 2025-05-08 11:32:40 +02:00
traberphandGitHub d6f282757e Merge pull request #3 from rentschler/main
change logo and fix pwa
2025-05-01 16:28:14 +02:00
Jonathan Rentschler 94ffe174c8 fix: add display standalone to mainfest json 2025-05-01 16:25:00 +02:00
Jonathan Rentschler c6f8c9f912 replace placeholder logo 2025-05-01 16:15:23 +02:00
rentschlerandGitHub 88f942eb60 Merge branch 'traberph:main' into main 2025-05-01 16:13:03 +02:00
traberphandGitHub a8aefb0e62 add manifest for pwa
add manifest for pwa
2025-05-01 16:12:07 +02:00
Jonathan Rentschler 81aa6ec59b added logo to assets folder 2025-05-01 16:10:28 +02:00
traberph 896f13a448 manifest
Signed-off-by: Philipp Traber <traber.ph@gmail.com>
2025-05-01 16:07:55 +02:00
traberphandGitHub 076ad18f10 change emoji for consistency 2025-02-19 12:05:23 +01:00
traberphandGitHub da365ac881 Fix link in readme 2025-02-19 12:00:00 +01:00
Philipp Traber 9064802962 change base image to reduce image size
Signed-off-by: Philipp Traber <philipp.traber@uni-konstanz.de>
2024-12-05 13:02:11 +01:00
Philipp Traber 4019969841 fix versions 2024-10-08 14:33:08 +02:00
12 changed files with 120 additions and 49 deletions
+31
View File
@@ -0,0 +1,31 @@
name: Ruff - Code Quality Check
on:
pull_request:
branches:
- main
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Ruff
run: |
pip install ruff
ruff format --check
ruff check
+3 -1
View File
@@ -1 +1,3 @@
.venv .venv
venv
__pycache__
+2
View File
@@ -0,0 +1,2 @@
[lint]
extend-select = ["I"]
+4
View File
@@ -0,0 +1,4 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "charliermarsh.ruff"
}
+3 -4
View File
@@ -1,7 +1,6 @@
FROM python:3 FROM python:3.12-alpine
WORKDIR /app WORKDIR /app
COPY requirements.txt ./ COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt RUN pip install -r requirements.txt --no-cache-dir
COPY src/ . COPY src/ .
CMD [ "fastapi", "run", "api.py" ] CMD [ "fastapi", "run", "api.py" ]
+5 -5
View File
@@ -1,17 +1,17 @@
# Spätzle check # Spätzle check
Me and my colleges have been eating in the Mensa at our university for a while now. If spätzle are on the menu it was always a gamble if they are good or really bad. Until somebody told us the problem of the bad spätzle is that they are vegan (no hate against vegan products but some products just shouldn't be vegan). Me and my colleagues have been eating in the Mensa at our university for a while now. If spätzle are on the menu it was always a gamble if they are good or really bad. Until somebody told us the problem of the bad spätzle is that they are vegan (no hate against vegan products but some products just shouldn't be vegan).
Because of the lack of eggs the spätzle are not sticking together and are just a big pile of mush. Because of the lack of eggs the spätzle are not sticking together and are just a big pile of mush.
Using the allergen information on the mensa website we can check if the spätzle are vegan or not and therefore can decide if one can eat them or not. Using the allergen information on the mensa website we can check if the spätzle are vegan or not and therefore can decide if one can eat them or not.
This project serves a simple [website](spaetzle.traberph.de) that checks the spätzle for you. This project serves a simple [website](https://spaetzle.traberph.de) that checks the spätzle for you.
## How to run ## How to run
Simply run it with docker-compose Simply run it with docker compose
``` ```
docker-compose up docker compose up
``` ```
+3 -3
View File
@@ -1,5 +1,5 @@
fastapi[standard]==0.114.0 fastapi[standard]==0.115.0
Jinja2==3.1.4 Jinja2==3.1.4
requests==2.32.3 requests==2.32.3
cachetools==5.5.0 cachetools==5.5.0
beautifulsoup4==4.12.3 beautifulsoup4==4.12.3
+56 -36
View File
@@ -1,58 +1,78 @@
from fastapi import FastAPI import logging
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from cachetools import cached, TTLCache
from bs4 import BeautifulSoup
import requests
import re import re
import requests
from bs4 import BeautifulSoup
from cachetools import TTLCache, cached
from fastapi import FastAPI, Request
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.templating import Jinja2Templates
logger = logging.getLogger("uvicorn.error")
cache = TTLCache(maxsize=1, ttl=3600) cache = TTLCache(maxsize=1, ttl=3600)
app = FastAPI() app = FastAPI()
templates = Jinja2Templates(directory="template") templates = Jinja2Templates(directory="template")
regex = re.compile(r'Spätzle <sup>([^/]*)<\/sup>') regex = re.compile(r"spätzle <sup>([^/]*)</sup>", re.IGNORECASE)
@cached(cache) @cached(cache)
def checkMensa(): def checkMensa():
print("Checking Mensa") logger.info("Checking Mensa")
url = 'https://seezeit.com/essen/speiseplaene/mensa-giessberg/'
r = requests.get(url)
soup = BeautifulSoup(r.text, 'html.parser')
today = soup.find('a', class_='heute')['rel'][0] try:
today_menu = soup.find('div', id=f'tab{today}') url = "https://seezeit.com/essen/speiseplaene/mensa-giessberg/"
meals = today_menu.find_all('div', class_='title') r = requests.get(url)
soup = BeautifulSoup(r.text, "html.parser")
spaetzle = None today = soup.find("a", class_="heute")["rel"][0]
for m in meals: today_menu = soup.find("div", id=f"tab{today}")
if "Spätzle" in m.text: meals = today_menu.find_all("div", class_="title")
spaetzle = m
break
if spaetzle is not None: spaetzle = None
ingredients = re.search(regex, str(spaetzle)).group(1) for m in meals:
if '28' in ingredients: if "spätzle" in m.text.lower():
answer = "Spätzle with egg today" spaetzle = m
color = "green" break
emoji = "✅"
if spaetzle is not None:
ingredients = re.search(regex, str(spaetzle)).group(1)
if "28" in ingredients:
answer = "Spätzle with egg today"
color = "green"
emoji = "✅"
else:
answer = "WARNING: Spätzle without egg today"
color = "red"
emoji = "❌"
else: else:
answer = "WARNING: Spätzle without egg today" answer = "No Spätzle today "
color = "red" color = "grey"
emoji = "❌" emoji = "🤷"
else: except Exception:
answer = "No Spätzle today " answer = "No menu today"
color = "grey" color = "grey"
emoji = "🤷🏻‍♂️" emoji = "⛱️"
finally:
logger.info(f"Answer: {answer}, Color: {color}, Emoji: {emoji}")
return answer, color, emoji
return answer, color, emoji
@app.get("/", response_class=HTMLResponse) @app.get("/", response_class=HTMLResponse)
async def root(request: Request): async def root(request: Request):
answer, color, emoji = checkMensa() answer, color, emoji = checkMensa()
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="main.html", context={"text": answer, "color": color , "emoji": emoji} request=request,
name="main.html",
context={"text": answer, "color": color, "emoji": emoji},
) )
@app.get("/manifest.json")
async def manifest(request: Request):
return FileResponse(path="assets/manifest.json")
@app.get("/icons/512.png")
async def icon(request: Request):
return FileResponse(path="assets/logo.png")
Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

+12
View File
@@ -0,0 +1,12 @@
{
"name": "Spätzle Check",
"start_url": "/",
"icons": [
{
"src": "icons/512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"display": "standalone"
}
+1
View File
@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="manifest" href="/manifest.json">
<title>{{text}} - {{emoji}}</title> <title>{{text}} - {{emoji}}</title>
<style> <style>