mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender-pwa.git
synced 2025-08-12 14:43:52 +02:00
Merge pull request #100 from HTWK-Leipzig/94-delete-calendar-token
94 delete calendar token
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"htwkalender/service/db"
|
||||||
"htwkalender/service/events"
|
"htwkalender/service/events"
|
||||||
v1 "htwkalender/service/fetch/v1"
|
v1 "htwkalender/service/fetch/v1"
|
||||||
v2 "htwkalender/service/fetch/v2"
|
v2 "htwkalender/service/fetch/v2"
|
||||||
@@ -170,6 +171,29 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
|
_, err := e.Router.AddRoute(echo.Route{
|
||||||
|
Method: http.MethodDelete,
|
||||||
|
Path: "/api/feed",
|
||||||
|
Handler: func(c echo.Context) error {
|
||||||
|
token := c.QueryParam("token")
|
||||||
|
err := db.DeleteFeed(app.Dao(), token)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusNotFound, err)
|
||||||
|
} else {
|
||||||
|
return c.JSON(http.StatusOK, "Feed deleted")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Middlewares: []echo.MiddlewareFunc{
|
||||||
|
apis.ActivityLogger(app),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
_, err := e.Router.AddRoute(echo.Route{
|
_, err := e.Router.AddRoute(echo.Route{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase"
|
"github.com/pocketbase/pocketbase"
|
||||||
"github.com/pocketbase/pocketbase/daos"
|
"github.com/pocketbase/pocketbase/daos"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
@@ -39,6 +41,25 @@ func FindFeedByToken(token string, app *pocketbase.PocketBase) (*model.Feed, err
|
|||||||
return &feed, err
|
return &feed, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteFeed(db *daos.Dao, feedId string) error {
|
||||||
|
|
||||||
|
sqlResult, err := db.DB().Delete("feeds", dbx.NewExp("id = {:id}", dbx.Params{"id": feedId})).Execute()
|
||||||
|
var deletedRows int64
|
||||||
|
if sqlResult != nil {
|
||||||
|
deletedRows, _ = sqlResult.RowsAffected()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
if deletedRows == 0 {
|
||||||
|
return errors.New("No feed with id " + feedId + " found")
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func GetAllFeeds(db *daos.Dao) ([]model.Feed, error) {
|
func GetAllFeeds(db *daos.Dao) ([]model.Feed, error) {
|
||||||
var feeds []model.Feed
|
var feeds []model.Feed
|
||||||
err := db.DB().Select("*").From("feeds").All(&feeds)
|
err := db.DB().Select("*").From("feeds").All(&feeds)
|
||||||
|
55
backend/service/db/dbFeeds_test.go
Normal file
55
backend/service/db/dbFeeds_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pocketbase/pocketbase/daos"
|
||||||
|
"github.com/pocketbase/pocketbase/tests"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const testDataDir = "./mockData"
|
||||||
|
|
||||||
|
func TestDeleteFeed(t *testing.T) {
|
||||||
|
|
||||||
|
setupTestApp := func(t *testing.T) *daos.Dao {
|
||||||
|
testApp, err := tests.NewTestApp(testDataDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
dao := daos.New(testApp.Dao().DB())
|
||||||
|
return dao
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
db *daos.Dao
|
||||||
|
feedId string
|
||||||
|
}
|
||||||
|
testsCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "TestDeleteFeed",
|
||||||
|
args: args{
|
||||||
|
db: setupTestApp(t),
|
||||||
|
feedId: "fkoqti06ohlnsb8",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TestDeleteFeedNotExisting",
|
||||||
|
args: args{
|
||||||
|
db: setupTestApp(t),
|
||||||
|
feedId: "test324",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range testsCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := DeleteFeed(tt.args.db, tt.args.feedId); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("DeleteFeed() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
BIN
backend/service/db/mockData/data.db
Normal file
BIN
backend/service/db/mockData/data.db
Normal file
Binary file not shown.
BIN
backend/service/db/mockData/logs.db
Normal file
BIN
backend/service/db/mockData/logs.db
Normal file
Binary file not shown.
@@ -19,6 +19,7 @@ const isDisabled = (routeName: RouteRecordName | null | undefined) => {
|
|||||||
<RouterView />
|
<RouterView />
|
||||||
<!-- show CalendarPreview but only on specific router views -->
|
<!-- show CalendarPreview but only on specific router views -->
|
||||||
<CalendarPreview v-if="isDisabled($route.name)"/>
|
<CalendarPreview v-if="isDisabled($route.name)"/>
|
||||||
|
<Toast />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@@ -38,3 +38,18 @@ export async function saveIndividualFeed(
|
|||||||
});
|
});
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteIndividualFeed(token: string): Promise<void> {
|
||||||
|
await fetch("/api/feed?token=" + token, {
|
||||||
|
method: "DELETE",
|
||||||
|
}).then ((response) => {
|
||||||
|
if (response.ok) {
|
||||||
|
return Promise.resolve(response);
|
||||||
|
} else {
|
||||||
|
return Promise.reject(response);
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
@@ -79,7 +79,7 @@ const actions = [
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Toast />
|
|
||||||
<div class="flex flex-column">
|
<div class="flex flex-column">
|
||||||
<div class="flex align-items-center justify-content-center h-4rem m-2">
|
<div class="flex align-items-center justify-content-center h-4rem m-2">
|
||||||
<h2>
|
<h2>
|
||||||
|
@@ -46,8 +46,7 @@ const items = computed(() => [
|
|||||||
height="40"
|
height="40"
|
||||||
src="../../public/htwk.svg"
|
src="../../public/htwk.svg"
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
class="h-10 w-10 mr-6"
|
class="h-10 w-10 mr-3"
|
||||||
style="margin-left: 1rem"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #item="{ item }">
|
<template #item="{ item }">
|
||||||
|
@@ -73,22 +73,6 @@ const onSelectAllChange = (event: MultiSelectAllChangeEvent) => {
|
|||||||
function selectChange() {
|
function selectChange() {
|
||||||
selectAll.value = selectedModules.value.length === modules.value.length;
|
selectAll.value = selectedModules.value.length === modules.value.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function itemsLabel(selectedModules: Module[]): string {
|
|
||||||
return (selectedModules ? selectedModules.length : 0) != 1
|
|
||||||
? t("additionalModules.modules")
|
|
||||||
: t("additionalModules.module");
|
|
||||||
}
|
|
||||||
|
|
||||||
function itemsLabelWithNumber(selectedModules: Module[]): string {
|
|
||||||
return (
|
|
||||||
selectedModules.length.toString() +
|
|
||||||
" " +
|
|
||||||
itemsLabel(selectedModules) +
|
|
||||||
" " +
|
|
||||||
t("additionalModules.dropDownFooterSelected")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -111,7 +95,7 @@ function itemsLabelWithNumber(selectedModules: Module[]): string {
|
|||||||
:placeholder="$t('additionalModules.dropDown')"
|
:placeholder="$t('additionalModules.dropDown')"
|
||||||
:auto-filter-focus="true"
|
:auto-filter-focus="true"
|
||||||
:show-toggle-all="false"
|
:show-toggle-all="false"
|
||||||
:selected-items-label="itemsLabelWithNumber(selectedModules)"
|
:selected-items-label="$t('additionalModules.footerModulesSelected', { count: selectedModules.length ?? 0 })"
|
||||||
@change="selectChange()"
|
@change="selectChange()"
|
||||||
@selectall-change="onSelectAllChange($event)"
|
@selectall-change="onSelectAllChange($event)"
|
||||||
>
|
>
|
||||||
@@ -138,11 +122,7 @@ function itemsLabelWithNumber(selectedModules: Module[]): string {
|
|||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="py-2 px-3">
|
<div class="py-2 px-3">
|
||||||
<b>{{ selectedModules ? selectedModules.length : 0 }}</b>
|
{{ t('additionalModules.footerModulesSelected', { count: selectedModules.length ?? 0 }) }}
|
||||||
item{{
|
|
||||||
(selectedModules ? selectedModules.length : 0) > 1 ? "s" : ""
|
|
||||||
}}
|
|
||||||
selected.
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MultiSelect>
|
</MultiSelect>
|
||||||
|
@@ -3,15 +3,20 @@ import { computed, Ref, ref } from "vue";
|
|||||||
import { Module } from "../../model/module.ts";
|
import { Module } from "../../model/module.ts";
|
||||||
import moduleStore from "../../store/moduleStore";
|
import moduleStore from "../../store/moduleStore";
|
||||||
import { fetchAllModules } from "../../api/fetchCourse.ts";
|
import { fetchAllModules } from "../../api/fetchCourse.ts";
|
||||||
import { saveIndividualFeed } from "../../api/createFeed.ts";
|
import {deleteIndividualFeed, saveIndividualFeed} from "../../api/createFeed.ts";
|
||||||
import tokenStore from "../../store/tokenStore";
|
import tokenStore from "../../store/tokenStore";
|
||||||
import router from "../../router";
|
import router from "../../router";
|
||||||
import ModuleTemplateDialog from "../ModuleTemplateDialog.vue";
|
import ModuleTemplateDialog from "../ModuleTemplateDialog.vue";
|
||||||
import { onlyWhitespace } from "../../helpers/strings.ts";
|
import { onlyWhitespace } from "../../helpers/strings.ts";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useToast } from "primevue/usetoast";
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
const visible = ref(false);
|
||||||
|
|
||||||
const store = moduleStore();
|
const store = moduleStore();
|
||||||
|
|
||||||
const tableData = computed(() =>
|
const tableData = computed(() =>
|
||||||
store.getAllModules().map((module: Module) => {
|
store.getAllModules().map((module: Module) => {
|
||||||
return {
|
return {
|
||||||
@@ -21,7 +26,7 @@ const tableData = computed(() =>
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const columns = ref([
|
const columns = computed(() => [
|
||||||
{ field: "Course", header: t("moduleInformation.course") },
|
{ field: "Course", header: t("moduleInformation.course") },
|
||||||
{ field: "Module", header: t("moduleInformation.module") },
|
{ field: "Module", header: t("moduleInformation.module") },
|
||||||
{ field: "Reminder", header: t("renameModules.reminder") },
|
{ field: "Reminder", header: t("renameModules.reminder") },
|
||||||
@@ -49,6 +54,31 @@ async function finalStep() {
|
|||||||
await saveIndividualFeed(tokenStore().token, store.getAllModules());
|
await saveIndividualFeed(tokenStore().token, store.getAllModules());
|
||||||
await router.push("/calendar-link");
|
await router.push("/calendar-link");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteFeed() {
|
||||||
|
deleteIndividualFeed(tokenStore().token).then(
|
||||||
|
() => {
|
||||||
|
toast.add({
|
||||||
|
severity: "success",
|
||||||
|
summary: t('editCalendarView.toast.success'),
|
||||||
|
detail: t('editCalendarView.toast.successDetail'),
|
||||||
|
life: 3000,
|
||||||
|
});
|
||||||
|
visible.value = false;
|
||||||
|
router.push("/");
|
||||||
|
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
toast.add({
|
||||||
|
severity: "error",
|
||||||
|
summary: t('editCalendarView.toast.error'),
|
||||||
|
detail: t('editCalendarView.toast.errorDetail'),
|
||||||
|
life: 3000,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -150,13 +180,28 @@ async function finalStep() {
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
<template #footer>
|
||||||
|
<div class="card flex align-items-center justify-content-between border-round m-2">
|
||||||
|
<Button type="button" severity="danger" outlined @click="visible = true" icon="pi pi-trash" :label="$t('editCalendarView.delete')"/>
|
||||||
|
<Button type="button" severity="info" outlined @click="router.push('edit-additional-modules')" icon="pi pi-plus" :label="$t('editCalendarView.addModules')"/>
|
||||||
|
<Button type="button" severity="success" outlined @click="finalStep()" icon="pi pi-save" :label="$t('editCalendarView.save')"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
</div>
|
</div>
|
||||||
<div
|
</div>
|
||||||
class="flex align-items-center justify-content-center border-round m-2"
|
<div class="card flex justify-content-center">
|
||||||
>
|
<Dialog v-model:visible="visible" modal header="Header" :style="{ width: '50rem' }" :breakpoints="{ '1199px': '75vw', '575px': '90vw' }">
|
||||||
<Button @click="finalStep()">{{ $t("renameModules.nextStep") }}</Button>
|
<template #header>
|
||||||
</div>
|
<div class="inline-flex align-items-center justify-content-center gap-2">
|
||||||
|
<span class="font-bold white-space-nowrap">{{ $t('editCalendarView.dialog.headline') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<p class="m-0">{{ $t('editCalendarView.dialog.subTitle') }}</p>
|
||||||
|
<template #footer>
|
||||||
|
<Button :label="$t('editCalendarView.dialog.delete')" severity="danger" icon="pi pi-trash" @click="deleteFeed()" autofocus />
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -52,7 +52,21 @@
|
|||||||
"headline": "Bearbeite deinen HTWKalender",
|
"headline": "Bearbeite deinen HTWKalender",
|
||||||
"subTitle": "Füge deinen Link oder Token ein um den Kalender zu bearbeiten",
|
"subTitle": "Füge deinen Link oder Token ein um den Kalender zu bearbeiten",
|
||||||
"loadCalendar": "Kalender laden",
|
"loadCalendar": "Kalender laden",
|
||||||
"noCalendarFound": "Keinen Kalender gefunden"
|
"noCalendarFound": "Keinen Kalender gefunden",
|
||||||
|
"save": "Speichern",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"addModules": "Module hinzufügen",
|
||||||
|
"dialog": {
|
||||||
|
"headline": "Kalender löschen",
|
||||||
|
"subTitle": "Sind Sie sicher, dass Sie Ihren Kalender löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.\nBitte entfernen Sie den Kalender anschließend aus Ihrer Kalender-App.",
|
||||||
|
"delete": "Löschen"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"success": "Erfolg",
|
||||||
|
"error": "Fehler",
|
||||||
|
"successDetail": "Kalender erfolgreich gelöscht",
|
||||||
|
"errorDetail": "Fehler beim Löschen des Kalenders"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"additionalModules": {
|
"additionalModules": {
|
||||||
"subTitle": "Füge weitere Module hinzu die nicht in deinem Studiengang enthalten sind.",
|
"subTitle": "Füge weitere Module hinzu die nicht in deinem Studiengang enthalten sind.",
|
||||||
|
@@ -52,7 +52,21 @@
|
|||||||
"headline": "edit your HTWKalender",
|
"headline": "edit your HTWKalender",
|
||||||
"subTitle": "please enter your link or calendar token",
|
"subTitle": "please enter your link or calendar token",
|
||||||
"loadCalendar": "load calendar",
|
"loadCalendar": "load calendar",
|
||||||
"noCalendarFound": "no calendar found"
|
"noCalendarFound": "no calendar found",
|
||||||
|
"save": "save",
|
||||||
|
"delete": "delete",
|
||||||
|
"addModules": "add modules",
|
||||||
|
"dialog": {
|
||||||
|
"headline": "delete calendar",
|
||||||
|
"subTitle": "Are you sure you want to delete your calendar? This action cannot be undone.\nPlease remove the calendar afterwords from your calendar app.",
|
||||||
|
"delete": "delete"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"success": "Success",
|
||||||
|
"error": "Error",
|
||||||
|
"successDetail": "Calendar successfully deleted",
|
||||||
|
"errorDetail": "Calendar could not be deleted"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"additionalModules": {
|
"additionalModules": {
|
||||||
"subTitle": "Select additional modules that are not listed in the regular semester for your course",
|
"subTitle": "Select additional modules that are not listed in the regular semester for your course",
|
||||||
|
@@ -12,7 +12,7 @@ import Card from "primevue/card";
|
|||||||
import DataView from "primevue/dataview";
|
import DataView from "primevue/dataview";
|
||||||
import Dialog from "primevue/dialog";
|
import Dialog from "primevue/dialog";
|
||||||
import ToggleButton from "primevue/togglebutton";
|
import ToggleButton from "primevue/togglebutton";
|
||||||
import "primevue/resources/themes/viva-dark/theme.css";
|
import "primevue/resources/themes/tailwind-light/theme.css";
|
||||||
import "primeicons/primeicons.css";
|
import "primeicons/primeicons.css";
|
||||||
import "primeflex/primeflex.css";
|
import "primeflex/primeflex.css";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
|
@@ -52,7 +52,7 @@ function loadCalendar(): void {
|
|||||||
moduleStore().addModule(module);
|
moduleStore().addModule(module);
|
||||||
});
|
});
|
||||||
modules.value = moduleStore().modules;
|
modules.value = moduleStore().modules;
|
||||||
router.push("/edit-additional-modules");
|
router.push("/edit-calendar");
|
||||||
} else {
|
} else {
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: "error",
|
severity: "error",
|
||||||
@@ -66,7 +66,6 @@ function loadCalendar(): void {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Toast />
|
|
||||||
<div class="flex flex-column">
|
<div class="flex flex-column">
|
||||||
<div class="flex align-items-center justify-content-center h-4rem mt-2">
|
<div class="flex align-items-center justify-content-center h-4rem mt-2">
|
||||||
<h3 class="text-4xl">
|
<h3 class="text-4xl">
|
||||||
|
Reference in New Issue
Block a user