Skip to content

Commit

Permalink
PWA (#118)
Browse files Browse the repository at this point in the history
* AppManifest registratet and linked

* basic pwa service worker via vite-pwa-plugin

* ldap using in prod mode

* registering manifest.json for production mode saved, added app icons - state: chrome recognizing  app as instalable

* semi-fixed  auth problematic in offline mode

* changed some default strategy and added strategy for fonts and backend

* putting data in indexeddb is working, stll commented Code for cache and data that is not working yet

* alomst working offline - some routes left

* changed manifest path to relative one

* offline working without admin routes and post events - putting everything into cache

* basic functionality of offline banner

* deactivated one button if offline

* binding to root.isonline

* variablie is Offline

* boolean isOffline and Component offline Banner

* working data binding and event listening with navigator.onLine

* removed a console log, aded binding to travel edit and delete button

* manifest einbindung neu

* added deactivatio of buttons

* load method sets isOffline again

* changed font caching strategy to network first

* NetworkFirst for font - but makes problems

* added some comments

* offline working with indexedDB

* indexeddb offline - not working on mobile browsers

* back to cache use for backend routes

* changed Dev container build process - no vite anymore but nodemon for file changes

* sw using indexed db

* InstallationHinweis -logik but not pretty done

* locales added

* moved boolean to Installation Komponent fixed some conditions

* NetworkFirst for font - but makes problems

* added some comments

* offline working with indexedDB

* back to cache use for backend routes

* sw using indexed db

* little sw changes

* beginning of push notification implementation - working in chrome partly

* using mongo express also in prod mode

* added push object with subscription to user type and model + route to update user subscriptions

* push otification uses subscriptions of user to send notification additionally to mail

* pushNotification subscription moved to load() - use it after user logged in

* make public vapid key aviable in Frontend

* NetworkFirst for font - but makes problems

* added some comments

* offline working with indexedDB

* back to cache use for backend routes

* sw using indexed db

* beginning of push notification implementation - working in chrome partly

* removed push subscription from main.ts

* removed mistake from mail.ts

* InstallationHinweis -logik but not pretty done

* moved boolean to Installation Komponent fixed some conditions

* NetworkFirst for font - but makes problems

* added some comments

* offline working with indexedDB

* back to cache use for backend routes

* sw using indexed db

* moved push subscription to function and added button for permission triggering. - button ; cleared main.ts - fixed datat load and store as well as clearing  db on logout

* working push per subscription in session so far

* moved subscription to sessions

* moved Installationbanner to App.vue - moved mobile and alreadyInstalled boolean to root and moved "show installation Instructions" Banner to Dropdown Menu

* removed events from eventListeners

* finally changed bg color in manifest.json

* events wait until added

* diverses managing of push but now recieving push messages dont work anymore

* zwischen commit bei dem nichts geht

* little changes - push working again

* moved push Logik into push service js

* removed pushBanner - not needed

* push including url for notification click event

* removed PushBanner

* moved -load and store details for Reports aut of loading Promise for Faster view

* Installationbanner for different Browsers filled with process infos

* little changes

* added some locales

* service worker registration in own file + eventlistener for Update - Notification for user to reload Page

* removed useless config

* removed async from  data saving to indexed

* few changes, code cleaning

* removed console logs

* removed some comments

* few order switches

* moved promptInstall Eventlistener to App to catch the events on loading

* examiner fecth inside the if (hasData)

* Load pdf button invisible if offline

* improvemtns on push type

* cleaned code a bit

* another manifest icon size added

* some changes to clean code and remove functions from app.vue to helper.ts

* added vapid in env.example , and removed comments vrom App

* reload automatically +manifest removed from cache

* added default installation instructions and renamed icons folder to appicons

* little changes on push /subscribe call and removed cot needed code from Instalation.vue

* added comments and reduced nesting

* little change on comment

* fixed mail.ts and sendPushnotification calling

* some typos fixed in locales

* fixed maximum File Size for precached Files

* fixed error vapid is not https, starting to fix session and subscription problem

* changed error log for no https in push.ts

* make it work in dev mode

* refinements

* clean up push notifications

* add allowedHosts

* optimize push notifications

* clean up

* style banner

* fix error for missing env var

---------

Co-authored-by: Sarah <[email protected]>
Co-authored-by: david-loe <[email protected]>
  • Loading branch information
3 people authored Feb 5, 2025
1 parent bb83042 commit e64715b
Show file tree
Hide file tree
Showing 41 changed files with 8,163 additions and 3,133 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ COOKIE_SECRET=secret
# Magic Login (use something like: 'openssl rand -base64 60')
MAGIC_LOGIN_SECRET=secret

# VAPID keys for WebPush Notifications (generate with: 'docker compose exec backend npx web-push generate-vapid-keys')
VITE_PUBLIC_VAPID_KEY=''
PRIVATE_VAPID_KEY=''

# Ports
FRONTEND_PORT=5000
BACKEND_PORT=8000
Expand Down
6 changes: 2 additions & 4 deletions backend/app.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import MongoStore from 'connect-mongo'
import cors from 'cors'
import express, { Request as ExRequest, Response as ExResponse } from 'express'
import { rateLimit } from 'express-rate-limit'
import session from 'express-session'
import mongoose from 'mongoose'
import swaggerUi from 'swagger-ui-express'
import auth from './auth.js'
import { errorHandler, RateLimitExceededError } from './controller/error.js'
import { connectDB } from './db.js'
import { connectDB, sessionStore } from './db.js'
import { RegisterRoutes } from './dist/routes.js'
import swaggerDocument from './dist/swagger.json' with { type: 'json' }
import i18n from './i18n.js'
Expand Down Expand Up @@ -55,7 +53,7 @@ if (process.env.RATE_LIMIT_WINDOW_MS && process.env.RATE_LIMIT) {

app.use(
session({
store: MongoStore.create({ client: mongoose.connection.getClient() }),
store: sessionStore,
secret: process.env.COOKIE_SECRET ? process.env.COOKIE_SECRET : 'secret',
cookie: {
maxAge: 2 * 24 * 60 * 60 * 1000,
Expand Down
2 changes: 1 addition & 1 deletion backend/authStrategies/magiclogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import MagicLoginStrategy from 'passport-magic-login'
import { escapeRegExp } from '../../common/scripts.js'
import { NotAllowedError } from '../controller/error.js'
import i18n from '../i18n.js'
import { sendMail } from '../mail/mail.js'
import User from '../models/user.js'
import { sendMail } from '../notifications/mail.js'
const secret = process.env.MAGIC_LOGIN_SECRET
const callbackUrl = process.env.VITE_BACKEND_URL + '/auth/magiclogin/callback'
const options = {
Expand Down
8 changes: 4 additions & 4 deletions backend/controller/expenseReportController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { Body, Delete, Get, Middlewares, Post, Produces, Queries, Query, Request
import { Expense, ExpenseReportState, ExpenseReport as IExpenseReport, Locale, _id } from '../../common/types.js'
import { checkIfUserIsProjectSupervisor, documentFileHandler, fileHandler, writeToDisk } from '../helper.js'
import i18n from '../i18n.js'
import { sendNotificationMail } from '../mail/mail.js'
import ExpenseReport, { ExpenseReportDoc } from '../models/expenseReport.js'
import User from '../models/user.js'
import { sendNotification } from '../notifications/notification.js'
import { generateExpenseReportReport } from '../pdf/expenseReport.js'
import { sendViaMail, writeToDiskFilePath } from '../pdf/helper.js'
import { Controller, GetterQuery, SetterBody } from './controller.js'
Expand Down Expand Up @@ -116,7 +116,7 @@ export class ExpenseReportController extends Controller {

return await this.setter(ExpenseReport, {
requestBody: extendedBody,
cb: sendNotificationMail,
cb: sendNotification,
allowNew: false,
async checkOldObject(oldObject: ExpenseReportDoc) {
if (oldObject.owner._id.equals(request.user!._id) && oldObject.state === 'inWork') {
Expand Down Expand Up @@ -253,7 +253,7 @@ export class ExpenseReportExamineController extends Controller {
}
return await this.setter(ExpenseReport, {
requestBody: extendedBody,
cb: (e: IExpenseReport) => sendNotificationMail(e, extendedBody._id ? 'backToInWork' : undefined),
cb: (e: IExpenseReport) => sendNotification(e, extendedBody._id ? 'backToInWork' : undefined),
allowNew: true,
async checkOldObject(oldObject: ExpenseReportDoc) {
if (oldObject.state === 'underExamination' && checkIfUserIsProjectSupervisor(request.user!, oldObject.project._id)) {
Expand All @@ -272,7 +272,7 @@ export class ExpenseReportExamineController extends Controller {
const extendedBody = Object.assign(requestBody, { state: 'refunded' as ExpenseReportState, editor: request.user?._id })

const cb = async (expenseReport: IExpenseReport) => {
sendNotificationMail(expenseReport)
sendNotification(expenseReport)
sendViaMail(expenseReport)
if (process.env.BACKEND_SAVE_REPORTS_ON_DISK.toLowerCase() === 'true') {
await writeToDisk(
Expand Down
10 changes: 5 additions & 5 deletions backend/controller/healthCareCostController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import {
} from '../../common/types.js'
import { checkIfUserIsProjectSupervisor, documentFileHandler, fileHandler, writeToDisk } from '../helper.js'
import i18n from '../i18n.js'
import { sendNotificationMail } from '../mail/mail.js'
import HealthCareCost, { HealthCareCostDoc } from '../models/healthCareCost.js'
import Organisation from '../models/organisation.js'
import User from '../models/user.js'
import { sendNotification } from '../notifications/notification.js'
import { generateHealthCareCostReport } from '../pdf/healthCareCost.js'
import { sendViaMail, writeToDiskFilePath } from '../pdf/helper.js'
import { Controller, GetterQuery, SetterBody } from './controller.js'
Expand Down Expand Up @@ -126,7 +126,7 @@ export class HealthCareCostController extends Controller {

return await this.setter(HealthCareCost, {
requestBody: extendedBody,
cb: sendNotificationMail,
cb: sendNotification,
allowNew: false,
async checkOldObject(oldObject: HealthCareCostDoc) {
if (oldObject.owner._id.equals(request.user!._id) && oldObject.state === 'inWork') {
Expand Down Expand Up @@ -250,7 +250,7 @@ export class HealthCareCostExamineController extends Controller {
})

const cb = async (healthCareCost: IHealthCareCost) => {
sendNotificationMail(healthCareCost)
sendNotification(healthCareCost)
sendViaMail(healthCareCost)
if (process.env.BACKEND_SAVE_REPORTS_ON_DISK.toLowerCase() === 'true') {
await writeToDisk(
Expand Down Expand Up @@ -302,7 +302,7 @@ export class HealthCareCostExamineController extends Controller {
}
return await this.setter(HealthCareCost, {
requestBody: extendedBody,
cb: (e: IHealthCareCost) => sendNotificationMail(e, extendedBody._id ? 'backToInWork' : undefined),
cb: (e: IHealthCareCost) => sendNotification(e, extendedBody._id ? 'backToInWork' : undefined),
allowNew: true,
async checkOldObject(oldObject: HealthCareCostDoc) {
if (oldObject.state === 'underExamination' && checkIfUserIsProjectSupervisor(request.user!, oldObject.project._id)) {
Expand Down Expand Up @@ -371,7 +371,7 @@ export class HealthCareCostConfirmController extends Controller {
})

const cb = async (healthCareCost: IHealthCareCost) => {
sendNotificationMail(healthCareCost)
sendNotification(healthCareCost)
sendViaMail(healthCareCost)
if (process.env.BACKEND_SAVE_REPORTS_ON_DISK.toLowerCase() === 'true') {
await writeToDisk(
Expand Down
14 changes: 7 additions & 7 deletions backend/controller/travelController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { Body, Delete, Get, Middlewares, Post, Produces, Queries, Query, Request
import { Travel as ITravel, Locale, Stage, TravelExpense, TravelState, _id } from '../../common/types.js'
import { checkIfUserIsProjectSupervisor, documentFileHandler, fileHandler, writeToDisk } from '../helper.js'
import i18n from '../i18n.js'
import { sendNotificationMail } from '../mail/mail.js'
import Travel, { TravelDoc } from '../models/travel.js'
import User from '../models/user.js'
import { sendNotification } from '../notifications/notification.js'
import { generateAdvanceReport } from '../pdf/advance.js'
import { sendViaMail, writeToDiskFilePath } from '../pdf/helper.js'
import { generateTravelReport } from '../pdf/travel.js'
Expand Down Expand Up @@ -137,7 +137,7 @@ export class TravelController extends Controller {
}
return await this.setter(Travel, {
requestBody: extendedBody,
cb: sendNotificationMail,
cb: sendNotification,
checkOldObject: async (oldObject: TravelDoc) =>
!oldObject.historic &&
(oldObject.state === 'appliedFor' || oldObject.state === 'rejected' || oldObject.state === 'approved') &&
Expand Down Expand Up @@ -196,7 +196,7 @@ export class TravelController extends Controller {

return await this.setter(Travel, {
requestBody: extendedBody,
cb: sendNotificationMail,
cb: sendNotification,
allowNew: false,
async checkOldObject(oldObject: TravelDoc) {
if (oldObject.owner._id.equals(request.user!._id) && oldObject.state === 'approved') {
Expand Down Expand Up @@ -277,7 +277,7 @@ export class TravelApproveController extends Controller {
}
}
const cb = async (travel: ITravel) => {
sendNotificationMail(travel)
sendNotification(travel)
if (travel.advance.amount !== null && travel.advance.amount > 0) {
sendViaMail(travel)
if (process.env.BACKEND_SAVE_REPORTS_ON_DISK.toLowerCase() === 'true') {
Expand Down Expand Up @@ -308,7 +308,7 @@ export class TravelApproveController extends Controller {

return await this.setter(Travel, {
requestBody: extendedBody,
cb: sendNotificationMail,
cb: sendNotification,
allowNew: false,
checkOldObject: async (oldObject: TravelDoc) =>
oldObject.state === 'appliedFor' && checkIfUserIsProjectSupervisor(request.user!, oldObject.project._id)
Expand Down Expand Up @@ -446,7 +446,7 @@ export class TravelExamineController extends Controller {
const extendedBody = Object.assign(requestBody, { state: 'refunded' as TravelState, editor: request.user?._id })

const cb = async (travel: ITravel) => {
sendNotificationMail(travel)
sendNotification(travel)
sendViaMail(travel)
if (process.env.BACKEND_SAVE_REPORTS_ON_DISK.toLowerCase() === 'true') {
await writeToDisk(await writeToDiskFilePath(travel), await generateTravelReport(travel, i18n.language as Locale))
Expand Down Expand Up @@ -480,7 +480,7 @@ export class TravelExamineController extends Controller {
return await this.setter(Travel, {
requestBody: extendedBody,
allowNew: false,
cb: (e: ITravel) => sendNotificationMail(e, 'backToApproved'),
cb: (e: ITravel) => sendNotification(e, 'backToApproved'),
async checkOldObject(oldObject: TravelDoc) {
if (oldObject.state === 'underExamination' && checkIfUserIsProjectSupervisor(request.user!, oldObject.project._id)) {
await oldObject.saveToHistory()
Expand Down
9 changes: 8 additions & 1 deletion backend/controller/userController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import { Request as ExRequest } from 'express'
import { DeleteResult } from 'mongodb'
import { Types } from 'mongoose'
import { Body, Consumes, Delete, Get, Middlewares, Post, Queries, Query, Request, Route, Security } from 'tsoa'
import { PushSubscription } from 'web-push'
import { _id, User as IUser, locales, tokenAdminUser } from '../../common/types.js'
import { documentFileHandler, fileHandler } from '../helper.js'
import i18n from '../i18n.js'
import { sendMail } from '../mail/mail.js'
import ExpenseReport from '../models/expenseReport.js'
import HealthCareCost from '../models/healthCareCost.js'
import Token from '../models/token.js'
import Travel from '../models/travel.js'
import User, { userSchema } from '../models/user.js'
import { mongooseSchemaToVueformSchema } from '../models/vueformGenerator.js'
import { sendMail } from '../notifications/mail.js'
import { Controller, GetterQuery, SetterBody } from './controller.js'
import { NotAllowedError, NotFoundError } from './error.js'
import { File, IdDocument, idDocumentToId } from './types.js'
Expand Down Expand Up @@ -61,6 +62,12 @@ export class UserController extends Controller {
const result = await request.user!.save()
return { message: 'alerts.successSaving', result: result }
}

@Post('subscription')
public async subscribe(@Body() requestBody: PushSubscription, @Request() request: ExRequest) {
request.session.subscription = requestBody
return { message: 'alerts.successSaving', result: requestBody }
}
}

@Route('users')
Expand Down
3 changes: 3 additions & 0 deletions backend/db.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios from 'axios'
import MongoStore from 'connect-mongo'
import mongoose, { Model } from 'mongoose'
import { mergeDeep } from '../common/scripts.js'
import {
Expand Down Expand Up @@ -36,6 +37,8 @@ export async function connectDB() {
}
}

export const sessionStore = MongoStore.create({ client: mongoose.connection.getClient() })

export async function disconnectDB() {
await mongoose.disconnect()
}
Expand Down
14 changes: 14 additions & 0 deletions backend/express-session.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'express-session'
import type { Types } from 'mongoose'
import { PushSubscription } from 'web-push'

declare module 'express-session' {
interface SessionData {
subscription?: PushSubscription
passport: {
user: {
_id: Types.ObjectId
}
}
}
}
Loading

0 comments on commit e64715b

Please sign in to comment.