move pwa manifest to backend

This commit is contained in:
Julian Krauser 2025-04-24 16:49:16 +02:00
parent 7aa9038a61
commit b4a7986c8a
16 changed files with 724 additions and 11 deletions

BIN
src/assets/admin-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
src/assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View file

@ -1,5 +1,5 @@
import { dataSource } from "../../../data-source";
import { setting } from "../../../entity/setting";
import { setting } from "../../../entity/management/setting";
import DatabaseActionException from "../../../exceptions/databaseActionException";
import { StringHelper } from "../../../helpers/stringHelper";
import { CreateOrUpdateSettingCommand, DeleteSettingCommand } from "./settingCommand";

View file

@ -2,12 +2,13 @@ import { Request, Response } from "express";
import CalendarService from "../service/club/calendarService";
import CalendarTypeService from "../service/configuration/calendarTypeService";
import { calendar } from "../entity/club/calendar";
import { createEvents } from "ics";
import moment from "moment";
import InternalException from "../exceptions/internalException";
import CalendarFactory from "../factory/admin/club/calendar";
import { CalendarHelper } from "../helpers/calendarHelper";
import SettingHelper from "../helpers/settingsHelper";
import sharp from "sharp";
import ico from "sharp-ico";
import { FileSystemHelper } from "../helpers/fileSystemHelper";
/**
* @description get all calendar items by types or nscdr
@ -71,3 +72,136 @@ export async function getApplicationConfig(req: Request, res: Response): Promise
res.json(config);
}
/**
* @description get application Manifest
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getApplicationManifest(req: Request, res: Response): Promise<any> {
const backendUrl = `${req.protocol}://${req.get("host")}`;
const frontenUrl = `${req.get("referer")}`;
const manifest = {
id: "ff_admin_webapp",
lang: "de",
name: SettingHelper.getSetting("club.name"),
short_name: SettingHelper.getSetting("club.name"),
theme_color: "#990b00",
display: "standalone",
orientation: "portrait-primary",
start_url: frontenUrl,
icons: [
{
src: `${backendUrl}/api/public/favicon.ico`,
sizes: "48x48",
type: "image/ico",
},
{
src: `${backendUrl}/api/public/icon.png?width=512&height=512`,
sizes: "512x512",
type: "image/png",
},
],
};
res.set({
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/manifest+json",
});
res.json(manifest);
}
/**
* @description get application Logo
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getApplicationLogo(req: Request, res: Response): Promise<any> {
let setLogo = SettingHelper.getSetting("club.logo");
res.set({
"Access-Control-Allow-Origin": "*",
"Cross-Origin-Resource-Policy": "cross-origin",
"Cross-Origin-Embedder-Policy": "credentialless",
"Timing-Allow-Origin": "*",
});
if (setLogo == "") {
res.sendFile(FileSystemHelper.readAssetFile("admin-logo.png", true));
} else {
res.sendFile(FileSystemHelper.formatPath("/app/admin-logo.png"));
}
}
/**
* @description get application Favicon
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getApplicationFavicon(req: Request, res: Response): Promise<any> {
let icon = FileSystemHelper.readAssetFile("icon.png", true);
let setLogo = SettingHelper.getSetting("club.icon");
if (setLogo != "") {
icon = FileSystemHelper.formatPath("/app/icon.png");
}
let image = await sharp(icon)
.resize(48, 48, {
fit: "inside",
})
.png()
.toBuffer();
let buffer = ico.encode([image]);
res.set({
"Access-Control-Allow-Origin": "*",
"Cross-Origin-Resource-Policy": "cross-origin",
"Cross-Origin-Embedder-Policy": "credentialless",
"Timing-Allow-Origin": "*",
});
res.setHeader("Content-Type", "image/x-icon");
res.send(buffer);
}
/**
* @description get application Icon
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getApplicationIcon(req: Request, res: Response): Promise<any> {
const width = parseInt((req.query.width as string) ?? "");
const height = parseInt((req.query.height as string) ?? "");
let icon = FileSystemHelper.readAssetFile("icon.png", true);
let setLogo = SettingHelper.getSetting("club.icon");
if (setLogo != "") {
icon = FileSystemHelper.formatPath("/app/icon.png");
}
let image = await sharp(icon)
.resize(width, height, {
fit: "inside",
})
.png()
.toBuffer();
res.set({
"Access-Control-Allow-Origin": "*",
"Cross-Origin-Resource-Policy": "cross-origin",
"Cross-Origin-Embedder-Policy": "credentialless",
"Timing-Allow-Origin": "*",
});
res.setHeader("Content-Type", "image/png");
res.send(image);
}

View file

@ -50,7 +50,7 @@ import { TemplatesAndProtocolSort1742549956787 } from "./migrations/174254995678
import { QueryToUUID1742922178643 } from "./migrations/1742922178643-queryToUUID";
import { NewsletterColumnType1744351418751 } from "./migrations/1744351418751-newsletterColumnType";
import { QueryUpdatedAt1744795756230 } from "./migrations/1744795756230-QueryUpdatedAt";
import { setting } from "./entity/setting";
import { setting } from "./entity/management/setting";
import { SettingsFromEnv1745059495808 } from "./migrations/1745059495808-settingsFromEnv";
import { DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_TYPE, DB_USERNAME } from "./env.defaults";

View file

@ -20,9 +20,20 @@ export abstract class FileSystemHelper {
return readFileSync(this.formatPath(...filePath), "base64");
}
static readRootFile(filePath: string) {
return readFileSync(this.normalizePath(process.cwd(), filePath), "utf8");
}
static readTemplateFile(filePath: string) {
this.createFolder(filePath);
return readFileSync(process.cwd() + filePath, "utf8");
return readFileSync(this.normalizePath(process.cwd(), "src", "templates", filePath), "utf8");
}
static readAssetFile(filePath: string, returnPath: boolean = false) {
let path = this.normalizePath(process.cwd(), "src", "assets", filePath);
if (returnPath) {
return path;
}
return readFileSync(path, "utf8");
}
static writeFile(filePath: string, filename: string, file: any) {

View file

@ -9,10 +9,10 @@ export abstract class TemplateHelper {
static getTemplateFromFile(template: string) {
let tmpFile;
try {
tmpFile = FileSystemHelper.readTemplateFile(`/src/templates/${template}.template.html`);
tmpFile = FileSystemHelper.readTemplateFile(`${template}.template.html`);
} catch (err) {
tmpFile = FileSystemHelper.readTemplateFile(
`/src/templates/${template.split(".")[template.split(".").length - 1]}.template.html`
`${template.split(".")[template.split(".").length - 1]}.template.html`
);
}
return tmpFile;

View file

@ -1,5 +1,12 @@
import express from "express";
import { getApplicationConfig, getCalendarItemsByTypes } from "../controller/publicController";
import {
getApplicationConfig,
getApplicationFavicon,
getApplicationIcon,
getApplicationLogo,
getApplicationManifest,
getCalendarItemsByTypes,
} from "../controller/publicController";
var router = express.Router({ mergeParams: true });
@ -11,4 +18,20 @@ router.get("/configuration", async (req, res) => {
await getApplicationConfig(req, res);
});
router.get("/manifest.webmanifest", async (req, res) => {
await getApplicationManifest(req, res);
});
router.get("/applogo.png", async (req, res) => {
await getApplicationLogo(req, res);
});
router.get("/favicon.ico", async (req, res) => {
await getApplicationFavicon(req, res);
});
router.get("/icon.png", async (req, res) => {
await getApplicationIcon(req, res);
});
export default router;

View file

@ -5,7 +5,7 @@ import Parser from "rss-parser";
var router = express.Router({ mergeParams: true });
router.get("/version", async (req: Request, res: Response) => {
let serverPackage = FileSystemHelper.readTemplateFile("/package.json");
let serverPackage = FileSystemHelper.readRootFile("/package.json");
let serverJson = JSON.parse(serverPackage);
res.send({
name: serverJson.name,

View file

@ -25,4 +25,10 @@ router.put("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "to
await finishInvite(req, res, true);
});
/**
* TODO:
* set basic settings like clubname ...
* enable upload of images and icons: transform pwa-> 512x512 png / 48x48 ico
*/
export default router;

