Compare commits

..

9 commits

Author SHA1 Message Date
32c5776fe8 Altersgrenze 2025-01-09 20:56:10 +01:00
bd13a775c4 member communication 2025-01-07 11:46:31 +01:00
93afdec274 member communication 2025-01-07 11:46:31 +01:00
2398084764 member communication 2025-01-07 11:46:31 +01:00
c8b45512c5 membership 2025-01-07 11:46:31 +01:00
c7b0adee07 membership 2025-01-07 11:46:31 +01:00
1f95f8f64d import from old database 2025-01-07 11:46:31 +01:00
3ea7c423c9 import from old database 2025-01-07 11:46:31 +01:00
3b584767f3 force exposed prod port to 5000 2025-01-07 11:46:31 +01:00
63 changed files with 790 additions and 292 deletions

View file

@ -10,7 +10,6 @@ SERVER_PORT = portnumber
JWT_SECRET = ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890
JWT_EXPIRATION = [0-9]*(y|d|h|m|s)
REFRESH_EXPIRATION = [0-9]*(y|d|h|m|s)
PWA_REFRESH_EXPIRATION = [0-9]*(y|d|h|m|s)
MAIL_USERNAME = mail_username
MAIL_PASSWORD = mail_password

View file

@ -35,12 +35,13 @@ WORKDIR /app
RUN mkdir -p /app/export
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
ENV SERVER_PORT=5000
COPY --from=build /app/src/templates /app/src/templates
COPY --from=build /app/dist /app/dist
COPY --from=build /app/node_modules /app/node_modules
COPY --from=build /app/package.json /app/package.json
EXPOSE 5000
EXPOSE ${SERVER_PORT}
CMD [ "npm", "run", "start" ]

View file

@ -6,7 +6,7 @@ Administration für Feuerwehren und Vereine (Backend).
Dieses Projekt, `ff-admin-server`, ist das Backend zur Verwaltung von Mitgliederdaten. Die zugehörige Webapp ist im Repository [ff-admin-ui](https://forgejo.jk-effects.cloud/Ehrenamt/ff-admin) zu finden.
Eine Demo zusammen mit der `ff-admin` finden Sie unter [https://admin-demo.ff-admin.de](https://admin-demo.ff-admin.de).
Eine Demo zusammen mit der `ff-admin` finden Sie unter [ff-admin-demo.jk-effects.cloud](ff-admin-demo.jk-effects.cloud).
## Installation
@ -25,21 +25,20 @@ services:
container_name: ff_member_administration_server
restart: unless-stopped
environment:
- DB_TYPE=<mysql|sqlite> # default ist auf mysql gesetzt
- DB_TYPE=mysql
- DB_HOST=ffm-db
- DB_PORT=<number> # default ist auf 3306 gesetzt
- DB_PORT=<number> # fallback ist auf 3306 gesetzt
- DB_NAME=ffadmin
- DB_USERNAME=administration_backend
- DB_PASSWORD=<dbuserpasswd>
- JWT_SECRET=<tobemodified>
- JWT_EXPIRATION=<number[m|d] - bsp.:15m> # default ist auf 15m gesetzt
- REFRESH_EXPIRATION=<number[m|d] - bsp.:1d> # default ist auf 1d gesetzt
- PWA_REFRESH_EXPIRATION=<number[m|d] - bsp.:5d> # default ist auf 5d gesetzt
- JWT_EXPIRATION=<number[m|d] - bsp.:15m>
- REFRESH_EXPIRATION=<number[m|d] - bsp.:1d>
- MAIL_USERNAME=<mailadress|username>
- MAIL_PASSWORD=<password>
- MAIL_HOST=<url>
- MAIL_PORT=<port> # default ist auf 578 gesetzt
- MAIL_SECURE=<boolean> # default ist auf false gesetzt
- MAIL_PORT=<port>
- MAIL_SECURE=<boolean>
- CLUB_NAME=<tobemodified>
- CLUB_WEBSITE=<tobemodified>
volumes:

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "ff-admin-server",
"version": "1.1.1",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ff-admin-server",
"version": "1.1.1",
"version": "1.0.0",
"license": "GPL-3.0-only",
"dependencies": {
"cors": "^2.8.5",

View file

@ -1,6 +1,6 @@
{
"name": "ff-admin-server",
"version": "1.1.1",
"version": "1.0.0",
"description": "Feuerwehr/Verein Mitgliederverwaltung Server",
"main": "dist/index.js",
"scripts": {
@ -30,8 +30,10 @@
"handlebars": "^4.7.8",
"ics": "^3.8.1",
"jsonwebtoken": "^9.0.2",
"knex": "^3.1.0",
"moment": "^2.30.1",
"ms": "^2.1.3",
"mssql": "^10.0.4",
"mysql": "^2.18.1",
"node-schedule": "^2.1.1",
"nodemailer": "^6.9.14",

View file

@ -43,6 +43,7 @@ export default abstract class MemberCommandHandler {
* @returns {Promise<void>}
*/
static async update(updateMember: UpdateMemberCommand): Promise<void> {
console.log(updateMember);
return await dataSource
.createQueryBuilder()
.update(member)

View file

@ -31,7 +31,7 @@ export default abstract class MembershipCommandHandler {
.createQueryBuilder()
.update(membership)
.set({
end: new Date(new Date(createMembership.start).setDate(new Date(createMembership.start).getDate() - 1)),
end: new Date(createMembership.start).setDate(new Date(createMembership.start).getDate() - 1),
terminationReason: "beendet durch neuen Eintrag.",
})
.where("end IS NULL")

View file

@ -6,5 +6,4 @@ export interface SynchronizeProtocolPresenceCommand {
export interface ProtocolPresenceCommand {
memberId: number;
absent: boolean;
excused: boolean;
}

View file

@ -74,7 +74,6 @@ export default abstract class ProtocolPresenceCommandHandler {
.update(protocolPresence)
.set({
absent: member.absent,
excused: member.excused,
})
.where("memberId = :memberId", { memberId: member.memberId })
.andWhere("protocolId = :protocolId", { protocolId })

View file

@ -1,6 +1,5 @@
export interface CreateRefreshCommand {
userId: number;
isFromPwa?: boolean;
}
export interface DeleteRefreshCommand {

View file

@ -1,9 +1,11 @@
import { dataSource } from "../data-source";
import { refresh } from "../entity/refresh";
import { PWA_REFRESH_EXPIRATION, REFRESH_EXPIRATION } from "../env.defaults";
import { REFRESH_EXPIRATION } from "../env.defaults";
import InternalException from "../exceptions/internalException";
import { JWTHelper } from "../helpers/jwtHelper";
import { StringHelper } from "../helpers/stringHelper";
import UserService from "../service/user/userService";
import { JWTRefresh } from "../type/jwtTypes";
import { CreateRefreshCommand, DeleteRefreshCommand } from "./refreshCommand";
import ms from "ms";
@ -14,6 +16,10 @@ export default abstract class RefreshCommandHandler {
* @returns {Promise<string>}
*/
static async create(createRefresh: CreateRefreshCommand): Promise<string> {
// let createRefreshToken: JWTRefresh = {
// userId: createRefresh.userId,
// };
// const refreshToken = await JWTHelper.create(createRefreshToken);
const refreshToken = StringHelper.random(32);
return await dataSource
@ -23,9 +29,7 @@ export default abstract class RefreshCommandHandler {
.values({
token: refreshToken,
user: await UserService.getById(createRefresh.userId),
expiry: createRefresh.isFromPwa
? new Date(Date.now() + ms(PWA_REFRESH_EXPIRATION))
: new Date(Date.now() + ms(REFRESH_EXPIRATION)),
expiry: new Date(Date.now() + ms(REFRESH_EXPIRATION)),
})
.execute()
.then((result) => {

View file

@ -60,10 +60,7 @@ export default abstract class AwardCommandHandler {
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException(
`Failed deleting award ${err.code.includes("ER_ROW_IS_REFERENCED") ? " due to referenced data" : ""}`,
err
);
throw new InternalException("Failed deleting award", err);
});
}
}

View file

@ -66,10 +66,7 @@ export default abstract class CalendarTypeCommandHandler {
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException(
`Failed deleting calendarType${err.code.includes("ER_ROW_IS_REFERENCED") ? " due to referenced data" : ""}`,
err
);
throw new InternalException("Failed deleting calendarType", err);
});
}
}

View file

@ -66,12 +66,7 @@ export default abstract class CommunicationTypeCommandHandler {
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException(
`Failed deleting communicationType${
err.code.includes("ER_ROW_IS_REFERENCED") ? " due to referenced data" : ""
}`,
err
);
throw new InternalException("Failed deleting communicationType", err);
});
}
}

View file

@ -64,12 +64,7 @@ export default abstract class ExecutivePositionCommandHandler {
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException(
`Failed deleting executivePosition${
err.code.includes("ER_ROW_IS_REFERENCED") ? " due to referenced data" : ""
}`,
err
);
throw new InternalException("Failed deleting executivePosition", err);
});
}
}

View file

@ -64,12 +64,7 @@ export default abstract class MembershipStatusCommandHandler {
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException(
`Failed deleting membershipStatus${
err.code.includes("ER_ROW_IS_REFERENCED") ? " due to referenced data" : ""
}`,
err
);
throw new InternalException("Failed deleting membershipStatus", err);
});
}
}

View file

@ -4,3 +4,7 @@ export interface SetNewsletterConfigCommand {
comTypeId: number;
config: NewsletterConfigType;
}
export interface DeleteNewsletterConfigCommand {
comTypeId: number;
}

View file

@ -1,7 +1,7 @@
import { dataSource } from "../../../data-source";
import { newsletterConfig } from "../../../entity/settings/newsletterConfig";
import InternalException from "../../../exceptions/internalException";
import { SetNewsletterConfigCommand } from "./newsletterConfigCommand";
import { DeleteNewsletterConfigCommand, SetNewsletterConfigCommand } from "./newsletterConfigCommand";
export default abstract class NewsletterConfigCommandHandler {
/**
@ -27,4 +27,21 @@ export default abstract class NewsletterConfigCommandHandler {
throw new InternalException("Failed setting newsletterConfig", err);
});
}
/**
* @description delete newsletterConfig
* @param {DeleteNewsletterConfigCommand} deleteNewsletterConfig
* @returns {Promise<void>}
*/
static async delete(deleteNewsletterConfig: DeleteNewsletterConfigCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.delete()
.from(newsletterConfig)
.where("comTypeId = :comTypeId", { comTypeId: deleteNewsletterConfig.comTypeId })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException("Failed setting newsletterConfig", err);
});
}
}

