commit 2e3553044cdb1f2a57267f79c0070525acd13db2 Author: Jan Felix Wiebe Date: Fri Jun 13 22:53:29 2025 +0200 initial commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6b4d0b0 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +SPACEAPI_TOKEN=your-secret-token-here \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dcb26ea --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +# Use Python 3.11 slim image as base +FROM python:3.11-slim + +# Set working directory +WORKDIR /app + +# Copy requirements first to leverage Docker cache +COPY requirements.txt . + +# Install dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the rest of the application +COPY . . + +# Expose the port the app runs on +EXPOSE 5000 + +# Set environment variables +ENV FLASK_APP=app.py +ENV FLASK_ENV=production + +# Command to run the application +CMD ["flask", "run", "--host=0.0.0.0"] \ No newline at end of file diff --git a/__pycache__/spaceapi.cpython-313.pyc b/__pycache__/spaceapi.cpython-313.pyc new file mode 100644 index 0000000..3b47d14 Binary files /dev/null and b/__pycache__/spaceapi.cpython-313.pyc differ diff --git a/app.py b/app.py new file mode 100644 index 0000000..e5581b6 --- /dev/null +++ b/app.py @@ -0,0 +1,77 @@ +from flask import Flask, jsonify, request +from spaceapi import SpaceAPI, Location, Contact, State, LinkedSpace +import json +from dotenv import load_dotenv +import os + +# Load environment variables from .env file +load_dotenv() + +app = Flask(__name__) + +# Global variable to store the current state +current_state = {} + +def require_token(f): + def decorated(*args, **kwargs): + token = request.headers.get('X-SpaceAPI-Token') + if not token or token != os.getenv('SPACEAPI_TOKEN'): + return jsonify({"error": "Invalid or missing token"}), 401 + return f(*args, **kwargs) + decorated.__name__ = f.__name__ + return decorated + +@app.route('/') +def hello_world(): + space = SpaceAPI( + api_compatibility=["14", "15"], + space="Leitstelle511 - Chaos Computer Club Hannover", + logo="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Logo_CCC.svg/330px-Logo_CCC.svg.png", + url="http://hannover.ccc.de", + location=Location( + address="Klaus-Müller-Kilian-Weg 2, 30167 Hannover, Germany", + lat=52.3881, + lon=9.7181 + ), + contact=Contact( + email="kontakt@hannover.ccc.de", + irc="irc://hackint.eu/leitstelle511", + ml="511@hannover.ccc.de", + matrix="#leitstelle511-public:leitstelle511.net" + ), + state=current_state, + projects=[ + "https://hannover.ccc.de/projekte/schule/", + "https://hackover.de" + ], + linked_spaces=[ + LinkedSpace( + endpoint="https://leinelab.org/api/spaceapi.json", + website="https://leinelab.org" + ) + ] + ) + return jsonify(space.__dict__) + +@app.route('/state', methods=['POST']) +@require_token +def update_state(): + global current_state + try: + data = request.get_json() + if not data or 'open' not in data: + return jsonify({"error": "Invalid state data. 'open' field is required"}), 400 + + # Create new state with provided open status + # lastchange will be automatically set to current timestamp + current_state = State(open=data['open']) + + return jsonify({ + "message": "State updated successfully", + "state": current_state.__dict__ + }) + except Exception as e: + return jsonify({"error": str(e)}), 400 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..af44043 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +flask==3.0.2 +python-dotenv==1.0.1 +spaceapi==0.1.0 \ No newline at end of file diff --git a/spaceapi.py b/spaceapi.py new file mode 100644 index 0000000..5ae977f --- /dev/null +++ b/spaceapi.py @@ -0,0 +1,39 @@ +from dataclasses import dataclass, field +from typing import List, Optional +from datetime import datetime +import time + +@dataclass +class Location: + address: str + lat: float + lon: float + +@dataclass +class Contact: + email: str + irc: str + ml: str + matrix: str + +@dataclass +class State: + open: bool + lastchange: int = field(default_factory=lambda: int(time.time())) + +@dataclass +class LinkedSpace: + endpoint: str + website: str + +@dataclass +class SpaceAPI: + api_compatibility: List[str] + space: str + logo: str + url: str + location: Location + contact: Contact + state: State + projects: List[str] + linked_spaces: List[LinkedSpace]