Skip to content

Commit

Permalink
Merge pull request #283 from gampig/feature/database-versioning
Browse files Browse the repository at this point in the history
Feature/database versioning
  • Loading branch information
gampig authored Nov 20, 2024
2 parents 0804dd1 + eb54f4c commit 2a9bf76
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"args": {
"VARIANT": "16-bullseye"
"VARIANT": "20"
}
},

Expand Down
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
VUE_APP_TITLE=FeuerwehrApp
VUE_APP_SUPPORTED_DATABASE_SCHEMA_VERSION=1
3 changes: 2 additions & 1 deletion .firebaserc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"projects": {
"default": "feuerwehr-parkstetten"
"default": "feuerwehr-parkstetten",
"test": "feuerwehr-parkstetten-test"
}
}
5 changes: 5 additions & 0 deletions database.rules.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"rules": {
"schemaVersion": {
".read": true,
".write": false
},

"appSettings": {
"feuerwehrGeraete": {
".read": "auth != null && (true === root.child('users').child(auth.uid).child('roles').child('ROLE_VEHICLE').val() || true === root.child('users').child(auth.uid).child('roles').child('ROLE_GROUPLEADER').val())"
Expand Down
176 changes: 126 additions & 50 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,69 +1,145 @@
<template>
<v-app id="feuerwehr-app">
<router-view />
<Loading :visible="loading" text="Anmelden..." />
<Loading v-if="loading" visible :text="loadingScreenText" />
<router-view v-else />
</v-app>
</template>

<script>
<script lang="ts" setup>
import version from "@/utils/version";
import modules from "./modules";
import Loading from "@/components/Loading";
import Loading from "@/components/Loading.vue";
import { requires } from "./utils/routerAuth";
import { mapActions, mapState } from "pinia";
import { useAuthStore } from "./stores/auth";
import { useDatabaseSchemaStore } from "./stores/databaseSchema";
import { computed, ref, watch, watchEffect } from "vue";
import router from "./router";
import handleError from "./utils/store/handleError";
export default {
components: { Loading },
const authStore = useAuthStore();
const databaseSchemaStore = useDatabaseSchemaStore();
computed: {
...mapState(useAuthStore, ["loading", "loggedIn"]),
},
const loadingAuth = computed(() => authStore.loading);
const loggedIn = computed(() => authStore.loggedIn);
const loadingDatabaseSchemaVersion = computed(
() => databaseSchemaStore.loading
);
watch: {
loggedIn() {
this.onStateChanged();
},
},
const serviceWorkerRegistration = ref<ServiceWorkerRegistration | null>(null);
const updateFound = ref(false);
const updateDownloaded = ref(false);
const updateIsRequired = computed(() => databaseSchemaStore.updateIsRequired);
created() {
this.onStateChanged();
},
const loading = computed(
() =>
loadingAuth.value ||
loadingDatabaseSchemaVersion.value ||
updateIsRequired.value
);
const loadingScreenText = computed(() => {
if (loadingAuth.value) {
return "Anmelden...";
} else if (loadingDatabaseSchemaVersion.value) {
return "Lade Daten...";
} else if (updateIsRequired.value) {
if (updateFound.value) {
return "Lade Update herunter...";
} else if (updateDownloaded.value) {
return "Update wurde heruntergeladen. Bitte App neu laden.";
} else {
return "Warte auf erforderliches Update...";
}
} else {
return "Laden...";
}
});
methods: {
...mapActions(useAuthStore, ["updateClientMetadata"]),
function checkAuthState() {
if (
loggedIn.value === true &&
!loadingDatabaseSchemaVersion.value &&
!updateIsRequired.value
) {
onLogin();
} else if (loggedIn.value === false) {
onLogout();
}
}
onStateChanged() {
if (this.loggedIn === true) {
this.onLogin();
} else if (this.loggedIn === false) {
this.onLogout();
}
},
onLogin() {
modules.onLogin();
const lastOnline = new Date().toISOString();
this.updateClientMetadata({
lastOnline,
version,
});
},
onLogout() {
modules.onLogout();
this.toLoginPage();
},
toLoginPage() {
if (requires(this.$route, "requiresAuth")) {
this.$router.replace({
name: "UserLogin",
params: { nextUrl: { name: "Home" } },
function onLogin() {
modules.onLogin();
const lastOnline = new Date().toISOString();
authStore.updateClientMetadata({
lastOnline,
version,
});
}
function onLogout() {
modules.onLogout();
toLoginPage();
}
function toLoginPage() {
if (requires(router.currentRoute, "requiresAuth")) {
router.replace({
name: "UserLogin",
params: { nextRouteName: "Home" },
});
}
}
function fetchDatabaseSchemaVersion() {
databaseSchemaStore.bind();
}
function checkDatabaseSchemaVersion() {
if (updateIsRequired.value) {
if (updateDownloaded.value) {
if (serviceWorkerRegistration.value?.waiting) {
alert(
"App wird neugestartet, damit ein erforderliches Update durchgeführt wird."
);
serviceWorkerRegistration.value.waiting.postMessage({
type: "SKIP_WAITING",
});
} else {
alert(
"Bitte lade die App neu.\n\nEin erforderliches Update wurde heruntergeladen und muss installiert werden."
);
}
},
}
}
}
document.addEventListener(
"swUpdateFound",
() => {
updateFound.value = true;
},
};
{ once: true }
);
document.addEventListener(
"swUpdated",
(event) => {
if (event instanceof CustomEvent) {
updateDownloaded.value = true;
serviceWorkerRegistration.value = event.detail;
} else {
handleError(new Error("Event ist nicht vom Typ CustomEvent"));
}
},
{ once: true }
);
watch(
[loggedIn, loadingDatabaseSchemaVersion, updateIsRequired],
checkAuthState
);
watchEffect(checkDatabaseSchemaVersion);
fetchDatabaseSchemaVersion();
checkAuthState();
</script>
4 changes: 4 additions & 0 deletions src/registerServiceWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ if (process.env.NODE_ENV === "production") {
},
updatefound() {
console.log("New content is downloading.");
document.dispatchEvent(new CustomEvent("swUpdateFound"));
},
updated(registration) {
if (router.currentRoute.meta?.skipWaiting) {
Expand All @@ -28,6 +29,9 @@ if (process.env.NODE_ENV === "production") {
}
} else {
console.log("New content is available; please refresh.");
document.dispatchEvent(
new CustomEvent("swUpdated", { detail: registration })
);
}
},
offline() {
Expand Down
41 changes: 41 additions & 0 deletions src/stores/databaseSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineStore } from "pinia";
import firebase from "firebase/app";

interface State {
loading: Boolean;
localSchemaVersion?: Number | null;
remoteSchemaVersion?: Number | null;
}

export const useDatabaseSchemaStore = defineStore("databaseSchema", {
state: (): State => ({
loading: true,
localSchemaVersion: process.env.VUE_APP_SUPPORTED_DATABASE_SCHEMA_VERSION,
remoteSchemaVersion: undefined,
}),

getters: {
updateIsRequired: (state) =>
state.localSchemaVersion !== undefined &&
state.localSchemaVersion !== null &&
state.remoteSchemaVersion !== undefined &&
state.remoteSchemaVersion !== null &&
state.localSchemaVersion < state.remoteSchemaVersion,
},

actions: {
bind() {
firebase
.database()
.ref("schemaVersion")
.once("value")
.then((snapshot) => {
const version = snapshot.val();
if (typeof version === "number") {
this.remoteSchemaVersion = version;
}
})
.finally(() => (this.loading = false));
},
},
});
19 changes: 15 additions & 4 deletions src/utils/routerAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ export function requires(to: AppRoute, required: AuthTypes) {
);
}

function getParamsForNext(to: AppRoute): { [key: string]: string } {
if (to.name !== "UserLogin") {
return { nextUrl: to.fullPath };
} else {
if (to.params.nextUrl) {
return { nextUrl: to.params.nextUrl };
} else if (to.params.nextRouteName) {
return { nextRouteName: to.params.nextRouteName };
} else {
return {};
}
}
}

export function checkAuth(
to: AppRoute,
from: AppRoute,
Expand All @@ -21,10 +35,7 @@ export function checkAuth(
if (loggedIn === false) {
next({
name: "UserLogin",
params:
to.name === "UserLogin"
? { nextUrl: to.params.nextUrl }
: { nextUrl: to.fullPath },
params: getParamsForNext(to),
replace: true,
});
return;
Expand Down
6 changes: 6 additions & 0 deletions src/views/user/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ export default Vue.extend({
nextUrl(): string | null {
return this.$route.params.nextUrl || null;
},
nextRouteName(): string | null {
return this.$route.params.nextRouteName || null;
},
},
watch: {
loggedIn(loggedIn) {
if (loggedIn) {
if (this.nextUrl) {
this.$router.replace(this.nextUrl);
} else if (this.nextRouteName) {
this.$router.replace({ name: this.nextRouteName });
} else {
this.$router.replace({ name: "Home" });
}
Expand Down

0 comments on commit 2a9bf76

Please sign in to comment.