View file

@ -1,5 +1,5 @@
import { dataSource } from "../../data-source";
import { setting } from "../../entity/setting";
import { setting } from "../../entity/management/setting";
import InternalException from "../../exceptions/internalException";
import { SettingString } from "../../type/settingTypes";

View file

@ -2,6 +2,8 @@ import ms from "ms";
export type SettingTopic = "club" | "app" | "session" | "mail" | "backup" | "security";
export type SettingString =
| "club.icon"
| "club.logo"
| "club.name"
| "club.imprint"
| "club.privacy"
@ -23,6 +25,8 @@ export type SettingTypeAtom = "longstring" | "string" | "ms" | "number" | "boole
export type SettingType = SettingTypeAtom | `${SettingTypeAtom}/crypt` | `${SettingTypeAtom}/rand`;
export type SettingValueMapping = {
"club.icon": string;
"club.logo": string;
"club.name": string;
"club.imprint": string;
"club.privacy": string;
@ -54,6 +58,8 @@ export type SettingsSchema = {
};
export const settingsType: SettingsSchema = {
"club.icon": { type: "string", optional: true },
"club.logo": { type: "string", optional: true },
"club.name": { type: "string", default: "FF Admin" },
"club.imprint": { type: "url", optional: true },
"club.privacy": { type: "url", optional: true },