Compare commits

...

9 Commits

Author SHA1 Message Date
ba1d36b71a bs 2025-10-02 08:20:36 +02:00
c27a054497 bs 2025-09-12 09:10:37 +02:00
a0095a993a Merge remote-tracking branch 'origin/main' 2025-09-12 09:02:18 +02:00
db6b16d261 hallo 2025-09-12 09:02:09 +02:00
PuppyLuny
b4245f2c29 data 2025-09-12 08:56:55 +02:00
8db70323a1 hallo 2025-09-12 08:54:30 +02:00
03da907c73 hallo 2025-09-12 08:40:47 +02:00
fb60f8582d hallo 2025-09-12 08:27:41 +02:00
f59275deea DOCKER 2025-09-11 09:07:12 +02:00
12 changed files with 357 additions and 37 deletions

View File

@@ -1,21 +1,21 @@
FROM python:3.11-slim
# Arbeitsverzeichnis
WORKDIR /app
# Git installieren
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
# Systemabhängigkeiten (optional, falls gebraucht)
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Repository klonen
ARG REPO_URL
RUN git clone ${REPO_URL} /app
# Requirements installieren
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Abhängigkeiten installieren (falls requirements.txt existiert)
RUN pip install --no-cache-dir -r requirements.txt || true
# Code kopieren
COPY . .
# Entrypoint-Skript für git pull beim Start
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENV FLASK_APP=app.py
ENV FLASK_ENV=development
EXPOSE 5000
ENTRYPOINT ["/entrypoint.sh"]
CMD ["flask", "run", "--host=0.0.0.0"]

32
app.py
View File

@@ -1,12 +1,36 @@
from flask import Flask, render_template, request
from flask import Flask, render_template, request, url_for, redirect, session
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import declarative_base, sessionmaker
app = Flask(__name__)
app.secret_key = "HURENSOHN"
@app.route('/')
def hello_world(): # put application's code here
return render_template("index.html")
def index():
return render_template('base.html',
site_name='Mein Spiel Wiki',
current_year=2025,
popular_items=[],
categories=[]
)
@app.route('/login', methods=['GET', 'POST'])
def login():
return render_template("login.html")
@app.route('/register', methods=['GET', 'POST'])
def register():
return render_template("register.html")
@app.route('/logout')
def logout():
session.pop()
return redirect(url_for('index'))
@app.route('/search', methods=['POST'])
def search():
return ""
if __name__ == '__main__':
app.run(debug=True)

View File

@@ -8,7 +8,7 @@ services:
volumes:
- ".:/app"
ports:
- "5000:5000"
- "5001:5000"
environment:
- FLASK_APP=app.py
- FLASK_ENV=development
@@ -28,7 +28,5 @@ services:
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:

View File

@@ -0,0 +1,3 @@
flask
psycopg2-binary
sqlalchemy

30
src/Database/Database.py Normal file
View File

@@ -0,0 +1,30 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
class Database:
def __init__(self, connection_string: dict):
"""
connection_string ist ein Dictionary, z. B.:
{
"driver": "postgresql+psycopg2",
"username": "postgres",
"password": "mein_passwort",
"host": "localhost",
"port": 5432,
"database": "myapp"
}
"""
self.connection_string = connection_string
self.engine = self._create_engine()
self.SessionLocal = sessionmaker(bind=self.engine)
def _create_engine(self):
conn = self.connection_string
url = f"{conn['driver']}://{conn['username']}:{conn['password']}@{conn['host']}:{conn['port']}/{conn['database']}"
return create_engine(url, echo=True)
def get_engine(self):
return self.engine
def get_session(self):
return self.SessionLocal()

15
src/Database/User.py Normal file
View File

@@ -0,0 +1,15 @@
from sqlalchemy import Column, Integer, String, DateTime, func
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
username = Column(String(50), unique=True, nullable=False)
password = Column(String(255), nullable=False)
created_at = Column(DateTime, server_default=func.now())
def __repr__(self):
return f"<User(id={self.id}, username='{self.username}')>"

