feat:#22 refactor to cache room schedule and webworker config

This commit is contained in:
survellow
2024-08-02 23:55:58 +02:00
parent e4bde20397
commit 4fcfa76e1c
14 changed files with 164 additions and 83 deletions

View File

@ -16,7 +16,6 @@
import { BSON } from "bson";
import { RoomOccupancyList } from "@/model/roomOccupancyList.ts";
import { addMonths } from "date-fns";
import { formatYearMonthDay } from "@/helpers/dates";
const END_OF_SUMMER_SEMESTER = "0930";
@ -59,32 +58,19 @@ export function getSemesterStart(date: Date): Date {
/**
* Fetches the room occupancy for a given date range.
* @param from_date the start date of the date range
* @param to_date the end date of the date range
* @returns RoomOccupancyList - the room occupancy list
* @returns RoomOccupancyList - the room occupancy list containing all rooms
*/
export async function fetchRoomOccupancy(
from_date?: string,
to_date?: string,
): Promise<RoomOccupancyList> {
if (from_date == undefined) {
const new_from_date = getSemesterStart(new Date());
from_date = new_from_date.toISOString();
}
if (to_date == undefined) {
const new_to_date = getSemesterStart(addMonths(new Date(), 6));
to_date = new_to_date.toISOString();
}
export async function fetchRoomOccupancy(): Promise<RoomOccupancyList> {
let roomOccupancyList: RoomOccupancyList = new RoomOccupancyList(
new Date(),
new Date(2000, 0, 1),
0,
0,
[],
);
await fetch("/api/schedule/rooms?from=" + from_date + "&to=" + to_date)
await fetch("/api/schedule/rooms")
.then((response) => {
return response.arrayBuffer();
})

View File

@ -27,8 +27,6 @@ import {useI18n} from "vue-i18n";
import allLocales from "@fullcalendar/core/locales-all";
import router from "@/router";
import {formatYearMonthDay} from "@/helpers/dates";
import {useQuery} from "@tanstack/vue-query";
import {fetchRoomOccupancy} from "@/api/fetchRoomOccupancy";
import {isValid} from "date-fns";
import {RoomOccupancyList} from "@/model/roomOccupancyList";
@ -39,6 +37,10 @@ const props = defineProps({
type: String,
required: true,
},
occupancy: {
type: RoomOccupancyList,
required: true,
},
});
const date: Ref<Date> = ref(new Date());
@ -56,6 +58,7 @@ function setDateFromQuery() {
const day = queryDate.substring(6, 8);
date.value = new Date(`${year}-${month}-${day}`);
if (!isValid(date.value)) {
console.error("Invalid date in URL, using current date instead.");
date.value = new Date();
}
}
@ -77,6 +80,9 @@ const selectedRoom = computed(() => props.room);
* @returns Anonymized occupancy events
*/
function transformData(data: RoomOccupancyList) {
if (!data || !selectedRoom.value || !currentDateFrom.value || !currentDateTo.value) {
return [];
}
return data
.decodeOccupancy(
selectedRoom.value,
@ -89,15 +95,9 @@ function transformData(data: RoomOccupancyList) {
}));
}
const { data: occupancy } = useQuery({
queryKey: ["roomOccupancy"], //, selectedRoom, currentDateFrom, currentDateTo],
queryFn: () => fetchRoomOccupancy(),
staleTime: 12 * 3600000, // 12 hours
});
const occupations = computed(() => {
if (!occupancy.value) return;
return transformData(occupancy.value);
if (!props.occupancy) return;
return transformData(props.occupancy);
});
watch(occupations, () => fullCalendar.value?.getApi().refetchEvents());

View File

@ -34,6 +34,56 @@ function setup() {
de,
ja,
},
datetimeFormats: {
en: {
short: {
year: "numeric",
month: "short",
day: "numeric",
},
long: {
year: "numeric",
month: "short",
day: "numeric",
weekday: "short",
hour: "numeric",
minute: "numeric",
hour12: true,
},
},
de: {
short: {
year: "numeric",
month: "short",
day: "numeric",
},
long: {
year: "numeric",
month: "short",
day: "numeric",
weekday: "short",
hour: "numeric",
minute: "numeric",
hour12: false,
},
},
ja: {
short: {
year: "numeric",
month: "short",
day: "numeric",
},
long: {
year: "numeric",
month: "short",
day: "numeric",
weekday: "short",
hour: "numeric",
minute: "numeric",
hour12: true,
},
},
},
});
return _i18n;
}

View File

