Compare commits
59 Commits
kh-blazor-
...
b8f8cc4150
| Author | SHA1 | Date | |
|---|---|---|---|
| b8f8cc4150 | |||
| c2336f166a | |||
| e242b9fcb3 | |||
| 5b1f7ac764 | |||
| 39fba36a2a | |||
| 18687087f7 | |||
| 79203e5c5b | |||
| e7f38c88bf | |||
| 8deb7fb86e | |||
| 874262b60d | |||
| 0cbdae7ae7 | |||
| e771fffa8e | |||
| 9833898fb6 | |||
| cda497e9da | |||
| cf7001e042 | |||
| 8dd75b0b16 | |||
| 88d9fe591c | |||
| b8e5dacb5c | |||
| ad917e7261 | |||
| 28bbaeca36 | |||
| 4c052a52fa | |||
| 7dca8ed858 | |||
| 8c85c53522 | |||
| 64c3f4dd3b | |||
| eee8462b74 | |||
| 957e9a6d5e | |||
| e1c31b4fb9 | |||
| 880284d616 | |||
| 7a58550b35 | |||
| fce76d9185 | |||
| 312440278e | |||
| b8822bda3c | |||
| e810fab3da | |||
| f9de191036 | |||
| 3a12cf7bde | |||
| cc8fef4cc5 | |||
| 28e1d5300e | |||
| af16e09093 | |||
| e782d20d6c | |||
| 3d163eb153 | |||
| b7946158b7 | |||
| 207fe757b2 | |||
| bce54b3b04 | |||
| 0165f058e6 | |||
| 89ea3ae7a1 | |||
| 9c72506d21 | |||
| e3d4cd275d | |||
| 332692fc41 | |||
| e8180d0cb4 | |||
| b103ea97e5 | |||
| aad23ca449 | |||
| e4cf3cb00d | |||
| fd51d7c79e | |||
| 1611ca620e | |||
| d410f72fb8 | |||
| c421a4fd8f | |||
| 0b774f00b7 | |||
| a2c756e4de | |||
| d6b360a0f8 |
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
diplomarbeitsbuch.pdf
|
||||
47
00-praemble.md
Normal file
@@ -0,0 +1,47 @@
|
||||
\newpage
|
||||
|
||||
# Kurzfassung {.unnumbered .unlisted}
|
||||
|
||||
In dieser Diplomarbeit geht es um die Entwicklung von „AlumniHub“ – einer Webanwendung für den Absolventenverein der HTL Ungargasse. Wir haben die alte, händische Verwaltung durch ein modernes, automatisiertes System ersetzt, das auf ASP.NET Core, Blazor und dem Oqtane CMS basiert. Ziel war es, eine Plattform zu schaffen, die Kommunikation, Event-Planung und Networking an einem zentralen Ort bündelt.
|
||||
|
||||
Die technische Arbeit haben wir uns im Team aufgeteilt: Konstantin Hintermayer hat als Product Owner die Linux-Serverinfrastruktur, die PostgreSQL-Datenbank und die CI/CD-Pipelines aufgesetzt sowie das digitale „Schwarze Brett“ programmiert. Adam Gaiswinkler hat das responsive Design im Stil der Schule entworfen und das Event-Anmeldetool sowie die „Hall of Fame“ inklusive PDF-Export umgesetzt. Florian Edlmayer war für den Premiumbereich mit seinem Freigabe-Workflow verantwortlich, hat die DSGVO-Vorgaben umgesetzt und den sicheren Login über LinkedIn (OAuth 2.0) eingebaut. Das Ergebnis ist eine leistungsstarke Plattform, die dem Verein die tägliche Verwaltungsarbeit deutlich erleichtert.
|
||||
\cleardoublepage
|
||||
|
||||
# Abstract {.unnumbered .unlisted}
|
||||
|
||||
This diploma thesis covers the development of "AlumniHub," a web platform designed for the alumni association of the HTL Ungargasse. Our goal was to replace their old, manual administration with an automated system using ASP.NET Core, Blazor, and the Oqtane CMS. The platform now handles communication, event planning, and networking in one central place.
|
||||
|
||||
The technical work was split between the team: Konstantin Hintermayer (Product Owner) was responsible for the Linux server infrastructure, the PostgreSQL database, and CI/CD pipelines, while also building the digital "Blackboard." Adam Gaiswinkler created the responsive UI in the school’s style and implemented the event registration tool and the "Hall of Fame" with automated PDF exports. Florian Edlmayer developed the premium area and its approval workflow, ensured GDPR compliance, and integrated the LinkedIn Login (OAuth 2.0). Overall, we built a high-performance system that significantly simplifies the association’s daily administrative work.
|
||||
|
||||
\newpage
|
||||
|
||||
# Danksagung {.unnumbered .unlisted}
|
||||
|
||||
Obwohl die Umsetzung dieses Projektes durch das Projektteam selbst erfolgte, gab es eine Reihe an Personen, die uns tatkräftig unterstützt haben.
|
||||
|
||||
An erster Stelle möchten wir uns bei Herrn Prof. Thomas Gürth und Herrn Prof. Johannes Kreuzer bedanken. Ohne Ihre umfangreiche Unterstützung, Kreativität und fachliche Kompetenz wäre das Projekt in dieser Form nicht realisierbar gewesen. Sie beide haben uns durch dieses Projekt begleitet, uns mit wertvollen Ideen und Hilfestellungen versorgt und uns gleichzeitig den notwendigen Freiraum für eigenständiges Lernen und Arbeiten gelassen.
|
||||
|
||||
Neben einer exzellenten Betreuung bedarf eine Diplomarbeit auch eines engagierten Auftraggebers. Bei Lukas Aigner bedanken wir uns für das Vertrauen in unser Team und die Ermöglichung dieses Projektes. Trotz zeitlicher Verzögerungen im Rahmen der Arbeit, erfuhren wir von seiner Seite großes Verständnis und fanden stets eine konstruktive Basis für Diskussionen vor.
|
||||
|
||||
Ein besonders komplexer Aspekt dieser Arbeit war die Infrastruktur, für die im Prozess drei verschiedene Lösungen evaluiert wurden. Wir danken Stefan Reinel und dem Team von LiveDesign für die bereitwillige Unterstützung. Ohne die aktuelle Infrastruktur hätten wir bis heute keine zufriedenstellende Lösung für unsere Homepage gefunden.
|
||||
|
||||
Als wir im Oktober kurzfristig eine neue Produktionsumgebung benötigten, wurde uns unbürokratisch durch die Schule geholfen. Herr Prof. Harald Dassler und Herr Prof. Andreas Resch haben uns zur Weiterentwicklung eine virtuelle Maschine zur Verfügung gestellt und den externen Zugriff ermöglicht. Es ist nicht selbstverständlich, dass in einer Institution dieser Größe ein solcher Wunsch so schnell und unkompliziert umgesetzt wird.
|
||||
|
||||
Für den „frischen Wind“ und das sorgfältige Korrekturlesen der Arbeit bedanken wir uns herzlich bei Frau Prof. Gertrude Brindlmayer. Ihr geschulter Blick auf Details, die dem Team im Arbeitsprozess entgangen waren, stellte eine große Bereicherung für die finale Qualität dieser Arbeit dar.
|
||||
|
||||
\newpage
|
||||
|
||||
# Vorwort {.unnumbered .unlisted}
|
||||
|
||||
Diese Diplomarbeit ist im Schuljahr 2025/2026 an der HTL SZU Ungargasse im Rahmen des Projekts Alumnihub entstanden. Unser Ziel war es, zusammen mit dem Absolventenverein die veraltete Mitgliederverwaltung zu modernisieren und eine neue digitale Plattform für ehemalige Schülerinnen und Schüler zu entwickeln. Was am Anfang nur eine Pflichtaufgabe für die Schule war, wurde für uns schnell zu einem Projekt, mit dem wir uns voll identifiziert haben. Besonders herausfordernd war, dass unser Team im Laufe der Zeit kleiner wurde und wir am Ende nur noch zu dritt waren. Konstantin Hintermayer, Florian Edlmayer und Adam Gaiswinkler mussten deshalb viel mehr Verantwortung übernehmen und technisches Wissen schneller aufbauen als eigentlich geplant. Die Arbeit am AlumniHub war für uns die ideale Chance, das Wissen aus der Theorie endlich mal in einem echten und anspruchsvollen Softwareprojekt einzusetzen. Es war eine stressige, aber sehr lehrreiche Zeit, und wir sind stolz auf das, was wir am Ende als Team abgeliefert haben.
|
||||
|
||||
\newpage
|
||||
|
||||
# Einsatz Künstlicher Intelligenz (KI-Disclaimer) {.unnumbered .unlisted}
|
||||
|
||||
Diese Diplomarbeit und die Software Alumnihub wurden von uns eigenständig entwickelt. KI-Tools haben wir lediglich als Unterstützung für Aufgaben wie Korrekturlesen, Übersetzungen oder zur gezielten Recherche genutzt. Auch beim Debugging im Code war die KI eine Hilfe. Der eigentliche Quellcode, die inhaltliche Ausarbeitung und alle technischen Entscheidungen stammen jedoch direkt von uns. Sämtliche KI-Vorschläge wurden von uns kritisch hinterfragt und nur nach eigener Prüfung übernommen.
|
||||
|
||||
\cleardoublepage
|
||||
\pagestyle{empty}
|
||||
\pagenumbering{arabic}
|
||||
\setcounter{page}{1}
|
||||
105
00_preamble.tex
Normal file
@@ -0,0 +1,105 @@
|
||||
\begin{titlepage}
|
||||
\begin{minipage}[c]{0.25\textwidth}
|
||||
\includegraphics[width=\textwidth]{images/preamble/szu.png}
|
||||
\end{minipage}
|
||||
\hfill
|
||||
\begin{minipage}[c]{0.7\textwidth}
|
||||
\raggedleft
|
||||
\textbf{Schulzentrum HTL HAK Ungargasse} \\
|
||||
Höhere Lehranstalt für Informationstechnologie \\
|
||||
Ausbildungsschwerpunkt Netzwerktechnik
|
||||
\end{minipage}
|
||||
\rule{\textwidth}{0.4pt}
|
||||
|
||||
\vspace{1cm}
|
||||
|
||||
\begin{center}
|
||||
\includegraphics[width=0.6\textwidth]{images/preamble/logo.png} \\
|
||||
\vspace{1.5cm}
|
||||
|
||||
\textbf{\Huge DIPLOMARBEIT} \\
|
||||
\vspace{1cm}
|
||||
\textbf{\huge AlumniHub} \\
|
||||
\vspace{0.5cm}
|
||||
\textit{\Large Plattform für den Absolventenverein des SZ HTL HAK Ungargasse}
|
||||
\end{center}
|
||||
|
||||
\vspace{1.75cm}
|
||||
|
||||
\begin{minipage}[t]{0.5\textwidth}
|
||||
\textbf{Verfasser:} \\
|
||||
Adam Gaiswinkler \\
|
||||
Florian Edlmayer \\
|
||||
Konstantin Hintermayer
|
||||
\end{minipage}
|
||||
\begin{minipage}[t]{0.4\textwidth}
|
||||
\textbf{Betreuer:} \\
|
||||
Prof. Thomas Gürth
|
||||
\end{minipage}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\begin{minipage}[t]{0.5\textwidth}
|
||||
\textbf{Klasse:} \\
|
||||
5BHITN
|
||||
\end{minipage}
|
||||
\begin{minipage}[t]{0.4\textwidth}
|
||||
\textbf{Schuljahr:} \\
|
||||
2025/26
|
||||
\end{minipage}
|
||||
|
||||
\vspace{0.75cm}
|
||||
|
||||
\begin{minipage}[t]{0.5\textwidth}
|
||||
\textbf{Abgabevermerk:} \\
|
||||
Wien, am 26.03.2026
|
||||
\end{minipage}
|
||||
\begin{minipage}[t]{0.4\textwidth}
|
||||
\textbf{Projektnummer:} \\
|
||||
3U\_HI\_2026\_04 \\
|
||||
\hrulefill
|
||||
\end{minipage}
|
||||
|
||||
\vspace{0.25cm}
|
||||
|
||||
\textbf{übernommen von:} \\
|
||||
\hrulefill
|
||||
\end{titlepage}
|
||||
|
||||
\newpage
|
||||
\pagenumbering{Roman}
|
||||
\cleardoublepage
|
||||
\section*{Eidesstattliche Erklärung}
|
||||
Ich erkläre, dass ich die vorliegende Diplomarbeit selbstständig und ohne fremde Hilfe verfasst, andere als die angegebenen Quellen und Hilfsmittel nicht benutzt, sowie die aus anderen Werken übernommene Inhalte durch Quellenangaben erkenntlich gemacht habe.
|
||||
|
||||
\vspace{1.5cm}
|
||||
|
||||
\begin{tabular}{@{}p{5cm}p{1cm}p{7cm}@{}}
|
||||
Wien, am 26.03.2026 & & \hrulefill \\
|
||||
& & Adam Gaiswinkler
|
||||
\end{tabular}
|
||||
|
||||
\vspace{1.2cm}
|
||||
|
||||
\begin{tabular}{@{}p{5cm}p{1cm}p{7cm}@{}}
|
||||
Wien, am 26.03.2026 & & \hrulefill \\
|
||||
& & Florian Edlmayer
|
||||
\end{tabular}
|
||||
|
||||
\vspace{1.2cm}
|
||||
|
||||
\begin{tabular}{@{}p{5cm}p{1cm}p{7cm}@{}}
|
||||
Wien, am 26.03.2026 & & \hrulefill \\
|
||||
& & Konstantin Hintermayer
|
||||
\end{tabular}
|
||||
|
||||
\makeatletter
|
||||
\renewcommand{\tableofcontents}{%
|
||||
\@starttoc{toc}%
|
||||
}
|
||||
\renewcommand{\listoffigures}{%
|
||||
\@starttoc{lof}%
|
||||
}
|
||||
\renewcommand{\listoftables}{%
|
||||
\@starttoc{lot}%
|
||||
}
|
||||
5
01-toc.md
Normal file
@@ -0,0 +1,5 @@
|
||||
\cleardoublepage
|
||||
|
||||
# Inhaltsverzeichnis {.unnumbered .unlisted}
|
||||
|
||||
\tableofcontents
|
||||
442
02-Allgemein.md
Normal file
@@ -0,0 +1,442 @@
|
||||
\cleardoublepage
|
||||
\pagestyle{fancy}
|
||||
\pagenumbering{arabic}
|
||||
|
||||
# Einleitung Allgemeiner Teil
|
||||
|
||||
## Diplomarbeitsantrag
|
||||
|
||||
### Ausgangslage
|
||||
|
||||
Der Absolventenverein verwaltet derzeit rund 300 Alumni, deren Daten in einer CSV-Datei gepflegt werden. Die Event-Anmeldungen der letzten Jahre erfolgten über “Perspective Funnels”, dessen Lizenz mittlerweile abgelaufen ist. Für die kommenden Veranstaltungen wird daher ein Ersatz für die Event-Registrierung benötigt. Die aktuellen Abläufe sind dezentral und teilweise manuell, was zu einem erhöhten Verwaltungsaufwand und fehlender Übersicht führt. Eine integrierte Plattform für Mitgliederverwaltung, Kommunikation und Event-Organisation existiert bislang nicht.
|
||||
|
||||
### Zielsetzung
|
||||
|
||||
Ziel der Diplomarbeit ist es, ein Content-Management-System als Basis für eine innovative, barrierefreie und sichere Website des Absolventenvereins zu nutzen, dabei vorhandene Funktionen des CMS anzupassen und eigene Module wie eine Eventanmeldung, eine Hall of Fame, ein Schwarzes Brett als Kommunikationsplattform sowie einen Premiumbereich zu entwickeln. Die Anwendung soll modular, erweiterbar und leicht wartbar sein und auf einem Schulserver betrieben werden, um eine kontrollierte und geschützte Umgebung für die Verwaltung der Mitglieder- und Veranstaltungsdaten zu gewährleisten.
|
||||
|
||||
### Tatsächliches Ergebnis
|
||||
|
||||
Im Rahmen des Projekts konnten alle priorisierten Tickets aus dem Product Backlog erfolgreich umgesetzt werden.
|
||||
|
||||
Konstantin Hintermayer übernahm als Product Owner die Kommunikation mit dem Auftraggeber und stellte einen funktionsfähigen Linux-Server als technische Grundlage bereit. Zusätzlich erarbeitete er Skripte sowie Dokumentationen zur Serverlandschaft und führte die Grundkonfiguration des CMS durch. Weiters wurden die Module „Auswertung“ und „Schwarzes Brett“ erfolgreich an den Kunden übergeben.
|
||||
|
||||
Adam Gaiswinkler war für die Dokumentation der Benutzeroberfläche und des Backends sowie für die Entwicklung eines CMS-Moduls zur Anmeldung und für die „Hall of Fame“ verantwortlich. Auch die grundlegende Konfiguration des CMS wurde erfolgreich umgesetzt.
|
||||
|
||||
Florian Edlmayer entwickelte ein funktionierendes CMS-Modul für den Premiumbereich der Absolventen. Zusätzlich erstellte er eine vollständige und gesetzeskonforme Datenschutzerklärung. Ein weiterer wichtiger Bestandteil war die erfolgreiche Implementierung einer OAuth-Authentifizierung über LinkedIn.
|
||||
|
||||
## Das Team
|
||||
|
||||
Konstantin Hintermayer
|
||||
|
||||
Geboren am: 25.05.2007
|
||||
|
||||
E-Mail: [konstantin.hintermayer@edu.szu.at]
|
||||
|
||||
Individuelle Themenstellung: Projektleitung (Product Owner), (Server-) Infrastruktur, Aufsetzen und Installieren der Server / Linux / Datenbank, Adminzugriff, Skripten, Entwicklung der Auswertungen und des schwarzen Bretts.
|
||||
|
||||
Florian Edlmayer
|
||||
|
||||
Geboren am: 20.08.2006
|
||||
|
||||
E-Mail: [florian.edlmayer@edu.szu.at]
|
||||
|
||||
Individuelle Themenstellung: Entwicklung eines CMS-Moduls für den Premiumbereich des Absolventenvereins,Verfassung der Datenschutzerklärung und zuständig für Informationssicherheit, Implementierung von OAuth, Datenbankmanagement.
|
||||
|
||||
Adam Gaiswinkler
|
||||
|
||||
Geboren am:
|
||||
|
||||
E-Mail: [adam.gaiswinkler@edu.szu.at]
|
||||
|
||||
Individuelle Themenstellung:Entwicklung zweier CMS-Module: (Anmeldetool für Treffen und Hall of Fame), Grundkonfiguration des CMS, Starker Fokus auf nutzerfreundliches und responsives Design, Web-Entwicklung mit C# und ASP.NET.
|
||||
|
||||
## Fachliches Umfeld(Technologien)
|
||||
|
||||
Im fachlichen Umfeld dieser Diplomarbeit kommt das Content-Management-System Oqtane zum Einsatz. Oqtane basiert auf modernen Webtechnologien wie ASP.NET Core und der Plattform .NET und ermöglicht die Entwicklung modularer und skalierbarer Webanwendungen. Durch seine komponentenbasierte Architektur können Funktionen in Form von Modulen flexibel entwickelt und in die Anwendung integriert werden. Dies erleichtert sowohl die Erweiterbarkeit als auch die Wartung der Plattform. Besonders im Kontext dieser Arbeit bietet Oqtane eine geeignete Grundlage, um eine dynamische und benutzerorientierte Alumni-Plattform umzusetzen, da sowohl Inhalte als auch Benutzerverwaltung effizient verwaltet werden können.
|
||||
|
||||
Siehe Details unter: Konstantin Hintermayer individueller Teil
|
||||
|
||||
# Projektplanung
|
||||
|
||||
## Projektziele
|
||||
|
||||
### Hauptziele
|
||||
|
||||
- **HZ01 – Verwaltung von Absolventenprofilen**
|
||||
Das System ermöglicht die zentrale Erstellung und Pflege individueller Profile für Absolventen.
|
||||
|
||||
- **HZ02 – Organisation von Veranstaltungen**
|
||||
Die Applikation stellt Funktionen zur Planung und Durchführung von Vereinstreffen und Events bereit.
|
||||
|
||||
- **HZ03 – Darstellung der Hall of Fame**
|
||||
Besondere Leistungen und Erfolge von Absolventen werden in einer digitalen Ehrengalerie präsentiert.
|
||||
|
||||
- **HZ04 – Firmen- und Jobstellenmarkt**
|
||||
Integration einer Plattform für Stellenangebote, Praktika und die Vernetzung mit Partnerunternehmen.
|
||||
|
||||
- **HZ05 – Zugriffsverwaltung und Sicherheit**
|
||||
Implementierung einer rollenbasierten Zugriffskontrolle und Gewährleistung der Datensicherheit nach DSGVO-Standard.
|
||||
|
||||
- **HZ06 – Schülerzugang und CMS**
|
||||
Einbindung eines Redaktionssystems für Beiträge und eines speziellen Zugangs für aktuelle Schüler.
|
||||
|
||||
### Hardwareanforderungen und Infrastruktur
|
||||
|
||||
- **HW01 – Benutzerkapazität (300 Nutzer)**
|
||||
Das System unterstützt mindestens 300 gleichzeitig registrierte Benutzer für Administration und Nutzung.
|
||||
|
||||
- **HW02 – Aktive Zugriffsrate (50 Zugriffe/Tag)**
|
||||
Die Infrastruktur ist auf ca. 50 tägliche aktive Nutzer ohne Performanceverluste ausgelegt.
|
||||
|
||||
- **HW03 – Einfache Skalierbarkeit**
|
||||
Die Architektur erlaubt eine flexible Erweiterung der Ressourcen ohne strukturelle Änderungen.
|
||||
|
||||
- **HW04/05 – Hochverfügbarkeit (99,5 %)**
|
||||
Der Betrieb erfolgt auf skalierbaren Cloud-Servern mit einer angestrebten Verfügbarkeit von 99,5 % pro Jahr.
|
||||
|
||||
- **HW07 – Disaster Recovery (24h)**
|
||||
Im Falle eines Totalausfalls muss das System innerhalb von 24 Stunden wieder benutzbar sein.
|
||||
|
||||
• **HW12/13 – Automatisierte Sicherung**
|
||||
Implementierung einer automatisierten Datensicherung für Datenbanken und Binaries auf unabhängigen Speichern.
|
||||
|
||||
• **RED-1 – Redundanz der Datenbank**
|
||||
Die Datenbank wird zur Ausfallsicherheit redundant geführt und nutzt automatische Master-Promotion im Fehlerfall.
|
||||
|
||||
### Schnittstellenanforderungen
|
||||
|
||||
- **SnT-1 – REST-API (Frontend/Backend)**
|
||||
Bereitstellung einer API zur Kommunikation zwischen dem Oqtane-CMS und dem Blazor-Frontend für dynamische CRUD-Operationen.
|
||||
|
||||
- **SnT-2 – SMTP-E-Mail-Versand (Brevo)**
|
||||
Einbindung des Brevo SMTP-Dienstes für den Versand von bis zu 300 transaktionalen E-Mails pro Tag.
|
||||
|
||||
- **SnT-3 – LinkedIn OAuth (Phase 2)**
|
||||
Schnittstelle zur Authentifizierung und zum automatisierten Abruf von Profildaten über LinkedIn.
|
||||
|
||||
### Software und Zugriffsverwaltung
|
||||
|
||||
- **SW-1/2 – Barrierefreiheit und Browser-Support**
|
||||
Unterstützung gängiger Browser und Einhaltung des WCAG 2.1 AA Standards für Barrierefreiheit.
|
||||
|
||||
- **SW-4 – Sicherer Administrationszugriff**
|
||||
Administrativer Zugriff erfolgt ausschließlich über eine verschlüsselte VPN-Verbindung (Wireguard) und SSH.
|
||||
|
||||
- **ZUG-1–4 – Authentifizierung und Rollen**
|
||||
Implementierung klassischer Login-Verfahren, Passwort-Reset-Funktionen und einer detaillierten Benutzerverwaltung für Administratoren.
|
||||
|
||||
- **ZUG-6/7 – Magic Link und 2FA (Phase 2)**
|
||||
Erweiterung der Sicherheit durch passwortlose Anmeldung via E-Mail-Link und Zwei-Faktor-Authentifizierung.
|
||||
|
||||
### Funktionsmodule (Phase 2)
|
||||
|
||||
- **HoF-1/2 – Hall of Fame Funktionen**
|
||||
Nutzer können ihre Erfolge online präsentieren und als ansprechendes PDF für Treffen exportieren.
|
||||
|
||||
- **JOB-1/2 – Job- und Praktikumsbörse**
|
||||
Plattform für Stellenanzeigen und Bewerbungen inklusive Dashboard für Arbeitgeber und statistischen Auswertungen.
|
||||
|
||||
- **PRE-1–3 – Premiumbereich**
|
||||
Exklusiver Zugang für Premiummitglieder inklusive Einsicht in Ingenieuranträge und erweiterter Event-Organisation.
|
||||
|
||||
- **ScB-1–4 – Schwarzes Brett**
|
||||
Ein interaktiver Feed für Nachrichten, Event-Ankündigungen und Werbung mit integriertem Meldesystem für Inhalte.
|
||||
|
||||
- **AfT-1–5 – Anmeldetool für Treffen**
|
||||
Umfassendes Tool zur Eventerstellung, zum Versand von Einladungen (E-Mail/SMS) und zur Zielgruppenfilterung.
|
||||
|
||||
- **Pro-1–4 – Erweiterte Profilverwaltung**
|
||||
Nutzer können ihre Daten manuell pflegen oder automatisiert mit ihrem LinkedIn-Profil abgleichen lassen.
|
||||
|
||||
### Daten und Dokumentation
|
||||
|
||||
- **DB-1 – PostgreSQL Datenbank**
|
||||
Entwicklung eines relationalen Datenbankdesigns inklusive Indizierung und Verschlüsselung zur Gewährleistung der Datenintegrität.
|
||||
|
||||
- **DOK-1–6 – Projektdokumentation**
|
||||
Erstellung aller notwendigen Handbücher (Benutzer, Admin, Git) und des Disaster-Recovery-Handbuchs.
|
||||
|
||||
### Nicht-Ziele
|
||||
|
||||
- **NZ01 – Zahlungsprozesse**
|
||||
Die technische Abwicklung von Zahlungen im Premiumbereich liegt außerhalb des Projektscopes.
|
||||
|
||||
- **NZ02 – Externe Kalenderlösungen**
|
||||
Die Integration externer Dienste wie V-Calendar oder V-Event ist nicht vorgesehen.
|
||||
|
||||
## Aufgabenverteilung
|
||||
|
||||
### Konstantin Hintermayer
|
||||
|
||||
| | |
|
||||
| :----------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Themenstellung: | Die individuelle Themenstellung umfasst die Konzeption und Umsetzung der Infrastruktur sowie die Entwicklung von Modulen für Auswertungen und ein Schwarzes Brett innerhalb der Plattform. |
|
||||
| Auflistung der einzelnen Ziele und Anforderungen | HW01, HW02, HW03, HW04/05, HW07, RED-1, SW-4, ScB-1–4 |
|
||||
|
||||
Table: Aufgabenverteilung Konstantin Hintermayer
|
||||
|
||||
### Florian Edlmayer
|
||||
|
||||
| | |
|
||||
| :----------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Themenstellung: | Die individuelle Themenstellung umfasst die Entwicklung eines Premiumbereichs sowie die Umsetzung von Datenschutz- und Informationssicherheitsmaßnahmen, einschließlich Zugriffs- und Profilverwaltung. |
|
||||
| Auflistung der einzelnen Ziele und Anforderungen | HZ01, HZ05, HW12/13, SnT-3, ZUG-1–4, ZUG-6/7, PRE-1–3, Pro-1–4, DB-1 |
|
||||
|
||||
Table: Aufgabenverteilung Florian Edlmayer
|
||||
|
||||
### Adam Gaiswinkler
|
||||
|
||||
| | |
|
||||
| :----------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Themenstellung: | Die individuelle Themenstellung umfasst die Entwicklung einer Hall of Fame, die Implementierung eines Anmeldetools für Treffen sowie die Konfiguration des Content-Management-Systems. |
|
||||
| Auflistung der einzelnen Ziele und Anforderungen | HZ02, HZ03, HZ06, SnT-2, SW-1/2, HoF-1/2, AfT-1–5 |
|
||||
|
||||
Table: Aufgabenverteilung Adam Gaiswinkler
|
||||
|
||||
## Geplante Projektablauf
|
||||
|
||||
### Meilensteine
|
||||
|
||||
| # | Datum | Beschreibung |
|
||||
| :-- | :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| _1_ | _19.09.2025_ | Fertigstellung und Abgabe des Diplomarbeitsantrags. |
|
||||
| _2_ | _30.10.2025_ | [cite_start]Server-Setup mit SSH-Zugriff [cite: 83][cite_start], CMS-Grundkonfiguration (Oqtane) im SZU-Design [cite: 69, 83][cite_start], Implementierung der Eingabemaske und API für das Anmeldetool [cite: 137] [cite_start]sowie Erstellung der DSGVO-Richtlinien [cite: 38] [cite_start]und Backupstrategie[cite: 55, 62]. |
|
||||
| _3_ | _27.11.2025_ | [cite_start]Entwicklung grafischer Auswertungen für Anmeldedaten [cite: 120] [cite_start]und Integration der OAuth-Authentifizierung zum automatisierten Abruf von Profildaten[cite: 78, 148]. |
|
||||
| _4_ | _18.12.2025_ | Realisierung des Schwarzen Bretts inkl. [cite_start]Sperrfunktion für Administratoren [cite: 133][cite_start], Entwicklung des Hall of Fame Moduls mit PDF-Generierung [cite: 105] [cite_start]sowie Aufbau der Premiumkunden-Verwaltung[cite: 113]. |
|
||||
| _5_ | _21.01.2026_ | [cite_start]Erstellung der technischen Systemdokumentation [cite: 155][cite_start], Durchführung von Modultests und Implementierung der Premium-Serviceverwaltung[cite: 113]. |
|
||||
| _6_ | _08.02.2026_ | [cite_start]Projektabschluss der Infrastruktur-Tasks, Übergabe der Disaster-Recovery-Skripte [cite: 58] [cite_start]und Finalisierung der gesamten Projektdokumentation[cite: 155]. |
|
||||
| _7_ | _12.03.2026_ | Finale Abgabe des Diplomarbeitsbuchs. |
|
||||
| _8_ | _22.04.2026_ | Defensio (Projektabschluss). |
|
||||
|
||||
Table: Meilensteine
|
||||
|
||||
### Sprints
|
||||
|
||||
| # | Beginn | Ende | Hinweis | Meilensteine |
|
||||
| :-- | :--------- | :--------- | :--------------- | :------------- |
|
||||
| 1 | 02.10.2025 | 16.10.2025 | | |
|
||||
| 2 | 16.10.2025 | 30.10.2025 | | 1. Meilenstein |
|
||||
| 3 | 30.10.2025 | 13.11.2025 | Herbstferien | |
|
||||
| 4 | 13.11.2025 | 27.11.2025 | | 2. Meilenstein |
|
||||
| 5 | 27.11.2025 | 11.12.2025 | | |
|
||||
| 6 | 11.12.2025 | 25.12.2025 | Weihnachtsferien | 3. Meilenstein |
|
||||
| 7 | 25.12.2025 | 08.01.2026 | Weihnachtsferien | |
|
||||
| 8 | 08.01.2026 | 22.01.2026 | | 4. Meilenstein |
|
||||
| 9 | 22.01.2026 | 05.02.2026 | Semesterferien | |
|
||||
| 10 | 05.02.2026 | 19.02.2026 | | 5. Meilenstein |
|
||||
| 11 | 19.02.2026 | 05.03.2026 | | |
|
||||
| 12 | 05.03.2026 | 19.03.2026 | | 6. Meilenstein |
|
||||
|
||||
Table: Sprints und deren Schwerpunkte
|
||||
|
||||
## Projektumfeld Analyse
|
||||
|
||||
### Grafische Darstellung
|
||||
|
||||
```mermaid
|
||||
|
||||
flowchart TD
|
||||
%% Interne Umwelten (Oben)
|
||||
subgraph Interne ["Interne Umwelten"]
|
||||
direction LR
|
||||
N1["1) Konstantin Hintermayer<br/>Projektleiter"]
|
||||
N2["2) Florian Edlmayer<br/>Projektmitarbeiter"]
|
||||
N3["3) Adam Gaiswinkler<br/>Projektmitarbeiter"]
|
||||
N4["4) Ing. Thomas Gürth<br/>Projektbetreuer"]
|
||||
end
|
||||
|
||||
%% Zentraler Knoten
|
||||
Hub(("Alumnihub"))
|
||||
|
||||
%% Externe Umwelten (Unten)
|
||||
subgraph Externe ["Externe Umwelten"]
|
||||
direction LR
|
||||
N5["5) Absolventenverein"]
|
||||
N6["6) Schülerinnen/Schüler"]
|
||||
N7["7) Lehrerinnen/Lehrer"]
|
||||
N8["8) Konkurrenz"]
|
||||
end
|
||||
|
||||
%% Verbindungslinien
|
||||
N1 & N2 & N3 & N4 --- Hub
|
||||
Hub --- N5 & N6 & N7 & N8
|
||||
|
||||
%% Legende (Ganz unten)
|
||||
subgraph Legend ["Legende"]
|
||||
direction LR
|
||||
L1["... positiver Einfluss"]
|
||||
L2["... potenzieller positiver und/oder negativer Einfluss"]
|
||||
L3["... negativer Einfluss"]
|
||||
end
|
||||
|
||||
%% Styling für bessere Lesbarkeit und Ähnlichkeit zur Vorlage
|
||||
classDef green fill:#008000,stroke:#333,stroke-width:2px,color:#fff,font-weight:bold,font-size:16px;
|
||||
classDef blue fill:#2E64FE,stroke:#333,stroke-width:2px,color:#fff,font-weight:bold,font-size:16px;
|
||||
classDef red fill:#FF0000,stroke:#333,stroke-width:2px,color:#fff,font-weight:bold,font-size:16px;
|
||||
classDef center fill:#D1D9F0,stroke:#333,stroke-width:3px,color:#000,font-weight:bold,font-size:20px;
|
||||
|
||||
class N1,N2,N3,N4,L1 green;
|
||||
class N5,N6,N7,L2 blue;
|
||||
class N8,L3 red;
|
||||
class Hub center;
|
||||
|
||||
```
|
||||
|
||||
### Beschreibung der wichtigsten Umfelder
|
||||
|
||||
| 8 | Konkurrenz | Potenziell vorhandene Konkurrenz | - |
|
||||
|
||||
Table: Projektumfelder und deren Bewertung
|
||||
|
||||
## Risikoanalyse
|
||||
|
||||
| # | Bezeichnung | Beschreibung | Wahrscheinlichkeit (%) | Auswirkung (%) | Risikowert |
|
||||
| :-- | :----------------- | :-------------------------------------------------------------------------------------------------------------------------------- | :--------------------: | :------------: | :--------: |
|
||||
| 1 | Infrastruktur | [cite_start]Instabilität oder Fehlkonfiguration der Cloud-Server (Hetzner CX22), die den Live-Betrieb unterbrechen[cite: 45, 51]. | 60 | 90 | 5400 |
|
||||
| 2 | Datenverlust | [cite_start]Kritischer Ausfall der Datenbank ohne erfolgreiche Wiederherstellung durch automatisierte Backups[cite: 51, 62]. | 15 | 95 | 1425 |
|
||||
| 3 | DSGVO-Verstoß | [cite_start]Fehlende Konformität bei der Speicherung personenbezogener Daten oder beim Opt-In-Verfahren[cite: 38, 39, 128]. | 10 | 100 | 1000 |
|
||||
| 4 | Sicherheitslücke | [cite_start]Unbefugter Zugriff auf das System durch Schwachstellen in der VPN- oder SSH-Verbindung[cite: 83]. | 15 | 85 | 1275 |
|
||||
| 5 | API-Schnittstellen | [cite_start]Ausfall oder Inkompatibilität externer Dienste wie LinkedIn OAuth oder Brevo Mailservice[cite: 74, 78]. | 30 | 50 | 1500 |
|
||||
| 6 | Performance | [cite_start]Beeinträchtigung der Systemleistung bei steigenden Zugriffszahlen in Phase 2[cite: 44, 46]. | 40 | 40 | 1600 |
|
||||
| 7 | Wiederherstellung | [cite_start]Überschreitung des geplanten Disaster-Recovery-Zeitraums von 24 Stunden[cite: 51, 66]. | 25 | 70 | 1750 |
|
||||
| 8 | Oqtane-Integration | [cite_start]Komplexität bei der Entwicklung und Einbindung benutzerdefinierter CMS-Module[cite: 69, 72]. | 30 | 45 | 1350 |
|
||||
| 9 | Dokumentation | [cite_start]Lückenhafte technische Dokumentation erschwert die Wartung durch den Absolventenverein[cite: 155]. | 20 | 30 | 600 |
|
||||
|
||||
Table: Risikoanalyse und Bewertung
|
||||
|
||||
### Grafische Darstellung
|
||||
|
||||
```mermaid
|
||||
quadrantChart
|
||||
title Risikoportfolio Alumnihub
|
||||
x-axis Geringe Auswirkung --> Hohe Auswirkung
|
||||
y-axis Geringe Wahrscheinlichkeit --> Hohe Wahrscheinlichkeit
|
||||
quadrant-1 Kritisch
|
||||
quadrant-2 Beobachten
|
||||
quadrant-3 Akzeptabel
|
||||
quadrant-4 Handlungsbedarf
|
||||
Infrastruktur: [0.9, 0.6]
|
||||
Datenverlust: [0.95, 0.15]
|
||||
DSGVO_Verstoss: [0.99, 0.1]
|
||||
Sicherheitsluecke: [0.85, 0.15]
|
||||
API_Schnittstellen: [0.5, 0.3]
|
||||
Performance: [0.4, 0.4]
|
||||
Wiederherstellung: [0.7, 0.25]
|
||||
Oqtane_Integration: [0.45, 0.3]
|
||||
Dokumentation: [0.3, 0.2]
|
||||
```
|
||||
|
||||
## Projektressourcen
|
||||
|
||||
### 2.6.3 Personelle Ressourcen (geplant)
|
||||
|
||||
| Teammitglied | Personenstunden |
|
||||
| :------------------------- | :-------------: |
|
||||
| **Konstantin Hintermayer** | 180 |
|
||||
| **Florian Edlmayer** | 180 |
|
||||
| **Adam Gaiswinkler** | 180 |
|
||||
| **SUMME** | **540** |
|
||||
|
||||
_Tabelle 7: Personelle Ressourcen_
|
||||
|
||||
Table: Geplante persönliche Zeitressourcen
|
||||
|
||||
### Persönliche Ressourcen(real)
|
||||
|
||||
| Monat/Datum | Florian Edlmayer | Adam Gaiswinkler | Konstantin Hintermayer |
|
||||
| :---------- | :--------------- | :--------------- | :--------------------- |
|
||||
| 30.05. | 1.0h | 1.0h | 0.2h |
|
||||
| 13.06. | 0.5h | 0.8h | 13.8h |
|
||||
| 27.06. | 1.2h | 2.1h | 1.5h |
|
||||
| 11.07. | 0.8h | 0.5h | 5.0h |
|
||||
| 25.07. | 1.5h | 1.2h | 2.5h |
|
||||
| 08.08. | 0.5h | 1.5h | 1.0h |
|
||||
| 22.08. | 1.0h | 2.0h | 1.8h |
|
||||
| 05.09. | 2.3h | 1.4h | 3.2h |
|
||||
| 19.09. | 1.8h | 2.8h | 4.5h |
|
||||
| 03.10. | 10.8h | 18.1h | 22.8h |
|
||||
| 17.10. | 1.0h | 5.5h | 10.8h |
|
||||
| 31.10. | 8.0h | 3.5h | 2.2h |
|
||||
| 14.11. | 3.8h | 10.0h | 5.0h |
|
||||
| 28.11. | 6.0h | 14.3h | 22.5h |
|
||||
| 12.12. | 25.0h | 20.0h | 30.0h |
|
||||
| 26.12. | 15.0h | 14.7h | 25.0h |
|
||||
| 09.01. | 3.0h | 20.0h | 25.0h |
|
||||
| 23.01. | 30.0h | 12.5h | 24.0h |
|
||||
| 06.02. | 17.5h | 47.9h | 16.0h |
|
||||
| 20.02. | 31.0h | 9.5h | 24.0h |
|
||||
| 06.03. | 42.9h | 24.0h | 8.7h |
|
||||
| **Gesamt** | **204.6h** | **213.3h** | **249.5h** |
|
||||
|
||||
Table: Persönliche Ressourcen(real)
|
||||
|
||||
## Projektmanagement mit Scrum
|
||||
|
||||
### Grundlagen der Scrum-Methode
|
||||
|
||||
Für die Organisation und Umsetzung des Projekts wurde das agile Projektmanagement-Framework Scrum verwendet. Scrum ist ein weit verbreitetes Vorgehensmodell in der Softwareentwicklung, das besonders für Projekte geeignet ist, bei denen sich Anforderungen im Laufe der Entwicklung verändern können. Im Gegensatz zu klassischen Projektmanagementmethoden arbeitet Scrum nicht mit einer vollständig im Voraus geplanten Entwicklung, sondern mit kurzen, wiederkehrenden Entwicklungszyklen.
|
||||
|
||||
Das zentrale Element von Scrum ist der sogenannte Sprint. Ein Sprint stellt einen fest definierten Zeitraum dar, in dem bestimmte Funktionen oder Anforderungen umgesetzt werden. In diesem Projekt betrug die Dauer eines Sprints zwei Wochen. Innerhalb dieses Zeitraums arbeitete das Entwicklungsteam daran, ausgewählte Aufgaben zu implementieren und am Ende des Sprints ein funktionsfähiges Ergebnis zu präsentieren.
|
||||
|
||||
Die agile Arbeitsweise von Scrum basiert auf einer iterativen und inkrementellen Entwicklung. Das bedeutet, dass die Software Schritt für Schritt erweitert wird. Nach jedem Sprint steht eine neue Version der Anwendung zur Verfügung, die zusätzliche Funktionen enthält. Dadurch können Änderungen oder neue Anforderungen flexibel in zukünftige Sprints integriert werden.
|
||||
|
||||
Die grundlegende Struktur und der Ablauf eines Scrum-Projekts sind in Abbildung X dargestellt.
|
||||
|
||||

