Merge pull request 'patches v1.2.1' (#54) from develop into main

Reviewed-on: #54
This commit is contained in:
Julian Krauser 2025-01-27 14:19:55 +00:00
commit 3165eb789f
35 changed files with 891 additions and 272 deletions

View file

@ -20,13 +20,13 @@ Um den Container hochzufahren, erstellen Sie eine `docker-compose.yml` Datei mit
version: "3" version: "3"
services: services:
ff-ff-admin-server: ff-admin-server:
image: docker.registry.jk-effects.cloud/ehrenamt/ff-admin/server:latest image: docker.registry.jk-effects.cloud/ehrenamt/ff-admin/server:latest
container_name: ff_member_administration_server container_name: ff_member_administration_server
restart: unless-stopped restart: unless-stopped
environment: environment:
- DB_TYPE=<mysql|sqlite> # default ist auf mysql gesetzt - DB_TYPE=<mysql|sqlite> # default ist auf mysql gesetzt
- DB_HOST=ffm-db - DB_HOST=ff-db
- DB_PORT=<number> # default ist auf 3306 gesetzt - DB_PORT=<number> # default ist auf 3306 gesetzt
- DB_NAME=ffadmin - DB_NAME=ffadmin
- DB_USERNAME=administration_backend - DB_USERNAME=administration_backend

View file

@ -1,7 +1,5 @@
import { Salutation } from "../../../enums/salutation";
export interface CreateMemberCommand { export interface CreateMemberCommand {
salutation: Salutation; salutationId: number;
firstname: string; firstname: string;
lastname: string; lastname: string;
nameaffix: string; nameaffix: string;
@ -11,7 +9,7 @@ export interface CreateMemberCommand {
export interface UpdateMemberCommand { export interface UpdateMemberCommand {
id: number; id: number;
salutation: Salutation; salutationId: number;
firstname: string; firstname: string;
lastname: string; lastname: string;
nameaffix: string; nameaffix: string;

View file

@ -21,7 +21,7 @@ export default abstract class MemberCommandHandler {
.insert() .insert()
.into(member) .into(member)
.values({ .values({
salutation: createMember.salutation, salutationId: createMember.salutationId,
firstname: createMember.firstname, firstname: createMember.firstname,
lastname: createMember.lastname, lastname: createMember.lastname,
nameaffix: createMember.nameaffix, nameaffix: createMember.nameaffix,
@ -50,7 +50,7 @@ export default abstract class MemberCommandHandler {
.createQueryBuilder() .createQueryBuilder()
.update(member) .update(member)
.set({ .set({
salutation: updateMember.salutation, salutationId: updateMember.salutationId,
firstname: updateMember.firstname, firstname: updateMember.firstname,
lastname: updateMember.lastname, lastname: updateMember.lastname,
nameaffix: updateMember.nameaffix, nameaffix: updateMember.nameaffix,

View file

@ -0,0 +1,12 @@
export interface CreateSalutationCommand {
salutation: string;
}
export interface UpdateSalutationCommand {
id: number;
salutation: string;
}
export interface DeleteSalutationCommand {
id: number;
}

View file

@ -0,0 +1,69 @@
import { dataSource } from "../../../data-source";
import { salutation } from "../../../entity/settings/salutation";
import InternalException from "../../../exceptions/internalException";
import { CreateSalutationCommand, DeleteSalutationCommand, UpdateSalutationCommand } from "./salutationCommand";
export default abstract class SalutationCommandHandler {
/**
* @description create salutation
* @param {CreateSalutationCommand} createSalutation
* @returns {Promise<number>}
*/
static async create(createSalutation: CreateSalutationCommand): Promise<number> {
return await dataSource
.createQueryBuilder()
.insert()
.into(salutation)
.values({
salutation: createSalutation.salutation,
})
.execute()
.then((result) => {
return result.identifiers[0].id;
})
.catch((err) => {
throw new InternalException("Failed creating salutation", err);
});
}
/**
* @description update salutation
* @param {UpdateSalutationCommand} updateSalutation
* @returns {Promise<void>}
*/
static async update(updateSalutation: UpdateSalutationCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.update(salutation)
.set({
salutation: updateSalutation.salutation,
})
.where("id = :id", { id: updateSalutation.id })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException("Failed updating salutation", err);
});
}
/**
* @description delete salutation
* @param {DeleteSalutationCommand} deleteSalutation
* @returns {Promise<void>}
*/
static async delete(deleteSalutation: DeleteSalutationCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.delete()
.from(salutation)
.where("id = :id", { id: deleteSalutation.id })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException(
`Failed deleting salutation ${err.code.includes("ER_ROW_IS_REFERENCED") ? " due to referenced data" : ""}`,
err
);
});
}
}

View file

@ -285,15 +285,15 @@ export async function createMemberPrintoutList(req: Request, res: Response): Pro
* @returns {Promise<*>} * @returns {Promise<*>}
*/ */
export async function createMember(req: Request, res: Response): Promise<any> { export async function createMember(req: Request, res: Response): Promise<any> {
const salutation = req.body.salutation; const salutationId = parseInt(req.body.salutationId);
const firstname = req.body.firstname; const firstname = req.body.firstname;
const lastname = req.body.lastname; const lastname = req.body.lastname;
const nameaffix = req.body.nameaffix; const nameaffix = req.body.nameaffix;
const birthdate = req.body.birthdate; const birthdate = req.body.birthdate;
const internalId = req.body.internalId; const internalId = req.body.internalId || null;
let createMember: CreateMemberCommand = { let createMember: CreateMemberCommand = {
salutation, salutationId,
firstname, firstname,
lastname, lastname,
nameaffix, nameaffix,
@ -453,7 +453,7 @@ export async function addCommunicationToMember(req: Request, res: Response): Pro
*/ */
export async function updateMemberById(req: Request, res: Response): Promise<any> { export async function updateMemberById(req: Request, res: Response): Promise<any> {
const memberId = parseInt(req.params.id); const memberId = parseInt(req.params.id);
const salutation = req.body.salutation; const salutationId = parseInt(req.body.salutationId);
const firstname = req.body.firstname; const firstname = req.body.firstname;
const lastname = req.body.lastname; const lastname = req.body.lastname;
const nameaffix = req.body.nameaffix; const nameaffix = req.body.nameaffix;
@ -462,7 +462,7 @@ export async function updateMemberById(req: Request, res: Response): Promise<any
let updateMember: UpdateMemberCommand = { let updateMember: UpdateMemberCommand = {
id: memberId, id: memberId,
salutation, salutationId,
firstname, firstname,
lastname, lastname,
nameaffix, nameaffix,

View file

@ -21,7 +21,6 @@ import UserService from "../../../service/user/userService";
import { TemplateHelper } from "../../../helpers/templateHelper"; import { TemplateHelper } from "../../../helpers/templateHelper";
import MailHelper from "../../../helpers/mailHelper"; import MailHelper from "../../../helpers/mailHelper";
import { NewsletterEventType, NewsletterHelper } from "../../../helpers/newsletterHelper"; import { NewsletterEventType, NewsletterHelper } from "../../../helpers/newsletterHelper";
import { Salutation } from "../../../enums/salutation";
/** /**
* @description get all newsletters * @description get all newsletters
@ -142,7 +141,7 @@ export async function createNewsletterPrintoutPreviewById(req: Request, res: Res
data.recipient = { data.recipient = {
firstname: recipient.firstname, firstname: recipient.firstname,
lastname: recipient.lastname, lastname: recipient.lastname,
salutation: Salutation.none, salutation: "Anrede",
nameaffix: "", nameaffix: "",
street: "Straße", street: "Straße",
streetNumber: "Hausnummer", streetNumber: "Hausnummer",
@ -248,7 +247,7 @@ export async function createNewsletterMailPreviewById(req: Request, res: Respons
data.recipient = { data.recipient = {
firstname: recipient.firstname, firstname: recipient.firstname,
lastname: recipient.lastname, lastname: recipient.lastname,
salutation: Salutation.none, salutation: "Anrede",
nameaffix: "", nameaffix: "",
street: "Straße", street: "Straße",
streetNumber: "Hausnummer", streetNumber: "Hausnummer",

View file

@ -0,0 +1,87 @@
import { Request, Response } from "express";
import SalutationService from "../../../service/settings/salutationService";
import SalutationFactory from "../../../factory/admin/settings/salutation";
import {
CreateSalutationCommand,
DeleteSalutationCommand,
UpdateSalutationCommand,
} from "../../../command/settings/salutation/salutationCommand";
import SalutationCommandHandler from "../../../command/settings/salutation/salutationCommandHandler";
/**
* @description get all salutations
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getAllSalutations(req: Request, res: Response): Promise<any> {
let salutations = await SalutationService.getAll();
res.json(SalutationFactory.mapToBase(salutations));
}
/**
* @description get salutation by id
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getSalutationById(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let salutation = await SalutationService.getById(id);
res.json(SalutationFactory.mapToSingle(salutation));
}
/**
* @description create new salutation
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function createSalutation(req: Request, res: Response): Promise<any> {
const salutation = req.body.salutation;
let createSalutation: CreateSalutationCommand = {
salutation: salutation,
};
await SalutationCommandHandler.create(createSalutation);
res.sendStatus(204);
}
/**
* @description update salutation
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function updateSalutation(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
const salutation = req.body.salutation;
let updateSalutation: UpdateSalutationCommand = {
id: id,
salutation: salutation,
};
await SalutationCommandHandler.update(updateSalutation);
res.sendStatus(204);
}
/**
* @description delete salutation
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function deleteSalutation(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let deleteSalutation: DeleteSalutationCommand = {
id: id,
};
await SalutationCommandHandler.delete(deleteSalutation);
res.sendStatus(204);
}

View file

@ -10,6 +10,7 @@ import MailHelper from "../../../helpers/mailHelper";
import { CLUB_NAME } from "../../../env.defaults"; import { CLUB_NAME } from "../../../env.defaults";
import { UpdateUserPermissionsCommand } from "../../../command/user/user/userPermissionCommand"; import { UpdateUserPermissionsCommand } from "../../../command/user/user/userPermissionCommand";
import UserPermissionCommandHandler from "../../../command/user/user/userPermissionCommandHandler"; import UserPermissionCommandHandler from "../../../command/user/user/userPermissionCommandHandler";
import BadRequestException from "../../../exceptions/badRequestException";
/** /**
* @description get All users * @description get All users
@ -137,7 +138,11 @@ export async function updateUserRoles(req: Request, res: Response): Promise<any>
export async function deleteUser(req: Request, res: Response): Promise<any> { export async function deleteUser(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id); const id = parseInt(req.params.id);
let user = await UserService.getById(id); let { mail, isOwner } = await UserService.getById(id);
if (isOwner) {
throw new BadRequestException("Owner cannot be deleted");
}
let deleteUser: DeleteUserCommand = { let deleteUser: DeleteUserCommand = {
id: id, id: id,
@ -147,7 +152,7 @@ export async function deleteUser(req: Request, res: Response): Promise<any> {
try { try {
// sendmail // sendmail
await MailHelper.sendMail( await MailHelper.sendMail(
user.mail, mail,
`Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`, `Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
`Ihr Nutzerkonto des Adminportals wurde erfolgreich gelöscht.` `Ihr Nutzerkonto des Adminportals wurde erfolgreich gelöscht.`
); );

View file

@ -71,6 +71,9 @@ import { ProtocolPresenceExcuse1737287798828 } from "./migrations/1737287798828-
import { webapi } from "./entity/user/webapi"; import { webapi } from "./entity/user/webapi";
import { webapiPermission } from "./entity/user/webapi_permission"; import { webapiPermission } from "./entity/user/webapi_permission";
import { AddWebapiTokens1737453096674 } from "./migrations/1737453096674-addwebapiTokens"; import { AddWebapiTokens1737453096674 } from "./migrations/1737453096674-addwebapiTokens";
import { salutation } from "./entity/settings/salutation";
import { SalutationAsTable1737796878058 } from "./migrations/1737796878058-salutationAsTable";
import { UpdateViews1737800468938 } from "./migrations/1737800468938-updateViews";
const dataSource = new DataSource({ const dataSource = new DataSource({
type: DB_TYPE as any, type: DB_TYPE as any,
@ -96,6 +99,7 @@ const dataSource = new DataSource({
executivePosition, executivePosition,
membershipStatus, membershipStatus,
qualification, qualification,
salutation,
member, member,
memberAwards, memberAwards,
memberExecutivePositions, memberExecutivePositions,
@ -152,6 +156,8 @@ const dataSource = new DataSource({
FinishInternalIdTransfer1736505324488, FinishInternalIdTransfer1736505324488,
ProtocolPresenceExcuse1737287798828, ProtocolPresenceExcuse1737287798828,
AddWebapiTokens1737453096674, AddWebapiTokens1737453096674,
SalutationAsTable1737796878058,
UpdateViews1737800468938,
], ],
migrationsRun: true, migrationsRun: true,
migrationsTransactionMode: "each", migrationsTransactionMode: "each",

View file

@ -1,6 +1,5 @@
import { calendar } from "../entity/club/calendar"; import { calendar } from "../entity/club/calendar";
import { member } from "../entity/club/member/member"; import { member } from "../entity/club/member/member";
import { Salutation } from "../enums/salutation";
export const newsletterDemoData: { export const newsletterDemoData: {
title: string; title: string;
@ -18,7 +17,9 @@ export const newsletterDemoData: {
} }
> >
>; >;
recipient: Partial<member & { street: string; streetNumber: string; streetNumberAdd: string }>; recipient: Partial<
Omit<member, "salutation"> & { salutation: string; street: string; streetNumber: string; streetNumberAdd: string }
>;
} = { } = {
title: "Beispiel Newsletter Daten", title: "Beispiel Newsletter Daten",
description: "Zusammenfassung der Demodaten.", description: "Zusammenfassung der Demodaten.",
@ -63,7 +64,7 @@ export const newsletterDemoData: {
recipient: { recipient: {
firstname: "Julian", firstname: "Julian",
lastname: "Krauser", lastname: "Krauser",
salutation: Salutation.sir, salutation: "Herr",
nameaffix: "", nameaffix: "",
street: "Straße", street: "Straße",
streetNumber: "Hausnummer", streetNumber: "Hausnummer",

View file

@ -1,32 +1,16 @@
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, PrimaryColumn } from "typeorm"; import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, PrimaryColumn } from "typeorm";
import { Salutation } from "../../../enums/salutation";
import { membership } from "./membership"; import { membership } from "./membership";
import { memberAwards } from "./memberAwards"; import { memberAwards } from "./memberAwards";
import { memberQualifications } from "./memberQualifications"; import { memberQualifications } from "./memberQualifications";
import { memberExecutivePositions } from "./memberExecutivePositions"; import { memberExecutivePositions } from "./memberExecutivePositions";
import { communication } from "./communication"; import { communication } from "./communication";
import { CommunicationViewModel } from "../../../viewmodel/admin/club/member/communication.models"; import { salutation } from "../../settings/salutation";
@Entity() @Entity()
export class member { export class member {
@PrimaryColumn({ generated: "increment", type: "int" }) @PrimaryColumn({ generated: "increment", type: "int" })
id: number; id: number;
@Column({
type: "varchar",
length: "255",
default: Salutation.none.toString(),
transformer: {
to(value: Salutation) {
return value.toString();
},
from(value: string) {
return Salutation[value as keyof typeof Salutation];
},
},
})
salutation: Salutation;
@Column({ type: "varchar", length: 255 }) @Column({ type: "varchar", length: 255 })
firstname: string; firstname: string;
@ -42,6 +26,9 @@ export class member {
@Column({ type: "varchar", length: 255, unique: true, nullable: true }) @Column({ type: "varchar", length: 255, unique: true, nullable: true })
internalId?: string; internalId?: string;
@Column()
salutationId: number;
@OneToMany(() => communication, (communications) => communications.member) @OneToMany(() => communication, (communications) => communications.member)
communications: communication[]; communications: communication[];
@ -53,6 +40,9 @@ export class member {
@JoinColumn() @JoinColumn()
sendNewsletter?: communication; sendNewsletter?: communication;
@ManyToOne(() => salutation, (salutation) => salutation.members)
salutation: salutation;
@OneToMany(() => membership, (membership) => membership.member) @OneToMany(() => membership, (membership) => membership.member)
memberships: membership[]; memberships: membership[];

View file

@ -0,0 +1,15 @@
import { Column, Entity, OneToMany, PrimaryColumn } from "typeorm";
import { memberAwards } from "../club/member/memberAwards";
import { member } from "../club/member/member";
@Entity()
export class salutation {
@PrimaryColumn({ generated: "increment", type: "int" })
id: number;
@Column({ type: "varchar", length: 255, unique: true })
salutation: string;
@OneToMany(() => member, (member) => member.salutation)
members: member[];
}

View file

@ -1,6 +0,0 @@
export enum Salutation {
sir = "sir",
madam = "madam",
other = "other",
none = "none",
}

View file

@ -1,6 +1,7 @@
import { member } from "../../../../entity/club/member/member"; import { member } from "../../../../entity/club/member/member";
import { MemberStatisticsViewModel, MemberViewModel } from "../../../../viewmodel/admin/club/member/member.models"; import { MemberStatisticsViewModel, MemberViewModel } from "../../../../viewmodel/admin/club/member/member.models";
import { memberView } from "../../../../views/memberView"; import { memberView } from "../../../../views/memberView";
import SalutationFactory from "../../settings/salutation";
import CommunicationFactory from "./communication"; import CommunicationFactory from "./communication";
import MembershipFactory from "./membership"; import MembershipFactory from "./membership";
@ -13,7 +14,7 @@ export default abstract class MemberFactory {
public static mapToSingle(record: member): MemberViewModel { public static mapToSingle(record: member): MemberViewModel {
return { return {
id: record?.id, id: record?.id,
salutation: record?.salutation, salutation: SalutationFactory.mapToSingle(record?.salutation),
firstname: record?.firstname, firstname: record?.firstname,
lastname: record?.lastname, lastname: record?.lastname,
nameaffix: record?.nameaffix, nameaffix: record?.nameaffix,

View file

@ -0,0 +1,25 @@
import { salutation } from "../../../entity/settings/salutation";
import { SalutationViewModel } from "../../../viewmodel/admin/settings/salutation.models";
export default abstract class SalutationFactory {
/**
* @description map record to salutation
* @param {salutation} record
* @returns {SalutationViewModel}
*/
public static mapToSingle(record: salutation): SalutationViewModel {
return {
id: record.id,
salutation: record.salutation,
};
}
/**
* @description map records to salutation
* @param {Array<salutation>} records
* @returns {Array<SalutationViewModel>}
*/
public static mapToBase(records: Array<salutation>): Array<SalutationViewModel> {
return records.map((r) => this.mapToSingle(r));
}
}

View file

@ -11,6 +11,7 @@ export default abstract class DynamicQueryBuilder {
"executivePosition", "executivePosition",
"membershipStatus", "membershipStatus",
"qualification", "qualification",
"salutation",
"member", "member",
"memberAwards", "memberAwards",
"memberExecutivePositions", "memberExecutivePositions",

View file

@ -100,7 +100,7 @@ export abstract class NewsletterHelper {
recipient: { recipient: {
firstname: recipient.firstname, firstname: recipient.firstname,
lastname: recipient.lastname, lastname: recipient.lastname,
salutation: recipient.salutation, salutation: recipient.salutation.salutation,
nameaffix: recipient.nameaffix, nameaffix: recipient.nameaffix,
...(showAdress ...(showAdress
? { ? {

View file

@ -8,105 +8,105 @@ export class MemberDataViews1734520998539 implements MigrationInterface {
name = "MemberDataViews1734520998539"; name = "MemberDataViews1734520998539";
public async up(queryRunner: QueryRunner): Promise<void> { public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createView( // await queryRunner.createView(
new View({ // new View({
name: "member_view", // name: "member_view",
expression: (datasource: DataSource) => // expression: (datasource: DataSource) =>
datasource // datasource
.getRepository(member) // .getRepository(member)
.createQueryBuilder("member") // .createQueryBuilder("member")
.select("member.id", "id") // .select("member.id", "id")
.addSelect("member.salutation", "salutation") // .addSelect("member.salutation", "salutation")
.addSelect("member.firstname", "firstname") // .addSelect("member.firstname", "firstname")
.addSelect("member.lastname", "lastname") // .addSelect("member.lastname", "lastname")
.addSelect("member.nameaffix", "nameaffix") // .addSelect("member.nameaffix", "nameaffix")
.addSelect("member.birthdate", "birthdate") // .addSelect("member.birthdate", "birthdate")
.addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge") // .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge")
.addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear"), // .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear"),
}), // }),
true // true
); // );
await queryRunner.createView( // await queryRunner.createView(
new View({ // new View({
name: "member_executive_positions_view", // name: "member_executive_positions_view",
expression: (datasource: DataSource) => // expression: (datasource: DataSource) =>
datasource // datasource
.getRepository(memberExecutivePositions) // .getRepository(memberExecutivePositions)
.createQueryBuilder("memberExecutivePositions") // .createQueryBuilder("memberExecutivePositions")
.select("executivePosition.id", "positionId") // .select("executivePosition.id", "positionId")
.addSelect("executivePosition.position", "position") // .addSelect("executivePosition.position", "position")
.addSelect("member.id", "memberId") // .addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation") // .addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname") // .addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname") // .addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix") // .addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate") // .addSelect("member.birthdate", "memberBirthdate")
.addSelect( // .addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURRENT_DATE)))", // "SUM(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURRENT_DATE)))",
"durationInDays" // "durationInDays"
) // )
.leftJoin("memberExecutivePositions.executivePosition", "executivePosition") // .leftJoin("memberExecutivePositions.executivePosition", "executivePosition")
.leftJoin("memberExecutivePositions.member", "member") // .leftJoin("memberExecutivePositions.member", "member")
.groupBy("executivePosition.id"), // .groupBy("executivePosition.id"),
}), // }),
true // true
); // );
await queryRunner.createView( // await queryRunner.createView(
new View({ // new View({
name: "member_qualifications_view", // name: "member_qualifications_view",
expression: (datasource: DataSource) => // expression: (datasource: DataSource) =>
datasource // datasource
.getRepository(memberQualifications) // .getRepository(memberQualifications)
.createQueryBuilder("memberQualifications") // .createQueryBuilder("memberQualifications")
.select("qualification.id", "qualificationId") // .select("qualification.id", "qualificationId")
.addSelect("qualification.qualification", "qualification") // .addSelect("qualification.qualification", "qualification")
.addSelect("member.id", "memberId") // .addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation") // .addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname") // .addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname") // .addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix") // .addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate") // .addSelect("member.birthdate", "memberBirthdate")
.addSelect( // .addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURRENT_DATE)))", // "SUM(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURRENT_DATE)))",
"durationInDays" // "durationInDays"
) // )
.leftJoin("memberQualifications.qualification", "qualification") // .leftJoin("memberQualifications.qualification", "qualification")
.leftJoin("memberQualifications.member", "member") // .leftJoin("memberQualifications.member", "member")
.groupBy("qualification.id"), // .groupBy("qualification.id"),
}), // }),
true // true
); // );
await queryRunner.createView( // await queryRunner.createView(
new View({ // new View({
name: "membership_view", // name: "membership_view",
expression: (datasource: DataSource) => // expression: (datasource: DataSource) =>
datasource // datasource
.getRepository(membership) // .getRepository(membership)
.createQueryBuilder("membership") // .createQueryBuilder("membership")
.select("status.id", "statusId") // .select("status.id", "statusId")
.addSelect("status.status", "status") // .addSelect("status.status", "status")
.addSelect("member.id", "memberId") // .addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation") // .addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname") // .addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname") // .addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix") // .addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate") // .addSelect("member.birthdate", "memberBirthdate")
.addSelect( // .addSelect(
"SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", // "SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))",
"durationInDays" // "durationInDays"
) // )
.leftJoin("membership.status", "status") // .leftJoin("membership.status", "status")
.leftJoin("membership.member", "member") // .leftJoin("membership.member", "member")
.groupBy("status.id"), // .groupBy("status.id"),
}), // }),
true // true
); // );
} }
public async down(queryRunner: QueryRunner): Promise<void> { public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropView("membership_view"); // await queryRunner.dropView("membership_view");
await queryRunner.dropView("member_qualifications_view"); // await queryRunner.dropView("member_qualifications_view");
await queryRunner.dropView("member_executive_positions_view"); // await queryRunner.dropView("member_executive_positions_view");
await queryRunner.dropView("member_view"); // await queryRunner.dropView("member_view");
} }
} }

View file

@ -8,106 +8,104 @@ export class ExtendViewValues1736084198860 implements MigrationInterface {
name = "ExtendViewValues1736084198860"; name = "ExtendViewValues1736084198860";
public async up(queryRunner: QueryRunner): Promise<void> { public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropView("membership_view"); // await queryRunner.dropView("membership_view");
await queryRunner.dropView("member_view"); // await queryRunner.dropView("member_view");
// await queryRunner.createView(
await queryRunner.createView( // new View({
new View({ // name: "member_view",
name: "member_view", // expression: (datasource: DataSource) =>
expression: (datasource: DataSource) => // datasource
datasource // .getRepository(member)
.getRepository(member) // .createQueryBuilder("member")
.createQueryBuilder("member") // .select("member.id", "id")
.select("member.id", "id") // .addSelect("member.salutation", "salutation")
.addSelect("member.salutation", "salutation") // .addSelect("member.firstname", "firstname")
.addSelect("member.firstname", "firstname") // .addSelect("member.lastname", "lastname")
.addSelect("member.lastname", "lastname") // .addSelect("member.nameaffix", "nameaffix")
.addSelect("member.nameaffix", "nameaffix") // .addSelect("member.birthdate", "birthdate")
.addSelect("member.birthdate", "birthdate") // .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge")
.addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge") // .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear")
.addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear") // .addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge"),
.addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge"), // }),
}), // true
true // );
); // await queryRunner.createView(
await queryRunner.createView( // new View({
new View({ // name: "membership_view",
name: "membership_view", // expression: (datasource: DataSource) =>
expression: (datasource: DataSource) => // datasource
datasource // .getRepository(membership)
.getRepository(membership) // .createQueryBuilder("membership")
.createQueryBuilder("membership") // .select("status.id", "statusId")
.select("status.id", "statusId") // .addSelect("status.status", "status")
.addSelect("status.status", "status") // .addSelect("member.id", "memberId")
.addSelect("member.id", "memberId") // .addSelect("member.salutation", "memberSalutation")
.addSelect("member.salutation", "memberSalutation") // .addSelect("member.firstname", "memberFirstname")
.addSelect("member.firstname", "memberFirstname") // .addSelect("member.lastname", "memberLastname")
.addSelect("member.lastname", "memberLastname") // .addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.nameaffix", "memberNameaffix") // .addSelect("member.birthdate", "memberBirthdate")
.addSelect("member.birthdate", "memberBirthdate") // .addSelect(
.addSelect( // "SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))",
"SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", // "durationInDays"
"durationInDays" // )
) // .addSelect(
.addSelect( // "CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))",
"CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))", // "durationInYears"
"durationInYears" // )
) // .leftJoin("membership.status", "status")
.leftJoin("membership.status", "status") // .leftJoin("membership.member", "member")
.leftJoin("membership.member", "member") // .groupBy("status.id")
.groupBy("status.id") // .addGroupBy("member.id"),
.addGroupBy("member.id"), // }),
}), // true
true // );
);
} }
public async down(queryRunner: QueryRunner): Promise<void> { public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropView("membership_view"); // await queryRunner.dropView("membership_view");
await queryRunner.dropView("member_view"); // await queryRunner.dropView("member_view");
// await queryRunner.createView(
await queryRunner.createView( // new View({
new View({ // name: "member_view",
name: "member_view", // expression: (datasource: DataSource) =>
expression: (datasource: DataSource) => // datasource
datasource // .getRepository(member)
.getRepository(member) // .createQueryBuilder("member")
.createQueryBuilder("member") // .select("member.id", "id")
.select("member.id", "id") // .addSelect("member.salutation", "salutation")
.addSelect("member.salutation", "salutation") // .addSelect("member.firstname", "firstname")
.addSelect("member.firstname", "firstname") // .addSelect("member.lastname", "lastname")
.addSelect("member.lastname", "lastname") // .addSelect("member.nameaffix", "nameaffix")
.addSelect("member.nameaffix", "nameaffix") // .addSelect("member.birthdate", "birthdate")
.addSelect("member.birthdate", "birthdate") // .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge")
.addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge") // .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear"),
.addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear"), // }),
}), // true
true // );
); // await queryRunner.createView(
await queryRunner.createView( // new View({
new View({ // name: "membership_view",
name: "membership_view", // expression: (datasource: DataSource) =>
expression: (datasource: DataSource) => // datasource
datasource // .getRepository(membership)
.getRepository(membership) // .createQueryBuilder("membership")
.createQueryBuilder("membership") // .select("status.id", "statusId")
.select("status.id", "statusId") // .addSelect("status.status", "status")
.addSelect("status.status", "status") // .addSelect("member.id", "memberId")
.addSelect("member.id", "memberId") // .addSelect("member.salutation", "memberSalutation")
.addSelect("member.salutation", "memberSalutation") // .addSelect("member.firstname", "memberFirstname")
.addSelect("member.firstname", "memberFirstname") // .addSelect("member.lastname", "memberLastname")
.addSelect("member.lastname", "memberLastname") // .addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.nameaffix", "memberNameaffix") // .addSelect("member.birthdate", "memberBirthdate")
.addSelect("member.birthdate", "memberBirthdate") // .addSelect(
.addSelect( // "SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))",
"SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", // "durationInDays"
"durationInDays" // )
) // .leftJoin("membership.status", "status")
.leftJoin("membership.status", "status") // .leftJoin("membership.member", "member")
.leftJoin("membership.member", "member") // .groupBy("status.id"),
.groupBy("status.id"), // }),
}), // true
true // );
);
} }
} }

View file

@ -0,0 +1,91 @@
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm";
import { DB_TYPE } from "../env.defaults";
import { member } from "../entity/club/member/member";
import { salutation } from "../entity/settings/salutation";
export class SalutationAsTable1737796878058 implements MigrationInterface {
name = "SalutationAsTable1737796878058";
public async up(queryRunner: QueryRunner): Promise<void> {
const variableType_int = DB_TYPE == "mysql" ? "int" : "integer";
await queryRunner.createTable(
new Table({
name: "salutation",
columns: [
{ name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" },
{ name: "salutation", type: "varchar", length: "255", isUnique: true, isNullable: false },
],
}),
true
);
// ! has to be sql. Else the column would be dropped and created - resulting in data loss.
await queryRunner.query(
`ALTER TABLE \`member\` CHANGE \`salutation\` \`salutationId\` varchar(255) NOT NULL DEFAULT ''`
);
// ! has to be sql. Else no data is returned.
const existing_salutations = await queryRunner.query(
"SELECT DISTINCT salutationId FROM `member` `member` GROUP BY salutationId"
);
for (let s of existing_salutations.map((s: any) => s.salutationId) as Array<string>) {
await queryRunner.manager.getRepository(salutation).save({ salutation: s });
}
const salutations = await queryRunner.manager.getRepository(salutation).find();
for (let salutation of salutations) {
await queryRunner.manager
.getRepository(member)
.createQueryBuilder("member")
.update({ salutationId: salutation.id })
.where({ salutationId: salutation.salutation })
.execute();
}
await queryRunner.query(
`ALTER TABLE \`member\` CHANGE \`salutationId\` \`salutationId\` ${variableType_int} NOT NULL`
);
await queryRunner.createForeignKey(
"member",
new TableForeignKey({
columnNames: ["salutationId"],
referencedColumnNames: ["id"],
referencedTableName: "salutation",
onDelete: "RESTRICT",
onUpdate: "RESTRICT",
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
const table = await queryRunner.getTable("member");
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("salutationId") !== -1);
await queryRunner.dropForeignKey("member", foreignKey);
// ! has to be sql. Else the column would be dropped and created - resulting in data loss.
await queryRunner.query(
`ALTER TABLE \`member\` CHANGE \`salutationId\` \`salutationId\` varchar(255) NOT NULL DEFAULT ''`
);
const salutations = await queryRunner.manager.getRepository(salutation).find();
for (let salutation of salutations) {
await queryRunner.manager
.getRepository(member)
.createQueryBuilder("member")
.update({ salutationId: salutation.salutation as unknown as number })
.where({ salutationId: salutation.id })
.execute();
}
await queryRunner.query(
`ALTER TABLE \`member\` CHANGE \`salutationId\` \`salutation\` varchar(255) NOT NULL DEFAULT 'none'`
);
await queryRunner.dropTable("webapi");
}
}

View file

@ -0,0 +1,229 @@
import { DataSource, MigrationInterface, QueryRunner, View } from "typeorm";
import { member } from "../entity/club/member/member";
import { memberExecutivePositions } from "../entity/club/member/memberExecutivePositions";
import { memberQualifications } from "../entity/club/member/memberQualifications";
import { membership } from "../entity/club/member/membership";
export class UpdateViews1737800468938 implements MigrationInterface {
name = "UpdateViews1737800468938";
public async up(queryRunner: QueryRunner): Promise<void> {
// await queryRunner.dropView("membership_view");
// await queryRunner.dropView("member_executive_positions_view");
// await queryRunner.dropView("member_qualifications_view");
// await queryRunner.dropView("member_view");
await queryRunner.createView(
new View({
name: "member_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(member)
.createQueryBuilder("member")
.select("member.id", "id")
.addSelect("member.firstname", "firstname")
.addSelect("member.lastname", "lastname")
.addSelect("member.nameaffix", "nameaffix")
.addSelect("member.birthdate", "birthdate")
.addSelect("salutation.salutation", "salutation")
.addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge")
.addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear")
.addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge")
.leftJoin("member.salutation", "salutation"),
}),
true
);
await queryRunner.createView(
new View({
name: "member_executive_positions_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(memberExecutivePositions)
.createQueryBuilder("memberExecutivePositions")
.select("executivePosition.id", "positionId")
.addSelect("executivePosition.position", "position")
.addSelect("member.id", "memberId")
.addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURRENT_DATE)))",
"durationInDays"
)
.leftJoin("memberExecutivePositions.executivePosition", "executivePosition")
.leftJoin("memberExecutivePositions.member", "member")
.leftJoin("member.salutation", "salutation")
.groupBy("executivePosition.id")
.addGroupBy("member.id"),
}),
true
);
await queryRunner.createView(
new View({
name: "member_qualifications_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(memberQualifications)
.createQueryBuilder("memberQualifications")
.select("qualification.id", "qualificationId")
.addSelect("qualification.qualification", "qualification")
.addSelect("member.id", "memberId")
.addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURRENT_DATE)))",
"durationInDays"
)
.leftJoin("memberQualifications.qualification", "qualification")
.leftJoin("memberQualifications.member", "member")
.leftJoin("member.salutation", "salutation")
.groupBy("qualification.id")
.addGroupBy("member.id"),
}),
true
);
await queryRunner.createView(
new View({
name: "membership_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(membership)
.createQueryBuilder("membership")
.select("status.id", "statusId")
.addSelect("status.status", "status")
.addSelect("member.id", "memberId")
.addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect(
"SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))",
"durationInDays"
)
.addSelect(
"CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))",
"durationInYears"
)
.leftJoin("membership.status", "status")
.leftJoin("membership.member", "member")
.leftJoin("member.salutation", "salutation")
.groupBy("status.id")
.addGroupBy("member.id"),
}),
true
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropView("membership_view");
await queryRunner.dropView("member_executive_positions_view");
await queryRunner.dropView("member_qualifications_view");
await queryRunner.dropView("member_view");
await queryRunner.createView(
new View({
name: "member_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(member)
.createQueryBuilder("member")
.select("member.id", "id")
.addSelect("member.salutation", "salutation")
.addSelect("member.firstname", "firstname")
.addSelect("member.lastname", "lastname")
.addSelect("member.nameaffix", "nameaffix")
.addSelect("member.birthdate", "birthdate")
.addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge")
.addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear")
.addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge"),
}),
true
);
await queryRunner.createView(
new View({
name: "member_executive_positions_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(memberExecutivePositions)
.createQueryBuilder("memberExecutivePositions")
.select("executivePosition.id", "positionId")
.addSelect("executivePosition.position", "position")
.addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURRENT_DATE)))",
"durationInDays"
)
.leftJoin("memberExecutivePositions.executivePosition", "executivePosition")
.leftJoin("memberExecutivePositions.member", "member")
.groupBy("executivePosition.id"),
}),
true
);
await queryRunner.createView(
new View({
name: "member_qualifications_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(memberQualifications)
.createQueryBuilder("memberQualifications")
.select("qualification.id", "qualificationId")
.addSelect("qualification.qualification", "qualification")
.addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURRENT_DATE)))",
"durationInDays"
)
.leftJoin("memberQualifications.qualification", "qualification")
.leftJoin("memberQualifications.member", "member")
.groupBy("qualification.id"),
}),
true
);
await queryRunner.createView(
new View({
name: "membership_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(membership)
.createQueryBuilder("membership")
.select("status.id", "statusId")
.addSelect("status.status", "status")
.addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect(
"SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))",
"durationInDays"
)
.addSelect(
"CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))",
"durationInYears"
)
.leftJoin("membership.status", "status")
.leftJoin("membership.member", "member")
.groupBy("status.id")
.addGroupBy("member.id"),
}),
true
);
}
}

View file

@ -6,6 +6,7 @@ import communicationType from "./settings/communicationType";
import executivePosition from "./settings/executivePosition"; import executivePosition from "./settings/executivePosition";
import membershipStatus from "./settings/membershipStatus"; import membershipStatus from "./settings/membershipStatus";
import qualification from "./settings/qualification"; import qualification from "./settings/qualification";
import salutation from "./settings/salutation";
import calendarType from "./settings/calendarType"; import calendarType from "./settings/calendarType";
import queryStore from "./settings/queryStore"; import queryStore from "./settings/queryStore";
import template from "./settings/template"; import template from "./settings/template";
@ -43,6 +44,7 @@ router.use(
membershipStatus membershipStatus
); );
router.use("/qualification", PermissionHelper.passCheckMiddleware("read", "settings", "qualification"), qualification); router.use("/qualification", PermissionHelper.passCheckMiddleware("read", "settings", "qualification"), qualification);
router.use("/salutation", PermissionHelper.passCheckMiddleware("read", "settings", "salutation"), salutation);
router.use("/calendartype", PermissionHelper.passCheckMiddleware("read", "settings", "calendar_type"), calendarType); router.use("/calendartype", PermissionHelper.passCheckMiddleware("read", "settings", "calendar_type"), calendarType);
router.use("/querystore", PermissionHelper.passCheckMiddleware("read", "settings", "query_store"), queryStore); router.use("/querystore", PermissionHelper.passCheckMiddleware("read", "settings", "query_store"), queryStore);
router.use("/template", PermissionHelper.passCheckMiddleware("read", "settings", "template"), template); router.use("/template", PermissionHelper.passCheckMiddleware("read", "settings", "template"), template);

View file

@ -0,0 +1,45 @@
import express, { Request, Response } from "express";
import {
createSalutation,
deleteSalutation,
getAllSalutations,
getSalutationById,
updateSalutation,
} from "../../../controller/admin/settings/salutationController";
import PermissionHelper from "../../../helpers/permissionHelper";
var router = express.Router({ mergeParams: true });
router.get("/", async (req: Request, res: Response) => {
await getAllSalutations(req, res);
});
router.get("/:id", async (req: Request, res: Response) => {
await getSalutationById(req, res);
});
router.post(
"/",
PermissionHelper.passCheckMiddleware("create", "settings", "salutation"),
async (req: Request, res: Response) => {
await createSalutation(req, res);
}
);
router.patch(
"/:id",
PermissionHelper.passCheckMiddleware("update", "settings", "salutation"),
async (req: Request, res: Response) => {
await updateSalutation(req, res);
}
);
router.delete(
"/:id",
PermissionHelper.passCheckMiddleware("delete", "settings", "salutation"),
async (req: Request, res: Response) => {
await deleteSalutation(req, res);
}
);
export default router;

View file

@ -14,10 +14,11 @@ import auth from "./auth";
import admin from "./admin/index"; import admin from "./admin/index";
import user from "./user"; import user from "./user";
import detectPWA from "../middleware/detectPWA"; import detectPWA from "../middleware/detectPWA";
import api from "./webapi"; import webapi from "./webapi";
import authenticateAPI from "../middleware/authenticateAPI"; import authenticateAPI from "../middleware/authenticateAPI";
import server from "./server"; import server from "./server";
import PermissionHelper from "../helpers/permissionHelper"; import PermissionHelper from "../helpers/permissionHelper";
import preventWebapiAccess from "../middleware/preventWebApiAccess";
export default (app: Express) => { export default (app: Express) => {
app.set("query parser", "extended"); app.set("query parser", "extended");
@ -32,14 +33,14 @@ export default (app: Express) => {
app.use(detectPWA); app.use(detectPWA);
app.use("/api/public", publicAvailable); app.use("/api/public", publicAvailable);
app.use("/api/setup", allowSetup, setup); app.use("/api/setup", preventWebapiAccess, allowSetup, setup);
app.use("/api/reset", reset); app.use("/api/reset", preventWebapiAccess, reset);
app.use("/api/invite", invite); app.use("/api/invite", preventWebapiAccess, invite);
app.use("/api/auth", auth); app.use("/api/auth", preventWebapiAccess, auth);
app.use("/api/webapi", authenticateAPI, api); app.use("/api/webapi", authenticateAPI, webapi);
app.use(authenticate); app.use(authenticate);
app.use("/api/admin", admin); app.use("/api/admin", admin);
app.use("/api/user", user); app.use("/api/user", preventWebapiAccess, user);
app.use("/api/server", PermissionHelper.isAdminMiddleware(), server); app.use("/api/server", preventWebapiAccess, PermissionHelper.isAdminMiddleware(), server);
app.use(errorHandler); app.use(errorHandler);
}; };

View file

@ -49,7 +49,8 @@ export default abstract class MemberService {
) )
.leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred") .leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred")
.leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1") .leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1")
.leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming"); .leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming")
.leftJoinAndSelect("member.salutation", "salutation");
if (search != "") { if (search != "") {
search.split(" ").forEach((term, index) => { search.split(" ").forEach((term, index) => {
@ -68,7 +69,7 @@ export default abstract class MemberService {
} }
if (ids.length != 0) { if (ids.length != 0) {
query = query.where({ id: ids }); query = query.where("member.id IN (:...ids)", { ids: ids });
} }
if (!noLimit) { if (!noLimit) {
@ -123,6 +124,7 @@ export default abstract class MemberService {
.leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1") .leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1")
.leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming") .leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming")
.leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred") .leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred")
.leftJoinAndSelect("member.salutation", "salutation")
.where("member.id = :id", { id: id }) .where("member.id = :id", { id: id })
.getOneOrFail() .getOneOrFail()
.then((res) => { .then((res) => {

View file

@ -0,0 +1,41 @@
import { dataSource } from "../../data-source";
import { salutation } from "../../entity/settings/salutation";
import InternalException from "../../exceptions/internalException";
export default abstract class SalutationService {
/**
* @description get all salutations
* @returns {Promise<Array<salutation>>}
*/
static async getAll(): Promise<Array<salutation>> {
return await dataSource
.getRepository(salutation)
.createQueryBuilder("salutation")
.orderBy("salutation", "ASC")
.getMany()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("salutations not found", err);
});
}
/**
* @description get salutation by id
* @returns {Promise<salutation>}
*/
static async getById(id: number): Promise<salutation> {
return await dataSource
.getRepository(salutation)
.createQueryBuilder("salutation")
.where("salutation.id = :id", { id: id })
.getOneOrFail()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("salutation not found by id", err);
});
}
}

View file

@ -11,6 +11,7 @@ export type PermissionModule =
| "executive_position" | "executive_position"
| "communication_type" | "communication_type"
| "membership_status" | "membership_status"
| "salutation"
| "calendar_type" | "calendar_type"
| "user" | "user"
| "role" | "role"
@ -53,6 +54,7 @@ export const permissionModules: Array<PermissionModule> = [
"executive_position", "executive_position",
"communication_type", "communication_type",
"membership_status", "membership_status",
"salutation",
"calendar_type", "calendar_type",
"user", "user",
"role", "role",
@ -71,6 +73,7 @@ export const sectionsAndModules: SectionsAndModulesObject = {
"executive_position", "executive_position",
"communication_type", "communication_type",
"membership_status", "membership_status",
"salutation",
"calendar_type", "calendar_type",
"query_store", "query_store",
"template", "template",

View file

@ -1,10 +1,10 @@
import { Salutation } from "../../../../enums/salutation"; import { SalutationViewModel } from "../../settings/salutation.models";
import { CommunicationViewModel } from "./communication.models"; import { CommunicationViewModel } from "./communication.models";
import { MembershipViewModel } from "./membership.models"; import { MembershipViewModel } from "./membership.models";
export interface MemberViewModel { export interface MemberViewModel {
id: number; id: number;
salutation: Salutation; salutation: SalutationViewModel;
firstname: string; firstname: string;
lastname: string; lastname: string;
nameaffix: string; nameaffix: string;
@ -19,7 +19,7 @@ export interface MemberViewModel {
export interface MemberStatisticsViewModel { export interface MemberStatisticsViewModel {
id: number; id: number;
salutation: Salutation; salutation: string;
firstname: string; firstname: string;
lastname: string; lastname: string;
nameaffix: string; nameaffix: string;

View file

@ -1,5 +1,3 @@
import { Salutation } from "../../../../enums/salutation";
export interface MembershipViewModel { export interface MembershipViewModel {
id: number; id: number;
start: Date; start: Date;
@ -15,7 +13,7 @@ export interface MembershipStatisticsViewModel {
status: string; status: string;
statusId: number; statusId: number;
memberId: number; memberId: number;
memberSalutation: Salutation; memberSalutation: string;
memberFirstname: string; memberFirstname: string;
memberLastname: string; memberLastname: string;
memberNameaffix: string; memberNameaffix: string;

View file

@ -0,0 +1,4 @@
export interface SalutationViewModel {
id: number;
salutation: string;
}

View file

@ -1,6 +1,5 @@
import { DataSource, ViewColumn, ViewEntity } from "typeorm"; import { DataSource, ViewColumn, ViewEntity } from "typeorm";
import { memberExecutivePositions } from "../entity/club/member/memberExecutivePositions"; import { memberExecutivePositions } from "../entity/club/member/memberExecutivePositions";
import { Salutation } from "../enums/salutation";
@ViewEntity({ @ViewEntity({
expression: (datasource: DataSource) => expression: (datasource: DataSource) =>
@ -10,18 +9,20 @@ import { Salutation } from "../enums/salutation";
.select("executivePosition.id", "positionId") .select("executivePosition.id", "positionId")
.addSelect("executivePosition.position", "position") .addSelect("executivePosition.position", "position")
.addSelect("member.id", "memberId") .addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname") .addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname") .addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix") .addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate") .addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect( .addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURRENT_DATE)))", "SUM(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURRENT_DATE)))",
"durationInDays" "durationInDays"
) )
.leftJoin("memberExecutivePositions.executivePosition", "executivePosition") .leftJoin("memberExecutivePositions.executivePosition", "executivePosition")
.leftJoin("memberExecutivePositions.member", "member") .leftJoin("memberExecutivePositions.member", "member")
.groupBy("executivePosition.id"), .leftJoin("member.salutation", "salutation")
.groupBy("executivePosition.id")
.addGroupBy("member.id"),
}) })
export class memberExecutivePositionsView { export class memberExecutivePositionsView {
@ViewColumn() @ViewColumn()
@ -37,7 +38,7 @@ export class memberExecutivePositionsView {
memberId: number; memberId: number;
@ViewColumn() @ViewColumn()
memberSalutation: Salutation; memberSalutation: string;
@ViewColumn() @ViewColumn()
memberFirstname: string; memberFirstname: string;

View file

@ -1,6 +1,5 @@
import { DataSource, ViewColumn, ViewEntity } from "typeorm"; import { DataSource, ViewColumn, ViewEntity } from "typeorm";
import { memberQualifications } from "../entity/club/member/memberQualifications"; import { memberQualifications } from "../entity/club/member/memberQualifications";
import { Salutation } from "../enums/salutation";
@ViewEntity({ @ViewEntity({
expression: (datasource: DataSource) => expression: (datasource: DataSource) =>
@ -10,18 +9,20 @@ import { Salutation } from "../enums/salutation";
.select("qualification.id", "qualificationId") .select("qualification.id", "qualificationId")
.addSelect("qualification.qualification", "qualification") .addSelect("qualification.qualification", "qualification")
.addSelect("member.id", "memberId") .addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname") .addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname") .addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix") .addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate") .addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect( .addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURRENT_DATE)))", "SUM(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURRENT_DATE)))",
"durationInDays" "durationInDays"
) )
.leftJoin("memberQualifications.qualification", "qualification") .leftJoin("memberQualifications.qualification", "qualification")
.leftJoin("memberQualifications.member", "member") .leftJoin("memberQualifications.member", "member")
.groupBy("qualification.id"), .leftJoin("member.salutation", "salutation")
.groupBy("qualification.id")
.addGroupBy("member.id"),
}) })
export class memberQualificationsView { export class memberQualificationsView {
@ViewColumn() @ViewColumn()
@ -37,7 +38,7 @@ export class memberQualificationsView {
memberId: number; memberId: number;
@ViewColumn() @ViewColumn()
memberSalutation: Salutation; memberSalutation: string;
@ViewColumn() @ViewColumn()
memberFirstname: string; memberFirstname: string;

View file

@ -1,6 +1,5 @@
import { DataSource, ViewColumn, ViewEntity } from "typeorm"; import { DataSource, ViewColumn, ViewEntity } from "typeorm";
import { member } from "../entity/club/member/member"; import { member } from "../entity/club/member/member";
import { Salutation } from "../enums/salutation";
@ViewEntity({ @ViewEntity({
expression: (datasource: DataSource) => expression: (datasource: DataSource) =>
@ -8,21 +7,22 @@ import { Salutation } from "../enums/salutation";
.getRepository(member) .getRepository(member)
.createQueryBuilder("member") .createQueryBuilder("member")
.select("member.id", "id") .select("member.id", "id")
.addSelect("member.salutation", "salutation")
.addSelect("member.firstname", "firstname") .addSelect("member.firstname", "firstname")
.addSelect("member.lastname", "lastname") .addSelect("member.lastname", "lastname")
.addSelect("member.nameaffix", "nameaffix") .addSelect("member.nameaffix", "nameaffix")
.addSelect("member.birthdate", "birthdate") .addSelect("member.birthdate", "birthdate")
.addSelect("salutation.salutation", "salutation")
.addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge") .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge")
.addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear") .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear")
.addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge"), .addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge")
.leftJoin("member.salutation", "salutation"),
}) })
export class memberView { export class memberView {
@ViewColumn() @ViewColumn()
id: number; id: number;
@ViewColumn() @ViewColumn()
salutation: Salutation; salutation: string;
@ViewColumn() @ViewColumn()
firstname: string; firstname: string;

View file

@ -1,6 +1,5 @@
import { DataSource, ViewColumn, ViewEntity } from "typeorm"; import { DataSource, ViewColumn, ViewEntity } from "typeorm";
import { membership } from "../entity/club/member/membership"; import { membership } from "../entity/club/member/membership";
import { Salutation } from "../enums/salutation";
@ViewEntity({ @ViewEntity({
expression: (datasource: DataSource) => expression: (datasource: DataSource) =>
@ -10,11 +9,11 @@ import { Salutation } from "../enums/salutation";
.select("status.id", "statusId") .select("status.id", "statusId")
.addSelect("status.status", "status") .addSelect("status.status", "status")
.addSelect("member.id", "memberId") .addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname") .addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname") .addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix") .addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate") .addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect("SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", "durationInDays") .addSelect("SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", "durationInDays")
.addSelect( .addSelect(
"CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))", "CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))",
@ -22,6 +21,7 @@ import { Salutation } from "../enums/salutation";
) )
.leftJoin("membership.status", "status") .leftJoin("membership.status", "status")
.leftJoin("membership.member", "member") .leftJoin("membership.member", "member")
.leftJoin("member.salutation", "salutation")
.groupBy("status.id") .groupBy("status.id")
.addGroupBy("member.id"), .addGroupBy("member.id"),
}) })
@ -42,7 +42,7 @@ export class membershipView {
memberId: number; memberId: number;
@ViewColumn() @ViewColumn()
memberSalutation: Salutation; memberSalutation: string;
@ViewColumn() @ViewColumn()
memberFirstname: string; memberFirstname: string;