View file

@ -66,10 +66,7 @@ export default abstract class QualificationCommandHandler {
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException(
`Failed deleting qualification${err.code.includes("ER_ROW_IS_REFERENCED") ? " due to referenced data" : ""}`,
err
);
throw new InternalException("Failed deleting qualification", err);
});
}
}

View file

@ -63,10 +63,7 @@ export default abstract class QueryStoreCommandHandler {
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException(
`Failed deleting queryStore${err.code.includes("ER_ROW_IS_REFERENCED") ? " due to referenced data" : ""}`,
err
);
throw new InternalException("Failed deleting queryStore", err);
});
}
}

View file

@ -64,10 +64,7 @@ export default abstract class TemplateCommandHandler {
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException(
`Failed deleting template${err.code.includes("ER_ROW_IS_REFERENCED") ? " due to referenced data" : ""}`,
err
);
throw new InternalException("Failed deleting template", err);
});
}
}

View file

@ -61,13 +61,8 @@ export async function getAllMembers(req: Request, res: Response): Promise<any> {
let offset = parseInt((req.query.offset as string) ?? "0");
let count = parseInt((req.query.count as string) ?? "25");
let search = (req.query.search as string) ?? "";
let noLimit = req.query.noLimit === "true";
let ids = ((req.query.ids ?? "") as string)
.split(",")
.filter((i) => i)
.map((i) => parseInt(i));
let [members, total] = await MemberService.getAll({ offset, count, search, noLimit, ids });
let [members, total] = await MemberService.getAll(offset, count, search);
res.json({
members: MemberFactory.mapToBase(members),
@ -232,14 +227,14 @@ export async function getCommunicationByMemberAndRecord(req: Request, res: Respo
* @returns {Promise<*>}
*/
export async function createMemberPrintoutList(req: Request, res: Response): Promise<any> {
let members = await MemberService.getByRunningMembership();
let members = await MemberService.getByRunningMembership()
let pdf = await PdfExport.renderFile({
title: "Mitgliederliste",
template: "member.list",
saveToDisk: false,
data: {
member: members,
member: members
},
});
@ -601,7 +596,7 @@ export async function updateCommunicationOfMember(req: Request, res: Response):
communicationId: recordId,
};
await MemberCommandHandler.updateNewsletter(updateNewsletter);
} else if (currentUserNewsletterMain.sendNewsletter?.id == recordId) {
} else if (currentUserNewsletterMain.sendNewsletter.id == recordId) {
await MemberCommandHandler.unsetNewsletter(memberId);
}

View file

@ -6,10 +6,7 @@ import NewsletterDatesFactory from "../../../factory/admin/club/newsletter/newsl
import NewsletterRecipientsService from "../../../service/club/newsletter/newsletterRecipientsService";
import NewsletterRecipientsFactory from "../../../factory/admin/club/newsletter/newsletterRecipients";
import { FileSystemHelper } from "../../../helpers/fileSystemHelper";
import {
CreateNewsletterCommand,
SynchronizeNewsletterCommand,
} from "../../../command/club/newsletter/newsletterCommand";
import { CreateNewsletterCommand, SynchronizeNewsletterCommand } from "../../../command/club/newsletter/newsletterCommand";
import NewsletterCommandHandler from "../../../command/club/newsletter/newsletterCommandHandler";
import { SynchronizeNewsletterDatesCommand } from "../../../command/club/newsletter/newsletterDatesCommand";
import NewsletterDatesCommandHandler from "../../../command/club/newsletter/newsletterDatesCommandHandler";
@ -191,14 +188,15 @@ export async function createNewsletter(req: Request, res: Response): Promise<any
export async function getNewsletterPrintoutProgressById(req: Request, res: Response): Promise<any> {
let newsletterId = parseInt(req.params.newsletterId);
res.writeHead(200, {
"Content-Type": "text/plain",
"Transfer-Encoding": "chunked",
});
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
res.flushHeaders();
const progressHandler = (data: NewsletterEventType) => {
if (data.newsletterId == newsletterId && data.kind == "pdf") {
res.write(JSON.stringify(data) + "//");
res.write(JSON.stringify(data));
}
};
@ -289,14 +287,15 @@ export async function sendNewsletterById(req: Request, res: Response): Promise<a
export async function getNewsletterSendingProgressById(req: Request, res: Response): Promise<any> {
let newsletterId = parseInt(req.params.newsletterId);
res.writeHead(200, {
"Content-Type": "text/plain",
"Transfer-Encoding": "chunked",
});
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
res.flushHeaders();
const progressHandler = (data: NewsletterEventType) => {
if (data.newsletterId == newsletterId && data.kind == "mail") {
res.write(JSON.stringify(data) + "//");
res.write(JSON.stringify(data));
}
};

View file

@ -260,9 +260,7 @@ export async function createProtocolPrintoutById(req: Request, res: Response): P
agenda,
decisions,
presence: presence.filter((p) => !p.absent).map((p) => p.member),
absent: presence.filter((p) => p.absent).map((p) => ({ ...p.member, excused: p.excused })),
excused_absent: presence.filter((p) => p.absent && p.excused).map((p) => p.member),
unexcused_absent: presence.filter((p) => p.absent && !p.excused).map((p) => p.member),
absent: presence.filter((p) => p.absent).map((p) => p.member),
votings,
},
});
@ -391,7 +389,6 @@ export async function synchronizeProtocolPrecenseById(req: Request, res: Respons
members: presence.map((p) => ({
memberId: p.memberId,
absent: p.absent,
excused: p.excused,
})),
protocolId,
};

View file

@ -2,7 +2,7 @@ import { Request, Response } from "express";
import NewsletterConfigService from "../../../service/settings/newsletterConfigService";
import NewsletterConfigFactory from "../../../factory/admin/settings/newsletterConfig";
import NewsletterConfigCommandHandler from "../../../command/settings/newsletterConfig/newsletterConfigCommandHandler";
import { SetNewsletterConfigCommand } from "../../../command/settings/newsletterConfig/newsletterConfigCommand";
import { DeleteNewsletterConfigCommand, SetNewsletterConfigCommand } from "../../../command/settings/newsletterConfig/newsletterConfigCommand";
/**
* @description get all newsletterConfigs
@ -47,3 +47,20 @@ export async function setNewsletterConfig(req: Request, res: Response): Promise<
res.send(id);
}
/**
* @description delete award
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function deleteNewsletterConfig(req: Request, res: Response): Promise<any> {
const comTypeId = parseInt(req.params.comTypeId);
let deleteNewsletterConfig: DeleteNewsletterConfigCommand = {
comTypeId: comTypeId,
};
await NewsletterConfigCommandHandler.delete(deleteNewsletterConfig);
res.sendStatus(204);
}

View file

@ -8,6 +8,9 @@ import UserService from "../service/user/userService";
import speakeasy from "speakeasy";
import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException";
import RefreshService from "../service/refreshService";
import UserPermissionService from "../service/user/userPermissionService";
import PermissionHelper from "../helpers/permissionHelper";
import RolePermissionService from "../service/user/rolePermissionService";
/**
* @description Check authentication status by token
@ -36,7 +39,6 @@ export async function login(req: Request, res: Response): Promise<any> {
let refreshCommand: CreateRefreshCommand = {
userId: id,
isFromPwa: req.isPWA,
};
let refreshToken = await RefreshCommandHandler.create(refreshCommand);
@ -81,7 +83,6 @@ export async function refresh(req: Request, res: Response): Promise<any> {
let refreshCommand: CreateRefreshCommand = {
userId: tokenUserId,
isFromPwa: req.isPWA,
};
let refreshToken = await RefreshCommandHandler.create(refreshCommand);

View file

@ -66,8 +66,6 @@ import { PostalCode1735927918979 } from "./migrations/1735927918979-postalCode";
import { ProtocolAbsent1736072179716 } from "./migrations/1736072179716-protocolAbsent";
import { Memberlist1736079005086 } from "./migrations/1736079005086-memberlist";
import { ExtendViewValues1736084198860 } from "./migrations/1736084198860-extendViewValues";
import { FinishInternalIdTransfer1736505324488 } from "./migrations/1736505324488-finishInternalIdTransfer";
import { ProtocolPresenceExcuse1737287798828 } from "./migrations/1737287798828-protocolPresenceExcuse";
const dataSource = new DataSource({
type: DB_TYPE as any,
@ -144,8 +142,6 @@ const dataSource = new DataSource({
ProtocolAbsent1736072179716,
Memberlist1736079005086,
ExtendViewValues1736084198860,
FinishInternalIdTransfer1736505324488,
ProtocolPresenceExcuse1737287798828,
],
migrationsRun: true,
migrationsTransactionMode: "each",

View file

@ -40,17 +40,17 @@ export class communication {
@Column()
typeId: number;
@ManyToOne(() => member, (member) => member.awards, {
nullable: false,
onDelete: "CASCADE",
onUpdate: "RESTRICT",
})
member: member;
@ManyToOne(() => communicationType, (communicationType) => communicationType.communications, {
nullable: false,
onDelete: "RESTRICT",
onUpdate: "RESTRICT",
})
type: communicationType;
@ManyToOne(() => member, (member) => member.awards, {
nullable: false,
onDelete: "CASCADE",
onUpdate: "RESTRICT",
})
member: member;
}

View file

@ -7,6 +7,12 @@ export class membership {
@PrimaryColumn({ generated: "increment", type: "int" })
id: number;
/**
* @deprecated
*/
@Column({ type: "varchar", length: 255, unique: true, nullable: true })
internalId?: string;
@Column({ type: "date" })
start: Date;

View file

@ -13,9 +13,6 @@ export class protocolPresence {
@Column({ type: "boolean", default: false })
absent: boolean;
@Column({ type: "boolean", default: true })
excused: boolean;
@ManyToOne(() => member, {
nullable: false,
onDelete: "CASCADE",

View file

@ -9,7 +9,7 @@ export class protocolPrintout {
@Column({ type: "varchar", length: 255 })
title: string;
@Column({ type: "int", default: "1" })
@Column({ type: "int" })
iteration: number;
@Column({ type: "varchar", length: 255 })

View file

@ -13,7 +13,6 @@ export const SERVER_PORT = Number(process.env.SERVER_PORT ?? 5000);
export const JWT_SECRET = process.env.JWT_SECRET ?? "my_jwt_secret_string_ilughfnadiuhgq§$IUZGFVRweiouarbt1oub3h5q4a";
export const JWT_EXPIRATION = process.env.JWT_EXPIRATION ?? "15m";
export const REFRESH_EXPIRATION = process.env.REFRESH_EXPIRATION ?? "1d";
export const PWA_REFRESH_EXPIRATION = process.env.PWA_REFRESH_EXPIRATION ?? "5d";
export const MAIL_USERNAME = process.env.MAIL_USERNAME ?? "";
export const MAIL_PASSWORD = process.env.MAIL_PASSWORD ?? "";
@ -36,7 +35,6 @@ export function configCheck() {
if (JWT_SECRET == "" || typeof JWT_SECRET != "string") throw new Error("set valid value to JWT_SECRET");
checkMS(JWT_EXPIRATION, "JWT_EXPIRATION");
checkMS(REFRESH_EXPIRATION, "REFRESH_EXPIRATION");
checkMS(PWA_REFRESH_EXPIRATION, "PWA_REFRESH_EXPIRATION");
if (MAIL_USERNAME == "" || typeof MAIL_USERNAME != "string") throw new Error("set valid value to MAIL_USERNAME");
if (MAIL_PASSWORD == "" || typeof MAIL_PASSWORD != "string") throw new Error("set valid value to MAIL_PASSWORD");
@ -44,6 +42,7 @@ export function configCheck() {
if (typeof MAIL_PORT != "number") throw new Error("set valid numeric value to MAIL_PORT");
if (MAIL_SECURE != "true" && MAIL_SECURE != "false") throw new Error("set 'true' or 'false' to MAIL_SECURE");
console.log(CLUB_WEBSITE);
if (
CLUB_WEBSITE != "" &&
!/^(http(s):\/\/.)[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/.test(CLUB_WEBSITE)

View file

@ -12,7 +12,6 @@ export default abstract class ProtocolPresenceFactory {
return {
memberId: record.member.id,
absent: record.absent,
excused: record.excused,
protocolId: record.protocolId,
};
}

View file

@ -33,21 +33,12 @@ export abstract class NewsletterHelper {
private static formatJobEmit(
event: "progress" | "complete",
kind: "pdf" | "mail",
factor: "success" | "failed" | "info",
newsletterId: number,
total: number,
iteration: number,
msg: string
) {
this.jobStatus.emit<NewsletterEventType>(event, {
kind,
newsletterId,
factor,
total,
iteration,
msg,
date: new Date(),
});
this.jobStatus.emit<NewsletterEventType>(event, { kind, newsletterId, total, iteration, msg, date: new Date() });
}
public static buildData(
@ -141,7 +132,9 @@ export abstract class NewsletterHelper {
}
}
let members = await MemberService.getAll({ noLimit: true });
console.log(queryMemberIds);
let members = await MemberService.getAll(0, 1000);
return members[0].filter((m) => queryMemberIds.includes(m.id));
}
@ -178,9 +171,18 @@ export abstract class NewsletterHelper {
allowedForMail.includes(m.sendNewsletter?.type?.id)
);
this.formatJobEmit("progress", "mail", "info", newsletterId, mailRecipients.length, 0, "starting sending");
this.formatJobEmit("progress", "mail", newsletterId, mailRecipients.length, 0, "starting sending");
for (const [index, rec] of mailRecipients.entries()) {
this.formatJobEmit(
"progress",
"mail",
newsletterId,
mailRecipients.length,
index,
`start sending to ${rec.sendNewsletter.email}`
);
let data = this.buildData(newsletter, dates, rec);
const { body } = await TemplateHelper.renderFileForModule({
@ -195,10 +197,9 @@ export abstract class NewsletterHelper {
this.formatJobEmit(
"progress",
"mail",
"success",
newsletterId,
mailRecipients.length,
index + 1,
index,
`successfully sent to ${rec.sendNewsletter.email}`
);
})
@ -206,19 +207,18 @@ export abstract class NewsletterHelper {
this.formatJobEmit(
"progress",
"mail",
"failed",
newsletterId,
mailRecipients.length,
index + 1,
index,
`failed to send to ${rec.sendNewsletter.email}`
);
console.log("mail send", err);
});
}
this.formatJobEmit(
"complete",
"mail",
"info",
newsletterId,
mailRecipients.length,
mailRecipients.length,
@ -249,12 +249,21 @@ export abstract class NewsletterHelper {
(m) => !notAllowedForPdf.includes(m.sendNewsletter?.type?.id) || m.sendNewsletter == null
);
this.formatJobEmit("progress", "pdf", "info", newsletterId, pdfRecipients.length + 1, 0, "starting sending");
this.formatJobEmit("progress", "pdf", newsletterId, pdfRecipients.length, 0, "starting sending");
for (const [index, rec] of [
...pdfRecipients,
{ id: 0, firstname: "Alle Mitglieder", lastname: CLUB_NAME } as member,
].entries()) {
this.formatJobEmit(
"progress",
"pdf",
newsletterId,
pdfRecipients.length,
index,
`start print for ${rec.lastname}, ${rec.firstname}`
);
let data = this.buildData(newsletter, dates, rec, printWithAdress.includes(rec.sendNewsletter?.type?.id));
await PdfExport.renderFile({
@ -268,10 +277,9 @@ export abstract class NewsletterHelper {
this.formatJobEmit(
"progress",
"pdf",
"success",
newsletterId,
pdfRecipients.length + 1,
index + 1,
pdfRecipients.length,
index,
`successfully printed for ${rec.lastname}, ${rec.firstname}`
);
})
@ -279,15 +287,24 @@ export abstract class NewsletterHelper {
this.formatJobEmit(
"progress",
"pdf",
"failed",
newsletterId,
pdfRecipients.length + 1,
index + 1,
pdfRecipients.length,
index,
`failed print for ${rec.lastname}, ${rec.firstname}`
);
console.log("pdf print", err);
});
}
this.formatJobEmit(
"progress",
"pdf",
newsletterId,
pdfRecipients.length,
pdfRecipients.length,
"starting pdf combine"
);
await PdfExport.sqashToSingleFile(
`newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`,
"allPdfsTogether",
@ -297,10 +314,9 @@ export abstract class NewsletterHelper {
this.formatJobEmit(
"progress",
"pdf",
"success",
newsletterId,
pdfRecipients.length + 1,
pdfRecipients.length + 1,
pdfRecipients.length,
pdfRecipients.length,
"sucessfully combined pdf"
);
})
@ -308,21 +324,20 @@ export abstract class NewsletterHelper {
this.formatJobEmit(
"progress",
"pdf",
"failed",
newsletterId,
pdfRecipients.length + 1,
pdfRecipients.length + 1,
pdfRecipients.length,
pdfRecipients.length,
"failed combining pdf"
);
console.log("pdf squash", err);
});
this.formatJobEmit(
"complete",
"pdf",
"info",
newsletterId,
pdfRecipients.length + 1,
pdfRecipients.length + 1,
pdfRecipients.length,
pdfRecipients.length,
`completed printing process`
);
}

View file

@ -12,7 +12,6 @@ declare global {
username: string;
isOwner: boolean;
permissions: PermissionObject;
isPWA: boolean;
}
}
}

View file

@ -1,11 +0,0 @@
import { Request, Response } from "express";
export default async function detectPWA(req: Request, res: Response, next: Function) {
const userAgent = req.headers["user-agent"] || "";
if ((userAgent.includes("Mobile") && userAgent.includes("Standalone")) || req.headers["x-pwa-client"] === "true") {
req.isPWA = true;
} else {
req.isPWA = false;
}
next();
}

View file

@ -1,7 +1,6 @@
import { Request, Response } from "express";
import { ExceptionBase } from "../exceptions/exceptionsBaseType";
import CustomRequestException from "../exceptions/customRequestException";
import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException";
export default function errorHandler(err: ExceptionBase | Error, req: Request, res: Response, next: Function) {
let status = 500;
@ -12,11 +11,7 @@ export default function errorHandler(err: ExceptionBase | Error, req: Request, r
msg = err.message;
}
if (err instanceof CustomRequestException) {
console.log("Custom Handler", status, msg);
} else {
console.log("Error Handler", err);
}
console.log("Handler", err);
res.status(status).send(msg);
}

View file

@ -18,25 +18,27 @@ export class InternalId1735822722235 implements MigrationInterface {
})
);
// let memberships = await queryRunner.manager.getRepository(membership).find();
// let internalIds = memberships.reduce<{ [key: number]: Array<string> }>((acc, cur) => {
// let memberId = cur.memberId;
// let setIds = acc[memberId] ?? [];
// if (cur?.internalId) {
// setIds.push(cur.internalId);
// }
// acc[memberId] = setIds;
// return acc;
// }, {});
let memberships = await queryRunner.manager.getRepository(membership).find();
console.log(memberships);
let internalIds = memberships.reduce<{ [key: number]: Array<string> }>((acc, cur) => {
let memberId = cur.memberId;
let setIds = acc[memberId] ?? [];
if (cur?.internalId) {
setIds.push(cur.internalId);
}
acc[memberId] = setIds;
return acc;
}, {});
// for (const [id, value] of Object.entries(internalIds)) {
// const ids = value.filter((v) => v != null).join(", ");
// if (ids) {
// let m = await queryRunner.manager.getRepository(member).findOneByOrFail({ id: parseInt(id) });
// m.internalId = ids;
// await queryRunner.manager.getRepository(member).save(m);
// }
// }
console.log(internalIds);
for (const [id, value] of Object.entries(internalIds)) {
const ids = value.filter((v) => v != null).join(", ");
if (ids) {
let m = await queryRunner.manager.getRepository(member).findOneByOrFail({ id: parseInt(id) });
m.internalId = ids;
await queryRunner.manager.getRepository(member).save(m);
}
}
}
public async down(queryRunner: QueryRunner): Promise<void> {

View file

@ -1,23 +0,0 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
export class FinishInternalIdTransfer1736505324488 implements MigrationInterface {
name = "FinishInternalIdTransfer1736505324488";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn("membership", "internalId");
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
"membership",
new TableColumn({
name: "internalId",
type: "varchar",
length: "255",
default: null,
isNullable: true,
isUnique: true,
})
);
}
}

View file

@ -1,21 +0,0 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
export class ProtocolPresenceExcuse1737287798828 implements MigrationInterface {
name = "ProtocolPresenceExcuse1737287798828";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
"protocol_presence",
new TableColumn({
name: "excused",
type: "tinyint",
default: "1",
isNullable: false,
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn("protocol_presence", "excused");
}
}

View file

@ -1,5 +1,6 @@
import express, { Request, Response } from "express";
import {
deleteNewsletterConfig,
getAllNewsletterConfigs,
getNewsletterConfigById,
setNewsletterConfig,
@ -24,4 +25,12 @@ router.put(
}
);
router.delete(
"/:comTypeId",
PermissionHelper.passCheckMiddleware("create", "settings", "newsletter_config"),
async (req: Request, res: Response) => {
await deleteNewsletterConfig(req, res);
}
);
export default router;

584
src/routes/fillfromold.ts Normal file
View file

@ -0,0 +1,584 @@
import express from "express";
import knex from "knex";
import moment from "moment";
import AwardService from "../service/settings/awardService";
import { CreateAwardCommand, DeleteAwardCommand } from "../command/settings/award/awardCommand";
import AwardCommandHandler from "../command/settings/award/awardCommandHandler";
import MemberService from "../service/club/member/memberService";
import { CreateMemberCommand, DeleteMemberCommand } from "../command/club/member/memberCommand";
import MemberCommandHandler from "../command/club/member/memberCommandHandler";
import MemberAwardCommandHandler from "../command/club/member/memberAwardCommandHandler";
import { CreateMemberAwardCommand, DeleteMemberAwardCommand } from "../command/club/member/memberAwardCommand";
import MemberAwardService from "../service/club/member/memberAwardService";
import CommunicationService from "../service/club/member/communicationService";
import { CreateCommunicationCommand, DeleteCommunicationCommand } from "../command/club/member/communicationCommand";
import CommunicationCommandHandler from "../command/club/member/communicationCommandHandler";
import MembershipService from "../service/club/member/membershipService";
import {
CreateMembershipCommand,
DeleteMembershipCommand,
UpdateMembershipCommand,
} from "../command/club/member/membershipCommand";
import MembershipCommandHandler from "../command/club/member/membershipCommandHandler";
import MemberExecutivePositionService from "../service/club/member/memberExecutivePositionService";
import {
CreateMemberExecutivePositionCommand,
DeleteMemberExecutivePositionCommand,
} from "../command/club/member/memberExecutivePositionCommand";
import MemberExecutivePositionCommandHandler from "../command/club/member/memberExecutivePositionCommandHandler";
import CommunicationTypeService from "../service/settings/communicationTypeService";
import {
CreateCommunicationTypeCommand,
DeleteCommunicationTypeCommand,
} from "../command/settings/communicationType/communicationTypeCommand";
import CommunicationTypeCommandHandler from "../command/settings/communicationType/communicationTypeCommandHandler";
import ExecutivePositionService from "../service/settings/executivePositionService";
import {
CreateExecutivePositionCommand,
DeleteExecutivePositionCommand,
} from "../command/settings/executivePosition/executivePositionCommand";
import ExecutivePositionCommandHandler from "../command/settings/executivePosition/executivePositionCommandHandler";
import MembershipStatusService from "../service/settings/membershipStatusService";
import {
CreateMembershipStatusCommand,
DeleteMembershipStatusCommand,
} from "../command/settings/membershipStatus/membershipStatusCommand";
import MembershipStatusCommandHandler from "../command/settings/membershipStatus/membershipStatusCommandHandler";
var router = express.Router({ mergeParams: true });
router.get("/", async (req, res) => {
const dbhost = process.env.OLD_DB_HOST;
const dbport = Number(process.env.OLD_DB_PORT);
const dbuser = process.env.OLD_DB_USER;
const dbuserpass = process.env.OLD_DB_USERPASS;
const dbname = process.env.OLD_DB_NAME;
const db = knex({
client: 'mssql',
connection: {
host: dbhost,
port: dbport,
user: dbuser,
password: dbuserpass,
database: dbname,
options: {
trustServerCertificate: true,
enableArithAbort: true,
encrypt: true
}
},
debug: false,
pool: {
min: 0, max: 30
}
});
let queryResult = await db('Mitglieder');
console.log(`Read ${queryResult.length} Mitglieder`);
// Delete members
let [members, total] = await MemberService.getAll(0, 9999);
for (const m of members) {
// delete all member awards of this member
let memberAwards = await MemberAwardService.getAll(m.id);
for (const award of memberAwards) {
let deleteMemberAward: DeleteMemberAwardCommand = {
id: award.id, memberId: m.id
};
console.log(`Deleting award ${award.id} from ${m.firstname} ${m.lastname}`);
await MemberAwardCommandHandler.delete(deleteMemberAward)
}
// Delete communication from this member
let communications = await CommunicationService.getAll(m.id);
for (const comm of communications) {
let deleteCommunication: DeleteCommunicationCommand = {
id: comm.id, memberId: 0
};
console.log(`Delete communication ${comm.type} from ${m.firstname} ${m.lastname}`);
await CommunicationCommandHandler.delete(deleteCommunication);
}
// Delete membership entries
let memberships = await MembershipService.getAll(m.id);
for (const membership of memberships) {
let deleteMembership: DeleteMembershipCommand = {
id: membership.id, memberId: m.id
};
console.log(`Delete membership ${membership.id} from ${m.firstname} ${m.lastname}`);
await MembershipCommandHandler.delete(deleteMembership);
}
// Delete membership positions entries
let memberExecutivePositions = await MemberExecutivePositionService.getAll(m.id);
for (const memberPos of memberExecutivePositions) {
let deleteMemberExecutivePosition: DeleteMemberExecutivePositionCommand = {
id: memberPos.id, memberId: m.id
};
console.log(`Delete position ${deleteMemberExecutivePosition.id} from ${m.firstname} ${m.lastname}`);
await MemberExecutivePositionCommandHandler.delete(deleteMemberExecutivePosition);
}
// delete member itself
let deleteMember: DeleteMemberCommand = {
id: m.id,
};
console.log(`Delete member ${m.id}`);
await MemberCommandHandler.delete(deleteMember);
}
// Delete awards
let awards = await AwardService.getAll();
for (const award of awards) {
let deleteAward: DeleteAwardCommand = {
id: award.id,
};
console.log(`Delete award ${award.id}`);
await AwardCommandHandler.delete(deleteAward);
}
// Delete communication types
let commTypes = await CommunicationTypeService.getAll();
for (const commType of commTypes) {
let deleteCommunicationType: DeleteCommunicationTypeCommand = {
id: commType.id,
};
console.log(`Delete communication type ${commType.type}`);
await CommunicationTypeCommandHandler.delete(deleteCommunicationType);
}
// Delete postions
let positions = await ExecutivePositionService.getAll();
for (const position of positions) {
let deletePosition: DeleteExecutivePositionCommand = {
id: position.id,
};
console.log(`Delete position ${position.position}`);
await ExecutivePositionCommandHandler.delete(deletePosition);
}
// Delete membership states
let states = await MembershipStatusService.getAll();
for (const state of states) {
let deleteMembershipStatus: DeleteMembershipStatusCommand = {
id: state.id
};
console.log(`Delete membership status ${state.status}`);
await MembershipStatusCommandHandler.delete(deleteMembershipStatus);
}
// Create Awards
const sAwardsById :any = {};
const sAwardsByName :any = {};
const awardNames = ['Ehrennadel Silber', 'Ehrennadel Gold', 'Ehrenkreuz Silber', 'Ehrenkreuz Gold', 'Ehrenmitgliedschaft', 'Ehrung 25 Jahre aktiv', 'Ehrung 40 Jahre aktiv'];
for (const awardName of awardNames) {
let createAward: CreateAwardCommand = {
award: awardName,
};
const awardId = await AwardCommandHandler.create(createAward);
console.log(`Created award ${awardName} with id ${awardId}`);
sAwardsById[awardId] = awardName;
sAwardsByName[awardName] = awardId;
}
// Create communication types
const createCommunicationTypeMail: CreateCommunicationTypeCommand = {
type: "Post", useColumns: ['city','street','streetNumber','streetNumberAddition']
}
const createCommunicationTypeEMail: CreateCommunicationTypeCommand = {
type: "Email", useColumns: ['email']
}
const createCommunicationTypeSMS: CreateCommunicationTypeCommand = {
type: "SMS", useColumns: ['mobile']
}
const createCommunicationTypeWhatsApp: CreateCommunicationTypeCommand = {
type: "WhatsApp", useColumns: ['mobile']
}
const createCommTypes = [createCommunicationTypeMail, createCommunicationTypeEMail, createCommunicationTypeSMS, createCommunicationTypeWhatsApp];
const sCommTypesByName: any = {};
const sCommTypesById: any = {};
for (const createCommType of createCommTypes) {
const commTypeId = await CommunicationTypeCommandHandler.create(createCommType);
console.log(`Create communication type ${createCommType.type} with id ${commTypeId}`);
sCommTypesByName[createCommType.type] = commTypeId;
sCommTypesById[commTypeId] = createCommType.type;
}
// Create positions
const sFunktionenByOldId :any = {};
let positionsResult: any[] = await db('Funktionen');
for (const position of positionsResult) {
const posName: string = position.Name.trim();
let createExecutivePosition: CreateExecutivePositionCommand = {
position: posName,
}
sFunktionenByOldId[position.IdFunktion] = await ExecutivePositionCommandHandler.create(createExecutivePosition);
}
// create membership states
let sStatesByName : any = {};
for (const state of ['aktiv', 'passiv', 'fördernd']) {
let createMembershipStatus: CreateMembershipStatusCommand = {
status: state
}
sStatesByName[state] = await MembershipStatusCommandHandler.create(createMembershipStatus);
}
// Add all members
for (const queryResultElement of queryResult) {
const ineternalID = queryResultElement.ID
let nameAffix: string = "";
let nn = queryResultElement.Nachname.trim();
if (nn.endsWith(", jun.")) {
nameAffix = "jun.";
nn = nn.substring(0, nn.length - 6);
}
if (nn.endsWith(", sen.")) {
nameAffix = "sen.";
nn = nn.substring(0, nn.length - 6);
}
// before 1.1.2009: Altersobergrenze für Aktive: 60
const limit60 = moment('2009-01-01');
// before 1.7.2017: 63
const limit63 = moment('2017-07-01');
// from 1.7.2017: 65
const alter60 = moment(queryResultElement.Geboren).add(60, 'years');
const alter63 = moment(queryResultElement.Geboren).add(63, 'years');
const alter65 = moment(queryResultElement.Geboren).add(65, 'years');
const eintritt = moment(queryResultElement.Eingetreten);
const verstorben = moment(queryResultElement.verstorben);
let newMember: CreateMemberCommand = {
salutation: queryResultElement.Anrede.trim(),
firstname: queryResultElement.Vorname.trim(),
lastname: nn,
nameaffix: nameAffix,
birthdate: queryResultElement.Geboren ? queryResultElement.Geboren : "1900-01-01",
internalId: queryResultElement.ID,
};
const memberId = await MemberCommandHandler.create(newMember);
console.log(`Created member ${newMember.firstname} ${newMember.lastname} with id ${memberId}`);
let stateId = sStatesByName["aktiv"];
if (!queryResultElement.Eingetreten) {
throw new Error('Eingetreten missing');
}
if (queryResultElement["Übergang Passiv"] && moment(queryResultElement["Übergang Passiv"]).format('L') === moment(queryResultElement.Eingetreten).format('L')) {
stateId = sStatesByName["fördernd"];
}
let start = queryResultElement.Eingetreten;
let createMembership: CreateMembershipCommand = {
memberId: memberId, start: start, statusId: stateId,
};
let ms1 = await MembershipCommandHandler.create(createMembership);
if (queryResultElement["Übergang Passiv"] && moment(queryResultElement["Übergang Passiv"]).format('L') !== moment(queryResultElement.Eingetreten).format('L')) {
// membership transitioned to passiv
// no knowledge about active time => assume active until age of 60 or 65
let end: Date = queryResultElement["Übergang Passiv"];
let terminationReason: string = "aktiv >> passiv";
let newStart = queryResultElement["Übergang Passiv"];
let newStateId = sStatesByName["passiv"];
if (queryResultElement.Geboren == null && queryResultElement.verzogenDatum) {
terminationReason = "verzogen";
end = queryResultElement.verzogenDatum
} else {
if (queryResultElement["Übergang Passiv"] < queryResultElement.Eingetreten) {
if (alter60.isBefore(limit60)) {
terminationReason = "passiv (Altersobergrenze: 60)";
end = alter60.toDate();
} else {
if (alter63.isBefore(limit63)) {
terminationReason = "passiv (Altersobergrenze: 63)";
end = alter63.toDate();
} else {
terminationReason = "passiv (Altersobergrenze: 65)";
end = alter65.toDate();
}
}
}
}
if (end) {
newStart = end;
if (!(queryResultElement.verstorben && moment(queryResultElement.verstorben).isBefore(moment(end)))) {
// finish current membership (but only if member was alive at this time)
let updateMembership: UpdateMembershipCommand = {
end: end,
id: ms1,
memberId: memberId,
start: start,
statusId: stateId,
terminationReason: terminationReason,
};
await MembershipCommandHandler.update(updateMembership);
// create new membership with passiv state
stateId = newStateId;
start = newStart;
let createMembership: CreateMembershipCommand = {
// internalId: queryResultElement.ID,
memberId: memberId,
start: start,
statusId: stateId,
};
ms1 = await MembershipCommandHandler.create(createMembership);
}
}
}
if (!queryResultElement["Übergang Passiv"] && queryResultElement.verstorben) {
let terminationReason;
let end;
let newStart;
let newStateId;
if (alter60.isBefore(limit60) && alter60.isBefore(verstorben)) {
if (alter60.isBefore(eintritt)) {
stateId = sStatesByName["passiv"];
} else {
terminationReason = "passiv (Altersobergrenze: 60)";
end = alter60.toDate();
newStart = end;
newStateId = sStatesByName["passiv"];
}
} else {
if (alter63.isBefore(limit63) && alter63.isBefore(verstorben)) {
if (alter63.isBefore(eintritt)) {
stateId = sStatesByName["passiv"];
} else {
terminationReason = "passiv (Altersobergrenze: 63)";
end = alter63.toDate();
newStart = end;
newStateId = sStatesByName["passiv"];
}
} else {
if (alter65.isBefore(verstorben)) {
if (alter65.isBefore(eintritt)) {
stateId = sStatesByName["passiv"];
} else {
terminationReason = "passiv (Altersobergrenze: 65)";
end = alter65.toDate();
newStart = end;
newStateId = sStatesByName["passiv"];
}
} else {
end = queryResultElement.verstorben;
terminationReason = "verstorben";
}
}
}
if (end) {
let updateMembership: UpdateMembershipCommand = {
end: end,
id: ms1,
memberId: memberId,
start: start,
statusId: stateId,
terminationReason: terminationReason,
};
await MembershipCommandHandler.update(updateMembership);
if (newStart) {
// create new membership with passiv state
stateId = newStateId;
start = newStart;
let createMembership: CreateMembershipCommand = {
memberId: memberId,
start: start,
statusId: stateId,
};
ms1 = await MembershipCommandHandler.create(createMembership);
}
}
}
if (queryResultElement.Ausgetreten) {
let updateMembership: UpdateMembershipCommand = {
end: queryResultElement.Ausgetreten,
id: ms1,
memberId: memberId,
start: start,
statusId: stateId,
terminationReason: "Austritt",
};
await MembershipCommandHandler.update(updateMembership);
}
if (queryResultElement.verstorben) {
let updateMembership: UpdateMembershipCommand = {
end: queryResultElement.verstorben,
id: ms1,
memberId: memberId,
start: start,
statusId: stateId,
terminationReason: "verstorben",
};
await MembershipCommandHandler.update(updateMembership);
}
if (!queryResultElement.Ausgetreten && !queryResultElement.verstorben && queryResultElement.verzogen) {
let end = new Date('2024-12-31');
if (queryResultElement.verzogenDatum) {
end = queryResultElement.verzogenDatum;
} else {
if (queryResultElement["Übergang Passiv"]) {
end = queryResultElement["Übergang Passiv"];
}
}
let updateMembership: UpdateMembershipCommand = {
end: end,
id: ms1,
memberId: memberId,
start: start,
statusId: stateId,
terminationReason: "verzogen",
};
await MembershipCommandHandler.update(updateMembership);
}
// add member's awards
for (const awardId in sAwardsById) {
const awardName = sAwardsById[awardId];
if (queryResultElement[awardName]) {
const awardDate = queryResultElement[awardName];
console.log(`Member ${newMember.firstname} ${newMember.lastname} got award ${awardName} at ${awardDate}`);
let given = true;
let note = "";
if (awardName === "Ehrung 25 Jahre aktiv" && queryResultElement["keineEhrung25JahreAktivGrund"] !== null) {
given = false;
note = queryResultElement["keineEhrung25JahreAktivGrund"].trim();
}
if (awardName === "Ehrennadel Silber" && queryResultElement["keineEhrennadelSilberGrund"] !== null) {
given = false;
note = queryResultElement["keineEhrennadelSilberGrund"].trim();
}
if (awardName === "Ehrennadel Gold" && queryResultElement["keineEhrennadelGoldGrund"] !== null) {
given = false;
note = queryResultElement["keineEhrennadelGoldGrund"].trim();
}
let newMemberAward: CreateMemberAwardCommand = {
awardId: parseInt(awardId), date: awardDate, given: given, memberId: memberId, note: note,
};
await MemberAwardCommandHandler.create(newMemberAward);
}
}
// add member's executive positions (if there are any)
let memberPositionsResult: any[] = await db('MitgliedFunktionen').where('MitgliedFunktionen.IdMitglied', '=', ineternalID);
for (const position of memberPositionsResult) {
const start = position.Beginn;
const end = position.Ende;
const executivePositionId: number = sFunktionenByOldId[position.IdFunktion]
let createMemberExecutivePosition: CreateMemberExecutivePositionCommand = {
end: end, executivePositionId: executivePositionId, memberId: memberId, note: "", start: start
}
console.log(`Adding position ${executivePositionId} to ${newMember.firstname} ${newMember.lastname}`);
await MemberExecutivePositionCommandHandler.create(createMemberExecutivePosition);
}
// add communications
if (!queryResultElement.verzogen) {
const createCommunicationCommands: CreateCommunicationCommand[] = [];
let havePreferred = false;
if (queryResultElement.Mobiltelefon && !queryResultElement.verstorben && !queryResultElement.verzogenDatum) {
const createCommunicationMobile: CreateCommunicationCommand = {
memberId: memberId,
typeId: sCommTypesByName['SMS'],
isSMSAlarming: false,
city: "",
email: "",
mobile: queryResultElement.Mobiltelefon.trim(),
postalCode: "",
preferred: false,
street: "",
streetNumber: 0,
streetNumberAddition: "",
}
createCommunicationCommands.push(createCommunicationMobile);
}
if (queryResultElement.email && !queryResultElement.verstorben && !queryResultElement.verzogenDatum) {
const createCommunicationEmail: CreateCommunicationCommand = {
memberId: memberId,
typeId: sCommTypesByName['Email'],
isSMSAlarming: false,
city: "",
email: queryResultElement.email,
mobile: "",
preferred: queryResultElement.EinladungNurPerEmail,
postalCode: "",
street: "",
streetNumber: 0,
streetNumberAddition: "",
}
if (createCommunicationEmail.preferred) {
havePreferred = true;
}
createCommunicationCommands.push(createCommunicationEmail);
}
if (queryResultElement.Mobiltelefon && queryResultElement.EinladungNurPerEmail && !queryResultElement.email && !queryResultElement.verstorben && !queryResultElement.verzogenDatum) {
const preferred = havePreferred === false;
const createCommunicationWhatsApp: CreateCommunicationCommand = {
memberId: memberId,
typeId: sCommTypesByName['WhatsApp'],
isSMSAlarming: false,
city: "",
email: "",
mobile: queryResultElement.Mobiltelefon.trim(),
postalCode: "",
preferred: preferred,
street: "",
streetNumber: 0,
streetNumberAddition: "",
}
if (createCommunicationWhatsApp.preferred) {
havePreferred = true;
}
createCommunicationCommands.push(createCommunicationWhatsApp);
}
if (queryResultElement.Ort && queryResultElement.street) {
const preferred = havePreferred === false;
const createCommunicationMail: CreateCommunicationCommand = {
memberId: memberId,
typeId: sCommTypesByName['Post'],
isSMSAlarming: false,
city: queryResultElement.Ort,
email: "",
mobile: "",
preferred: preferred,
postalCode: queryResultElement.PLZ,
street: queryResultElement.street.trim(),
streetNumber: queryResultElement.streetnumber,
streetNumberAddition: queryResultElement.streetnumberaddition.trim(),
}
if (createCommunicationMail.preferred) {
havePreferred = true;
}
createCommunicationCommands.push(createCommunicationMail);
}
if (createCommunicationCommands.length > 0 && !havePreferred) {
console.log(`No preferred communication for ${newMember.firstname} ${newMember.lastname}`)
}
for (const createCommunicationCommand of createCommunicationCommands) {
console.log(`Adding communication for ${sCommTypesById[createCommunicationCommand.typeId]} to ${newMember.firstname} ${newMember.lastname}`)
await CommunicationCommandHandler.create(createCommunicationCommand);
}
}
}
res.sendStatus(200);
});
export default router;

View file

@ -13,7 +13,7 @@ import reset from "./reset";
import auth from "./auth";
import admin from "./admin/index";
import user from "./user";
import detectPWA from "../middleware/detectPWA";
import fillfromold from "./fillfromold";
export default (app: Express) => {
app.set("query parser", "extended");
@ -26,9 +26,9 @@ export default (app: Express) => {
app.use(cors());
app.options("*", cors());
app.use(detectPWA);
app.use("/api/public", publicAvailable);
app.use("/api/setup", allowSetup, setup);
app.use("/api/fillfromold", fillfromold);
app.use("/api/reset", reset);
app.use("/api/invite", invite);
app.use("/api/auth", auth);

View file

@ -12,7 +12,6 @@ export default abstract class CalendarService {
.getRepository(calendar)
.createQueryBuilder("calendar")
.leftJoinAndSelect("calendar.type", "type")
.orderBy("starttime", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -16,7 +16,6 @@ export default abstract class CommunicationService {
.leftJoinAndSelect("communication.member", "member")
.leftJoinAndSelect("member.sendNewsletter", "sendNewsletter")
.where("communication.memberId = :memberId", { memberId: memberId })
.orderBy("communicationType.type", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -14,7 +14,6 @@ export default abstract class MemberAwardService {
.createQueryBuilder("memberAwards")
.leftJoinAndSelect("memberAwards.award", "award")
.where("memberAwards.memberId = :memberId", { memberId: memberId })
.orderBy("award.award", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -14,8 +14,6 @@ export default abstract class MemberExecutivePositionService {
.createQueryBuilder("memberExecutivePositions")
.leftJoinAndSelect("memberExecutivePositions.executivePosition", "executivePosition")
.where("memberExecutivePositions.memberId = :memberId", { memberId: memberId })
.orderBy("executivePosition.position", "ASC")
.addOrderBy("memberExecutivePositions.start", "DESC")
.getMany()
.then((res) => {
return res;

View file

@ -14,7 +14,6 @@ export default abstract class MemberQualificationService {
.createQueryBuilder("memberQualifications")
.leftJoinAndSelect("memberQualifications.qualification", "qualification")
.where("memberQualifications.memberId = :memberId", { memberId: memberId })
.orderBy("qualification.qualification", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -8,19 +8,7 @@ export default abstract class MemberService {
* @description get all members
* @returns {Promise<[Array<member>, number]>}
*/
static async getAll({
offset = 0,
count = 25,
search = "",
noLimit = false,
ids = [],
}: {
offset?: number;
count?: number;
search?: string;
noLimit?: boolean;
ids?: Array<number>;
}): Promise<[Array<member>, number]> {
static async getAll(offset: number = 0, count: number = 25, search: string = ""): Promise<[Array<member>, number]> {
let query = dataSource
.getRepository(member)
.createQueryBuilder("member")
@ -66,15 +54,9 @@ export default abstract class MemberService {
});
}
if (ids.length != 0) {
query = query.where({ id: ids });
}
if (!noLimit) {
query = query.offset(offset).limit(count);
}
return await query
.offset(offset)
.limit(count)
.orderBy("member.lastname")
.addOrderBy("member.firstname")
.addOrderBy("member.nameaffix")
@ -142,9 +124,6 @@ export default abstract class MemberService {
.createQueryBuilder("member")
.leftJoinAndSelect("member.memberships", "membership")
.where("membership.end IS NULL")
.orderBy("member.lastname")
.addOrderBy("member.firstname")
.addOrderBy("member.nameaffix")
.getMany()
.then((res) => {
return res;

View file

@ -12,7 +12,6 @@ export default abstract class AwardService {
return await dataSource
.getRepository(award)
.createQueryBuilder("award")
.orderBy("award", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -11,7 +11,6 @@ export default abstract class CalendarTypeService {
return await dataSource
.getRepository(calendarType)
.createQueryBuilder("calendarType")
.orderBy("type", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -11,7 +11,6 @@ export default abstract class CommunicationTypeService {
return await dataSource
.getRepository(communicationType)
.createQueryBuilder("communicationType")
.orderBy("type", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -12,7 +12,6 @@ export default abstract class ExecutivePositionService {
return await dataSource
.getRepository(executivePosition)
.createQueryBuilder("executivePosition")
.orderBy("position", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -12,7 +12,6 @@ export default abstract class MembershipStatusService {
return await dataSource
.getRepository(membershipStatus)
.createQueryBuilder("membershipStatus")
.orderBy("status", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -13,7 +13,6 @@ export default abstract class NewsletterConfigService {
.getRepository(newsletterConfig)
.createQueryBuilder("newsletterConfig")
.leftJoinAndSelect("newsletterConfig.comType", "comType")
.orderBy("comType.type", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -13,7 +13,6 @@ export default abstract class QualificationService {
return await dataSource
.getRepository(qualification)
.createQueryBuilder("qualification")
.orderBy("qualification", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -11,7 +11,6 @@ export default abstract class QueryStoreService {
return await dataSource
.getRepository(query)
.createQueryBuilder("queryStore")
.orderBy("title", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -12,7 +12,6 @@ export default abstract class TemplateService {
return await dataSource
.getRepository(template)
.createQueryBuilder("template")
.orderBy("template", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -14,7 +14,6 @@ export default abstract class TemplateUsageService {
.leftJoinAndSelect("templateUsage.header", "headerTemplate")
.leftJoinAndSelect("templateUsage.body", "bodyTemplate")
.leftJoinAndSelect("templateUsage.footer", "footerTemplate")
.orderBy("scope", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -12,7 +12,6 @@ export default abstract class RoleService {
.getRepository(role)
.createQueryBuilder("role")
.leftJoinAndSelect("role.permissions", "role_permissions")
.orderBy("role", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -15,8 +15,6 @@ export default abstract class UserService {
.leftJoinAndSelect("user.roles", "roles")
.leftJoinAndSelect("user.permissions", "permissions")
.leftJoinAndSelect("roles.permissions", "role_permissions")
.orderBy("firstname", "ASC")
.addOrderBy("lastname", "ASC")
.getMany()
.then((res) => {
return res;

View file

@ -14,19 +14,9 @@
<br />
<br />
<h2>Anwesenheit ({{presence.length}})</h2>
<p>
{{#each presence}}{{this.firstname}} {{this.lastname}}{{#unless @last}}, {{/unless}}{{/each}}{{#unless
presence.length}}---{{/unless}}
</p>
<p>{{#each presence}} {{this.firstname}} {{this.lastname}}{{#unless @last}}, {{/unless}} {{/each}}</p>
<h2>Abwesenheit ({{absent.length}})</h2>
<p>
entschuldigt: {{#each excused_absent}}{{this.firstname}} {{this.lastname}}{{#unless @last}},
{{/unless}}{{/each}}{{#unless excused_absent.length}}---{{/unless}}
</p>
<p>
unentschuldigt: {{#each unexcused_absent}}{{this.firstname}} {{this.lastname}}{{#unless @last}},
{{/unless}}{{/each}}{{#unless unexcused_absent.length}}---{{/unless}}
</p>
<p>{{#each absent}} {{this.firstname}} {{this.lastname}}{{#unless @last}}, {{/unless}} {{/each}}</p>
<br />
<h2>Agenda</h2>
{{#each agenda}}

View file

@ -3,6 +3,5 @@ import { MemberViewModel } from "../member/member.models";
export interface ProtocolPresenceViewModel {
memberId: number;
absent: boolean;
excused: boolean;
protocolId: number;
}