Compare commits

...

23 commits

Author SHA1 Message Date
f356a75067 Merge branch 'develop' into milestone/ff-admin-unit 2025-06-08 07:39:36 +02:00
3f54b5d243 Merge branch 'main' into develop 2025-06-07 13:52:59 +00:00
de6ed4fba8 update packages 2025-06-07 15:44:44 +02:00
fff9ebc4fa Merge pull request 'move-database-to-postgres-only' (#115) from move-database-to-postgres-only into develop
Reviewed-on: #115
2025-06-07 13:39:21 +00:00
dc43df8937 fix: spelling of invite orUpdate 2025-06-07 15:38:56 +02:00
feb03ba99d better insert template usage scopes 2025-06-07 15:38:34 +02:00
874b863b1e update package.json 2025-06-07 15:25:41 +02:00
97ffae009b migrate schema to postgres only 2025-06-07 15:20:20 +02:00
19457824ee 1.6.0 2025-06-06 09:36:53 +02:00
e4365135a1 Merge pull request 'minor v1.6.0' (#114) from develop into main
Reviewed-on: #114
2025-06-06 07:35:59 +00:00
c849b8eb18 fix: full fun of migrations from start 2025-06-06 09:33:06 +02:00
0582cb3af7 fix: database request ILIKE and demodata 2025-06-06 09:11:39 +02:00
1f8ec2eeed Merge branch 'main' into develop 2025-06-05 14:35:04 +00:00
db7a4fff03 enhance: case-insensitive search with ILike 2025-06-05 08:04:11 +02:00
2998943dfa Merge pull request 'feature/#103-member-extend-data' (#111) from feature/#103-member-extend-data into develop
Reviewed-on: #111
2025-06-03 13:33:05 +00:00
38bfa3dd75 member print 2025-06-03 15:32:41 +02:00
fded8a663a education controller, service, command 2025-06-03 15:20:46 +02:00
5368a96d0f fix: save false value to database 2025-05-31 07:32:25 +02:00
573f92d098 enhance: add membership total view 2025-05-30 15:13:38 +02:00
f6a0a61ed8 change: provide createdAt Date for newsletter 2025-05-29 11:21:08 +02:00
2357497d3a enhance: sort newsletter by creation date 2025-05-29 11:12:33 +02:00
4074617145 1.5.4 2025-05-22 10:03:56 +02:00
3a82c74829 Merge pull request 'patches v1.5.4' (#104) from fix/queryBuilder into main
Reviewed-on: #104
2025-05-22 08:03:29 +00:00
67 changed files with 1792 additions and 1506 deletions

838
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "ff-admin-server",
"version": "1.5.3",
"version": "1.6.0",
"description": "Feuerwehr/Verein Mitgliederverwaltung Server",
"main": "dist/index.js",
"scripts": {
@ -41,44 +41,41 @@
"moment": "^2.30.1",
"morgan": "^1.10.0",
"ms": "^2.1.3",
"multer": "^1.4.5-lts.2",
"mysql": "^2.18.1",
"multer": "^2.0.1",
"node-schedule": "^2.1.1",
"nodemailer": "^7.0.2",
"nodemailer": "^7.0.3",
"pdf-lib": "^1.17.1",
"pg": "^8.15.6",
"puppeteer": "^24.8.0",
"pg": "^8.16.0",
"puppeteer": "^24.10.0",
"qrcode": "^1.5.4",
"reflect-metadata": "^0.2.2",
"rss-parser": "^3.13.0",
"sharp": "^0.34.1",
"sharp": "^0.34.2",
"sharp-ico": "^0.1.5",
"socket.io": "^4.8.1",
"speakeasy": "^2.0.0",
"sqlite3": "^5.1.7",
"typeorm": "^0.3.22",
"typeorm": "^0.3.24",
"uuid": "^11.1.0",
"validator": "^13.15.0"
"validator": "^13.15.15"
},
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^5.0.1",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.3",
"@types/ip": "^1.1.3",
"@types/jsonwebtoken": "^9.0.9",
"@types/lodash.clonedeep": "^4.5.9",
"@types/lodash.uniqby": "^4.7.9",
"@types/morgan": "^1.9.9",
"@types/morgan": "^1.9.10",
"@types/ms": "^2.1.0",
"@types/multer": "^1.4.12",
"@types/mysql": "^2.15.27",
"@types/node": "^22.15.12",
"@types/multer": "^1.4.13",
"@types/node": "^22.15.30",
"@types/node-schedule": "^2.1.7",
"@types/nodemailer": "^6.4.17",
"@types/pg": "~8.12.0",
"@types/pg": "~8.15.4",
"@types/qrcode": "~1.5.5",
"@types/speakeasy": "^2.0.10",
"@types/uuid": "^10.0.0",
"@types/validator": "^13.15.0",
"@types/validator": "^13.15.1",
"ts-node": "10.9.2",
"typescript": "^5.8.3"
}

View file

@ -5,6 +5,7 @@ export interface CreateMemberCommand {
nameaffix: string;
birthdate: Date;
internalId?: string;
note?: string;
}
export interface UpdateMemberCommand {
@ -15,6 +16,7 @@ export interface UpdateMemberCommand {
nameaffix: string;
birthdate: Date;
internalId?: string;
note?: string;
}
export interface DeleteMemberCommand {

View file

@ -23,6 +23,7 @@ export default abstract class MemberCommandHandler {
nameaffix: createMember.nameaffix,
birthdate: createMember.birthdate,
internalId: createMember.internalId,
note: createMember.note,
})
.execute()
.then((result) => {
@ -49,6 +50,7 @@ export default abstract class MemberCommandHandler {
nameaffix: updateMember.nameaffix,
birthdate: updateMember.birthdate,
internalId: updateMember.internalId,
note: updateMember.note,
})
.where("id = :id", { id: updateMember.id })
.execute()

View file

@ -0,0 +1,23 @@
export interface CreateMemberEducationCommand {
start: Date;
end?: Date;
note?: string;
place?: string;
memberId: string;
educationId: number;
}
export interface UpdateMemberEducationCommand {
id: number;
start: Date;
end?: Date;
note?: string;
place?: string;
memberId: string;
educationId: number;
}
export interface DeleteMemberEducationCommand {
id: number;
memberId: string;
}

View file

@ -0,0 +1,82 @@
import { dataSource } from "../../../data-source";
import { memberEducations } from "../../../entity/club/member/memberEducations";
import DatabaseActionException from "../../../exceptions/databaseActionException";
import InternalException from "../../../exceptions/internalException";
import {
CreateMemberEducationCommand,
DeleteMemberEducationCommand,
UpdateMemberEducationCommand,
} from "./memberEducationCommand";
export default abstract class MemberEducationCommandHandler {
/**
* @description create memberEducation
* @param {CreateMemberEducationCommand} createMemberEducation
* @returns {Promise<number>}
*/
static async create(createMemberEducation: CreateMemberEducationCommand): Promise<number> {
return await dataSource
.createQueryBuilder()
.insert()
.into(memberEducations)
.values({
note: createMemberEducation.note,
place: createMemberEducation.place,
start: createMemberEducation.start,
end: createMemberEducation.end,
memberId: createMemberEducation.memberId,
educationId: createMemberEducation.educationId,
})
.execute()
.then((result) => {
return result.identifiers[0].id;
})
.catch((err) => {
throw new DatabaseActionException("CREATE", "memberEducation", err);
});
}
/**
* @description update memberEducation
* @param {UpdateMemberEducationCommand} updateMemberEducation
* @returns {Promise<void>}
*/
static async update(updateMemberEducation: UpdateMemberEducationCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.update(memberEducations)
.set({
note: updateMemberEducation.note,
start: updateMemberEducation.start,
end: updateMemberEducation.end,
place: updateMemberEducation.place,
educationId: updateMemberEducation.educationId,
})
.where("id = :id", { id: updateMemberEducation.id })
.andWhere("memberId = :memberId", { memberId: updateMemberEducation.memberId })
.execute()
.then(() => {})
.catch((err) => {
throw new DatabaseActionException("UPDATE", "memberEducation", err);
});
}
/**
* @description delete memberEducation
* @param {DeleteMemberEducationCommand} deleteMemberEducation
* @returns {Promise<void>}
*/
static async delete(deleteMemberEducation: DeleteMemberEducationCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.delete()
.from(memberEducations)
.where("id = :id", { id: deleteMemberEducation.id })
.andWhere("memberId = :memberId", { memberId: deleteMemberEducation.memberId })
.execute()
.then(() => {})
.catch((err) => {
throw new DatabaseActionException("DELETE", "memberEducation", err);
});
}
}

View file

@ -0,0 +1,14 @@
export interface CreateEducationCommand {
education: string;
description?: string;
}
export interface UpdateEducationCommand {
id: number;
education: string;
description?: string;
}
export interface DeleteEducationCommand {
id: number;
}

View file

@ -0,0 +1,68 @@
import { dataSource } from "../../../data-source";
import { education } from "../../../entity/configuration/education";
import DatabaseActionException from "../../../exceptions/databaseActionException";
import { CreateEducationCommand, DeleteEducationCommand, UpdateEducationCommand } from "./educationCommand";
export default abstract class EducationCommandHandler {
/**
* @description create education
* @param {CreateEducationCommand} createEducation
* @returns {Promise<number>}
*/
static async create(createEducation: CreateEducationCommand): Promise<number> {
return await dataSource
.createQueryBuilder()
.insert()
.into(education)
.values({
education: createEducation.education,
description: createEducation.description,
})
.execute()
.then((result) => {
return result.identifiers[0].id;
})
.catch((err) => {
throw new DatabaseActionException("CREATE", "education", err);
});
}
/**
* @description update education
* @param {UpdateEducationCommand} updateEducation
* @returns {Promise<void>}
*/
static async update(updateEducation: UpdateEducationCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.update(education)
.set({
education: updateEducation.education,
description: updateEducation.description,
})
.where("id = :id", { id: updateEducation.id })
.execute()
.then(() => {})
.catch((err) => {
throw new DatabaseActionException("UPDATE", "education", err);
});
}
/**
* @description delete education
* @param {DeleteEducationCommand} deleteEducation
* @returns {Promise<void>}
*/
static async delete(deleteEducation: DeleteEducationCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.delete()
.from(education)
.where("id = :id", { id: deleteEducation.id })
.execute()
.then(() => {})
.catch((err) => {
throw new DatabaseActionException("DELETE", "education", err);
});
}
}

View file

@ -26,7 +26,7 @@ export default abstract class InviteCommandHandler {
lastname: createInvite.lastname,
secret: createInvite.secret,
})
.orUpdate(["firstName", "lastName", "token", "secret"], ["mail"])
.orUpdate(["firstname", "lastname", "token", "secret"], ["mail"])
.execute()
.then((result) => {
return token;

View file

@ -49,6 +49,14 @@ import {
import CommunicationCommandHandler from "../../../command/club/member/communicationCommandHandler";
import { PdfExport } from "../../../helpers/pdfExport";
import { PermissionModule } from "../../../type/permissionTypes";
import MemberEducationFactory from "../../../factory/admin/club/member/memberEducation";
import MemberEducationService from "../../../service/club/member/memberEducationService";
import {
CreateMemberEducationCommand,
DeleteMemberEducationCommand,
UpdateMemberEducationCommand,
} from "../../../command/club/member/memberEducationCommand";
import MemberEducationCommandHandler from "../../../command/club/member/memberEducationCommandHandler";
/**
* @description get all members
@ -144,6 +152,7 @@ export async function getMemberPrintoutById(req: Request, res: Response): Promis
let qualifications = await MemberQualificationService.getAll(memberId);
let positions = await MemberExecutivePositionService.getAll(memberId);
let communications = await CommunicationService.getAll(memberId);
let educations = await MemberEducationService.getAll(memberId);
let pdf = await PdfExport.renderFile({
title: "Mitglieder-Ausdruck",
@ -157,6 +166,7 @@ export async function getMemberPrintoutById(req: Request, res: Response): Promis
qualifications,
positions,
communications,
educations,
},
});
@ -195,6 +205,19 @@ export async function getMembershipStatisticsById(req: Request, res: Response):
res.json(MembershipFactory.mapToBaseStatistics(member));
}
/**
* @description get member total statistics by id
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getMembershipTotalStatisticsById(req: Request, res: Response): Promise<any> {
const memberId = req.params.memberId;
let member = await MembershipService.getTotalStatisticsById(memberId);
res.json(MembershipFactory.mapToSingleTotalStatistic(member));
}
/**
* @description get membership by member and record
* @param req {Request} Express req object
@ -263,6 +286,33 @@ export async function getQualificationByMemberAndRecord(req: Request, res: Respo
res.json(MemberQualificationFactory.mapToSingle(qualification));
}
/**
* @description get educations by member
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getEducationsByMember(req: Request, res: Response): Promise<any> {
const memberId = req.params.memberId;
let educations = await MemberEducationService.getAll(memberId);
res.json(MemberEducationFactory.mapToBase(educations));
}
/**
* @description get education by member and record
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getEducationByMemberAndRecord(req: Request, res: Response): Promise<any> {
const memberId = req.params.memberId;
const recordId = parseInt(req.params.id);
let education = await MemberEducationService.getById(memberId, recordId);
res.json(MemberEducationFactory.mapToSingle(education));
}
/**
* @description get executive positions by member
* @param req {Request} Express req object
@ -330,6 +380,7 @@ export async function createMember(req: Request, res: Response): Promise<any> {
const nameaffix = req.body.nameaffix;
const birthdate = req.body.birthdate;
const internalId = req.body.internalId || null;
const note = req.body.note || null;
let createMember: CreateMemberCommand = {
salutationId,
@ -338,6 +389,7 @@ export async function createMember(req: Request, res: Response): Promise<any> {
nameaffix,
birthdate,
internalId,
note,
};
let memberId = await MemberCommandHandler.create(createMember);
@ -413,6 +465,33 @@ export async function addQualificationToMember(req: Request, res: Response): Pro
res.sendStatus(204);
}
/**
* @description add education to member
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function addEducationToMember(req: Request, res: Response): Promise<any> {
const memberId = req.params.memberId;
const note = req.body.note;
const place = req.body.place;
const start = req.body.start;
const end = req.body.end || null;
const educationId = req.body.educationId;
let createMemberEducation: CreateMemberEducationCommand = {
note,
start,
end,
place,
memberId,
educationId,
};
await MemberEducationCommandHandler.create(createMemberEducation);
res.sendStatus(204);
}
/**
* @description add executive positions to member
* @param req {Request} Express req object
@ -491,6 +570,7 @@ export async function updateMemberById(req: Request, res: Response): Promise<any
const nameaffix = req.body.nameaffix;
const birthdate = req.body.birthdate;
const internalId = req.body.internalId || null;
const note = req.body.note || null;
let updateMember: UpdateMemberCommand = {
id: memberId,
@ -500,6 +580,7 @@ export async function updateMemberById(req: Request, res: Response): Promise<any
nameaffix,
birthdate,
internalId,
note,
};
await MemberCommandHandler.update(updateMember);
@ -589,6 +670,35 @@ export async function updateQualificationOfMember(req: Request, res: Response):
res.sendStatus(204);
}
/**
* @description update education of member
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function updateEducationOfMember(req: Request, res: Response): Promise<any> {
const memberId = req.params.memberId;
const recordId = parseInt(req.params.recordId);
const start = req.body.start;
const end = req.body.end || null;
const note = req.body.note;
const place = req.body.place;
const educationId = req.body.educationId;
let updateMemberEducation: UpdateMemberEducationCommand = {
id: recordId,
start,
end,
note,
place,
memberId,
educationId,
};
await MemberEducationCommandHandler.update(updateMemberEducation);
res.sendStatus(204);
}
/**
* @description update executive position of member
* @param req {Request} Express req object
@ -729,6 +839,25 @@ export async function deleteQualificationOfMember(req: Request, res: Response):
res.sendStatus(204);
}
/**
* @description delete education from member
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function deleteEducationsOfMember(req: Request, res: Response): Promise<any> {
const memberId = req.params.memberId;
const recordId = parseInt(req.params.recordId);
let deleteMemberEducation: DeleteMemberEducationCommand = {
id: recordId,
memberId,
};
await MemberEducationCommandHandler.delete(deleteMemberEducation);
res.sendStatus(204);
}
/**
* @description delete executive position from member
* @param req {Request} Express req object

View file

@ -0,0 +1,91 @@
import { Request, Response } from "express";
import EducationService from "../../../service/configuration/education";
import EducationFactory from "../../../factory/admin/configuration/education";
import {
CreateEducationCommand,
DeleteEducationCommand,
UpdateEducationCommand,
} from "../../../command/configuration/education/educationCommand";
import EducationCommandHandler from "../../../command/configuration/education/educationCommandHandler";
/**
* @description get all educations
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getAllEducations(req: Request, res: Response): Promise<any> {
let educations = await EducationService.getAll();
res.json(EducationFactory.mapToBase(educations));
}
/**
* @description get education by id
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getEducationById(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let education = await EducationService.getById(id);
res.json(EducationFactory.mapToSingle(education));
}
/**
* @description create new education
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function createEducation(req: Request, res: Response): Promise<any> {
const education = req.body.education;
const description = req.body.description;
let createEducation: CreateEducationCommand = {
education: education,
description: description,
};
await EducationCommandHandler.create(createEducation);
res.sendStatus(204);
}
/**
* @description update education
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function updateEducation(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
const education = req.body.education;
const description = req.body.description;
let updateEducation: UpdateEducationCommand = {
id: id,
education: education,
description: description,
};
await EducationCommandHandler.update(updateEducation);
res.sendStatus(204);
}
/**
* @description delete education
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function deleteEducation(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let deleteEducation: DeleteEducationCommand = {
id: id,
};
await EducationCommandHandler.delete(deleteEducation);
res.sendStatus(204);
}

View file

@ -1,7 +1,7 @@
import "dotenv/config";
import "reflect-metadata";
import { DataSource } from "typeorm";
import { configCheck, DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_TYPE, DB_USERNAME } from "./env.defaults";
import { configCheck, DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_USERNAME } from "./env.defaults";
import { user } from "./entity/management/user";
import { refresh } from "./entity/refresh";
@ -34,7 +34,7 @@ import { query } from "./entity/configuration/query";
import { memberView } from "./views/memberView";
import { memberExecutivePositionsView } from "./views/memberExecutivePositionView";
import { memberQualificationsView } from "./views/memberQualificationsView";
import { membershipView } from "./views/membershipsView";
import { membershipTotalView, membershipView } from "./views/membershipsView";
import { template } from "./entity/configuration/template";
import { templateUsage } from "./entity/configuration/templateUsage";
import { newsletter } from "./entity/club/newsletter/newsletter";
@ -45,22 +45,16 @@ import { webapi } from "./entity/management/webapi";
import { webapiPermission } from "./entity/management/webapi_permission";
import { salutation } from "./entity/configuration/salutation";
import { setting } from "./entity/management/setting";
import { education } from "./entity/configuration/education";
import { memberEducations } from "./entity/club/member/memberEducations";
import { BackupAndResetDatabase1738166124200 } from "./migrations/1738166124200-BackupAndResetDatabase";
import { CreateSchema1738166167472 } from "./migrations/1738166167472-CreateSchema";
import { TemplatesAndProtocolSort1742549956787 } from "./migrations/1742549956787-templatesAndProtocolSort";
import { QueryToUUID1742922178643 } from "./migrations/1742922178643-queryToUUID";
import { NewsletterColumnType1744351418751 } from "./migrations/1744351418751-newsletterColumnType";
import { QueryUpdatedAt1744795756230 } from "./migrations/1744795756230-QueryUpdatedAt";
import { SettingsFromEnv1745059495807 } from "./migrations/1745059495807-settingsFromEnv";
import { MemberCreatedAt1746006549262 } from "./migrations/1746006549262-memberCreatedAt";
import { UserLoginRoutine1746252454922 } from "./migrations/1746252454922-UserLoginRoutine";
import { SettingsFromEnv_SET1745059495808 } from "./migrations/1745059495808-settingsFromEnv_set";
import { BackupAndResetDatabase1749296262915 } from "./migrations/1749296262915-BackupAndResetDatabase";
import { CreateSchema1749296280721 } from "./migrations/1749296280721-CreateSchema";
configCheck();
const dataSource = new DataSource({
type: DB_TYPE as any,
type: "postgres",
host: DB_HOST,
port: DB_PORT,
username: DB_USERNAME,
@ -68,7 +62,6 @@ const dataSource = new DataSource({
database: DB_NAME,
synchronize: false,
logging: process.env.NODE_ENV ? true : ["schema", "error", "warn", "log", "migration"],
bigNumberStrings: false,
entities: [
user,
refresh,
@ -82,6 +75,7 @@ const dataSource = new DataSource({
communicationType,
executivePosition,
membershipStatus,
education,
qualification,
salutation,
member,
@ -89,6 +83,7 @@ const dataSource = new DataSource({
memberExecutivePositions,
memberQualifications,
membership,
memberEducations,
protocol,
protocolAgenda,
protocolDecision,
@ -108,22 +103,12 @@ const dataSource = new DataSource({
memberExecutivePositionsView,
memberQualificationsView,
membershipView,
membershipTotalView,
webapi,
webapiPermission,
setting,
],
migrations: [
BackupAndResetDatabase1738166124200,
CreateSchema1738166167472,
TemplatesAndProtocolSort1742549956787,
QueryToUUID1742922178643,
NewsletterColumnType1744351418751,
QueryUpdatedAt1744795756230,
SettingsFromEnv1745059495807,
SettingsFromEnv_SET1745059495808,
MemberCreatedAt1746006549262,
UserLoginRoutine1746252454922,
],
migrations: [BackupAndResetDatabase1749296262915, CreateSchema1749296280721],
migrationsRun: true,
migrationsTransactionMode: "each",
subscribers: [],

View file

@ -45,7 +45,7 @@ export class calendar {
@UpdateDateColumn()
updatedAt: Date;
@Column({ type: "varchar", nullable: true, default: null, unique: true })
@Column({ type: "varchar", length: "255", nullable: true, default: null, unique: true })
webpageId: string;
@ManyToOne(() => calendarType, (t) => t.calendar, {

View file

@ -8,6 +8,7 @@ import {
OneToMany,
OneToOne,
PrimaryColumn,
PrimaryGeneratedColumn,
} from "typeorm";
import { membership } from "./membership";
import { memberAwards } from "./memberAwards";
@ -16,10 +17,11 @@ import { memberExecutivePositions } from "./memberExecutivePositions";
import { communication } from "./communication";
import { salutation } from "../../configuration/salutation";
import { getTypeByORM } from "../../../migrations/ormHelper";
import { memberEducations } from "./memberEducations";
@Entity()
export class member {
@PrimaryColumn({ generated: "uuid", type: "varchar" })
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({ type: "varchar", length: 255 })
@ -37,6 +39,9 @@ export class member {
@Column({ type: "varchar", length: 255, unique: true, nullable: true })
internalId?: string;
@Column({ type: "varchar", length: 255, nullable: true })
note?: string;
@Column()
salutationId: number;
@ -66,6 +71,9 @@ export class member {
@OneToMany(() => memberQualifications, (qualifications) => qualifications.member, { cascade: ["insert"] })
qualifications: memberQualifications[];
@OneToMany(() => memberEducations, (educations) => educations.member, { cascade: ["insert"] })
educations: memberEducations[];
firstMembershipEntry?: membership;
lastMembershipEntry?: membership;
preferredCommunication?: Array<communication>;

View file

@ -0,0 +1,43 @@
import { Column, ColumnType, Entity, ManyToOne, PrimaryColumn } from "typeorm";
import { member } from "./member";
import { education } from "../../configuration/education";
import { getTypeByORM } from "../../../migrations/ormHelper";
@Entity()
export class memberEducations {
@PrimaryColumn({ generated: "increment", type: "int" })
id: number;
@Column({ type: getTypeByORM("date").type as ColumnType })
start: Date;
@Column({ type: getTypeByORM("date").type as ColumnType, nullable: true })
end?: Date;
@Column({ type: "varchar", length: 255, nullable: true })
note?: string;
@Column({ type: "varchar", length: 255, nullable: true })
place?: string;
@Column()
memberId: string;
@Column()
educationId: number;
@ManyToOne(() => member, (member) => member.awards, {
nullable: false,
onDelete: "CASCADE",
onUpdate: "RESTRICT",
})
member: member;
@ManyToOne(() => education, (education) => education.members, {
nullable: false,
onDelete: "RESTRICT",
onUpdate: "RESTRICT",
cascade: ["insert"],
})
education: education;
}

View file

@ -1,4 +1,4 @@
import { Column, Entity, ManyToOne, OneToMany, PrimaryColumn } from "typeorm";
import { Column, CreateDateColumn, Entity, ManyToOne, OneToMany, PrimaryColumn } from "typeorm";
import { newsletterDates } from "./newsletterDates";
import { newsletterRecipients } from "./newsletterRecipients";
import { query } from "../../configuration/query";
@ -14,6 +14,9 @@ export class newsletter {
@Column({ type: "varchar", length: 255, default: "" })
description: string;
@CreateDateColumn()
createdAt: Date;
@Column({ type: "text", default: "" })
newsletterTitle: string;

View file

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

View file

@ -1,8 +1,8 @@
import { Column, Entity, PrimaryColumn, UpdateDateColumn } from "typeorm";
import { Column, Entity, PrimaryColumn, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
@Entity()
export class query {
@PrimaryColumn({ generated: "uuid", type: "varchar" })
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({ type: "varchar", length: 255, unique: true })

View file

@ -1,4 +1,4 @@
import { Column, Entity, JoinTable, ManyToMany, OneToMany, PrimaryColumn } from "typeorm";
import { Column, Entity, JoinTable, ManyToMany, OneToMany, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
import { role } from "./role";
import { userPermission } from "./user_permission";
import { LoginRoutineEnum } from "../../enums/loginRoutineEnum";
@ -7,7 +7,7 @@ import { APPLICATION_SECRET } from "../../env.defaults";
@Entity()
export class user {
@PrimaryColumn({ generated: "uuid", type: "varchar" })
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({ type: "varchar", unique: true, length: 255 })

View file

@ -2,9 +2,8 @@ import "dotenv/config";
import ms from "ms";
import ip from "ip";
export const DB_TYPE = process.env.DB_TYPE ?? "mysql";
export const DB_HOST = process.env.DB_HOST ?? "";
export const DB_PORT = Number(process.env.DB_PORT ?? 3306);
export const DB_PORT = Number(process.env.DB_PORT ?? 5432);
export const DB_NAME = process.env.DB_NAME ?? "";
export const DB_USERNAME = process.env.DB_USERNAME ?? "";
export const DB_PASSWORD = process.env.DB_PASSWORD ?? "";
@ -39,7 +38,6 @@ export const TRUST_PROXY = ((): Array<string> | string | boolean | number | null
})();
export function configCheck() {
if (DB_TYPE != "mysql" && DB_TYPE != "postgres") throw new Error("set valid value to DB_TYPE (mysql|postgres)");
if (DB_HOST == "" || typeof DB_HOST != "string") throw new Error("set valid value to DB_HOST");
if (DB_NAME == "" || typeof DB_NAME != "string") throw new Error("set valid value to DB_NAME");
if (DB_USERNAME == "" || typeof DB_USERNAME != "string") throw new Error("set valid value to DB_USERNAME");

View file

@ -1,14 +1,5 @@
import { DB_TYPE } from "../../../../env.defaults";
export default abstract class DateMappingHelper {
static mapDate(entry: any) {
switch (DB_TYPE) {
case "postgres":
return `${entry?.years ?? 0} years ${entry?.months ?? 0} months ${entry?.days ?? 0} days`;
case "mysql":
return entry.toString();
case "sqlite":
return entry;
}
return `${entry?.years ?? 0} years ${entry?.months ?? 0} months ${entry?.days ?? 0} days`;
}
}

View file

@ -20,7 +20,8 @@ export default abstract class MemberFactory {
lastname: record?.lastname,
nameaffix: record?.nameaffix,
birthdate: record?.birthdate,
internalId: record.internalId,
internalId: record?.internalId,
note: record?.note,
firstMembershipEntry: record?.firstMembershipEntry
? MembershipFactory.mapToSingle(record.firstMembershipEntry)
: null,

View file

@ -0,0 +1,30 @@
import { memberEducations } from "../../../../entity/club/member/memberEducations";
import { MemberEducationViewModel } from "../../../../viewmodel/admin/club/member/memberEducation.models";
export default abstract class MemberEducationFactory {
/**
* @description map record to memberEducation
* @param {memberEducation} record
* @returns {MemberEducationViewModel}
*/
public static mapToSingle(record: memberEducations): MemberEducationViewModel {
return {
id: record.id,
start: record.start,
end: record.end,
note: record.note,
place: record.place,
education: record.education.education,
educationId: record.education.id,
};
}
/**
* @description map records to memberEducation
* @param {Array<memberEducation>} records
* @returns {Array<MemberEducationViewModel>}
*/
public static mapToBase(records: Array<memberEducations>): Array<MemberEducationViewModel> {
return records.map((r) => this.mapToSingle(r));
}
}

View file

@ -1,9 +1,10 @@
import { membership } from "../../../../entity/club/member/membership";
import {
MembershipStatisticsViewModel,
MembershipTotalStatisticsViewModel,
MembershipViewModel,
} from "../../../../viewmodel/admin/club/member/membership.models";
import { membershipView } from "../../../../views/membershipsView";
import { membershipTotalView, membershipView } from "../../../../views/membershipsView";
import DateMappingHelper from "./dateMappingHelper";
export default abstract class MembershipFactory {
@ -53,6 +54,25 @@ export default abstract class MembershipFactory {
};
}
/**
* @description map view record to MembershipTotalStatisticsViewModel
* @param {membershipTotalView} record
* @returns {MembershipTotalStatisticsViewModel}
*/
public static mapToSingleTotalStatistic(record: membershipTotalView): MembershipTotalStatisticsViewModel {
return {
durationInDays: record.durationInDays,
durationInYears: record.durationInYears,
exactDuration: DateMappingHelper.mapDate(record.exactDuration),
memberId: record.memberId,
memberSalutation: record.memberSalutation,
memberFirstname: record.memberFirstname,
memberLastname: record.memberLastname,
memberNameaffix: record.memberNameaffix,
memberBirthdate: record.memberBirthdate,
};
}
/**
* @description map records to MembershipStatisticsViewModel
* @param {Array<membershipView>} records

View file

@ -18,6 +18,7 @@ export default abstract class NewsletterFactory {
newsletterSignatur: record.newsletterSignatur,
isSent: record.isSent,
recipientsByQueryId: record?.recipientsByQuery ? record.recipientsByQuery.id : null,
createdAt: record.createdAt,
};
}

View file

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

View file

@ -8,6 +8,7 @@ import DatabaseActionException from "../exceptions/databaseActionException";
import { availableTemplates } from "../type/templateTypes";
import SettingHelper from "./settingsHelper";
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
import { education } from "../entity/configuration/education";
export type BackupSection =
| "member"
@ -55,6 +56,7 @@ export default abstract class BackupHelper {
"member_executive_positions",
"membership",
"communication",
"member_educations",
],
memberBase: [
"award",
@ -63,6 +65,7 @@ export default abstract class BackupHelper {
"membership_status",
"communication_type",
"salutation",
"education",
],
protocol: [
"protocol",
@ -246,6 +249,8 @@ export default abstract class BackupHelper {
.leftJoin("positions.executivePosition", "executivePosition")
.leftJoin("member.qualifications", "qualifications")
.leftJoin("qualifications.qualification", "qualification")
.leftJoin("member.educations", "educations")
.leftJoin("educations.education", "education")
.select([
...(collectIds ? ["member.id"] : []),
"member.firstname",
@ -253,6 +258,7 @@ export default abstract class BackupHelper {
"member.nameaffix",
"member.birthdate",
"member.internalId",
"member.note",
])
.addSelect(["salutation.salutation"])
.addSelect([
@ -280,6 +286,14 @@ export default abstract class BackupHelper {
"qualification.qualification",
"qualification.description",
])
.addSelect([
"educations.start",
"educations.end",
"educations.place",
"educations.note",
"education.education",
"education.description",
])
.getMany();
}
private static async getMemberBase(): Promise<{ [key: string]: Array<any> }> {
@ -294,6 +308,7 @@ export default abstract class BackupHelper {
qualification: await dataSource
.getRepository("qualification")
.find({ select: { qualification: true, description: true } }),
education: await dataSource.getRepository("education").find({ select: { education: true, description: true } }),
};
}
private static async getProtocol(collectIds: boolean): Promise<Array<any>> {
@ -344,6 +359,7 @@ export default abstract class BackupHelper {
"newsletter.newsletterText",
"newsletter.newsletterSignatur",
"newsletter.isSent",
"newsletter.createdAt",
])
.addSelect(["dates.calendarId", "dates.diffTitle", "dates.diffDescription"])
.addSelect(["recipients.memberId"])
@ -534,6 +550,13 @@ export default abstract class BackupHelper {
.map((d) => ({ ...d, id: undefined })),
"qualification"
),
education: uniqBy(
data
.map((d) => (d.education ?? []).map((c: any) => c.education))
.flat()
.map((d) => ({ ...d, id: undefined })),
"education"
),
});
let salutation = await this.transactionManager.getRepository("salutation").find();
@ -541,6 +564,7 @@ export default abstract class BackupHelper {
let membership = await this.transactionManager.getRepository("membership_status").find();
let award = await this.transactionManager.getRepository("award").find();
let qualification = await this.transactionManager.getRepository("qualification").find();
let education = await this.transactionManager.getRepository("education").find();
let position = await this.transactionManager.getRepository("executive_position").find();
let dataWithMappedIds = data.map((d) => ({
...d,
@ -583,6 +607,13 @@ export default abstract class BackupHelper {
id: qualification.find((iq) => iq.qualification == q.qualification.qualification)?.id ?? undefined,
},
})),
educations: (d.educations ?? []).map((e: any) => ({
...e,
education: {
...e.education,
id: education.find((id) => id.education == e.education.education)?.id ?? undefined,
},
})),
}));
await this.transactionManager.getRepository("member").save(dataWithMappedIds);
}
@ -593,6 +624,7 @@ export default abstract class BackupHelper {
let award = await this.transactionManager.getRepository("award").find();
let qualification = await this.transactionManager.getRepository("qualification").find();
let position = await this.transactionManager.getRepository("executive_position").find();
let education = await this.transactionManager.getRepository("education").find();
await this.transactionManager
.createQueryBuilder()
@ -634,10 +666,19 @@ export default abstract class BackupHelper {
.insert()
.into("qualification")
.values(
(data?.["qualification"] ?? []).filter((d) => !qualification.map((q) => q.award).includes(d.qualification))
(data?.["qualification"] ?? []).filter(
(d) => !qualification.map((q) => q.qualification).includes(d.qualification)
)
)
.orIgnore()
.execute();
await this.transactionManager
.createQueryBuilder()
.insert()
.into("education")
.values((data?.["education"] ?? []).filter((d) => !education.map((q) => q.education).includes(d.education)))
.orIgnore()
.execute();
}
private static async setProtocol(data: Array<any>, collectedIds: boolean): Promise<void> {
let members = await this.transactionManager.getRepository("member").find();

View file

@ -12,6 +12,16 @@ export abstract class DemoDataHelper {
return newsletterDemoData;
case "member":
return memberDemoData;
case "listprint":
return {
today: new Date(),
list: [memberDemoData.memberships],
};
case "listprint.member":
return {
today: new Date(),
list: [memberDemoData.member],
};
default:
return {};
}

View file

@ -17,11 +17,13 @@ export default abstract class DynamicQueryBuilder {
"memberAwards",
"memberExecutivePositions",
"memberQualifications",
"memberEducations",
"membership",
"memberView",
"memberExecutivePositionsView",
"memberQualificationsView",
"membershipView",
"membershipTotalView",
];
public static getTableMeta(tableName: string): TableMeta {
@ -226,19 +228,19 @@ export default abstract class DynamicQueryBuilder {
query += ` IS NOT NULL`;
break;
case "contains":
query += ` LIKE :${parameterKey}`;
query += ` ILIKE :${parameterKey}`;
parameters[parameterKey] = `%${condition.value}%`;
break;
case "notContains":
query += ` NOT LIKE :${parameterKey}`;
query += ` NOT ILIKE :${parameterKey}`;
parameters[parameterKey] = `%${condition.value}%`;
break;
case "startsWith":
query += ` LIKE :${parameterKey}`;
query += ` ILIKE :${parameterKey}`;
parameters[parameterKey] = `${condition.value}%`;
break;
case "endsWith":
query += ` LIKE :${parameterKey}`;
query += ` ILIKE :${parameterKey}`;
parameters[parameterKey] = `%${condition.value}`;
break;
case "timespanEq":
@ -292,7 +294,12 @@ export default abstract class DynamicQueryBuilder {
results.forEach((res) => {
if (typeof value === "object" && value instanceof Date) {
res[newKey] = new Date(value).toISOString();
} else if (typeof value === "object" && !Array.isArray(value) && !(value instanceof Buffer)) {
} else if (
typeof value === "object" &&
!Array.isArray(value) &&
!(value instanceof Buffer) &&
value !== null
) {
let string = "";
for (const key of Object.keys(value)) {
string += `${value[key]} ${key} `;
@ -301,7 +308,7 @@ export default abstract class DynamicQueryBuilder {
// JSON.stringify(value).replace(/["\\{}]/g, "").replaceAll(",", ", ");
} else if (String(value) != "undefined") {
res[newKey] = String(value);
res[newKey] = value !== null ? String(value) : "";
}
});
}

View file

@ -76,7 +76,7 @@ export default abstract class SettingHelper {
* @param value The value to set
*/
public static async setSetting<K extends SettingString>(key: K, value: SettingValueMapping[K]): Promise<void> {
if (value === undefined || value === null || value == "") {
if (value === undefined || value === null || value === "") {
if (key != "mail.password") this.resetSetting(key);
return;
}

View file

@ -1,177 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import {
invite_table,
refresh_table,
reset_table,
role_permission_table,
role_table,
user_permission_table,
user_roles_table,
user_table,
webapi_permission_table,
webapi_table,
} from "./baseSchemaTables/admin";
import { templateUsage } from "../entity/configuration/templateUsage";
import {
award_table,
communication_type_table,
executive_position_table,
member_awards_table,
member_communication_table,
member_executive_positions_table,
member_executive_positions_view_mysql,
member_executive_positions_view_postgres,
member_executive_positions_view_sqlite,
member_qualifications_table,
member_qualifications_view_mysql,
member_qualifications_view_postgres,
member_qualifications_view_sqlite,
member_table,
member_view_mysql,
member_view_postgres,
member_view_sqlite,
membership_status_table,
membership_table,
membership_view_mysql,
membership_view_postgres,
membership_view_sqlite,
qualification_table,
salutation_table,
} from "./baseSchemaTables/member";
import { query_table, template_table, template_usage_table } from "./baseSchemaTables/query_template";
import {
protocol_agenda_table,
protocol_decision_table,
protocol_presence_table,
protocol_printout_table,
protocol_table,
protocol_voting_table,
} from "./baseSchemaTables/protocol";
import { calendar_table, calendar_type_table } from "./baseSchemaTables/calendar";
import {
newsletter_config_table,
newsletter_dates_table,
newsletter_recipients_table,
newsletter_table,
} from "./baseSchemaTables/newsletter";
import { DB_TYPE } from "../env.defaults";
export class CreateSchema1738166167472 implements MigrationInterface {
name = "CreateSchema1738166167472";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(reset_table, true, true, true);
await queryRunner.createTable(invite_table, true, true, true);
await queryRunner.createTable(role_table, true, true, true);
await queryRunner.createTable(role_permission_table, true, true, true);
await queryRunner.createTable(user_table, true, true, true);
await queryRunner.createTable(user_roles_table, true, true, true);
await queryRunner.createTable(user_permission_table, true, true, true);
await queryRunner.createTable(refresh_table, true, true, true);
await queryRunner.createTable(webapi_table, true, true, true);
await queryRunner.createTable(webapi_permission_table, true, true, true);
await queryRunner.createTable(salutation_table, true, true, true);
await queryRunner.createTable(award_table, true, true, true);
await queryRunner.createTable(communication_type_table, true, true, true);
await queryRunner.createTable(membership_status_table, true, true, true);
await queryRunner.createTable(executive_position_table, true, true, true);
await queryRunner.createTable(qualification_table, true, true, true);
await queryRunner.createTable(member_table, true, true, true);
await queryRunner.createTable(member_awards_table, true, true, true);
await queryRunner.createTable(member_communication_table, true, true, true);
await queryRunner.createTable(membership_table, true, true, true);
await queryRunner.createTable(member_executive_positions_table, true, true, true);
await queryRunner.createTable(member_qualifications_table, true, true, true);
if (DB_TYPE == "postgres") await queryRunner.createView(member_view_postgres, true);
else if (DB_TYPE == "mysql") await queryRunner.createView(member_view_mysql, true);
else if (DB_TYPE == "sqlite") await queryRunner.createView(member_view_sqlite, true);
if (DB_TYPE == "postgres") await queryRunner.createView(membership_view_postgres, true);
else if (DB_TYPE == "mysql") await queryRunner.createView(membership_view_mysql, true);
else if (DB_TYPE == "sqlite") await queryRunner.createView(membership_view_sqlite, true);
if (DB_TYPE == "postgres") await queryRunner.createView(member_qualifications_view_postgres, true);
else if (DB_TYPE == "mysql") await queryRunner.createView(member_qualifications_view_mysql, true);
else if (DB_TYPE == "sqlite") await queryRunner.createView(member_qualifications_view_sqlite, true);
if (DB_TYPE == "postgres") await queryRunner.createView(member_executive_positions_view_postgres, true);
else if (DB_TYPE == "mysql") await queryRunner.createView(member_executive_positions_view_mysql, true);
else if (DB_TYPE == "sqlite") await queryRunner.createView(member_executive_positions_view_sqlite, true);
await queryRunner.createTable(query_table, true, true, true);
await queryRunner.createTable(template_table, true, true, true);
await queryRunner.createTable(template_usage_table, true, true, true);
await queryRunner.manager
.createQueryBuilder()
.insert()
.into(templateUsage)
.values([{ scope: "newsletter" }, { scope: "protocol" }, { scope: "member.list" }])
.orIgnore()
.execute();
await queryRunner.createTable(protocol_table, true, true, true);
await queryRunner.createTable(protocol_agenda_table, true, true, true);
await queryRunner.createTable(protocol_decision_table, true, true, true);
await queryRunner.createTable(protocol_presence_table, true, true, true);
await queryRunner.createTable(protocol_voting_table, true, true, true);
await queryRunner.createTable(protocol_printout_table, true, true, true);
await queryRunner.createTable(calendar_type_table, true, true, true);
await queryRunner.createTable(calendar_table, true, true, true);
await queryRunner.createTable(newsletter_config_table, true, true, true);
await queryRunner.createTable(newsletter_table, true, true, true);
await queryRunner.createTable(newsletter_dates_table, true, true, true);
await queryRunner.createTable(newsletter_recipients_table, true, true, true);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable("newsletter_dates", true, true, true);
await queryRunner.dropTable("newsletter_recipients", true, true, true);
await queryRunner.dropTable("newsletter", true, true, true);
await queryRunner.dropTable("newsletter_config", true, true, true);
await queryRunner.dropTable("calendar", true, true, true);
await queryRunner.dropTable("calendar_type", true, true, true);
await queryRunner.dropTable("protocol_agenda", true, true, true);
await queryRunner.dropTable("protocol_decision", true, true, true);
await queryRunner.dropTable("protocol_presence", true, true, true);
await queryRunner.dropTable("protocol_voting", true, true, true);
await queryRunner.dropTable("protocol_printout", true, true, true);
await queryRunner.dropTable("protocol", true, true, true);
await queryRunner.dropTable("template_usage", true, true, true);
await queryRunner.dropTable("template", true, true, true);
await queryRunner.dropTable("query", true, true, true);
await queryRunner.dropView("member_view");
await queryRunner.dropView("membership_view");
await queryRunner.dropView("member_qualifications_view");
await queryRunner.dropView("member_executive_positions_view");
await queryRunner.dropTable("member_awards", true, true, true);
await queryRunner.dropTable("communication", true, true, true);
await queryRunner.dropTable("membership", true, true, true);
await queryRunner.dropTable("member_executive_positions", true, true, true);
await queryRunner.dropTable("member_qualifications", true, true, true);
await queryRunner.dropTable("member", true, true, true);
await queryRunner.dropTable("salutation", true, true, true);
await queryRunner.dropTable("award", true, true, true);
await queryRunner.dropTable("communication_type", true, true, true);
await queryRunner.dropTable("membership_status", true, true, true);
await queryRunner.dropTable("executive_position", true, true, true);
await queryRunner.dropTable("qualification", true, true, true);
await queryRunner.dropTable("webapi_permission", true, true, true);
await queryRunner.dropTable("webapi", true, true, true);
await queryRunner.dropTable("refresh", true, true, true);
await queryRunner.dropTable("user_permission", true, true, true);
await queryRunner.dropTable("user_roles", true, true, true);
await queryRunner.dropTable("user", true, true, true);
await queryRunner.dropTable("role_permission", true, true, true);
await queryRunner.dropTable("role", true, true, true);
await queryRunner.dropTable("invite", true, true, true);
await queryRunner.dropTable("reset", true, true, true);
}
}

View file

@ -1,55 +0,0 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
import { templateUsage } from "../entity/configuration/templateUsage";
import { getTypeByORM, getDefaultByORM } from "./ormHelper";
export class TemplatesAndProtocolSort1742549956787 implements MigrationInterface {
name = "TemplatesAndProtocolSort1742549956787";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.manager
.createQueryBuilder()
.insert()
.into(templateUsage)
.values([{ scope: "member" }])
.orIgnore()
.execute();
await queryRunner.manager
.createQueryBuilder()
.delete()
.from(templateUsage)
.where({ scope: "member.list" })
.execute();
await queryRunner.addColumn(
"protocol_agenda",
new TableColumn({ name: "sort", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) })
);
await queryRunner.addColumn(
"protocol_decision",
new TableColumn({ name: "sort", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) })
);
await queryRunner.addColumn(
"protocol_voting",
new TableColumn({ name: "sort", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) })
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn("protocol_agenda", "sort");
await queryRunner.dropColumn("protocol_decision", "sort");
await queryRunner.dropColumn("protocol_voting", "sort");
await queryRunner.manager
.createQueryBuilder()
.insert()
.into(templateUsage)
.values([{ scope: "member.list" }])
.orIgnore()
.execute();
await queryRunner.manager.createQueryBuilder().delete().from(templateUsage).where({ scope: "member" }).execute();
}
}

View file

@ -1,94 +0,0 @@
import { MigrationInterface, QueryRunner, TableColumn, TableForeignKey } from "typeorm";
import { getTypeByORM, isIncrementPrimary, isUUIDPrimary } from "./ormHelper";
import { query } from "../entity/configuration/query";
export class QueryToUUID1742922178643 implements MigrationInterface {
name = "QueryToUUID1742922178643";
public async up(queryRunner: QueryRunner): Promise<void> {
const table = await queryRunner.getTable("newsletter");
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("recipientsByQueryId") !== -1);
await queryRunner.dropForeignKey("newsletter", foreignKey);
// const entries = await queryRunner.manager.getRepository("query").find({ select: { title: true, query: true } });
// await queryRunner.clearTable("query");
await queryRunner.dropColumn("newsletter", "recipientsByQueryId");
await queryRunner.dropColumn("query", "id");
await queryRunner.addColumn(
"query",
new TableColumn({
name: "id",
...getTypeByORM("uuid"),
...isUUIDPrimary,
})
);
await queryRunner.addColumn(
"newsletter",
new TableColumn({
name: "recipientsByQueryId",
...getTypeByORM("uuid", true),
})
);
// await queryRunner.manager.getRepository("query").save(entries);
await queryRunner.createForeignKey(
"newsletter",
new TableForeignKey({
columnNames: ["recipientsByQueryId"],
referencedColumnNames: ["id"],
referencedTableName: "query",
onDelete: "CASCADE",
onUpdate: "RESTRICT",
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
const table = await queryRunner.getTable("newsletter");
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("recipientsByQueryId") !== -1);
await queryRunner.dropForeignKey("newsletter", foreignKey);
const entries = await queryRunner.manager.getRepository(query).find({ select: { title: true, query: true } });
await queryRunner.clearTable("query");
await queryRunner.dropColumn("newsletter", "recipientsByQueryId");
await queryRunner.dropColumn("query", "id");
await queryRunner.addColumn(
"query",
new TableColumn({
name: "id",
...getTypeByORM("int"),
...isIncrementPrimary,
})
);
await queryRunner.addColumn(
"newsletter",
new TableColumn({
name: "recipientsByQueryId",
...getTypeByORM("int", true),
})
);
await queryRunner.manager
.createQueryBuilder()
.insert()
.into("query")
.values(entries.map((e, i) => ({ ...e, id: i + 1 })))
.execute();
await queryRunner.createForeignKey(
"newsletter",
new TableForeignKey({
columnNames: ["recipientsByQueryId"],
referencedColumnNames: ["id"],
referencedTableName: "query",
onDelete: "CASCADE",
onUpdate: "RESTRICT",
})
);
}
}

View file

@ -1,43 +0,0 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
import { getDefaultByORM, getTypeByORM } from "./ormHelper";
import { newsletter } from "../entity/club/newsletter/newsletter";
export class NewsletterColumnType1744351418751 implements MigrationInterface {
name = "NewsletterColumnType1744351418751";
public async up(queryRunner: QueryRunner): Promise<void> {
let newsletters = await queryRunner.manager.getRepository("newsletter").find();
await queryRunner.dropColumn("newsletter", "newsletterTitle");
await queryRunner.dropColumn("newsletter", "newsletterSignatur");
await queryRunner.addColumn(
"newsletter",
new TableColumn({ name: "newsletterTitle", ...getTypeByORM("text"), default: getDefaultByORM("string") })
);
await queryRunner.addColumn(
"newsletter",
new TableColumn({ name: "newsletterSignatur", ...getTypeByORM("text"), default: getDefaultByORM("string") })
);
await queryRunner.manager.getRepository("newsletter").save(newsletters);
}
public async down(queryRunner: QueryRunner): Promise<void> {
let newsletters = await queryRunner.manager.getRepository("newsletter").find();
await queryRunner.dropColumn("newsletter", "newsletterTitle");
await queryRunner.dropColumn("newsletter", "newsletterSignatur");
await queryRunner.addColumn(
"newsletter",
new TableColumn({ name: "newsletterTitle", ...getTypeByORM("varchar"), default: getDefaultByORM("string") })
);
await queryRunner.addColumn(
"newsletter",
new TableColumn({ name: "newsletterSignatur", ...getTypeByORM("varchar"), default: getDefaultByORM("string") })
);
await queryRunner.manager.getRepository("newsletter").save(newsletters);
}
}

View file

@ -1,22 +0,0 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
import { getTypeByORM, getDefaultByORM } from "./ormHelper";
export class QueryUpdatedAt1744795756230 implements MigrationInterface {
name = "QueryUpdatedAt1744795756230";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
"query",
new TableColumn({
name: "updatedAt",
...getTypeByORM("datetime", false, 6),
default: getDefaultByORM("currentTimestamp", 6),
onUpdate: getDefaultByORM<string>("currentTimestamp", 6),
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn("query", "updatedAt");
}
}

View file

@ -1,16 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import { setting_table } from "./baseSchemaTables/admin";
import SettingHelper from "../helpers/settingsHelper";
import ms from "ms";
export class SettingsFromEnv1745059495807 implements MigrationInterface {
name = "SettingsFromEnv1745059495807";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(setting_table, true, true, true);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable(setting_table.name, true, true, true);
}
}

View file

@ -1,29 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import { setting_table } from "./baseSchemaTables/admin";
import SettingHelper from "../helpers/settingsHelper";
import ms from "ms";
export class SettingsFromEnv_SET1745059495808 implements MigrationInterface {
name = "SettingsFromEnv_SET1745059495808";
public async up(queryRunner: QueryRunner): Promise<void> {
// transfer settings of env to database
await SettingHelper.setSetting("club.name", process.env.CLUB_NAME);
await SettingHelper.setSetting("club.website", process.env.CLUB_WEBSITE);
await SettingHelper.setSetting("session.jwt_expiration", process.env.JWT_EXPIRATION as ms.StringValue);
await SettingHelper.setSetting("session.refresh_expiration", process.env.REFRESH_EXPIRATION as ms.StringValue);
await SettingHelper.setSetting(
"session.pwa_refresh_expiration",
process.env.PWA_REFRESH_EXPIRATION as ms.StringValue
);
await SettingHelper.setSetting("mail.username", process.env.MAIL_USERNAME);
await SettingHelper.setSetting("mail.password", process.env.MAIL_PASSWORD);
await SettingHelper.setSetting("mail.host", process.env.MAIL_HOST);
await SettingHelper.setSetting("mail.port", Number(process.env.MAIL_PORT ?? "578"));
await SettingHelper.setSetting("mail.secure", process.env.MAIL_SECURE == "true");
await SettingHelper.setSetting("backup.interval", Number(process.env.BACKUP_INTERVAL ?? "1"));
await SettingHelper.setSetting("backup.copies", Number(process.env.BACKUP_COPIES ?? "7"));
}
public async down(queryRunner: QueryRunner): Promise<void> {}
}

View file

@ -1,21 +0,0 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
import { getTypeByORM, getDefaultByORM } from "./ormHelper";
export class MemberCreatedAt1746006549262 implements MigrationInterface {
name = "MemberCreatedAt1746006549262";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
"member",
new TableColumn({
name: "createdAt",
...getTypeByORM("datetime", false, 6),
default: getDefaultByORM("currentTimestamp", 6),
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn("member", "createdAt");
}
}

View file

@ -1,39 +0,0 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
import { getDefaultByORM, getTypeByORM } from "./ormHelper";
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
import { CodingHelper } from "../helpers/codingHelper";
import { APPLICATION_SECRET } from "../env.defaults";
export class UserLoginRoutine1746252454922 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
let users = await queryRunner.manager.getRepository("user").find({ select: ["id", "secret"] });
await queryRunner.dropColumns("user", ["secret", "static"]);
await queryRunner.addColumns("user", [
new TableColumn({ name: "secret", ...getTypeByORM("text"), default: getDefaultByORM("string") }),
new TableColumn({
name: "routine",
...getTypeByORM("varchar"),
default: getDefaultByORM("string", LoginRoutineEnum.totp),
}),
]);
await queryRunner.manager.getRepository("user").save(users.map((u) => ({ id: u.id, secret: u.secret })));
}
public async down(queryRunner: QueryRunner): Promise<void> {
let users = await queryRunner.manager.getRepository("user").find({ select: ["id", "secret"] });
await queryRunner.dropColumn("user", "secret");
await queryRunner.addColumns("user", [
new TableColumn({ name: "secret", ...getTypeByORM("varchar"), default: getDefaultByORM("string") }),
new TableColumn({ name: "static", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) }),
]);
await queryRunner.manager.getRepository("user").save(users.map((u) => ({ id: u.id, secret: u.secret })));
await queryRunner.dropColumn("user", "routine");
}
}

View file

@ -1,21 +1,19 @@
import { MigrationInterface, QueryRunner, Table } from "typeorm";
import BackupHelper from "../helpers/backupHelper";
import { getDefaultByORM, getTypeByORM, isIncrementPrimary } from "./ormHelper";
import InternalException from "../exceptions/internalException";
import { DB_TYPE } from "../env.defaults";
import BackupHelper from "../helpers/backupHelper";
import { getTypeByORM, isIncrementPrimary, getDefaultByORM } from "./ormHelper";
export class BackupAndResetDatabase1738166124200 implements MigrationInterface {
name = "BackupAndResetDatabase1738166124200";
export class BackupAndResetDatabase1749296262915 implements MigrationInterface {
name = "BackupAndResetDatabase1749296262915";
public async up(queryRunner: QueryRunner): Promise<void> {
let query = DB_TYPE == "postgres" ? "SELECT name FROM migrations" : "SELECT `name` FROM `migrations`";
let migrations = await queryRunner.query(query);
let migrations = await queryRunner.query("SELECT name FROM migrations");
if (
(await queryRunner.hasTable("user")) &&
migrations.findIndex((m: any) => m.name == "MoveSendNewsletterFlag1737816852011") == -1
migrations.findIndex((m: any) => m.name == "MemberExtendData1748953828644") == -1
) {
throw new InternalException(
"Cannot update due to skiped version. Update to v1.2.2 Version first to prevent data loss and get access to the newer Versions."
"Cannot update due to skiped version. Update to v1.6.0 Version first to prevent data loss and get access to the newer Versions."
);
}

View file

@ -0,0 +1,176 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import {
reset_table,
invite_table,
role_table,
role_permission_table,
user_table,
user_roles_table,
user_permission_table,
refresh_table,
webapi_table,
webapi_permission_table,
setting_table,
} from "./baseSchemaTables/admin";
import { calendar_type_table, calendar_table } from "./baseSchemaTables/calendar";
import {
salutation_table,
award_table,
communication_type_table,
membership_status_table,
executive_position_table,
qualification_table,
member_table,
member_awards_table,
member_communication_table,
membership_table,
member_executive_positions_table,
member_qualifications_table,
member_view,
membership_view,
member_qualifications_view,
member_executive_positions_view,
education_table,
member_educations_table,
membership_total_view,
} from "./baseSchemaTables/member";
import {
newsletter_config_table,
newsletter_table,
newsletter_dates_table,
newsletter_recipients_table,
} from "./baseSchemaTables/newsletter";
import {
protocol_table,
protocol_agenda_table,
protocol_decision_table,
protocol_presence_table,
protocol_voting_table,
protocol_printout_table,
} from "./baseSchemaTables/protocol";
import { query_table, template_table, template_usage_table } from "./baseSchemaTables/query_template";
import { availableTemplates } from "../type/templateTypes";
export class CreateSchema1749296280721 implements MigrationInterface {
name = "CreateSchema1749296280721";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(reset_table, true, true, true);
await queryRunner.createTable(invite_table, true, true, true);
await queryRunner.createTable(role_table, true, true, true);
await queryRunner.createTable(role_permission_table, true, true, true);
await queryRunner.createTable(user_table, true, true, true);
await queryRunner.createTable(user_roles_table, true, true, true);
await queryRunner.createTable(user_permission_table, true, true, true);
await queryRunner.createTable(refresh_table, true, true, true);
await queryRunner.createTable(webapi_table, true, true, true);
await queryRunner.createTable(webapi_permission_table, true, true, true);
await queryRunner.createTable(setting_table, true, true, true);
await queryRunner.createTable(salutation_table, true, true, true);
await queryRunner.createTable(award_table, true, true, true);
await queryRunner.createTable(communication_type_table, true, true, true);
await queryRunner.createTable(membership_status_table, true, true, true);
await queryRunner.createTable(executive_position_table, true, true, true);
await queryRunner.createTable(qualification_table, true, true, true);
await queryRunner.createTable(education_table, true, true, true);
await queryRunner.createTable(member_table, true, true, true);
await queryRunner.createTable(member_awards_table, true, true, true);
await queryRunner.createTable(member_communication_table, true, true, true);
await queryRunner.createTable(membership_table, true, true, true);
await queryRunner.createTable(member_executive_positions_table, true, true, true);
await queryRunner.createTable(member_qualifications_table, true, true, true);
await queryRunner.createTable(member_educations_table, true, true, true);
await queryRunner.createView(member_view, true);
await queryRunner.createView(membership_view, true);
await queryRunner.createView(membership_total_view, true);
await queryRunner.createView(member_qualifications_view, true);
await queryRunner.createView(member_executive_positions_view, true);
await queryRunner.createTable(query_table, true, true, true);
await queryRunner.createTable(template_table, true, true, true);
await queryRunner.createTable(template_usage_table, true, true, true);
await queryRunner.manager
.createQueryBuilder()
.insert()
.into(template_usage_table.name)
.values(
availableTemplates.map((at) => ({
scope: at,
}))
)
.orIgnore()
.execute();
await queryRunner.createTable(protocol_table, true, true, true);
await queryRunner.createTable(protocol_agenda_table, true, true, true);
await queryRunner.createTable(protocol_decision_table, true, true, true);
await queryRunner.createTable(protocol_presence_table, true, true, true);
await queryRunner.createTable(protocol_voting_table, true, true, true);
await queryRunner.createTable(protocol_printout_table, true, true, true);
await queryRunner.createTable(calendar_type_table, true, true, true);
await queryRunner.createTable(calendar_table, true, true, true);
await queryRunner.createTable(newsletter_config_table, true, true, true);
await queryRunner.createTable(newsletter_table, true, true, true);
await queryRunner.createTable(newsletter_dates_table, true, true, true);
await queryRunner.createTable(newsletter_recipients_table, true, true, true);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable(newsletter_dates_table, true, true, true);
await queryRunner.dropTable(newsletter_recipients_table, true, true, true);
await queryRunner.dropTable(newsletter_table, true, true, true);
await queryRunner.dropTable(newsletter_config_table, true, true, true);
await queryRunner.dropTable(calendar_table, true, true, true);
await queryRunner.dropTable(calendar_type_table, true, true, true);
await queryRunner.dropTable(protocol_agenda_table, true, true, true);
await queryRunner.dropTable(protocol_decision_table, true, true, true);
await queryRunner.dropTable(protocol_presence_table, true, true, true);
await queryRunner.dropTable(protocol_voting_table, true, true, true);
await queryRunner.dropTable(protocol_printout_table, true, true, true);
await queryRunner.dropTable(protocol_table, true, true, true);
await queryRunner.dropTable(template_usage_table, true, true, true);
await queryRunner.dropTable(template_table, true, true, true);
await queryRunner.dropTable(query_table, true, true, true);
await queryRunner.dropView(member_view);
await queryRunner.dropView(membership_view);
await queryRunner.dropView(membership_total_view);
await queryRunner.dropView(member_qualifications_view);
await queryRunner.dropView(member_executive_positions_view);
await queryRunner.dropTable(member_awards_table, true, true, true);
await queryRunner.dropTable(member_communication_table, true, true, true);
await queryRunner.dropTable(membership_table, true, true, true);
await queryRunner.dropTable(member_executive_positions_table, true, true, true);
await queryRunner.dropTable(member_qualifications_table, true, true, true);
await queryRunner.dropTable(member_educations_table, true, true, true);
await queryRunner.dropTable(member_table, true, true, true);
await queryRunner.dropTable(salutation_table, true, true, true);
await queryRunner.dropTable(award_table, true, true, true);
await queryRunner.dropTable(communication_type_table, true, true, true);
await queryRunner.dropTable(membership_status_table, true, true, true);
await queryRunner.dropTable(executive_position_table, true, true, true);
await queryRunner.dropTable(qualification_table, true, true, true);
await queryRunner.dropTable(education_table, true, true, true);
await queryRunner.dropTable(setting_table, true, true, true);
await queryRunner.dropTable(webapi_permission_table, true, true, true);
await queryRunner.dropTable(webapi_table, true, true, true);
await queryRunner.dropTable(refresh_table, true, true, true);
await queryRunner.dropTable(user_permission_table, true, true, true);
await queryRunner.dropTable(user_roles_table, true, true, true);
await queryRunner.dropTable(user_table, true, true, true);
await queryRunner.dropTable(role_permission_table, true, true, true);
await queryRunner.dropTable(role_table, true, true, true);
await queryRunner.dropTable(invite_table, true, true, true);
await queryRunner.dropTable(reset_table, true, true, true);
}
}

View file

@ -1,5 +1,6 @@
import { Table, TableForeignKey } from "typeorm";
import { Table, TableForeignKey, TableIndex, TableUnique } from "typeorm";
import { getDefaultByORM, getTypeByORM, isIncrementPrimary, isUUIDPrimary } from "../ormHelper";
import { LoginRoutineEnum } from "../../enums/loginRoutineEnum";
export const invite_table = new Table({
name: "invite",
@ -16,7 +17,12 @@ export const role_table = new Table({
name: "role",
columns: [
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
{ name: "role", ...getTypeByORM("varchar"), isUnique: true },
{ name: "role", ...getTypeByORM("varchar") },
],
uniques: [
new TableUnique({
columnNames: ["role"],
}),
],
});
@ -45,8 +51,8 @@ export const user_table = new Table({
{ name: "username", ...getTypeByORM("varchar"), isUnique: true },
{ name: "firstname", ...getTypeByORM("varchar") },
{ name: "lastname", ...getTypeByORM("varchar") },
{ name: "secret", ...getTypeByORM("varchar") },
{ name: "static", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
{ name: "secret", ...getTypeByORM("text") },
{ name: "routine", ...getTypeByORM("varchar"), default: getDefaultByORM("string", LoginRoutineEnum.totp) },
{ name: "isOwner", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
],
});
@ -73,6 +79,14 @@ export const user_roles_table = new Table({
onUpdate: "RESTRICT",
}),
],
indices: [
new TableIndex({
columnNames: ["userId"],
}),
new TableIndex({
columnNames: ["roleId"],
}),
],
});
export const user_permission_table = new Table({
@ -114,11 +128,19 @@ export const webapi_table = new Table({
name: "webapi",
columns: [
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
{ name: "token", ...getTypeByORM("varchar"), isUnique: true },
{ name: "title", ...getTypeByORM("varchar"), isUnique: true },
{ name: "token", ...getTypeByORM("text") },
{ name: "title", ...getTypeByORM("varchar") },
{ name: "createdAt", ...getTypeByORM("datetime", false, 6), default: getDefaultByORM("currentTimestamp", 6) },
{ name: "lastUsage", ...getTypeByORM("datetime", true, 6), default: getDefaultByORM("null") },
{ name: "expiry", ...getTypeByORM("date", true), default: getDefaultByORM("null") },
{ name: "lastUsage", ...getTypeByORM("datetime", true, 6) },
{ name: "expiry", ...getTypeByORM("date", true) },
],
uniques: [
new TableUnique({
columnNames: ["token"],
}),
new TableUnique({
columnNames: ["title"],
}),
],
});

View file

@ -1,4 +1,4 @@
import { Table, TableForeignKey } from "typeorm";
import { Table, TableForeignKey, TableUnique } from "typeorm";
import { getDefaultByORM, getTypeByORM, isIncrementPrimary, isUUIDPrimary } from "../ormHelper";
export const calendar_type_table = new Table({
@ -32,7 +32,7 @@ export const calendar_table = new Table({
default: getDefaultByORM("currentTimestamp", 6),
onUpdate: getDefaultByORM<string>("currentTimestamp", 6),
},
{ name: "webpageId", ...getTypeByORM("varchar", true), default: getDefaultByORM("null"), isUnique: true },
{ name: "webpageId", ...getTypeByORM("varchar", true) },
{ name: "typeId", ...getTypeByORM("int") },
],
foreignKeys: [
@ -44,4 +44,9 @@ export const calendar_table = new Table({
onUpdate: "RESTRICT",
}),
],
uniques: [
new TableUnique({
columnNames: ["webpageId"],
}),
],
});

View file

@ -1,4 +1,4 @@
import { Table, TableForeignKey, View } from "typeorm";
import { Table, TableForeignKey, TableUnique, View } from "typeorm";
import { getDefaultByORM, getTypeByORM, isIncrementPrimary, isUUIDPrimary } from "../ormHelper";
export const salutation_table = new Table({
@ -51,17 +51,28 @@ export const qualification_table = new Table({
],
});
export const education_table = new Table({
name: "education",
columns: [
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
{ name: "education", ...getTypeByORM("varchar"), isUnique: true },
{ name: "description", ...getTypeByORM("varchar"), isNullable: true },
],
});
/** member and relations */
export const member_table = new Table({
name: "member",
columns: [
{ name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary },
{ name: "salutationId", ...getTypeByORM("int") },
{ name: "internalId", ...getTypeByORM("varchar", true), default: getDefaultByORM("null"), isUnique: true },
{ name: "internalId", ...getTypeByORM("varchar", true) },
{ name: "firstname", ...getTypeByORM("varchar") },
{ name: "lastname", ...getTypeByORM("varchar") },
{ name: "nameaffix", ...getTypeByORM("varchar") },
{ name: "birthdate", ...getTypeByORM("date") },
{ name: "createdAt", ...getTypeByORM("datetime", false, 6), default: getDefaultByORM("currentTimestamp", 6) },
{ name: "note", ...getTypeByORM("varchar", true) },
],
foreignKeys: [
new TableForeignKey({
@ -72,6 +83,11 @@ export const member_table = new Table({
onUpdate: "RESTRICT",
}),
],
uniques: [
new TableUnique({
columnNames: ["internalId"],
}),
],
});
export const membership_table = new Table({
@ -163,7 +179,7 @@ export const member_awards_table = new Table({
name: "member_awards",
columns: [
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
{ name: "given", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
{ name: "given", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", true) },
{ name: "note", ...getTypeByORM("varchar"), isNullable: true },
{ name: "date", ...getTypeByORM("date") },
{ name: "memberId", ...getTypeByORM("uuid") },
@ -194,13 +210,13 @@ export const member_communication_table = new Table({
{ name: "preferred", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
{ name: "isSendNewsletter", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
{ name: "isSMSAlarming", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
{ name: "mobile", ...getTypeByORM("varchar"), isNullable: true },
{ name: "email", ...getTypeByORM("varchar"), isNullable: true },
{ name: "postalCode", ...getTypeByORM("varchar"), default: getDefaultByORM("null"), isNullable: true },
{ name: "city", ...getTypeByORM("varchar"), isNullable: true },
{ name: "street", ...getTypeByORM("varchar"), isNullable: true },
{ name: "mobile", ...getTypeByORM("varchar", true) },
{ name: "email", ...getTypeByORM("varchar", true) },
{ name: "postalCode", ...getTypeByORM("varchar", true) },
{ name: "city", ...getTypeByORM("varchar", true) },
{ name: "street", ...getTypeByORM("varchar", true) },
{ name: "streetNumber", ...getTypeByORM("int", true) },
{ name: "streetNumberAddition", ...getTypeByORM("varchar"), isNullable: true },
{ name: "streetNumberAddition", ...getTypeByORM("varchar", true) },
{ name: "memberId", ...getTypeByORM("uuid") },
{ name: "typeId", ...getTypeByORM("int") },
],
@ -222,108 +238,60 @@ export const member_communication_table = new Table({
],
});
/** views */
export const member_view_mysql = new View({
name: "member_view",
expression: `
SELECT
\`member\`.\`id\` AS \`id\`,
\`member\`.\`internalId\` AS \`internalId\`,
\`member\`.\`firstname\` AS \`firstname\`,
\`member\`.\`lastname\` AS \`lastname\`,
\`member\`.\`nameaffix\` AS \`nameaffix\`,
\`member\`.\`birthdate\` AS \`birthdate\`,
\`salutation\`.\`salutation\` AS \`salutation\`,
TIMESTAMPDIFF(YEAR, \`member\`.\`birthdate\`, CURDATE()) AS \`todayAge\`,
YEAR(CURDATE()) - YEAR(\`member\`.\`birthdate\`) AS \`ageThisYear\`,
CONCAT(
TIMESTAMPDIFF(YEAR, \`member\`.\`birthdate\`, CURDATE()), ' years ',
TIMESTAMPDIFF(MONTH, \`member\`.\`birthdate\`, CURDATE()) % 12, ' months ',
TIMESTAMPDIFF(DAY,
DATE_ADD(
\`member\`.\`birthdate\`,
INTERVAL TIMESTAMPDIFF(MONTH, \`member\`.\`birthdate\`, CURDATE()) MONTH
),
CURDATE()
), ' days'
) AS \`exactAge\`
FROM \`member\` \`member\`
LEFT JOIN \`salutation\` \`salutation\` ON \`salutation\`.\`id\`=\`member\`.\`salutationId\`
`,
export const member_educations_table = new Table({
name: "member_educations",
columns: [
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
{ name: "start", ...getTypeByORM("date") },
{ name: "end", ...getTypeByORM("date", true), default: getDefaultByORM("null") },
{ name: "note", ...getTypeByORM("varchar"), isNullable: true },
{ name: "place", ...getTypeByORM("varchar"), isNullable: true },
{ name: "memberId", ...getTypeByORM("uuid") },
{ name: "educationId", ...getTypeByORM("int") },
],
foreignKeys: [
new TableForeignKey({
columnNames: ["memberId"],
referencedTableName: "member",
referencedColumnNames: ["id"],
onDelete: "CASCADE",
onUpdate: "RESTRICT",
}),
new TableForeignKey({
columnNames: ["educationId"],
referencedTableName: "education",
referencedColumnNames: ["id"],
onDelete: "RESTRICT",
onUpdate: "RESTRICT",
}),
],
});
export const member_view_postgres = new View({
/** views */
export const member_view = new View({
name: "member_view",
expression: `
SELECT
"member"."id" AS "id",
"member"."internalId" AS "internalId",
"member"."firstname" AS "firstname",
"member"."lastname" AS "lastname",
"member"."nameaffix" AS "nameaffix",
"member"."birthdate" AS "birthdate",
"member"."internalId" AS "internalId",
"member"."note" AS "note",
"salutation"."salutation" AS "salutation",
DATE_PART('year', AGE(CURRENT_DATE, member.birthdate)) AS "todayAge",
EXTRACT(YEAR FROM CURRENT_DATE) - EXTRACT(YEAR FROM member.birthdate) AS "ageThisYear",
AGE(CURRENT_DATE, member.birthdate) AS "exactAge"
DATE_PART('year', AGE(CURRENT_DATE, "member"."birthdate")) AS "todayAge",
EXTRACT(YEAR FROM CURRENT_DATE) - EXTRACT(YEAR FROM "member"."birthdate") AS "ageThisYear",
AGE(CURRENT_DATE, "member"."birthdate") AS "exactAge"
FROM "member" "member"
LEFT JOIN "salutation" "salutation" ON "salutation"."id"="member"."salutationId"
`,
`
.replace(/\s+/g, " ")
.trim(),
});
export const member_view_sqlite = new View({
name: "member_view",
expression: `
SELECT
member.id AS id,
member.internalId AS internalId,
member.firstname AS firstname,
member.lastname AS lastname,
member.nameaffix AS nameaffix,
member.birthdate AS birthdate,
salutation.salutation AS salutation,
(strftime('%Y', 'now') - strftime('%Y', member.birthdate) - (strftime('%m-%d', 'now') < strftime('%m-%d', member.birthdate))) AS todayAge,
FLOOR(strftime('%Y', 'now') - strftime('%Y', member.birthdate)) AS ageThisYear,
(strftime('%Y', 'now') - strftime('%Y', member.birthdate)) || ' years ' ||
(strftime('%m', 'now') - strftime('%m', member.birthdate)) || ' months ' ||
(strftime('%d', 'now') - strftime('%d', member.birthdate)) || ' days'
AS exactAge
FROM member member
LEFT JOIN salutation salutation ON salutation.id=member.salutationId
`,
});
export const member_executive_positions_view_mysql = new View({
name: "member_executive_positions_view",
expression: `
SELECT
\`executivePosition\`.\`id\` AS \`positionId\`,
\`executivePosition\`.\`position\` AS \`position\`,
\`member\`.\`id\` AS \`memberId\`,
\`member\`.\`firstname\` AS \`memberFirstname\`,
\`member\`.\`lastname\` AS \`memberLastname\`,
\`member\`.\`nameaffix\` AS \`memberNameaffix\`,
\`member\`.\`birthdate\` AS \`memberBirthdate\`,
\`salutation\`.\`salutation\` AS \`memberSalutation\`,
SUM(DATEDIFF(COALESCE(\`memberExecutivePositions\`.\`end\`, CURDATE()), \`memberExecutivePositions\`.\`start\`)) AS \`durationInDays\`,
SUM(TIMESTAMPDIFF(YEAR, \`memberExecutivePositions\`.\`start\`, COALESCE(\`memberExecutivePositions\`.\`end\`, CURDATE()))) AS \`durationInYears\`,
CONCAT(
SUM(FLOOR(TIMESTAMPDIFF(DAY, \`memberExecutivePositions\`.\`start\`, COALESCE(\`memberExecutivePositions\`.\`end\`, CURDATE())) / 365.25)),
' years ',
SUM(FLOOR(MOD(TIMESTAMPDIFF(MONTH, \`memberExecutivePositions\`.\`start\`, COALESCE(\`memberExecutivePositions\`.\`end\`, CURDATE())), 12))),
' months ',
SUM(FLOOR(MOD(TIMESTAMPDIFF(DAY, \`memberExecutivePositions\`.\`start\`, COALESCE(\`memberExecutivePositions\`.\`end\`, CURDATE())), 30))),
' days'
) AS \`exactDuration\`
FROM \`member_executive_positions\` \`memberExecutivePositions\`
LEFT JOIN \`executive_position\` \`executivePosition\` ON \`executivePosition\`.\`id\`=\`memberExecutivePositions\`.\`executivePositionId\`
LEFT JOIN \`member\` \`member\` ON \`member\`.\`id\`=\`memberExecutivePositions\`.\`memberId\`
LEFT JOIN \`salutation\` \`salutation\` ON \`salutation\`.\`id\`=\`member\`.\`salutationId\`
GROUP BY \`executivePosition\`.\`id\`, \`member\`.\`id\`, \`salutation\`.\`id\`
`,
});
export const member_executive_positions_view_postgres = new View({
export const member_executive_positions_view = new View({
name: "member_executive_positions_view",
expression: `
SELECT
@ -343,66 +311,12 @@ export const member_executive_positions_view_postgres = new View({
LEFT JOIN "member" "member" ON "member"."id"="memberExecutivePositions"."memberId"
LEFT JOIN "salutation" "salutation" ON "salutation"."id"="member"."salutationId"
GROUP BY "executivePosition"."id", "member"."id", "salutation"."id"
`,
`
.replace(/\s+/g, " ")
.trim(),
});
export const member_executive_positions_view_sqlite = new View({
name: "member_executive_positions_view",
expression: `
SELECT
executivePosition.id AS positionId,
executivePosition.position AS position,
member.id AS memberId,
member.firstname AS memberFirstname,
member.lastname AS memberLastname,
member.nameaffix AS memberNameaffix,
member.birthdate AS memberBirthdate,
salutation.salutation AS memberSalutation,
SUM(JULIANDAY(COALESCE(memberExecutivePositions.end, DATE('now'))) - JULIANDAY(memberExecutivePositions.start)) AS durationInDays,
SUM(FLOOR((JULIANDAY(COALESCE(memberExecutivePositions.end, DATE('now'))) - JULIANDAY(memberExecutivePositions.start)) / 365.25)) AS durationInYears,
SUM((strftime('%Y', COALESCE(memberExecutivePositions.end, DATE('now'))) - strftime('%Y', memberExecutivePositions.start))) || ' years ' ||
SUM((strftime('%m', COALESCE(memberExecutivePositions.end, DATE('now'))) - strftime('%m', memberExecutivePositions.start))) || ' months ' ||
SUM((strftime('%d', COALESCE(memberExecutivePositions.end, DATE('now'))) - strftime('%d', memberExecutivePositions.start))) || ' days'
AS exactDuration
FROM member_executive_positions memberExecutivePositions
LEFT JOIN executive_position executivePosition ON executivePosition.id=memberExecutivePositions.executivePositionId
LEFT JOIN member member ON member.id=memberExecutivePositions.memberId
LEFT JOIN salutation salutation ON salutation.id=member.salutationId
GROUP BY executivePosition.id, member.id, salutation.id
`,
});
export const member_qualifications_view_mysql = new View({
name: "member_qualifications_view",
expression: `
SELECT
\`qualification\`.\`id\` AS \`qualificationId\`,
\`qualification\`.\`qualification\` AS \`qualification\`,
\`member\`.\`id\` AS \`memberId\`,
\`member\`.\`firstname\` AS \`memberFirstname\`,
\`member\`.\`lastname\` AS \`memberLastname\`,
\`member\`.\`nameaffix\` AS \`memberNameaffix\`,
\`member\`.\`birthdate\` AS \`memberBirthdate\`,
\`salutation\`.\`salutation\` AS \`memberSalutation\`,
SUM(DATEDIFF(COALESCE(\`memberQualifications\`.\`end\`, CURDATE()), \`memberQualifications\`.\`start\`)) AS \`durationInDays\`,
SUM(TIMESTAMPDIFF(YEAR, \`memberQualifications\`.\`start\`, COALESCE(\`memberQualifications\`.\`end\`, CURDATE()))) AS \`durationInYears\`,
CONCAT(
SUM(FLOOR(TIMESTAMPDIFF(DAY, \`memberQualifications\`.\`start\`, COALESCE(\`memberQualifications\`.\`end\`, CURDATE())) / 365.25)),
' years ',
SUM(FLOOR(MOD(TIMESTAMPDIFF(MONTH, \`memberQualifications\`.\`start\`, COALESCE(\`memberQualifications\`.\`end\`, CURDATE())), 12))),
' months ',
SUM(FLOOR(MOD(TIMESTAMPDIFF(DAY, \`memberQualifications\`.\`start\`, COALESCE(\`memberQualifications\`.\`end\`, CURDATE())), 30))),
' days'
) AS \`exactDuration\`
FROM \`member_qualifications\` \`memberQualifications\`
LEFT JOIN \`qualification\` \`qualification\` ON \`qualification\`.\`id\`=\`memberQualifications\`.\`qualificationId\`
LEFT JOIN \`member\` \`member\` ON \`member\`.\`id\`=\`memberQualifications\`.\`memberId\`
LEFT JOIN \`salutation\` \`salutation\` ON \`salutation\`.\`id\`=\`member\`.\`salutationId\`
GROUP BY \`qualification\`.\`id\`, \`member\`.\`id\`, \`salutation\`.\`id\`
`,
});
export const member_qualifications_view_postgres = new View({
export const member_qualifications_view = new View({
name: "member_qualifications_view",
expression: `
SELECT
@ -422,66 +336,12 @@ export const member_qualifications_view_postgres = new View({
LEFT JOIN "member" "member" ON "member"."id"="memberQualifications"."memberId"
LEFT JOIN "salutation" "salutation" ON "salutation"."id"="member"."salutationId"
GROUP BY "qualification"."id", "member"."id", "salutation"."id"
`,
`
.replace(/\s+/g, " ")
.trim(),
});
export const member_qualifications_view_sqlite = new View({
name: "member_qualifications_view",
expression: `
SELECT
qualification.id AS qualificationId,
qualification.qualification AS qualification,
member.id AS memberId,
member.firstname AS memberFirstname,
member.lastname AS memberLastname,
member.nameaffix AS memberNameaffix,
member.birthdate AS memberBirthdate,
salutation.salutation AS memberSalutation,
SUM(JULIANDAY(COALESCE(memberQualifications.end, DATE('now'))) - JULIANDAY(memberQualifications.start)) AS durationInDays,
SUM(FLOOR((JULIANDAY(COALESCE(memberQualifications.end, DATE('now'))) - JULIANDAY(memberQualifications.start)) / 365.25)) AS durationInYears,
SUM((strftime('%Y', COALESCE(memberQualifications.end, DATE('now'))) - strftime('%Y', memberQualifications.start))) || ' years ' ||
SUM((strftime('%m', COALESCE(memberQualifications.end, DATE('now'))) - strftime('%m', memberQualifications.start))) || ' months ' ||
SUM((strftime('%d', COALESCE(memberQualifications.end, DATE('now'))) - strftime('%d', memberQualifications.start))) || ' days'
AS exactDuration
FROM member_qualifications memberQualifications
LEFT JOIN qualification qualification ON qualification.id=memberQualifications.qualificationId
LEFT JOIN member member ON member.id=memberQualifications.memberId
LEFT JOIN salutation salutation ON salutation.id=member.salutationId
GROUP BY qualification.id, member.id, salutation.id
`,
});
export const membership_view_mysql = new View({
name: "membership_view",
expression: `
SELECT
\`status\`.\`id\` AS \`statusId\`,
\`status\`.\`status\` AS \`status\`,
\`member\`.\`id\` AS \`memberId\`,
\`member\`.\`firstname\` AS \`memberFirstname\`,
\`member\`.\`lastname\` AS \`memberLastname\`,
\`member\`.\`nameaffix\` AS \`memberNameaffix\`,
\`member\`.\`birthdate\` AS \`memberBirthdate\`,
\`salutation\`.\`salutation\` AS \`memberSalutation\`,
SUM(DATEDIFF(COALESCE(\`membership\`.\`end\`, CURDATE()), \`membership\`.\`start\`)) AS \`durationInDays\`,
SUM(TIMESTAMPDIFF(YEAR, \`membership\`.\`start\`, COALESCE(\`membership\`.\`end\`, CURDATE()))) AS \`durationInYears\`,
CONCAT(
SUM(FLOOR(TIMESTAMPDIFF(DAY, \`membership\`.\`start\`, COALESCE(\`membership\`.\`end\`, CURDATE())) / 365.25)),
' years ',
SUM(FLOOR(MOD(TIMESTAMPDIFF(MONTH, \`membership\`.\`start\`, COALESCE(\`membership\`.\`end\`, CURDATE())), 12))),
' months ',
SUM(FLOOR(MOD(TIMESTAMPDIFF(DAY, \`membership\`.\`start\`, COALESCE(\`membership\`.\`end\`, CURDATE())), 30))),
' days'
) AS \`exactDuration\`
FROM \`membership\` \`membership\`
LEFT JOIN \`membership_status\` \`status\` ON \`status\`.\`id\`=\`membership\`.\`statusId\`
LEFT JOIN \`member\` \`member\` ON \`member\`.\`id\`=\`membership\`.\`memberId\`
LEFT JOIN \`salutation\` \`salutation\` ON \`salutation\`.\`id\`=\`member\`.\`salutationId\`
GROUP BY \`status\`.\`id\`, \`member\`.\`id\`, \`salutation\`.\`id\`
`,
});
export const membership_view_postgres = new View({
export const membership_view = new View({
name: "membership_view",
expression: `
SELECT
@ -500,32 +360,31 @@ export const membership_view_postgres = new View({
LEFT JOIN "membership_status" "status" ON "status"."id"="membership"."statusId"
LEFT JOIN "member" "member" ON "member"."id"="membership"."memberId"
LEFT JOIN "salutation" "salutation" ON "salutation"."id"="member"."salutationId"
GROUP BY "status"."id","member"."id", "salutation"."id"
`,
GROUP BY "status"."id", "member"."id", "salutation"."id"
`
.replace(/\s+/g, " ")
.trim(),
});
export const membership_view_sqlite = new View({
name: "membership_view",
export const membership_total_view = new View({
name: "membership_total_view",
expression: `
SELECT
status.id AS statusId,
status.status AS status,
member.id AS memberId,
member.firstname AS memberFirstname,
member.lastname AS memberLastname,
member.nameaffix AS memberNameaffix,
member.birthdate AS memberBirthdate,
salutation.salutation AS memberSalutation,
SUM(JULIANDAY(COALESCE(membership.end, DATE('now'))) - JULIANDAY(membership.start)) AS durationInDays,
SUM(FLOOR((JULIANDAY(COALESCE(membership.end, DATE('now'))) - JULIANDAY(membership.start)) / 365.25)) AS durationInYears,
SUM((strftime('%Y', COALESCE(membership.end, DATE('now'))) - strftime('%Y', membership.start))) || ' years ' ||
SUM((strftime('%m', COALESCE(membership.end, DATE('now'))) - strftime('%m', membership.start))) || ' months ' ||
SUM((strftime('%d', COALESCE(membership.end, DATE('now'))) - strftime('%d', membership.start))) || ' days'
AS exactDuration
FROM membership membership
LEFT JOIN membership_status status ON status.id=membership.statusId
LEFT JOIN member member ON member.id=membership.memberId
LEFT JOIN salutation salutation ON salutation.id=member.salutationId
GROUP BY status.id, member.id, salutation.id
`,
SELECT
"member"."id" AS "memberId",
"member"."firstname" AS "memberFirstname",
"member"."lastname" AS "memberLastname",
"member"."nameaffix" AS "memberNameaffix",
"member"."birthdate" AS "memberBirthdate",
"salutation"."salutation" AS "memberSalutation",
SUM(COALESCE("membership"."end", CURRENT_DATE) - "membership"."start") AS "durationInDays",
SUM(EXTRACT(YEAR FROM AGE(COALESCE("membership"."end", CURRENT_DATE), "membership"."start"))) AS "durationInYears",
SUM(AGE(COALESCE("membership"."end", CURRENT_DATE), "membership"."start")) AS "exactDuration"
FROM "membership" "membership"
LEFT JOIN "membership_status" "status" ON "status"."id"="membership"."statusId"
LEFT JOIN "member" "member" ON "member"."id"="membership"."memberId"
LEFT JOIN "salutation" "salutation" ON "salutation"."id"="member"."salutationId"
GROUP BY "member"."id", "salutation"."id"
`
.replace(/\s+/g, " ")
.trim(),
});

View file

@ -1,4 +1,4 @@
import { Table, TableForeignKey } from "typeorm";
import { Table, TableForeignKey, TableUnique } from "typeorm";
import { getDefaultByORM, getTypeByORM, isIncrementPrimary } from "../ormHelper";
export const newsletter_table = new Table({
@ -7,11 +7,12 @@ export const newsletter_table = new Table({
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
{ name: "title", ...getTypeByORM("varchar") },
{ name: "description", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
{ name: "newsletterTitle", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
{ name: "newsletterTitle", ...getTypeByORM("text"), default: getDefaultByORM("string") },
{ name: "newsletterText", ...getTypeByORM("text"), default: getDefaultByORM("string") },
{ name: "newsletterSignatur", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
{ name: "newsletterSignatur", ...getTypeByORM("text"), default: getDefaultByORM("string") },
{ name: "isSent", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
{ name: "recipientsByQueryId", ...getTypeByORM("int", true) },
{ name: "recipientsByQueryId", ...getTypeByORM("uuid", true) },
{ name: "createdAt", ...getTypeByORM("datetime", false, 6), default: getDefaultByORM("currentTimestamp", 6) },
],
foreignKeys: [
new TableForeignKey({
@ -22,6 +23,11 @@ export const newsletter_table = new Table({
onUpdate: "RESTRICT",
}),
],
uniques: [
new TableUnique({
columnNames: ["title"],
}),
],
});
export const newsletter_dates_table = new Table({

View file

@ -1,11 +1,11 @@
import { Table, TableForeignKey } from "typeorm";
import { Table, TableForeignKey, TableUnique } from "typeorm";
import { getDefaultByORM, getTypeByORM, isIncrementPrimary } from "../ormHelper";
export const protocol_table = new Table({
name: "protocol",
columns: [
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
{ name: "title", ...getTypeByORM("varchar") },
{ name: "title", ...getTypeByORM("varchar"), isUnique: true },
{ name: "date", ...getTypeByORM("date") },
{ name: "starttime", ...getTypeByORM("time", true) },
{ name: "endtime", ...getTypeByORM("time", true) },
@ -19,6 +19,7 @@ export const protocol_agenda_table = new Table({
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
{ name: "topic", ...getTypeByORM("varchar") },
{ name: "context", ...getTypeByORM("text"), default: getDefaultByORM("string") },
{ name: "sort", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
{ name: "protocolId", ...getTypeByORM("int") },
],
foreignKeys: [
@ -38,6 +39,7 @@ export const protocol_decision_table = new Table({
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
{ name: "topic", ...getTypeByORM("varchar") },
{ name: "context", ...getTypeByORM("text"), default: getDefaultByORM("string") },
{ name: "sort", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
{ name: "protocolId", ...getTypeByORM("int") },
],
foreignKeys: [
@ -86,6 +88,7 @@ export const protocol_voting_table = new Table({
{ name: "favour", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
{ name: "abstain", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
{ name: "against", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
{ name: "sort", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
{ name: "protocolId", ...getTypeByORM("int") },
],
foreignKeys: [

View file

@ -1,12 +1,18 @@
import { Table, TableForeignKey } from "typeorm";
import { getDefaultByORM, getTypeByORM, isIncrementPrimary } from "../ormHelper";
import { getDefaultByORM, getTypeByORM, isIncrementPrimary, isUUIDPrimary } from "../ormHelper";
export const query_table = new Table({
name: "query",
columns: [
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
{ name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary },
{ name: "title", ...getTypeByORM("varchar"), isUnique: true },
{ name: "query", ...getTypeByORM("text"), default: getDefaultByORM("string") },
{
name: "updatedAt",
...getTypeByORM("datetime", false, 6),
default: getDefaultByORM("currentTimestamp", 6),
onUpdate: getDefaultByORM<string>("currentTimestamp", 6),
},
],
});

View file

@ -1,5 +1,3 @@
import { DB_TYPE } from "../env.defaults";
export type ORMType = "int" | "bigint" | "boolean" | "date" | "datetime" | "time" | "text" | "varchar" | "uuid";
export type ORMDefault = "currentTimestamp" | "string" | "boolean" | "number" | "null";
export type ColumnConfig = {
@ -15,83 +13,38 @@ export type Primary = {
};
export function getTypeByORM(type: ORMType, nullable: boolean = false, length: number = 255): ColumnConfig {
const dbType = DB_TYPE;
const typeMap: Record<string, Record<ORMType, string>> = {
mysql: {
int: "int",
bigint: "bigint",
boolean: "tinyint",
date: "date",
datetime: "datetime",
time: "time",
text: "text",
varchar: "varchar",
uuid: "varchar",
},
postgres: {
int: "integer",
bigint: "bigint",
boolean: "boolean",
date: "date",
datetime: "timestamp",
time: "time",
text: "text",
varchar: "character varying",
uuid: "uuid",
},
sqlite: {
int: "integer",
bigint: "integer",
boolean: "integer",
date: "date",
datetime: "datetime",
time: "text",
text: "text",
varchar: "varchar",
uuid: "varchar",
},
const typeMap: Record<ORMType, string> = {
int: "integer",
bigint: "bigint",
boolean: "boolean",
date: "date",
datetime: "timestamp",
time: "time",
text: "text",
varchar: "character varying",
uuid: "uuid",
};
let obj: ColumnConfig = {
type: typeMap[dbType]?.[type] || type,
type: typeMap[type] || type,
isNullable: nullable,
};
if (type == "datetime") obj.precision = length;
else if (dbType != "sqlite" && (obj.type == "varchar" || type == "varchar")) obj.length = `${length}`;
else if (dbType != "postgres" && type == "uuid") obj.length = "36";
else if (obj.type == "varchar" || type == "varchar") obj.length = `${length}`;
return obj;
}
export function getDefaultByORM<T = string | null>(type: ORMDefault, data?: string | number | boolean): T {
const dbType = DB_TYPE;
const typeMap: Record<string, Record<ORMDefault, string | null>> = {
mysql: {
currentTimestamp: `CURRENT_TIMESTAMP(${data ?? 6})`,
string: `'${data ?? ""}'`,
boolean: Boolean(data).toString(),
number: Number(data).toString(),
null: null,
},
postgres: {
currentTimestamp: `now()`,
string: `'${data ?? ""}'`,
boolean: Boolean(data) == true ? "true" : "false",
number: Number(data).toString(),
null: null,
},
sqlite: {
currentTimestamp: `datetime('now')`,
string: `'${data ?? ""}'`,
boolean: Boolean(data) == true ? "1" : "0",
number: Number(data).toString(),
null: null,
},
const typeMap: Record<ORMDefault, string | null> = {
currentTimestamp: `now()`,
string: `'${data ?? ""}'`,
boolean: Boolean(data) == true ? "true" : "false",
number: Number(data).toString(),
null: null,
};
return (typeMap[dbType]?.[type] || type) as T;
return (typeMap[type] || type) as T;
}
export const isIncrementPrimary: Primary = {

View file

@ -2,12 +2,14 @@ import express, { Request, Response } from "express";
import {
addAwardToMember,
addCommunicationToMember,
addEducationToMember,
addExecutivePositionToMember,
addMembershipToMember,
addQualificationToMember,
createMember,
deleteAwardOfMember,
deleteCommunicationOfMember,
deleteEducationsOfMember,
deleteExecutivePositionOfMember,
deleteMemberById,
deleteMembershipOfMember,
@ -17,6 +19,8 @@ import {
getAwardsByMember,
getCommunicationByMemberAndRecord,
getCommunicationsByMember,
getEducationByMemberAndRecord,
getEducationsByMember,
getExecutivePositionByMemberAndRecord,
getExecutivePositionsByMember,
getMemberById,
@ -26,11 +30,13 @@ import {
getMembershipByMemberAndRecord,
getMembershipsByMember,
getMembershipStatisticsById,
getMembershipTotalStatisticsById,
getMemberStatisticsById,
getQualificationByMemberAndRecord,
getQualificationsByMember,
updateAwardOfMember,
updateCommunicationOfMember,
updateEducationOfMember,
updateExecutivePositionOfMember,
updateMemberById,
updateMembershipOfMember,
@ -72,6 +78,10 @@ router.get("/:memberId/memberships/statistics", async (req: Request, res: Respon
await getMembershipStatisticsById(req, res);
});
router.get("/:memberId/memberships/totalstatistics", async (req: Request, res: Response) => {
await getMembershipTotalStatisticsById(req, res);
});
router.get("/:memberId/membership/:id", async (req: Request, res: Response) => {
await getMembershipByMemberAndRecord(req, res);
});
@ -92,6 +102,14 @@ router.get("/:memberId/qualification/:id", async (req: Request, res: Response) =
await getQualificationByMemberAndRecord(req, res);
});
router.get("/:memberId/educations", async (req: Request, res: Response) => {
await getEducationsByMember(req, res);
});
router.get("/:memberId/education/:id", async (req: Request, res: Response) => {
await getEducationByMemberAndRecord(req, res);
});
router.get("/:memberId/positions", async (req: Request, res: Response) => {
await getExecutivePositionsByMember(req, res);
});
@ -140,6 +158,14 @@ router.post(
}
);
router.post(
"/:memberId/education",
PermissionHelper.passCheckMiddleware("create", "club", "member"),
async (req: Request, res: Response) => {
await addEducationToMember(req, res);
}
);
router.post(
"/:memberId/position",
PermissionHelper.passCheckMiddleware("create", "club", "member"),
@ -188,6 +214,14 @@ router.patch(
}
);
router.patch(
"/:memberId/education/:recordId",
PermissionHelper.passCheckMiddleware("update", "club", "member"),
async (req: Request, res: Response) => {
await updateEducationOfMember(req, res);
}
);
router.patch(
"/:memberId/position/:recordId",
PermissionHelper.passCheckMiddleware("update", "club", "member"),
@ -236,6 +270,14 @@ router.delete(
}
);
router.delete(
"/:memberId/education/:recordId",
PermissionHelper.passCheckMiddleware("delete", "club", "member"),
async (req: Request, res: Response) => {
await deleteEducationsOfMember(req, res);
}
);
router.delete(
"/:memberId/position/:recordId",
PermissionHelper.passCheckMiddleware("delete", "club", "member"),

View file

@ -0,0 +1,45 @@
import express, { Request, Response } from "express";
import {
createEducation,
deleteEducation,
getAllEducations,
getEducationById,
updateEducation,
} from "../../../controller/admin/configuration/educationController";
import PermissionHelper from "../../../helpers/permissionHelper";
var router = express.Router({ mergeParams: true });
router.get("/", async (req: Request, res: Response) => {
await getAllEducations(req, res);
});
router.get("/:id", async (req: Request, res: Response) => {
await getEducationById(req, res);
});
router.post(
"/",
PermissionHelper.passCheckMiddleware("create", "configuration", "education"),
async (req: Request, res: Response) => {
await createEducation(req, res);
}
);
router.patch(
"/:id",
PermissionHelper.passCheckMiddleware("update", "configuration", "education"),
async (req: Request, res: Response) => {
await updateEducation(req, res);
}
);
router.delete(
"/:id",
PermissionHelper.passCheckMiddleware("delete", "configuration", "education"),
async (req: Request, res: Response) => {
await deleteEducation(req, res);
}
);
export default router;

View file

@ -7,6 +7,7 @@ import communicationType from "./configuration/communicationType";
import executivePosition from "./configuration/executivePosition";
import membershipStatus from "./configuration/membershipStatus";
import qualification from "./configuration/qualification";
import education from "./configuration/education";
import salutation from "./configuration/salutation";
import calendarType from "./configuration/calendarType";
import queryStore from "./configuration/queryStore";
@ -70,6 +71,14 @@ router.use(
]),
qualification
);
router.use(
"/education",
PermissionHelper.passCheckSomeMiddleware([
{ requiredPermission: "read", section: "configuration", module: "education" },
{ requiredPermission: "read", section: "club", module: "member" },
]),
education
);
router.use(
"/salutation",
PermissionHelper.passCheckSomeMiddleware([

View file

@ -0,0 +1,49 @@
import { dataSource } from "../../../data-source";
import { memberEducations } from "../../../entity/club/member/memberEducations";
import DatabaseActionException from "../../../exceptions/databaseActionException";
import InternalException from "../../../exceptions/internalException";
export default abstract class MemberEducationService {
/**
* @description get all by member id
* @param {string} memberId
* @returns {Promise<Array<memberEducations>>}
*/
static async getAll(memberId: string): Promise<Array<memberEducations>> {
return await dataSource
.getRepository(memberEducations)
.createQueryBuilder("memberEducations")
.leftJoinAndSelect("memberEducations.education", "education")
.where("memberEducations.memberId = :memberId", { memberId: memberId })
.orderBy("education.education", "ASC")
.getMany()
.then((res) => {
return res;
})
.catch((err) => {
throw new DatabaseActionException("SELECT", "memberEducations", err);
});
}
/**
* @description get by memberId and recordId
* @param {string} memberId
* @param {number} recordId
* @returns {Promise<Array<member>>}
*/
static async getById(memberId: string, recordId: number): Promise<memberEducations> {
return await dataSource
.getRepository(memberEducations)
.createQueryBuilder("memberEducations")
.leftJoinAndSelect("memberEducations.education", "education")
.where("memberEducations.memberId = :memberId", { memberId: memberId })
.andWhere("memberEducations.id = :recordId", { recordId: recordId })
.getOneOrFail()
.then((res) => {
return res;
})
.catch((err) => {
throw new DatabaseActionException("SELECT", "memberEducations", err);
});
}
}

View file

@ -1,9 +1,8 @@
import { Brackets, Like, Not, SelectQueryBuilder } from "typeorm";
import { Brackets, ILike, Not, SelectQueryBuilder } from "typeorm";
import { dataSource } from "../../../data-source";
import { member } from "../../../entity/club/member/member";
import DatabaseActionException from "../../../exceptions/databaseActionException";
import { memberView } from "../../../views/memberView";
import { DB_TYPE } from "../../../env.defaults";
export default abstract class MemberService {
/**
@ -30,10 +29,12 @@ export default abstract class MemberService {
if (searchBits.length < 2) {
query = query.where(
`member.firstname LIKE :searchQuery OR member.lastname LIKE :searchQuery OR member.internalId LIKE :searchQuery`,
{
searchQuery: `%${searchBits[0]}%`,
}
new Brackets((qb) =>
qb
.where({ firstname: ILike(`%${searchBits[0]}%`) })
.orWhere({ lastname: ILike(`%${searchBits[0]}%`) })
.orWhere({ internalId: ILike(`%${searchBits[0]}%`) })
)
);
} else {
searchBits
@ -42,12 +43,12 @@ export default abstract class MemberService {
query = query
.orWhere(
new Brackets((qb) =>
qb.where({ firstname: Like(`%${term[0]}%`) }).andWhere({ lastname: Like(`%${term[1]}%`) })
qb.where({ firstname: ILike(`%${term[0]}%`) }).andWhere({ lastname: ILike(`%${term[1]}%`) })
)
)
.orWhere(
new Brackets((qb) =>
qb.where({ firstname: Like(`%${term[1]}%`) }).andWhere({ lastname: Like(`%${term[0]}%`) })
qb.where({ firstname: ILike(`%${term[1]}%`) }).andWhere({ lastname: ILike(`%${term[0]}%`) })
)
);
});
@ -192,17 +193,13 @@ export default abstract class MemberService {
"member.firstMembershipEntry",
"member.memberships",
"membership_first",
DB_TYPE == "postgres"
? 'membership_first.memberId = member.id AND membership_first.start = (SELECT MIN("m_first"."start") FROM "membership" "m_first" WHERE "m_first"."memberId" = "member"."id")'
: "membership_first.memberId = member.id AND membership_first.start = (SELECT MIN(m_first.start) FROM membership m_first WHERE m_first.memberId = member.id)"
'membership_first.memberId = member.id AND membership_first.start = (SELECT MIN("m_first"."start") FROM "membership" "m_first" WHERE "m_first"."memberId" = "member"."id")'
)
.leftJoinAndMapOne(
"member.lastMembershipEntry",
"member.memberships",
"membership_last",
DB_TYPE == "postgres"
? 'membership_last.memberId = member.id AND membership_last.start = (SELECT MAX("m_last"."start") FROM "membership" "m_last" WHERE "m_last"."memberId" = "member"."id")'
: "membership_last.memberId = member.id AND membership_last.start = (SELECT MAX(m_last.start) FROM membership m_last WHERE m_last.memberId = member.id)"
'membership_last.memberId = member.id AND membership_last.start = (SELECT MAX("m_last"."start") FROM "membership" "m_last" WHERE "m_last"."memberId" = "member"."id")'
)
.leftJoinAndSelect("membership_first.status", "status_first")
.leftJoinAndSelect("membership_last.status", "status_last")

View file

@ -2,7 +2,7 @@ import { dataSource } from "../../../data-source";
import { membership } from "../../../entity/club/member/membership";
import DatabaseActionException from "../../../exceptions/databaseActionException";
import InternalException from "../../../exceptions/internalException";
import { membershipView } from "../../../views/membershipsView";
import { membershipTotalView, membershipView } from "../../../views/membershipsView";
export default abstract class MembershipService {
/**
@ -66,4 +66,23 @@ export default abstract class MembershipService {
throw new DatabaseActionException("SELECT", "membershipView", err);
});
}
/**
* @description get membership total statistics by memberId
* @param {string} memberId
* @returns {Promise<Array<membershipTotalView>>}
*/
static async getTotalStatisticsById(memberId: string): Promise<membershipTotalView> {
return await dataSource
.getRepository(membershipTotalView)
.createQueryBuilder("membershipTotalView")
.where("membershipTotalView.memberId = :memberId", { memberId: memberId })
.getOneOrFail()
.then((res) => {
return res;
})
.catch((err) => {
throw new DatabaseActionException("SELECT", "membershipTotalView", err);
});
}
}

View file

@ -12,6 +12,7 @@ export default abstract class NewsletterService {
return await dataSource
.getRepository(newsletter)
.createQueryBuilder("newsletter")
.orderBy("createdAt", "DESC")
.offset(offset)
.limit(count)
.getManyAndCount()

View file

@ -0,0 +1,41 @@
import { dataSource } from "../../data-source";
import { education } from "../../entity/configuration/education";
import DatabaseActionException from "../../exceptions/databaseActionException";
export default abstract class EducationService {
/**
* @description get all educations
* @returns {Promise<Array<education>>}
*/
static async getAll(): Promise<Array<education>> {
return await dataSource
.getRepository(education)
.createQueryBuilder("education")
.orderBy("education", "ASC")
.getMany()
.then((res) => {
return res;
})
.catch((err) => {
throw new DatabaseActionException("SELECT", "education", err);
});
}
/**
* @description get education by id
* @returns {Promise<education>}
*/
static async getById(id: number): Promise<education> {
return await dataSource
.getRepository(education)
.createQueryBuilder("education")
.where("education.id = :id", { id: id })
.getOneOrFail()
.then((res) => {
return res;
})
.catch((err) => {
throw new DatabaseActionException("SELECT", "education", err);
});
}
}

View file

@ -58,7 +58,7 @@
<br />
{{/each}} {{/if}} {{#if qualifications.length}}
<br />
<h2>Qualifikationen</h2>
<h2>Qualifikationen / Funktionen</h2>
{{#each qualifications}}
<div>
<h3>{{this.qualification.qualification}}: {{date this.date}}</h3>
@ -69,6 +69,19 @@
{{/if}}
</div>
<br />
{{/each}} {{/if}} {{#if educations.length}}
<br />
<h2>Aus-/Fortbildungen</h2>
{{#each educations}}
<div>
<h3>{{this.education.education}}: {{date this.start}}{{#if this.end}} bis {{date this.end}}{{/if}}</h3>
{{#if this.place}}
<p>Ausbildungsort: {{this.place}}</p>
{{/if}}{{#if this.note}}
<p>Notiz: {{this.note}}</p>
{{/if}}
</div>
<br />
{{/each}} {{/if}}
</body>
<style>

View file

@ -13,6 +13,7 @@ export type PermissionModule =
| "communication_type"
| "membership_status"
| "salutation"
| "education"
| "calendar_type"
| "user"
| "role"
@ -70,6 +71,7 @@ export const permissionModules: Array<PermissionModule> = [
"communication_type",
"membership_status",
"salutation",
"education",
"calendar_type",
"user",
"role",
@ -91,6 +93,7 @@ export const sectionsAndModules: SectionsAndModulesObject = {
"communication_type",
"membership_status",
"salutation",
"education",
"calendar_type",
"query_store",
"template",

View file

@ -15,6 +15,7 @@ export interface MemberViewModel {
sendNewsletter?: CommunicationViewModel;
smsAlarming?: Array<CommunicationViewModel>;
preferredCommunication?: Array<CommunicationViewModel>;
note?: string;
}
export interface MemberStatisticsViewModel {

View file

@ -0,0 +1,26 @@
export interface MemberEducationViewModel {
id: number;
start: Date;
end?: Date;
place?: string;
note?: string;
education: string;
educationId: number;
}
export interface CreateMemberEducationViewModel {
start: Date;
end?: Date;
place?: string;
note?: string;
educationId: number;
}
export interface UpdateMemberEducationViewModel {
id: number;
start: Date;
end?: Date;
place?: string;
note?: string;
educationId: number;
}

View file

@ -20,3 +20,15 @@ export interface MembershipStatisticsViewModel {
memberNameaffix: string;
memberBirthdate: Date;
}
export interface MembershipTotalStatisticsViewModel {
durationInDays: number;
durationInYears: number;
exactDuration: string;
memberId: string;
memberSalutation: string;
memberFirstname: string;
memberLastname: string;
memberNameaffix: string;
memberBirthdate: Date;
}

View file

@ -7,4 +7,5 @@ export interface NewsletterViewModel {
newsletterSignatur: string;
isSent: boolean;
recipientsByQueryId?: string;
createdAt: Date;
}

View file

@ -0,0 +1,16 @@
export interface EducationViewModel {
id: number;
education: string;
description: string | null;
}
export interface CreateEducationViewModel {
education: string;
description: string | null;
}
export interface UpdateEducationViewModel {
id: number;
education: string;
description: string | null;
}

View file

@ -1,36 +1,5 @@
import { DataSource, ViewColumn, ViewEntity } from "typeorm";
import { memberExecutivePositions } from "../entity/club/member/memberExecutivePositions";
import { DB_TYPE } from "../env.defaults";
let durationInDays: string;
let durationInYears: string;
let exactDuration: string;
if (DB_TYPE == "postgres") {
durationInDays = `SUM(COALESCE("memberExecutivePositions"."end", CURRENT_DATE) - "memberExecutivePositions"."start")`;
durationInYears = `SUM(EXTRACT(YEAR FROM AGE(COALESCE("memberExecutivePositions"."end", CURRENT_DATE), "memberExecutivePositions"."start")))`;
exactDuration = `SUM(AGE(COALESCE("memberExecutivePositions"."end", CURRENT_DATE), "memberExecutivePositions"."start"))`;
} else if (DB_TYPE == "mysql") {
durationInDays = `SUM(DATEDIFF(COALESCE(memberExecutivePositions.end, CURDATE()), memberExecutivePositions.start))`;
durationInYears = `SUM(TIMESTAMPDIFF(YEAR, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURDATE())))`;
exactDuration = `
CONCAT(
SUM(FLOOR(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURDATE())) / 365.25)),
' years ',
SUM(FLOOR(MOD(TIMESTAMPDIFF(MONTH, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURDATE())), 12))),
' months ',
SUM(FLOOR(MOD(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURDATE())), 30))),
' days'
)
`;
} else if (DB_TYPE == "sqlite") {
durationInDays = `SUM(JULIANDAY(COALESCE(memberExecutivePositions.end, DATE('now'))) - JULIANDAY(memberExecutivePositions.start))`;
durationInYears = `SUM(FLOOR((JULIANDAY(COALESCE(memberExecutivePositions.end, DATE('now'))) - JULIANDAY(memberExecutivePositions.start)) / 365.25))`;
exactDuration = `
SUM((strftime('%Y', COALESCE(memberExecutivePositions.end, DATE('now'))) - strftime('%Y', memberExecutivePositions.start))) || ' years ' ||
SUM((strftime('%m', COALESCE(memberExecutivePositions.end, DATE('now'))) - strftime('%m', memberExecutivePositions.start))) || ' months ' ||
SUM((strftime('%d', COALESCE(memberExecutivePositions.end, DATE('now'))) - strftime('%d', memberExecutivePositions.start))) || ' days'
`;
}
@ViewEntity({
expression: (datasource: DataSource) =>
@ -45,9 +14,18 @@ if (DB_TYPE == "postgres") {
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect(durationInDays, "durationInDays")
.addSelect(durationInYears, "durationInYears")
.addSelect(exactDuration, "exactDuration")
.addSelect(
`SUM(COALESCE("memberExecutivePositions"."end", CURRENT_DATE) - "memberExecutivePositions"."start")`,
"durationInDays"
)
.addSelect(
`SUM(EXTRACT(YEAR FROM AGE(COALESCE("memberExecutivePositions"."end", CURRENT_DATE), "memberExecutivePositions"."start")))`,
"durationInYears"
)
.addSelect(
`SUM(AGE(COALESCE("memberExecutivePositions"."end", CURRENT_DATE), "memberExecutivePositions"."start"))`,
"exactDuration"
)
.leftJoin("memberExecutivePositions.executivePosition", "executivePosition")
.leftJoin("memberExecutivePositions.member", "member")
.leftJoin("member.salutation", "salutation")

View file

@ -1,36 +1,5 @@
import { DataSource, ViewColumn, ViewEntity } from "typeorm";
import { memberQualifications } from "../entity/club/member/memberQualifications";
import { DB_TYPE } from "../env.defaults";
let durationInDays: string;
let durationInYears: string;
let exactDuration: string;
if (DB_TYPE == "postgres") {
durationInDays = `SUM(COALESCE("memberQualifications"."end", CURRENT_DATE) - "memberQualifications"."start") `;
durationInYears = `SUM(EXTRACT(YEAR FROM AGE(COALESCE("memberQualifications"."end", CURRENT_DATE), "memberQualifications"."start")))`;
exactDuration = `SUM(AGE(COALESCE("memberQualifications"."end", CURRENT_DATE), "memberQualifications"."start"))`;
} else if (DB_TYPE == "mysql") {
durationInDays = `SUM(DATEDIFF(COALESCE(memberQualifications.end, CURDATE()), memberQualifications.start))`;
durationInYears = `SUM(TIMESTAMPDIFF(YEAR, memberQualifications.start, COALESCE(memberQualifications.end, CURDATE())))`;
exactDuration = `
CONCAT(
SUM(FLOOR(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURDATE())) / 365.25)),
' years ',
SUM(FLOOR(MOD(TIMESTAMPDIFF(MONTH, memberQualifications.start, COALESCE(memberQualifications.end, CURDATE())), 12))),
' months ',
SUM(FLOOR(MOD(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURDATE())), 30))),
' days'
)
`;
} else if (DB_TYPE == "sqlite") {
durationInDays = `SUM(JULIANDAY(COALESCE(memberQualifications.end, DATE('now'))) - JULIANDAY(memberQualifications.start))`;
durationInYears = `SUM(FLOOR((JULIANDAY(COALESCE(memberQualifications.end, DATE('now'))) - JULIANDAY(memberQualifications.start)) / 365.25))`;
exactDuration = `
SUM((strftime('%Y', COALESCE(memberQualifications.end, DATE('now'))) - strftime('%Y', memberQualifications.start))) || ' years ' ||
SUM((strftime('%m', COALESCE(memberQualifications.end, DATE('now'))) - strftime('%m', memberQualifications.start))) || ' months ' ||
SUM((strftime('%d', COALESCE(memberQualifications.end, DATE('now'))) - strftime('%d', memberQualifications.start))) || ' days'
`;
}
@ViewEntity({
expression: (datasource: DataSource) =>
@ -45,9 +14,18 @@ if (DB_TYPE == "postgres") {
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect(durationInDays, "durationInDays")
.addSelect(durationInYears, "durationInYears")
.addSelect(exactDuration, "exactDuration")
.addSelect(
`SUM(COALESCE("memberQualifications"."end", CURRENT_DATE) - "memberQualifications"."start")`,
"durationInDays"
)
.addSelect(
`SUM(EXTRACT(YEAR FROM AGE(COALESCE("memberQualifications"."end", CURRENT_DATE), "memberQualifications"."start")))`,
"durationInYears"
)
.addSelect(
`SUM(AGE(COALESCE("memberQualifications"."end", CURRENT_DATE), "memberQualifications"."start"))`,
"exactDuration"
)
.leftJoin("memberQualifications.qualification", "qualification")
.leftJoin("memberQualifications.member", "member")
.leftJoin("member.salutation", "salutation")

View file

@ -1,39 +1,5 @@
import { DataSource, ViewColumn, ViewEntity } from "typeorm";
import { member } from "../entity/club/member/member";
import { DB_TYPE } from "../env.defaults";
let todayAge: string;
let ageThisYear: string;
let exactAge: string;
if (DB_TYPE == "postgres") {
todayAge = `DATE_PART('year', AGE(CURRENT_DATE, member.birthdate))`;
ageThisYear = `EXTRACT(YEAR FROM CURRENT_DATE) - EXTRACT(YEAR FROM member.birthdate)`;
exactAge = `AGE(CURRENT_DATE, member.birthdate)`;
} else if (DB_TYPE == "mysql") {
todayAge = `TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE()) AS todayAge`;
ageThisYear = `YEAR(CURDATE()) - YEAR(member.birthdate) AS ageThisYear`;
exactAge = `
CONCAT(
TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE()), ' years ',
TIMESTAMPDIFF(MONTH, member.birthdate, CURDATE()) % 12, ' months ',
TIMESTAMPDIFF(DAY,
DATE_ADD(
member.birthdate,
INTERVAL TIMESTAMPDIFF(MONTH, member.birthdate, CURDATE()) MONTH
),
CURDATE()
), ' days'
)
`;
} else if (DB_TYPE == "sqlite") {
todayAge = `(strftime('%Y', 'now') - strftime('%Y', member.birthdate) - (strftime('%m-%d', 'now') < strftime('%m-%d', member.birthdate)))`;
ageThisYear = `strftime('%Y', 'now') - strftime('%Y', member.birthdate)`;
exactAge = `
(strftime('%Y', 'now') - strftime('%Y', member.birthdate)) || ' years ' ||
(strftime('%m', 'now') - strftime('%m', member.birthdate)) || ' months ' ||
(strftime('%d', 'now') - strftime('%d', member.birthdate)) || ' days'
`;
}
@ViewEntity({
expression: (datasource: DataSource) =>
@ -41,15 +7,16 @@ if (DB_TYPE == "postgres") {
.getRepository(member)
.createQueryBuilder("member")
.select("member.id", "id")
.select("member.internalId", "internalId")
.addSelect("member.internalId", "internalId")
.addSelect("member.note", "note")
.addSelect("member.firstname", "firstname")
.addSelect("member.lastname", "lastname")
.addSelect("member.nameaffix", "nameaffix")
.addSelect("member.birthdate", "birthdate")
.addSelect("salutation.salutation", "salutation")
.addSelect(todayAge, "todayAge")
.addSelect(ageThisYear, "ageThisYear")
.addSelect(exactAge, "exactAge")
.addSelect(`DATE_PART('year', AGE(CURRENT_DATE, member.birthdate))`, "todayAge")
.addSelect(`EXTRACT(YEAR FROM CURRENT_DATE) - EXTRACT(YEAR FROM member.birthdate)`, "ageThisYear")
.addSelect(`AGE(CURRENT_DATE, member.birthdate)`, "exactAge")
.leftJoin("member.salutation", "salutation"),
})
export class memberView {
@ -62,6 +29,9 @@ export class memberView {
@ViewColumn()
internalId: string;
@ViewColumn()
note: string;
@ViewColumn()
firstname: string;

View file

@ -1,36 +1,5 @@
import { DataSource, ViewColumn, ViewEntity } from "typeorm";
import { membership } from "../entity/club/member/membership";
import { DB_TYPE } from "../env.defaults";
let durationInDays: string;
let durationInYears: string;
let exactDuration: string;
if (DB_TYPE == "postgres") {
durationInDays = `SUM(COALESCE("membership"."end", CURRENT_DATE) - "membership"."start") `;
durationInYears = `SUM(EXTRACT(YEAR FROM AGE(COALESCE("membership"."end", CURRENT_DATE), "membership"."start")))`;
exactDuration = `SUM(AGE(COALESCE("membership"."end", CURRENT_DATE), "membership"."start"))`;
} else if (DB_TYPE == "mysql") {
durationInDays = `SUM(DATEDIFF(COALESCE(membership.end, CURDATE()), membership.start))`;
durationInYears = `SUM(TIMESTAMPDIFF(YEAR, membership.start, COALESCE(membership.end, CURDATE())))`;
exactDuration = `
CONCAT(
SUM(FLOOR(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURDATE())) / 365.25)),
' years ',
SUM(FLOOR(MOD(TIMESTAMPDIFF(MONTH, membership.start, COALESCE(membership.end, CURDATE())), 12))),
' months ',
SUM(FLOOR(MOD(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURDATE())), 30))),
' days'
)
`;
} else if (DB_TYPE == "sqlite") {
durationInDays = `SUM(JULIANDAY(COALESCE(membership.end, DATE('now'))) - JULIANDAY(membership.start))`;
durationInYears = `SUM(FLOOR((JULIANDAY(COALESCE(membership.end, DATE('now'))) - JULIANDAY(membership.start)) / 365.25))`;
exactDuration = `
SUM((strftime('%Y', COALESCE(membership.end, DATE('now'))) - strftime('%Y', membership.start))) || ' years ' ||
SUM((strftime('%m', COALESCE(membership.end, DATE('now'))) - strftime('%m', membership.start))) || ' months ' ||
SUM((strftime('%d', COALESCE(membership.end, DATE('now'))) - strftime('%d', membership.start))) || ' days'
`;
}
@ViewEntity({
expression: (datasource: DataSource) =>
@ -45,9 +14,12 @@ if (DB_TYPE == "postgres") {
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect(durationInDays, "durationInDays")
.addSelect(durationInYears, "durationInYears")
.addSelect(exactDuration, "exactDuration")
.addSelect(`SUM(COALESCE("membership"."end", CURRENT_DATE) - "membership"."start") `, "durationInDays")
.addSelect(
`SUM(EXTRACT(YEAR FROM AGE(COALESCE("membership"."end", CURRENT_DATE), "membership"."start")))`,
"durationInYears"
)
.addSelect(`SUM(AGE(COALESCE("membership"."end", CURRENT_DATE), "membership"."start"))`, "exactDuration")
.leftJoin("membership.status", "status")
.leftJoin("membership.member", "member")
.leftJoin("member.salutation", "salutation")
@ -89,3 +61,55 @@ export class membershipView {
@ViewColumn()
memberBirthdate: Date;
}
@ViewEntity({
expression: (datasource: DataSource) =>
datasource
.getRepository(membership)
.createQueryBuilder("membership")
.select("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(COALESCE("membership"."end", CURRENT_DATE) - "membership"."start") `, "durationInDays")
.addSelect(
`SUM(EXTRACT(YEAR FROM AGE(COALESCE("membership"."end", CURRENT_DATE), "membership"."start")))`,
"durationInYears"
)
.addSelect(`SUM(AGE(COALESCE("membership"."end", CURRENT_DATE), "membership"."start"))`, "exactDuration")
.leftJoin("membership.status", "status")
.leftJoin("membership.member", "member")
.leftJoin("member.salutation", "salutation")
.groupBy("member.id")
.addGroupBy("salutation.id"),
})
export class membershipTotalView {
@ViewColumn()
durationInDays: number;
@ViewColumn()
durationInYears: number;
@ViewColumn()
exactDuration: string;
@ViewColumn()
memberId: string;
@ViewColumn()
memberSalutation: string;
@ViewColumn()
memberFirstname: string;
@ViewColumn()
memberLastname: string;
@ViewColumn()
memberNameaffix: string;
@ViewColumn()
memberBirthdate: Date;
}