Compare commits
38 Commits
885eeea339
...
gaisi-patc
| Author | SHA1 | Date | |
|---|---|---|---|
| c2549ffd3f | |||
| 665e21c859 | |||
| df9d4e1687 | |||
| 654eeb4590 | |||
| b0d67e4d6d | |||
| 64b148fe6a | |||
| 23c27600ef | |||
| e86b1c6b76 | |||
| b6886ce52a | |||
| bbf3626dcb | |||
| a1ecaaadb4 | |||
| 63d3436f0e | |||
| 756980c5ae | |||
| 416babf6ac | |||
| 104cfca699 | |||
| fea6d6bdda | |||
| 395000aa47 | |||
| adb32e1234 | |||
| c51a6f09b4 | |||
| a6aad1478f | |||
| 2d9b380935 | |||
| 33cce846ac | |||
| 2a886bcd8d | |||
| df3e4ef53c | |||
| bd5bb446b3 | |||
| 1f38008509 | |||
| d6357adeed | |||
| 71854cd101 | |||
| 152ddf5bcc | |||
| fc002cdcfc | |||
| a24cb4cd76 | |||
| 975b7bb319 | |||
| 42e26b896d | |||
| f06c193b71 | |||
| 723031782b | |||
| 85a670eb04 | |||
| c9d9221a19 | |||
| 011542a32d |
@@ -26,4 +26,7 @@ jobs:
|
|||||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
run: |
|
run: |
|
||||||
go run scripts/gemini_review/main.go
|
cd scripts/gemini_review
|
||||||
|
go build -o gemini_review_bin main.go
|
||||||
|
cd ../..
|
||||||
|
./scripts/gemini_review/gemini_review_bin
|
||||||
|
|||||||
@@ -13,7 +13,19 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Count words in Markdown files
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.22'
|
||||||
|
|
||||||
|
- name: Run Gemini Review
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GITEA_URL: ${{ github.server_url }}
|
||||||
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
run: |
|
run: |
|
||||||
echo "Counting words in all .md files..."
|
cd scripts/wordcount
|
||||||
find . -name "*.md" -not -path "*/.*" -exec wc -w {} + | sort -rn
|
go build -o wordcount_bin main.go
|
||||||
|
cd ../..
|
||||||
|
./scripts/wordcount/wordcount_bin
|
||||||
|
|||||||
@@ -1,62 +1,242 @@
|
|||||||
# Diplomarbeitsbuch
|
# Diplomarbeitsbuch – Individueller Teil
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Einleitung
|
## 1. Einleitung
|
||||||
### Ausgangssituation
|
|
||||||
### Motivation
|
### Motivation
|
||||||
### Kurzbeschreibung des Projekts
|
|
||||||
### Persönlicher Aufgabenbereich
|
Zu Beginn des Projekts war meine persönliche Motivation überschaubar. Wie viele Schulprojekte startete auch AlumniHub zunächst als eine Pflichtaufgabe – ich war zwar von Anfang an dabei, jedoch ohne besonders großes Interesse am Thema. Das Projekt war zu Beginn eines von vielen schulischen Anforderungen, und ich sah es zunächst nicht als etwas, mit dem ich mich wirklich identifizieren würde. Die Aufgabenstellung klang zwar interessant, aber der persönliche Bezug fehlte noch.
|
||||||
### Abgrenzung der Arbeit
|
|
||||||
Die Datenbankinfrastruktur war zum Projektstart bereits vorhanden und wurde nicht im Rahmen dieser Arbeit konzipiert. Die Diplomarbeit beschränkt sich auf die Entwicklung der Anwendungsschicht, bestehend aus CMS-Konfiguration, Modul- und Theme-Entwicklung.
|
Das änderte sich im Laufe des Projekts grundlegend – und zwar durch einen Umstand, den ich anfangs nicht als Chance gesehen hätte: die Teamverkleinerung. Als das Team von ursprünglich sechs auf drei Personen verkleinert wurde, änderte sich meine Rolle schlagartig. Plötzlich trug ich deutlich mehr Verantwortung – nicht nur für meinen eigenen Bereich, sondern auch für das Gesamtprojekt. Aufgaben, die ursprünglich andere übernehmen sollten, lagen nun in meinen Händen. Diese Situation zwang mich, proaktiver zu handeln, eigenständiger zu entscheiden und tiefer in die Materie einzutauchen als ursprünglich geplant.
|
||||||
|
|
||||||
|
Genau in diesem Moment begann mein Interesse zu wachsen. Je mehr Verantwortung ich übernahm, desto mehr identifizierte ich mich mit dem Projekt und seinen Zielen. Ich merkte, dass die Entscheidungen die ich traf direkte Auswirkungen auf das Endergebnis hatten – und das motivierte mich, die Arbeit wirklich gut zu machen und nicht nur fertigzustellen.
|
||||||
|
|
||||||
|
Die technische Herausforderung, eine moderne Webanwendung mit Blazor und ASP.NET umzusetzen, sowie die Möglichkeit, ein echtes System für eine reale Organisation zu entwickeln, wurden zur echten Motivation. Die Aufgabenstellung bot mir die Chance, theoretisches Wissen aus dem Unterricht in einem praxisnahen Umfeld anzuwenden und gleichzeitig einen konkreten Mehrwert für den Absolventenverein zu schaffen. Rückblickend war die Teamverkleinerung – obwohl sie damals als Problem wahrgenommen wurde – einer der wichtigsten Faktoren für meine persönliche Entwicklung im Rahmen dieses Projekts.
|
||||||
|
|
||||||
### Individuelle Themenstellung
|
### Individuelle Themenstellung
|
||||||
|
|
||||||
|
Entwicklung des Anmeldetools und Hall-of-Fame-Moduls für Oqtane, Integration in das bestehende CMS-System, Responsive UI-Entwicklung mit Blazor und ASP.NET Core
|
||||||
|
|
||||||
|
### Teamrolle
|
||||||
|
|
||||||
|
Developer, Tester der eigenen Module
|
||||||
|
|
||||||
|
### Untersuchungsanliegen
|
||||||
|
|
||||||
|
Modulentwicklung mit Blazor und ASP.NET Core, Integration eigenständiger Module in die Oqtane-Architektur, Entwicklung reaktiver Benutzeroberflächen mit Blazor-Komponenten, Responsive UI-Design mit Bootstrap und eigenem CSS, PDF-Generierung mit QuestPDF, Fehlerbehebung und Optimierung der mobilen Darstellung
|
||||||
|
|
||||||
|
### Persönlicher Aufgabenbereich
|
||||||
|
|
||||||
|
Der persönliche Aufgabenbereich umfasste die Entwicklung der beiden zentralen CMS-Module der Plattform: das Anmeldetool für Veranstaltungen sowie das Hall-of-Fame-Modul. Beide Module wurden als eigenständige, wiederverwendbare Oqtane-Module konzipiert und vollständig in C# und Blazor implementiert. Neben der technischen Umsetzung lag ein besonderer Fokus auf der Benutzerfreundlichkeit, der responsiven Darstellung und der nahtlosen Integration in das bestehende CMS-System.
|
||||||
|
|
||||||
|
### Abgrenzung der Arbeit
|
||||||
|
|
||||||
|
Das Datenbankdesign sowie die Infrastruktur und das Server-Setup waren nicht Teil meines persönlichen Aufgabenbereichs und wurden von anderen Teammitgliedern verantwortet. Mein persönlicher Anteil beschränkt sich auf die Entwicklung der Anwendungsschicht, konkret auf die Implementierung der beiden CMS-Module – das Anmeldetool und das Hall-of-Fame-Modul – sowie deren Integration in das bestehende System.
|
||||||
|
|
||||||
---
|
---
|
||||||
## 3. Projektumfeld & Rahmenbedingungen
|
|
||||||
### Einsatz eines bestehenden CMS (Oqtane)
|
## 2. Technologischer Überblick
|
||||||
### Teamarbeit
|
|
||||||
Das Projektteam wurde im Verlauf des Projekts von sechs auf drei Personen reduziert.
|
Herzlich willkommen zu meinem individuellen Teil der Diplomarbeit. In diesem Abschnitt dokumentiere ich meinen persönlichen Beitrag zum Projekt AlumniHub – konkret die Entwicklung des Anmeldetools und des Hall-of-Fame-Moduls für das Content-Management-System Oqtane.
|
||||||
### Schulisches Umfeld (HTL)
|
|
||||||
### Zeitliche Rahmenbedingungen
|
Im Rahmen meiner Arbeit habe ich verschiedene Technologien und Werkzeuge eingesetzt, die in diesem Kapitel kurz vorgestellt werden. Dabei habe ich bewusst darauf geachtet, die Erklärungen so zu gestalten, dass sie auch für Leserinnen und Leser ohne technischen Hintergrund verständlich sind. Wer bereits Erfahrung in der Webentwicklung mitbringt, wird die einzelnen Technologien möglicherweise schon kennen – die Beschreibungen sollen jedoch in jedem Fall nachvollziehbar machen, welche Rolle die jeweilige Technologie in meiner Arbeit gespielt hat.
|
||||||
|
|
||||||
|
### 2.1 C# und ASP.NET Core
|
||||||
|
|
||||||
|
C# ist eine moderne Programmiersprache von Microsoft, die besonders für die Entwicklung von Webanwendungen und Softwaresystemen geeignet ist. Sie ist klar strukturiert, gut lesbar und weit verbreitet in der professionellen Softwareentwicklung. ASP.NET Core ist ein Framework – also eine Art Werkzeugkasten – das auf C# aufbaut und die Entwicklung von Webseiten und Webanwendungen vereinfacht. Es stellt vorgefertigte Bausteine bereit, sodass man nicht alles von Grund auf neu programmieren muss. Für AlumniHub bildete ASP.NET Core die technische Grundlage aller entwickelten Module.
|
||||||
|
|
||||||
|
### 2.2 Blazor
|
||||||
|
|
||||||
|
Blazor ist ein Framework, das es ermöglicht, interaktive Weboberflächen direkt in C# zu entwickeln. Normalerweise werden solche Oberflächen – also alles, was der Benutzer auf dem Bildschirm sieht und mit dem er interagiert – mit einer anderen Programmiersprache namens JavaScript umgesetzt. Blazor erlaubt es, dasselbe in C# zu schreiben, was die Entwicklung vereinheitlicht und übersichtlicher macht. Konkret bedeutet das: Wenn ein Benutzer beispielsweise auf den „Zusagen"-Button klickt, reagiert die Seite sofort und aktualisiert sich automatisch – ohne dass die gesamte Seite neu geladen werden muss.
|
||||||
|
|
||||||
|
### 2.3 Oqtane
|
||||||
|
|
||||||
|
Oqtane ist ein Content-Management-System (CMS) – also eine Software, mit der Webseiten und deren Inhalte verwaltet werden können, ähnlich wie WordPress oder Typo3. Das Besondere an Oqtane ist, dass es vollständig auf Blazor und C# aufbaut und eine modulare Architektur besitzt: Man kann eigene Erweiterungen – sogenannte Module – entwickeln und in das System einbinden, ohne den Kern der Software verändern zu müssen. Für AlumniHub wurde Oqtane als Grundlage gewählt, weil es Benutzerverwaltung, Seitenstruktur und viele weitere Standardfunktionen bereits mitbringt und somit viel Entwicklungsaufwand spart.
|
||||||
|
|
||||||
|
### 2.4 Bootstrap und CSS
|
||||||
|
|
||||||
|
CSS (Cascading Style Sheets) ist die Sprache, mit der das Aussehen einer Webseite festgelegt wird – also Farben, Schriftarten, Abstände und das Layout. Bootstrap ist eine fertige Sammlung von CSS-Regeln und Hilfsmitteln, die von Twitter entwickelt wurde und kostenlos verfügbar ist. Der große Vorteil von Bootstrap ist, dass es sogenanntes Responsive Design einfach umsetzbar macht: Die Webseite passt sich automatisch an verschiedene Bildschirmgrößen an – egal ob Desktop, Tablet oder Smartphone. Für AlumniHub wurde Bootstrap als Basis verwendet, ergänzt durch eigenes CSS für das individuelle Erscheinungsbild der Plattform.
|
||||||
|
|
||||||
|
### 2.5 QuestPDF
|
||||||
|
|
||||||
|
QuestPDF ist eine kostenlose Open-Source-Bibliothek – also eine fertige Programmsammlung – die es ermöglicht, PDF-Dokumente direkt aus C#-Code heraus zu erstellen. Anstatt ein PDF manuell zu gestalten, beschreibt man im Code wie das Dokument aussehen soll, und QuestPDF generiert daraus automatisch eine fertige PDF-Datei. Im Hall-of-Fame-Modul wurde QuestPDF eingesetzt, um jedem Absolventen zu ermöglichen, sein eigenes Profil als visuell ansprechendes PDF herunterzuladen.
|
||||||
|
|
||||||
|
### 2.6 Gitea
|
||||||
|
|
||||||
|
Wenn mehrere Personen gemeinsam an einem Softwareprojekt arbeiten, braucht man ein System, das alle Änderungen am Code nachverfolgt und verhindert, dass sich Änderungen verschiedener Personen gegenseitig überschreiben. Dieses Konzept nennt sich Versionskontrolle. Gitea ist eine selbst gehostete Plattform für genau diesen Zweck – ähnlich wie GitHub, aber auf einem eigenen Server betrieben. Jede Änderung am Code wird als sogenannter „Commit" gespeichert, sodass man jederzeit nachvollziehen kann, wer wann was geändert hat, und bei Bedarf auf eine ältere Version zurückwechseln kann.
|
||||||
|
|
||||||
|
### 2.7 Entwicklungsumgebung
|
||||||
|
|
||||||
|
Eine Entwicklungsumgebung – auch IDE (Integrated Development Environment) genannt – ist ein Programm, das Entwicklerinnen und Entwickler beim Schreiben von Code unterstützt. Sie bietet unter anderem Funktionen wie automatische Vervollständigung, Fehlererkennung und integrierte Debugging-Werkzeuge, die das Auffinden und Beheben von Fehlern im Code erleichtern.
|
||||||
|
|
||||||
|
Zu Beginn des Projekts wurde Visual Studio 2022 auf Windows verwendet. Visual Studio 2022 ist die führende Entwicklungsumgebung von Microsoft für .NET-Projekte und bietet eine umfangreiche Unterstützung für ASP.NET Core und Blazor. Im Laufe des Projekts erfolgte jedoch ein Wechsel von Windows auf macOS. Da Visual Studio 2022 auf Mac nicht mehr verfügbar ist – Microsoft hat die Mac-Version eingestellt – wurde als Ersatz Google Antigravity eingesetzt. Google Antigravity ist eine next-generation IDE von Google, die plattformübergreifend funktioniert und eine moderne Entwicklungsumgebung für .NET-Projekte auf macOS bietet. Der Umstieg auf Antigravity ermöglichte es, die Entwicklung auf dem neuen System ohne größere Unterbrechungen fortzusetzen.
|
||||||
|
|
||||||
|
### 2.8 Plattformwechsel: Windows zu macOS
|
||||||
|
|
||||||
|
Ein besonderer Aspekt der Entwicklung war der Wechsel von Windows auf macOS während des Projektverlaufs. Unter Plattform versteht man in der Softwareentwicklung das Betriebssystem, auf dem eine Anwendung läuft – also etwa Windows, macOS oder Linux. Dieser Wechsel brachte spezifische Herausforderungen mit sich, da Oqtane primär für Windows entwickelt wurde. Obwohl Oqtane grundsätzlich auch auf macOS und Linux lauffähig ist, ist die Unterstützung für diese Plattformen veraltet und nicht vollständig angepasst. In der Praxis bedeutete das: Der Code ließ sich teilweise nicht fehlerfrei kompilieren – also in ein lauffähiges Programm umwandeln – und Oqtane startete zunächst nur mit Fehlermeldungen. Durch den Einsatz von Google Antigravity als IDE konnten diese Probleme weitgehend gelöst werden, da Antigravity eine bessere plattformübergreifende Integration bietet als die ursprünglich verwendete Entwicklungsumgebung.
|
||||||
|
|
||||||
---
|
---
|
||||||
## 4. Technologischer Überblick
|
|
||||||
### 4.1 Web-Entwicklung mit ASP.NET & C#
|
## 3. Entwicklung des Oqtane Themes
|
||||||
#### Backend-Logik
|
|
||||||
#### API-basierte Kommunikation
|
### 3.1 Ziel des Themes
|
||||||
#### Zusammenspiel mit Oqtane
|
|
||||||
### 4.2 Oqtane – Überblick
|
Im Rahmen des Projekts AlumniHub wurde ein eigenes Theme für das Content-Management-System Oqtane entwickelt. Ziel dieser Entwicklung war es, das Standarddesign von Oqtane vollständig durch eine projektspezifische Benutzeroberfläche zu ersetzen, die den Anforderungen des Absolventenvereins der HTL Ungargasse entspricht. Das Standardtheme von Oqtane ist funktional, jedoch generisch gehalten und bietet keinen Bezug zur Schule oder zum Projekt. Aus diesem Grund wurde frühzeitig die Entscheidung getroffen, ein vollständig eigenes Theme zu entwickeln, das sowohl optisch als auch technisch auf die Bedürfnisse der Plattform zugeschnitten ist.
|
||||||
#### Grundidee & Architektur
|
|
||||||
#### Modul- & Theme-Konzept
|
Das visuelle Design orientiert sich dabei am bestehenden Erscheinungsbild der offiziellen Schulwebseite szu.at. Es wurde bewusst ein schlichtes Grau-Weiß-Farbschema gewählt, das eine klare, professionelle und zeitlose Optik erzeugt. Auf auffällige Farben oder komplexe Animationen wurde verzichtet, um die Inhalte in den Vordergrund zu stellen und eine ruhige Benutzeroberfläche zu schaffen, die für ein breites Publikum – von Schülerinnen und Schülern über Lehrkräfte bis hin zu ehemaligen Absolventinnen und Absolventen – zugänglich ist.
|
||||||
#### Vorteile für das Projekt
|
|
||||||
### 4.3 CMS-Grundkonfiguration
|
Neben der visuellen Gestaltung standen auch technische Anforderungen im Mittelpunkt. Das Theme sollte auf unterschiedlichen Endgeräten – sowohl auf Desktop-Computern als auch auf Smartphones und Tablets – zuverlässig und benutzerfreundlich funktionieren. Responsive Design war daher von Anfang an eine zentrale Anforderung. Darüber hinaus sollte das Theme vollständig in die Oqtane-Architektur integriert sein, sodass alle Standardfunktionen des CMS wie Benutzerverwaltung, Seitenverwaltung und Modulintegration weiterhin ohne Einschränkungen genutzt werden können.
|
||||||
#### Initiale Einrichtung
|
|
||||||
#### Modul-Integration
|
### 3.2 Technische Umsetzung
|
||||||
#### Rollen & Berechtigungen
|
|
||||||
#### Grundlegende Systemeinstellungen
|
Als technische Grundlage diente die Theme-Architektur von Oqtane. Das Layout wurde in einer zentralen Razor-Datei (`Theme.razor`) definiert, welche von der Basisklasse `ThemeBase` erbt. Durch diese Vererbung stehen im Theme automatisch zentrale Funktionen des Frameworks zur Verfügung, darunter der Seitenzustand (`PageState`), Navigationsdaten, Systemeinstellungen sowie Informationen über den aktuell angemeldeten Benutzer. Dies ermöglicht eine tiefe Integration des Themes in das CMS, ohne dass zusätzliche Schnittstellen oder externe Datenzugriffe notwendig sind.
|
||||||
|
|
||||||
|
#### Navigationsleiste
|
||||||
|
|
||||||
|
Ein zentrales Element des Themes ist die fixierte Navigationsleiste am oberen Rand der Seite. Sie ist in drei klar voneinander getrennte Bereiche gegliedert: Auf der linken Seite befindet sich das Logo der HTL Ungargasse, das als visuelles Erkennungsmerkmal der Schule dient und beim Klick zur Startseite führt. In der Mitte werden die Navigationspunkte der Plattform angezeigt, die dynamisch aus der Seitenstruktur des CMS generiert werden. Auf der rechten Seite befinden sich die Benutzerfunktionen, also Login, Registrierung und bei angemeldeten Benutzern ein Link zum eigenen Profil.
|
||||||
|
|
||||||
|
Die dynamische Generierung der Navigationspunkte stellte dabei eine besondere Herausforderung dar. Das Oqtane-Framework stellt standardmäßig eine `<Menu>`-Komponente bereit, die automatisch alle im System registrierten Seiten in der Navigation anzeigt. Diese Komponente gibt jedoch nicht nur öffentlich sichtbare Inhaltsseiten aus, sondern auch interne Systemseiten wie Login, Register, Reset, Profile, Search, Privacy, Terms, Not Found oder Admin. Diese Seiten sind für den normalen Benutzer nicht relevant und sollten daher nicht in der Hauptnavigation erscheinen.
|
||||||
|
|
||||||
|
Da die Standardkomponente das Burger-Menü automatisch generierte und keine Möglichkeit bot, dieses individuell anzupassen, wurde auf sie verzichtet. Da ein eigenes, maßgeschneidertes Burger-Menü benötigt wurde, wurde stattdessen eine vollständig eigene Navigation implementiert. Dabei wurde auf die Basisklassen `MenuBase` und `MenuItemsBase` zurückgegriffen, die von Oqtane bereitgestellt werden und den Zugriff auf die Seitenliste ermöglichen. Die Navigationspunkte werden direkt über `PageState.Pages` abgerufen und anschließend mittels LINQ gefiltert. Dabei werden nur Root-Seiten berücksichtigt, also Seiten ohne übergeordnete Seite (`ParentId == null`), die als Navigationsseite markiert sind und nicht in einer manuell definierten Ausschlussliste (`hiddenNames`) stehen.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var hiddenNames = new[]
|
||||||
|
{
|
||||||
|
"Login", "Register", "Reset", "Profile",
|
||||||
|
"Search", "Privacy", "Terms", "Not Found", "Admin"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var item in PageState.Pages
|
||||||
|
.Where(p => p.ParentId == null
|
||||||
|
&& !hiddenNames.Contains(p.Name)))
|
||||||
|
{
|
||||||
|
@item.Name
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Durch diesen Ansatz werden ausschließlich die für Benutzer relevanten Inhaltsseiten in der Navigation angezeigt. Gleichzeitig bleibt die Navigation vollständig dynamisch: Werden im CMS neue Seiten angelegt und als Navigationsseiten markiert, erscheinen diese automatisch im Menü, ohne dass eine Anpassung am Theme-Code notwendig ist. Dies reduziert den Wartungsaufwand erheblich und stellt sicher, dass die Navigation stets dem aktuellen Stand der Plattform entspricht.
|
||||||
|
|
||||||
|
#### Responsive Design und Burger-Menü
|
||||||
|
|
||||||
|
Da die Plattform auch auf mobilen Endgeräten genutzt werden soll, war die Umsetzung eines responsiven Layouts eine wichtige Anforderung. Auf Desktop-Geräten werden alle Navigationspunkte direkt in der Navigationsleiste angezeigt. Auf kleineren Bildschirmen wie Smartphones oder Tablets würde dies jedoch zu Platzproblemen führen, da die Anzahl der Navigationspunkte die verfügbare Breite überschreiten kann.
|
||||||
|
|
||||||
|
Aus diesem Grund wurde für mobile Endgeräte ein sogenanntes Burger-Menü implementiert. Dabei handelt es sich um ein Icon mit drei horizontalen Linien, das auf kleinen Bildschirmen anstelle der ausgeschriebenen Navigationspunkte angezeigt wird. Beim Anklicken öffnet sich eine seitliche Sidebar, in der alle Navigationspunkte vertikal untereinander dargestellt werden. Die Sidebar ist so aufgebaut, dass der obere Bereich die Navigationspunkte enthält und bei einer großen Anzahl von Einträgen gescrollt werden kann. Am unteren Rand der Sidebar sind die Login- und Registrierungsfunktionen fixiert, sodass diese unabhängig von der Anzahl der Navigationspunkte immer sichtbar und erreichbar sind.
|
||||||
|
|
||||||
|
Die technische Umsetzung des Burger-Menüs basiert auf einer CSS-Checkbox-Lösung. Das Öffnen und Schließen der Sidebar wird über den Zustand einer versteckten Checkbox gesteuert, der mittels CSS-Selektoren ausgewertet wird. Dieser Ansatz ermöglicht eine funktionsfähige mobile Navigation ohne den Einsatz zusätzlicher JavaScript-Frameworks oder komplexer Logik. Die gesamte Darstellungslogik – also welche Elemente auf welcher Bildschirmgröße sichtbar sind – wird über CSS Media Queries gesteuert, die zwischen der Desktop- und der mobilen Ansicht unterscheiden.
|
||||||
|
|
||||||
|
#### Pane-Struktur
|
||||||
|
|
||||||
|
Für den Inhaltsbereich der Plattform wurden mehrere sogenannte Panes definiert. Panes sind in Oqtane Platzhalter-Bereiche innerhalb eines Themes, in die später Module oder Inhalte eingefügt werden können. Durch die Definition mehrerer Panes mit unterschiedlichen Breiten und Positionen können Seiten sehr flexibel und ohne Änderungen am Theme-Code strukturiert werden.
|
||||||
|
|
||||||
|
Im entwickelten Theme stehen unter anderem folgende Panes zur Verfügung: ein vollbreiter Bereich oben (`Top 100%`), ein linker Bereich (`Left 50%`), eine rechte Seitenleiste (`Right Sidebar 33%`) sowie ein vollbreiter Bereich am unteren Rand (`Bottom 100%`). Diese Struktur ermöglicht es, Seiten sowohl ein- als auch mehrspaltig aufzubauen, je nach den Anforderungen des jeweiligen Inhalts.
|
||||||
|
|
||||||
|
#### Weitere Integrationen
|
||||||
|
|
||||||
|
Neben Navigation und Layout wurden weitere Funktionen in das Theme integriert. Die Cookie-Consent-Komponente von Oqtane wurde eingebunden, sodass beim ersten Besuch der Plattform ein Hinweis zur Verwendung von Cookies angezeigt wird. Diese Funktion ist über die Site-Einstellungen von Oqtane steuerbar, das Theme rendert lediglich die entsprechende Komponente.
|
||||||
|
|
||||||
|
Darüber hinaus wurde das ControlPanel von Oqtane integriert, das Administratorinnen und Administratoren direkten Zugriff auf Verwaltungsfunktionen bietet. Login- und Registrierungsoptionen werden über den `SettingService` aus den Site-Einstellungen geladen, sodass diese Funktionen ohne Anpassungen am Code aktiviert oder deaktiviert werden können.
|
||||||
|
|
||||||
|
### 3.3 Herausforderungen
|
||||||
|
|
||||||
|
Die größte Herausforderung bei der Theme-Entwicklung war die Umsetzung einer individuellen Navigation. Die von Oqtane bereitgestellte Standardkomponente generierte das Burger-Menü automatisch und bot keine Möglichkeit, dieses individuell anzupassen. Da ein eigenes, maßgeschneidertes Burger-Menü benötigt wurde, das sich nahtlos in das Theme-Design einfügt, wurde die Standardkomponente vollständig ersetzt und eine eigene Navigation implementiert. Durch den direkten Zugriff auf `PageState.Pages` und eine LINQ-basierte Filterlogik konnte eine vollständig kontrollierbare und flexibel gestaltbare Navigation realisiert werden. Dieser Ansatz erforderte zwar mehr Entwicklungsaufwand, ermöglichte dafür aber die gewünschte individuelle Gestaltung des Burger-Menüs.
|
||||||
|
|
||||||
---
|
---
|
||||||
## 5. System- und Lösungsarchitektur
|
|
||||||
### Gesamtarchitektur des Systems
|
## 4. Umsetzung der Module
|
||||||
### Einordnung von CMS, Modulen und Theme
|
|
||||||
### Datenfluss
|
### 4.1 Anmeldetool
|
||||||
### Architekturentscheidungen
|
|
||||||
---
|
#### 4.1.1 Ziel des Moduls
|
||||||
## 6. Entwicklung des Oqtane Themes
|
|
||||||
### Ziel des Themes
|
Das Anmeldetool wurde entwickelt, um Mitgliedern des Absolventenvereins eine einfache und strukturierte Möglichkeit zu bieten, sich zu Veranstaltungen und Treffen an- oder abzumelden. Ziel des Moduls ist es, den organisatorischen Aufwand bei der Planung und Verwaltung von Treffen deutlich zu reduzieren und gleichzeitig eine benutzerfreundliche Oberfläche für alle Beteiligten bereitzustellen.
|
||||||
### Technische Umsetzung
|
|
||||||
### Herausforderungen
|
Das Modul wurde als zusätzliche Funktionserweiterung in das bestehende Content-Management-System integriert. Es ermöglicht sowohl die Verwaltung der Veranstaltungen auf Seiten der Organisatoren als auch eine intuitive Interaktion für die Teilnehmerinnen und Teilnehmer. Durch den Einsatz moderner Webtechnologien konnte dabei eine reaktionsschnelle und geräteübergreifend nutzbare Lösung geschaffen werden.
|
||||||
---
|
|
||||||
## 7. Umsetzung der Module
|
#### 4.1.2 Frontend (Eingabemaske)
|
||||||
### 7.1 Anmeldetool
|
|
||||||
#### Ziel des Moduls
|
Die Benutzeroberfläche des Anmeldetools wurde mithilfe von Blazor realisiert, einem Framework der ASP.NET-Technologieplattform, das die Entwicklung interaktiver Weboberflächen in C# ermöglicht. Die Eingabemaske wurde als eigenständige Blazor-Komponente implementiert, die flexibel in verschiedene Seiten der Anwendung eingebettet werden kann.
|
||||||
#### Frontend (Eingabemaske)
|
|
||||||
#### Backend-Logik
|
Beim Aufruf der Seite wird dem Benutzer zunächst eine kurze Beschreibung der jeweiligen Veranstaltung angezeigt. Im Anschluss stehen zwei klar gekennzeichnete Schaltflächen zur Verfügung: Zusagen zur Bestätigung der Teilnahme sowie Absagen zur Ablehnung. Die Schaltflächen wurden farblich differenziert gestaltet – eine grüne Hervorhebung signalisiert die Zusage, eine rote Darstellung steht für die Absage. Diese Gestaltung orientiert sich an etablierten Designkonventionen moderner Webanwendungen und verbessert die intuitive Bedienbarkeit erheblich. Darüber hinaus wurde besonderer Wert auf eine responsive Darstellung gelegt, damit das Tool sowohl auf Desktop-Geräten als auch auf Smartphones und Tablets problemlos genutzt werden kann.
|
||||||
#### API-Schnittstelle
|
|
||||||
#### Datenauswertung
|
#### 4.1.3 API-Schnittstelle
|
||||||
#### UX-Überlegungen
|
|
||||||
---
|
Die Anbindung des Anmeldetools an das bestehende CMS erfolgt über klar definierte Schnittstellen innerhalb der ASP.NET-Architektur. Die Komponente kommuniziert mit dem Backend, um Veranstaltungsdaten abzurufen sowie Anmelde- und Absagestatus der Teilnehmenden zu übermitteln und zu persistieren.
|
||||||
### 7.2 Hall of Fame
|
|
||||||
Das Hall-of-Fame-Modul ist ein Oqtane-Modul, das ehemalige Absolventinnen und Absolventen der Schule auf der Vereinswebseite präsentiert. Es wurde als eigenständiges, wiederverwendbares Modul innerhalb des Oqtane-Frameworks entwickelt und ermöglicht registrierten Benutzerinnen und Benutzern, sich selbst mit einem persönlichen Profil einzutragen. Die Einträge werden erst nach bewusster Veröffentlichung durch die jeweilige Person auf der öffentlichen Seite angezeigt, können als PDF exportiert werden und unterliegen einem Meldesystem zur Qualitätssicherung.
|
Die Datenübertragung erfolgt nach dem Prinzip der sauberen Komponentenarchitektur: Die Blazor-Komponente ist als eigenständige Einheit konzipiert, die über Parameter und Rückruffunktionen mit übergeordneten Seitenkomponenten kommuniziert. Diese Struktur gewährleistet eine klare Trennung zwischen Darstellungslogik und Datenzugriff und erleichtert zukünftige Erweiterungen der Schnittstelle erheblich.
|
||||||
|
|
||||||
|
#### 4.1.4 Datenauswertung
|
||||||
|
|
||||||
|
Die im Anmeldetool erfassten Daten bilden die Grundlage für die Verwaltung und Auswertung von Veranstaltungsteilnahmen. Organisatoren können auf Basis der gespeicherten An- und Abmeldungen die Teilnehmerzahlen einsehen und die Planung entsprechend anpassen.
|
||||||
|
|
||||||
|
Die Architektur des Moduls ist bereits auf zukünftige Erweiterungen ausgelegt. Geplante Erweiterungen umfassen eine übersichtliche Teilnehmerliste mit Namen und Anmeldestatus, die Möglichkeit eine maximale Teilnehmeranzahl je Veranstaltung festzulegen sowie eine direkte Speicherung und Auswertung der Anmeldedaten in einer Datenbank. Durch diese Erweiterungen soll das Anmeldetool künftig nicht nur als Interaktionselement für Teilnehmer, sondern auch als vollwertiges Verwaltungswerkzeug für Veranstaltungsorganisatoren dienen.
|
||||||
|
|
||||||
|
#### 4.1.5 UX-Überlegungen (User Experience)
|
||||||
|
|
||||||
|
Ein zentraler Aspekt bei der Entwicklung des Anmeldetools war die Benutzerfreundlichkeit der Oberfläche. UX (User Experience) bezeichnet dabei die Gesamtheit aller Erfahrungen, die ein Benutzer bei der Interaktion mit einer Anwendung macht – von der visuellen Gestaltung über die Bedienbarkeit bis hin zur allgemeinen Zufriedenheit mit dem System. Die Module wurden so gestaltet, dass Nutzerinnen und Nutzer die Funktionen ohne zusätzliche Schulung verwenden können. Eine klare Struktur, eine intuitive Bedienung sowie ein konsistentes Erscheinungsbild innerhalb des bestehenden Systems standen dabei im Vordergrund.
|
||||||
|
|
||||||
|
Während der Testphase wurden mehrere visuelle Darstellungsprobleme identifiziert und behoben. Dazu zählten Fehler bei der Overlay-Darstellung – insbesondere in Bezug auf Hintergrundfarben, Transparenzen und z-Index-Ebenen – sowie sogenannte Clipping-Fehler auf mobilen Endgeräten, bei denen Inhalte teilweise abgeschnitten oder außerhalb des sichtbaren Bereichs angezeigt wurden. Die Layoutstruktur und Elementgrößen wurden angepasst, ausreichende Abstände zu Seitenrändern sichergestellt und Tests auf verschiedenen Browser- und Gerätevarianten durchgeführt.
|
||||||
|
|
||||||
|
Diese Maßnahmen stellen einen ersten Schritt in der kontinuierlichen Verbesserung der mobilen Benutzeroberfläche dar. Weitere Feinabstimmungen sind geplant, um das Anmeldetool langfristig als stabile, benutzerfreundliche und geräteübergreifend konsistente Lösung zu etablieren.
|
||||||
|
|
||||||
|
### 4.2 Hall of Fame
|
||||||
|
|
||||||
|
Das Hall-of-Fame-Modul ist ein zentrales Modul der AlumniHub-Plattform. Es dient dazu, ehemalige Absolventinnen und Absolventen der HTL Ungargasse sichtbar zu machen und ihre beruflichen Werdegänge zu präsentieren. Das Modul wurde als eigenständiges, wiederverwendbares Oqtane-Modul entwickelt und ermöglicht es registrierten Benutzerinnen und Benutzern, sich selbst mit einem persönlichen Profil einzutragen.
|
||||||
|
|
||||||
|
#### Benutzeroberfläche
|
||||||
|
|
||||||
|
Die Benutzeroberfläche des Moduls besteht aus mehreren Komponenten, die zusammen eine vollständige und benutzerfreundliche Verwaltung der Hall-of-Fame-Einträge ermöglichen.
|
||||||
|
|
||||||
|
##### Übersichtsseite (Index.razor)
|
||||||
|
|
||||||
|
Die Hauptseite der Hall of Fame ist die erste Seite, die Besucherinnen und Besucher zu sehen bekommen. Sie zeigt alle veröffentlichten Einträge in einem responsiven Kartenlayout. Auf Desktop-Geräten werden die Karten in drei Spalten nebeneinander dargestellt, auf kleineren Bildschirmen passt sich das Layout automatisch an und zeigt die Karten einspaltig untereinander an.
|
||||||
|
|
||||||
|
Jede Karte enthält das Foto der jeweiligen Person als großes Vorschaubild im oberen Bereich. Darunter werden Name und Abschlussjahrgang als Überschrift angezeigt. Im unteren Bereich der Karte befindet sich ein Teaser der Beschreibung, also ein kurzer Ausschnitt aus dem Werdegang der Person. Da Beschreibungen unterschiedlich lang sein können, wurde die Kartenhöhe durch eine Kombination aus CSS-Flexbox und einer Maximalhöhe mit `overflow: hidden` vereinheitlicht. Dadurch weisen alle Karten in einer Zeile dieselbe Höhe auf, was ein sauberes und gleichmäßiges Erscheinungsbild der Übersichtsseite gewährleistet. Am unteren Rand jeder Karte befindet sich ein „Details ansehen"-Button, der die Besucherin oder den Besucher zur vollständigen Detailseite des Eintrags weiterleitet.
|
||||||
|
|
||||||
|
Über der Kartenliste befindet sich eine Filterleiste mit einer Suchfunktion und einer Sortieroption. Die Suchleiste ermöglicht eine Echtzeit-Suche über Name und Beschreibung aller Einträge – die Ergebnisse aktualisieren sich direkt beim Tippen, ohne dass die Seite neu geladen werden muss. Neben der Suchleiste befindet sich ein Sortier-Dropdown, über das zwischen verschiedenen Sortierkriterien gewählt werden kann, darunter Datum, Name und Jahrgang. Ein Toggle-Button mit dynamischem Pfeil-Icon ermöglicht das Umschalten zwischen aufsteigender und absteigender Sortierrichtung.
|
||||||
|
|
||||||
|
Für angemeldete Benutzerinnen und Benutzer wird im oberen rechten Bereich der Seite ein zusätzlicher Button angezeigt. Hat die Person noch keinen eigenen Eintrag erstellt, erscheint ein „Eintragen"-Button. Existiert bereits ein Eintrag, ändert sich der Button automatisch zu „Mein Eintrag", über den der bestehende Eintrag bearbeitet werden kann. Administratorinnen und Administratoren sehen zusätzlich Warnhinweise bei gemeldeten Einträgen sowie einen Lösch-Button bei jedem Eintrag.
|
||||||
|
|
||||||
|
##### Detailseite (Details.razor)
|
||||||
|
|
||||||
|
Die Detailseite zeigt einen einzelnen Hall-of-Fame-Eintrag in seiner vollständigen Form. Das Layout ist zweispaltig aufgebaut: Auf der linken Seite befindet sich das Foto der Person, eingebettet in einen unscharfen Bildhintergrund, der dasselbe Foto in einer größeren, weichgezeichneten Version als Hintergrund verwendet. Dieser Effekt verleiht der Seite eine moderne und ansprechende Optik. Das eigentliche Foto ist dabei als klar umrandetes Porträtbild in der Mitte des linken Bereichs positioniert.
|
||||||
|
|
||||||
|
Auf der rechten Seite werden zunächst eine Breadcrumb-Navigation („Hall of Fame / Details") sowie Name und Jahrgang der Person angezeigt. Der Jahrgang wird in blauer Schrift als „Absolvent des Jahrgangs [Jahr]" dargestellt, was einen visuellen Akzent setzt und den Jahrgang klar hervorhebt. Darunter folgt die vollständige Beschreibung unter der Überschrift „Werdegang & Erfolge".
|
||||||
|
|
||||||
|
Am unteren Rand der Seite befinden sich je nach Konfiguration des Eintrags bis zu vier Buttons. Der „PDF Vorschau"-Button öffnet einen modalen Dialog mit einer vollständigen Vorschau des generierten PDFs sowie einem „Herunterladen"-Button. Der „Zurück"-Button führt zurück zur Übersichtsseite. Falls die Person einen optionalen Link hinterlegt hat, wird zusätzlich ein „Webseite besuchen"-Button angezeigt, der die Besucherin oder den Besucher direkt zur externen Webseite der Person führt. Für eingeloggte Benutzer gibt es außerdem einen „Melden"-Button, über den ein Eintrag gemeldet werden kann.
|
||||||
|
|
||||||
|
##### Edit-Seite (Edit.razor)
|
||||||
|
|
||||||
|
Die Edit-Komponente dient sowohl zum Erstellen eines neuen Eintrags als auch zum Bearbeiten eines bestehenden. Das Formular ist klar strukturiert und enthält alle notwendigen Felder für einen vollständigen Hall-of-Fame-Eintrag.
|
||||||
|
|
||||||
|
Das Formular enthält zunächst ein Textfeld für den Namen der Person sowie ein Zahlenfeld für den Abschlussjahrgang. Für die Beschreibung wird ein Rich-Text-Editor eingesetzt, der Formatierungsmöglichkeiten wie Fett, Kursiv, Listen und weitere Optionen bietet. Unterhalb des Editors wird ein Live-Zeichenzähler angezeigt, der die aktuelle Zeichenanzahl in Echtzeit aktualisiert und die maximal erlaubte Zeichenanzahl von 504 Zeichen anzeigt. Dadurch sehen Benutzerinnen und Benutzer jederzeit, wie viel Platz noch zur Verfügung steht.
|
||||||
|
|
||||||
|
Für das Foto gibt es ein Upload-Feld mit einer Vorschaufunktion. Wird ein Bild ausgewählt, wird es sofort als Vorschau angezeigt, bevor der Eintrag gespeichert wird. Erlaubt sind nur JPG- und PNG-Dateien mit einer maximalen Größe von 5 MB. Zusätzlich gibt es ein optionales Link-Feld, in das eine externe Webseite der Person eingetragen werden kann.
|
||||||
|
|
||||||
|
Am unteren Rand des Formulars befinden sich zwei Speicheroptionen: „Als Entwurf speichern" und „Veröffentlichen". Einträge im Entwurfsmodus sind nur für die eigene Person sichtbar und erscheinen nicht in der öffentlichen Übersicht. Erst durch das Veröffentlichen wird der Eintrag für alle Besucherinnen und Besucher sichtbar. Eine Eigentümerprüfung verhindert, dass Benutzerinnen und Benutzer fremde Einträge bearbeiten, und eine Duplikatprüfung stellt sicher, dass pro Person nur ein Eintrag erstellt werden kann.
|
||||||
|
|
||||||
|
##### Meldefunktion
|
||||||
|
|
||||||
|
Die Meldefunktion ermöglicht es angemeldeten Benutzerinnen und Benutzern, einen Hall-of-Fame-Eintrag zu melden, wenn dieser unangemessene oder falsche Inhalte enthält. Durch Klicken auf den „Melden"-Button auf der Detailseite öffnet sich ein kleines Popup-Fenster, in das der Meldegrund eingetragen werden kann. Nach dem Absenden wird die Meldung in der Datenbank gespeichert und im Admin-Modul angezeigt. Administratorinnen und Administratoren können dort alle eingegangenen Meldungen einsehen und bei Bedarf den betreffenden Eintrag löschen oder freigeben.
|
||||||
|
|
||||||
|
Die Meldefunktion ist dabei nicht direkt im Hall-of-Fame-Modul implementiert, sondern wird über eine zentrale Schnittstelle aus einem gemeinsamen Interfaces-Paket eingebunden. Dieses Konzept ermöglicht es, dieselbe Melde-Oberfläche in beliebig vielen weiteren Modulen wiederzuverwenden, ohne die Logik mehrfach implementieren zu müssen.
|
||||||
|
|
||||||
|
##### PDF-Export
|
||||||
|
|
||||||
|
Der PDF-Export ist eine besondere Funktion des Hall-of-Fame-Moduls. Er ermöglicht es Benutzerinnen und Benutzern, ihren Eintrag als visuell ansprechendes PDF-Dokument herunterzuladen. Die PDF-Generierung wird über die Open-Source-Bibliothek QuestPDF in der Community-Edition realisiert.
|
||||||
|
|
||||||
|
Das generierte PDF folgt einem Glasmorphismus-Design und ist im Format DIN A4 gehalten. Das Foto der Person wird als vollflächiges Hintergrundbild verwendet, das die gesamte Seite ausfüllt. Im oberen Bereich der Seite befindet sich ein halbtransparenter dunkler Bereich, in dem der Name der Person in großen Großbuchstaben mit weitem Zeichenabstand dargestellt wird. Darunter wird der Jahrgang mit erhöhtem Zeichenabstand angezeigt. Im unteren Bereich der Seite befindet sich ein weiterer halbtransparenter Bereich mit der vollständigen Beschreibung. Der charakteristische Glaseffekt wird durch mehrschichtige halbtransparente Hintergründe mit abgerundeten Ecken erzeugt, die dem Dokument ein modernes und professionelles Erscheinungsbild verleihen.
|
||||||
|
|
||||||
|
Die PDF-Vorschau kann direkt auf der Detailseite über den „PDF Vorschau"-Button geöffnet werden. Es öffnet sich ein modaler Dialog, der das generierte PDF in einer Vorschau anzeigt. Über einen „Herunterladen"-Button kann das PDF direkt auf das Gerät gespeichert werden.
|
||||||
|
|
||||||
#### Datenmodell
|
#### Datenmodell
|
||||||
|
|
||||||
Das Modul verwendet zwei Entitäten, die in der Datenbank als Tabellen abgebildet werden.
|
Das Modul verwendet zwei Entitäten, die in der Datenbank als Tabellen abgebildet werden.
|
||||||
|
|
||||||
**Entität HallOfFame**
|
**Entität HallOfFame**
|
||||||
|
|
||||||
Die zentrale Entität repräsentiert einen einzelnen Absolventeneintrag und wird in der Datenbanktabelle `SZUAbsolventenvereinHallOfFame` gespeichert.
|
Die zentrale Entität repräsentiert einen einzelnen Absolventeneintrag und wird in der Datenbanktabelle `SZUAbsolventenvereinHallOfFame` gespeichert.
|
||||||
|
|
||||||
| Spalte | Datentyp | Beschreibung |
|
| Spalte | Datentyp | Beschreibung |
|
||||||
|--------|----------|--------------|
|
|--------|----------|--------------|
|
||||||
| `HallOfFameId` | `int` (PK, Auto-Inkrement) | Primärschlüssel |
|
| `HallOfFameId` | `int` (PK, Auto-Inkrement) | Primärschlüssel |
|
||||||
@@ -74,9 +254,13 @@ Die zentrale Entität repräsentiert einen einzelnen Absolventeneintrag und wird
|
|||||||
| `CreatedOn` | `DateTime` | Erstellzeitpunkt (Audit) |
|
| `CreatedOn` | `DateTime` | Erstellzeitpunkt (Audit) |
|
||||||
| `ModifiedBy` | `string` | Zuletzt geändert von (Audit) |
|
| `ModifiedBy` | `string` | Zuletzt geändert von (Audit) |
|
||||||
| `ModifiedOn` | `DateTime` | Zeitpunkt der letzten Änderung (Audit) |
|
| `ModifiedOn` | `DateTime` | Zeitpunkt der letzten Änderung (Audit) |
|
||||||
Die Entität implementiert das Oqtane-Interface `IAuditable`, wodurch die Audit-Felder automatisch vom Framework befüllt werden. Der Fremdschlüssel `ModuleId` verknüpft jeden Eintrag mit einer bestimmten Modulinstanz und ermöglicht so den Multi-Tenant-Betrieb: Mehrere Hall-of-Fame-Module auf verschiedenen Seiten der Website verwalten jeweils unabhängige Datensätze.
|
|
||||||
|
Die Entität implementiert das Oqtane-Interface `IAuditable`, wodurch die Audit-Felder automatisch vom Framework befüllt werden. Der Fremdschlüssel `ModuleId` verknüpft jeden Eintrag mit einer bestimmten Modulinstanz und ermöglicht so den Multi-Tenant-Betrieb.
|
||||||
|
|
||||||
**Entität HallOfFameReport**
|
**Entität HallOfFameReport**
|
||||||
|
|
||||||
Die zweite Entität bildet einzelne Meldungen zu einem Eintrag ab und wird in der Tabelle `SZUAbsolventenvereinHallOfFameReport` gespeichert.
|
Die zweite Entität bildet einzelne Meldungen zu einem Eintrag ab und wird in der Tabelle `SZUAbsolventenvereinHallOfFameReport` gespeichert.
|
||||||
|
|
||||||
| Spalte | Datentyp | Beschreibung |
|
| Spalte | Datentyp | Beschreibung |
|
||||||
|--------|----------|--------------|
|
|--------|----------|--------------|
|
||||||
| `HallOfFameReportId` | `int` (PK, Auto-Inkrement) | Primärschlüssel |
|
| `HallOfFameReportId` | `int` (PK, Auto-Inkrement) | Primärschlüssel |
|
||||||
@@ -86,70 +270,131 @@ Die zweite Entität bildet einzelne Meldungen zu einem Eintrag ab und wird in de
|
|||||||
| `CreatedOn` | `DateTime` | Erstellzeitpunkt (Audit) |
|
| `CreatedOn` | `DateTime` | Erstellzeitpunkt (Audit) |
|
||||||
| `ModifiedBy` | `string` | Zuletzt geändert von (Audit) |
|
| `ModifiedBy` | `string` | Zuletzt geändert von (Audit) |
|
||||||
| `ModifiedOn` | `DateTime` | Zeitpunkt der letzten Änderung (Audit) |
|
| `ModifiedOn` | `DateTime` | Zeitpunkt der letzten Änderung (Audit) |
|
||||||
Der Fremdschlüssel zu `SZUAbsolventenvereinHallOfFame` ist mit kaskadierendem Löschen konfiguriert, sodass beim Löschen eines Eintrags automatisch alle zugehörigen Meldungen entfernt werden. Zwischen den beiden Entitäten besteht somit eine 1:n-Beziehung: Ein Eintrag kann beliebig viele Meldungen besitzen.
|
|
||||||
|
Der Fremdschlüssel zu `SZUAbsolventenvereinHallOfFame` ist mit kaskadierendem Löschen konfiguriert, sodass beim Löschen eines Eintrags automatisch alle zugehörigen Meldungen entfernt werden. Zwischen den beiden Entitäten besteht eine 1:n-Beziehung: Ein Eintrag kann beliebig viele Meldungen besitzen.
|
||||||
|
|
||||||
#### Datenbankmigrationen
|
#### Datenbankmigrationen
|
||||||
Die Datenbankstruktur wird über Entity Framework Core Migrationen versioniert verwaltet. Oqtane verwendet ein eigenes Migrationssystem, das auf der Klasse `MultiDatabaseMigration` basiert und die Kompatibilität mit verschiedenen Datenbankanbietern sicherstellt.
|
|
||||||
|
Die Datenbankstruktur wird über Entity Framework Core Migrationen versioniert verwaltet.
|
||||||
|
|
||||||
| Migration | Versionsnummer | Inhalt |
|
| Migration | Versionsnummer | Inhalt |
|
||||||
|-----------|---------------|--------|
|
|-----------|----------------|--------|
|
||||||
| `InitializeModule` | `01.00.00.00` | Erstellt die Haupttabelle mit allen Grundspalten (Name, Year, Description, Image, Link, Status, UserId) sowie den Audit-Spalten |
|
| `InitializeModule` | `01.00.00.00` | Erstellt die Haupttabelle mit allen Grundspalten sowie den Audit-Spalten |
|
||||||
| `AddReportingColumns` | `01.00.00.02` | Erweitert die Haupttabelle um die Spalten `IsReported` und `ReportReason` |
|
| `AddReportingColumns` | `01.00.00.02` | Erweitert die Haupttabelle um die Spalten `IsReported` und `ReportReason` |
|
||||||
| `AddReportTable` | `01.00.00.03` | Erstellt die eigenständige Report-Tabelle mit Fremdschlüssel zur Haupttabelle |
|
| `AddReportTable` | `01.00.00.03` | Erstellt die eigenständige Report-Tabelle mit Fremdschlüssel zur Haupttabelle |
|
||||||
Jede Migration definiert sowohl eine Aufwärts- als auch eine Abwärtsmethode, sodass ein Rollback möglich ist.
|
|
||||||
#### Benutzeroberfläche (Razor-Komponenten)
|
|
||||||
Das Modul umfasst vier Blazor-Razor-Komponenten.
|
|
||||||
**Index.razor – Übersichtsseite**
|
|
||||||
Die Übersichtsseite zeigt alle veröffentlichten Einträge in einem responsiven Kartenlayout mit drei Spalten auf Desktop-Breite. Die Funktionalität umfasst eine Echtzeit-Textsuche über Name und Beschreibung, eine umschaltbare Sortierung nach Datum, Name oder Jahrgang sowie eine Kartenanzeige mit Bild, Name, Jahrgang und gekürzter Beschreibung. Administratorinnen und Administratoren sehen zusätzlich Warnmeldungen bei gemeldeten Einträgen und einen Lösch-Button. Angemeldete Benutzerinnen und Benutzer können über einen eigenen Button ihren Eintrag erstellen oder bearbeiten.
|
|
||||||
**Edit.razor – Erstellungs- und Bearbeitungsseite**
|
|
||||||
Die Edit-Komponente dient sowohl zum Erstellen als auch zum Bearbeiten eines Eintrags. Sie bietet Formularfelder für Name, Jahrgang, Beschreibung (mit Live-Zeichenzähler), Foto-Upload und Link. Über zwei Speicheroptionen kann zwischen Entwurf und Veröffentlichung gewählt werden. Eine Eigentümerprüfung verhindert das Bearbeiten fremder Einträge, und eine Duplikatprüfung verhindert das Erstellen mehrerer Einträge pro Person.
|
|
||||||
**Details.razor – Detailseite**
|
|
||||||
Die Details-Komponente zeigt einen einzelnen Eintrag in einem zweispaltigen Layout mit unscharfem Bildhintergrund. Administratorinnen und Administratoren sehen bei gemeldeten Einträgen eine Liste aller Meldungen mit Lösch-Button. Ein modaler Dialog ermöglicht die PDF-Vorschau sowie den Download. Die Meldefunktion wird über die zentrale `IReportUI`-Komponente eingebunden.
|
|
||||||
**Settings.razor – Moduleinstellungen**
|
|
||||||
Die Settings-Komponente bietet eine einfache Oberfläche für Moduleinstellungen über Oqtanes Setting-Service.
|
|
||||||
#### Gemeinsame Melde-Komponente (IReportUI)
|
|
||||||
Die Meldefunktion in der Detailseite ist nicht direkt im Hall-of-Fame-Modul implementiert, sondern wird über eine zentrale Schnittstelle aus dem Interfaces-Paket eingebunden. Das Hall-of-Fame-Modell implementiert das Interface `IReportable`, das eine Entität als meldbar kennzeichnet. In der Detailseite wird per Dependency Injection eine `IReportUI`-Implementierung injiziert – die konkrete `ReportComponent` stammt dabei aus dem Admin-Modul und stellt den Melden-Button samt modalem Dialog bereit. Die Komponente wird über Blazors `DynamicComponent` dynamisch gerendert. Ist keine Implementierung im Container registriert, wird die Meldefunktion schlicht nicht angezeigt. Dieses Konzept ermöglicht es, die Melde-Oberfläche zentral zu pflegen und in beliebig vielen weiteren Modulen wiederzuverwenden, ohne dass die einzelnen Module die Melde-Logik selbst implementieren müssen.
|
|
||||||
#### PDF-Export mit QuestPDF
|
|
||||||
Für die Generierung der PDF-Dokumente wird die Open-Source-Bibliothek QuestPDF in der Community-Edition eingesetzt. Im Server-Projekt wurde ein benutzerdefiniertes MSBuild-Target erstellt, das nach jedem Build automatisch die QuestPDF-DLL sowie die plattformspezifischen nativen Bibliotheken in das bin-Verzeichnis des Oqtane-Servers kopiert. Dies ist notwendig, weil Oqtane Module zur Laufzeit dynamisch lädt und QuestPDF native Abhängigkeiten (unter anderem die SkiaSharp-Rendering-Engine) benötigt.
|
|
||||||
Das PDF-Design folgt einem Glasmorphismus-Ansatz. Jede Seite hat das Format DIN A4 ohne Ränder. Über die Layers-API von QuestPDF wird das Hintergrundbild von der Inhaltsebene getrennt. Der Name wird in 36 Punkt ExtraBold mit Großbuchstaben und Zeichenabstand dargestellt, der Jahrgang in 15 Punkt mit erhöhtem Zeichenabstand und die Beschreibung in 11 Punkt mit 1,5-fachem Zeilenabstand. Der Glaseffekt wird durch halbtransparente dunkle Hintergründe mit mehrschichtigen Rahmen unterschiedlicher Transparenz erzeugt.
|
|
||||||
#### Implementierungsdetails und Problemlösungen
|
#### Implementierungsdetails und Problemlösungen
|
||||||
|
|
||||||
Während der Entwicklung traten mehrere technische Herausforderungen auf, die im Folgenden zusammen mit ihren Lösungen beschrieben werden.
|
Während der Entwicklung traten mehrere technische Herausforderungen auf, die im Folgenden zusammen mit ihren Lösungen beschrieben werden.
|
||||||
|
|
||||||
**Bild-Upload-System**
|
**Bild-Upload-System**
|
||||||
In der ursprünglichen Version des Moduls mussten Benutzerinnen und Benutzer eine Bild-URL manuell in ein Textfeld eingeben. Das implementierte Bild-Upload-System ersetzt dies durch eine Blazor-InputFile-Komponente mit Live-Vorschau, Fortschrittsanzeige und Lösch-Button. Die Upload-Methode prüft die Dateigröße (maximal 5 MB), öffnet die Datei als Stream und übermittelt sie als MultipartFormDataContent an den Server, der den Dateityp serverseitig validiert, einen UUID-Dateinamen generiert und die Datei speichert. Das System unterstützt beide Blazor-Rendering-Modi.
|
|
||||||
|
In der ursprünglichen Version des Moduls mussten Benutzerinnen und Benutzer eine Bild-URL manuell in ein Textfeld eingeben. Dies war wenig benutzerfreundlich und erforderte, dass Bilder zunächst anderweitig hochgeladen und verlinkt werden mussten. Das implementierte Bild-Upload-System ersetzt dies durch eine Blazor-`InputFile`-Komponente mit Live-Vorschau, Fortschrittsanzeige und Lösch-Button. Die Upload-Methode prüft die Dateigröße (maximal 5 MB), öffnet die Datei als Stream und übermittelt sie als `MultipartFormDataContent` an den Server, der den Dateityp serverseitig validiert, einen UUID-Dateinamen generiert und die Datei speichert. Das System unterstützt beide Blazor-Rendering-Modi.
|
||||||
|
|
||||||
**Concurrency Exception beim Löschen**
|
**Concurrency Exception beim Löschen**
|
||||||
|
|
||||||
Beim Löschen von Einträgen mit vorhandenen Meldungen trat eine `DbUpdateConcurrencyException` auf, verursacht durch Konflikte im Entity Framework Change Tracker. Die Lösung bestand in der Aufteilung der Löschoperation in zwei separate Transaktionen mit jeweils eigenem DbContext: zuerst werden alle zugehörigen Meldungen gelöscht, danach der Haupteintrag.
|
Beim Löschen von Einträgen mit vorhandenen Meldungen trat eine `DbUpdateConcurrencyException` auf, verursacht durch Konflikte im Entity Framework Change Tracker. Die Lösung bestand in der Aufteilung der Löschoperation in zwei separate Transaktionen mit jeweils eigenem DbContext: zuerst werden alle zugehörigen Meldungen gelöscht, danach der Haupteintrag.
|
||||||
|
|
||||||
**Kartendesign-Optimierung**
|
**Kartendesign-Optimierung**
|
||||||
|
|
||||||
Karten hatten ursprünglich unterschiedliche Höhen durch variierende Beschreibungslängen. Die Lösung kombiniert CSS-Flexbox auf dem Kartenelement mit einer Maximalhöhe von 150 Pixeln und `overflow: hidden` auf dem Beschreibungscontainer, sodass alle Karten einer Zeile dieselbe Höhe aufweisen.
|
Karten hatten ursprünglich unterschiedliche Höhen durch variierende Beschreibungslängen. Die Lösung kombiniert CSS-Flexbox auf dem Kartenelement mit einer Maximalhöhe von 150 Pixeln und `overflow: hidden` auf dem Beschreibungscontainer, sodass alle Karten einer Zeile dieselbe Höhe aufweisen.
|
||||||
|
|
||||||
**Sortier-Toggle**
|
**Sortier-Toggle**
|
||||||
|
|
||||||
Die ursprünglich fest codierten Sortierrichtungen wurden durch einen Toggle-Button neben dem Sortier-Dropdown ersetzt, der mit einem dynamischen Pfeil-Icon zwischen aufsteigender und absteigender Sortierung umschaltet. Die Sortierlogik ist in einer berechneten Eigenschaft gekapselt, die Suche und Sortierung kombiniert.
|
Die ursprünglich fest codierten Sortierrichtungen wurden durch einen Toggle-Button neben dem Sortier-Dropdown ersetzt, der mit einem dynamischen Pfeil-Icon zwischen aufsteigender und absteigender Sortierung umschaltet. Die Sortierlogik ist in einer berechneten Eigenschaft gekapselt, die Suche und Sortierung kombiniert.
|
||||||
|
|
||||||
---
|
---
|
||||||
## 8. Projektorganisation & Teamarbeit
|
|
||||||
### 8.1 Planung & Meilensteine
|
## 5. Übergangslösung, Probleme & Learnings
|
||||||
#### Meilensteine
|
|
||||||
#### Soll-/Ist-Vergleich
|
### 5.1 Übergangslösung (Sommer 2025)
|
||||||
#### Zeitverzug
|
|
||||||
### 8.2 Teamverkleinerung
|
|
||||||
#### Downsizing von 6 auf 3 Personen
|
|
||||||
#### Neue Aufgabenverteilung
|
|
||||||
#### Auswirkungen auf Theme-Entwicklung, Module und Zeitplanung
|
|
||||||
---
|
|
||||||
## 9. Übergangslösung, Probleme & Learnings
|
|
||||||
### 9.1 Übergangslösung (Sommer 2025)
|
|
||||||
#### Gründe & Technische Umsetzung
|
#### Gründe & Technische Umsetzung
|
||||||
|
|
||||||
|
Da AlumniHub zum Zeitpunkt des ersten Absolventenstreffens im Sommer 2025 noch nicht vollständig fertiggestellt war und das Hosting von Oqtane auf dem Hetzner-Server zu unerwarteten technischen Problemen geführt hatte, wurde im Team die Entscheidung getroffen, eine eigenständige Übergangslösung zu entwickeln. Diese sollte pünktlich zur Veranstaltung einsatzbereit sein und die wichtigsten Funktionen abdecken: die Möglichkeit für Absolventinnen und Absolventen, ihre Teilnahme am Treffen zu bestätigen oder abzusagen, sowie die Möglichkeit, direktes Feedback zum Diplomprojekt AlumniHub abzugeben.
|
||||||
|
|
||||||
|
Da für die Entwicklung nur wenig Zeit zur Verfügung stand und eine vollständige Integration in die noch nicht fertige AlumniHub-Plattform nicht realistisch war, wurde bewusst auf einen schlanken, eigenständigen Technologie-Stack gesetzt. Dieser sollte schnell entwickelt, einfach deploybar und zuverlässig betreibbar sein – ohne die Komplexität eines vollständigen CMS-Systems.
|
||||||
|
|
||||||
|
Das Frontend der Übergangslösung wurde mit den grundlegenden Webtechnologien HTML, CSS und JavaScript umgesetzt. Die Hauptseite (`index.html`) präsentiert den Besucherinnen und Besuchern eine übersichtliche und schlichte Oberfläche, die sich am Erscheinungsbild der HTL Ungargasse orientiert – mit dem Schullogo im Header und einem Grau-Weiß-Farbschema. Im Mittelpunkt der Seite befindet sich ein zentraler Button, über den ein modales Overlay-Formular geöffnet werden kann. Über dieses Formular konnten Nutzerinnen und Nutzer ihre E-Mail-Adresse eingeben und ihr Feedback zum Projekt abgeben. Die Steuerung des Overlays erfolgt über JavaScript: Das Formular öffnet sich beim Klick auf den Button und schließt sich automatisch, wenn außerhalb des Formularfensters geklickt wird. Dadurch ist eine intuitive Bedienung ohne zusätzliche Schaltflächen möglich.
|
||||||
|
|
||||||
|
Für die Verarbeitung von Zu- und Absagen zum Absolvententreffen wurden zusätzlich zwei separate Bestätigungsseiten erstellt: `zusage.html` und `absage.html`. Diese Seiten werden dem Nutzer nach dem erfolgreichen Absenden des jeweiligen Formulars angezeigt und bestätigen den Eingang der Rückmeldung. Das Design aller drei Seiten ist einheitlich gehalten und folgt dem gleichen Layout-Prinzip mit Header, Logo und zentriertem Hauptinhalt.
|
||||||
|
|
||||||
|
Das Backend der Lösung, das für die Verarbeitung der Formulardaten und den Versand von Bestätigungs-E-Mails zuständig war, wurde von einem Teammitglied entwickelt und war nicht Teil des eigenen Aufgabenbereichs. Es wurde mit Node.js und dem Framework Express umgesetzt und auf einem virtuellen Server bei Hetzner Cloud gehostet.
|
||||||
|
|
||||||
#### Differenzen zur finalen Lösung
|
#### Differenzen zur finalen Lösung
|
||||||
### 9.2 Probleme
|
|
||||||
|
Im direkten Vergleich zur finalen AlumniHub-Plattform unterscheidet sich die Übergangslösung in mehreren wesentlichen Punkten. Während AlumniHub auf dem vollständigen CMS Oqtane basiert und eine umfangreiche Funktionspalette bietet – darunter Benutzerverwaltung, Hall of Fame, Anmeldetool, Datenauswertung und ein individuelles Theme – beschränkte sich die Übergangslösung bewusst auf das absolut Notwendige.
|
||||||
|
|
||||||
|
Es gab keine Benutzerkonten und keine Authentifizierung. Jede Person, die die Seite aufrief, konnte das Formular ausfüllen und absenden, ohne sich vorher registrieren oder anmelden zu müssen. Es gab keine Datenbank im klassischen Sinne – die eingegangenen Formulardaten wurden serverseitig als einfache JSON-Dateien gespeichert. Es gab keine administrativen Funktionen, keine Übersichten und keine Möglichkeit, die Daten direkt über eine Benutzeroberfläche auszuwerten.
|
||||||
|
|
||||||
|
Diese bewusste Reduktion auf das Wesentliche war jedoch kein Nachteil, sondern eine pragmatische Entscheidung: Die Lösung musste schnell funktionieren und zuverlässig sein – und das war sie. Nach dem Absolvententreffen wurde die Seite nicht einfach abgeschaltet, sondern zu einer reinen Feedback-Seite für das Diplomprojekt umfunktioniert. Über diese Seite konnten Interessierte – darunter Lehrkräfte, Mitschülerinnen und Mitschüler sowie externe Besucher der Präsentation – weiterhin direktes Feedback zum Projekt abgeben. Die Seite blieb so lange in Betrieb, bis die vollständig entwickelte AlumniHub-Plattform diese Funktion nativ übernahm.
|
||||||
|
|
||||||
|
### 5.2 Probleme
|
||||||
|
|
||||||
#### Technische Probleme
|
#### Technische Probleme
|
||||||
|
|
||||||
|
Eine der größten technischen Herausforderungen im gesamten Projektverlauf war der Zeitdruck, der insbesondere im Vorfeld des ersten Absolventenstreffens spürbar war.
|
||||||
|
|
||||||
|
Ein weiteres technisches Problem ergab sich durch den Wechsel von Windows auf macOS während der Entwicklung. Da Oqtane primär für Windows entwickelt ist und die macOS-Unterstützung veraltet bzw. nicht vollständig angepasst ist, traten nach dem Plattformwechsel Build-Fehler auf und Oqtane startete zunächst nur mit Fehlern. Diese Probleme erschwerten die Weiterentwicklung erheblich, da zunächst die Entwicklungsumgebung stabilisiert werden musste, bevor die eigentliche Arbeit an den Modulen fortgesetzt werden konnte. Durch den Wechsel auf Google Antigravity als IDE konnten die Probleme schließlich gelöst und die Entwicklung auf macOS erfolgreich fortgesetzt werden. Da AlumniHub zu diesem Zeitpunkt noch nicht einsatzbereit war, musste die Übergangslösung in sehr kurzer Zeit konzipiert, entwickelt und in Betrieb genommen werden. Dieser Zeitdruck führte dazu, dass keine ausreichende Zeit für gründliches Testen oder für die Umsetzung zusätzlicher Funktionen blieb.
|
||||||
|
|
||||||
|
Ein weiteres technisches Problem betraf das Hosting von Oqtane auf dem Hetzner-Server. Die Einrichtung und der Betrieb von Oqtane auf dem Server bereiteten unerwartete Schwierigkeiten, die den regulären Entwicklungsfortschritt verlangsamten. Da dieser Bereich nicht zum eigenen Aufgabengebiet gehörte, konnten die genauen Ursachen nicht vollständig nachvollzogen werden. Die Probleme wirkten sich jedoch auf den gesamten Zeitplan des Projekts aus und waren mitverantwortlich dafür, dass AlumniHub zum Zeitpunkt des ersten Treffens noch nicht fertig war.
|
||||||
|
|
||||||
|
In den letzten Monaten des Projekts wurde daher ein Wechsel des Hosting-Anbieters vorgenommen. Statt Hetzner wird die Plattform nun bei LiveDesign gehostet, einem österreichischen Unternehmen, das sich auf Hosting, Webdesign und individuelle Webservices spezialisiert hat. LiveDesign betreibt seine Datacenter ausschließlich in Österreich und bietet eine zuverlässige Infrastruktur für CMS-Systeme wie Oqtane. Durch diesen Wechsel konnten die bestehenden Stabilitätsprobleme behoben und der Betrieb der Plattform deutlich verbessert werden.
|
||||||
|
|
||||||
|
#### Kontakt mit Oqtane- und Hetzner-Support
|
||||||
|
|
||||||
|
Im Verlauf des Projekts wurde sowohl der Support von Oqtane als auch der Support von Hetzner kontaktiert, um Hilfe bei den aufgetretenen technischen Problemen zu erhalten.
|
||||||
|
|
||||||
|
Der Kontakt mit dem Oqtane-Support erfolgte über GitHub Issues sowie per E-Mail. Die gemeldeten Probleme betrafen spezifische Verhaltensweisen des Frameworks beim Hosting auf dem Hetzner-Server. Trotz der Kontaktaufnahme konnte keine hilfreiche Antwort erhalten werden, die zur Lösung der Probleme beigetragen hätte. Die offizielle Dokumentation sowie vorhandene Community-Beiträge lieferten ebenfalls keine ausreichenden Antworten auf die spezifischen Konfigurationsprobleme.
|
||||||
|
|
||||||
|
Auch der Hetzner-Support wurde bezüglich der Serverprobleme kontaktiert. Die Anfragen bezogen sich auf die Serverkonfiguration und mögliche Ursachen für die Instabilität beim Betrieb von Oqtane. Auch hier führten die Rückmeldungen nicht zu einer vollständigen Lösung des Problems. Die Erfahrung zeigte, dass bei der Nutzung spezialisierter Frameworks auf externen Hosting-Plattformen mit eingeschränktem Support gerechnet werden muss und eine intensive Eigenrecherche oft unumgänglich ist.
|
||||||
|
|
||||||
#### Organisatorische Probleme
|
#### Organisatorische Probleme
|
||||||
### 9.3 Learnings
|
|
||||||
|
Neben den technischen Herausforderungen gab es auch auf organisatorischer Ebene ein zentrales Problem, das den Projektverlauf im Sommer 2025 erheblich beeinflusste. Während der Sommermonate war die aktive Mitarbeit innerhalb des Teams sehr ungleich verteilt. Ein Großteil der Teammitglieder war urlaubsbedingt nicht oder nur eingeschränkt erreichbar und beteiligte sich in dieser Zeit kaum am Projekt. Nur ein kleiner Teil des Teams arbeitete in dieser Phase aktiv weiter und trieb den Fortschritt voran.
|
||||||
|
|
||||||
|
Diese Situation führte zu einer deutlichen Ungleichverteilung der Arbeitsbelastung. Wenige Personen mussten in dieser Zeit deutlich mehr Verantwortung übernehmen als ursprünglich geplant, was zu Frustration und Verzögerungen führte. Im Nachhinein zeigt sich, dass klarere Absprachen und verbindlichere Vereinbarungen zu Beginn des Sommers dazu beigetragen hätten, diese Situation zu vermeiden oder zumindest abzumildern.
|
||||||
|
|
||||||
|
### 5.3 Learnings
|
||||||
|
|
||||||
#### Technisch
|
#### Technisch
|
||||||
|
|
||||||
|
Das Projekt hat auf technischer Ebene wertvolle praktische Kenntnisse vermittelt, die über den schulischen Rahmen hinausgehen. Der konsequente Einsatz von Git zur Versionskontrolle war dabei eine der wichtigsten Erfahrungen. Durch die tägliche Arbeit mit Branches, Commits und Merges wurde ein tiefes Verständnis für kollaborative Softwareentwicklung aufgebaut. Es wurde deutlich, wie wichtig aussagekräftige Commit-Nachrichten, regelmäßige Commits und eine saubere Branch-Struktur für die Zusammenarbeit im Team sind.
|
||||||
|
|
||||||
|
Im Bereich Responsive Design wurden durch die praktische Umsetzung des Themes und der Übergangslösung wichtige Kenntnisse vertieft. Es wurde erfahren, wie man mit CSS Media Queries und Flexbox Layouts baut, die sich zuverlässig an unterschiedliche Bildschirmgrößen anpassen. Besonders die Umsetzung des Burger-Menüs für mobile Geräte war eine lehrreiche Erfahrung, die zeigte, wie man komplexe UI-Funktionen mit minimalem JavaScript-Einsatz realisieren kann.
|
||||||
|
|
||||||
|
Auch der Umgang mit JavaScript zur Steuerung von UI-Elementen wurde durch die Arbeit am Projekt verbessert. Die Implementierung der Overlay-Steuerung in der Übergangslösung sowie die Menü-Logik im Theme haben gezeigt, wie JavaScript gezielt und effizient eingesetzt werden kann, um eine bessere Benutzererfahrung zu schaffen.
|
||||||
|
|
||||||
#### Methodisch
|
#### Methodisch
|
||||||
|
|
||||||
|
Auf methodischer Ebene hat das Projekt gezeigt, wie wichtig eine klare und verbindliche Aufgabenverteilung von Beginn an ist. In Phasen, in denen nicht klar war, wer welche Aufgaben übernimmt, entstanden Lücken und Verzögerungen. Eine strukturiertere Planung mit klar definierten Zuständigkeiten hätte in solchen Situationen geholfen, den Überblick zu behalten und die Arbeit effizienter aufzuteilen.
|
||||||
|
|
||||||
|
Darüber hinaus hat sich gezeigt, dass regelmäßige Team-Meetings ein wichtiges Werkzeug für den Projekterfolg sind. In Phasen, in denen der Austausch im Team spärlicher war, dauerte es länger, bis Probleme erkannt und gelöst wurden. Kurze, regelmäßige Abstimmungen hätten dazu beigetragen, den aktuellen Stand besser zu kommunizieren und gemeinsam schneller auf Hindernisse zu reagieren.
|
||||||
|
|
||||||
#### Persönlich
|
#### Persönlich
|
||||||
|
|
||||||
|
Auf persönlicher Ebene war die wichtigste Erkenntnis aus diesem Projekt die Bedeutung von Eigeninitiative. In einem größeren Teamprojekt kann man sich nicht immer darauf verlassen, dass andere Aufgaben erledigen oder Entscheidungen treffen. Gerade in den Phasen, in denen nicht alle Teammitglieder aktiv waren, hat sich gezeigt, dass proaktives Handeln entscheidend ist, um das Projekt voranzubringen. Diese Erfahrung hat das Bewusstsein dafür gestärkt, Verantwortung nicht nur für den eigenen Bereich, sondern auch für das Gesamtprojekt zu übernehmen und bei Bedarf auch Aufgaben außerhalb des ursprünglich geplanten Bereichs zu übernehmen.
|
||||||
|
|
||||||
---
|
---
|
||||||
## 10. Testen & Qualitätssicherung
|
|
||||||
|
## 6. Testen & Qualitätssicherung
|
||||||
|
|
||||||
### Funktionstests der Module
|
### Funktionstests der Module
|
||||||
|
|
||||||
|
Alle entwickelten Module – das Anmeldetool und das Hall-of-Fame-Modul – wurden manuell auf ihre korrekte Funktionsweise getestet. Dabei wurden sämtliche Benutzeraktionen systematisch durchgegangen, darunter das An- und Abmelden zu Veranstaltungen, das Erstellen, Bearbeiten und Veröffentlichen von Hall-of-Fame-Einträgen, das Hochladen von Bildern sowie die Melde- und Löschfunktionen. Fehler und unerwartetes Verhalten wurden dokumentiert und im Anschluss behoben.
|
||||||
|
|
||||||
|
Beim Anmeldetool wurde insbesondere geprüft, ob die Zusage- und Absage-Buttons korrekt reagieren und der Status unmittelbar in der Oberfläche angezeigt wird. Beim Hall-of-Fame-Modul lag ein besonderer Fokus auf dem Bild-Upload: Es wurde getestet, ob die Größenbeschränkung von 5 MB korrekt greift, ob die Vorschau sofort erscheint und ob ungültige Dateiformate abgelehnt werden. Auch die Duplikatprüfung – also die Sicherstellung, dass pro Person nur ein Eintrag erstellt werden kann – sowie die Eigentümerprüfung wurden gezielt getestet.
|
||||||
|
|
||||||
### Theme-Tests
|
### Theme-Tests
|
||||||
|
|
||||||
|
Das entwickelte Oqtane-Theme wurde auf verschiedenen Browsern und Geräten getestet, um eine konsistente Darstellung sicherzustellen. Getestet wurde unter anderem auf aktuellen Versionen von Chrome, Firefox und Safari sowie auf mobilen Geräten mit unterschiedlichen Bildschirmgrößen. Ein besonderer Fokus lag dabei auf der korrekten Darstellung des Burger-Menüs auf mobilen Endgeräten sowie auf der Funktionsfähigkeit der dynamischen Navigation.
|
||||||
|
|
||||||
### Usability-Tests
|
### Usability-Tests
|
||||||
### Bekannte Einschränkungen
|
|
||||||
|
Neben den technischen Tests wurde die Plattform auch von weiteren Personen – darunter Mitschülerinnen und Mitschüler sowie Lehrkräfte – auf ihre Benutzerfreundlichkeit geprüft. Das Feedback aus diesen Tests floss in die weitere Entwicklung ein und führte unter anderem zu Anpassungen in der Darstellung und Bedienung einzelner Elemente.
|
||||||
|
|
||||||
|
Ein häufiges Feedback war, dass die Unterscheidung zwischen Zusage und Absage auf den ersten Blick nicht immer sofort klar war. Daraufhin wurden die Schaltflächen farblich deutlicher gestaltet – Grün für die Zusage, Rot für die Absage – und die Beschriftungen präzisiert. Solche Rückmeldungen aus echten Nutzertests sind wertvoll, weil Entwicklerinnen und Entwickler ihre eigene Anwendung oft anders wahrnehmen als Personen, die das System zum ersten Mal sehen.
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
## 11. Fazit & Ausblick
|
test
|
||||||
### Zielerreichung & Zusammenfassung
|
|
||||||
### Persönliche Reflexion
|
|
||||||
### Erweiterungsmöglichkeiten
|
|
||||||
|
|||||||
@@ -109,37 +109,268 @@ Darüber hinaus besteht das Recht, sich bei der österreichischen Datenschutzbeh
|
|||||||
Durch die Kombination aus klar definierten Rechtsgrundlagen, transparenten Informationspflichten und technischen Schutzmaßnahmen wird ein hohes Datenschutzniveau gewährleistet und der verantwortungsvolle Umgang mit personenbezogenen Daten sichergestellt.
|
Durch die Kombination aus klar definierten Rechtsgrundlagen, transparenten Informationspflichten und technischen Schutzmaßnahmen wird ein hohes Datenschutzniveau gewährleistet und der verantwortungsvolle Umgang mit personenbezogenen Daten sichergestellt.
|
||||||
# 5. Automatisierung von Serverprozessen
|
# 5. Automatisierung von Serverprozessen
|
||||||
|
|
||||||
## 5.1 Notwendigkeit von Datensicherung
|
## 5.1 Notwendigkeit von Datensicherung [^4][^5]
|
||||||
Die Datensicherung ist ein wesentlicher Bestandteil der Website des Absolventenvereins. Sie dient dazu, die auf der Website gespeicherten Daten vor Verlust oder Beschädigung zu schützen.Digitale Daten bilden die Grundlage für die Funktion der Website und müssen daher regelmäßig gesichert werden. Ein Verlust oder eine Beschädigung der Daten kann zu erheblichen Problemen führen.
|
Die Datensicherung ist ein wesentlicher Bestandteil der Website des Absolventenvereins. Sie dient dazu, die auf der Website gespeicherten Daten vor Verlust oder Beschädigung zu schützen.Digitale Daten bilden die Grundlage für die Funktion der Website und müssen daher regelmäßig gesichert werden. Ein Verlust oder eine Beschädigung der Daten kann zu erheblichen Problemen führen.
|
||||||
|
|
||||||
Mögliche Ursachen für einen Datenverlust sind unter anderem Hardwaredefekte, Softwarefehler, menschliches Versagen oder Cyberangriffe.
|
Mögliche Ursachen für einen Datenverlust sind unter anderem Hardwaredefekte, Softwarefehler, menschliches Versagen oder Cyberangriffe.Ohne geeignete Sicherheitsmaßnahmen besteht das Risiko, dass Daten unwiederruflich verloren gehen oder nur mit erheblichen Kosten wiederhergestellt werden können.
|
||||||
|
|
||||||
|
Neben der technischen besteht ebennfalls eine rechtliche und organisatorische Notwendigkeit der Datensicherung. Gemäß Art. 5 Abs. 1 lit. f DSGVO müssen personenbezogene Daten gesichert werden, um die Integrität und Vertraulichkeit der Daten zu gewährleisten. Dazu zählt auch der Schutz vor unbeabsichtigtem Verlust. Eine Backup-Sicherung ist daher ein notwendiger Schritt, um die Daten vor Verlust zu schützen.
|
||||||
|
|
||||||
## 5.2 Konzeption des Backup-Systems
|
## 5.2 Konzeption des Backup-Systems
|
||||||
|
Ziel des Backu-Systems war es im Falle eines DAtenverlustes oder eines Systemausfalls die Daten wiederherstellen zu können. Es wurde eine Startegie enwickelt die es ermöglicht sowohl die Datenbank als auch die Dateien auf dem Server zu sichern.
|
||||||
|
Der Umfang des Backups besteht aus zwei zentralen Komponenten:
|
||||||
|
1. Die Postgres Datenbank
|
||||||
|
2. Der Ordner "oqtane.server" auf dem Server
|
||||||
|
|
||||||
|
Damit wird eine vollständige Sicherung der Website ermöglicht, da bei einer reinen Datenbanksicherung wichtige Anwendungsdatein fehlen würden
|
||||||
|
|
||||||
|
Alle Backups werden lokal auf dem Server gespeichert. Diese Backups sind alle zu finden unter: /var/backups
|
||||||
|
Für jedes Backup wird in diesem Verzeichnis ein Unterordner erstellt,dessen Name einen UTC-Zeitstempel enthält.Das ermöglicht eine saubere Trennung der Sicherungsstände und einfache chronologische Zuordnung.
|
||||||
|
|
||||||
|
TS="$(date -u +'%Y%m%dT%H%M%SZ')"
|
||||||
|
DEST_DIR="${BACKUP_ROOT}/${TS}"
|
||||||
|
|
||||||
|
date -u erzeugt einen Zeitstempel in UTC, wodurch die Bennenung unabhängig von Zeitzonen eindeutig bleibt.
|
||||||
|
Der Zeitstempel wird als Ordnername verwendet, sodass jedes Backup separat abgelegt wird (z. B. 20260226T023000Z).
|
||||||
|
|
||||||
|
Die Sicherung der PostgreSQL-Datenbank erfolgt mithilfe des Programms pg_dump, welches ein vollständiges Abbild der Datenbank erzeugt:
|
||||||
|
|
||||||
|
pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -Fc \
|
||||||
|
-f "${DEST_DIR}/db_${PGDATABASE}.dump"
|
||||||
|
|
||||||
|
Hierbei werden Host, Port, Benutzer und Datenbankname über Variablen definiert, wodurch das Skript flexibel einsetzbar bleibt. Der Parameter -Fc erstellt das Backup im sogenannten Custom-Format. Dieses Format bietet den Vorteil, dass beim Wiederherstellen einzelne Tabellen oder Datenbankobjekte selektiv importiert werden können. Zudem ist es komprimiert und effizienter als ein reines SQL-Textdump. Die Ausgabe wird direkt in eine Datei im zuvor erstellten Backup-Ordner geschrieben. Die Authentifizierung erfolgt über eine .pgpass-Datei, wodurch das Backup automatisiert und ohne manuelle Passworteingabe ausgeführt werden kann.
|
||||||
|
|
||||||
|
Neben der Datenbank wird auch der gesamte Anwendungsordner gesichert. Dies geschieht mithilfe des tar-Programms, das Dateien zu einem komprimierten Archiv zusammenfasst:
|
||||||
|
|
||||||
|
tar -cvpzf "${DEST_DIR}/oqtane_files.tar.gz" \
|
||||||
|
--absolute-names "$OQTANE_DIR"
|
||||||
|
|
||||||
|
Das Programm tar steht für Tape Archive und wurde ursprünglich entwickelt, um mehrere Dateien auf Magnetbändern zu sichern. Heute wird es unter Linux und Unix-Systemen verwendet, um mehrere Dateien und Verzeichnisse zu einem einzigen Archiv zusammenzufassen. Dabei kann das Archiv optional komprimiert werden.
|
||||||
|
|
||||||
|
Der Parameter -c erstellt ein neues Archiv, -v gibt die verarbeiteten Dateien aus, -p erhält die ursprünglichen Dateiberechtigungen und -z aktiviert die gzip-Komprimierung. Die Komprimierung reduziert den Speicherbedarf der Sicherung erheblich. Besonders relevant ist die Option --absolute-names, da dadurch die vollständigen Pfadangaben im Archiv gespeichert werden. Dies erleichtert eine spätere exakte Wiederherstellung der ursprünglichen Verzeichnisstruktur. Durch die Kombination aus Datenbank-Dump und komprimiertem Dateisystem-Archiv entsteht ein vollständiges Vollbackup des Systems.
|
||||||
|
|
||||||
|
Ein Cronjob ist eine zeitgesteuerte Aufgabe unter Linux-Systemen, die automatisch zu einem festgelegten Zeitpunkt ausgeführt wird. Er basiert auf dem Hintergrunddienst cron, der regelmäßig überprüft, ob geplante Befehle gestartet werden müssen.
|
||||||
|
|
||||||
|
Im vorliegenden Projekt wird ein Cronjob verwendet, um das Backup-Skript täglich automatisiert auszuführen. Der entsprechende Eintrag lautet:
|
||||||
|
|
||||||
|
30 2 * * * /home/florian/backup-script.sh >> /home/florian/backup.log 2>&1
|
||||||
|
|
||||||
|
Die ersten fünf Felder definieren den Ausführungszeitpunkt. In diesem Fall bedeutet die Konfiguration, dass das Skript täglich um 02:30 Uhr gestartet wird. Die nächtliche Ausführung reduziert die Systembelastung während der regulären Nutzung.
|
||||||
|
|
||||||
|
Der hintere Teil des Befehls (>> /home/florian/backup.log 2>&1) sorgt dafür, dass sowohl normale Ausgaben als auch Fehlermeldungen in einer Logdatei gespeichert werden. Dadurch kann überprüft werden, ob das Backup erfolgreich durchgeführt wurde.
|
||||||
|
|
||||||
|
Durch den Einsatz eines Cronjobs wird eine regelmäßige und zuverlässige Datensicherung ohne manuelles Eingreifen gewährleistet.
|
||||||
|
|
||||||
|
Ein weiterer Bestandteil des Backup-Systems ist ein automatischer Mechanismus zur Verwaltung alter Sicherungen. Da Backups langfristig Speicherplatz beanspruchen können, wurde eine Funktion implementiert, die nur eine definierte Anzahl an Sicherungen beibehält. In der Konfiguration des Skripts wird festgelegt, dass maximal 30 Backups gespeichert werden.
|
||||||
|
|
||||||
|
RETAIN=30
|
||||||
|
|
||||||
|
Während der Ausführung überprüft das Skript, wie viele Backup-Ordner im Backup-Verzeichnis vorhanden sind. Die Ordner werden anhand ihres Zeitstempels sortiert, wodurch die chronologische Reihenfolge der Sicherungen eindeutig bestimmt werden kann. Sollte die Anzahl der vorhandenen Backups den definierten Wert überschreiten, werden automatisch die ältesten Sicherungen gelöscht.
|
||||||
|
|
||||||
|
if (( ${#backups[@]} > RETAIN )); then
|
||||||
|
for old in "${backups[@]:RETAIN}"; do
|
||||||
|
rm -rf -- "$old"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
Durch diesen Rotationsmechanismus wird verhindert, dass sich im Laufe der Zeit eine unbegrenzte Anzahl von Backups ansammelt und dadurch der verfügbare Speicherplatz auf dem Server erschöpft wird. Gleichzeitig bleibt eine ausreichende Anzahl an Sicherungsständen erhalten, um im Fehlerfall auf frühere Systemzustände zurückgreifen zu können.
|
||||||
|
|
||||||
|
Die gewählte Anzahl von 30 Sicherungen ermöglicht es, bei täglicher Ausführung des Backup-Skripts ungefähr einen Monat an Wiederherstellungspunkten vorzuhalten. Dadurch wird ein sinnvoller Kompromiss zwischen Datensicherheit und effizienter Speicherverwaltung erreicht.
|
||||||
## 5.3 Implementierung der Backup-Skripte
|
## 5.3 Implementierung der Backup-Skripte
|
||||||
|
Die Implementierung des Backup-Systems erfolgte mittels eines Bash-Skripts, das auf dem Linux-Server ausgeführt wird. Bash eignet sich besonders für administrative Aufgaben, da sie direkten Zugriff auf Systembefehle, Dateien und Prozesse bietet. Das Skript wurde so aufgebaut, dass es robust, automatisierbar und nachvollziehbar arbeitet.
|
||||||
|
|
||||||
|
Zu Beginn des Skripts wird folgende Konfiguration gesetzt:
|
||||||
|
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
Die erste Zeile definiert den Interpreter, mit dem das Skript ausgeführt wird. Dadurch wird sichergestellt, dass unabhängig von der Systemkonfiguration die korrekte Bash-Version verwendet wird.
|
||||||
|
|
||||||
|
Die Anweisung set -euo pipefail erhöht die Sicherheit und Stabilität des Skripts:
|
||||||
|
|
||||||
|
-e beendet das Skript sofort, wenn ein Befehl fehlschlägt
|
||||||
|
|
||||||
|
-u verhindert die Verwendung nicht definierter Variablen
|
||||||
|
|
||||||
|
-o pipefail sorgt dafür, dass auch Fehler innerhalb von Befehls-Pipelines erkannt werden
|
||||||
|
|
||||||
|
Diese Einstellungen verhindern, dass das Backup bei Fehlern unbemerkt unvollständig ausgeführt wird.
|
||||||
|
|
||||||
|
Im nächsten Schritt werden zentrale Konfigurationsvariablen definiert:
|
||||||
|
|
||||||
|
BACKUP_ROOT="/var/backups/fullserver"
|
||||||
|
RETAIN=30
|
||||||
|
|
||||||
|
PGHOST="localhost"
|
||||||
|
PGPORT="5432"
|
||||||
|
PGUSER="florian"
|
||||||
|
PGDATABASE="oqtane"
|
||||||
|
|
||||||
|
Durch die Verwendung von Variablen bleibt das Skript flexibel und wartbar. Änderungen, beispielsweise am Speicherort oder an der Datenbank, können an einer zentralen Stelle vorgenommen werden, ohne den restlichen Code anzupassen.
|
||||||
|
|
||||||
|
Die Sicherung der PostgreSQL-Datenbank erfolgt mittels pg_dump im Custom-Format. Dieses Format ist besonders geeignet, da es eine flexible Wiederherstellung ermöglicht. Die Ausgabe wird direkt in den jeweiligen Backup-Ordner geschrieben. Die Authentifizierung erfolgt über eine .pgpass-Datei, wodurch das Backup ohne interaktive Passworteingabe automatisiert durchgeführt werden kann.
|
||||||
|
|
||||||
|
Anschließend wird der gesamte Anwendungsordner mit tar archiviert und komprimiert. Dabei werden Dateirechte und Verzeichnisstrukturen vollständig erhalten. Dies ist insbesondere bei Webanwendungen wichtig, da falsche Berechtigungen nach einer Wiederherstellung zu Funktionsstörungen führen könnten.
|
||||||
|
|
||||||
|
Ein weiterer wichtiger Bestandteil der Implementierung ist das integrierte Rotationsprinzip. Das Skript überprüft automatisch, wie viele Backup-Ordner vorhanden sind. Falls die definierte Grenze von 30 Sicherungen überschritten wird, werden die ältesten Backups gelöscht. Dadurch wird verhindert, dass der verfügbare Speicherplatz durch alte Sicherungen dauerhaft belegt wird. Die Sortierung erfolgt anhand der Zeitstempel im Ordnernamen, wodurch die chronologische Reihenfolge eindeutig bestimmbar ist.
|
||||||
|
|
||||||
|
Zusätzlich gibt das Skript Statusmeldungen mit Zeitangaben aus:
|
||||||
|
|
||||||
|
echo "[$(date -Iseconds)] Backup-Job abgeschlossen."
|
||||||
|
|
||||||
|
Diese Meldungen werden bei automatischer Ausführung über den Cronjob in eine Logdatei geschrieben. Dadurch kann jederzeit überprüft werden, ob das Backup erfolgreich abgeschlossen wurde oder ob Fehler aufgetreten sind.
|
||||||
|
|
||||||
|
Die Implementierung zeichnet sich durch folgende Eigenschaften aus:
|
||||||
|
|
||||||
|
vollständige Sicherung von Datenbank und Anwendungsdateien
|
||||||
|
|
||||||
|
automatische tägliche Ausführung
|
||||||
|
|
||||||
|
integrierte Fehlerbehandlung
|
||||||
|
|
||||||
|
strukturierte Archivierung mit Zeitstempel
|
||||||
|
|
||||||
|
automatische Speicherverwaltung durch Rotationsmechanismus
|
||||||
|
|
||||||
|
Durch diese Umsetzung wurde ein zuverlässiges und wartbares Backup-System geschaffen, das den kontinuierlichen Betrieb der Webanwendung unterstützt und im Fehlerfall eine schnelle Wiederherstellung ermöglicht.
|
||||||
## 5.4 Wiederherstellung von Daten mittels Restore-Skripten
|
## 5.4 Wiederherstellung von Daten mittels Restore-Skripten
|
||||||
|
Neben der Datensicherung stellt die strukturierte Wiederherstellung der Daten einen zentralen Bestandteil des Backup-Systems dar. Zu diesem Zweck wurde ein eigenes Bash-Skript implementiert, das sowohl die PostgreSQL-Datenbank als auch die Anwendungsdateien aus einem gewählten Backup-Ordner wiederherstellt
|
||||||
|
|
||||||
|
Resolve-Skript
|
||||||
|
|
||||||
|
. Ziel war es, einen klar definierten und kontrollierten Prozess zu schaffen, der im Fehlerfall eine vollständige Rücksetzung des Systems ermöglicht.
|
||||||
|
|
||||||
|
Das Skript beginnt – analog zum Backup-Skript – mit einer sicheren Ausführungskonfiguration:
|
||||||
|
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
Auch hier sorgt set -euo pipefail dafür, dass das Skript bei Fehlern sofort abbricht, keine undefinierten Variablen verwendet werden und Fehler innerhalb von Befehls-Pipelines korrekt erkannt werden. Dadurch wird verhindert, dass ein unvollständiger oder fehlerhafter Restore durchgeführt wird.
|
||||||
|
|
||||||
|
Zu Beginn wird festgelegt, welcher Backup-Ordner verwendet werden soll. Wird beim Start ein Argument übergeben, wird dieses als Backup-Verzeichnis interpretiert. Andernfalls wird automatisch das neueste Backup ausgewählt:
|
||||||
|
|
||||||
|
if [[ $# -ge 1 ]]; then
|
||||||
|
BACKUP_SUBDIR="$1"
|
||||||
|
else
|
||||||
|
BACKUP_SUBDIR="$(ls -1 "${BACKUP_ROOT}" | sort | tail -n 1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
Diese Logik ermöglicht eine flexible Nutzung des Skripts. Administratoren können gezielt einen bestimmten Sicherungsstand auswählen oder standardmäßig den aktuellsten verwenden.
|
||||||
|
|
||||||
|
Vor Beginn der Wiederherstellung erfolgt eine deutliche Sicherheitswarnung:
|
||||||
|
|
||||||
|
read -rp "Fortfahren? (ja/nein): " answer
|
||||||
|
if [[ "$answer" != "ja" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
Diese Abfrage dient als Schutzmechanismus, da der Restore-Prozess bestehende Daten überschreibt. Erst nach expliziter Bestätigung wird die Wiederherstellung gestartet.
|
||||||
|
|
||||||
|
Die Wiederherstellung der PostgreSQL-Datenbank erfolgt in mehreren Schritten. Zunächst wird – falls vorhanden – die bestehende Datenbank gelöscht:
|
||||||
|
|
||||||
|
dropdb -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" "$PGDATABASE"
|
||||||
|
|
||||||
|
Anschließend wird eine neue, leere Datenbank erstellt:
|
||||||
|
|
||||||
|
createdb -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" "$PGDATABASE"
|
||||||
|
|
||||||
|
Danach wird der zuvor gesicherte Dump eingespielt:
|
||||||
|
|
||||||
|
pg_restore -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
|
||||||
|
-d "$PGDATABASE" -c "$DUMP_FILE"
|
||||||
|
|
||||||
|
Das Programm pg_restore liest das im Custom-Format gespeicherte Backup ein und stellt sämtliche Tabellen, Indizes und Datenbankobjekte wieder her. Die Option -c sorgt dafür, dass bestehende Objekte vor dem Import entfernt werden, wodurch Konflikte vermieden werden.
|
||||||
|
|
||||||
|
Neben der Datenbank wird auch der Anwendungsordner wiederhergestellt. Bevor die neuen Dateien entpackt werden, wird der aktuell vorhandene Ordner zur Sicherheit umbenannt:
|
||||||
|
|
||||||
|
mv "$OQTANE_DIR" "${OQTANE_DIR}.old.$(date +%s)"
|
||||||
|
|
||||||
|
Diese Maßnahme verhindert einen vollständigen Datenverlust, falls beim Restore ein Problem auftreten sollte. Anschließend wird das Archiv entpackt:
|
||||||
|
|
||||||
|
tar -xvpzf "$FILES_ARCHIVE"
|
||||||
|
|
||||||
|
Der Parameter -x extrahiert das Archiv, -p stellt die ursprünglichen Dateiberechtigungen wieder her und -z dekomprimiert das gzip-Archiv. Dadurch wird die komplette Ordnerstruktur in ihrem ursprünglichen Zustand wiederhergestellt.
|
||||||
|
|
||||||
|
Zusammenfassend ermöglicht das Restore-Skript eine vollständige Rücksetzung der Webanwendung auf einen definierten Sicherungsstand. Durch die Kombination aus Sicherheitsabfrage, kontrollierter Datenbank-Neuerstellung und strukturiertem Dateirestore wird ein zuverlässiger und nachvollziehbarer Wiederherstellungsprozess gewährleistet. Das System ergänzt somit das Backup-Konzept um eine praxisnahe und technisch saubere Lösung für den Ernstfall.
|
||||||
|
|
||||||
|
## 5.5 Fazit zur Datensicherung
|
||||||
|
|
||||||
|
Durch die Implementierung eines automatisierten Backup- und Restore-Systems wurde eine zuverlässige Grundlage für die Datensicherheit der Webanwendung geschaffen. Das entwickelte System ermöglicht eine regelmäßige Sicherung sowohl der PostgreSQL-Datenbank als auch der vollständigen Anwendungsdateien. Durch die automatisierte Ausführung mittels Cronjob wird sichergestellt, dass die Backups ohne manuelles Eingreifen täglich erstellt werden.
|
||||||
|
|
||||||
|
Zusätzlich sorgt das integrierte Rotationsprinzip dafür, dass ältere Sicherungen automatisch entfernt werden und der verfügbare Speicherplatz effizient genutzt wird. Die strukturierte Ablage der Backups mit Zeitstempeln erleichtert die Auswahl eines bestimmten Sicherungsstandes.
|
||||||
|
|
||||||
|
Neben der Datensicherung wurde auch ein Restore-Skript implementiert, das eine vollständige Wiederherstellung des Systems ermöglicht. Dabei werden sowohl die Datenbank als auch die Anwendungsdateien aus einem ausgewählten Backup wiederhergestellt. Sicherheitsmechanismen wie Bestätigungsabfragen und das temporäre Umbenennen bestehender Daten reduzieren das Risiko von Datenverlust während des Wiederherstellungsprozesses.
|
||||||
|
|
||||||
|
Insgesamt stellt das entwickelte Backup-System eine robuste und praxisnahe Lösung dar, die den sicheren Betrieb der Webanwendung unterstützt und im Fehlerfall eine schnelle Wiederherstellung der Daten ermöglicht.
|
||||||
|
|
||||||
# 6. Benutzerverwaltung und Authentifizierung per LinkedIn
|
# 6. Benutzerverwaltung und Authentifizierung per LinkedIn
|
||||||
## 6.1 Anmeldung über LinkedIn
|
## 6.1 Authentifizierung mittels OAuth 2.0
|
||||||
## 6.2 Profilabgleich und Benutzerverifikation
|
Zur Anmeldung auf der entwickelten Webplattform wurde eine Authentifizierung über das Business-Netzwerk LinkedIn implementiert. Dabei kommt das standardisierte Autorisierungsprotokoll OAuth 2.0 zum Einsatz. Dieses Verfahren ermöglicht es, Benutzer über externe Identitätsanbieter zu authentifizieren, ohne dass deren Zugangsdaten direkt an die Webanwendung übertragen werden müssen.
|
||||||
|
|
||||||
# 7. Octane
|
OAuth 2.0 basiert auf dem Prinzip der delegierten Autorisierung. Dabei erlaubt ein Benutzer einer Anwendung, bestimmte Informationen seines Kontos bei einem externen Dienst zu verwenden. Die eigentlichen Zugangsdaten – beispielsweise das LinkedIn-Passwort – verbleiben dabei ausschließlich beim Identitätsanbieter.
|
||||||
|
|
||||||
## 7.1 Einführung in das verwendete CMS Octane
|
Der grundlegende Ablauf einer OAuth-Authentifizierung ist in Abbildung X dargestellt. Dabei wird der Benutzer zunächst zur Login-Seite des externen Anbieters weitergeleitet. Nach erfolgreicher Anmeldung bestätigt der Benutzer, dass die Anwendung Zugriff auf bestimmte Profildaten erhalten darf.
|
||||||
## 7.2 Modulares System von Octane
|
 Erstellt von ChatGbt
|
||||||
## 7.3 Entwicklung eigener Module
|
|
||||||
## 7.4 Vorteile der modularen Erweiterbarkeit
|
|
||||||
|
|
||||||
# 8. Implementierung des Premium-Bereichs
|
Die Abbildung zeigt den Ablauf der OAuth-Authentifizierung. Zunächst erfolgt die Weiterleitung zur LinkedIn-Anmeldeseite, anschließend wird ein Autorisierungscode an die Webanwendung zurückgegeben, welcher gegen einen Zugriffstoken ausgetauscht wird.
|
||||||
|
|
||||||
## 8.1 Ziel und Zweck des Premium-Bereichs
|
Nach der Autorisierung sendet LinkedIn einen sogenannten Authorization Code an die Webanwendung zurück. Dieser Code wird anschließend vom Server der Anwendung gegen einen Access Token ausgetauscht. Mit Hilfe dieses Tokens kann die Webanwendung anschließend die freigegebenen Benutzerdaten vom LinkedIn-Server abrufen.
|
||||||
## 8.2 Funktionen und Features des Premium-Zugangs
|
|
||||||
## 8.3 Zugriffsbeschränkung und Benutzerrechte
|
|
||||||
## 8.4 Mehrwert für registrierte Mitglieder
|
|
||||||
|
|
||||||
# 9. Technologien
|
Der Vorteil dieses Verfahrens liegt darin, dass die Webanwendung zu keinem Zeitpunkt Zugriff auf das Passwort des Benutzers erhält. Dadurch wird ein höheres Sicherheitsniveau erreicht und gleichzeitig die Benutzerfreundlichkeit verbessert, da sich Benutzer mit ihrem bestehenden LinkedIn-Konto anmelden können.
|
||||||
|
## 6.2 Technische Umsetzung der LinkedIn-Anmeldung in Oqtane
|
||||||
|
Die technische Umsetzung der LinkedIn-Authentifizierung erfolgte über das integrierte External Login System des Content-Management-Systems Oqtane. Dieses System ermöglicht die Integration externer Identitätsanbieter über standardisierte Protokolle wie OAuth 2.0.
|
||||||
|
|
||||||
## 9.1 ASP.NET und .NET
|
Für die Verbindung mit LinkedIn wurde zunächst eine Entwickleranwendung im LinkedIn Developer Portal erstellt. Dabei werden zwei zentrale Zugangsdaten generiert:
|
||||||
## 9.2 Postgres Datenbank
|
|
||||||
## 9.3 LinkedIn OAuth 2.0
|
Client ID
|
||||||
|
|
||||||
|
Client Secret
|
||||||
|
|
||||||
|
Diese dienen zur Identifikation der Webanwendung gegenüber den LinkedIn-Servern.
|
||||||
|
|
||||||
|
Innerhalb der Oqtane-Konfiguration wurde anschließend ein neuer OAuth-Provider eingerichtet. Dabei mussten mehrere Parameter definiert werden, darunter die Autorisierungs- und Token-Endpunkte von LinkedIn.
|
||||||
|
|
||||||
|
Beispielsweise wird für die Benutzerautorisierung folgende Adresse verwendet:
|
||||||
|
|
||||||
|
https://www.linkedin.com/oauth/v2/authorization
|
||||||
|
|
||||||
|
Nach erfolgreicher Anmeldung stellt LinkedIn über folgenden Endpunkt ein Zugriffstoken bereit:
|
||||||
|
|
||||||
|
https://www.linkedin.com/oauth/v2/accessToken
|
||||||
|
|
||||||
|
Zusätzlich wurde eine sogenannte Redirect URL definiert. Diese URL wird von LinkedIn verwendet, um den Benutzer nach erfolgreicher Authentifizierung wieder zurück zur Webanwendung zu leiten.
|
||||||
|
|
||||||
|
In der Konfiguration wurde außerdem festgelegt, welche Benutzerdaten von LinkedIn übernommen werden sollen. Dazu gehören unter anderem:
|
||||||
|
|
||||||
|
Benutzername
|
||||||
|
|
||||||
|
E-Mail-Adresse
|
||||||
|
|
||||||
|
eindeutige Benutzer-ID
|
||||||
|
|
||||||
|
Diese Daten werden von Oqtane als sogenannte Claims verarbeitet und anschließend dem Benutzerkonto der Plattform zugeordnet.
|
||||||
|
|
||||||
|
Darüber hinaus wurde in den Einstellungen aktiviert, dass bei der ersten Anmeldung automatisch ein neues Benutzerkonto erstellt werden kann. Dadurch können sich neue Nutzer direkt über ihr LinkedIn-Konto registrieren.
|
||||||
|
## 6.3 Ablauf des Anmeldeprozesses
|
||||||
|
Der eigentliche Anmeldevorgang erfolgt in mehreren aufeinanderfolgenden Schritten. Zunächst wählt der Benutzer auf der Login-Seite der Webplattform die Option zur Anmeldung über LinkedIn aus.
|
||||||
|
|
||||||
|
Daraufhin wird der Benutzer zur LinkedIn-Authentifizierungsseite weitergeleitet. Dort meldet sich der Benutzer mit seinem LinkedIn-Konto an und bestätigt die Autorisierung der Anwendung.
|
||||||
|
|
||||||
|
Nach erfolgreicher Authentifizierung sendet LinkedIn eine Antwort an die zuvor definierte Redirect-URL der Webanwendung. Diese Antwort enthält einen Autorisierungscode, der anschließend vom Server der Webanwendung gegen einen Access Token ausgetauscht wird.
|
||||||
|
|
||||||
|
Mit Hilfe dieses Tokens kann die Anwendung anschließend die freigegebenen Benutzerdaten vom LinkedIn-Server abrufen. Diese Daten werden verwendet, um den Benutzer in der lokalen Datenbank zu identifizieren oder ein neues Benutzerkonto zu erstellen.
|
||||||
|
|
||||||
|
# 7. Implementierung des Premium-Bereichs
|
||||||
|
## 7.1 Ziel und Zweck des Premium-Bereichs
|
||||||
|
## 7.2 Funktionalität und Features des Premium-Bereichs
|
||||||
|
## 7.3 Zugriffsbeschränkung und Benutzerrechte
|
||||||
|
## 7.4 Mehrwert für registrierte Mitglieder
|
||||||
|
|
||||||
|
# 8. Technologien
|
||||||
|
|
||||||
|
## 8.1 ASP.NET und .NET
|
||||||
|
## 8.2 Postgres Datenbank
|
||||||
|
## 8.3 LinkedIn OAuth 2.0
|
||||||
|
|
||||||
# 10. Learnigs
|
# 10. Learnigs
|
||||||
## 10.1 Technische und fachliche Erkenntnisse
|
## 10.1 Technische und fachliche Erkenntnisse
|
||||||
@@ -160,4 +391,9 @@ Mögliche Ursachen für einen Datenverlust sind unter anderem Hardwaredefekte, S
|
|||||||
|
|
||||||
[^3]: DSB – Österreichische Datenschutzbehörde: https://dsb.gv.at/
|
[^3]: DSB – Österreichische Datenschutzbehörde: https://dsb.gv.at/
|
||||||
|
|
||||||
|
[^4]:Datensicherung und Datenverlust: https://www.bsi.bund.de/DE/Themen/Verbraucherinnen-und-Verbraucher/Informationen-und-Empfehlungen/Cyber-Sicherheitsempfehlungen/Daten-sichern-verschluesseln-und-loeschen/Datensicherung-und-Datenverlust/Datensicherung-wie-geht-das/datensicherung-wie-geht-das_node.html
|
||||||
|
|
||||||
|
[^5]: ChatGPT Notwendigkeit der Datensicherung: https://chatgpt.com/c/69a06632-e4fc-8384-bd6c-a543d7bbd00d
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,59 @@ include_toc: true
|
|||||||
gitea: none
|
gitea: none
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 1. Einleitung des individuellen Teils
|
||||||
|
|
||||||
|
In diesem Abschnitt wird meine persönliche Aufgabenstellung im Rahmen des Projektes (`Alumnihub`) beschrieben.
|
||||||
|
|
||||||
|
### Auftrag / persönliche Aufgabenstellungen
|
||||||
|
Meine Zuständigkeiten und Verantwortlichkeiten:
|
||||||
|
- Product Owner
|
||||||
|
- Infrastruktur
|
||||||
|
- Entwicklung
|
||||||
|
- Auswertungen
|
||||||
|
- Schwarzes Brett
|
||||||
|
|
||||||
|
### Motivation
|
||||||
|
|
||||||
|
Lernen von ASP.NET und der Entwicklung mit Blazor und Oqtane. Ich habe Interesse an dem Thema Webentwicklung. Privat entwickle ich schon seit Jahren viel mit React.JS.
|
||||||
|
|
||||||
|
## 2. Anforderungen an das entwickelte Modul bzw. die Funktionalität
|
||||||
|
|
||||||
|
- funktionale / nicht‑funktionale Anforderungen
|
||||||
|
- Use Cases
|
||||||
|
|
||||||
|
## 3. Technische Grundlagen
|
||||||
|
Mein Aufgabenbereich umfasst einerseits die Entwicklung eigener Module, sowie das Bereitstellen des Services. Als Betriebssystem habe ich mich für Linux entschieden, einfach, da ich mit Linux im Serverumfeld die meisten und besten Erfahrungen gemacht habe.
|
||||||
|
|
||||||
|
(Diese Entscheidung wurde gemeinsam getroffen:)
|
||||||
|
Auch steht die Wahl der Programmiersprache und des CMS an. Nachdem wir im Unterricht fast ausschließlich mit C# entwickelt haben und nicht in eine komplett unbekannte Entwicklungsumgebung abdriften wollten, haben wir uns für Webentwicklung mit ASP.NET Core 9 und (Upgrade im Lauf der Diplomarbeit auf .NET Core 10) dem CMS Oqtane entschieden. Auch hier gab es einige Kandidaten:
|
||||||
|
- Piranha CMS
|
||||||
|
>Piranha erscheint auf den ersten Blick nicht so flexibel wie Oqtane, basiert auf .NET 8.0 und wird nicht so aktiv gewartet.
|
||||||
|
- Umbraco
|
||||||
|
>Viel Arbeit mit Partials, welche in der Admin Oberfläche geschieht, aber sehr gut dokumentiert. Im großen und ganzen wirkt Umbraco nicht so flexibel.
|
||||||
|
- DNN / Dot Net Nuke
|
||||||
|
>Platzhirsch. Kennt man, wird von der DNN Foundation gewartet. Arbeitet mit dem Dotnet Framework, welches nicht unter Linux läuft. Und ein Windows Server würde ich ich nicht einfach so ins Internet, abgesehen von den Lizenzkosten, die das kosten würde.
|
||||||
|
- Oqtane
|
||||||
|
>Schlecht dokumentiert, auf den ersten Blick sehr modular und flexibel.
|
||||||
|
|
||||||
|
Am Ende haben wir uns für das Oqtane Framework trotz seiner schlechten Dokumentation entschieden.
|
||||||
|
|
||||||
|
Im Bereich der Datenbanken musste ich mir ein paar Fragen stellen.
|
||||||
|
1. Auf welche Art Datenbank setzen wir? SQL, NoSQL, Graph, ...
|
||||||
|
2. Mit welcher speziellen implementiereung bekommen wir Support und haben Wissen im Team?
|
||||||
|
3. Ist das auserkorene System kompatibel mit dem CMS auf dem wir aufbauen?
|
||||||
|
|
||||||
|
Es war von Anfang an klar, dass es ein SQL basiertes System wird, da wir im Team nur mit SQL-basierten Systemen erfahrungen haben. Außerdem unterstützt unser CMS (Oqtane) nur SQL basierte Systeme. In der Linuxwelt kommen jetzt nur noch ein paar Datenbanken in die Auswahl: PostgreSQL, MySQL / MariaDB, SQLite. Da ist die Wahl auf PostgreSQL gefallen. Grund dafür war meine Vorerfahrung mit diesem DBMS, welche ich im Nebenjob errungen habe.
|
||||||
|
|
||||||
|
|
||||||
# Technologie
|
# Technologie
|
||||||
## Entwicklung mit Asp.Net (Was ist Blazor? / Was ist Razor? / Kestrel)
|
## Entwicklung mit ASP.NET (Was ist Blazor? / Was ist Razor? / Kestrel)
|
||||||
## Was ist Oqtane? Architektur von Oqtane?
|
## Was ist Oqtane? Architektur von Oqtane?
|
||||||
Oqtane ist ein Framework und CMS zur Entwicklung von Webseiten mithilfe von ASP.Net und Blazor. [^5] Ein Oqtane System besteht aus mehreren Komponenten.
|
Oqtane ist ein Framework und CMS zur Entwicklung von Webseiten mithilfe von ASP.NET und Blazor. [^5] Ein Oqtane-System besteht aus mehreren Komponenten.
|
||||||
|
|
||||||
In dieser Diplomarbeit fokussieren wir uns hauptsächlich auf `Themes` und `Modules`, aber es gibt auch `Language Packs` und `Pure Extensions`. [^6]
|
In dieser Diplomarbeit fokussieren wir uns hauptsächlich auf `Themes` und `Modules`, aber es gibt auch `Language Packs` und `Pure Extensions`. [^6]
|
||||||
|
|
||||||
Ein Modul soll neue Funktoinalitäten in das CMS hinzufügen und ein Theme soll die ganze Gestaltung der Website (die Shell) festlegen. [^6]
|
Ein `Module` (Modul) soll neue Funktionalitäten in das CMS hinzufügen und ein `Theme` soll die ganze Gestaltung der Website (die Shell) festlegen. [^6]
|
||||||
|
|
||||||
[^5]: https://www.oqtane.org/#about
|
[^5]: https://www.oqtane.org/#about
|
||||||
[^6]: https://docs.oqtane.org/dev/extensions/index.html
|
[^6]: https://docs.oqtane.org/dev/extensions/index.html
|
||||||
@@ -18,15 +63,15 @@ Ein Modul soll neue Funktoinalitäten in das CMS hinzufügen und ein Theme soll
|
|||||||
### Architektur eines Moduls
|
### Architektur eines Moduls
|
||||||
Ein Modul in Oqtane besteht aus 4 Projekten. Server, Client, Shared und Package.
|
Ein Modul in Oqtane besteht aus 4 Projekten. Server, Client, Shared und Package.
|
||||||
|
|
||||||
Im Server Projekt liegt Source Code, welcher Serverseitig ausgeführt werden soll. In der Praxis bedeutet das: alle Repositories, Controller, Manager, Migratoinen und Server-Services (entwickelt nach einem Interface definiert im Client) und Server-Startup logik.
|
Im Server-Projekt liegt Sourcecode, welcher serverseitig ausgeführt werden soll. In der Praxis bedeutet das: alle Repositories, Controller, Manager, Migrationen und Server-Services (entwickelt nach einem Interface definiert im Client) und Server-Startuplogik.
|
||||||
|
|
||||||
Im Client Projekt liegen Code und Razor Komponenten für den Client. Also Client-Staruplogik, Client-Services (+ Inferfaces dafür, die Services hier sollen lediglich die Server-Services über HTTP aufrufen), Ressource-Dateien (.resx), die Komponenten / das User Interface und die Moduldefinitionen für jedes Modul.
|
Im Client-Projekt liegen Code und Razor-Komponenten für den Client. Also Client-Staruplogik, Client-Services (+ Inferfaces dafür, die Services hier sollen lediglich die Server-Services über HTTP aufrufen), Ressourcendateien (.resx), die Komponenten / das User Interface und die Moduldefinitionen für jedes Modul.
|
||||||
|
|
||||||
Im Shared Projekt wird geteilter Source-Code abgelegt, der Server und Clientseitig verwendet wird. In der Praxis bleibt's hierbei bei den EntityFramework Modellen zum speichern der Daten im Arbeitsspeicher.
|
Im Shared-Projekt wird geteilter Sourcecode abgelegt, der server- und clientseitig verwendet wird. In der Praxis bleibt es hierbei bei den EntityFramework-Modellen zum Speichern der Daten im Arbeitsspeicher.
|
||||||
|
|
||||||
Im Package Projekt findet man Skripte zum debuggen und releasen eines Moduls. Und die Nuget Spezifikation.
|
Im Package Projekt findet man Skripte zum Debuggen und Releasen eines Moduls. Und die NuGet-Spezifikation.
|
||||||
- Beim Debug werden die dlls, pdbs und statischen Assets wie Skripte und Stylesheets der 3 anderen Projekte in den bereits gebauten Oqtane.Server `oqtane.framework/oqtane.server/bin/debug/net10.0/...` kopiert.
|
- Beim Debug werden die DLLs, PDBs und statischen Assets wie Skripte und Stylesheets der 3 anderen Projekte in den bereits gebauten Oqtane.Server `oqtane.framework/oqtane.server/bin/debug/net10.0/...` kopiert.
|
||||||
- Beim Release wird ein Nuget Paket erstellt und unter oqtane.framework/oqtane.server/Packages abgelegt. Dort abgelegte Nuget's werden beim nächsten Start des Oqtane Servers installiert (DB Migrationen werden gemacht und nuget's entpackt).
|
- Beim Release wird ein NuGet-Paket erstellt und unter oqtane.framework/oqtane.server/Packages abgelegt. Dort abgelegte NuGet-Pakete werden beim nächsten Start des Oqtane Servers installiert (DB Migrationen werden gemacht und die Pakete entpackt).
|
||||||
|
|
||||||
## Systemarchitektur (Postgres / Oqtane / Nginx )
|
## Systemarchitektur (Postgres / Oqtane / Nginx )
|
||||||
```mermaid
|
```mermaid
|
||||||
@@ -49,47 +94,41 @@ architecture-beta
|
|||||||
## Dependency injection
|
## Dependency injection
|
||||||
|
|
||||||
### Dependency Inversion Principle [^1]
|
### Dependency Inversion Principle [^1]
|
||||||
Das Dependency-Inversion-Principle (DIP / auf Deutsch: Abhängigkeits-Umkehr-Prinzip) ist eines von den 5 `SOLID` Prinzipien in der Softwareentwicklung.
|
Das Dependency-Inversion-Principle (DIP / auf Deutsch: Abhängigkeits-Umkehr-Prinzip) ist eines von den fünf `SOLID` Prinzipien in der Softwareentwicklung.
|
||||||
|
|
||||||
Das DIP unterscheidet zwischen high-level und low-level Modulen.
|
Das DIP unterscheidet zwischen high-level und low-level Modulen.
|
||||||
- Die high-level Module beschreiben die Applikations- / Buisnesslogik, ohne direkt mit den low-level Modulen zu interagieren, sondern lediglich auf abstraktionen. [^3]
|
- Die High-Level-Module beschreiben die Applikations- / Businesslogik, ohne direkt mit den Low-Level-Modulen zu interagieren, sondern lediglich auf Abstraktionen. [^3]
|
||||||
- Die Abstraktionen sollen nicht von Implementierungsdetails abhängig sein, sondern die low-level Implementierung sollen gemäß der Abstraktionsschickt implemetiert werden. [^3]
|
- Die Abstraktionen sollen nicht von Implementierungsdetails abhängig sein, sondern die Low-Level-Implementierung sollen gemäß der Abstraktionsschicht implementiert werden. [^3]
|
||||||
|
|
||||||
Ausgangslage ist eine Softwarearchitektur im Direct-Dependency-Graph Model.
|
Ausgangslage ist eine Softwarearchitektur im Direct-Dependency-Graph-Modell.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
architecture-beta
|
architecture-beta
|
||||||
service a(mdi:package-variant-closed)[Klasse A]
|
service a(mdi:package-variant-closed)[Klasse A]
|
||||||
service b(mdi:package-variant-closed)[Klasse B]
|
service b(mdi:package-variant-closed)[Klasse B]
|
||||||
service c(mdi:package-variant-closed)[Klasse C]
|
|
||||||
|
|
||||||
a:R --> L:b
|
a:R --> L:b
|
||||||
b:R --> L:c
|
|
||||||
```
|
```
|
||||||
Bei diesem Beispiel ist die Klasse A ein high-level Modul, welches direkt auf die Klasse B referenziert, was das DI-Prinzip verbietet.
|
Bei diesem Beispiel ist die Klasse A ein high-level Modul, welches direkt auf die Klasse B referenziert, was das DI-Prinzip verbietet.
|
||||||
Das Problem dabei: Die einzelnen Klassen sind eng gekoppelt, was das austauschen von B mit einer anderen Klasse unmöglich macht. Genau dieses Problem wird vom DIP gelöst.
|
Das Problem dabei: Die einzelnen Klassen sind eng gekoppelt, was das Austauschen von B mit einer anderen Klasse unmöglich macht. Genau dieses Problem wird vom DIP gelöst.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
architecture-beta
|
architecture-beta
|
||||||
service a(mdi:package-variant-closed)[Klasse A]
|
service a(mdi:package-variant-closed)[Klasse A]
|
||||||
service b(mdi:package-variant-closed)[Klasse B]
|
service b(mdi:package-variant-closed)[Klasse B]
|
||||||
service ib(mdi:car-clutch)[Interface B]
|
service ib(mdi:car-clutch)[Interface B]
|
||||||
service c(mdi:package-variant-closed)[Klasse C]
|
|
||||||
service ic(mdi:car-clutch)[Interface C]
|
|
||||||
|
|
||||||
a:B --> T:ib
|
a:B --> T:ib
|
||||||
ib:R <-- L:b
|
ib:R <-- L:b
|
||||||
b:B --> T:ic
|
|
||||||
ic:R <-- L:c
|
|
||||||
```
|
```
|
||||||
Das high-level Modul ruft lediglich eine Abstraktion eines low-level Moduls auf, welche von einem, oder mehreren low-level Modulen implementiert worden ist. Für das high-level Modul ist es hier egal, welches low-level Modul die Implementierung bereitstellt. Dadurch erhält man einen viel modulareren Aufbau in der Software. Die einzelnen Module sind auch leichter austauschbar, testbar. Genau diese Modularität macht dependency injection möglich.
|
Das High-Level-Modul ruft lediglich eine Abstraktion eines Low-Level-Moduls auf, welche von einem, oder mehreren Low-Level-Modulen implementiert wurde. Für das High-Level-Modul ist es hier egal, welches Low-Level-Modul die Implementierung bereitstellt. Dadurch erhält man einen viel modulareren Aufbau in der Software. Die einzelnen Module sind auch leichter austauschbar und testbar. Genau diese Modularität macht Dependency Injection möglich.
|
||||||
|
|
||||||
### Microsoft Dependency Injection Framework
|
### Microsoft Dependency Injection Framework
|
||||||
Dependency Injektion ist in .Net genau so wie Konfiguration, Protokollierung und das Optionsmuster ins Framework integriert. [^4]
|
Dependency Injektion ist in .NET genau so wie Konfiguration, Protokollierung und das Optionsmuster ins Framework integriert. [^4]
|
||||||
|
|
||||||
Alle Dependencies werden in einem `service container` zur verwaltung registriert. .Net hat einen eingebauten `service container` (eine Implementierung des `IServiceProvider`). [^4]
|
Alle Dependencies werden in einem `Service-Container` zur Verwaltung registriert. .NET hat einen eingebauten `Service-Container` (eine Implementierung des `IServiceProvider`). [^4]
|
||||||
|
|
||||||
Das Dependency Injection Framework verwaltet alle Instanzen. Nach Bedarf werden instanzen erstellt, oder wieder entsorgt (sofern das Service nicht mehr gebraucht wird). Beim instanzieren einer Klasse werden alle im Konstruktor erwarteten Dependencies bereitgestellt, bzw. selbst instanziert und dannach bereitgestellt. [^4]
|
Das Dependency Injection Framework verwaltet alle Instanzen. Nach Bedarf werden Instanzen erstellt, oder wieder entsorgt (sofern das Service nicht mehr gebraucht wird). Beim Instanziieren einer Klasse werden alle im Konstruktor erwarteten Dependencies bereitgestellt, bzw. selbst instanziiert und dannach bereitgestellt. [^4]
|
||||||
|
|
||||||
Hier ein Beispiel aus der Dokumentation von Microsoft: [^4]
|
Hier ein Beispiel aus der Dokumentation von Microsoft: [^4]
|
||||||
```c#
|
```c#
|
||||||
@@ -127,13 +166,13 @@ public sealed class Worker(IMessageWriter messageWriter) : BackgroundService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Das ist ein simples Beispiel, welches Teile des DI Frameworks zeigt. Wir haben einen Service (Klasse Worker), ein Dependency (Klasse MessageWriter) und eine Abstraktionsebene, von dem Dependency (Interface IMessage Writer).
|
Das ist ein simples Beispiel, welches Teile des DI Frameworks zeigt. Wir haben einen Service (Klasse Worker), ein Dependency (Klasse MessageWriter) und eine Abstraktionsebene, von dem Dependency (Interface IMessageWriter).
|
||||||
|
|
||||||
Bei Programstart wird zuerst manuell der `Service Container` erstellt, dannach alle Module registriert (entweder als HostedService, oder als Modul mit einer spezifischen Lifetime (Scoped, Transient, Singleton)).
|
Bei Programmstart wird zuerst manuell der `Service-Container` erstellt, dannach alle Module registriert (entweder als HostedService, oder als Modul mit einer spezifischen Lifetime (Scoped, Transient, Singleton)).
|
||||||
|
|
||||||
Mit dem Aufruf von `builder.Build()` wird intern ein Dependency Graph erstellt und mit `host.Run()` wird versucht die Klasse Worker zu instanzieren und zu starten. Nachdem Worker ein Dependency auf IMessageWriter hat wird über den zuvor erstelltem Dependency Graph die implementierung von IMessageWriter gesucht. Jetzt wird MessageWriter instanziert und dem Konstruktor von Worker übergeben, damit seine Dependencies befriedigt werden.
|
Mit dem Aufruf von `builder.Build()` wird intern ein Dependency Graph erstellt und mit `host.Run()` wird versucht die Klasse Worker zu instanziieren und zu starten. Nachdem Worker ein Dependency auf IMessageWriter hat, wird über den zuvor erstellten Dependency-Graph die Implementierung von IMessageWriter gesucht. Jetzt wird MessageWriter instanziiert und dem Konstruktor von Worker übergeben, damit seine Dependencies befriedigt werden.
|
||||||
|
|
||||||
So sehen der Abhängigkeitsgraph bei diesem Beispiel aus.
|
So sieht der Abhängigkeitsgraph bei diesem Beispiel aus.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
architecture-beta
|
architecture-beta
|
||||||
@@ -152,6 +191,29 @@ architecture-beta
|
|||||||
[^3]: https://www.oodesign.com/dependency-inversion-principle
|
[^3]: https://www.oodesign.com/dependency-inversion-principle
|
||||||
|
|
||||||
[^4]: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/overview
|
[^4]: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/overview
|
||||||
|
|
||||||
|
# Continuous Integration
|
||||||
|
|
||||||
|
## Automatisierter Build und Release Prozess mithilfe von Gitea Actions.
|
||||||
|
|
||||||
|
Gitea, das Versionskontrollsystem dieser Diplomarbeit, hat einen Continuous-Integration-System eingebaut. Im Kern ist es baugleich zu den GitHub-Pipelines. Man kann im `.gitea/workflow` Ordner `.yml` Dateien ablegen, welche dann das Verhalten der Workflows definieren.
|
||||||
|
|
||||||
|
Man kann definieren auf welcher Änderung im Git Repository die Pipeline losgetreten wird (Keyword: `on`) und entweder eigene Kommandos aufreihen, oder auf bestehende `actions` zurückgreifen, welche dann der Reihe nach ausgeführt werden (Keyword: `jobs`).
|
||||||
|
|
||||||
|
Die meisten Pipelines sind folgendermaßen Aufgebaut:
|
||||||
|
Clone -> Checkout -> Submodule Checkout (optional) -> Dependencies einrichten (zum Beispiel das dotnet SDK) -> Build ausführen. -> Release erstellen und Artefakte veröffentlichen (z.B. in Registries). Aber man kann auch andere Dinge tun, z.B. mithilfe von Künstlicher Intelligenz Code und Dokumentation überprüfen.
|
||||||
|
|
||||||
|
Anwendungen von Gitea Actions bei dieser Diplomarbeit:
|
||||||
|
|
||||||
|
- APT-Package Repository:
|
||||||
|
> Zum Bauen von Oqtane und allen Modulen, verpacken in ein .deb Paket und in die Registry pushen.
|
||||||
|
- Interfaces Projekt
|
||||||
|
> Zum Bauen vom Interfaces-Projekt, verpacken in ein NuGet Paket und in die Registry pushen.
|
||||||
|
- ursprünglich: oqtane.framework
|
||||||
|
> Zum bauen und Verpacken in einen Docker Container und in die Registry pushen.
|
||||||
|
- PM Repository:
|
||||||
|
> Zum automatischen Überprüfen der Dokumente, unter anderem, mithilfe von KI, wie zum Beispiel Gemini.
|
||||||
|
|
||||||
# Projektmanagement
|
# Projektmanagement
|
||||||
## Scrum
|
## Scrum
|
||||||
## YouTrack
|
## YouTrack
|
||||||
@@ -164,7 +226,7 @@ architecture-beta
|
|||||||
## Arbeitszeiteinschätzung (Zeitverzug)
|
## Arbeitszeiteinschätzung (Zeitverzug)
|
||||||
## Teamleitung (Motivation / Downsizing)
|
## Teamleitung (Motivation / Downsizing)
|
||||||
## Produktion != Staging
|
## Produktion != Staging
|
||||||
## Sprints und Meetings (in Zukunft ja Asyncron
|
## Sprints und Meetings (in Zukunft ja asynchron)
|
||||||
|
|
||||||
# Modules
|
# Modules
|
||||||
## Mass Mailer
|
## Mass Mailer
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@@ -14,6 +15,12 @@ import (
|
|||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Suggestion struct {
|
||||||
|
LineNumber uint64 `json:"line_number"`
|
||||||
|
Comment string `json:"comment"`
|
||||||
|
Snippet string `json:"snippet,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
apiKey := os.Getenv("GEMINI_API_KEY")
|
apiKey := os.Getenv("GEMINI_API_KEY")
|
||||||
token := os.Getenv("GITEA_TOKEN")
|
token := os.Getenv("GITEA_TOKEN")
|
||||||
@@ -55,7 +62,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer geminiClient.Close()
|
defer geminiClient.Close()
|
||||||
|
|
||||||
model := geminiClient.GenerativeModel("gemini-1.5-flash")
|
model := geminiClient.GenerativeModel("gemini-3-flash-preview")
|
||||||
|
|
||||||
// Get PR files
|
// Get PR files
|
||||||
files, _, err := client.ListPullRequestFiles(owner, repo, prNumber, gitea.ListPullRequestFilesOptions{})
|
files, _, err := client.ListPullRequestFiles(owner, repo, prNumber, gitea.ListPullRequestFilesOptions{})
|
||||||
@@ -63,7 +70,7 @@ func main() {
|
|||||||
log.Fatalf("Failed to get PR files: %v", err)
|
log.Fatalf("Failed to get PR files: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var reviews []string
|
var giteaComments []gitea.CreatePullReviewComment
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if !strings.HasSuffix(file.Filename, ".md") {
|
if !strings.HasSuffix(file.Filename, ".md") {
|
||||||
continue
|
continue
|
||||||
@@ -76,24 +83,35 @@ func main() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
review, err := getGeminiReview(ctx, model, content)
|
suggestions, err := getGeminiReview(ctx, model, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error getting review for %s: %v\n", file.Filename, err)
|
fmt.Printf("Error getting review for %s: %v\n", file.Filename, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
reviews = append(reviews, fmt.Sprintf("#### Review for `%s`\n\n%s", file.Filename, review))
|
for _, s := range suggestions {
|
||||||
|
body := s.Comment
|
||||||
|
if s.Snippet != "" {
|
||||||
|
body += fmt.Sprintf("\n\n```suggestion\n%s\n```", s.Snippet)
|
||||||
|
}
|
||||||
|
giteaComments = append(giteaComments, gitea.CreatePullReviewComment{
|
||||||
|
Path: file.Filename,
|
||||||
|
Body: body,
|
||||||
|
NewLineNum: int64(s.LineNumber),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(reviews) > 0 {
|
if len(giteaComments) > 0 {
|
||||||
commentBody := "### 🤖 Gemini Writing Review\n\n" + strings.Join(reviews, "\n\n---\n\n")
|
_, _, err = client.CreatePullReview(owner, repo, prNumber, gitea.CreatePullReviewOptions{
|
||||||
_, _, err = client.CreateIssueComment(owner, repo, prNumber, gitea.CreateIssueCommentOption{
|
State: gitea.ReviewStateRequestChanges,
|
||||||
Body: commentBody,
|
Body: "### 🤖 Gemini Writing Review\n\nI've found some areas for improvement in the documentation. Please see the inline comments below.",
|
||||||
|
Comments: giteaComments,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to post PR comment: %v", err)
|
log.Fatalf("Failed to create PR review: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Println("Successfully posted review comments.")
|
fmt.Printf("Successfully created PR review with %d inline comments.\n", len(giteaComments))
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("No Markdown files to review or no suggestions found.")
|
fmt.Println("No Markdown files to review or no suggestions found.")
|
||||||
}
|
}
|
||||||
@@ -113,32 +131,56 @@ func readFile(path string) (string, error) {
|
|||||||
return string(content), nil
|
return string(content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGeminiReview(ctx context.Context, model *genai.GenerativeModel, content string) (string, error) {
|
func getGeminiReview(ctx context.Context, model *genai.GenerativeModel, content string) ([]Suggestion, error) {
|
||||||
prompt := fmt.Sprintf(`
|
prompt := fmt.Sprintf(`
|
||||||
Review the following Markdown content for spelling errors, grammar mistakes, and style improvements.
|
Review the following Markdown content for spelling errors, grammar mistakes, and style improvements.
|
||||||
Provide your feedback as a list of bullet points. For each point:
|
Analyze the text line by line.
|
||||||
1. Identify the issue.
|
|
||||||
2. Provide the original text.
|
|
||||||
3. Suggest an alternative or fix.
|
|
||||||
|
|
||||||
Content:
|
For each issue found, provide a suggestion in JSON format with:
|
||||||
|
- "line_number": The 1-indexed line number where the issue occurs.
|
||||||
|
- "comment": A brief explanation of the problem and the suggested fix.
|
||||||
|
- "snippet": The corrected text for that line (optional, but highly recommended for spell/grammar fixes).
|
||||||
|
|
||||||
|
Return ONLY a JSON array of suggestions. If no issues are found, return "[]".
|
||||||
|
|
||||||
|
Content with line numbers for reference:
|
||||||
%s
|
%s
|
||||||
`, content)
|
`, addLineNumbers(content))
|
||||||
|
|
||||||
resp, err := model.GenerateContent(ctx, genai.Text(prompt))
|
resp, err := model.GenerateContent(ctx, genai.Text(prompt))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resp.Candidates) == 0 || len(resp.Candidates[0].Content.Parts) == 0 {
|
if len(resp.Candidates) == 0 || len(resp.Candidates[0].Content.Parts) == 0 {
|
||||||
return "No suggestions found.", nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var result strings.Builder
|
var rawJS strings.Builder
|
||||||
for _, part := range resp.Candidates[0].Content.Parts {
|
for _, part := range resp.Candidates[0].Content.Parts {
|
||||||
if text, ok := part.(genai.Text); ok {
|
if text, ok := part.(genai.Text); ok {
|
||||||
result.WriteString(string(text))
|
rawJS.WriteString(string(text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result.String(), nil
|
|
||||||
|
// Clean up markdown code blocks if present
|
||||||
|
js := strings.TrimSpace(rawJS.String())
|
||||||
|
js = strings.TrimPrefix(js, "```json")
|
||||||
|
js = strings.TrimSuffix(js, "```")
|
||||||
|
js = strings.TrimSpace(js)
|
||||||
|
|
||||||
|
var suggestions []Suggestion
|
||||||
|
if err := json.Unmarshal([]byte(js), &suggestions); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal suggestions: %v (raw response: %s)", err, js)
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addLineNumbers(text string) string {
|
||||||
|
lines := strings.Split(text, "\n")
|
||||||
|
for i, line := range lines {
|
||||||
|
lines[i] = fmt.Sprintf("%d: %s", i+1, line)
|
||||||
|
}
|
||||||
|
return strings.Join(lines, "\n")
|
||||||
}
|
}
|
||||||
|
|||||||
97
scripts/wordcount/main.go
Normal file
97
scripts/wordcount/main.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
token := os.Getenv("GITEA_TOKEN")
|
||||||
|
baseURL := os.Getenv("GITEA_URL")
|
||||||
|
repoFullName := os.Getenv("GITHUB_REPOSITORY")
|
||||||
|
prNumberStr := os.Getenv("PR_NUMBER")
|
||||||
|
|
||||||
|
if token == "" || repoFullName == "" || prNumberStr == "" {
|
||||||
|
log.Fatal("Missing required environment variables: GITEA_TOKEN, GITHUB_REPOSITORY, PR_NUMBER")
|
||||||
|
}
|
||||||
|
|
||||||
|
if baseURL == "" {
|
||||||
|
baseURL = "https://gitea.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
prNumber, err := strconv.ParseInt(prNumberStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Invalid PR_NUMBER: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
repoParts := strings.Split(repoFullName, "/")
|
||||||
|
if len(repoParts) != 2 {
|
||||||
|
log.Fatalf("Invalid GITHUB_REPOSITORY format: %s", repoFullName)
|
||||||
|
}
|
||||||
|
owner, repo := repoParts[0], repoParts[1]
|
||||||
|
|
||||||
|
client, err := gitea.NewClient(baseURL, gitea.SetToken(token))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create Gitea client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get PR files
|
||||||
|
files, _, err := client.ListPullRequestFiles(owner, repo, prNumber, gitea.ListPullRequestFilesOptions{})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to get PR files: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var counts []string
|
||||||
|
for _, file := range files {
|
||||||
|
if !strings.HasSuffix(file.Filename, ".md") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Reviewing file: %s\n", file.Filename)
|
||||||
|
content, err := readFile(file.Filename)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error reading file %s: %v\n", file.Filename, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
count := countWords(content)
|
||||||
|
counts = append(counts, fmt.Sprintf("#### Word count for `%s`\n\nWord count: %d", file.Filename, count))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(counts) > 0 {
|
||||||
|
commentBody := "### 🤖 Word Count Report\n\n" + strings.Join(counts, "\n\n---\n\n")
|
||||||
|
_, _, err = client.CreateIssueComment(owner, repo, prNumber, gitea.CreateIssueCommentOption{
|
||||||
|
Body: commentBody,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to post PR comment: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println("Successfully posted review comments.")
|
||||||
|
} else {
|
||||||
|
fmt.Println("No Markdown files to review or no suggestions found.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFile(path string) (string, error) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
content, err := io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(content), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func countWords(text string) int {
|
||||||
|
return len(strings.Fields(text))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user