BIN
static/HURENSOHN.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

View File

@@ -0,0 +1,88 @@
/* Bootstrap Dark Mode aktivieren */
:root, [data-bs-theme=dark] {
--bs-body-bg: #0f1724; /* Hintergrund */
--bs-body-color: #e6eef8; /* Textfarbe */
--bs-border-color: rgba(255,255,255,0.1);
--bs-link-color: #ffcc33;
--bs-link-hover-color: #ffdb66;
/* Deine Custom-Werte */
--accent: #ffcc33;
}
/* Container */
.wrap {
max-width: 1200px;
margin: 28px auto;
padding: 20px;
}
/* Topbar */
header.topbar {
@extend .d-flex, .align-items-center, .justify-content-between, .rounded-3, .p-3;
background: var(--bs-dark-bg-subtle, rgba(255,255,255,0.05));
}
.logo {
width: 48px;
height: 48px;
border-radius: 8px;
background: linear-gradient(135deg, var(--accent), #ff9a3c);
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
color: #081022;
}
/* Layout */
.grid {
display: grid;
grid-template-columns: 1fr 320px;
gap: 18px;
margin-top: 18px;
}
main.card, aside.sidebar {
background: var(--bs-dark-bg-subtle, rgba(255,255,255,0.05));
padding: 18px;
border-radius: 12px;
}
/* Hero */
.hero {
height: 180px;
border-radius: 8px;
background-size: cover;
background-position: center;
margin-bottom: 12px;
overflow: hidden;
}
/* Helpers */
a.button {
display: inline-block;
padding: 8px 12px;
border-radius: 999px;
background: var(--accent);
color: #081022;
text-decoration: none;
font-weight: 600;
}
nav a {
color: var(--bs-secondary-color);
text-decoration: none;
margin-right: 12px;
}
/* Responsive */
@media (max-width: 880px) {
.grid {
grid-template-columns: 1fr;
}
.search {
display: none;
}
}

View File

@@ -1,19 +1,137 @@
<html lang="de">
<!doctype html>
<html lang="de" data-bs-theme=dark>
<head>
<link rel="stylesheet" href="/css/style.css">
<meta charstet="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{% block title %}Spiel-Fandom{% endblock %} — {{ site_name or 'My Game Wiki' }}</title>
<meta name="description"
content="{% block description %}Fandom-Seite für {{ site_name or 'ein Spiel' }}{% endblock %}">
<title> {% block title %} {% endblock %} </title>
<link rel="preconnect" href="https://fonts.gstatic.com">
<link rel="stylesheet" href="/static/css/style.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700;900&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<style>
</style>
{% block extra_head %}{% endblock %}
</head>
<body>
<div class="wrap">
<header class="topbar d-flex justify-content-between p-3">
<div class="brand">
<div class="logo">GF</div>
<div>
<h1>{{ site_name or 'Game Fandom' }}</h1>
<div style="font-size:0.85rem;color:var(--muted)">{% block tagline %}Community-geführtes Wiki &
Guides{% endblock %}</div>
</div>
</div>
<h1> Herzlich Willkommen auf der Website der BI23b! </h1>
<p> Zurücklehnen und genießen... </p>
<div class="search m-2 d-flex align-items-end" role="search">
<div>
{% block login_logout %}
{% endblock %}
</div>
<form action="{{ url_for('search') }}" method="get">
<input name="q" placeholder="Suche Artikel, Charaktere, Items..." aria-label="Suche"
value="{{ request.args.get('q','') }}" class="form-control">
</form>
</div>
</header>
<nav style="margin-top:12px;color:var(--muted)">
<a href="{{ url_for('index') }}">Startseite</a>
</nav>
<div class="grid">
<main class="card border-0">
{% block hero %}
<div class="hero"
style="background-image: url('{{ url_for('static', filename='images/hero.jpg') }}');"></div>
{% endblock %}
<article>
<h2 class="title">{% block page_title %}Willkommen{% endblock %}</h2>
<div class="meta">{% block meta %}Letzte Aktualisierung: —{% endblock %}</div>
{% block content %}
<p>Hier kommt der Seiteninhalt.</p>
{% endblock %}
</article>
<section style="margin-top:18px">
<h3>Beliebte Artikel</h3>
<div class="card-row">
{% for item in popular_items|default([]) %}
<div class="mini-card">
<a href="{{ url_for('article', slug=item.slug) }}"><strong>{{ item.title }}</strong></a>
<div style="color:var(--muted);font-size:0.9rem">{{ item.short_desc }}</div>
</div>
{% else %}
<div class="mini-card">Keine Daten.</div>
{% endfor %}
</div>
</section>
</main>
<aside class="sidebar">
<div style="margin-bottom:12px">
<h4>Neueste Diskussionen</h4>
<ul style="padding-left:14px;color:var(--muted);margin-top:8px">
{% for d in discussions|default([]) %}
<li><a href="{{ url_for('discussion', id=d.id) }}">{{ d.title }}</a></li>
{% else %}
<li>Keine Diskussionen.</li>
{% endfor %}
</ul>
</div>
<div style="margin-bottom:12px">
<h4>Kategorien</h4>
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:8px">
{% for c in categories|default([]) %}
<a href="{{ url_for('category', slug=c.slug) }}" class="button">{{ c.name }}</a>
{% else %}
<div style="color:var(--muted)">Keine Kategorien.</div>
{% endfor %}
</div>
</div>
<div>
<h4>Über</h4>
<p style="color:var(--muted);font-size:0.95rem">{% block about_snippet %}Fandom-Community. Leitfäden,
Lore, Builds und mehr.{% endblock %}</p>
</div>
</aside>
</div>
<footer>
<div>© {{ current_year or 2025 }} {{ site_name or 'Game Fandom' }} — Erstellt mit ❤️</div>
<div style="margin-top:6px;color:var(--muted);font-size:0.85rem">Powered by Flask • <a href="#">Datenschutz</a>
</div>
</footer>
</div>
{% block scripts %}
<script>
// kleines Script: z.B. optionales keyboard shortcut für Suche (/)
document.addEventListener('keydown', function (e) {
if (e.key === "/" && document.activeElement.tagName !== 'INPUT' && document.activeElement.tagName !== 'TEXTAREA') {
const q = document.querySelector('.search input');
if (q) {
e.preventDefault();
q.focus();
}
}
});
</script>
{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
</body>
</html>

View File

@@ -1,5 +1,6 @@
{% extends 'base.html' %}
{% block title %}
HURENSOHN
{% extends "base.html" %}
{% block title %}Startseite{% endblock %}
{% block page_title %}Willkommen im {{ site_name or 'Game Fandom' }}{% endblock %}
{% block content %}
<p>Dies ist die Startseite deines Wikis. Wähle eine Kategorie oder suche nach Artikeln.</p>
{% endblock %}

18
templates/login.html Normal file
View File

@@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block page_title %}Anmelden{% endblock %}
{% block content %}
<form method="post" action="{{ url_for('login') }}" class="auth-form">
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">Email address</label>
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp">
<div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<p>Noch keinen Account? <a href="{{ url_for('register') }}">Jetzt registrieren</a>.</p>
{% endblock %}

25
templates/register.html Normal file
View File

@@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block title %}Registrieren{% endblock %}
{% block page_title %}Registrieren{% endblock %}
{% block content %}
<form method="post" action="{{ url_for('register') }}" class="auth-form">
<label for="username">Benutzername</label>
<input type="text" id="username" name="username" required>
<label for="email">E-Mail</label>
<input type="email" id="email" name="email" required>
<label for="password">Passwort</label>
<input type="password" id="password" name="password" required>
<label for="confirm">Passwort bestätigen</label>
<input type="password" id="confirm" name="confirm" required>
<button type="submit">Registrieren</button>
</form>
<p>Schon ein Konto? <a href="{{ url_for('login') }}">Zum Login</a>.</p>
{% endblock %}