|
||||
_Abbildung X: Übersicht des Scrum-Frameworks mit Rollen, Artefakten und Ereignissen_
|
||||
|
||||
Die Abbildung zeigt die wichtigsten Elemente des Scrum-Frameworks. Auf der linken Seite sind die Anforderungen des Projekts dargestellt, die im sogenannten Product Backlog gesammelt werden. Dieses Backlog enthält alle geplanten Funktionen, Verbesserungen und Aufgaben des Projekts. Diese Anforderungen stammen häufig von Stakeholdern oder zukünftigen Benutzern der Anwendung.
|
||||
|
||||
Während der Sprintplanung wählt das Entwicklungsteam gemeinsam mit dem Product Owner jene Aufgaben aus dem Product Backlog aus, die im nächsten Sprint umgesetzt werden sollen. Diese Aufgaben werden anschließend im Sprint Backlog gesammelt und bilden die Grundlage für die Arbeit während des Sprints.
|
||||
|
||||
Im Zentrum der Abbildung befindet sich der Sprint-Zyklus, der typischerweise zwischen einer und vier Wochen dauert. In diesem Projekt wurde eine Sprintdauer von zwei Wochen festgelegt. Während dieser Phase arbeitet das Entwicklungsteam an der Umsetzung der definierten Aufgaben. Dabei durchläuft die Entwicklung mehrere Schritte, wie beispielsweise Design, Entwicklung, Testen und Deployment.
|
||||
|
||||
Ein wichtiger Bestandteil des Scrum-Prozesses ist das Daily Scrum, ein kurzes tägliches Meeting des Teams. In diesem Meeting berichten die Teammitglieder über ihren aktuellen Fortschritt, geplante Aufgaben sowie mögliche Hindernisse im Entwicklungsprozess. Ziel dieses Meetings ist es, den Arbeitsfortschritt zu koordinieren und Probleme frühzeitig zu erkennen.
|
||||
|
||||
Am Ende jedes Sprints findet das Sprint Review statt. Dabei präsentiert das Entwicklungsteam die umgesetzten Funktionen des Produkts. Stakeholder und Projektbeteiligte haben in diesem Meeting die Möglichkeit, Feedback zu geben und neue Anforderungen einzubringen.
|
||||
|
||||
Anschließend folgt die Sprint Retrospective, in der das Team den Ablauf des vergangenen Sprints reflektiert. Dabei wird analysiert, welche Aspekte des Arbeitsprozesses gut funktioniert haben und welche Verbesserungen für zukünftige Sprints möglich sind.
|
||||
|
||||
Durch diese strukturierte Vorgehensweise ermöglicht Scrum eine kontinuierliche Weiterentwicklung der Software sowie eine regelmäßige Überprüfung des Projektfortschritts.
|
||||
|
||||
### Rollenverteilung im Projektteam
|
||||
|
||||
Das Scrum-Framework definiert drei zentrale Rollen, die jeweils unterschiedliche Aufgaben innerhalb des Projekts übernehmen. Diese Rollen tragen dazu bei, Verantwortlichkeiten klar zu strukturieren und den Entwicklungsprozess effizient zu gestalten.
|
||||
|
||||
Der Product Owner ist für die inhaltliche Planung des Produkts verantwortlich. Zu seinen Aufgaben gehört die Verwaltung und Priorisierung des Product Backlogs. Er entscheidet, welche Funktionen oder Anforderungen für das Produkt am wichtigsten sind und sorgt dafür, dass das Entwicklungsteam stets an den relevantesten Aufgaben arbeitet. Außerdem fungiert der Product Owner als Verbindung zwischen Stakeholdern und Entwicklungsteam.
|
||||
|
||||
Der Scrum Master unterstützt das Team bei der Anwendung der Scrum-Methodik und stellt sicher, dass die Scrum-Prinzipien im Projekt eingehalten werden. Zu seinen Aufgaben gehört unter anderem die Organisation und Moderation der Scrum-Meetings. Darüber hinaus hilft er dem Team dabei, Hindernisse im Entwicklungsprozess zu identifizieren und zu beseitigen.
|
||||
|
||||
Das Entwicklungsteam ist für die technische Umsetzung der Anforderungen verantwortlich. Es besteht aus den Entwicklern, die die Software planen, implementieren, testen und integrieren. Das Team organisiert seine Arbeit weitgehend selbstständig und entscheidet gemeinsam über die technische Umsetzung der Aufgaben.
|
||||
|
||||
Durch diese klar definierten Rollen wird sichergestellt, dass organisatorische und technische Aufgaben effizient verteilt werden können und der Entwicklungsprozess strukturiert abläuft.
|
||||
|
||||
### Sprintplanung und Umsetzung
|
||||
|
||||
Die Sprintplanung stellt einen wichtigen Bestandteil des Scrum-Prozesses dar. Zu Beginn jedes neuen Sprints trifft sich das Team, um gemeinsam zu entscheiden, welche Aufgaben innerhalb des kommenden Zeitraums umgesetzt werden sollen.
|
||||
|
||||
Als Grundlage dient das Product Backlog, in dem alle bekannten Anforderungen des Projekts gesammelt werden. Während der Sprintplanung wählt das Team jene Aufgaben aus, die innerhalb der nächsten zwei Wochen realistisch umgesetzt werden können. Diese Aufgaben werden anschließend in das Sprint Backlog übernommen.
|
||||
|
||||
Während des Sprints arbeitet das Entwicklungsteam an der Umsetzung dieser Aufgaben. Die Arbeit wird dabei häufig in kleinere Teilaufgaben unterteilt, um die Fortschritte besser verfolgen zu können.
|
||||
|
||||
Zur Koordination der täglichen Arbeit findet das sogenannte Daily Scrum statt. Dieses kurze Meeting dauert in der Regel maximal 15 Minuten und dient dazu, den aktuellen Stand der Entwicklung zu besprechen. Jedes Teammitglied beantwortet dabei typischerweise drei Fragen:
|
||||
|
||||
• Was habe ich seit dem letzten Meeting erledigt?
|
||||
• Woran werde ich heute arbeiten?
|
||||
• Welche Hindernisse gibt es aktuell?
|
||||
|
||||
Durch diese regelmäßige Abstimmung wird sichergestellt, dass alle Teammitglieder über den aktuellen Projektstand informiert sind und mögliche Probleme frühzeitig erkannt werden.
|
||||
|
||||
Am Ende eines Sprints werden die entwickelten Funktionen im Sprint Review präsentiert. In diesem Meeting wird überprüft, welche Anforderungen erfolgreich umgesetzt wurden und ob weitere Anpassungen notwendig sind. Anschließend reflektiert das Team im Rahmen der Sprint Retrospective den Arbeitsprozess und identifiziert mögliche Verbesserungen für zukünftige Sprints.
|
||||
|
||||
### Vorteile von Scrum für die Webentwicklung
|
||||
|
||||
Die Verwendung von Scrum bietet insbesondere für Webentwicklungsprojekte zahlreiche Vorteile. Webanwendungen entwickeln sich häufig dynamisch weiter, da sich Anforderungen, Technologien oder Benutzerbedürfnisse im Laufe der Zeit verändern können.
|
||||
|
||||
Durch die Aufteilung der Entwicklung in kurze Sprints kann das Projekt flexibel auf solche Veränderungen reagieren. Neue Anforderungen können einfach in zukünftige Sprints integriert werden, ohne den gesamten Projektplan neu strukturieren zu müssen.
|
||||
|
||||
Ein weiterer Vorteil liegt in der kontinuierlichen Überprüfung der entwickelten Funktionen. Da am Ende jedes Sprints ein funktionierender Teil der Software präsentiert wird, können Fehler oder Verbesserungsmöglichkeiten frühzeitig erkannt werden. Dies reduziert das Risiko größerer Probleme in späteren Projektphasen.
|
||||
|
||||
Darüber hinaus fördert Scrum die Zusammenarbeit innerhalb des Teams. Regelmäßige Meetings und eine transparente Aufgabenverteilung sorgen dafür, dass alle Teammitglieder stets über den aktuellen Stand des Projekts informiert sind.
|
||||
|
||||
Insgesamt ermöglicht Scrum eine strukturierte, flexible und transparente Vorgehensweise bei der Entwicklung von Webanwendungen. Dadurch kann die Qualität der entwickelten Software verbessert und der Entwicklungsprozess effizienter gestaltet werden.
|
||||
@@ -1,8 +1,8 @@
|
||||
# Diplomarbeitsbuch – Individueller Teil
|
||||
\cleardoublepage
|
||||
|
||||
---
|
||||
# Adam Gaiswinkler
|
||||
|
||||
## 1. Einleitung
|
||||
## Einleitung
|
||||
|
||||
### Motivation
|
||||
|
||||
@@ -34,51 +34,47 @@ Der persönliche Aufgabenbereich umfasste die Entwicklung der beiden zentralen C
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## 2. Technologischer Überblick
|
||||
## Technologischer Überblick
|
||||
|
||||
In diesem Kapitel werden die Technologien und Werkzeuge vorgestellt, die für die Entwicklung des Anmeldetools, des Hall-of-Fame-Moduls und des Oqtane-Themes eingesetzt wurden. Die Beschreibungen sind dabei so gestaltet, dass sie auch für Leserinnen und Leser ohne technischen Hintergrund verständlich sind.
|
||||
|
||||
### 2.1 C# und ASP.NET Core
|
||||
### 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.
|
||||
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 [@aspnet_core_docs]. 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
|
||||
|
||||
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.
|
||||
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 [@blazor_docs]. 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
|
||||
|
||||
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.
|
||||
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 [@oqtane_about].
|
||||
|
||||
### 2.4 Bootstrap und CSS
|
||||
### 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.
|
||||
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 [@bootstrap].
|
||||
|
||||
### 2.5 QuestPDF
|
||||
### 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.
|
||||
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 [@questpdf].
|
||||
|
||||
### 2.6 Gitea
|
||||
### 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.
|
||||
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 [@gitea_about]. 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
|
||||
### 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 JetBrains Rider eingesetzt. JetBrains Rider 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 JetBrains Rider ermöglichte es, die Entwicklung auf dem neuen System ohne größere Unterbrechungen fortzusetzen.
|
||||
|
||||
### 2.8 Plattformwechsel: Windows zu macOS
|
||||
### 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 JetBrains Rider als IDE konnten diese Probleme weitgehend gelöst werden, da JetBrains Rider eine bessere plattformübergreifende Integration bietet als die ursprünglich verwendete Entwicklungsumgebung.
|
||||
|
||||
---
|
||||
## Entwicklung des Oqtane Themes
|
||||
|
||||
## 3. Entwicklung des Oqtane Themes
|
||||
|
||||
### 3.1 Ziel des Themes
|
||||
### Ziel des Themes
|
||||
|
||||
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.
|
||||
|
||||
@@ -86,7 +82,7 @@ Das visuelle Design orientiert sich dabei am bestehenden Erscheinungsbild der of
|
||||
|
||||
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.
|
||||
|
||||
### 3.2 Technische Umsetzung
|
||||
### Technische Umsetzung
|
||||
|
||||
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.
|
||||
|
||||
@@ -135,41 +131,39 @@ Neben Navigation und Layout wurden weitere Funktionen in das Theme integriert. D
|
||||
|
||||
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
|
||||
### Herausforderungen
|
||||
|
||||
Die eigene Implementierung der Navigation über `PageState.Pages` und eine LINQ-basierte Filterlogik erwies sich als die richtige Entscheidung. Sie löste nicht nur das Problem der fehlenden Anpassbarkeit der Standardkomponente, sondern brachte gleichzeitig einen wesentlichen Mehrwert: Die Navigation ist vollständig wartungsfrei. Werden im CMS neue Seiten angelegt, erscheinen diese automatisch im Menü – ohne dass eine einzige Zeile Code angepasst werden muss. Zudem ermöglichte dieser Ansatz die vollständige Kontrolle über das Erscheinungsbild des Burger-Menüs, was mit der Standardkomponente nicht möglich gewesen wäre.
|
||||
|
||||
---
|
||||
## Umsetzung der Module
|
||||
|
||||
## 4. Umsetzung der Module
|
||||
### Anmeldetool
|
||||
|
||||
### 4.1 Anmeldetool
|
||||
|
||||
#### 4.1.1 Ziel des Moduls
|
||||
#### Ziel des Moduls
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
#### 4.1.2 Frontend (Eingabemaske)
|
||||
#### Frontend (Eingabemaske)
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
#### 4.1.3 API-Schnittstelle
|
||||
#### API-Schnittstelle
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
#### 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)
|
||||
#### 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 Benutzerinnen und Benutzer 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.
|
||||
|
||||
@@ -177,7 +171,7 @@ Während der Testphase wurden mehrere visuelle Darstellungsprobleme identifizier
|
||||
|
||||
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
|
||||
### 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.
|
||||
|
||||
@@ -235,23 +229,25 @@ Das Modul verwendet zwei Entitäten, die in der Datenbank als Tabellen abgebilde
|
||||
|
||||
Die zentrale Entität repräsentiert einen einzelnen Absolventeneintrag und wird in der Datenbanktabelle `SZUAbsolventenvereinHallOfFame` gespeichert.
|
||||
|
||||
| Spalte | Datentyp | Beschreibung |
|
||||
|--------|----------|--------------|
|
||||
| `HallOfFameId` | `int` (PK, Auto-Inkrement) | Primärschlüssel |
|
||||
| `ModuleId` | `int` (FK → `Module`) | Fremdschlüssel zur Oqtane-Modulinstanz |
|
||||
| `Name` | `string` | Name der Person |
|
||||
| `Year` | `int` | Abschlussjahrgang |
|
||||
| `Description` | `string` | Beschreibung bzw. Werdegang |
|
||||
| `Image` | `string` | Relativer Pfad zum hochgeladenen Foto |
|
||||
| `Link` | `string` | Optionaler externer Link |
|
||||
| `Status` | `string` (max. 50) | Veröffentlichungsstatus: „Draft" oder „Published" |
|
||||
| `UserId` | `int` | ID der Benutzerin bzw. des Benutzers, der den Eintrag erstellt hat |
|
||||
| `IsReported` | `bool` | Kennzeichnung, ob der Eintrag gemeldet wurde |
|
||||
| `ReportReason` | `string` | (Legacy) Ursprüngliches Feld für Meldegrund, abgelöst durch die Report-Tabelle |
|
||||
| `CreatedBy` | `string` | Erstellt von (Audit) |
|
||||
| `CreatedOn` | `DateTime` | Erstellzeitpunkt (Audit) |
|
||||
| `ModifiedBy` | `string` | Zuletzt geändert von (Audit) |
|
||||
| `ModifiedOn` | `DateTime` | Zeitpunkt der letzten Änderung (Audit) |
|
||||
| Spalte | Datentyp | Beschreibung |
|
||||
| -------------- | -------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `HallOfFameId` | `int` (PK, Auto-Inkrement) | Primärschlüssel |
|
||||
| `ModuleId` | `int` (FK → `Module`) | Fremdschlüssel zur Oqtane-Modulinstanz |
|
||||
| `Name` | `string` | Name der Person |
|
||||
| `Year` | `int` | Abschlussjahrgang |
|
||||
| `Description` | `string` | Beschreibung bzw. Werdegang |
|
||||
| `Image` | `string` | Relativer Pfad zum hochgeladenen Foto |
|
||||
| `Link` | `string` | Optionaler externer Link |
|
||||
| `Status` | `string` (max. 50) | Veröffentlichungsstatus: „Draft" oder „Published" |
|
||||
| `UserId` | `int` | ID der Benutzerin bzw. des Benutzers, der den Eintrag erstellt hat |
|
||||
| `IsReported` | `bool` | Kennzeichnung, ob der Eintrag gemeldet wurde |
|
||||
| `ReportReason` | `string` | (Legacy) Ursprüngliches Feld für Meldegrund, abgelöst durch die Report-Tabelle |
|
||||
| `CreatedBy` | `string` | Erstellt von (Audit) |
|
||||
| `CreatedOn` | `DateTime` | Erstellzeitpunkt (Audit) |
|
||||
| `ModifiedBy` | `string` | Zuletzt geändert von (Audit) |
|
||||
| `ModifiedOn` | `DateTime` | Zeitpunkt der letzten Änderung (Audit) |
|
||||
|
||||
Table: Datenmodell der Entität HallOfFame
|
||||
|
||||
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.
|
||||
|
||||
@@ -259,15 +255,17 @@ Die Entität implementiert das Oqtane-Interface `IAuditable`, wodurch die Audit-
|
||||
|
||||
Die zweite Entität bildet einzelne Meldungen zu einem Eintrag ab und wird in der Tabelle `SZUAbsolventenvereinHallOfFameReport` gespeichert.
|
||||
|
||||
| Spalte | Datentyp | Beschreibung |
|
||||
|--------|----------|--------------|
|
||||
| `HallOfFameReportId` | `int` (PK, Auto-Inkrement) | Primärschlüssel |
|
||||
| `HallOfFameId` | `int` (FK → `SZUAbsolventenvereinHallOfFame`) | Zugehöriger Eintrag |
|
||||
| `Reason` | `string` | Meldegrund |
|
||||
| `CreatedBy` | `string` | Erstellt von (Audit) |
|
||||
| `CreatedOn` | `DateTime` | Erstellzeitpunkt (Audit) |
|
||||
| `ModifiedBy` | `string` | Zuletzt geändert von (Audit) |
|
||||
| `ModifiedOn` | `DateTime` | Zeitpunkt der letzten Änderung (Audit) |
|
||||
| Spalte | Datentyp | Beschreibung |
|
||||
| -------------------- | --------------------------------------------- | -------------------------------------- |
|
||||
| `HallOfFameReportId` | `int` (PK, Auto-Inkrement) | Primärschlüssel |
|
||||
| `HallOfFameId` | `int` (FK → `SZUAbsolventenvereinHallOfFame`) | Zugehöriger Eintrag |
|
||||
| `Reason` | `string` | Meldegrund |
|
||||
| `CreatedBy` | `string` | Erstellt von (Audit) |
|
||||
| `CreatedOn` | `DateTime` | Erstellzeitpunkt (Audit) |
|
||||
| `ModifiedBy` | `string` | Zuletzt geändert von (Audit) |
|
||||
| `ModifiedOn` | `DateTime` | Zeitpunkt der letzten Änderung (Audit) |
|
||||
|
||||
Table: Datenmodell der Entität HallOfFameReport
|
||||
|
||||
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.
|
||||
|
||||
@@ -275,13 +273,13 @@ Der Fremdschlüssel zu `SZUAbsolventenvereinHallOfFame` ist mit kaskadierendem L
|
||||
|
||||
Die Datenbankstruktur wird über Entity Framework Core Migrationen versioniert verwaltet.
|
||||
|
||||
| Migration | Versionsnummer | Inhalt |
|
||||
|-----------|----------------|--------|
|
||||
| `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` |
|
||||
| Migration | Versionsnummer | Inhalt |
|
||||
| --------------------- | -------------- | ----------------------------------------------------------------------------- |
|
||||
| `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` |
|
||||
| `AddReportTable` | `01.00.00.03` | Erstellt die eigenständige Report-Tabelle mit Fremdschlüssel zur Haupttabelle |
|
||||
|
||||
> **Hinweis:** Version `01.00.00.01` wurde im Entwicklungsprozess übersprungen, da eine fehlerhafte Migration erstellt und anschließend wieder gelöscht werden musste.
|
||||
| `AddReportTable` | `01.00.00.03` | Erstellt die eigenständige Report-Tabelle mit Fremdschlüssel zur Haupttabelle |
|
||||
Table: Übersicht der Datenbank migrationen
|
||||
|
||||
#### Implementierungsdetails und Problemlösungen
|
||||
|
||||
@@ -303,11 +301,9 @@ Karten hatten ursprünglich unterschiedliche Höhen durch variierende Beschreibu
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
### Übergangslösung, Probleme & Learnings
|
||||
|
||||
## 5. Übergangslösung, Probleme & Learnings
|
||||
|
||||
### 5.1 Übergangslösung (Sommer 2025)
|
||||
### Übergangslösung (Sommer 2025)
|
||||
|
||||
#### Gründe & Technische Umsetzung
|
||||
|
||||
@@ -329,7 +325,7 @@ Es gab keine Benutzerkonten und keine Authentifizierung. Jede Person, die die Se
|
||||
|
||||
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
|
||||
### Probleme
|
||||
|
||||
#### Technische Probleme
|
||||
|
||||
@@ -355,7 +351,7 @@ Neben den technischen Herausforderungen gab es auch auf organisatorischer Ebene
|
||||
|
||||
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
|
||||
### Learnings
|
||||
|
||||
#### Technisch
|
||||
|
||||
@@ -375,9 +371,7 @@ Darüber hinaus hat sich gezeigt, dass regelmäßige Team-Meetings ein wichtiges
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## 6. Testen & Qualitätssicherung
|
||||
### Testen & Qualitätssicherung
|
||||
|
||||
### Funktionstests der Module
|
||||
|
||||
@@ -395,11 +389,9 @@ Neben den technischen Tests wurde die Plattform auch von weiteren Personen – d
|
||||
|
||||
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.
|
||||
|
||||
### Fazit und Ausblick
|
||||
|
||||
---
|
||||
## 7. Fazit und Ausblick
|
||||
|
||||
### 7.1 Zielerreichung
|
||||
### Zielerreichung
|
||||
|
||||
Rückblickend auf das Projekt AlumniHub lässt sich festhalten, dass die zentralen Ziele meines individuellen Aufgabenbereichs erfolgreich umgesetzt wurden.
|
||||
|
||||
@@ -411,32 +403,10 @@ Das **Theme** wurde von einem anderen Teammitglied entwickelt und ist ebenfalls
|
||||
|
||||
Insgesamt bin ich mit dem erreichten Stand zufrieden – insbesondere angesichts der Herausforderungen durch die Teamverkleinerung, den Plattformwechsel von Windows auf macOS und die technischen Probleme mit dem ursprünglichen Hosting.
|
||||
|
||||
### 7.2 Ausblick
|
||||
### Ausblick
|
||||
|
||||
Für die Weiterentwicklung der Plattform gibt es mehrere sinnvolle nächste Schritte. Im Bereich des Anmeldetools wäre die Implementierung einer vollständigen Teilnehmerliste mit direkter Datenbankanbindung der wichtigste nächste Schritt. Ergänzend dazu sollte die Möglichkeit geschaffen werden, maximale Teilnehmerzahlen pro Veranstaltung festzulegen, sodass Organisatoren die Kapazität von Treffen besser steuern können.
|
||||
|
||||
Langfristig könnte die Plattform um eine automatische E-Mail-Benachrichtigung erweitert werden, die Mitglieder an bevorstehende Veranstaltungen erinnert. Auch eine Exportfunktion für die Anmeldedaten – beispielsweise als CSV oder PDF – wäre für die Vereinsverwaltung hilfreich.
|
||||
|
||||
AlumniHub bietet als Plattform eine solide Grundlage, die in den kommenden Jahren kontinuierlich erweitert werden kann, um den Absolventenverein der HTL Ungargasse langfristig digital zu unterstützen.
|
||||
|
||||
---
|
||||
|
||||
## 8. Quellen
|
||||
|
||||
Microsoft: ASP.NET Core Documentation. https://learn.microsoft.com/en-us/aspnet/core/ [Zugriff: 17.03.2026]
|
||||
|
||||
Microsoft: Blazor Documentation. https://learn.microsoft.com/en-us/aspnet/core/blazor/ [Zugriff: 17.03.2026]
|
||||
|
||||
Microsoft: Entity Framework Core – Migrations. https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/ [Zugriff: 17.03.2026]
|
||||
|
||||
Oqtane Framework: https://www.oqtane.org/ [Zugriff: 17.03.2026]
|
||||
|
||||
Oqtane Developer Documentation: https://docs.oqtane.org/ [Zugriff: 17.03.2026]
|
||||
|
||||
QuestPDF Open Source Library: https://www.questpdf.com/ [Zugriff: 17.03.2026]
|
||||
|
||||
Bootstrap Framework: https://getbootstrap.com/ [Zugriff: 17.03.2026]
|
||||
|
||||
Gitea – Open Source Git Service: https://about.gitea.com/ [Zugriff: 17.03.2026]
|
||||
|
||||
LiveDesign – Hosting, Design & Branding: https://livedesign.at/ [Zugriff: 17.03.2026]
|
||||
@@ -1,113 +1,48 @@
|
||||
---
|
||||
include_toc: true
|
||||
gitea: none
|
||||
---
|
||||
# 2. Projektmanagement mit Scrum
|
||||
## 2.1 Grundlagen der Scrum-Methode
|
||||
Für die Organisation und Umsetzung des Projekts wurde das agile Projektmanagement-Framework Scrum verwendet. Scrum ist ein weit verbreitetes Vorgehensmodell in der Softwareentwicklung, das besonders für Projekte geeignet ist, bei denen sich Anforderungen im Laufe der Entwicklung verändern können. Im Gegensatz zu klassischen Projektmanagementmethoden arbeitet Scrum nicht mit einer vollständig im Voraus geplanten Entwicklung, sondern mit kurzen, wiederkehrenden Entwicklungszyklen.
|
||||
\cleardoublepage
|
||||
|
||||
Das zentrale Element von Scrum ist der sogenannte Sprint. Ein Sprint stellt einen fest definierten Zeitraum dar, in dem bestimmte Funktionen oder Anforderungen umgesetzt werden. In diesem Projekt betrug die Dauer eines Sprints zwei Wochen. Innerhalb dieses Zeitraums arbeitete das Entwicklungsteam daran, ausgewählte Aufgaben zu implementieren und am Ende des Sprints ein funktionsfähiges Ergebnis zu präsentieren.
|
||||
# Florian Edlmayer
|
||||
|
||||
Die agile Arbeitsweise von Scrum basiert auf einer iterativen und inkrementellen Entwicklung. Das bedeutet, dass die Software Schritt für Schritt erweitert wird. Nach jedem Sprint steht eine neue Version der Anwendung zur Verfügung, die zusätzliche Funktionen enthält. Dadurch können Änderungen oder neue Anforderungen flexibel in zukünftige Sprints integriert werden.
|
||||
## Einleitung des individuellen Teils
|
||||
|
||||
Die grundlegende Struktur und der Ablauf eines Scrum-Projekts sind in Abbildung X dargestellt.
|
||||
### Ausgangssituation
|
||||
|
||||

