mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender.git
synced 2026-01-16 19:42:26 +01:00
feat: Introduce data manager service with PocketBase authentication and professor dashboard.
This commit is contained in:
@@ -19,12 +19,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, onMounted, onUnmounted } from "vue";
|
import { computed, ref, onMounted, onUnmounted } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useToast } from "primevue/usetoast";
|
||||||
import LocaleSwitcher from "./LocaleSwitcher.vue";
|
import LocaleSwitcher from "./LocaleSwitcher.vue";
|
||||||
import DarkModeSwitcher from "./DarkModeSwitcher.vue";
|
import DarkModeSwitcher from "./DarkModeSwitcher.vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { pb, login, logout } from "../service/pocketbase";
|
import { pb, login, logout } from "../service/pocketbase";
|
||||||
|
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
@@ -98,6 +100,34 @@ const items = computed(() => {
|
|||||||
return menuItems;
|
return menuItems;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function handleLogin() {
|
||||||
|
try {
|
||||||
|
await login();
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: t('toast.loginSuccess'),
|
||||||
|
detail: t('toast.loginSuccessDetail'),
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error?.message && error.message.includes('Login restricted')) {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: t('toast.loginError'),
|
||||||
|
detail: t('toast.loginRestricted'),
|
||||||
|
life: 5000
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: t('toast.loginError'),
|
||||||
|
detail: error?.message || t('toast.loginErrorDetail'),
|
||||||
|
life: 5000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleDarkModeToggled(isDarkVar: boolean) {
|
function handleDarkModeToggled(isDarkVar: boolean) {
|
||||||
// Do something with isDark value
|
// Do something with isDark value
|
||||||
// For example, update the root isDark value
|
// For example, update the root isDark value
|
||||||
@@ -173,7 +203,7 @@ function handleDarkModeToggled(isDarkVar: boolean) {
|
|||||||
label="Login"
|
label="Login"
|
||||||
icon="pi pi-sign-in"
|
icon="pi pi-sign-in"
|
||||||
class="p-button-text"
|
class="p-button-text"
|
||||||
@click="login"
|
@click="handleLogin"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
v-else
|
v-else
|
||||||
|
|||||||
@@ -9,6 +9,13 @@
|
|||||||
"description": "Dein individueller Stundenplan mit Sportevents und Prüfungen. Finde kommende Veranstaltungen oder freie Räume zum Lernen und Arbeiten.",
|
"description": "Dein individueller Stundenplan mit Sportevents und Prüfungen. Finde kommende Veranstaltungen oder freie Räume zum Lernen und Arbeiten.",
|
||||||
"english": "Englisch",
|
"english": "Englisch",
|
||||||
"german": "Deutsch",
|
"german": "Deutsch",
|
||||||
|
"toast": {
|
||||||
|
"loginError": "Login fehlgeschlagen",
|
||||||
|
"loginRestricted": "Login nur für Mitarbeitende der HTWK Leipzig.",
|
||||||
|
"loginSuccess": "Login erfolgreich",
|
||||||
|
"loginSuccessDetail": "Du kannst nun deinen Kalender erstellen.",
|
||||||
|
"loginErrorDetail": "Fehler beim Login. Bitte versuche es erneut."
|
||||||
|
},
|
||||||
"courseSelection": {
|
"courseSelection": {
|
||||||
"headline": "Willkommen beim HTWKalender",
|
"headline": "Willkommen beim HTWKalender",
|
||||||
"winterSemester": "Wintersemester",
|
"winterSemester": "Wintersemester",
|
||||||
|
|||||||
@@ -9,6 +9,13 @@
|
|||||||
"description": "Your individual timetable with sports events and exams. Find upcoming events or free rooms for studying and working.",
|
"description": "Your individual timetable with sports events and exams. Find upcoming events or free rooms for studying and working.",
|
||||||
"english": "English",
|
"english": "English",
|
||||||
"german": "German",
|
"german": "German",
|
||||||
|
"toast": {
|
||||||
|
"loginError": "login failed",
|
||||||
|
"loginRestricted": "login restricted to htwk employees",
|
||||||
|
"loginSuccess": "login successful",
|
||||||
|
"loginSuccessDetail": "you can now create your calendar.",
|
||||||
|
"loginErrorDetail": "login failed. please try again."
|
||||||
|
},
|
||||||
"courseSelection": {
|
"courseSelection": {
|
||||||
"headline": "welcome to HTWKalender",
|
"headline": "welcome to HTWKalender",
|
||||||
"winterSemester": "winter semester",
|
"winterSemester": "winter semester",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const pb = new PocketBase("/");
|
|||||||
// Production: https://cal.htwk-leipzig.de/callback
|
// Production: https://cal.htwk-leipzig.de/callback
|
||||||
|
|
||||||
export const login = async () => {
|
export const login = async () => {
|
||||||
await pb.collection('users').authWithOAuth2({ provider: 'oidc' });
|
return await pb.collection('users').authWithOAuth2({ provider: 'oidc', scopes: ['openid', 'email', 'profile'] });
|
||||||
}
|
}
|
||||||
|
|
||||||
export const logout = () => {
|
export const logout = () => {
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ onMounted(async () => {
|
|||||||
return (b.confidenceScore || 0) - (a.confidenceScore || 0); // Descending order
|
return (b.confidenceScore || 0) - (a.confidenceScore || 0); // Descending order
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Clear temporary entries and set actual modules
|
||||||
modules.value = sortedModules;
|
modules.value = sortedModules;
|
||||||
|
|
||||||
// Pre-select modules with confidence score >= 0.7 (good match or better)
|
// Pre-select modules with confidence score >= 0.7 (good match or better)
|
||||||
@@ -77,12 +78,15 @@ onMounted(async () => {
|
|||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
console.error("Failed to fetch modules", error);
|
console.error("Failed to fetch modules", error);
|
||||||
const err = error as { status?: number; message?: string };
|
const err = error as { status?: number; message?: string };
|
||||||
|
// Clear temporary entries on error
|
||||||
|
modules.value = [];
|
||||||
// If unauthorized, redirect to home
|
// If unauthorized, redirect to home
|
||||||
if (err.status === 401 || err.message?.includes("401")) {
|
if (err.status === 401 || err.message?.includes("401")) {
|
||||||
pb.authStore.clear();
|
pb.authStore.clear();
|
||||||
router.push("/");
|
router.push("/");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
// Always disable loading animation
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ func setupApp() *pocketbase.PocketBase {
|
|||||||
})
|
})
|
||||||
service.AddRoutes(services)
|
service.AddRoutes(services)
|
||||||
service.AddSchedules(services)
|
service.AddSchedules(services)
|
||||||
|
service.AddHooks(app)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|||||||
34
services/data-manager/service/hooks.go
Normal file
34
services/data-manager/service/hooks.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pocketbase/pocketbase"
|
||||||
|
"github.com/pocketbase/pocketbase/apis"
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddHooks(app *pocketbase.PocketBase) {
|
||||||
|
app.OnRecordAuthWithOAuth2Request("users").BindFunc(func(e *core.RecordAuthWithOAuth2RequestEvent) error {
|
||||||
|
email := e.OAuth2User.Email
|
||||||
|
|
||||||
|
// If email is not in the main field, try to extract it from RawUser
|
||||||
|
if email == "" {
|
||||||
|
if rawEmail, ok := e.OAuth2User.RawUser["email"].(string); ok {
|
||||||
|
email = rawEmail
|
||||||
|
// Explicitly set the email on the OAuth2User so PocketBase uses it
|
||||||
|
e.OAuth2User.Email = rawEmail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if email == "" {
|
||||||
|
return apis.NewBadRequestError("No email received from OAuth2 provider. Please ensure your account has an email address and the 'email' scope is granted.", nil)
|
||||||
|
}
|
||||||
|
// Restrict login to @htwk-leipzig.de employees only (not students)
|
||||||
|
if !strings.HasSuffix(email, "@htwk-leipzig.de") {
|
||||||
|
return apis.NewBadRequestError("Login restricted to @htwk-leipzig.de emails. Students (@stud.htwk-leipzig.de) are not allowed.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.Next()
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user