@ -33,7 +33,9 @@
"noRoomsAvailable": "Keine Räume verfügbar",
"available": "verfügbar",
"occupied": "belegt",
"stub": "bitte online prüfen"
"stub": "bitte online prüfen",
"lastUpdate": "Zeitpunkt der letzten Aktualisierung",
"noData": "Es konnten keine Daten geladen werden."
},
"freeRooms": {
"freeRooms": "Freie Räume",

View File

@ -33,7 +33,9 @@
"noRoomsAvailable": "no rooms listed",
"available": "available",
"occupied": "occupied",
"stub": "please check online"
"stub": "please check online",
"lastUpdate": "time of last update",
"noData": "could not load data"
},
"freeRooms": {
"freeRooms": "free rooms",

View File

@ -33,7 +33,9 @@
"noRoomsAvailable": "利用可能な部屋がありません",
"available": "利用可能",
"occupied": "占有中",
"stub": "オンラインでご確認ください。"
"stub": "オンラインでご確認ください。",
"lastUpdate": "最終更新時刻",
"noData": "データをロードできませんでした。"
},
"freeRooms": {
"freeRooms": "空いている部屋",

View File

@ -65,6 +65,7 @@ class RoomOccupancy {
export class RoomOccupancyList {
constructor(
public start: Date,
public updated: Date,
public granularity: number,
public blocks: number,
public rooms: RoomOccupancy[],
@ -312,6 +313,7 @@ export class RoomOccupancyList {
public static fromJSON(json: Document): RoomOccupancyList {
return new RoomOccupancyList(
json.start,
json.updated,
json.granularity,
json.blocks,
json.rooms.map(

View File

@ -18,16 +18,29 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<script lang="ts" setup>
import { Ref, computed, ref, watch } from "vue";
import { fetchRoom } from "../api/fetchRoom.ts";
import DynamicPage from "./DynamicPage.vue";
import RoomOccupationOffline from "../components/RoomOccupationOffline.vue";
import { computedAsync } from "@vueuse/core";
import router from "@/router";
import { useQuery, useQueryClient } from "@tanstack/vue-query";
import { fetchRoomOccupancy } from "@/api/fetchRoomOccupancy.ts";
type Room = {
name: string;
};
const queryClient = useQueryClient();
const { data: occupancy, dataUpdatedAt: updateDate } = useQuery({
queryKey: ["roomOccupancy"],
queryFn: () => fetchRoomOccupancy(),
staleTime: 12 * 3600000, // 12 hours
});
// Manually refetch the room occupancy data
function refetchRoomOccupancy() {
queryClient.invalidateQueries({ queryKey: ["roomOccupancy"] });
}
const selectedRoom: Ref<Room> = ref({ name: "" });
// Watch for changes in URL parameter
@ -38,21 +51,15 @@ router.afterEach(async (to) => {
}
});
const rooms = computedAsync<Set<string>>(async () => {
const rooms = computed<Set<string>>(() => {
let rooms: Set<string> = new Set();
return await fetchRoom()
.then((data) => {
rooms = new Set(data);
return rooms;
})
.finally(() => {
const room = router.currentRoute.value.query.room;
if (room && typeof room === "string") {
// check if room is available in roomsList
setRoomFromList(room, rooms);
}
});
}, new Set());
occupancy.value?.rooms.forEach((room) => {
rooms.add(room.name);
});
return rooms;
});
const roomsList = computed(() => {
return Array.from(rooms.value).map((room) => {
@ -102,7 +109,17 @@ watch(selectedRoom, (newRoom: Room) => {
/>
</template>
<template #content>
<RoomOccupationOffline :room="selectedRoom.name" />
</template>
<RoomOccupationOffline v-if="occupancy" :room="selectedRoom.name" :occupancy="occupancy"/>
<div v-else>
<p>{{ $t('roomFinderPage.noData') }}</p>
</div>
<!--last update date-->
<div class="flex align-items-baseline justify-content-end text-right text-xs text-gray-500">
<span v-if="occupancy">test: {{ $d(occupancy.updated, 'long') }}</span>
<span>{{ $t("roomFinderPage.lastUpdate") }}: {{ $d(new Date(updateDate), 'long') }}</span>
<Button size="small" @click="refetchRoomOccupancy" icon="pi pi-refresh" class="p-button-text p-button-sm" />
</div>
</template>
</DynamicPage>
</template>

View File

@ -82,7 +82,7 @@ export default defineConfig({
},
registerType: "autoUpdate",
workbox: {
globPatterns: ["**/*.{js,css,html,ico,png,svg,json,vue,txt,woff2}"],
globPatterns: ["**/*.{js,css,html,ico,png,svg,json,vue,txt,woff2}", "/api/schedule/rooms"],
cleanupOutdatedCaches: true,
runtimeCaching: [
{
@ -96,6 +96,17 @@ export default defineConfig({
},
},
},
{
urlPattern: ("/api/schedule/rooms"),
method: "GET",
handler: "StaleWhileRevalidate",
options: {
cacheName: "room-schedule-cache",
expiration: {
maxAgeSeconds: 12 * 60 * 60, // 12 hours
},
},
},
{
urlPattern: /^https?.*/,
handler: "NetworkFirst",