|
||||
*Abbildung X: Übersicht des Scrum-Frameworks mit Rollen, Artefakten und Ereignissen*
|
||||
Der Absolventenverein der HTL Ungargasse hatte keine digitale Infrastruktur zur Vernetzung seiner Mitglieder. AlumniHub soll das ändern: eine zentrale Plattform für Mitgliederverwaltung, Veranstaltungen und Netzwerkfunktionen.
|
||||
|
||||
Die Abbildung zeigt die wichtigsten Elemente des Scrum-Frameworks. Auf der linken Seite sind die Anforderungen des Projekts dargestellt, die im sogenannten Product Backlog gesammelt werden. Dieses Backlog enthält alle geplanten Funktionen, Verbesserungen und Aufgaben des Projekts. Diese Anforderungen stammen häufig von Stakeholdern oder zukünftigen Benutzern der Anwendung.
|
||||
### Zielsetzung
|
||||
|
||||
Während der Sprintplanung wählt das Entwicklungsteam gemeinsam mit dem Product Owner jene Aufgaben aus dem Product Backlog aus, die im nächsten Sprint umgesetzt werden sollen. Diese Aufgaben werden anschließend im Sprint Backlog gesammelt und bilden die Grundlage für die Arbeit während des Sprints.
|
||||
Mein persönlicher Beitrag umfasst die DSGVO-konforme Datenschutzimplementierung, ein automatisiertes Backup- und Restore-System, die Anbindung von LinkedIn als externem Identitätsanbieter sowie den Premium-Bereich.
|
||||
|
||||
Im Zentrum der Abbildung befindet sich der Sprint-Zyklus, der typischerweise zwischen einer und vier Wochen dauert. In diesem Projekt wurde eine Sprintdauer von zwei Wochen festgelegt. Während dieser Phase arbeitet das Entwicklungsteam an der Umsetzung der definierten Aufgaben. Dabei durchläuft die Entwicklung mehrere Schritte, wie beispielsweise Design, Entwicklung, Testen und Deployment.
|
||||
### Nutzen für den Absolventenverein
|
||||
|
||||
Ein wichtiger Bestandteil des Scrum-Prozesses ist das Daily Scrum, ein kurzes tägliches Meeting des Teams. In diesem Meeting berichten die Teammitglieder über ihren aktuellen Fortschritt, geplante Aufgaben sowie mögliche Hindernisse im Entwicklungsprozess. Ziel dieses Meetings ist es, den Arbeitsfortschritt zu koordinieren und Probleme frühzeitig zu erkennen.
|
||||
Für den Verein bedeutet das: rechtskonforme Datenverarbeitung, gesicherte Datenverfügbarkeit und ein niedrigschwelliger Login für Absolventinnen und Absolventen mit bestehendem LinkedIn-Konto.
|
||||
|
||||
Am Ende jedes Sprints findet das Sprint Review statt. Dabei präsentiert das Entwicklungsteam die umgesetzten Funktionen des Produkts. Stakeholder und Projektbeteiligte haben in diesem Meeting die Möglichkeit, Feedback zu geben und neue Anforderungen einzubringen.
|
||||
### Themenstellung
|
||||
|
||||
Anschließend folgt die Sprint Retrospective, in der das Team den Ablauf des vergangenen Sprints reflektiert. Dabei wird analysiert, welche Aspekte des Arbeitsprozesses gut funktioniert haben und welche Verbesserungen für zukünftige Sprints möglich sind.
|
||||
|
||||
Durch diese strukturierte Vorgehensweise ermöglicht Scrum eine kontinuierliche Weiterentwicklung der Software sowie eine regelmäßige Überprüfung des Projektfortschritts.
|
||||
|
||||
## 2.2 Rollenverteilung im Projektteam
|
||||
Das Scrum-Framework definiert drei zentrale Rollen, die jeweils unterschiedliche Aufgaben innerhalb des Projekts übernehmen. Diese Rollen tragen dazu bei, Verantwortlichkeiten klar zu strukturieren und den Entwicklungsprozess effizient zu gestalten.
|
||||
|
||||
Der Product Owner ist für die inhaltliche Planung des Produkts verantwortlich. Zu seinen Aufgaben gehört die Verwaltung und Priorisierung des Product Backlogs. Er entscheidet, welche Funktionen oder Anforderungen für das Produkt am wichtigsten sind und sorgt dafür, dass das Entwicklungsteam stets an den relevantesten Aufgaben arbeitet. Außerdem fungiert der Product Owner als Verbindung zwischen Stakeholdern und Entwicklungsteam.
|
||||
|
||||
Der Scrum Master unterstützt das Team bei der Anwendung der Scrum-Methodik und stellt sicher, dass die Scrum-Prinzipien im Projekt eingehalten werden. Zu seinen Aufgaben gehört unter anderem die Organisation und Moderation der Scrum-Meetings. Darüber hinaus hilft er dem Team dabei, Hindernisse im Entwicklungsprozess zu identifizieren und zu beseitigen.
|
||||
|
||||
Das Entwicklungsteam ist für die technische Umsetzung der Anforderungen verantwortlich. Es besteht aus den Entwicklern, die die Software planen, implementieren, testen und integrieren. Das Team organisiert seine Arbeit weitgehend selbstständig und entscheidet gemeinsam über die technische Umsetzung der Aufgaben.
|
||||
|
||||
Durch diese klar definierten Rollen wird sichergestellt, dass organisatorische und technische Aufgaben effizient verteilt werden können und der Entwicklungsprozess strukturiert abläuft.
|
||||
|
||||
## 2.3 Sprintplanung und Umsetzung
|
||||
Die Sprintplanung stellt einen wichtigen Bestandteil des Scrum-Prozesses dar. Zu Beginn jedes neuen Sprints trifft sich das Team, um gemeinsam zu entscheiden, welche Aufgaben innerhalb des kommenden Zeitraums umgesetzt werden sollen.
|
||||
|
||||
Als Grundlage dient das Product Backlog, in dem alle bekannten Anforderungen des Projekts gesammelt werden. Während der Sprintplanung wählt das Team jene Aufgaben aus, die innerhalb der nächsten zwei Wochen realistisch umgesetzt werden können. Diese Aufgaben werden anschließend in das Sprint Backlog übernommen.
|
||||
|
||||
Während des Sprints arbeitet das Entwicklungsteam an der Umsetzung dieser Aufgaben. Die Arbeit wird dabei häufig in kleinere Teilaufgaben unterteilt, um die Fortschritte besser verfolgen zu können.
|
||||
|
||||
Zur Koordination der täglichen Arbeit findet das sogenannte Daily Scrum statt. Dieses kurze Meeting dauert in der Regel maximal 15 Minuten und dient dazu, den aktuellen Stand der Entwicklung zu besprechen. Jedes Teammitglied beantwortet dabei typischerweise drei Fragen:
|
||||
|
||||
• Was habe ich seit dem letzten Meeting erledigt?
|
||||
• Woran werde ich heute arbeiten?
|
||||
• Welche Hindernisse gibt es aktuell?
|
||||
|
||||
Durch diese regelmäßige Abstimmung wird sichergestellt, dass alle Teammitglieder über den aktuellen Projektstand informiert sind und mögliche Probleme frühzeitig erkannt werden.
|
||||
|
||||
Am Ende eines Sprints werden die entwickelten Funktionen im Sprint Review präsentiert. In diesem Meeting wird überprüft, welche Anforderungen erfolgreich umgesetzt wurden und ob weitere Anpassungen notwendig sind. Anschließend reflektiert das Team im Rahmen der Sprint Retrospective den Arbeitsprozess und identifiziert mögliche Verbesserungen für zukünftige Sprints.
|
||||
|
||||
## 2.4 Vorteile von Scrum für die Webentwicklung
|
||||
Die Verwendung von Scrum bietet insbesondere für Webentwicklungsprojekte zahlreiche Vorteile. Webanwendungen entwickeln sich häufig dynamisch weiter, da sich Anforderungen, Technologien oder Benutzerbedürfnisse im Laufe der Zeit verändern können.
|
||||
|
||||
Durch die Aufteilung der Entwicklung in kurze Sprints kann das Projekt flexibel auf solche Veränderungen reagieren. Neue Anforderungen können einfach in zukünftige Sprints integriert werden, ohne den gesamten Projektplan neu strukturieren zu müssen.
|
||||
|
||||
Ein weiterer Vorteil liegt in der kontinuierlichen Überprüfung der entwickelten Funktionen. Da am Ende jedes Sprints ein funktionierender Teil der Software präsentiert wird, können Fehler oder Verbesserungsmöglichkeiten frühzeitig erkannt werden. Dies reduziert das Risiko größerer Probleme in späteren Projektphasen.
|
||||
|
||||
Darüber hinaus fördert Scrum die Zusammenarbeit innerhalb des Teams. Regelmäßige Meetings und eine transparente Aufgabenverteilung sorgen dafür, dass alle Teammitglieder stets über den aktuellen Stand des Projekts informiert sind.
|
||||
|
||||
Insgesamt ermöglicht Scrum eine strukturierte, flexible und transparente Vorgehensweise bei der Entwicklung von Webanwendungen. Dadurch kann die Qualität der entwickelten Software verbessert und der Entwicklungsprozess effizienter gestaltet werden.
|
||||
# 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:
|
||||
|
||||
• Datenschutzerklärung
|
||||
• Backup und Restore Systeme
|
||||
• Entwicklung
|
||||
• Premiumbereich
|
||||
• LinkedIn Anmeldung
|
||||
|
||||
# 2. Anforderungen an das entwickelte Modul bzw. die Funktionalität
|
||||
funktionale / nicht‑funktionale Anforderungen
|
||||
|
||||
Use Cases
|
||||
|
||||
# 3. Individuelle Themenstellung & Zielsetzung
|
||||
## 3.1 Themenstellung
|
||||
|
||||
### Entwicklung von einem CMS-Modul
|
||||
#### Entwicklung eines CMS-Moduls
|
||||
|
||||
• **Premiumbereich:**
|
||||
Ein Schwerpunkt der Arbeit ist die Entwicklung eines Oqtane-Moduls, das einen Premiumbereich bereitstellt. Dieser Bereich soll ausschließlich autorisierten Benutzerinnen und Benutzern zugänglich sein und spezielle Inhalte oder Funktionen anbieten wie zum Beispiel das hochladen und ansehen von Ingeneuranträgen als Vorlage oder das finden und Kontakt aufnehmen mit anderen Benutzern. Dabei werden grundlegende Konzepte wie Benutzerrollen, Zugriffsrechte und die Integration in das bestehende CMS eingebunden.
|
||||
|
||||
### Erstellung einer Datenschutzerklärung
|
||||
##### Erstellung einer Datenschutzerklärung
|
||||
|
||||
Eine weitere wichtige Aufgabe war die Erstellung einer gesetzeskonformen Datenschutzerklärung. Diese ist erforderlich, um die Nutzerinnen und Nutzer unserer Webanwendung transparent und verständlich darüber zu informieren, welche personenbezogenen Daten erhoben, verarbeitet und gespeichert werden, zu welchem Zweck dies geschieht und welche Rechte ihnen in Bezug auf ihre Daten zustehen.
|
||||
|
||||
### LinkedIn Anmeldung
|
||||
#### LinkedIn-Anmeldung
|
||||
|
||||
Zur Verbesserung der Benutzerfreundlichkeit und zur vereinfachten Anmeldung wird eine Anmeldung über LinkedIn mithilfe OAuths implementiert. Diese ermöglicht es Benutzerinnen und Benutzern, sich mit einem bestehenden LinkedIn-Konto zu registrieren oder anzumelden.
|
||||
Zur Verbesserung der Benutzerfreundlichkeit und zur vereinfachten Anmeldung wird eine Anmeldung über LinkedIn mithilfe des OAuth-2.0-Protokolls implementiert. Diese ermöglicht es Benutzerinnen und Benutzern, sich mit einem bestehenden LinkedIn-Konto zu registrieren oder anzumelden.
|
||||
|
||||
#### Datenbank-Backup und Restore
|
||||
|
||||
### Datenbank Backup und Restore
|
||||
Abschließend wurde das Thema Datensicherung umfassend behandelt, da die Sicherstellung der Datenintegrität und Systemverfügbarkeit einen zentralen Bestandteil moderner Webanwendungen darstellt. In diesem Zusammenhang wurde ein Skript zur automatisierten Erstellung von Datenbank-Backups entwickelt, das in regelmäßigen Abständen Sicherungskopien der relevanten Daten erzeugt. Zusätzlich wurde ein weiteres Skript implementiert, das im Falle eines Datenverlusts oder Systemausfalls eine strukturierte und zuverlässige Wiederherstellung der gesicherten Daten ermöglicht.
|
||||
|
||||
Ziel dieser Maßnahmen ist es, das Risiko von Datenverlusten zu minimieren und einen stabilen sowie sicheren Betrieb der Webanwendung zu gewährleisten. Die Backup- und Restore-Prozesse sollen automatisiert ablaufen, um menschliche Fehler zu reduzieren und eine kontinuierliche, regelmäßige Datensicherung sicherzustellen. Dadurch wird eine hohe Ausfallsicherheit sowie die langfristige Verfügbarkeit der gespeicherten Informationen gewährleistet.
|
||||
## 3.2 Zielsetzung
|
||||
|
||||
### Technische Zielsetzung
|
||||
### Detail-Zielsetzungen
|
||||
|
||||
#### Zielsetzung
|
||||
|
||||
##### Technische Zielsetzung
|
||||
|
||||
Die technische Zielsetzung dieses Diplomarbeitsprojekts bestand darin, eine stabile und sichere Webplattform für Alumni zu entwickeln. Als technische Grundlage wurde das Content-Management-System Oqtane verwendet, welches auf modernen Webtechnologien basiert und eine modulare Erweiterung von Funktionen ermöglicht. Die Implementierung erfolgte innerhalb der von Oqtane bereitgestellten Architektur, wodurch verschiedene Funktionen effizient integriert werden konnten.
|
||||
|
||||
Ein zentraler Bestandteil der technischen Umsetzung war die Integration einer externen Authentifizierung über LinkedIn mithilfe des Protokolls OAuth 2.0. Dadurch können sich Benutzer direkt mit ihrem LinkedIn-Konto auf der Plattform anmelden, ohne ein separates Benutzerkonto erstellen zu müssen. Dies vereinfacht den Registrierungsprozess und erhöht gleichzeitig die Sicherheit, da sensible Zugangsdaten ausschließlich beim externen Anbieter verbleiben.
|
||||
@@ -115,7 +50,8 @@ Ein zentraler Bestandteil der technischen Umsetzung war die Integration einer ex
|
||||
Zusätzlich wurde eine Datenbank auf Basis von PostgreSQL eingesetzt, um Benutzerinformationen, Inhalte sowie weitere Systemdaten zuverlässig zu speichern. Um die langfristige Verfügbarkeit dieser Daten sicherzustellen, wurde ein automatisiertes Backup-System implementiert. Dieses System erstellt regelmäßig Sicherungskopien der Datenbank sowie der wichtigsten Systemdateien. Ergänzend dazu wurde ein Restore-Skript entwickelt, mit dem gespeicherte Daten im Falle eines Systemfehlers oder Datenverlustes wiederhergestellt werden können.
|
||||
|
||||
Ein weiterer wichtiger Aspekt der technischen Zielsetzung war die Einhaltung rechtlicher Anforderungen im Bereich Datenschutz. Daher wurde eine Datenschutzerklärung erstellt und die Plattform so konzipiert, dass personenbezogene Daten der Benutzer entsprechend den Vorgaben der Datenschutz-Grundverordnung verarbeitet werden.
|
||||
### Funktionale Zielsetzung
|
||||
|
||||
##### Funktionale Zielsetzung
|
||||
|
||||
Die funktionale Zielsetzung des Projekts bestand darin, eine Onlineplattform zu entwickeln, die ehemaligen Schülerinnen und Schülern die Möglichkeit bietet, miteinander in Kontakt zu bleiben und sich über aktuelle Veranstaltungen zu informieren. Die Plattform dient somit als digitales Netzwerk für Alumni und soll den Austausch zwischen ehemaligen Mitgliedern der Bildungseinrichtung fördern.
|
||||
|
||||
@@ -126,7 +62,8 @@ Darüber hinaus bietet die Plattform Funktionen zur Darstellung und Verwaltung v
|
||||
Zusätzlich wurde ein Premiumbereich entwickelt, der bestimmte Inhalte oder Funktionen exklusiv für berechtigte Benutzer bereitstellt. Dieser Bereich erweitert die Möglichkeiten der Plattform und ermöglicht es, spezielle Inhalte nur bestimmten Benutzergruppen zugänglich zu machen.
|
||||
|
||||
Durch diese Funktionen entsteht eine Plattform, die nicht nur als Informationsquelle dient, sondern auch aktiv zur Vernetzung der Alumni beiträgt.
|
||||
### Gestalterische Zielsetzung
|
||||
|
||||
##### Gestalterische Zielsetzung
|
||||
|
||||
Neben den technischen und funktionalen Anforderungen spielte auch die Gestaltung der Plattform eine wichtige Rolle. Ziel war es, eine übersichtliche und benutzerfreundliche Oberfläche zu entwickeln, die eine einfache Navigation und intuitive Bedienung ermöglicht.
|
||||
|
||||
@@ -136,9 +73,67 @@ Darüber hinaus wurde darauf geachtet, dass die Plattform ein modernes Erscheinu
|
||||
|
||||
Insgesamt verfolgt die gestalterische Zielsetzung das Ziel, eine Plattform zu schaffen, die sowohl funktional als auch optisch ansprechend ist und den Benutzern eine positive Nutzungserfahrung bietet.
|
||||
|
||||
# 4. Datenschutz und rechtliche Grundlagen
|
||||
## Anforderungen an das entwickelte Modul bzw. die Funktionalität
|
||||
|
||||
funktionale / nicht‑funktionale Anforderungen
|
||||
|
||||
Use Cases
|
||||
|
||||
## Technologien
|
||||
|
||||
### ASP.NET und .NET
|
||||
|
||||
Für die Entwicklung der Webanwendung wurde das Framework ASP.NET in Kombination mit der Plattform .NET verwendet. .NET ist eine von Microsoft entwickelte Entwicklungsplattform zur Erstellung moderner Anwendungen für Web, Desktop, Cloud und mobile Systeme.
|
||||
|
||||
ASP.NET stellt innerhalb der .NET-Plattform ein Framework dar, das speziell für die Entwicklung von Webanwendungen und Webservices konzipiert wurde. Es bietet eine Vielzahl an Funktionen zur Verarbeitung von HTTP-Anfragen, zur Verwaltung von Benutzersitzungen sowie zur Integration von Datenbanken und externen Diensten.
|
||||
|
||||
Die Anwendung basiert auf .NET 8, einer aktuellen Version der Plattform, welche insbesondere Verbesserungen in den Bereichen Performance, Sicherheit und Skalierbarkeit bietet. Durch die Nutzung dieser Plattform ist es möglich, stabile und performante Webanwendungen zu entwickeln, die auch bei steigender Benutzeranzahl zuverlässig funktionieren.
|
||||
|
||||
Ein wesentliches Konzept von ASP.NET ist die serverseitige Verarbeitung von Webanfragen. Wenn ein Benutzer eine Seite im Browser aufruft, wird zunächst eine Anfrage an den Webserver gesendet. Der Server verarbeitet diese Anfrage innerhalb der ASP.NET-Anwendung und sendet anschließend eine generierte Antwort zurück an den Browser.
|
||||
|
||||
Durch diese Architektur wird sichergestellt, dass die eigentliche Logik der Anwendung auf dem Server ausgeführt wird, während der Browser lediglich für die Darstellung der Benutzeroberfläche zuständig ist.
|
||||
|
||||
Ein weiterer Vorteil von ASP.NET besteht in der klar strukturierten Architektur moderner Webanwendungen. Typischerweise wird eine Anwendung in mehrere Schichten unterteilt, beispielsweise:
|
||||
|
||||
• Präsentationsschicht (Benutzeroberfläche)
|
||||
• Geschäftslogik
|
||||
• Datenzugriffsschicht
|
||||
|
||||
Diese Struktur erleichtert die Wartung und Erweiterung der Anwendung erheblich, da Änderungen gezielt innerhalb einzelner Komponenten vorgenommen werden können.
|
||||
|
||||
### PostgreSQL-Datenbank
|
||||
|
||||
Zur Speicherung der Daten der Plattform wurde das relationale Datenbanksystem PostgreSQL eingesetzt. PostgreSQL gehört zu den leistungsfähigsten Open-Source-Datenbanken und wird weltweit in vielen professionellen Anwendungen verwendet.
|
||||
|
||||
Eine relationale Datenbank speichert Informationen in Tabellen, die aus Zeilen und Spalten bestehen. Jede Tabelle repräsentiert eine bestimmte Art von Daten, beispielsweise Benutzer, Veranstaltungen oder Systeminformationen. Beziehungen zwischen Tabellen werden durch sogenannte Schlüssel definiert.
|
||||
|
||||
Durch diese Struktur kann beispielsweise gespeichert werden:
|
||||
|
||||
• welcher Benutzer existiert
|
||||
• welche Veranstaltungen geplant sind
|
||||
• welcher Benutzer sich für welches Event angemeldet hat
|
||||
|
||||
PostgreSQL bietet darüber hinaus zahlreiche Funktionen, die für moderne Webanwendungen wichtig sind. Dazu gehören unter anderem:
|
||||
|
||||
• hohe Stabilität und Zuverlässigkeit
|
||||
• Unterstützung komplexer Datenabfragen mittels SQL
|
||||
• Transaktionssicherheit zur Vermeidung von Datenverlust
|
||||
• Erweiterbarkeit durch zusätzliche Module
|
||||
|
||||
Ein weiterer wichtiger Vorteil besteht darin, dass PostgreSQL sehr gut mit modernen Webframeworks wie ASP.NET zusammenarbeitet. Dadurch kann die Anwendung effizient auf gespeicherte Daten zugreifen und diese verarbeiten.
|
||||
|
||||
Da die Plattform personenbezogene Daten von Benutzern speichert, wurde zusätzlich ein Backup-System implementiert, das regelmäßige Sicherungen der Datenbank erstellt. Dadurch können Daten im Falle eines Fehlers oder eines Systemausfalls wiederhergestellt werden.
|
||||
|
||||
### OAuth 2.0
|
||||
|
||||
Für die sichere Anmeldung über externe Identitätsanbieter wurde das Protokoll OAuth 2.0 implementiert. OAuth 2.0 ist ein branchenweiter Standard für die Autorisierung, der es Benutzern ermöglicht, Anwendungen von Drittanbietern Zugriff auf ihre Daten zu gewähren, ohne ihre Passwörter für das jeweilige Portal preiszugeben. Im Rahmen dieses Projekts wurde OAuth 2.0 als Authentifizierungsverfahren mit LinkedIn gewählt, um den Registrierungsprozess der Benutzer signifikant zu vereinfachen, da bestehende LinkedIn-Accounts genutzt werden können. Gleichzeitig wird dadurch ein hohes Sicherheitsniveau sichergestellt.
|
||||
|
||||
## Module
|
||||
|
||||
### Datenschutz und rechtliche Grundlagen
|
||||
|
||||
#### Bedeutung der Datenschutz-Grundverordnung (DSGVO) [@dsgvo]
|
||||
|
||||
## 4.1 Bedeutung der Datenschutz-Grundverordnung (DSGVO) [^1]
|
||||
Die Datenschutz-Grundverordnung (DSGVO) ist eine rechtsverbindliche Verordnung der Europäischen Union, die seit dem 25. Mai 2018 unmittelbar in allen EU-Mitgliedstaaten gilt und den Schutz personenbezogener Daten regelt. Sie wurde erlassen, um die Rechte natürlicher Personen bei der Verarbeitung ihrer Daten zu stärken und ein einheitliches Datenschutzniveau innerhalb der EU zu gewährleisten.
|
||||
|
||||
Der Anwendungsbereich der DSGVO umfasst alle Verarbeitungen personenbezogener Daten, unabhängig davon, ob diese automatisiert oder in nicht-automatisierten Akten erfolgt. Personenbezogene Daten sind dabei definiert als alle Informationen, die sich auf eine identifizierte oder identifizierbare natürliche Person beziehen.
|
||||
@@ -149,7 +144,7 @@ Die DSGVO verfolgt mehrere grundsätzliche Ziele:
|
||||
• Einheitlicher Datenschutz in der EU, sodass sowohl Unternehmen innerhalb als auch außerhalb der EU, die Daten von EU-Bürgern verarbeiten, denselben Regeln unterliegen.
|
||||
• Schaffung klarer Pflichten für Datenverantwortliche und -verarbeiter zur rechtskonformen Datenverarbeitung.
|
||||
|
||||
Die DSGVO enthält dabei zentral die Grundsätze der Datenverarbeitung (§ 5 DSGVO), die sicherstellen, dass personenbezogene Daten nur rechtmäßig, zweckgebunden und in transparentem Umfang verarbeitet werden. Dazu gehören unter anderem:
|
||||
Die DSGVO enthält dabei zentral die Grundsätze der Datenverarbeitung (Art. 5 DSGVO), die sicherstellen, dass personenbezogene Daten nur rechtmäßig, zweckgebunden und in transparentem Umfang verarbeitet werden. Dazu gehören unter anderem:
|
||||
|
||||
• Rechtmäßigkeit, Fairness und Transparenz der Verarbeitung
|
||||
• Zweckbindung der Datenerhebung
|
||||
@@ -158,7 +153,8 @@ Die DSGVO enthält dabei zentral die Grundsätze der Datenverarbeitung (§ 5 DSG
|
||||
|
||||
Die Einhaltung dieser Prinzipien ist für jede Organisation verpflichtend, die personenbezogene Daten verarbeitet – unabhängig von ihrer Größe oder Branche. Dies umfasst sowohl technische Aspekte der Datenverarbeitung als auch die Informationspflichten gegenüber Betroffenen, wie sie etwa in Datenschutzerklärungen umgesetzt werden müssen.
|
||||
|
||||
## 4.2 Umsetzung der Datenschutzanforderungen auf der Website [^2]
|
||||
#### Umsetzung der Datenschutzanforderungen auf der Website [@ris]
|
||||
|
||||
Im Rahmen der Diplomarbeit wurde eine umfassende Analyse der Datenschutzanforderungen durchgeführt und entsprechende Maßnahmen zur Umsetzung auf der Website implementiert. Dabei wurden insbesondere die Vorgaben der DSGVO berücksichtigt, um sicherzustellen, dass die Website den geltenden Datenschutzbestimmungen entspricht.
|
||||
|
||||
Ein zentrales Element der Umsetzung war die Erstellung einer gesetzeskonformen Datenschutzerklärung. Diese informiert die Nutzerinnen und Nutzer über:
|
||||
@@ -179,14 +175,15 @@ Neben der Informationspflicht wurden auch technische und organisatorische Maßna
|
||||
• Regelmäßige Sicherheitsupdates der eingesetzten Software
|
||||
• Minimierung der Datenerhebung auf das Notwendige: Es werden nur die Daten erhoben, die für die Funktion der Website unbedingt erforderlich sind. (Grundprinzip der Datenminimierung nach Art. 5 Abs. 1 lit. c DSGVO)
|
||||
|
||||
## 4.3 Schutz personenbezogener Daten der Benutzer [^3]
|
||||
#### Schutz personenbezogener Daten der Benutzer [@dsb]
|
||||
|
||||
Der Schutz personenbezogener Daten der Benutzer stellt einen zentralen Bestandteil der Website des Absolventenvereins dar. Sämtliche Datenverarbeitungen erfolgen unter Berücksichtigung der Vorgaben der Datenschutz-Grundverordnung (DSGVO) sowie des österreichischen Datenschutzgesetzes (DSG).
|
||||
|
||||
Gemäß Art. 5 Abs. 1 lit. c DSGVO gilt der Grundsatz der Datenminimierung, wonach nur jene personenbezogenen Daten erhoben werden dürfen, die für den jeweiligen Zweck erforderlich sind. Auf der Website werden daher ausschließlich solche Daten verarbeitet, die für die Verwaltung der Mitgliedschaft, die Organisation von Veranstaltungen sowie die Kommunikation mit Absolventinnen und Absolventen notwendig sind.
|
||||
|
||||
Darüber hinaus wird der Grundsatz der Zweckbindung gemäß Art. 5 Abs. 1 lit. b DSGVO eingehalten. Die erhobenen Daten werden ausschließlich für die in der Datenschutzerklärung definierten Zwecke verarbeitet, insbesondere zur Organisation der Vereinstätigkeit und zur Durchführung von Absolvententreffen.
|
||||
|
||||
Eine darüberhinausgehende Weitergabe an Dritte erfolgt grundsätzlich nicht, außer wenn dies zur Erfüllung der Vereinszwecke erforderlich ist oder eine gesetzliche Verpflichtung besteht.
|
||||
Eine darüber hinausgehende Weitergabe an Dritte erfolgt grundsätzlich nicht, außer wenn dies zur Erfüllung der Vereinszwecke erforderlich ist oder eine gesetzliche Verpflichtung besteht.
|
||||
|
||||
Zur Gewährleistung der Integrität und Vertraulichkeit gemäß Art. 5 Abs. 1 lit. f DSGVO werden geeignete technische und organisatorische Maßnahmen eingesetzt. Dazu zählen insbesondere der Betrieb der Website auf einem Server innerhalb Österreichs, der Abschluss von Auftragsverarbeitungsverträgen gemäß Art. 28 DSGVO mit externen Dienstleistern sowie Maßnahmen zum Schutz vor unbefugtem Zugriff, Verlust oder Missbrauch von Daten.
|
||||
|
||||
@@ -196,32 +193,71 @@ 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.
|
||||
|
||||
# 5. Automatisierung von Serverprozessen
|
||||
### Backup & Restore
|
||||
|
||||
## 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.
|
||||
#### Notwendigkeit von Datensicherung [@bsi_datensicherung][@bsi_grundschutz]
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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 unwiederbringlich verloren gehen oder nur mit erheblichen Kosten wiederhergestellt werden können.
|
||||
|
||||
## 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.
|
||||
Neben der technischen besteht ebenfalls 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.
|
||||
|
||||
#### Konzeption des Backup-Systems
|
||||
|
||||
Ziel des Backup-Systems war es im Falle eines Datenverlustes oder eines Systemausfalls die Daten wiederherstellen zu können. Es wurde eine Strategie entwickelt 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
|
||||
|
||||
1. Die PostgreSQL-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
|
||||
|
||||
Damit wird eine vollständige Sicherung der Website ermöglicht, da bei einer reinen Datenbanksicherung wichtige Anwendungsdateien fehlen würden.
|
||||
|
||||
```mermaid
|
||||
%%| filename: flowchart-backup-system
|
||||
%%| fig-cap: Ablaufdiagramm des Backup-Systems
|
||||
graph TD
|
||||
%% Styling
|
||||
classDef startEnd fill:#f9f,stroke:#333,stroke-width:2px;
|
||||
classDef process fill:#d1e8ff,stroke:#0056b3,stroke-width:1px;
|
||||
classDef critical fill:#ffcccb,stroke:#b30000,stroke-width:1px;
|
||||
|
||||
Start((Start: Cronjob 02:30)) --> Init[Initialisierung: set -euo pipefail]
|
||||
Init --> Vars[Definiere Pfade & PG-Credentials]
|
||||
|
||||
subgraph Sicherungsphase
|
||||
Dir[Erstelle Verzeichnis: /var/backups/UTC_TS] --> DB[pg_dump -Fc: Custom Format Dump]
|
||||
DB --> Files[tar -cvpzf: Komprimierung oqtane.server]
|
||||
end
|
||||
|
||||
Files --> Check{Fehler aufgetreten?}
|
||||
Check -- Ja --> Mail[Log Error & Abbruch]
|
||||
Check -- Nein --> Rotate[Lade Liste der Backup-Ordner]
|
||||
|
||||
subgraph Speicherverwaltung [Retention Management]
|
||||
Rotate --> Count{Anzahl > 30?}
|
||||
Count -- Ja --> Delete[rm -rf: Lösche älteste Ordner]
|
||||
Count -- Nein --> Finish[Schließe Log-Eintrag ab]
|
||||
Delete --> Finish
|
||||
end
|
||||
|
||||
Finish --> Ende((Ende))
|
||||
|
||||
%% Zuweisung Klassen
|
||||
class Start,Ende startEnd;
|
||||
class Init,Vars,Dir,DB,Files,Rotate,Delete,Finish process;
|
||||
class Mail critical;
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
```bash
|
||||
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.
|
||||
date -u erzeugt einen Zeitstempel in UTC, wodurch die Benennung 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:
|
||||
@@ -278,13 +314,14 @@ Durch diesen Rotationsmechanismus wird verhindert, dass sich im Laufe der Zeit e
|
||||
|
||||
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
|
||||
#### 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:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
##!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
```
|
||||
|
||||
@@ -335,14 +372,15 @@ Die Implementierung zeichnet sich durch folgende Eigenschaften aus:
|
||||
• 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
|
||||
|
||||
#### 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 (Restore-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:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
##!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
```
|
||||
|
||||
@@ -408,7 +446,7 @@ Der Parameter -x extrahiert das Archiv, -p stellt die ursprünglichen Dateiberec
|
||||
|
||||
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
|
||||
#### 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.
|
||||
|
||||
@@ -418,26 +456,29 @@ Neben der Datensicherung wurde auch ein Restore-Skript implementiert, das eine v
|
||||
|
||||
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.1 Authentifizierung mittels OAuth 2.0
|
||||
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.
|
||||
### Benutzerverwaltung und Authentifizierung per LinkedIn
|
||||
|
||||
#### Authentifizierung mittels OAuth 2.0
|
||||
|
||||
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 [@rfc6749]. Dieses Verfahren ermöglicht es, Benutzer über externe Identitätsanbieter zu authentifizieren, ohne dass deren Zugangsdaten direkt an die Webanwendung übertragen werden müssen.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||

|
||||
*Abbildung: Ablauf der OAuth-Authentifizierung (Erstellt mit ChatGPT)*
|
||||
|
||||
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.
|
||||

|
||||
|
||||
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.
|
||||
Die Abbildung zeigt den Ablauf der OAuth-Authentifizierung. Zunächst erfolgt die Weiterleitung zur LinkedIn-Anmeldeseite, anschließend wird ein Autorisierungscode (Authorization Code) an die Webanwendung zurückgegeben, welcher gegen einen Zugriffstoken (Access Token) ausgetauscht wird.
|
||||
|
||||
Nach der Autorisierung sendet LinkedIn einen solchen Autorisierungscode an die Webanwendung zurück. Dieser Code wird anschließend vom Server der Anwendung gegen ein Zugriffstoken ausgetauscht. Mit Hilfe dieses Tokens kann die Webanwendung anschließend die freigegebenen Benutzerdaten vom LinkedIn-Server abrufen.
|
||||
|
||||
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.
|
||||
#### Technische Umsetzung der LinkedIn-Anmeldung in Oqtane
|
||||
|
||||
Für die Verbindung mit LinkedIn wurde zunächst eine Entwickleranwendung im LinkedIn Developer Portal erstellt. Dabei werden zwei zentrale Zugangsdaten generiert:
|
||||
Die technische Umsetzung der LinkedIn-Authentifizierung erfolgte über das integrierte External Login System [@oqtane_about] des Content-Management-Systems Oqtane. Dieses System ermöglicht die Integration externer Identitätsanbieter über standardisierte Protokolle wie OAuth 2.0.
|
||||
|
||||
Für die Verbindung mit LinkedIn wurde zunächst eine Entwickleranwendung im LinkedIn Developer Portal [@linkedin_auth_flow] erstellt. Dabei werden zwei zentrale Zugangsdaten generiert:
|
||||
|
||||
• Client ID
|
||||
• Client Secret
|
||||
@@ -454,7 +495,7 @@ Nach erfolgreicher Anmeldung stellt LinkedIn über folgenden Endpunkt ein Zugrif
|
||||
|
||||
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.
|
||||
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:
|
||||
|
||||
@@ -465,25 +506,56 @@ In der Konfiguration wurde außerdem festgelegt, welche Benutzerdaten von Linked
|
||||
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
|
||||
|
||||
#### 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.
|
||||
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 ein Zugriffstoken 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.
|
||||
Wie in Abschnitt 4.3.1 beschrieben, werden die abgerufenen Profildaten zur Identifikation oder Neuanlage des Benutzerkontos in der lokalen Datenbank verwendet.
|
||||
|
||||
### Implementierung des Premium-Bereichs
|
||||
|
||||
#### Ziel und Zweck des Premium-Bereichs
|
||||
|
||||
# 7. Implementierung des Premium-Bereichs
|
||||
## 7.1 Ziel und Zweck des Premium-Bereichs
|
||||
Der Premium-Bereich wurde mit dem Ziel entwickelt, den Mitgliedern des SZU Absolventenvereins einen klar abgegrenzten, exklusiven Bereich innerhalb der Vereinswebsite bereitzustellen. Das Premium-Bereich-Modul ist eine Erweiterung für das Content-Management-System Oqtane und bildet das Kernstück des digitalen Mitgliederbereichs. Es ermöglicht Mitgliedern, sich über das Hochladen eines Ingenieur-Antrags als PDF-Dokument für eine zeitlich begrenzte Premium-Mitgliedschaft zu qualifizieren. Premium-Mitglieder erhalten Zugang zu exklusiven Funktionen wie dem Einsehen genehmigter Anträge anderer Mitglieder sowie einer Mitgliedersuche mit integrierter Kontaktfunktion.
|
||||
|
||||
Der zentrale Zweck besteht darin, einen Anreiz für aktives Engagement im Verein zu schaffen. Dadurch entsteht ein Kreislauf, in dem qualitativ hochwertige Anträge die Wissensbasis der Gemeinschaft bereichern und gleichzeitig den einreichenden Mitgliedern einen konkreten Gegenwert in Form von Premium-Funktionen bieten. Der Premium-Bereich dient darüber hinaus als digitales Netzwerk-Instrument. Durch die Kombination aus Antrags-Einsicht, Mitgliedersuche und Kontaktfunktion wird eine Plattform geschaffen, die den fachlichen Austausch zwischen Absolventen fördert und die Vernetzung innerhalb der Gemeinschaft stärkt. Die zeitliche Begrenzung der Premium-Mitgliedschaft auf 12 Monate sorgt dafür, dass Mitglieder regelmäßig aktiv bleiben und neue Inhalte beitragen, um ihren Premium-Status aufrechtzuerhalten.
|
||||
|
||||
Die Entwicklung erfolgte als eigenständiges Oqtane-Modul, wodurch eine nahtlose Integration in die bestehende Vereinswebsite gewährleistet wird. Das Modul nutzt dabei die vom Framework bereitgestellten Mechanismen für Authentifizierung, Autorisierung, Datenbankzugriff und Dateiverwaltung. Die Architektur folgt etablierten Entwurfsmustern wie dem Repository-Pattern, Dependency Injection und dem Service-Layer-Muster. Das kumulative Premium-System mit vollständigem Audit-Trail stellt sicher, dass alle Statusänderungen transparent und nachvollziehbar sind. Die modulare Struktur erlaubt eine einfache Erweiterung um zusätzliche Funktionen, ohne die bestehende Codebasis grundlegend verändern zu müssen.
|
||||
|
||||
## 7.2 Funktionalität und Features des Premium-Bereichs
|
||||
```mermaid
|
||||
%%| filename: architecture-premium-module
|
||||
%%| fig-cap: Architektur des Premium-Bereich-Moduls
|
||||
graph TD
|
||||
subgraph Client [Client-Seite / WebAssembly]
|
||||
UI[Index.razor / Apply.razor] --> SvcC[IApplicationService]
|
||||
end
|
||||
|
||||
subgraph Server [ASP.NET Core Server]
|
||||
SvcC --> Ctrl[ApplicationController]
|
||||
Ctrl --> SvcS[IServerApplicationService]
|
||||
SvcS --> Prem[IPremiumService]
|
||||
SvcS --> Rep[IApplicationRepository]
|
||||
end
|
||||
|
||||
subgraph Data [Persistence Layer]
|
||||
Rep --> EF[EF Core Context]
|
||||
EF --> DB[(PostgreSQL)]
|
||||
Prem --> DB
|
||||
end
|
||||
|
||||
%% Styles
|
||||
style Client fill:#e1f5fe,stroke:#01579b
|
||||
style Server fill:#fff3e0,stroke:#e65100
|
||||
style Data fill:#f1f8e9,stroke:#33691e
|
||||
```
|
||||
|
||||
#### Funktionalität und Features des Premium-Bereichs
|
||||
|
||||
Der Premium-Bereich umfasst mehrere miteinander verknüpfte Funktionen, die gemeinsam ein geschlossenes System bilden.
|
||||
|
||||
**Ingenieur-Antrags-Workflow**
|
||||
@@ -506,11 +578,39 @@ public class EngineerApplication : ModelBase
|
||||
}
|
||||
```
|
||||
|
||||
Die Klasse erbt von `ModelBase`, wodurch automatisch die Audit-Felder `CreatedBy`, `CreatedOn`, `ModifiedBy` und `ModifiedOn` zur Verfügung stehen. Das Feld `Status` bildet den aktuellen Workflow-Schritt ab und kann die Werte „Draft", „Submitted", „Approved" oder „Rejected" annehmen. Die hochgeladene PDF-Datei wird nicht direkt gespeichert, sondern über eine `FileId` im Oqtane-Dateisystem referenziert.
|
||||
Die Klasse erbt von `ModelBase`, wodurch automatisch die Audit-Felder `CreatedBy`, `CreatedOn`, `ModifiedBy` und `ModifiedOn` zur Verfügung stehen. Das Feld `Status` bildet den aktuellen Workflow-Schritt ab und kann die Werte „Draft", „Submitted", „Approved" oder „Rejected" annehmen. Die hochgeladene PDF-Datei wird nicht direkt gespeichert, sondern über eine `FileId` im Oqtane-Dateisystem referenziert. Das nachfolgende Klassendiagramm veranschaulicht diese Struktur und zeigt, wie Basis-Metadaten durch die Vorfahrenklasse abgedeckt werden, während antragsbezogene Daten in der Hauptklasse gekapselt bleiben.
|
||||
|
||||
```mermaid
|
||||
%%| filename: classdiagram-engineer-application
|
||||
%%| fig-cap: Klassendiagramm der EngineerApplication-Entität
|
||||
classDiagram
|
||||
class EngineerApplication {
|
||||
+int ApplicationId
|
||||
+int UserId
|
||||
+int ModuleId
|
||||
+int FileId
|
||||
+string Title
|
||||
+string ShortDescription
|
||||
+string Status
|
||||
+DateTime? SubmittedOn
|
||||
+DateTime? ApprovedOn
|
||||
}
|
||||
|
||||
class ModelBase {
|
||||
+int CreatedBy
|
||||
+DateTime CreatedOn
|
||||
+int ModifiedBy
|
||||
+DateTime ModifiedOn
|
||||
}
|
||||
|
||||
EngineerApplication --|> ModelBase : inherits
|
||||
```
|
||||
|
||||
Der Lebenszyklus eines Antrags durchläuft mehrere Phasen. Der Benutzer beginnt auf der Antragsseite `Apply.razor`, wo ein Formular mit Feldern für Titel, Kurzbeschreibung und einer Dateiauswahl über den integrierten Oqtane FileManager bereitgestellt wird. Der FileManager stellt eine benutzerfreundliche Upload-Oberfläche mit Fortschrittsanzeige bereit und akzeptiert ausschließlich PDF-Dateien. Die hochgeladene Datei wird dabei im Oqtane-Dateisystem abgelegt und das Modul speichert lediglich die resultierende Datei-ID in der Antragstabelle.
|
||||
|
||||
Beim Absenden des Formulars erstellt die clientseitige Methode `SubmitApplication` ein neues `EngineerApplication`-Objekt und befüllt es mit den Formulardaten. Der Status wird dabei automatisch auf „Published" gesetzt, zusammen mit den Zeitstempeln für Einreichung und Genehmigung. Die Methode unterscheidet anhand der `ApplicationId`, ob ein neuer Antrag über den Service `AddApplicationAsync` erstellt oder ein bestehender über `UpdateApplicationAsync` aktualisiert werden soll. Falls keine Datei ausgewählt wurde, wird der Vorgang mit einer entsprechenden Fehlermeldung abgebrochen. Die Kommunikation mit dem Server erfolgt über den injizierten `ApplicationService`, der als HTTP-Client die Anfragen an den REST-Controller weiterleitet.
|
||||
Beim Absenden des Formulars erstellt die clientseitige Methode `SubmitApplication` ein neues `EngineerApplication`-Objekt und befüllt es mit den Formulardaten. Der Status wird dabei automatisch auf „Published" gesetzt, zusammen mit den Zeitstempeln für Einreichung und Genehmigung. Die Methode unterscheidet anhand der `ApplicationId`, ob ein neuer Antrag über den Service `AddApplicationAsync` erstellt oder ein bestehender über `UpdateApplicationAsync` aktualisiert werden soll. Falls keine Datei ausgewählt wurde, wird der Vorgang mit einer entsprechenden Fehlermeldung abgebrochen. Die Kommunikation mit dem Server erfolgt über den injizierten `ApplicationService`, der als HTTP-Client die Anfragen an den REST-Controller weiterleitet. Wie die nachfolgende Abbildung zeigt, führt die klare Gestaltung der Benutzeroberfläche den Benutzer im Browser intuitiv durch den Einreichungsprozess und stellt auch den Bearbeitungsstatus des Antrags übersichtlich dar.
|
||||
|
||||

|
||||
|
||||
Serverseitig nimmt die Methode `AddApplicationAsync` im `ServerEngineerApplicationService` den Antrag entgegen. Dort wird zunächst geprüft, ob der Benutzer authentifiziert ist und die View-Berechtigung für das Modul besitzt. Die User-ID wird aus dem HTTP-Kontext extrahiert und dem Antrag zugewiesen, um sicherzustellen, dass kein Benutzer Anträge im Namen anderer erstellen kann. Anschließend wird der Antrag über das Repository in der Datenbank persistiert.
|
||||
|
||||
@@ -518,7 +618,9 @@ Ein Administrator kann den Antrag entweder genehmigen oder ablehnen. Bei einer G
|
||||
|
||||
**Anzeige genehmigter Anträge**
|
||||
|
||||
Die Antrags-Listenansicht bildet das zweite zentrale Feature. Premium-Mitglieder sehen alle genehmigten und veröffentlichten Anträge in einer übersichtlichen Kartenansicht. Jede Karte enthält den Titel, den Namen des Autors, die Kurzbeschreibung und das Veröffentlichungsdatum. Über integrierte Schaltflächen können die zugehörigen PDF-Dokumente direkt im Browser betrachtet oder heruntergeladen werden. Der PDF-Viewer wird als modales Fenster mit eingebettetem iFrame realisiert.
|
||||
Die Antrags-Listenansicht bildet das zweite zentrale Feature. Wie in der nachfolgenden Abbildung zu sehen ist, sehen Premium-Mitglieder alle genehmigten und veröffentlichten Anträge in einer benutzerfreundlichen Kartenansicht und können Titel, Autor sowie Kurzbeschreibung auf einen Blick erfassen. Jede Karte enthält das Veröffentlichungsdatum und integrierte Schaltflächen, über welche das zugehörige PDF-Dokument bei Bedarf direkt im Browser via eingebettetem iFrame betrachtet oder heruntergeladen werden kann.
|
||||
|
||||

|
||||
|
||||
Die serverseitige Methode `GetApplicationsAsync` steuert dabei die rollenbasierte Zugriffslogik und entscheidet, welche Anträge ein Benutzer sehen darf:
|
||||
|
||||
@@ -557,7 +659,9 @@ Administratoren erhalten alle Anträge zurück. Für andere Benutzer werden zun
|
||||
|
||||
**Mitgliedersuche und Kontaktfunktion**
|
||||
|
||||
Die Mitgliedersuche ermöglicht Premium-Mitgliedern das gezielte Auffinden anderer registrierter Benutzer. Die clientseitige Blazor-Komponente `UserSearch.razor` stellt ein Eingabefeld bereit, über das der Benutzer seinen Suchbegriff eingibt. Bei jeder Suche wird der eingegebene Text an den `ServerUserContactService` auf dem Server übermittelt, der die eigentliche Suchlogik implementiert.
|
||||
Die in der nachfolgenden Abbildung gezeigte Mitgliedersuche ermöglicht Premium-Mitgliedern das gezielte Auffinden anderer registrierter Benutzer. Die clientseitige Blazor-Komponente `UserSearch.razor` stellt ein Eingabefeld bereit, über das der Benutzer seinen Suchbegriff eingibt. Aus der dann resultierenden Listenansicht heraus ermöglicht eine Kontaktfunktion, Nachrichten sicher an die gefundenen Personen zu senden, ohne deren direkte E-Mail-Adresse offenzulegen. Bei jeder Suche wird der eingegebene Text an den `ServerUserContactService` auf dem Server übermittelt, der die eigentliche Suchlogik implementiert.
|
||||
|
||||

|
||||
|
||||
Die serverseitige Methode `SearchUsersAsync` prüft zunächst, ob der Suchbegriff mindestens drei Zeichen umfasst, um übermäßig breite Abfragen zu vermeiden, und ob der anfragende Benutzer authentifiziert ist. Anschließend durchsucht sie die Benutzerdatenbank sowohl nach Anzeigenamen als auch nach Benutzernamen, wobei die Suche groß-/kleinschreibungsunabhängig erfolgt. Die Ergebnisse werden auf maximal 20 Treffer begrenzt.
|
||||
|
||||
@@ -623,7 +727,8 @@ public class AddPremiumTables : MultiDatabaseMigration
|
||||
engAppBuilder.Drop();
|
||||
|
||||
var userPremBuilder = new UserPremiumEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
userPremBuilder.Drop();
|
||||
us
|
||||
erPremBuilder.Drop();
|
||||
|
||||
var premEventBuilder = new PremiumEventEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
premEventBuilder.Drop();
|
||||
@@ -633,7 +738,8 @@ public class AddPremiumTables : MultiDatabaseMigration
|
||||
|
||||
Insgesamt definieren vier Migrationen die Datenbankstruktur: Die erste erstellt die Basistabelle des Moduls, die zweite fügt die drei Tabellen für Ingenieur-Anträge, Premium-Status und Premium-Events hinzu, die dritte entfernt nicht mehr benötigte Spalten nach der Umstellung auf den Oqtane FileManager, und die vierte ergänzt die Felder für Titel und Kurzbeschreibung. Alle Migrationen werden bei der Modulinstallation automatisch ausgeführt.
|
||||
|
||||
## 7.3 Zugriffsbeschränkung und Benutzerrechte
|
||||
#### Zugriffsbeschränkung und Benutzerrechte
|
||||
|
||||
Der Zugriff auf den Premium-Bereich wird durch ein mehrstufiges Berechtigungssystem gesteuert, das sowohl auf Server- als auch auf Client-Seite durchgesetzt wird. Die verfügbaren Berechtigungen werden in der `ModuleInfo`-Klasse registriert:
|
||||
|
||||
```C#
|
||||
@@ -680,11 +786,37 @@ private bool IsUserPremium(System.Security.Claims.ClaimsPrincipal user)
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
%%| filename: flowchart-premium-access
|
||||
%%| fig-cap: Zugriffsprüfung für Premium-Inhalte
|
||||
flowchart TD
|
||||
Req([Anfrage auf Premium-Inhalt]) --> Auth{User.Identity.IsAuthenticated?}
|
||||
|
||||
Auth -- Nein --> Guest[Login-Seite anzeigen]
|
||||
Auth -- Ja --> Admin{Ist Administrator?}
|
||||
|
||||
Admin -- Ja --> Grant[Zugriff gewährt]
|
||||
Admin -- Nein --> Role{In Rolle 'Premium Member'?}
|
||||
|
||||
Role -- Ja --> Grant
|
||||
Role -- Nein --> DB{Eintrag in SZUAbsolventenvereinUserPremium?}
|
||||
|
||||
DB -- Nein --> Deny[Premium-Upgrade-Seite anzeigen]
|
||||
DB -- Ja --> Valid{Ablaufdatum > Heute?}
|
||||
|
||||
Valid -- Ja --> Grant
|
||||
Valid -- Nein --> Deny
|
||||
|
||||
style Grant fill:#c8e6c9,stroke:#2e7d32
|
||||
style Deny fill:#ffcdd2,stroke:#c62828
|
||||
```
|
||||
|
||||
Zuerst wird die Oqtane-Rollenzugehörigkeit geprüft, anschließend der datenbankbasierte Premium-Status mit Ablaufdatum. Das kumulative System sorgt dafür, dass bei mehrfacher Vergabe die Laufzeiten addiert und nicht überschrieben werden — ein Mitglied, das während einer laufenden Premium-Mitgliedschaft einen weiteren Antrag genehmigt bekommt, erhält die zusätzlichen 12 Monate auf das bestehende Ablaufdatum aufgeschlagen.
|
||||
|
||||
Die Zugriffsbeschränkung wird konsequent auf beiden Seiten durchgesetzt: Clientseitig entscheidet die Blazor-Komponente anhand der Benutzerrolle, welche UI-Elemente angezeigt werden. Serverseitig prüft jeder Service-Aufruf die Berechtigung des anfragenden Benutzers über das `[Authorize]`-Attribut mit Policies wie `PolicyNames.ViewModule` und `PolicyNames.EditModule`, bevor Daten zurückgegeben oder Änderungen vorgenommen werden. Dadurch wird sichergestellt, dass selbst bei einer Manipulation der Client-Anfrage keine unberechtigten Zugriffe möglich sind.
|
||||
|
||||
## 7.4 Mehrwert für registrierte Mitglieder
|
||||
#### Mehrwert für registrierte Mitglieder
|
||||
|
||||
Der Premium-Bereich schafft einen konkreten Mehrwert für registrierte Mitglieder auf mehreren Ebenen. Auf fachlicher Ebene entsteht durch die gesammelten Ingenieur-Anträge eine wachsende Wissensbasis, die allen Premium-Mitgliedern zugänglich ist. Absolventen können von den Arbeiten anderer Mitglieder lernen, sich inspirieren lassen und fachliche Ansätze vergleichen. Durch die Möglichkeit, Titel und Kurzbeschreibungen zu hinterlegen, können Mitglieder gezielt nach relevanten Themen suchen, ohne jedes PDF-Dokument einzeln öffnen zu müssen.
|
||||
|
||||
Auf sozialer Ebene fördert der Premium-Bereich die Vernetzung innerhalb der Absolventengemeinschaft. Die Mitgliedersuche mit integrierter Kontaktfunktion ermöglicht es, gezielt nach ehemaligen Kommilitonen oder Fachkollegen zu suchen und direkt Kontakt aufzunehmen. Der duale Benachrichtigungsmechanismus — bestehend aus In-App-Nachricht und E-Mail — stellt sicher, dass Kontaktanfragen zuverlässig zugestellt werden und kein Mitglied eine Nachricht verpasst.
|
||||
@@ -693,51 +825,10 @@ Auf motivationaler Ebene bietet das zeitlich begrenzte Premium-System einen Anre
|
||||
|
||||
Der Datenschutz wird dabei konsequent gewahrt. Die Mitgliedersuche gibt nur ausgewählte, nicht-sensible Informationen wie Anzeigenamen und Benutzernamen zurück. E-Mail-Adressen und andere persönliche Daten werden serverseitig herausgefiltert und sind für andere Mitglieder nicht einsehbar. Die Kontaktaufnahme erfolgt ausschließlich über das plattformeigene Benachrichtigungssystem, sodass kein Mitglied seine persönlichen Kontaktdaten preisgeben muss.
|
||||
|
||||
# 8. Technologien
|
||||
## Learnings
|
||||
|
||||
## 8.1 ASP.NET und .NET
|
||||
Für die Entwicklung der Webanwendung wurde das Framework ASP.NET in Kombination mit der Plattform .NET verwendet. .NET ist eine von Microsoft entwickelte Entwicklungsplattform zur Erstellung moderner Anwendungen für Web, Desktop, Cloud und mobile Systeme.
|
||||
### Technische und fachliche Erkenntnisse
|
||||
|
||||
ASP.NET stellt innerhalb der .NET-Plattform ein Framework dar, das speziell für die Entwicklung von Webanwendungen und Webservices konzipiert wurde. Es bietet eine Vielzahl an Funktionen zur Verarbeitung von HTTP-Anfragen, zur Verwaltung von Benutzersitzungen sowie zur Integration von Datenbanken und externen Diensten.
|
||||
|
||||
Die Anwendung basiert auf .NET 8, einer aktuellen Version der Plattform, welche insbesondere Verbesserungen in den Bereichen Performance, Sicherheit und Skalierbarkeit bietet. Durch die Nutzung dieser Plattform ist es möglich, stabile und performante Webanwendungen zu entwickeln, die auch bei steigender Benutzeranzahl zuverlässig funktionieren.
|
||||
|
||||
Ein wesentliches Konzept von ASP.NET ist die serverseitige Verarbeitung von Webanfragen. Wenn ein Benutzer eine Seite im Browser aufruft, wird zunächst eine Anfrage an den Webserver gesendet. Der Server verarbeitet diese Anfrage innerhalb der ASP.NET-Anwendung und sendet anschließend eine generierte Antwort zurück an den Browser.
|
||||
|
||||
Durch diese Architektur wird sichergestellt, dass die eigentliche Logik der Anwendung auf dem Server ausgeführt wird, während der Browser lediglich für die Darstellung der Benutzeroberfläche zuständig ist.
|
||||
|
||||
Ein weiterer Vorteil von ASP.NET besteht in der klar strukturierten Architektur moderner Webanwendungen. Typischerweise wird eine Anwendung in mehrere Schichten unterteilt, beispielsweise:
|
||||
|
||||
• Präsentationsschicht (Benutzeroberfläche)
|
||||
• Geschäftslogik
|
||||
• Datenzugriffsschicht
|
||||
|
||||
Diese Struktur erleichtert die Wartung und Erweiterung der Anwendung erheblich, da Änderungen gezielt innerhalb einzelner Komponenten vorgenommen werden können.
|
||||
|
||||
## 8.2 PostgreSQL-Datenbank
|
||||
Zur Speicherung der Daten der Plattform wurde das relationale Datenbanksystem PostgreSQL eingesetzt. PostgreSQL gehört zu den leistungsfähigsten Open-Source-Datenbanken und wird weltweit in vielen professionellen Anwendungen verwendet.
|
||||
|
||||
Eine relationale Datenbank speichert Informationen in Tabellen, die aus Zeilen und Spalten bestehen. Jede Tabelle repräsentiert eine bestimmte Art von Daten, beispielsweise Benutzer, Veranstaltungen oder Systeminformationen. Beziehungen zwischen Tabellen werden durch sogenannte Schlüssel definiert.
|
||||
|
||||
Durch diese Struktur kann beispielsweise gespeichert werden:
|
||||
|
||||
• welcher Benutzer existiert
|
||||
• welche Veranstaltungen geplant sind
|
||||
• welcher Benutzer sich für welches Event angemeldet hat
|
||||
|
||||
PostgreSQL bietet darüber hinaus zahlreiche Funktionen, die für moderne Webanwendungen wichtig sind. Dazu gehören unter anderem:
|
||||
|
||||
• hohe Stabilität und Zuverlässigkeit
|
||||
• Unterstützung komplexer Datenabfragen mittels SQL
|
||||
• Transaktionssicherheit zur Vermeidung von Datenverlust
|
||||
• Erweiterbarkeit durch zusätzliche Module
|
||||
|
||||
Ein weiterer wichtiger Vorteil besteht darin, dass PostgreSQL sehr gut mit modernen Webframeworks wie ASP.NET zusammenarbeitet. Dadurch kann die Anwendung effizient auf gespeicherte Daten zugreifen und diese verarbeiten.
|
||||
|
||||
Da die Plattform personenbezogene Daten von Benutzern speichert, wurde zusätzlich ein Backup-System implementiert, das regelmäßige Sicherungen der Datenbank erstellt. Dadurch können Daten im Falle eines Fehlers oder eines Systemausfalls wiederhergestellt werden.
|
||||
|
||||
# 9. Learnings
|
||||
## 9.1 Technische und fachliche Erkenntnisse
|
||||
Während der Umsetzung meiner Diplomarbeit konnte ich umfangreiche technische Erfahrungen in der Entwicklung moderner Webanwendungen sammeln. Ein zentraler Bestandteil der Arbeit war die Implementierung einer Alumni-Plattform, die ehemaligen Studierenden eine Möglichkeit bietet, sich zu vernetzen, Profile zu verwalten und sich für Veranstaltungen anzumelden.
|
||||
|
||||
Ein wesentlicher Lernaspekt war die praktische Anwendung des Webframeworks ASP.NET auf Basis der Plattform .NET 8. Dabei konnte ich ein tieferes Verständnis für die Architektur moderner Webanwendungen entwickeln. Besonders wichtig war das Zusammenspiel zwischen Backend-Logik, Datenbankzugriff und Benutzeroberfläche. Durch die Entwicklung verschiedener Funktionen, wie beispielsweise Eventverwaltung, Benutzerprofile und Premiumbereiche, wurde deutlich, wie wichtig eine klare Strukturierung der Anwendung ist.
|
||||
@@ -749,7 +840,9 @@ Ein weiterer wichtiger Bestandteil der Arbeit war die Integration eines externen
|
||||
Zusätzlich wurden Funktionen im Bereich Datenschutz umgesetzt, die den Anforderungen der DSGVO entsprechen. Dazu zählen unter anderem Möglichkeiten zur Verwaltung personenbezogener Daten sowie automatisierte Löschmechanismen. Diese Aspekte haben mir gezeigt, wie wichtig Datenschutz und rechtliche Anforderungen bei der Entwicklung moderner Webanwendungen sind.
|
||||
|
||||
Insgesamt konnte ich durch die praktische Umsetzung der Plattform ein deutlich tieferes Verständnis für Webtechnologien, Systemarchitekturen und Backend-Entwicklung gewinnen.
|
||||
## 9.2 Agile Projektarbeit und Teamarbeit (Zeitmanagement)
|
||||
|
||||
### Agile Projektarbeit und Teamarbeit (Zeitmanagement)
|
||||
|
||||
Neben den technischen Aspekten konnte ich auch wichtige Erfahrungen im Bereich der agilen Projektarbeit sammeln. Für die Organisation des Projekts wurde das agile Framework Scrum eingesetzt.
|
||||
|
||||
Die Arbeit wurde in Sprints von jeweils zwei Wochen unterteilt. Innerhalb eines Sprints wurden konkrete Aufgaben definiert, die bis zum Ende des Zeitraums umgesetzt werden sollten. Diese Struktur ermöglichte eine klare Planung der Arbeitsschritte und half dabei, größere Entwicklungsaufgaben in kleinere, überschaubare Teilaufgaben zu unterteilen.
|
||||
@@ -761,7 +854,9 @@ Auch das Zeitmanagement spielte eine zentrale Rolle. Da die Entwicklung der Plat
|
||||
Darüber hinaus zeigte sich, dass agile Methoden besonders gut für Softwareprojekte geeignet sind, da sie flexible Anpassungen während der Entwicklung ermöglichen. Neue Anforderungen oder Verbesserungen konnten relativ einfach in zukünftige Sprints integriert werden.
|
||||
|
||||
Diese Erfahrungen haben mir ein besseres Verständnis für moderne Softwareentwicklungsprozesse vermittelt und gezeigt, wie wichtig strukturierte Planung und kontinuierliche Verbesserung in technischen Projekten sind.
|
||||
## 9.3 Persönliche Weiterentwicklung
|
||||
|
||||
### Persönliche Weiterentwicklung
|
||||
|
||||
Neben den technischen und organisatorischen Erkenntnissen stellte die Diplomarbeit auch eine wichtige persönliche Weiterentwicklung dar. Die eigenständige Planung und Umsetzung eines komplexen Softwareprojekts erforderte ein hohes Maß an Selbstorganisation und Durchhaltevermögen.
|
||||
|
||||
Während der Entwicklung der Alumni-Plattform musste ich regelmäßig neue Technologien und Konzepte erlernen. Dazu gehörten unter anderem Webframeworks, Datenbanksysteme, Authentifizierungsprotokolle sowie Aspekte der IT-Sicherheit und des Datenschutzes. Der Umgang mit diesen Technologien hat meine Fähigkeiten im Bereich der Softwareentwicklung deutlich erweitert.
|
||||
@@ -771,9 +866,11 @@ Darüber hinaus lernte ich, technische Probleme systematisch zu analysieren und
|
||||
Ein weiterer wichtiger Lernaspekt war die Verbesserung meiner Fähigkeiten im Bereich der Dokumentation. Die Erstellung eines Diplomarbeitsbuchs erfordert eine strukturierte und verständliche Beschreibung technischer Inhalte. Dadurch konnte ich lernen, komplexe technische Zusammenhänge klar und nachvollziehbar darzustellen.
|
||||
|
||||
Zusammenfassend hat mir die Diplomarbeit nicht nur tiefere technische Kenntnisse vermittelt, sondern auch meine Fähigkeiten im Bereich Problemlösung, Selbstorganisation und Projektplanung gestärkt. Diese Kompetenzen stellen eine wichtige Grundlage für zukünftige berufliche Tätigkeiten im Bereich der Softwareentwicklung dar.
|
||||
# 10. Fazit und Ausblick
|
||||
|
||||
## 10.1 Zusammenfassung der Arbeit
|
||||
## Fazit und Ausblick
|
||||
|
||||
### Zusammenfassung der Arbeit
|
||||
|
||||
Ziel dieser Diplomarbeit war die Konzeption und Entwicklung einer webbasierten Alumni-Plattform für den Absolventenverein. Die Plattform soll ehemaligen Studierenden ermöglichen, miteinander in Kontakt zu bleiben, Informationen auszutauschen und an Veranstaltungen des Vereins teilzunehmen. Dadurch wird die Kommunikation zwischen Absolventinnen und Absolventen sowie der Bildungseinrichtung langfristig gestärkt.
|
||||
|
||||
Im Rahmen der Arbeit wurde eine moderne Webanwendung entwickelt, die auf dem Framework ASP.NET und der Plattform .NET 8 basiert. Die Speicherung der Daten erfolgt in der relationalen Datenbank PostgreSQL, welche eine strukturierte Verwaltung der Benutzerdaten, Eventinformationen und Systemdaten ermöglicht.
|
||||
@@ -785,7 +882,9 @@ Neben der Benutzerverwaltung wurden auch weitere Funktionen umgesetzt. Dazu zäh
|
||||
Die Entwicklung der Plattform erfolgte nach agilen Prinzipien mithilfe des Scrum-Frameworks. Die Arbeit wurde in mehrere zweiwöchige Sprints unterteilt, wodurch eine strukturierte und iterative Entwicklung der einzelnen Funktionen möglich war.
|
||||
|
||||
Zusammenfassend konnte mit der Diplomarbeit eine funktionale und erweiterbare Plattform entwickelt werden, die eine moderne Grundlage für die digitale Vernetzung von Alumni darstellt.
|
||||
## 10.2 Mögliche Erweiterungen der Website
|
||||
|
||||
### Mögliche Erweiterungen der Website
|
||||
|
||||
Obwohl bereits viele grundlegende Funktionen implementiert wurden, bietet die Plattform weiteres Potenzial für zukünftige Erweiterungen und Verbesserungen.
|
||||
|
||||
Eine mögliche Erweiterung wäre beispielsweise die Integration eines internen Nachrichtensystems. Dadurch könnten Alumni direkt über die Plattform miteinander kommunizieren und Kontakte einfacher pflegen. Ebenso wäre die Implementierung eines Forums oder einer Diskussionsplattform denkbar, in der Mitglieder Erfahrungen austauschen oder berufliche Themen diskutieren können.
|
||||
@@ -805,7 +904,9 @@ Integration weiterer Social-Media-Plattformen
|
||||
erweiterte Such- und Filterfunktionen für Alumni-Profile
|
||||
|
||||
Durch diese Erweiterungen könnte die Plattform langfristig noch attraktiver und vielseitiger gestaltet werden.
|
||||
## 10.3 Zukunftspotenzial für den Absolventenverein
|
||||
|
||||
### Zukunftspotenzial für den Absolventenverein
|
||||
|
||||
Die entwickelte Alumni-Plattform bietet dem Absolventenverein eine moderne digitale Infrastruktur zur Verwaltung und Vernetzung seiner Mitglieder. Durch die zentrale Plattform können Informationen, Veranstaltungen und Kontakte effizient organisiert werden.
|
||||
|
||||
Ein wesentlicher Vorteil besteht darin, dass der Verein seine Mitglieder dauerhaft an die Institution binden kann. Ehemalige Studierende haben die Möglichkeit, weiterhin mit ihrer Ausbildungsstätte und anderen Absolventinnen und Absolventen in Verbindung zu bleiben. Dies stärkt das Netzwerk des Vereins und kann langfristig auch neue Kooperationen oder berufliche Kontakte ermöglichen.
|
||||
@@ -815,14 +916,3 @@ Darüber hinaus kann die Plattform auch zur Organisation von Veranstaltungen gen
|
||||
Langfristig kann die Plattform zu einem zentralen digitalen Treffpunkt für Absolventinnen und Absolventen werden. Besonders im beruflichen Umfeld kann ein starkes Alumni-Netzwerk einen großen Mehrwert bieten, beispielsweise durch den Austausch von Erfahrungen oder beruflichen Möglichkeiten.
|
||||
|
||||
Insgesamt besitzt die entwickelte Plattform ein großes Zukunftspotenzial. Durch kontinuierliche Erweiterungen und die aktive Nutzung durch die Mitglieder kann sie zu einem wichtigen Instrument für die Vernetzung und Weiterentwicklung des Absolventenvereins werden.
|
||||
# 11. Quellenverzeichnis
|
||||
|
||||
[^1]: DSGVO – Datenschutz-Grundverordnung: https://eur-lex.europa.eu/eli/reg/2016/679/
|
||||
|
||||
[^2]: RIS – Rechtsinformationssystem des Bundes: https://www.ris.bka.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
|
||||
@@ -1,13 +1,12 @@
|
||||
---
|
||||
include_toc: true
|
||||
gitea: none
|
||||
---
|
||||
\cleardoublepage
|
||||
|
||||
## 1. Einleitung des individuellen Teils
|
||||
# Konstantin Hintermayer
|
||||
|
||||
## Einleitung des individuellen Teils
|
||||
|
||||
In diesem Abschnitt wird meine persönliche Aufgabenstellung im Rahmen des Projektes (`Alumnihub`) beschrieben.
|
||||
|
||||
### 1.1 Auftrag / persönliche Aufgabenstellungen
|
||||
### Auftrag / persönliche Aufgabenstellungen
|
||||
|
||||
Meine Zuständigkeiten und Verantwortlichkeiten:
|
||||
|
||||
@@ -17,28 +16,28 @@ Meine Zuständigkeiten und Verantwortlichkeiten:
|
||||
- Auswertungen
|
||||
- Schwarzes Brett
|
||||
|
||||
### 1.2 Motivation
|
||||
### Motivation
|
||||
|
||||
Gegenstand der Diplomarbeit ist die Entwicklung modularer Webanwendungen mit Blazor und Oqtane. Aufbauend auf fundierten Kenntnissen in der Fullstack-Entwicklung (React, Node.js, Golang), welche privat bei diversen Projekten gesammelt worden sind, fokussiert sich die Arbeit auf die Architekturvorteile des .NET-Stacks. Besonders im Fokus stehen die Konsistenz durch statische Typisierung sowie das Zusammenspiel modularer Komponenten in verteilten Systemen.
|
||||
|
||||
Mein Untersuchungsanliegen: Inwieweit optimiert die Integration von Blazor und dem Oqtane-Framework die Konsistenz und Wartbarkeit modularer Web-Architekturen im Vergleich zu den für mich gewohnten Technologie-Stacks (React/Node.js)?
|
||||
|
||||
## 2. Anforderungen an das entwickelte Modul bzw. die Funktionalität
|
||||
## Anforderungen an das entwickelte Modul bzw. die Funktionalität
|
||||
|
||||
### 2.1 Modulanforderungen / funktionale Anforderungen
|
||||
### Modulanforderungen / funktionale Anforderungen
|
||||
|
||||
### 2.2 Infrastrukturanforderungen / nicht‑funktionale Anforderungen
|
||||
### Infrastrukturanforderungen / nicht‑funktionale Anforderungen
|
||||
|
||||
- funktionale / nicht‑funktionale Anforderungen
|
||||
- Use Cases
|
||||
|
||||
## 3. Technisches Umfeld
|
||||
## Technisches Umfeld
|
||||
|
||||
Mein Aufgabenbereich umfasst einerseits die Entwicklung eigener Module, sowie das Bereitstellen des Services.
|
||||
|
||||
### 3.1 Auswahlverfahren
|
||||
### Auswahlverfahren
|
||||
|
||||
#### 3.1.1 Entscheidungsfindung CMS
|
||||
#### Entscheidungsfindung CMS
|
||||
|
||||
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 die Webentwicklung mit ASP.NET Core 9 (Upgrade im Lauf der Diplomarbeit auf .NET Core 10) und dem CMS Oqtane entschieden. Auch hier gab es einige Kandidaten:
|
||||
|
||||
@@ -53,7 +52,7 @@ Auch steht die Wahl der Programmiersprache und des CMS an. Nachdem wir im Unterr
|
||||
|
||||
Insbesondere aufgrund seiner sehr hohen Flexibilität, haben wir uns am Ende für Oqtane entschieden.
|
||||
|
||||
#### 3.1.2 Entscheidungsfindung restliche Infrastruktur
|
||||
#### Entscheidungsfindung restliche Infrastruktur
|
||||
|
||||
Als Betriebssystem habe ich mich für Linux entschieden, einfach, da ich mit Linux im Serverumfeld die meisten und besten Erfahrungen gemacht habe.
|
||||
|
||||
@@ -65,15 +64,15 @@ Im Bereich der Datenbanken musste ich mir ein paar Fragen stellen:
|
||||
|
||||
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 Datenbankmanagementsysteme in die Auswahl: PostgreSQL, MySQL / MariaDB, SQLite. Da ist die Wahl auf PostgreSQL gefallen. Grund dafür war meine Vorerfahrung mit diesem Datenbankmanagementsystem, welche ich in meinem Nebenjob erlangt habe.
|
||||
|
||||
### 3.2 Beschreibung und Architektur von Oqtane
|
||||
### Beschreibung und 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. [@oqtane_about] 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`. [@oqtane_docs_extensions]
|
||||
|
||||
Ein `Module` (Modul) soll neue Funktionalitäten in das CMS hinzufügen und ein `Theme` soll die ganze Gestaltung der Webseite (die Shell) festlegen. [^6]
|
||||
Ein `Module` (Modul) soll neue Funktionalitäten in das CMS hinzufügen und ein `Theme` soll die ganze Gestaltung der Webseite (die Shell) festlegen. [@oqtane_docs_extensions]
|
||||
|
||||
#### 3.2.1 Architektur eines Moduls
|
||||
#### Architektur eines Moduls
|
||||
|
||||
Ein Modul in Oqtane besteht aus vier Projekten:
|
||||
|
||||
@@ -87,7 +86,7 @@ Ein Modul in Oqtane besteht aus vier Projekten:
|
||||
- Beim Debug werden die DLLs, PDBs und statischen Assets wie Skripte und Stylesheets der drei 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-Pakete werden beim nächsten Start des Oqtane Servers installiert (Datenbank Migrationen werden gemacht und die Pakete entpackt).
|
||||
|
||||
### 3.3 Zusammenspiel der Infrastruktur
|
||||
### Zusammenspiel der Infrastruktur
|
||||
|
||||
In diesem Kapitel erkläre ich wie die ausgewählten Komponenten zusammenspielen. Wir verwenden NginX als Reverse Proxy, welcher bei uns die SSL Terminierung macht. Das Zertifikat, welches verwendet wird, wird von Let's Encrypt bereit gestellt und mittels HTTP-Challenges und dem Certbot auf dem Server aktualisiert und verwaltet. Oqtane selber läuft als Systemd-Service im Kestrel Backend. Kestrel ist ein kleiner Webserver, welcher in das ASP.NET Core Framework eingebaut worden ist. Oqtane (bzw. der ASP.NET Core Server "Kestrel") hört auf der Loopback IP und Port 5000. Damit ist Oqtane nur durch NginX erreichbar. PostgreSQL ist die Datenbank in dem System: Sie hört wieder auf der Loopback IP und Port 5432. In der folgenden Grafik ist das System Schematisch dargestellt.
|
||||
|
||||
@@ -114,11 +113,13 @@ Zusätzlich gab es einen Administrationszugang zu den Servern, welcher über SSH
|
||||
| Schule | Highport |
|
||||
| LiveDesign | IPSEC VPN |
|
||||
|
||||
Table: SSH Zugänge in den unterschiedlichen Umgebungen
|
||||
|
||||
Die VPN basierten Zugänge sind tendenziell schwieriger zu finden und auszunutzen, während die Lösung in der Schule mittels Highport den SSH Service öffentlich erreichbar macht. Durch den `Highport` ist der SSH Service schwieriger zu finden. Zur Authentifizierung mit SSH verwenden wir SSH-Keys, da diese durch ihre komplexität sicherer sind, als Passwörter.
|
||||
|
||||
### 3.4 Entwicklung mit ASP.NET
|
||||
### Entwicklung mit ASP.NET
|
||||
|
||||
#### 3.4.1 Blazor [^7]
|
||||
#### Blazor [@wikipedia_blazor]
|
||||
|
||||
Blazor ist ein kostenloses und quelloffenes Web-Framework, welches es möglich macht Benutzeroberflächen für Web-Browser, basierend auf C# und HTML, zu erstellen. Es wird von Microsoft als teil des ASP.NET Core Frameworks entwickelt.
|
||||
|
||||
@@ -161,26 +162,26 @@ Razor hat auch eine Reihe an Keywords, wie zum Beispiel (nur Auszugsweise, bzw.
|
||||
- foreach: Für Wiederholungen im Markup
|
||||
- if: Für Verzweigungen im Markup
|
||||
|
||||
#### 3.4.2 Kommunikation zwischen Front- und Backend
|
||||
#### Kommunikation zwischen Front- und Backend
|
||||
|
||||
Wie Front- und Backend miteinander interagieren hängt hauptsächlich vom Render-Modus ab. Oqtane kann auf verschiedene Arten betrieben werden. Hierbei ist es dem Modulentwickler ziemlich egal, welche Art der Kommunikation (WebSockets, HTTP Long-Polling, REST) verwendet wird. SignalR ist eine Library aus dem ASP.NET Framework, welche es möglich macht, Server zu Client Kommunikation zu betreiben. Oqtane verwendet SignalR im `Interactive Server (SignalR)` Render-Modus.
|
||||
|
||||
### 3.5 Dependency Injection
|
||||
### Dependency Injection
|
||||
|
||||
Dependency Injection ist ein Entwurfsmuster, bei dem die Abhängigkeiten eines Objekts nicht von diesem selbst erzeugt, sondern von außen „injiziert“ werden.
|
||||
|
||||
Wie der Software-Architekt Martin Fowler, der den Begriff im Jahr 2004 maßgeblich prägte, beschreibt, geht es im Kern darum, die Erzeugung von Objekten von deren Nutzung zu trennen [^9]. Anstatt dass eine Klasse ihre Hilfsobjekte mittels des new-Operators selbst instanziiert, werden ihr diese meist über den Konstruktor zur Verfügung gestellt.
|
||||
Wie der Software-Architekt Martin Fowler, der den Begriff im Jahr 2004 maßgeblich prägte, beschreibt, geht es im Kern darum, die Erzeugung von Objekten von deren Nutzung zu trennen [@fowler_dependency_injection]. Anstatt dass eine Klasse ihre Hilfsobjekte mittels des new-Operators selbst instanziiert, werden ihr diese meist über den Konstruktor zur Verfügung gestellt.
|
||||
|
||||
In den folgenden beiden Kapiteln wird das Dependency Inversion Principle und das Microsoft Dependency Injection Framework genauer vorgestellt.
|
||||
|
||||
#### 3.5.1 Dependency Inversion Principle [^1]
|
||||
#### Dependency Inversion Principle [@ms_dependency_inversion][@logrocket_dependency_inversion]
|
||||
|
||||
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.
|
||||
|
||||
- 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 Abstraktionsschicht implementiert werden. [^3]
|
||||
- Die High-Level-Module beschreiben die Applikations- / Businesslogik, ohne direkt mit den Low-Level-Modulen zu interagieren, sondern lediglich auf Abstraktionen. [@oodesign_dependency_inversion]
|
||||
- Die Abstraktionen sollen nicht von Implementierungsdetails abhängig sein, sondern die Low-Level-Implementierung sollen gemäß der Abstraktionsschicht implementiert werden. [@oodesign_dependency_inversion]
|
||||
|
||||
Ausgangslage ist eine Softwarearchitektur im Direct-Dependency-Graph-Modell.
|
||||
|
||||
@@ -201,21 +202,21 @@ architecture-beta
|
||||
service b(mdi:package-variant-closed)[Klasse B]
|
||||
service ib(mdi:car-clutch)[Interface B]
|
||||
|
||||
a:B --> T:ib
|
||||
a:R --> L:ib
|
||||
ib:R <-- L:b
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
#### 3.5.2 Microsoft Dependency Injection Framework
|
||||
#### Microsoft Dependency Injection Framework
|
||||
|
||||
Dependency Injection ist in .NET genau so wie Konfiguration, Protokollierung und das Optionsmuster ins Framework integriert. [^4]
|
||||
Dependency Injection ist in .NET genau so wie Konfiguration, Protokollierung und das Optionsmuster ins Framework integriert. [@ms_di_overview]
|
||||
|
||||
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`). [@ms_di_overview]
|
||||
|
||||
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 danach 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 danach bereitgestellt. [@ms_di_overview]
|
||||
|
||||
Hier ein Beispiel aus der Dokumentation von Microsoft: [^4]
|
||||
Hier ein Beispiel aus der Dokumentation von Microsoft: [@ms_di_overview]
|
||||
|
||||
```c#
|
||||
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
|
||||
@@ -268,10 +269,12 @@ architecture-beta
|
||||
service ib(mdi:car-clutch)[IMessageWriter]
|
||||
|
||||
a:R --> L:ib
|
||||
|
||||
ib:R <-- L:b
|
||||
|
||||
```
|
||||
|
||||
### 3.6 Continuous Integration
|
||||
### Continuous Integration
|
||||
|
||||
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.
|
||||
|
||||
@@ -291,17 +294,17 @@ Anwendungen von Gitea Actions bei dieser Diplomarbeit:
|
||||
- PM Repository:
|
||||
> Zum automatischen Überprüfen der Dokumente, unter anderem, mithilfe von KI, wie zum Beispiel Gemini.
|
||||
|
||||
### 3.7 Debian Paket
|
||||
### Debian Paket
|
||||
|
||||
TODO
|
||||
|
||||
## 4 Projektmanagement & Tools
|
||||
## Projektmanagement & Tools
|
||||
|
||||
### 4.1 Scrum
|
||||
### Scrum
|
||||
|
||||
### 4.2 YouTrack
|
||||
### YouTrack
|
||||
|
||||
### 4.3 Git
|
||||
### Git
|
||||
|
||||
Git fungierte im Rahmen dieser Diplomarbeit als zentrales Werkzeug zur Versionskontrolle und Koordination sämtlicher Entwicklungsschritte. Die wichtigste Komponente stellt dabei der Commit dar, welcher als eindeutiger Snapshot des Projektzustands dient und über eine spezifische Commit-SHA (Secure Hash Algorithm) referenziert werden kann. Um eine lückenlose Nachvollziehbarkeit zu gewährleisten, wurde für jeden abgeschlossenen Arbeitsschritt ein eigener Commit erstellt.
|
||||
|
||||
@@ -319,43 +322,43 @@ Sobald eine Änderung erfolgreich getestet wurde, wird sie durch einen Merge wie
|
||||
|
||||
Für die Sicherung und Zusammenarbeit wurde im Rahmen dieser Arbeit Remote-Repositories auf einer Gitea Instanz verwendet. Durch die Befehle push und pull wird der lokale Stand mit dem Server synchronisiert. Dies gewährleistet eine konsistente Datenbasis und dient gleichzeitig als kontinuierliches Backup des Projektfortschritts.
|
||||
|
||||
### 4.4 Gitea
|
||||
### Gitea
|
||||
|
||||
Als schlanke und selbst gehostete Open-Source-Alternative zu Plattformen wie GitHub oder GitLab wurde für die Verwaltung der Repositories Gitea eingesetzt. Hauptgrund für die Benutzung von Gitea war meine bereits aufgesetzte Instanz, welche schon mehrere Jahre in Verwendung war und mit der es im Team auch schon Erfahrungen gab. Gitea bietet neben der reinen Git-Verwaltung essenzielle Werkzeuge für den Software-Lebenszyklus, wie ein integriertes Issue-Tracking, Code-Reviews über Pull-Requests sowie eine Benutzerverwaltung, und unterstützt somit die strukturierte Umsetzung der Diplomarbeit im Team.[^12]
|
||||
Als schlanke und selbst gehostete Open-Source-Alternative zu Plattformen wie GitHub oder GitLab wurde für die Verwaltung der Repositories Gitea eingesetzt. Hauptgrund für die Benutzung von Gitea war meine bereits aufgesetzte Instanz, welche schon mehrere Jahre in Verwendung war und mit der es im Team auch schon Erfahrungen gab. Gitea bietet neben der reinen Git-Verwaltung essenzielle Werkzeuge für den Software-Lebenszyklus, wie ein integriertes Issue-Tracking, Code-Reviews über Pull-Requests sowie eine Benutzerverwaltung, und unterstützt somit die strukturierte Umsetzung der Diplomarbeit im Team.[@gitea_docs]
|
||||
|
||||
#### 4.4.1 Repositories
|
||||
#### Repositories
|
||||
|
||||
Ein Repository bildet den zentralen Speicherort für einen Projektteil. In Gitea wurden separate Repositories für die einzelnen Module und Themes, das Oqtane Framework, Skripte, die Dokumentation angelegt. Ein zusätzliches Repository bindet alle übrigen Quellcode-Repositories als Submodule ein, das macht die Einrichtung einer neuen Entwicklungsumgebung sehr kompfortabel. Dies ermöglichte eine saubere Trennung der verschiedenen Projektkomponenten. [^12]
|
||||
Ein Repository bildet den zentralen Speicherort für einen Projektteil. In Gitea wurden separate Repositories für die einzelnen Module und Themes, das Oqtane Framework, Skripte, die Dokumentation angelegt. Ein zusätzliches Repository bindet alle übrigen Quellcode-Repositories als Submodule ein, das macht die Einrichtung einer neuen Entwicklungsumgebung sehr kompfortabel. Dies ermöglichte eine saubere Trennung der verschiedenen Projektkomponenten. [@gitea_docs]
|
||||
|
||||
#### 4.4.2 Issues
|
||||
#### Issues
|
||||
|
||||
Zur Aufgabenplanung und Fehlerverfolgung wurde das integrierte Issue-System genutzt. Jede anstehende Aufgabe oder entdeckte Schwachstelle wurde als „Issue“ erfasst, einem Verantwortlichen zugewiesen und mit Labels (z. B. „Bug“, „Feature“ oder „Dokumentation“) versehen. Dies half dabei, den Überblick über den Projektfortschritt zu behalten und die Anforderungen aus dem Lastenheft strukturiert abzuarbeiten. [^12][^13]
|
||||
Zur Aufgabenplanung und Fehlerverfolgung wurde das integrierte Issue-System genutzt. Jede anstehende Aufgabe oder entdeckte Schwachstelle wurde als „Issue“ erfasst, einem Verantwortlichen zugewiesen und mit Labels (z. B. „Bug“, „Feature“ oder „Dokumentation“) versehen. Dies half dabei, den Überblick über den Projektfortschritt zu behalten und die Anforderungen aus dem Lastenheft strukturiert abzuarbeiten. [@gitea_docs][@gitea_issue_tracker]
|
||||
|
||||
#### 4.4.3 Pull Requests
|
||||
#### Pull Requests
|
||||
|
||||
Um die Qualität des Codes zu sichern, wurden Änderungen nicht direkt in den Hauptzweig eingespielt, sondern über Pull Requests eingereicht. Ein Teammitglied konnte so die Änderungen eines anderen sichten, kommentieren und bei Bedarf Korrekturen anfordern. Erst nach einer erfolgreichen Überprüfung wurde der Code in den main-Branch gemergt. [^12][^14]
|
||||
Um die Qualität des Codes zu sichern, wurden Änderungen nicht direkt in den Hauptzweig eingespielt, sondern über Pull Requests eingereicht. Ein Teammitglied konnte so die Änderungen eines anderen sichten, kommentieren und bei Bedarf Korrekturen anfordern. Erst nach einer erfolgreichen Überprüfung wurde der Code in den main-Branch gemergt. [@gitea_docs][@gitea_pull_requests]
|
||||
|
||||
#### 4.4.4 Actions
|
||||
#### Actions
|
||||
|
||||
Gitea Actions wurden eingesetzt, um CI/CD-Pipelines (Continuous Integration / Continuous Deployment) zu realisieren. Bei jedem Push oder Pull Request wurden automatisierte Skripte ausgeführt, die das Projekt bauten. Dies reduzierte manuelle Fehlerquellen erheblich. Außerdem konnten wir mithilfe von CI/CD den Release Prozess einmalig festlegen und automatisieren, ohne bei jedem Update manuell den selben Prozess wiederholt durchgehen zu müssen. Das APT-Package Projekt enthält die CI/CD Konfiguration für das bauen von Oqtane, der Module und Themes, sowie das verpacken in ein APT Paket und dem veröffentlichen aller Pakete als eingenes Gitea Release. [^12][^15]
|
||||
Gitea Actions wurden eingesetzt, um CI/CD-Pipelines (Continuous Integration / Continuous Deployment) zu realisieren. Bei jedem Push oder Pull Request wurden automatisierte Skripte ausgeführt, die das Projekt bauten. Dies reduzierte manuelle Fehlerquellen erheblich. Außerdem konnten wir mithilfe von CI/CD den Release Prozess einmalig festlegen und automatisieren, ohne bei jedem Update manuell den selben Prozess wiederholt durchgehen zu müssen. Das APT-Package Projekt enthält die CI/CD Konfiguration für das bauen von Oqtane, der Module und Themes, sowie das verpacken in ein APT Paket und dem veröffentlichen aller Pakete als eingenes Gitea Release. [@gitea_docs][@gitea_actions]
|
||||
|
||||
#### 4.4.5 Releases
|
||||
#### Releases
|
||||
|
||||
Über die Release-Funktion wurden wichtige Meilensteine der Diplomarbeit festgeschrieben. Hierbei wird ein spezifischer Git-Tag mit einer Versionsnummer versehen und die dazugehörigen Binärdateien, Pakete und Dokumente archiviert. So lässt sich jederzeit auf einen stabilen, abgabebereiten Stand des Projekts zugreifen. [^12]
|
||||
Über die Release-Funktion wurden wichtige Meilensteine der Diplomarbeit festgeschrieben. Hierbei wird ein spezifischer Git-Tag mit einer Versionsnummer versehen und die dazugehörigen Binärdateien, Pakete und Dokumente archiviert. So lässt sich jederzeit auf einen stabilen, abgabebereiten Stand des Projekts zugreifen. [@gitea_docs]
|
||||
|
||||
#### 4.4.6 Package Repositories
|
||||
#### Package Repositories
|
||||
|
||||
Gitea fungierte zusätzlich als Register für Pakete und Container-Images. Selbst erstellte Artefakte, wie das Debian Paket für die Bereitstellung der Anwendung, wurden direkt in der Gitea-Instanz versioniert gespeichert. Dadurch waren alle notwendigen Komponenten für das Deployment an einem zentralen Ort verfügbar und abrufbar. Gitea selbst unterstützt verschiedenste Pakettypen. Darunter fallen unteranderem NuGet- und Debianpakete. Für beide haben wir in dieser Arbeit verwendung gefunden. [^12][^16]
|
||||
Gitea fungierte zusätzlich als Register für Pakete und Container-Images. Selbst erstellte Artefakte, wie das Debian Paket für die Bereitstellung der Anwendung, wurden direkt in der Gitea-Instanz versioniert gespeichert. Dadurch waren alle notwendigen Komponenten für das Deployment an einem zentralen Ort verfügbar und abrufbar. Gitea selbst unterstützt verschiedenste Pakettypen. Darunter fallen unteranderem NuGet- und Debianpakete. Für beide haben wir in dieser Arbeit verwendung gefunden. [@gitea_docs][@gitea_packages]
|
||||
|
||||
### 4.5 Kommunikation
|
||||
### Kommunikation
|
||||
|
||||
## 5 Module
|
||||
## Module
|
||||
|
||||
### 5.1 Admin Modules
|
||||
### Admin Modules
|
||||
|
||||
Eine C#-Solution, welche einige Module, welche für den Admineinsatz geschrieben worden sind beinhaltet. Dieses Modul besteht aus 3 Teilmodulen. Einem Modul für den Versand von Rundmails, eines für die Einstellung von der Token Lebenszeit bei Tokens, welche per E-Mail verschickt werden und eines, welches das Reporting-System übernimmt.
|
||||
|
||||
#### 5.1.1 Mass Mailing
|
||||
#### Mass Mailing
|
||||
|
||||
Das Mass Mailer Modul ist eine administrative Erweiterung für den Alumnihub, die es dem Vorstand ermöglicht, personalisierte Rundschreiben an alle registrierten Mitglieder zu versenden. Da die Pflege der Mitgliederdaten direkt im CMS erfolgt, bietet dieses Modul eine nahtlose Integration ohne den Export von CSV-Listen in externe Newsletter-Tools.
|
||||
|
||||
@@ -365,20 +368,20 @@ Für den tatsächlichen Versand der E-Mails nutzen wir den Cloud-Dienst Brevo. D
|
||||
|
||||
`Batch-Processing`: Mails werden nicht sofort ("Fire and Forget") versendet, sondern in eine Versandwarteschlange geschrieben. Nachdem schon die Notifications Infrastruktur, welche sich auch um den Mail versand kümmert, ins Framework eingebaut worden ist, wird diese gleich zum `schedulen` unserer E-Mails genutzt. Immer 100 Mails alle 24 Stunden bis alle Ziele die Mails erhalten haben. Das Limit von 100 / Tag ist konservativ sehr niedrig angesetzt, damit Funktionen wie Passwort Reset Mails nicht (leicht) dadurch beeinflusst werden können.
|
||||
|
||||
#### 5.1.2 Token Lifetime
|
||||
#### Token Lifetime
|
||||
|
||||
Das Token Lifetime Modul wurde geschrieben, um die Token-Lebenszeit konfigurierbar zu machen. Notwendig war das, um die Passwort Reset Links im initialen Mail versand länger gültig sein zu lassen. Durch das `Batch Processing` war es möglich, dass eine Mail erst Tage nach erstellen des Links hinaus geschickt wird und bei einer Standard Ablaufdauer von 2 Tagen sind manche Links schon ungültig, bis sie den Mail Server erreichen. Ziel war es, die Änderung der Lebenszeit für Administratoren im User Interface im Admin Bereich möglich zu machen.
|
||||
|
||||
Technisch bedeutet das, dass die standardmäßig vorkonfigurierten `DataProtectionTokenProviderOptions` explizit konfiguriert werden müssen. [^10] Der ASP.NET Core `UserManager`, welcher das generieren der Tokens übernimmt, verwendet einen `DataProtectorTokenProvider` und dieser wiederum kann mithilfe der `DataProtectionTokenProviderOptions` konfiguriert werden.
|
||||
Technisch bedeutet das, dass die standardmäßig vorkonfigurierten `DataProtectionTokenProviderOptions` explizit konfiguriert werden müssen. [@andrewlock_token_lifetime] Der ASP.NET Core `UserManager`, welcher das generieren der Tokens übernimmt, verwendet einen `DataProtectorTokenProvider` und dieser wiederum kann mithilfe der `DataProtectionTokenProviderOptions` konfiguriert werden.
|
||||
|
||||
Es gibt 2 Möglichkeiten, wie man dieses Problem Lösen kann:
|
||||
|
||||
- `der Workaround`: Ein eigenes Modul, welches in seiner `Server/Startup.cs` die benötigten Werte korrekt setzt. Das hat den Vorteil, dass wir keine Änderungen im CMS selbst haben, sondern nur unsere eigene Erweiterung dafür schreiben. Andererseits könnte durch die undeterministische Ladereihenfolge eine `race-condition` auftreten. [^11] Darüber hinaus besteht kein gleichzeitiger Zugriff auf die appsettings.json und den IServiceProvider, in dem die Konfiguration gesetzt werden muss. => Nachdem wir in der Konfigurationsphase auch noch keinen Datenbankzugriff haben, können die Werte nicht aus der Datenbank geladen werden, sondern wir müssen auf eine Textdatei zurückgreifen.
|
||||
- `die saubere Lösung`: Eine Änderung im Kern von Oqtane. Also wird in unserem Fork von Oqtane die Konfigurationslogik für die Tokenlifetime implementiert. In diesem Fall könnte die Konfigurationslogik direkt in `OqtaneServiceCollectionExtensions.cs` hinzugefügt werden, da hier auch alle anderen Oqtane Spezifischen Konfigurationen gesetzt werden. Das hat den Vorteil, dass der Code aufgeräumter und sauberer ist und die `race-condition` verhindert werden kann. [^11] Der initiale Grund dagegen ist, dass wir ein weiteres Git Repository zum warten haben (den Fork vom Oqtane.Framework), welches jetzt nicht mehr mit Upstream Commit-gleich ist.
|
||||
- `der Workaround`: Ein eigenes Modul, welches in seiner `Server/Startup.cs` die benötigten Werte korrekt setzt. Das hat den Vorteil, dass wir keine Änderungen im CMS selbst haben, sondern nur unsere eigene Erweiterung dafür schreiben. Andererseits könnte durch die undeterministische Ladereihenfolge eine `race-condition` auftreten. [@race_conditions_pdf] Darüber hinaus besteht kein gleichzeitiger Zugriff auf die appsettings.json und den IServiceProvider, in dem die Konfiguration gesetzt werden muss. => Nachdem wir in der Konfigurationsphase auch noch keinen Datenbankzugriff haben, können die Werte nicht aus der Datenbank geladen werden, sondern wir müssen auf eine Textdatei zurückgreifen.
|
||||
- `die saubere Lösung`: Eine Änderung im Kern von Oqtane. Also wird in unserem Fork von Oqtane die Konfigurationslogik für die Tokenlifetime implementiert. In diesem Fall könnte die Konfigurationslogik direkt in `OqtaneServiceCollectionExtensions.cs` hinzugefügt werden, da hier auch alle anderen Oqtane Spezifischen Konfigurationen gesetzt werden. Das hat den Vorteil, dass der Code aufgeräumter und sauberer ist und die `race-condition` verhindert werden kann. [@race_conditions_pdf] Der initiale Grund dagegen ist, dass wir ein weiteres Git Repository zum warten haben (den Fork vom Oqtane.Framework), welches jetzt nicht mehr mit Upstream Commit-gleich ist.
|
||||
|
||||
`Der Workaround` ist die Möglichkeit für die wir uns entschieden haben, allerdings ist das nicht die schönste Lösung. Eine eventuell nachfolgende Diplomarbeit kann an dieser Stelle ansetzen und `die saubere Lösung` implementieren.
|
||||
|
||||
#### 5.1.3 Reporting System
|
||||
#### Reporting System
|
||||
|
||||
Eine weitere Anforderung der Diplomarbeit war es Einträge in Modulen wie der `Hall of Fame`, dem `Schwarzen Brett` und dem Premium Bereich (`Engineer Applications`) melden zu können. Am Anfang war es wichtig, dass jeder schnell vorankommt, allerdings haben wir die Kommunikation Teamintern ein wenig verschlafen und dadurch ein paar Funktionen doppelt geschrieben. Dadurch kam es zu Inkonsistenzen in der Verwendung der unterschiedlichen Reporting Systeme. Deswegen haben wir uns am Ende für eine globales Reporting System entschieden.
|
||||
|
||||
@@ -409,9 +412,12 @@ Damit DI funktioniert muss für den DI Consumer (`also das Modul, welches das Re
|
||||
Die Implementierung des IReportingComponents stellt nur eine Property (`ReportType`, welche den TypeName der Razor Komponente zurückliefert, damit Dynamic Component sie laden kann) und eine Methode (`ConstructParameterList`, welche das Parameter Dictionary erstellt. Nur zwecks Typensicherheit eingefügt) bereit. Mit dem Dynamic Component von Razor ist es möglich, per C# Code unterschiedliche Komponenten zu rendern und damit auch die per DI injizierte Klasse.
|
||||
|
||||
```razor
|
||||
@inject IReportUI ReportingComponent
|
||||
@inject IReportUI ReportUI
|
||||
|
||||
<DynamicComponent Type="@ReportingComponent.ReportType" Parameters="@ReportingComponent.ConstructParameterList(_item, RenderModeBoundary)"/>
|
||||
<DynamicComponent
|
||||
Type="@ReportUI.ReportType"
|
||||
Parameters="@ReportUI.ConstructParameterList(_item, RenderModeBoundary)"
|
||||
/>
|
||||
|
||||
@code {
|
||||
private IReportable _item;
|
||||
@@ -420,11 +426,11 @@ Die Implementierung des IReportingComponents stellt nur eine Property (`ReportTy
|
||||
|
||||
Die Bereitstellung des Moduls geschieht im `AdminModules` Modul.
|
||||
|
||||
### 5.2 Event Registration
|
||||
### Event Registration
|
||||
|
||||
Dieses Modul ermöglicht es Administratoren und Absolventen, Veranstaltungen zu erstellen, auf der Seite zu veröffentlichen und zu verwalten, während Mitglieder ihre Teilnahme direkt über das Portal bestätigen oder absagen können. Dieses Modul dient der organisatorischen Unterstützung von Absolvententreffen und anderen Vereinsaktivitäten. Dieses Modul wurde gemeinsam mit Adam Gaiswinkler geschrieben, wobei er sich um die Darstellung der Events im Frontend gekümmert hat, während ich mich der Infrastruktur im Hintergrund angenommen habe.
|
||||
|
||||
#### 5.2.1 Backend und Datenhaltung
|
||||
#### Backend und Datenhaltung
|
||||
|
||||
Die serverseitige Implementierung basiert auf dem Repository-Pattern des Oqtane-Frameworks. Hierbei kommen zwei zentrale Repositories zum Einsatz:
|
||||
|
||||
@@ -432,7 +438,46 @@ Das `EventRepository` verwaltet die Metadaten der Veranstaltungen wie Name, Besc
|
||||
Das `ResponseRepository` speichert die Rückmeldungen der Benutzer. Ein Eintrag verknüpft dabei die UserId mit der EventId und dem Status der Rückmeldung.
|
||||
Die Kommunikation zwischen dem Client und dem Server erfolgt über einen REST-API-Controller `EventRegistrationController`, der sicherstellt, dass nur autorisierte Benutzer Änderungen vornehmen oder detaillierte Statistiken einsehen können.
|
||||
|
||||
#### 5.2.2 Statistik und Visualisierung
|
||||
##### Entity Relationship Diagram
|
||||
|
||||
\
|
||||
|
||||
```mermaid
|
||||
%%| filename: erd-event-registration
|
||||
%%| fig-cap: ER Diagramm des Event Registration Moduls
|
||||
erDiagram
|
||||
direction LR
|
||||
EVENT ||--o{ RESPONSE : "has"
|
||||
EVENT {
|
||||
int EventId PK
|
||||
int ModuleId
|
||||
string Name
|
||||
string Description
|
||||
datetime EventDate
|
||||
string Location
|
||||
string CreatedBy
|
||||
datetime CreatedOn
|
||||
string ModifiedBy
|
||||
datetime ModifiedOn
|
||||
}
|
||||
RESPONSE {
|
||||
int EventResponseId PK
|
||||
bool ResponseType
|
||||
int OwnerId FK
|
||||
int EventRegistrationId FK
|
||||
int ModuleId
|
||||
string CreatedBy
|
||||
datetime CreatedOn
|
||||
string ModifiedBy
|
||||
datetime ModifiedOn
|
||||
}
|
||||
USER ||--o{ RESPONSE : "has"
|
||||
USER {
|
||||
int UserId PK
|
||||
}
|
||||
```
|
||||
|
||||
#### Statistik und Visualisierung
|
||||
|
||||
Ein wesentlicher Teil der administrativen Ansicht ist die Visualisierung der Anmeldezahlen. Hierfür wurde eine Integration von Chart.js realisiert, um den aktuellen Stand der Rückmeldungen grafisch aufzubereiten.
|
||||
|
||||
@@ -444,18 +489,24 @@ Um die Brücke zwischen dem C#-basierten Blazor-Frontend und der JavaScript-Bibl
|
||||
|
||||
Durch diese Trennung bleibt die Geschäftslogik im C#-Code, während für die performante und ansprechende Darstellung auf etablierte Web-Technologien zurückgegriffen wird.
|
||||
|
||||
### 5.3 Schwarzes Brett
|
||||
### Schwarzes Brett
|
||||
|
||||
Das Modul "Schwarzes Brett" dient als digitale Anschlagtafel für den Absolventenverein. Mitglieder können hier Gesuche, Angebote oder allgemeine Informationen veröffentlichen. Es stellt eine zentrale Informationsdrehscheibe dar, die den informellen Austausch innerhalb des Vereins fördern soll.
|
||||
|
||||
#### 5.3.1 Struktur und Anzeige
|
||||
#### Struktur und Anzeige
|
||||
|
||||
{ latex-placement="ht" }
|
||||
|
||||
<!-- ![Detailansicht eines Eintrags auf dem Schwarzen Brett][img-ref] -->
|
||||
|
||||
Die Anzeige der Einträge erfolgt in einer responsiven Grid-Ansicht (Index-Komponente), wobei jeder Eintrag als Karte (Card) dargestellt wird. Dieses Design sorgt für eine übersichtliche Präsentation auch bei einer größeren Anzahl von Mitteilungen.
|
||||
|
||||
- Bilderunterstützung: Das Modul nutzt die Oqtane-interne Dateiverwaltung. Wenn ein Bild für einen Eintrag hochgeladen wurde, wird dieses über einen Image-Proxy skaliert und als Vorschaubild angezeigt. Fehlt ein Bild, wird ein konsistenter Platzhalter verwendet, um das visuelle Gleichgewicht der Grid-Ansicht zu wahren.
|
||||
- Detailansicht: Die Details-Komponente bietet eine fokussierte Ansicht des Eintrags mit vollständiger HTML-Beschreibung, die über einen Rich-Text-Editor gepflegt werden kann. Ergänzt wird dies durch Metadaten wie Erstellungsdatum und Autor.
|
||||
|
||||
#### 5.3.2 Automatisierter E-Mail-Digest
|
||||
<!-- [img-ref]: BlackBoard-Details.png "Detailansicht eines Eintrags auf dem Schwarzen Brett" -->
|
||||
|
||||
#### Automatisierter E-Mail-Digest
|
||||
|
||||
Um die Mitglieder regelmäßig über neue Inhalte zu informieren, wurde ein automatisierter `Cronjob` implementiert. Dieser Job läuft im Hintergrund des Oqtane-Frameworks und führt folgende Schritte aus:
|
||||
|
||||
@@ -465,25 +516,25 @@ Um die Mitglieder regelmäßig über neue Inhalte zu informieren, wurde ein auto
|
||||
- Versand: Die generierten Notifications werden in die Warteschlange der Notification-Infrastruktur eingereiht und sukzessive versendet.
|
||||
Integration des Reporting-Systems
|
||||
|
||||
#### 5.3.3 Reporting System
|
||||
#### Reporting System
|
||||
|
||||
Ein wichtiges Merkmal des Schwarzen Bretts zur Sicherstellung der Inhaltsqualität ist die Anbindung an das globale Reporting-System (siehe 5.4). In der Detailansicht wird über Dependency Injection die IReportUI-Komponente eingebunden. Mithilfe der DynamicComponent von Blazor wird die Melde-Funktion nahtlos in die Oberfläche des Moduls integriert. Dadurch können unangemessene Inhalte direkt von Benutzern gemeldet werden.
|
||||
|
||||
#### 5.3.4 Technischer Hintergrund
|
||||
#### Technischer Hintergrund
|
||||
|
||||
Auf der Serverseite folgt das Modul dem etablierten Muster mit einem `BlackBoardRepository` für den effizienten Datenbankzugriff und einem `BlackBoardController` für die API-Bereitstellung. Die Implementierung des Scheduled Jobs als HostedServiceBase ermöglicht eine tiefe Integration in die Oqtane-Infrastruktur bei gleichzeitig geringem Ressourcenverbrauch.
|
||||
|
||||
## 6 Learnings
|
||||
## Learnings
|
||||
|
||||
### 6.1 Produktion != Staging
|
||||
### Produktion != Staging
|
||||
|
||||
Ein Learning, welches doch relativ schnell aufkam ist im Bereich der IT eigentlich kein unbekanntes. Wir hatten dieses Learning relativ bald, im Frühling 2025, als die ersten Probleme mit dem Deployment von Oqtane aufkamen. Oqtane war zwar in unserer Entwicklungsumgebung sehr einfach zum einrichten gewesen, das Deployment in der Cloud vom Hetzner war jedoch geplagt von Problemen. Im Zeitraum von Mai bis Okober hatten wir keine laufende Produktivumgebung. Dadurch sind wir mit dieser Diplomarbeit auch in [Zeitverzug](#Arbeitszeiteinschätzung-und-verzug) gekommen. Hätte ich mich vor dem Start der Diplomarbeit mit dem Deployment von Oqtane auseinander gesetzt, dann wäre das in [Probleme mit Oqtane](#Probleme mit Oqtane) beschriebene Problem früher aufgekommen und der Zeitverzug wäre nicht so groß, oder noch ganz vermeidbar gewesen.
|
||||
Ein Learning, welches doch relativ schnell aufkam ist im Bereich der IT eigentlich kein unbekanntes. Wir hatten dieses Learning relativ bald, im Frühling 2025, als die ersten Probleme mit dem Deployment von Oqtane aufkamen. Oqtane war zwar in unserer Entwicklungsumgebung sehr einfach zum einrichten gewesen, das Deployment in der Cloud vom Hetzner war jedoch geplagt von Problemen. Im Zeitraum von Mai bis Okober hatten wir keine laufende Produktivumgebung. Dadurch sind wir mit dieser Diplomarbeit auch in [Zeitverzug](#arbeitszeiteinschätzung-zeitverzug) gekommen. Hätte ich mich vor dem Start der Diplomarbeit mit dem Deployment von Oqtane auseinander gesetzt, dann wäre das in [Probleme mit Oqtane](#fehlende-oder-nur-schlechte-dokumentation-von-oqtane) beschriebene Problem früher aufgekommen und der Zeitverzug wäre nicht so groß, oder noch ganz vermeidbar gewesen.
|
||||
|
||||
### 6.2 Teamleitung (Motivation / Downsizing)
|
||||
### Teamleitung (Motivation / Downsizing)
|
||||
|
||||
Nachdem ich mich von Anfang an volkommen in das Deploymentproblem von Oqtane gestürzt habe, habe ich meine Rolle als Teamleitung etwas schleifen lassen. Dadurch fehlte bei einigen Teammitgliedern initial die Identifikation mit dem Projekt und in weitererfolge auch die Motivation an diesem Projekt mitzuarbeiten. Nachdem im Verlauf des Frühlings und über den Sommer von der hälfte des Teams trotz Besprechungen und Mahnungen keine Beiträge zu dem Projekt kamen, haben Hr. Prof. Gürth und ich uns dazu entschieden uns von 2 Personen vor unterschreiben des Projektantrages zu trennen. Grund dazu war die Angst, die mangelnde Motivation zieht das restliche Team mit hinunter. Wir wollten uns trotz des Downsizings nicht an Funktionalitäten sparen und haben uns für das nächste halbe bis dreiviertel Jahr einen ziemlich strikten Zeitplan vorgenommen.
|
||||
|
||||
### 6.3 Arbeitszeiteinschätzung (Zeitverzug)
|
||||
### Arbeitszeiteinschätzung (Zeitverzug)
|
||||
|
||||
Ein wesentliches Learning aus dem Projektverlauf war die Diskrepanz zwischen der initialen Planung und dem tatsächlichen Aufwand. Ursprünglich wurde der Zeitaufwand für das Aufsetzen der Infrastruktur und die Einarbeitung in das Oqtane-Framework auf etwa drei Wochen geschätzt. In der Realität nahm dieser Prozess jedoch mehrere Monate in Anspruch.
|
||||
|
||||
@@ -501,10 +552,10 @@ Um das Projektziel dennoch zu erreichen, wurde der Zeitplan im Herbst 2025 massi
|
||||
`90% fertig, oder fertig?`: Es gibt einige "Regeln", wie: das `Paretoprinzip`, `Hofstadter’s Law` und die `90-90 Regel`. Letztere wurde im Jahr 1985 von Jon Bentley in einer Kolumne "Programming pearls" veröffentlicht. Ausgeschrieben lautet sie:
|
||||
|
||||
> [Rule of Credibility] The first 90 percent of the code accounts for the first 90 percent of the development time. The remaining 10 percent of the code accounts for the other 90 percent of the development time.
|
||||
> (Jon Bentley. 1985. Programmimg pearls. Commun. ACM 28, 9 (Sept. 1985), 896–901. https://doi.org/10.1145/4284.315122) [^8]
|
||||
> (Jon Bentley. 1985. Programmimg pearls. Commun. ACM 28, 9 (Sept. 1985), 896–901. https://doi.org/10.1145/4284.315122) [@bentley1985programming]
|
||||
> Diese Diplomarbeit liefert weitere Evidenz, dass diese Faustregel stimmt.
|
||||
|
||||
### 6.4 Sprints und Meetings (in Zukunft ja asynchron)
|
||||
### Sprints und Meetings (in Zukunft ja asynchron)
|
||||
|
||||
Ein zentrales Problem in unserer ursprünglichen Arbeitsweise war die Kopplung von Besprechungsterminen mit festen „Commit-Deadlines“ (dem Ende des aktuellen Sprint zyklusses). Da wir uns einmal pro Woche für sechs Stunden am Stück trafen, entstand ein destruktives Muster:
|
||||
|
||||
@@ -514,40 +565,8 @@ Ein zentrales Problem in unserer ursprünglichen Arbeitsweise war die Kopplung v
|
||||
|
||||
Lösungsansatz: Meetings und Besprechungen asynchron zueinander setzen.
|
||||
|
||||
- Asynchrone Daily-Updates: Statusberichte erfolgen schriftlich (z. B. in Gitea Issues oder YouTrack), nicht mehr in stundenlangen Call-Marathons. Das nimmt den zeitlichen Druck vom einzelnen Entwickler. Oder zumindest in kurzen Commitnachrichten, welche am Ende des Tages automatisch an alle Teammitglieder zum Überblick gesendet werden (eventuell mit @username tagging, um eine Person nochmal genau anzusprechen)
|
||||
- Asynchrone Daily-Updates: Statusberichte erfolgen schriftlich (z. B. in Gitea Issues oder YouTrack), nicht mehr in stundenlangen Call-Marathons. Das nimmt den zeitlichen Druck vom einzelnen Entwickler. Oder zumindest in kurzen Commitnachrichten, welche am Ende des Tages automatisch an alle Teammitglieder zum Überblick gesendet werden (eventuell mit \@username tagging, um eine Person nochmal genau anzusprechen)
|
||||
- Review-First-Policy: Ein Feature gilt erst dann als „fertig“, wenn es einen asynchronen Code-Review-Prozess durchlaufen hat. Das Meeting dient nur noch der Klärung von Blockern, nicht der Präsentation von Code. Das war eigentlich schon von Anfang an in unserer `Definition of Done` festgelegt worden.
|
||||
- Entkoppelung von Meeting und Deadline: Meetings sollten der Synchronisation dienen, während die Abgabe von Arbeitspaketen kontinuierlich (Continuous Integration) erfolgen muss, um Lastspitzen (in der [Gitea Actions](#Continuous Integration) Pipeline) am Tag der Besprechung zu vermeiden.
|
||||
|
||||
# Quellen
|
||||
|
||||
[^1]: https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles#dependency-inversion
|
||||
|
||||
[^2]: https://blog.logrocket.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
|
||||
|
||||
[^5]: https://www.oqtane.org/#about
|
||||
|
||||
[^6]: https://docs.oqtane.org/dev/extensions/index.html
|
||||
|
||||
[^7]: https://en.wikipedia.org/wiki/Blazor
|
||||
|
||||
[^8]: https://dl.acm.org/doi/10.1145/4284.315122
|
||||
|
||||
[^9]: https://martinfowler.com/articles/injection.html
|
||||
|
||||
[^10]: https://andrewlock.net/implementing-custom-token-providers-for-passwordless-authentication-in-asp-net-core-identity/#changing-the-default-token-lifetime
|
||||
|
||||
[^11]: https://www.cs.umd.edu/projects/syschat/raceConditions.pdf
|
||||
|
||||
[^12]: https://docs.gitea.com/
|
||||
|
||||
[^13]: https://docs.gitea.com/installation/comparison#issue-tracker
|
||||
|
||||
[^14]: https://docs.gitea.com/usage/pull-request
|
||||
|
||||
[^15]: https://docs.gitea.com/usage/actions/overview
|
||||
|
||||
[^16]: https://docs.gitea.com/usage/packages/overview
|
||||
\pagebreak
|
||||
5
97-tabellenverzeichnis.md
Normal file
@@ -0,0 +1,5 @@
|
||||
\cleardoublepage
|
||||
|
||||
# Tabellenverzeichnis
|
||||
|
||||
\listoftables
|
||||
5
98-abbildungsverzeichnis.md
Normal file
@@ -0,0 +1,5 @@
|
||||
\cleardoublepage
|
||||
|
||||
# Abbildungsverzeichnis
|
||||
|
||||
\listoffigures
|
||||
6
99-bibliographie.md
Normal file
@@ -0,0 +1,6 @@
|
||||
\cleardoublepage
|
||||
|
||||
# Quellenverzeichnis
|
||||
|
||||
::: {#refs}
|
||||
:::
|
||||
7
_extensions/diagram/_extension.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
title: diagram
|
||||
author: Albert Krewinkel
|
||||
version: 1.2.0
|
||||
quarto-required: ">=1.3"
|
||||
contributes:
|
||||
filters:
|
||||
- diagram.lua
|
||||
660
_extensions/diagram/diagram.lua
Normal file
@@ -0,0 +1,660 @@
|
||||
--[[
|
||||
diagram – create images and figures from code blocks.
|
||||
|
||||
See copyright notice in file LICENSE.
|
||||
]]
|
||||
-- The filter uses the Figure AST element, which was added in pandoc 3.
|
||||
PANDOC_VERSION:must_be_at_least '3.0'
|
||||
|
||||
local version = pandoc.types.Version '1.2.0'
|
||||
|
||||
-- Report Lua warnings to stderr if the `warn` function is not plugged into
|
||||
-- pandoc's logging system.
|
||||
if not warn then
|
||||
-- fallback
|
||||
warn = function(...) io.stderr:write(table.concat({ ... })) end
|
||||
elseif PANDOC_VERSION < '3.1.4' then
|
||||
-- starting with pandoc 3.1.4, warnings are reported to pandoc's logging
|
||||
-- system, so no need to print warnings to stderr.
|
||||
warn '@on'
|
||||
end
|
||||
|
||||
local io = require 'io'
|
||||
local pandoc = require 'pandoc'
|
||||
local system = require 'pandoc.system'
|
||||
local utils = require 'pandoc.utils'
|
||||
local List = require 'pandoc.List'
|
||||
local stringify = utils.stringify
|
||||
local with_temporary_directory = system.with_temporary_directory
|
||||
local with_working_directory = system.with_working_directory
|
||||
|
||||
--- Returns a filter-specific directory in which cache files can be
|
||||
--- stored, or nil if no such directory is available.
|
||||
local function cachedir ()
|
||||
local cache_home = os.getenv 'XDG_CACHE_HOME'
|
||||
if not cache_home or cache_home == '' then
|
||||
local user_home = system.os == 'windows'
|
||||
and os.getenv 'USERPROFILE'
|
||||
or os.getenv 'HOME'
|
||||
|
||||
if not user_home or user_home == '' then
|
||||
return nil
|
||||
end
|
||||
cache_home = pandoc.path.join{user_home, '.cache'} or nil
|
||||
end
|
||||
|
||||
-- Create filter cache directory
|
||||
return pandoc.path.join{cache_home, 'pandoc-diagram-filter'}
|
||||
end
|
||||
|
||||
--- Path holding the image cache, or `nil` if the cache is not used.
|
||||
local image_cache = nil
|
||||
|
||||
local mimetype_for_extension = {
|
||||
jpeg = 'image/jpeg',
|
||||
jpg = 'image/jpeg',
|
||||
pdf = 'application/pdf',
|
||||
png = 'image/png',
|
||||
svg = 'image/svg+xml',
|
||||
}
|
||||
|
||||
local extension_for_mimetype = {
|
||||
['application/pdf'] = 'pdf',
|
||||
['image/jpeg'] = 'jpg',
|
||||
['image/png'] = 'png',
|
||||
['image/svg+xml'] = 'svg',
|
||||
}
|
||||
|
||||
--- Converts a list of format specifiers to a set of MIME types.
|
||||
local function mime_types_set (tbl)
|
||||
local set = {}
|
||||
local mime_type
|
||||
for _, image_format_spec in ipairs(tbl) do
|
||||
mime_type = mimetype_for_extension[image_format_spec] or image_format_spec
|
||||
set[mime_type] = true
|
||||
end
|
||||
return set
|
||||
end
|
||||
|
||||
--- Reads the contents of a file.
|
||||
local function read_file (filepath)
|
||||
local fh = io.open(filepath, 'rb')
|
||||
local contents = fh:read('a')
|
||||
fh:close()
|
||||
return contents
|
||||
end
|
||||
|
||||
--- Writes the contents into a file at the given path.
|
||||
local function write_file (filepath, content)
|
||||
local fh = io.open(filepath, 'wb')
|
||||
fh:write(content)
|
||||
fh:close()
|
||||
end
|
||||
|
||||
--- Like `pandoc.pipe`, but allows "multi word" paths:
|
||||
-- Supplying a list as the first argument will use the first element as
|
||||
-- the executable path and prepend the remaining elements to the list of
|
||||
-- arguments.
|
||||
local function pipe (command, args, input)
|
||||
local cmd
|
||||
if pandoc.utils.type(command) == 'List' then
|
||||
command = command:map(stringify)
|
||||
cmd = command:remove(1)
|
||||
args = command .. args
|
||||
else
|
||||
cmd = stringify(command)
|
||||
end
|
||||
return pandoc.pipe(cmd, args, input)
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Diagram Engines
|
||||
--
|
||||
|
||||
-- PlantUML engine; assumes that there's a `plantuml` binary.
|
||||
local plantuml = {
|
||||
line_comment_start = [[']],
|
||||
mime_types = mime_types_set{'pdf', 'png', 'svg'},
|
||||
compile = function (self, puml)
|
||||
local mime_type = self.mime_type or 'image/svg+xml'
|
||||
-- PlantUML format identifiers correspond to common file extensions.
|
||||
local format = extension_for_mimetype[mime_type]
|
||||
if not format then
|
||||
format, mime_type = 'svg', 'image/svg+xml'
|
||||
end
|
||||
local args = {'-t' .. format, "-pipe", "-charset", "UTF8"}
|
||||
return pipe(self.execpath or 'plantuml', args, puml), mime_type
|
||||
end,
|
||||
}
|
||||
|
||||
--- GraphViz engine for the dot language
|
||||
local graphviz = {
|
||||
line_comment_start = '//',
|
||||
mime_types = mime_types_set{'jpg', 'pdf', 'png', 'svg'},
|
||||
mime_type = 'image/svg+xml',
|
||||
compile = function (self, code)
|
||||
local mime_type = self.mime_type
|
||||
-- GraphViz format identifiers correspond to common file extensions.
|
||||
local format = extension_for_mimetype[mime_type]
|
||||
if not format then
|
||||
format, mime_type = 'svg', 'image/svg+xml'
|
||||
end
|
||||
return pipe(self.execpath or 'dot', {"-T"..format}, code), mime_type
|
||||
end,
|
||||
}
|
||||
|
||||
--- Mermaid engine
|
||||
local mermaid = {
|
||||
line_comment_start = '%%',
|
||||
mime_types = mime_types_set{'pdf', 'png', 'svg'},
|
||||
compile = function (self, code)
|
||||
local mime_type = self.mime_type or 'image/svg+xml'
|
||||
local file_extension = extension_for_mimetype[mime_type]
|
||||
return with_temporary_directory("diagram", function (tmpdir)
|
||||
return with_working_directory(tmpdir, function ()
|
||||
local infile = 'diagram.mmd'
|
||||
local outfile = 'diagram.' .. file_extension
|
||||
write_file(infile, code)
|
||||
pipe(
|
||||
self.execpath or 'mmdc',
|
||||
{"--pdfFit", "--iconPacksNamesAndUrls", "mdi#https://unpkg.com/@iconify-json/mdi@1.2.3/icons.json", "--input", infile, "--output", outfile},
|
||||
''
|
||||
)
|
||||
return read_file(outfile), mime_type
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
}
|
||||
|
||||
--- TikZ
|
||||
--
|
||||
|
||||
--- LaTeX template used to compile TikZ images.
|
||||
local tikz_template = pandoc.template.compile [[
|
||||
\documentclass{standalone}
|
||||
\usepackage{tikz}
|
||||
$for(header-includes)$
|
||||
$it$
|
||||
$endfor$
|
||||
$additional-packages$
|
||||
\begin{document}
|
||||
$body$
|
||||
\end{document}
|
||||
]]
|
||||
|
||||
--- The TikZ engine uses pdflatex to compile TikZ code to an image
|
||||
local tikz = {
|
||||
line_comment_start = '%%',
|
||||
|
||||
mime_types = {
|
||||
['application/pdf'] = true,
|
||||
},
|
||||
|
||||
--- Compile LaTeX with TikZ code to an image
|
||||
compile = function (self, src, user_opts)
|
||||
return with_temporary_directory("tikz", function (tmpdir)
|
||||
return with_working_directory(tmpdir, function ()
|
||||
-- Define file names:
|
||||
local file_template = "%s/tikz-image.%s"
|
||||
local tikz_file = file_template:format(tmpdir, "tex")
|
||||
local pdf_file = file_template:format(tmpdir, "pdf")
|
||||
|
||||
-- Treat string values as raw LaTeX
|
||||
local meta = {
|
||||
['header-includes'] = user_opts['header-includes'],
|
||||
['additional-packages'] = {pandoc.RawInline(
|
||||
'latex',
|
||||
stringify(user_opts['additional-packages'] or '')
|
||||
)},
|
||||
}
|
||||
local tex_code = pandoc.write(
|
||||
pandoc.Pandoc({pandoc.RawBlock('latex', src)}, meta),
|
||||
'latex',
|
||||
{template = tikz_template}
|
||||
)
|
||||
write_file(tikz_file, tex_code)
|
||||
|
||||
-- Execute the LaTeX compiler:
|
||||
local success, result = pcall(
|
||||
pipe,
|
||||
self.execpath or 'pdflatex',
|
||||
{ '-interaction=nonstopmode', '-output-directory', tmpdir, tikz_file },
|
||||
''
|
||||
)
|
||||
if not success then
|
||||
warn(string.format(
|
||||
"The call\n%s\nfailed with error code %s. Output:\n%s",
|
||||
result.command,
|
||||
result.error_code,
|
||||
result.output
|
||||
))
|
||||
end
|
||||
return read_file(pdf_file), 'application/pdf'
|
||||
end)
|
||||
end)
|
||||
end
|
||||
}
|
||||
|
||||
--- Asymptote diagram engine
|
||||
local asymptote = {
|
||||
line_comment_start = '%%',
|
||||
mime_types = {
|
||||
['application/pdf'] = true,
|
||||
},
|
||||
compile = function (self, code)
|
||||
return with_temporary_directory("asymptote", function(tmpdir)
|
||||
return with_working_directory(tmpdir, function ()
|
||||
local pdf_file = "pandoc_diagram.pdf"
|
||||
local args = {'-tex', 'pdflatex', "-o", "pandoc_diagram", '-'}
|
||||
pipe(self.execpath or 'asy', args, code)
|
||||
return read_file(pdf_file), 'application/pdf'
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
}
|
||||
|
||||
--- Cetz diagram engine
|
||||
local cetz = {
|
||||
line_comment_start = '%%',
|
||||
mime_types = mime_types_set{'jpg', 'pdf', 'png', 'svg'},
|
||||
mime_type = 'image/svg+xml',
|
||||
compile = function (self, code)
|
||||
local mime_type = self.mime_type
|
||||
local format = extension_for_mimetype[mime_type]
|
||||
if not format then
|
||||
format, mime_type = 'svg', 'image/svg+xml'
|
||||
end
|
||||
local preamble = [[
|
||||
#import "@preview/cetz:0.3.4"
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
]]
|
||||
|
||||
local typst_code = preamble .. code
|
||||
|
||||
return with_temporary_directory("diagram", function (tmpdir)
|
||||
return with_working_directory(tmpdir, function ()
|
||||
local outfile = 'diagram.' .. format
|
||||
local execpath = self.execpath
|
||||
if not execpath and quarto and quarto.version >= '1.4' then
|
||||
-- fall back to the Typst exec shipped with Quarto.
|
||||
execpath = List{'quarto', 'typst'}
|
||||
end
|
||||
pipe(
|
||||
execpath or 'typst',
|
||||
{"compile", "-f", format, "-", outfile},
|
||||
typst_code
|
||||
)
|
||||
return read_file(outfile), mime_type
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
}
|
||||
|
||||
--- D2 engine for the D2 language
|
||||
local d2 = {
|
||||
line_comment_start = '#',
|
||||
mime_types = mime_types_set{'png', 'svg'},
|
||||
|
||||
compile = function (self, code, user_opts)
|
||||
return with_temporary_directory('diagram', function (tmpdir)
|
||||
return with_working_directory(tmpdir, function ()
|
||||
-- D2 format identifiers correspond to common file extensions.
|
||||
local mime_type = self.mime_type or 'image/svg+xml'
|
||||
local file_extension = extension_for_mimetype[mime_type]
|
||||
local infile = 'diagram.d2'
|
||||
local outfile = 'diagram.' .. file_extension
|
||||
|
||||
args = {'--bundle', '--pad=0', '--scale=1'}
|
||||
|
||||
d2_user_opts = {
|
||||
'layout',
|
||||
}
|
||||
for _, d2_user_opt in pairs(d2_user_opts) do
|
||||
if user_opts[d2_user_opt] then
|
||||
table.insert(args, '--' .. d2_user_opt .. '=' .. user_opts[d2_user_opt])
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(args, infile)
|
||||
table.insert(args, outfile)
|
||||
|
||||
write_file(infile, code)
|
||||
|
||||
pipe(self.execpath or 'd2', args, '')
|
||||
|
||||
return read_file(outfile), mime_type
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
}
|
||||
|
||||
local default_engines = {
|
||||
asymptote = asymptote,
|
||||
dot = graphviz,
|
||||
mermaid = mermaid,
|
||||
plantuml = plantuml,
|
||||
tikz = tikz,
|
||||
cetz = cetz,
|
||||
d2 = d2,
|
||||
}
|
||||
|
||||
--
|
||||
-- Configuration
|
||||
--
|
||||
|
||||
--- Options for the output format of the given name.
|
||||
local function format_options (name)
|
||||
local pdf2svg = name ~= 'latex' and name ~= 'context'
|
||||
local is_office_format = name == 'docx' or name == 'odt'
|
||||
-- Office formats seem to work better with PNG than with SVG.
|
||||
local preferred_mime_types = is_office_format
|
||||
and pandoc.List{'image/png', 'application/pdf'}
|
||||
or pandoc.List{'application/pdf', 'image/png'}
|
||||
-- Prefer SVG for non-PDF output formats, except for Office formats
|
||||
if is_office_format then
|
||||
preferred_mime_types:insert('image/svg+xml')
|
||||
elseif pdf2svg then
|
||||
preferred_mime_types:insert(1, 'image/svg+xml')
|
||||
end
|
||||
return {
|
||||
name = name,
|
||||
pdf2svg = pdf2svg,
|
||||
preferred_mime_types = preferred_mime_types,
|
||||
best_mime_type = function (self, supported_mime_types, requested)
|
||||
return self.preferred_mime_types:find_if(function (preferred)
|
||||
return supported_mime_types[preferred] and
|
||||
(not requested or
|
||||
(pandoc.utils.type(requested) == 'List' and
|
||||
requested:includes(preferred)) or
|
||||
(pandoc.utils.type(requested) == 'table' and
|
||||
requested[preferred]) or
|
||||
|
||||
-- Assume string, Inlines, and Blocks values specify the only
|
||||
-- acceptable MIME type.
|
||||
stringify(requested) == preferred)
|
||||
end)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
--- Returns a configured diagram engine.
|
||||
local function get_engine (name, engopts, format)
|
||||
local engine = default_engines[name] or
|
||||
select(2, pcall(require, stringify(engopts.package)))
|
||||
|
||||
-- Sanity check
|
||||
if not engine then
|
||||
warn(PANDOC_SCRIPT_FILE, ": No such engine '", name, "'.")
|
||||
return nil
|
||||
elseif engopts == false then
|
||||
-- engine is disabled
|
||||
return nil
|
||||
elseif engopts == true then
|
||||
-- use default options
|
||||
return engine
|
||||
end
|
||||
|
||||
local execpath = engopts.execpath or os.getenv(name:upper() .. '_BIN')
|
||||
|
||||
local mime_type = format:best_mime_type(
|
||||
engine.mime_types,
|
||||
engopts['mime-type'] or engopts['mime-types']
|
||||
)
|
||||
if not mime_type then
|
||||
warn(PANDOC_SCRIPT_FILE, ": Cannot use ", name, " with ", format.name)
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
execpath = execpath,
|
||||
compile = engine.compile,
|
||||
line_comment_start = engine.line_comment_start,
|
||||
mime_type = mime_type,
|
||||
opt = engopts or {},
|
||||
}
|
||||
end
|
||||
|
||||
--- Returns the diagram engine configs.
|
||||
local function configure (meta, format_name)
|
||||
local conf = meta.diagram or {}
|
||||
local format = format_options(format_name)
|
||||
meta.diagram = nil
|
||||
|
||||
-- cache for image files
|
||||
if conf.cache then
|
||||
image_cache = conf['cache-dir']
|
||||
and stringify(conf['cache-dir'])
|
||||
or cachedir()
|
||||
pandoc.system.make_directory(image_cache, true)
|
||||
end
|
||||
|
||||
-- engine configs
|
||||
local engine = {}
|
||||
for name, engopts in pairs(conf.engine or default_engines) do
|
||||
engine[name] = get_engine(name, engopts, format)
|
||||
end
|
||||
|
||||
return {
|
||||
engine = engine,
|
||||
format = format,
|
||||
cache = image_cache and true,
|
||||
image_cache = image_cache,
|
||||
}
|
||||
end
|
||||
|
||||
--
|
||||
-- Format conversion
|
||||
--
|
||||
|
||||
--- Converts a PDF to SVG.
|
||||
local pdf2svg = function (imgdata)
|
||||
-- Using `os.tmpname()` instead of a hash would be slightly cleaner, but the
|
||||
-- function causes problems on Windows (and wasm). See, e.g.,
|
||||
-- https://github.com/pandoc-ext/diagram/issues/49
|
||||
local pdf_file = 'diagram-' .. pandoc.utils.sha1(imgdata) .. '.pdf'
|
||||
write_file(pdf_file, imgdata)
|
||||
local args = {
|
||||
'--export-type=svg',
|
||||
'--export-plain-svg',
|
||||
'--export-filename=-',
|
||||
pdf_file
|
||||
}
|
||||
return pandoc.pipe('inkscape', args, ''), os.remove(pdf_file)
|
||||
end
|
||||
|
||||
local function properties_from_code (code, comment_start)
|
||||
local props = {}
|
||||
local pattern = comment_start:gsub('%p', '%%%1') .. '| ' ..
|
||||
'([-_%w]+): ([^\n]*)\n'
|
||||
for key, value in code:gmatch(pattern) do
|
||||
if key == 'fig-cap' then
|
||||
props['caption'] = value
|
||||
else
|
||||
props[key] = value
|
||||
end
|
||||
end
|
||||
return props
|
||||
end
|
||||
|
||||
local function diagram_options (cb, comment_start)
|
||||
local attribs = comment_start
|
||||
and properties_from_code(cb.text, comment_start)
|
||||
or {}
|
||||
for key, value in pairs(cb.attributes) do
|
||||
attribs[key] = value
|
||||
end
|
||||
|
||||
local alt
|
||||
local caption
|
||||
local fig_attr = {id = cb.identifier}
|
||||
local filename
|
||||
local image_attr = {}
|
||||
local user_opt = {}
|
||||
|
||||
for attr_name, value in pairs(attribs) do
|
||||
if attr_name == 'alt' then
|
||||
alt = value
|
||||
elseif attr_name == 'caption' then
|
||||
-- Read caption attribute as Markdown
|
||||
caption = attribs.caption
|
||||
and pandoc.read(attribs.caption).blocks
|
||||
or nil
|
||||
elseif attr_name == 'filename' then
|
||||
filename = value
|
||||
elseif attr_name == 'label' then
|
||||
fig_attr.id = value
|
||||
elseif attr_name == 'name' then
|
||||
fig_attr.name = value
|
||||
else
|
||||
-- Check for prefixed attributes
|
||||
local prefix, key = attr_name:match '^(%a+)%-(%a[-%w]*)$'
|
||||
if prefix == 'fig' then
|
||||
fig_attr[key] = value
|
||||
elseif prefix == 'image' or prefix == 'img' then
|
||||
image_attr[key] = value
|
||||
elseif prefix == 'opt' then
|
||||
user_opt[key] = value
|
||||
else
|
||||
-- Use as image attribute
|
||||
image_attr[attr_name] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
['alt'] = alt or
|
||||
(caption and pandoc.utils.blocks_to_inlines(caption)) or
|
||||
{},
|
||||
['caption'] = caption,
|
||||
['fig-attr'] = fig_attr,
|
||||
['filename'] = filename,
|
||||
['image-attr'] = image_attr,
|
||||
['opt'] = user_opt,
|
||||
}
|
||||
end
|
||||
|
||||
local function get_cached_image (hash, mime_type)
|
||||
if not image_cache then
|
||||
return nil
|
||||
end
|
||||
local filename = hash .. '.' .. extension_for_mimetype[mime_type]
|
||||
local imgpath = pandoc.path.join{image_cache, filename}
|
||||
local success, imgdata = pcall(read_file, imgpath)
|
||||
if success then
|
||||
return imgdata, mime_type
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function cache_image (codeblock, imgdata, mimetype)
|
||||
-- do nothing if caching is disabled or not possible.
|
||||
if not image_cache then
|
||||
return
|
||||
end
|
||||
local ext = extension_for_mimetype[mimetype]
|
||||
local filename = pandoc.sha1(codeblock.text) .. '.' .. ext
|
||||
local imgpath = pandoc.path.join{image_cache, filename}
|
||||
write_file(imgpath, imgdata)
|
||||
end
|
||||
|
||||
-- Executes each document's code block to find matching code blocks:
|
||||
local function code_to_figure (conf)
|
||||
return function (block)
|
||||
-- Check if a converter exists for this block. If not, return the block
|
||||
-- unchanged.
|
||||
local diagram_type = block.classes[1]
|
||||
if not diagram_type then
|
||||
return nil
|
||||
end
|
||||
|
||||
local engine = conf.engine[diagram_type]
|
||||
if not engine then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Unified properties.
|
||||
local dgr_opt = diagram_options(block, engine.line_comment_start)
|
||||
for optname, value in pairs(engine.opt or {}) do
|
||||
dgr_opt.opt[optname] = dgr_opt.opt[optname] or value
|
||||
end
|
||||
|
||||
local run_pdf2svg = engine.mime_type == 'application/pdf'
|
||||
and conf.format.pdf2svg
|
||||
|
||||
-- Try to retrieve the image data from the cache.
|
||||
local imgdata, imgtype
|
||||
if conf.cache then
|
||||
imgdata, imgtype = get_cached_image(
|
||||
pandoc.sha1(block.text),
|
||||
run_pdf2svg and 'image/svg+xml' or engine.mime_type
|
||||
)
|
||||
end
|
||||
|
||||
if not imgdata or not imgtype then
|
||||
-- No cached image; call the converter
|
||||
local success
|
||||
success, imgdata, imgtype =
|
||||
pcall(engine.compile, engine, block.text, dgr_opt.opt)
|
||||
|
||||
-- Bail if an error occurred; imgdata contains the error message
|
||||
-- when that happens.
|
||||
if not success then
|
||||
warn(PANDOC_SCRIPT_FILE, ': ', tostring(imgdata))
|
||||
return nil
|
||||
elseif not imgdata then
|
||||
warn(PANDOC_SCRIPT_FILE, ': Diagram engine returned no image data.')
|
||||
return nil
|
||||
elseif not imgtype then
|
||||
warn(PANDOC_SCRIPT_FILE, ': Diagram engine did not return a MIME type.')
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Convert SVG if necessary.
|
||||
if imgtype == 'application/pdf' and conf.format.pdf2svg then
|
||||
imgdata, imgtype = pdf2svg(imgdata), 'image/svg+xml'
|
||||
end
|
||||
|
||||
-- If we got here, then the transformation went ok and `img` contains
|
||||
-- the image data.
|
||||
cache_image(block, imgdata, imgtype)
|
||||
end
|
||||
|
||||
-- Use the block's filename attribute or create a new name by hashing the
|
||||
-- image content.
|
||||
local basename, _extension = pandoc.path.split_extension(
|
||||
dgr_opt.filename or pandoc.sha1(imgdata)
|
||||
)
|
||||
local fname = basename .. '.' .. extension_for_mimetype[imgtype]
|
||||
|
||||
-- Store the data in the media bag:
|
||||
pandoc.mediabag.insert(fname, imgtype, imgdata)
|
||||
|
||||
-- Create the image object.
|
||||
local image = pandoc.Image(dgr_opt.alt, fname, "", dgr_opt['image-attr'])
|
||||
|
||||
-- Create a figure if the diagram has a caption; otherwise return
|
||||
-- just the image.
|
||||
return dgr_opt.caption and
|
||||
pandoc.Figure(
|
||||
pandoc.Plain{image},
|
||||
dgr_opt.caption,
|
||||
dgr_opt['fig-attr']
|
||||
) or
|
||||
pandoc.Plain{image}
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable(
|
||||
{{
|
||||
Pandoc = function (doc)
|
||||
local conf = configure(doc.meta, FORMAT)
|
||||
return doc:walk {
|
||||
CodeBlock = code_to_figure(conf),
|
||||
}
|
||||
end
|
||||
}},
|
||||
{
|
||||
version = version,
|
||||
}
|
||||
)
|
||||
54
_extensions/header/header.tex
Normal file
@@ -0,0 +1,54 @@
|
||||
\usepackage{etoolbox}
|
||||
\makeatletter
|
||||
\patchcmd{\LT@array}{\tabskip\z@}{\tabskip\fill}{}{}
|
||||
\makeatother
|
||||
|
||||
\usepackage[hyphens]{url}
|
||||
\usepackage[hidelinks]{hyperref}
|
||||
\setlength{\emergencystretch}{3em} % Prevent overfull lines
|
||||
|
||||
\usepackage{tocloft}
|
||||
|
||||
% Dotted lines for all levels in TOC
|
||||
\renewcommand{\cftsecleader}{\cftdotfill{\cftdotsep}}
|
||||
\renewcommand{\cftsubsecleader}{\cftdotfill{\cftdotsep}}
|
||||
\renewcommand{\cftsubsubsecleader}{\cftdotfill{\cftdotsep}}
|
||||
|
||||
% Bold section numbers and names
|
||||
\renewcommand{\cftsecfont}{\bfseries}
|
||||
\renewcommand{\cftsecpagefont}{\bfseries}
|
||||
|
||||
% Adjust vertical spacing before sections
|
||||
\setlength{\cftbeforesecskip}{0.5em}
|
||||
|
||||
\usepackage{fvextra}
|
||||
\fvset{breaklines=true}
|
||||
|
||||
\usepackage{needspace}
|
||||
\let\oldShaded\Shaded
|
||||
\let\oldendShaded\endShaded
|
||||
\renewenvironment{Shaded}{\needspace{8\baselineskip}\oldShaded}{\oldendShaded}
|
||||
|
||||
\usepackage{fancyhdr}
|
||||
|
||||
% Define the fancy style but don't activate yet
|
||||
\fancypagestyle{fancy}{
|
||||
\fancyhf{} % clear all header and footer fields
|
||||
\fancyhead[RO,LE]{\includegraphics[height=2cm]{images/preamble/logo.png}}
|
||||
\fancyhead[LO,RE]{\nouppercase{\leftmark}}
|
||||
\fancyfoot[RO,LE]{\thepage}
|
||||
\renewcommand{\headrulewidth}{0.4pt}
|
||||
\renewcommand{\footrulewidth}{0.4pt}
|
||||
\newgeometry{top=2.5cm, bottom=4cm, left=3cm, right=2cm, includehead, includefoot, a4paper}
|
||||
\setlength{\headheight}{2cm}
|
||||
\setlength{\footskip}{1.5cm}
|
||||
}
|
||||
|
||||
\renewcommand{\sectionmark}[1]{\markboth{#1}{}} % Set section title to \leftmark
|
||||
|
||||
% Default style for preamble/TOC
|
||||
\pagestyle{plain}
|
||||
\fancyhf{}
|
||||
\fancyfoot[C]{\thepage}
|
||||
\renewcommand{\headrulewidth}{0pt}
|
||||
\renewcommand{\footrulewidth}{0pt}
|
||||
30
_extensions/razor/razor.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE language SYSTEM "language.dtd">
|
||||
<language name="Razor" version="1" kateversion="2.4" section="Markup" extensions="*.cshtml;*.razor" mimetype="text/x-cshtml" author="Antigravity" license="MIT">
|
||||
<highlighting>
|
||||
<contexts>
|
||||
<context name="Normal" attribute="Normal Text" lineEndContext="#stay">
|
||||
<!-- Razor specific tags -->
|
||||
<StringDetect String="@{" attribute="Control Flow" context="CSharpBlock" />
|
||||
<StringDetect String="@(" attribute="Control Flow" context="CSharpExpr" />
|
||||
<RegExpr String="@[a-zA-Z_][a-zA-Z0-9_.]*" attribute="Control Flow" context="#stay" />
|
||||
<!-- Fallback to HTML -->
|
||||
<IncludeRules context="##HTML" />
|
||||
</context>
|
||||
|
||||
<context name="CSharpBlock" attribute="Normal Text" lineEndContext="#stay">
|
||||
<DetectChar char="}" attribute="Control Flow" context="#pop" />
|
||||
<IncludeRules context="##C#" />
|
||||
</context>
|
||||
|
||||
<context name="CSharpExpr" attribute="Normal Text" lineEndContext="#stay">
|
||||
<DetectChar char=")" attribute="Control Flow" context="#pop" />
|
||||
<IncludeRules context="##C#" />
|
||||
</context>
|
||||
</contexts>
|
||||
<itemDatas>
|
||||
<itemData name="Normal Text" defStyleNum="dsNormal" />
|
||||
<itemData name="Control Flow" defStyleNum="dsControlFlow" />
|
||||
</itemDatas>
|
||||
</highlighting>
|
||||
</language>
|
||||
519
_extensions/sources/ieee.csl
Normal file
@@ -0,0 +1,519 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0" demote-non-dropping-particle="sort-only">
|
||||
<info>
|
||||
<title>IEEE Reference Guide version 11.29.2023</title>
|
||||
<title-short>Institute of Electrical and Electronics Engineers</title-short>
|
||||
<id>http://www.zotero.org/styles/ieee</id>
|
||||
<link href="http://www.zotero.org/styles/ieee" rel="self"/>
|
||||
<link href="https://journals.ieeeauthorcenter.ieee.org/your-role-in-article-production/ieee-editorial-style-manual/" rel="documentation"/>
|
||||
<author>
|
||||
<name>Michael Berkowitz</name>
|
||||
<email>mberkowi@gmu.edu</email>
|
||||
</author>
|
||||
<contributor>
|
||||
<name>Julian Onions</name>
|
||||
<email>julian.onions@gmail.com</email>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Rintze Zelle</name>
|
||||
<uri>http://twitter.com/rintzezelle</uri>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Stephen Frank</name>
|
||||
<uri>http://www.zotero.org/sfrank</uri>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Sebastian Karcher</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Giuseppe Silano</name>
|
||||
<email>g.silano89@gmail.com</email>
|
||||
<uri>http://giuseppesilano.net</uri>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Patrick O'Brien</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Brenton M. Wiernik</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Oliver Couch</name>
|
||||
<email>oliver.couch@gmail.com</email>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Andrew Dunning</name>
|
||||
<uri>https://orcid.org/0000-0003-0464-5036</uri>
|
||||
</contributor>
|
||||
<category citation-format="numeric"/>
|
||||
<category field="engineering"/>
|
||||
<category field="generic-base"/>
|
||||
<summary>IEEE style as per the 2023 guidelines.</summary>
|
||||
<updated>2024-03-27T11:41:27+00:00</updated>
|
||||
<rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
|
||||
</info>
|
||||
<locale xml:lang="en">
|
||||
<date form="text">
|
||||
<date-part name="month" form="short" suffix=" "/>
|
||||
<date-part name="day" form="numeric-leading-zeros" suffix=", "/>
|
||||
<date-part name="year"/>
|
||||
</date>
|
||||
<terms>
|
||||
<term name="chapter" form="short">ch.</term>
|
||||
<term name="chapter-number" form="short">ch.</term>
|
||||
<term name="presented at">presented at the</term>
|
||||
<term name="available at">available</term>
|
||||
<!-- always use three-letter abbreviations for months -->
|
||||
<term name="month-06" form="short">Jun.</term>
|
||||
<term name="month-07" form="short">Jul.</term>
|
||||
<term name="month-09" form="short">Sep.</term>
|
||||
</terms>
|
||||
</locale>
|
||||
<!-- Macros -->
|
||||
<macro name="status">
|
||||
<choose>
|
||||
<if variable="page issue volume" match="none">
|
||||
<text variable="status" text-case="capitalize-first" suffix="" font-weight="bold"/>
|
||||
</if>
|
||||
</choose>
|
||||
</macro>
|
||||
<macro name="edition">
|
||||
<choose>
|
||||
<if type="bill book chapter graphic legal_case legislation motion_picture paper-conference report song" match="any">
|
||||
<choose>
|
||||
<if is-numeric="edition">
|
||||
<group delimiter=" ">
|
||||
<number variable="edition" form="ordinal"/>
|
||||
<text term="edition" form="short"/>
|
||||
</group>
|
||||
</if>
|
||||
<else>
|
||||
<text variable="edition" text-case="capitalize-first" suffix="."/>
|
||||
</else>
|
||||
</choose>
|
||||
</if>
|
||||
</choose>
|
||||
</macro>
|
||||
<macro name="issued">
|
||||
<choose>
|
||||
<if type="article-journal report" match="any">
|
||||
<date variable="issued">
|
||||
<date-part name="month" form="short" suffix=" "/>
|
||||
<date-part name="year" form="long"/>
|
||||
</date>
|
||||
</if>
|
||||
<else-if type="bill book chapter graphic legal_case legislation song thesis" match="any">
|
||||
<date variable="issued">
|
||||
<date-part name="year" form="long"/>
|
||||
</date>
|
||||
</else-if>
|
||||
<else-if type="paper-conference" match="any">
|
||||
<date variable="issued">
|
||||
<date-part name="month" form="short"/>
|
||||
<date-part name="year" prefix=" "/>
|
||||
</date>
|
||||
</else-if>
|
||||
<else-if type="motion_picture" match="any">
|
||||
<date variable="issued" form="text" prefix="(" suffix=")"/>
|
||||
</else-if>
|
||||
<else>
|
||||
<date variable="issued" form="text"/>
|
||||
</else>
|
||||
</choose>
|
||||
</macro>
|
||||
<macro name="author">
|
||||
<names variable="author">
|
||||
<name and="text" et-al-min="7" et-al-use-first="1" initialize-with=". "/>
|
||||
<label form="short" prefix=", " text-case="capitalize-first"/>
|
||||
<et-al font-style="italic"/>
|
||||
<substitute>
|
||||
<names variable="editor"/>
|
||||
<names variable="translator"/>
|
||||
<text macro="director"/>
|
||||
</substitute>
|
||||
</names>
|
||||
</macro>
|
||||
<macro name="editor">
|
||||
<names variable="editor">
|
||||
<name initialize-with=". " delimiter=", " and="text"/>
|
||||
<label form="short" prefix=", " text-case="capitalize-first"/>
|
||||
</names>
|
||||
</macro>
|
||||
<macro name="director">
|
||||
<names variable="director">
|
||||
<name and="text" et-al-min="7" et-al-use-first="1" initialize-with=". "/>
|
||||
<et-al font-style="italic"/>
|
||||
</names>
|
||||
</macro>
|
||||
<macro name="locators">
|
||||
<group delimiter=", ">
|
||||
<text macro="edition"/>
|
||||
<group delimiter=" ">
|
||||
<text term="volume" form="short"/>
|
||||
<number variable="volume" form="numeric"/>
|
||||
</group>
|
||||
<group delimiter=" ">
|
||||
<number variable="number-of-volumes" form="numeric"/>
|
||||
<text term="volume" form="short" plural="true"/>
|
||||
</group>
|
||||
<group delimiter=" ">
|
||||
<text term="issue" form="short"/>
|
||||
<number variable="issue" form="numeric"/>
|
||||
</group>
|
||||
</group>
|
||||
</macro>
|
||||
<macro name="title">
|
||||
<choose>
|
||||
<if type="bill book graphic legal_case legislation motion_picture song standard software" match="any">
|
||||
<text variable="title" font-style="italic"/>
|
||||
</if>
|
||||
<else>
|
||||
<text variable="title" quotes="true"/>
|
||||
</else>
|
||||
</choose>
|
||||
</macro>
|
||||
<macro name="publisher">
|
||||
<choose>
|
||||
<if type="bill book chapter graphic legal_case legislation motion_picture paper-conference song" match="any">
|
||||
<group delimiter=": ">
|
||||
<text variable="publisher-place"/>
|
||||
<text variable="publisher"/>
|
||||
</group>
|
||||
</if>
|
||||
<else>
|
||||
<group delimiter=", ">
|
||||
<text variable="publisher"/>
|
||||
<text variable="publisher-place"/>
|
||||
</group>
|
||||
</else>
|
||||
</choose>
|
||||
</macro>
|
||||
<macro name="event">
|
||||
<choose>
|
||||
<!-- Published Conference Paper -->
|
||||
<if type="paper-conference speech" match="any">
|
||||
<choose>
|
||||
<if variable="container-title" match="any">
|
||||
<group delimiter=" ">
|
||||
<text term="in"/>
|
||||
<text variable="container-title" font-style="italic"/>
|
||||
</group>
|
||||
</if>
|
||||
<!-- Unpublished Conference Paper -->
|
||||
<else>
|
||||
<group delimiter=" ">
|
||||
<text term="presented at"/>
|
||||
<text variable="event"/>
|
||||
</group>
|
||||
</else>
|
||||
</choose>
|
||||
</if>
|
||||
</choose>
|
||||
</macro>
|
||||
<macro name="access">
|
||||
<choose>
|
||||
<if type="webpage post post-weblog" match="any">
|
||||
<!-- https://url.com/ (accessed Mon. DD, YYYY). -->
|
||||
<choose>
|
||||
<if variable="URL">
|
||||
<group delimiter=". " prefix=" ">
|
||||
<group delimiter=": ">
|
||||
<text term="accessed" text-case="capitalize-first"/>
|
||||
<date variable="accessed" form="text"/>
|
||||
</group>
|
||||
<text term="online" prefix="[" suffix="]" text-case="capitalize-first"/>
|
||||
<group delimiter=": ">
|
||||
<text term="available at" text-case="capitalize-first"/>
|
||||
<text variable="URL"/>
|
||||
</group>
|
||||
</group>
|
||||
</if>
|
||||
</choose>
|
||||
</if>
|
||||
<else-if match="any" variable="DOI">
|
||||
<!-- doi: 10.1000/xyz123. -->
|
||||
<text variable="DOI" prefix=" doi: " suffix="."/>
|
||||
</else-if>
|
||||
<else-if variable="URL">
|
||||
<!-- Accessed: Mon. DD, YYYY. [Medium]. Available: https://URL.com/ -->
|
||||
<group delimiter=". " prefix=" " suffix=". ">
|
||||
<!-- Accessed: Mon. DD, YYYY. -->
|
||||
<group delimiter=": ">
|
||||
<text term="accessed" text-case="capitalize-first"/>
|
||||
<date variable="accessed" form="text"/>
|
||||
</group>
|
||||
<!-- [Online Video]. -->
|
||||
<group prefix="[" suffix="]" delimiter=" ">
|
||||
<choose>
|
||||
<if variable="medium" match="any">
|
||||
<text variable="medium" text-case="capitalize-first"/>
|
||||
</if>
|
||||
<else>
|
||||
<text term="online" text-case="capitalize-first"/>
|
||||
<choose>
|
||||
<if type="motion_picture">
|
||||
<text term="video" text-case="capitalize-first"/>
|
||||
</if>
|
||||
</choose>
|
||||
</else>
|
||||
</choose>
|
||||
</group>
|
||||
</group>
|
||||
<!-- Available: https://URL.com/ -->
|
||||
<group delimiter=": " prefix=" ">
|
||||
<text term="available at" text-case="capitalize-first"/>
|
||||
<text variable="URL"/>
|
||||
</group>
|
||||
</else-if>
|
||||
</choose>
|
||||
</macro>
|
||||
<macro name="page">
|
||||
<choose>
|
||||
<if type="article-journal" variable="number" match="all">
|
||||
<group delimiter=" ">
|
||||
<text value="Art."/>
|
||||
<text term="issue" form="short"/>
|
||||
<text variable="number"/>
|
||||
</group>
|
||||
</if>
|
||||
<else>
|
||||
<group delimiter=" ">
|
||||
<label variable="page" form="short"/>
|
||||
<text variable="page"/>
|
||||
</group>
|
||||
</else>
|
||||
</choose>
|
||||
</macro>
|
||||
<macro name="citation-locator">
|
||||
<group delimiter=" ">
|
||||
<choose>
|
||||
<if locator="page">
|
||||
<label variable="locator" form="short"/>
|
||||
</if>
|
||||
<else>
|
||||
<label variable="locator" form="short" text-case="capitalize-first"/>
|
||||
</else>
|
||||
</choose>
|
||||
<text variable="locator"/>
|
||||
</group>
|
||||
</macro>
|
||||
<macro name="geographic-location">
|
||||
<group delimiter=", " suffix=".">
|
||||
<choose>
|
||||
<if variable="publisher-place">
|
||||
<text variable="publisher-place" text-case="title"/>
|
||||
</if>
|
||||
<else-if variable="event-place">
|
||||
<text variable="event-place" text-case="title"/>
|
||||
</else-if>
|
||||
</choose>
|
||||
</group>
|
||||
</macro>
|
||||
<!-- Series -->
|
||||
<macro name="collection">
|
||||
<choose>
|
||||
<if variable="collection-title" match="any">
|
||||
<text term="in" suffix=" "/>
|
||||
<group delimiter=", " suffix=". ">
|
||||
<text variable="collection-title"/>
|
||||
<text variable="collection-number" prefix="no. "/>
|
||||
<text variable="volume" prefix="vol. "/>
|
||||
</group>
|
||||
</if>
|
||||
</choose>
|
||||
</macro>
|
||||
<!-- Citation -->
|
||||
<citation>
|
||||
<sort>
|
||||
<key variable="citation-number"/>
|
||||
</sort>
|
||||
<layout delimiter=", ">
|
||||
<group prefix="[" suffix="]" delimiter=", ">
|
||||
<text variable="citation-number"/>
|
||||
<text macro="citation-locator"/>
|
||||
</group>
|
||||
</layout>
|
||||
</citation>
|
||||
<!-- Bibliography -->
|
||||
<bibliography entry-spacing="0" second-field-align="flush">
|
||||
<layout>
|
||||
<!-- Citation Number -->
|
||||
<text variable="citation-number" prefix="[" suffix="]"/>
|
||||
<!-- Author(s) -->
|
||||
<text macro="author" suffix=", "/>
|
||||
<!-- Rest of Citation -->
|
||||
<choose>
|
||||
<!-- Specific Formats -->
|
||||
<if type="article-journal">
|
||||
<group delimiter=", ">
|
||||
<text macro="title"/>
|
||||
<text variable="container-title" font-style="italic" form="short"/>
|
||||
<text macro="locators"/>
|
||||
<text macro="page"/>
|
||||
<text macro="issued"/>
|
||||
<text macro="status"/>
|
||||
</group>
|
||||
<choose>
|
||||
<if variable="URL DOI" match="none">
|
||||
<text value="."/>
|
||||
</if>
|
||||
<else>
|
||||
<text value=","/>
|
||||
</else>
|
||||
</choose>
|
||||
<text macro="access"/>
|
||||
</if>
|
||||
<else-if type="paper-conference speech" match="any">
|
||||
<group delimiter=", " suffix=", ">
|
||||
<text macro="title"/>
|
||||
<text macro="event"/>
|
||||
<text macro="editor"/>
|
||||
</group>
|
||||
<text macro="collection"/>
|
||||
<group delimiter=", " suffix=".">
|
||||
<text macro="publisher"/>
|
||||
<text macro="issued"/>
|
||||
<text macro="page"/>
|
||||
<text macro="status"/>
|
||||
</group>
|
||||
<text macro="access"/>
|
||||
</else-if>
|
||||
<else-if type="chapter">
|
||||
<group delimiter=", " suffix=".">
|
||||
<text macro="title"/>
|
||||
<group delimiter=" ">
|
||||
<text term="in" suffix=" "/>
|
||||
<text variable="container-title" font-style="italic"/>
|
||||
</group>
|
||||
<text macro="locators"/>
|
||||
<text macro="editor"/>
|
||||
<text macro="collection"/>
|
||||
<text macro="publisher"/>
|
||||
<text macro="issued"/>
|
||||
<group delimiter=" ">
|
||||
<label variable="chapter-number" form="short"/>
|
||||
<text variable="chapter-number"/>
|
||||
</group>
|
||||
<text macro="page"/>
|
||||
</group>
|
||||
<text macro="access"/>
|
||||
</else-if>
|
||||
<else-if type="report">
|
||||
<group delimiter=", " suffix=".">
|
||||
<text macro="title"/>
|
||||
<text macro="publisher"/>
|
||||
<group delimiter=" ">
|
||||
<text variable="genre"/>
|
||||
<text variable="number"/>
|
||||
</group>
|
||||
<text macro="issued"/>
|
||||
</group>
|
||||
<text macro="access"/>
|
||||
</else-if>
|
||||
<else-if type="thesis">
|
||||
<group delimiter=", " suffix=".">
|
||||
<text macro="title"/>
|
||||
<text variable="genre"/>
|
||||
<text macro="publisher"/>
|
||||
<text macro="issued"/>
|
||||
</group>
|
||||
<text macro="access"/>
|
||||
</else-if>
|
||||
<else-if type="software">
|
||||
<group delimiter=". " suffix=".">
|
||||
<text macro="title"/>
|
||||
<text macro="issued" prefix="(" suffix=")"/>
|
||||
<text variable="genre"/>
|
||||
<text macro="publisher"/>
|
||||
</group>
|
||||
<text macro="access"/>
|
||||
</else-if>
|
||||
<else-if type="article">
|
||||
<group delimiter=", " suffix=".">
|
||||
<text macro="title"/>
|
||||
<text macro="issued"/>
|
||||
<group delimiter=": ">
|
||||
<text macro="publisher" font-style="italic"/>
|
||||
<text variable="number"/>
|
||||
</group>
|
||||
</group>
|
||||
<text macro="access"/>
|
||||
</else-if>
|
||||
<else-if type="webpage post-weblog post" match="any">
|
||||
<group delimiter=", " suffix=".">
|
||||
<text macro="title"/>
|
||||
<text variable="container-title"/>
|
||||
</group>
|
||||
<text macro="access"/>
|
||||
</else-if>
|
||||
<else-if type="patent">
|
||||
<group delimiter=", ">
|
||||
<text macro="title"/>
|
||||
<text variable="number"/>
|
||||
<text macro="issued"/>
|
||||
</group>
|
||||
<text macro="access"/>
|
||||
</else-if>
|
||||
<!-- Online Video -->
|
||||
<else-if type="motion_picture">
|
||||
<text macro="geographic-location" suffix=". "/>
|
||||
<group delimiter=", " suffix=".">
|
||||
<text macro="title"/>
|
||||
<text macro="issued"/>
|
||||
</group>
|
||||
<text macro="access"/>
|
||||
</else-if>
|
||||
<else-if type="standard">
|
||||
<group delimiter=", " suffix=".">
|
||||
<text macro="title"/>
|
||||
<group delimiter=" ">
|
||||
<text variable="genre"/>
|
||||
<text variable="number"/>
|
||||
</group>
|
||||
<text macro="geographic-location"/>
|
||||
<text macro="issued"/>
|
||||
</group>
|
||||
<text macro="access"/>
|
||||
</else-if>
|
||||
<!-- Generic/Fallback Formats -->
|
||||
<else-if type="bill book graphic legal_case legislation report song" match="any">
|
||||
<group delimiter=", " suffix=". ">
|
||||
<text macro="title"/>
|
||||
<text macro="locators"/>
|
||||
</group>
|
||||
<text macro="collection"/>
|
||||
<group delimiter=", " suffix=".">
|
||||
<text macro="publisher"/>
|
||||
<text macro="issued"/>
|
||||
<text macro="page"/>
|
||||
</group>
|
||||
<text macro="access"/>
|
||||
</else-if>
|
||||
<else-if type="article-magazine article-newspaper broadcast interview manuscript map patent personal_communication song speech thesis webpage" match="any">
|
||||
<group delimiter=", " suffix=".">
|
||||
<text macro="title"/>
|
||||
<text variable="container-title" font-style="italic"/>
|
||||
<text macro="locators"/>
|
||||
<text macro="publisher"/>
|
||||
<text macro="page"/>
|
||||
<text macro="issued"/>
|
||||
</group>
|
||||
<text macro="access"/>
|
||||
</else-if>
|
||||
<else>
|
||||
<group delimiter=", " suffix=". ">
|
||||
<text macro="title"/>
|
||||
<text variable="container-title" font-style="italic"/>
|
||||
<text macro="locators"/>
|
||||
</group>
|
||||
<text macro="collection"/>
|
||||
<group delimiter=", " suffix=".">
|
||||
<text macro="publisher"/>
|
||||
<text macro="page"/>
|
||||
<text macro="issued"/>
|
||||
</group>
|
||||
<text macro="access"/>
|
||||
</else>
|
||||
</choose>
|
||||
</layout>
|
||||
</bibliography>
|
||||
</style>
|
||||
230
_extensions/sources/tgm.csl
Normal file
@@ -0,0 +1,230 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<style xmlns="http://purl.org/net/xbiblio/csl" class="note" default-locale="de-DE" version="1.0" demote-non-dropping-particle="sort-only">
|
||||
<info>
|
||||
<title>TGM Wien Diplomarbeit (Deutsch)</title>
|
||||
<id>http://www.zotero.org/styles/tgm-wien-diplom</id>
|
||||
<link href="http://www.zotero.org/styles/tgm-wien-diplom" rel="self"/>
|
||||
<link href="http://www.tgm.ac.at/" rel="documentation"/>
|
||||
<author>
|
||||
<name>Gabriele Schachinger</name>
|
||||
<email>gs@i-gs.at</email>
|
||||
</author>
|
||||
<category citation-format="note"/>
|
||||
<category field="social_science"/>
|
||||
<updated>2021-02-10T05:28:52+00:00</updated>
|
||||
<rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
|
||||
</info>
|
||||
<locale xml:lang="de">
|
||||
<terms>
|
||||
<term name="et-al">u.a.</term>
|
||||
<term name="and">und</term>
|
||||
<term name="retrieved">zugegriffen am</term>
|
||||
<term name="accessed">abgerufen am</term>
|
||||
<term name="ibid">ebenda</term>
|
||||
<term name="page" form="short">S.</term>
|
||||
<term name="section" form="short">Abs.</term>
|
||||
<term name="editor" form="verb-short">hrsg. von</term>
|
||||
<term name="editor" form="short">(Hrsg.)</term>
|
||||
</terms>
|
||||
</locale>
|
||||
<!--
|
||||
macro definitions
|
||||
author
|
||||
editor
|
||||
accessed
|
||||
date
|
||||
year-date
|
||||
pages
|
||||
point-locators
|
||||
point-locators-subsequent
|
||||
hasUrl
|
||||
-->
|
||||
<macro name="author">
|
||||
<names variable="author">
|
||||
<name and="text" name-as-sort-order="all" sort-separator=", " delimiter=", " delimiter-precedes-last="never"/>
|
||||
<label form="short" text-case="lowercase" strip-periods="true"/>
|
||||
<substitute>
|
||||
<names variable="editor"/>
|
||||
<names variable="translator"/>
|
||||
</substitute>
|
||||
</names>
|
||||
</macro>
|
||||
<macro name="editor">
|
||||
<names variable="editor translator">
|
||||
<name and="text" name-as-sort-order="all" sort-separator=", " delimiter=", " delimiter-precedes-last="never"/>
|
||||
<label form="short" prefix=" " strip-periods="true"/>
|
||||
</names>
|
||||
</macro>
|
||||
<macro name="accessed">
|
||||
<text term="accessed"/>
|
||||
<date variable="accessed" prefix=" ">
|
||||
<date-part form="numeric-leading-zeros" name="day" suffix="."/>
|
||||
<date-part form="numeric-leading-zeros" name="month" suffix="."/>
|
||||
<date-part form="long" name="year"/>
|
||||
</date>
|
||||
</macro>
|
||||
<macro name="date">
|
||||
<choose>
|
||||
<if variable="issued">
|
||||
<date variable="issued">
|
||||
<date-part form="numeric-leading-zeros" name="day" suffix="."/>
|
||||
<date-part form="numeric-leading-zeros" name="month" suffix="."/>
|
||||
<date-part form="long" name="year"/>
|
||||
</date>
|
||||
</if>
|
||||
<else>
|
||||
<date variable="accessed">
|
||||
<date-part form="numeric-leading-zeros" name="day" suffix="."/>
|
||||
<date-part form="numeric-leading-zeros" name="month" suffix="."/>
|
||||
<date-part form="long" name="year"/>
|
||||
</date>
|
||||
</else>
|
||||
</choose>
|
||||
</macro>
|
||||
<macro name="year-date">
|
||||
<choose>
|
||||
<if type="webpage">
|
||||
<choose>
|
||||
<if variable="issued">
|
||||
<date variable="issued">
|
||||
<date-part name="year"/>
|
||||
</date>
|
||||
</if>
|
||||
<else>
|
||||
<date variable="accessed">
|
||||
<date-part name="year"/>
|
||||
</date>
|
||||
</else>
|
||||
</choose>
|
||||
</if>
|
||||
<else>
|
||||
<date variable="issued">
|
||||
<date-part name="year"/>
|
||||
</date>
|
||||
</else>
|
||||
</choose>
|
||||
</macro>
|
||||
<macro name="pages">
|
||||
<label variable="locator" form="short" prefix=", " suffix=" "/>
|
||||
<text variable="page"/>
|
||||
</macro>
|
||||
<macro name="point-locators">
|
||||
<label variable="locator" form="short" prefix=", " suffix=" "/>
|
||||
<text variable="locator"/>
|
||||
</macro>
|
||||
<macro name="point-locators-subsequent">
|
||||
<group>
|
||||
<choose>
|
||||
<if locator="page" match="none">
|
||||
<choose>
|
||||
<if type="bill book graphic legal_case legislation motion_picture report song" match="any">
|
||||
<choose>
|
||||
<if variable="volume">
|
||||
<group>
|
||||
<text term="volume" form="short" text-case="lowercase" suffix=" "/>
|
||||
<number variable="volume" form="numeric"/>
|
||||
<label variable="locator" form="short" prefix=", "/>
|
||||
</group>
|
||||
</if>
|
||||
<else>
|
||||
<label variable="locator" form="short" suffix=" "/>
|
||||
</else>
|
||||
</choose>
|
||||
</if>
|
||||
</choose>
|
||||
</if>
|
||||
<else-if type="bill book graphic legal_case legislation motion_picture report song" match="any">
|
||||
<number variable="volume" form="numeric" suffix=":"/>
|
||||
</else-if>
|
||||
</choose>
|
||||
<label variable="locator" form="short" suffix=" "/>
|
||||
<text variable="locator"/>
|
||||
</group>
|
||||
</macro>
|
||||
<macro name="hasUrl">
|
||||
<choose>
|
||||
<if variable="URL" match="any">
|
||||
<text value="yes"/>
|
||||
</if>
|
||||
<else>
|
||||
<text value="no"/>
|
||||
</else>
|
||||
</choose>
|
||||
</macro>
|
||||
<citation et-al-min="3" et-al-use-first="1" disambiguate-add-year-suffix="true">
|
||||
<layout delimiter="; " suffix=".">
|
||||
<choose>
|
||||
<if position="ibid">
|
||||
<text term="ibid"/>
|
||||
<text macro="point-locators-subsequent" prefix=", "/>
|
||||
</if>
|
||||
<else>
|
||||
<text macro="author"/>
|
||||
<text macro="year-date" prefix=" (" suffix=")"/>
|
||||
<text macro="point-locators"/>
|
||||
<choose>
|
||||
<if variable="URL" match="any">
|
||||
<text value="(siehe Internet-/Intranetverzeichnis)" prefix=" "/>
|
||||
</if>
|
||||
</choose>
|
||||
</else>
|
||||
</choose>
|
||||
</layout>
|
||||
</citation>
|
||||
<bibliography hanging-indent="true">
|
||||
<sort>
|
||||
<key macro="hasUrl"/>
|
||||
<key macro="author"/>
|
||||
<key macro="date"/>
|
||||
</sort>
|
||||
<layout suffix=".">
|
||||
<group font-weight="bold">
|
||||
<text macro="author"/>
|
||||
<text macro="year-date" prefix=" (" suffix="):" font-weight="bold"/>
|
||||
</group>
|
||||
<text variable="title" prefix=" "/>
|
||||
<choose>
|
||||
<if type="webpage">
|
||||
<text macro="date" prefix=", "/>
|
||||
</if>
|
||||
<else-if type="speech" match="any">
|
||||
<text variable="publisher-place" prefix=", "/>
|
||||
<text macro="date" prefix=", "/>
|
||||
<text macro="accessed" prefix=", "/>
|
||||
</else-if>
|
||||
<else-if type="article-newspaper article-magazine article-journal" match="any">
|
||||
<text value=" In: " font-style="italic"/>
|
||||
<text variable="container-title" font-style="italic"/>
|
||||
<text variable="collection-title" prefix=", Reihe "/>
|
||||
<text variable="volume" prefix=", Band "/>
|
||||
<text variable="issue" prefix=", Ausgabe "/>
|
||||
<text macro="date" prefix=", "/>
|
||||
<text macro="pages"/>
|
||||
</else-if>
|
||||
<else-if type="chapter paper-conference" match="any">
|
||||
<text macro="editor" prefix=", in: " suffix=": "/>
|
||||
<text variable="container-title" font-style="italic"/>
|
||||
<text variable="publisher-place" prefix=", "/>
|
||||
<text variable="edition" prefix=", "/>
|
||||
<text macro="year-date" prefix=", "/>
|
||||
<text macro="pages"/>
|
||||
</else-if>
|
||||
<else>
|
||||
<text variable="edition" prefix=", "/>
|
||||
<text variable="publisher-place" prefix=", "/>
|
||||
<text variable="publisher" prefix=", "/>
|
||||
<text macro="year-date" prefix=", "/>
|
||||
</else>
|
||||
</choose>
|
||||
<choose>
|
||||
<if variable="URL">
|
||||
<text macro="accessed" prefix=", "/>
|
||||
<text variable="URL" prefix=", "/>
|
||||
</if>
|
||||
</choose>
|
||||
<text variable="ISBN" prefix=", ISBN: "/>
|
||||
<text variable="DOI" prefix=", DOI: "/>
|
||||
<text variable="note" prefix=", "/>
|
||||
</layout>
|
||||
</bibliography>
|
||||
</style>
|
||||
|
Before Width: | Height: | Size: 351 KiB After Width: | Height: | Size: 351 KiB |
BIN
images/04-Florian/ingenieur-antraege-liste.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
images/04-Florian/ingenieur-antrag-status.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 471 KiB After Width: | Height: | Size: 471 KiB |
BIN
images/04-Florian/premium-mitglieder-suche.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
images/05-Konstantin/BlackBoard-Details.png
Normal file
|
After Width: | Height: | Size: 508 KiB |
|
Before Width: | Height: | Size: 344 KiB After Width: | Height: | Size: 344 KiB |
BIN
images/preamble/logo.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
images/preamble/szu.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
7
metadata.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
include_toc: true
|
||||
gitea: none
|
||||
lang: de-DE
|
||||
csl: _extensions/sources/tgm.csl
|
||||
link-citations: true
|
||||
---
|
||||
27
pandoc-cmd.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
pandoc \
|
||||
00-praemble.md \
|
||||
01-toc.md \
|
||||
02-Allgemein.md \
|
||||
03-Diplomarbeitsbuch-individueller-teil-Adam-Gaiswinkler.md \
|
||||
04-Diplomarbeitsbuch-individueller-teil-Florian-Edlmayer.md \
|
||||
05-Diplomarbeitsbuch-individueller-teil-Konstantin-Hintermayer.md \
|
||||
97-tabellenverzeichnis.md \
|
||||
98-abbildungsverzeichnis.md \
|
||||
99-bibliographie.md \
|
||||
--number-sections \
|
||||
--lua-filter _extensions/diagram/diagram.lua \
|
||||
--syntax-definition _extensions/razor/razor.xml \
|
||||
--citeproc \
|
||||
--bibliography sources.bib \
|
||||
--csl _extensions/sources/tgm.csl \
|
||||
--include-before-body 00_preamble.tex \
|
||||
-H _extensions/header/header.tex \
|
||||
-V geometry:"top=2.5cm, bottom=2cm, left=3cm, right=2cm, a4paper" \
|
||||
-V toc-title="Inhaltsverzeichnis" \
|
||||
-V classoption=twoside \
|
||||
-V fontsize=12pt \
|
||||
-V linestretch=1.5 \
|
||||
-o diplomarbeitsbuch.pdf
|
||||
# --toc \
|
||||
# --toc-depth=3 \
|
||||
146
scripts/timetracking/main.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
func main() {
|
||||
url := os.Getenv("GITEA_URL")
|
||||
token := os.Getenv("GITEA_TOKEN")
|
||||
repoFullName := os.Getenv("GITEA_REPO") // format: owner/repo
|
||||
orgName := os.Getenv("GITEA_ORG")
|
||||
|
||||
if url == "" || token == "" {
|
||||
log.Fatal("Missing required environment variables: GITEA_URL, GITEA_TOKEN")
|
||||
}
|
||||
|
||||
if repoFullName == "" && orgName == "" {
|
||||
log.Fatal("Either GITEA_REPO or GITEA_ORG must be set")
|
||||
}
|
||||
|
||||
client, err := gitea.NewClient(url, gitea.SetToken(token))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create Gitea client: %v", err)
|
||||
}
|
||||
|
||||
var repos []*gitea.Repository
|
||||
if orgName != "" {
|
||||
fmt.Printf("Fetching all repositories for organization: %s\n", orgName)
|
||||
page := 1
|
||||
for {
|
||||
orgRepos, _, err := client.ListOrgRepos(orgName, gitea.ListOrgReposOptions{
|
||||
ListOptions: gitea.ListOptions{Page: page, PageSize: 50},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch organization repositories: %v", err)
|
||||
}
|
||||
if len(orgRepos) == 0 {
|
||||
break
|
||||
}
|
||||
repos = append(repos, orgRepos...)
|
||||
page++
|
||||
}
|
||||
} else {
|
||||
parts := strings.Split(repoFullName, "/")
|
||||
if len(parts) != 2 {
|
||||
log.Fatal("GITEA_REPO must be in format 'owner/repo'")
|
||||
}
|
||||
repo, _, err := client.GetRepo(parts[0], parts[1])
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch repository: %v", err)
|
||||
}
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
|
||||
// Start date for Sprint 1 (2024-10-02)
|
||||
startDate := time.Date(2025, 5, 2, 0, 0, 0, 0, time.Local)
|
||||
|
||||
// user -> bucketIndex -> hours
|
||||
data := make(map[string]map[int]float64)
|
||||
maxBucket := 0
|
||||
|
||||
for _, r := range repos {
|
||||
fmt.Printf("Processing repository: %s/%s\n", r.Owner.UserName, r.Name)
|
||||
page := 1
|
||||
for {
|
||||
times, _, err := client.ListRepoTrackedTimes(r.Owner.UserName, r.Name, gitea.ListTrackedTimesOptions{
|
||||
ListOptions: gitea.ListOptions{Page: page, PageSize: 50},
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: Failed to fetch time tracking for %s (page %d): %v\n", r.Name, page, err)
|
||||
break
|
||||
}
|
||||
if len(times) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, t := range times {
|
||||
if t.Created.Before(startDate) {
|
||||
continue
|
||||
}
|
||||
|
||||
daysSinceStart := int(t.Created.Sub(startDate).Hours() / 24)
|
||||
bucketIndex := daysSinceStart / 14
|
||||
if bucketIndex < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if bucketIndex > maxBucket {
|
||||
maxBucket = bucketIndex
|
||||
}
|
||||
|
||||
userName := t.UserName
|
||||
if data[userName] == nil {
|
||||
data[userName] = make(map[int]float64)
|
||||
}
|
||||
|
||||
hours := float64(t.Time) / 3600.0
|
||||
data[userName][bucketIndex] += hours
|
||||
}
|
||||
page++
|
||||
}
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
fmt.Println("No time tracking entries found.")
|
||||
return
|
||||
}
|
||||
|
||||
// Print Table
|
||||
users := make([]string, 0, len(data))
|
||||
for u := range data {
|
||||
users = append(users, u)
|
||||
}
|
||||
sort.Strings(users)
|
||||
|
||||
// Header
|
||||
fmt.Print("\nNutzer ")
|
||||
for i := 0; i <= maxBucket; i++ {
|
||||
start := startDate.AddDate(0, 0, i*14)
|
||||
fmt.Printf("| %s ", start.Format("02.01."))
|
||||
}
|
||||
fmt.Println("| Gesamt")
|
||||
fmt.Println(strings.Repeat("-", 14+(maxBucket+1)*10+8))
|
||||
|
||||
for _, u := range users {
|
||||
fmt.Printf("%-12s ", u)
|
||||
total := 0.0
|
||||
for i := 0; i <= maxBucket; i++ {
|
||||
h := data[u][i]
|
||||
total += h
|
||||
if h > 0 {
|
||||
fmt.Printf("| %7.1fh ", h)
|
||||
} else {
|
||||
fmt.Printf("| %8s ", "-")
|
||||
}
|
||||
}
|
||||
fmt.Printf("| %7.1fh\n", total)
|
||||
}
|
||||
}
|
||||
251
sources.bib
Normal file
@@ -0,0 +1,251 @@
|
||||
@online{ms_dependency_inversion,
|
||||
title = {Dependency Inversion},
|
||||
url = {https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles#dependency-inversion},
|
||||
author = {{Microsoft}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{logrocket_dependency_inversion,
|
||||
title = {Dependency Inversion Principle},
|
||||
url = {https://blog.logrocket.com/dependency-inversion-principle/},
|
||||
author = {{LogRocket}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{oodesign_dependency_inversion,
|
||||
title = {Dependency Inversion Principle},
|
||||
url = {https://www.oodesign.com/dependency-inversion-principle},
|
||||
author = {{OO Design}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{ms_di_overview,
|
||||
title = {Dependency Injection Overview},
|
||||
url = {https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/overview},
|
||||
author = {{Microsoft}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{oqtane_about,
|
||||
title = {About Oqtane},
|
||||
url = {https://www.oqtane.org/#about},
|
||||
author = {{Oqtane Foundation}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{oqtane_docs_extensions,
|
||||
title = {Oqtane Extensions},
|
||||
url = {https://docs.oqtane.org/dev/extensions/index.html},
|
||||
author = {{Oqtane Foundation}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{wikipedia_blazor,
|
||||
title = {Blazor},
|
||||
url = {https://en.wikipedia.org/wiki/Blazor},
|
||||
author = {{Wikipedia}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@article{bentley1985programming,
|
||||
title = {Programming pearls},
|
||||
author = {Jon Bentley},
|
||||
journal = {Communications of the ACM},
|
||||
volume = {28},
|
||||
number = {9},
|
||||
pages = {896--901},
|
||||
year = {1985},
|
||||
publisher = {ACM},
|
||||
doi = {10.1145/4284.315122}
|
||||
}
|
||||
|
||||
@online{fowler_dependency_injection,
|
||||
title = {Inversion of Control Containers and the Dependency Injection pattern},
|
||||
url = {https://martinfowler.com/articles/injection.html},
|
||||
author = {Martin Fowler},
|
||||
year = {2004},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{andrewlock_token_lifetime,
|
||||
title = {Implementing custom token providers for passwordless authentication in ASP.NET Core Identity},
|
||||
url = {https://andrewlock.net/implementing-custom-token-providers-for-passwordless-authentication-in-asp-net-core-identity/#changing-the-default-token-lifetime},
|
||||
author = {Andrew Lock},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{race_conditions_pdf,
|
||||
title = {Race Conditions},
|
||||
url = {https://www.cs.umd.edu/projects/syschat/raceConditions.pdf},
|
||||
author = {University of Maryland},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{gitea_docs,
|
||||
title = {Gitea Documentation},
|
||||
url = {https://docs.gitea.com/},
|
||||
author = {{Gitea}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{gitea_issue_tracker,
|
||||
title = {Gitea Issue Tracker Comparison},
|
||||
url = {https://docs.gitea.com/installation/comparison#issue-tracker},
|
||||
author = {{Gitea}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{gitea_pull_requests,
|
||||
title = {Gitea Pull Requests},
|
||||
url = {https://docs.gitea.com/usage/pull-request},
|
||||
author = {{Gitea}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{gitea_actions,
|
||||
title = {Gitea Actions Overview},
|
||||
url = {https://docs.gitea.com/usage/actions/overview},
|
||||
author = {{Gitea}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{gitea_packages,
|
||||
title = {Gitea Packages Overview},
|
||||
url = {https://docs.gitea.com/usage/packages/overview},
|
||||
author = {{Gitea}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{dsgvo,
|
||||
title = {Datenschutz-Grundverordnung (DSGVO)},
|
||||
url = {https://eur-lex.europa.eu/eli/reg/2016/679/},
|
||||
author = {{Europäische Union}},
|
||||
year = {2016},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{ris,
|
||||
title = {Rechtsinformationssystem des Bundes (RIS)},
|
||||
url = {https://www.ris.bka.gv.at/},
|
||||
author = {{Bundeskanzleramt Österreich}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{dsb,
|
||||
title = {Österreichische Datenschutzbehörde (DSB)},
|
||||
url = {https://dsb.gv.at/},
|
||||
author = {{Datenschutzbehörde}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{bsi_datensicherung,
|
||||
title = {Datensicherung und Datenverlust},
|
||||
url = {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},
|
||||
author = {{Bundesamt für Sicherheit in der Informationstechnik (BSI)}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{chatgpt_datensicherung,
|
||||
title = {Notwendigkeit der Datensicherung},
|
||||
url = {https://chatgpt.com/c/69a06632-e4fc-8384-bd6c-a543d7bbd00d},
|
||||
author = {{OpenAI (ChatGPT)}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{aspnet_core_docs,
|
||||
title = {ASP.NET Core Documentation},
|
||||
url = {https://learn.microsoft.com/en-us/aspnet/core/},
|
||||
author = {{Microsoft}},
|
||||
year = {2026},
|
||||
urldate = {2026-03-17}
|
||||
}
|
||||
|
||||
@online{blazor_docs,
|
||||
title = {Blazor Documentation},
|
||||
url = {https://learn.microsoft.com/en-us/aspnet/core/blazor/},
|
||||
author = {{Microsoft}},
|
||||
year = {2026},
|
||||
urldate = {2026-03-17}
|
||||
}
|
||||
|
||||
@online{ef_core_migrations,
|
||||
title = {Entity Framework Core – Migrations},
|
||||
url = {https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/},
|
||||
author = {{Microsoft}},
|
||||
year = {2026},
|
||||
urldate = {2026-03-17}
|
||||
}
|
||||
|
||||
@online{questpdf,
|
||||
title = {QuestPDF Open Source Library},
|
||||
url = {https://www.questpdf.com/},
|
||||
author = {{QuestPDF}},
|
||||
year = {2026},
|
||||
urldate = {2026-03-17}
|
||||
}
|
||||
|
||||
@online{bootstrap,
|
||||
title = {Bootstrap Framework},
|
||||
url = {https://getbootstrap.com/},
|
||||
author = {{Bootstrap}},
|
||||
year = {2026},
|
||||
urldate = {2026-03-17}
|
||||
}
|
||||
|
||||
@online{gitea_about,
|
||||
title = {Gitea – Open Source Git Service},
|
||||
url = {https://about.gitea.com/},
|
||||
author = {{Gitea}},
|
||||
year = {2026},
|
||||
urldate = {2026-03-17}
|
||||
}
|
||||
|
||||
@online{livedesign,
|
||||
title = {LiveDesign – Hosting, Design & Branding},
|
||||
url = {https://livedesign.at/},
|
||||
author = {{LiveDesign}},
|
||||
year = {2026},
|
||||
urldate = {2026-03-17}
|
||||
}
|
||||
|
||||
@online{bsi_grundschutz,
|
||||
title = {IT-Grundschutz-Kompendium – CON.3: Datensicherungskonzept},
|
||||
url = {https://www.bsi.bund.de/SharedDocs/Downloads/DE/BSI/Grundschutz/Kompendium/IT_Grundschutz_Kompendium_Edition2023.pdf},
|
||||
author = {{BSI}},
|
||||
year = {2023},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{rfc6749,
|
||||
title = {IETF RFC 6749 – The OAuth 2.0 Authorization Framework},
|
||||
url = {https://datatracker.ietf.org/doc/html/rfc6749},
|
||||
author = {{IETF}},
|
||||
year = {2012},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{linkedin_auth_flow,
|
||||
title = {LinkedIn Developer Documentation – Authorization Code Flow},
|
||||
url = {https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow},
|
||||
author = {{Microsoft}},
|
||||
year = {2026},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||